Análisis de Bland-Altman para comparar algoritmos de segmentación#

Supongamos que hemos estado utilizando un algoritmo de segmentación durante muchos años y ahora estamos considerando reemplazarlo por una versión más nueva y rápida. Necesitamos asegurarnos de que podemos comparar los resultados entre estos dos. Como los algoritmos de segmentación típicamente no etiquetan los objetos en el mismo orden e incluso el número de objetos puede diferir, no podemos comparar fácilmente los objetos por pares. Se recomienda resumir los objetos segmentados por imagen y luego comparar los resultados producidos en carpetas de imágenes.

En este notebook compararemos estadísticas derivadas de los resultados de segmentación producidos por dos algoritmos en una carpeta de imágenes.

folder = '../../data/BBBC007_batch/' 
from skimage.io import imread
from skimage.measure import regionprops
from utils import bland_altman_plot
import napari_segment_blobs_and_things_with_membranes as nsbatwm
import pyclesperanto_prototype as cle
import os
import numpy as np
import pandas as pd
import stackview

Algoritmos de segmentación a comparar#

Aquí escribimos los dos algoritmos de segmentación como funciones de Python y los probamos en una imagen.

test_image = imread(folder + "17P1_POS0013_D_1UL.tif")
stackview.insight(test_image)
shape(340, 340)
dtypeuint16
size225.8 kB
min1
max255
def segmentation_1(image):
    return nsbatwm.voronoi_otsu_labeling(image)
segmentation_1(test_image)
nsbatwm made image
shape(340, 340)
dtypeint32
size451.6 kB
min0
max46
def segmentation_2(image):
    return nsbatwm.gauss_otsu_labeling(image)
test_labels = segmentation_2(test_image)
test_labels
nsbatwm made image
shape(340, 340)
dtypeint32
size451.6 kB
min0
max41

Mediciones cuantitativas#

Más adelante, queremos comparar mediciones. Por lo tanto, escribimos una función de Python que determina estas mediciones. En este ejemplo, calcularemos el área media de los núcleos segmentados.

def mean_metric(image, label_image, metric):
    
    properties = regionprops(label_image, image)
    
    values = [p[metric] for p in properties]
    
    return np.mean(values)
mean_metric(test_image, test_labels, "area")
235.70731707317074

Recopilación de mediciones de carpetas#

Ahora aplicamos estos dos algoritmos y las mediciones en una carpeta de imágenes.

def compare_measurements_from_algorithms(algorithm_1, algorithm_2, folder, metric):
    measurements = {
        metric + '_1':[],
        metric + '_2':[]
    }

    # Iterar sobre todos los archivos en la carpeta
    for filename in os.listdir(folder):
        file_path = os.path.join(folder, filename)

        # Comprobar si el elemento actual es un archivo
        if os.path.isfile(file_path) and filename.endswith(".tif"):
            # cargar imagen
            image = imread(file_path)

            # segmentarla usando ambos algoritmos
            labels_1 = algorithm_1(image)
            labels_2 = algorithm_2(image)

            # determinar el área media y almacenarla
            measurements[metric + '_1'].append(mean_metric(image, labels_1, metric))
            measurements[metric + '_2'].append(mean_metric(image, labels_2, metric))
    
    return measurements
measurements = compare_measurements_from_algorithms(segmentation_1, 
                                                    segmentation_2, 
                                                    folder, 
                                                    'area')

pd.DataFrame(measurements)
area_1 area_2
0 210.086957 235.707317
1 206.866667 244.973684
2 203.023256 268.615385
3 185.103448 214.720000
4 184.147059 362.956522
5 267.057692 730.894737

Gráficos de Bland-Altman#

Ahora usamos el gráfico de Bland-Altman para visualizar las diferencias.

bland_altman_plot(measurements['area_1'], measurements['area_2'], 'area')
../_images/300e285258e47df4e1558c5cf4cf8c8c2283211e2937c7094869e7e030da2471.png

En el caso mostrado arriba, la diferencia promedio de la medición del área es de aproximadamente -100, lo que significa que el primer algoritmo produce en promedio mediciones de área más pequeñas que el segundo.

Con fines de demostración, ahora compararemos el mismo algoritmo en una variante de CPU y GPU.

def segmentation_1_gpu(image):
    return cle.voronoi_otsu_labeling(image)
measurements_cpu_vs_gpu = compare_measurements_from_algorithms(segmentation_1, 
                                                    segmentation_1_gpu, 
                                                    folder, 
                                                    'area')
bland_altman_plot(measurements_cpu_vs_gpu['area_1'], measurements_cpu_vs_gpu['area_2'], 'area')
../_images/f09d5e7420d6888d3c6a2904eb8afa2e531fe8e51c26d3cd359669d5acbb1002.png

En este caso, vemos que la diferencia promedio es de aproximadamente 0. Además, el intervalo de confianza es mucho más pequeño que en la comparación anterior.

Ejercicio#

Compare también el segundo algoritmo de segmentación con su variante de GPU.

Ejercicio#

Compare las mediciones de intensidad media de dos algoritmos donde el área parece bastante diferente. ¿Puede predecir cómo se verá el gráfico de Bland-Altman?