@@ -48,14 +48,19 @@ def __init__(self, matrix=None, reference=None):
4848
4949 '''
5050 if matrix is None :
51- matrix = np .eye (4 )
51+ matrix = [np .eye (4 )]
52+
53+ if np .ndim (matrix ) == 2 :
54+ matrix = [matrix ]
5255
5356 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'
5659 super (Affine , self ).__init__ ()
5760
5861 if reference :
62+ if isinstance (reference , str ):
63+ reference = loadimg (reference )
5964 self .reference = reference
6065
6166 @property
@@ -116,28 +121,55 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
116121 file = sys .stderr )
117122 reference = moving
118123
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+
119147 # 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 )
129161 moved_image .header .set_data_dtype (output_dtype )
130162
131163 return moved_image
132164
133- def map_point (self , coords , forward = True ):
165+ def map_point (self , coords , index = 0 , forward = True ):
134166 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 :
136168 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 ] )
138170 return affine .dot (coords )[:- 1 ]
139171
140- def map_voxel (self , index , moving = None ):
172+ def map_voxel (self , index , nindex = 0 , moving = None ):
141173 try :
142174 reference = self .reference
143175 except ValueError :
@@ -152,16 +184,16 @@ def map_voxel(self, index, moving=None):
152184 raise ValueError ('Reference and moving spaces are both undefined' )
153185
154186 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 :
156188 index = np .append (index , [1 ])
157189
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 )))
159191 return tuple (matrix .dot (index )[:- 1 ])
160192
161193 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 ))
165197
166198 if self ._reference :
167199 self .reference ._to_hdf5 (x5_root .create_group ('Reference' ))
@@ -171,24 +203,26 @@ def to_filename(self, filename, fmt='X5', moving=None):
171203 '''
172204
173205 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}
179215Transform: MatrixOffsetTransformBase_double_3_3
180- Parameters: {}
216+ Parameters: {1 }
181217FixedParameters: 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 ])))
184219 return filename
185220
186221 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' )
192226 return filename
193227
194228 if fmt .lower () == 'fsl' :
@@ -209,8 +243,15 @@ def to_filename(self, filename, fmt='X5', moving=None):
209243 moving .affine )))
210244
211245 # 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' )
214255 return filename
215256 return super (Affine , self ).to_filename (filename , fmt = fmt )
216257
@@ -222,17 +263,29 @@ def load(filename, fmt='X5', reference=None):
222263 with open (filename ) as itkfile :
223264 itkxfm = itkfile .read ().splitlines ()
224265
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':
234283 # parameters = LPS.dot(self.matrix.dot(LPS))
235284 # parameters = parameters[:3, :].reshape(-1).tolist()
285+ elif fmt .lower () in ('x5' , 'bids' ):
286+ raise NotImplementedError
287+ else :
288+ raise NotImplementedError
236289
237290 if reference and isinstance (reference , str ):
238291 reference = loadimg (reference )
0 commit comments