use of qupath.lib.geom.Point2 in project qupath by qupath.
the class RoiTools method getShapeROI.
/**
* Create a {@link ROI} from an Shape with a specified 'flatness'.
* This will try to return a RectangleROI or PolygonROI if possible,
* or AreaROI if neither of the other classes can adequately represent the area.
*
* In the input shape is an Ellipse2D then an EllipseROI will be returned.
*
* @param shape
* @param plane
* @param flatness - can be used to prefer polygons, see {@code Shape.getPathIterator(AffineTransform, double)}
* @return
*/
public static ROI getShapeROI(Shape shape, ImagePlane plane, double flatness) {
if (shape instanceof Rectangle2D) {
Rectangle2D bounds = shape.getBounds2D();
return ROIs.createRectangleROI(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), plane);
}
if (shape instanceof Ellipse2D) {
Rectangle2D bounds = shape.getBounds2D();
return ROIs.createEllipseROI(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), plane);
}
if (shape instanceof Line2D) {
Line2D line = (Line2D) shape;
return ROIs.createLineROI(line.getX1(), line.getY1(), line.getX2(), line.getY2(), plane);
}
boolean isClosed = false;
List<Point2> points = null;
if (!(shape instanceof Area)) {
PathIterator iterator = shape.getPathIterator(null, flatness);
double[] coords = new double[6];
points = new ArrayList<>();
while (!iterator.isDone()) {
int type = iterator.currentSegment(coords);
if (type == PathIterator.SEG_CLOSE) {
isClosed = true;
break;
} else
points.add(new Point2(coords[0], coords[1]));
iterator.next();
}
}
// (e.g. by checking isRectangular, isPolygonal)
if (isClosed) {
Area area;
if (shape instanceof Area) {
area = (Area) shape;
} else
area = new Area(shape);
return getShapeROI(area, plane, flatness);
} else if (points.size() == 2) {
// Handle straight lines, with only two end points
Point2 p1 = points.get(0);
Point2 p2 = points.get(1);
return ROIs.createLineROI(p1.getX(), p1.getY(), p2.getX(), p2.getY(), plane);
} else
// Handle polylines
return ROIs.createPolylineROI(points, plane);
}
use of qupath.lib.geom.Point2 in project qupath by qupath.
the class ShapeSimplifier method simplifyPath.
/**
* Create a simplified path (fewer coordinates) using method based on Visvalingam’s Algorithm.
* <p>
* See references:
* https://hydra.hull.ac.uk/resources/hull:8338
* https://www.jasondavies.com/simplify/
* http://bost.ocks.org/mike/simplify/
*
* @param path
* @param altitudeThreshold
* @return
*/
public static Path2D simplifyPath(Path2D path, double altitudeThreshold) {
List<Point2> points = new ArrayList<>();
PathIterator iter = path.getPathIterator(null, 0.5);
// int nVerticesBefore = 0;
// int nVerticesAfter = 0;
Path2D pathNew = new Path2D.Float();
while (!iter.isDone()) {
points.clear();
getNextClosedSegment(iter, points);
// nVerticesBefore += points.size();
if (points.isEmpty())
break;
ShapeSimplifier.simplifyPolygonPoints(points, altitudeThreshold);
// nVerticesAfter += points.size();
boolean firstPoint = true;
for (Point2 p : points) {
double xx = p.getX();
double yy = p.getY();
if (firstPoint) {
pathNew.moveTo(xx, yy);
firstPoint = false;
} else
pathNew.lineTo(xx, yy);
}
pathNew.closePath();
}
return pathNew;
}
use of qupath.lib.geom.Point2 in project qupath by qupath.
the class RoiTools method splitAreaToPolygons.
/**
* Split Area into PolygonROIs for the exterior and the holes.
* <p>
* The first array returned gives the <i>holes</i> and the second the positive regions (admittedly, it might have
* been more logical the other way around).
*
* <pre>
* {@code
* var polygons = splitAreaToPolygons(area, -1, 0, 0);
* var holes = polygons[0];
* var regions = polygons[1];
* }
* </pre>
*
* @param area
* @param c
* @param z
* @param t
* @return
*/
public static PolygonROI[][] splitAreaToPolygons(final Area area, int c, int z, int t) {
Map<Boolean, List<PolygonROI>> map = new HashMap<>();
map.put(Boolean.TRUE, new ArrayList<>());
map.put(Boolean.FALSE, new ArrayList<>());
PathIterator iter = area.getPathIterator(null, 0.5);
var plane = ImagePlane.getPlaneWithChannel(c, z, t);
List<Point2> points = new ArrayList<>();
double areaTempSigned = 0;
double areaCached = 0;
double[] seg = new double[6];
double startX = Double.NaN, startY = Double.NaN;
double x0 = 0, y0 = 0, x1 = 0, y1 = 0;
boolean closed = false;
while (!iter.isDone()) {
switch(iter.currentSegment(seg)) {
case PathIterator.SEG_MOVETO:
// Log starting positions - need them again for closing the path
startX = seg[0];
startY = seg[1];
x0 = startX;
y0 = startY;
iter.next();
areaCached += areaTempSigned;
areaTempSigned = 0;
points.clear();
points.add(new Point2(startX, startY));
closed = false;
continue;
case PathIterator.SEG_CLOSE:
x1 = startX;
y1 = startY;
closed = true;
break;
case PathIterator.SEG_LINETO:
x1 = seg[0];
y1 = seg[1];
points.add(new Point2(x1, y1));
closed = false;
break;
default:
// Shouldn't happen because of flattened PathIterator
throw new RuntimeException("Invalid area computation!");
}
;
areaTempSigned += 0.5 * (x0 * y1 - x1 * y0);
// Add polygon if it has just been closed
if (closed) {
if (areaTempSigned < 0)
map.get(Boolean.FALSE).add(ROIs.createPolygonROI(points, plane));
else if (areaTempSigned > 0)
map.get(Boolean.TRUE).add(ROIs.createPolygonROI(points, plane));
// Zero indicates the shape is empty...
}
// Update the coordinates
x0 = x1;
y0 = y1;
iter.next();
}
// TODO: Decide which is positive and which is negative
areaCached += areaTempSigned;
PolygonROI[][] polyOutput = new PolygonROI[2][];
if (areaCached < 0) {
polyOutput[0] = map.get(Boolean.TRUE).toArray(PolygonROI[]::new);
polyOutput[1] = map.get(Boolean.FALSE).toArray(PolygonROI[]::new);
} else {
polyOutput[0] = map.get(Boolean.FALSE).toArray(PolygonROI[]::new);
polyOutput[1] = map.get(Boolean.TRUE).toArray(PolygonROI[]::new);
}
return polyOutput;
}
use of qupath.lib.geom.Point2 in project qupath by qupath.
the class RoiTools method getLinearPathPoints.
static List<Point2> getLinearPathPoints(final Path2D path, final PathIterator iter) {
List<Point2> points = new ArrayList<>();
double[] seg = new double[6];
while (!iter.isDone()) {
switch(iter.currentSegment(seg)) {
case PathIterator.SEG_MOVETO:
// Fall through
case PathIterator.SEG_LINETO:
points.add(new Point2(seg[0], seg[1]));
break;
case PathIterator.SEG_CLOSE:
// points.add(points.get(0));
break;
default:
throw new IllegalArgumentException("Invalid polygon " + path + " - only line connections are allowed");
}
;
iter.next();
}
return points;
}
use of qupath.lib.geom.Point2 in project qupath by qupath.
the class ConvexHull method getConvexHull.
/**
* TODO: Consider a more efficient convex hull calculation.
*
* For implementation details, see
* <ul>
* <li>http://en.wikipedia.org/wiki/Gift_wrapping_algorithm</li>
* <li>http://en.wikipedia.org/wiki/Graham_scan</li>
* </ul>
*
* @param points
* @return
*/
public static List<Point2> getConvexHull(List<Point2> points) {
if (points == null || points.isEmpty())
return null;
// Find the left-most point
Point2 pointOnHull = points.get(0);
for (Point2 p : points) {
if (p.getX() < pointOnHull.getX())
pointOnHull = p;
}
List<Point2> hull = new ArrayList<>(points.size());
while (true) {
// logger.info("Adding: " + pointOnHull);
hull.add(pointOnHull);
Point2 endPoint = points.get(0);
double dx = endPoint.getX() - pointOnHull.getX();
double dy = endPoint.getY() - pointOnHull.getY();
for (Point2 s : points) {
double sx = s.getX() - pointOnHull.getX();
double sy = s.getY() - pointOnHull.getY();
if ((endPoint.equals(pointOnHull)) || sx * dy - sy * dx > 0) {
endPoint = s;
dx = sx;
dy = sy;
}
}
pointOnHull = endPoint;
if (endPoint.equals(hull.get(0))) {
logger.trace("Original points: {}, Convex hull points: {}", points.size(), hull.size());
return hull;
}
// if (endPoint == hull.get(0) || endPoint.distanceSq(hull.get(0)) < 0.00000001)
// return hull;
}
}
Aggregations