@@ -48,14 +48,19 @@ def __init__(self, matrix=None, reference=None):
48
48
49
49
'''
50
50
if matrix is None :
51
- matrix = np .eye (4 )
51
+ matrix = [np .eye (4 )]
52
+
53
+ if np .ndim (matrix ) == 2 :
54
+ matrix = [matrix ]
52
55
53
56
self ._matrix = np .array (matrix )
54
- assert self ._matrix .ndim in ( 2 , 3 ) , 'affine matrix should be 2D or 3D'
55
- assert self ._matrix .shape [0 ] == self ._matrix .shape [1 ], 'affine matrix is not square'
57
+ assert self ._matrix .ndim == 3 , 'affine matrix should be 3D'
58
+ assert self ._matrix .shape [- 2 ] == self ._matrix .shape [- 1 ], 'affine matrix is not square'
56
59
super (Affine , self ).__init__ ()
57
60
58
61
if reference :
62
+ if isinstance (reference , str ):
63
+ reference = loadimg (reference )
59
64
self .reference = reference
60
65
61
66
@property
@@ -116,28 +121,55 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
116
121
file = sys .stderr )
117
122
reference = moving
118
123
124
+ nvols = 1
125
+ if len (moving .shape ) > 3 :
126
+ nvols = moving .shape [3 ]
127
+
128
+ movaff = moving .affine
129
+ movingdata = moving .get_data ()
130
+ if nvols == 1 :
131
+ movingdata = movingdata [..., np .newaxis ]
132
+
133
+ nmats = self ._matrix .shape [0 ]
134
+ if nvols != nmats and nmats > 1 :
135
+ raise ValueError ("""\
136
+ The moving image contains {0} volumes, while the transform is defined for \
137
+ {1} volumes""" .format (nvols , nmats ))
138
+
139
+ singlemat = None
140
+ if nmats == 1 :
141
+ singlemat = np .linalg .inv (movaff ).dot (self ._matrix [0 ].dot (reference .affine ))
142
+
143
+ if singlemat is not None and nvols > nmats :
144
+ print ('Warning: resampling a 4D volume with a single affine matrix' ,
145
+ file = sys .stderr )
146
+
119
147
# Compose an index to index affine matrix
120
- matrix = np .linalg .inv (moving .affine ).dot (self ._matrix .dot (reference .affine ))
121
- mdata = moving .get_data ()
122
- moved = ndi .affine_transform (
123
- mdata , matrix = matrix [:mdata .ndim , :mdata .ndim ],
124
- offset = matrix [:mdata .ndim , mdata .ndim ],
125
- output_shape = reference .shape , order = order , mode = mode ,
126
- cval = cval , prefilter = prefilter )
127
-
128
- moved_image = moving .__class__ (moved , reference .affine , moving .header )
148
+ moved = []
149
+ for i in range (nvols ):
150
+ i2imat = singlemat if singlemat is not None else np .linalg .inv (
151
+ movaff ).dot (self ._matrix [i ].dot (reference .affine ))
152
+ mdata = movingdata [..., i ]
153
+ moved += [ndi .affine_transform (
154
+ mdata , matrix = i2imat [:- 1 , :- 1 ],
155
+ offset = i2imat [:- 1 , - 1 ],
156
+ output_shape = reference .shape , order = order , mode = mode ,
157
+ cval = cval , prefilter = prefilter )]
158
+
159
+ moved_image = moving .__class__ (
160
+ np .squeeze (np .stack (moved , - 1 )), reference .affine , moving .header )
129
161
moved_image .header .set_data_dtype (output_dtype )
130
162
131
163
return moved_image
132
164
133
- def map_point (self , coords , forward = True ):
165
+ def map_point (self , coords , index = 0 , forward = True ):
134
166
coords = np .array (coords )
135
- if coords .shape [0 ] == self ._matrix .shape [0 ] - 1 :
167
+ if coords .shape [0 ] == self ._matrix [ index ] .shape [0 ] - 1 :
136
168
coords = np .append (coords , [1 ])
137
- affine = self ._matrix if forward else np .linalg .inv (self ._matrix )
169
+ affine = self ._matrix [ index ] if forward else np .linalg .inv (self ._matrix [ index ] )
138
170
return affine .dot (coords )[:- 1 ]
139
171
140
- def map_voxel (self , index , moving = None ):
172
+ def map_voxel (self , index , nindex = 0 , moving = None ):
141
173
try :
142
174
reference = self .reference
143
175
except ValueError :
@@ -152,16 +184,16 @@ def map_voxel(self, index, moving=None):
152
184
raise ValueError ('Reference and moving spaces are both undefined' )
153
185
154
186
index = np .array (index )
155
- if index .shape [0 ] == self ._matrix .shape [0 ] - 1 :
187
+ if index .shape [0 ] == self ._matrix [ nindex ] .shape [0 ] - 1 :
156
188
index = np .append (index , [1 ])
157
189
158
- matrix = reference .affine .dot (self ._matrix .dot (np .linalg .inv (moving .affine )))
190
+ matrix = reference .affine .dot (self ._matrix [ nindex ] .dot (np .linalg .inv (moving .affine )))
159
191
return tuple (matrix .dot (index )[:- 1 ])
160
192
161
193
def _to_hdf5 (self , x5_root ):
162
- x5_root .create_dataset ('Transform' , data = self ._matrix [: 3 , :] )
163
- x5_root . create_dataset ( 'Inverse' , data = np . linalg . inv ( self . _matrix )[: 3 , :])
164
- x5_root [ 'Type' ] = 'affine'
194
+ xform = x5_root .create_dataset ('Transform' , data = self ._matrix )
195
+ xform . attrs [ 'Type' ] = 'affine'
196
+ x5_root . create_dataset ( 'Inverse' , data = np . linalg . inv ( self . _matrix ))
165
197
166
198
if self ._reference :
167
199
self .reference ._to_hdf5 (x5_root .create_group ('Reference' ))
@@ -171,24 +203,26 @@ def to_filename(self, filename, fmt='X5', moving=None):
171
203
'''
172
204
173
205
if fmt .lower () in ['itk' , 'ants' , 'elastix' , 'nifty' ]:
174
- parameters = LPS .dot (self .matrix .dot (LPS ))
175
- parameters = parameters [:3 , :3 ].reshape (- 1 ).tolist () + parameters [:3 , 3 ].tolist ()
176
- itkfmt = """\
177
- #Insight Transform File V1.0
178
- #Transform 0
206
+ with open (filename , 'w' ) as f :
207
+ f .write ('#Insight Transform File V1.0\n ' )
208
+
209
+ for i in range (self .matrix .shape [0 ]):
210
+ parameters = LPS .dot (self .matrix [i ].dot (LPS ))
211
+ parameters = parameters [:3 , :3 ].reshape (- 1 ).tolist () + \
212
+ parameters [:3 , 3 ].tolist ()
213
+ itkfmt = """\
214
+ #Transform {0}
179
215
Transform: MatrixOffsetTransformBase_double_3_3
180
- Parameters: {}
216
+ Parameters: {1 }
181
217
FixedParameters: 0 0 0\n """ .format
182
- with open (filename , 'w' ) as f :
183
- f .write (itkfmt (' ' .join (['%g' % p for p in parameters ])))
218
+ f .write (itkfmt (i , ' ' .join (['%g' % p for p in parameters ])))
184
219
return filename
185
220
186
221
if fmt .lower () == 'afni' :
187
- parameters = LPS .dot (self .matrix .dot (LPS ))
188
- parameters = parameters [:3 , :].reshape (- 1 ).tolist ()
189
- np .savetxt (filename , np .atleast_2d (parameters ),
190
- delimiter = '\t ' , header = """\
191
- 3dvolreg matrices (DICOM-to-DICOM, row-by-row):""" )
222
+ parameters = np .swapaxes (LPS .dot (self .matrix .dot (LPS )), 0 , 1 )
223
+ parameters = parameters [:, :3 , :].reshape ((self .matrix .shape [0 ], - 1 ))
224
+ np .savetxt (filename , parameters , delimiter = '\t ' , header = """\
225
+ 3dvolreg matrices (DICOM-to-DICOM, row-by-row):""" , fmt = '%g' )
192
226
return filename
193
227
194
228
if fmt .lower () == 'fsl' :
@@ -209,8 +243,15 @@ def to_filename(self, filename, fmt='X5', moving=None):
209
243
moving .affine )))
210
244
211
245
# Compose FSL transform
212
- mat = np .linalg .inv (post .dot (self .matrix .dot (pre )))
213
- np .savetxt (filename , mat , delimiter = ' ' , fmt = '%g' )
246
+ mat = np .linalg .inv (
247
+ np .swapaxes (post .dot (self .matrix .dot (pre )), 0 , 1 ))
248
+
249
+ if self .matrix .shape [0 ] > 1 :
250
+ for i in range (self .matrix .shape [0 ]):
251
+ np .savetxt ('%s.%03d' % (filename , i ), mat [i ],
252
+ delimiter = ' ' , fmt = '%g' )
253
+ else :
254
+ np .savetxt (filename , mat [0 ], delimiter = ' ' , fmt = '%g' )
214
255
return filename
215
256
return super (Affine , self ).to_filename (filename , fmt = fmt )
216
257
@@ -222,17 +263,29 @@ def load(filename, fmt='X5', reference=None):
222
263
with open (filename ) as itkfile :
223
264
itkxfm = itkfile .read ().splitlines ()
224
265
225
- parameters = np .fromstring (itkxfm [3 ].split (':' )[- 1 ].strip (), dtype = float , sep = ' ' )
226
- offset = np .fromstring (itkxfm [4 ].split (':' )[- 1 ].strip (), dtype = float , sep = ' ' )
227
- if len (parameters ) == 12 :
228
- matrix = from_matvec (parameters [:9 ].reshape ((3 , 3 )), parameters [9 :])
229
- c_neg = from_matvec (np .eye (3 ), offset * - 1.0 )
230
- c_pos = from_matvec (np .eye (3 ), offset )
231
- matrix = LPS .dot (c_pos .dot (matrix .dot (c_neg .dot (LPS ))))
232
-
233
- # if fmt.lower() == 'afni':
266
+ if 'Insight Transform File' not in itkxfm [0 ]:
267
+ raise ValueError (
268
+ "File %s does not look like an ITK transform text file." % filename )
269
+
270
+ matlist = []
271
+ for nxfm in range (len (itkxfm [1 :]) // 4 ):
272
+ parameters = np .fromstring (itkxfm [nxfm * 4 + 3 ].split (':' )[- 1 ].strip (),
273
+ dtype = float , sep = ' ' )
274
+ offset = np .fromstring (itkxfm [nxfm * 4 + 4 ].split (':' )[- 1 ].strip (),
275
+ dtype = float , sep = ' ' )
276
+ if len (parameters ) == 12 :
277
+ matrix = from_matvec (parameters [:9 ].reshape ((3 , 3 )), parameters [9 :])
278
+ c_neg = from_matvec (np .eye (3 ), offset * - 1.0 )
279
+ c_pos = from_matvec (np .eye (3 ), offset )
280
+ matlist .append (LPS .dot (c_pos .dot (matrix .dot (c_neg .dot (LPS )))))
281
+ matrix = np .stack (matlist )
282
+ # elif fmt.lower() == 'afni':
234
283
# parameters = LPS.dot(self.matrix.dot(LPS))
235
284
# parameters = parameters[:3, :].reshape(-1).tolist()
285
+ elif fmt .lower () in ('x5' , 'bids' ):
286
+ raise NotImplementedError
287
+ else :
288
+ raise NotImplementedError
236
289
237
290
if reference and isinstance (reference , str ):
238
291
reference = loadimg (reference )
0 commit comments