use of qupath.lib.projects.Project in project qupath by qupath.
the class ProjectBrowser method getPopup.
ContextMenu getPopup() {
Action actionOpenImage = new Action("Open image", e -> qupath.openImageEntry(getSelectedEntry()));
Action actionRemoveImage = new Action("Remove image(s)", e -> {
Collection<ImageRow> imageRows = getSelectedImageRowsRecursive();
Collection<ProjectImageEntry<BufferedImage>> entries = ProjectTreeRow.getEntries(imageRows);
if (entries.isEmpty())
return;
// Don't allow us to remove any entries that are currently open (in any viewer)
for (var viewer : qupath.getViewers()) {
var imageData = viewer.getImageData();
var entry = imageData == null ? null : getProject().getEntry(imageData);
if (entry != null && entries.contains(entry)) {
Dialogs.showErrorMessage("Remove project entries", "Please close all images you want to remove!");
return;
}
}
if (entries.size() == 1) {
if (!Dialogs.showConfirmDialog("Remove project entry", "Remove " + entries.iterator().next().getImageName() + " from project?"))
return;
} else if (!Dialogs.showYesNoDialog("Remove project entries", String.format("Remove %d entries?", entries.size())))
return;
var result = Dialogs.showYesNoCancelDialog("Remove project entries", "Delete all associated data?");
if (result == DialogButton.CANCEL)
return;
project.removeAllImages(entries, result == DialogButton.YES);
refreshTree(null);
syncProject(project);
if (tree != null) {
boolean isExpanded = tree.getRoot() != null && tree.getRoot().isExpanded();
tree.setRoot(model.getRoot());
tree.getRoot().setExpanded(isExpanded);
}
});
Action actionDuplicateImages = new Action("Duplicate image(s)", e -> {
Collection<ImageRow> imageRows = getSelectedImageRowsRecursive();
if (imageRows.isEmpty()) {
logger.debug("Nothing to duplicate - no entries selected");
return;
}
boolean singleImage = false;
String name = "";
String title = "Duplicate images";
String namePrompt = "Append to image name";
String nameHelp = "Specify text to append to the image name to distinguish duplicated images";
if (imageRows.size() == 1) {
title = "Duplicate image";
namePrompt = "Duplicate image name";
nameHelp = "Specify name for the duplicated image";
singleImage = true;
name = imageRows.iterator().next().getDisplayableString();
name = GeneralTools.generateDistinctName(name, project.getImageList().stream().map(p -> p.getImageName()).collect(Collectors.toSet()));
}
var params = new ParameterList().addStringParameter("name", namePrompt, name, nameHelp).addBooleanParameter("copyData", "Also duplicate data files", true, "Duplicate any associated data files along with the image");
if (!Dialogs.showParameterDialog(title, params))
return;
boolean copyData = params.getBooleanParameterValue("copyData");
name = params.getStringParameterValue("name");
// Ensure we have a single space and then the text to append, with extra whitespace removed
if (!singleImage && !name.isBlank())
name = " " + name.strip();
for (var imageRow : imageRows) {
try {
var newEntry = project.addDuplicate(ProjectTreeRow.getEntry(imageRow), copyData);
if (newEntry != null && !name.isBlank()) {
if (singleImage)
newEntry.setImageName(name);
else
newEntry.setImageName(newEntry.getImageName() + name);
}
} catch (Exception ex) {
Dialogs.showErrorNotification("Duplicating image", "Error duplicating " + ProjectTreeRow.getEntry(imageRow).getImageName());
logger.error(ex.getLocalizedMessage(), ex);
}
}
try {
project.syncChanges();
} catch (Exception ex) {
logger.error("Error synchronizing project changes: " + ex.getLocalizedMessage(), ex);
}
refreshProject();
if (imageRows.size() == 1)
logger.debug("Duplicated 1 image entry");
else
logger.debug("Duplicated {} image entries");
});
Action actionSetImageName = new Action("Rename image", e -> {
TreeItem<ProjectTreeRow> path = tree.getSelectionModel().getSelectedItem();
if (path == null)
return;
if (path.getValue().getType() == ProjectTreeRow.Type.IMAGE) {
if (setProjectEntryImageName(ProjectTreeRow.getEntry(path.getValue())) && project != null)
syncProject(project);
}
});
// Add a metadata value
Action actionAddMetadataValue = new Action("Add metadata", e -> {
Project<BufferedImage> project = getProject();
Collection<ImageRow> imageRows = getSelectedImageRowsRecursive();
if (project != null && !imageRows.isEmpty()) {
TextField tfMetadataKey = new TextField();
var suggestions = project.getImageList().stream().map(p -> p.getMetadataKeys()).flatMap(Collection::stream).distinct().sorted().collect(Collectors.toList());
TextFields.bindAutoCompletion(tfMetadataKey, suggestions);
TextField tfMetadataValue = new TextField();
Label labKey = new Label("New key");
Label labValue = new Label("New value");
labKey.setLabelFor(tfMetadataKey);
labValue.setLabelFor(tfMetadataValue);
tfMetadataKey.setTooltip(new Tooltip("Enter the name for the metadata entry"));
tfMetadataValue.setTooltip(new Tooltip("Enter the value for the metadata entry"));
ProjectImageEntry<BufferedImage> entry = imageRows.size() == 1 ? ProjectTreeRow.getEntry(imageRows.iterator().next()) : null;
int nMetadataValues = entry == null ? 0 : entry.getMetadataKeys().size();
GridPane pane = new GridPane();
pane.setVgap(5);
pane.setHgap(5);
pane.add(labKey, 0, 0);
pane.add(tfMetadataKey, 1, 0);
pane.add(labValue, 0, 1);
pane.add(tfMetadataValue, 1, 1);
String name = imageRows.size() + " images";
if (entry != null) {
name = entry.getImageName();
if (nMetadataValues > 0) {
Label labelCurrent = new Label("Current metadata");
TextArea textAreaCurrent = new TextArea();
textAreaCurrent.setEditable(false);
String keyString = entry.getMetadataSummaryString();
if (keyString.isEmpty())
textAreaCurrent.setText("No metadata entries yet");
else
textAreaCurrent.setText(keyString);
textAreaCurrent.setPrefRowCount(3);
labelCurrent.setLabelFor(textAreaCurrent);
pane.add(labelCurrent, 0, 2);
pane.add(textAreaCurrent, 1, 2);
}
}
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("Metadata");
dialog.getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().setHeaderText("Set metadata for " + name);
dialog.getDialogPane().setContent(pane);
Optional<ButtonType> result = dialog.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) {
String key = tfMetadataKey.getText().trim();
String value = tfMetadataValue.getText();
if (key.isEmpty()) {
logger.warn("Attempted to set metadata value for {}, but key was empty!", name);
} else {
// Set metadata for all entries
for (var temp : imageRows) ProjectTreeRow.getEntry(temp).putMetadataValue(key, value);
syncProject(project);
tree.refresh();
}
}
} else {
Dialogs.showErrorMessage("Edit image description", "No entry is selected!");
}
});
// Edit the description for the image
Action actionEditDescription = new Action("Edit description", e -> {
Project<?> project = getProject();
ProjectImageEntry<?> entry = getSelectedEntry();
if (project != null && entry != null) {
if (showDescriptionEditor(entry)) {
descriptionText.set(entry.getDescription());
syncProject(project);
}
} else {
Dialogs.showErrorMessage("Edit image description", "No entry is selected!");
}
});
// Mask the name of the images and shuffle the entry
Action actionMaskImageNames = ActionTools.createSelectableAction(PathPrefs.maskImageNamesProperty(), "Mask image names");
// Refresh thumbnail according to current display settings
Action actionRefreshThumbnail = new Action("Refresh thumbnail", e -> {
TreeItem<ProjectTreeRow> path = tree.getSelectionModel().getSelectedItem();
if (path == null)
return;
if (path.getValue().getType() == ProjectTreeRow.Type.IMAGE) {
ProjectImageEntry<BufferedImage> entry = ProjectTreeRow.getEntry(path.getValue());
if (!isCurrentImage(entry)) {
logger.warn("Cannot refresh entry for image that is not open!");
return;
}
BufferedImage imgThumbnail = qupath.getViewer().getRGBThumbnail();
imgThumbnail = resizeForThumbnail(imgThumbnail);
try {
entry.setThumbnail(imgThumbnail);
} catch (IOException e1) {
logger.error("Error writing thumbnail", e1);
}
tree.refresh();
}
});
// Open the project directory using Explorer/Finder etc.
Action actionOpenProjectDirectory = createBrowsePathAction("Project...", () -> getProjectPath());
Action actionOpenProjectEntryDirectory = createBrowsePathAction("Project entry...", () -> getProjectEntryPath());
Action actionOpenImageServerDirectory = createBrowsePathAction("Image server...", () -> getImageServerPath());
Menu menuSort = new Menu("Sort by...");
ContextMenu menu = new ContextMenu();
var hasProjectBinding = qupath.projectProperty().isNotNull();
var menuOpenDirectories = MenuTools.createMenu("Open directory...", actionOpenProjectDirectory, actionOpenProjectEntryDirectory, actionOpenImageServerDirectory);
menuOpenDirectories.visibleProperty().bind(hasProjectBinding);
// MenuItem miOpenProjectDirectory = ActionUtils.createMenuItem(actionOpenProjectDirectory);
MenuItem miOpenImage = ActionUtils.createMenuItem(actionOpenImage);
MenuItem miRemoveImage = ActionUtils.createMenuItem(actionRemoveImage);
MenuItem miDuplicateImage = ActionUtils.createMenuItem(actionDuplicateImages);
MenuItem miSetImageName = ActionUtils.createMenuItem(actionSetImageName);
MenuItem miRefreshThumbnail = ActionUtils.createMenuItem(actionRefreshThumbnail);
MenuItem miEditDescription = ActionUtils.createMenuItem(actionEditDescription);
MenuItem miAddMetadata = ActionUtils.createMenuItem(actionAddMetadataValue);
MenuItem miMaskImages = ActionUtils.createCheckMenuItem(actionMaskImageNames);
// Set visibility as menu being displayed
menu.setOnShowing(e -> {
TreeItem<ProjectTreeRow> selected = tree.getSelectionModel().getSelectedItem();
ProjectImageEntry<BufferedImage> selectedEntry = selected == null ? null : ProjectTreeRow.getEntry(selected.getValue());
var entries = getSelectedImageRowsRecursive();
boolean isImageEntry = selectedEntry != null;
// miOpenProjectDirectory.setVisible(project != null && project.getBaseDirectory().exists());
miOpenImage.setVisible(isImageEntry);
miDuplicateImage.setVisible(isImageEntry);
miSetImageName.setVisible(isImageEntry);
miAddMetadata.setVisible(!entries.isEmpty());
miEditDescription.setVisible(isImageEntry);
miRefreshThumbnail.setVisible(isImageEntry && isCurrentImage(selectedEntry));
miRemoveImage.setVisible(selected != null && project != null && !project.getImageList().isEmpty());
if (project == null) {
menuSort.setVisible(false);
return;
}
Map<String, MenuItem> newItems = new TreeMap<>();
for (ProjectImageEntry<?> entry : project.getImageList()) {
// Add all entry metadata keys
for (String key : entry.getMetadataKeys()) {
if (!newItems.containsKey(key))
newItems.put(key, ActionUtils.createMenuItem(createSortByKeyAction(key, key)));
}
// Add all additional keys
for (String key : baseMetadataKeys) {
if (!newItems.containsKey(key))
newItems.put(key, ActionUtils.createMenuItem(createSortByKeyAction(key, key)));
}
}
menuSort.getItems().setAll(newItems.values());
menuSort.getItems().add(0, ActionUtils.createMenuItem(createSortByKeyAction("None", null)));
menuSort.getItems().add(1, new SeparatorMenuItem());
menuSort.setVisible(true);
if (menu.getItems().isEmpty())
e.consume();
});
SeparatorMenuItem separator = new SeparatorMenuItem();
separator.visibleProperty().bind(menuSort.visibleProperty());
menu.getItems().addAll(miOpenImage, miRemoveImage, miDuplicateImage, new SeparatorMenuItem(), miSetImageName, miAddMetadata, miEditDescription, miMaskImages, miRefreshThumbnail, separator, menuSort);
separator = new SeparatorMenuItem();
separator.visibleProperty().bind(menuOpenDirectories.visibleProperty());
if (Desktop.isDesktopSupported()) {
menu.getItems().addAll(separator, menuOpenDirectories);
}
return menu;
}
use of qupath.lib.projects.Project in project qupath by qupath.
the class ObjectClassifierLoadCommand method run.
@Override
public void run() {
project = qupath.getProject();
var listClassifiers = new ListView<String>();
externalObjectClassifiers = new HashMap<>();
listClassifiers.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
var labelPlaceholder = new Label("Object classifiers in the\n" + "current project will appear here");
labelPlaceholder.setAlignment(Pos.CENTER);
labelPlaceholder.setTextAlignment(TextAlignment.CENTER);
listClassifiers.setPlaceholder(labelPlaceholder);
refreshNames(listClassifiers.getItems());
// Provide an option to remove a classifier
var popup = new ContextMenu();
var miAdd = new MenuItem("Add classifier");
miAdd.setOnAction(e -> {
List<File> files = Dialogs.promptForMultipleFiles(title, null, "QuPath classifier file", "json");
if (files == null || files.isEmpty())
return;
try {
addClassifierFiles(files);
List<String> updatedNames = new ArrayList<>();
updatedNames.addAll(project.getPixelClassifiers().getNames());
updatedNames.addAll(externalObjectClassifiers.keySet());
} catch (IOException ex) {
Dialogs.showErrorMessage(title, ex);
}
});
var miRemove = new MenuItem("Delete selected");
popup.getItems().setAll(miAdd, miRemove);
miRemove.disableProperty().bind(listClassifiers.getSelectionModel().selectedItemProperty().isNull());
listClassifiers.setContextMenu(popup);
miRemove.setOnAction(e -> {
var selectedItems = new ArrayList<>(listClassifiers.getSelectionModel().getSelectedItems());
if (selectedItems.isEmpty() || project == null)
return;
try {
String message = selectedItems.size() == 1 ? "'" + selectedItems.get(0) + "'" : selectedItems.size() + " classifiers";
if (!Dialogs.showConfirmDialog(title, "Are you sure you want to delete " + message + "?"))
return;
for (var selected : selectedItems) {
if (!project.getObjectClassifiers().getNames().contains(selected)) {
Dialogs.showErrorMessage(title, "Unable to delete " + selected + " - not found in the current project");
return;
}
project.getObjectClassifiers().remove(selected);
listClassifiers.getItems().remove(selected);
}
} catch (Exception ex) {
Dialogs.showErrorMessage("Error deleting classifier", ex);
}
});
// listClassifiers.setOnMouseClicked(e -> {
// if (e.getClickCount() == 2) {
// List<File> files = Dialogs.promptForMultipleFiles(title, null, "QuPath classifier file", "json");
// if (files == null || files.isEmpty())
// return;
//
// try {
// addClassifierFiles(files);
// List<String> updatedNames = new ArrayList<>();
// updatedNames.addAll(project.getPixelClassifiers().getNames());
// updatedNames.addAll(externalObjectClassifiers.keySet());
// } catch (IOException ex) {
// Dialogs.showErrorMessage(title, ex);
// }
// }
// });
// Support drag & drop for classifiers
listClassifiers.setOnDragOver(e -> {
e.acceptTransferModes(TransferMode.COPY);
e.consume();
});
listClassifiers.setOnDragDropped(e -> {
Dragboard dragboard = e.getDragboard();
if (dragboard.hasFiles()) {
logger.trace("File(s) dragged onto classifier listView");
try {
var files = dragboard.getFiles().stream().filter(f -> f.isFile() && !f.isHidden()).collect(Collectors.toList());
addClassifierFiles(files);
} catch (Exception ex) {
String plural = dragboard.getFiles().size() == 1 ? "" : "s";
Dialogs.showErrorMessage("Error adding classifier" + plural, ex.getLocalizedMessage());
}
}
refreshNames(listClassifiers.getItems());
e.consume();
});
var label = new Label("Choose classifier");
label.setLabelFor(listClassifiers);
// var enableButtons = qupath.viewerProperty().isNotNull().and(selectedClassifier.isNotNull());
var btnApplyClassifier = new Button("Apply classifier");
btnApplyClassifier.textProperty().bind(Bindings.createStringBinding(() -> {
if (listClassifiers.getSelectionModel().getSelectedItems().size() > 1)
return "Apply classifiers sequentially";
return "Apply classifier";
}, listClassifiers.getSelectionModel().getSelectedItems()));
btnApplyClassifier.disableProperty().bind(listClassifiers.getSelectionModel().selectedItemProperty().isNull());
btnApplyClassifier.setOnAction(e -> {
var imageData = qupath.getImageData();
if (imageData == null) {
Dialogs.showErrorMessage(title, "No image open!");
return;
}
runClassifier(imageData, project, externalObjectClassifiers, listClassifiers.getSelectionModel().getSelectedItems(), true);
});
// var pane = new BorderPane();
// pane.setPadding(new Insets(10.0));
// pane.setTop(label);
// pane.setCenter(comboClassifiers);
// pane.setBottom(btnApplyClassifier);
var pane = new GridPane();
pane.setPadding(new Insets(10.0));
pane.setHgap(5);
pane.setVgap(10);
int row = 0;
PaneTools.setFillWidth(Boolean.TRUE, label, listClassifiers, btnApplyClassifier);
PaneTools.setVGrowPriority(Priority.ALWAYS, listClassifiers);
PaneTools.setHGrowPriority(Priority.ALWAYS, label, listClassifiers, btnApplyClassifier);
PaneTools.setMaxWidth(Double.MAX_VALUE, label, listClassifiers, btnApplyClassifier);
PaneTools.addGridRow(pane, row++, 0, "Choose object classification model to apply to the current image", label);
PaneTools.addGridRow(pane, row++, 0, "Drag and drop a file here to add a new classifier", listClassifiers);
PaneTools.addGridRow(pane, row++, 0, "Apply object classification to all open images", btnApplyClassifier);
PaneTools.setMaxWidth(Double.MAX_VALUE, listClassifiers, btnApplyClassifier);
var stage = new Stage();
stage.setTitle(title);
stage.setScene(new Scene(pane));
stage.initOwner(qupath.getStage());
// stage.sizeToScene();
stage.setWidth(300);
stage.setHeight(400);
stage.focusedProperty().addListener((v, o, n) -> {
if (n)
refreshNames(listClassifiers.getItems());
});
// stage.setResizable(false);
stage.show();
}
use of qupath.lib.projects.Project in project qupath by qupath.
the class QuPathGUI method createRecentProjectsMenu.
private Menu createRecentProjectsMenu() {
// Create a recent projects list in the File menu
ObservableList<URI> recentProjects = PathPrefs.getRecentProjectList();
Menu menuRecent = MenuTools.createMenu("Recent projects...");
EventHandler<Event> validationHandler = e -> {
menuRecent.getItems().clear();
for (URI uri : recentProjects) {
if (uri == null)
continue;
String name = Project.getNameFromURI(uri);
name = ".../" + name;
MenuItem item = new MenuItem(name);
item.setOnAction(e2 -> {
Project<BufferedImage> project;
try {
project = ProjectIO.loadProject(uri, BufferedImage.class);
setProject(project);
} catch (Exception e1) {
Dialogs.showErrorMessage("Project error", "Cannot find project " + uri);
logger.error("Error loading project", e1);
}
});
menuRecent.getItems().add(item);
}
};
// Ensure the menu is populated
menuRecent.parentMenuProperty().addListener((v, o, n) -> {
if (o != null && o.getOnMenuValidation() == validationHandler)
o.setOnMenuValidation(null);
if (n != null)
n.setOnMenuValidation(validationHandler);
});
return menuRecent;
}
Aggregations