Search in sources :

Example 1 with IndexedPixel

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);
    }
}
Also used : CreateObjectOptions(qupath.opencv.ml.pixel.PixelClassifierTools.CreateObjectOptions) Arrays(java.util.Arrays) ImageServer(qupath.lib.images.servers.ImageServer) LoggerFactory(org.slf4j.LoggerFactory) ChannelType(qupath.lib.images.servers.ImageServerMetadata.ChannelType) Map(java.util.Map) PixelClassifierTools(qupath.opencv.ml.pixel.PixelClassifierTools) StandardPathClasses(qupath.lib.objects.classes.PathClassFactory.StandardPathClasses) BufferedImageTools(qupath.lib.awt.common.BufferedImageTools) Path(java.nio.file.Path) BufferedImage(java.awt.image.BufferedImage) PixelClassifiers(qupath.opencv.ml.pixel.PixelClassifiers) Collection(java.util.Collection) PathObjects(qupath.lib.objects.PathObjects) UUID(java.util.UUID) Collectors(java.util.stream.Collectors) PathAnnotationObject(qupath.lib.objects.PathAnnotationObject) PathObject(qupath.lib.objects.PathObject) Objects(java.util.Objects) Project(qupath.lib.projects.Project) ImagePlane(qupath.lib.regions.ImagePlane) ColorModelBuilder(qupath.lib.analysis.heatmaps.ColorModels.ColorModelBuilder) org.bytedeco.opencv.global.opencv_core(org.bytedeco.opencv.global.opencv_core) GsonTools(qupath.lib.io.GsonTools) OpenCVTools(qupath.opencv.tools.OpenCVTools) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) ROIs(qupath.lib.roi.ROIs) Point2(qupath.lib.geom.Point2) LinkedHashSet(java.util.LinkedHashSet) ImageData(qupath.lib.images.ImageData) IndexedPixel(qupath.opencv.tools.OpenCVTools.IndexedPixel) RoiTools(qupath.lib.roi.RoiTools) Logger(org.slf4j.Logger) Files(java.nio.file.Files) PointerScope(org.bytedeco.javacpp.PointerScope) RegionRequest(qupath.lib.regions.RegionRequest) PathClass(qupath.lib.objects.classes.PathClass) PixelClassifierMetadata(qupath.lib.classifiers.pixel.PixelClassifierMetadata) IOException(java.io.IOException) PathObjectTools(qupath.lib.objects.PathObjectTools) ROI(qupath.lib.roi.interfaces.ROI) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) ColorModel(java.awt.image.ColorModel) PixelCalibration(qupath.lib.images.servers.PixelCalibration) PathObjectPredicate(qupath.lib.objects.PathObjectPredicates.PathObjectPredicate) Comparator(java.util.Comparator) Collections(java.util.Collections) ImageServerMetadata(qupath.lib.images.servers.ImageServerMetadata) ArrayList(java.util.ArrayList) IndexedPixel(qupath.opencv.tools.OpenCVTools.IndexedPixel) PointerScope(org.bytedeco.javacpp.PointerScope) ROI(qupath.lib.roi.interfaces.ROI) PathObject(qupath.lib.objects.PathObject) Point2(qupath.lib.geom.Point2) RegionRequest(qupath.lib.regions.RegionRequest) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet)

Aggregations

BufferedImage (java.awt.image.BufferedImage)1 ColorModel (java.awt.image.ColorModel)1 IOException (java.io.IOException)1 Files (java.nio.file.Files)1 Path (java.nio.file.Path)1 ArrayList (java.util.ArrayList)1 Arrays (java.util.Arrays)1 Collection (java.util.Collection)1 Collections (java.util.Collections)1 Comparator (java.util.Comparator)1 HashSet (java.util.HashSet)1 LinkedHashMap (java.util.LinkedHashMap)1 LinkedHashSet (java.util.LinkedHashSet)1 Map (java.util.Map)1 Objects (java.util.Objects)1 UUID (java.util.UUID)1 Collectors (java.util.stream.Collectors)1 PointerScope (org.bytedeco.javacpp.PointerScope)1 org.bytedeco.opencv.global.opencv_core (org.bytedeco.opencv.global.opencv_core)1 Logger (org.slf4j.Logger)1