Search in sources :

Example 41 with PathObject

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

the class PathHierarchyPaintingHelper method paintObject.

/**
 * Paint an object (or, more precisely, its ROI), optionally along with the ROIs of any child objects.
 *
 * This is subject to the OverlayOptions, and therefore may not actually end up painting anything
 * (if the settings are such that objects of the class provided are not to be displayed)
 *
 * @param pathObject
 * @param paintChildren
 * @param g
 * @param boundsDisplayed
 * @param overlayOptions
 * @param selectionModel
 * @param downsample
 * @return true if anything was painted, false otherwise
 */
public static boolean paintObject(PathObject pathObject, boolean paintChildren, Graphics2D g, Rectangle boundsDisplayed, OverlayOptions overlayOptions, PathObjectSelectionModel selectionModel, double downsample) {
    if (pathObject == null)
        return false;
    // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
    // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    // Always paint the selected object
    // Note: this makes the assumption that child ROIs are completely contained within their parents;
    // this probably should be the case, but isn't guaranteed
    boolean isSelected = (selectionModel != null && selectionModel.isSelected(pathObject)) && (PathPrefs.useSelectedColorProperty().get() || !PathObjectTools.hasPointROI(pathObject));
    boolean isDetectedObject = pathObject.isDetection() || (pathObject.isTile() && pathObject.hasMeasurements());
    // Check if the PathClass isn't being shown
    PathClass pathClass = pathObject.getPathClass();
    if (!isSelected && overlayOptions != null && overlayOptions.isPathClassHidden(pathClass))
        return false;
    boolean painted = false;
    // See if we need to check the children
    ROI pathROI = pathObject.getROI();
    if (pathROI != null) {
        double roiBoundsX = pathROI.getBoundsX();
        double roiBoundsY = pathROI.getBoundsY();
        double roiBoundsWidth = pathROI.getBoundsWidth();
        double roiBoundsHeight = pathROI.getBoundsHeight();
        if (PathObjectTools.hasPointROI(pathObject) || boundsDisplayed == null || pathROI instanceof LineROI || boundsDisplayed.intersects(roiBoundsX, roiBoundsY, Math.max(roiBoundsWidth, 1), Math.max(roiBoundsHeight, 1))) {
            // Paint the ROI, if necessary
            if (isSelected || (overlayOptions.getShowDetections() && isDetectedObject) || (overlayOptions.getShowAnnotations() && pathObject.isAnnotation()) || (overlayOptions.getShowTMAGrid() && pathObject.isTMACore())) {
                boolean doFill = overlayOptions.getFillDetections() || pathObject instanceof ParallelTileObject;
                boolean doOutline = true;
                Color color = null;
                boolean useMapper = false;
                double fillOpacity = .75;
                if (isSelected && PathPrefs.useSelectedColorProperty().get() && PathPrefs.colorSelectedObjectProperty().getValue() != null)
                    color = ColorToolsAwt.getCachedColor(PathPrefs.colorSelectedObjectProperty().get());
                else {
                    MeasurementMapper mapper = overlayOptions.getMeasurementMapper();
                    useMapper = mapper != null && mapper.isValid() && pathObject.isDetection();
                    if (useMapper) {
                        if (pathObject.hasMeasurements()) {
                            Integer rgb = mapper.getColorForObject(pathObject);
                            // If the mapper returns null, the object shouldn't be painted
                            if (rgb == null)
                                return false;
                            // , mapper.getColorMapper().hasAlpha());
                            color = ColorToolsAwt.getCachedColor(rgb);
                        } else
                            color = null;
                        // System.out.println(color + " - " + pathObject.getMeasurementList().getMeasurementValue(mapper.));
                        fillOpacity = 1.0;
                        // Outlines are not so helpful with the measurement mapper
                        if (doFill)
                            doOutline = doOutline && !pathObject.isTile();
                    } else {
                        Integer rgb = ColorToolsFX.getDisplayedColorARGB(pathObject);
                        color = ColorToolsAwt.getCachedColor(rgb);
                    }
                // color = PathObjectHelpers.getDisplayedColor(pathObject);
                }
                // Check if we have only one or two pixels to draw - if so, we can be done quickly
                if (isDetectedObject && downsample > 4 && roiBoundsWidth / downsample < 3 && roiBoundsHeight / downsample < 3) {
                    int x = (int) roiBoundsX;
                    int y = (int) roiBoundsY;
                    // Prefer rounding up, lest we lose a lot of regions unnecessarily
                    int w = (int) (roiBoundsWidth + .9);
                    int h = (int) (roiBoundsHeight + .9);
                    if (w > 0 && h > 0) {
                        g.setColor(color);
                        // g.setColor(DisplayHelpers.getMoreTranslucentColor(color));
                        // g.setStroke(getCachedStroke(overlayOptions.strokeThinThicknessProperty().get()));
                        g.fillRect(x, y, w, h);
                    }
                    painted = true;
                } else {
                    Stroke stroke = null;
                    // Decide whether to fill or not
                    Color colorFill = doFill && (isDetectedObject || PathObjectTools.hasPointROI(pathObject)) ? color : null;
                    if (colorFill != null && fillOpacity != 1) {
                        if (pathObject instanceof ParallelTileObject)
                            colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
                        else if (pathObject instanceof PathCellObject && overlayOptions.getShowCellBoundaries() && overlayOptions.getShowCellNuclei()) {
                            // if (isSelected)
                            // colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
                            // else
                            colorFill = ColorToolsAwt.getMoreTranslucentColor(colorFill);
                        } else if (pathObject.getParent() instanceof PathDetectionObject) {
                            colorFill = ColorToolsAwt.getTranslucentColor(colorFill);
                        } else if (pathObject instanceof PathTileObject && pathClass == null && color != null && color.getRGB() == PathPrefs.colorTileProperty().get()) {
                            // Don't fill in empty, unclassified tiles
                            // DisplayHelpers.getMoreTranslucentColor(colorFill);
                            colorFill = null;
                        }
                    }
                    // Color colorStroke = doOutline ? (colorFill == null ? color : (downsample > overlayOptions.strokeThinThicknessProperty().get() ? null : DisplayHelpers.darkenColor(color))) : null;
                    Color colorStroke = doOutline ? (colorFill == null ? color : ColorToolsAwt.darkenColor(color)) : null;
                    // For thick lines, antialiasing is very noticeable... less so for thin lines (of which there may be a huge number)
                    if (isDetectedObject) {
                        // Detections inside detections get half the line width
                        if (pathObject.getParent() instanceof PathDetectionObject)
                            stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get() / 2.0);
                        else
                            stroke = getCachedStroke(PathPrefs.detectionStrokeThicknessProperty().get());
                    } else {
                        double thicknessScale = downsample * (isSelected && !PathPrefs.useSelectedColorProperty().get() ? 1.6 : 1);
                        float thickness = (float) (PathPrefs.annotationStrokeThicknessProperty().get() * thicknessScale);
                        if (isSelected && pathObject.getParent() == null && PathPrefs.selectionModeProperty().get()) {
                            stroke = getCachedStrokeDashed(thickness);
                        } else {
                            stroke = getCachedStroke(thickness);
                        }
                    }
                    g.setStroke(stroke);
                    boolean paintSymbols = overlayOptions.getDetectionDisplayMode() == DetectionDisplayMode.CENTROIDS && pathObject.isDetection() && !pathObject.isTile();
                    if (paintSymbols) {
                        pathROI = PathObjectTools.getROI(pathObject, true);
                        double x = pathROI.getCentroidX();
                        double y = pathROI.getCentroidY();
                        double radius = PathPrefs.detectionStrokeThicknessProperty().get() * 2.0;
                        if (pathObject.getParent() instanceof PathDetectionObject)
                            radius /= 2.0;
                        Shape shape;
                        int nSubclasses = 0;
                        if (pathClass != null) {
                            nSubclasses = PathClassTools.splitNames(pathClass).size();
                        }
                        switch(nSubclasses) {
                            case 0:
                                var ellipse = localEllipse2D.get();
                                ellipse.setFrame(x - radius, y - radius, radius * 2, radius * 2);
                                shape = ellipse;
                                break;
                            case 1:
                                var rect = localRect2D.get();
                                rect.setFrame(x - radius, y - radius, radius * 2, radius * 2);
                                shape = rect;
                                break;
                            case 2:
                                var triangle = localPath2D.get();
                                double sqrt3 = Math.sqrt(3.0);
                                triangle.reset();
                                triangle.moveTo(x, y - radius * 2.0 / sqrt3);
                                triangle.lineTo(x - radius, y + radius / sqrt3);
                                triangle.lineTo(x + radius, y + radius / sqrt3);
                                triangle.closePath();
                                shape = triangle;
                                break;
                            case 3:
                                var plus = localPath2D.get();
                                plus.reset();
                                plus.moveTo(x, y - radius);
                                plus.lineTo(x, y + radius);
                                plus.moveTo(x - radius, y);
                                plus.lineTo(x + radius, y);
                                shape = plus;
                                break;
                            default:
                                var cross = localPath2D.get();
                                cross.reset();
                                radius /= Math.sqrt(2);
                                cross.moveTo(x - radius, y - radius);
                                cross.lineTo(x + radius, y + radius);
                                cross.moveTo(x + radius, y - radius);
                                cross.lineTo(x - radius, y + radius);
                                shape = cross;
                                break;
                        }
                        paintShape(shape, g, colorStroke, stroke, colorFill);
                    } else if (pathObject instanceof PathCellObject) {
                        PathCellObject cell = (PathCellObject) pathObject;
                        if (overlayOptions.getShowCellBoundaries())
                            paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
                        if (overlayOptions.getShowCellNuclei())
                            paintROI(cell.getNucleusROI(), g, colorStroke, stroke, colorFill, downsample);
                        painted = true;
                    } else {
                        if ((overlayOptions.getFillAnnotations() && pathObject.isAnnotation() && pathObject.getPathClass() != PathClassFactory.getPathClass(StandardPathClasses.REGION) && (pathObject.getPathClass() != null || !pathObject.hasChildren())) || (pathObject.isTMACore() && overlayOptions.getShowTMACoreLabels()))
                            paintROI(pathROI, g, colorStroke, stroke, ColorToolsAwt.getMoreTranslucentColor(colorStroke), downsample);
                        else
                            paintROI(pathROI, g, colorStroke, stroke, colorFill, downsample);
                        painted = true;
                    }
                }
            }
        }
    }
    // Paint the children, if necessary
    if (paintChildren) {
        for (PathObject childObject : pathObject.getChildObjectsAsArray()) {
            // Only call the painting method if required
            ROI childROI = childObject.getROI();
            if ((childROI != null && boundsDisplayed.intersects(childROI.getBoundsX(), childROI.getBoundsY(), childROI.getBoundsWidth(), childROI.getBoundsHeight())) || childObject.hasChildren())
                painted = paintObject(childObject, paintChildren, g, boundsDisplayed, overlayOptions, selectionModel, downsample) | painted;
        }
    }
    return painted;
}
Also used : BasicStroke(java.awt.BasicStroke) Stroke(java.awt.Stroke) PathDetectionObject(qupath.lib.objects.PathDetectionObject) Shape(java.awt.Shape) RectangularShape(java.awt.geom.RectangularShape) Color(java.awt.Color) 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) ParallelTileObject(qupath.lib.plugins.ParallelTileObject) PathClass(qupath.lib.objects.classes.PathClass) PathTileObject(qupath.lib.objects.PathTileObject) PathObject(qupath.lib.objects.PathObject) MeasurementMapper(qupath.lib.gui.tools.MeasurementMapper) LineROI(qupath.lib.roi.LineROI) PathCellObject(qupath.lib.objects.PathCellObject)

Example 42 with PathObject

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

the class PathHierarchyPaintingHelper method paintConnections.

/**
 * Paint connections between objects (e.g. from Delaunay triangulation).
 *
 * @param connections
 * @param hierarchy
 * @param g2d
 * @param color
 * @param downsampleFactor
 */
public static void paintConnections(final PathObjectConnections connections, final PathObjectHierarchy hierarchy, Graphics2D g2d, final Color color, final double downsampleFactor) {
    if (hierarchy == null || connections == null || connections.isEmpty())
        return;
    float alpha = (float) (1f - downsampleFactor / 5);
    alpha = Math.min(alpha, 0.25f);
    float thickness = PathPrefs.detectionStrokeThicknessProperty().get();
    if (alpha < .1f || thickness / downsampleFactor <= 0.5)
        return;
    g2d = (Graphics2D) g2d.create();
    // Shape clipShape = g2d.getClip();
    g2d.setStroke(getCachedStroke(thickness));
    // g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha * .5f));
    // g2d.setColor(ColorToolsAwt.getColorWithOpacity(getPreferredOverlayColor(), 1));
    g2d.setColor(ColorToolsAwt.getColorWithOpacity(color.getRGB(), alpha));
    // g2d.setColor(Color.BLACK);
    Line2D line = new Line2D.Double();
    // We can have trouble whenever two objects are outside the clip, but their connections would be inside it
    // Here, we just enlarge the region (by quite a lot)
    // It's not guaranteed to work, but it usually does... and avoids much expensive computations
    Rectangle bounds = g2d.getClipBounds();
    int factor = 1;
    Rectangle bounds2 = factor > 0 ? new Rectangle(bounds.x - bounds.width * factor, bounds.y - bounds.height * factor, bounds.width * (factor * 2 + 1), bounds.height * (factor * 2 + 1)) : bounds;
    ImageRegion imageRegion = AwtTools.getImageRegion(bounds2, 0, 0);
    // ImageRegion imageRegion = AwtTools.getImageRegion(bounds, 0, 0);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
    // g2d.draw(g2d.getClipBounds());
    Collection<PathObject> pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, imageRegion, null);
    // double threshold = downsampleFactor*downsampleFactor*4;
    for (PathObject pathObject : pathObjects) {
        ROI roi = PathObjectTools.getROI(pathObject, true);
        double x1 = roi.getCentroidX();
        double y1 = roi.getCentroidY();
        for (PathObjectConnectionGroup dt : connections.getConnectionGroups()) {
            for (PathObject siblingObject : dt.getConnectedObjects(pathObject)) {
                ROI roi2 = PathObjectTools.getROI(siblingObject, true);
                double x2 = roi2.getCentroidX();
                double y2 = roi2.getCentroidY();
                if (bounds.intersectsLine(x1, y1, x2, y2)) {
                    line.setLine(x1, y1, x2, y2);
                    g2d.draw(line);
                }
            }
        }
    }
    g2d.dispose();
}
Also used : PathObject(qupath.lib.objects.PathObject) Rectangle(java.awt.Rectangle) ImageRegion(qupath.lib.regions.ImageRegion) PathObjectConnectionGroup(qupath.lib.objects.PathObjectConnectionGroup) Line2D(java.awt.geom.Line2D) 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)

Example 43 with PathObject

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

the class QuPathViewer method updateRoiEditor.

private void updateRoiEditor() {
    PathObject pathObjectSelected = getSelectedObject();
    ROI previousROI = roiEditor.getROI();
    ROI newROI = pathObjectSelected != null && pathObjectSelected.isEditable() ? pathObjectSelected.getROI() : null;
    if (previousROI == newROI)
        roiEditor.ensureHandlesUpdated();
    else
        roiEditor.setROI(newROI);
    repaint();
}
Also used : PathObject(qupath.lib.objects.PathObject) RectangleROI(qupath.lib.roi.RectangleROI) ROI(qupath.lib.roi.interfaces.ROI)

Example 44 with PathObject

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

the class HierarchyOverlay method paintOverlay.

@Override
public void paintOverlay(final Graphics2D g2d, final ImageRegion imageRegion, final double downsampleFactor, final ImageData<BufferedImage> imageData, final boolean paintCompletely) {
    if (this.imageData != imageData) {
        this.imageData = imageData;
        updateOverlayServer();
    }
    // Get the selection model, which can influence colours (TODO: this might not be the best way to do it!)
    PathObjectHierarchy hierarchy = imageData == null ? null : imageData.getHierarchy();
    if (hierarchy == null)
        return;
    if (!isVisible() && hierarchy.getSelectionModel().noSelection())
        return;
    // Default RenderingHints (may be temporarily changed in some places)
    var defaultAntiAlias = RenderingHints.VALUE_ANTIALIAS_ON;
    var defaultStroke = RenderingHints.VALUE_STROKE_PURE;
    // Doesn't seem to help...?
    // boolean fastRendering = true;
    // if (fastRendering) {
    // defaultAntiAlias = RenderingHints.VALUE_ANTIALIAS_OFF;
    // defaultStroke = RenderingHints.VALUE_STROKE_DEFAULT;
    // }
    OverlayOptions overlayOptions = getOverlayOptions();
    long timestamp = overlayOptions.lastChangeTimestamp().get();
    int pointRadius = PathPrefs.pointRadiusProperty().get();
    if (overlayOptionsTimestamp != timestamp || pointRadius != lastPointRadius) {
        lastPointRadius = pointRadius;
        overlayOptionsTimestamp = timestamp;
    }
    int t = imageRegion.getT();
    int z = imageRegion.getZ();
    Rectangle serverBounds = AwtTools.getBounds(imageRegion);
    // Ensure antialias is on...?
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, defaultAntiAlias);
    // Get the displayed clip bounds for fast checking if ROIs need to be drawn
    Shape shapeRegion = g2d.getClip();
    if (shapeRegion == null)
        shapeRegion = AwtTools.getBounds(imageRegion);
    var boundsDisplayed = shapeRegion.getBounds();
    // Ensure the bounds do not extend beyond what the server actually contains
    boundsDisplayed = boundsDisplayed.intersection(serverBounds);
    if (boundsDisplayed.width <= 0 || boundsDisplayed.height <= 0)
        return;
    // Get the annotations & selected objects (which must be painted directly)
    Collection<PathObject> selectedObjects = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
    selectedObjects.removeIf(p -> !p.hasROI() || (p.getROI().getZ() != z || p.getROI().getT() != t));
    ImageRegion region = AwtTools.getImageRegion(boundsDisplayed, z, t);
    // Paint detection objects
    long startTime = System.currentTimeMillis();
    if (overlayOptions.getShowDetections() && !hierarchy.isEmpty()) {
        // If we aren't downsampling by much, or we're upsampling, paint directly - making sure to paint the right number of times, and in the right order
        if (overlayServer == null || regionStore == null || downsampleFactor < 1.0) {
            Collection<PathObject> pathObjects;
            try {
                Set<PathObject> pathObjectsToPaint = new TreeSet<>(comparator);
                pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, region, pathObjectsToPaint);
            } catch (IllegalArgumentException e) {
                // This can happen (rarely) in a multithreaded environment if the level of a detection changes.
                // However, protecting against this fully by caching the level with integer boxing/unboxing would be expensive.
                logger.debug("Exception requesting detections to paint: " + e.getLocalizedMessage(), e);
                pathObjects = hierarchy.getObjectsForRegion(PathDetectionObject.class, region, null);
            }
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
            PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, pathObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
            if (overlayOptions.getShowConnections()) {
                Object connections = imageData.getProperty(DefaultPathObjectConnectionGroup.KEY_OBJECT_CONNECTIONS);
                if (connections instanceof PathObjectConnections)
                    PathHierarchyPaintingHelper.paintConnections((PathObjectConnections) connections, hierarchy, g2d, imageData.isFluorescence() ? ColorToolsAwt.TRANSLUCENT_WHITE : ColorToolsAwt.TRANSLUCENT_BLACK, downsampleFactor);
            }
        } else {
            // On the other hand, if a large image has been updated then we may be browsing quickly - better to repaint quickly while tiles may still be loading
            if (paintCompletely) {
                regionStore.paintRegionCompletely(overlayServer, g2d, shapeRegion, z, t, downsampleFactor, null, null, 5000);
            } else {
                regionStore.paintRegion(overlayServer, g2d, shapeRegion, z, t, downsampleFactor, null, null, null);
            }
        }
    }
    long endTime = System.currentTimeMillis();
    if (endTime - startTime > 500)
        logger.debug("Painting time: {} seconds", GeneralTools.formatNumber((endTime - startTime) / 1000.0, 4));
    // The setting below stops some weird 'jiggling' effects during zooming in/out, or poor rendering of shape ROIs
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, defaultAntiAlias);
    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, defaultStroke);
    // Prepare to handle labels, if we need to
    Collection<PathObject> objectsWithNames = new ArrayList<>();
    Collection<PathObject> annotations = hierarchy.getObjectsForRegion(PathAnnotationObject.class, region, null);
    for (var iterator = annotations.iterator(); iterator.hasNext(); ) {
        var next = iterator.next();
        if ((next.getName() != null && !next.getName().isBlank()))
            objectsWithNames.add(next);
        if (selectedObjects.contains(next))
            iterator.remove();
    }
    // Paint the annotations
    List<PathObject> pathObjectList = new ArrayList<>(annotations);
    Collections.sort(pathObjectList, Comparator.comparingInt(PathObject::getLevel).reversed().thenComparing(Comparator.comparingDouble((PathObject p) -> -p.getROI().getArea())));
    PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, pathObjectList, overlayOptions, null, downsampleFactor);
    // Ensure that selected objects are painted last, to make sure they aren't obscured
    if (!selectedObjects.isEmpty()) {
        Composite previousComposite = g2d.getComposite();
        float opacity = overlayOptions.getOpacity();
        if (opacity < 1) {
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
            PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, selectedObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
            g2d.setComposite(previousComposite);
        } else {
            PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, boundsDisplayed, selectedObjects, overlayOptions, hierarchy.getSelectionModel(), downsampleFactor);
        }
    }
    // Paint labels
    if (overlayOptions.getShowNames() && !objectsWithNames.isEmpty()) {
        double requestedFontSize;
        switch(PathPrefs.viewerFontSizeProperty().get()) {
            case HUGE:
                requestedFontSize = 24;
                break;
            case LARGE:
                requestedFontSize = 18;
                break;
            case SMALL:
                requestedFontSize = 10;
                break;
            case TINY:
                requestedFontSize = 8;
                break;
            case MEDIUM:
            default:
                requestedFontSize = 14;
                break;
        }
        float fontSize = (float) (requestedFontSize * downsampleFactor);
        if (!GeneralTools.almostTheSame(font.getSize2D(), fontSize, 0.001))
            font = font.deriveFont(fontSize);
        g2d.setFont(font);
        var metrics = g2d.getFontMetrics(font);
        var rect = new Rectangle2D.Double();
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        for (var annotation : objectsWithNames) {
            var name = annotation.getName();
            var roi = annotation.getROI();
            if (name != null && !name.isBlank() && roi != null && !overlayOptions.isPathClassHidden(annotation.getPathClass())) {
                g2d.setColor(ColorToolsAwt.TRANSLUCENT_BLACK);
                var bounds = metrics.getStringBounds(name, g2d);
                double pad = 5.0 * downsampleFactor;
                double x = roi.getCentroidX() - bounds.getWidth() / 2.0;
                double y = roi.getCentroidY() + bounds.getY() + metrics.getAscent() + pad;
                rect.setFrame(x + bounds.getX() - pad, y + bounds.getY() - pad, bounds.getWidth() + pad * 2, bounds.getHeight() + pad * 2);
                g2d.fill(rect);
                g2d.setColor(Color.WHITE);
                g2d.drawString(name, (float) x, (float) y);
            }
        }
    }
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathObjectConnections(qupath.lib.objects.PathObjectConnections) Shape(java.awt.Shape) AlphaComposite(java.awt.AlphaComposite) Composite(java.awt.Composite) Rectangle(java.awt.Rectangle) ArrayList(java.util.ArrayList) ImageRegion(qupath.lib.regions.ImageRegion) PathObject(qupath.lib.objects.PathObject) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) TreeSet(java.util.TreeSet) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathObject(qupath.lib.objects.PathObject)

Example 45 with PathObject

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

the class MeasurementExporter method exportMeasurements.

/**
 * Exports the measurements of one or more entries in the project.
 * This function first opens all the images in the project to store
 * all the column names and values of the measurements.
 * Then, it loops through the maps containing the values to write
 * them to the given output stream.
 * @param stream
 */
public void exportMeasurements(OutputStream stream) {
    long startTime = System.currentTimeMillis();
    Map<ProjectImageEntry<?>, String[]> imageCols = new HashMap<>();
    Map<ProjectImageEntry<?>, Integer> nImageEntries = new HashMap<>();
    List<String> allColumns = new ArrayList<>();
    Multimap<String, String> valueMap = LinkedListMultimap.create();
    String pattern = "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)";
    for (ProjectImageEntry<?> entry : imageList) {
        try {
            ImageData<?> imageData = entry.readImageData();
            ObservableMeasurementTableData model = new ObservableMeasurementTableData();
            Collection<PathObject> pathObjects = imageData == null ? Collections.emptyList() : imageData.getHierarchy().getObjects(null, type);
            if (filter != null)
                pathObjects = pathObjects.stream().filter(filter).collect(Collectors.toList());
            model.setImageData(imageData, pathObjects);
            List<String> data = SummaryMeasurementTableCommand.getTableModelStrings(model, separator, excludeColumns);
            // Get header
            String[] header;
            String headerString = data.get(0);
            if (headerString.chars().filter(e -> e == '"').count() > 1)
                header = headerString.split(separator.equals("\t") ? "\\" + separator : separator + pattern, -1);
            else
                header = headerString.split(separator);
            imageCols.put(entry, header);
            nImageEntries.put(entry, data.size() - 1);
            for (String col : header) {
                if (!allColumns.contains(col) && !excludeColumns.contains(col))
                    allColumns.add(col);
            }
            // To keep the same column order, just delete non-relevant columns
            if (!includeOnlyColumns.isEmpty())
                allColumns.removeIf(n -> !includeOnlyColumns.contains(n));
            for (int i = 1; i < data.size(); i++) {
                String[] row;
                String rowString = data.get(i);
                // Check if some values in the row are escaped
                if (rowString.chars().filter(e -> e == '"').count() > 1)
                    row = rowString.split(separator.equals("\t") ? "\\" + separator : separator + pattern, -1);
                else
                    row = rowString.split(separator);
                // Put value in map
                for (int elem = 0; elem < row.length; elem++) {
                    if (allColumns.contains(header[elem]))
                        valueMap.put(header[elem], row[elem]);
                }
            }
        } catch (Exception e) {
            logger.error(e.getLocalizedMessage(), e);
        }
    }
    try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8))) {
        writer.write(String.join(separator, allColumns));
        writer.write(System.lineSeparator());
        Iterator[] its = new Iterator[allColumns.size()];
        for (int col = 0; col < allColumns.size(); col++) {
            its[col] = valueMap.get(allColumns.get(col)).iterator();
        }
        for (ProjectImageEntry<?> entry : imageList) {
            for (int nObject = 0; nObject < nImageEntries.get(entry); nObject++) {
                for (int nCol = 0; nCol < allColumns.size(); nCol++) {
                    if (Arrays.stream(imageCols.get(entry)).anyMatch(allColumns.get(nCol)::equals)) {
                        String val = (String) its[nCol].next();
                        // NaN values -> blank
                        if (val.equals("NaN"))
                            val = "";
                        writer.write(val);
                    }
                    if (nCol < allColumns.size() - 1)
                        writer.write(separator);
                }
                writer.write(System.lineSeparator());
            }
        }
    } catch (Exception e) {
        logger.error("Error writing to file: " + e.getLocalizedMessage(), e);
    }
    long endTime = System.currentTimeMillis();
    long timeMillis = endTime - startTime;
    String time = null;
    if (timeMillis > 1000 * 60)
        time = String.format("Total processing time: %.2f minutes", timeMillis / (1000.0 * 60.0));
    else if (timeMillis > 1000)
        time = String.format("Total processing time: %.2f seconds", timeMillis / (1000.0));
    else
        time = String.format("Total processing time: %d milliseconds", timeMillis);
    logger.info("Processed {} images", imageList.size());
    logger.info(time);
}
Also used : Arrays(java.util.Arrays) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) Multimap(com.google.common.collect.Multimap) ArrayList(java.util.ArrayList) PathRootObject(qupath.lib.objects.PathRootObject) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) Map(java.util.Map) OutputStreamWriter(java.io.OutputStreamWriter) OutputStream(java.io.OutputStream) PrintWriter(java.io.PrintWriter) LinkedListMultimap(com.google.common.collect.LinkedListMultimap) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) Iterator(java.util.Iterator) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) BufferedImage(java.awt.image.BufferedImage) Predicate(java.util.function.Predicate) Collection(java.util.Collection) FileOutputStream(java.io.FileOutputStream) Collectors(java.util.stream.Collectors) File(java.io.File) StandardCharsets(java.nio.charset.StandardCharsets) PathObject(qupath.lib.objects.PathObject) List(java.util.List) SummaryMeasurementTableCommand(qupath.lib.gui.commands.SummaryMeasurementTableCommand) Collections(java.util.Collections) PathPrefs(qupath.lib.gui.prefs.PathPrefs) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) PathObject(qupath.lib.objects.PathObject) Iterator(java.util.Iterator) ProjectImageEntry(qupath.lib.projects.ProjectImageEntry) OutputStreamWriter(java.io.OutputStreamWriter) PrintWriter(java.io.PrintWriter)

Aggregations

PathObject (qupath.lib.objects.PathObject)182 ArrayList (java.util.ArrayList)84 ROI (qupath.lib.roi.interfaces.ROI)74 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)61 List (java.util.List)48 BufferedImage (java.awt.image.BufferedImage)37 IOException (java.io.IOException)37 PathClass (qupath.lib.objects.classes.PathClass)37 Collectors (java.util.stream.Collectors)35 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)34 Map (java.util.Map)33 Logger (org.slf4j.Logger)33 LoggerFactory (org.slf4j.LoggerFactory)33 ImageData (qupath.lib.images.ImageData)31 TMACoreObject (qupath.lib.objects.TMACoreObject)31 Collection (java.util.Collection)29 Collections (java.util.Collections)29 HashMap (java.util.HashMap)28 PathObjectTools (qupath.lib.objects.PathObjectTools)26 Arrays (java.util.Arrays)25