use of qupath.opencv.tools.OpenCVTools.IndexedPixel 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);
}
}
Aggregations