7
$\begingroup$

Following in the footsteps of this blog post: http://blog.wolfram.com/2011/09/09/building-a-microscopy-application-in-mathematica/, I took a microscope picture of some particles on a surface, and attempted to write a script to identify and count them automatically. However, I've been having a difficult time seperating closely spaced particles, and the MaxDetect method in the blog post seems to not work too well on this data set.

Why I find this failure surprising: Picking out the dark spots by hand is fairly trivial, and I imagine that if one were to 3D print a surface where darker pixel values represent depressions, the problem could be solved by rolling some marbles around and seeing where they stick.

Here's the picture / data:

enter image description here

The first thing I did was then to binarize (with the Kittler-Illingworth minimum error thresholding method) and color negate the data using the command:

ColorNegate[Binarize[image, Method -> "MinimumError"]]

Which yielded:

enter image description here

I then attempted to apply ImageAdjust[DistanceTransform[...]] to the image, yielding:

enter image description here

Which I then applied MaxDetect to, yielding:

enter image description here

So, interestingly, there seems to be a ring of local maxima around each particle. Even if you use Erosion extensively, there still seem to be significant clusters of local maxima corresponding to each particle. As such, I've been having a very difficult time using the MaxDetect method in the aforementioned blog post to individually identify and count each particle.

Let's say that the precise centroid coordinates of each particle are not important to know exactly. Is there a "nice" method of assigning a distinct morphological component or coordinate to each particle without a lot of hands-on tuning? Where perhaps we can end up with something like this (where I've hand-identified particles)?

enter image description here

$\endgroup$
1
  • $\begingroup$ There is a lot of noise in your image. Apply a GaussianFilter[#, 5] & before doing anything, and everything becomes better. After that, binarizing with Method -> "Cluster" or Method -> "Entropy" works much better for your image. $\endgroup$
    – user484
    Aug 8, 2013 at 16:34

4 Answers 4

5
$\begingroup$

Perhaps using ImageCorrelate can be useful. Lets call the original image img0. You can use the drawing tools (or whatever other method you would like) to grab a kernel. This is the one I chose:

ker = enter image description here

In my case, I obtained this kernel by simply using the Image Assistant and cropping out one of the spots from img0.

i = ImageCorrelate[img0, ker, NormalizedSquaredEuclideanDistance];

From here use MorphologicalComponents and ComponentMeasurements to grab the central coordinates.

dots = Point[#[[2]]] & /@ ComponentMeasurements[
    MorphologicalComponents[ColorNegate[Binarize[i, 0.22]]], 
    "Centroid"];

Here is the result

Show[img0, Graphics[{Yellow, dots}]]

enter image description here

It isn't perfect (is misses the triplet and has a "phantom" dot) but for very little tweaking it does a decent job.

$\endgroup$
4
  • $\begingroup$ Is your kernel the image itself? $\endgroup$
    – rcollyer
    Aug 8, 2013 at 14:08
  • $\begingroup$ @rcollyer yup, I just used the Image Assistant to crop out one of the spots from img0. $\endgroup$
    – chuy
    Aug 8, 2013 at 14:11
  • $\begingroup$ I see, its a spot. Would you mind elaborating on that "point" in your answer? (Yes, the pun was intended.) $\endgroup$
    – rcollyer
    Aug 8, 2013 at 14:18
  • $\begingroup$ @chuy Would it help if the dots had coloration w.r.t the background? $\endgroup$ Aug 8, 2013 at 20:03
6
$\begingroup$

The image file goes by the name of "dots" in the following.

This gives the basic layout of the components:

c = DeleteSmallComponents@ColorNegate[Binarize[dots, Method -> "MinimumError"]];
m = MorphologicalComponents[c] // Colorize 

colors

centroids = ComponentMeasurements[ m , "Centroid"];
radii = ComponentMeasurements[ m , "EquivalentDiskRadius"][[All, 2]];
componentLabels = (# /. {Rule[n_, b_] :> Text[n, b]}) & /@ centroids;
Graphics[componentLabels, Axes -> True, AxesOrigin -> {0, 0}]
points = componentLabels[[All, 2]];
areas = MapThread[Text, {Round@radii, points}];
Graphics[areas, Axes -> True, AxesOrigin -> {0, 0}]
estimates = ({Which[#[[1]] < 15, Blue, #[[1]] < 20, Red, True, Green],
        Point[#[[2]]]} & /@ areas) /. {Blue -> 
     Sequence[Blue, PointSize[.01]], 
    Red -> Sequence[Red, PointSize[.02]], 
    Green -> Sequence[Green, PointSize[0.03]]};
Graphics[{estimates}, Axes -> True, AxesOrigin -> {0, 0}]

This shows where the components lie:

components

The sizes of the components:

sizes

And the best guesses for singles (blue), pairs (red), and triples (green):

illust

$\endgroup$
4
  • $\begingroup$ +1 Clever idea I think - to work out what 'should' be there given the size of the blob... $\endgroup$
    – cormullion
    Aug 8, 2013 at 15:22
  • $\begingroup$ @cormullion "AuthalicRadius" gives a very similar ordering for the blob sizes, but I didn't include it because I don't yet understand how it works. $\endgroup$
    – DavidC
    Aug 8, 2013 at 15:34
  • $\begingroup$ Very nice, should the variable "weights" be referring to the variable "areas" instead? $\endgroup$
    – DrBubbles
    May 11, 2017 at 20:44
  • $\begingroup$ @DrBubbles, Good catch! I have now replaced weights with areas. Thanks. $\endgroup$
    – DavidC
    May 12, 2017 at 0:06
2
$\begingroup$

I think you can do better in the binarization, which will make all subsequent tasks easier. I tried:

Manipulate[Binarize[img, t], {t, 0, 1}]

on your original image and found t=0.4 to be a good value. Calling this binarized image binImg you can get better segmentation using

MorphologicalComponents[ColorNegate[binImg]] // Colorize

enter image description here

This captures the triplet of closely spaced dots in the upper left as well as the two doubles in the bottom center. It may look as if the ones in the upper right have been lost, but they are just small (you can verify this by dilating the whole thing, when they become more apparent).

$\endgroup$
0
$\begingroup$

I tried an ImageCorrelate approach as well, like chuy, and it didn't work out too badly. I worked on a section of the image, and used a simple DiskMatrix:

cell = DiskMatrix[15] ;
s2 = ImageAdjust[ImageCorrelate[section, cell]]

imagecorrelate

Then MaxDetect finds the center points:

s3 = ImageAdjust[MaxDetect[s2]];
s4 = ImageMultiply[s3, Binarize[section]]

max detect

centers = ComponentMeasurements[s4, "Centroid"];
Show[
 section, 
 Graphics[{Red, 
    Opacity[.5], 
    Disk[#, 8] & /@ centers[[All, 2]]}]
 ]

component measurements

You could probably eliminate some of the obvious doubled ones - but I forget how to do that.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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