Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save lacan/543b389eca4b0b73f5f78d9a6a6d895c to your computer and use it in GitHub Desktop.

Select an option

Save lacan/543b389eca4b0b73f5f78d9a6a6d895c to your computer and use it in GitHub Desktop.

Revisions

  1. lacan revised this gist Apr 25, 2023. 1 changed file with 5 additions and 2 deletions.
    7 changes: 5 additions & 2 deletions Cluster detections by distance and different classes.groovy
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,6 @@
    // Define Clusters based on distance and from cells with different classifications
    // This script generally works for images wehre multiple cell signals are segmented as detections
    // 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
    @@ -8,9 +9,11 @@
    // @date 20230425


    // Distance for nearest neighgor clustering, in microns
    // 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 )
  2. lacan created this gist Apr 25, 2023.
    55 changes: 55 additions & 0 deletions Cluster detections by distance and different classes.groovy
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,55 @@
    // Define Clusters based on distance and from cells with different classifications
    // This script generally works for images wehre multiple cell signals are segmented as detections
    // 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 neighgor clustering, in microns
    def distance = 10

    // 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