Last active
April 25, 2023 10:44
-
-
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
This file contains hidden or 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
// 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