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.

 
Image of tumor, outlined by software
 

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()

symbol Info

Annotations are annotations, detections are detections. Cells are also detections - but tiles can be detections too! Subcellular detections are also, well, detections. It is important to be aware of what objects you have in your project when using these sweeping commands. If you add up the area of all of your detections, you may think you are adding up all of your cells - but you might be double counting "spots" created by the subcellular detection command! All of these objects have at least one ROI, but cells have two, one nuclear and one cell ROI. Think of the ROI as the border line of the object - without any of the measurement data or other fun QuPathy stuff.

 

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:

  1. 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.

  2. Get all of our cells and loop through each of them.

  3. Check each cell to see if it’s parent has the class Tumor. getParent checks what is above something in the hierarchy.

  4. If the condition is TRUE, remove that object. Wait while QuPath removes that object.

  5. 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.

symbol Warning

When using a script of the ADD OBJECTS.groovy type in the demo project, the hierarchy is resolved automatically. This can result in different behavior than if the objects are created "naturally." Please keep this in mind when following along - you creating the objects will be slightly different from loading them!

QuPath tabular menu.  The tab “Hierarchy” is selected, with option below for “Tumor (Polygon) (Tumor).  A bullet below reads, “Annotation (Rectangle) (6274 Objects)
 
 
A slide with dyed cells.  Tumor cells are outlined.
 

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()) }

Some further information on loops: https://www.tutorialspoint.com/groovy/groovy_loops.htm