This is not an intro programming class, so I am not going to go into the details of what these commands are and how they work in any detail - if you need additional help there are a wide variety of intro programming courses available. These examples are strictly from a QuPath point of view!
if statements and each loops
Let’s take the same example as in the Selecting what you want section, a rectangular annotation where cells were generated, and a Tumor annotation drawn on top of them. Images shown should be from the demo project.
All the cells are back! Lets delete the ones in the tumor again - but this time we will do it the slow way. Not super exciting, but hopefully educational.
"IF the cell is inside the Tumor annotation, THEN delete it" is how I would phrase this in English.
First we need to get the right objects to cycle through - and this time it is all of the cells, not the annotations.
getCellObjects()
There are other similar commands to get other types of objects, just like there were for selecting things.
getDetectionObjects() getAnnotationObjects()
Info
So, back to getCellObjects()
- I want to cycle through these and, if a particular object is inside the tumor annotation, delete it.
I usually use the name->
format these days to keep my loops within loops straight, it has seemed like good practice. It is not necessary for simple loops when "it" is assumed to be the object you are cycling through. I will leave "it" in place here.
//I do not recommend running this except on an EXTREMELY small area resolveHierarchy() getCellObjects().each{ if(it.getParent().getPathClass() == getPathClass("Tumor")){ removeObject(it, true) } }
To break the script down:
Resolve the Hierarchy so that cells within the Tumor annotation have the Tumor annotation as their Parent - not the outer box annotation. The Tumor annotation’s parent will be the white box.
Get all of our cells and loop through each of them.
Check each cell to see if it’s parent has the class Tumor. getParent checks what is above something in the hierarchy.
If the condition is TRUE, remove that object. Wait while QuPath removes that object.
Repeat for all cells.
The above code will work… but it will be VERY slow - I do not recommend it for large areas. That is because QuPath has to access and remove each object individually. This is terribly inefficient. It does demonstrate how you can get a bunch of objects, loop through them, and check if a certain condition is met, and then do something to the object.
If you are not familiar with the hierarchy, read about it here. In general, objects "inside" of other objects can also be considered "below" an object. By default, cells are below the object they were created within - in this case the white box in the image above. The cells inside the tumor area are not actually "below" it in the hierarchy, as I drew that separately from the Cell Detection.
Warning
Same result, but took 200 seconds this time. Yikes!
Could we do that more efficiently? Absolutely. What wereally want to avoid doing is adding or removing objects individually - there are generally functions that will handle that smoothly for you.
removeObject(object, true) removeObjects(listOfObjects,true) addObject(object) addObjects(listOfObjects)
Back to the script. To use removeObjects, I need a list. Think of a list like a box or a stack. You can put cells or other things into it, and take them out, but the box to contain them is a variable that needs to exist first. You create it with brackets. I will make that my first line, then fill that list within the loop.
listOfCells = [] resolveHierarchy() getCellObjects().each{ if(it.getParent().getPathClass() == getPathClass("Tumor")){ listOfCells << it }else{/*something could be put here*/} } removeObjects(listOfCells, true)
Running the above script, it finishes in 0.05 seconds. A far cry from the 200s it took the first time around! So, try to avoid using addObject or removeObject whenever possible, and create a list instead - work in bulk.
Also, I added in an else statement in order to show that for all of these if statements, you can also follow with an "else" to do something when the if statement is "false".
The << in the above example is loading the cell, "it", into the list. You can find more information about Groovy lists in general here: https://www.baeldung.com/groovy-lists
One handy function is .unique, if you only want one instance of any group of objects. For multiplex classifiers, you can have a lot of classes, so you can create a list of all of the classes from all of the objects, and then compress that into a short list with only one instance per class!
listOfClasses = [] getCellObjects().each{ listOfClasses << it.getPathClass()} shortList = listOfClasses.unique()
.unique will ensure that only one example of each object within the list exists.
If you specify a Set, by default you only can have one of a type of object, so the code above could have been:
Set listOfClasses = [] getCellObjects().each{ listOfClasses << it.getPathClass()}
Set listOfClasses would have the same length as shortList - although they would be different types of objects. Not all of the same functions will work with Sets and Lists, but I am not going to dig into that here. If you find your Set or List is causing a problem, you may want to ask on the image.sc forum.
Controlling loops
You can use "break" to end a loop early, or "return" to end one iteration of the loop early (starts the loop with the next entry in the sequence).
Other loop formats
//cell can be any word for (cell in getCellObjects){ }
Set each cell’s name to an integer, starting with 0.
getCellObjects().eachWithIndex{cell, index -> cell.setName(index.toString()) }
One additional note about loops - most of the loops discussed here allow you to do things within them, but do not modify and then return something into another variable. Using ‘collect’ instead of ‘each’ or ‘findAll’ can allow you to perform some operation on a list or objects passed into the loop. One example involving stripping white space can be found here, and another that simplifies ROIs (not the objects themselves!) can be found here!
Some further information on loops: https://www.tutorialspoint.com/groovy/groovy_loops.htm