ImageJ: Thresholding#

Hide code cell content
%load_ext autoreload
%autoreload 2

# Default imports
import sys
sys.path.append('../../../')
from helpers import *
from matplotlib import pyplot as plt
from myst_nb import glue
import numpy as np
from scipy import ndimage

Introducción#

Aquí, exploraremos algunos métodos de ImageJ para aplicar umbrales a imágenes, generando imágenes binarias, imágenes etiquetadas y ROI. También afrontaremos algunas de las complicaciones asociadas.

Umbral global#

El comando de umbral principal en ImageJ es Image ‣ Adjust ‣ Threshold…, con el atajo Shift+T.

Esto abre un cuadro de diálogo Threshold o Umbral que le permite identificar píxeles por encima de un umbral, por debajo de un umbral o que se encuentran entre dos umbrales. Estas opciones se controlan mediante una combinación de los controles deslizantes de umbral y la casilla de verificación Dark background. También hay un menú desplegable que te permite seleccionar de una lista de métodos de umbral automatizados.

../../../_images/imagej-thresholds-dialog.png

Durante la vista previa, los píxeles que se consideran en primer plano se muestran en rojo de forma predeterminada (es posible cambiar esto, pero yo nunca lo hago). Después de elegir los umbrales adecuados, presionar Apply produce una imagen binaria. Esto reemplaza el original, por lo que es conveniente duplicar la imagen primero.

Imágenes binarias en ImageJ

Aunque realmente solo se necesita un bit para cada píxel en una imagen binaria, la implementación en ImageJ actualmente usa 8 bits, por lo que los valores de píxeles reales permitidos son 0 y 255. Para complicar las cosas, ImageJ también permite que cualquiera de estos represente en primer plano, con la opción oculta en Process ‣ Binary ‣ Options…, y 0 se considera “negro” y 255 “blanco” . Personalmente, prefiero que el blanco represente el primer plano (es decir, las cosas interesantes que hemos detectado), por lo que asumiré que la opción Black background ha sido marcada.

Sin embargo, debes tener en cuenta que esta convención no se adopta universalmente. Además, si eliges Invert LUT, los colores se invierten de todos modos, por lo que surge aún más confusión. Por lo tanto, si descubre que cualquier procesamiento de imágenes binarias produce resultados extraños, asegúrese de verificar las opciones binarias y el estado de LUT.

El cuadro de diálogo Umbral es bueno para explorar interactivamente diferentes métodos de umbralización automatizados, pero puede resultar difícil compararlos sistemáticamente. Image ‣ Adjust ‣ Auto Threshold… ayuda con esto, proporcionando una opción para probar todos los métodos.

../../../_images/imagej-thresholds-all-dialog.png

Aplicar esto al famoso blobs.gif de ImageJ revela que no todos los métodos funcionan igual de bien:

../../../_images/imagej-thresholds-all-blobs.png

Métodos de umbral automatizados

Los diversos umbrales automatizados se describen en https://imagej.net/Auto_Threshold, a menudo con referencias a los artículos originales publicados en los que se basan.

Creando objetos#

Una vez que tenemos una imagen binaria, el siguiente paso es identificar objetos para una mayor exploración.

Generar y medir ROIs#

Los ROIs son una buena forma de representar objetos en ImageJ porque son fáciles de medir. En 2D, existen varias opciones para generar ROIs a partir de una imagen con umbral, cada una con aplicaciones ligeramente diferentes:

  • Haz clic en un objeto con la Wand tool ../../../_images/wand.png; esto es bueno para elegir interactivamente una región conectada.

  • Edit ‣ Selection ‣ Create Selection: esto crea un único retorno de la inversión que contiene todos los píxeles de primer plano. Las regiones desconectadas se pueden separar agregando el ROI al Administrador de ROI y eligiendo More >> Split.

  • Analyze ‣ Analyze Particles…: esto detecta y mide todas las regiones de primer plano como objetos individuales, opcionalmente filtrando objetos según su forma o área.

Crear ROIs sin aplicar un umbral

La Wand tool ../../../_images/wand.png, Edit ‣ Selection ‣ Create Selection & Analyze ‣ Analyze Particles… también se puede utilizar cuando se obtiene una vista previa de un umbral en una imagen, pero aún no se ha convertido a binario. Por lo tanto, es posible que no necesite presionar Apply en el cuadro de diálogo Threshold.

De hecho, debido a que no está 100% claro si los píxeles blancos o negros representan el primer plano en una imagen binaria de ImageJ, tiendo a establecer un umbral incluso en una imagen que es ya binaria. De esa manera puedo visualizar lo que cualquiera de estos comandos tratará como primer plano: con la visualización de umbral predeterminada, el primer plano está resaltado en rojo.

Me gusta el hecho de que Edit ‣ Selection ‣ Create Selection proporciona una forma muy rápida de convertir una región con umbral en un único retorno de la inversión. Lo uso más a menudo en combinación con Edit ‣ Selection ‣ Restore Selection para visualizar lo que se ha detectado encima de otra imagen. Puede que al final no necesariamente use el ROI único para las mediciones, pero puede ser muy bueno para orientarme.

Cuando desees medir varios objetos rápidamente, Analyze ‣ Analyze Particles… es posiblemente la opción más automatizada y versátil. Sus diversas opciones también permiten ignorar regiones que son particularmente pequeñas o grandes, rectas o redondas (usando una métrica {guilabel} Circularity). Puedes generar resultados resumidos y agregar ROIs para cada región al ROI Manager.

../../../_images/imagej-thresholds-analyze-dialog.png

Redirigir medidas

Aunque las imágenes binarias pueden mostrar las formas de los objetos que se van a medir, las mediciones de intensidad de píxeles realizadas en una imagen binaria no son muy útiles. Puedes utilizar las técnicas anteriores para crear ROIs a partir de imágenes binarias y luego aplicarlas a la imagen original para obtener mediciones significativas.

Es posible hacer esto cambiando la opción Redirect to: en Analyze ‣ Set Measurements. ., que redirige las mediciones a realizar sobre una imagen específica. Configurar una imagen de redirección antes de ejecutar Analyze ‣ Analyze Particles… significa que las “partículas” de ROI se pueden detectar en una imagen y medir en una imagen diferente.

Normalmente no recomiendo esto, ya que imagino que podría olvidarme de restablecer la opción Redirect to: cuando termine. Prefiero agregar mis ROIs al ROI Manager y transferirlos de esa manera.

Precaución con ROIs con agujeros

Históricamente, ha habido un problema con mi enfoque preferido para usar la salida del ROI Manager de Analyze ‣ Analyze Particles….

Parece que Analyze Particles… maneja los agujeros correctamente al realizar mediciones por sí solo, pero de forma predeterminada generaría ROIs que carecían de agujeros. Esto significaba que si midiera los ROIs más adelante podría obtener resultados diferentes, porque los ROIs incluían píxeles adicionales que en realidad estaban excluidos (como agujeros) en la imagen con umbral.

../../../_images/imagej-thresholds-analyze-holes.png

Figura 78 Resultados diferentes al usar Analyze ‣ Analyze Particles… directamente y luego medir el ROI que generó. La imagen es binaria, por lo que el valor medio debe ser 255 si solo se miden los píxeles por encima del umbral.#

Sin embargo, hay una solución, que solo descubrí cuando escribí sobre ella aquí. Desde ImageJ v1.53g, hay una casilla de verificación Composite ROIs cuando se utiliza Analyze Particles…. Si esto está activado **, las ROIs con agujeros se generan como se esperaba.

../../../_images/imagej-thresholds-analyze-composite.png

Figura 79 Solucionar el problema con las regiones con agujeros mediante el uso de Composite ROIs.#

Esto demuestra la necesidad de tener cuidado con cualquier software de análisis de imágenes y de comprobar las diversas opciones asociadas incluso con los comandos más comunes. No siempre están haciendo lo que cabría esperar.

Generando imágenes etiquetadas#

Los ROI no son la única forma de representar objetos de imagen: a veces las imágenes etiquetadas son más útiles.

Analyze ‣ Analyze Particles… puede generar imágenes etiquetadas usando la opción Show: Count Masks. Esto generará una nueva imagen en la que cada píxel tiene un valor entero único que indica el número del objeto del que forma parte, o cero si está en segundo plano. Con una LUT adecuadamente colorida (a menudo Image ‣ Lookup Tables ‣ Glasbey), esto puede crear una exhibición útil y alegre de objetos (Figura 63).

../../../_images/imagej-thresholds-analyze-labels.png

Figura 80 Creando una imagen etiquetada con Analyze ‣ Analyze Particles.#

Conectividad (4 u 8) es una consideración importante al convertir una imagen binaria en objetos, ya que puede tener un impacto importante en la cantidad y el tamaño de los objetos.

Averigüe qué tipo de conectividad utilizan

  • Analyze ‣ Analyze Particles…

  • la Wand tool ../../../_images/wand.png

Nota: Puedes investigar esto aplicando los comandos/herramientas a una imagen binaria que tenga algunos píxeles conectados diagonalmente. Una forma de hacerlo es establecer un umbral en una imagen adecuada; otra es utilizar la Brush tool ../../../_images/brush.png para dibujar sus propios píxeles en la imagen binaria.

lanzar ImageJ.JS

Al momento de escribir este artículo, Analyze ‣ Analyze Particles… usa conectividad 8.

Para la Wand tool la respuesta correcta dependerá de la configuración que vea si hace doble clic en la herramienta. Hay tres opciones: 4 conectividades, 8 conectividades y «heredado» (donde «heredado» parece comportarse un poco como 8 conectividades).

../../../_images/imagej-thresholds-wand-dialog.png

Umbral local#

Se pueden encontrar algunos métodos para realizar umbrales locales en Image ‣ Adjust ‣ Auto Local Threshold. El cuadro de diálogo nuevamente ofrece la opción de probarlos todos.

../../../_images/imagej-thresholds-local-dialog.png

El uso de filtros locales automatizados es complicado por el hecho de que es necesario ajustar los tamaños y parámetros de las ventanas. Lo que esto significa en cada caso se describe en https://imagej.net/Auto_Local_Threshold

Actualmente, el umbral local de ImageJ también requiere que la imagen se convierta a 8 bits. Esto debe hacerse con cierta precaución, ya que puede implicar incorporar subrepticiamente la [configuración de brillo y contraste] (sec_bit_ depths_converting) en el umbral.

Por estas razones, tiendo a evitar los umbrales locales.

Explora varios métodos automatizados para establecer umbrales en los diferentes canales de File ‣ Open samples ‣ HeLa Cells, utilizando umbrales automatizados locales y globales, para sacar sus propias conclusiones. sobre qué métodos podrías preferir para diferentes imágenes.

lanzar ImageJ.JS

Anexo: Cuestiones prácticas#

Terminamos esta sección con una breve discusión de algunas cuestiones prácticas no obvias que afectan el umbral, relacionadas con las profundidades de bits y los tipos de imágenes.

Usando NaNs#

El primer problema aparece al hacer clic en Apply en el cuadro de diálogo Image ‣ Adjust ‣ Threshold… para una imagen de 32 bits. Esto lleva a un mensaje que pregunta si se debe Set Background Pixels to NaN.

Si se elige esta opción, ya no se crea una imagen binaria. En cambio, el umbral da como resultado una imagen en la que los píxeles de primer plano conservan sus valores originales, mientras que los píxeles de fondo son NaN o Not A Number.

NaN es un valor especial que solo se puede almacenar en imágenes de punto flotante, que ImageJ ignora al realizar mediciones posteriores. Por lo tanto, se utiliza para enmascarar regiones y al mismo tiempo preservar valores de píxeles significativos en otros lugares.

Esta es una opción avanzada que es potencialmente útil, pero puede resultar un poco difícil trabajar con ella. Debe tener mucho cuidado al utilizar una imagen que contenga NaN, ya que algunos comandos pueden comportarse de manera sorprendente.

Crea una imagen que incluya píxeles NaN y luego mide algunos ROIs dibujados en ella. ¿Las mediciones de área se ven afectadas por la presencia o no de NaN?

lanzar ImageJ.JS

¡Si lo son! Si mides el área de una imagen que contiene NaN, el resultado es menor que si mides el área de la misma imagen convertida a 8 bits, ya que solo se incluyen las partes que no son NaN. Si mides una región que contiene solo NaN, el área es 0.

A través de experimentos o conjeturas, ¿qué supones que les sucede a los NaNs con una imagen de 32 bits convertida a 8 o 16 bits?

Dado que NaN no es un número entero, no se puede almacenar en una imagen de entero sin signo de 8 o 16 bits. En cambio, todos los NaN simplemente se vuelven cero.

Binning de histogramas#

La segunda forma en que importan las profundidades de bits y los tipos es que los histogramas de imágenes > 8 bits implican agrupar (o hacer binning de) los datos. Por ejemplo, con una imagen de 32 bits probablemente no tendría sentido crear un histograma que tenga recuentos separados para todos los valores de píxeles posibles: además de recuentos para píxeles con valores exactos 1 y 2, tendríamos miles de recuentos para píxeles con fracciones intermedias y la mayoría de estos recuentos serían 0.

En cambio, el histograma se genera dividiendo el rango total de datos (valores máximos y mínimos de píxeles) en 256 bins o contenedores, separados con anchos iguales, y contando cuántos píxeles tienen valores que caen en el rango de cada bin. Por lo tanto, es como una conversión sutil a una precisión de 8 bits para el cálculo del umbral, pero sin cambiar realmente los datos originales. El mismo tipo de conversión se utiliza para imágenes de 16 bits, a menos que utilices el comando Image ‣ Adjust ‣ Auto Threshold, que puede usar un Histograma de 16 bits con 65536 contenedores.

Aquí es donde la opción Don’t reset range en el cuadro de diálogo de umbral de ImageJ se vuelve relevante para imágenes de 16 o 32 bits, cuando se usa en combinación con el boton Auto.

  • Si Don’t reset range está seleccionado, entonces la agrupación utiliza los valores mínimo y máximo actuales en el cuadro de diálogo de brillo/contraste.

  • Si Don’t reset range no está seleccionado, entonces la opcion de binning restablece los valores mínimo y máximo en el cuadro de diálogo de brillo/contraste antes de usarlos.

Personalmente, esto me parece confuso; No me gustan los umbrales que afectan el brillo/contraste, y mi pobre cerebro incluso tiene dificultades para procesar lo negativo en Don’t reset range para tener una sensación intuitiva de qué efecto tendrá realmente la opción. Y lo que empeora las cosas es que el comportamiento de ImageJ en este sentido ha cambiado entre las versiones, por lo que existe la posibilidad de que cualquier consejo específico que dé aquí no coincida con la versión que estás usando de todos modos.

Sé que no soy el único que está confundido, ya que este ha sido el tema de una larga discusión en el foro image.sc. Entonces, el mensaje principal es: ten en cuenta que se produce binning y esta atento a cualquier efecto inesperado que los controles deslizantes de brillo/contraste puedan tener furtivamente en el umbral.

Explora configurar los umbrales con y sin Don’t reset range seleccionado para la imagen File ‣ Open Samples ‣ M51 Galaxy (16-bits), y presionando el botón Auto para determinar un umbral.

Manten Image ‣ Adjust ‣ Brightness/Contrast… abierto mientras exploras, para que puedas realizar ajustes antes de calcular los umbrales.

../../../_images/imagej-m51.png

lanzar ImageJ.JS

Esta imagen muestra un uso potencial de la opción Don’t reset range: usada con precaución.

../../../_images/fe6156640b061745f050cbff733dc17cf4ccfe0f0da3092179c38b24fb1018e9.png

Figura 81 Umbral de una imagen de 16 bits con y sin “Don’t reset range” seleccionado.#

¿Cuáles son las implicaciones de utilizar un histograma de 256 bins para establecer el umbral de una imagen de 32 bits?

En particular, ¿cómo podrían los píxeles atípicos afectar la precisión con la que se puede definir un umbral, automática o manualmente?

Para explorar esto, puedes usar el ejemplo extremo de cell_outlier.tif junto con el comando Image ‣ Adjust ‣ Threshold….

¿Cómo podrías reducir (manualmente) el impacto de cualquier problema que encuentres?

Nota: Analyze ‣ Histogram te permite investigar el histograma de la imagen con diferentes números de contenedores, pero cualquier cambio que realices aquí no se reflejará en el histograma realmente utilizado para el umbral.

lanzar ImageJ.JS

Primero, una implicación positiva del uso de un histograma de 256 bits para el umbral es que puede ser rápido: se agregan más contenedores o bins a los cálculos involucrados. Además, crear demasiados contenedores tiene como resultado que la mayoría de ellos sean cero, lo que podría causar que algunos algoritmos automatizados de determinación de umbrales fallen.

Una implicación negativa es que usar 256 contenedores significa que sólo son posibles 256 umbrales diferentes: es decir, si el rango de tu imagen es 0–25500, entonces los umbrales que podrías obtener son 0, 100, 200, … 25500. Si el umbral óptimo es realmente 150, esto no se encontrará. Pero, por lo general, si tu rango de valores de píxeles es tan grande, de todos modos no necesitas un umbral muy detallado para obtener resultados aceptables.

Esto cambia si tienes valores atípicos. Un solo píxel extremo, que puede ocurrir cuando un píxel de una cámara CCD está de alguna manera «roto», puede causar que la mayoría de los demás píxeles de la imagen queden comprimidos en sólo unos pocos contenedores. Entonces, la resolución del histograma podría ser demasiado pequeña para establecer un umbral razonable.

Es un problema suficientemente “oscuro” que, con suerte, no te molestará. Sin embargo, si te encuentras con el problema, hay dos formas posibles de solucionarlo:

  1. Ajusta los controles deslizantes de brillo/contraste y asegúrate de que Don’t reset range ** esté** seleccionado. Este es el beneficio de la opción existente.

  2. Convierte la imagen a 8 bits manualmente tu mismo/a. Esto te permite elegir efectivamente el rango de los contenedores de histograma (usando Brightness/Contrast…; ver Types & bit-depths) Dado que el umbral se establece utilizando 256 contenedores, en realidad no se está perdiendo ninguna información que no se iba a perder de todos modos.