Search in sources :

Example 1 with PixelClassifier

use of qupath.lib.classifiers.pixel.PixelClassifier 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 2 with PixelClassifier

use of qupath.lib.classifiers.pixel.PixelClassifier in project qupath by qupath.

the class SimpleThresholdCommand method updateClassification.

private void updateClassification() {
    // for (var viewer : qupath.getViewers()) {
    // var imageData = viewer.getImageData();
    // if (imageData == null) {
    // selectedOverlay.set(null);
    // viewer.resetCustomPixelLayerOverlay();
    // }
    // }
    var channel = selectedChannel.get();
    var thresholdValue = threshold.get();
    var resolution = selectedResolution.get();
    if (channel == null || thresholdValue == null || resolution == null) {
        resetOverlays();
        return;
    }
    var feature = selectedPrefilter.get();
    double sigmaValue = sigma.get();
    PixelClassifier classifier;
    List<ImageOp> ops = new ArrayList<>();
    if (feature != null && sigmaValue > 0) {
        ops.add(feature.buildOp(sigmaValue));
    }
    ops.add(ImageOps.Threshold.threshold(threshold.get()));
    Map<Integer, PathClass> classifications = new LinkedHashMap<>();
    classifications.put(0, classificationsBelow.getSelectionModel().getSelectedItem());
    classifications.put(1, classificationsAbove.getSelectionModel().getSelectedItem());
    var op = ImageOps.Core.sequential(ops);
    var transformer = ImageOps.buildImageDataOp(channel).appendOps(op);
    classifier = PixelClassifiers.createClassifier(transformer, resolution.getPixelCalibration(), classifications);
    // Create classifier
    var overlay = PixelClassificationOverlay.create(qupath.getOverlayOptions(), classifier);
    overlay.setLivePrediction(true);
    var previousOverlay = selectedOverlay.get();
    if (previousOverlay != null)
        previousOverlay.stop();
    selectedOverlay.set(overlay);
    this.currentClassifier.set(classifier);
    ensureOverlays();
}
Also used : PathClass(qupath.lib.objects.classes.PathClass) ImageOp(qupath.opencv.ops.ImageOp) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap)

Example 3 with PixelClassifier

use of qupath.lib.classifiers.pixel.PixelClassifier in project qupath by qupath.

the class QP method loadPixelClassifier.

/**
 * Load a pixel classifier for a project or file path.
 *
 * @param name the name of the classifier within the current project, or file path to a classifier to load from disk.
 * @return the requested {@link PixelClassifier}
 * @throws IllegalArgumentException if the classifier cannot be found
 */
public static PixelClassifier loadPixelClassifier(String name) throws IllegalArgumentException {
    var project = getProject();
    Exception exception = null;
    PixelClassifier pixelClassifier = null;
    if (project != null) {
        try {
            var pixelClassifiers = project.getPixelClassifiers();
            if (pixelClassifiers.contains(name))
                pixelClassifier = pixelClassifiers.get(name);
        } catch (Exception e) {
            exception = e;
            logger.debug("Pixel classifier '{}' not found in project", name);
        }
    }
    try {
        var path = Paths.get(name);
        if (Files.exists(path))
            pixelClassifier = PixelClassifiers.readClassifier(path);
    } catch (Exception e) {
        exception = e;
        logger.debug("Pixel classifier '{}' cannot be read from file", name);
    }
    if (pixelClassifier == null)
        throw new IllegalArgumentException("Unable to find pixel classifier " + name, exception);
    // Fix URIs if we need to
    if (pixelClassifier instanceof UriResource) {
        UriUpdater.fixUris((UriResource) pixelClassifier, project);
    }
    return pixelClassifier;
}
Also used : UriResource(qupath.lib.io.UriResource) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) IOException(java.io.IOException) FileNotFoundException(java.io.FileNotFoundException) NoSuchElementException(java.util.NoSuchElementException)

Example 4 with PixelClassifier

use of qupath.lib.classifiers.pixel.PixelClassifier in project qupath by qupath.

the class LoadResourceCommand method run.

@Override
public void run() {
    String title = resourceType.getDialogTitle();
    var cachedServers = new WeakHashMap<ImageData<BufferedImage>, ImageServer<BufferedImage>>();
    var overlay = PixelClassificationOverlay.create(qupath.getOverlayOptions(), cachedServers, new ColorModelRenderer(null));
    overlay.setMaxThreads(nThreads);
    for (var viewer : qupath.getViewers()) viewer.setCustomPixelLayerOverlay(overlay);
    var comboClassifiers = new ComboBox<String>();
    try {
        updateAvailableItems(comboClassifiers.getItems());
    } catch (IOException e) {
        logger.error(e.getLocalizedMessage(), e);
    }
    var selectedResource = Bindings.createObjectBinding(() -> {
        String name = comboClassifiers.getSelectionModel().getSelectedItem();
        cachedServers.clear();
        if (name != null) {
            try {
                var project = qupath.getProject();
                var manager = resourceType.getManager(project);
                S resource;
                if (manager != null && manager.contains(name))
                    resource = manager.get(name);
                else
                    resource = extras.get(name);
                if (resource instanceof UriResource) {
                    UriUpdater.fixUris((UriResource) resource, project);
                }
                return resource;
            } catch (Exception ex) {
                // TODO: Investigate why this is triggered twice
                Dialogs.showErrorNotification(resourceType.getDialogTitle(), ex);
            }
        }
        return null;
    }, comboClassifiers.getSelectionModel().selectedItemProperty());
    var label = new Label(resourceType.choosePrompt());
    label.setLabelFor(comboClassifiers);
    selectedResource.addListener((v, o, n) -> {
        cachedServers.clear();
        updateServers(n, cachedServers);
    });
    // Add file chooser
    var menu = new ContextMenu();
    var miLoadClassifier = new MenuItem("Import from files");
    miLoadClassifier.setOnAction(e -> {
        List<File> files = Dialogs.promptForMultipleFiles(title, null, resourceType.filePrompt(), "json");
        if (files == null || files.isEmpty())
            return;
        try {
            addExternalJson(files, resourceType.getManager(qupath.getProject()), extras);
            updateAvailableItems(comboClassifiers.getItems());
        } catch (IOException ex) {
            Dialogs.showErrorMessage(title, ex);
        }
    });
    var miOpenAsText = new MenuItem("Show as text");
    miOpenAsText.setOnAction(e -> {
        var name = comboClassifiers.getSelectionModel().getSelectedItem();
        var resource = selectedResource.get();
        if (resource == null)
            return;
        try {
            // Pass the resource class, since it can be required for including the appropriate JSON properties
            var json = GsonTools.getInstance(true).toJson(resource, resourceType.getResourceClass());
            if (!name.endsWith(".json"))
                name = name + ".json";
            // Show in script editor if possible; this may include better formatting and syntax highlighting
            var scriptEditor = qupath == null ? null : qupath.getScriptEditor();
            if (scriptEditor != null)
                scriptEditor.showScript(name, json);
            else
                Dialogs.showTextWindow(qupath.getStage(), name, json, Modality.NONE, false);
        } catch (Exception ex) {
            Dialogs.showErrorMessage("Show model as text", "Unable to create a text representation of '" + name + "', sorry!");
        }
    });
    miOpenAsText.disableProperty().bind(selectedResource.isNull());
    // Enable setting number of threads
    var miThreads = new MenuItem("Set parallel threads");
    miThreads.setOnAction(e -> {
        var params = new ParameterList().addIntParameter("nThreads", "Number of parallel threads", nThreads, null, "Number of threads to use for live prediction");
        if (!Dialogs.showParameterDialog("Set parallel threads", params))
            return;
        // var result = Dialogs.showInputDialog("Set parallel threads", "Number of threads to use for live prediction", (double)nThreads);
        var val = params.getIntParameterValue("nThreads");
        if (val == nThreads)
            return;
        if (val < 0)
            nThreads = PathPrefs.numCommandThreadsProperty().get();
        else
            nThreads = Math.max(1, val);
        if (overlay != null)
            overlay.setMaxThreads(nThreads);
    });
    menu.getItems().addAll(miLoadClassifier, miOpenAsText, new SeparatorMenuItem(), miThreads);
    var btnLoadExistingClassifier = GuiTools.createMoreButton(menu, Side.RIGHT);
    var classifierName = new SimpleStringProperty(null);
    classifierName.bind(comboClassifiers.getSelectionModel().selectedItemProperty());
    var pane = new GridPane();
    pane.setPadding(new Insets(10.0));
    pane.setHgap(5);
    pane.setVgap(10);
    pane.setPrefWidth(350.0);
    int row = 0;
    PaneTools.addGridRow(pane, row++, 0, "Choose model to apply to the current image", label, comboClassifiers, btnLoadExistingClassifier);
    PaneTools.setToExpandGridPaneWidth(comboClassifiers);
    if (resourceType.getResourceClass().equals(PixelClassifier.class)) {
        var labelRegion = new Label("Region");
        var comboRegionFilter = PixelClassifierUI.createRegionFilterCombo(qupath.getOverlayOptions());
        @SuppressWarnings("unchecked") var tilePane = PixelClassifierUI.createPixelClassifierButtons(qupath.imageDataProperty(), (ObjectExpression<PixelClassifier>) selectedResource, classifierName);
        PaneTools.addGridRow(pane, row++, 0, "Control where the pixel classification is applied during preview", labelRegion, comboRegionFilter, comboRegionFilter);
        PaneTools.addGridRow(pane, row++, 0, "Apply pixel classification", tilePane, tilePane, tilePane);
        PaneTools.setToExpandGridPaneWidth(tilePane);
    } else if (resourceType.getResourceClass().equals(DensityMapBuilder.class)) {
        @SuppressWarnings("unchecked") var buttonPane = DensityMapUI.createButtonPane(qupath, qupath.imageDataProperty(), (ObjectExpression<DensityMapBuilder>) selectedResource, classifierName, Bindings.createObjectBinding(() -> overlay), false);
        PaneTools.addGridRow(pane, row++, 0, null, buttonPane, buttonPane, buttonPane);
        PaneTools.setToExpandGridPaneWidth(buttonPane);
    }
    // Handle drag and drop
    pane.setOnDragOver(e -> {
        e.acceptTransferModes(TransferMode.COPY);
        e.consume();
    });
    pane.setOnDragDropped(e -> {
        logger.trace("File(s) dragged onto pane");
        Dragboard dragboard = e.getDragboard();
        if (dragboard.hasFiles()) {
            try {
                addExternalJson(dragboard.getFiles(), resourceType.getManager(qupath.getProject()), extras);
                updateAvailableItems(comboClassifiers.getItems());
            } catch (Exception ex) {
                Dialogs.showErrorMessage(title, ex);
            }
        }
    });
    var stage = new Stage();
    stage.setTitle(title);
    stage.setScene(new Scene(pane));
    stage.initOwner(qupath.getStage());
    stage.sizeToScene();
    stage.setResizable(false);
    stage.show();
    stage.setOnHiding(e -> {
        if (overlay != null)
            overlay.stop();
        logger.debug("Resetting overlay");
        for (var viewer : qupath.getViewers()) {
            if (viewer.getCustomPixelLayerOverlay() == overlay)
                viewer.resetCustomPixelLayerOverlay();
        }
    });
    stage.focusedProperty().addListener((v, o, n) -> {
        if (n && overlay != null) {
            for (var viewer : qupath.getViewers()) viewer.setCustomPixelLayerOverlay(overlay);
        }
        // Make sure we have all the servers we need - but don't reset existing ones
        updateServers(selectedResource.get(), cachedServers);
    });
    overlay.setLivePrediction(true);
}
Also used : UriResource(qupath.lib.io.UriResource) Insets(javafx.geometry.Insets) Label(javafx.scene.control.Label) ContextMenu(javafx.scene.control.ContextMenu) BufferedImage(java.awt.image.BufferedImage) Stage(javafx.stage.Stage) Dragboard(javafx.scene.input.Dragboard) GridPane(javafx.scene.layout.GridPane) ComboBox(javafx.scene.control.ComboBox) DensityMapBuilder(qupath.lib.analysis.heatmaps.DensityMaps.DensityMapBuilder) MenuItem(javafx.scene.control.MenuItem) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) IOException(java.io.IOException) SimpleStringProperty(javafx.beans.property.SimpleStringProperty) SeparatorMenuItem(javafx.scene.control.SeparatorMenuItem) Scene(javafx.scene.Scene) IOException(java.io.IOException) ColorModelRenderer(qupath.lib.gui.images.stores.ColorModelRenderer) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) ParameterList(qupath.lib.plugins.parameters.ParameterList) File(java.io.File) ObjectExpression(javafx.beans.binding.ObjectExpression) WeakHashMap(java.util.WeakHashMap)

Aggregations

PixelClassifier (qupath.lib.classifiers.pixel.PixelClassifier)4 IOException (java.io.IOException)3 BufferedImage (java.awt.image.BufferedImage)2 ArrayList (java.util.ArrayList)2 LinkedHashMap (java.util.LinkedHashMap)2 ObjectExpression (javafx.beans.binding.ObjectExpression)2 ComboBox (javafx.scene.control.ComboBox)2 ContextMenu (javafx.scene.control.ContextMenu)2 MenuItem (javafx.scene.control.MenuItem)2 File (java.io.File)1 FileNotFoundException (java.io.FileNotFoundException)1 Arrays (java.util.Arrays)1 Collections (java.util.Collections)1 List (java.util.List)1 Map (java.util.Map)1 NoSuchElementException (java.util.NoSuchElementException)1 Objects (java.util.Objects)1 WeakHashMap (java.util.WeakHashMap)1 Collectors (java.util.stream.Collectors)1 Bindings (javafx.beans.binding.Bindings)1