Created
April 13, 2023 13:42
-
-
Save petebankhead/26eba44839494f4e7ddcd127597a31b8 to your computer and use it in GitHub Desktop.
Create a point annotation showing equally-spaced splits along a polyline or simple polygon
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Create a point annotation showing equally-spaced splits | |
* along a polyline or simple polygon (i.e. no holes or | |
* self-intersections). | |
* | |
* See https://forum.image.sc/t/divide-free-hand-lines-at-regular-interval/79845/4 | |
* | |
* Written for QuPath v0.4.3 using JTS. | |
* | |
* @author Pete Bankhead | |
*/ | |
import org.locationtech.jts.geom.Coordinate | |
import org.locationtech.jts.geom.Geometry | |
import org.locationtech.jts.geom.LineSegment | |
import org.locationtech.jts.geom.LineString | |
import org.locationtech.jts.geom.Polygon | |
import qupath.lib.geom.Point2 | |
import qupath.lib.objects.PathObjects | |
import qupath.lib.roi.ROIs | |
import static qupath.lib.gui.scripting.QPEx.* | |
// Define number of segments (1 more than number of splits) | |
int numSegments = 4 | |
// Get the current selection & make sure it contains a line or simple polygon | |
def selected = getSelectedObject() | |
def roi = selected?.getROI() | |
def geometry = roi?.getGeometry() | |
if (!isCompatibleGeometry(geometry)) { | |
println "WARN: You need to select a line ROI, or a simple polygon (no holes or intersections)!" | |
return | |
} | |
// Determine distance between each split | |
double length = roi.getLength() | |
double segmentLength = length / numSegments | |
// Identify the split points | |
List<Point2> splitPoints = [] | |
def coords = geometry.getCoordinates() as List | |
// For an area, add the first coordinate | |
if (geometry instanceof Polygon) | |
splitPoints.add(toPoint(coords[0])) | |
// Add the split points | |
for (int i = 1; i < numSegments; i++) { | |
def splitCoord = findSplitCoordinate(coords, i * segmentLength) | |
splitPoints.add(toPoint(splitCoord)) | |
} | |
// Create new point ROI | |
def points = ROIs.createPointsROI(splitPoints, roi.getImagePlane()) | |
def newAnnotation = PathObjects.createAnnotationObject(points, selected.getPathClass()) | |
addObject(newAnnotation) | |
Coordinate findSplitCoordinate(List<Coordinate> coordinates, double searchLength) { | |
def lastCoordinate = coordinates[0] | |
def iterator = coordinates[1..coordinates.size()-1].iterator() | |
while (iterator.hasNext()) { | |
def nextCoordinate = iterator.next() | |
double tempLength = nextCoordinate.distance(lastCoordinate) | |
if (searchLength <= tempLength) { | |
return new LineSegment(lastCoordinate, nextCoordinate) | |
.pointAlong( | |
searchLength / tempLength | |
) | |
} else { | |
searchLength -= tempLength | |
} | |
lastCoordinate = nextCoordinate | |
} | |
} | |
boolean isCompatibleGeometry(Geometry geom) { | |
if (geom == null || geom.getNumGeometries() != 1) | |
return false | |
if (geom instanceof LineString) | |
return true | |
if (geom instanceof Polygon && ((Polygon)geom).getNumInteriorRing() == 0) | |
return true | |
return false | |
} | |
Point2 toPoint(Coordinate coord) { | |
return new Point2(coord.x, coord.y) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment