Search in sources :

Example 31 with ROI

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

the class BrushTool method getUpdatedObject.

private PathObject getUpdatedObject(MouseEvent e, ROI shapeROI, PathObject currentObject, double flatness) {
    Point2D p = mouseLocationToImage(e, true, requestPixelSnapping());
    var viewer = getViewer();
    ImagePlane plane = shapeROI == null ? ImagePlane.getPlane(viewer.getZPosition(), viewer.getTPosition()) : shapeROI.getImagePlane();
    Geometry shapeNew;
    boolean subtractMode = isSubtractMode(e);
    Geometry shapeCurrent = shapeROI == null ? null : shapeROI.getGeometry();
    Geometry shapeDrawn = createShape(e, p.getX(), p.getY(), PathPrefs.useTileBrushProperty().get() && !e.isShiftDown(), subtractMode ? null : shapeCurrent);
    if (shapeDrawn == null)
        return currentObject;
    // Do our pixel snapping now, with the simpler geometry (rather than latter when things are already complex)
    if (requestPixelSnapping())
        shapeDrawn = GeometryTools.roundCoordinates(shapeDrawn);
    lastPoint = p;
    try {
        if (shapeROI != null) {
            // Check to see if any changes are required at all
            if (shapeDrawn == null || (subtractMode && !shapeCurrent.intersects(shapeDrawn)) || (!subtractMode && shapeCurrent.covers(shapeDrawn)))
                return currentObject;
            // TODO: Consider whether a preference should be used rather than the shift key?
            // Anyhow, this will switch to 'dodge' mode, and avoid overlapping existing annotations
            boolean avoidOtherAnnotations = requestParentClipping(e);
            if (subtractMode) {
                // If subtracting... then just subtract
                shapeNew = shapeROI.getGeometry().difference(shapeDrawn);
            } else if (avoidOtherAnnotations) {
                shapeNew = shapeCurrent.union(shapeDrawn);
                shapeNew = refineGeometryByParent(shapeNew);
            } else {
                // Just add, regardless of whether there are other annotations below or not
                var temp = shapeROI.getGeometry();
                try {
                    shapeNew = temp.union(shapeDrawn);
                } catch (Exception e2) {
                    shapeNew = shapeROI.getGeometry();
                }
            }
        } else {
            shapeNew = shapeDrawn;
        }
        // If we aren't snapping, at least remove some vertices
        if (!requestPixelSnapping()) {
            try {
                shapeNew = VWSimplifier.simplify(shapeNew, 0.1);
            } catch (Exception e2) {
                logger.error("Error simplifying ROI: " + e2.getLocalizedMessage(), e2);
            }
        }
        // Make sure we fit inside the image
        shapeNew = GeometryTools.constrainToBounds(shapeNew, 0, 0, viewer.getServerWidth(), viewer.getServerHeight());
        // Sometimes we can end up with a GeometryCollection containing lines/non-areas... if so, remove these
        if (shapeNew instanceof GeometryCollection) {
            shapeNew = GeometryTools.ensurePolygonal(shapeNew);
        }
        ROI roiNew = GeometryTools.geometryToROI(shapeNew, plane);
        if (currentObject instanceof PathAnnotationObject) {
            ((PathAnnotationObject) currentObject).setROI(roiNew);
            return currentObject;
        }
        // shapeNew = new PathAreaROI(new Area(shapeNew.getShape()));
        PathObject pathObjectNew = PathObjects.createAnnotationObject(roiNew, PathPrefs.autoSetAnnotationClassProperty().get());
        if (currentObject != null) {
            pathObjectNew.setName(currentObject.getName());
            pathObjectNew.setColorRGB(currentObject.getColorRGB());
            pathObjectNew.setPathClass(currentObject.getPathClass());
        }
        return pathObjectNew;
    } catch (Exception ex) {
        logger.error("Error updating ROI", ex);
        return currentObject;
    }
}
Also used : Geometry(org.locationtech.jts.geom.Geometry) GeometryCollection(org.locationtech.jts.geom.GeometryCollection) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) Point2D(java.awt.geom.Point2D) ImagePlane(qupath.lib.regions.ImagePlane) RectangleROI(qupath.lib.roi.RectangleROI) ROI(qupath.lib.roi.interfaces.ROI)

Example 32 with ROI

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

the class AbstractPathDraggingROITool method mouseReleased.

@Override
public void mouseReleased(MouseEvent e) {
    if (e.getButton() != MouseButton.PRIMARY) {
        return;
    }
    var viewer = getViewer();
    PathObject selectedObject = viewer.getSelectedObject();
    if (selectedObject == null)
        return;
    RoiEditor editor = viewer.getROIEditor();
    ROI currentROI = selectedObject.getROI();
    if (currentROI != null && editor.getROI() == currentROI && editor.hasActiveHandle()) {
        editor.setROI(null);
        // Remove empty ROIs
        if (currentROI.isEmpty()) {
            if (selectedObject.getParent() != null)
                viewer.getHierarchy().removeObject(selectedObject, true);
            viewer.setSelectedObject(null);
        } else {
            commitObjectToHierarchy(e, selectedObject);
        }
    // editor.ensureHandlesUpdated();
    // editor.resetActiveHandle();
    // if (PathPrefs.getReturnToMoveMode())
    // modes.setMode(Modes.MOVE);
    }
}
Also used : PathObject(qupath.lib.objects.PathObject) RoiEditor(qupath.lib.roi.RoiEditor) ROI(qupath.lib.roi.interfaces.ROI)

Example 33 with ROI

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

the class ObservableMeasurementTableDataTest method test.

@SuppressWarnings("javadoc")
@Test
public void test() {
    // See https://github.com/locationtech/jts/issues/571
    for (int counter = 0; counter < 50; counter++) {
        ImageData<BufferedImage> imageData = new ImageData<>(null);
        PathClass tumorClass = PathClassFactory.getPathClass(StandardPathClasses.TUMOR);
        PathClass stromaClass = PathClassFactory.getPathClass(StandardPathClasses.STROMA);
        // PathClass otherClass = PathClassFactory.getDefaultPathClass(PathClasses.OTHER);
        PathClass artefactClass = PathClassFactory.getPathClass("Artefact");
        PathObjectHierarchy hierarchy = imageData.getHierarchy();
        // Add a parent annotation
        PathObject parent = PathObjects.createAnnotationObject(ROIs.createRectangleROI(500, 500, 1000, 1000, ImagePlane.getDefaultPlane()));
        // Create 100 tumor detections
        // ROI emptyROI = ROIs.createEmptyROI();
        ROI smallROI = ROIs.createRectangleROI(500, 500, 1, 1, ImagePlane.getDefaultPlane());
        for (int i = 0; i < 100; i++) {
            if (i < 25)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getNegative(tumorClass)));
            else if (i < 50)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getOnePlus(tumorClass)));
            else if (i < 75)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getTwoPlus(tumorClass)));
            else if (i < 100)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getThreePlus(tumorClass)));
        }
        // Create 100 stroma detections
        for (int i = 0; i < 100; i++) {
            if (i < 50)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getNegative(stromaClass)));
            else if (i < 60)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getOnePlus(stromaClass)));
            else if (i < 70)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getTwoPlus(stromaClass)));
            else if (i < 100)
                parent.addPathObject(PathObjects.createDetectionObject(smallROI, PathClassFactory.getThreePlus(stromaClass)));
        }
        // Create 50 artefact detections
        for (int i = 0; i < 50; i++) {
            parent.addPathObject(PathObjects.createDetectionObject(smallROI, artefactClass));
        }
        hierarchy.addPathObject(parent);
        ObservableMeasurementTableData model = new ObservableMeasurementTableData();
        model.setImageData(imageData, Collections.singletonList(parent));
        // Check tumor counts
        assertEquals(100, model.getNumericValue(parent, "Num Tumor (base)"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: Negative"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: 1+"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: 2+"), EPSILON);
        assertEquals(25, model.getNumericValue(parent, "Num Tumor: 3+"), EPSILON);
        assertTrue(Double.isNaN(model.getNumericValue(parent, "Num Tumor: 4+")));
        // Check tumor H-score, Allred score & positive %
        assertEquals(150, model.getNumericValue(parent, "Tumor: H-score"), EPSILON);
        assertEquals(75, model.getNumericValue(parent, "Tumor: Positive %"), EPSILON);
        assertEquals(2, model.getNumericValue(parent, "Tumor: Allred intensity"), EPSILON);
        assertEquals(5, model.getNumericValue(parent, "Tumor: Allred proportion"), EPSILON);
        assertEquals(7, model.getNumericValue(parent, "Tumor: Allred score"), EPSILON);
        // Check tumor H-score unaffected when tumor detections added without intensity classification
        for (int i = 0; i < 10; i++) parent.addPathObject(PathObjects.createDetectionObject(smallROI, tumorClass));
        hierarchy.fireHierarchyChangedEvent(this);
        model.refreshEntries();
        // model.setImageData(imageData, Collections.singletonList(parent));
        assertEquals(100, model.getNumericValue(parent, "Num Stroma (base)"), EPSILON);
        assertEquals(50, model.getNumericValue(parent, "Num Stroma: Negative"), EPSILON);
        assertEquals(150, model.getNumericValue(parent, "Tumor: H-score"), EPSILON);
        assertEquals(75, model.getNumericValue(parent, "Tumor: Positive %"), EPSILON);
        // Check stroma scores
        assertEquals(100, model.getNumericValue(parent, "Num Stroma (base)"), EPSILON);
        assertEquals(120, model.getNumericValue(parent, "Stroma: H-score"), EPSILON);
        // Check complete scores
        assertEquals(135, model.getNumericValue(parent, "Stroma + Tumor: H-score"), EPSILON);
        // Add a new parent that completely contains the current object, and confirm complete scores agree
        PathObject parentNew = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0, 0, 2000, 2000, ImagePlane.getDefaultPlane()));
        hierarchy.addPathObject(parentNew);
        model.refreshEntries();
        assertEquals(135, model.getNumericValue(parent, "Stroma + Tumor: H-score"), EPSILON);
        assertEquals(135, model.getNumericValue(parentNew, "Stroma + Tumor: H-score"), EPSILON);
        // Create a new object and demonstrate Allred dependence on a single cell
        PathObject parentAllred = PathObjects.createAnnotationObject(ROIs.createRectangleROI(4000, 4000, 1000, 1000, ImagePlane.getDefaultPlane()));
        ROI newROI = ROIs.createEllipseROI(4500, 4500, 10, 10, ImagePlane.getDefaultPlane());
        for (int i = 0; i < 100; i++) parentAllred.addPathObject(PathObjects.createDetectionObject(newROI, PathClassFactory.getNegative(tumorClass)));
        hierarchy.addPathObject(parentAllred);
        model.refreshEntries();
        assertEquals(0, model.getNumericValue(parentAllred, "Tumor: Allred score"), EPSILON);
        parentAllred.addPathObject(PathObjects.createDetectionObject(newROI, PathClassFactory.getThreePlus(tumorClass)));
        hierarchy.fireHierarchyChangedEvent(parentAllred);
        model.refreshEntries();
        assertEquals(4, model.getNumericValue(parentAllred, "Tumor: Allred score"), EPSILON);
    }
}
Also used : PathClass(qupath.lib.objects.classes.PathClass) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ObservableMeasurementTableData(qupath.lib.gui.measure.ObservableMeasurementTableData) PathObject(qupath.lib.objects.PathObject) ImageData(qupath.lib.images.ImageData) ROI(qupath.lib.roi.interfaces.ROI) BufferedImage(java.awt.image.BufferedImage) Test(org.junit.jupiter.api.Test)

Example 34 with ROI

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

the class MoveTool method mouseMoved.

@Override
public void mouseMoved(MouseEvent e) {
    super.mouseMoved(e);
    // We don't want to change a waiting cursor unnecessarily
    var viewer = getViewer();
    Cursor cursorType = viewer.getCursor();
    if (cursorType == Cursor.WAIT)
        return;
    // If we are already translating, we must need a move cursor
    if (viewer.getROIEditor().isTranslating()) {
        if (cursorType != Cursor.MOVE)
            viewer.setCursor(Cursor.MOVE);
        return;
    }
    // Check if we should have a panning or moving cursor, changing if required
    ROI currentROI = viewer.getCurrentROI();
    if (currentROI != null && canAdjust(viewer.getSelectedObject())) {
        Point2D p2 = mouseLocationToImage(e, true, false);
        double xx = p2.getX();
        double yy = p2.getY();
        if (RoiTools.areaContains(currentROI, xx, yy)) {
            ensureCursorType(Cursor.MOVE);
            return;
        }
    }
    ensureCursorType(Cursor.HAND);
}
Also used : Point2D(java.awt.geom.Point2D) Cursor(javafx.scene.Cursor) ROI(qupath.lib.roi.interfaces.ROI)

Example 35 with ROI

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

the class MoveTool method mouseReleased.

@Override
public void mouseReleased(MouseEvent e) {
    super.mouseReleased(e);
    if (e.isConsumed())
        return;
    var viewer = getViewer();
    RoiEditor editor = viewer.getROIEditor();
    if (editor != null && (editor.hasActiveHandle() || editor.isTranslating())) {
        boolean roiChanged = (editor.isTranslating() && editor.finishTranslation()) || editor.hasActiveHandle();
        editor.resetActiveHandle();
        // if (editor.isTranslating())
        // editor.finishTranslation();
        e.consume();
        PathObject pathObject = viewer.getSelectedObject();
        if (requestParentClipping(e) && pathObject instanceof PathAnnotationObject) {
            ROI roiNew = refineROIByParent(pathObject.getROI());
            ((PathAnnotationObject) pathObject).setROI(roiNew);
        }
        if (pathObject != null && pathObject.hasROI() && pathObject.getROI().isEmpty()) {
            if (pathObject.getParent() != null)
                viewer.getHierarchy().removeObject(pathObject, true);
            viewer.setSelectedObject(null);
        } else {
            PathObjectHierarchy hierarchy = viewer.getHierarchy();
            if (pathObject instanceof TMACoreObject) {
                hierarchy.fireHierarchyChangedEvent(pathObject);
            } else if (pathObject != null) {
                // Handle ROI changes only if required
                if (roiChanged) {
                    var updatedROI = editor.getROI();
                    if (pathObject.getROI() != updatedROI && pathObject instanceof PathROIObject)
                        ((PathROIObject) pathObject).setROI(updatedROI);
                    // PathObject parentPrevious = pathObject.getParent();
                    hierarchy.removeObjectWithoutUpdate(pathObject, true);
                    if (getCurrentParent() == null || !PathPrefs.clipROIsForHierarchyProperty().get() || e.isShiftDown())
                        hierarchy.addPathObject(pathObject);
                    else
                        hierarchy.addPathObjectBelowParent(getCurrentParent(), pathObject, true);
                // PathObject parentNew = pathObject.getParent();
                // if (parentPrevious == parentNew)
                // hierarchy.fireHierarchyChangedEvent(this, parentPrevious);
                // else
                // hierarchy.fireHierarchyChangedEvent(this);
                }
            }
            viewer.setSelectedObject(pathObject);
        }
    }
    // Optionally continue a dragging movement until the canvas comes to a standstill
    if (pDragging != null && requestDynamicDragging && System.currentTimeMillis() - lastDragTimestamp < 100 && (dx * dx + dy * dy > viewer.getDownsampleFactor())) {
        mover = new ViewerMover(viewer);
        mover.startMoving(dx, dy, false);
    } else
        viewer.setDoFasterRepaint(false);
    // Make sure we don't have a previous point (to prevent weird dragging artefacts)
    pDragging = null;
// // If we were translating, stop
// if (editor.isTranslating()) {
// editor.finishTranslation();
// // TODO: Make this more efficient!
// viewer.getPathObjectHierarchy().fireHierarchyChangedEvent();
// return;
// }
}
Also used : PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) RoiEditor(qupath.lib.roi.RoiEditor) TMACoreObject(qupath.lib.objects.TMACoreObject) PathROIObject(qupath.lib.objects.PathROIObject) ROI(qupath.lib.roi.interfaces.ROI)

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