Systèmes de coordonnées#
Lorsque nous travaillons avec des données d’image 3D, nous parlons souvent de X, Y et Z lors de la description des dimensions de l’image. Selon le logiciel avec lequel on travaille, ces dimensions sont spécifiées un peu différemment. Ce notebook donne un aperçu de deux façons différentes établies dans le domaine. Nous appellerons les deux systèmes le “système ZYX” et le “système 012”. Les bibliothèques Python telles que numpy et scipy suivent le système 012. Les logiciels tels que ImageJ, CLIJ et clesperanto suivent le système ZYX.
Nous commençons par ouvrir une pile d’images 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)
Cette image a une forme de (192, 256, 256). Ces nombres sont triés selon le système 012, qui ne définit pas de termes tels que “largeur”, “hauteur” et “profondeur”. Dans le système ZYX, cette pile d’images a 192 tranches. Chacune de ces tranches fait 256 pixels de haut et 256 pixels de large. Ainsi, le système ZYX interprète les nombres imprimés ci-dessus dans l’ordre inverse.
Nous allons maintenant extraire une tranche Z du jeu de données 3D et la visualiser. Dans le système 012, nous prenons une tranche de la pile dans la première dimension (indice 0). Elle a la position de tranche Z=100 (système ZYX) ou la position 100 le long de la dimension 0 (système 012).
slice = image[100]
cle.imshow(slice)
Translation d’images#
Pour expliquer davantage la différence entre les systèmes de coordonnées, nous allons maintenant utiliser une matrice de transformation affine pour traduire l’image. Nous allons la traduire de 100 pixels dans la direction Y (système ZYX), également connue sous le nom de dimension 1 (système 012) et -50 pixels dans la direction X, alias la dimension 2.
tz = 0
ty = 100
tx = -50
t0 = 0
t1 = 100
t2 = -50
Si vous n’êtes pas encore familier avec les matrices de transformation affine, cet article Wikipedia donne un excellent aperçu.
Translation d’images avec clesperanto#
clesperanto suit le système ZYX, et les matrices de transformation affine sont généralement écrites sous la forme suivante. Veuillez noter que dans le système dit ZYX, le vecteur de translation se lit x-y-z de haut en bas.
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])
Translation d’images avec scipy#
Dans scipy, qui suit le système 012, la transformation ressemble à ceci :
matrix = np.asarray([
[1, 0, 0, t0],
[0, 1, 0, t1],
[0, 0, 1, t2],
[0, 0, 0, 1],
])
Notez que la fonction affine_transform de scipy attend une transformation qui décrit la transformation de l’image de sortie vers l’image source. C’est l’inverse de la matrice de transformation définie ci-dessus. Par conséquent, nous appelons inv() pour inverser la matrice. C’est très courant dans les logiciels qui appliquent des transformations affines. C’est techniquement logique, même si ce n’est peut-être pas la façon la plus intuitive de travailler avec des transformations.
scipy_transformed = ndi.affine_transform(image, inv(matrix))
cle.imshow(scipy_transformed[100])
Gardez ça simple#
Pour garder les transformations affines et les systèmes de coordonnées faciles à utiliser, nous avons une classe AffineTransform3D dans clesperanto qui gérera les matrices de transformation pour nous et nous n’aurons plus à y penser. Nous devons juste garder à l’esprit que X va de gauche à droite, Y va de haut en bas et Z va de l’avant vers l’arrière dans notre pile d’images.
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])
La même chose fonctionne aussi avec la mise à l’échelle et les rotations.
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])
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])
Note : Si vous mettez à l’échelle d’abord et faites pivoter après, ou si vous faites pivoter d’abord et mettez à l’échelle après, cela fait une différence :
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])