Label image refinement#

Similar to refining binary images it is also possible to refine label images. This notebook shows how to do this.

See also

import pyclesperanto_prototype as cle
from skimage.data import cells3d
import matplotlib.pyplot as plt
from skimage.morphology import closing, disk
from skimage.io import imsave
from skimage.filters.rank import maximum
from skimage.segmentation import expand_labels
from skimage.measure import label
# load image
image = cells3d()
membrane2d = cle.push(image[30,0])

# segment and label the image
labels = label(cle.gaussian_blur(membrane2d, sigma_x=2, sigma_y=2) < 2000)

# visualization
fig, axs = plt.subplots(1,2, figsize=(10,10))
cle.imshow(membrane2d, plot=axs[0], max_display_intensity=5000)
cle.imshow(labels, labels=True, plot=axs[1])
../_images/12_open_close_labels_2_0.png

Dilating / expanding labels#

When dilating labels in a label image, we cannot apply a maximum filter, because the maximum filter lead to labels swapping into each other. The label with the larger value would increas while labels with lower values would decrease. We can demonstrate that using maximum filters and the expand_labels function in scikit-image.

r = 25

maximum_label_image = maximum(labels, footprint=disk(r))
expaned_label_image = expand_labels(labels, distance=r)

fig, axs = plt.subplots(1,3, figsize=(10,10))
cle.imshow(maximum_label_image, labels=True, plot=axs[0])
cle.imshow(labels, labels=True, plot=axs[1])
cle.imshow(expaned_label_image, labels=True, plot=axs[2])


axs[0].set_title("Maximum filter")
axs[1].set_title("Original labels")
axs[2].set_title("Expanded labels")
C:\Users\rober\AppData\Local\Temp\ipykernel_15152\668206942.py:3: UserWarning: Possible precision loss converting image of type int64 to uint8 as required by rank filters. Convert manually using skimage.util.img_as_ubyte to silence this warning.
  maximum_label_image = maximum(labels, footprint=disk(r))
C:\Users\rober\Anaconda3\envs\bio_39\lib\site-packages\skimage\util\dtype.py:541: UserWarning: Downcasting int64 to uint8 without scaling because max value 26 fits in uint8
  return _convert(image, np.uint8, force_copy)
Text(0.5, 1.0, 'Expanded labels')
../_images/12_open_close_labels_4_2.png

Dilating and eroding labels#

There is also the opposite of dilation: Erosion of labels. The latter function is available in clesperanto.

r = 5

fix, axs = plt.subplots(1,3, figsize=(10,10))

cle.imshow(cle.dilate_labels(labels, radius=r), plot=axs[0], labels=True)
cle.imshow(labels, plot=axs[1], labels=True)
cle.imshow(cle.erode_labels(labels, radius=r), plot=axs[2], labels=True)

axs[0].set_title("Dilated labels (r=" + str(r) + ")")
axs[1].set_title("Original labels")
axs[2].set_title("Eroded labels (r=" + str(r) + ")")
Text(0.5, 1.0, 'Eroded labels (r=5)')
../_images/12_open_close_labels_6_1.png

Label opening and closing#

Opening and closing are uncommon operations applied to label images as discussed here. They are also powerful options for post-processing label images. To show in more detail what these operations are doing, we look at the results in a for-loop using different radii.

for i, r in enumerate(range(0, 20, 5)):
    
    fix, axs = plt.subplots(1,3, figsize=(10,10))
    
    cle.imshow(cle.closing_labels(labels, radius=r), plot=axs[0], labels=True)
    cle.imshow(labels, plot=axs[1], labels=True)
    cle.imshow(cle.opening_labels(labels, radius=r), plot=axs[2], labels=True)
    
    axs[0].set_title("Closed labels (r=" + str(r) + ")")
    axs[1].set_title("Original labels")
    axs[2].set_title("Opened labels (r=" + str(r) + ")")
../_images/12_open_close_labels_8_0.png ../_images/12_open_close_labels_8_1.png ../_images/12_open_close_labels_8_2.png ../_images/12_open_close_labels_8_3.png

Comparison of label closing to binary closing#

The closing operations applied to label images is very similar to a binary closing.

r = 5

fix, axs = plt.subplots(1,3, figsize=(10,10))

cle.imshow(labels, plot=axs[0], labels=True)
cle.imshow(cle.closing_labels(labels, radius=r), plot=axs[1], labels=True)
cle.imshow(cle.closing_sphere(labels != 0, radius_x=r, radius_y=r), plot=axs[2], color_map="Greys_r")

axs[0].set_title("Original labels")
axs[1].set_title("Closed labels (r=" + str(r) + ")")
axs[2].set_title("Binary closing (r=" + str(r) + ")")
c:\structure\code\pyclesperanto_prototype\pyclesperanto_prototype\_tier9\_imshow.py:14: UserWarning: The imshow parameter color_map is deprecated. Use colormap instead.
  warnings.warn("The imshow parameter color_map is deprecated. Use colormap instead.")
Text(0.5, 1.0, 'Binary closing (r=5)')
../_images/12_open_close_labels_10_2.png

Comparison of label closing to morphological closing#

If dedicated label closing operations are not available, also morphological closing can be used. However, as shown above for the maximum-filter, also morphological closing will show the same swapping over of labels. Hence, it is recommended to only use morphological closing with small radii and when labels are not too close.

r = 6

fix, axs = plt.subplots(1,3, figsize=(10,10))
    
cle.imshow(cle.closing_labels(labels, radius=r), plot=axs[0], labels=True)
cle.imshow(labels, plot=axs[1], labels=True)
cle.imshow(cle.closing_sphere(labels, radius_x=r, radius_y=r), plot=axs[2], labels=True)


axs[0].set_title("Label closing (r=" + str(r) + ")")
axs[1].set_title("Original labels")
axs[2].set_title("Morphological closing (r=" + str(r) + ")")
Text(0.5, 1.0, 'Morphological closing (r=6)')
../_images/12_open_close_labels_12_1.png
r = 24

fix, axs = plt.subplots(1,3, figsize=(10,10))
    
cle.imshow(cle.closing_labels(labels, radius=r), plot=axs[0], labels=True)
cle.imshow(labels, plot=axs[1], labels=True)
cle.imshow(cle.closing_sphere(labels, radius_x=r, radius_y=r), plot=axs[2], labels=True)

axs[0].set_title("Label closing (r=" + str(r) + ")")
axs[1].set_title("Original labels")
axs[2].set_title("Morphological closing (r=" + str(r) + ")")
Text(0.5, 1.0, 'Morphological closing (r=24)')
../_images/12_open_close_labels_13_1.png