Message Boards Message Boards

0
|
5410 Views
|
7 Replies
|
6 Total Likes
View groups...
Share
Share this post:

Contradictory results of CaliperWidth in ComponentMeasurements

Hello all,

I am trying to get the thickness and length of bacteria in an image, and wanted to exclude some ill-shaped ones. For that, my plan is using SelectComponents and choosing bacteria with certain properties, one of them CaliperWidth.

However, sometimes the result seems far off. Maybe I misunderstand what CaliperWidth is measuring. I though it finds the smallest width that can be obtained when fitting the whole shape into a caliper. Shouldn't then the caliper width only change slightly when the shape rotates? But the differences are quite large.

I am aware that the rotation will generate some gray pixels at the border between black and white, and the caliper width determination requires some kind of binarization threshold. But this cannot explain the large differences.

Anyway, here is an example:

data = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, {0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 
    0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 
    0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 
    0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 
    0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0}, {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 
    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 
    1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0}};
img = Image[data];
t = Table[
   ComponentMeasurements[ImageRotate[img, x], "CaliperWidth"], {x, 0, 
    Pi, Pi/100}];
ListPlot[t[[All, 1, 2]]]

The values range from 15.5 to 24.2. Does anyone have an explanation?

Thanks for your help,

Max

7 Replies

The interesting pattern @Jim Baldwin observed has most likely to do with the number of gray levels induced by aliasing; if this number of gray levels is plotted against rotation this becomes obvious:

xrimg = Table[{x, ImageRotate[img, x]}, {x, 0, Pi, Pi/100}];
ListLinePlot[{#1, (Length@*DeleteDuplicates@*Flatten@*ImageData)[#2]} & @@@ xrimg, PlotRange -> All]

enter image description here

So I guess one should work here with binarized images only.

But my main point here is that I was in a way wrong in my post above: This method is not suitable to calculate caliper width! One can easily see this in the following example, where the diameter of the circle by no means has anything to do with caliper:

enter image description here

But nevertheless this method can be useful if the true shape is used instead of the convex hull, and if one no longer concentrates on "caliper" but more on a term like "belly". That gives me my modified routine:

bellyWidth[img_Image] := Module[{borderPts, order, baktMesh},
  borderPts = ImageValuePositions[Thinning[EdgeDetect[Binarize[img]]], 1];
  order = First@FindCurvePath[borderPts];
  baktMesh = MeshRegion[Polygon@borderPts[[order]]];
  -2. NMinValue[SignedRegionDistance[baktMesh, {\[FormalX], \[FormalY]}], {\[FormalX], \[FormalY]}  \[Element] baktMesh]]

Lets check:

rimg = Table[ImageRotate[img, x], {x, 0, Pi, Pi/25}];
{#, bellyWidth[#]} & /@ rimg

enter image description here

The discrepancies now are (far) below one pixel.

POSTED BY: Henrik Schachner

I was thinking about a solution to that nice problem - and here is my approach; I basically create the ConvexHullMesh of the bacteria shape and minimize its SignedRegionDistance - the result is exactly what is wanted:

borderPts = ImageValuePositions[Thinning[EdgeDetect[img]], 1];
baktMesh = ConvexHullMesh[borderPts];
{rad, posRules} = NMinimize[SignedRegionDistance[baktMesh, {\[FormalX], \[FormalY]}], {\[FormalX], \[FormalY]} \[Element] baktMesh];
Show[baktMesh, Graphics[{Red, Circle[{\[FormalX], \[FormalY]} /. posRules, Abs[rad]]}]]

enter image description here

So I take the diameter of the resulting circle as (at least a good approximation of) the caliper value.

One can put this into a routine

caliperWidth[img_Image] := Module[{baktMesh},
  baktMesh = ConvexHullMesh[ImageValuePositions[Thinning[EdgeDetect[img]], 1]];
  -2 NMinValue[SignedRegionDistance[baktMesh, {\[FormalX], \[FormalY]}], {\[FormalX], \[FormalY]}  \[Element] baktMesh] ]

and check for different rotations of the original image:

rimg = Table[ImageRotate[img, x], {x, 0, Pi, Pi/10}];
{#, caliperWidth[#]} & /@ rimg

enter image description here

The results seem to be quite reasonable.

POSTED BY: Henrik Schachner
Posted 3 years ago

That really reduces the variability. I wonder if the variability/bias could be reduced more by taking an average of a set of arbitrary rotations? I say this because there does seem to be a pattern:

rimg = Table[{x, ImageRotate[img, x]}, {x, 0, Pi, Pi/100}];
t = {#[[1]], caliperWidth[#[[2]]]} & /@ rimg;
ListPlot[t, Joined -> True]

Rotations and caliperWidth

POSTED BY: Jim Baldwin

Hello Sander and Henrik,

I also think it looks like a bug. After adding the two additional lines to Henrik's code, I find it is about 13.7 px, but the value from CaliperWidth is 15.5 px.

Manipulate[
 Labeled[Show[img, 
   Graphics[{Red, Line[pts], 
     InfiniteLine[{pts[[1]], 
       pts[[1]] + {pts[[1, 2]] - pts[[2, 2]], 
         pts[[2, 1]] - pts[[1, 1]]}}], 
     InfiniteLine[{pts[[2]], 
       pts[[2]] + {pts[[1, 2]] - pts[[2, 2]], 
         pts[[2, 1]] - pts[[1, 1]]}}]}], ImageSize -> Large], 
  EuclideanDistance @@ pts], {{pts, {{20, 20}, {10, 10}}}, Locator}]

Thank you for submitting it.

Best,

Max

I suspect it is not just the differences; if I am not mistaken, the results ("CaliperWidth" == smallest diameter of the convex hull) seem to be wrong altogether. You can see this simply like so:

Manipulate[Labeled[Show[img, Graphics[{Red, Line[pts]}], ImageSize -> Large], 
  EuclideanDistance @@ pts], {{pts, {{20, 20}, {10, 10}}}, Locator}]
POSTED BY: Henrik Schachner

You would need two parallel lines at the end of your red lines. such that everything is between 'callipers'.

POSTED BY: Sander Huisman

Indeed, seems to be a bug. A little bit difference is expected due to grayscales when you rotate and so on. But should be ~2px max. "CaliperLength" seems to work properly.

I will submit it as a bug.

POSTED BY: Sander Huisman
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract