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 lacan/543b389eca4b0b73f5f78d9a6a6d895c to your computer and use it in GitHub Desktop.
Save lacan/543b389eca4b0b73f5f78d9a6a6d895c to your computer and use it in GitHub Desktop.
[Cluster detections by distance and different classes] Clusters detections with different classifications into clusters based on distance #qupath #groovy
// Define Clusters based on distance and from cells with different classifications
// This script works for images wehre multiple cell signals are segmented as detections
// The detections must be children of annotations objects
// And then a clustering step is used to get multi-classification cells
// The clustering results are provided as circles withg name "Cluster" so you can filter from the original detections
// Works in QuPAth 0.4.3
//
// @author Olivier Burri
// @date 20230425
// Distance for nearest neighbour clustering, in microns
def distance = 10
// START OF SCRIPT
// Remove older clusters
def oldClusters = getAllObjects().findAll { it.getROI() instanceof EllipseROI }
removeObjects( oldClusters, false )
// Work per annotation to keep hierarchy
getAnnotationObjects().each{ annotation ->
def cells = annotation.getChildObjects().findAll{ it.getPathClass() != getPathClass("Ignore*") }
def delaunay = DelaunayTools.newBuilder( cells )
.calibration( getCurrentServer().getPixelCalibration() )
.centroids()
.build()
// Make clusters based on distance and make sure that you cannot cluster cells with the same class
def clusters = delaunay.getClusters( DelaunayTools.centroidDistancePredicate( distance, true ).and( DelaunayTools.sameClassificationPredicate().negate() ) )
// Create the clusters on the dataset
def averaged = clusters.collect{ current ->
// Compute average position
def meanX = current.collect{ it.getROI().getCentroidX() }.sum() / current.size()
def meanY = current.collect{ it.getROI().getCentroidY() }.sum() / current.size()
def roi = ROIs.createEllipseROI( meanX-distance/2, meanY-distance/2, distance, distance, null)
// Get Path Class
def pathClass = PathClass.fromCollection( current.collect{ it.getPathClass().getName() }.toUnique().toSorted() )
def object = PathObjects.createDetectionObject( roi, pathClass )
// Help identify this object for results export and filtering later.
object.setName( "Cluster" )
return object
}
// Add them to the annotation
annotation.addChildObjects( averaged )
}
fireHierarchyUpdate()
import qupath.lib.roi.EllipseROI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment