4. Thresholding
Contents
4. Thresholding¶
When performing logical operations of the type image > value
we have seen that this allowed us to perform thresholding, one the basic image processing methods to identify objects in an image. Here we come back to this topic and show how we can automated the threshold selection, combine masks etc.
import numpy as np
import matplotlib.pyplot as plt
import skimage
import skimage.io
Importing the image¶
Let’s import the image, plot it and check its dimensions:
#image_stack = skimage.io.imread('images/46658_784_B12_1.tif')
image_stack = skimage.io.imread('https://github.com/guiwitz/PyImageCourse_beginner/raw/master/images/46658_784_B12_1.tif')
image_stack.shape
(2048, 2048, 3)
plt.subplots(figsize=(10,10))
plt.imshow(image_stack);
We have again a three channel image, i.e. our image is composed of three planes. For the moment we only want to work with the nuclei, so we isolate that channel, the third one:
image_nuclei = image_stack[:,:,2]
plt.subplots(figsize=(10,10))
plt.imshow(image_nuclei, cmap = 'gray');
9.2 Manual investigation¶
We have seen in the previous chapter that if we want to isolate nuclei, we have to know more or less what intensity they have so that we can use a cutoff value on pixel intensities. We can look at the histogram of pixel values:
plt.hist(np.ravel(image_nuclei));
Clearly we have background intensity between 0 and ~50, and the signal spread out between 50 and 255. We can create a mask of all the pixels with a value above 50:
mask = image_nuclei > 50
plt.imshow(mask, cmap = 'gray');
For the sake of the experiment, let’s use a higher threshold:
mask = image_nuclei > 120
plt.imshow(mask, cmap = 'gray');
Clearly, if the threshold is too high, we loose import parts of the image.
Automatic thresholding¶
Setting a threshold manually is not ideal for two reasons:
your eyes might fool you
if you have to do this operation on 100s of images you don’t want to do it by hand.
Several methods have been developed to detect an “ideal” threshold based on the pixel histogram. One of the most famous ones is the Otsu method: it sets a threshold by dividing the pixels in two classes and tries to minimize the variance of both classes.
Such classical algorithms are all implemented in scikit-image and we can just use them out of the box. All algorithm are grouped in logical groups (filtering, measuring etc.) Often the easiest is just to use Google to find them.
For example the Otsu algorithm:
import skimage.filters
help(skimage.filters.threshold_otsu)
Help on function threshold_otsu in module skimage.filters.thresholding:
threshold_otsu(image=None, nbins=256, *, hist=None)
Return threshold value based on Otsu's method.
Either image or hist must be provided. If hist is provided, the actual
histogram of the image is ignored.
Parameters
----------
image : (N, M[, ..., P]) ndarray, optional
Grayscale input image.
nbins : int, optional
Number of bins used to calculate histogram. This value is ignored for
integer arrays.
hist : array, or 2-tuple of arrays, optional
Histogram from which to determine the threshold, and optionally a
corresponding array of bin center intensities. If no hist provided,
this function will compute it from the image.
Returns
-------
threshold : float
Upper threshold value. All pixels with an intensity higher than
this value are assumed to be foreground.
References
----------
.. [1] Wikipedia, https://en.wikipedia.org/wiki/Otsu's_Method
Examples
--------
>>> from skimage.data import camera
>>> image = camera()
>>> thresh = threshold_otsu(image)
>>> binary = image <= thresh
Notes
-----
The input image must be grayscale.
my_otsu_threshold = skimage.filters.threshold_otsu(image_nuclei)
print(my_otsu_threshold)
66
Using that value we can create a new mask:
mask_nuclei = image_nuclei > my_otsu_threshold
plt.imshow(mask_nuclei, cmap = 'gray');
Combining masks¶
We can do the same thresholding operation on the other channel contained in the image. Let’s first “isolate” the first channel (red):
image_cells = image_stack[:,:,0]
plt.subplots(figsize=(10,10))
plt.imshow(image_cells, cmap = 'gray');
mask_cells = image_cells > skimage.filters.threshold_otsu(image_cells)
plt.subplots(figsize=(10,10))
plt.imshow(mask_cells, cmap = 'gray');
We can in principle combine masks however we want. For example we can consider pixels that are ON (True
) in both masks by multiplying them as only True x True = True
both_masks = mask_cells * mask_nuclei
plt.subplots(figsize=(10,10))
plt.imshow(both_masks, cmap = 'gray');
or we can consider pixels that are ON (True
) in one or the other image by adding them as True + False = True
:
plt.subplots(figsize=(10,10))
plt.imshow(mask_cells + mask_nuclei, cmap = 'gray');
To make things cleaner, instead of just using operators such as +
or *
we can be more explicit and use logical Numpy operations such as np.logical_or()
:
combine_or = np.logical_or(mask_cells, mask_nuclei)
plt.imshow(combine_or, cmap = 'gray');
Now that we have segmented elements in our image, we want to be able to quantify those elements, which is what is done in chapter 11. In part 10 we can see how we can improve the masks by cleaning them up using binary/morphological operations.