@@ -348,14 +348,28 @@ plt.xlim(0, 1.0)
348
348
349
349
![ ] ( fig/maize-root-cluster-histogram.png ) {alt='Grayscale histogram of the maize root image'}
350
350
351
- The histogram has a significant peak around 0.2, and a second,
352
- smaller peak very near 1.0.
351
+ The histogram has a significant peak around 0.2 and then a broader "hill" around 0.6 followed by a
352
+ smaller peak near 1.0. Looking at the grayscale image, we can identify the peak at 0.2 with the
353
+ background and the broader peak with the foreground.
353
354
Thus, this image is a good candidate for thresholding with Otsu's method.
354
355
The mathematical details of how this works are complicated (see
355
356
[ the scikit-image documentation] ( https://scikit-image.org/docs/dev/api/skimage.filters.html#threshold-otsu )
356
357
if you are interested),
357
- but the outcome is that Otsu's method finds a threshold value between
358
- the two peaks of a grayscale histogram.
358
+ but the outcome is that Otsu's method finds a threshold value between the two peaks of a grayscale
359
+ histogram which might correspond well to the foreground and background depending on the data and
360
+ application.
361
+
362
+ :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: instructor
363
+
364
+ The histogram of the maize root image may prompt questions from learners about the interpretation
365
+ of the peaks and the broader region around 0.6. The focus here is on the separation of background
366
+ and foreground pixel values. We note that Otsu's method does not work well
367
+ for the image with the shapes used earlier in this episode, as the foreground pixel values are more
368
+ distributed. These examples could be augmented with a discussion of unimodal, bimodal, and multimodal
369
+ histograms. While these points can lead to fruitful considerations, the text in this episode attempts
370
+ to reduce cognitive load and deliberately simplifies the discussion.
371
+
372
+ ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
359
373
360
374
The ` ski.filters.threshold_otsu() ` function can be used to determine
361
375
the threshold automatically via Otsu's method.
@@ -463,10 +477,10 @@ def measure_root_mass(filename, sigma=1.0):
463
477
binary_mask = blurred_image > t
464
478
465
479
# determine root mass ratio
466
- rootPixels = np.count_nonzero(binary_mask)
480
+ root_pixels = np.count_nonzero(binary_mask)
467
481
w = binary_mask.shape[1 ]
468
482
h = binary_mask.shape[0 ]
469
- density = rootPixels / (w * h)
483
+ density = root_pixels / (w * h)
470
484
471
485
return density
472
486
```
@@ -613,9 +627,8 @@ and label from the image before applying Otsu's method.
613
627
## Solution
614
628
615
629
We can apply a simple binary thresholding with a threshold
616
- ` t=0.95 ` to remove the label and circle from the image. We use the
617
- binary mask to set the pixels in the blurred image to zero
618
- (black).
630
+ ` t=0.95 ` to remove the label and circle from the image. We can then use the
631
+ binary mask to calculate the Otsu threshold without the pixels from the label and circle.
619
632
620
633
``` python
621
634
def enhanced_root_mass (filename , sigma ):
@@ -628,21 +641,22 @@ def enhanced_root_mass(filename, sigma):
628
641
629
642
# perform binary thresholding to mask the white label and circle
630
643
binary_mask = blurred_image < 0.95
631
- # use the mask to remove the circle and label from the blurred image
632
- blurred_image[ ~ binary_mask] = 0
633
-
634
- # perform automatic thresholding to produce a binary image
635
- t = ski.filters.threshold_otsu(blurred_image)
636
- binary_mask = blurred_image > t
644
+
645
+ # perform automatic thresholding using only the pixels with value True in the binary mask
646
+ t = ski.filters.threshold_otsu(blurred_image[binary_mask])
647
+
648
+ # update binary mask to identify pixels which are both less than 0.95 and greater than t
649
+ binary_mask = ( blurred_image < 0.95 ) & (blurred_image > t)
637
650
638
651
# determine root mass ratio
639
- rootPixels = np.count_nonzero(binary_mask)
652
+ root_pixels = np.count_nonzero(binary_mask)
640
653
w = binary_mask.shape[1 ]
641
654
h = binary_mask.shape[0 ]
642
- density = rootPixels / (w * h)
655
+ density = root_pixels / (w * h)
643
656
644
657
return density
645
658
659
+
646
660
all_files = glob.glob(" data/trial-*.jpg" )
647
661
for filename in all_files:
648
662
density = enhanced_root_mass(filename = filename, sigma = 1.5 )
@@ -654,11 +668,26 @@ The output of the improved program does illustrate that the white circles
654
668
and labels were skewing our root mass ratios:
655
669
656
670
``` output
657
- data/trial-016.jpg,0.045935837765957444
658
- data/trial-020.jpg,0.058800033244680854
659
- data/trial-216.jpg,0.13705003324468085
660
- data/trial-293.jpg,0.13164461436170213
671
+ data/trial-016.jpg,0.046250166223404256
672
+ data/trial-020.jpg,0.05886968085106383
673
+ data/trial-216.jpg,0.13712117686170214
674
+ data/trial-293.jpg,0.13190342420212767
661
675
```
676
+ :::::::::::::::::::::::::::::::::::::::::: spoiler
677
+
678
+ ### What is ` & ` doing in the example above?
679
+
680
+ The ` & ` operator above means that we have defined a logical AND statement. This combines the two tests of pixel intensities in the blurred image such that both must be true for a pixel's position to be set to ` True ` in the resulting mask.
681
+
682
+ | Result of ` t < blurred_image ` | Result of ` blurred_image < 0.95 ` | Resulting value in ` binary_mask ` |
683
+ | ----------| ---------| ---------|
684
+ | False | True | False |
685
+ | True | False | False |
686
+ | True | True | True |
687
+
688
+ Knowing how to construct this kind of logical operation can be very helpful in image processing. The University of Minnesota Library's [ guide to Boolean operators] ( https://libguides.umn.edu/BooleanOperators ) is a good place to start if you want to learn more.
689
+
690
+ ::::::::::::::::::::::::::::::::::::::::::::::::::
662
691
663
692
Here are the binary images produced by the additional thresholding.
664
693
Note that we have not completely removed the offending white pixels.
0 commit comments