use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class PathClassPane method createClassPane.
private Pane createClassPane() {
listClasses = new ListView<>();
var filteredList = qupath.getAvailablePathClasses().filtered(createPredicate(null));
listClasses.setItems(filteredList);
listClasses.setTooltip(new Tooltip("Annotation classes available (right-click to add or remove)"));
listClasses.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> updateAutoSetPathClassProperty());
listClasses.setCellFactory(v -> new PathClassListCell(qupath));
listClasses.getSelectionModel().select(0);
listClasses.setPrefSize(100, 200);
listClasses.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
var copyCombo = new KeyCodeCombination(KeyCode.C, KeyCodeCombination.SHORTCUT_DOWN);
var pasteCombo = new KeyCodeCombination(KeyCode.V, KeyCodeCombination.SHORTCUT_DOWN);
// Intercept space presses because we handle them elsewhere
listClasses.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.SPACE || e.getCode() == KeyCode.T) {
toggleSelectedClassesVisibility();
e.consume();
return;
} else if (e.getCode() == KeyCode.S) {
setSelectedClassesVisibility(true);
e.consume();
return;
} else if (e.getCode() == KeyCode.H) {
setSelectedClassesVisibility(false);
e.consume();
return;
}
});
listClasses.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.isConsumed())
return;
if (e.getCode() == KeyCode.BACK_SPACE) {
promptToRemoveSelectedClasses();
e.consume();
return;
} else if (e.getCode() == KeyCode.ENTER) {
promptToEditSelectedClass();
e.consume();
return;
} else if (copyCombo.match(e)) {
// Copy the list if needed
String s = listClasses.getSelectionModel().getSelectedItems().stream().map(p -> p.toString()).collect(Collectors.joining(System.lineSeparator()));
if (!s.isBlank()) {
Clipboard.getSystemClipboard().setContent(Map.of(DataFormat.PLAIN_TEXT, s));
}
e.consume();
return;
} else if (pasteCombo.match(e)) {
logger.debug("Paste not implemented for classification list!");
e.consume();
return;
}
});
listClasses.setOnMouseClicked(e -> {
if (!e.isPopupTrigger() && e.getClickCount() == 2)
promptToEditSelectedClass();
});
ContextMenu menuClasses = createClassesMenu();
listClasses.setContextMenu(menuClasses);
// Add the class list
BorderPane paneClasses = new BorderPane();
paneClasses.setCenter(listClasses);
Action setSelectedObjectClassAction = new Action("Set class", e -> {
var hierarchy = getHierarchy();
if (hierarchy == null)
return;
PathClass pathClass = getSelectedPathClass();
if (pathClass == PathClassFactory.getPathClassUnclassified())
pathClass = null;
var pathObjects = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
List<PathObject> changed = new ArrayList<>();
for (PathObject pathObject : pathObjects) {
if (pathObject.isTMACore())
continue;
if (pathObject.getPathClass() == pathClass)
continue;
pathObject.setPathClass(pathClass);
changed.add(pathObject);
}
if (!changed.isEmpty()) {
hierarchy.fireObjectClassificationsChangedEvent(this, changed);
GuiTools.refreshList(listClasses);
}
});
setSelectedObjectClassAction.setLongText("Set the class of the currently-selected annotation(s)");
Action autoClassifyAnnotationsAction = new Action("Auto set");
autoClassifyAnnotationsAction.setLongText("Automatically set all new annotations to the selected class");
autoClassifyAnnotationsAction.selectedProperty().bindBidirectional(doAutoSetPathClass);
doAutoSetPathClass.addListener((e, f, g) -> updateAutoSetPathClassProperty());
Button btnSetClass = ActionUtils.createButton(setSelectedObjectClassAction);
ToggleButton btnAutoClass = ActionUtils.createToggleButton(autoClassifyAnnotationsAction);
// Create a button to show context menu (makes it more obvious to the user that it exists)
Button btnMore = GuiTools.createMoreButton(menuClasses, Side.RIGHT);
GridPane paneClassButtons = new GridPane();
paneClassButtons.add(btnSetClass, 0, 0);
paneClassButtons.add(btnAutoClass, 1, 0);
paneClassButtons.add(btnMore, 2, 0);
GridPane.setHgrow(btnSetClass, Priority.ALWAYS);
GridPane.setHgrow(btnAutoClass, Priority.ALWAYS);
var tfFilter = new TextField();
tfFilter.setTooltip(new Tooltip("Type to filter classifications in list"));
tfFilter.setPromptText("Filter classifications in list");
filterText.bind(tfFilter.textProperty());
filterText.addListener((v, o, n) -> filteredList.setPredicate(createPredicate(n)));
var paneBottom = PaneTools.createRowGrid(tfFilter, paneClassButtons);
PaneTools.setMaxWidth(Double.MAX_VALUE, btnSetClass, btnAutoClass, tfFilter);
paneClasses.setBottom(paneBottom);
return paneClasses;
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class PathClassPane method selectObjectsByClassification.
/**
* Select objects by classification, logging the step (if performed) in the history workflow.
* @param imageData the {@link ImageData} containing objects to be selected
* @param pathClasses classifications that will result in an object being selected
* @return true if a selection command was run, false otherwise (e.g. if no pathClasses were specified)
*/
public static boolean selectObjectsByClassification(ImageData<?> imageData, PathClass... pathClasses) {
var hierarchy = imageData.getHierarchy();
if (pathClasses.length == 0) {
logger.warn("Cannot select objects by classification - no classifications selected!");
return false;
}
QP.selectObjectsByPathClass(hierarchy, pathClasses);
var s = Arrays.stream(pathClasses).map(p -> p == null || p == PathClassFactory.getPathClassUnclassified() ? "null" : "\"" + p.toString() + "\"").collect(Collectors.joining(", "));
imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Select objects by classification", "selectObjectsByClassification(" + s + ");"));
return true;
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class PathClassPane method promptToImportClasses.
/**
* Prompt to import available class list from another project.
* @return true if the class list was changed, false otherwise.
*/
boolean promptToImportClasses() {
File file = Dialogs.promptForFile("Import classifications", null, "QuPath project", ProjectIO.getProjectExtension());
if (file == null)
return false;
if (!file.getAbsolutePath().toLowerCase().endsWith(ProjectIO.getProjectExtension())) {
Dialogs.showErrorMessage("Import PathClasses", file.getName() + " is not a project file!");
return false;
}
try {
Project<?> project = ProjectIO.loadProject(file, BufferedImage.class);
List<PathClass> pathClasses = project.getPathClasses();
if (pathClasses.isEmpty()) {
Dialogs.showErrorMessage("Import PathClasses", "No classes found in " + file.getName());
return false;
}
ObservableList<PathClass> availableClasses = qupath.getAvailablePathClasses();
if (pathClasses.size() == availableClasses.size() && availableClasses.containsAll(pathClasses)) {
Dialogs.showInfoNotification("Import PathClasses", file.getName() + " contains same classifications - no changes to make");
return false;
}
availableClasses.setAll(pathClasses);
return true;
} catch (Exception ex) {
Dialogs.showErrorMessage("Error reading project", ex);
return false;
}
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class PathClassPane method createClassesMenu.
ContextMenu createClassesMenu() {
ContextMenu menu = new ContextMenu();
Action actionAddClass = new Action("Add class", e -> promptToAddClass());
Action actionRemoveClass = new Action("Remove class", e -> promptToRemoveSelectedClasses());
Action actionResetClasses = new Action("Reset to default classes", e -> promptToResetClasses());
Action actionImportClasses = new Action("Import classes from project", e -> promptToImportClasses());
actionRemoveClass.disabledProperty().bind(Bindings.createBooleanBinding(() -> {
PathClass item = listClasses.getSelectionModel().getSelectedItem();
return item == null || PathClassFactory.getPathClassUnclassified() == item;
}, listClasses.getSelectionModel().selectedItemProperty()));
MenuItem miRemoveClass = ActionUtils.createMenuItem(actionRemoveClass);
MenuItem miAddClass = ActionUtils.createMenuItem(actionAddClass);
MenuItem miResetAllClasses = ActionUtils.createMenuItem(actionResetClasses);
// MenuItem miPopulateFromImage = ActionUtils.createMenuItem(actionPopulateFromImage);
// MenuItem miPopulateFromImageBase = ActionUtils.createMenuItem(actionPopulateFromImageBase);
// MenuItem miClearAllClasses = new MenuItem("Clear all classes");
// miClearAllClasses.setOnAction(e -> promptToClearClasses());
MenuItem miPopulateFromImage = new MenuItem("All classes (including sub-classes)");
miPopulateFromImage.setOnAction(e -> promptToPopulateFromImage(false));
MenuItem miPopulateFromImageBase = new MenuItem("Base classes only");
miPopulateFromImageBase.setOnAction(e -> promptToPopulateFromImage(true));
MenuItem miPopulateFromChannels = new MenuItem("Populate from image channels");
miPopulateFromChannels.setOnAction(e -> promptToPopulateFromChannels());
Menu menuPopulate = new Menu("Populate from existing objects");
menuPopulate.getItems().addAll(miPopulateFromImage, miPopulateFromImageBase);
MenuItem miSelectObjects = new MenuItem("Select objects by classification");
miSelectObjects.disableProperty().bind(Bindings.createBooleanBinding(() -> {
var item = listClasses.getSelectionModel().getSelectedItem();
return item == null;
}, listClasses.getSelectionModel().selectedItemProperty()));
miSelectObjects.setOnAction(e -> {
var imageData = qupath.getImageData();
if (imageData == null)
return;
selectObjectsByClassification(imageData, getSelectedPathClasses().toArray(PathClass[]::new));
});
MenuItem miSetHidden = new MenuItem("Hide classes in viewer");
miSetHidden.setOnAction(e -> setSelectedClassesVisibility(false));
MenuItem miSetVisible = new MenuItem("Show classes in viewer");
miSetVisible.setOnAction(e -> setSelectedClassesVisibility(true));
// MenuItem miToggleClassVisible = new MenuItem("Toggle display class");
// miToggleClassVisible.setOnAction(e -> {
// OverlayOptions overlayOptions = qupath.getViewer().getOverlayOptions();
// for (var pathClass : getSelectedPathClasses()) {
// if (pathClass == null || pathClass == PathClassFactory.getPathClassUnclassified())
// continue;
// overlayOptions.setPathClassHidden(pathClass, !overlayOptions.isPathClassHidden(pathClass));
// }
// listClasses.refresh();
// });
menu.setOnShowing(e -> {
var hierarchy = getHierarchy();
menuPopulate.setDisable(hierarchy == null);
miPopulateFromImage.setDisable(hierarchy == null);
miPopulateFromImageBase.setDisable(hierarchy == null);
miPopulateFromChannels.setDisable(qupath.getImageData() == null);
var selected = getSelectedPathClasses();
boolean hasClasses = !selected.isEmpty();
// boolean hasClasses = selected.size() > 1 ||
// (selected.size() == 1 && selected.get(0) != null && selected.get(0) != PathClassFactory.getPathClassUnclassified());
miSetVisible.setDisable(!hasClasses);
miSetHidden.setDisable(!hasClasses);
// miRemoveClass.setDisable(!hasClasses);
});
MenuItem miImportFromProject = ActionUtils.createMenuItem(actionImportClasses);
menu.getItems().addAll(MenuTools.createMenu("Add/Remove...", miAddClass, miRemoveClass), menuPopulate, miPopulateFromChannels, miResetAllClasses, miImportFromProject, new SeparatorMenuItem(), MenuTools.createMenu("Show/Hide...", miSetVisible, miSetHidden), // new SeparatorMenuItem(),
miSelectObjects);
return menu;
}
use of qupath.lib.objects.classes.PathClass in project qupath by qupath.
the class PathClassPane method promptToEditSelectedClass.
/**
* Prompt to edit the selected classification.
* @return true if changes were made, false otherwise
*/
boolean promptToEditSelectedClass() {
PathClass pathClassSelected = getSelectedPathClass();
if (promptToEditClass(pathClassSelected)) {
// listModelPathClasses.fireListDataChangedEvent();
GuiTools.refreshList(listClasses);
var project = qupath.getProject();
// Make sure we have updated the classes in the project
if (project != null) {
project.setPathClasses(listClasses.getItems());
}
qupath.repaintViewers();
// TODO: Considering the only thing that can change is the color, firing an event should be unnecessary?
// In any case, it doesn't make sense to do the current image only... should do all or none
var hierarchy = getHierarchy();
if (hierarchy != null)
hierarchy.fireHierarchyChangedEvent(listClasses);
return true;
}
return false;
}
Aggregations