use of qupath.lib.objects.PathObject in project qupath by qupath.
the class BrushTool method createShape.
/**
* Create a new Geometry using the specified tool, assuming a user click/drag at the provided x & y coordinates.
* @param e
*
* @param x
* @param y
* @param useTiles If true, request generating a shape from existing tile objects.
* @param addToShape If provided, it can be assumed that any new shape ought to be added to this one.
* The purpose is that this method may (optionally) use the shape to refine the one it will generate,
* e.g. to avoid having isolated or jagged boundaries.
* @return
*/
protected Geometry createShape(MouseEvent e, double x, double y, boolean useTiles, Geometry addToShape) {
// See if we're on top of a tile
if (useTiles) {
List<PathObject> listSelectable = getSelectableObjectList(x, y);
for (PathObject temp : listSelectable) {
// if ((temp instanceof PathDetectionObject) && temp.getROI() instanceof PathArea)
if (temp instanceof PathTileObject && temp.hasROI() && temp.getROI().isArea() && !(temp.getROI() instanceof RectangleROI)) {
creatingTiledROI = true;
return temp.getROI().getGeometry();
}
}
// If we're currently creating a tiled, ROI, but now not clicked on a tile, just return
if (creatingTiledROI)
return null;
}
// Compute a diameter scaled according to the pressure being applied
double diameter = Math.max(1, getBrushDiameter());
Geometry geometry;
if (lastPoint == null) {
var shapeFactory = new GeometricShapeFactory(getGeometryFactory());
shapeFactory.setCentre(new Coordinate(x, y));
shapeFactory.setSize(diameter);
// shapeFactory.setCentre(new Coordinate(x-diameter/2, y-diameter/2));
geometry = shapeFactory.createEllipse();
} else {
if (lastPoint.distanceSq(x, y) == 0)
return null;
var factory = getGeometryFactory();
geometry = factory.createLineString(new Coordinate[] { new Coordinate(lastPoint.getX(), lastPoint.getY()), new Coordinate(x, y) }).buffer(diameter / 2.0);
}
return geometry;
}
use of qupath.lib.objects.PathObject in project qupath by qupath.
the class BrushTool method mouseDragged.
@Override
public void mouseDragged(MouseEvent e) {
// Note: if the 'freehand' part of the polygon creation isn't desired, just comment out this whole method
super.mouseDragged(e);
ensureCursorType(getRequestedCursor());
if (!e.isPrimaryButtonDown()) {
return;
}
// Can only modify annotations
var viewer = getViewer();
PathObject pathObject = viewer.getSelectedObject();
if (pathObject == null || !pathObject.isAnnotation() || !pathObject.isEditable())
return;
if (pathObject != currentObject) {
logger.warn("Selected object has changed from {} to {}", currentObject, pathObject);
return;
}
ROI currentROI = pathObject.getROI();
if (!(currentROI instanceof ROI))
return;
ROI shapeROI = currentROI;
PathObject pathObjectUpdated = getUpdatedObject(e, shapeROI, pathObject, -1);
if (pathObject != pathObjectUpdated) {
viewer.setSelectedObject(pathObjectUpdated, PathPrefs.selectionModeProperty().get());
} else {
viewer.repaint();
}
}
use of qupath.lib.objects.PathObject 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.objects.PathObject 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.objects.PathObject in project qupath by qupath.
the class AbstractPathTool method getSelectableObjectList.
/**
* Get a list of all selectable objects overlapping the specified x, y coordinates, ordered by depth in the hierarchy
* @param x
* @param y
* @return
*/
List<PathObject> getSelectableObjectList(double x, double y) {
var viewer = getViewer();
PathObjectHierarchy hierarchy = viewer.getHierarchy();
if (hierarchy == null)
return Collections.emptyList();
Collection<PathObject> pathObjects = PathObjectTools.getObjectsForLocation(hierarchy, x, y, viewer.getZPosition(), viewer.getTPosition(), viewer.getMaxROIHandleSize());
if (pathObjects.isEmpty())
return Collections.emptyList();
List<PathObject> pathObjectList = new ArrayList<>(pathObjects);
if (pathObjectList.size() == 1)
return pathObjectList;
Collections.sort(pathObjectList, PathObjectHierarchy.HIERARCHY_COMPARATOR);
return pathObjectList;
}
Aggregations