use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class SplitAnnotationsPlugin method getTasks.
@Override
protected Collection<Runnable> getTasks(final PluginRunner<T> runner) {
Collection<? extends PathObject> parentObjects = getParentObjects(runner);
if (parentObjects == null || parentObjects.isEmpty())
return Collections.emptyList();
// Add a single task, to avoid multithreading - which may complicate setting parents
List<Runnable> tasks = new ArrayList<>(1);
PathObjectHierarchy hierarchy = getHierarchy(runner);
// Want to reset selection
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
tasks.add(() -> {
/*
* Resolving the hierarchy with many objects can be very slow.
* Here, we take an object, split it and then add it below the original object in the hierarchy.
* We then just need to remove the originals, allowing the newly-added objects to have their
* parents reassigned.
*/
List<PathObject> toAdd = new ArrayList<>();
List<PathObject> toRemove = new ArrayList<>();
List<PathObject> localSplit = new ArrayList<>();
Set<PathObject> toSelect = new HashSet<>();
for (PathObject pathObject : parentObjects) {
localSplit.clear();
ROI roiOrig = pathObject.getROI();
if (roiOrig == null) {
toSelect.add(pathObject);
continue;
}
var splitROIs = RoiTools.splitROI(roiOrig);
if (splitROIs.size() == 1)
continue;
toRemove.add(pathObject);
for (var r : splitROIs) {
var annotation = PathObjects.createAnnotationObject(r, pathObject.getPathClass());
annotation.setLocked(pathObject.isLocked());
localSplit.add(annotation);
}
if (pathObject.hasChildren()) {
for (var temp : localSplit) hierarchy.addPathObjectBelowParent(pathObject, temp, false);
} else
pathObject.addPathObjects(localSplit);
toAdd.addAll(localSplit);
}
if (toAdd.isEmpty() && toRemove.isEmpty())
return;
hierarchy.getSelectionModel().clearSelection();
toSelect.addAll(toAdd);
hierarchy.removeObjects(toRemove, true);
// hierarchy.addPathObjects(toAdd, false);
hierarchy.getSelectionModel().selectObjects(toSelect);
if (toSelect.contains(selected))
hierarchy.getSelectionModel().setSelectedObject(selected, true);
});
return tasks;
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class QP method clearTMAGrid.
/**
* Remove the TMA grid from the current {@code PathObjectHierarchy}.
*
* @see #getCurrentHierarchy
*/
public static void clearTMAGrid() {
PathObjectHierarchy hierarchy = getCurrentHierarchy();
if (hierarchy == null)
return;
hierarchy.setTMAGrid(null);
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
if (selected instanceof TMACoreObject)
hierarchy.getSelectionModel().setSelectedObject(null);
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class Commands method promptToDeleteObjects.
/**
* Prompt to delete objects of a specified type, or all objects.
* @param imageData
* @param cls
*/
public static void promptToDeleteObjects(ImageData<?> imageData, Class<? extends PathObject> cls) {
if (imageData == null)
return;
PathObjectHierarchy hierarchy = imageData.getHierarchy();
// Handle no specified class - indicates all objects of all types should be cleared
if (cls == null) {
int n = hierarchy.nObjects();
if (n == 0)
return;
String message;
if (n == 1)
message = "Delete object?";
else
message = "Delete all " + n + " objects?";
if (Dialogs.showYesNoDialog("Delete objects", message)) {
hierarchy.clearAll();
hierarchy.getSelectionModel().setSelectedObject(null);
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Clear all objects", "clearAllObjects();"));
}
return;
}
// Handle clearing TMA grid
if (TMACoreObject.class.equals(cls)) {
if (hierarchy.getTMAGrid() != null) {
if (Dialogs.showYesNoDialog("Delete objects", "Clear TMA grid?")) {
hierarchy.setTMAGrid(null);
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
if (selected instanceof TMACoreObject)
hierarchy.getSelectionModel().setSelectedObject(null);
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Clear TMA Grid", "clearTMAGrid();"));
}
return;
}
}
// Handle clearing objects of another specified type
Collection<PathObject> pathObjects = hierarchy.getObjects(null, cls);
if (pathObjects.isEmpty())
return;
int n = pathObjects.size();
String message = n == 1 ? "Delete 1 object?" : "Delete " + n + " objects?";
if (Dialogs.showYesNoDialog("Delete objects", message)) {
hierarchy.removeObjects(pathObjects, true);
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
if (selected != null && selected.getClass().isAssignableFrom(cls))
hierarchy.getSelectionModel().setSelectedObject(null);
if (selected != null && selected.getClass().isAssignableFrom(cls))
hierarchy.getSelectionModel().setSelectedObject(null);
if (cls == PathDetectionObject.class)
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Clear detections", "clearDetections();"));
else if (cls == PathAnnotationObject.class)
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Clear annotations", "clearAnnotations();"));
else if (cls == TMACoreObject.class)
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Clear TMA grid", "clearTMAGrid();"));
else
logger.warn("Cannot clear all objects for class {}", cls);
}
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class Commands method mergeSelectedAnnotations.
// /**
// * Merge the points ROIs of different objects to create a single object containing all points with a specific {@link PathClass}.
// * @param imageData the image data containing points to merge
// * @param selectedOnly if true, use only classes found within the currently selected objects
// */
// public static void mergePointsForClasses(ImageData<?> imageData, boolean selectedOnly) {
// var hierarchy = imageData == null ? null : imageData.getHierarchy();
// if (hierarchy == null) {
// Dialogs.showNoImageError("Merge points");
// return;
// }
// if (selectedOnly) {
// PathObjectTools.mergePointsForSelectedObjectClasses(hierarchy);
// imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep(
// "Merge points for selected classifications",
// "mergePointsForSelectedObjectClasses();"
// ));
// } else {
// PathObjectTools.mergePointsForAllClasses(hierarchy);
// imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep(
// "Merge points for all classifications",
// "mergePointsForAllClasses();"
// ));
// }
// }
/**
* Merge the currently-selected annotations for an image, replacing them with a single new annotation.
* @param imageData
*/
public static void mergeSelectedAnnotations(ImageData<?> imageData) {
if (imageData == null)
return;
PathObjectHierarchy hierarchy = imageData.getHierarchy();
logger.debug("Merging selected annotations");
QP.mergeSelectedAnnotations(hierarchy);
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Merge selected annotations", "mergeSelectedAnnotations()"));
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class ExportObjectsCommand method runGeoJsonExport.
/**
* Run the path object GeoJSON export command.
* @param qupath
* @return success
* @throws IOException
*/
public static boolean runGeoJsonExport(QuPathGUI qupath) throws IOException {
// Get ImageData
var imageData = qupath.getImageData();
if (imageData == null)
return false;
// Get hierarchy
PathObjectHierarchy hierarchy = imageData.getHierarchy();
String allObjects = "All objects";
String selectedObjects = "Selected objects";
String defaultObjects = hierarchy.getSelectionModel().noSelection() ? allObjects : selectedObjects;
// Params
var parameterList = new ParameterList().addChoiceParameter("exportOptions", "Export ", defaultObjects, Arrays.asList(allObjects, selectedObjects), "Choose which objects to export - run a 'Select annotations/detections' command first if needed").addBooleanParameter("excludeMeasurements", "Exclude measurements", false, "Exclude object measurements during export - for large numbers of detections this can help reduce the file size").addBooleanParameter("doPretty", "Pretty JSON", false, "Pretty GeoJSON is more human-readable but results in larger file sizes").addBooleanParameter("doFeatureCollection", "Export as FeatureCollection", true, "Export as a 'FeatureCollection', which is a standard GeoJSON way to represent multiple objects; if not, a regular JSON object/array will be export").addBooleanParameter("doZip", "Compress data (zip)", false, "Compressed files take less memory");
if (!Dialogs.showParameterDialog("Export objects", parameterList))
return false;
Collection<PathObject> toProcess;
var comboChoice = parameterList.getChoiceParameterValue("exportOptions");
if (comboChoice.equals("Selected objects")) {
if (hierarchy.getSelectionModel().noSelection()) {
Dialogs.showErrorMessage("No selection", "No selection detected!");
return false;
}
toProcess = hierarchy.getSelectionModel().getSelectedObjects();
} else
toProcess = hierarchy.getObjects(null, null);
// Remove PathRootObject
toProcess = toProcess.stream().filter(e -> !e.isRootObject()).collect(Collectors.toList());
// Check if includes ellipse(s), as they will need to be polygonized
var nEllipses = toProcess.stream().filter(ann -> isEllipse(ann)).count();
if (nEllipses > 0) {
String message = nEllipses == 1 ? "1 ellipse will be polygonized, continue?" : String.format("%d ellipses will be polygonized, continue?", nEllipses);
var response = Dialogs.showYesNoDialog("Ellipse polygonization", message);
if (!response)
return false;
}
File outFile;
// Get default name & output directory
var project = qupath.getProject();
String defaultName = imageData.getServer().getMetadata().getName();
if (project != null) {
var entry = project.getEntry(imageData);
if (entry != null)
defaultName = entry.getImageName();
}
defaultName = GeneralTools.getNameWithoutExtension(defaultName);
File defaultDirectory = project == null || project.getPath() == null ? null : project.getPath().toFile();
while (defaultDirectory != null && !defaultDirectory.isDirectory()) defaultDirectory = defaultDirectory.getParentFile();
if (parameterList.getBooleanParameterValue("doZip"))
outFile = Dialogs.promptToSaveFile("Export to file", defaultDirectory, defaultName + ".zip", "ZIP archive", ".zip");
else
outFile = Dialogs.promptToSaveFile("Export to file", defaultDirectory, defaultName + ".geojson", "GeoJSON", ".geojson");
// If user cancels
if (outFile == null)
return false;
List<GeoJsonExportOptions> options = new ArrayList<>();
if (parameterList.getBooleanParameterValue("excludeMeasurements"))
options.add(GeoJsonExportOptions.EXCLUDE_MEASUREMENTS);
if (parameterList.getBooleanParameterValue("doPretty"))
options.add(GeoJsonExportOptions.PRETTY_JSON);
if (parameterList.getBooleanParameterValue("doFeatureCollection"))
options.add(GeoJsonExportOptions.FEATURE_COLLECTION);
// Export
QP.exportObjectsToGeoJson(toProcess, outFile.getAbsolutePath(), options.toArray(GeoJsonExportOptions[]::new));
// Notify user of success
int nObjects = toProcess.size();
String message = nObjects == 1 ? "1 object was exported to " + outFile.getAbsolutePath() : String.format("%d objects were exported to %s", nObjects, outFile.getAbsolutePath());
Dialogs.showInfoNotification("Succesful export", message);
// Get history workflow
var historyWorkflow = imageData.getHistoryWorkflow();
// args for workflow step
Map<String, String> map = new LinkedHashMap<>();
map.put("path", outFile.getPath());
String method = comboChoice.equals(allObjects) ? "exportAllObjectsToGeoJson" : "exportSelectedObjectsToGeoJson";
String methodTitle = comboChoice.equals(allObjects) ? "Export all objects" : "Export selected objects";
String optionsString = options.stream().map(o -> "\"" + o.name() + "\"").collect(Collectors.joining(", "));
map.put("options", optionsString);
if (!optionsString.isEmpty())
optionsString = ", " + optionsString;
String methodString = String.format("%s(%s%s)", method, "\"" + GeneralTools.escapeFilePath(outFile.getPath()) + "\"", optionsString);
historyWorkflow.addStep(new DefaultScriptableWorkflowStep(methodTitle, map, methodString));
return true;
}
Aggregations