Thresholding#

Thresholding is a technique of image segmentation. It separates a given single-channel image (or stack) into two regions: Pixels with intensity below a given threshold, also called “background” and pixels with intensity above a given threshold, “foreground”. Typically those algorithms result in binary images where background intensity is 0 and foreground intensity is 1. When applying such algorithms in ImageJ, foreground pixels are 255. In scikit-image, background pixels are False and foreground pixels are True.

See also

from skimage.io import imread
from stackview import imshow

from skimage import filters
from skimage.filters import try_all_threshold
from matplotlib import pyplot as plt
import numpy as np
image = imread("data/blobs.tif")
imshow(image)
_images/5b743d1a86c4641439e80adc2c4703c10a7b1dc413eba9187213e05b429b7017.png

Image segmentation using thresholding#

The threshold_otsu operation, also known as Otsu’s method (Otsu et al., IEEE Transactions on Systems, Man, and Cybernetics, Vol. 9 (1), 1979), delivers a number: the intensity threshold that allows differentiating foreground (object) and background in the image.

threshold = filters.threshold_otsu(image)

When using methods such as thresholding in notebooks, it is recommended to print out the result to see what it actually returns. Here, we are using the method from scikit-image, which returns the threshold that is applied. Printing that threshold can be helpful later when reproducing the workflow, also if others want to apply the same threshold to the dataset in other software.

threshold
np.int64(120)

Using numpy arrays, we can apply the threshold by applying the >= operator. The result will be a binary image.

binary_image = image >= threshold

imshow(binary_image)
_images/712ac5f127bf7b4ba08d662f5543a99e5a64b958f85a1e403d4a3b3680324dab.png

We can also determine in which type the binary image is processed by printing out minimum and maximum of the image:

binary_image.max()
np.True_
binary_image.min()
np.False_

As shown earlier, matplotlib allows us to draw an outline on top of an image visualized using imshow using the contour command.

# create a new plot
fig, axes = plt.subplots(1,1)

# add two images
axes.imshow(image, cmap=plt.cm.gray)
axes.contour(binary_image, [0.5], linewidths=1.2, colors='r')
<matplotlib.contour.QuadContourSet at 0x1e8cd98dc40>
_images/084b85780e908690ca8fc8e974c2f7f4e5e72ad8aa69846b176c1f25297baaea.png

There is a list of thresholding algorithms available. It is possible to apply them all to your data and see differences:

fig, ax = try_all_threshold(image, figsize=(10, 8), verbose=False)
plt.show()
_images/cfb1224b27784431ba4cd15186691069e2ad41e0f8f2ebac0459a32649c412a0.png

Thresholding using pyclesperanto#

Furthermore, also other libraries such as pyclesperanto offer thresholding algorithms. The implementation here does not return the threshold, it directly returns the binary image.

import pyclesperanto as cle

binary_image2 = cle.threshold_otsu(image)
imshow(binary_image2)
_images/d56854eed699fb224f672ccdf532d6d30d357cfa43189d31447c6622610bb267.png

Here we can also see that different libraries store binary images in different ways. pyclesperanto for example stores the positive pixels in binary images not as True but with a 1 instead:

binary_image2.max()
1.0
binary_image2.min()
0.0

Exercise#

Segment blobs.tif using the Yen algorithm.

Subtract the Yen-segmented image from the Otsu-segmented image and visualize the result.

Repeat the same for the image data/banana/banana0002.tif.