use of qupath.lib.projects.Project in project qupath by qupath.
the class ProjectImportImagesCommand method promptToImportImages.
/**
* Prompt to import images to the current project.
*
* @param qupath QuPath instance, used to access the current project and stage
* @param builder if not null, this will be used to create the servers. If null, a combobox will be shown to choose an installed builder.
* @param defaultPaths URIs to use to prepopulate the list
* @return
*/
static List<ProjectImageEntry<BufferedImage>> promptToImportImages(QuPathGUI qupath, ImageServerBuilder<BufferedImage> builder, String... defaultPaths) {
var project = qupath.getProject();
if (project == null) {
Dialogs.showNoProjectError(commandName);
return Collections.emptyList();
}
ListView<String> listView = new ListView<>();
listView.setPrefWidth(480);
listView.setMinHeight(100);
listView.getItems().addAll(defaultPaths);
listView.setPlaceholder(new Label("Drag & drop image or project files for import, \nor choose from the options below"));
Button btnFile = new Button("Choose files");
btnFile.setOnAction(e -> loadFromFileChooser(listView.getItems()));
Button btnURL = new Button("Input URL");
btnURL.setOnAction(e -> loadFromSingleURL(listView.getItems()));
Button btnClipboard = new Button("From clipboard");
btnClipboard.setOnAction(e -> loadFromClipboard(listView.getItems()));
Button btnFileList = new Button("From path list");
btnFileList.setOnAction(e -> loadFromTextFile(listView.getItems()));
TitledPane paneList = new TitledPane("Image paths", listView);
paneList.setCollapsible(false);
BorderPane paneImages = new BorderPane();
class BuilderListCell extends ListCell<ImageServerBuilder<BufferedImage>> {
@Override
protected void updateItem(ImageServerBuilder<BufferedImage> item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
if (item == null)
setText("Default (let QuPath decide)");
else
setText(item.getName());
}
}
}
boolean requestBuilder = builder == null;
ComboBox<ImageServerBuilder<BufferedImage>> comboBuilder = new ComboBox<>();
Label labelBuilder = new Label("Image provider");
if (requestBuilder) {
comboBuilder.setCellFactory(p -> new BuilderListCell());
comboBuilder.setButtonCell(new BuilderListCell());
List<ImageServerBuilder<BufferedImage>> availableBuilders = new ArrayList<>(ImageServerProvider.getInstalledImageServerBuilders(BufferedImage.class));
if (!availableBuilders.contains(null))
availableBuilders.add(0, null);
comboBuilder.getItems().setAll(availableBuilders);
comboBuilder.getSelectionModel().selectFirst();
labelBuilder.setLabelFor(comboBuilder);
labelBuilder.setMinWidth(Label.USE_PREF_SIZE);
}
ComboBox<ImageType> comboType = new ComboBox<>();
comboType.getItems().setAll(ImageType.values());
Label labelType = new Label("Set image type");
labelType.setLabelFor(comboType);
labelType.setMinWidth(Label.USE_PREF_SIZE);
ComboBox<Rotation> comboRotate = new ComboBox<>();
comboRotate.getItems().setAll(Rotation.values());
Label labelRotate = new Label("Rotate image");
labelRotate.setLabelFor(comboRotate);
labelRotate.setMinWidth(Label.USE_PREF_SIZE);
TextField tfArgs = new TextField();
Label labelArgs = new Label("Optional args");
labelArgs.setLabelFor(tfArgs);
labelArgs.setMinWidth(Label.USE_PREF_SIZE);
CheckBox cbPyramidalize = new CheckBox("Auto-generate pyramids");
cbPyramidalize.setSelected(pyramidalizeProperty.get());
CheckBox cbImportObjects = new CheckBox("Import objects");
cbImportObjects.setSelected(importObjectsProperty.get());
PaneTools.setMaxWidth(Double.MAX_VALUE, comboBuilder, comboType, comboRotate, cbPyramidalize, cbImportObjects, tfArgs);
PaneTools.setFillWidth(Boolean.TRUE, comboBuilder, comboType, comboRotate, cbPyramidalize, cbImportObjects, tfArgs);
PaneTools.setHGrowPriority(Priority.ALWAYS, comboBuilder, comboType, comboRotate, cbPyramidalize, cbImportObjects, tfArgs);
GridPane paneType = new GridPane();
paneType.setPadding(new Insets(5));
paneType.setHgap(5);
paneType.setVgap(5);
int row = 0;
if (requestBuilder)
PaneTools.addGridRow(paneType, row++, 0, "Specify the library used to open images", labelBuilder, comboBuilder);
PaneTools.addGridRow(paneType, row++, 0, "Specify the default image type for all images being imported (required for analysis, can be changed later under the 'Image' tab)", labelType, comboType);
PaneTools.addGridRow(paneType, row++, 0, "Optionally rotate images on import", labelRotate, comboRotate);
PaneTools.addGridRow(paneType, row++, 0, "Optionally pass reader-specific arguments to the image provider.\nUsually this should just be left empty.", labelArgs, tfArgs);
PaneTools.addGridRow(paneType, row++, 0, "Dynamically create image pyramids for large, single-resolution images", cbPyramidalize, cbPyramidalize);
PaneTools.addGridRow(paneType, row++, 0, "Read and import objects (e.g. annotations) from the image file, if possible", cbImportObjects, cbImportObjects);
paneImages.setCenter(paneList);
paneImages.setBottom(paneType);
// TilePane paneButtons = new TilePane();
// paneButtons.getChildren().addAll(btnFile, btnURL, btnClipboard, btnFileList);
GridPane paneButtons = PaneTools.createColumnGridControls(btnFile, btnURL, btnClipboard, btnFileList);
paneButtons.setHgap(5);
paneButtons.setPadding(new Insets(5));
BorderPane pane = new BorderPane();
pane.setCenter(paneImages);
pane.setBottom(paneButtons);
// Support drag & drop for files
pane.setOnDragOver(e -> {
e.acceptTransferModes(TransferMode.COPY);
e.consume();
});
pane.setOnDragDropped(e -> {
Dragboard dragboard = e.getDragboard();
if (dragboard.hasFiles()) {
logger.trace("Files dragged onto project import dialog");
try {
var paths = dragboard.getFiles().stream().filter(f -> f.isFile() && !f.isHidden()).map(f -> f.getAbsolutePath()).collect(Collectors.toList());
paths.removeAll(listView.getItems());
if (!paths.isEmpty())
listView.getItems().addAll(paths);
} catch (Exception ex) {
Dialogs.showErrorMessage("Drag & Drop", ex);
}
}
e.setDropCompleted(true);
e.consume();
});
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setResizable(true);
dialog.initOwner(qupath.getStage());
dialog.setTitle("Import images to project");
ButtonType typeImport = new ButtonType("Import", ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().addAll(typeImport, ButtonType.CANCEL);
ScrollPane scroll = new ScrollPane(pane);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
dialog.getDialogPane().setContent(scroll);
Optional<ButtonType> result = dialog.showAndWait();
if (!result.isPresent() || result.get() != typeImport)
return Collections.emptyList();
// // Do the actual import
// List<String> pathSucceeded = new ArrayList<>();
// List<String> pathFailed = new ArrayList<>();
// for (String path : listView.getItems()) {
// if (qupath.getProject().addImage(path.trim()))
// pathSucceeded.add(path);
// else
// pathFailed.add(path);
// }
ImageType type = comboType.getValue();
Rotation rotation = comboRotate.getValue();
boolean pyramidalize = cbPyramidalize.isSelected();
boolean importObjects = cbImportObjects.isSelected();
pyramidalizeProperty.set(pyramidalize);
importObjectsProperty.set(importObjects);
ImageServerBuilder<BufferedImage> requestedBuilder = requestBuilder ? comboBuilder.getSelectionModel().getSelectedItem() : builder;
List<String> argsList = new ArrayList<>();
String argsString = tfArgs.getText();
// TODO: Use a smarter approach to splitting! Currently we support so few arguments that splitting on spaces should be ok... for now.
String[] argsSplit = argsString == null || argsString.isBlank() ? new String[0] : argsString.split(" ");
for (var a : argsSplit) {
argsList.add(a);
}
if (rotation != null && rotation != Rotation.ROTATE_NONE) {
argsList.add("--rotate");
argsList.add(rotation.toString());
}
if (!argsList.isEmpty())
logger.debug("Args: [{}]", argsList.stream().collect(Collectors.joining(", ")));
String[] args = argsList.toArray(String[]::new);
List<String> pathSucceeded = new ArrayList<>();
List<String> pathFailed = new ArrayList<>();
List<ProjectImageEntry<BufferedImage>> entries = new ArrayList<>();
Task<Collection<ProjectImageEntry<BufferedImage>>> worker = new Task<>() {
@Override
protected Collection<ProjectImageEntry<BufferedImage>> call() throws Exception {
AtomicLong counter = new AtomicLong(0L);
List<String> items = new ArrayList<>(listView.getItems());
updateMessage("Checking for compatible image readers...");
// Limit the size of the thread pool
// The previous use of a cached thread pool caused trouble when importing may large, non-pyramidal images
var pool = Executors.newFixedThreadPool(ThreadTools.getParallelism(), ThreadTools.createThreadFactory("project-import", true));
// var pool = Executors.newCachedThreadPool(ThreadTools.createThreadFactory("project-import", true));
List<Future<List<ServerBuilder<BufferedImage>>>> results = new ArrayList<>();
List<ProjectImageEntry<BufferedImage>> projectImages = new ArrayList<>();
for (var item : items) {
// Try to load items from a project if possible
if (item.toLowerCase().endsWith(ProjectIO.DEFAULT_PROJECT_EXTENSION)) {
try {
var tempProject = ProjectIO.loadProject(GeneralTools.toURI(item), BufferedImage.class);
projectImages.addAll(tempProject.getImageList());
} catch (Exception e) {
logger.error("Unable to add images from {} ({})", item, e.getLocalizedMessage());
}
continue;
}
results.add(pool.submit(() -> {
try {
var uri = GeneralTools.toURI(item);
UriImageSupport<BufferedImage> support;
if (requestedBuilder == null)
support = ImageServers.getImageSupport(uri, args);
else
support = ImageServers.getImageSupport(requestedBuilder, uri, args);
if (support != null)
return support.getBuilders();
} catch (Exception e) {
logger.error("Unable to add {}");
logger.error(e.getLocalizedMessage(), e);
}
return new ArrayList<ServerBuilder<BufferedImage>>();
}));
}
List<ProjectImageEntry<BufferedImage>> failures = Collections.synchronizedList(new ArrayList<>());
// If we have projects, try adding images from these first
if (!projectImages.isEmpty()) {
if (projectImages.size() == 1)
updateMessage("Importing 1 image from existing projects");
else
updateMessage("Importing " + projectImages.size() + " images from existing projects");
for (var temp : projectImages) {
try {
project.addDuplicate(temp, true);
} catch (Exception e) {
failures.add(temp);
}
}
}
// If we have 'standard' image paths, use these next
List<ServerBuilder<BufferedImage>> builders = new ArrayList<>();
for (var result : results) {
try {
builders.addAll(result.get());
} catch (ExecutionException e) {
logger.error("Execution exception importing image", e);
}
}
long max = builders.size();
if (!builders.isEmpty()) {
if (max == 1)
updateMessage("Adding 1 image to project");
else
updateMessage("Adding " + max + " images to project");
// Add everything in order first
List<ProjectImageEntry<BufferedImage>> entries = new ArrayList<>();
for (var builder : builders) {
// if (rotation != null && rotation != Rotation.ROTATE_NONE)
// builder = RotatedImageServer.getRotatedBuilder(builder, rotation);
// if (swapRedBlue)
// builder = RearrangeRGBImageServer.getSwapRedBlueBuilder(builder);
entries.add(project.addImage(builder));
}
// Initialize (the slow bit)
int n = builders.size();
for (var entry : entries) {
pool.submit(() -> {
try {
initializeEntry(entry, type, pyramidalize, importObjects);
} catch (Exception e) {
failures.add(entry);
logger.warn("Exception adding " + entry, e);
} finally {
long i = counter.incrementAndGet();
updateProgress(i, max);
String name = entry.getImageName();
if (name != null) {
updateMessage("Added " + i + "/" + n + " - " + name);
}
}
});
}
}
pool.shutdown();
try {
pool.awaitTermination(60, TimeUnit.MINUTES);
} catch (Exception e) {
logger.error("Exception waiting for project import to complete: " + e.getLocalizedMessage(), e);
}
if (!failures.isEmpty()) {
String message;
if (failures.size() == 1)
message = "Failed to load one image.";
else
message = "Failed to load " + failures.size() + " images.";
if (requestedBuilder != null)
message += "\nThe image type might not be supported by '" + requestedBuilder.getName() + "'";
Dialogs.showErrorMessage("Import images", message);
var toRemove = failures.stream().filter(p -> project.getImageList().contains(p)).collect(Collectors.toList());
project.removeAllImages(toRemove, true);
}
// Now save changes
project.syncChanges();
// builders.parallelStream().forEach(builder -> {
// // builders.parallelStream().forEach(builder -> {
// try (var server = builder.build()) {
// var entry = addSingleImageToProject(project, server);
// updateMessage("Added " + entry.getImageName());
// } catch (Exception e) {
// logger.warn("Exception adding " + builder, e);
// } finally {
// updateProgress(counter.incrementAndGet(), max);
// }
// });
updateProgress(max, max);
return entries;
}
};
ProgressDialog progress = new ProgressDialog(worker);
progress.setTitle("Project import");
qupath.submitShortTask(worker);
progress.showAndWait();
try {
project.syncChanges();
} catch (IOException e1) {
Dialogs.showErrorMessage("Sync project", e1);
}
qupath.refreshProject();
StringBuilder sb = new StringBuilder();
if (!pathSucceeded.isEmpty()) {
sb.append("Successfully imported " + pathSucceeded.size() + " paths:\n");
for (String path : pathSucceeded) sb.append("\t" + path + "\n");
sb.append("\n");
qupath.refreshProject();
ProjectBrowser.syncProject(qupath.getProject());
}
if (!pathFailed.isEmpty()) {
sb.append("Unable to import " + pathFailed.size() + " paths:\n");
for (String path : pathFailed) sb.append("\t" + path + "\n");
sb.append("\n");
TextArea textArea = new TextArea();
textArea.setText(sb.toString());
if (pathSucceeded.isEmpty())
Dialogs.showErrorMessage(commandName, textArea);
else
Dialogs.showMessageDialog(commandName, textArea);
}
// TODO: Add failed and successful paths to pathFailed/pathSucceeded, so the line below prints something
if (sb.length() > 0)
logger.info(sb.toString());
return entries;
}
use of qupath.lib.projects.Project in project qupath by qupath.
the class ProjectMetadataEditorCommand method showProjectMetadataEditor.
public static void showProjectMetadataEditor(Project<?> project) {
if (project == null) {
logger.warn("No project available!");
return;
}
Set<String> metadataNameSet = new TreeSet<>();
List<ImageEntryWrapper> entries = new ArrayList<>();
for (ProjectImageEntry<?> entry : project.getImageList()) {
entries.add(new ImageEntryWrapper(entry));
metadataNameSet.addAll(entry.getMetadataKeys());
}
TableView<ImageEntryWrapper> table = new TableView<>();
TableColumn<ImageEntryWrapper, String> colName = new TableColumn<>(IMAGE_NAME);
colName.setCellValueFactory(v -> v.getValue().getNameBinding());
table.getColumns().add(colName);
table.setEditable(true);
for (String metadataName : metadataNameSet) {
TableColumn<ImageEntryWrapper, String> col = new TableColumn<>(metadataName);
col.setCellFactory(TextFieldTableCell.<ImageEntryWrapper>forTableColumn());
col.setOnEditCommit(e -> {
ImageEntryWrapper entry = e.getRowValue();
String n = e.getNewValue();
if (n == null || n.isEmpty())
entry.removeMetadataValue(e.getTableColumn().getText());
else
entry.putMetadataValue(e.getTableColumn().getText(), n);
});
col.setCellValueFactory(v -> v.getValue().getProperty(metadataName));
col.setEditable(true);
table.getColumns().add(col);
}
table.getItems().setAll(entries);
table.getSelectionModel().setCellSelectionEnabled(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
// Handle deleting entries
table.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
if (e.getCode() == KeyCode.BACK_SPACE || e.getCode() == KeyCode.DELETE) {
var positions = table.getSelectionModel().getSelectedCells().stream().filter(p -> !IMAGE_NAME.equals(p.getTableColumn().getText())).collect(Collectors.toList());
if (positions.isEmpty())
return;
if (positions.size() == 1) {
setTextForSelectedCells(positions, null);
} else {
if (Dialogs.showConfirmDialog("Project metadata", "Clear metadata for " + positions.size() + " selected cells?")) {
setTextForSelectedCells(positions, null);
}
}
table.refresh();
}
});
BooleanBinding selectedCells = Bindings.createBooleanBinding(() -> table.getSelectionModel().selectedItemProperty() == null, table.getSelectionModel().selectedItemProperty());
MenuBar menubar = new MenuBar();
Menu menuEdit = new Menu("Edit");
MenuItem miCopy = new MenuItem("Copy selected cells");
miCopy.disableProperty().bind(selectedCells);
miCopy.setAccelerator(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN));
miCopy.setOnAction(e -> copySelectedCellsToClipboard(table, true));
MenuItem miCopyFull = new MenuItem("Copy full table");
miCopyFull.setOnAction(e -> copyEntireTableToClipboard(table));
MenuItem miPaste = new MenuItem("Paste");
miPaste.setAccelerator(new KeyCodeCombination(KeyCode.V, KeyCombination.SHORTCUT_DOWN));
miPaste.disableProperty().bind(selectedCells);
miPaste.setOnAction(e -> pasteClipboardContentsToTable(table));
MenuItem miSet = new MenuItem("Set cell contents");
miSet.disableProperty().bind(selectedCells);
miSet.setOnAction(e -> {
String input = Dialogs.showInputDialog("Set metadata cells", "Metadata text", "");
if (input == null)
return;
setTextForSelectedCells(table.getSelectionModel().getSelectedCells(), input);
table.refresh();
});
menuEdit.getItems().addAll(miCopy, miCopyFull, miPaste, miSet);
menubar.getMenus().add(menuEdit);
BorderPane pane = new BorderPane();
pane.setTop(menubar);
pane.setCenter(table);
// menubar.setUseSystemMenuBar(true);
menubar.useSystemMenuBarProperty().bindBidirectional(PathPrefs.useSystemMenubarProperty());
Dialog<ButtonType> dialog = new Dialog<>();
var qupath = QuPathGUI.getInstance();
if (qupath != null)
dialog.initOwner(qupath.getStage());
dialog.setTitle("Project metadata");
dialog.setHeaderText(null);
dialog.setResizable(true);
dialog.getDialogPane().setContent(pane);
dialog.getDialogPane().getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
dialog.getDialogPane().setPrefWidth(500);
Optional<ButtonType> result = dialog.showAndWait();
if (result.isPresent() && result.get().getButtonData() == ButtonData.OK_DONE) {
// Make the changes
for (ImageEntryWrapper wrapper : entries) {
wrapper.commitChanges();
}
// Write the project
ProjectBrowser.syncProject(project);
}
// Stage stage = new Stage();
// stage.initOwner(qupath.getStage());
// stage.setTitle("Project metadata");
// stage.setScene(new Scene(pane, 500, 400));
// stage.showAndWait();
}
use of qupath.lib.projects.Project in project qupath by qupath.
the class TMASummaryViewer method initialize.
private void initialize() {
model = new TMATableModel();
groupByIDProperty.addListener((v, o, n) -> refreshTableData());
MenuBar menuBar = new MenuBar();
Menu menuFile = new Menu("File");
MenuItem miOpen = new MenuItem("Open...");
miOpen.setAccelerator(new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN));
miOpen.setOnAction(e -> {
File file = Dialogs.getChooser(stage).promptForFile(null, null, "TMA data files", new String[] { "qptma" });
if (file == null)
return;
setInputFile(file);
});
MenuItem miSave = new MenuItem("Save As...");
miSave.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
miSave.setOnAction(e -> SummaryMeasurementTableCommand.saveTableModel(model, null, Collections.emptyList()));
MenuItem miImportFromImage = new MenuItem("Import from current image...");
miImportFromImage.setAccelerator(new KeyCodeCombination(KeyCode.I, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
miImportFromImage.setOnAction(e -> setTMAEntriesFromOpenImage());
MenuItem miImportFromProject = new MenuItem("Import from current project... (experimental)");
miImportFromProject.setAccelerator(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN));
miImportFromProject.setOnAction(e -> setTMAEntriesFromOpenProject());
MenuItem miImportClipboard = new MenuItem("Import from clipboard...");
miImportClipboard.setOnAction(e -> {
String text = Clipboard.getSystemClipboard().getString();
if (text == null) {
Dialogs.showErrorMessage("Import scores", "Clipboard is empty!");
return;
}
int n = importScores(text);
if (n > 0) {
setTMAEntries(new ArrayList<>(entriesBase));
}
Dialogs.showMessageDialog("Import scores", "Number of scores imported: " + n);
});
Menu menuEdit = new Menu("Edit");
MenuItem miCopy = new MenuItem("Copy table to clipboard");
miCopy.setOnAction(e -> {
SummaryMeasurementTableCommand.copyTableContentsToClipboard(model, Collections.emptyList());
});
combinedPredicate.addListener((v, o, n) -> {
// We want any other changes triggered by this to have happened,
// so that the data has already been updated
Platform.runLater(() -> handleTableContentChange());
});
// Reset the scores for missing cores - this ensures they will be NaN and not influence subsequent results
MenuItem miResetMissingScores = new MenuItem("Reset scores for missing cores");
miResetMissingScores.setOnAction(e -> {
int changes = 0;
for (TMAEntry entry : entriesBase) {
if (!entry.isMissing())
continue;
boolean changed = false;
for (String m : entry.getMeasurementNames().toArray(new String[0])) {
if (!TMASummaryEntry.isSurvivalColumn(m) && !Double.isNaN(entry.getMeasurementAsDouble(m))) {
entry.putMeasurement(m, null);
changed = true;
}
}
if (changed)
changes++;
}
if (changes == 0) {
logger.info("No changes made when resetting scores for missing cores!");
return;
}
logger.info("{} change(s) made when resetting scores for missing cores!", changes);
table.refresh();
updateSurvivalCurves();
if (scatterPane != null)
scatterPane.updateChart();
if (histogramDisplay != null)
histogramDisplay.refreshHistogram();
});
menuEdit.getItems().add(miResetMissingScores);
MenuTools.addMenuItems(menuFile, miOpen, miSave, null, miImportClipboard, null, miImportFromImage, miImportFromProject);
menuBar.getMenus().add(menuFile);
menuEdit.getItems().add(miCopy);
menuBar.getMenus().add(menuEdit);
menuFile.setOnShowing(e -> {
boolean imageDataAvailable = QuPathGUI.getInstance() != null && QuPathGUI.getInstance().getImageData() != null && QuPathGUI.getInstance().getImageData().getHierarchy().getTMAGrid() != null;
miImportFromImage.setDisable(!imageDataAvailable);
boolean projectAvailable = QuPathGUI.getInstance() != null && QuPathGUI.getInstance().getProject() != null && !QuPathGUI.getInstance().getProject().getImageList().isEmpty();
miImportFromProject.setDisable(!projectAvailable);
});
// Double-clicking previously used for comments... but conflicts with tree table expansion
// table.setOnMouseClicked(e -> {
// if (!e.isPopupTrigger() && e.getClickCount() > 1)
// promptForComment();
// });
table.setPlaceholder(new Text("Drag TMA data folder onto window, or choose File -> Open"));
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
BorderPane pane = new BorderPane();
pane.setTop(menuBar);
menuBar.useSystemMenuBarProperty().bindBidirectional(PathPrefs.useSystemMenubarProperty());
// menuBar.setUseSystemMenuBar(true);
// Create options
ToolBar toolbar = new ToolBar();
Label labelMeasurementMethod = new Label("Combination method");
labelMeasurementMethod.setLabelFor(comboMeasurementMethod);
labelMeasurementMethod.setTooltip(new Tooltip("Method whereby measurements for multiple cores with the same " + TMACoreObject.KEY_UNIQUE_ID + " will be combined"));
CheckBox cbHidePane = new CheckBox("Hide pane");
cbHidePane.setSelected(hidePaneProperty.get());
cbHidePane.selectedProperty().bindBidirectional(hidePaneProperty);
CheckBox cbGroupByID = new CheckBox("Group by ID");
entriesBase.addListener((Change<? extends TMAEntry> event) -> {
if (!event.getList().stream().anyMatch(e -> e.getMetadataValue(TMACoreObject.KEY_UNIQUE_ID) != null)) {
cbGroupByID.setSelected(false);
cbGroupByID.setDisable(true);
} else {
cbGroupByID.setDisable(false);
}
});
cbGroupByID.setSelected(groupByIDProperty.get());
cbGroupByID.selectedProperty().bindBidirectional(groupByIDProperty);
CheckBox cbUseSelected = new CheckBox("Use selection only");
cbUseSelected.selectedProperty().bindBidirectional(useSelectedProperty);
CheckBox cbSkipMissing = new CheckBox("Hide missing cores");
cbSkipMissing.selectedProperty().bindBidirectional(skipMissingCoresProperty);
skipMissingCoresProperty.addListener((v, o, n) -> {
table.refresh();
updateSurvivalCurves();
if (histogramDisplay != null)
histogramDisplay.refreshHistogram();
updateSurvivalCurves();
if (scatterPane != null)
scatterPane.updateChart();
});
toolbar.getItems().addAll(labelMeasurementMethod, comboMeasurementMethod, new Separator(Orientation.VERTICAL), cbHidePane, new Separator(Orientation.VERTICAL), cbGroupByID, new Separator(Orientation.VERTICAL), cbUseSelected, new Separator(Orientation.VERTICAL), cbSkipMissing);
comboMeasurementMethod.getItems().addAll(TMAEntries.MeasurementCombinationMethod.values());
comboMeasurementMethod.getSelectionModel().select(TMAEntries.MeasurementCombinationMethod.MEDIAN);
selectedMeasurementCombinationProperty.addListener((v, o, n) -> table.refresh());
ContextMenu popup = new ContextMenu();
MenuItem miSetMissing = new MenuItem("Set missing");
miSetMissing.setOnAction(e -> setSelectedMissingStatus(true));
MenuItem miSetAvailable = new MenuItem("Set available");
miSetAvailable.setOnAction(e -> setSelectedMissingStatus(false));
MenuItem miExpand = new MenuItem("Expand all");
miExpand.setOnAction(e -> {
if (table.getRoot() == null)
return;
for (TreeItem<?> item : table.getRoot().getChildren()) {
item.setExpanded(true);
}
});
MenuItem miCollapse = new MenuItem("Collapse all");
miCollapse.setOnAction(e -> {
if (table.getRoot() == null)
return;
for (TreeItem<?> item : table.getRoot().getChildren()) {
item.setExpanded(false);
}
});
popup.getItems().addAll(miSetMissing, miSetAvailable, new SeparatorMenuItem(), miExpand, miCollapse);
table.setContextMenu(popup);
table.setRowFactory(e -> {
TreeTableRow<TMAEntry> row = new TreeTableRow<>();
// // Make rows invisible if they don't pass the predicate
// row.visibleProperty().bind(Bindings.createBooleanBinding(() -> {
// TMAEntry entry = row.getItem();
// if (entry == null || (entry.isMissing() && skipMissingCoresProperty.get()))
// return false;
// return entries.getPredicate() == null || entries.getPredicate().test(entry);
// },
// skipMissingCoresProperty,
// entries.predicateProperty()));
// Style rows according to what they contain
row.styleProperty().bind(Bindings.createStringBinding(() -> {
if (row.isSelected())
return "";
TMAEntry entry = row.getItem();
if (entry == null || entry instanceof TMASummaryEntry)
return "";
else if (entry.isMissing())
return "-fx-background-color:rgb(225,225,232)";
else
return "-fx-background-color:rgb(240,240,245)";
}, row.itemProperty(), row.selectedProperty()));
// });
return row;
});
BorderPane paneTable = new BorderPane();
paneTable.setTop(toolbar);
paneTable.setCenter(table);
MasterDetailPane mdTablePane = new MasterDetailPane(Side.RIGHT, paneTable, createSidePane(), true);
mdTablePane.showDetailNodeProperty().bind(Bindings.createBooleanBinding(() -> !hidePaneProperty.get() && !entriesBase.isEmpty(), hidePaneProperty, entriesBase));
mdTablePane.setDividerPosition(2.0 / 3.0);
pane.setCenter(mdTablePane);
model.getItems().addListener(new ListChangeListener<TMAEntry>() {
@Override
public void onChanged(ListChangeListener.Change<? extends TMAEntry> c) {
if (histogramDisplay != null)
histogramDisplay.refreshHistogram();
updateSurvivalCurves();
if (scatterPane != null)
scatterPane.updateChart();
}
});
Label labelPredicate = new Label();
labelPredicate.setPadding(new Insets(5, 5, 5, 5));
labelPredicate.setAlignment(Pos.CENTER);
// labelPredicate.setStyle("-fx-background-color: rgba(20, 120, 20, 0.15);");
labelPredicate.setStyle("-fx-background-color: rgba(120, 20, 20, 0.15);");
labelPredicate.textProperty().addListener((v, o, n) -> {
if (n.trim().length() > 0)
pane.setBottom(labelPredicate);
else
pane.setBottom(null);
});
labelPredicate.setMaxWidth(Double.MAX_VALUE);
labelPredicate.setMaxHeight(labelPredicate.getPrefHeight());
labelPredicate.setTextAlignment(TextAlignment.CENTER);
predicateMeasurements.addListener((v, o, n) -> {
if (n == null)
labelPredicate.setText("");
else if (n instanceof TablePredicate) {
TablePredicate tp = (TablePredicate) n;
if (tp.getOriginalCommand().trim().isEmpty())
labelPredicate.setText("");
else
labelPredicate.setText("Predicate: " + tp.getOriginalCommand());
} else
labelPredicate.setText("Predicate: " + n.toString());
});
// predicate.set(new TablePredicate("\"Tumor\" > 100"));
scene = new Scene(pane);
scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
KeyCode code = e.getCode();
if ((code == KeyCode.SPACE || code == KeyCode.ENTER) && entrySelected != null) {
promptForComment();
return;
}
});
}
use of qupath.lib.projects.Project in project qupath by qupath.
the class LoadResourceCommand method addExternalJson.
private void addExternalJson(List<File> files, Manager<S> manager, Map<String, S> externalResources) {
String plural = files.size() > 1 ? "s" : "";
Set<String> currentNames = new HashSet<>();
boolean addToManager = false;
if (manager != null) {
try {
currentNames.addAll(manager.getNames());
} catch (IOException e) {
logger.error(e.getLocalizedMessage(), e);
}
var response = Dialogs.showYesNoCancelDialog("Copy classifier file" + plural, "Copy classifier" + plural + " to the current project?");
if (response == DialogButton.CANCEL)
return;
addToManager = response == DialogButton.YES;
}
List<File> fails = new ArrayList<>();
for (var file : files) {
try {
if (!GeneralTools.getExtension(file).get().equals(".json"))
throw new IOException(String.format("We need JSON files (.json), not %s", GeneralTools.getExtension(file).orElse("missing any file extension")));
// TODO: Check if classifier is valid before adding it
var classifier = resourceType.readFromPath(file.toPath());
// Get a unique name (adding number if needed)
String name = GeneralTools.getNameWithoutExtension(file);
name = GeneralTools.generateDistinctName(name, currentNames);
if (addToManager)
manager.put(name, classifier);
else
externalResources.put(name, classifier);
currentNames.add(name);
logger.debug("Added {}", name);
} catch (IOException ex) {
Dialogs.showErrorNotification(String.format("Could not add %s", file.getName()), ex.getLocalizedMessage());
fails.add(file);
}
}
if (!fails.isEmpty()) {
String failedClassifiers = fails.stream().map(e -> "- " + e.getName()).collect(Collectors.joining(System.lineSeparator()));
String pluralize = fails.size() == 1 ? "" : "s";
Dialogs.showErrorMessage("Error adding classifier" + pluralize, String.format("Could not add the following:%s%s", System.lineSeparator(), failedClassifiers));
}
int nSuccess = files.size() - fails.size();
String plural2 = nSuccess > 1 ? "s" : "";
if (nSuccess > 0)
Dialogs.showInfoNotification("Classifier" + plural2 + " added successfully", String.format("%d classifier" + plural2 + " added", nSuccess));
}
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();
}
Aggregations