@@ -270,6 +270,43 @@ def __init__(self, fileobj, header):
270270
271271 self .file .write (self .header .tostring ())
272272
273+ # `Tractogram` streamlines are in RAS+ and mm space, we will compute
274+ # the affine matrix that will bring them back to 'voxelmm' as required
275+ # by the TRK format.
276+ affine = np .eye (4 )
277+
278+ # Applied the inverse of the affine found in the TRK header.
279+ # rasmm -> voxel
280+ affine = np .dot (np .linalg .inv (self .header [Field .VOXEL_TO_RASMM ]),
281+ affine )
282+
283+ # If the voxel order implied by the affine does not match the voxel
284+ # order in the TRK header, change the orientation.
285+ # voxel (affine) -> voxel (header)
286+ header_ornt = asstr (self .header [Field .VOXEL_ORDER ])
287+ affine_ornt = "" .join (aff2axcodes (self .header [Field .VOXEL_TO_RASMM ]))
288+ header_ornt = axcodes2ornt (header_ornt )
289+ affine_ornt = axcodes2ornt (affine_ornt )
290+ ornt = nib .orientations .ornt_transform (affine_ornt , header_ornt )
291+ M = nib .orientations .inv_ornt_aff (ornt , self .header [Field .DIMENSIONS ])
292+ affine = np .dot (M , affine )
293+
294+ # TrackVis considers coordinate (0,0,0) to be the corner of the
295+ # voxel whereas `Tractogram` streamlines assume (0,0,0) is the
296+ # center of the voxel. Thus, streamlines are shifted of half a voxel.
297+ offset = np .eye (4 )
298+ offset [:- 1 , - 1 ] += 0.5
299+ affine = np .dot (offset , affine )
300+
301+ # Finally send the streamlines in mm space.
302+ # voxel -> voxelmm
303+ scale = np .eye (4 )
304+ scale [range (3 ), range (3 )] *= self .header [Field .VOXEL_SIZES ]
305+ affine = np .dot (scale , affine )
306+
307+ # The TRK format uses float32 as the data type for points.
308+ self ._affine_rasmm_to_voxmm = affine .astype (np .float32 )
309+
273310 def write (self , tractogram ):
274311 i4_dtype = np .dtype ("<i4" ) # Always save in little-endian.
275312 f4_dtype = np .dtype ("<f4" ) # Always save in little-endian.
@@ -350,50 +387,17 @@ def write(self, tractogram):
350387
351388 self .header ['scalar_name' ][i ] = scalar_name
352389
353- # `Tractogram` streamlines are in RAS+ and mm space, we will compute
354- # the affine matrix that will bring them back to 'voxelmm' as required
355- # by the TRK format.
356- affine = np .eye (4 )
357-
358- # Applied the inverse of the affine found in the TRK header.
359- # rasmm -> voxel
360- affine = np .dot (np .linalg .inv (self .header [Field .VOXEL_TO_RASMM ]),
361- affine )
362-
363- # If the voxel order implied by the affine does not match the voxel
364- # order in the TRK header, change the orientation.
365- # voxel (affine) -> voxel (header)
366- header_ornt = asstr (self .header [Field .VOXEL_ORDER ])
367- affine_ornt = "" .join (aff2axcodes (self .header [Field .VOXEL_TO_RASMM ]))
368- header_ornt = axcodes2ornt (header_ornt )
369- affine_ornt = axcodes2ornt (affine_ornt )
370- ornt = nib .orientations .ornt_transform (affine_ornt , header_ornt )
371- M = nib .orientations .inv_ornt_aff (ornt , self .header [Field .DIMENSIONS ])
372- affine = np .dot (M , affine )
373-
374- # TrackVis considers coordinate (0,0,0) to be the corner of the
375- # voxel whereas `Tractogram` streamlines assume (0,0,0) is the
376- # center of the voxel. Thus, streamlines are shifted of half a voxel.
377- offset = np .eye (4 )
378- offset [:- 1 , - 1 ] += 0.5
379- affine = np .dot (offset , affine )
380-
381- # Finally send the streamlines in mm space.
382- # voxel -> voxelmm
383- scale = np .eye (4 )
384- scale [range (3 ), range (3 )] *= self .header [Field .VOXEL_SIZES ]
385- affine = np .dot (scale , affine )
386-
387- # The TRK format uses float32 as the data type for points.
388- affine = affine .astype (np .float32 )
390+ # Make sure streamlines are in rasmm then send them to voxmm.
391+ tractogram = tractogram .to_world (lazy = True )
392+ tractogram = tractogram .apply_affine (self ._affine_rasmm_to_voxmm ,
393+ lazy = True )
389394
390395 for t in tractogram :
391396 if any ((len (d ) != len (t .streamline )
392397 for d in t .data_for_points .values ())):
393398 raise DataError ("Missing scalars for some points!" )
394399
395- points = apply_affine (affine ,
396- np .asarray (t .streamline , dtype = f4_dtype ))
400+ points = np .asarray (t .streamline , dtype = f4_dtype )
397401 scalars = [np .asarray (t .data_for_points [k ], dtype = f4_dtype )
398402 for k in data_for_points_keys ]
399403 scalars = np .concatenate ([np .ndarray ((len (points ), 0 ),
@@ -747,8 +751,9 @@ def _read():
747751 for name , slice_ in data_per_streamline_slice .items ():
748752 tractogram .data_per_streamline [name ] = properties [:, slice_ ]
749753
750- # Bring tractogram to RAS+ and mm space
751- tractogram .apply_affine (affine .astype (np .float32 ))
754+ # Bring tractogram to RAS+ and mm space.
755+ tractogram = tractogram .apply_affine (affine .astype (np .float32 ))
756+ tractogram ._affine_to_rasmm = np .eye (4 )
752757
753758 return cls (tractogram , header = hdr )
754759
0 commit comments