(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])
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])
¿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
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])
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
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])
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])