use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class TMACommands method promptToExportTMAData.
/**
* Prompt to export summary TMA data for a specific image to a directory.
* @param qupath
* @param imageData
*/
public static void promptToExportTMAData(QuPathGUI qupath, ImageData<BufferedImage> imageData) {
String title = "Export TMA data";
if (imageData == null) {
Dialogs.showNoImageError(title);
return;
}
PathObjectHierarchy hierarchy = imageData == null ? null : imageData.getHierarchy();
if (hierarchy == null || hierarchy.isEmpty() || hierarchy.getTMAGrid() == null || hierarchy.getTMAGrid().nCores() == 0) {
Dialogs.showErrorMessage(title, "No TMA data available!");
return;
}
var overlayOptions = qupath.getViewers().stream().filter(v -> v.getImageData() == imageData).map(v -> v.getOverlayOptions()).findFirst().orElse(qupath.getOverlayOptions());
String defaultName = ServerTools.getDisplayableImageName(imageData.getServer());
File file = Dialogs.promptToSaveFile(null, null, defaultName, "TMA data", ".qptma");
if (file != null) {
if (!file.getName().endsWith(".qptma"))
file = new File(file.getParentFile(), file.getName() + ".qptma");
double downsample = PathPrefs.tmaExportDownsampleProperty().get();
TMADataIO.writeTMAData(file, imageData, overlayOptions, downsample);
WorkflowStep step = new DefaultScriptableWorkflowStep("Export TMA data", "exportTMAData(\"" + GeneralTools.escapeFilePath(file.getParentFile().getAbsolutePath()) + "\", " + downsample + ")");
imageData.getHistoryWorkflow().addStep(step);
// PathAwtIO.writeTMAData(file, imageData, viewer.getOverlayOptions(), Double.NaN);
}
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class TMACommands method promptToDeleteTMAGridRowOrColumn.
private static boolean promptToDeleteTMAGridRowOrColumn(ImageData<?> imageData, TMARemoveType type) {
String typeString = type.toString();
String title = "Delete TMA " + typeString;
boolean removeRow = type == TMARemoveType.ROW;
if (imageData == null) {
Dialogs.showNoImageError(title);
return false;
}
if (imageData.getHierarchy().getTMAGrid() == null) {
Dialogs.showErrorMessage(title, "No image with dearrayed TMA cores selected!");
return false;
}
PathObjectHierarchy hierarchy = imageData.getHierarchy();
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
TMACoreObject selectedCore = null;
if (selected != null)
selectedCore = PathObjectTools.getAncestorTMACore(selected);
// Try to identify the row/column that we want
TMAGrid grid = hierarchy.getTMAGrid();
int row = -1;
int col = -1;
if (selectedCore != null) {
for (int y = 0; y < grid.getGridHeight(); y++) {
for (int x = 0; x < grid.getGridWidth(); x++) {
if (grid.getTMACore(y, x) == selectedCore) {
row = y;
col = x;
break;
}
}
}
}
// We need a selected core to know what to remove
if (row < 0 || col < 0) {
Dialogs.showErrorMessage(title, "Please select a TMA core to indicate which " + typeString + " to remove");
return false;
}
// Check we have enough rows/columns - if not, this is just a clear operation
if ((removeRow && grid.getGridHeight() <= 1) || (!removeRow && grid.getGridWidth() <= 1)) {
if (Dialogs.showConfirmDialog(title, "Are you sure you want to delete the entire TMA grid?"))
hierarchy.setTMAGrid(null);
return false;
}
// Confirm the removal - add 1 due to 'base 0' probably not being expected by most users...
int num = removeRow ? row : col;
if (!Dialogs.showConfirmDialog(title, "Are you sure you want to delete " + typeString + " " + (num + 1) + " from TMA grid?"))
return false;
// Create a new grid
List<TMACoreObject> coresNew = new ArrayList<>();
for (int r = 0; r < grid.getGridHeight(); r++) {
if (removeRow && row == r)
continue;
for (int c = 0; c < grid.getGridWidth(); c++) {
if (!removeRow && col == c)
continue;
coresNew.add(grid.getTMACore(r, c));
}
}
int newWidth = removeRow ? grid.getGridWidth() : grid.getGridWidth() - 1;
TMAGrid gridNew = DefaultTMAGrid.create(coresNew, newWidth);
hierarchy.setTMAGrid(gridNew);
hierarchy.getSelectionModel().clearSelection();
// Request new labels
promptToRelabelTMAGrid(imageData);
return true;
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class Commands method convertDetectionsToPoints.
/**
* Convert detection objects to point annotations based upon their ROI centroids.
* @param imageData the image data to process
* @param preferNucleus if true, use a nucleus ROI for cell objects (if available
*/
public static void convertDetectionsToPoints(ImageData<?> imageData, boolean preferNucleus) {
if (imageData == null) {
Dialogs.showNoImageError("Convert detections to points");
return;
}
PathObjectHierarchy hierarchy = imageData.getHierarchy();
Collection<PathObject> pathObjects = hierarchy.getDetectionObjects();
if (pathObjects.isEmpty()) {
Dialogs.showErrorMessage("Detections to points", "No detections found!");
return;
}
// Remove any detections that don't have a ROI - can't do much with them
Iterator<PathObject> iter = pathObjects.iterator();
while (iter.hasNext()) {
if (!iter.next().hasROI())
iter.remove();
}
if (pathObjects.isEmpty()) {
logger.warn("No detections found with ROIs!");
return;
}
// Check if existing objects should be deleted
String message = pathObjects.size() == 1 ? "Delete detection after converting to a point?" : String.format("Delete %d detections after converting to points?", pathObjects.size());
var button = Dialogs.showYesNoCancelDialog("Detections to points", message);
if (button == Dialogs.DialogButton.CANCEL)
return;
boolean deleteDetections = button == Dialogs.DialogButton.YES;
PathObjectTools.convertToPoints(hierarchy, pathObjects, preferNucleus, deleteDetections);
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class QuPathGUI method setViewerPopupMenu.
private void setViewerPopupMenu(final QuPathViewerPlus viewer) {
final ContextMenu popup = new ContextMenu();
MenuItem miAddRow = new MenuItem("Add row");
miAddRow.setOnAction(e -> viewerManager.addRow(viewer));
MenuItem miAddColumn = new MenuItem("Add column");
miAddColumn.setOnAction(e -> viewerManager.addColumn(viewer));
MenuItem miRemoveRow = new MenuItem("Remove row");
miRemoveRow.setOnAction(e -> viewerManager.removeViewerRow(viewer));
MenuItem miRemoveColumn = new MenuItem("Remove column");
miRemoveColumn.setOnAction(e -> viewerManager.removeViewerColumn(viewer));
MenuItem miCloseViewer = new MenuItem("Close viewer");
miCloseViewer.setOnAction(e -> {
viewerManager.closeViewer(viewer);
// viewerManager.removeViewer(viewer);
});
MenuItem miResizeGrid = new MenuItem("Reset grid size");
miResizeGrid.setOnAction(e -> {
viewerManager.resetGridSize();
});
MenuItem miToggleSync = ActionTools.createCheckMenuItem(defaultActions.TOGGLE_SYNCHRONIZE_VIEWERS, null);
MenuItem miMatchResolutions = ActionTools.createMenuItem(defaultActions.MATCH_VIEWER_RESOLUTIONS);
Menu menuMultiview = MenuTools.createMenu("Multi-view", miToggleSync, miMatchResolutions, miCloseViewer, null, miResizeGrid, null, // null,
miAddRow, miAddColumn, null, miRemoveRow, miRemoveColumn);
Menu menuView = MenuTools.createMenu("Display", ActionTools.createCheckMenuItem(defaultActions.SHOW_ANALYSIS_PANE, null), defaultActions.BRIGHTNESS_CONTRAST, null, ActionTools.createAction(() -> Commands.setViewerDownsample(viewer, 0.25), "400%"), ActionTools.createAction(() -> Commands.setViewerDownsample(viewer, 1), "100%"), ActionTools.createAction(() -> Commands.setViewerDownsample(viewer, 2), "50%"), ActionTools.createAction(() -> Commands.setViewerDownsample(viewer, 10), "10%"), ActionTools.createAction(() -> Commands.setViewerDownsample(viewer, 100), "1%"));
ToggleGroup groupTools = new ToggleGroup();
Menu menuTools = MenuTools.createMenu("Set tool", ActionTools.createCheckMenuItem(defaultActions.MOVE_TOOL, groupTools), ActionTools.createCheckMenuItem(defaultActions.RECTANGLE_TOOL, groupTools), ActionTools.createCheckMenuItem(defaultActions.ELLIPSE_TOOL, groupTools), ActionTools.createCheckMenuItem(defaultActions.LINE_TOOL, groupTools), ActionTools.createCheckMenuItem(defaultActions.POLYGON_TOOL, groupTools), ActionTools.createCheckMenuItem(defaultActions.POLYLINE_TOOL, groupTools), ActionTools.createCheckMenuItem(defaultActions.BRUSH_TOOL, groupTools), ActionTools.createCheckMenuItem(defaultActions.POINTS_TOOL, groupTools), null, ActionTools.createCheckMenuItem(defaultActions.SELECTION_MODE));
// Handle awkward 'TMA core missing' option
CheckMenuItem miTMAValid = new CheckMenuItem("Set core valid");
miTMAValid.setOnAction(e -> setTMACoreMissing(viewer.getHierarchy(), false));
CheckMenuItem miTMAMissing = new CheckMenuItem("Set core missing");
miTMAMissing.setOnAction(e -> setTMACoreMissing(viewer.getHierarchy(), true));
Menu menuTMA = new Menu("TMA");
MenuTools.addMenuItems(menuTMA, miTMAValid, miTMAMissing, null, defaultActions.TMA_ADD_NOTE, null, MenuTools.createMenu("Add", createImageDataAction(imageData -> TMACommands.promptToAddRowBeforeSelected(imageData), "Add TMA row before"), createImageDataAction(imageData -> TMACommands.promptToAddRowAfterSelected(imageData), "Add TMA row after"), createImageDataAction(imageData -> TMACommands.promptToAddColumnBeforeSelected(imageData), "Add TMA column before"), createImageDataAction(imageData -> TMACommands.promptToAddColumnAfterSelected(imageData), "Add TMA column after")), MenuTools.createMenu("Remove", createImageDataAction(imageData -> TMACommands.promptToDeleteTMAGridRow(imageData), "Remove TMA row"), createImageDataAction(imageData -> TMACommands.promptToDeleteTMAGridColumn(imageData), "column")));
// Create an empty placeholder menu
Menu menuSetClass = MenuTools.createMenu("Set class");
// CheckMenuItem miTMAValid = new CheckMenuItem("Set core valid");
// miTMAValid.setOnAction(e -> setTMACoreMissing(viewer.getHierarchy(), false));
// CheckMenuItem miTMAMissing = new CheckMenuItem("Set core missing");
// miTMAMissing.setOnAction(e -> setTMACoreMissing(viewer.getHierarchy(), true));
Menu menuCells = MenuTools.createMenu("Cells", ActionTools.createCheckMenuItem(defaultActions.SHOW_CELL_BOUNDARIES_AND_NUCLEI, null), ActionTools.createCheckMenuItem(defaultActions.SHOW_CELL_NUCLEI, null), ActionTools.createCheckMenuItem(defaultActions.SHOW_CELL_BOUNDARIES, null), ActionTools.createCheckMenuItem(defaultActions.SHOW_CELL_CENTROIDS, null));
MenuItem miClearSelectedObjects = new MenuItem("Delete object");
miClearSelectedObjects.setOnAction(e -> {
PathObjectHierarchy hierarchy = viewer.getHierarchy();
if (hierarchy == null)
return;
if (hierarchy.getSelectionModel().singleSelection()) {
GuiTools.promptToRemoveSelectedObject(hierarchy.getSelectionModel().getSelectedObject(), hierarchy);
} else {
GuiTools.promptToClearAllSelectedObjects(viewer.getImageData());
}
});
// Create a standard annotations menu
Menu menuAnnotations = GuiTools.populateAnnotationsMenu(this, new Menu("Annotations"));
SeparatorMenuItem topSeparator = new SeparatorMenuItem();
popup.setOnShowing(e -> {
// Check if we have any cells
ImageData<?> imageData = viewer.getImageData();
if (imageData == null)
menuCells.setVisible(false);
else
menuCells.setVisible(!imageData.getHierarchy().getDetectionObjects().isEmpty());
// Check what to show for TMA cores or annotations
Collection<PathObject> selectedObjects = viewer.getAllSelectedObjects();
PathObject pathObject = viewer.getSelectedObject();
menuTMA.setVisible(false);
if (pathObject instanceof TMACoreObject) {
boolean isMissing = ((TMACoreObject) pathObject).isMissing();
miTMAValid.setSelected(!isMissing);
miTMAMissing.setSelected(isMissing);
menuTMA.setVisible(true);
}
// Add clear objects option if we have more than one non-TMA object
if (imageData == null || imageData.getHierarchy().getSelectionModel().noSelection() || imageData.getHierarchy().getSelectionModel().getSelectedObject() instanceof TMACoreObject)
miClearSelectedObjects.setVisible(false);
else {
if (imageData.getHierarchy().getSelectionModel().singleSelection()) {
miClearSelectedObjects.setText("Delete object");
miClearSelectedObjects.setVisible(true);
} else {
miClearSelectedObjects.setText("Delete objects");
miClearSelectedObjects.setVisible(true);
}
}
boolean hasAnnotations = pathObject instanceof PathAnnotationObject || (!selectedObjects.isEmpty() && selectedObjects.stream().allMatch(p -> p.isAnnotation()));
updateSetAnnotationPathClassMenu(menuSetClass, viewer);
menuAnnotations.setVisible(hasAnnotations);
topSeparator.setVisible(hasAnnotations || pathObject instanceof TMACoreObject);
// Occasionally, the newly-visible top part of a popup menu can have the wrong size?
popup.setWidth(popup.getPrefWidth());
});
// popup.add(menuClassify);
popup.getItems().addAll(miClearSelectedObjects, menuTMA, menuSetClass, menuAnnotations, topSeparator, menuMultiview, menuCells, menuView, menuTools);
popup.setAutoHide(true);
// Enable circle pop-up for quick classification on right-click
CirclePopupMenu circlePopup = new CirclePopupMenu(viewer.getView(), null);
viewer.getView().addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {
if ((e.isPopupTrigger() || e.isSecondaryButtonDown()) && e.isShiftDown() && !getAvailablePathClasses().isEmpty()) {
circlePopup.setAnimationDuration(Duration.millis(200));
updateSetAnnotationPathClassMenu(circlePopup, viewer);
circlePopup.show(e.getScreenX(), e.getScreenY());
e.consume();
return;
} else if (circlePopup.isShown())
circlePopup.hide();
if (e.isPopupTrigger() || e.isSecondaryButtonDown()) {
popup.show(viewer.getView().getScene().getWindow(), e.getScreenX(), e.getScreenY());
e.consume();
}
});
// // It's necessary to make the Window the owner, since otherwise the context menu does not disappear when clicking elsewhere on the viewer
// viewer.getView().setOnContextMenuRequested(e -> {
// popup.show(viewer.getView().getScene().getWindow(), e.getScreenX(), e.getScreenY());
// // popup.show(viewer.getView(), e.getScreenX(), e.getScreenY());
// });
}
use of qupath.lib.objects.hierarchy.PathObjectHierarchy in project qupath by qupath.
the class PathObjectTools method mergePointsForAllClasses.
/**
* Merge point annotations sharing the same {@link PathClass} and {@link ImagePlane},
* creating multi-point annotations for all matching points and removing the (previously-separated) annotations.
*
* @param hierarchy object hierarchy to modify
* @return true if changes are made to the hierarchy, false otherwise
*/
public static boolean mergePointsForAllClasses(PathObjectHierarchy hierarchy) {
if (hierarchy == null)
return false;
var pathClasses = hierarchy.getAnnotationObjects().stream().filter(p -> p.getROI().isPoint()).map(p -> p.getPathClass()).collect(Collectors.toSet());
boolean changes = false;
for (PathClass pathClass : pathClasses) changes = changes || mergePointsForClass(hierarchy, pathClass);
return changes;
}
Aggregations