use of qupath.lib.objects.hierarchy.TMAGrid in project qupath by qupath.
the class QuPathViewer method getImageLocationString.
/**
* Get a string representing the image coordinates for a particular x & y location.
* @param xx x-coordinate in the image space (not the component/viewer space)
* @param yy y-coordinate in the image space (not the component/viewer space)
* @param useCalibratedUnits
* @return
*/
private String getImageLocationString(double xx, double yy, boolean useCalibratedUnits) {
ImageServer<BufferedImage> server = getServer();
if (server == null)
return "";
String units;
if (xx < 0 || yy < 0 || xx > server.getWidth() - 1 || yy > server.getHeight() - 1)
return "";
double xDisplay = xx;
double yDisplay = yy;
PixelCalibration cal = server.getPixelCalibration();
if (useCalibratedUnits && cal.hasPixelSizeMicrons()) {
units = GeneralTools.micrometerSymbol();
xDisplay *= cal.getPixelWidthMicrons();
yDisplay *= cal.getPixelHeightMicrons();
} else {
units = "px";
}
// See if we're on top of a TMA core
String prefix = "";
TMAGrid tmaGrid = getHierarchy().getTMAGrid();
if (tmaGrid != null) {
TMACoreObject core = PathObjectTools.getTMACoreForPixel(tmaGrid, xx, yy);
if (core != null && core.getName() != null)
prefix = "Core: " + core.getName() + "\n";
}
String s = null;
RegionRequest request = ImageRegionStoreHelpers.getTileRequest(server, xx, yy, downsampleFactor.get(), getZPosition(), getTPosition());
if (request != null) {
BufferedImage img = regionStore.getCachedTile(server, request);
int xi = 0, yi = 0;
if (img == null) {
// Try getting a value from the thumbnail for the whole image
BufferedImage imgThumbnail = regionStore.getCachedThumbnail(server, getZPosition(), getTPosition());
if (imgThumbnail != null) {
img = imgThumbnail;
double downsample = (double) server.getWidth() / imgThumbnail.getWidth();
xi = (int) (xx / downsample + .5);
yi = (int) (yy / downsample + .5);
}
} else {
xi = (int) ((xx - request.getX()) / request.getDownsample());
yi = (int) ((yy - request.getY()) / request.getDownsample());
}
if (img != null) {
// Make sure we are within range
xi = Math.min(xi, img.getWidth() - 1);
yi = Math.min(yi, img.getHeight() - 1);
// Get the value, having applied any required color transforms
if (imageDisplay != null)
s = imageDisplay.getTransformedValueAsString(img, xi, yi);
}
}
// Append z, t position if required
String zString = null;
if (server.nZSlices() > 1) {
double zSpacing = server.getPixelCalibration().getZSpacingMicrons();
if (!useCalibratedUnits || Double.isNaN(zSpacing))
zString = "z = " + getZPosition();
else
zString = String.format("z = %.2f %s", getZPosition() * zSpacing, GeneralTools.micrometerSymbol());
}
String tString = null;
if (server.nTimepoints() > 1) {
// TODO: Consider use of TimeUnit
// TimeUnit timeUnit = server.getTimeUnit();
// if (!useMicrons || timeUnit == null)
tString = "t = " + getTPosition();
// else
// tString = String.format("z = %.2f %s", getTPosition(), timeUnit.toString());
}
String dimensionString;
if (tString == null && zString == null)
dimensionString = "";
else {
dimensionString = "\n";
if (zString != null) {
dimensionString += zString;
if (tString != null)
dimensionString += ", " + tString;
} else
dimensionString += tString;
}
if (s != null)
return String.format("%s%.2f, %.2f %s\n%s%s", prefix, xDisplay, yDisplay, units, s, dimensionString);
else
return String.format("%s%.2f, %.2f %s%s", prefix, xDisplay, yDisplay, units, dimensionString);
// if (s != null)
// return String.format("<html><center>%.2f, %.2f %s<br>%s", xDisplay, yDisplay, units, s);
// else
// return String.format("<html><center>%.2f, %.2f %s", xDisplay, yDisplay, units);
}
use of qupath.lib.objects.hierarchy.TMAGrid in project qupath by qupath.
the class QuPathViewer method getTooltipText.
private String getTooltipText(final double x, final double y) {
// Try to show which TMA core is selected - if we have a TMA image
PathObjectHierarchy hierarchy = getHierarchy();
TMAGrid tmaGrid = hierarchy == null ? null : hierarchy.getTMAGrid();
if (tmaGrid != null) {
Point2D p = componentPointToImagePoint(x, y, null, false);
TMACoreObject core = PathObjectTools.getTMACoreForPixel(tmaGrid, p.getX(), p.getY());
if (core != null) {
if (core.isMissing())
return String.format("TMA Core %s\n(missing)", core.getName());
else
return String.format("TMA Core %s", core.getName());
}
}
return null;
}
use of qupath.lib.objects.hierarchy.TMAGrid in project qupath by qupath.
the class PathClassifierTools method runClassifier.
/**
* Apply a classifier to the detection objects in a hierarchy.
* @param hierarchy
* @param classifier
*/
public static void runClassifier(final PathObjectHierarchy hierarchy, final PathObjectClassifier classifier) {
// Apply classifier to everything
// If we have a TMA grid, do one core at a time
long startTime = System.currentTimeMillis();
TMAGrid tmaGrid = hierarchy.getTMAGrid();
Collection<PathObject> pathObjects = new ArrayList<>();
int nClassified = 0;
// tmaGrid = null;
if (tmaGrid != null) {
for (TMACoreObject core : tmaGrid.getTMACoreList()) {
pathObjects = PathObjectTools.getDescendantObjects(core, pathObjects, PathDetectionObject.class);
nClassified += classifier.classifyPathObjects(pathObjects);
pathObjects.clear();
}
} else {
hierarchy.getObjects(pathObjects, PathDetectionObject.class);
nClassified = classifier.classifyPathObjects(pathObjects);
}
long endTime = System.currentTimeMillis();
logger.info(String.format("Classification time: %.2f seconds", (endTime - startTime) / 1000.));
// Fire a change event for all detection objects
if (nClassified > 0)
hierarchy.fireObjectClassificationsChangedEvent(classifier, hierarchy.getObjects(null, PathDetectionObject.class));
else
logger.warn("No objects classified!");
}
use of qupath.lib.objects.hierarchy.TMAGrid in project qupath by qupath.
the class QP method relabelTMAGrid.
/**
* Relabel a TMA grid. This will only be effective if enough labels are supplied for the full grid - otherwise no changes will be made.
* <p>
* For a TMA core at column c and row r, the label format will be 'Hc-Vr' or 'Hc-Vr', where H is the horizontal label and V the vertical label,
* depending upon the status of the 'rowFirst' flag.
* <p>
* An examples of label would be 'A-1', 'A-2', 'B-1', 'B-2' etc.
*
* @param hierarchy The hierarchy containing the TMA grid to be relabelled.
* @param labelsHorizontal A String containing labels for each TMA column, separated by spaces, or a numeric or alphabetic range (e.g. 1-10, or A-G)
* @param labelsVertical A String containing labels for each TMA row, separated by spaces, or a numeric or alphabetic range (e.g. 1-10, or A-G)
* @param rowFirst TRUE if the horizontal label should be added before the vertical label, FALSE otherwise
* @return TRUE if there were sufficient horizontal and vertical labels to label the entire grid, FALSE otherwise.
*/
public static boolean relabelTMAGrid(final PathObjectHierarchy hierarchy, final String labelsHorizontal, final String labelsVertical, final boolean rowFirst) {
if (hierarchy == null || hierarchy.getTMAGrid() == null) {
logger.error("Cannot relabel TMA grid - no grid found!");
return false;
}
TMAGrid grid = hierarchy.getTMAGrid();
String[] columnLabels = PathObjectTools.parseTMALabelString(labelsHorizontal);
String[] rowLabels = PathObjectTools.parseTMALabelString(labelsVertical);
if (columnLabels.length < grid.getGridWidth()) {
logger.error("Cannot relabel full TMA grid - not enough column labels specified!");
return false;
}
if (rowLabels.length < grid.getGridHeight()) {
logger.error("Cannot relabel full TMA grid - not enough row labels specified!");
return false;
}
for (int r = 0; r < grid.getGridHeight(); r++) {
for (int c = 0; c < grid.getGridWidth(); c++) {
String name;
if (rowFirst)
name = rowLabels[r] + "-" + columnLabels[c];
else
name = columnLabels[c] + "-" + rowLabels[r];
grid.getTMACore(r, c).setName(name);
}
}
hierarchy.fireObjectsChangedEvent(null, new ArrayList<>(grid.getTMACoreList()));
return true;
}
use of qupath.lib.objects.hierarchy.TMAGrid in project qupath by qupath.
the class DragDropImportListener method handleFileDropImpl.
private void handleFileDropImpl(final QuPathViewer viewer, final List<File> list) throws IOException {
// Shouldn't occur... but keeps FindBugs happy to check
if (list == null) {
logger.warn("No files given!");
return;
}
// Check if we have only jar files
int nJars = 0;
for (File file : list) {
if (file.getName().toLowerCase().endsWith(".jar"))
nJars++;
}
if (nJars == list.size()) {
if (qupath.canInstallExtensions())
qupath.installExtensions(list);
else
Dialogs.showErrorMessage("Install extensions", "Sorry, extensions can only be installed when QuPath is run as a standalone application.");
return;
}
// Try to get a hierarchy for importing ROIs
ImageData<BufferedImage> imageData = viewer == null ? null : viewer.getImageData();
PathObjectHierarchy hierarchy = imageData == null ? null : imageData.getHierarchy();
// Some consumers can only handle one file
boolean singleFile = list.size() == 1;
// Gather together the extensions - if this has length one, we know all the files have the same extension
Set<String> allExtensions = list.stream().map(f -> GeneralTools.getExtension(f).orElse("")).collect(Collectors.toSet());
// If we have a zipped file, create a set that includes the files within the zip image
// This helps us determine whether or not a zip file contains an image or objects, for example
Set<String> allUnzippedExtensions = allExtensions;
if (allExtensions.contains(".zip")) {
allUnzippedExtensions = list.stream().flatMap(f -> {
try {
return PathIO.unzippedExtensions(f.toPath()).stream();
} catch (IOException e) {
logger.debug(e.getLocalizedMessage(), e);
return Arrays.stream(new String[0]);
}
}).collect(Collectors.toSet());
}
// Extract the first (and possibly only) file
File file = list.get(0);
String fileName = file.getName().toLowerCase();
// Check if this is a hierarchy file
if (singleFile && (fileName.endsWith(PathPrefs.getSerializationExtension()))) {
// If we have a different path, open as a new image
if (viewer == null) {
Dialogs.showErrorMessage("Load data", "Please drag the file onto a specific viewer to open!");
return;
}
try {
// Check if we should be importing objects or opening the file
if (imageData != null) {
var dialog = new Dialog<ButtonType>();
var btOpen = new ButtonType("Open image");
var btImport = new ButtonType("Import objects");
dialog.getDialogPane().getButtonTypes().setAll(btOpen, btImport, ButtonType.CANCEL);
dialog.setTitle("Open data");
dialog.setHeaderText("What do you want to do with the data file?");
dialog.setContentText("You can\n" + " 1. Open the image in the current viewer\n" + " 2. Import objects and add them to the current image");
// dialog.setHeaderText("What do you want to do?");
var choice = dialog.showAndWait().orElse(ButtonType.CANCEL);
if (choice == ButtonType.CANCEL)
return;
if (choice == btImport) {
var pathObjects = PathIO.readObjects(file);
hierarchy.addPathObjects(pathObjects);
return;
}
}
qupath.openSavedData(viewer, file, false, true);
} catch (Exception e) {
Dialogs.showErrorMessage("Load data", e);
}
return;
}
// Check if this is a directory - if so, look for a single project file
if (singleFile && file.isDirectory()) {
// Identify all files in the directory, and also all potential project files
File[] filesInDirectory = file.listFiles(f -> !f.isHidden());
List<File> projectFiles = Arrays.stream(filesInDirectory).filter(f -> f.isFile() && f.getAbsolutePath().toLowerCase().endsWith(ProjectIO.getProjectExtension())).collect(Collectors.toList());
if (projectFiles.size() == 1) {
file = projectFiles.get(0);
logger.warn("Selecting project file {}", file);
} else if (projectFiles.size() > 1) {
// Prompt to select which project file to open
logger.debug("Multiple project files found in directory {}", file);
String[] fileNames = projectFiles.stream().map(f -> f.getName()).toArray(n -> new String[n]);
String selectedName = Dialogs.showChoiceDialog("Select project", "Select project to open", fileNames, fileNames[0]);
if (selectedName == null)
return;
file = new File(file, selectedName);
} else if (filesInDirectory.length == 0) {
// If we have an empty directory, offer to set it as a project
if (Dialogs.showYesNoDialog("Create project", "Create project for empty directory?")) {
Project<BufferedImage> project = Projects.createProject(file, BufferedImage.class);
qupath.setProject(project);
if (!project.isEmpty())
project.syncChanges();
return;
} else
// Can't do anything else with an empty folder
return;
}
}
// Check if this is a project
if (singleFile && (fileName.endsWith(ProjectIO.getProjectExtension()))) {
try {
Project<BufferedImage> project = ProjectIO.loadProject(file, BufferedImage.class);
qupath.setProject(project);
} catch (Exception e) {
// Dialogs.showErrorMessage("Project error", e);
logger.error("Could not open as project file: {}, opening in the Script Editor instead", e);
qupath.getScriptEditor().showScript(file);
}
return;
}
// Check if it is an object file in GeoJSON format (.geojson)
if (PathIO.getObjectFileExtensions(false).containsAll(allUnzippedExtensions)) {
if (imageData == null || hierarchy == null) {
qupath.getScriptEditor().showScript(file);
logger.info("Opening the dragged file in the Script Editor as there is no currently opened image in the viewer");
// Dialogs.showErrorMessage("Open object file", "Please open an image first to import objects!");
return;
}
List<PathObject> pathObjects = new ArrayList<>();
List<WorkflowStep> steps = new ArrayList<>();
for (var tempFile : list) {
try {
var tempObjects = PathIO.readObjects(tempFile);
if (tempObjects.isEmpty()) {
logger.warn("No objects found in {}, opening the dragged file in the Script Editor instead", tempFile.getAbsolutePath());
qupath.getScriptEditor().showScript(file);
return;
}
pathObjects.addAll(tempObjects);
// Add step to workflow
Map<String, String> map = new HashMap<>();
map.put("path", file.getPath());
String method = "Import objects";
String methodString = String.format("%s(%s%s%s)", "importObjectsFromFile", "\"", GeneralTools.escapeFilePath(tempFile.getPath()), "\"");
steps.add(new DefaultScriptableWorkflowStep(method, map, methodString));
} catch (IOException | IllegalArgumentException e) {
Dialogs.showErrorNotification("Object import", e.getLocalizedMessage());
return;
}
}
// Ask confirmation to user
int nObjects = pathObjects.size();
String message = nObjects == 1 ? "Add object to the hierarchy?" : String.format("Add %d objects to the hierarchy?", nObjects);
var confirm = Dialogs.showConfirmDialog("Add to hierarchy", message);
if (!confirm)
return;
// Add objects to hierarchy
hierarchy.addPathObjects(pathObjects);
imageData.getHistoryWorkflow().addSteps(steps);
return;
}
// Check if this is TMA dearraying data file
if (singleFile && (fileName.endsWith(TMADataIO.TMA_DEARRAYING_DATA_EXTENSION))) {
if (hierarchy == null)
Dialogs.showErrorMessage("TMA grid import", "Please open an image first before importing a dearrayed TMA grid!");
else {
TMAGrid tmaGrid = TMADataIO.importDearrayedTMAData(file);
if (tmaGrid != null) {
if (hierarchy.isEmpty() || Dialogs.showYesNoDialog("TMA grid import", "Set TMA grid for existing hierarchy?"))
hierarchy.setTMAGrid(tmaGrid);
} else
Dialogs.showErrorMessage("TMA grid import", "Could not parse TMA grid from " + file.getName());
}
return;
}
// Open file with an extension supported by the Script Editor
ScriptEditor scriptEditor = qupath.getScriptEditor();
if (scriptEditor instanceof DefaultScriptEditor && ((DefaultScriptEditor) scriptEditor).supportsFile(file)) {
scriptEditor.showScript(file);
return;
}
// Check handlers
for (DropHandler<File> handler : dropHandlers) {
if (handler.handleDrop(viewer, list))
return;
}
// Assume we have images
if (singleFile && file.isFile()) {
// Try to open as an image, if the extension is known
if (viewer == null) {
Dialogs.showErrorMessage("Open image", "Please drag the file only a specific viewer to open!");
return;
}
qupath.openImage(viewer, file.getAbsolutePath(), true, true);
return;
} else if (qupath.getProject() != null) {
// Try importing multiple images to a project
String[] potentialFiles = list.stream().filter(f -> f.isFile()).map(f -> f.getAbsolutePath()).toArray(String[]::new);
if (potentialFiles.length > 0) {
ProjectCommands.promptToImportImages(qupath, potentialFiles);
return;
}
}
if (qupath.getProject() == null) {
if (list.size() > 1) {
Dialogs.showErrorMessage("Drag & drop", "Could not handle multiple file drop - if you want to handle multiple images, you need to create a project first");
return;
}
}
if (list.size() > 1)
Dialogs.showErrorMessage("Drag & drop", "Sorry, I couldn't figure out what to do with these files - try opening one at a time");
else
Dialogs.showErrorMessage("Drag & drop", "Sorry, I couldn't figure out what to do with " + list.get(0).getName());
}
Aggregations