Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petebankhead/26eba44839494f4e7ddcd127597a31b8 to your computer and use it in GitHub Desktop.
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
/**
* 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