(image-segmentation:voronoi-otsu-labeling=)

Étiquetage de Voronoi-Otsu#

Ce flux de travail pour la segmentation d’image est une approche plutôt simple et pourtant puissante, par exemple pour détecter et segmenter les noyaux dans les images de microscopie à fluorescence. Un marqueur de noyaux tel que le GFP nucléaire, le DAPI ou l’histone-RFP en combinaison avec diverses techniques de microscopie peut être utilisé pour générer des images de type approprié.

from skimage.io import imread, imshow
import matplotlib.pyplot as plt
import pyclesperanto_prototype as cle
import napari_segment_blobs_and_things_with_membranes as nsbatwm

Pour démontrer le flux de travail, nous utilisons des données d’image du Broad Bio Image Challenge : Nous avons utilisé l’ensemble d’images BBBC022v1 Gustafsdottir et al., PLOS ONE, 2013, disponible dans la Collection de référence de bio-images du Broad Ljosa et al., Nature Methods, 2012.

input_image = imread("../../data/BBBC022/IXMtest_A02_s9.tif")[:,:,0]

input_crop = input_image[0:200, 200:400]

fig, axs = plt.subplots(1, 2, figsize=(15, 15))
cle.imshow(input_image, plot=axs[0])
cle.imshow(input_crop, plot=axs[1])
../_images/bf69dc66f331ad66f2ca4567db95415d6269d30eb08c53f71c25e93ad9d38fa7.png

Application de l’algorithme#

L’étiquetage de Voronoi-Otsu est une commande dans clesperanto, qui demande deux paramètres sigma. Le premier sigma contrôle la proximité des cellules détectées (spot_sigma) et le second contrôle la précision du contour des objets segmentés (outline_sigma).

sigma_spot_detection = 5
sigma_outline = 1

segmented = cle.voronoi_otsu_labeling(input_image, spot_sigma=sigma_spot_detection, outline_sigma=sigma_outline)
segmented_crop = segmented[0:200, 200:400]

fig, axs = plt.subplots(1, 2, figsize=(15, 15))
cle.imshow(segmented, labels=True, plot=axs[0])
cle.imshow(segmented_crop, labels=True, plot=axs[1])
../_images/538916549cc4a2b0af64468a723c080bd652a2fb99cd0b928d05bc259f6fcdfd.png

Comment ça marche ?#

Le flux de travail d’étiquetage Voronoi-Otsu est une combinaison de flou gaussien, de détection de spots, de seuillage et de watershed binaire. Le lecteur intéressé peut consulter le code source ouvert. L’approche est similaire à l’application d’un watershed avec graines à une image binaire, par exemple dans MorphoLibJ ou scikit-image. Cependant, les graines sont calculées automatiquement et ne peuvent pas être transmises.

À des fins de démonstration, nous ne le faisons que sur l’image 2D recadrée montrée ci-dessus. Si cet algorithme est appliqué à des données 3D, il est recommandé de les rendre d’abord isotropes.

image_to_segment = input_crop
print(image_to_segment.shape)
(200, 200)

Dans une première étape, nous floutons l’image avec un sigma donné et détectons les maxima dans l’image résultante.

blurred = cle.gaussian_blur(image_to_segment, sigma_x=sigma_spot_detection, sigma_y=sigma_spot_detection, sigma_z=sigma_spot_detection)

detected_spots = cle.detect_maxima_box(blurred, radius_x=0, radius_y=0, radius_z=0)

number_of_spots = cle.sum_of_all_pixels(detected_spots)
print("nombre de spots détectés", number_of_spots)

fig, axs = plt.subplots(1, 2, figsize=(15, 15))
cle.imshow(blurred, plot=axs[0])
cle.imshow(detected_spots, plot=axs[1])
number of detected spots 29.0
../_images/9cbb6a7f36c42313cc4c4dcbffe4707c7240a9559c28cad3ec957e28aa6fa122.png

De plus, nous repartons de l’image recadrée et la floutons à nouveau, avec un sigma différent. Ensuite, nous seuillons l’image en utilisant la méthode de seuillage d’Otsu (Otsu et al 1979).

blurred = cle.gaussian_blur(image_to_segment, sigma_x=sigma_outline, sigma_y=sigma_outline, sigma_z=sigma_outline)

binary = cle.threshold_otsu(blurred)

fig, axs = plt.subplots(1, 2, figsize=(15, 15))
cle.imshow(blurred, plot=axs[0])
cle.imshow(binary, plot=axs[1])
../_images/ba7efc7ae9e340ac9ee90c8548ba08f0c4cace70582da1ec689bb82e03803aa4.png

Ensuite, nous prenons l’image binaire des spots et l’image binaire de segmentation et appliquons une opération binary_and pour exclure les spots qui ont été détectés dans la zone d’arrière-plan. Ceux-ci correspondaient probablement à du bruit.

selected_spots = cle.binary_and(binary, detected_spots)

number_of_spots = cle.sum_of_all_pixels(selected_spots)
print("nombre de spots sélectionnés", number_of_spots)

fig, axs = plt.subplots(1, 3, figsize=(15, 15))
cle.imshow(detected_spots, plot=axs[0])
cle.imshow(binary, plot=axs[1])
cle.imshow(selected_spots, plot=axs[2])
number of selected spots 11.0
../_images/04a050b1fbbf0500e6180ee8778ae37eab7cab5bffc0bd4c750b4aa093a8099f.png

Ensuite, nous séparons l’espace de l’image entre les spots sélectionnés en utilisant un diagramme de Voronoi qui est limité aux pixels positifs de l’image binaire.

voronoi_diagram = cle.masked_voronoi_labeling(selected_spots, binary)

fig, axs = plt.subplots(1, 3, figsize=(15, 15))
cle.imshow(selected_spots, plot=axs[0])
cle.imshow(binary, plot=axs[1])
cle.imshow(voronoi_diagram, labels=True, plot=axs[2])
../_images/000b45cf1f03acfbbfd364c033bf107ab68da2cbca54e346ba9b08149ce783ad.png

Autres implémentations de l’étiquetage Voronoi-Otsu#

Il existe une implémentation alternative de l’algorithme dans le plugin napari scriptable napari-segment-blobs-and-things-with-membranes.

Le code ici est presque identique au code ci-dessus. La principale différence est que nous appelons nsbatwm.voronoi_otsu_labeling() au lieu de cle.voronoi_otsu_labeling().

sigma_spot_detection = 5
sigma_outline = 1

segmented2 = nsbatwm.voronoi_otsu_labeling(input_image, spot_sigma=sigma_spot_detection, outline_sigma=sigma_outline)

segmented_crop2 = segmented2[0:200, 200:400]

fig, axs = plt.subplots(1, 2, figsize=(15, 15))
cle.imshow(segmented2, labels=True, plot=axs[0])
cle.imshow(segmented_crop2, labels=True, plot=axs[1])
../_images/fb9f7b8500682ab39e50278adefaff958914f3fbfe0ef57d4ceb1867228a8247.png