ISH image analysis - need help with debugging script for scoring cells as high, medium and low

Hello,
I am using a script (see below) in QuPath version 0.2.0-m9 to detect cells and score them based on number of ISH spots. I have used the code-segment from Pete’s post on Spot-detection. The cell detection and sub-cellular Detection work ok but I get the following error in the Def PathClass = ... line.

ERROR: MissingMethodException at line 19: No signature of method: static qupath.lib.objects.classes.PathClassFactory.getNonIntensityAncestorClass() is applicable for argument types: (qupath.lib.objects.classes.PathClass) values: [Negative]
ERROR: Script error (MissingMethodException)

I am wondering if this is related to the QuPath version as Pete’s post did indicate that the code is valid only for milestone <= m2.

Here is my script:

setImageType('BRIGHTFIELD_H_DAB');
clearDetections();
setColorDeconvolutionStains('{"Name" : "H-DAB default", "Stain 1" : "Hematoxylin", "Values 1" : "0.71395 0.60296 0.35597 ", "Stain 2" : "DAB", "Values 2" : "0.45896 0.60895 0.64694 ", "Background" : " 255 255 255 "}');
selectAnnotations();

//Detect cells
runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "requestedPixelSizeMicrons": 0.5,  "backgroundRadiusMicrons": 6.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1.5,  "minAreaMicrons": 10.0,  "maxAreaMicrons": 350.0,  "threshold": 0.075,  "maxBackground": 5.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansionMicrons": 6.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true,  "thresholdCompartment": "Cell: DAB OD mean",  "thresholdPositive1": 0.5,  "thresholdPositive2": 0.4,  "thresholdPositive3": 0.6000000000000001,  "singleThreshold": true}');

//Detect number of ISH spots and clusters per cell
//Break clusters to dots - split by Intesity = true
runPlugin('qupath.imagej.detect.cells.SubcellularDetection', '{"detection[DAB]": 0.2,  "doSmoothing": true,  "splitByIntensity": true,  "splitByShape": true,  "spotSizeMicrons": 1.0,  "minSpotSizeMicrons": 0.5,  "maxSpotSizeMicrons": 4.0,  "includeClusters": true}');

// Need this for classifying cells based on number of ish spots
import qupath.lib.objects.classes.PathClassFactory

// Score cells as high, medium and low based on # of spots
for (cell in getCellObjects()) {
    // Determine the current classification, with any intensity component removed
    def pathClass = PathClassFactory.getNonIntensityAncestorClass(cell.getPathClass())

    // Determine how many single spots we have
    double numSingleSpots = measurement(cell, "Subcellular: DAB: Num single spots")

    // Apply the new classification
    if (numSingleSpots >= 15)
        cell.setPathClass(getDerivedPathClass(pathClass, "High"))

    if (numSingleSpots >= 7 && numSingleSpots < 15)
        cell.setPathClass(getDerivedPathClass(pathClass, "Medium"))

    if (numSingleSpots >= 3 && numSingleSpots < 7)        
        cell.setPathClass(getDerivedPathClass(pathClass, "low"))
        
    if (numSingleSpots < 3)        
        cell.setPathClass(getDerivedPathClass(pathClass, "negative"))
        
}

// Ensure the GUI is updated
fireHierarchyUpdate()

Here is a sample image in case this helps: 434936.tif (7.0 MB)

It looks like the current script only uses one measurement, so you can use a slightly adapted form of the easy method from the post:

setImageType('BRIGHTFIELD_H_DAB');
clearDetections();
setColorDeconvolutionStains('{"Name" : "H-DAB default", "Stain 1" : "Hematoxylin", "Values 1" : "0.71395 0.60296 0.35597 ", "Stain 2" : "DAB", "Values 2" : "0.45896 0.60895 0.64694 ", "Background" : " 255 255 255 "}');
selectAnnotations();

//Detect cells
runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "requestedPixelSizeMicrons": 0.5,  "backgroundRadiusMicrons": 6.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1.5,  "minAreaMicrons": 10.0,  "maxAreaMicrons": 350.0,  "threshold": 0.075,  "maxBackground": 5.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansionMicrons": 6.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true,  "thresholdCompartment": "Cell: DAB OD mean",  "thresholdPositive1": 0.5,  "thresholdPositive2": 0.4,  "thresholdPositive3": 0.6000000000000001,  "singleThreshold": true}');

//Detect number of ISH spots and clusters per cell
//Break clusters to dots - split by Intesity = true
runPlugin('qupath.imagej.detect.cells.SubcellularDetection', '{"detection[DAB]": 0.2,  "doSmoothing": true,  "splitByIntensity": true,  "splitByShape": true,  "spotSizeMicrons": 1.0,  "minSpotSizeMicrons": 0.5,  "maxSpotSizeMicrons": 4.0,  "includeClusters": true}');

// Set three thresholds
setCellIntensityClassifications("Subcellular: DAB: Num spots estimated", 3, 7, 15)

Otherwise, since you don’t seem to need any ‘base’ classification other than the intensity classifications you’ve got, you can just use

def pathClass = null

and keep the rest of your script. Or finally, the real answer to your question (although one I had to look up): use

def pathClass = PathClassTools.getNonIntensityAncestorClass(cell.getPathClass())

The method shifted to ‘Tools’ rather than ‘Factory’ for more consistency. It should be a default import (at least it is in m10), so you can drop the import statement too.

@petebankhead many thanks for the (always prompt!) reply.

The new pathClass definition works well with m9.

Yes, I could have used the easy method for scoring cells. However, I thought that i may need the pathClass object properly defined for analysis of spot localization (i.e. classify spots as “nuclear” or “cytoplasmic”). However, I may be wrong.

Specifically, I would like to classify the ISH spots as nuclear or cytoplasmic and then classify cells solely based on the number of ISH spots in the cytoplasm. In addition, I also want to detect cells that have ISH spots only in the nucleus as this may indicate genomic hybridization of the ISH probe (which theoretically should not happen but we have seen this in several instances and have confirmed it through very painful independent validation).

Its not clear to me how the code provided for Spot localization should be used. My guess is that I should first detect the cells, then run subcellular detection, then do spot localization and then finally classify cells based on nuclear or cytoplasmic ISH spots. I tried to implement this workflow using the following script (which is incomplete as it only has steps 1 through 3). However, after running spot localization, I was unable to figure out which specific field in the child object (subcellularObject) gets updated, which is something I need to poll to classify the cells based on cytoplasmic and nuclear only spot localization. Any help to resolve this issue is greatly appreciated.

Here is the incomplete code:

setImageType('BRIGHTFIELD_H_DAB');
clearDetections();
setColorDeconvolutionStains('{"Name" : "H-DAB default", "Stain 1" : "Hematoxylin", "Values 1" : "0.71395 0.60296 0.35597 ", "Stain 2" : "DAB", "Values 2" : "0.45896 0.60895 0.64694 ", "Background" : " 255 255 255 "}');
selectAnnotations();

//Detect cells
runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "requestedPixelSizeMicrons": 0.5,  "backgroundRadiusMicrons": 6.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1.5,  "minAreaMicrons": 10.0,  "maxAreaMicrons": 350.0,  "threshold": 0.075,  "maxBackground": 5.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansionMicrons": 6.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true,  "thresholdCompartment": "Cell: DAB OD mean",  "thresholdPositive1": 0.5,  "thresholdPositive2": 0.4,  "thresholdPositive3": 0.6000000000000001,  "singleThreshold": true}');

//Detect number of ISH spots and clusters per cell
//Break clusters to dots - split by Intesity = true
runPlugin('qupath.imagej.detect.cells.SubcellularDetection', '{"detection[DAB]": 0.2,  "doSmoothing": true,  "splitByIntensity": true,  "splitByShape": true,  "spotSizeMicrons": 1.0,  "minSpotSizeMicrons": 0.5,  "maxSpotSizeMicrons": 4.0,  "includeClusters": true}');

// RUN SPOT LOCALIZATION
// Get all spots/clusters
def clusters = getObjects({p -> p.class == qupath.imagej.detect.cells.SubcellularDetection.SubcellularObject.class})

// Loop through all clusters
for (c in clusters) {
    // Find the containing cell
    def cell = c.getParent()
    // Check the current classification - remove the last part if it
    // was generated by a previous run of this command
    def pathClass = c.getPathClass()
    if (["Nuclear", "Cytoplasmic"].contains(c.getName())) {
        pathClass = pathClass.getParentClass()
    }
    // Check the location of the cluster centroid relative to the nucleus,
    // and classify accordingly
    def nucleus = cell.getNucleusROI()
    if (nucleus != null && nucleus.contains(c.getROI().getCentroidX(), c.getROI().getCentroidY())) {
        c.setPathClass(getDerivedPathClass(c.getPathClass(), "Nuclear"))
    } else
        c.setPathClass(getDerivedPathClass(c.getPathClass(), "Cytoplasmic"))
}
//END OF SPOT LOCALIZATION CODE-SEGMENT

// Ensure the GUI is updated
fireHierarchyUpdate()