# Including the actual length scale of the image in processing microscopic images

Following the wonderful methodologies provided in this post, I have learned how to process microscopic images of the types shown below in order to analyze them. When it comes to analysing the size distribution of the particles, the images always have a scale indicator, which give the actual scale of the image. By default, when we measure the size distribution e.g. using ComponentMeasurements, everything is measured in pixels. My question is:

• Is there a way to incorporate/detect in Mathematica the actual given length-scale of the image? (in example below $$500 nm$$ for the shown spacing), that is, to convert pixels to $$nm$$ (or $$nm^2$$ for area). Example given below (source):

To detect the particles, I've adopted the following approach learned from Niki Estner's previous answer:

img = Import["https://i.stack.imgur.com/ryzmV.jpg"]
ridges = RidgeFilter[-img, 1];
distRidges =
DistanceTransform@ColorNegate@MorphologicalBinarize[ridges];
distMax = MaxDetect[distRidges, 1];
morph = WatershedComponents[ridges, distMax, Method -> "Basins"];
comp = ComponentMeasurements[{img, morph}, {"Centroid", "Neighbors"}];

edges = Dilation[EdgeDetect[Image[morph], 1, .001], 0.5];
edgeOverlay =
Show[img, SetAlphaChannel[ColorReplace[edges, White -> Red], edges]]


yielding the following detection result:

and to extract the particle sizes in order to estimate the size distribution histogram, I've used the "Area" property in ComponentMeasurements as follows:

sizesls = {};
areaInPixels = ComponentMeasurements[morph, {"Area"}];
For[i = 1, i <= Length[areaInPixels], i++,
eltmp = areaInPixels[[i]][[2]];
AppendTo[sizesls, eltmp[[1]]];
];
Histogram[sizels]


but the size distribution is obtained in pixels, as opposed to using the actual scale of the image in $$nm$$ as indicated in the original image. From the source of the image, the indicated expected mean particle size is $$60 nm.$$

• I think it is necessary to perform a verification on whatever algorithm you use. This could be done by taking a screenshot of, say, the lower left quadrant of the picture, printing it out at maximum size (maximally filling an 8.5" x 11" sheet of paper), and manually determining the size of each particle. [Say, outline each in pen, measure the min & max dia. of each with a ruler, then average.] Then compare that with size distributions found by the various algorithms, applied to that same screenshot (or, better, you could do this for all four quadrants). Have you done this? – theorist Aug 18 '19 at 19:35
• @theorist Hi, to verify I've overlaid the extracted portion on top of the original image to make sure they overlap perfectly. And it has been a successful test, is that what you meant? – user52181 Aug 21 '19 at 16:15
• Hi @user929304. No, I meant that you need some completely independent verification that your algorithm is accurate, & one approach is to manually (by hand) measure the sizes, and compare those measurements to MMA's. To accurately measure the sizes manually, you need to enlarge the picture as much as reasonably possible. Ideally, you'd take the entire original image (w/o the MMA overlay), and print it on a poster printer (@~24" x 36"). Then you (or a grad student!) would manually measure each particle. And (unlike most algorithms) you have the judgement not to be fooled by overlaps, holes, etc. – theorist Aug 21 '19 at 21:20
• I.e., especially if you plan to use this in a publication, you need some sort of verification that is completely independent of the algorithm. The fact that different algorithms give different results tells you that you need some independent way of determining which, if any, of the algorithms give a result that corresponds to reality. Any good reviewer will request this. – theorist Aug 22 '19 at 4:55

First of all, I would exclude the scale from the "particle search algorithm", like this:

imgWithScale = Import["https://i.stack.imgur.com/ryzmV.jpg"];
img = ImageTake[imgWithScale, 280]


(followed by the same steps you used above).

Then I would extract the nm/pixel scale:

scaleArea = ImageTake[imgWithScale, {-20, -15}]
whitePixels = PixelValuePositions[Binarize[scaleArea], 1];
{left, right} = MinMax[whitePixels[[All, 1]]];
nmPerPx = 500./(right - left)


(Note that I'm using Part array access ([[All, ...) instead For loops to extract specific elements from a nested list. This is almost always shorter, faster and more readable. Never use For in Mathematica)

Next, I'm not sure the Area is the best measure of particle size. Your particles are mostly ciruclar, so we can try a few others:

measurements = {"EquivalentDiskRadius", "MeanCentroidDistance",
(* MeanIntensity is dimensionless, the other ones are lengths,
i.e scaled by nmPerPx^1 *)
scale = nmPerPx^{1, 1, 1, 1, 1, 0};
(* only count particles that aren't clipped by an image border, with
condition #AdjacentBorderCount == 0 & *)
comp = ComponentMeasurements[{img, morph}, measurements, #AdjacentBorderCount == 0 &];
Multicolumn[
Histogram[#1, PlotLabel -> #2, ImageSize -> 300] & , {scale*
Transpose[comp[[All, 2]]], measurements}]]


(I've used EquivalentDiskRadius, the radius of a disk with the same area, so it's easier to compare with the other lengths.)

It seems that Length and CaliperLength are much better estimates for the particle size, because they are less dependent on occlusion.

We can also easily see that there's a correlation between intensity (i.e. depth) and size:

compare = {4, 6};
ListPlot[comp[[All, 2, compare]],
AxesLabel -> measurements[[compare]]]


If you want to explore the correlation further, you can use LocatorPane and Dynamic to see which component is where interactively:

nfIdx = Nearest[comp[[All, 2, compare]] -> comp[[All, 1]]];
nf = Nearest[comp[[All, 2, compare]]];
pt = comp[[1, 2, compare]];

Row[{
LocatorPane[Dynamic[pt],
Dynamic[ListPlot[comp[[All, 2, compare]],
AxesLabel -> measurements[[compare]], GridLines -> nf[pt],
ImageSize -> 500]]],
Dynamic[
HighlightImage[img,
ColorNegate[Binarize[Image[(morph - nfIdx[pt][[1]])^2], 0]],
ImageSize -> 400], TrackedSymbols :> {pt}]}]


• Sorry, forgot to copy scaleArea... – Niki Estner Aug 17 '19 at 12:30
• Very nice! Your last interactive plot seems to include holes as well as particles. Are these holes included in your CaliperLength histogram and, if so, might they be eliminated by filtering out shapes showing concavity? – theorist Aug 18 '19 at 19:29

You could extract the conversion factor in a semi-automated way.

imgcrop = ImageTake[img, {-20, -15}, {-150, -1}]


bars = MorphologicalComponents[imgcrop, .1];
Image@bars


The x-coordinates of the centroids of the bars are then

xcoords = ComponentMeasurements[bars, "Centroid"][[All, 2, 1]]
(* {0.5, 16., 31.8, 47.0556, 63., 79., 95., 110., 126., 142.} *)


And the mean difference between x-coordinates of successive bars is

Mean[Differences[xcoords]]
(* 15.7222 *)


The conversion factor in nanometers/pixel is (assuming I'm reading the scale right and each space between the ticks is 500 nm)

nmPerPixel = 500/Mean[Differences[xcoords]]
(* 31.8021 *)


Then, from your image analysis, the areas in pixels are

areaPixels =
Flatten[ComponentMeasurements[morph, {"Area"}][[All, 2]]];


And the areas converted to square nanometers

areaSqNanometers = nmPerPixel^2 areaPixels;
Histogram[areaSqNanometers]


First you need to find scaling coefficient from your *.jpg by using Coordinate Tools. Place cursor on first tick of scale, horizontal coordinate is 234, last tick corresponds to 391. So, scaling coefficient is hscale=500./(391-234). Assumimg that area has a shape of circle, Histogram command can be simplified to

 areaInPixels[[All, 2]] // Flatten // hscale Sqrt[4 #/\[Pi]] & // Histogram


which gives this distribution of sizes. Mean size is about 60 nm.