Search in sources :

Example 76 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class PointsTool method mousePressed.

@Override
public void mousePressed(MouseEvent e) {
    super.mousePressed(e);
    if (!e.isPrimaryButtonDown() || e.isConsumed()) {
        return;
    }
    // Get a server, if we can
    var viewer = getViewer();
    ImageServer<?> server = viewer.getServer();
    if (server == null)
        return;
    var viewerPlane = viewer.getImagePlane();
    // Find out the coordinates in the image domain
    Point2D p = mouseLocationToImage(e, false, requestPixelSnapping());
    double xx = p.getX();
    double yy = p.getY();
    // If we are outside the image, ignore click
    if (xx < 0 || yy < 0 || xx >= server.getWidth() || yy >= server.getHeight())
        return;
    // See if we have a selected ROI
    PathObject currentObjectTemp = viewer.getSelectedObject();
    if (!(currentObjectTemp == null || currentObjectTemp instanceof PathROIObject))
        return;
    PathROIObject currentObject = (PathROIObject) currentObjectTemp;
    ROI currentROI = currentObject == null ? null : currentObject.getROI();
    RoiEditor editor = viewer.getROIEditor();
    double radius = PathPrefs.pointRadiusProperty().get();
    ROI points = null;
    if (currentROI != null && currentROI.isPoint() && (currentROI.isEmpty() || currentROI.getImagePlane().equals(viewerPlane)))
        points = currentROI;
    // If Alt is pressed, try to delete a point
    if (e.isAltDown()) {
        handleAltClick(xx, yy, currentObject);
    } else // Create a new ROI if we've got Alt & Shift pressed - or we just don't have a point ROI
    if (points == null || (!PathPrefs.multipointToolProperty().get() && !editor.grabHandle(xx, yy, radius, e.isShiftDown())) || (e.isShiftDown() && e.getClickCount() > 1)) {
        // PathPoints is effectively ready from the start - don't need to finalize
        points = ROIs.createPointsROI(xx, yy, viewerPlane);
        currentObject = (PathROIObject) PathObjects.createAnnotationObject(points, PathPrefs.autoSetAnnotationClassProperty().get());
        viewer.getHierarchy().addPathObject(currentObject);
        viewer.setSelectedObject(currentObject);
        // viewer.createAnnotationObject(points);
        editor.setROI(points);
        editor.grabHandle(xx, yy, radius, e.isShiftDown());
    } else if (points != null) {
        // Add point to current ROI, or adjust the position of a nearby point
        ImagePlane plane = points == null || points.isEmpty() ? viewerPlane : points.getImagePlane();
        ROI points2 = addPoint(points, xx, yy, radius, plane);
        if (points2 == points) {
            // If we didn't add a point, try to grab a handle
            if (!editor.grabHandle(xx, yy, radius, e.isShiftDown()))
                return;
            points2 = (PointsROI) editor.setActiveHandlePosition(xx, yy, 0.25, e.isShiftDown());
        } else {
            editor.setROI(points2);
            editor.grabHandle(xx, yy, radius, e.isShiftDown());
        }
        if (points2 != points) {
            currentObject.setROI(points2);
            viewer.getHierarchy().updateObject(currentObject, true);
        // viewer.getHierarchy().fireHierarchyChangedEvent(this, currentObject);
        }
    }
    viewer.repaint();
}
Also used : PathObject(qupath.lib.objects.PathObject) RoiEditor(qupath.lib.roi.RoiEditor) Point2D(java.awt.geom.Point2D) ImagePlane(qupath.lib.regions.ImagePlane) PathROIObject(qupath.lib.objects.PathROIObject) ROI(qupath.lib.roi.interfaces.ROI) PointsROI(qupath.lib.roi.PointsROI)

Example 77 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class DensityMaps method findHotspots.

/**
 * Find hotspots in a density map.
 *
 * @param hierarchy hierarchy used to obtain selected objects and add hotspots
 * @param densityServer the density map to query
 * @param channel channel in which to find hotspots (usually 0)
 * @param nHotspots maximum number of hotspots to find per selected annotation
 * @param radius hotspot radius, in calibrated units
 * @param minCount minimum value required in the 'count' channel (the last channel)
 * @param hotspotClass the classification to apply to hotspots
 * @param deleteExisting optionally delete existing annotations identified as hotspots
 * @param peaksOnly optionally restrict hotspots to only include intensity peaks
 * @throws IOException
 */
public static void findHotspots(PathObjectHierarchy hierarchy, ImageServer<BufferedImage> densityServer, int channel, int nHotspots, double radius, double minCount, PathClass hotspotClass, boolean deleteExisting, boolean peaksOnly) throws IOException {
    if (nHotspots <= 0) {
        logger.warn("Number of hotspots requested is {}!", nHotspots);
        return;
    }
    logger.debug("Finding {} hotspots in {} for channel {}, radius {}", nHotspots, densityServer, channel, radius);
    Collection<PathObject> parents = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
    if (parents.isEmpty())
        parents = Collections.singleton(hierarchy.getRootObject());
    double downsample = densityServer.getDownsampleForResolution(0);
    var toDelete = new HashSet<PathObject>();
    // Handle deleting existing hotspots
    if (deleteExisting) {
        toDelete.addAll(hierarchy.getAnnotationObjects().stream().filter(p -> p.getPathClass() == hotspotClass && p.isAnnotation() && p.getName() != null && p.getName().startsWith("Hotspot")).collect(Collectors.toList()));
    }
    // Convert radius to pixels
    double radiusPixels = radius / densityServer.getPixelCalibration().getAveragedPixelSize().doubleValue();
    try (@SuppressWarnings("unchecked") var scope = new PointerScope()) {
        for (var parent : parents) {
            ROI roi = parent.getROI();
            // We need a ROI to define the area of interest
            if (roi == null) {
                if (densityServer.nTimepoints() > 1 || densityServer.nZSlices() > 1) {
                    logger.warn("Hotspot detection without a parent object not supported for images with multiple z-slices/timepoints.");
                    logger.warn("I will apply detection to the first plane only. If you need hotspots elsewhere, create an annotation first and use it to define the ROI.");
                }
                roi = ROIs.createRectangleROI(0, 0, densityServer.getWidth(), densityServer.getHeight(), ImagePlane.getDefaultPlane());
            }
            // Erode the ROI & see if any hotspot could fit
            var roiEroded = RoiTools.buffer(roi, -radiusPixels);
            if (roiEroded.isEmpty() || roiEroded.getArea() == 0) {
                logger.warn("ROI is too small! Cannot detected hotspots with radius {} in {}", radius, parent);
                continue;
            }
            // Read the image
            var plane = roi.getImagePlane();
            RegionRequest request = RegionRequest.createInstance(densityServer.getPath(), downsample, 0, 0, densityServer.getWidth(), densityServer.getHeight(), plane.getZ(), plane.getT());
            var img = densityServer.readBufferedImage(request);
            // Create a mask
            var imgMask = BufferedImageTools.createROIMask(img.getWidth(), img.getHeight(), roiEroded, request);
            // Switch to OpenCV
            var mat = OpenCVTools.imageToMat(img);
            var matMask = OpenCVTools.imageToMat(imgMask);
            // Find hotspots
            var channels = OpenCVTools.splitChannels(mat);
            var density = channels.get(channel);
            if (minCount > 0) {
                var thresholdMask = opencv_core.greaterThan(channels.get(channels.size() - 1), minCount).asMat();
                opencv_core.bitwise_and(matMask, thresholdMask, matMask);
                thresholdMask.close();
            }
            // TODO: Limit to peaks
            if (peaksOnly) {
                var matMaxima = OpenCVTools.findRegionalMaxima(density);
                var matPeaks = OpenCVTools.shrinkLabels(matMaxima);
                matPeaks.put(opencv_core.greaterThan(matPeaks, 0));
                opencv_core.bitwise_and(matMask, matPeaks, matMask);
                matPeaks.close();
                matMaxima.close();
            }
            // Sort in descending order
            var maxima = new ArrayList<>(OpenCVTools.getMaskedPixels(density, matMask));
            Collections.sort(maxima, Comparator.comparingDouble((IndexedPixel p) -> p.getValue()).reversed());
            // Try to get as many maxima as we need
            // Impose minimum separation
            var points = maxima.stream().map(p -> new Point2(p.getX() * downsample, p.getY() * downsample)).collect(Collectors.toList());
            var hotspotCentroids = new ArrayList<Point2>();
            double distSqThreshold = radiusPixels * radiusPixels * 4;
            for (var p : points) {
                // Check not too close to an existing hotspot
                boolean skip = false;
                for (var p2 : hotspotCentroids) {
                    if (p.distanceSq(p2) < distSqThreshold) {
                        skip = true;
                        break;
                    }
                }
                if (!skip) {
                    hotspotCentroids.add(p);
                    if (hotspotCentroids.size() == nHotspots)
                        break;
                }
            }
            var hotspots = new ArrayList<PathObject>();
            int i = 0;
            for (var p : hotspotCentroids) {
                i++;
                var ellipse = ROIs.createEllipseROI(p.getX() - radiusPixels, p.getY() - radiusPixels, radiusPixels * 2, radiusPixels * 2, roi.getImagePlane());
                var hotspot = PathObjects.createAnnotationObject(ellipse, hotspotClass);
                hotspot.setName("Hotspot " + i);
                hotspots.add(hotspot);
            }
            if (hotspots.isEmpty())
                logger.warn("No hotspots found in {}", parent);
            else if (hotspots.size() < nHotspots) {
                logger.warn("Only {}/{} hotspots could be found in {}", hotspots.size(), nHotspots, parent);
            }
            parent.addPathObjects(hotspots);
        }
        hierarchy.fireHierarchyChangedEvent(DensityMaps.class);
        if (!toDelete.isEmpty())
            hierarchy.removeObjects(toDelete, true);
    }
}
Also used : CreateObjectOptions(qupath.opencv.ml.pixel.PixelClassifierTools.CreateObjectOptions) Arrays(java.util.Arrays) ImageServer(qupath.lib.images.servers.ImageServer) LoggerFactory(org.slf4j.LoggerFactory) ChannelType(qupath.lib.images.servers.ImageServerMetadata.ChannelType) Map(java.util.Map) PixelClassifierTools(qupath.opencv.ml.pixel.PixelClassifierTools) StandardPathClasses(qupath.lib.objects.classes.PathClassFactory.StandardPathClasses) BufferedImageTools(qupath.lib.awt.common.BufferedImageTools) Path(java.nio.file.Path) BufferedImage(java.awt.image.BufferedImage) PixelClassifiers(qupath.opencv.ml.pixel.PixelClassifiers) Collection(java.util.Collection) PathObjects(qupath.lib.objects.PathObjects) UUID(java.util.UUID) Collectors(java.util.stream.Collectors) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) Objects(java.util.Objects) Project(qupath.lib.projects.Project) ImagePlane(qupath.lib.regions.ImagePlane) ColorModelBuilder(qupath.lib.analysis.heatmaps.ColorModels.ColorModelBuilder) org.bytedeco.opencv.global.opencv_core(org.bytedeco.opencv.global.opencv_core) GsonTools(qupath.lib.io.GsonTools) OpenCVTools(qupath.opencv.tools.OpenCVTools) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) ROIs(qupath.lib.roi.ROIs) Point2(qupath.lib.geom.Point2) LinkedHashSet(java.util.LinkedHashSet) ImageData(qupath.lib.images.ImageData) IndexedPixel(qupath.opencv.tools.OpenCVTools.IndexedPixel) RoiTools(qupath.lib.roi.RoiTools) Logger(org.slf4j.Logger) Files(java.nio.file.Files) PointerScope(org.bytedeco.javacpp.PointerScope) RegionRequest(qupath.lib.regions.RegionRequest) PathClass(qupath.lib.objects.classes.PathClass) PixelClassifierMetadata(qupath.lib.classifiers.pixel.PixelClassifierMetadata) IOException(java.io.IOException) PathObjectTools(qupath.lib.objects.PathObjectTools) ROI(qupath.lib.roi.interfaces.ROI) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) ColorModel(java.awt.image.ColorModel) PixelCalibration(qupath.lib.images.servers.PixelCalibration) PathObjectPredicate(qupath.lib.objects.PathObjectPredicates.PathObjectPredicate) Comparator(java.util.Comparator) Collections(java.util.Collections) ImageServerMetadata(qupath.lib.images.servers.ImageServerMetadata) ArrayList(java.util.ArrayList) IndexedPixel(qupath.opencv.tools.OpenCVTools.IndexedPixel) PointerScope(org.bytedeco.javacpp.PointerScope) ROI(qupath.lib.roi.interfaces.ROI) PathObject(qupath.lib.objects.PathObject) Point2(qupath.lib.geom.Point2) RegionRequest(qupath.lib.regions.RegionRequest) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet)

Example 78 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class FillAnnotationHolesPlugin method getTasks.

@Override
protected Collection<Runnable> getTasks(final PluginRunner<T> runner) {
    Collection<? extends PathObject> parentObjects = getParentObjects(runner);
    if (parentObjects == null || parentObjects.isEmpty())
        return Collections.emptyList();
    // Add a single task, to avoid multithreading - which may complicate setting parents
    List<Runnable> tasks = new ArrayList<>(1);
    PathObjectHierarchy hierarchy = getHierarchy(runner);
    // Want to reset selection
    PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
    Collection<PathObject> previousSelection = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
    tasks.add(() -> {
        Map<PathROIObject, ROI> toUpdate = new HashMap<>();
        for (PathObject pathObject : parentObjects) {
            ROI roiOrig = pathObject.getROI();
            if (roiOrig == null || !roiOrig.isArea())
                continue;
            ROI roiUpdated = roiOrig;
            roiUpdated = RoiTools.fillHoles(roiUpdated);
            if (roiOrig != roiUpdated && pathObject instanceof PathROIObject) {
                toUpdate.put((PathROIObject) pathObject, roiUpdated);
            }
        }
        if (toUpdate.isEmpty())
            return;
        hierarchy.getSelectionModel().clearSelection();
        if (!toUpdate.isEmpty()) {
            hierarchy.removeObjects(toUpdate.keySet(), true);
            toUpdate.forEach((p, r) -> p.setROI(r));
            hierarchy.addPathObjects(toUpdate.keySet());
        }
        hierarchy.getSelectionModel().selectObjects(previousSelection);
        hierarchy.getSelectionModel().setSelectedObject(selected, true);
    });
    return tasks;
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PathObject(qupath.lib.objects.PathObject) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) PathROIObject(qupath.lib.objects.PathROIObject) ROI(qupath.lib.roi.interfaces.ROI)

Example 79 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class DilateAnnotationPlugin method addExpandedAnnotation.

/**
 * Create and add a new annotation by expanding the ROI of the specified PathObject.
 *
 * @param bounds
 * @param hierarchy
 * @param pathObject
 * @param radiusPixels
 * @param constrainToParent
 * @param removeInterior
 */
private static void addExpandedAnnotation(final Rectangle bounds, final PathObjectHierarchy hierarchy, final PathObject pathObject, final double radiusPixels, final boolean constrainToParent, final boolean removeInterior, final LineCap cap) {
    ROI roi = pathObject.getROI();
    Geometry geometry = roi.getGeometry();
    int capVal = BufferParameters.CAP_ROUND;
    if (cap == LineCap.FLAT)
        capVal = BufferParameters.CAP_FLAT;
    else if (cap == LineCap.SQUARE)
        capVal = BufferParameters.CAP_SQUARE;
    Geometry geometry2 = BufferOp.bufferOp(geometry, radiusPixels, BufferParameters.DEFAULT_QUADRANT_SEGMENTS, capVal);
    // If the radius is negative (i.e. an erosion), then the parent will be the original object itself
    boolean isErosion = radiusPixels < 0;
    PathObject parent = isErosion ? pathObject : pathObject.getParent();
    if (constrainToParent && !isErosion) {
        Geometry parentShape;
        if (parent == null || parent.getROI() == null)
            parentShape = ROIs.createRectangleROI(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), ImagePlane.getPlane(roi)).getGeometry();
        else
            parentShape = parent.getROI().getGeometry();
        geometry2 = geometry2.intersection(parentShape);
    }
    if (removeInterior) {
        // Difference isn't supported for GeometryCollections
        if (isErosion) {
            geometry = GeometryTools.homogenizeGeometryCollection(geometry);
            geometry2 = geometry.difference(geometry2);
        } else {
            if (geometry.getArea() == 0.0)
                geometry = geometry.buffer(0.5);
            geometry2 = GeometryTools.homogenizeGeometryCollection(geometry2);
            geometry = GeometryTools.homogenizeGeometryCollection(geometry);
            geometry2 = geometry2.difference(geometry);
        }
    }
    ROI roi2 = GeometryTools.geometryToROI(geometry2, ImagePlane.getPlane(roi));
    if (roi2.isEmpty()) {
        logger.debug("Updated ROI is empty after {} px expansion", radiusPixels);
        return;
    }
    // Create a new annotation, with properties based on the original
    PathObject annotation2 = PathObjects.createAnnotationObject(roi2, pathObject.getPathClass());
    annotation2.setName(pathObject.getName());
    annotation2.setColorRGB(pathObject.getColorRGB());
    if (constrainToParent || isErosion)
        hierarchy.addPathObjectBelowParent(parent, annotation2, true);
    else
        hierarchy.addPathObject(annotation2);
}
Also used : Geometry(org.locationtech.jts.geom.Geometry) PathObject(qupath.lib.objects.PathObject) ROI(qupath.lib.roi.interfaces.ROI)

Example 80 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class ShapeFeaturesPlugin method addRunnableTasks.

@Override
protected void addRunnableTasks(final ImageData<T> imageData, final PathObject parentObject, List<Runnable> tasks) {
    PixelCalibration cal = imageData == null ? null : imageData.getServer().getPixelCalibration();
    boolean useMicrons = params.getBooleanParameterValue("useMicrons") && cal != null && cal.hasPixelSizeMicrons();
    double pixelWidth = useMicrons ? cal.getPixelWidthMicrons() : 1;
    double pixelHeight = useMicrons ? cal.getPixelHeightMicrons() : 1;
    String unit = useMicrons ? GeneralTools.micrometerSymbol() : "px";
    boolean doArea = params.getBooleanParameterValue("area");
    boolean doPerimeter = params.getBooleanParameterValue("perimeter");
    boolean doCircularity = params.getBooleanParameterValue("circularity");
    ROI roi = (parentObject.hasROI() && parentObject.getROI().isArea()) ? parentObject.getROI() : null;
    if (roi != null) {
        tasks.add(new Runnable() {

            @Override
            public void run() {
                try {
                    MeasurementList measurementList = parentObject.getMeasurementList();
                    ROI roi;
                    if (parentObject instanceof PathCellObject) {
                        roi = ((PathCellObject) parentObject).getNucleusROI();
                        if (roi != null && roi.isArea())
                            addMeasurements(measurementList, roi, "Nucleus Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
                        roi = parentObject.getROI();
                        if (roi != null && roi.isArea())
                            addMeasurements(measurementList, roi, "Cell Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
                    } else {
                        roi = parentObject.getROI();
                        if (roi != null && roi.isArea())
                            addMeasurements(measurementList, roi, "ROI Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
                    }
                    measurementList.close();
                } catch (Exception e) {
                    e.printStackTrace();
                    throw (e);
                }
            }
        });
    }
}
Also used : MeasurementList(qupath.lib.measurements.MeasurementList) PixelCalibration(qupath.lib.images.servers.PixelCalibration) ROI(qupath.lib.roi.interfaces.ROI) PathCellObject(qupath.lib.objects.PathCellObject)

Aggregations

ROI (qupath.lib.roi.interfaces.ROI)87 PathObject (qupath.lib.objects.PathObject)61 ArrayList (java.util.ArrayList)31 BufferedImage (java.awt.image.BufferedImage)24 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)24 IOException (java.io.IOException)20 RegionRequest (qupath.lib.regions.RegionRequest)19 List (java.util.List)17 Collectors (java.util.stream.Collectors)17 RectangleROI (qupath.lib.roi.RectangleROI)17 Logger (org.slf4j.Logger)16 LoggerFactory (org.slf4j.LoggerFactory)16 PolygonROI (qupath.lib.roi.PolygonROI)16 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)15 Point2D (java.awt.geom.Point2D)14 Collection (java.util.Collection)14 Collections (java.util.Collections)14 Geometry (org.locationtech.jts.geom.Geometry)14 PathClass (qupath.lib.objects.classes.PathClass)14 ImagePlane (qupath.lib.regions.ImagePlane)13