use of qupath.lib.gui.viewer.QuPathViewerPlus 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.gui.viewer.QuPathViewerPlus in project qupath by qupath.
the class QuPathGUI method initializeMainComponent.
private BorderPane initializeMainComponent() {
pane = new BorderPane();
// Create a reasonably-sized viewer
QuPathViewerPlus viewer = new QuPathViewerPlus(null, imageRegionStore, overlayOptions, viewerDisplayOptions);
// Add analysis panel & viewer to split pane
viewerManager = new MultiviewManager(viewer);
viewerProperty.bind(viewerManager.activeViewerProperty());
// Now that we have a viewer, we can create an undo/redo manager
undoRedoManager = new UndoRedoManager(this);
// TODO: MOVE INITIALIZING MANAGERS ELSEWHERE
actions.addAll(new Menus(this).getActions());
// Add a recent projects menu
getMenu("File", true).getItems().add(1, createRecentProjectsMenu());
// analysisPanel = createAnalysisPanel();
initializeAnalysisPanel();
analysisPanel.setMinWidth(300);
analysisPanel.setPrefWidth(400);
splitPane.setMinWidth(analysisPanel.getMinWidth() + 200);
splitPane.setPrefWidth(analysisPanel.getPrefWidth() + 200);
SplitPane.setResizableWithParent(analysisPanel, Boolean.FALSE);
// paneCommands.setRight(cbPin);
mainViewerPane = CommandFinderTools.createCommandFinderPane(this, viewerManager.getNode(), CommandFinderTools.commandBarDisplayProperty());
// paneViewer.setTop(tfCommands);
// paneViewer.setCenter(viewerManager.getNode());
splitPane.getItems().addAll(analysisPanel, mainViewerPane);
// splitPane.getItems().addAll(viewerManager.getComponent());
SplitPane.setResizableWithParent(viewerManager.getNode(), Boolean.TRUE);
pane.setCenter(splitPane);
toolbar = new ToolBarComponent(this);
pane.setTop(toolbar.getToolBar());
setAnalysisPaneVisible(showAnalysisPane.get());
showAnalysisPane.addListener((v, o, n) -> setAnalysisPaneVisible(n));
// setInitialLocationAndMagnification(getViewer());
// Prepare the viewer
setupViewer(viewerManager.getActiveViewer());
// Ensure the mode is set
// Ensure actions are set appropriately
selectedToolProperty.addListener((v, o, n) -> {
Action action = toolActions.get(n);
if (action != null)
action.setSelected(true);
activateTools(getViewer());
if (n == PathTools.POINTS)
defaultActions.COUNTING_PANEL.handle(null);
updateCursor();
});
setSelectedTool(getSelectedTool());
return pane;
}
use of qupath.lib.gui.viewer.QuPathViewerPlus in project qupath by qupath.
the class QuPathGUI method openImageEntry.
/**
* Open the image represented by the specified ProjectImageEntry.
* <p>
* If an image is currently open, this command will prompt to save any changes.
*
* @param entry
* @return
*/
public boolean openImageEntry(ProjectImageEntry<BufferedImage> entry) {
Project<BufferedImage> project = getProject();
if (entry == null || project == null)
return false;
// Check if we're changing ImageData at all
var viewer = getViewer();
ImageData<BufferedImage> imageData = viewer.getImageData();
if (imageData != null && project.getEntry(imageData) == entry) {
return false;
}
// String path = entry.getServerPath();
for (QuPathViewerPlus v : viewerManager.getViewers()) {
ImageData<BufferedImage> data = v.getImageData();
if (data != null && project.getEntry(data) == entry) {
// if (data != null && data.getServer().getPath().equals(path)) {
viewerManager.setActiveViewer(v);
return true;
}
}
// If the current ImageData belongs to the current project, check if there are changes to save
if (imageData != null && project != null) {
if (!checkSaveChanges(imageData))
return false;
}
// Check if we need to rotate the image
try {
imageData = entry.readImageData();
viewer.setImageData(imageData);
// setInitialLocationAndMagnification(viewer);
if (imageData != null && (imageData.getImageType() == null || imageData.getImageType() == ImageType.UNSET)) {
var setType = PathPrefs.imageTypeSettingProperty().get();
if (setType == ImageTypeSetting.AUTO_ESTIMATE || setType == ImageTypeSetting.PROMPT) {
var type = GuiTools.estimateImageType(imageData.getServer(), imageRegionStore.getThumbnail(imageData.getServer(), 0, 0, true));
logger.info("Image type estimated to be {}", type);
if (setType == ImageTypeSetting.PROMPT) {
ImageDetailsPane.promptToSetImageType(imageData, type);
} else {
imageData.setImageType(type);
// Don't want to retain this as a change resulting in a prompt to save the data
imageData.setChanged(false);
}
}
}
return true;
} catch (Exception e) {
Dialogs.showErrorMessage("Load ImageData", e);
return false;
}
}
use of qupath.lib.gui.viewer.QuPathViewerPlus in project qupath by qupath.
the class QuPathGUI method openSavedData.
/**
* Open a saved data file within a particular viewer, optionally keeping the same ImageServer as is currently open.
* The purpose of this is to make it possible for a project (for example) to open the correct server prior to
* opening the data file, enabling it to make use of relative path names and not have to rely on the absolute path
* encoded within the ImageData.
*
* @param viewer
* @param file
* @param keepExistingServer if true and the viewer already has an ImageServer, then any ImageServer path recorded within the data file will be ignored
* @param promptToSaveChanges if true, the user will be prompted to ask whether to save changes or not
* @return
* @throws IOException
*/
public boolean openSavedData(QuPathViewer viewer, final File file, final boolean keepExistingServer, boolean promptToSaveChanges) throws IOException {
if (viewer == null) {
if (getViewers().size() == 1)
viewer = getViewer();
else {
Dialogs.showErrorMessage("Open saved data", "Please specify the viewer where the data should be opened!");
return false;
}
}
// First check to see if the ImageData is already open - if so, just activate the viewer
for (QuPathViewerPlus v : viewerManager.getViewers()) {
ImageData<?> data = v.getImageData();
if (data != null && data.getLastSavedPath() != null && new File(data.getLastSavedPath()).equals(file)) {
viewerManager.setActiveViewer(v);
return true;
}
}
ServerBuilder<BufferedImage> serverBuilder = null;
ImageData<BufferedImage> imageData = viewer.getImageData();
// If we are loading data related to the same image server, load into that - otherwise open a new image if we can find it
try {
serverBuilder = PathIO.extractServerBuilder(file.toPath());
} catch (Exception e) {
logger.warn("Unable to read server path from file: {}", e.getLocalizedMessage());
}
var existingBuilder = imageData == null || imageData.getServer() == null ? null : imageData.getServer().getBuilder();
boolean sameServer = Objects.equals(existingBuilder, serverBuilder);
// If we don't have the same server, try to check the path is valid.
// If it isn't, then prompt to enter a new path.
// Currently, URLs are always assumed to be valid, but files may have moved.
// TODO: Make it possible to recover data if a stored URL ceases to be valid.
ImageServer<BufferedImage> server = null;
if (sameServer || (imageData != null && keepExistingServer))
server = imageData.getServer();
else {
try {
server = serverBuilder.build();
} catch (Exception e) {
logger.error("Unable to build server " + serverBuilder, e);
}
// TODO: Ideally we would use an interface like ProjectCheckUris instead
if (server == null && serverBuilder != null) {
var uris = serverBuilder.getURIs();
var urisUpdated = new HashMap<URI, URI>();
for (var uri : uris) {
var pathUri = GeneralTools.toPath(uri);
if (pathUri != null && Files.exists(pathUri)) {
urisUpdated.put(uri, uri);
continue;
}
String currentPath = pathUri == null ? uri.toString() : pathUri.toString();
var newPath = Dialogs.promptForFilePathOrURL("Set path to missing image", currentPath, file.getParentFile(), null);
if (newPath == null)
return false;
try {
urisUpdated.put(uri, GeneralTools.toURI(newPath));
} catch (URISyntaxException e) {
throw new IOException(e);
}
}
serverBuilder = serverBuilder.updateURIs(urisUpdated);
try {
server = serverBuilder.build();
} catch (Exception e) {
logger.error("Unable to build server " + serverBuilder, e);
}
}
if (server == null)
return false;
//
// Small optimization... put in a thumbnail request early in a background thread.
// This way that it will be fetched while the image data is being read -
// generally leading to improved performance in the viewer's setImageData method
// (specifically the updating of the ImageDisplay, which needs a thumbnail)
final ImageServer<BufferedImage> serverTemp = server;
poolMultipleThreads.submit(() -> {
imageRegionStore.getThumbnail(serverTemp, 0, 0, true);
});
}
if (promptToSaveChanges && imageData != null && imageData.isChanged()) {
if (!isReadOnly() && !promptToSaveChangesOrCancel("Save changes", imageData))
return false;
}
try {
ImageData<BufferedImage> imageData2 = PathIO.readImageData(file, imageData, server, BufferedImage.class);
if (imageData2 != imageData) {
viewer.setImageData(imageData2);
}
} catch (IOException e) {
Dialogs.showErrorMessage("Read image data", e);
}
return true;
}
Aggregations