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;
}
}
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);
}
}
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);
}
}
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);
}
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;
// }
}
Aggregations