@@ -152,6 +152,181 @@ def test_round_trip():
152152 assert_true (streamlist_equal (streams , streams2 ))
153153
154154
155+ def test_points_processing ():
156+ # We may need to process points if they are in voxel or mm format
157+ out_f = BytesIO ()
158+ ijk0 = np .arange (15 ).reshape ((5 ,3 )) / 2.0
159+ ijk1 = ijk0 + 20
160+ vx_streams = [(ijk0 , None , None ), (ijk1 , None , None )]
161+ vxmm_streams = [(ijk0 * [[2 ,3 ,4 ]], None , None ),
162+ (ijk1 * [[2 ,3 ,4 ]], None , None )]
163+ def _rt (streams , hdr , points_space ):
164+ # run round trip through IO object
165+ out_f .seek (0 )
166+ tv .write (out_f , streams , hdr , points_space = points_space )
167+ out_f .seek (0 )
168+ res0 = tv .read (out_f )
169+ out_f .seek (0 )
170+ return res0 , tv .read (out_f , points_space = points_space )
171+ # voxmm is the default. In this case we don't do anything to the points,
172+ # and we let the header pass through without further checks
173+ (raw_streams , hdr ), (proc_streams , _ ) = _rt (vxmm_streams , {}, None )
174+ assert_true (streamlist_equal (raw_streams , proc_streams ))
175+ assert_true (streamlist_equal (vxmm_streams , proc_streams ))
176+ (raw_streams , hdr ), (proc_streams , _ ) = _rt (vxmm_streams , {}, 'voxmm' )
177+ assert_true (streamlist_equal (raw_streams , proc_streams ))
178+ assert_true (streamlist_equal (vxmm_streams , proc_streams ))
179+ # with 'voxels' as input, check for not all voxel_size == 0, warn if any
180+ # voxel_size == 0
181+ for hdr in ( # these cause read / write errors
182+ # empty header has 0 voxel sizes
183+ {},
184+ {'voxel_size' : [0 ,0 ,0 ]}, # the default
185+ {'voxel_size' : [- 2 ,3 ,4 ]}, # negative not valid
186+ ):
187+ # Check error on write
188+ out_f .seek (0 )
189+ assert_raises (tv .HeaderError ,
190+ tv .write , out_f , vx_streams , hdr , None , 'voxel' )
191+ out_f .seek (0 )
192+ # bypass write error and check read
193+ tv .write (out_f , vxmm_streams , hdr , None , points_space = None )
194+ out_f .seek (0 )
195+ assert_raises (tv .HeaderError , tv .read , out_f , False , 'voxel' )
196+ # There's a warning for any voxel sizes == 0
197+ hdr = {'voxel_size' : [2 , 3 , 0 ]}
198+ with ErrorWarnings ():
199+ assert_raises (UserWarning , _rt , vx_streams , hdr , 'voxel' )
200+ # This should be OK
201+ hdr = {'voxel_size' : [2 , 3 , 4 ]}
202+ (raw_streams , hdr ), (proc_streams , _ ) = _rt (vx_streams , hdr , 'voxel' )
203+ assert_true (streamlist_equal (vxmm_streams , raw_streams ))
204+ assert_true (streamlist_equal (vx_streams , proc_streams ))
205+ # Now we try with rasmm points. In this case we need valid voxel_size, and
206+ # voxel_order, and vox_to_ras. The voxel_order has to match the vox_to_ras,
207+ # and so do the voxel sizes
208+ aff = np .diag ([2 ,3 ,4 ,1 ])
209+ # In this case the trk -> vx and vx -> mm invert each other
210+ rasmm_streams = vxmm_streams
211+ for hdr in ( # all these cause read and write errors for rasmm
212+ # Empty header has no valid affine
213+ {},
214+ # Error if ras_to_mm not defined (as in version 1)
215+ {'voxel_size' : [2 , 3 , 4 ], 'voxel_order' : 'RAS' , 'version' :1 },
216+ # or it's all zero
217+ {'voxel_size' : [2 , 3 , 4 ], 'voxel_order' : 'RAS' ,
218+ 'vox_to_ras' : np .zeros ((4 ,4 ))},
219+ # as it is by default
220+ {'voxel_size' : [2 , 3 , 4 ], 'voxel_order' : 'RAS' },
221+ # or the voxel_size doesn't match the affine
222+ {'voxel_size' : [2 , 2 , 4 ], 'voxel_order' : 'RAS' ,
223+ 'vox_to_ras' : aff },
224+ # or the voxel_order doesn't match the affine
225+ {'voxel_size' : [2 , 3 , 4 ], 'voxel_order' : 'LAS' ,
226+ 'vox_to_ras' : aff },
227+ ):
228+ # Check error on write
229+ out_f .seek (0 )
230+ assert_raises (tv .HeaderError ,
231+ tv .write , out_f , rasmm_streams , hdr , None , 'rasmm' )
232+ out_f .seek (0 )
233+ # bypass write error and check read
234+ tv .write (out_f , vxmm_streams , hdr , None , points_space = None )
235+ out_f .seek (0 )
236+ assert_raises (tv .HeaderError , tv .read , out_f , False , 'rasmm' )
237+ # This should be OK
238+ hdr = {'voxel_size' : [2 , 3 , 4 ], 'voxel_order' : 'RAS' ,
239+ 'vox_to_ras' : aff }
240+ (raw_streams , hdr ), (proc_streams , _ ) = _rt (rasmm_streams , hdr , 'rasmm' )
241+ assert_true (streamlist_equal (vxmm_streams , raw_streams ))
242+ assert_true (streamlist_equal (rasmm_streams , proc_streams ))
243+ # More complex test to check matrix orientation
244+ fancy_affine = np .array ([[0. , - 2 , 0 , 10 ],
245+ [3 , 0 , 0 , 20 ],
246+ [0 , 0 , 4 , 30 ],
247+ [0 , 0 , 0 , 1 ]])
248+ hdr = {'voxel_size' : [3 , 2 , 4 ], 'voxel_order' : 'ALS' ,
249+ 'vox_to_ras' : fancy_affine }
250+ def f (pts ): # from vx to mm
251+ pts = pts [:,[1 ,0 ,2 ]] * [[- 2 ,3 ,4 ]] # apply zooms / reorder
252+ return pts + [[10 ,20 ,30 ]] # apply translations
253+ xyz0 , xyz1 = f (ijk0 ), f (ijk1 )
254+ fancy_rasmm_streams = [(xyz0 , None , None ), (xyz1 , None , None )]
255+ fancy_vxmm_streams = [(ijk0 * [[3 ,2 ,4 ]], None , None ),
256+ (ijk1 * [[3 ,2 ,4 ]], None , None )]
257+ (raw_streams , hdr ), (proc_streams , _ ) = _rt (
258+ fancy_rasmm_streams , hdr , 'rasmm' )
259+ assert_true (streamlist_equal (fancy_vxmm_streams , raw_streams ))
260+ assert_true (streamlist_equal (fancy_rasmm_streams , proc_streams ))
261+
262+
263+ def test__check_hdr_points_space ():
264+ # Test checking routine for points_space input given header
265+ # None or voxmm -> no checks, pass through
266+ assert_equal (tv ._check_hdr_points_space ({}, None ), None )
267+ assert_equal (tv ._check_hdr_points_space ({}, 'voxmm' ), None )
268+ # strange value for points_space -> ValueError
269+ assert_raises (ValueError ,
270+ tv ._check_hdr_points_space , {}, 'crazy' )
271+ # Input not in (None, 'voxmm', 'voxels', 'rasmm') - error
272+ # voxels means check voxel sizes present and not all 0.
273+ hdr = tv .empty_header ()
274+ assert_array_equal (hdr ['voxel_size' ], [0 ,0 ,0 ])
275+ assert_raises (tv .HeaderError ,
276+ tv ._check_hdr_points_space , hdr , 'voxel' )
277+ # Negative voxel size gives error - because it is not what trackvis does,
278+ # and this not what we mean by 'voxmm'
279+ hdr ['voxel_size' ] = [- 2 , 3 , 4 ]
280+ assert_raises (tv .HeaderError ,
281+ tv ._check_hdr_points_space , hdr , 'voxel' )
282+ # Warning here only
283+ hdr ['voxel_size' ] = [2 , 3 , 0 ]
284+ with ErrorWarnings ():
285+ assert_raises (UserWarning ,
286+ tv ._check_hdr_points_space , hdr , 'voxel' )
287+ # This is OK
288+ hdr ['voxel_size' ] = [2 , 3 , 4 ]
289+ assert_equal (tv ._check_hdr_points_space (hdr , 'voxel' ), None )
290+ # rasmm - check there is an affine, that it matches voxel_size and
291+ # voxel_order
292+ # no affine
293+ hdr ['voxel_size' ] = [2 , 3 , 4 ]
294+ assert_raises (tv .HeaderError ,
295+ tv ._check_hdr_points_space , hdr , 'rasmm' )
296+ # still no affine
297+ hdr ['voxel_order' ] = 'RAS'
298+ assert_raises (tv .HeaderError ,
299+ tv ._check_hdr_points_space , hdr , 'rasmm' )
300+ # nearly an affine, but 0 at position 3,3 - means not recorded in trackvis
301+ # standard
302+ hdr ['vox_to_ras' ] = np .diag ([2 ,3 ,4 ,0 ])
303+ assert_raises (tv .HeaderError ,
304+ tv ._check_hdr_points_space , hdr , 'rasmm' )
305+ # This affine doesn't match RAS voxel order
306+ hdr ['vox_to_ras' ] = np .diag ([- 2 ,3 ,4 ,1 ])
307+ assert_raises (tv .HeaderError ,
308+ tv ._check_hdr_points_space , hdr , 'rasmm' )
309+ # This affine doesn't match the voxel size
310+ hdr ['vox_to_ras' ] = np .diag ([3 ,3 ,4 ,1 ])
311+ assert_raises (tv .HeaderError ,
312+ tv ._check_hdr_points_space , hdr , 'rasmm' )
313+ # This should be OK
314+ good_aff = np .diag ([2 ,3 ,4 ,1 ])
315+ hdr ['vox_to_ras' ] = good_aff
316+ assert_equal (tv ._check_hdr_points_space (hdr , 'rasmm' ),
317+ None )
318+ # Default voxel order of LPS assumed
319+ hdr ['voxel_order' ] = ''
320+ # now the RAS affine raises an error
321+ assert_raises (tv .HeaderError ,
322+ tv ._check_hdr_points_space , hdr , 'rasmm' )
323+ # this affine does have LPS voxel order
324+ good_lps = np .dot (np .diag ([- 1 ,- 1 ,1 ,1 ]), good_aff )
325+ hdr ['vox_to_ras' ] = good_lps
326+ assert_equal (tv ._check_hdr_points_space (hdr , 'rasmm' ),
327+ None )
328+
329+
155330def test_empty_header ():
156331 for endian in '<>' :
157332 for version in (1 , 2 ):
0 commit comments