Search in sources :

Example 11 with DefaultScriptableWorkflowStep

use of qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep in project qupath by qupath.

the class GuiTools method promptToClearAllSelectedObjects.

/**
 * Prompt user to select all currently-selected objects (except TMA core objects).
 *
 * @param imageData
 * @return
 */
public static boolean promptToClearAllSelectedObjects(final ImageData<?> imageData) {
    // Get all non-TMA core objects
    PathObjectHierarchy hierarchy = imageData.getHierarchy();
    Collection<PathObject> selectedRaw = hierarchy.getSelectionModel().getSelectedObjects();
    List<PathObject> selected = selectedRaw.stream().filter(p -> !(p instanceof TMACoreObject)).collect(Collectors.toList());
    if (selected.isEmpty()) {
        if (selectedRaw.size() > selected.size())
            Dialogs.showErrorMessage("Delete selected objects", "No valid objects selected! \n\nNote: Individual TMA cores cannot be deleted with this method.");
        else
            Dialogs.showErrorMessage("Delete selected objects", "No objects selected!");
        return false;
    }
    int n = selected.size();
    String message;
    if (n == 1)
        message = "Delete selected object?";
    else
        message = "Delete " + n + " selected objects?";
    if (Dialogs.showYesNoDialog("Delete objects", message)) {
        // Check for descendants
        List<PathObject> children = new ArrayList<>();
        for (PathObject temp : selected) {
            children.addAll(temp.getChildObjects());
        }
        children.removeAll(selected);
        boolean keepChildren = true;
        if (!children.isEmpty()) {
            Dialogs.DialogButton response = Dialogs.showYesNoCancelDialog("Delete objects", "Keep descendant objects?");
            if (response == Dialogs.DialogButton.CANCEL)
                return false;
            keepChildren = response == Dialogs.DialogButton.YES;
        }
        hierarchy.removeObjects(selected, keepChildren);
        hierarchy.getSelectionModel().clearSelection();
        imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Delete selected objects", "clearSelectedObjects(" + keepChildren + ");"));
        if (keepChildren)
            logger.info(selected.size() + " object(s) deleted");
        else
            logger.info(selected.size() + " object(s) deleted with descendants");
        imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Delete selected objects", "clearSelectedObjects();"));
        logger.info(selected.size() + " object(s) deleted");
        return true;
    } else
        return false;
}
Also used : CheckComboBox(org.controlsfx.control.CheckComboBox) UnaryOperator(java.util.function.UnaryOperator) TextFormatter(javafx.scene.control.TextFormatter) PathRootObject(qupath.lib.objects.PathRootObject) ParameterList(qupath.lib.plugins.parameters.ParameterList) PointsROI(qupath.lib.roi.PointsROI) Matcher(java.util.regex.Matcher) Action(java.awt.Desktop.Action) ColorTools(qupath.lib.common.ColorTools) GraphicsContext(javafx.scene.canvas.GraphicsContext) Canvas(javafx.scene.canvas.Canvas) Screen(javafx.stage.Screen) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) Platform(javafx.application.Platform) CountDownLatch(java.util.concurrent.CountDownLatch) Transparency(java.awt.Transparency) Clipboard(javafx.scene.input.Clipboard) SimpleDoubleProperty(javafx.beans.property.SimpleDoubleProperty) StringProperty(javafx.beans.property.StringProperty) Callable(java.util.concurrent.Callable) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) DefaultColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains.DefaultColorDeconvolutionStains) Bindings(javafx.beans.binding.Bindings) NumberFormat(java.text.NumberFormat) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap) Slider(javafx.scene.control.Slider) SplitAnnotationsPlugin(qupath.lib.plugins.objects.SplitAnnotationsPlugin) GridPane(javafx.scene.layout.GridPane) Color(javafx.scene.paint.Color) GeneralTools(qupath.lib.common.GeneralTools) Commands(qupath.lib.gui.commands.Commands) Node(javafx.scene.Node) CheckBox(javafx.scene.control.CheckBox) IOException(java.io.IOException) File(java.io.File) PathObjectTools(qupath.lib.objects.PathObjectTools) Menu(javafx.scene.control.Menu) ROI(qupath.lib.roi.interfaces.ROI) SimpleObjectProperty(javafx.beans.property.SimpleObjectProperty) ObservableValue(javafx.beans.value.ObservableValue) Image(javafx.scene.image.Image) Button(javafx.scene.control.Button) ImageServer(qupath.lib.images.servers.ImageServer) CombineOp(qupath.lib.roi.RoiTools.CombineOp) DoubleBinding(javafx.beans.binding.DoubleBinding) ListCell(javafx.scene.control.ListCell) CheckMenuItem(javafx.scene.control.CheckMenuItem) LoggerFactory(org.slf4j.LoggerFactory) RenderingHints(java.awt.RenderingHints) Side(javafx.geometry.Side) ContextMenu(javafx.scene.control.ContextMenu) URI(java.net.URI) QuPathGUI(qupath.lib.gui.QuPathGUI) TextField(javafx.scene.control.TextField) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) Collection(java.util.Collection) Spinner(javafx.scene.control.Spinner) Collectors(java.util.stream.Collectors) StainVector(qupath.lib.color.StainVector) PathObject(qupath.lib.objects.PathObject) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) QuPathViewer(qupath.lib.gui.viewer.QuPathViewer) List(java.util.List) Robot(javafx.scene.robot.Robot) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) Pattern(java.util.regex.Pattern) ClipboardContent(javafx.scene.input.ClipboardContent) Scene(javafx.scene.Scene) ListView(javafx.scene.control.ListView) TextArea(javafx.scene.control.TextArea) ParsePosition(java.text.ParsePosition) DoubleProperty(javafx.beans.property.DoubleProperty) ColorDeconvolutionHelper(qupath.lib.color.ColorDeconvolutionHelper) Function(java.util.function.Function) Dialogs(qupath.lib.gui.dialogs.Dialogs) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains) SwingUtilities(javax.swing.SwingUtilities) Graphics2D(java.awt.Graphics2D) ActionTools(qupath.lib.gui.ActionTools) Tooltip(javafx.scene.control.Tooltip) ColorPicker(javafx.scene.control.ColorPicker) ImageData(qupath.lib.images.ImageData) Desktop(java.awt.Desktop) ObjectProperty(javafx.beans.property.ObjectProperty) Logger(org.slf4j.Logger) Label(javafx.scene.control.Label) WritableImage(javafx.scene.image.WritableImage) TMACoreObject(qupath.lib.objects.TMACoreObject) DoubleSpinnerValueFactory(javafx.scene.control.SpinnerValueFactory.DoubleSpinnerValueFactory) SimpleBooleanProperty(javafx.beans.property.SimpleBooleanProperty) Stage(javafx.stage.Stage) SpinnerValueFactory(javafx.scene.control.SpinnerValueFactory) SwingFXUtils(javafx.embed.swing.SwingFXUtils) Window(javafx.stage.Window) Collections(java.util.Collections) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) TMACoreObject(qupath.lib.objects.TMACoreObject) ArrayList(java.util.ArrayList) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) PathObject(qupath.lib.objects.PathObject) Dialogs(qupath.lib.gui.dialogs.Dialogs)

Example 12 with DefaultScriptableWorkflowStep

use of qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep in project qupath by qupath.

the class ImageData method setImageType.

/**
 * Set the image type.
 * @param type
 */
public void setImageType(final ImageType type) {
    if (this.type == type)
        return;
    logger.trace("Setting image type to {}", type);
    ImageType oldType = this.type;
    this.type = type;
    // Log the step
    getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Set image type", Collections.singletonMap("Image type", type), "setImageType(\'" + type.name() + "');"));
    if (isBrightfield())
        addColorDeconvolutionStainsToWorkflow(this);
    // TODO: REINTRODUCE LOGGING!
    // // Log the step
    // getWorkflow().addStep(
    // new DefaultScriptableWorkflowStep("Set image type",
    // Collections.singletonMap("Image type", type),
    // QP.class.getSimpleName() + ".setImageType(\'" + type.toString() + "');")
    // );
    // if (isBrightfield())
    // addColorDeconvolutionStainsToWorkflow(this);
    pcs.firePropertyChange("imageType", oldType, type);
    changes = true;
}
Also used : DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep)

Example 13 with DefaultScriptableWorkflowStep

use of qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep in project qupath by qupath.

the class PixelClassifierUI method promptToSavePredictionImage.

// public static Pane createSaveObjectClassifierPane(ObjectExpression<Project<BufferedImage>> project, ObjectExpression<ObjectClassifier<BufferedImage>> classifier, StringProperty savedName) {
// return new SaveResourcePaneBuilder<>(ObjectClassifier.class, classifier)
// .project(project)
// .savedName(savedName)
// .title("Object classifier")
// .build();
// }
private static boolean promptToSavePredictionImage(ImageData<BufferedImage> imageData, PixelClassifier classifier, String classifierName) {
    Objects.requireNonNull(imageData);
    Objects.requireNonNull(classifier);
    var server = PixelClassifierTools.createPixelClassificationServer(imageData, classifier);
    ImageWriter<BufferedImage> writer;
    var allWriters = ImageWriterTools.getCompatibleWriters(server, "ome.tif");
    if (allWriters == null || allWriters.isEmpty()) {
        allWriters = ImageWriterTools.getCompatibleWriters(server, null);
    }
    if (allWriters.isEmpty()) {
        Dialogs.showErrorMessage("Save prediction", "Sorry, I could not find any compatible image writers!");
        return false;
    } else if (allWriters.size() > 1) {
        Map<String, ImageWriter<BufferedImage>> map = new LinkedHashMap<>();
        for (var w : allWriters) map.put(w.getName(), w);
        var choice = Dialogs.showChoiceDialog("Save prediction", "Choose image writer", map.keySet(), map.keySet().iterator().next());
        writer = choice == null ? null : map.get(choice);
        if (writer == null)
            return false;
    } else
        writer = allWriters.iterator().next();
    var file = Dialogs.promptToSaveFile("Save prediction", null, classifierName, writer.getName(), writer.getDefaultExtension());
    if (file == null)
        return false;
    try {
        var path = file.getAbsolutePath();
        writer.writeImage(server, path);
        if (classifierName != null && !classifierName.isBlank()) {
            imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Write prediction image", String.format("writePredictionImage(\"%s\", \"%s\")", classifierName, path)));
        }
    } catch (IOException e) {
        Dialogs.showErrorMessage("Save prediction", e);
    }
    return true;
}
Also used : DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) IOException(java.io.IOException) Map(java.util.Map) LinkedHashMap(java.util.LinkedHashMap) BufferedImage(java.awt.image.BufferedImage)

Example 14 with DefaultScriptableWorkflowStep

use of qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep in project qupath by qupath.

the class PixelClassifierUI method promptToCreateObjects.

/**
 * Prompt the user to create objects directly from the pixels of an {@link ImageServer}.
 * Often, the {@link ImageServer} has been created by applying a {@link PixelClassifier}.
 *
 * @param imageData the {@link ImageData} to which objects should be added
 * @param classifier the {@link ImageServer} used to generate objects
 * @param classifierName the name of the classifier; if not null and the command runs to completion, it will be logged in the history
 * 						 workflow of the {@link ImageData} for later scripting.
 * @return true if changes were made, false otherwise
 */
public static boolean promptToCreateObjects(ImageData<BufferedImage> imageData, PixelClassifier classifier, String classifierName) {
    Objects.requireNonNull(imageData);
    Objects.requireNonNull(classifier);
    // Check what is selected
    List<SelectionChoice> choices = buildChoiceList(imageData.getHierarchy(), SelectionChoice.FULL_IMAGE, SelectionChoice.CURRENT_SELECTION, SelectionChoice.ANNOTATIONS, SelectionChoice.TMA);
    SelectionChoice defaultChoice;
    if (choices.contains(SelectionChoice.CURRENT_SELECTION))
        defaultChoice = SelectionChoice.CURRENT_SELECTION;
    else if (choices.contains(SelectionChoice.ANNOTATIONS))
        defaultChoice = SelectionChoice.ANNOTATIONS;
    else
        defaultChoice = choices.get(0);
    var parentChoice = Dialogs.showChoiceDialog("Pixel classifier", "Choose parent objects", choices, defaultChoice);
    if (parentChoice == null)
        return false;
    var outputObjectTypes = Arrays.asList("Annotation", "Detection");
    // To avoid confusing the user unnecessarily, if we *only* have ignored classes then set default for includeIgnored to true
    var labels = classifier.getMetadata().getClassificationLabels();
    boolean allIgnored = !labels.isEmpty() && labels.values().stream().allMatch(p -> p == null || PathClassTools.isIgnoredClass(p));
    boolean includeIgnored = allIgnored;
    var cal = imageData.getServer().getPixelCalibration();
    var units = cal.unitsMatch2D() ? cal.getPixelWidthUnit() + "^2" : cal.getPixelWidthUnit() + "x" + cal.getPixelHeightUnit();
    ParameterList params;
    if (lastCreateObjectParams != null) {
        params = lastCreateObjectParams.duplicate();
        params.setHiddenParameters(false, params.getKeyValueParameters(true).keySet().toArray(String[]::new));
        ((BooleanParameter) params.getParameters().get("includeIgnored")).setValue(includeIgnored);
    } else {
        params = new ParameterList().addChoiceParameter("objectType", "New object type", "Annotation", outputObjectTypes, "Define the type of objects that will be created").addDoubleParameter("minSize", "Minimum object size", 0, units, "Minimum size of a region to keep (smaller regions will be dropped)").addDoubleParameter("minHoleSize", "Minimum hole size", 0, units, "Minimum size of a hole to keep (smaller holes will be filled)").addBooleanParameter("doSplit", "Split objects", false, "Split multi-part regions into separate objects").addBooleanParameter("clearExisting", "Delete existing objects", false, "Delete any existing objects within the selected object before adding new objects (or entire image if no object is selected)").addBooleanParameter("includeIgnored", "Create objects for ignored classes", includeIgnored, "Create objects for classifications that are usually ignored (e.g. \"Ignore*\", \"Region*\")").addBooleanParameter("selectNew", "Set new objects to selected", false, "Set the newly-created objects to be selected");
    }
    if (!Dialogs.showParameterDialog("Create objects", params))
        return false;
    boolean createDetections = params.getChoiceParameterValue("objectType").equals("Detection");
    boolean doSplit = params.getBooleanParameterValue("doSplit");
    includeIgnored = params.getBooleanParameterValue("includeIgnored");
    double minSize = params.getDoubleParameterValue("minSize");
    double minHoleSize = params.getDoubleParameterValue("minHoleSize");
    boolean clearExisting = params.getBooleanParameterValue("clearExisting");
    boolean selectNew = params.getBooleanParameterValue("selectNew");
    lastCreateObjectParams = params;
    parentChoice.handleSelection(imageData);
    List<CreateObjectOptions> options = new ArrayList<>();
    if (doSplit)
        options.add(CreateObjectOptions.SPLIT);
    if (clearExisting)
        options.add(CreateObjectOptions.DELETE_EXISTING);
    if (includeIgnored)
        options.add(CreateObjectOptions.INCLUDE_IGNORED);
    else if (allIgnored) {
        Dialogs.showErrorMessage(title, "Cannot create objects - all class names have an asterisk to show they should be 'ignored'!");
        return false;
    }
    if (selectNew)
        options.add(CreateObjectOptions.SELECT_NEW);
    var optionsArray = options.toArray(CreateObjectOptions[]::new);
    String optionsString = "";
    if (!options.isEmpty())
        optionsString = ", " + options.stream().map(o -> "\"" + o.name() + "\"").collect(Collectors.joining(", "));
    try {
        if (createDetections) {
            if (PixelClassifierTools.createDetectionsFromPixelClassifier(imageData, classifier, minSize, minHoleSize, optionsArray)) {
                if (classifierName != null) {
                    imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Pixel classifier create detections", String.format("createDetectionsFromPixelClassifier(\"%s\", %s, %s)", classifierName, minSize, minHoleSize + optionsString)));
                }
                return true;
            }
        } else {
            if (PixelClassifierTools.createAnnotationsFromPixelClassifier(imageData, classifier, minSize, minHoleSize, optionsArray)) {
                if (classifierName != null) {
                    imageData.getHistoryWorkflow().addStep(new DefaultScriptableWorkflowStep("Pixel classifier create annotations", String.format("createAnnotationsFromPixelClassifier(\"%s\", %s, %s)", classifierName, minSize, minHoleSize + optionsString)));
                }
                return true;
            }
        }
    } catch (IOException e) {
        Dialogs.showErrorMessage(title, e);
    }
    return false;
}
Also used : Button(javafx.scene.control.Button) CreateObjectOptions(qupath.opencv.ml.pixel.PixelClassifierTools.CreateObjectOptions) Arrays(java.util.Arrays) ImageServer(qupath.lib.images.servers.ImageServer) PathTileObject(qupath.lib.objects.PathTileObject) BooleanBinding(javafx.beans.binding.BooleanBinding) CheckMenuItem(javafx.scene.control.CheckMenuItem) LoggerFactory(org.slf4j.LoggerFactory) Side(javafx.geometry.Side) ImageWriter(qupath.lib.images.writers.ImageWriter) ParameterList(qupath.lib.plugins.parameters.ParameterList) ComboBox(javafx.scene.control.ComboBox) ContextMenu(javafx.scene.control.ContextMenu) Map(java.util.Map) PixelClassifierTools(qupath.opencv.ml.pixel.PixelClassifierTools) Pane(javafx.scene.layout.Pane) MenuItem(javafx.scene.control.MenuItem) BufferedImage(java.awt.image.BufferedImage) Collectors(java.util.stream.Collectors) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathObject(qupath.lib.objects.PathObject) Objects(java.util.Objects) List(java.util.List) BooleanProperty(javafx.beans.property.BooleanProperty) Project(qupath.lib.projects.Project) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) GuiTools(qupath.lib.gui.tools.GuiTools) RegionFilter(qupath.lib.gui.viewer.RegionFilter) BorderPane(javafx.scene.layout.BorderPane) StringProperty(javafx.beans.property.StringProperty) PathCellObject(qupath.lib.objects.PathCellObject) SaveResourcePaneBuilder(qupath.process.gui.commands.ui.SaveResourcePaneBuilder) ObjectExpression(javafx.beans.binding.ObjectExpression) PathClassTools(qupath.lib.objects.classes.PathClassTools) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) Bindings(javafx.beans.binding.Bindings) ArrayList(java.util.ArrayList) StandardRegionFilters(qupath.lib.gui.viewer.RegionFilter.StandardRegionFilters) LinkedHashMap(java.util.LinkedHashMap) Dialogs(qupath.lib.gui.dialogs.Dialogs) Tooltip(javafx.scene.control.Tooltip) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) StringExpression(javafx.beans.binding.StringExpression) ImageWriterTools(qupath.lib.images.writers.ImageWriterTools) Commands(qupath.lib.gui.commands.Commands) IOException(java.io.IOException) TMACoreObject(qupath.lib.objects.TMACoreObject) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) SimpleBooleanProperty(javafx.beans.property.SimpleBooleanProperty) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) BooleanParameter(qupath.lib.plugins.parameters.BooleanParameter) Collections(java.util.Collections) PaneTools(qupath.lib.gui.tools.PaneTools) ArrayList(java.util.ArrayList) IOException(java.io.IOException) BooleanParameter(qupath.lib.plugins.parameters.BooleanParameter) DefaultScriptableWorkflowStep(qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep) ParameterList(qupath.lib.plugins.parameters.ParameterList) CreateObjectOptions(qupath.opencv.ml.pixel.PixelClassifierTools.CreateObjectOptions)

Example 15 with DefaultScriptableWorkflowStep

use of qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep 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)

Aggregations

DefaultScriptableWorkflowStep (qupath.lib.plugins.workflow.DefaultScriptableWorkflowStep)31 PathObject (qupath.lib.objects.PathObject)12 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)12 WorkflowStep (qupath.lib.plugins.workflow.WorkflowStep)12 ArrayList (java.util.ArrayList)10 List (java.util.List)10 Map (java.util.Map)10 Dialogs (qupath.lib.gui.dialogs.Dialogs)10 Collectors (java.util.stream.Collectors)9 QuPathGUI (qupath.lib.gui.QuPathGUI)9 BufferedImage (java.awt.image.BufferedImage)8 IOException (java.io.IOException)8 Arrays (java.util.Arrays)8 ImageData (qupath.lib.images.ImageData)8 File (java.io.File)7 StringProperty (javafx.beans.property.StringProperty)7 Logger (org.slf4j.Logger)7 LoggerFactory (org.slf4j.LoggerFactory)7 GeneralTools (qupath.lib.common.GeneralTools)7 Collections (java.util.Collections)6