Sistemas de coordenadas#

Cuando trabajamos con datos de imágenes 3D, a menudo hablamos de X, Y y Z al describir las dimensiones de la imagen. Dependiendo del software con el que se trabaje, estas dimensiones se especifican de manera un poco diferente. Este cuaderno proporciona una visión general de dos formas diferentes establecidas en el campo. Llamaremos a los dos sistemas el “sistema ZYX” y el “sistema 012”. Las bibliotecas de Python como numpy y scipy siguen el sistema 012. Software como ImageJ, CLIJ y clesperanto siguen el sistema ZYX.

Comenzamos abriendo una pila de imágenes 3D.

import numpy as np
from skimage.io import imread
import scipy.ndimage as ndi
import pyclesperanto_prototype as cle
from scipy.linalg import inv
image = imread('../../data/Haase_MRT_tfl3d1.tif')

image.shape
(192, 256, 256)

Esta imagen tiene una forma de (192, 256, 256). Estos números están ordenados según el sistema 012, que no define términos como “ancho”, “alto” y “profundidad”. En el sistema ZYX, esta pila de imágenes tiene 192 cortes. Cada uno de estos cortes tiene 256 píxeles de alto y 256 píxeles de ancho. Por lo tanto, el sistema ZYX interpreta los números impresos arriba en el orden opuesto.

Ahora extraeremos un corte Z del conjunto de datos 3D y lo visualizaremos. En el sistema 012, tomamos un corte de la pila en la primera dimensión (índice 0). Tiene la posición de corte Z=100 (sistema ZYX) o posición 100 a lo largo de la dimensión 0 (sistema 012).

slice = image[100]

cle.imshow(slice)
../_images/f6f425b4271ad0bac2c89313d02cd9f54f3e1995d56b969a09f638a34e031aeb.png

Traslación de imágenes#

Para explicar un poco más la diferencia entre los sistemas de coordenadas, ahora usaremos una matriz de transformación afín para trasladar la imagen. La trasladaremos 100 píxeles en la dirección Y (sistema ZYX), también conocida como dimensión 1 (sistema 012) y -50 píxeles en la dirección X, también conocida como dimensión 2.

tz = 0
ty = 100
tx = -50

t0 = 0
t1 = 100
t2 = -50

Si no estás familiarizado con las matrices de transformación afín todavía, este artículo de Wikipedia ofrece una excelente visión general.

Traslación de imágenes usando clesperanto#

clesperanto sigue el sistema ZYX, y las matrices de transformación afín se escriben típicamente de la siguiente forma. Ten en cuenta que en el llamado sistema ZYX, el vector de traslación se lee x-y-z de arriba a abajo.

matrix = np.asarray([
 [1, 0, 0, tx],
 [0, 1, 0, ty],
 [0, 0, 1, tz],
 [0, 0, 0, 1],
])

cle_transformed = cle.affine_transform(image, transform=matrix)

cle.imshow(cle_transformed[100])
../_images/2558ce5b9dfff672643aa2ede7e688927ce7b5cc65ea635eefbda0d61927ee19.png

Traslación de imágenes usando scipy#

En scipy, que sigue el sistema 012, la transformación se ve así:

matrix = np.asarray([
 [1, 0, 0, t0],
 [0, 1, 0, t1],
 [0, 0, 1, t2],
 [0, 0, 0, 1],
])

Ten en cuenta que la función affine_transform en scipy espera una transformación que describa la transformación de la imagen de salida a la imagen de origen. Esta es la inversa de la matriz de transformación definida arriba. Por lo tanto, llamamos a inv() para invertir la matriz. Esto es muy común en software que aplica transformaciones afines. Técnicamente tiene sentido, aunque puede que no sea la forma más intuitiva de trabajar con transformaciones.

scipy_transformed = ndi.affine_transform(image, inv(matrix))

cle.imshow(scipy_transformed[100])
../_images/2558ce5b9dfff672643aa2ede7e688927ce7b5cc65ea635eefbda0d61927ee19.png

Mantenlo simple#

Para mantener las transformaciones afines y los sistemas de coordenadas fáciles de usar, tenemos una clase AffineTransform3D en clesperanto que gestionará las matrices de transformación por nosotros y no tendremos que pensar más en ellas. Solo necesitamos tener en cuenta que X va de izquierda a derecha, Y va de arriba a abajo y Z va de adelante hacia atrás en nuestra pila de imágenes.

transform = cle.AffineTransform3D()
transform.translate(
    translate_x=tx,
    translate_y=ty,
    translate_z=tz
)

cle_translated2 = cle.affine_transform(image, transform=transform)

cle.imshow(cle_translated2[100])
../_images/2558ce5b9dfff672643aa2ede7e688927ce7b5cc65ea635eefbda0d61927ee19.png

Lo mismo también funciona con escalado y rotaciones.

scale_factor = 2

transform = cle.AffineTransform3D()
transform.scale(scale_x=scale_factor)

cle_translated2 = cle.affine_transform(image, transform=transform, auto_size=True)

cle.imshow(cle_translated2[100])
../_images/5237b68192ba9f6cbdb6421d22065c38df6083aef60f17209ed6f563d02cb166.png
scale_factor = 2
rotation_angle = 45

transform = cle.AffineTransform3D()

transform.scale(scale_x=scale_factor)
transform.rotate_around_z_axis(rotation_angle)

cle_translated2 = cle.affine_transform(image, transform=transform, auto_size=True)

cle.imshow(cle_translated2[100])
../_images/92b6e9e5c6389642d49eb8b3041f2dbd32ac58c4c1bd67c2c022f00b5670f53c.png

Nota: Si escalas primero y rotas después, o si rotas primero y escalas después, hace una diferencia:

transform = cle.AffineTransform3D()

transform.rotate_around_z_axis(rotation_angle)
transform.scale(scale_x=scale_factor)

cle_translated2 = cle.affine_transform(image, transform=transform, auto_size=True)

cle.imshow(cle_translated2[100])
../_images/7122df885a419152f378af4608c28548c76db6abe4ca16b6008988e0c0d189b1.png