Search in sources :

Example 1 with ProgressDialog

use of org.controlsfx.dialog.ProgressDialog 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 ProgressDialog

use of org.controlsfx.dialog.ProgressDialog in project qupath by qupath.

the class CreateTrainingImageCommand method promptToCreateTrainingImage.

/**
 * Prompt to create a training image, based upon annotations throughout a project.
 * @param project
 * @param availableClasses
 * @return the entry of the new training image, created within the project
 */
public static ProjectImageEntry<BufferedImage> promptToCreateTrainingImage(Project<BufferedImage> project, List<PathClass> availableClasses) {
    if (project == null) {
        Dialogs.showErrorMessage(NAME, "You need a project!");
        return null;
    }
    if (availableClasses.isEmpty()) {
        Dialogs.showErrorMessage(NAME, "Please ensure classifications are available in QuPath!");
        return null;
    }
    List<PathClass> pathClasses = new ArrayList<>(availableClasses);
    if (!pathClasses.contains(pathClass))
        pathClass = pathClasses.get(0);
    var params = new ParameterList().addEmptyParameter("Generates a single image from regions extracted from the project.").addEmptyParameter("Before running this command, add classified rectangle annotations to select the regions.").addChoiceParameter("pathClass", "Classification", pathClass, pathClasses, "Select classification for annotated regions").addIntParameter("maxWidth", "Preferred image width", maxWidth, "px", "Preferred maximum width of the training image, in pixels").addBooleanParameter("doZ", "Do z-stacks", doZ, "Take all slices of a z-stack, where possible").addBooleanParameter("rectanglesOnly", "Rectangles only", rectanglesOnly, "Only extract regions annotated with rectangles. Otherwise, the bounding box of all regions with the classification will be taken.").addEmptyParameter("Note this command requires images to have similar bit-depths/channels/pixel sizes for compatibility.");
    if (!Dialogs.showParameterDialog(NAME, params))
        return null;
    pathClass = (PathClass) params.getChoiceParameterValue("pathClass");
    maxWidth = params.getIntParameterValue("maxWidth");
    doZ = params.getBooleanParameterValue("doZ");
    rectanglesOnly = params.getBooleanParameterValue("rectanglesOnly");
    var task = new Task<SparseImageServer>() {

        @Override
        protected SparseImageServer call() throws Exception {
            return createSparseServer(project, pathClass, maxWidth, doZ, rectanglesOnly);
        }
    };
    var dialog = new ProgressDialog(task);
    dialog.setTitle(NAME);
    dialog.setHeaderText("Creating training image...");
    Executors.newSingleThreadExecutor().submit(task);
    dialog.showAndWait();
    try {
        var server = task.get();
        // var server = createSparseServer(project, pathClass, maxWidth, doZ, rectanglesOnly);
        if (server == null || server.getManager().getRegions().isEmpty()) {
            Dialogs.showErrorMessage("Sparse image server", "No suitable annotations found in the current project!");
            return null;
        }
        var entry = ProjectCommands.addSingleImageToProject(project, server, null);
        server.close();
        project.syncChanges();
        return entry;
    } catch (Exception e) {
        Dialogs.showErrorMessage("Sparse image server", e);
        return null;
    }
}
Also used : PathClass(qupath.lib.objects.classes.PathClass) Task(javafx.concurrent.Task) ArrayList(java.util.ArrayList) ParameterList(qupath.lib.plugins.parameters.ParameterList) ProgressDialog(org.controlsfx.dialog.ProgressDialog) IOException(java.io.IOException)

Example 3 with ProgressDialog

use of org.controlsfx.dialog.ProgressDialog in project qupath by qupath.

the class DefaultScriptEditor method handleRunProject.

/**
 * Request project image entries to run script for.
 * @param doSave
 */
void handleRunProject(final boolean doSave) {
    Project<BufferedImage> project = qupath.getProject();
    if (project == null) {
        Dialogs.showNoProjectError("Script editor");
        return;
    }
    ScriptTab tab = getCurrentScriptObject();
    if (tab == null || tab.getEditorComponent().getText().trim().length() == 0) {
        Dialogs.showErrorMessage("Script editor", "No script selected!");
        return;
    }
    if (tab.getLanguage() == null) {
        Dialogs.showErrorMessage("Script editor", "Scripting language is unknown!");
        return;
    }
    // Ensure that the previous images remain selected if the project still contains them
    // FilteredList<ProjectImageEntry<?>> sourceList = new FilteredList<>(FXCollections.observableArrayList(project.getImageList()));
    String sameImageWarning = doSave ? "A selected image is open in the viewer!\nUse 'File>Reload data' to see changes." : null;
    var listSelectionView = ProjectDialogs.createImageChoicePane(qupath, project.getImageList(), previousImages, sameImageWarning);
    Dialog<ButtonType> dialog = new Dialog<>();
    dialog.initOwner(qupath.getStage());
    dialog.setTitle("Select project images");
    dialog.getDialogPane().getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK);
    dialog.getDialogPane().setContent(listSelectionView);
    dialog.setResizable(true);
    dialog.getDialogPane().setPrefWidth(600);
    dialog.initModality(Modality.APPLICATION_MODAL);
    Optional<ButtonType> result = dialog.showAndWait();
    if (!result.isPresent() || result.get() != ButtonType.OK)
        return;
    previousImages.clear();
    // previousImages.addAll(listSelectionView.getTargetItems());
    previousImages.addAll(ProjectDialogs.getTargetItems(listSelectionView));
    if (previousImages.isEmpty())
        return;
    List<ProjectImageEntry<BufferedImage>> imagesToProcess = new ArrayList<>(previousImages);
    ProjectTask worker = new ProjectTask(project, imagesToProcess, tab, doSave);
    ProgressDialog progress = new ProgressDialog(worker);
    progress.initOwner(qupath.getStage());
    progress.setTitle("Batch script");
    progress.getDialogPane().setHeaderText("Batch processing...");
    progress.getDialogPane().setGraphic(null);
    progress.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
    progress.getDialogPane().lookupButton(ButtonType.CANCEL).addEventFilter(ActionEvent.ACTION, e -> {
        if (Dialogs.showYesNoDialog("Cancel batch script", "Are you sure you want to stop the running script after the current image?")) {
            worker.quietCancel();
            progress.setHeaderText("Cancelling...");
            // worker.cancel(false);
            progress.getDialogPane().lookupButton(ButtonType.CANCEL).setDisable(true);
        }
        e.consume();
    });
    // Clear console if necessary
    if (autoClearConsole.get() && getCurrentScriptObject() != null) {
        tab.getConsoleComponent().clear();
    }
    // Create & run task
    runningTask.set(qupath.createSingleThreadExecutor(this).submit(worker));
    progress.show();
}
Also used : ArrayList(java.util.ArrayList) ProgressDialog(org.controlsfx.dialog.ProgressDialog) BufferedImage(java.awt.image.BufferedImage) ProgressDialog(org.controlsfx.dialog.ProgressDialog) Dialog(javafx.scene.control.Dialog) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) ButtonType(javafx.scene.control.ButtonType)

Example 4 with ProgressDialog

use of org.controlsfx.dialog.ProgressDialog in project qupath by qupath.

the class MeasurementExportCommand method createAndShowDialog.

private void createAndShowDialog() {
    project = qupath.getProject();
    if (project == null) {
        Dialogs.showNoProjectError("Export measurements");
        return;
    }
    BorderPane mainPane = new BorderPane();
    BorderPane imageEntryPane = new BorderPane();
    GridPane optionPane = new GridPane();
    optionPane.setHgap(5.0);
    optionPane.setVgap(5.0);
    // TOP PANE (SELECT PROJECT ENTRY FOR EXPORT)
    project = qupath.getProject();
    pathObjectCombo = new ComboBox<>();
    separatorCombo = new ComboBox<>();
    includeCombo = new CheckComboBox<String>();
    String sameImageWarning = "A selected image is open in the viewer!\nData should be saved before exporting.";
    var listSelectionView = ProjectDialogs.createImageChoicePane(qupath, project.getImageList(), previousImages, sameImageWarning);
    // BOTTOM PANE (OPTIONS)
    int row = 0;
    Label pathOutputLabel = new Label("Output file");
    var btnChooseFile = new Button("Choose");
    btnChooseFile.setOnAction(e -> {
        String extSelected = separatorCombo.getSelectionModel().getSelectedItem();
        String ext = extSelected.equals("Tab (.tsv)") ? ".tsv" : ".csv";
        String extDesc = ext.equals(".tsv") ? "TSV (Tab delimited)" : "CSV (Comma delimited)";
        File pathOut = Dialogs.promptToSaveFile("Output file", Projects.getBaseDirectory(project), "measurements" + ext, extDesc, ext);
        if (pathOut != null) {
            if (pathOut.isDirectory())
                pathOut = new File(pathOut.getAbsolutePath() + File.separator + "measurements" + ext);
            outputText.setText(pathOut.getAbsolutePath());
        }
    });
    pathOutputLabel.setLabelFor(outputText);
    PaneTools.addGridRow(optionPane, row++, 0, "Choose output file", pathOutputLabel, outputText, outputText, btnChooseFile, btnChooseFile);
    outputText.setMaxWidth(Double.MAX_VALUE);
    btnChooseFile.setMaxWidth(Double.MAX_VALUE);
    Label pathObjectLabel = new Label("Export type");
    pathObjectLabel.setLabelFor(pathObjectCombo);
    pathObjectCombo.getItems().setAll("Image", "Annotations", "Detections", "Cells", "TMA cores");
    pathObjectCombo.getSelectionModel().selectFirst();
    pathObjectCombo.valueProperty().addListener((v, o, n) -> {
        if (n != null)
            setType(n);
    });
    PaneTools.addGridRow(optionPane, row++, 0, "Choose the export type", pathObjectLabel, pathObjectCombo, pathObjectCombo, pathObjectCombo, pathObjectCombo);
    Label separatorLabel = new Label("Separator");
    separatorLabel.setLabelFor(separatorCombo);
    separatorCombo.getItems().setAll("Tab (.tsv)", "Comma (.csv)", "Semicolon (.csv)");
    separatorCombo.getSelectionModel().selectFirst();
    PaneTools.addGridRow(optionPane, row++, 0, "Choose a value separator", separatorLabel, separatorCombo, separatorCombo, separatorCombo, separatorCombo);
    Label includeLabel = new Label("Columns to include (Optional)");
    includeLabel.setLabelFor(includeCombo);
    GuiTools.installSelectAllOrNoneMenu(includeCombo);
    Button btnPopulateColumns = new Button("Populate\t");
    ProgressIndicator progressIndicator = new ProgressIndicator();
    progressIndicator.setPrefSize(20, 20);
    progressIndicator.setMinSize(20, 20);
    progressIndicator.setOpacity(0);
    Button btnResetColumns = new Button("Reset");
    PaneTools.addGridRow(optionPane, row++, 0, "Choose the specific column(s) to include (default: all)", includeLabel, includeCombo, btnPopulateColumns, progressIndicator, btnResetColumns);
    btnPopulateColumns.setOnAction(e -> {
        includeCombo.setDisable(true);
        Set<String> allColumnsForCombo = Collections.synchronizedSet(new LinkedHashSet<>());
        setType(pathObjectCombo.getSelectionModel().getSelectedItem());
        for (int i = 0; i < ProjectDialogs.getTargetItems(listSelectionView).size(); i++) {
            ProjectImageEntry<BufferedImage> entry = ProjectDialogs.getTargetItems(listSelectionView).get(i);
            int updatedEntries = i;
            executor.submit(() -> {
                try {
                    progressIndicator.setOpacity(100);
                    ImageData<?> imageData = entry.readImageData();
                    ObservableMeasurementTableData model = new ObservableMeasurementTableData();
                    model.setImageData(imageData, imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type));
                    allColumnsForCombo.addAll(model.getAllNames());
                    imageData.getServer().close();
                    if (updatedEntries == ProjectDialogs.getTargetItems(listSelectionView).size() - 1) {
                        Platform.runLater(() -> {
                            allColumnsForCombo.removeIf(n -> n == null);
                            includeCombo.getItems().setAll(allColumnsForCombo);
                            includeCombo.getCheckModel().clearChecks();
                            includeCombo.setDisable(false);
                        });
                        progressIndicator.setOpacity(0);
                    }
                } catch (Exception ex) {
                    logger.warn("Error loading columns for entry " + entry.getImageName() + ": " + ex.getLocalizedMessage());
                }
            });
        }
        btnResetColumns.fire();
    });
    btnPopulateColumns.disableProperty().addListener((v, o, n) -> {
        if (n != null && n == true)
            includeCombo.setDisable(true);
    });
    var targetItemBinding = Bindings.size(listSelectionView.getTargetItems()).isEqualTo(0);
    btnPopulateColumns.disableProperty().bind(targetItemBinding);
    btnResetColumns.disableProperty().bind(targetItemBinding);
    btnResetColumns.setOnAction(e -> includeCombo.getCheckModel().clearChecks());
    // Add listener to separatorCombo
    separatorCombo.getSelectionModel().selectedItemProperty().addListener((v, o, n) -> {
        if (outputText == null || n == null)
            return;
        String currentOut = outputText.getText();
        if (n.equals("Tab (.tsv)") && currentOut.endsWith(".csv"))
            outputText.setText(currentOut.replace(".csv", ".tsv"));
        else if ((n.equals("Comma (.csv)") || n.equals("Semicolon (.csv)")) && currentOut.endsWith(".tsv"))
            outputText.setText(currentOut.replace(".tsv", ".csv"));
    });
    PaneTools.getContentsOfType(optionPane, Label.class, false).forEach(e -> e.setMinWidth(160));
    PaneTools.setToExpandGridPaneWidth(outputText, pathObjectCombo, separatorCombo, includeCombo);
    btnPopulateColumns.setMinWidth(100);
    btnResetColumns.setMinWidth(75);
    dialog = Dialogs.builder().title("Export measurements").buttons(btnExport, ButtonType.CANCEL).content(mainPane).build();
    dialog.getDialogPane().setPrefSize(600, 400);
    imageEntryPane.setCenter(listSelectionView);
    // Set the disabledProperty according to (1) targetItems.size() > 0 and (2) outputText.isEmpty()
    var emptyOutputTextBinding = outputText.textProperty().isEqualTo("");
    dialog.getDialogPane().lookupButton(btnExport).disableProperty().bind(Bindings.or(emptyOutputTextBinding, targetItemBinding));
    mainPane.setTop(imageEntryPane);
    mainPane.setBottom(optionPane);
    Optional<ButtonType> result = dialog.showAndWait();
    if (!result.isPresent() || result.get() != btnExport || result.get() == ButtonType.CANCEL)
        return;
    String curExt = Files.getFileExtension(outputText.getText());
    if (curExt.equals("") || (!curExt.equals("csv") && !curExt.equals("tsv"))) {
        curExt = curExt.length() > 1 ? "." + curExt : curExt;
        String extSelected = separatorCombo.getSelectionModel().getSelectedItem();
        String ext = extSelected.equals("Tab (.tsv)") ? ".tsv" : ".csv";
        outputText.setText(outputText.getText().substring(0, outputText.getText().length() - curExt.length()) + ext);
    }
    if (new File(outputText.getText()).getParent() == null) {
        String ext = Files.getFileExtension(outputText.getText()).equals("tsv") ? ".tsv" : ".csv";
        String extDesc = ext.equals(".tsv") ? "TSV (Tab delimited)" : "CSV (Comma delimited)";
        File pathOut = Dialogs.promptToSaveFile("Output file", Projects.getBaseDirectory(project), outputText.getText(), extDesc, ext);
        if (pathOut == null)
            return;
        else
            outputText.setText(pathOut.getAbsolutePath());
    }
    var checkedItems = includeCombo.getCheckModel().getCheckedItems();
    String[] include = checkedItems.stream().collect(Collectors.toList()).toArray(new String[checkedItems.size()]);
    String separator = defSep;
    switch(separatorCombo.getSelectionModel().getSelectedItem()) {
        case "Tab (.tsv)":
            separator = "\t";
            break;
        case "Comma (.csv)":
            separator = ",";
            break;
        case "Semicolon (.csv)":
            separator = ";";
            break;
    }
    ;
    MeasurementExporter exporter;
    exporter = new MeasurementExporter().imageList(ProjectDialogs.getTargetItems(listSelectionView)).separator(separator).includeOnlyColumns(include).exportType(type);
    ExportTask worker = new ExportTask(exporter, outputText.getText());
    ProgressDialog progress = new ProgressDialog(worker);
    progress.setWidth(600);
    progress.initOwner(qupath.getStage());
    progress.setTitle("Export measurements...");
    progress.getDialogPane().setHeaderText("Export measurements");
    progress.getDialogPane().setGraphic(null);
    progress.getDialogPane().getButtonTypes().add(ButtonType.CANCEL);
    progress.getDialogPane().lookupButton(ButtonType.CANCEL).addEventFilter(ActionEvent.ACTION, e -> {
        if (Dialogs.showYesNoDialog("Cancel export", "Are you sure you want to stop the export after the current image?")) {
            worker.quietCancel();
            progress.setHeaderText("Cancelling...");
            // worker.cancel(false);
            progress.getDialogPane().lookupButton(ButtonType.CANCEL).setDisable(true);
        }
        e.consume();
    });
    // Create & run task
    runningTask.set(qupath.createSingleThreadExecutor(this).submit(worker));
    progress.show();
}
Also used : BorderPane(javafx.scene.layout.BorderPane) GridPane(javafx.scene.layout.GridPane) Label(javafx.scene.control.Label) ProgressDialog(org.controlsfx.dialog.ProgressDialog) BufferedImage(java.awt.image.BufferedImage) FileNotFoundException(java.io.FileNotFoundException) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) Button(javafx.scene.control.Button) ProgressIndicator(javafx.scene.control.ProgressIndicator) MeasurementExporter(qupath.lib.gui.tools.MeasurementExporter) File(java.io.File) ButtonType(javafx.scene.control.ButtonType)

Aggregations

BufferedImage (java.awt.image.BufferedImage)3 ArrayList (java.util.ArrayList)3 ButtonType (javafx.scene.control.ButtonType)3 ProgressDialog (org.controlsfx.dialog.ProgressDialog)3 File (java.io.File)2 FileNotFoundException (java.io.FileNotFoundException)2 IOException (java.io.IOException)2 Task (javafx.concurrent.Task)2 Button (javafx.scene.control.Button)2 Dialog (javafx.scene.control.Dialog)2 Label (javafx.scene.control.Label)2 BorderPane (javafx.scene.layout.BorderPane)2 GridPane (javafx.scene.layout.GridPane)2 Graphics2D (java.awt.Graphics2D)1 RenderingHints (java.awt.RenderingHints)1 URI (java.net.URI)1 Collection (java.util.Collection)1 Collections (java.util.Collections)1 List (java.util.List)1 Optional (java.util.Optional)1