Search in sources :

Example 1 with ImageRegion

use of qupath.lib.regions.ImageRegion in project qupath by qupath.

the class RigidObjectEditorCommand method run.

@Override
public void run() {
    // Object is already being edited
    if (this.originalObject != null) {
        // viewer.setSelectedObject(tempObject);
        return;
    }
    // Get the selected object
    viewer = qupath.getViewer();
    PathObject pathObject = getSelectedObject(viewer);
    if (pathObject == null || !(pathObject.isAnnotation() || pathObject.isTMACore())) {
        Dialogs.showErrorNotification("Rotate annotation", "No annotation selected!");
        return;
    }
    if (pathObject.isLocked()) {
        Dialogs.showErrorNotification("Rotate annotation", "Selected annotation is locked!");
        return;
    }
    // if (pathObject.getROI().isPoint()) {
    // Dialogs.showErrorNotification("Rotate annotation", "Point annotations cannot be rotated, sorry!");
    // return;
    // }
    ImageRegion bounds = viewer.getServerBounds();
    if (pathObject.isTMACore()) {
        for (PathObject child : pathObject.getChildObjectsAsArray()) {
            if (isSuitableAnnotation(child)) {
                originalObjectROIs.put(child, child.getROI());
            }
        }
        if (originalObjectROIs.isEmpty()) {
            Dialogs.showErrorMessage("Rigid refinement problem", "TMA core must contain empty annotations objects for rigid refinement");
            return;
        }
    }
    originalObjectROIs.put(pathObject, pathObject.getROI());
    this.originalObject = pathObject;
    viewer.setActiveTool(null);
    qupath.setToolSwitchingEnabled(false);
    viewer.addViewerListener(this);
    viewer.getView().addEventHandler(MouseEvent.ANY, mouseListener);
    // // Remove selected object & create an overlay showing the currently-being-edited version
    // viewer.getHierarchy().removeObject(originalObject, true, true);
    transformer = new RoiAffineTransformer(bounds, originalObject.getROI());
    // editingROI = new RotatedROI((PathArea)originalObject.getROI());
    // editingROI.setAngle(Math.PI/3);
    overlay = new AffineEditOverlay(viewer.getOverlayOptions());
    viewer.getCustomOverlayLayers().add(overlay);
    PathPrefs.paintSelectedBoundsProperty().set(false);
    // Create & show temporary object
    for (Entry<PathObject, ROI> entry : originalObjectROIs.entrySet()) ((PathROIObject) entry.getKey()).setROI(transformer.getTransformedROI(entry.getValue(), false));
    // Reset any existing editor (and its visible handles)
    viewer.getROIEditor().setROI(null);
    viewer.repaint();
// tempObject = createTransformedObject();
// ((PathAnnotationObject)tempObject).setLocked(true);
// viewer.setSelectedObject(tempObject);
}
Also used : PathObject(qupath.lib.objects.PathObject) ImageRegion(qupath.lib.regions.ImageRegion) PolylineROI(qupath.lib.roi.PolylineROI) ROI(qupath.lib.roi.interfaces.ROI)

Example 2 with ImageRegion

use of qupath.lib.regions.ImageRegion in project qupath by qupath.

the class ParallelTileObject method resolveOverlaps.

/**
 * Request that the tile object attempts to resolve overlaps with its neighboring tiles.
 */
public synchronized void resolveOverlaps() {
    // // If we don't have any children, notify that the test is complete
    // if (!hasChildren()) {
    // for (ParallelTileObject pto : map.keySet())
    // pto.notifyTestComplete(this);
    // map.clear();
    // checkAllTestsComplete();
    // return;
    // }
    long startTime = System.currentTimeMillis();
    int nRemoved = 0;
    boolean preferNucleus = false;
    // If we do have children, loop through & perform tests
    Iterator<Entry<ParallelTileObject, Rectangle2D>> iterMap = map.entrySet().iterator();
    while (iterMap.hasNext()) {
        Entry<ParallelTileObject, Rectangle2D> entry = iterMap.next();
        // If the parallel tile object hasn't been processed yet, then just continue - nothing to compare
        ParallelTileObject pto = entry.getKey();
        if (!pto.isComplete())
            continue;
        ParallelTileObject first, second;
        // Choose a consistent order for the comparison
        if (this.getROI().getBoundsX() > pto.getROI().getBoundsX() || this.getROI().getBoundsY() > pto.getROI().getBoundsY()) {
            first = this;
            second = pto;
        } else {
            first = pto;
            second = this;
        }
        // ROI firstBounds = first.getROI();
        // ROI secondBounds = second.getROI();
        // Compare this object's lists with that object's list
        List<PathObject> listFirst = first.getObjectsForRegion(entry.getValue());
        List<PathObject> listSecond = second.getObjectsForRegion(entry.getValue());
        // Only need to compare potential overlaps if both lists are non-empty
        if (!listFirst.isEmpty() && !listSecond.isEmpty()) {
            Map<ROI, Geometry> cache = new HashMap<>();
            Iterator<PathObject> iterFirst = listFirst.iterator();
            while (iterFirst.hasNext()) {
                PathObject firstObject = iterFirst.next();
                ROI firstROI = PathObjectTools.getROI(firstObject, preferNucleus);
                ImageRegion firstRegion = ImageRegion.createInstance(firstROI);
                Geometry firstGeometry = null;
                double firstArea = Double.NaN;
                Iterator<PathObject> iterSecond = listSecond.iterator();
                while (iterSecond.hasNext()) {
                    PathObject secondObject = iterSecond.next();
                    ROI secondROI = PathObjectTools.getROI(secondObject, preferNucleus);
                    // Do quick overlap test
                    if (!firstRegion.intersects(secondROI.getBoundsX(), secondROI.getBoundsY(), secondROI.getBoundsWidth(), secondROI.getBoundsHeight()))
                        continue;
                    // Get geometries
                    if (firstGeometry == null) {
                        firstGeometry = firstROI.getGeometry();
                        firstArea = firstGeometry.getArea();
                    }
                    Geometry secondGeometry = cache.get(secondROI);
                    if (secondGeometry == null) {
                        secondGeometry = secondROI.getGeometry();
                        cache.put(secondROI, secondGeometry);
                    }
                    Geometry intersection;
                    try {
                        // Get the intersection
                        if (!firstGeometry.intersects(secondGeometry))
                            continue;
                        intersection = firstGeometry.intersection(secondGeometry);
                    } catch (Exception e) {
                        logger.warn("Error resolving overlaps: {}", e.getLocalizedMessage());
                        logger.debug(e.getLocalizedMessage(), e);
                        continue;
                    }
                    if (intersection.isEmpty())
                        continue;
                    // Check areas
                    double intersectionArea = intersection.getArea();
                    double secondArea = secondGeometry.getArea();
                    double threshold = 0.1;
                    if (firstArea >= secondArea) {
                        if (intersectionArea / secondArea > threshold) {
                            second.removePathObject(secondObject);
                            nRemoved++;
                        }
                    } else {
                        if (intersectionArea / firstArea > threshold) {
                            first.removePathObject(firstObject);
                            nRemoved++;
                            break;
                        }
                    }
                }
            }
        }
        // Remove the neighbor from the map
        iterMap.remove();
        pto.notifyTestComplete(this);
    }
    checkAllTestsComplete();
    long endTime = System.currentTimeMillis();
    logger.debug(String.format("Resolved %d overlaps: %.2f seconds", nRemoved, (endTime - startTime) / 1000.));
// logger.info(String.format("Resolved %d possible overlaps with %d iterations (tested %d of %d): %.2f seconds", nOverlaps, counter, detectedCounter-skipCounter, detectedCounter, (endTime2 - startTime2) / 1000.));
}
Also used : HashMap(java.util.HashMap) Rectangle2D(java.awt.geom.Rectangle2D) ImageRegion(qupath.lib.regions.ImageRegion) ROI(qupath.lib.roi.interfaces.ROI) Geometry(org.locationtech.jts.geom.Geometry) Entry(java.util.Map.Entry) PathObject(qupath.lib.objects.PathObject)

Example 3 with ImageRegion

use of qupath.lib.regions.ImageRegion in project qupath by qupath.

the class TileExporter method getTiledRegionRequests.

Collection<RegionRequestWrapper> getTiledRegionRequests(double downsample) {
    List<RegionRequestWrapper> requests = new ArrayList<>();
    if (downsample == 0)
        throw new IllegalArgumentException("No downsample was specified!");
    ImageRegion regionLocal = region == null ? RegionRequest.createInstance(server, downsample) : region;
    // Z and T shouldn't be lower than 0
    int minZLocal = minZ < 0 ? 0 : minZ;
    int minTLocal = minT < 0 ? 0 : minT;
    // Cap Z and T variables to their maximum possible value if needed
    int maxZLocal = maxZ > server.nZSlices() || maxZ == -1 ? server.nZSlices() : maxZ;
    int maxTLocal = maxT > server.nTimepoints() || maxT == -1 ? server.nTimepoints() : maxT;
    // Create another region to account for ImageRegion and RegionRequest params simultaneously
    var regionLocal2 = RegionRequest.createInstance(server.getPath(), downsample, regionLocal);
    for (int t = minTLocal; t < maxTLocal; t++) {
        regionLocal2 = regionLocal2.updateT(t);
        for (int z = minZLocal; z < maxZLocal; z++) {
            regionLocal2 = regionLocal2.updateZ(z);
            requests.addAll(splitRegionRequests(regionLocal2, tileWidth, tileHeight, overlapX, overlapY, includePartialTiles));
        }
    }
    return requests;
}
Also used : ArrayList(java.util.ArrayList) ImageRegion(qupath.lib.regions.ImageRegion)

Example 4 with ImageRegion

use of qupath.lib.regions.ImageRegion 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 5 with ImageRegion

use of qupath.lib.regions.ImageRegion 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)

Aggregations

ImageRegion (qupath.lib.regions.ImageRegion)12 PathObject (qupath.lib.objects.PathObject)6 BufferedImage (java.awt.image.BufferedImage)4 ArrayList (java.util.ArrayList)3 QuPathViewer (qupath.lib.gui.viewer.QuPathViewer)3 ROI (qupath.lib.roi.interfaces.ROI)3 Graphics2D (java.awt.Graphics2D)2 Rectangle (java.awt.Rectangle)2 HashMap (java.util.HashMap)2 Geometry (org.locationtech.jts.geom.Geometry)2 Logger (org.slf4j.Logger)2 LoggerFactory (org.slf4j.LoggerFactory)2 OverlayOptions (qupath.lib.gui.viewer.OverlayOptions)2 ImageData (qupath.lib.images.ImageData)2 ImageServer (qupath.lib.images.servers.ImageServer)2 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)2 RegionRequest (qupath.lib.regions.RegionRequest)2 Calibration (ij.measure.Calibration)1 AlphaComposite (java.awt.AlphaComposite)1 Composite (java.awt.Composite)1