Creación de mosaicos de imágenes - el enfoque ingenuo#

En el procesamiento de imágenes en mosaico, el primer paso es cortar la imagen en baldosas. Aunque esto podría hacerse con numpy, usaremos dask porque viene con múltiples características muy útiles en este contexto.

import dask
import dask.array as da
from skimage.filters import gaussian
from skimage.data import cells3d
from pyclesperanto_prototype import imshow

En el primer ejemplo, usaremos una imagen que muestra núcleos de microscopía de fluorescencia y simplemente eliminaremos el ruido de la imagen usando un desenfoque gaussiano. Haremos esto baldosa por baldosa. Para ello, primero definimos el procedimiento que debe aplicarse a todas las baldosas. Incorporamos una declaración de impresión en esta función para cuando se ejecute y cuán grande es la imagen que se está procesando.

def procedure(image):
    print("proceduring", image.shape)
    return gaussian(image, sigma=5)
image = cells3d()[30,1]
imshow(image)
../_images/e6d2393f7c605c769fb82b772552ade1566917e2a294f57aa9e4f747b0c78793.png

Después de cargar la imagen, se puede crear un mosaico de esta manera. En dask, las baldosas también se llaman chunks.

tiles = da.from_array(image, chunks=(128, 128))
tiles
Array Chunk
Bytes 128.00 kiB 32.00 kiB
Shape (256, 256) (128, 128)
Count 4 Tasks 4 Chunks
Type uint16 numpy.ndarray
256 256

A continuación, le decimos a dask qué hacer con nuestras baldosas. Queremos mapear la función procedure en todas las baldosas individuales. Ten en cuenta que esto aún no procesa toda la imagen.

tile_map = da.map_blocks(procedure, tiles)
proceduring (0, 0)
proceduring (1, 1)

Como podemos leer, la función se ejecutó dos veces con imágenes muy pequeñas (0x0 y 1x1 píxeles). Dask hace eso en principio para explorar si la función funciona. A continuación, ejecutaremos realmente nuestro procedimiento en las baldosas de la imagen.

result = tile_map.compute() # Advertencia: Esto carga todos los datos de la imagen en la memoria
proceduringproceduring (128, 128)
 (128, 128)
proceduring (128, 128)
proceduring (128, 128)

La salida impresa parece un poco caótica porque dask ejecutó el procedimiento en múltiples baldosas en paralelo. Si inspeccionamos el resultado, veremos que vuelve a ser una imagen.

result.shape
(256, 256)
type(result)
numpy.ndarray

Nota: La función imshow puede no funcionar en conjuntos de datos grandes. La estamos usando aquí con fines de demostración.

imshow(result)
../_images/7b0a8ba73291ddde763b05183cfdf2a3d3557bede55f4d5dd6dd35a5022f1c2e.png

Efectos de borde#

Cuando procesamos imágenes baldosa por baldosa, siempre debemos asumir que a lo largo del borde aparecen artefactos que resultan de cortar la imagen en baldosas. Como nuestra imagen de ejemplo cabe en la memoria, podemos aplicar procedure a ella y compararla con el resultado del procesamiento de imagen en mosaico

untiled_result = procedure(image)
imshow(untiled_result)
proceduring (256, 256)
../_images/b31924a9f3cf423d048676a3c5ccca6648eb37f59f08ecc030e068593386819d.png

Las diferencias no son obvias, pero podemos visualizarlas.

difference = result - untiled_result
imshow(difference)
../_images/e4126918c50e4b551767159a05ace9b17cb60db4891f34ce4000369b3cfe9c54.png

Al aplicar un desenfoque gaussiano con un sigma pequeño, estos efectos pueden ser insignificantes. En caso de que los efectos causen problemas graves en nuestro flujo de trabajo de procesamiento de imágenes, es posible que queramos reducir o incluso prevenir esos artefactos.