Search in sources :

Example 6 with PathAnnotationObject

use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.

the class BrushTool method getUpdatedObject.

private PathObject getUpdatedObject(MouseEvent e, ROI shapeROI, PathObject currentObject, double flatness) {
    Point2D p = mouseLocationToImage(e, true, requestPixelSnapping());
    var viewer = getViewer();
    ImagePlane plane = shapeROI == null ? ImagePlane.getPlane(viewer.getZPosition(), viewer.getTPosition()) : shapeROI.getImagePlane();
    Geometry shapeNew;
    boolean subtractMode = isSubtractMode(e);
    Geometry shapeCurrent = shapeROI == null ? null : shapeROI.getGeometry();
    Geometry shapeDrawn = createShape(e, p.getX(), p.getY(), PathPrefs.useTileBrushProperty().get() && !e.isShiftDown(), subtractMode ? null : shapeCurrent);
    if (shapeDrawn == null)
        return currentObject;
    // Do our pixel snapping now, with the simpler geometry (rather than latter when things are already complex)
    if (requestPixelSnapping())
        shapeDrawn = GeometryTools.roundCoordinates(shapeDrawn);
    lastPoint = p;
    try {
        if (shapeROI != null) {
            // Check to see if any changes are required at all
            if (shapeDrawn == null || (subtractMode && !shapeCurrent.intersects(shapeDrawn)) || (!subtractMode && shapeCurrent.covers(shapeDrawn)))
                return currentObject;
            // TODO: Consider whether a preference should be used rather than the shift key?
            // Anyhow, this will switch to 'dodge' mode, and avoid overlapping existing annotations
            boolean avoidOtherAnnotations = requestParentClipping(e);
            if (subtractMode) {
                // If subtracting... then just subtract
                shapeNew = shapeROI.getGeometry().difference(shapeDrawn);
            } else if (avoidOtherAnnotations) {
                shapeNew = shapeCurrent.union(shapeDrawn);
                shapeNew = refineGeometryByParent(shapeNew);
            } else {
                // Just add, regardless of whether there are other annotations below or not
                var temp = shapeROI.getGeometry();
                try {
                    shapeNew = temp.union(shapeDrawn);
                } catch (Exception e2) {
                    shapeNew = shapeROI.getGeometry();
                }
            }
        } else {
            shapeNew = shapeDrawn;
        }
        // If we aren't snapping, at least remove some vertices
        if (!requestPixelSnapping()) {
            try {
                shapeNew = VWSimplifier.simplify(shapeNew, 0.1);
            } catch (Exception e2) {
                logger.error("Error simplifying ROI: " + e2.getLocalizedMessage(), e2);
            }
        }
        // Make sure we fit inside the image
        shapeNew = GeometryTools.constrainToBounds(shapeNew, 0, 0, viewer.getServerWidth(), viewer.getServerHeight());
        // Sometimes we can end up with a GeometryCollection containing lines/non-areas... if so, remove these
        if (shapeNew instanceof GeometryCollection) {
            shapeNew = GeometryTools.ensurePolygonal(shapeNew);
        }
        ROI roiNew = GeometryTools.geometryToROI(shapeNew, plane);
        if (currentObject instanceof PathAnnotationObject) {
            ((PathAnnotationObject) currentObject).setROI(roiNew);
            return currentObject;
        }
        // shapeNew = new PathAreaROI(new Area(shapeNew.getShape()));
        PathObject pathObjectNew = PathObjects.createAnnotationObject(roiNew, PathPrefs.autoSetAnnotationClassProperty().get());
        if (currentObject != null) {
            pathObjectNew.setName(currentObject.getName());
            pathObjectNew.setColorRGB(currentObject.getColorRGB());
            pathObjectNew.setPathClass(currentObject.getPathClass());
        }
        return pathObjectNew;
    } catch (Exception ex) {
        logger.error("Error updating ROI", ex);
        return currentObject;
    }
}
Also used : Geometry(org.locationtech.jts.geom.Geometry) GeometryCollection(org.locationtech.jts.geom.GeometryCollection) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) Point2D(java.awt.geom.Point2D) ImagePlane(qupath.lib.regions.ImagePlane) RectangleROI(qupath.lib.roi.RectangleROI) ROI(qupath.lib.roi.interfaces.ROI)

Example 7 with PathAnnotationObject

use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.

the class MoveTool method mouseReleased.

@Override
public void mouseReleased(MouseEvent e) {
    super.mouseReleased(e);
    if (e.isConsumed())
        return;
    var viewer = getViewer();
    RoiEditor editor = viewer.getROIEditor();
    if (editor != null && (editor.hasActiveHandle() || editor.isTranslating())) {
        boolean roiChanged = (editor.isTranslating() && editor.finishTranslation()) || editor.hasActiveHandle();
        editor.resetActiveHandle();
        // if (editor.isTranslating())
        // editor.finishTranslation();
        e.consume();
        PathObject pathObject = viewer.getSelectedObject();
        if (requestParentClipping(e) && pathObject instanceof PathAnnotationObject) {
            ROI roiNew = refineROIByParent(pathObject.getROI());
            ((PathAnnotationObject) pathObject).setROI(roiNew);
        }
        if (pathObject != null && pathObject.hasROI() && pathObject.getROI().isEmpty()) {
            if (pathObject.getParent() != null)
                viewer.getHierarchy().removeObject(pathObject, true);
            viewer.setSelectedObject(null);
        } else {
            PathObjectHierarchy hierarchy = viewer.getHierarchy();
            if (pathObject instanceof TMACoreObject) {
                hierarchy.fireHierarchyChangedEvent(pathObject);
            } else if (pathObject != null) {
                // Handle ROI changes only if required
                if (roiChanged) {
                    var updatedROI = editor.getROI();
                    if (pathObject.getROI() != updatedROI && pathObject instanceof PathROIObject)
                        ((PathROIObject) pathObject).setROI(updatedROI);
                    // PathObject parentPrevious = pathObject.getParent();
                    hierarchy.removeObjectWithoutUpdate(pathObject, true);
                    if (getCurrentParent() == null || !PathPrefs.clipROIsForHierarchyProperty().get() || e.isShiftDown())
                        hierarchy.addPathObject(pathObject);
                    else
                        hierarchy.addPathObjectBelowParent(getCurrentParent(), pathObject, true);
                // PathObject parentNew = pathObject.getParent();
                // if (parentPrevious == parentNew)
                // hierarchy.fireHierarchyChangedEvent(this, parentPrevious);
                // else
                // hierarchy.fireHierarchyChangedEvent(this);
                }
            }
            viewer.setSelectedObject(pathObject);
        }
    }
    // Optionally continue a dragging movement until the canvas comes to a standstill
    if (pDragging != null && requestDynamicDragging && System.currentTimeMillis() - lastDragTimestamp < 100 && (dx * dx + dy * dy > viewer.getDownsampleFactor())) {
        mover = new ViewerMover(viewer);
        mover.startMoving(dx, dy, false);
    } else
        viewer.setDoFasterRepaint(false);
    // Make sure we don't have a previous point (to prevent weird dragging artefacts)
    pDragging = null;
// // If we were translating, stop
// if (editor.isTranslating()) {
// editor.finishTranslation();
// // TODO: Make this more efficient!
// viewer.getPathObjectHierarchy().fireHierarchyChangedEvent();
// return;
// }
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) RoiEditor(qupath.lib.roi.RoiEditor) TMACoreObject(qupath.lib.objects.TMACoreObject) PathROIObject(qupath.lib.objects.PathROIObject) ROI(qupath.lib.roi.interfaces.ROI)

Example 8 with PathAnnotationObject

use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.

the class ImageJMacroRunner method runPlugin.

@Override
public boolean runPlugin(final PluginRunner<BufferedImage> runner, final String arg) {
    if (!parseArgument(runner.getImageData(), arg))
        return false;
    if (dialog == null) {
        dialog = new Stage();
        dialog.initOwner(qupath.getStage());
        dialog.setTitle("ImageJ macro runner");
        BorderPane pane = new BorderPane();
        if (arg != null)
            macroText = arg;
        // Create text area
        final TextArea textArea = new TextArea();
        textArea.setPrefRowCount(12);
        textArea.setPrefSize(400, 400);
        textArea.setWrapText(true);
        textArea.setFont(Font.font("Courier"));
        if (macroText != null)
            textArea.setText(macroText);
        BorderPane panelMacro = new BorderPane();
        // panelMacro.setBorder(BorderFactory.createTitledBorder("Macro"));
        panelMacro.setCenter(textArea);
        ParameterPanelFX parameterPanel = new ParameterPanelFX(getParameterList(runner.getImageData()));
        panelMacro.setBottom(parameterPanel.getPane());
        // Create button panel
        Button btnRun = new Button("Run");
        btnRun.setOnAction(e -> {
            macroText = textArea.getText().trim();
            if (macroText.length() == 0)
                return;
            PathObjectHierarchy hierarchy = getHierarchy(runner);
            PathObject pathObject = hierarchy.getSelectionModel().singleSelection() ? hierarchy.getSelectionModel().getSelectedObject() : null;
            if (pathObject instanceof PathAnnotationObject || pathObject instanceof TMACoreObject) {
                SwingUtilities.invokeLater(() -> {
                    runMacro(params, qupath.getViewer().getImageData(), qupath.getViewer().getImageDisplay(), pathObject, macroText);
                });
            } else {
                // DisplayHelpers.showErrorMessage(getClass().getSimpleName(), "Sorry, ImageJ macros can only be run for single selected images");
                // logger.warn("ImageJ macro being run in current thread");
                // runPlugin(runner, arg); // TODO: Consider running in a background thread?
                // Run in a background thread
                Collection<? extends PathObject> parents = getParentObjects(runner);
                if (parents.isEmpty()) {
                    Dialogs.showErrorMessage("ImageJ macro runner", "No annotation or TMA core objects selected!");
                    return;
                }
                List<Runnable> tasks = new ArrayList<>();
                for (PathObject parent : parents) addRunnableTasks(qupath.getViewer().getImageData(), parent, tasks);
                qupath.submitShortTask(() -> runner.runTasks(tasks, true));
            // runner.runTasks(tasks);
            // Runnable r = new Runnable() {
            // public void run() {
            // runPlugin(runner, arg);
            // }
            // };
            // new Thread(r).start();
            }
        });
        Button btnClose = new Button("Close");
        btnClose.setOnAction(e -> dialog.hide());
        GridPane panelButtons = PaneTools.createRowGridControls(btnRun, btnClose);
        pane.setCenter(panelMacro);
        pane.setBottom(panelButtons);
        panelButtons.setPadding(new Insets(5, 0, 0, 0));
        pane.setPadding(new Insets(10, 10, 10, 10));
        dialog.setScene(new Scene(pane));
    }
    dialog.show();
    return true;
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) BorderPane(javafx.scene.layout.BorderPane) GridPane(javafx.scene.layout.GridPane) Insets(javafx.geometry.Insets) TextArea(javafx.scene.control.TextArea) TMACoreObject(qupath.lib.objects.TMACoreObject) ArrayList(java.util.ArrayList) Scene(javafx.scene.Scene) ParameterPanelFX(qupath.lib.gui.dialogs.ParameterPanelFX) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) Button(javafx.scene.control.Button) Stage(javafx.stage.Stage)

Example 9 with PathAnnotationObject

use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.

the class IntensityFeaturesPlugin method processObject.

static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageData<BufferedImage> imageData) throws IOException {
    // Determine amount to downsample
    var server = imageData.getServer();
    var stains = imageData.getColorDeconvolutionStains();
    PixelCalibration cal = server.getPixelCalibration();
    double downsample = calculateDownsample(cal, params);
    if (downsample <= 0) {
        logger.warn("Effective downsample must be > 0 (requested value {})", downsample);
    }
    // Determine region shape
    RegionType regionType = (RegionType) params.getChoiceParameterValue("region");
    // Try to get ROI
    boolean useROI = regionType == RegionType.ROI || regionType == RegionType.NUCLEUS;
    ROI roi = null;
    if (regionType == RegionType.NUCLEUS) {
        if (pathObject instanceof PathCellObject)
            roi = ((PathCellObject) pathObject).getNucleusROI();
    } else
        roi = pathObject.getROI();
    // pathROI = ((PathCellObject)pathObject).getNucleusROI();
    if (roi == null)
        return false;
    // Create a map - this is useful for occasions when tiling is needed
    Map<FeatureColorTransform, List<FeatureComputer>> map = new LinkedHashMap<>();
    if (server.isRGB()) {
        for (FeatureColorTransform transform : FeatureColorTransformEnum.values()) {
            List<FeatureComputer> list = new ArrayList<>();
            map.put(transform, list);
            for (FeatureComputerBuilder builder : builders) {
                list.add(builder.build());
            }
        }
    } else {
        for (FeatureColorTransform transform : getBasicChannelTransforms(server.nChannels())) {
            List<FeatureComputer> list = new ArrayList<>();
            map.put(transform, list);
            for (FeatureComputerBuilder builder : builders) {
                list.add(builder.build());
            }
        }
    }
    String prefix = getDiameterString(server, params);
    // Create tiled ROIs, if required
    ImmutableDimension sizePreferred = ImmutableDimension.getInstance((int) (2000 * downsample), (int) (2000 * downsample));
    // ImmutableDimension sizePreferred = new ImmutableDimension((int)(200*downsample), (int)(200*downsample));
    Collection<? extends ROI> rois = RoiTools.computeTiledROIs(roi, sizePreferred, sizePreferred, false, 0);
    if (rois.size() > 1)
        logger.info("Splitting {} into {} tiles for intensity measurements", roi, rois.size());
    for (ROI pathROI : rois) {
        if (Thread.currentThread().isInterrupted()) {
            logger.warn("Measurement skipped - thread interrupted!");
            return false;
        }
        // Get bounds
        RegionRequest region;
        if (useROI) {
            region = RegionRequest.createInstance(server.getPath(), downsample, pathROI);
        } else {
            ImmutableDimension size = getPreferredTileSizePixels(server, params);
            // RegionRequest region = RegionRequest.createInstance(server.getPath(), downsample, (int)(pathROI.getCentroidX() + .5) - size.width/2, (int)(pathROI.getCentroidY() + .5) - size.height/2, size.width, size.height, pathROI.getT(), pathROI.getZ());
            // Try to align with pixel boundaries according to the downsample being used - otherwise, interpolation can cause some strange, pattern artefacts
            int xStart = (int) (Math.round(pathROI.getCentroidX() / downsample) * downsample) - size.width / 2;
            int yStart = (int) (Math.round(pathROI.getCentroidY() / downsample) * downsample) - size.height / 2;
            int width = Math.min(server.getWidth(), xStart + size.width) - xStart;
            int height = Math.min(server.getHeight(), yStart + size.height) - yStart;
            region = RegionRequest.createInstance(server.getPath(), downsample, xStart, yStart, width, height, pathROI.getT(), pathROI.getZ());
        }
        // // Check image large enough to do *anything* of value
        // if (region.getWidth() / downsample < 1 || region.getHeight() / downsample < 1) {
        // logger.trace("Requested region is too small! {}", region);
        // return false;
        // }
        // System.out.println(bounds);
        // System.out.println("Size: " + size);
        BufferedImage img = server.readBufferedImage(region);
        if (img == null) {
            logger.error("Could not read image - unable to compute intensity features for {}", pathObject);
            return false;
        }
        // Create mask ROI if necessary
        // If we just have 1 pixel, we want to use it so that the mean/min/max measurements are valid (even if nothing else is)
        byte[] maskBytes = null;
        if (useROI && img.getWidth() * img.getHeight() > 1) {
            BufferedImage imgMask = BufferedImageTools.createROIMask(img.getWidth(), img.getHeight(), pathROI, region);
            maskBytes = ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData();
        }
        boolean isRGB = server.isRGB();
        List<FeatureColorTransform> transforms;
        if (isRGB)
            transforms = Arrays.asList(FeatureColorTransformEnum.values());
        else
            transforms = getBasicChannelTransforms(server.nChannels());
        int w = img.getWidth();
        int h = img.getHeight();
        int[] rgbBuffer = isRGB ? img.getRGB(0, 0, w, h, null, 0, w) : null;
        float[] pixels = null;
        for (FeatureColorTransform transform : transforms) {
            // Check if the color transform is requested
            if (params.containsKey(transform.getKey()) && Boolean.TRUE.equals(params.getBooleanParameterValue(transform.getKey()))) {
                // Transform the pixels
                pixels = transform.getTransformedPixels(img, rgbBuffer, stains, pixels);
                // Create the simple image
                SimpleModifiableImage pixelImage = SimpleImages.createFloatImage(pixels, w, h);
                // Apply any arbitrary mask
                if (maskBytes != null) {
                    for (int i = 0; i < pixels.length; i++) {
                        if (maskBytes[i] == (byte) 0)
                            pixelImage.setValue(i % w, i / w, Float.NaN);
                    }
                } else if (regionType == RegionType.CIRCLE) {
                    // Apply circular tile mask
                    double cx = (w - 1) / 2;
                    double cy = (h - 1) / 2;
                    double radius = Math.max(w, h) * .5;
                    double distThreshold = radius * radius;
                    for (int y = 0; y < h; y++) {
                        for (int x = 0; x < w; x++) {
                            if ((cx - x) * (cx - x) + (cy - y) * (cy - y) > distThreshold)
                                pixelImage.setValue(x, y, Float.NaN);
                        }
                    }
                }
                // Do the computations
                for (FeatureComputer computer : map.get(transform)) {
                    computer.updateFeatures(pixelImage, transform, params);
                }
            }
        }
    }
    // Add measurements to the parent object
    for (Entry<FeatureColorTransform, List<FeatureComputer>> entry : map.entrySet()) {
        String name = prefix + ": " + entry.getKey().getName(imageData, false) + ":";
        for (FeatureComputer computer : entry.getValue()) computer.addMeasurements(pathObject, name, params);
    }
    pathObject.getMeasurementList().close();
    // Lock any measurements that require it
    if (pathObject instanceof PathAnnotationObject)
        ((PathAnnotationObject) pathObject).setLocked(true);
    else if (pathObject instanceof TMACoreObject)
        ((TMACoreObject) pathObject).setLocked(true);
    return true;
}
Also used : ArrayList(java.util.ArrayList) BufferedImage(java.awt.image.BufferedImage) LinkedHashMap(java.util.LinkedHashMap) HaralickFeatureComputer(qupath.lib.analysis.features.HaralickFeatureComputer) SimpleModifiableImage(qupath.lib.analysis.images.SimpleModifiableImage) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) ParameterList(qupath.lib.plugins.parameters.ParameterList) List(java.util.List) TMACoreObject(qupath.lib.objects.TMACoreObject) PixelCalibration(qupath.lib.images.servers.PixelCalibration) ROI(qupath.lib.roi.interfaces.ROI) ImmutableDimension(qupath.lib.geom.ImmutableDimension) RegionRequest(qupath.lib.regions.RegionRequest) PathCellObject(qupath.lib.objects.PathCellObject)

Example 10 with PathAnnotationObject

use of qupath.lib.objects.PathAnnotationObject in project qupath by qupath.

the class PathObjectListCell method updateTooltip.

void updateTooltip(final PathObject pathObject) {
    if (tooltip == null) {
        if (pathObject == null || !pathObject.isAnnotation())
            return;
        tooltip = new Tooltip();
        label.setTooltip(tooltip);
    } else if (pathObject == null || !pathObject.isAnnotation()) {
        label.setTooltip(null);
        return;
    }
    PathAnnotationObject annotation = (PathAnnotationObject) pathObject;
    String description = annotation.getDescription();
    if (description == null)
        description = label.getText();
    if (description == null) {
        label.setTooltip(null);
    } else {
        tooltip.setText(description);
        label.setTooltip(tooltip);
    }
}
Also used : PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) Tooltip(javafx.scene.control.Tooltip)

Aggregations

PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)19 PathObject (qupath.lib.objects.PathObject)16 ArrayList (java.util.ArrayList)11 ROI (qupath.lib.roi.interfaces.ROI)11 TMACoreObject (qupath.lib.objects.TMACoreObject)9 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)7 BufferedImage (java.awt.image.BufferedImage)6 List (java.util.List)6 TextArea (javafx.scene.control.TextArea)5 Tooltip (javafx.scene.control.Tooltip)5 IOException (java.io.IOException)4 Collections (java.util.Collections)4 Collectors (java.util.stream.Collectors)4 Point2D (java.awt.geom.Point2D)3 File (java.io.File)3 Collection (java.util.Collection)3 LinkedHashMap (java.util.LinkedHashMap)3 Bindings (javafx.beans.binding.Bindings)3 SimpleBooleanProperty (javafx.beans.property.SimpleBooleanProperty)3 Scene (javafx.scene.Scene)3