Skip to content

Commit 1f820ef

Browse files
committed
RF - options to read / write voxel and mm points
1 parent e13ec29 commit 1f820ef

File tree

2 files changed

+342
-27
lines changed

2 files changed

+342
-27
lines changed

nibabel/tests/test_trackvis.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
155330
def test_empty_header():
156331
for endian in '<>':
157332
for version in (1, 2):

0 commit comments

Comments
 (0)