pyclesperanto comes with multiple labeling operations. Those operations turn images of other kind (intensity images or binary images) into label images, where each segmented object has a label value that identifies it. All pixels that belong to the object have that value.
from skimage.io import imread
import pyclesperanto_prototype as cle
cle.select_device("RTX")
<NVIDIA GeForce RTX 3050 Ti Laptop GPU on Platform: NVIDIA CUDA (1 refs)>
For demonstration purposes we load an intensity image and create a binary image.
raw_image = cle.asarray(imread("../../data/blobs.tif"))
raw_image
cle._ image
|
binary_image = raw_image > 100
binary_image
cle._ image
|
The most common labeling algorithm is connected component labeling (CCL). This algorithm consumes a binary image and produces a label image so that neighboring white pixels in the binary image are combined to labels in the label image.
ccl_image_box = cle.connected_components_labeling_box(binary_image)
ccl_image_box
cle._ image
|
pyclesperanto has two functions which perform connected component labeling and take different pixel neighborhoods (a.k.a. footprint or structuring elements) into account.
ccl_image_diamond = cle.connected_components_labeling_diamond(binary_image)
ccl_image_diamond
cle._ image
|
The difference between these two might hard to spot, but is easy to measure:
print("Number of objects in Diamond CCL result:", ccl_image_diamond.max())
Number of objects in Diamond CCL result: 66.0
print("Number of objects in Box CCL result:", ccl_image_box.max())
Number of objects in Box CCL result: 65.0
We often use image blurring, e.g. using a Gaussian blur, Otus's thresholding method and CCL in combination. Thus, pyclesperanto has a function which combines these operations and make them easier accessible. The parameter outline_sigma
allows tuning the smoothness of the outline of the labeled objects. When increasing it, also small objects may disappear.
gol1 = cle.gauss_otsu_labeling(raw_image, outline_sigma=1)
gol1
cle._ image
|
gol5 = cle.gauss_otsu_labeling(raw_image, outline_sigma=5)
gol5
cle._ image
|
When touching objects are connected, it may make sense to erode a binary image before passing it to CCL. Afterwards, the labels can be dilated again so that the original binary image is filled with labels. This algorithm was suggested by Jan Brocher (Biovoxxel). Also this algorithm offers the parameter outline_sigma
as explained above. Furthermore, you can control the number of binary erosion operations that are applied by changing the parameter number_of_erosions
. Large values of this parameter can make objects disappear as well.
eol3 = cle.eroded_otsu_labeling(raw_image, outline_sigma=1, number_of_erosions=3)
eol3
cle._ image
|
eol5 = cle.eroded_otsu_labeling(raw_image, outline_sigma=1, number_of_erosions=5)
eol5
cle._ image
|
Another common approach is blurring the raw image, detecting maxima and using a binary watershed to flood a corresponding binary image with label values. Results are supposed to be similar to a binary image that has been processed by ImageJ's binary Watershed algorithm before it is passed to CCL. The algorithm has a parameter outline_sigma
as explained above. Furthermore, the spot_sigma
parameter allows to tune how distant local maxima are in the initial detection step.
vol1 = cle.voronoi_otsu_labeling(raw_image, outline_sigma=1, spot_sigma=1)
vol1
cle._ image
|
vol32 = cle.voronoi_otsu_labeling(raw_image, outline_sigma=1, spot_sigma=3.2)
vol32
cle._ image
|
tesselated_image = cle.extend_labeling_via_voronoi(vol32)
tesselated_image
cle._ image
|
Similarly, starting from a binary image, we can label the objects using connected component labeling and then partion the image.
partioned_image = cle.voronoi_labeling(binary_image)
partioned_image
cle._ image
|
You can exclude small and large labels using dedicated operations. It is also possible to select a size range of labels to keep or remove.
large_labels = cle.exclude_small_labels(ccl_image_diamond, maximum_size=350)
large_labels
cle._ image
|
small_labels = cle.exclude_large_labels(ccl_image_diamond, minimum_size=200)
small_labels
cle._ image
|
medium_sized_labels = cle.exclude_labels_out_of_size_range(ccl_image_diamond, minimum_size=200, maximum_size=350)
medium_sized_labels
cle._ image
|
You can also combine label images.
combined_labels = cle.combine_labels(small_labels, large_labels)
combined_labels
cle._ image
|
Dilating label images, is similar to a maximum filter. The only difference is that labels don't overwrite each other.
dilated_labels_3 = cle.dilate_labels(combined_labels, radius=3)
dilated_labels_3
cle._ image
|
dilated_labels_7 = cle.dilate_labels(combined_labels, radius=7)
dilated_labels_7
cle._ image
|
When eroding label images, basically two options exist: Erode labels using a minimum-filter after introducing a background-pixel between labels, and eroding the labels while keeping their connected regions connected.
eroded_labels_3 = cle.erode_labels(dilated_labels_7, radius=3)
eroded_labels_3
cle._ image
|
eroded_connected_labels_3 = cle.erode_connected_labels(dilated_labels_7, radius=3)
eroded_connected_labels_3
cle._ image
|
In a similar way, labels can also be opened. Note: Depending on the radius, small labels may disappear.
opened_labels_1 = cle.opening_labels(combined_labels, radius=1)
cle.imshow(opened_labels_1, labels=True)
opened_labels_5 = cle.opening_labels(combined_labels, radius=5)
opened_labels_5
cle._ image
|
Analogously, an operation for closing label images exists.
closed_labels_1 = cle.closing_labels(combined_labels, radius=1)
closed_labels_1
cle._ image
|
closed_labels_5 = cle.closing_labels(combined_labels, radius=5)
closed_labels_5
cle._ image
|
We can also exclude labels in a label image according to other parameters, e.g. using shape. Therefore, we need a parametric image where the pixels in the label correspond to the parameter we want to consider for excluding labels.
shape_parametric_image = cle.extension_ratio_map(closed_labels_1)
shape_parametric_image
cle._ image
|
minimum_extension_ratio = 1.8
maximum_extension_ratio = 100
elongated_labels = cle.exclude_labels_with_map_values_out_of_range(
shape_parametric_image,
closed_labels_1,
minimum_value_range=minimum_extension_ratio,
maximum_value_range=maximum_extension_ratio,
)
elongated_labels
cle._ image
|
label_border_image = cle.reduce_labels_to_label_edges(elongated_labels)
label_border_image
cle._ image
|
binary_border_image = cle.detect_label_edges(elongated_labels)
binary_border_image
cle._ image
|
Labels can also be reduced to their centroids.
label_centroids_image = cle.reduce_labels_to_centroids(elongated_labels)
label_centroids_image
cle._ image
|
Just for visualization purposes, it may make sense to apply a label dilation to this image.
visualization_label_centroids_image = cle.dilate_labels(label_centroids_image, radius=2)
visualization_label_centroids_image
cle._ image
|