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;
}
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;
};
}
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);
}
});
}
}
}
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);
}
}
Aggregations