How do I get a separate H-Score for Cytoplasm and Nuclear staining?

Hi there,

I am interested in generating a separate H-Score for a) Cytoplasmic staining and b) nuclear staining of the protein E-Cadherin. I am using DAB in the lab on TMA. E-Cadherin is a membrane protein which can also be expressed within the cytoplasm and nucleus.

When I look in the TMA core results, I only see one H-Score displayed. I am unsure if this is the H-Score based on nuclear staining, cytoplasmic staining, or some combination:

. I understand that the basic formula for H-Score is: 3x percentage of strong staining + 2x percentage of moderate staining + 1x percentage of weak staining nuclei (total ranging from 0 to 300).

I used the ‘Cell: DAB OD max’ as my thinking is that ‘cell’ encompasses both ‘nucleus’ and ‘cytoplasm’. Please correct me if I am misunderstanding.

I have already watched all of the videos in the QuPath playlists on Youtube for ‘Getting Started’ and ‘IHC Analysis’ (https://www.youtube.com/channel/UCqepVnS1QsB7B8nBA9T91EQ). I also watched this video for TMAs which was very helpful in guiding me through the process with TMAs: https://www.youtube.com/watch?v=dIfoooAy-wM .

Any and all help on how to generate a separate H-Score for cytoplasmic and nuclear staining will be much appreciated. Especially if you have any advice on the specific workflow.

Thank you,
Stefan

1 Like

The cell DAB OD max is going to be the single darkest pixel in the cell. I generally am not a fan of the Max and Min measurements because of that, they are too easily influenced by little bits of something in the cell boundaries. Mean, on the other hand, would be averaged across the whole cell, cytoplasm and nucleus included. QuPath does not support two different H-Scores by default (autocalculated as part of Positive cell detection), but you could:
A. Run it twice and export the results for the nuclear and cytoplasmic staining.
B. Calculate the H-score yourself twice based off of two sets of classifications, and add those measurements as a new measurement name each time (so that they don’t overwrite each other, which would be the default behavior for an H-score).

H-Score can be generated using whatever measurement you want without re-creating the cells using a script like:
setCellIntensityClassifications("Nucleus: DAB OD mean", 0.1, 0.2,0.3)
Insert whichever measurements and thresholds you want (for legitimate measurements, thresholds as determined by a pathologist, I suppose)

Accessing the dynamic measurements like H-score is a little tricky, but you can see how to do it from this post here:


That post should also show you how to create a new measurement, like “H-score: Nuclear” so that you can take the current H-score for whichever analysis you choose to do first, and put it in it’s own Measurement entry. Then run the classifier again, and repeat with a new measurement name like “H-score: Cytoplasm”.

Batch exporting analysis results, in case you want to export for a project:


Another H-score classification question

1 Like

Hello Research_Associate,

Firstly, thank you for your detailed response. I truly appreciate your knowledge, expertise, patience and dedication to the QuPath community.

Secondly, I was able to generate the script for generating new H-Scores setCellIntensityClassifications("Nucleus: DAB OD mean", 0.1, 0.2,0.3). Thank you.

I was unable to successfully create and store the measurement entries for “H-Score: Nuclear” and “H-Score: Cytoplasm”.

Would you please be able to guide me on how this process would work for TMAs? From what I understand, the script is supposed to work with annotations (not TMA measurements) to create an a column titled “H-Score: Cytoplasm” in the ‘Annotation Results’ (Measure --> Show Annotation measurements). However, I do not see that. Would you please clarify what the function of the script is and how I can collect the Cytoplasm H-Score data from it for exporting?

My process when attempting to follow your solution is as follows:

  1. Use the TMA Assistant under Workflow Assistant (completed beforehand)
  2. Load and re-run the classifier I previously created --> The threshold measurement I am using here is for ‘Cytoplasm: DAB OD mean’.
  3. Run the following script to attempt to store the Cytoplasmic H-Score data of the TMAs that was just collected. The script is from the link you previously directed me to: https://gist.github.com/Svidro/68dd668af64ad91b2f76022015dd8a45#file-accessing-dynamic-measurements-groovy
import qupath.lib.gui.models.ObservableMeasurementTableData
def ob = new ObservableMeasurementTableData();

def annotations = getAnnotationObjects()
 // This line creates all the measurements
 ob.setImageData(getCurrentImageData(),  annotations);


annotations.each { annotation->println( ob.getNumericValue(annotation, "H-score") )
}

  1. At this point I have three concerns:
    i) This is the response I receive from the script editor following running the above script – I am not sure what to make of this. Could you please elaborate if I followed the process correctly?

ii) I do not understand why the script is addressing the ‘annotations’ to collect the H-Score data? Shouldn’t the script be attempting to collect the H-Score data from the TMA measurements instead? At this point, my only annotations are of the regions I selected to train the classifier for stroma vs. tumor.
As shown here:

iii) Where is the data from “H-Score: Cytoplasm” located/stored in QuPath? How can l later export this? My vision is to later re-run the classifier for the ‘Nucleus: DAB OD mean’ (following ‘Cytoplasm: DAB OD mean’) to collect the H-Score for the nucleus. Then I will attempted to store the nuclear data in QuPath again as “H-Score: Nuclear” using the same script I quoted above. Lastly I will export the H-Score for both the Cytoplasmic and Nuclear regions stored in QuPath. Please advise me on if this is the most efficient way to collect the H-Scores for the nucleus and cytoplasm and what is the best way to go about this.

I deeply appreciate your help and patience.

Thank you,
Stefan

1 Like

Oh, right, you did mention TMAs. That could be a lot easier.
I used this for a fluorescence TMA using the “Num Immune cells” TMA measurement. You would want to switch the text sections to H-score based stuff. You can also duplicate this script within a script, such that you run it once before using the setIntensityClassifications, and once after. You would need to change the first text entry (where it puts the new measurement, in this case “Immune cell count”) between copies so that it doesn’t overwrite itself.

More explicitly, one copy of the script below would use “H-Score: Cytoplasm” and one copy would use “H-Score: Nucleus” or similar wording. Both would use the input of “Tumor: H-score” in place of “Num Immune cells”

hierarchy = getCurrentHierarchy()
cores = hierarchy.getTMAGrid().getTMACoreList()

import qupath.lib.gui.models.ObservableMeasurementTableData
def ob = new ObservableMeasurementTableData();
 // This line creates all the measurements
ob.setImageData(getCurrentImageData(),  cores);

print "before cores"
cores.each {

    it.getMeasurementList().putMeasurement("Immune cell count", ob.getNumericValue(it, "Num Immune cells"))

}

For your 4 i), the annotation was looking to get an entry that doesn’t exist. You could use that exact script to print a measurement after you had already made it (for example if you used the original H-score text instead of with the cytoplasm).

ii) I normally use annotations for pretty much everything, especially in TMAs so that I can get cell density. Force of habit. Usually there is damage to some TMA cores, and assuming all tissue areas are equal is… wildly incorrect. If you are only using H-scores, either should be fine.

iii) It doesn’t exist until you create it, which you should be able to do with the script above. At that point it should show up in the TMA measurement table.

I admit I kind of skimmed through and wasn’t paying a lot of attention with the first answer, so one other concern upon reviewing is to make sure that you understand that the Tumor H-score is ignoring all of the stroma cells. That means it would be artificially inflated compared to normal H-scores, which take all cells into account.
https://www.ascopost.com/issues/april-10-2015/calculating-h-score/

I think**
The 0 cells generally include the stromal cells. Not that you couldn’t try and use the H-score to mean something else, I guess, but it would definitely be a non-standard usage. @petebankhead has more experience with brightfield TMAs and might be able to chip in here.

Standard usage might be to annotate the Tumor area to separate it from the stroma, or perform some other type of general classifier along with smoothing. If you are both detecting your tumor cells using the E-cadherin stain, and generating a tumor H-score based off of the E-cadherin stain… I’m not sure that is good, since how would you end up with many 0 class cells?

Anyway, just checking since I don’t actually know how exactly you generated the initial Tumor and Stroma cells (you describe a trained classifier, but I don’t know what went into it :slight_smile: )

1 Like

Oh, and for additional fun, you could then use those new TMA measurements to generate heatmaps, possibly semitransparent ones.

1 Like

I cannot thank you enough for all of your help. You accurately understood and concisely developed a quick solution.

If anyone comes across this thread ever and needs the script with the substituted variables it is as follows:

hierarchy = getCurrentHierarchy()
cores = hierarchy.getTMAGrid().getTMACoreList()

import qupath.lib.gui.models.ObservableMeasurementTableData
def ob = new ObservableMeasurementTableData();
 // This line creates all the measurements
ob.setImageData(getCurrentImageData(),  cores);

print "before cores"
cores.each {

    it.getMeasurementList().putMeasurement("H-Score: Cytoplasm", ob.getNumericValue(it, "Tumor: H-score"))
/* after running the Cytoplasm classifier and this script, run the nucleus classifier and change "H-Score: Cytoplasm" from the above line to "H-Score: Nucleus" so that the cytoplasm H-Score data isn't overwritten with nucleus H-score data */
}

Regarding your response to ii), that is very interesting that you use annotations for each of the cores and makes sense - especially since I’ve noticed that QuPath will understandably misidentify folded-over tissue for being dark-brown staining. My workaround has been to select such ‘undesired’ regions with the brush tool then delete the descending objects (of the identified cells). For example I deleted the objects for the region highlighted in red in the image below:

How would I be able to use annotations for cores (instead of the TMA grid I presume)? Is there a way to convert the cores within the TMA’s grid into annotations? Would doing so be beneficial? Besides being able to calculate cell density, are there any other advantages to using annotations instead of TMA grids?

From what you wrote in response to iii), I am getting the impression that I would be able to generate a more accurate/representative H-Score of the cores by using annotations composed of stromal and tumor cells opposed to the TMA grid alone which only accounts for tumor cells? Am I understanding correctly?

As for how I trained my classifier, I just followed the workflow assistant under ‘TMA Assistant: Score nuclear staining’. I created annotations for stromal regions and defined them as such and did the same for tumor regions. As for building the classifier, I selected to use all of the features (just basic smoothing in addition to the standard watershed cell detection.

The heatmap tutorial you sent is also very cool - thank you for sharing!

1 Like

I tend to use the annotations inside of cores. If you run Simple Tissue Detection, it will give you the option of running it on all existing cores. You can then divide the positive cell count in the TMA grid by the Area of the child annotation within that core. Alternatively, you can take each annotation, and ask “which core am I in” and rename the annotation to that core, and just use annotation measurements (which will also include H-scores, etc).

If you want to get really complicated, you can take that simple tissue detection annotation, subdivide it into SLICs (puzzle piece tiles), classify those as “tissue” or “bad” (folds, other crap), then rebuild the annotation only including the puzzle pieces that contain good tissue. Probably only worth it if you had many, many TMAs.

As for the H-score itself, I would simply consult a pathologist if you have one available at your institution, to make sure you are using the measurement correctly. My limited understanding is that an H-score would be for the entire core, indicating, in part, how much of the core was tumor, and how strongly that was stained. So a core with 95% stroma would have a very low H-score, even if the 5% of cells that were positively stained were VERY positively stained. The way you are doing it, such a core would have a very high H-score, since all of the stroma would be ignored. I’m not really sure which is correct, but be clear when you describe your method what you did.

Maybe @VetPathologist would have a better idea than I would. As long as your tumor/stroma classifier is working correctly, and you only want an H-score for the tumor area, and you report that your H-score is only considering the tumor cells, it is probably fine. I think.

1 Like

I see what you are saying about the artificially inflated H-Score now. Do you know if there is a method I can apply in QuPath to have the program consider both stroma and tumor sections when creating an H-Score?

Probably not exactly in the way that you would want, but if you strip the tumor and stroma classifications, the H-score will be calculated for the whole set of cells

resetDetectionClassifications();
setCellIntensityClassifications("Measurement", 0.2,0.4,0.6);
//rest of script to copy H-score to TMA

Followed by applying your classifier for tumor/stroma again. I don’t know that there is a way to both have the H-score ignore tumor/stroma and still have tumor/stroma at the same time, so I would generate the H-score first.

1 Like