Diferencia de Gaussianos#
Show 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#
El filtrado de diferencia de Gaussianos (DoG) es una técnica muy útil para mejorar la apariencia de pequeños puntos y bordes en una imagen. Es bastante sencillo, pero aplicarlo manualmente con mucha frecuencia lleva mucho tiempo y es posible que tengas que experimentar con diferentes tamaños de filtro para obtener buenos resultados. Esto lo convierte en un excelente candidato para una macro.
Grabar una macro#
En lugar de sumergirse en la escritura del código, la forma más rápida de comenzar es dejar que ImageJ haga la mayor parte del trabajo duro por sí mismo. Entonces solo necesitas arreglar el resultado. El procedimiento es el siguiente:
Abre una imagen de ejemplo (2D, sin color) para usar, idealmente una que incluya objetos pequeños con forma de puntos o redondos. He usado
, después de extraer solo el canal rojo.Inicia Macro Recorder eligiendo Record: Macro aparezca en la parte superior de esta ventana (consulta la lista desplegable). De aquí en adelante, cada click que hagas que tenga un comando de macro correspondiente dará como resultado que el comando se agregue a la ventana.
. Asegúrate de queConvierte tu imagen a 32 bits. Esto reducirá las imprecisiones debidas al redondeo cada vez que se aplica el filtrado.
Duplica la imagen.
Aplica
a una de las imágenes (no importa si es la original o la duplicada), usando una sigma pequeña (por ejemplo, 1) para la supresión de ruido.Aplica
a la otra imagen, usando una sigma más grande (por ejemplo, 2).Ejecuta
y resta la segunda imagen filtrada de la primera. Esto produce la «diferencia de la imagen filtrada de los Gaussianos», en la que las pequeñas características deben aparecer de manera destacada y el fondo se elimina.¡Ten cuidado de elegir los títulos de imagen correctos y la operación de resta en la Calculadora de imágenes!
Presiona el botón Create en la grabadora de macros. Esto debería hacer que se abra un archivo de texto que contiene la macro grabada en el de Fiji (que puedes encontrar en ).
Guarda el archivo de texto. El nombre del archivo debe terminar con la extensión
.ijm
(para “ImageJ Macro”) e incluir un carácter de guion bajo «_» en algún lugar dentro de él.
¡Ahora tienes una macro!
Podrías probarlo presionando el tentador botón Run, pero no se garantiza que funcione (todavía). Nuestra macro sigue siendo bastante frágil: depende de los nombres exactos de las imágenes y puede confundirse fácilmente. Arreglaremos esto pronto.
Como alternativa,
Cerrar Fiji por completo (para garantizar que no quede nada de esta sesión)
Reabrir Fiji
Abre la imagen original que usaste.
Abre la macro (puedes simplemente arrastrarla a la barra de herramientas de ImageJ)
Ahora, con suerte, el botón Run te dará el mismo resultado que cuando aplicaste los comandos manualmente. Si no es así, sigue leyendo de todos modos y los siguientes pasos deberían solucionarlo.
Limpiando el código#
Ahora vuelve a abrir tu macro en el Editor de secuencias de comandos. Debería parecerse al mío:
Para copiar y pegar más fácilmente, el contenido se encuentra a continuación:
run("32-bit");
//run("Brightness/Contrast...");
run("Enhance Contrast", "saturated=0.35");
run("Duplicate...", " ");
run("Gaussian Blur...", "sigma=1");
selectWindow("C1-hela-cells.tif");
run("Gaussian Blur...", "sigma=2");
imageCalculator("Subtract create", "C1-hela-cells-1.tif","C1-hela-cells.tif");
selectWindow("Result of C1-hela-cells-1.tif");
Probablemente tu código no sea idéntico y puede que sea mejor. Un problema con las macros generadas automáticamente es que contienen (casi) todo, incluyendo a menudo muchos clics errantes u otros pasos no esenciales. Por ejemplo, cambié el contraste de una imagen, pero esto fue sólo para mirarla y no es necesario incluirlo en la macro.
Después de eliminar las líneas innecesarias, obtengo:
run("32-bit");
run("Duplicate...", "title=C1-hela-cells-1.tif");
run("Gaussian Blur...", "sigma=1");
selectWindow("C1-hela-cells.tif");
run("Gaussian Blur...", "sigma=2");
imageCalculator("Subtract create", "C1-hela-cells-1.tif","C1-hela-cells.tif");
Entendiendo el código#
Lo más probable es que puedas descubrir qué está haciendo la macro, si no necesariamente la terminología, con sólo mirarla. Tomando la primera línea, Run
es una **función_ que le dice a ImageJ que ejecute un comando, mientras que 32 bits
es un fragmento de texto (llamado cadena) que le dice qué comando. Las funciones siempre le dicen a ImageJ que haga algo o le brindan información, y pueden reconocerse porque normalmente van seguidas de paréntesis. Las cadenas son reconocibles porque están dentro de comillas dobles y el editor de scripts las muestra en un color diferente. Observa también que cada línea debe terminar con un punto y coma para que el intérprete de macros sepa que la línea ha terminado.
Las funciones pueden requerir diferentes cantidades de información para realizar su trabajo. Como mínimo, «Run» necesita saber el nombre del comando y la imagen a la que se debe aplicar, que aquí se considera la imagen que esté actualmente activa, es decir, la que se seleccionó más recientemente. Pero si el comando utilizado por «Run» requiere información adicional propia, entonces se incluye como una cadena adicional. Por lo tanto
run("Duplicate...", "title=C1-hela-cells-1.tif");
informa al comando
que la imagen que crea debe llamarse C1-hela-cells-1.tif, yrun("Gaussian Blur...", "sigma=1");
garantiza que
se ejecute con un valor sigma de 1.selectWindow
es otra función, agregada a la macro cada vez que haces click en una ventana en particular para activarla, y que requiere el nombre de la ventana de imagen para activarse. A partir de esto puedes ver que el nombre de mi archivo de ejemplo era C1-hela-cells.tif. Sin esta línea, la imagen duplicada se filtraría dos veces, y la original no se filtraría en absoluto.
Finalmente, el comando imageCalculator
. La primera cadena que se le proporciona le indica qué tipo de cálculo hacer y que debe «crear» una nueva imagen para el resultado, en lugar de reemplazar una de las imágenes existentes. Las siguientes dos cadenas le dan los títulos de las imágenes necesarias para el cálculo.
Eliminar dependencias de títulos#
El hecho de que el título de la imagen original aparezca en la macro anterior es un problema: si intentas ejecutarlo en otra imagen, es probable que descubras que no funciona porque selectWindow
no puede encontrar lo que busca. Entonces, el siguiente paso es eliminar esta dependencia del título para que la macro pueda aplicarse a cualquier imagen (2D).
Hay dos maneras de hacer esto. Una es insertar una línea que le diga a la macro el título de la imagen que se está procesando al inicio, por ejemplo.
titleOrig = getTitle();
donde getTitle()
es un ejemplo de una función que solicita información. Luego, el resultado se almacena como una variable, de modo que cada vez que escribamos titleOrig
más adelante, será reemplazado por la cadena correspondiente al título original [^fn_6]. Luego simplemente buscamos cualquier lugar donde aparezca el título y reemplazamos el texto con nuestro nuevo nombre de variable, es decir, en este caso escribiendo
selectWindow(titleOrig);
Si hacemos esto, la ventana que queremos probablemente se activará según sea necesario. Sin embargo, potencialmente existe un sutil problema. Es posible que tengamos dos imágenes abiertas al mismo tiempo con títulos idénticos, en cuyo caso no está claro qué ventana se debe seleccionar y, por lo tanto, los resultados podrían ser impredecibles. Un enfoque más seguro es obtener una referencia al ID de la imagen en lugar de su título. El ID es un número que debe ser único para cada imagen, lo cual es útil para ImageJ internamente pero que normalmente no nos importa a menos que estemos programando. Usando ID, el código de macro actualizado se convierte en:
idOrig = getImageID();
run("32-bit");
run("Duplicate...", "title=[My duplicated image]");
idDuplicate = getImageID();
run("Gaussian Blur...", "sigma=1");
selectImage(idOrig);
run("Gaussian Blur...", "sigma=2");
imageCalculator("Subtract create", idDuplicate, idOrig);
Tuvimos que cambiar selectWindow
a selectImage
para que las ID funcionaran. También cambié el título de la imagen duplicada a algo más general y sin sentido, que requería corchetes, porque incluye espacios que de otra manera estropearían las cosas [^fn_7]. Además, debido a que la imagen duplicada estará activa inmediatamente después de su creación, le solicito a ImageJ su ID en ese momento. Esto me permite pasar las dos ID (en lugar de títulos) al comando imageCalculator
cuando sea necesario.
Agregar comentarios#
Cada vez que las macros se vuelven más complicadas, puede resultar difícil recordar exactamente qué hacen todas las partes y por qué. Entonces es una muy buena idea agregar algunas notas y explicaciones adicionales. Esto se hace anteponiendo una línea con //
, después de lo cual podemos escribir lo que queramos porque el intérprete de macros lo ignorará. Estas notas adicionales se llaman comentarios y las agregaré de ahora en adelante.
Personalización de valores sigma#
Al cambiar el tamaño de los filtros Gaussianos, la macro se puede adaptar para detectar estructuras de diferentes tamaños. Sería relativamente fácil encontrar las líneas de «Desenfoque Gaussiano» y cambiar los valores sigma en consecuencia aquí, pero ajustar configuraciones como esta en macros más largas y complejas puede resultar raro. En tales casos, es útil extraer las configuraciones que desees cambiar e incluirlas al inicio de la macro.
Para hacer esto aquí, inserta las siguientes líneas al principio:
// Store the Gaussian sigma values -
// sigma1 should be less than sigma2
sigma1 = 1.5;
sigma2 = 2;
Luego, actualiza los comandos posteriores a:
run("Gaussian Blur...", "sigma="+sigma1);
selectImage(idOrig);
run("Gaussian Blur...", "sigma="+sigma2);
Esto crea dos nuevas variables, que representan los valores sigma a utilizar. Ahora, cada vez que desees cambiar sigma1
o sigma2
, no necesitas buscar en la macro las líneas correctas: simplemente puedes actualizar las líneas en la parte superior [^fn_8].
Agregando interactividad#
Normalmente me detendría en este punto. Aún así, es posible que desees compartir tu macro con alguien que no tenga tus habilidades de modificación de macros, en cuyo caso sería útil darle a esta persona un cuadro de diálogo en el que pueda escribir los valores sigma gaussianos que desee. Una manera fácil de hacer esto es eliminar la información del valor sigma de las líneas de comando «ejecutar», dando
run("Gaussian Blur...");
Dado que
no sabrá qué tamaño de filtros usar, preguntará. La desventaja de esto es que se solicita al usuario que ingrese valores sigma en dos momentos diferentes mientras se ejecuta la macro, lo cual es un poco más molesto de lo necesario.La alternativa es crear un cuadro de diálogo que solicite todas las configuraciones necesarias de una sola vez. Para hacer esto, actualiza el comienzo de tu macro para incluir algo como lo siguiente:
Dialog.create("Choose DoG filters");
Dialog.addNumber("Gaussian sigma 1", 1);
Dialog.addNumber("Gaussian sigma 2", 2);
Dialog.show();
sigma1 = Dialog.getNumber();
sigma2 = Dialog.getNumber();
La primera línea genera un cuadro de diálogo con el título que especifiques. Cada una de las dos líneas siguientes indica que la entrada requerida por el usuario debe ser un número con las indicaciones especificas y los valores predeterminados. Las otras líneas simplemente muestran el cuadro de diálogo y luego leen lo que el usuario escribió y lo colocan en variables. Esto está documentado en la [lista de funciones macro integradas] de ImageJ (https://imagej.nih.gov/ij/developer/macro/functions.html).
Puedes descargar la macro de ejemplo completa aquí.
Instalación de la macro#
Si deseas que la macro aparezca como una entrada en los menús de ImageJ, tienes un par de opciones.
Uno es el tentador
. Esto funciona, pero cada vez que lo probé descubrí que solo conserva la macro hasta que se reinicia ImageJ.Más útilmente,
Plugins`, y conservarse incluso cuando se reinicia ImageJ.Mejoras sugeridas#
Ahora deberías tener una macro que haga algo vagamente útil y que funcione en la mayoría de las imágenes 2D. Sin embargo, aún podría mejorarse de muchas maneras. Por ejemplo,
Puedes cerrar cualquier imagen no deseada (por ejemplo, el original y su duplicado) seleccionando sus ID y luego insertando comandos
close();
.Podrías hacer que la macro funcione en pilas de imágenes completas. Si deseas que procese cada plano por separado, esto implica solo insertar las palabras «apilar» y «duplicar» en varios lugares; grabando una nueva macro de la misma manera, pero usando una pila como imagen de ejemplo, puedes ver dónde hacer esto. Si deseas que el filtrado se aplique en 3D, puedes utilizar el comando
en lugar dePuedes crear un registro de las imágenes que has procesado, posiblemente incluyendo la configuración utilizada. El registro se genera incluyendo una línea
log(text);
, dondetext
es alguna cadena que tu has creado, p.e.texto = Nombre de la imagen: + getTitle()
.Lo más impresionante es que puedes convertir la macro en un detector de manchas completo estableciendo un umbral en la imagen filtrada de DoG y luego ejecutando el comando
. Si deseas medir las intensidades de los puntos originales, debes recordar ir a para asegurarte de que las medidas se redirigen a la imagen original - que posiblemente deberías haber duplicado al principio. Sin la duplicación, la imagen original habrá sido procesada con un filtro Gaussiano cuando tu macro alcance la etapa de medición.
En cualquier caso, el proceso de desarrollo de una macro suele ser el mismo:
Graba una macro que haga básicamente lo correcto
Elimina todas las líneas superfluas (ajuste de contraste, clicks errantes, etc.)
Reemplaza los títulos de las imágenes con referencias de identificación de imágenes
Agrega comentarios para describir lo que está haciendo la macro.
Localiza errores y realiza mejoras