Search in sources :

Example 1 with Polygonal

use of org.locationtech.jts.geom.Polygonal in project qupath by qupath.

the class PathObjectTileCache method getLocator.

PointOnGeometryLocator getLocator(ROI roi, boolean addToCache) {
    var locator = locatorMap.get(roi);
    if (locator == null) {
        var geometry = getGeometry(roi);
        if (geometry instanceof Polygonal || geometry instanceof LinearRing)
            locator = new IndexedPointInAreaLocator(geometry);
        else
            locator = new SimplePointInAreaLocator(geometry);
        // Workaround for multithreading bug in JTS 1.17.0 - see https://github.com/locationtech/jts/issues/571
        locator.locate(new Coordinate());
        locatorMap.put(roi, locator);
    }
    return locator;
}
Also used : IndexedPointInAreaLocator(org.locationtech.jts.algorithm.locate.IndexedPointInAreaLocator) Polygonal(org.locationtech.jts.geom.Polygonal) Coordinate(org.locationtech.jts.geom.Coordinate) LinearRing(org.locationtech.jts.geom.LinearRing) SimplePointInAreaLocator(org.locationtech.jts.algorithm.locate.SimplePointInAreaLocator)

Example 2 with Polygonal

use of org.locationtech.jts.geom.Polygonal in project qupath by qupath.

the class DelaunayTools method createGeometryExtractor.

private static Function<PathObject, Collection<Coordinate>> createGeometryExtractor(PixelCalibration cal, boolean preferNucleus, double densifyFactor, double erosion) {
    PrecisionModel precision = calibrated(cal) ? null : GeometryTools.getDefaultFactory().getPrecisionModel();
    AffineTransformation transform = calibrated(cal) ? AffineTransformation.scaleInstance(cal.getPixelWidth().doubleValue(), cal.getPixelHeight().doubleValue()) : null;
    return p -> {
        var roi = PathObjectTools.getROI(p, preferNucleus);
        if (roi == null || roi.isEmpty())
            return Collections.emptyList();
        var geom = roi.getGeometry();
        if (transform != null)
            geom = transform.transform(geom);
        // Shared boundaries can be problematic, so try to buffer away from these
        double buffer = -Math.abs(erosion);
        if (buffer < 0 && geom instanceof Polygonal) {
            var geomBefore = geom;
            geom = GeometryTools.attemptOperation(geom, g -> g.buffer(buffer));
            // Do not permit Geometry to disappear
            if (geom.isEmpty()) {
                geom = geomBefore;
            }
        }
        // Therefore we take extra care in case empty geometries are being generated accidentally.
        if (precision != null) {
            var geom2 = GeometryTools.attemptOperation(geom, g -> GeometryPrecisionReducer.reduce(g, precision));
            if (!geom2.isEmpty())
                geom = geom2;
        }
        if (densifyFactor > 0) {
            var geom2 = GeometryTools.attemptOperation(geom, g -> Densifier.densify(g, densifyFactor));
            if (!geom2.isEmpty())
                geom = geom2;
        }
        // if (!(geom instanceof Polygon))
        // logger.warn("Unexpected Geometry: {}", geom);
        // Making precise is essential! Otherwise there can be small artifacts occurring
        var coords = geom.getCoordinates();
        var output = new LinkedHashSet<Coordinate>();
        var p2 = precision;
        Coordinate lastCoordinate = null;
        if (p2 == null)
            p2 = GeometryTools.getDefaultFactory().getPrecisionModel();
        // Add coordinates, unless they are extremely close to an existing coordinate
        int n = coords.length;
        if (n == 0) {
            logger.warn("Empty Geometry found for {}", p);
            return Collections.emptyList();
        }
        double minDistance = densifyFactor * 0.5;
        var firstCoordinate = coords[0];
        while (n > 2 && firstCoordinate.distance(coords[n - 1]) < minDistance) n--;
        for (int i = 0; i < n; i++) {
            var c = coords[i];
            p2.makePrecise(c);
            if (i == 0 || c.distance(lastCoordinate) > minDistance) {
                output.add(c);
                lastCoordinate = c;
            }
        }
        return output;
    };
}
Also used : IncrementalDelaunayTriangulator(org.locationtech.jts.triangulate.IncrementalDelaunayTriangulator) LoggerFactory(org.slf4j.LoggerFactory) Coordinate(org.locationtech.jts.geom.Coordinate) HashMap(java.util.HashMap) PathClassFactory(qupath.lib.objects.classes.PathClassFactory) DelaunayTriangulationBuilder(org.locationtech.jts.triangulate.DelaunayTriangulationBuilder) Function(java.util.function.Function) ArrayList(java.util.ArrayList) QuadEdge(org.locationtech.jts.triangulate.quadedge.QuadEdge) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) BiPredicate(java.util.function.BiPredicate) LastFoundQuadEdgeLocator(org.locationtech.jts.triangulate.quadedge.LastFoundQuadEdgeLocator) GeometryPrecisionReducer(org.locationtech.jts.precision.GeometryPrecisionReducer) QuadEdgeSubdivision(org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision) Map(java.util.Map) GeometryTools(qupath.lib.roi.GeometryTools) AffineTransformation(org.locationtech.jts.geom.util.AffineTransformation) GeometryCombiner(org.locationtech.jts.geom.util.GeometryCombiner) LinkedHashSet(java.util.LinkedHashSet) RoiTools(qupath.lib.roi.RoiTools) GeometryFactory(org.locationtech.jts.geom.GeometryFactory) Logger(org.slf4j.Logger) QuadEdgeLocator(org.locationtech.jts.triangulate.quadedge.QuadEdgeLocator) Collection(java.util.Collection) Densifier(org.locationtech.jts.densify.Densifier) PathObjects(qupath.lib.objects.PathObjects) Quadtree(org.locationtech.jts.index.quadtree.Quadtree) PathClass(qupath.lib.objects.classes.PathClass) Set(java.util.Set) Polygonal(org.locationtech.jts.geom.Polygonal) 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) PixelCalibration(qupath.lib.images.servers.PixelCalibration) Polygon(org.locationtech.jts.geom.Polygon) ImagePlane(qupath.lib.regions.ImagePlane) Geometry(org.locationtech.jts.geom.Geometry) PrecisionModel(org.locationtech.jts.geom.PrecisionModel) ArrayDeque(java.util.ArrayDeque) Comparator(java.util.Comparator) Collections(java.util.Collections) Envelope(org.locationtech.jts.geom.Envelope) Vertex(org.locationtech.jts.triangulate.quadedge.Vertex) Polygonal(org.locationtech.jts.geom.Polygonal) Coordinate(org.locationtech.jts.geom.Coordinate) AffineTransformation(org.locationtech.jts.geom.util.AffineTransformation) PrecisionModel(org.locationtech.jts.geom.PrecisionModel)

Example 3 with Polygonal

use of org.locationtech.jts.geom.Polygonal in project qupath by qupath.

the class DistanceTools method centroidToBoundsDistance2D.

/**
 * Calculate the distance between source object centroids and the boundary of specified target objects, adding the result to the measurement list of the source objects.
 * Calculations are all made in 2D; distances will not be calculated between objects occurring on different z-planes of at different timepoints.
 *
 * @param sourceObjects source objects; measurements will be added based on centroid distances
 * @param targetObjects target objects; no measurements will be added
 * @param pixelWidth pixel width to use in Geometry conversion (use 1 for pixel units)
 * @param pixelHeight pixel height to use in Geometry conversion (use 1 for pixel units)
 * @param measurementName the name of the measurement to add to the measurement list
 */
public static void centroidToBoundsDistance2D(Collection<PathObject> sourceObjects, Collection<PathObject> targetObjects, double pixelWidth, double pixelHeight, String measurementName) {
    boolean preferNucleus = true;
    var timePoints = new TreeSet<Integer>();
    var zSlices = new TreeSet<Integer>();
    for (var temp : sourceObjects) {
        timePoints.add(temp.getROI().getT());
        zSlices.add(temp.getROI().getZ());
    }
    var transform = pixelWidth == 1 && pixelHeight == 1 ? null : AffineTransformation.scaleInstance(pixelWidth, pixelHeight);
    for (int t : timePoints) {
        for (int z : zSlices) {
            PrecisionModel precision = null;
            List<Geometry> areaGeometries = new ArrayList<>();
            List<Geometry> lineGeometries = new ArrayList<>();
            List<Geometry> pointGeometries = new ArrayList<>();
            for (var annotation : targetObjects) {
                var roi = annotation.getROI();
                if (roi != null && roi.getZ() == z && roi.getT() == t) {
                    var geom = annotation.getROI().getGeometry();
                    if (transform != null) {
                        geom = transform.transform(geom);
                        if (precision == null)
                            precision = geom.getPrecisionModel();
                    }
                    // var geom = converter.roiToGeometry(annotation.getROI());
                    if (geom instanceof Puntal)
                        pointGeometries.add(geom);
                    else if (geom instanceof Lineal)
                        lineGeometries.add(geom);
                    else if (geom instanceof Polygonal)
                        areaGeometries.add(geom);
                    else {
                        for (int i = 0; i < geom.getNumGeometries(); i++) {
                            var geom2 = geom.getGeometryN(i);
                            if (geom2 instanceof Puntal)
                                pointGeometries.add(geom2);
                            else if (geom2 instanceof Lineal)
                                lineGeometries.add(geom2);
                            else if (geom2 instanceof Polygonal)
                                areaGeometries.add(geom2);
                            else
                                logger.warn("Unexpected nested Geometry collection, some Geometries may be ignored");
                        }
                    }
                }
            }
            if (areaGeometries.isEmpty() && pointGeometries.isEmpty() && lineGeometries.isEmpty())
                continue;
            var precisionModel = precision == null ? GeometryTools.getDefaultFactory().getPrecisionModel() : precision;
            List<Coordinate> pointCoords = new ArrayList<>();
            Geometry temp = null;
            if (!areaGeometries.isEmpty())
                temp = areaGeometries.size() == 1 ? areaGeometries.get(0) : GeometryCombiner.combine(areaGeometries);
            Geometry shapeGeometry = temp;
            temp = null;
            if (!lineGeometries.isEmpty())
                temp = lineGeometries.size() == 1 ? lineGeometries.get(0) : GeometryCombiner.combine(lineGeometries);
            Geometry lineGeometry = temp;
            // Identify points, and create an STRtree to find nearest neighbors more quickly if there are a lot of them
            if (!pointGeometries.isEmpty()) {
                for (var geom : pointGeometries) {
                    for (var coord : geom.getCoordinates()) {
                        precisionModel.makePrecise(coord);
                        pointCoords.add(coord);
                    }
                }
            }
            STRtree pointTree = pointCoords != null && pointCoords.size() > 1000 ? createCoordinateCache(pointCoords) : null;
            CoordinateDistance coordinateDistance = new CoordinateDistance();
            int zi = z;
            int ti = t;
            var locator = shapeGeometry == null ? null : new IndexedPointInAreaLocator(shapeGeometry);
            // See https://github.com/locationtech/jts/issues/571
            if (locator != null)
                locator.locate(new Coordinate(0, 0));
            sourceObjects.parallelStream().forEach(p -> {
                var roi = PathObjectTools.getROI(p, preferNucleus);
                if (roi.getZ() != zi || roi.getT() != ti)
                    return;
                Coordinate coord = new Coordinate(roi.getCentroidX() * pixelWidth, roi.getCentroidY() * pixelHeight);
                precisionModel.makePrecise(coord);
                double pointDistance = pointCoords == null ? Double.POSITIVE_INFINITY : computeCoordinateDistance(coord, pointCoords, pointTree, coordinateDistance);
                double lineDistance = lineGeometry == null ? Double.POSITIVE_INFINITY : computeDistance(coord, lineGeometry, null);
                double shapeDistance = shapeGeometry == null ? Double.POSITIVE_INFINITY : computeDistance(coord, shapeGeometry, locator);
                double distance = Math.min(lineDistance, Math.min(pointDistance, shapeDistance));
                try (var ml = p.getMeasurementList()) {
                    ml.putMeasurement(measurementName, distance);
                }
            });
        }
    }
}
Also used : Polygonal(org.locationtech.jts.geom.Polygonal) ArrayList(java.util.ArrayList) PrecisionModel(org.locationtech.jts.geom.PrecisionModel) DistanceToPoint(org.locationtech.jts.algorithm.distance.DistanceToPoint) Geometry(org.locationtech.jts.geom.Geometry) IndexedPointInAreaLocator(org.locationtech.jts.algorithm.locate.IndexedPointInAreaLocator) Lineal(org.locationtech.jts.geom.Lineal) Coordinate(org.locationtech.jts.geom.Coordinate) TreeSet(java.util.TreeSet) STRtree(org.locationtech.jts.index.strtree.STRtree) Puntal(org.locationtech.jts.geom.Puntal)

Example 4 with Polygonal

use of org.locationtech.jts.geom.Polygonal in project qupath by qupath.

the class ObjectMeasurements method addShapeMeasurements.

private static void addShapeMeasurements(MeasurementList ml, Geometry geom, String baseName, String units, Collection<ShapeFeatures> features) {
    boolean isArea = geom instanceof Polygonal;
    boolean isLine = geom instanceof Lineal;
    var units2 = units + "^2";
    if (!baseName.isEmpty() && !baseName.endsWith(" "))
        baseName += " ";
    double area = geom.getArea();
    double length = geom.getLength();
    if (isArea && features.contains(ShapeFeatures.AREA))
        ml.putMeasurement(baseName + "Area " + units2, area);
    if ((isArea || isLine) && features.contains(ShapeFeatures.LENGTH))
        ml.putMeasurement(baseName + "Length " + units, length);
    if (isArea && features.contains(ShapeFeatures.CIRCULARITY)) {
        if (geom instanceof Polygon) {
            var polygon = (Polygon) geom;
            double ringArea, ringLength;
            if (polygon.getNumInteriorRing() == 0) {
                ringArea = area;
                ringLength = length;
            } else {
                var ring = ((Polygon) geom).getExteriorRing().getCoordinateSequence();
                ringArea = Area.ofRing(ring);
                ringLength = Length.ofLine(ring);
            }
            double circularity = Math.PI * 4 * ringArea / (ringLength * ringLength);
            ml.putMeasurement(baseName + "Circularity", circularity);
        } else {
            logger.debug("Cannot compute circularity for {}", geom.getClass());
        }
    }
    if (isArea && features.contains(ShapeFeatures.SOLIDITY)) {
        double solidity = area / geom.convexHull().getArea();
        ml.putMeasurement(baseName + "Solidity", solidity);
    }
    if (features.contains(ShapeFeatures.MAX_DIAMETER)) {
        double minCircleRadius = new MinimumBoundingCircle(geom).getRadius();
        ml.putMeasurement(baseName + "Max diameter " + units, minCircleRadius * 2);
    }
    if (features.contains(ShapeFeatures.MIN_DIAMETER)) {
        double minDiameter = new MinimumDiameter(geom).getLength();
        ml.putMeasurement(baseName + "Min diameter " + units, minDiameter);
    }
}
Also used : MinimumBoundingCircle(org.locationtech.jts.algorithm.MinimumBoundingCircle) MinimumDiameter(org.locationtech.jts.algorithm.MinimumDiameter) Lineal(org.locationtech.jts.geom.Lineal) Polygonal(org.locationtech.jts.geom.Polygonal) Polygon(org.locationtech.jts.geom.Polygon)

Aggregations

Polygonal (org.locationtech.jts.geom.Polygonal)4 Coordinate (org.locationtech.jts.geom.Coordinate)3 ArrayList (java.util.ArrayList)2 IndexedPointInAreaLocator (org.locationtech.jts.algorithm.locate.IndexedPointInAreaLocator)2 Geometry (org.locationtech.jts.geom.Geometry)2 Lineal (org.locationtech.jts.geom.Lineal)2 Polygon (org.locationtech.jts.geom.Polygon)2 PrecisionModel (org.locationtech.jts.geom.PrecisionModel)2 ArrayDeque (java.util.ArrayDeque)1 Collection (java.util.Collection)1 Collections (java.util.Collections)1 Comparator (java.util.Comparator)1 HashMap (java.util.HashMap)1 HashSet (java.util.HashSet)1 LinkedHashMap (java.util.LinkedHashMap)1 LinkedHashSet (java.util.LinkedHashSet)1 List (java.util.List)1 Map (java.util.Map)1 Set (java.util.Set)1 TreeSet (java.util.TreeSet)1