Search in sources :

Example 1 with Project

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;
}
Also used : Button(javafx.scene.control.Button) ImageServer(qupath.lib.images.servers.ImageServer) ServerTools(qupath.lib.images.servers.ServerTools) PathObjectReader(qupath.lib.objects.PathObjectReader) ListCell(javafx.scene.control.ListCell) LoggerFactory(org.slf4j.LoggerFactory) Scanner(java.util.Scanner) RenderingHints(java.awt.RenderingHints) ProjectBrowser(qupath.lib.gui.panes.ProjectBrowser) Future(java.util.concurrent.Future) Task(javafx.concurrent.Task) ScrollPane(javafx.scene.control.ScrollPane) ComboBox(javafx.scene.control.ComboBox) URI(java.net.URI) ImageServers(qupath.lib.images.servers.ImageServers) ImageType(qupath.lib.images.ImageData.ImageType) QuPathGUI(qupath.lib.gui.QuPathGUI) TextField(javafx.scene.control.TextField) BufferedImage(java.awt.image.BufferedImage) ImageServerProvider(qupath.lib.images.servers.ImageServerProvider) Collection(java.util.Collection) ChannelDisplayInfo(qupath.lib.display.ChannelDisplayInfo) ImageServerBuilder(qupath.lib.images.servers.ImageServerBuilder) Collectors(java.util.stream.Collectors) FileNotFoundException(java.io.FileNotFoundException) Executors(java.util.concurrent.Executors) PathObject(qupath.lib.objects.PathObject) Priority(javafx.scene.layout.Priority) List(java.util.List) BooleanProperty(javafx.beans.property.BooleanProperty) Project(qupath.lib.projects.Project) ProjectIO(qupath.lib.projects.ProjectIO) Clipboard(javafx.scene.input.Clipboard) Optional(java.util.Optional) ThreadTools(qupath.lib.common.ThreadTools) BorderPane(javafx.scene.layout.BorderPane) ButtonData(javafx.scene.control.ButtonBar.ButtonData) ListView(javafx.scene.control.ListView) TextArea(javafx.scene.control.TextArea) ButtonType(javafx.scene.control.ButtonType) ProgressDialog(org.controlsfx.dialog.ProgressDialog) ImageDisplay(qupath.lib.display.ImageDisplay) UriImageSupport(qupath.lib.images.servers.ImageServerBuilder.UriImageSupport) TransferMode(javafx.scene.input.TransferMode) ArrayList(java.util.ArrayList) Dialogs(qupath.lib.gui.dialogs.Dialogs) Dragboard(javafx.scene.input.Dragboard) Insets(javafx.geometry.Insets) Graphics2D(java.awt.Graphics2D) GridPane(javafx.scene.layout.GridPane) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) Dialog(javafx.scene.control.Dialog) Label(javafx.scene.control.Label) TitledPane(javafx.scene.control.TitledPane) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) GeneralTools(qupath.lib.common.GeneralTools) CheckBox(javafx.scene.control.CheckBox) IOException(java.io.IOException) ServerBuilder(qupath.lib.images.servers.ImageServerBuilder.ServerBuilder) File(java.io.File) ExecutionException(java.util.concurrent.ExecutionException) TimeUnit(java.util.concurrent.TimeUnit) AtomicLong(java.util.concurrent.atomic.AtomicLong) WrappedBufferedImageServer(qupath.lib.images.servers.WrappedBufferedImageServer) Collections(java.util.Collections) PathPrefs(qupath.lib.gui.prefs.PathPrefs) Rotation(qupath.lib.images.servers.RotatedImageServer.Rotation) PaneTools(qupath.lib.gui.tools.PaneTools) Task(javafx.concurrent.Task) TextArea(javafx.scene.control.TextArea) Label(javafx.scene.control.Label) ArrayList(java.util.ArrayList) ProgressDialog(org.controlsfx.dialog.ProgressDialog) ImageType(qupath.lib.images.ImageData.ImageType) ListView(javafx.scene.control.ListView) Button(javafx.scene.control.Button) ProgressDialog(org.controlsfx.dialog.ProgressDialog) Dialog(javafx.scene.control.Dialog) TextField(javafx.scene.control.TextField) ButtonType(javafx.scene.control.ButtonType) Dragboard(javafx.scene.input.Dragboard) ImageServerBuilder(qupath.lib.images.servers.ImageServerBuilder) UriImageSupport(qupath.lib.images.servers.ImageServerBuilder.UriImageSupport) GridPane(javafx.scene.layout.GridPane) ComboBox(javafx.scene.control.ComboBox) CheckBox(javafx.scene.control.CheckBox) ScrollPane(javafx.scene.control.ScrollPane) Collection(java.util.Collection) ImageServerBuilder(qupath.lib.images.servers.ImageServerBuilder) ServerBuilder(qupath.lib.images.servers.ImageServerBuilder.ServerBuilder) BorderPane(javafx.scene.layout.BorderPane) Insets(javafx.geometry.Insets) ListCell(javafx.scene.control.ListCell) BufferedImage(java.awt.image.BufferedImage) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) ExecutionException(java.util.concurrent.ExecutionException) TitledPane(javafx.scene.control.TitledPane) IOException(java.io.IOException) Rotation(qupath.lib.images.servers.RotatedImageServer.Rotation) FileNotFoundException(java.io.FileNotFoundException) IOException(java.io.IOException) ExecutionException(java.util.concurrent.ExecutionException) AtomicLong(java.util.concurrent.atomic.AtomicLong) Future(java.util.concurrent.Future)

Example 2 with Project

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();
}
Also used : ButtonData(javafx.scene.control.ButtonBar.ButtonData) BooleanBinding(javafx.beans.binding.BooleanBinding) ButtonType(javafx.scene.control.ButtonType) LoggerFactory(org.slf4j.LoggerFactory) TextFieldTableCell(javafx.scene.control.cell.TextFieldTableCell) Bindings(javafx.beans.binding.Bindings) TreeSet(java.util.TreeSet) ArrayList(java.util.ArrayList) TableColumn(javafx.scene.control.TableColumn) KeyCombination(javafx.scene.input.KeyCombination) ProjectBrowser(qupath.lib.gui.panes.ProjectBrowser) Dialogs(qupath.lib.gui.dialogs.Dialogs) Map(java.util.Map) TableView(javafx.scene.control.TableView) QuPathGUI(qupath.lib.gui.QuPathGUI) KeyCode(javafx.scene.input.KeyCode) Logger(org.slf4j.Logger) Dialog(javafx.scene.control.Dialog) MenuBar(javafx.scene.control.MenuBar) MenuItem(javafx.scene.control.MenuItem) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) Set(java.util.Set) TablePosition(javafx.scene.control.TablePosition) ObservableStringValue(javafx.beans.value.ObservableStringValue) KeyEvent(javafx.scene.input.KeyEvent) Collectors(java.util.stream.Collectors) Menu(javafx.scene.control.Menu) KeyCodeCombination(javafx.scene.input.KeyCodeCombination) List(java.util.List) Project(qupath.lib.projects.Project) SelectionMode(javafx.scene.control.SelectionMode) TreeMap(java.util.TreeMap) Clipboard(javafx.scene.input.Clipboard) Entry(java.util.Map.Entry) Optional(java.util.Optional) ClipboardContent(javafx.scene.input.ClipboardContent) BorderPane(javafx.scene.layout.BorderPane) PathPrefs(qupath.lib.gui.prefs.PathPrefs) BorderPane(javafx.scene.layout.BorderPane) ArrayList(java.util.ArrayList) MenuBar(javafx.scene.control.MenuBar) MenuItem(javafx.scene.control.MenuItem) KeyCodeCombination(javafx.scene.input.KeyCodeCombination) TableColumn(javafx.scene.control.TableColumn) BooleanBinding(javafx.beans.binding.BooleanBinding) TreeSet(java.util.TreeSet) Dialog(javafx.scene.control.Dialog) Menu(javafx.scene.control.Menu) ButtonType(javafx.scene.control.ButtonType) TableView(javafx.scene.control.TableView)

Example 3 with Project

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;
        }
    });
}
Also used : Arrays(java.util.Arrays) Change(javafx.collections.ListChangeListener.Change) ServerTools(qupath.lib.images.servers.ServerTools) ActionUtils(org.controlsfx.control.action.ActionUtils) HistogramDisplay(qupath.lib.gui.charts.HistogramDisplay) PathTableData(qupath.lib.gui.measure.PathTableData) MasterDetailPane(org.controlsfx.control.MasterDetailPane) ScrollPane(javafx.scene.control.ScrollPane) TabPane(javafx.scene.control.TabPane) ListChangeListener(javafx.collections.ListChangeListener) SpearmansCorrelation(org.apache.commons.math3.stat.correlation.SpearmansCorrelation) Map(java.util.Map) SimpleIntegerProperty(javafx.beans.property.SimpleIntegerProperty) ScriptException(javax.script.ScriptException) Set(java.util.Set) KeyEvent(javafx.scene.input.KeyEvent) Executors(java.util.concurrent.Executors) Platform(javafx.application.Platform) Separator(javafx.scene.control.Separator) BooleanProperty(javafx.beans.property.BooleanProperty) Project(qupath.lib.projects.Project) Clipboard(javafx.scene.input.Clipboard) ScrollBarPolicy(javafx.scene.control.ScrollPane.ScrollBarPolicy) CheckBoxTableCell(javafx.scene.control.cell.CheckBoxTableCell) SimpleDoubleProperty(javafx.beans.property.SimpleDoubleProperty) SummaryMeasurementTableCommand(qupath.lib.gui.commands.SummaryMeasurementTableCommand) ObservableList(javafx.collections.ObservableList) BorderPane(javafx.scene.layout.BorderPane) TMAObjectEntry(qupath.lib.gui.tma.TMAEntries.TMAObjectEntry) WeakChangeListener(javafx.beans.value.WeakChangeListener) TreeItem(javafx.scene.control.TreeItem) FXCollections(javafx.collections.FXCollections) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) Bindings(javafx.beans.binding.Bindings) IntegerProperty(javafx.beans.property.IntegerProperty) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) LinkedHashMap(java.util.LinkedHashMap) TabClosingPolicy(javafx.scene.control.TabPane.TabClosingPolicy) TextFields(org.controlsfx.control.textfield.TextFields) TreeTableView(javafx.scene.control.TreeTableView) TextAlignment(javafx.scene.text.TextAlignment) LinkedHashSet(java.util.LinkedHashSet) GridPane(javafx.scene.layout.GridPane) TitledPane(javafx.scene.control.TitledPane) ToolBar(javafx.scene.control.ToolBar) GeneralTools(qupath.lib.common.GeneralTools) Node(javafx.scene.Node) CheckBox(javafx.scene.control.CheckBox) IOException(java.io.IOException) ChartTools(qupath.lib.gui.charts.ChartTools) File(java.io.File) Menu(javafx.scene.control.Menu) DefaultTMAGrid(qupath.lib.objects.hierarchy.DefaultTMAGrid) KeyCodeCombination(javafx.scene.input.KeyCodeCombination) SelectionMode(javafx.scene.control.SelectionMode) TreeMap(java.util.TreeMap) SimpleObjectProperty(javafx.beans.property.SimpleObjectProperty) Tab(javafx.scene.control.Tab) ObservableValue(javafx.beans.value.ObservableValue) TMAGrid(qupath.lib.objects.hierarchy.TMAGrid) PathPrefs(qupath.lib.gui.prefs.PathPrefs) PaneTools(qupath.lib.gui.tools.PaneTools) PathIO(qupath.lib.io.PathIO) Button(javafx.scene.control.Button) Pos(javafx.geometry.Pos) LoggerFactory(org.slf4j.LoggerFactory) Scanner(java.util.Scanner) XYChart(javafx.scene.chart.XYChart) VBox(javafx.scene.layout.VBox) Side(javafx.geometry.Side) KeyCombination(javafx.scene.input.KeyCombination) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) ComboBox(javafx.scene.control.ComboBox) ContextMenu(javafx.scene.control.ContextMenu) TableView(javafx.scene.control.TableView) QuPathGUI(qupath.lib.gui.QuPathGUI) SortedList(javafx.collections.transformation.SortedList) Pane(javafx.scene.layout.Pane) Orientation(javafx.geometry.Orientation) TextField(javafx.scene.control.TextField) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) Predicate(java.util.function.Predicate) Collection(java.util.Collection) FilteredList(javafx.collections.transformation.FilteredList) Collectors(java.util.stream.Collectors) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) Text(javafx.scene.text.Text) SimpleBindings(javax.script.SimpleBindings) Priority(javafx.scene.layout.Priority) List(java.util.List) Entry(java.util.Map.Entry) Optional(java.util.Optional) NumberAxis(javafx.scene.chart.NumberAxis) Scene(javafx.scene.Scene) ListView(javafx.scene.control.ListView) ReadOnlyObjectProperty(javafx.beans.property.ReadOnlyObjectProperty) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) Action(org.controlsfx.control.action.Action) HashMap(java.util.HashMap) DoubleProperty(javafx.beans.property.DoubleProperty) TMAScoreImporter(qupath.lib.io.TMAScoreImporter) TreeTableRow(javafx.scene.control.TreeTableRow) TableColumn(javafx.scene.control.TableColumn) HashSet(java.util.HashSet) Dialogs(qupath.lib.gui.dialogs.Dialogs) ScatterChart(javafx.scene.chart.ScatterChart) Insets(javafx.geometry.Insets) Callback(javafx.util.Callback) Tooltip(javafx.scene.control.Tooltip) ExecutorService(java.util.concurrent.ExecutorService) ImageData(qupath.lib.images.ImageData) KeyCode(javafx.scene.input.KeyCode) ObjectProperty(javafx.beans.property.ObjectProperty) Logger(org.slf4j.Logger) Label(javafx.scene.control.Label) MenuBar(javafx.scene.control.MenuBar) CellDataFeatures(javafx.scene.control.TreeTableColumn.CellDataFeatures) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) ScriptEngineManager(javax.script.ScriptEngineManager) TMACoreObject(qupath.lib.objects.TMACoreObject) PearsonsCorrelation(org.apache.commons.math3.stat.correlation.PearsonsCorrelation) DropShadow(javafx.scene.effect.DropShadow) MenuTools(qupath.lib.gui.tools.MenuTools) ScriptContext(javax.script.ScriptContext) TMAEntry(qupath.lib.gui.tma.TMAEntries.TMAEntry) TreeTableColumn(javafx.scene.control.TreeTableColumn) SimpleBooleanProperty(javafx.beans.property.SimpleBooleanProperty) Stage(javafx.stage.Stage) ScriptEngine(javax.script.ScriptEngine) ChangeListener(javafx.beans.value.ChangeListener) Collections(java.util.Collections) BorderPane(javafx.scene.layout.BorderPane) Insets(javafx.geometry.Insets) MasterDetailPane(org.controlsfx.control.MasterDetailPane) Label(javafx.scene.control.Label) MenuBar(javafx.scene.control.MenuBar) ContextMenu(javafx.scene.control.ContextMenu) ListChangeListener(javafx.collections.ListChangeListener) KeyCode(javafx.scene.input.KeyCode) Menu(javafx.scene.control.Menu) ContextMenu(javafx.scene.control.ContextMenu) TreeTableRow(javafx.scene.control.TreeTableRow) TMAEntry(qupath.lib.gui.tma.TMAEntries.TMAEntry) Tooltip(javafx.scene.control.Tooltip) MenuItem(javafx.scene.control.MenuItem) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) KeyCodeCombination(javafx.scene.input.KeyCodeCombination) Text(javafx.scene.text.Text) Change(javafx.collections.ListChangeListener.Change) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) Scene(javafx.scene.Scene) CheckBox(javafx.scene.control.CheckBox) ToolBar(javafx.scene.control.ToolBar) File(java.io.File) Separator(javafx.scene.control.Separator)

Example 4 with Project

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));
}
Also used : ImageServer(qupath.lib.images.servers.ImageServer) LoggerFactory(org.slf4j.LoggerFactory) Side(javafx.geometry.Side) PixelClassificationOverlay(qupath.lib.gui.viewer.overlays.PixelClassificationOverlay) ParameterList(qupath.lib.plugins.parameters.ParameterList) ComboBox(javafx.scene.control.ComboBox) ColorModelRenderer(qupath.lib.gui.images.stores.ColorModelRenderer) ContextMenu(javafx.scene.control.ContextMenu) Map(java.util.Map) PixelClassifierTools(qupath.opencv.ml.pixel.PixelClassifierTools) Path(java.nio.file.Path) QuPathGUI(qupath.lib.gui.QuPathGUI) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) PixelClassifierUI(qupath.process.gui.commands.ml.PixelClassifierUI) DensityMapBuilder(qupath.lib.analysis.heatmaps.DensityMaps.DensityMapBuilder) UriUpdater(qupath.lib.io.UriUpdater) Set(java.util.Set) Collectors(java.util.stream.Collectors) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) List(java.util.List) Project(qupath.lib.projects.Project) GuiTools(qupath.lib.gui.tools.GuiTools) DensityMaps(qupath.lib.analysis.heatmaps.DensityMaps) ObservableList(javafx.collections.ObservableList) Scene(javafx.scene.Scene) ObjectExpression(javafx.beans.binding.ObjectExpression) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) GsonTools(qupath.lib.io.GsonTools) Bindings(javafx.beans.binding.Bindings) TransferMode(javafx.scene.input.TransferMode) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) Dialogs(qupath.lib.gui.dialogs.Dialogs) Dragboard(javafx.scene.input.Dragboard) Insets(javafx.geometry.Insets) DensityMapUI(qupath.process.gui.commands.density.DensityMapUI) WeakHashMap(java.util.WeakHashMap) GridPane(javafx.scene.layout.GridPane) ImageData(qupath.lib.images.ImageData) Modality(javafx.stage.Modality) Logger(org.slf4j.Logger) Label(javafx.scene.control.Label) Files(java.nio.file.Files) GeneralTools(qupath.lib.common.GeneralTools) IOException(java.io.IOException) File(java.io.File) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) TreeMap(java.util.TreeMap) Stage(javafx.stage.Stage) Manager(qupath.lib.projects.ResourceManager.Manager) PathPrefs(qupath.lib.gui.prefs.PathPrefs) UriResource(qupath.lib.io.UriResource) DialogButton(qupath.lib.gui.dialogs.Dialogs.DialogButton) PaneTools(qupath.lib.gui.tools.PaneTools) ArrayList(java.util.ArrayList) IOException(java.io.IOException) File(java.io.File) HashSet(java.util.HashSet)

Example 5 with Project

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();
}
Also used : Button(javafx.scene.control.Button) Pos(javafx.geometry.Pos) Scene(javafx.scene.Scene) Arrays(java.util.Arrays) ListView(javafx.scene.control.ListView) GsonTools(qupath.lib.io.GsonTools) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) Bindings(javafx.beans.binding.Bindings) TransferMode(javafx.scene.input.TransferMode) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap) Dialogs(qupath.lib.gui.dialogs.Dialogs) Dragboard(javafx.scene.input.Dragboard) Insets(javafx.geometry.Insets) ObjectClassifiers(qupath.lib.classifiers.object.ObjectClassifiers) ContextMenu(javafx.scene.control.ContextMenu) Map(java.util.Map) TextAlignment(javafx.scene.text.TextAlignment) UpdateUrisCommand(qupath.lib.gui.commands.UpdateUrisCommand) GridPane(javafx.scene.layout.GridPane) QuPathGUI(qupath.lib.gui.QuPathGUI) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) Label(javafx.scene.control.Label) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) Files(java.nio.file.Files) GeneralTools(qupath.lib.common.GeneralTools) UriUpdater(qupath.lib.io.UriUpdater) IOException(java.io.IOException) WorkflowStep(qupath.lib.plugins.workflow.WorkflowStep) Collectors(java.util.stream.Collectors) File(java.io.File) Priority(javafx.scene.layout.Priority) List(java.util.List) Project(qupath.lib.projects.Project) SelectionMode(javafx.scene.control.SelectionMode) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) Stage(javafx.stage.Stage) ObjectClassifier(qupath.lib.classifiers.object.ObjectClassifier) ObservableList(javafx.collections.ObservableList) UriResource(qupath.lib.io.UriResource) DialogButton(qupath.lib.gui.dialogs.Dialogs.DialogButton) PaneTools(qupath.lib.gui.tools.PaneTools) GridPane(javafx.scene.layout.GridPane) Insets(javafx.geometry.Insets) Label(javafx.scene.control.Label) ArrayList(java.util.ArrayList) ContextMenu(javafx.scene.control.ContextMenu) MenuItem(javafx.scene.control.MenuItem) IOException(java.io.IOException) Scene(javafx.scene.Scene) IOException(java.io.IOException) ListView(javafx.scene.control.ListView) Button(javafx.scene.control.Button) DialogButton(qupath.lib.gui.dialogs.Dialogs.DialogButton) Stage(javafx.stage.Stage) File(java.io.File) Dragboard(javafx.scene.input.Dragboard)

Aggregations

List (java.util.List)8 Collectors (java.util.stream.Collectors)8 Logger (org.slf4j.Logger)8 LoggerFactory (org.slf4j.LoggerFactory)8 Dialogs (qupath.lib.gui.dialogs.Dialogs)8 Project (qupath.lib.projects.Project)8 BufferedImage (java.awt.image.BufferedImage)7 IOException (java.io.IOException)7 ArrayList (java.util.ArrayList)7 Map (java.util.Map)7 GeneralTools (qupath.lib.common.GeneralTools)7 QuPathGUI (qupath.lib.gui.QuPathGUI)7 PathPrefs (qupath.lib.gui.prefs.PathPrefs)7 ImageData (qupath.lib.images.ImageData)7 File (java.io.File)6 Set (java.util.Set)6 Bindings (javafx.beans.binding.Bindings)6 Insets (javafx.geometry.Insets)6 Label (javafx.scene.control.Label)6 MenuItem (javafx.scene.control.MenuItem)6