Search in sources :

Example 1 with PathObjectConnections

use of qupath.lib.objects.PathObjectConnections 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 2 with PathObjectConnections

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

the class PathHierarchyImageServer method readTile.

@Override
protected BufferedImage readTile(TileRequest tileRequest) throws IOException {
    RegionRequest request = tileRequest.getRegionRequest();
    // long startTime = System.currentTimeMillis();
    // Get connections
    Object o = options.getShowConnections() ? imageData.getProperty(DefaultPathObjectConnectionGroup.KEY_OBJECT_CONNECTIONS) : null;
    PathObjectConnections connections = (o instanceof PathObjectConnections) ? (PathObjectConnections) o : null;
    List<PathObject> pathObjects = new ArrayList<>(getObjectsToPaint(request));
    if (pathObjects == null || pathObjects.isEmpty()) {
        // We can only return null if no connections - otherwise we might still need to draw something
        if (connections == null) {
            return null;
        }
    }
    // Because levels *can* change, we need to extract them first to avoid breaking the contract for comparable
    // in a multithreaded environment
    var levels = pathObjects.stream().collect(Collectors.toMap(p -> p, p -> p.getLevel()));
    var comparator = DefaultPathObjectComparator.getInstance().thenComparingInt(p -> levels.get(p));
    Collections.sort(pathObjects, comparator);
    // Collections.sort(pathObjects, new HierarchyOverlay.DetectionComparator());
    double downsampleFactor = request.getDownsample();
    int width = tileRequest.getTileWidth();
    int height = tileRequest.getTileHeight();
    BufferedImage img = createDefaultRGBImage(width, height);
    Graphics2D g2d = img.createGraphics();
    g2d.setClip(0, 0, width, height);
    // g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    double scale = 1.0 / downsampleFactor;
    // g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.scale(scale, scale);
    g2d.translate(-request.getX(), -request.getY());
    // Note we don't want to pass a selection model, as selections shouldn't be included
    if (pathObjects != null && !pathObjects.isEmpty())
        PathHierarchyPaintingHelper.paintSpecifiedObjects(g2d, AwtTools.getBounds(request), pathObjects, options, null, downsampleFactor);
    // See if we have any connections to draw
    if (connections != null) {
        PathHierarchyPaintingHelper.paintConnections(connections, hierarchy, g2d, imageData.isFluorescence() ? ColorToolsAwt.TRANSLUCENT_WHITE : ColorToolsAwt.TRANSLUCENT_BLACK, downsampleFactor);
    }
    g2d.dispose();
    // System.out.println("Single tile image creation time: " + (endTime - startTime)/1000.);
    return img;
}
Also used : ImageServer(qupath.lib.images.servers.ImageServer) DefaultPathObjectConnectionGroup(qupath.lib.objects.DefaultPathObjectConnectionGroup) ImageChannel(qupath.lib.images.servers.ImageChannel) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ArrayList(java.util.ArrayList) PathObjectConnections(qupath.lib.objects.PathObjectConnections) Graphics2D(java.awt.Graphics2D) GeneratingImageServer(qupath.lib.images.servers.GeneratingImageServer) ImageResolutionLevel(qupath.lib.images.servers.ImageServerMetadata.ImageResolutionLevel) TileRequest(qupath.lib.images.servers.TileRequest) PathHierarchyPaintingHelper(qupath.lib.gui.viewer.PathHierarchyPaintingHelper) URI(java.net.URI) ColorToolsAwt(qupath.lib.color.ColorToolsAwt) ImageData(qupath.lib.images.ImageData) BufferedImage(java.awt.image.BufferedImage) RegionRequest(qupath.lib.regions.RegionRequest) PixelType(qupath.lib.images.servers.PixelType) Collection(java.util.Collection) AwtTools(qupath.lib.awt.common.AwtTools) IOException(java.io.IOException) DefaultPathObjectComparator(qupath.lib.objects.DefaultPathObjectComparator) UUID(java.util.UUID) ServerBuilder(qupath.lib.images.servers.ImageServerBuilder.ServerBuilder) Collectors(java.util.stream.Collectors) OverlayOptions(qupath.lib.gui.viewer.OverlayOptions) AbstractTileableImageServer(qupath.lib.images.servers.AbstractTileableImageServer) PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathObject(qupath.lib.objects.PathObject) List(java.util.List) Collections(java.util.Collections) ImageServerMetadata(qupath.lib.images.servers.ImageServerMetadata) PathObjectConnections(qupath.lib.objects.PathObjectConnections) PathObject(qupath.lib.objects.PathObject) ArrayList(java.util.ArrayList) PathDetectionObject(qupath.lib.objects.PathDetectionObject) PathObject(qupath.lib.objects.PathObject) RegionRequest(qupath.lib.regions.RegionRequest) BufferedImage(java.awt.image.BufferedImage) Graphics2D(java.awt.Graphics2D)

Aggregations

ArrayList (java.util.ArrayList)2 OverlayOptions (qupath.lib.gui.viewer.OverlayOptions)2 PathDetectionObject (qupath.lib.objects.PathDetectionObject)2 PathObject (qupath.lib.objects.PathObject)2 PathObjectConnections (qupath.lib.objects.PathObjectConnections)2 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)2 AlphaComposite (java.awt.AlphaComposite)1 Composite (java.awt.Composite)1 Graphics2D (java.awt.Graphics2D)1 Rectangle (java.awt.Rectangle)1 Shape (java.awt.Shape)1 BufferedImage (java.awt.image.BufferedImage)1 IOException (java.io.IOException)1 URI (java.net.URI)1 Collection (java.util.Collection)1 Collections (java.util.Collections)1 List (java.util.List)1 TreeSet (java.util.TreeSet)1 UUID (java.util.UUID)1 Collectors (java.util.stream.Collectors)1