Skip to content

Instantly share code, notes, and snippets.

@beosro
Forked from Svidro/!Selecting Objects in QuPath
Created December 3, 2023 11:00
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 beosro/74ee8420c59c45947d2a1d056cc2ac34 to your computer and use it in GitHub Desktop.
Save beosro/74ee8420c59c45947d2a1d056cc2ac34 to your computer and use it in GitHub Desktop.
Selecting things in QuPath
Scripts mostly taken from Pete, and also from the forums. For easy access and reference.
Covers both Selecting, which is necessary to run most commands like cell detection, and collecting objects into a variable
for processing. The latter is more efficient when you do not need to run a command like cell detection.
TOC
A Selection guide.groovy - not actually a script, just a collection of useful tips.
Access top level objects.groovy - Creates a list of all objects at the "top" of the hierarchy. This list is dynamic.
Decendant and child objects.groovy - Shows accessing child objects or decendant objects.
Get objects within another object.groovy - ignores hierarchy in case of overlaps!
SelectClassificationsByName.groovy - Select objects based on their classifications having certain words
Selecting objects within a drawn annotation.groovy - <- What it says. Removes the drawn/selected annotation.
Selecting objects touching an annotation.groovy - uses Geometry functions to determine if the edges of objects are touching.
Testing selection models.groovy - For my reference.
Using selectionModel for groups of objects.groovy - Very basic example of creating a selection based on some thresholding.
//Warning, some functions like getArea() will not work on some types of objects, like points, and will cause the script to crash
//if you are searching across both area based annotations and points. getArea() also returns measurements IN PIXELS.
//Select all of a type
selectAnnotations();
selectDetections();
//get as complicated as you want in the selection here, goes through all objects
selectObjects { p -> p.getPathClass() == getPathClass("Stroma") && p.isAnnotation() }
//or from https://groups.google.com/forum/#!topic/qupath-users/PxrCwx5ttXI
selectObjects {
//Some criteria here
return it.isAnnotation() && it.getPathClass() == getPathClass('Tumor')
}
//These do not select, but each can be used to generate a list of objects, which can then be used...
getAnnotationObjects()
getDetectionObjects()
getSelectedObject()
getSelectedObjects()
//... in this type of structure
def Annotations = getAnnotationObjects()
getCurrentHierarchy().getSelectionModel().setSelectedObjects(Annotations, null)
//The total of these two lines is the same as selectAnnotations(), but is capable of being modified by:
def smallAnnotations = getAnnotationObjects().findAll {it.getROI().getArea() < 400000}
//similar to the selectObjects code above
//The 400000 in this case is in pixels, and would need to be modified by a metadata call (which varies depending on your version of QuPath)
//Selecting multiple single object in a row
resetSelection()
getCellObjects().each{
getCurrentHierarchy().getSelectionModel().setSelectedObject(it, true);
threshold = measurement(it, "Nucleus: Channel 2 mean")+measurement(it, "Nucleus: Channel 2 std dev")
runPlugin('qupath.imagej.detect.cells.SubcellularDetection', '{"detection[Channel 1]": -1.0, "detection[Channel 2]": '+threshold+', "detection[Channel 3]": -1.0, "doSmoothing": true, "splitByIntensity": false, "splitByShape": true, "spotSizeMicrons": 1.0, "minSpotSizeMicrons": 1, "maxSpotSizeMicrons": 2.0, "includeClusters": true}');
resetSelection();
fireHierarchyUpdate()
}
//In 0.2.0 many things can be replaced by simple thresholding and then using scripts like:
selectObjectsByClassification("Islet");
selectObjectsByClassification("Islet", "Stroma");
//Which directly selects objects in one line.
//Selecting objects within other objects, that may not match up with the hierarchy
//https://forum.image.sc/t/qupath-getting-detectionsobjects-within-non-parent-annotation/41784/2
def hierarchy = getCurrentHierarchy()
def parent = getSelectedObject()
def objects = hierarchy.getObjectsForROI(null, parent.getROI())
.findAll { it.isDetection() }
hierarchy.getObjectsForROI(qupath.lib.objects.PathDetectionObject, parent.getROI())
//accesses the highest level objects without cycling through all objects or annotations
//Important to note that this list is DYNAMIC and will adjust as objects are created or destroyed.
//0.1.2 or 0.2.0
//may want to resolveHierarchy() before running this in 0.2.0
topLevel = getCurrentHierarchy().getRootObject().getChildObjects()
//Finding decendant and child objects of annotations
//0.1.2
// Define classes
class1 = getPathClass('class1')
class2 = getPathClass('class2')
// Loop through and add child/descendant counts
hierarchy = getCurrentHierarchy()
getAnnotationObjects().each {
if (it.getPathClass() != class1)
return false
def children = it.getChildObjects()
def nClass2Children = children.count {it.getPathClass() == class2}
def descendants = hierarchy.getDescendantObjects(it, null, null)
def nClass2Descendants = descendants.count {it.getPathClass() == class2}
it.getMeasurementList().putMeasurement('Number of ' + class2 + ' child objects', nClass2Children)
it.getMeasurementList().putMeasurement('Number of ' + class2 + ' descendant objects', nClass2Descendants)
it.getMeasurementList().closeList()
}
//Sara McArdle
// 0.2.0 assigns a pathClass to detections that have an exactly matching annotation.
import static qupath.lib.gui.scripting.QPEx.*
def annots=getAnnotationObjects()
def annotgeos=annots.collect{it.getROI().getGeometry()}
def dets=getDetectionObjects()
def detgeos=dets.collect{it.getROI().getGeometry()}
annotgeos.eachWithIndex{a, idx->
def equality=detgeos.collect{it.equalsTopo(a)}
int match=equality.findIndexOf {it==true}
if (match>=0){
dets[match].setPathClass(annots[idx].getPathClass())
}
}
//0.2.0 only option for ignoring the hierarchy and selecting objects within another object.
//https://forum.image.sc/t/qupath-getting-detectionsobjects-within-non-parent-annotation/41784/2
//Stores the objects you want within a variable called "objects"
//Original:
def hierarchy = getCurrentHierarchy()
def parent = getSelectedObject()
def objects = hierarchy.getObjectsForROI(null, parent.getROI())
.findAll { it.isDetection() }
//one line version
def objects = getCurrentHierarchy().getObjectsForROI(qupath.lib.objects.PathDetectionObject, getSelectedObject().getROI())
//If you have single Annotations with different classifications, you could use something like the following
//Example, you have a Tissue annotation, and within that a specific Tumor annotation. You want the "cells" within the Tumor annotation
//[0] at the end of the next line gets the first (and only, in this example) annotation that is classified as Tumor.
tumorAnno = getAnnotationObjects().findAll{it.getPathClass() == getPathClass("Tumor")}[0]
objects = getCurrentHierarchy().getObjectsForROI(qupath.lib.objects.PathCellObject, tumorAnno.getROI())
/**
* Source https://gist.github.com/petebankhead/abb3f5a3a2b55ddc22f709c99964857e
* Helper script for QuPath to find objects with one or more classifications.
*
Mostly replaced by
selectObjectsByClassification("Islet");
in 0.2.0
* @author Pete Bankhead
*/
// Insert as many or few classifications here as required
selectObjects {checkForClassifications(it.getPathClass(), 'CD8', 'FoxP3')}
print 'Selected ' + getSelectedObjects().size()
boolean checkForSingleClassification(def pathClass, classificationName) {
if (pathClass == null)
return false
if (pathClass.getName() == classificationName)
return true
return checkForSingleClassification(pathClass.getParentClass(), classificationName)
}
boolean checkForClassifications(def pathClass, String...classificationNames) {
if (classificationNames.length == 0)
return false
for (String name : classificationNames) {
if (!checkForSingleClassification(pathClass, name))
return false
}
return true
}
//Sara McCardle
import static qupath.lib.gui.scripting.QPEx.*
def islets=getDetectionObjects()
def isletsgeos=islets.collect{it.getROI().getGeometry()}
def outsidegeo=getAnnotationObjects().find{it.getPathClass()==getPathClass("Tissue")}.getROI().getGeometry()
def intersections=[]
isletsgeos.eachWithIndex{entry,idx->
if (entry.intersects(outsidegeo)){
intersections<<idx
}
}
print(intersections)
getCurrentHierarchy().getSelectionModel().selectObjects(islets[intersections])
//This script allows you to draw a quick annotation, and then select all objects within it for... whatever reason
//It also deletes the drawn annotation by design with "clearSelectedObjects()" so comment out that line if you
//want to keep your annotation
// 0.1.2
// Get the current selected object & hierarchy
selected = getSelectedObject()
hierarchy = getCurrentHierarchy()
// Get all the objects inside the current selection
objectsToSelect = hierarchy.getDescendantObjects(selected, null, null)
clearSelectedObjects()
if (objectsToSelect != null) {
// Remove the current selected object
hierarchy.removeObject(selected, true)
// Update the selection
hierarchy.getSelectionModel().selectObjects(objectsToSelect)
}
//This is just a set of tests for figuring out how selection models work.
//0.1.2
//Select Tumor annotations within named Organ annotations
hierarchy = getCurrentHierarchy()
hierarchy.getSelectionModel().clearSelection();
def organs = getAnnotationObjects().findAll {it.getDisplayedName().equalsIgnoreCase("Organ")}
organs.each{hierarchy.getSelectionModel().selectObjects(hierarchy.getDescendantObjects(it, null, null).findAll{it.getPathClass() == getPathClass("Tumor")})}
//Another variant
def hierarchy = getCurrentHierarchy()
hierarchy.getSelectionModel().clearSelection()
hierarchy.getSelectionModel().selectObjects(getAnnotationObjects().findAll {it.getPathClass() == getPathClass("Tumor") &&
it.getParent().getDisplayedName().equalsIgnoreCase("Organ") && it.getLevel() != 1})
//This script is a modification of the script to find annotations of a certain size, which then uses
//getSelectionModel to take the list of annotations and have QuPath register them as selected
//getArea() returns a measurement in PIXELS not square microns.
//0.1.2 0.2.0
def smallAnnotations = getAnnotationObjects().findAll {it.getROI().getArea() < 400000}
//This line does the selecting, and you should be able to swap in any list of objects for smallAnnotations
getCurrentHierarchy().getSelectionModel().setSelectedObjects(smallAnnotations, null)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment