How to exclude Hematoxylin crystal staining (black dots)?

Hi Qupath community,

I have got quite a few black staining in my slides. I assume that it is somehow unfiltered hematoxylin crystals deposited on my tissue while staining. The problem is that this black dot is recognized as ‘positive’ cells. Is there anyway to set up a max threshold to exclude this black staining from my tissue staining?
Below is my code;

setImageType('BRIGHTFIELD_H_DAB');
setColorDeconvolutionStains('{"Name" : "H-DAB -modified", "Stain 1" : "Hematoxylin", "Values 1" : "0.73951 0.66033 0.13071 ", "Stain 2" : "DAB", "Values 2" : "0.35764 0.66796 0.65263 ", "Background" : " 210 173 178 "}');
createSelectAllObject(true);
selectAnnotations();
runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "backgroundRadius": 30.0,  "medianRadius": 2.0,  "sigma": 6.0,  "minArea": 10.0,  "maxArea": 1000.0,  "threshold": 0.1,  "maxBackground": 2.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansion": 5.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true,  "thresholdCompartment": "Cell: DAB OD mean",  "thresholdPositive1": 0.2,  "thresholdPositive2": 0.4,  "thresholdPositive3": 0.6,  "singleThreshold": true}');

There is not directly within a QuPath function at this time. A few options should have a similar result though, and I’m glad you included a picture! But first, note that once something is dark enough, you can’t really tell hematoxylin black from DAB black, or anything else. So at this point you are pretty much targeting “dark.”

That said, if you can ever classify the bad cells in any way, you can remove those cells via a script.

You may have some difficulty, though, as your mean nuclear intensity will vary with the percentage of the nucleus that is obscured. For example the lower right positive cell might only have a moderately dark nuclear OD because only half of it is obscured, which might put it in normal DAB OD range. My preferred option might be to use subcellular detections, based on the image. Set a sufficiently high OD and it should pick up areas within the cells over that threshold. Then classify any cells with sufficient subcellular detection area as “Other,” or remove them using the above script. There are many options for classification, so you have a lot of flexibility there.

Side note, the “true” in the createSelectAllObject selects it. So if you are running your cell detection on the entire image, you shouldn’t need the selectAnnotations line.

Do you want to detect positive cells at all based on DAB staining?

Note that you can either change the measurement used to classify cells as positive or negative so as to use the cytoplasm rather than the nucleus (towards the bottom of the Positive cell detection dialog) or run Cell detection instead (in which case no positive/negative classification occurs).

Hi, ‘Research associate’. Thanks for the advice. I will check the link you provided and try to implement on my tissue samples.

Hi, Petebankhead. Yes I am trying to detect DAB staining. Since the protein is expressed both in nucleus and cytoplasm, I used ‘Cell: DAB OD mean’

Bumjun

Hi Resaerch Associate. I tried to use subcellular detection to sort out ‘black’ staining.

But it does not seem to work at all on my image. Could you please check on it? I also attached the original image file in case you want to take a look at it.

Thanks!


Dox-11.tif (4.1 MB)

Below is my script;

setImageType('BRIGHTFIELD_H_DAB');
setColorDeconvolutionStains('{"Name" : "H-DAB -modified", "Stain 1" : "Hematoxylin", "Values 1" : "0.73951 0.66033 0.13071 ", "Stain 2" : "DAB", "Values 2" : "0.35764 0.66796 0.65263 ", "Background" : " 210 173 178 "}');
runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "backgroundRadius": 30.0,  "medianRadius": 2.0,  "sigma": 6.0,  "minArea": 10.0,  "maxArea": 1000.0,  "threshold": 0.1,  "maxBackground": 2.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansion": 5.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true,  "thresholdCompartment": "Cell: DAB OD mean",  "thresholdPositive1": 0.2,  "thresholdPositive2": 0.4,  "thresholdPositive3": 0.6,  "singleThreshold": true}');
runPlugin('qupath.imagej.detect.cells.SubcellularDetection', '{"detection[DAB]": 2.0,  "doSmoothing": false,  "splitByIntensity": false,  "splitByShape": false}');
1 Like

Scripts don’t copy well when pasted without correct formatting into a forum post, and I wasn’t going to spend the time to go through the script and edit all of the quotation marks, so please use the code delimiters in the future!
image
That said, it did give me a few hints. You are not using BioFormats to open your tiff files, so you have no pixel size values. Your Image tab probably looks like:
image
Subcellular detections require pixel sizes. One way to get around this is to force tif files to open with BioFormats in the Preferences:
image
That has its own set of problems, though, as without pixel sizes, it assigns something of its own.
image
So you would have to either put in “good” pixel values via ImageJ or a script… I think Pete made one somewhere but I forget where right now, or, multiply your values in the Cell Detection function by about 170 (or 170^2 for areas). They get huge. It looks like I posted something similar in your post on github.


runPlugin('qupath.imagej.detect.nuclei.WatershedCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "requestedPixelSizeMicrons": 170.0,  "backgroundRadiusMicrons": 0.0,  "medianRadiusMicrons": 0.0,  "sigmaMicrons": 1000.0,  "minAreaMicrons": 170000.0,  "maxAreaMicrons": 6.8E7,  "threshold": 0.15,  "maxBackground": 2.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansionMicrons": 800.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true}');
runPlugin('qupath.imagej.detect.cells.SubcellularDetection', '{"detection[DAB]": 1.5,  "doSmoothing": false,  "splitByIntensity": false,  "splitByShape": true,  "spotSizeMicrons": 30000.0,  "minSpotSizeMicrons": 0.5,  "maxSpotSizeMicrons": 2.0,  "includeClusters": true}');

Ah, and here there is a script to set pixel sizes. I have not tried it yet myself.

And now I have. That is so nice. I wish I had that around a few times a while back!
Further edit, it does not stay edited, so if you modify the metadata, you need to do it each time you reopen the image.

Have you tried detecting the artefactual deposits using the residual channel of the colour deconvolution?

Thank you so much Research Associate for your help. I did not realize the incorrect pixel value since it was not showing. But yeah I should be able to work on it now!

All the best :slight_smile:

That is a great idea. I will try it!

Bumjun

If you do try using the residual with subcellular detections, you may need to copy your color vectors and then change the image type to Brightfield Other.

Hi Research Associate.

Sorry for the a lot of question.

I found out that the internalized software for our microscope somehow distort the pixel value if we convert .zvi to .tif. However, pixel value was conserved once I saved it into .LSM and open with QuPath.


Since it does not automatically detect H-DAB, I manually added it through the script

setImageType('BRIGHTFIELD_H_DAB');

However, I cannot do color-deconvolution on this image. I get error message ‘No brightfield, RGB image selected’. Is there anyway to get through this?

On the other hand, I converted .LSM file to .tif using Image J. Since pixel value was conserved in LSM, I thought converting tif would work. However, interestingly, it did not pick up pixel value even if read with Bioformats as you can see.

And again, subcellular detection does not work. Could you explain why this happens and how to troubleshoot please? Sorry… it is very frustrating for me :frowning:
I cannot upload the first .LSM file, but uploading tif file here.DoxPan-L-14_(c1).tif (4.1 MB)

Hmm, I would start with the base file if possible. BioFormats handles CZI files, though for brightfield they would need to be 8bit (I think Zen defaults to 12 or 14 or something).

If you convert LSM to tif, I don’t think most programs attempt to copy over the metadata automatically. Zen certainly doesn’t seem to; it only keeps metadata (mostly) when converting between Zeiss file types (zvi,czi, lsm). However you get your tif, you would need to open any tif file in IJ and use Image->Properties to set it again before saving the file. Or better yet, write a script that does it for a whole folder!

I have occasionally had problems with the Zeiss brightfield images, but not the one you are specifically mentioning with generating the color vectors, unless the image was 14bit. But 14bit images and sets of color vectors that don’t add up are the only two times I have seen that message. And if you didn’t edit the color vectors yet, it shouldn’t be the latter.

Does it make a difference if you set the image type to H-DAB through the GUI? You should be able to double click on it.

If you can’t get the LSM files to work, it would probably be easiest to use the script in this post to set the pixel sizes in your tif images.

For anything more, I would need to see the original file, which you could host on a gmail account (google drive) if it won’t work on the forum.

Good morning Mike (if you are in Eastern Time zone). I was in Seminar whole day yesterday, so was not able to work on it.

Original CZI file is 16 bit I believe. I do not think I could convert 16 bit CZI to 8 bit CZI with ZEISS software we have. I will try to see if I can do it with more advanced ZEISS software in other lab today.

I am not sure what you meant by setting image type through GUI. But if I import .LSM file, I need to add script to be able to change it to H-DAB since QuPath does not recognize it. If I import .TIF file, though, I could select H-DAB from the ‘image type’ drop down option.

Regardless, it is more likely that I do not know how to properly use ‘subcellular detection’ option.

I just noticed that .TIF file converted from .LSM did maintain ‘correct’ pixel value. Pixel values just do not show up on the left handside menu somehow. Anyway. At least to have pixel size appear on the lefthand menu, I used following code you recommended;

setImageType('BRIGHTFIELD_H_DAB');
setColorDeconvolutionStains('{"Name" : "H-DAB default", "Stain 1" : "Hematoxylin", "Values 1" : "0.65111 0.70119 0.29049 ", "Stain 2" : "DAB", "Values 2" : "0.26917 0.56824 0.77759 ", "Background" : " 255 255 255 "}');
// Set the magnification & pixel size (be cautious!!!)
def metadata = getCurrentImageData().getServer().getOriginalMetadata()
metadata.magnification = 20
metadata.pixelWidthMicrons = 0.3225
metadata.pixelHeightMicrons = 0.3225

// If you want to trigger the 'Image' tab on the left to update, try setting a property to something different (and perhaps back again)
type = getCurrentImageData().getImageType()
setImageType(null)

// If you want to trigger the 'Image' tab on the left to update, try setting a property to something different (and perhaps back again)
type = getCurrentImageData().getImageType()
setImageType(null)

However pixel value did not change and there was an error message. Is it because .tif is not metadata? Then how should I change it? Sorry… I barely know coding…

ERROR: Error at line 6: Cannot set readonly property: pixelWidthMicrons for class: qupath.lib.images.servers.ImageServerMetadata

ERROR: Script error
    at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:2773)
    at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:3809)
    at org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:217)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecodeAdapter.java:496)
    at Script7.run(Script7.groovy:7)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:317)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:155)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:767)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:697)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:677)
    at qupath.lib.gui.scripting.DefaultScriptEditor$2.run(DefaultScriptEditor.java:1034)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

Regardless, again, even if pixel values are still ‘unknown’ on the left handside, scale bar on the lower left side confirms that pixel width and height is more or less same with ones from .LSM (0.3225um).

So I deleted ‘pixel value changing code’ and ran to detect cells first.

setImageType('BRIGHTFIELD_H_DAB');
setColorDeconvolutionStains('{"Name" : "H-DAB modified", "Stain 1" : "Hematoxylin", "Values 1" : "0.71582 0.68837 0.11728 ", "Stain 2" : "DAB", "Values 2" : "0.4204 0.76025 0.49527 ", "Background" : " 218 183 182 "}');
createSelectAllObject(true);
runPlugin('qupath.imagej.detect.cells.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD",  "backgroundRadius": 30.0,  "medianRadius": 2.0,  "sigma": 6.0,  "minArea": 10.0,  "maxArea": 2000.0,  "threshold": 0.1,  "maxBackground": 2.0,  "watershedPostProcess": true,  "excludeDAB": false,  "cellExpansion": 5.0,  "includeNuclei": true,  "smoothBoundaries": true,  "makeMeasurements": true,  "thresholdCompartment": "Cell: DAB OD mean",  "thresholdPositive1": 0.2,  "thresholdPositive2": 0.4,  "thresholdPositive3": 0.6,  "singleThreshold": true}');

QuPath would have not detected the cells if pixel value was incorrect at this step.

Then I drew a small rectangle where ‘black’ hematoxylin staining shows up, and clicked on ‘Run’ button on ‘subcellular detection’ which added following code on top of the above code;

runPlugin('qupath.imagej.detect.cells.SubcellularDetection', '{"detection[DAB]": 2.0,  "doSmoothing": false,  "splitByIntensity": false,  "splitByShape": true}');

However, it did not work.

I am thinking that maybe I am not running subcellular detection the way it has to be run?

Could you provide step by step guidance on running subcellular detection after cell detection?

The image I used is below.
DoxPan-L-14_(c1).tif (4.1 MB)

Thanks

Bumjun

I think Zen Lite has the function Change Pixel Type or something similar. It should work well from 16 to 8bit, though I have had problems with 14->8bit squishing as if the conversion was 16->8bit.

Interesting about the .lsm file. I have never tried that for brightfield images, so I am not sure about the problems you are running into there. I believe .lsm files were only intended for confocal microscopy, though, so that may be part of the issue. They may simply have the wrong metadata structure to support a brightfield image correctly.

In the end, the server needs to be able to read that metadata. Your subcellular detection window indicates that you don’t have access to that information, as half of the dialog box is missing. If you scroll alll the way up to my example of Subcellular spot detection, you can see what the full dialog box looks like. The function will only work if all of the fields are active and filled.
image

Again, not sure what is happening with the .lsm file, but the .tif file should work with the script to add metadata, and you should be able to run the subcellular detection after that.

*Edit: Yep, definitely intended for confocal data.

Thanks for the quick response Mike.

All the images above are worked on ‘8bit tif’ file converted from ‘8bit lsm’ file.

Anyhow, I will definitely find Zen Lite and convert 16bit .czi to 8 bit czi!

So do you not think I missed something to do before running ‘subcellular detection’ ?

I just want to make sure.

Thanks!

You should be fine once you have the pixel metadata in the image tab. You can either selectAnnotations or selectDetections before running it in a script.

Hi Mark,

Could you share the script you used to change pixel size?
I used below script on the link. I just changed magnification and pixel values;

setImageType('BRIGHTFIELD_H_DAB');
setColorDeconvolutionStains('{"Name" : "H-DAB default", "Stain 1" : "Hematoxylin", "Values 1" : "0.74945 0.65763 0.07647 ", "Stain 2" : "DAB", "Values 2" : "0.31563 0.75822 0.5705 ", "Background" : " 208 162 153 "}');
// Set the magnification & pixel size (be cautious!!!)
def metadata = getCurrentImageData().getServer().getOriginalMetadata()
metadata.magnification = 20
metadata.pixelWidthMicrons = 0.3225
metadata.pixelHeightMicrons = 0.3225

// If you want to trigger the 'Image' tab on the left to update, try setting a property to something different (and perhaps back again)
type = getCurrentImageData().getImageType()
setImageType(null)
setImageType(type)

However, it keeps generating error below, and pixel values do not change;

ERROR: Error at line 6: Cannot set readonly property: pixelWidthMicrons for class: qupath.lib.images.servers.ImageServerMetadata

ERROR: Script error
    at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:2773)
    at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:3809)
    at org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:217)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecodeAdapter.java:496)
    at Script1.run(Script1.groovy:7)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:317)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:155)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:767)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:697)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:677)
    at qupath.lib.gui.scripting.DefaultScriptEditor$2.run(DefaultScriptEditor.java:1034)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

Could you check what is the problem?

I used the attached image. Thanks!

DoxPan-L-34-Image Export-01.tif (5.1 MB)