Search in sources :

Example 46 with ROI

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

the class DelaunayTools method createGeometryExtractor.

private static Function<PathObject, Collection<Coordinate>> createGeometryExtractor(PixelCalibration cal, boolean preferNucleus, double densifyFactor, double erosion) {
    PrecisionModel precision = calibrated(cal) ? null : GeometryTools.getDefaultFactory().getPrecisionModel();
    AffineTransformation transform = calibrated(cal) ? AffineTransformation.scaleInstance(cal.getPixelWidth().doubleValue(), cal.getPixelHeight().doubleValue()) : null;
    return p -> {
        var roi = PathObjectTools.getROI(p, preferNucleus);
        if (roi == null || roi.isEmpty())
            return Collections.emptyList();
        var geom = roi.getGeometry();
        if (transform != null)
            geom = transform.transform(geom);
        // Shared boundaries can be problematic, so try to buffer away from these
        double buffer = -Math.abs(erosion);
        if (buffer < 0 && geom instanceof Polygonal) {
            var geomBefore = geom;
            geom = GeometryTools.attemptOperation(geom, g -> g.buffer(buffer));
            // Do not permit Geometry to disappear
            if (geom.isEmpty()) {
                geom = geomBefore;
            }
        }
        // Therefore we take extra care in case empty geometries are being generated accidentally.
        if (precision != null) {
            var geom2 = GeometryTools.attemptOperation(geom, g -> GeometryPrecisionReducer.reduce(g, precision));
            if (!geom2.isEmpty())
                geom = geom2;
        }
        if (densifyFactor > 0) {
            var geom2 = GeometryTools.attemptOperation(geom, g -> Densifier.densify(g, densifyFactor));
            if (!geom2.isEmpty())
                geom = geom2;
        }
        // if (!(geom instanceof Polygon))
        // logger.warn("Unexpected Geometry: {}", geom);
        // Making precise is essential! Otherwise there can be small artifacts occurring
        var coords = geom.getCoordinates();
        var output = new LinkedHashSet<Coordinate>();
        var p2 = precision;
        Coordinate lastCoordinate = null;
        if (p2 == null)
            p2 = GeometryTools.getDefaultFactory().getPrecisionModel();
        // Add coordinates, unless they are extremely close to an existing coordinate
        int n = coords.length;
        if (n == 0) {
            logger.warn("Empty Geometry found for {}", p);
            return Collections.emptyList();
        }
        double minDistance = densifyFactor * 0.5;
        var firstCoordinate = coords[0];
        while (n > 2 && firstCoordinate.distance(coords[n - 1]) < minDistance) n--;
        for (int i = 0; i < n; i++) {
            var c = coords[i];
            p2.makePrecise(c);
            if (i == 0 || c.distance(lastCoordinate) > minDistance) {
                output.add(c);
                lastCoordinate = c;
            }
        }
        return output;
    };
}
Also used : IncrementalDelaunayTriangulator(org.locationtech.jts.triangulate.IncrementalDelaunayTriangulator) LoggerFactory(org.slf4j.LoggerFactory) Coordinate(org.locationtech.jts.geom.Coordinate) HashMap(java.util.HashMap) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) DelaunayTriangulationBuilder(org.locationtech.jts.triangulate.DelaunayTriangulationBuilder) Function(java.util.function.Function) ArrayList(java.util.ArrayList) QuadEdge(org.locationtech.jts.triangulate.quadedge.QuadEdge) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) BiPredicate(java.util.function.BiPredicate) LastFoundQuadEdgeLocator(org.locationtech.jts.triangulate.quadedge.LastFoundQuadEdgeLocator) GeometryPrecisionReducer(org.locationtech.jts.precision.GeometryPrecisionReducer) QuadEdgeSubdivision(org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision) Map(java.util.Map) GeometryTools(qupath.lib.roi.GeometryTools) AffineTransformation(org.locationtech.jts.geom.util.AffineTransformation) GeometryCombiner(org.locationtech.jts.geom.util.GeometryCombiner) LinkedHashSet(java.util.LinkedHashSet) RoiTools(qupath.lib.roi.RoiTools) GeometryFactory(org.locationtech.jts.geom.GeometryFactory) Logger(org.slf4j.Logger) QuadEdgeLocator(org.locationtech.jts.triangulate.quadedge.QuadEdgeLocator) Collection(java.util.Collection) Densifier(org.locationtech.jts.densify.Densifier) PathObjects(qupath.lib.objects.PathObjects) Quadtree(org.locationtech.jts.index.quadtree.Quadtree) PathClass(qupath.lib.objects.classes.PathClass) Set(java.util.Set) Polygonal(org.locationtech.jts.geom.Polygonal) Collectors(java.util.stream.Collectors) PathObjectTools(qupath.lib.objects.PathObjectTools) PathObject(qupath.lib.objects.PathObject) ROI(qupath.lib.roi.interfaces.ROI) List(java.util.List) PixelCalibration(qupath.lib.images.servers.PixelCalibration) Polygon(org.locationtech.jts.geom.Polygon) ImagePlane(qupath.lib.regions.ImagePlane) Geometry(org.locationtech.jts.geom.Geometry) PrecisionModel(org.locationtech.jts.geom.PrecisionModel) ArrayDeque(java.util.ArrayDeque) Comparator(java.util.Comparator) Collections(java.util.Collections) Envelope(org.locationtech.jts.geom.Envelope) Vertex(org.locationtech.jts.triangulate.quadedge.Vertex) Polygonal(org.locationtech.jts.geom.Polygonal) Coordinate(org.locationtech.jts.geom.Coordinate) AffineTransformation(org.locationtech.jts.geom.util.AffineTransformation) PrecisionModel(org.locationtech.jts.geom.PrecisionModel)

Example 47 with ROI

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

the class QuPathViewer method paintViewer.

protected void paintViewer(Graphics g, int w, int h) {
    ImageServer<BufferedImage> server = getServer();
    if (server == null) {
        g.setColor(background);
        g.fillRect(0, 0, w, h);
        updateRepaintTimestamp();
        return;
    }
    // // Get dimensions
    // int w = getWidth();
    // int h = getHeight();
    Rectangle clip = g.getClipBounds();
    boolean clipFull;
    if (clip == null) {
        clip = new Rectangle(0, 0, w, h);
        g.setClip(0, 0, w, h);
        clipFull = true;
    } else
        clipFull = clip.x == 0 && clip.y == 0 && clip.width == w && clip.height == h;
    // Ensure we have a sufficiently-large buffer
    if (imgBuffer == null || imgBuffer.getWidth() != w || imgBuffer.getHeight() != h) {
        // Create buffered images & buffers for RGB pixel values
        imgBuffer = createBufferedImage(w, h);
        imgBuffer.setAccelerationPriority(1f);
        logger.trace("New buffered image created: {}", imgBuffer);
        // imgVolatile = createVolatileImage(w, h);
        imageUpdated = true;
        // If the size changed, ensure the AffineTransform is up-to-date
        updateAffineTransform();
    }
    // Get the displayed region
    Shape shapeRegion = getDisplayedRegionShape();
    // The visible shape must have changed if there wasn't one previously...
    // Otherwise check if it has changed & update accordingly
    // This will be used to notify listeners soon
    boolean shapeChanged = lastVisibleShape == null || !lastVisibleShape.equals(shapeRegion);
    long t1 = System.currentTimeMillis();
    // Only repaint the image if this is requested, otherwise only overlays need to be repainted
    if (imageUpdated || locationUpdated) {
        // || imgVolatile.contentsLost()) {
        // Set flags that image no longer requiring an update
        // By setting them early, they might still be reset during this run... in which case we don't want to thwart the re-run
        imageUpdated = false;
        locationUpdated = false;
        // updateBufferedImage(imgVolatile, shapeRegion, w, h);
        updateBufferedImage(imgBuffer, shapeRegion, w, h);
    }
    // if (imageUpdated || locationUpdated) {
    // updateBufferedImage(imgVolatile, shapeRegion, w, h);
    // //			updateBufferedImage(imgBuffer, shapeRegion, w, h);
    // //			logger.info("INITIAL Image drawing time: " + (System.currentTimeMillis() - t1));
    // imgVolatile.createGraphics().drawImage(imgBuffer, 0, 0, this);
    // }
    // while (imgVolatile.contentsLost()) {
    // imgVolatile.createGraphics().drawImage(imgBuffer, 0, 0, this);
    // }
    // Store the last shape visible
    lastVisibleShape = shapeRegion;
    // Draw the image from the buffer
    // The call to super.paintComponent is delayed until here to try to stop occasional flickering on Apple's Java 6
    g.setColor(background);
    if (clipFull)
        paintFinalImage(g, imgBuffer, this);
    else
        // g2d.drawImage(imgBuffer, 0, 0, getWidth(), getHeight(), this);
        g.drawImage(imgBuffer, clip.x, clip.y, clip.x + clip.width, clip.y + clip.height, clip.x, clip.y, clip.x + clip.width, clip.y + clip.height, null);
    if (logger.isTraceEnabled()) {
        long t2 = System.currentTimeMillis();
        logger.trace("Final image drawing time: {}", (t2 - t1));
    }
    // Really useful only for debugging graphics
    if (!(g instanceof Graphics2D)) {
        imageUpdated = false;
        // Notify any listeners of shape changes
        if (shapeChanged)
            fireVisibleRegionChangedEvent(lastVisibleShape);
        return;
    }
    double downsample = getDownsampleFactor();
    float opacity = overlayOptions.getOpacity();
    Graphics2D g2d = (Graphics2D) g.create();
    // Apply required transform to the graphics object (rotation, scaling, shifting...)
    g2d.transform(transform);
    Composite previousComposite = g2d.getComposite();
    boolean paintCompletely = thumbnailIsFullImage || !doFasterRepaint;
    // var regionBounds = AwtTools.getImageRegion(clip, getZPosition(), getTPosition());
    if (opacity > 0 || PathPrefs.alwaysPaintSelectedObjectsProperty().get()) {
        if (opacity < 1) {
            AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
            g2d.setComposite(composite);
        }
        Color color = getSuggestedOverlayColor();
        // Paint the overlay layers
        var imageData = this.imageDataProperty.get();
        for (PathOverlay overlay : allOverlayLayers.toArray(PathOverlay[]::new)) {
            logger.trace("Painting overlay: {}", overlay);
            if (overlay instanceof AbstractOverlay)
                ((AbstractOverlay) overlay).setPreferredOverlayColor(color);
            // overlay.paintOverlay(g2d, regionBounds, downsample, null, paintCompletely);
            overlay.paintOverlay(g2d, getServerBounds(), downsample, imageData, paintCompletely);
        }
    // if (hierarchyOverlay != null) {
    // hierarchyOverlay.setPreferredOverlayColor(color);
    // hierarchyOverlay.paintOverlay(g2d, getServerBounds(), downsampleFactor, null, paintCompletely);
    // }
    }
    // Paint the selected object
    PathObjectHierarchy hierarchy = getHierarchy();
    PathObject mainSelectedObject = getSelectedObject();
    Rectangle2D boundsRect = null;
    boolean useSelectedColor = PathPrefs.useSelectedColorProperty().get();
    boolean paintSelectedBounds = PathPrefs.paintSelectedBoundsProperty().get();
    for (PathObject selectedObject : hierarchy.getSelectionModel().getSelectedObjects().toArray(new PathObject[0])) {
        // TODO: Simplify this...
        if (selectedObject != null && selectedObject.hasROI() && selectedObject.getROI().getZ() == getZPosition() && selectedObject.getROI().getT() == getTPosition()) {
            if (!selectedObject.isDetection()) {
                // Ensure a selected ROI can be seen clearly
                if (previousComposite != null)
                    g2d.setComposite(previousComposite);
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            }
            Rectangle boundsDisplayed = shapeRegion.getBounds();
            ROI pathROI = selectedObject.getROI();
            // if ((PathPrefs.getPaintSelectedBounds() || (selectedObject.isDetection() && !PathPrefs.getUseSelectedColor())) && !(pathROI instanceof RectangleROI)) {
            if (pathROI != null && (paintSelectedBounds || (!useSelectedColor)) && !(pathROI instanceof RectangleROI) && !pathROI.isEmpty()) {
                Shape boundsShape = null;
                if (pathROI.isPoint()) {
                    var hull = pathROI.getConvexHull();
                    if (hull != null)
                        boundsShape = hull.getShape();
                }
                if (boundsShape == null) {
                    boundsRect = AwtTools.getBounds2D(pathROI, boundsRect);
                    boundsShape = boundsRect;
                }
                // Tried to match to pixel boundaries... but resulted in too much jiggling
                // boundsShape.setFrame(
                // Math.round(boundsShape.getX()/downsampleFactor)*downsampleFactor-downsampleFactor,
                // Math.round(boundsShape.getY()/downsampleFactor)*downsampleFactor-downsampleFactor,
                // Math.round(boundsShape.getWidth()/downsampleFactor)*downsampleFactor+2*downsampleFactor,
                // Math.round(boundsShape.getHeight()/downsampleFactor)*downsampleFactor+2*downsampleFactor);
                // boundsShape.setFrame(boundsShape.getX()-downsampleFactor, boundsShape.getY()-downsampleFactor, boundsShape.getWidth()+2*downsampleFactor, boundsShape.getHeight()+2*downsampleFactor);
                PathHierarchyPaintingHelper.paintShape(boundsShape, g2d, getSuggestedOverlayColor(), PathHierarchyPaintingHelper.getCachedStroke(Math.max(downsample, 1) * 2), null);
            // boundsShape.setFrame(boundsShape.getX()+downsampleFactor, boundsShape.getY()-downsampleFactor, boundsShape.getWidth(), boundsShape.getHeight());
            // PathHierarchyPaintingHelper.paintShape(boundsShape, g2d, new Color(1f, 1f, 1f, 0.75f), PathHierarchyPaintingHelper.getCachedStroke(Math.max(downsampleFactor, 1)*2), null, downsampleFactor);
            }
            // in a cached way
            if ((selectedObject.isDetection() && PathPrefs.useSelectedColorProperty().get()) || !PathObjectTools.hierarchyContainsObject(hierarchy, selectedObject))
                PathHierarchyPaintingHelper.paintObject(selectedObject, false, g2d, boundsDisplayed, overlayOptions, getHierarchy().getSelectionModel(), downsample);
            // Paint ROI handles, if required
            if (selectedObject == mainSelectedObject && roiEditor.hasROI()) {
                Stroke strokeThick = PathHierarchyPaintingHelper.getCachedStroke(PathPrefs.annotationStrokeThicknessProperty().get() * downsample);
                Color color = useSelectedColor ? ColorToolsAwt.getCachedColor(PathPrefs.colorSelectedObjectProperty().get()) : null;
                if (color == null)
                    color = ColorToolsAwt.getCachedColor(ColorToolsFX.getDisplayedColorARGB(selectedObject));
                g2d.setStroke(strokeThick);
                // Draw ROI handles using adaptive size
                double maxHandleSize = getMaxROIHandleSize();
                double minHandleSize = downsample;
                PathHierarchyPaintingHelper.paintHandles(roiEditor, g2d, minHandleSize, maxHandleSize, color, ColorToolsAwt.getTranslucentColor(color));
            }
        }
    }
    // Notify any listeners of shape changes
    if (shapeChanged)
        fireVisibleRegionChangedEvent(lastVisibleShape);
    updateRepaintTimestamp();
}
Also used : AbstractOverlay(qupath.lib.gui.viewer.overlays.AbstractOverlay) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) Stroke(java.awt.Stroke) Shape(java.awt.Shape) AlphaComposite(java.awt.AlphaComposite) Composite(java.awt.Composite) AlphaComposite(java.awt.AlphaComposite) Color(java.awt.Color) Rectangle(java.awt.Rectangle) Rectangle2D(java.awt.geom.Rectangle2D) RectangleROI(qupath.lib.roi.RectangleROI) ROI(qupath.lib.roi.interfaces.ROI) BufferedImage(java.awt.image.BufferedImage) Graphics2D(java.awt.Graphics2D) PathObject(qupath.lib.objects.PathObject) RectangleROI(qupath.lib.roi.RectangleROI) PathOverlay(qupath.lib.gui.viewer.overlays.PathOverlay)

Example 48 with ROI

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

the class PathHierarchyPaintingHelper method paintPoints.

private static void paintPoints(ROI pathPoints, Graphics2D g2d, double radius, Color colorStroke, Stroke stroke, Color colorFill, double downsample) {
    PointsROI pathPointsROI = pathPoints instanceof PointsROI ? (PointsROI) pathPoints : null;
    if (pathPointsROI != null && PathPrefs.showPointHullsProperty().get()) {
        ROI convexHull = pathPointsROI.getConvexHull();
        if (convexHull != null) {
            Color colorHull = colorFill != null ? colorFill : colorStroke;
            colorHull = ColorToolsAwt.getColorWithOpacity(colorHull, 0.1);
            if (colorHull != null)
                paintShape(RoiTools.getShape(convexHull), g2d, null, null, colorHull);
        // getConvexHull().draw(g, null, colorHull);
        }
    }
    RectangularShape ellipse;
    // double radius = pathPointsROI == null ? PointsROI.defaultPointRadiusProperty().get() : pathPointsROI.getPointRadius();
    // Ensure that points are drawn with at least a radius of one, after any transforms have been applied
    double scale = Math.max(1, downsample);
    radius = (Math.max(1 / scale, radius));
    // Get clip bounds
    Rectangle2D bounds = g2d.getClipBounds();
    if (bounds != null) {
        bounds.setRect(bounds.getX() - radius, bounds.getY() - radius, bounds.getWidth() + radius * 2, bounds.getHeight() + radius * 2);
    }
    // Don't fill if we have a small radius, and use a rectangle instead of an ellipse (as this repaints much faster)
    Graphics2D g = g2d;
    if (radius / downsample < 0.5) {
        if (colorStroke == null)
            colorStroke = colorFill;
        colorFill = null;
        ellipse = new Rectangle2D.Double();
        // Use opacity to avoid obscuring points completely
        int rule = AlphaComposite.SRC_OVER;
        float alpha = (float) (radius / downsample);
        var composite = g.getComposite();
        if (composite instanceof AlphaComposite) {
            var temp = (AlphaComposite) composite;
            rule = temp.getRule();
            alpha = temp.getAlpha() * alpha;
        }
        // If we are close to completely transparent, do not paint
        if (alpha < 0.01f)
            return;
        composite = AlphaComposite.getInstance(rule, alpha);
        g = (Graphics2D) g2d.create();
        g.setComposite(composite);
    // ellipse = new Ellipse2D.Double();
    } else
        ellipse = new Ellipse2D.Double();
    g.setStroke(stroke);
    for (Point2 p : pathPoints.getAllPoints()) {
        if (bounds != null && !bounds.contains(p.getX(), p.getY()))
            continue;
        ellipse.setFrame(p.getX() - radius, p.getY() - radius, radius * 2, radius * 2);
        if (colorFill != null) {
            g.setColor(colorFill);
            g.fill(ellipse);
        }
        if (colorStroke != null) {
            g.setColor(colorStroke);
            g.draw(ellipse);
        }
    }
    if (g != g2d)
        g.dispose();
}
Also used : AlphaComposite(java.awt.AlphaComposite) Color(java.awt.Color) Rectangle2D(java.awt.geom.Rectangle2D) PointsROI(qupath.lib.roi.PointsROI) EllipseROI(qupath.lib.roi.EllipseROI) PointsROI(qupath.lib.roi.PointsROI) RectangleROI(qupath.lib.roi.RectangleROI) LineROI(qupath.lib.roi.LineROI) ROI(qupath.lib.roi.interfaces.ROI) Graphics2D(java.awt.Graphics2D) Point2(qupath.lib.geom.Point2) RectangularShape(java.awt.geom.RectangularShape)

Example 49 with ROI

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

the class AbstractPathROITool method mousePressed.

@Override
public void mousePressed(MouseEvent e) {
    super.mousePressed(e);
    if (!e.isPrimaryButtonDown() || e.isConsumed()) {
        return;
    }
    var viewer = getViewer();
    PathObjectHierarchy hierarchy = viewer.getHierarchy();
    if (hierarchy == null)
        return;
    PathObject currentObject = viewer.getSelectedObject();
    ROI currentROI = currentObject == null ? null : currentObject.getROI();
    RoiEditor editor = viewer.getROIEditor();
    boolean adjustingPolygon = (currentROI instanceof PolygonROI || currentROI instanceof PolylineROI) && editor.getROI() == currentROI && (editor.isTranslating() || editor.hasActiveHandle());
    // If we're adjusting a polygon/polyline with an appropriate tool, return at leave it up to the tool to handle the custom things
    if (adjustingPolygon) {
        if (viewer.getActiveTool() == PathTools.POLYGON || viewer.getActiveTool() == PathTools.POLYLINE)
            return;
        else {
            viewer.getHierarchy().getSelectionModel().clearSelection();
            viewer.getHierarchy().fireHierarchyChangedEvent(currentObject);
        }
    }
    // Find out the coordinates in the image domain
    Point2D p2 = mouseLocationToImage(e, false, requestPixelSnapping());
    double xx = p2.getX();
    double yy = p2.getY();
    if (xx < 0 || yy < 0 || xx >= viewer.getServerWidth() || yy >= viewer.getServerHeight())
        return;
    // If we are double-clicking & we don't have a polygon, see if we can access a ROI
    if (!PathPrefs.selectionModeProperty().get() && e.getClickCount() > 1) {
        // Reset parent... for now
        resetConstrainedAreaParent();
        tryToSelect(xx, yy, e.getClickCount() - 2, false);
        e.consume();
        return;
    }
    // Set the current parent object based on the first click
    setConstrainedAreaParent(hierarchy, xx, yy, Collections.emptyList());
    // Create a new annotation
    PathObject pathObject = createNewAnnotation(e, xx, yy);
    if (pathObject == null)
        return;
    // Start editing the ROI immediately
    editor.setROI(pathObject.getROI());
    editor.grabHandle(xx, yy, viewer.getMaxROIHandleSize() * 1.5, e.isShiftDown());
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PolygonROI(qupath.lib.roi.PolygonROI) PathObject(qupath.lib.objects.PathObject) RoiEditor(qupath.lib.roi.RoiEditor) PolylineROI(qupath.lib.roi.PolylineROI) Point2D(java.awt.geom.Point2D) PolylineROI(qupath.lib.roi.PolylineROI) ROI(qupath.lib.roi.interfaces.ROI) PolygonROI(qupath.lib.roi.PolygonROI)

Example 50 with ROI

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

the class SubcellularDetection method processObject.

/**
 * Initial version of subcellular detection processing.
 *
 * @param pathObject
 * @param params
 * @param imageWrapper
 * @return
 * @throws InterruptedException
 * @throws IOException
 */
static boolean processObject(final PathObject pathObject, final ParameterList params, final ImageWrapper imageWrapper) throws InterruptedException, IOException {
    // Get the base classification for the object as it currently stands
    PathClass baseClass = PathClassTools.getNonIntensityAncestorClass(pathObject.getPathClass());
    // Variable to hold estimated spot count
    double estimatedSpots;
    // We assume that after this processing, any previous sub-cellular objects should be removed
    pathObject.clearPathObjects();
    // Ensure we have no existing subcellular detection measurements - if we do, remove them
    String[] existingMeasurements = pathObject.getMeasurementList().getMeasurementNames().stream().filter(n -> n.startsWith("Subcellular:")).toArray(n -> new String[n]);
    if (existingMeasurements.length > 0) {
        pathObject.getMeasurementList().removeMeasurements(existingMeasurements);
        pathObject.getMeasurementList().close();
    }
    // // If we're part of a TMA core, request the whole core...
    // if (pathObject.getParent() instanceof TMACoreObject && pathObject.getParent().hasROI()) {
    // regionStore.getImage(server, RegionRequest.createInstance(server.getPath(), 1, pathObject.getParent().getROI()), 25, true);
    // }
    ROI pathROI = pathObject.getROI();
    if (pathROI == null || pathROI.isEmpty())
        return false;
    // double downsample = 0.5;
    double downsample = 1;
    // Determine spot size
    ImageServer<BufferedImage> server = imageWrapper.getServer();
    PixelCalibration cal = server.getPixelCalibration();
    double minSpotArea, maxSpotArea, singleSpotArea;
    double pixelWidth, pixelHeight;
    if (cal.hasPixelSizeMicrons()) {
        double spotSizeMicrons = params.getDoubleParameterValue("spotSizeMicrons");
        double minSpotSizeMicrons = params.getDoubleParameterValue("minSpotSizeMicrons");
        double maxSpotSizeMicrons = params.getDoubleParameterValue("maxSpotSizeMicrons");
        pixelWidth = cal.getPixelWidthMicrons() * downsample;
        pixelHeight = cal.getPixelHeightMicrons() * downsample;
        singleSpotArea = spotSizeMicrons / (pixelWidth * pixelHeight);
        minSpotArea = minSpotSizeMicrons / (pixelWidth * pixelHeight);
        maxSpotArea = maxSpotSizeMicrons / (pixelWidth * pixelHeight);
    } else {
        singleSpotArea = params.getDoubleParameterValue("spotSizePixels");
        minSpotArea = params.getDoubleParameterValue("minSpotSizePixels");
        maxSpotArea = params.getDoubleParameterValue("maxSpotSizePixels");
        pixelWidth = downsample;
        pixelHeight = downsample;
    }
    boolean includeClusters = Boolean.TRUE.equals(params.getBooleanParameterValue("includeClusters"));
    boolean doSmoothing = Boolean.TRUE.equals(params.getBooleanParameterValue("doSmoothing"));
    boolean splitByIntensity = Boolean.TRUE.equals(params.getBooleanParameterValue("splitByIntensity"));
    boolean splitByShape = Boolean.TRUE.equals(params.getBooleanParameterValue("splitByShape"));
    // Get region to request - give a pixel as border
    int xStart = (int) Math.max(0, pathROI.getBoundsX() - 1);
    int yStart = (int) Math.max(0, pathROI.getBoundsY() - 1);
    int width = (int) Math.min(server.getWidth() - 1, pathROI.getBoundsX() + pathROI.getBoundsWidth() + 1.5) - xStart;
    int height = (int) Math.min(server.getHeight() - 1, pathROI.getBoundsY() + pathROI.getBoundsHeight() + 1.5) - yStart;
    if (width <= 0 || height <= 0) {
        logger.error("Negative ROI size for {}", pathROI);
        pathObject.setPathClass(baseClass);
        return false;
    }
    int z = pathROI.getZ();
    int t = pathROI.getT();
    // Don't associate with channel
    int c = -1;
    RegionRequest region = RegionRequest.createInstance(server.getPath(), 1.0, xStart, yStart, width, height, z, t);
    // Mask to indicate pixels within the cell
    byte[] cellMask = null;
    for (String channelName : imageWrapper.getChannelNames(true, true)) {
        double detectionThreshold = params.getDoubleParameterValue("detection[" + channelName + "]");
        if (Double.isNaN(detectionThreshold) || detectionThreshold < 0)
            continue;
        // // TODO: Consider whether to use channel numbers for non-brightfield images
        // if (!imageWrapper.imageData.isBrightfield())
        // c++;
        SimpleImage img = imageWrapper.getRegion(region, channelName);
        // Get an ImageJ-friendly calibration for ROI conversion
        Calibration calIJ = new Calibration();
        calIJ.xOrigin = -xStart / downsample;
        calIJ.yOrigin = -yStart / downsample;
        // Create a cell mask
        if (cellMask == null) {
            BufferedImage imgMask = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
            Graphics2D g2d = imgMask.createGraphics();
            if (downsample != 1)
                g2d.scale(1.0 / downsample, 1.0 / downsample);
            g2d.translate(-xStart, -yStart);
            Shape shape = RoiTools.getShape(pathROI);
            g2d.setColor(Color.WHITE);
            g2d.fill(shape);
            g2d.dispose();
            cellMask = (byte[]) ((DataBufferByte) imgMask.getRaster().getDataBuffer()).getData(0);
        }
        // Get a buffer containing the image pixels
        int w = img.getWidth();
        int h = img.getHeight();
        // Identify (& try to separate) spots
        // Mask out non-cell areas as we go
        FloatProcessor fpDetection = new FloatProcessor(w, h);
        if (doSmoothing) {
            for (int i = 0; i < w * h; i++) fpDetection.setf(i, img.getValue(i % w, i / w));
            fpDetection.smooth();
            for (int i = 0; i < w * h; i++) {
                if (cellMask[i] == (byte) 0)
                    fpDetection.setf(i, 0f);
            }
        } else {
            for (int i = 0; i < w * h; i++) {
                if (cellMask[i] == (byte) 0)
                    fpDetection.setf(i, 0f);
                else
                    fpDetection.setf(i, img.getValue(i % w, i / w));
            }
        }
        ByteProcessor bpSpots;
        if (splitByIntensity)
            bpSpots = new MaximumFinder().findMaxima(fpDetection, detectionThreshold / 10.0, detectionThreshold, MaximumFinder.SEGMENTED, false, false);
        else
            bpSpots = SimpleThresholding.thresholdAboveEquals(fpDetection, (float) detectionThreshold);
        if (splitByShape) {
            new EDM().toWatershed(bpSpots);
        }
        // Loop through spot ROIs & make a decision
        bpSpots.setThreshold(1, ImageProcessor.NO_THRESHOLD, ImageProcessor.NO_LUT_UPDATE);
        List<PolygonRoi> possibleSpotRois = RoiLabeling.getFilledPolygonROIs(bpSpots, Wand.FOUR_CONNECTED);
        List<PathObject> spotObjects = new ArrayList<>();
        List<PathObject> clusterObjects = new ArrayList<>();
        estimatedSpots = 0;
        for (PolygonRoi spotRoi : possibleSpotRois) {
            fpDetection.setRoi(spotRoi);
            ImageStatistics stats = fpDetection.getStatistics();
            // In v0.2
            // ImagePlane plane = ImagePlane.getPlaneWithChannel(spotRoi.getCPosition(), spotRoi.getZPosition(), spotRoi.getTPosition());
            // In v0.3
            ImagePlane plane = ImagePlane.getPlaneWithChannel(c, z, t);
            PathObject spotOrCluster = null;
            if (stats.pixelCount >= minSpotArea && stats.pixelCount <= maxSpotArea) {
                ROI roi = IJTools.convertToROI(spotRoi, calIJ, downsample, plane);
                // cluster = new SubcellularObject(roi, 1);
                spotOrCluster = createSubcellularObject(roi, 1);
                estimatedSpots += 1;
            } else if (includeClusters && stats.pixelCount >= minSpotArea) {
                // Add a cluster
                ROI roi = IJTools.convertToROI(spotRoi, calIJ, downsample, plane);
                double nSpots = stats.pixelCount / singleSpotArea;
                estimatedSpots += nSpots;
                // cluster = new SubcellularObject(roi, nSpots);
                spotOrCluster = createSubcellularObject(roi, nSpots);
            }
            if (spotOrCluster != null) {
                boolean isCluster = spotOrCluster.getMeasurementList().getMeasurementValue("Num spots") > 1;
                int rgb = imageWrapper.getChannelColor(channelName);
                rgb = isCluster ? ColorTools.makeScaledRGB(rgb, 0.5) : ColorTools.makeScaledRGB(rgb, 1.5);
                PathClass pathClass = PathClassFactory.getDerivedPathClass(spotOrCluster.getPathClass(), channelName + " object", rgb);
                spotOrCluster.setPathClass(pathClass);
                spotOrCluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Area", stats.pixelCount * pixelWidth * pixelHeight);
                spotOrCluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName + ": Mean channel intensity", stats.mean);
                // cluster.getMeasurementList().putMeasurement("Subcellular cluster: " + channelName +  ": Max channel intensity", stats.max);
                spotOrCluster.getMeasurementList().close();
                if (isCluster)
                    clusterObjects.add(spotOrCluster);
                else
                    spotObjects.add(spotOrCluster);
            }
        }
        // Add measurements
        MeasurementList measurementList = pathObject.getMeasurementList();
        measurementList.putMeasurement("Subcellular: " + channelName + ": Num spots estimated", estimatedSpots);
        measurementList.putMeasurement("Subcellular: " + channelName + ": Num single spots", spotObjects.size());
        measurementList.putMeasurement("Subcellular: " + channelName + ": Num clusters", clusterObjects.size());
        // Add spots
        pathObject.addPathObjects(spotObjects);
        pathObject.addPathObjects(clusterObjects);
    }
    return true;
}
Also used : Color(java.awt.Color) RoiLabeling(qupath.imagej.processing.RoiLabeling) ImageServer(qupath.lib.images.servers.ImageServer) ByteProcessor(ij.process.ByteProcessor) ImageProcessor(ij.process.ImageProcessor) IJTools(qupath.imagej.tools.IJTools) LoggerFactory(org.slf4j.LoggerFactory) DataBufferByte(java.awt.image.DataBufferByte) Wand(ij.gui.Wand) ParameterList(qupath.lib.plugins.parameters.ParameterList) ImageStatistics(ij.process.ImageStatistics) Map(java.util.Map) PluginRunner(qupath.lib.plugins.PluginRunner) Shape(java.awt.Shape) MeasurementListType(qupath.lib.measurements.MeasurementList.MeasurementListType) ColorTools(qupath.lib.common.ColorTools) BufferedImage(java.awt.image.BufferedImage) Collection(java.util.Collection) PathObjects(qupath.lib.objects.PathObjects) Collectors(java.util.stream.Collectors) EDM(ij.plugin.filter.EDM) StainVector(qupath.lib.color.StainVector) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathObject(qupath.lib.objects.PathObject) List(java.util.List) SimpleThresholding(qupath.imagej.processing.SimpleThresholding) ImagePlane(qupath.lib.regions.ImagePlane) AbstractInteractivePlugin(qupath.lib.plugins.AbstractInteractivePlugin) PathCellObject(qupath.lib.objects.PathCellObject) ColorTransformer(qupath.lib.color.ColorTransformer) PathClassTools(qupath.lib.objects.classes.PathClassTools) PolygonRoi(ij.gui.PolygonRoi) HashMap(java.util.HashMap) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) ArrayList(java.util.ArrayList) MeasurementList(qupath.lib.measurements.MeasurementList) ColorDeconvolutionStains(qupath.lib.color.ColorDeconvolutionStains) Graphics2D(java.awt.Graphics2D) MeasurementListFactory(qupath.lib.measurements.MeasurementListFactory) ImageData(qupath.lib.images.ImageData) RoiTools(qupath.lib.roi.RoiTools) Logger(org.slf4j.Logger) GeneralTools(qupath.lib.common.GeneralTools) RegionRequest(qupath.lib.regions.RegionRequest) Calibration(ij.measure.Calibration) SimpleImage(qupath.lib.analysis.images.SimpleImage) ColorTransformMethod(qupath.lib.color.ColorTransformer.ColorTransformMethod) PathClass(qupath.lib.objects.classes.PathClass) IOException(java.io.IOException) TMACoreObject(qupath.lib.objects.TMACoreObject) PathObjectTools(qupath.lib.objects.PathObjectTools) ROI(qupath.lib.roi.interfaces.ROI) FloatProcessor(ij.process.FloatProcessor) PixelCalibration(qupath.lib.images.servers.PixelCalibration) MaximumFinder(ij.plugin.filter.MaximumFinder) SimpleImages(qupath.lib.analysis.images.SimpleImages) ByteProcessor(ij.process.ByteProcessor) Shape(java.awt.Shape) MeasurementList(qupath.lib.measurements.MeasurementList) MaximumFinder(ij.plugin.filter.MaximumFinder) ArrayList(java.util.ArrayList) DataBufferByte(java.awt.image.DataBufferByte) BufferedImage(java.awt.image.BufferedImage) EDM(ij.plugin.filter.EDM) PathClass(qupath.lib.objects.classes.PathClass) PolygonRoi(ij.gui.PolygonRoi) ImagePlane(qupath.lib.regions.ImagePlane) FloatProcessor(ij.process.FloatProcessor) PixelCalibration(qupath.lib.images.servers.PixelCalibration) Calibration(ij.measure.Calibration) PixelCalibration(qupath.lib.images.servers.PixelCalibration) ROI(qupath.lib.roi.interfaces.ROI) Graphics2D(java.awt.Graphics2D) PathObject(qupath.lib.objects.PathObject) ImageStatistics(ij.process.ImageStatistics) SimpleImage(qupath.lib.analysis.images.SimpleImage) RegionRequest(qupath.lib.regions.RegionRequest)

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