0
$\begingroup$

I am trying to analyze images of cells that are dyed a certain colour. My objectives are to:

  1. identify individual cells
  2. measure signal intensity (pixels) within the regions that are identified as the cell (cells have different level of dye staining).

There is a Mathematica blog post that got me started- "how to count cells, annihilate sailboats and warp the mona lisa"

However, this example measures the average pixel size of the cells from the binarized/processed image, not the intensity of the pixels of each cell in the original, non binarized image.

So, what I am trying to do and asking help for is how to process the original image to identify cells and then use that information to map cells back on the original image, and count signal intensity in the defined areas on that original image (that has the pixel intensity information).

One issue is that there is no counter stain, so weakly stained or negative cells are difficult to detect and measure. For that, I have modified the blog code a little, which works well. Here's what I have so far-

i =img;
ibin = Binarize[i]
idil = Dilation[ibin, 5]
idsc = DeleteSmallComponents[idil, 250]

This gets me a binarized image with all cells (including negative and weak signal) as somewhat circular.

Next, I use this code to detect edges and distinguish each cell:

 distT = DistanceTransform[idsc, Padding -> 0];
    marker = MaxDetect[ImageAdjust[distT], 0.05];
    w = WatershedComponents[GradientFilter[idsc, 3], marker, 
      Method -> "Rainfall"]
    Colorize[w]

This gets me a multicolor, binarized map that approximates each cell.

The next step is where I need help. The code from the blog is:

cells = SelectComponents[w, "Count", 100 < # < 450 &];
measures = 
  ComponentMeasurements[
   cells, {"Centroid", "EquivalentDiskRadius", "Label"}];
Show[i,
 Graphics[{Blue, Circle @@ # & /@ (measures[[All, 2, 1 ;; 2]]),
   MapThread[Text, {measures[[All, 2, 3]],
     measures[[All, 2, 1]]}]}]]

Histogram[ComponentMeasurements[cells, "Count"][[All, 2]],
 250, AxesLabel -> {"pixels"},
 PlotLabel -> "Histogram of cell's pixel counts"]

This displays blue circles around the cells in the original image and displays a histogram of pixel size. So I know that it is identifying cells correctly.

However, I want it use the coordinates of the identified cells from the processed image, but analyze the pixel intensity on the original image with those coordinates, and then display that information as a histogram.

Thanks in advance.

-----------------update Oct 16---------

@ nikie

Thank you very much for your answer. I'm still a little bit stuck on the end. Here is a small test image and the code that I have so far.

test image

i = img;
ibin = Binarize[i];
icn = ColorNegate[ibin];
imax = MaxFilter[icn, 2];
idsc2 = DeleteSmallComponents[imax, 100];
distT = DistanceTransform[idsc2, Padding -> 0];
marker = MaxDetect[ImageAdjust[distT], 0.06];
w = WatershedComponents[GradientFilter[idsc2, 4], marker, 
  Method -> "Rainfall"]
  Colorize[w]

this does a decent job on selecting cells (1 error visible).

cells = SelectComponents[w, "Count", 100 < # < 500 &];
components = ComponentMeasurements[{cells, i}, {"Mean"}]

I am having trouble with
1. mapping the selected cells back on the original image and displaying that image (with circles) to check and verify the selection is what I want it to be.
2. I'm confused as to why the mean values all seem very similar, when the original image suggests that they should vary a bit more
3. understanding the "[[All, 2, 1 ;; 2]])" code, for example in the blog post

thanks so much

-------------- Oct 17 update-----------

@nikie,

thanks for all the explanations, you are very helpful!

I've ran into one issue however. It appears that the intensity values measured do not reflect how the image looks by eye, possibly due to the differences in area? If you take my watershed image (it's now "iw") and do the last steps like this -

cells = SelectComponents[iw, "Count", 200 < # < 700 &];
components = ComponentMeasurements[{cells, i}, 
{"Centroid", "EquivalentDiskRadius", "Label", "MeanIntensity", "Area"}]   

Show[i, Graphics[{Blue,Circle @@ # & /@ (components[[All, 2, 1 ;; 2]]),
MapThread[Text, {components[[All, 2, 3 ;; 5]], components[[All, 2, 1]]}]}]]

You can see that the visible intensity by eye doesn't match up with what it's measuring. Presumably because of the differences in white space around the defined circle? For example, the darkest cells (#6 when I run it) measures a meanintesity of 0.63, while the neighboring cell right above it measures at 0.78. These two cells have a significant difference in area, but if you look at some of the other cells that are more similar in area, they also have measurements that are opposite of what you'd expect when looking at it by eye. Am I missing/overlooking something?

image with area and mean intensity labels

Thanks for the help on manipulating lists. I understand that in the code above now. But when I try to make the histogram I get errors and cannot figure out what I am doing wrong-

Histogram[ComponentMeasurements[cells, "Count"][[All, 2; 4]], 500, 
 AxesLabel -> {"Mean Intensity"}, 
 PlotLabel -> "Histogram of cell signal"]

thank you for all your help, I am learning a lot!

$\endgroup$
  • $\begingroup$ You probably want to use the ComponentMeasurements overload that takes a label matrix: ComponentMeasurements[{MorphologicalComponents[cells], img}, {"Mean" ... $\endgroup$ – Niki Estner Oct 12 '16 at 18:35
  • $\begingroup$ @nikie thanks, I have been trying out different ways but I can't seem to tie it all together. I keep getting errors that "a valid image is required for computing mean" or that the labeled matrix is not valid. Could you show me an example for how to integrate it into the code above? sorry, I'm new and learning as I go. many thanks $\endgroup$ – Nathur Oct 14 '16 at 21:23
  • $\begingroup$ Got too long for a comment, so I posted my reply as an answer. If you're still stuck, please post an input image you use, otherwise I can't reproduce what your code does. $\endgroup$ – Niki Estner Oct 15 '16 at 7:43
  • $\begingroup$ a big thanks for all your help! I've ran into another question and so posted an update. $\endgroup$ – Nathur Oct 17 '16 at 15:46
  • $\begingroup$ Shouldn't that be [[All, 2, 4]] in the last block? Semicolon doesn't make sense here $\endgroup$ – Niki Estner Oct 17 '16 at 16:27
2
$\begingroup$

You didn't post any source image, so I took one from the ComponentMeasurements documentation:

img = Import["https://i.stack.imgur.com/xkQbv.png"]

enter image description here

Assuming you have gotten a label mask somehow (e.g. from WatershedComponents or MorphologicalComponents - this would be w in your code):

labels = MorphologicalComponents[Binarize[img]];

you can just use:

components = 
 ComponentMeasurements[{labels, img}, {"Mean"}]

{1 -> {{0.561812, 0.645969, 0.469243}}, 2 -> {{0.497716, 0.590507, 0.449912}}, 3 -> {{0.659482, 0.738556, 0.629148}}, 4 -> {{0.489471, 0.561901, 0.431764}}, 5 -> {{0.526972, 0.594479, 0.474589}}, 6 -> {{0.538431, 0.583034, 0.423811}}, 7 -> {{0.555199, 0.603462, 0.434888}}}

to get the mean RGB values for each component.

RGBColor /@ components[[All, 2, 1]]

enter image description here

The syntax is ComponentMeasurements[{labels, img}, ..., where labels is a 2d array (not an image) or label indices, and img is an image with the same dimensions. Other measurements that work on the images RGB values include "Data" (list of all RGB pixel values in each component), "Min", "Max", "Median", "StandardDeviation"

Add:

  1. mapping the selected cells back on the original image and displaying that image (with circles) to check and verify the selection is what I want it to be.

Generally speaking, you so this by using multiple measurements, like this:

components = 
 ComponentMeasurements[{cells, i}, {"Mean", "BoundingDiskCenter", 
   "BoundingDiskRadius"}]

Now the result not only contains the RGB mean of each component, but also the center and radius of the smallest bounding circle.

  1. I'm confused as to why the mean values all seem very similar, when the original image suggests that they should vary a bit more

If you want to check, you can get the mean another way. The Mask measurement returns a binary matrix for each component, with 1s at the component position and 0s for the background:

components = 
  ComponentMeasurements[{cells, i}, {"Mean", "BoundingDiskCenter", 
    "BoundingDiskRadius", "Mask"}];

So, e.g. the mask of the first component looks like this:

Image[components[[1, 2, 4]]]

enter image description here

You can now use Pick to pick the color values of the source image, where the mask is 1:

Mean[Flatten[Pick[ImageData[img], components[[1, 2, 4]], 1], 1]]

Which returns exactly the same mean RGB triplet as ComponentMeasurements.

  1. understanding the "[[All, 2, 1 ;; 2]])" code, for example in the blog post

It makes sense when you look at the components array: This contains a list of rules (you want All of these), each rule contains an index and the component data (you want the second item, the component data, so 2), and the component data contains stuff like the RGB mean, centroid, etc. - 1;;2 gives the first two measurements.

Have a look at https://reference.wolfram.com/language/tutorial/ManipulatingListsByTheirIndices.html if this is still unclear

Also related: https://mathematica.stackexchange.com/a/128380/242

$\endgroup$
  • $\begingroup$ thanks a lot! i just updated my question with a test image, I am still a bit stuck on the end. $\endgroup$ – Nathur Oct 16 '16 at 17:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.