Search in sources :

Example 6 with ObservableMeasurementTableData

use of qupath.lib.gui.measure.ObservableMeasurementTableData in project qupath by qupath.

the class SummaryMeasurementTableCommand method showTable.

/**
 * Show a measurement table for the specified image data.
 * @param imageData the image data
 * @param type the object type to show
 */
public void showTable(ImageData<BufferedImage> imageData, Class<? extends PathObject> type) {
    if (imageData == null) {
        Dialogs.showNoImageError("Show measurement table");
        return;
    }
    final PathObjectHierarchy hierarchy = imageData.getHierarchy();
    ObservableMeasurementTableData model = new ObservableMeasurementTableData();
    model.setImageData(imageData, imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type));
    SplitPane splitPane = new SplitPane();
    HistogramDisplay histogramDisplay = new HistogramDisplay(model, true);
    // table.setTableMenuButtonVisible(true);
    TableView<PathObject> table = new TableView<>();
    table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
    table.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<PathObject>() {

        @Override
        public void onChanged(ListChangeListener.Change<? extends PathObject> c) {
            synchronizeSelectionModelToTable(hierarchy, c, table);
        }
    });
    StringProperty displayedName = new SimpleStringProperty(ServerTools.getDisplayableImageName(imageData.getServer()));
    var title = Bindings.createStringBinding(() -> {
        if (type == null)
            return "Results " + displayedName.get();
        else
            return PathObjectTools.getSuitableName(type, false) + " results - " + displayedName.get();
    }, displayedName);
    // Handle double-click as a way to center on a ROI
    // var enter = new KeyCodeCombination(KeyCode.ENTER);
    table.setRowFactory(params -> {
        var row = new TableRow<PathObject>();
        row.setOnMouseClicked(e -> {
            if (e.getClickCount() == 2) {
                maybeCenterROI(row.getItem());
            }
        });
        // });
        return row;
    });
    // Create columns according to the table model
    // for (int i = 0; i < model.getColumnCount(); i++) {
    // // Add string column
    // if (model.getColumnClass(i).equals(String.class)) {
    // TableColumn<PathObject, String> col = null;
    // col = new TableColumn<>(model.getColumnName(i));
    // col.setCellValueFactory(new Callback<CellDataFeatures<PathObject, String>, ObservableValue<String>>() {
    // public ObservableValue<String> call(CellDataFeatures<PathObject, String> val) {
    // return new SimpleStringProperty(val.getValue().getDisplayedName());
    // }
    // });
    // col.setCellFactory(column -> new BasicTableCell<String>());
    // table.getColumns().add(col);
    // }
    // }
    boolean tmaCoreList = TMACoreObject.class.isAssignableFrom(type);
    // Add TMA core columns, if suitable
    if (tmaCoreList) {
        TableColumn<PathObject, ROI> col = new TableColumn<>("Image");
        col.setCellValueFactory(val -> new SimpleObjectProperty<>(val.getValue().getROI()));
        double maxWidth = maxDimForTMACore;
        double padding = 10;
        col.setCellFactory(column -> new TMACoreTableCell(table, imageData.getServer(), maxWidth, padding));
        col.widthProperty().addListener((v, o, n) -> table.refresh());
        col.setMaxWidth(maxWidth + padding * 2);
        table.getColumns().add(col);
        // While here, make sure we have fewer bins - don't usually have all that many cores
        histogramDisplay.setNumBins(10);
    }
    // Create numeric columns
    for (String columnName : model.getAllNames()) {
        // Add column
        if (model.isStringMeasurement(columnName)) {
            TableColumn<PathObject, String> col = new TableColumn<>(columnName);
            col.setCellValueFactory(column -> model.createStringMeasurement(column.getValue(), column.getTableColumn().getText()));
            col.setCellFactory(column -> new BasicTableCell<>());
            table.getColumns().add(col);
        } else {
            TableColumn<PathObject, Number> col = new TableColumn<>(columnName);
            col.setCellValueFactory(column -> model.createNumericMeasurement(column.getValue(), column.getTableColumn().getText()));
            col.setCellFactory(column -> new NumericTableCell<PathObject>(histogramDisplay));
            table.getColumns().add(col);
        }
    }
    // Set the PathObjects - need to deal with sorting, since a FilteredList won't handle it directly
    SortedList<PathObject> items = new SortedList<>(model.getItems());
    items.comparatorProperty().bind(table.comparatorProperty());
    table.setItems(items);
    List<ButtonBase> buttons = new ArrayList<>();
    ToggleButton btnHistogram = new ToggleButton("Show histograms");
    btnHistogram.selectedProperty().addListener((v, o, n) -> {
        if (n) {
            Pane paneHistograms = histogramDisplay.getPane();
            splitPane.getItems().add(paneHistograms);
        } else if (histogramDisplay != null)
            splitPane.getItems().remove(histogramDisplay.getPane());
    });
    buttons.add(btnHistogram);
    // Button btnScatterplot = new Button("Show scatterplots");
    // btnScatterplot.setOnAction(e -> {
    // SwingUtilities.invokeLater(() -> {
    // JDialog dialog = new ScatterplotDisplay(null, "Scatterplots: " + displayedName, model).getDialog();
    // dialog.setLocationRelativeTo(null);
    // dialog.setVisible(true);
    // });
    // });
    // buttons.add(btnScatterplot);
    Button btnCopy = new Button("Copy to clipboard");
    btnCopy.setOnAction(e -> {
        // TODO: Deal with repetition immediately below...
        Set<String> excludeColumns = new HashSet<>();
        for (TableColumn<?, ?> col : table.getColumns()) {
            if (!col.isVisible())
                excludeColumns.add(col.getText());
        }
        copyTableContentsToClipboard(model, excludeColumns);
    });
    buttons.add(btnCopy);
    Button btnSave = new Button("Save");
    btnSave.setOnAction(e -> {
        Set<String> excludeColumns = new HashSet<>();
        for (TableColumn<?, ?> col : table.getColumns()) {
            if (!col.isVisible())
                excludeColumns.add(col.getText());
        }
        File fileOutput = promptForOutputFile();
        if (fileOutput == null)
            return;
        if (saveTableModel(model, fileOutput, excludeColumns)) {
            WorkflowStep step;
            String includeColumns;
            if (excludeColumns.isEmpty())
                includeColumns = "";
            else {
                List<String> includeColumnList = new ArrayList<>(model.getAllNames());
                includeColumnList.removeAll(excludeColumns);
                includeColumns = ", " + includeColumnList.stream().map(s -> "'" + s + "'").collect(Collectors.joining(", "));
            }
            String path = qupath.getProject() == null ? fileOutput.toURI().getPath() : fileOutput.getParentFile().toURI().getPath();
            if (type == TMACoreObject.class) {
                step = new DefaultScriptableWorkflowStep("Save TMA measurements", String.format("saveTMAMeasurements('%s'%s)", path, includeColumns));
            } else if (type == PathAnnotationObject.class) {
                step = new DefaultScriptableWorkflowStep("Save annotation measurements", String.format("saveAnnotationMeasurements('%s\'%s)", path, includeColumns));
            } else if (type == PathDetectionObject.class) {
                step = new DefaultScriptableWorkflowStep("Save detection measurements", String.format("saveDetectionMeasurements('%s'%s)", path, includeColumns));
            } else {
                step = new DefaultScriptableWorkflowStep("Save measurements", String.format("saveMeasurements('%s', %s%s)", path, type == null ? null : type.getName(), includeColumns));
            }
            imageData.getHistoryWorkflow().addStep(step);
        }
    });
    buttons.add(btnSave);
    Stage frame = new Stage();
    frame.initOwner(qupath.getStage());
    frame.titleProperty().bind(title);
    BorderPane paneTable = new BorderPane();
    paneTable.setCenter(table);
    // Add text field to filter visible columns
    TextField tfColumnFilter = new TextField();
    GridPane paneFilter = new GridPane();
    paneFilter.add(new Label("Column filter"), 0, 0);
    paneFilter.add(tfColumnFilter, 1, 0);
    GridPane.setHgrow(tfColumnFilter, Priority.ALWAYS);
    paneFilter.setHgap(5);
    if (tmaCoreList) {
        CheckBox cbHideMissing = new CheckBox("Hide missing cores");
        paneFilter.add(cbHideMissing, 2, 0);
        cbHideMissing.selectedProperty().addListener((v, o, n) -> {
            if (n) {
                model.setPredicate(p -> (!(p instanceof TMACoreObject)) || !((TMACoreObject) p).isMissing());
            } else
                model.setPredicate(null);
        });
        cbHideMissing.setSelected(true);
    }
    paneFilter.setPadding(new Insets(2, 5, 2, 5));
    paneTable.setBottom(paneFilter);
    StringProperty columnFilter = tfColumnFilter.textProperty();
    columnFilter.addListener((v, o, n) -> {
        String val = n.toLowerCase().trim();
        if (val.isEmpty()) {
            for (TableColumn<?, ?> col : table.getColumns()) {
                if (!col.isVisible())
                    col.setVisible(true);
            }
            return;
        }
        for (TableColumn<?, ?> col : table.getColumns()) {
            col.setVisible(col.getText().toLowerCase().contains(val));
        }
    });
    BorderPane pane = new BorderPane();
    // pane.setCenter(table);
    splitPane.getItems().add(paneTable);
    pane.setCenter(splitPane);
    GridPane panelButtons = PaneTools.createColumnGridControls(buttons.toArray(new ButtonBase[0]));
    pane.setBottom(panelButtons);
    PathObjectHierarchyListener listener = new PathObjectHierarchyListener() {

        @Override
        public void hierarchyChanged(PathObjectHierarchyEvent event) {
            if (event.isChanging())
                return;
            if (!Platform.isFxApplicationThread()) {
                Platform.runLater(() -> hierarchyChanged(event));
                return;
            }
            if (imageData != null)
                displayedName.set(ServerTools.getDisplayableImageName(imageData.getServer()));
            model.refreshEntries();
            table.refresh();
            if (histogramDisplay != null)
                histogramDisplay.refreshHistogram();
        }
    };
    QuPathViewer viewer = qupath.getViewer();
    TableViewerListener tableViewerListener = new TableViewerListener(viewer, table);
    frame.setOnShowing(e -> {
        hierarchy.addPathObjectListener(listener);
        viewer.addViewerListener(tableViewerListener);
    });
    frame.setOnHiding(e -> {
        hierarchy.removePathObjectListener(listener);
        viewer.removeViewerListener(tableViewerListener);
    });
    Scene scene = new Scene(pane, 600, 500);
    frame.setScene(scene);
    frame.show();
    // Add ability to remove entries from table
    ContextMenu menu = new ContextMenu();
    Menu menuLimitClasses = new Menu("Show classes");
    menu.setOnShowing(e -> {
        Set<PathClass> representedClasses = model.getBackingListEntries().stream().map(p -> p.getPathClass() == null ? null : p.getPathClass().getBaseClass()).collect(Collectors.toCollection(() -> new HashSet<>()));
        representedClasses.remove(null);
        if (representedClasses.isEmpty()) {
            menuLimitClasses.setVisible(false);
        } else {
            menuLimitClasses.setVisible(true);
        }
        menuLimitClasses.getItems().clear();
        List<PathClass> sortedClasses = new ArrayList<>(representedClasses);
        Collections.sort(sortedClasses);
        MenuItem miClass = new MenuItem("All");
        miClass.setOnAction(e2 -> {
            model.setPredicate(null);
            histogramDisplay.refreshHistogram();
        });
        menuLimitClasses.getItems().add(miClass);
        for (PathClass pathClass : sortedClasses) {
            miClass = new MenuItem(pathClass.getName());
            miClass.setOnAction(e2 -> {
                model.setPredicate(p -> pathClass.isAncestorOf(p.getPathClass()));
                histogramDisplay.refreshHistogram();
            });
            menuLimitClasses.getItems().add(miClass);
        }
    });
    if (type != TMACoreObject.class) {
        menu.getItems().add(menuLimitClasses);
        table.setContextMenu(menu);
    }
}
Also used : Button(javafx.scene.control.Button) Pos(javafx.geometry.Pos) Arrays(java.util.Arrays) ImageServer(qupath.lib.images.servers.ImageServer) ServerTools(qupath.lib.images.servers.ServerTools) LoggerFactory(org.slf4j.LoggerFactory) HistogramDisplay(qupath.lib.gui.charts.HistogramDisplay) PathTableData(qupath.lib.gui.measure.PathTableData) MultipleSelectionModel(javafx.scene.control.MultipleSelectionModel) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) ListChangeListener(javafx.collections.ListChangeListener) ContextMenu(javafx.scene.control.ContextMenu) PathObjectHierarchyEvent(qupath.lib.objects.hierarchy.events.PathObjectHierarchyEvent) Map(java.util.Map) TableView(javafx.scene.control.TableView) QuPathGUI(qupath.lib.gui.QuPathGUI) SortedList(javafx.collections.transformation.SortedList) Pane(javafx.scene.layout.Pane) Shape(java.awt.Shape) PrintWriter(java.io.PrintWriter) SplitPane(javafx.scene.control.SplitPane) TextField(javafx.scene.control.TextField) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) ButtonBase(javafx.scene.control.ButtonBase) Collection(java.util.Collection) Set(java.util.Set) Canvas(javafx.scene.canvas.Canvas) WorkflowStep(qupath.lib.plugins.workflow.WorkflowStep) QuPathViewerListener(qupath.lib.gui.viewer.QuPathViewerListener) Collectors(java.util.stream.Collectors) StandardCharsets(java.nio.charset.StandardCharsets) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathObject(qupath.lib.objects.PathObject) Platform(javafx.application.Platform) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer) Priority(javafx.scene.layout.Priority) List(java.util.List) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) ToggleButton(javafx.scene.control.ToggleButton) Clipboard(javafx.scene.input.Clipboard) GuiTools(qupath.lib.gui.tools.GuiTools) ClipboardContent(javafx.scene.input.ClipboardContent) BorderPane(javafx.scene.layout.BorderPane) StringProperty(javafx.beans.property.StringProperty) TableViewSelectionModel(javafx.scene.control.TableView.TableViewSelectionModel) Scene(javafx.scene.Scene) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) Bindings(javafx.beans.binding.Bindings) ArrayList(java.util.ArrayList) TableColumn(javafx.scene.control.TableColumn) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) Dialogs(qupath.lib.gui.dialogs.Dialogs) TableCell(javafx.scene.control.TableCell) Insets(javafx.geometry.Insets) GridPane(javafx.scene.layout.GridPane) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) Label(javafx.scene.control.Label) GeneralTools(qupath.lib.common.GeneralTools) RegionRequest(qupath.lib.regions.RegionRequest) TableRow(javafx.scene.control.TableRow) CheckBox(javafx.scene.control.CheckBox) PathClass(qupath.lib.objects.classes.PathClass) IOException(java.io.IOException) TMACoreObject(qupath.lib.objects.TMACoreObject) PathObjectSelectionModel(qupath.lib.objects.hierarchy.events.PathObjectSelectionModel) File(java.io.File) PathObjectTools(qupath.lib.objects.PathObjectTools) Menu(javafx.scene.control.Menu) ROI(qupath.lib.roi.interfaces.ROI) SelectionMode(javafx.scene.control.SelectionMode) Stage(javafx.stage.Stage) SimpleObjectProperty(javafx.beans.property.SimpleObjectProperty) SwingFXUtils(javafx.embed.swing.SwingFXUtils) PathObjectHierarchyListener(qupath.lib.objects.hierarchy.events.PathObjectHierarchyListener) Collections(java.util.Collections) Image(javafx.scene.image.Image) PathPrefs(qupath.lib.gui.prefs.PathPrefs) ContentDisplay(javafx.scene.control.ContentDisplay) PaneTools(qupath.lib.gui.tools.PaneTools) WorkflowStep(qupath.lib.plugins.workflow.WorkflowStep) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) SortedList(javafx.collections.transformation.SortedList) ArrayList(java.util.ArrayList) Label(javafx.scene.control.Label) PathObjectHierarchyEvent(qupath.lib.objects.hierarchy.events.PathObjectHierarchyEvent) SplitPane(javafx.scene.control.SplitPane) ListChangeListener(javafx.collections.ListChangeListener) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) Button(javafx.scene.control.Button) ToggleButton(javafx.scene.control.ToggleButton) Stage(javafx.stage.Stage) TextField(javafx.scene.control.TextField) TableView(javafx.scene.control.TableView) HashSet(java.util.HashSet) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer) GridPane(javafx.scene.layout.GridPane) TMACoreObject(qupath.lib.objects.TMACoreObject) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) Scene(javafx.scene.Scene) ROI(qupath.lib.roi.interfaces.ROI) TableColumn(javafx.scene.control.TableColumn) Pane(javafx.scene.layout.Pane) SplitPane(javafx.scene.control.SplitPane) BorderPane(javafx.scene.layout.BorderPane) GridPane(javafx.scene.layout.GridPane) HistogramDisplay(qupath.lib.gui.charts.HistogramDisplay) PathObject(qupath.lib.objects.PathObject) CheckBox(javafx.scene.control.CheckBox) TableRow(javafx.scene.control.TableRow) File(java.io.File) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) BorderPane(javafx.scene.layout.BorderPane) Insets(javafx.geometry.Insets) PathObjectHierarchyListener(qupath.lib.objects.hierarchy.events.PathObjectHierarchyListener) StringProperty(javafx.beans.property.StringProperty) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) ContextMenu(javafx.scene.control.ContextMenu) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) PathClass(qupath.lib.objects.classes.PathClass) ContextMenu(javafx.scene.control.ContextMenu) Menu(javafx.scene.control.Menu) ToggleButton(javafx.scene.control.ToggleButton) MenuItem(javafx.scene.control.MenuItem) ButtonBase(javafx.scene.control.ButtonBase) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData)

Example 7 with ObservableMeasurementTableData

use of qupath.lib.gui.measure.ObservableMeasurementTableData 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

ObservableMeasurementTableData (qupath.lib.gui.measure.ObservableMeasurementTableData)7 BufferedImage (java.awt.image.BufferedImage)5 File (java.io.File)5 PrintWriter (java.io.PrintWriter)4 IOException (java.io.IOException)3 ArrayList (java.util.ArrayList)3 ImageData (qupath.lib.images.ImageData)3 PathObject (qupath.lib.objects.PathObject)3 TMACoreObject (qupath.lib.objects.TMACoreObject)3 ROI (qupath.lib.roi.interfaces.ROI)3 FileNotFoundException (java.io.FileNotFoundException)2 StandardCharsets (java.nio.charset.StandardCharsets)2 Arrays (java.util.Arrays)2 Collection (java.util.Collection)2 Collections (java.util.Collections)2 List (java.util.List)2 Map (java.util.Map)2 Collectors (java.util.stream.Collectors)2 Logger (org.slf4j.Logger)2 LoggerFactory (org.slf4j.LoggerFactory)2