use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class PointsTool method mousePressed.
@Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
if (!e.isPrimaryButtonDown() || e.isConsumed()) {
return;
}
// Get a server, if we can
var viewer = getViewer();
ImageServer<?> server = viewer.getServer();
if (server == null)
return;
var viewerPlane = viewer.getImagePlane();
// Find out the coordinates in the image domain
Point2D p = mouseLocationToImage(e, false, requestPixelSnapping());
double xx = p.getX();
double yy = p.getY();
// If we are outside the image, ignore click
if (xx < 0 || yy < 0 || xx >= server.getWidth() || yy >= server.getHeight())
return;
// See if we have a selected ROI
PathObject currentObjectTemp = viewer.getSelectedObject();
if (!(currentObjectTemp == null || currentObjectTemp instanceof PathROIObject))
return;
PathROIObject currentObject = (PathROIObject) currentObjectTemp;
ROI currentROI = currentObject == null ? null : currentObject.getROI();
RoiEditor editor = viewer.getROIEditor();
double radius = PathPrefs.pointRadiusProperty().get();
ROI points = null;
if (currentROI != null && currentROI.isPoint() && (currentROI.isEmpty() || currentROI.getImagePlane().equals(viewerPlane)))
points = currentROI;
// If Alt is pressed, try to delete a point
if (e.isAltDown()) {
handleAltClick(xx, yy, currentObject);
} else // Create a new ROI if we've got Alt & Shift pressed - or we just don't have a point ROI
if (points == null || (!PathPrefs.multipointToolProperty().get() && !editor.grabHandle(xx, yy, radius, e.isShiftDown())) || (e.isShiftDown() && e.getClickCount() > 1)) {
// PathPoints is effectively ready from the start - don't need to finalize
points = ROIs.createPointsROI(xx, yy, viewerPlane);
currentObject = (PathROIObject) PathObjects.createAnnotationObject(points, PathPrefs.autoSetAnnotationClassProperty().get());
viewer.getHierarchy().addPathObject(currentObject);
viewer.setSelectedObject(currentObject);
// viewer.createAnnotationObject(points);
editor.setROI(points);
editor.grabHandle(xx, yy, radius, e.isShiftDown());
} else if (points != null) {
// Add point to current ROI, or adjust the position of a nearby point
ImagePlane plane = points == null || points.isEmpty() ? viewerPlane : points.getImagePlane();
ROI points2 = addPoint(points, xx, yy, radius, plane);
if (points2 == points) {
// If we didn't add a point, try to grab a handle
if (!editor.grabHandle(xx, yy, radius, e.isShiftDown()))
return;
points2 = (PointsROI) editor.setActiveHandlePosition(xx, yy, 0.25, e.isShiftDown());
} else {
editor.setROI(points2);
editor.grabHandle(xx, yy, radius, e.isShiftDown());
}
if (points2 != points) {
currentObject.setROI(points2);
viewer.getHierarchy().updateObject(currentObject, true);
// viewer.getHierarchy().fireHierarchyChangedEvent(this, currentObject);
}
}
viewer.repaint();
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class DensityMaps method findHotspots.
/**
* Find hotspots in a density map.
*
* @param hierarchy hierarchy used to obtain selected objects and add hotspots
* @param densityServer the density map to query
* @param channel channel in which to find hotspots (usually 0)
* @param nHotspots maximum number of hotspots to find per selected annotation
* @param radius hotspot radius, in calibrated units
* @param minCount minimum value required in the 'count' channel (the last channel)
* @param hotspotClass the classification to apply to hotspots
* @param deleteExisting optionally delete existing annotations identified as hotspots
* @param peaksOnly optionally restrict hotspots to only include intensity peaks
* @throws IOException
*/
public static void findHotspots(PathObjectHierarchy hierarchy, ImageServer<BufferedImage> densityServer, int channel, int nHotspots, double radius, double minCount, PathClass hotspotClass, boolean deleteExisting, boolean peaksOnly) throws IOException {
if (nHotspots <= 0) {
logger.warn("Number of hotspots requested is {}!", nHotspots);
return;
}
logger.debug("Finding {} hotspots in {} for channel {}, radius {}", nHotspots, densityServer, channel, radius);
Collection<PathObject> parents = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
if (parents.isEmpty())
parents = Collections.singleton(hierarchy.getRootObject());
double downsample = densityServer.getDownsampleForResolution(0);
var toDelete = new HashSet<PathObject>();
// Handle deleting existing hotspots
if (deleteExisting) {
toDelete.addAll(hierarchy.getAnnotationObjects().stream().filter(p -> p.getPathClass() == hotspotClass && p.isAnnotation() && p.getName() != null && p.getName().startsWith("Hotspot")).collect(Collectors.toList()));
}
// Convert radius to pixels
double radiusPixels = radius / densityServer.getPixelCalibration().getAveragedPixelSize().doubleValue();
try (@SuppressWarnings("unchecked") var scope = new PointerScope()) {
for (var parent : parents) {
ROI roi = parent.getROI();
// We need a ROI to define the area of interest
if (roi == null) {
if (densityServer.nTimepoints() > 1 || densityServer.nZSlices() > 1) {
logger.warn("Hotspot detection without a parent object not supported for images with multiple z-slices/timepoints.");
logger.warn("I will apply detection to the first plane only. If you need hotspots elsewhere, create an annotation first and use it to define the ROI.");
}
roi = ROIs.createRectangleROI(0, 0, densityServer.getWidth(), densityServer.getHeight(), ImagePlane.getDefaultPlane());
}
// Erode the ROI & see if any hotspot could fit
var roiEroded = RoiTools.buffer(roi, -radiusPixels);
if (roiEroded.isEmpty() || roiEroded.getArea() == 0) {
logger.warn("ROI is too small! Cannot detected hotspots with radius {} in {}", radius, parent);
continue;
}
// Read the image
var plane = roi.getImagePlane();
RegionRequest request = RegionRequest.createInstance(densityServer.getPath(), downsample, 0, 0, densityServer.getWidth(), densityServer.getHeight(), plane.getZ(), plane.getT());
var img = densityServer.readBufferedImage(request);
// Create a mask
var imgMask = BufferedImageTools.createROIMask(img.getWidth(), img.getHeight(), roiEroded, request);
// Switch to OpenCV
var mat = OpenCVTools.imageToMat(img);
var matMask = OpenCVTools.imageToMat(imgMask);
// Find hotspots
var channels = OpenCVTools.splitChannels(mat);
var density = channels.get(channel);
if (minCount > 0) {
var thresholdMask = opencv_core.greaterThan(channels.get(channels.size() - 1), minCount).asMat();
opencv_core.bitwise_and(matMask, thresholdMask, matMask);
thresholdMask.close();
}
// TODO: Limit to peaks
if (peaksOnly) {
var matMaxima = OpenCVTools.findRegionalMaxima(density);
var matPeaks = OpenCVTools.shrinkLabels(matMaxima);
matPeaks.put(opencv_core.greaterThan(matPeaks, 0));
opencv_core.bitwise_and(matMask, matPeaks, matMask);
matPeaks.close();
matMaxima.close();
}
// Sort in descending order
var maxima = new ArrayList<>(OpenCVTools.getMaskedPixels(density, matMask));
Collections.sort(maxima, Comparator.comparingDouble((IndexedPixel p) -> p.getValue()).reversed());
// Try to get as many maxima as we need
// Impose minimum separation
var points = maxima.stream().map(p -> new Point2(p.getX() * downsample, p.getY() * downsample)).collect(Collectors.toList());
var hotspotCentroids = new ArrayList<Point2>();
double distSqThreshold = radiusPixels * radiusPixels * 4;
for (var p : points) {
// Check not too close to an existing hotspot
boolean skip = false;
for (var p2 : hotspotCentroids) {
if (p.distanceSq(p2) < distSqThreshold) {
skip = true;
break;
}
}
if (!skip) {
hotspotCentroids.add(p);
if (hotspotCentroids.size() == nHotspots)
break;
}
}
var hotspots = new ArrayList<PathObject>();
int i = 0;
for (var p : hotspotCentroids) {
i++;
var ellipse = ROIs.createEllipseROI(p.getX() - radiusPixels, p.getY() - radiusPixels, radiusPixels * 2, radiusPixels * 2, roi.getImagePlane());
var hotspot = PathObjects.createAnnotationObject(ellipse, hotspotClass);
hotspot.setName("Hotspot " + i);
hotspots.add(hotspot);
}
if (hotspots.isEmpty())
logger.warn("No hotspots found in {}", parent);
else if (hotspots.size() < nHotspots) {
logger.warn("Only {}/{} hotspots could be found in {}", hotspots.size(), nHotspots, parent);
}
parent.addPathObjects(hotspots);
}
hierarchy.fireHierarchyChangedEvent(DensityMaps.class);
if (!toDelete.isEmpty())
hierarchy.removeObjects(toDelete, true);
}
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class FillAnnotationHolesPlugin method getTasks.
@Override
protected Collection<Runnable> getTasks(final PluginRunner<T> runner) {
Collection<? extends PathObject> parentObjects = getParentObjects(runner);
if (parentObjects == null || parentObjects.isEmpty())
return Collections.emptyList();
// Add a single task, to avoid multithreading - which may complicate setting parents
List<Runnable> tasks = new ArrayList<>(1);
PathObjectHierarchy hierarchy = getHierarchy(runner);
// Want to reset selection
PathObject selected = hierarchy.getSelectionModel().getSelectedObject();
Collection<PathObject> previousSelection = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects());
tasks.add(() -> {
Map<PathROIObject, ROI> toUpdate = new HashMap<>();
for (PathObject pathObject : parentObjects) {
ROI roiOrig = pathObject.getROI();
if (roiOrig == null || !roiOrig.isArea())
continue;
ROI roiUpdated = roiOrig;
roiUpdated = RoiTools.fillHoles(roiUpdated);
if (roiOrig != roiUpdated && pathObject instanceof PathROIObject) {
toUpdate.put((PathROIObject) pathObject, roiUpdated);
}
}
if (toUpdate.isEmpty())
return;
hierarchy.getSelectionModel().clearSelection();
if (!toUpdate.isEmpty()) {
hierarchy.removeObjects(toUpdate.keySet(), true);
toUpdate.forEach((p, r) -> p.setROI(r));
hierarchy.addPathObjects(toUpdate.keySet());
}
hierarchy.getSelectionModel().selectObjects(previousSelection);
hierarchy.getSelectionModel().setSelectedObject(selected, true);
});
return tasks;
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class DilateAnnotationPlugin method addExpandedAnnotation.
/**
* Create and add a new annotation by expanding the ROI of the specified PathObject.
*
* @param bounds
* @param hierarchy
* @param pathObject
* @param radiusPixels
* @param constrainToParent
* @param removeInterior
*/
private static void addExpandedAnnotation(final Rectangle bounds, final PathObjectHierarchy hierarchy, final PathObject pathObject, final double radiusPixels, final boolean constrainToParent, final boolean removeInterior, final LineCap cap) {
ROI roi = pathObject.getROI();
Geometry geometry = roi.getGeometry();
int capVal = BufferParameters.CAP_ROUND;
if (cap == LineCap.FLAT)
capVal = BufferParameters.CAP_FLAT;
else if (cap == LineCap.SQUARE)
capVal = BufferParameters.CAP_SQUARE;
Geometry geometry2 = BufferOp.bufferOp(geometry, radiusPixels, BufferParameters.DEFAULT_QUADRANT_SEGMENTS, capVal);
// If the radius is negative (i.e. an erosion), then the parent will be the original object itself
boolean isErosion = radiusPixels < 0;
PathObject parent = isErosion ? pathObject : pathObject.getParent();
if (constrainToParent && !isErosion) {
Geometry parentShape;
if (parent == null || parent.getROI() == null)
parentShape = ROIs.createRectangleROI(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), ImagePlane.getPlane(roi)).getGeometry();
else
parentShape = parent.getROI().getGeometry();
geometry2 = geometry2.intersection(parentShape);
}
if (removeInterior) {
// Difference isn't supported for GeometryCollections
if (isErosion) {
geometry = GeometryTools.homogenizeGeometryCollection(geometry);
geometry2 = geometry.difference(geometry2);
} else {
if (geometry.getArea() == 0.0)
geometry = geometry.buffer(0.5);
geometry2 = GeometryTools.homogenizeGeometryCollection(geometry2);
geometry = GeometryTools.homogenizeGeometryCollection(geometry);
geometry2 = geometry2.difference(geometry);
}
}
ROI roi2 = GeometryTools.geometryToROI(geometry2, ImagePlane.getPlane(roi));
if (roi2.isEmpty()) {
logger.debug("Updated ROI is empty after {} px expansion", radiusPixels);
return;
}
// Create a new annotation, with properties based on the original
PathObject annotation2 = PathObjects.createAnnotationObject(roi2, pathObject.getPathClass());
annotation2.setName(pathObject.getName());
annotation2.setColorRGB(pathObject.getColorRGB());
if (constrainToParent || isErosion)
hierarchy.addPathObjectBelowParent(parent, annotation2, true);
else
hierarchy.addPathObject(annotation2);
}
use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.
the class ShapeFeaturesPlugin method addRunnableTasks.
@Override
protected void addRunnableTasks(final ImageData<T> imageData, final PathObject parentObject, List<Runnable> tasks) {
PixelCalibration cal = imageData == null ? null : imageData.getServer().getPixelCalibration();
boolean useMicrons = params.getBooleanParameterValue("useMicrons") && cal != null && cal.hasPixelSizeMicrons();
double pixelWidth = useMicrons ? cal.getPixelWidthMicrons() : 1;
double pixelHeight = useMicrons ? cal.getPixelHeightMicrons() : 1;
String unit = useMicrons ? GeneralTools.micrometerSymbol() : "px";
boolean doArea = params.getBooleanParameterValue("area");
boolean doPerimeter = params.getBooleanParameterValue("perimeter");
boolean doCircularity = params.getBooleanParameterValue("circularity");
ROI roi = (parentObject.hasROI() && parentObject.getROI().isArea()) ? parentObject.getROI() : null;
if (roi != null) {
tasks.add(new Runnable() {
@Override
public void run() {
try {
MeasurementList measurementList = parentObject.getMeasurementList();
ROI roi;
if (parentObject instanceof PathCellObject) {
roi = ((PathCellObject) parentObject).getNucleusROI();
if (roi != null && roi.isArea())
addMeasurements(measurementList, roi, "Nucleus Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
roi = parentObject.getROI();
if (roi != null && roi.isArea())
addMeasurements(measurementList, roi, "Cell Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
} else {
roi = parentObject.getROI();
if (roi != null && roi.isArea())
addMeasurements(measurementList, roi, "ROI Shape: ", pixelWidth, pixelHeight, unit, doArea, doPerimeter, doCircularity);
}
measurementList.close();
} catch (Exception e) {
e.printStackTrace();
throw (e);
}
}
});
}
}
Aggregations