Compiling results from multiple images: Run for Project vs Loop within Project

I’m trying to write a script that will open each image in a project, do a cell detection, and then report out the total # of cells detected. Ultimately, I’d like to report a total cell count for the project.

I have a script that will open the image, run the cell detection, and report the cell count.

  1. I’ve tried Run for Project, but then I don’t seem to be able to get a total cell count for the project.
  2. I’ve tried putting the cell detection routine in a loop, but I seem to be missing a command that selects the next image in the project, and the cell detection happens on the first image over and over again.

Is there a way for method 1 to work?
What command do I need to run in method 2 to advance the image selection in the loop (see code below)

import qupath.lib.objects.PathCellObject
import qupath.lib.gui.measure.ObservableMeasurementTableData
import qupath.lib.gui.tools.MeasurementExporter
import qupath.lib.measurements.Measurement
clearAnnotations();
clearDetections();

setImageType('FLUORESCENCE');

def output = new StringBuilder()
def project = getProject()
for (entry in project.getImageList()) {
    //selectImage(entry) //Need some way to switch focus to each file in loop before running calculations
    createSelectAllObject(true);
    runPlugin('qupath.imagej.detect.cells.WatershedCellDetection', '{"detectionImage": "Channel 1",  "requestedPixelSizeMicrons": 0.325,  "backgroundRadiusMicrons": 8.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1.5,  "minAreaMicrons": 10.0,  "maxAreaMicrons": 60.0,  "threshold": 100.0,  "watershedPostProcess": true,  "cellExpansionMicrons": 5.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true}');
    getProjectEntry().saveImageData(getCurrentImageData())
    //def imageData = entry.readImageData() //only needed if you need access to the pixels
    //def hierarchy = imageData.getHierarchy()
    def hierarchy = entry.readHierarchy()
    def cells = hierarchy.getDetectionObjects()
    def imagename = entry.getImageName()
    imagename=imagename.replaceAll(" ","")
    imagename=imagename.replaceAll(":","")
    imagename=imagename.replaceAll("#","")
    output << imagename + '\t' + cells.size() << '\n'
}
print output.toString()

Mostly the same as this thread:

Despite cycling through the “entry in project”, you are not using the entry that you cycle through. You do finally access the correct hierarchy after creating all of the objects, though.

    createSelectAllObject(true);
    runPlugin('qupath.imagej.detect.cells.WatershedCellDetection', '{"detectionImage": "Channel 1",  "requestedPixelSizeMicrons": 0.325,  "backgroundRadiusMicrons": 8.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1.5,  "minAreaMicrons": 10.0,  "maxAreaMicrons": 60.0,  "threshold": 100.0,  "watershedPostProcess": true,  "cellExpansionMicrons": 5.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true}');
    getProjectEntry().saveImageData(getCurrentImageData())

You needed this step earlier:

def hierarchy = entry.readHierarchy()

Most basic commands will assume you are in the current hierarchy if you Run them. Run for Project is what lets default commands apply to each image in turn. You need to change the script significantly to cycle through the project entries- it is not as simple as creating a for loop and running the same code, unfortunately. I would recommend looking for other examples of entry in project loops.

I suspect it would be easier to run the loop to count separately, if the count is what you want. Run the cell creation “for project” and then sum your cell counts the same way you currently are. If you want to get creative and put them both in one script, put the “for entry” loop at the end of the for project script, and just have it run each time, you only keep the final output line.

This might be the closest to what you want.

Some of the nifty shortcuts like createSelectAllObject may not work so well in that type of code structure as well - for example this is what I was using cycling through square images:

def project = getProject()
for (entry in project.getImageList()) {
    imageData = entry.readImageData()
    def hierarchy = imageData.getHierarchy()
    server = imageData.getServer()
    xdist = server.getWidth()
    ydist = server.getHeight()
    frame = PathObjects.createAnnotationObject(ROIs.createRectangleROI(0,0,xdist,ydist,ImagePlane.getPlane(0,0)));
    hierarchy.addPathObject(frame);
}

Last note, your clear annotations and detection statements will definitely only be applied to the current image.

1 Like

With the caveat that I haven’t tested it, I think that using Run for Project with something like this should work:

I’d strongly recommend not using your own loops unless you really need them, which can be complicated and error-prone. Rely instead on Run for Project, which will do a lot more to help you (like closing images once they are done).

If the log contains too much other stuff in addition to your measurements, making the desired output hard to untangle, you can do one or more of these:

  • Turn off Run → Show log in console
  • Split your script into two (Run for Project) runs: the first for detection, the second only to print the counts
  • Accumulate your output in a file instead of a String

In the last case (again untested, so perhaps with typos), it might looks like this:

buildFilePath(PROJECT_BASE_DIR, 'my_output.txt')
def file = new File(path)

createSelectAllObject(true);
runPlugin('qupath.imagej.detect.cells.WatershedCellDetection', '{"detectionImage": "Channel 1",  "requestedPixelSizeMicrons": 0.325,  "backgroundRadiusMicrons": 8.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1.5,  "minAreaMicrons": 10.0,  "maxAreaMicrons": 60.0,  "threshold": 100.0,  "watershedPostProcess": true,  "cellExpansionMicrons": 5.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true}');
def imagename = entry.getImageName()
  .replaceAll(" ","")
  .replaceAll(":","")
  .replaceAll("#","")

String output = imagename + '\t' + cells.size() + '\n'
if (file.exists())
  file << output
else
  file.text = output

To reset the file, just delete it before using Run for Project.

Finally, be sure to check out the Measurement exporter, which can often avoid the need for more complicated export scripts.

2 Likes