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

Etiquetado Voronoi-Otsu#

Este flujo de trabajo para la segmentación de imágenes es un enfoque bastante simple y a la vez poderoso, por ejemplo, para detectar y segmentar núcleos en imágenes de microscopía de fluorescencia. Un marcador de núcleos como GFP nuclear, DAPI o histona-RFP en combinación con varias técnicas de microscopía puede usarse para generar imágenes de tipo adecuado.

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

Para demostrar el flujo de trabajo, estamos utilizando datos de imágenes del Broad Bio Image Challenge: Utilizamos el conjunto de imágenes BBBC022v1 Gustafsdottir et al., PLOS ONE, 2013, disponible en la Colección de Referencia de Bioimágenes 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

Aplicando el algoritmo#

El etiquetado Voronoi-Otsu es un comando en clesperanto que requiere dos parámetros sigma. El primer sigma controla qué tan cerca pueden estar las células detectadas (spot_sigma) y el segundo controla qué tan precisamente se delinean los objetos segmentados (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

¿Cómo funciona?#

El flujo de trabajo de Etiquetado Voronoi-Otsu es una combinación de desenfoque gaussiano, detección de puntos, umbralización y watershed binario. El lector interesado puede ver el código fuente abierto. El enfoque es similar a aplicar un watershed con semillas a una imagen binaria, por ejemplo, en MorphoLibJ o scikit-image. Sin embargo, las semillas se calculan automáticamente y no se pueden pasar.

Con fines de demostración, lo hacemos solo en la imagen recortada 2D mostrada arriba. Si este algoritmo se aplica a datos 3D, se recomienda hacerlo isotrópico primero.

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

Como primer paso, desenfocamos la imagen con un sigma dado y detectamos máximos en la imagen resultante.

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("número de puntos detectados", 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

Además, comenzamos nuevamente desde la imagen recortada y la desenfocamos de nuevo, con un sigma diferente. Después, umbralizamos la imagen utilizando el método de umbralización de 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

Después, tomamos la imagen binaria de puntos y la imagen binaria de segmentación y aplicamos una operación binary_and para excluir los puntos que fueron detectados en el área de fondo. Es probable que estos correspondan a ruido.

selected_spots = cle.binary_and(binary, detected_spots)

number_of_spots = cle.sum_of_all_pixels(selected_spots)
print("número de puntos seleccionados", 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

A continuación, separamos el espacio de imagen entre los puntos seleccionados utilizando un diagrama de Voronoi que está limitado a los píxeles positivos en la imagen binaria.

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

Otras implementaciones de Etiquetado Voronoi-Otsu#

Existe una implementación alternativa del algoritmo en el plugin scriptable de napari napari-segment-blobs-and-things-with-membranes.

El código aquí es casi idéntico al código anterior. La principal diferencia es que llamamos a nsbatwm.voronoi_otsu_labeling() en lugar 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