24
24
class Affine (TransformBase ):
25
25
"""Represents linear transforms on image data."""
26
26
27
- __slots__ = ("_matrix" , )
27
+ __slots__ = ("_matrix" , "_inverse" )
28
28
29
29
def __init__ (self , matrix = None , reference = None ):
30
30
"""
@@ -57,6 +57,7 @@ def __init__(self, matrix=None, reference=None):
57
57
"""
58
58
super ().__init__ (reference = reference )
59
59
self ._matrix = np .eye (4 )
60
+ self ._inverse = np .eye (4 )
60
61
61
62
if matrix is not None :
62
63
matrix = np .array (matrix )
@@ -72,6 +73,7 @@ def __init__(self, matrix=None, reference=None):
72
73
73
74
# Normalize last row
74
75
self ._matrix [3 , :] = (0 , 0 , 0 , 1 )
76
+ self ._inverse = np .linalg .inv (self ._matrix )
75
77
76
78
def __eq__ (self , other ):
77
79
"""
@@ -90,6 +92,44 @@ def __eq__(self, other):
90
92
warnings .warn ("Affines are equal, but references do not match." )
91
93
return _eq
92
94
95
+ def __invert__ (self ):
96
+ """
97
+ Get the inverse of this transform.
98
+
99
+ Example
100
+ -------
101
+ >>> matrix = [[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
102
+ >>> Affine(np.linalg.inv(matrix)) == ~Affine(matrix)
103
+ True
104
+
105
+ """
106
+ return self .__class__ (self ._inverse )
107
+
108
+ def __matmul__ (self , b ):
109
+ """
110
+ Compose two Affines.
111
+
112
+ Example
113
+ -------
114
+ >>> xfm1 = Affine([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
115
+ >>> xfm1 @ ~xfm1 == Affine()
116
+ True
117
+
118
+ >>> xfm1 = Affine([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
119
+ >>> xfm1 @ np.eye(4) == xfm1
120
+ True
121
+
122
+ """
123
+ if not isinstance (b , self .__class__ ):
124
+ _b = self .__class__ (b )
125
+ else :
126
+ _b = b
127
+
128
+ retval = self .__class__ (self .matrix .dot (_b .matrix ))
129
+ if _b .reference :
130
+ retval .reference = _b .reference
131
+ return retval
132
+
93
133
@property
94
134
def matrix (self ):
95
135
"""Access the internal representation of this affine."""
@@ -124,14 +164,14 @@ def map(self, x, inverse=False):
124
164
affine = self ._matrix
125
165
coords = _as_homogeneous (x , dim = affine .shape [0 ] - 1 ).T
126
166
if inverse is True :
127
- affine = np . linalg . inv ( self ._matrix )
167
+ affine = self ._inverse
128
168
return affine .dot (coords ).T [..., :- 1 ]
129
169
130
170
def _to_hdf5 (self , x5_root ):
131
171
"""Serialize this object into the x5 file format."""
132
172
xform = x5_root .create_dataset ("Transform" , data = [self ._matrix ])
133
173
xform .attrs ["Type" ] = "affine"
134
- x5_root .create_dataset ("Inverse" , data = [np . linalg . inv ( self . _matrix ) ])
174
+ x5_root .create_dataset ("Inverse" , data = [( ~ self ). matrix ])
135
175
136
176
if self ._reference :
137
177
self .reference ._to_hdf5 (x5_root .create_group ("Reference" ))
@@ -175,7 +215,7 @@ def to_filename(self, filename, fmt="X5", moving=None):
175
215
lt ["dst" ] = io .VolumeGeometry .from_image (moving )
176
216
# However, the affine needs to be inverted
177
217
# (i.e., it is not a pure "points" convention).
178
- lt ["m_L" ] = np . linalg . inv ( self .matrix )
218
+ lt ["m_L" ] = ( ~ self ) .matrix
179
219
# to make LTA file format
180
220
lta = io .LinearTransformArray ()
181
221
lta ["type" ] = 1 # RAS2RAS
@@ -234,6 +274,11 @@ def __init__(self, transforms, reference=None):
234
274
[0., 1., 0., 2.],
235
275
[0., 0., 1., 3.],
236
276
[0., 0., 0., 1.]])
277
+ >>> (~xfm)[0].matrix # doctest: +NORMALIZE_WHITESPACE
278
+ array([[ 1., 0., 0., -1.],
279
+ [ 0., 1., 0., -2.],
280
+ [ 0., 0., 1., -3.],
281
+ [ 0., 0., 0., 1.]])
237
282
238
283
"""
239
284
super ().__init__ (reference = reference )
@@ -245,6 +290,7 @@ def __init__(self, transforms, reference=None):
245
290
).matrix
246
291
for xfm in transforms
247
292
], axis = 0 )
293
+ self ._inverse = np .linalg .inv (self ._matrix )
248
294
249
295
def __getitem__ (self , i ):
250
296
"""Enable indexed access to the series of matrices."""
@@ -304,7 +350,7 @@ def map(self, x, inverse=False):
304
350
affine = self .matrix
305
351
coords = _as_homogeneous (x , dim = affine .shape [- 1 ] - 1 ).T
306
352
if inverse is True :
307
- affine = np . linalg . inv ( affine )
353
+ affine = self . _inverse
308
354
return np .swapaxes (affine .dot (coords ), 1 , 2 )
309
355
310
356
def to_filename (self , filename , fmt = "X5" , moving = None ):
0 commit comments