Search in sources :

Example 51 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class SimpleTissueDetection2 method convertToPathObjects.

private static List<PathObject> convertToPathObjects(ByteProcessor bp, double minArea, boolean smoothCoordinates, Calibration cal, double downsample, double maxHoleArea, boolean excludeOnBoundary, boolean singleAnnotation, ImagePlane plane, List<PathObject> pathObjects) {
    // 
    // var roiIJ = new ThresholdToSelection().convert(bp);
    // var roi = IJTools.convertToROI(roiIJ, cal, downsample, plane);
    // roi = RoiTools.removeSmallPieces(roi, minArea, maxHoleArea);
    // List<PathObject> annotations = new ArrayList<>();
    // if (singleAnnotation)
    // annotations.add(PathObjects.createAnnotationObject(roi));
    // else {
    // for (var roi2 : RoiTools.splitROI(roi)) {
    // annotations.add(PathObjects.createAnnotationObject(roi2));
    // }
    // }
    // for (var annotation : annotations)
    // annotation.setLocked(true);
    // return annotations;
    // 
    List<PolygonRoi> rois = RoiLabeling.getFilledPolygonROIs(bp, Wand.FOUR_CONNECTED);
    if (pathObjects == null)
        pathObjects = new ArrayList<>(rois.size());
    // We might need a copy of the original image
    boolean fillAllHoles = maxHoleArea <= 0;
    ByteProcessor bpOrig = !fillAllHoles ? (ByteProcessor) bp.duplicate() : null;
    bp.setValue(255);
    for (PolygonRoi r : rois) {
        // Check for boundary overlap
        if (excludeOnBoundary) {
            Rectangle bounds = r.getBounds();
            if (bounds.x <= 0 || bounds.y <= 0 || bounds.x + bounds.width >= bp.getWidth() - 1 || bounds.y + bounds.height >= bp.getHeight() - 1)
                continue;
        }
        bp.setRoi(r);
        if (bp.getStatistics().area < minArea)
            continue;
        // Fill holes as we go - it might matter later
        bp.fill(r);
        // if (smoothCoordinates) {
        // //				r = new PolygonRoi(r.getInterpolatedPolygon(2.5, false), Roi.POLYGON);
        // r = new PolygonRoi(r.getInterpolatedPolygon(Math.min(2.5, r.getNCoordinates()*0.1), false), Roi.POLYGON); // TODO: Check this smoothing - it can be troublesome, causing nuclei to be outside cells
        // }
        PolygonROI pathPolygon = IJTools.convertToPolygonROI(r, cal, downsample, plane);
        // Smooth the coordinates, if we downsampled quite a lot
        if (smoothCoordinates) {
            pathPolygon = ROIs.createPolygonROI(ShapeSimplifier.smoothPoints(pathPolygon.getAllPoints()), ImagePlane.getPlaneWithChannel(pathPolygon));
            pathPolygon = ShapeSimplifier.simplifyPolygon(pathPolygon, downsample / 2);
        }
        pathObjects.add(PathObjects.createAnnotationObject(pathPolygon));
    }
    if (Thread.currentThread().isInterrupted())
        return null;
    // TODO: Optimise this - the many 'containsObject' calls are a (potentially easy-to-fix) bottleneck
    if (!fillAllHoles) {
        // Get the holes alone
        bp.copyBits(bpOrig, 0, 0, Blitter.DIFFERENCE);
        // new ImagePlus("Binary", bp).show();
        bp.setThreshold(127, Double.POSITIVE_INFINITY, ImageProcessor.NO_LUT_UPDATE);
        List<PathObject> holes = convertToPathObjects(bp, maxHoleArea, smoothCoordinates, cal, downsample, 0, false, false, plane, null);
        // For each object, fill in any associated holes
        List<Area> areaList = new ArrayList<>();
        for (int ind = 0; ind < pathObjects.size(); ind++) {
            if (holes.isEmpty())
                break;
            PathObject pathObject = pathObjects.get(ind);
            var geom = PreparedGeometryFactory.prepare(pathObject.getROI().getGeometry());
            areaList.clear();
            Iterator<PathObject> iter = holes.iterator();
            while (iter.hasNext()) {
                PathObject hole = iter.next();
                if (geom.covers(hole.getROI().getGeometry())) {
                    areaList.add(RoiTools.getArea(hole.getROI()));
                    iter.remove();
                }
            }
            if (areaList.isEmpty())
                continue;
            // If we have some areas, combine them
            // TODO: FIX MAJOR BOTTLENECK HERE!!!
            Area hole = areaList.get(0);
            for (int i = 1; i < areaList.size(); i++) {
                hole.add(areaList.get(i));
                if (i % 100 == 0) {
                    logger.debug("Added hole " + i + "/" + areaList.size());
                    if (Thread.currentThread().isInterrupted())
                        return null;
                }
            }
            // Now subtract & create a new object
            ROI pathROI = pathObject.getROI();
            if (RoiTools.isShapeROI(pathROI)) {
                Area areaMain = RoiTools.getArea(pathROI);
                areaMain.subtract(hole);
                pathROI = RoiTools.getShapeROI(areaMain, pathROI.getImagePlane());
                pathObjects.set(ind, PathObjects.createAnnotationObject(pathROI));
            }
        }
    }
    // This is a clumsy way to do it...
    if (singleAnnotation) {
        ROI roi = null;
        for (PathObject annotation : pathObjects) {
            ROI currentShape = annotation.getROI();
            if (roi == null)
                roi = currentShape;
            else
                roi = RoiTools.combineROIs(roi, currentShape, CombineOp.ADD);
        }
        pathObjects.clear();
        if (roi != null)
            pathObjects.add(PathObjects.createAnnotationObject(roi));
    }
    // Lock the objects
    for (PathObject pathObject : pathObjects) ((PathAnnotationObject) pathObject).setLocked(true);
    return pathObjects;
}
Also used : ByteProcessor(ij.process.ByteProcessor) ArrayList(java.util.ArrayList) Rectangle(java.awt.Rectangle) PolygonROI(qupath.lib.roi.PolygonROI) ROI(qupath.lib.roi.interfaces.ROI) PolygonRoi(ij.gui.PolygonRoi) PolygonROI(qupath.lib.roi.PolygonROI) Area(java.awt.geom.Area) PathObject(qupath.lib.objects.PathObject)

Example 52 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class LabeledImageServer method createIndexedColorTile.

private BufferedImage createIndexedColorTile(TileRequest tileRequest, Collection<PathObject> pathObjects) {
    RegionRequest request = tileRequest.getRegionRequest();
    double downsampleFactor = request.getDownsample();
    // Fill in the background color
    int width = tileRequest.getTileWidth();
    int height = tileRequest.getTileHeight();
    boolean doRGB = maxLabel > 255;
    // If we have > 255 labels, we can only use Graphics2D if we pretend to have an RGB image
    BufferedImage img = doRGB ? new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) : new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    WritableRaster raster = img.getRaster();
    Graphics2D g2d = img.createGraphics();
    int bgLabel = params.labels.get(params.unannotatedClass);
    Color color = getColorForLabel(bgLabel, doRGB);
    g2d.setColor(color);
    g2d.fillRect(0, 0, width, height);
    if (!pathObjects.isEmpty()) {
        g2d.setClip(0, 0, width, height);
        double scale = 1.0 / downsampleFactor;
        g2d.scale(scale, scale);
        g2d.translate(-request.getX(), -request.getY());
        BasicStroke stroke = new BasicStroke((float) (params.lineThickness * tileRequest.getDownsample()));
        g2d.setStroke(stroke);
        // We want to order consistently to avoid confusing overlaps
        for (var entry : params.labels.entrySet()) {
            var pathClass = getPathClass(entry.getKey());
            int c = entry.getValue();
            color = getColorForLabel(c, doRGB);
            List<PathObject> toDraw;
            if (instanceClassMapInverse != null) {
                var temp = instanceClassMapInverse.get(c);
                if (temp == null)
                    continue;
                toDraw = Collections.singletonList(temp);
            } else
                toDraw = pathObjects.stream().filter(p -> getPathClass(p) == pathClass).collect(Collectors.toList());
            for (var pathObject : toDraw) {
                var roi = params.roiFunction.apply(pathObject);
                g2d.setColor(color);
                if (roi.isArea())
                    g2d.fill(roi.getShape());
                else if (roi.isLine())
                    g2d.draw(roi.getShape());
                else if (roi.isPoint()) {
                    for (var p : roi.getAllPoints()) {
                        int x = (int) ((p.getX() - request.getX()) / downsampleFactor);
                        int y = (int) ((p.getY() - request.getY()) / downsampleFactor);
                        if (x >= 0 && x < width && y >= 0 && y < height) {
                            if (doRGB)
                                img.setRGB(x, y, color.getRGB());
                            else
                                raster.setSample(x, y, 0, c);
                        }
                    }
                }
            }
        }
        for (var entry : params.boundaryLabels.entrySet()) {
            int c = entry.getValue();
            color = getColorForLabel(c, doRGB);
            for (var pathObject : pathObjects) {
                // if (pathObject.getPathClass() == pathClass) {
                var pathClass = getPathClass(pathObject);
                if (params.labels.containsKey(pathClass)) {
                    // && !PathClassTools.isIgnoredClass(pathObject.getPathClass())) {
                    var roi = params.roiFunction.apply(pathObject);
                    if (roi.isArea()) {
                        g2d.setColor(color);
                        g2d.draw(roi.getShape());
                    }
                }
            }
        }
    }
    g2d.dispose();
    if (doRGB) {
        // Resort to RGB if we have to
        if (maxLabel >= 65536)
            return img;
        // Convert to unsigned short if we can
        var shortRaster = WritableRaster.createBandedRaster(DataBuffer.TYPE_USHORT, width, height, 1, null);
        int[] samples = img.getRGB(0, 0, width, height, null, 0, width);
        shortRaster.setSamples(0, 0, width, height, 0, samples);
        // System.err.println("Before: " + Arrays.stream(samples).summaryStatistics());
        raster = shortRaster;
    // samples = raster.getSamples(0, 0, width, height, 0, (int[])null);
    // System.err.println("After: " + Arrays.stream(samples).summaryStatistics());
    }
    return new BufferedImage((IndexColorModel) colorModel, raster, false, null);
}
Also used : BasicStroke(java.awt.BasicStroke) Color(java.awt.Color) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) Random(java.util.Random) PathObjectFilter(qupath.lib.objects.PathObjectFilter) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) DataBufferByte(java.awt.image.DataBufferByte) Function(java.util.function.Function) ArrayList(java.util.ArrayList) LinkedHashMap(java.util.LinkedHashMap) ChannelType(qupath.lib.images.servers.ImageServerMetadata.ChannelType) BandedSampleModel(java.awt.image.BandedSampleModel) ImageRegion(qupath.lib.regions.ImageRegion) Graphics2D(java.awt.Graphics2D) Map(java.util.Map) IndexColorModel(java.awt.image.IndexColorModel) ColorModelFactory(qupath.lib.color.ColorModelFactory) URI(java.net.URI) ColorToolsAwt(qupath.lib.color.ColorToolsAwt) ImageData(qupath.lib.images.ImageData) PathClassifierTools(qupath.lib.classifiers.PathClassifierTools) RoiTools(qupath.lib.roi.RoiTools) Logger(org.slf4j.Logger) ColorTools(qupath.lib.common.ColorTools) BufferedImage(java.awt.image.BufferedImage) RegionRequest(qupath.lib.regions.RegionRequest) Predicate(java.util.function.Predicate) Collection(java.util.Collection) PathClass(qupath.lib.objects.classes.PathClass) IOException(java.io.IOException) UUID(java.util.UUID) ServerBuilder(qupath.lib.images.servers.ImageServerBuilder.ServerBuilder) Collectors(java.util.stream.Collectors) PathObjectTools(qupath.lib.objects.PathObjectTools) PathObject(qupath.lib.objects.PathObject) ROI(qupath.lib.roi.interfaces.ROI) List(java.util.List) ColorModel(java.awt.image.ColorModel) TreeMap(java.util.TreeMap) BasicStroke(java.awt.BasicStroke) WritableRaster(java.awt.image.WritableRaster) Collections(java.util.Collections) DataBuffer(java.awt.image.DataBuffer) Color(java.awt.Color) BufferedImage(java.awt.image.BufferedImage) Graphics2D(java.awt.Graphics2D) PathObject(qupath.lib.objects.PathObject) WritableRaster(java.awt.image.WritableRaster) RegionRequest(qupath.lib.regions.RegionRequest)

Example 53 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class PointIO method readPointsObjectFromString.

/**
 * Helper method for readPointsObjectList(), will be removed in future releases.
 * @param s
 * @return
 */
@Deprecated
private static PathObject readPointsObjectFromString(String s) {
    List<Point2> pointsList = new ArrayList<>();
    Scanner scanner = new Scanner(s);
    String name = scanner.nextLine().split("\t")[1].trim();
    Integer color = Integer.parseInt(scanner.nextLine().split("\t")[1]);
    // Skip the coordinate count line...
    int count = Integer.parseInt(scanner.nextLine().split("\t")[1]);
    while (scanner.hasNextLine()) {
        String line = scanner.nextLine().trim();
        if (line.length() == 0)
            break;
        String[] splits = line.split("\t");
        double x = Double.parseDouble(splits[0]);
        double y = Double.parseDouble(splits[1]);
        pointsList.add(new Point2(x, y));
    }
    scanner.close();
    if (count != pointsList.size())
        logger.warn("Warning: {} points expected, {} points found", count, pointsList.size());
    ROI points = ROIs.createPointsROI(pointsList, ImagePlane.getDefaultPlane());
    PathObject pathObject = PathObjects.createAnnotationObject(points);
    if (name != null && name.length() > 0 && !"null".equals(name))
        pathObject.setName(name);
    pathObject.setColorRGB(color);
    return pathObject;
}
Also used : Scanner(java.util.Scanner) Point2(qupath.lib.geom.Point2) PathObject(qupath.lib.objects.PathObject) ArrayList(java.util.ArrayList) PointsROI(qupath.lib.roi.PointsROI) ROI(qupath.lib.roi.interfaces.ROI)

Example 54 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class PointIO method readPoints.

/**
 * Read a list of point annotations from a stream.
 * @param stream
 * @return list of PathObjects
 * @throws IOException
 */
public static List<PathObject> readPoints(InputStream stream) throws IOException {
    List<PathObject> pathObjects = new ArrayList<>();
    Map<String[], List<Point2>> pointsMap = new HashMap<>();
    Scanner scanner = null;
    String[] cols = null;
    try {
        scanner = new Scanner(stream);
        // Header
        cols = scanner.nextLine().split("\t");
        while (scanner.hasNextLine()) {
            putPointObjectFromString(scanner.nextLine(), cols, pointsMap);
        }
    } finally {
        if (scanner != null)
            scanner.close();
    }
    ImagePlane defaultPlane = ImagePlane.getDefaultPlane();
    for (var entry : pointsMap.entrySet()) {
        var temp = Arrays.asList(cols);
        String pathClass = temp.indexOf("class") > -1 ? entry.getKey()[temp.indexOf("class") - 2] : "";
        String name = temp.indexOf("name") > -1 ? entry.getKey()[temp.indexOf("name") - 2] : "";
        Integer color = null;
        if (temp.indexOf("color") > -1) {
            var colorTemp = entry.getKey()[temp.indexOf("color") - 2];
            if (colorTemp != null && !colorTemp.isEmpty())
                color = Integer.parseInt(colorTemp);
        }
        int c = temp.indexOf("c") > defaultPlane.getC() ? Integer.parseInt(entry.getKey()[temp.indexOf("c") - 2]) : defaultPlane.getC();
        int z = temp.indexOf("z") > defaultPlane.getZ() ? Integer.parseInt(entry.getKey()[temp.indexOf("z") - 2]) : defaultPlane.getZ();
        int t = temp.indexOf("t") > defaultPlane.getT() ? Integer.parseInt(entry.getKey()[temp.indexOf("t") - 2]) : defaultPlane.getT();
        ROI points = ROIs.createPointsROI(entry.getValue(), ImagePlane.getPlaneWithChannel(c, z, t));
        PathObject pathObject = PathObjects.createAnnotationObject(points);
        if (name != null && name.length() > 0 && !"null".equals(name))
            pathObject.setName(name);
        if (pathClass != null && pathClass.length() > 0 && !"null".equals(pathClass))
            pathObject.setPathClass(PathClassFactory.getPathClass(pathClass, color));
        pathObject.setColorRGB(color);
        if (pathObject != null)
            pathObjects.add(pathObject);
    }
    return pathObjects;
}
Also used : Scanner(java.util.Scanner) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) PointsROI(qupath.lib.roi.PointsROI) ROI(qupath.lib.roi.interfaces.ROI) PathObject(qupath.lib.objects.PathObject) ArrayList(java.util.ArrayList) List(java.util.List) ImagePlane(qupath.lib.regions.ImagePlane)

Example 55 with ROI

use of qupath.lib.roi.interfaces.ROI in project qupath by qupath.

the class PixelClassifierTools method createObjectsFromPixelClassifier.

/**
 * Create objects based upon an {@link ImageServer} that provides classification or probability output.
 *
 * @param server image server providing pixels from which objects should be created
 * @param labels classification labels; if null, these will be taken from ImageServer#getMetadata() and all non-ignored classifications will be used.
 * 		   Providing a map makes it possible to explicitly exclude some classifications.
 * @param roi region of interest in which objects should be created (optional; if null, the entire image is used)
 * @param creator function to create an object from a ROI (e.g. annotation or detection)
 * @param minArea minimum area for an object fragment to retain, in calibrated units based on the pixel calibration
 * @param minHoleArea minimum area for a hole to fill, in calibrated units based on the pixel calibration
 * @param doSplit if true, split connected regions into separate objects
 * @return the objects created within the ROI
 * @throws IOException
 */
public static Collection<PathObject> createObjectsFromPixelClassifier(ImageServer<BufferedImage> server, Map<Integer, PathClass> labels, ROI roi, Function<ROI, ? extends PathObject> creator, double minArea, double minHoleArea, boolean doSplit) throws IOException {
    // We need classification labels to do anything
    if (labels == null)
        labels = parseClassificationLabels(server.getMetadata().getClassificationLabels(), false);
    if (labels == null || labels.isEmpty())
        throw new IllegalArgumentException("Cannot create objects for server - no classification labels are available!");
    ChannelThreshold[] thresholds = labels.entrySet().stream().map(e -> ChannelThreshold.create(e.getKey())).toArray(ChannelThreshold[]::new);
    if (roi != null && !roi.isArea()) {
        logger.warn("Cannot create objects for non-area ROIs");
        return Collections.emptyList();
    }
    Geometry clipArea = roi == null ? null : roi.getGeometry();
    // Identify regions for selected ROI or entire image
    // This is a list because it might need to handle multiple z-slices or timepoints
    List<RegionRequest> regionRequests;
    if (roi != null) {
        var request = RegionRequest.createInstance(server.getPath(), server.getDownsampleForResolution(0), roi);
        regionRequests = Collections.singletonList(request);
    } else {
        regionRequests = RegionRequest.createAllRequests(server, server.getDownsampleForResolution(0));
    }
    double pixelArea = server.getPixelCalibration().getPixelWidth().doubleValue() * server.getPixelCalibration().getPixelHeight().doubleValue();
    double minAreaPixels = minArea / pixelArea;
    double minHoleAreaPixels = minHoleArea / pixelArea;
    // Create output array
    var pathObjects = new ArrayList<PathObject>();
    // Loop through region requests (usually 1, unless we have a z-stack or time series)
    for (RegionRequest regionRequest : regionRequests) {
        Map<Integer, Geometry> geometryMap = ContourTracing.traceGeometries(server, regionRequest, clipArea, thresholds);
        var labelMap = labels;
        pathObjects.addAll(geometryMap.entrySet().parallelStream().flatMap(e -> geometryToObjects(e.getValue(), creator, labelMap.get(e.getKey()), minAreaPixels, minHoleAreaPixels, doSplit, regionRequest.getPlane()).stream()).collect(Collectors.toList()));
    }
    pathObjects.sort(DefaultPathObjectComparator.getInstance());
    return pathObjects;
}
Also used : ImageServer(qupath.lib.images.servers.ImageServer) Arrays(java.util.Arrays) PathClassTools(qupath.lib.objects.classes.PathClassTools) LoggerFactory(org.slf4j.LoggerFactory) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) PathObjectHierarchy(qupath.lib.objects.hierarchy.PathObjectHierarchy) ChannelThreshold(qupath.lib.analysis.images.ContourTracing.ChannelThreshold) Function(java.util.function.Function) ArrayList(java.util.ArrayList) ClassifierFunction(qupath.opencv.ml.pixel.PixelClassifiers.ClassifierFunction) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) ChannelType(qupath.lib.images.servers.ImageServerMetadata.ChannelType) Map(java.util.Map) Reclassifier(qupath.lib.objects.classes.Reclassifier) GeometryTools(qupath.lib.roi.GeometryTools) ImageData(qupath.lib.images.ImageData) Logger(org.slf4j.Logger) RegionRequest(qupath.lib.regions.RegionRequest) BufferedImage(java.awt.image.BufferedImage) PathObjects(qupath.lib.objects.PathObjects) Collection(java.util.Collection) PathClass(qupath.lib.objects.classes.PathClass) Set(java.util.Set) DefaultPathObjectComparator(qupath.lib.objects.DefaultPathObjectComparator) IOException(java.io.IOException) Collectors(java.util.stream.Collectors) PathObjectTools(qupath.lib.objects.PathObjectTools) PathObject(qupath.lib.objects.PathObject) ROI(qupath.lib.roi.interfaces.ROI) List(java.util.List) PixelClassifier(qupath.lib.classifiers.pixel.PixelClassifier) PixelClassificationImageServer(qupath.lib.classifiers.pixel.PixelClassificationImageServer) ColorModel(java.awt.image.ColorModel) ContourTracing(qupath.lib.analysis.images.ContourTracing) ImagePlane(qupath.lib.regions.ImagePlane) Geometry(org.locationtech.jts.geom.Geometry) Comparator(java.util.Comparator) Collections(java.util.Collections) ImageServerMetadata(qupath.lib.images.servers.ImageServerMetadata) DataBuffer(java.awt.image.DataBuffer) Geometry(org.locationtech.jts.geom.Geometry) ArrayList(java.util.ArrayList) ChannelThreshold(qupath.lib.analysis.images.ContourTracing.ChannelThreshold) RegionRequest(qupath.lib.regions.RegionRequest)

Aggregations

ROI (qupath.lib.roi.interfaces.ROI)87 PathObject (qupath.lib.objects.PathObject)61 ArrayList (java.util.ArrayList)31 BufferedImage (java.awt.image.BufferedImage)24 PathObjectHierarchy (qupath.lib.objects.hierarchy.PathObjectHierarchy)24 IOException (java.io.IOException)20 RegionRequest (qupath.lib.regions.RegionRequest)19 List (java.util.List)17 Collectors (java.util.stream.Collectors)17 RectangleROI (qupath.lib.roi.RectangleROI)17 Logger (org.slf4j.Logger)16 LoggerFactory (org.slf4j.LoggerFactory)16 PolygonROI (qupath.lib.roi.PolygonROI)16 PathAnnotationObject (qupath.lib.objects.PathAnnotationObject)15 Point2D (java.awt.geom.Point2D)14 Collection (java.util.Collection)14 Collections (java.util.Collections)14 Geometry (org.locationtech.jts.geom.Geometry)14 PathClass (qupath.lib.objects.classes.PathClass)14 ImagePlane (qupath.lib.regions.ImagePlane)13