Skip to content

Commit 6e4a144

Browse files
committed
Merge pull request #332 from effigies/freesurfer4d
MRG: FreeSurfer nifti surfaces can have >3 dimensions Quick workaround for https://mail.python.org/pipermail/neuroimaging/2015-July/000132.html. This uses the FreeSurfer hacks on any shapes beginning with (-1, 1, 1) or (27307, 1, 6), which seems fairly safe, but we can restrict down to 4-dimensional, or possibly only allow (-1, 1, 1, 1) or (27307, 1, 6, 1) in the 4D case.
2 parents e471f8c + 24431eb commit 6e4a144

File tree

2 files changed

+75
-31
lines changed

2 files changed

+75
-31
lines changed

nibabel/nifti1.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -693,27 +693,23 @@ def get_data_shape(self):
693693
694694
Notes
695695
-----
696-
Allows for freesurfer hack for large vectors described in
697-
https://github.com/nipy/nibabel/issues/100 and
698-
https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn5022&r=5022#77
696+
Applies freesurfer hack for large vectors described in `issue 100`_ and
697+
`save_nifti.m <save77_>`_.
699698
700699
Allows for freesurfer hack for 7th order icosahedron surface described
701-
in
702-
https://github.com/nipy/nibabel/issues/309
703-
https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/load_nifti.m?r=8776#86
704-
https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?r=8776#50
700+
in `issue 309`_, load_nifti.m_, and `save_nifti.m <save50_>`_.
705701
'''
706702
shape = super(Nifti1Header, self).get_data_shape()
707-
# Apply freesurfer hack for vector
708-
if shape == (-1, 1, 1):
703+
# Apply freesurfer hack for large vectors
704+
if shape[:3] == (-1, 1, 1):
709705
vec_len = int(self._structarr['glmin'])
710706
if vec_len == 0:
711707
raise HeaderDataError('-1 in dim[1] but 0 in glmin; '
712708
'inconsistent freesurfer type header?')
713-
return (vec_len, 1, 1)
709+
return (vec_len, 1, 1) + shape[3:]
714710
# Apply freesurfer hack for ico7 surface
715-
elif shape == (27307, 1, 6):
716-
return (163842, 1, 1)
711+
elif shape[:3] == (27307, 1, 6):
712+
return (163842, 1, 1) + shape[3:]
717713
else: # Normal case
718714
return shape
719715

@@ -723,31 +719,56 @@ def set_data_shape(self, shape):
723719
If ``ndims == len(shape)`` then we set zooms for dimensions higher than
724720
``ndims`` to 1.0
725721
722+
Nifti1 images can have up to seven dimensions. For FreeSurfer-variant
723+
Nifti surface files, the first dimension is assumed to correspond to
724+
vertices/nodes on a surface, and dimensions two and three are
725+
constrained to have depth of 1. Dimensions 4-7 are constrained only by
726+
type bounds.
727+
726728
Parameters
727729
----------
728730
shape : sequence
729731
sequence of integers specifying data array shape
730732
731733
Notes
732734
-----
733-
Applies freesurfer hack for large vectors described in
734-
https://github.com/nipy/nibabel/issues/100 and
735-
https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn5022&r=5022#77
735+
Applies freesurfer hack for large vectors described in `issue 100`_ and
736+
`save_nifti.m <save77_>`_.
736737
737738
Allows for freesurfer hack for 7th order icosahedron surface described
738-
in
739-
https://github.com/nipy/nibabel/issues/309
740-
https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/load_nifti.m?r=8776#86
741-
https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?r=8776#50
739+
in `issue 309`_, load_nifti.m_, and `save_nifti.m <save50_>`_.
740+
741+
The Nifti1 `standard header`_ allows for the following "point set"
742+
definition of a surface, not currently implemented in nibabel.
743+
744+
::
745+
746+
To signify that the vector value at each voxel is really a
747+
spatial coordinate (e.g., the vertices or nodes of a surface mesh):
748+
- dataset must have a 5th dimension
749+
- intent_code must be NIFTI_INTENT_POINTSET
750+
- dim[0] = 5
751+
- dim[1] = number of points
752+
- dim[2] = dim[3] = dim[4] = 1
753+
- dim[5] must be the dimensionality of space (e.g., 3 => 3D space).
754+
- intent_name may describe the object these points come from
755+
(e.g., "pial", "gray/white" , "EEG", "MEG").
756+
757+
.. _issue 100: https://github.com/nipy/nibabel/issues/100
758+
.. _issue 309: https://github.com/nipy/nibabel/issues/309
759+
.. _save77: https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn8776&r=8776#77
760+
.. _save50: https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn8776&r=8776#50
761+
.. _load_nifti.m: https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/load_nifti.m?spec=svn8776&r=8776#86
762+
.. _standard header: http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h
742763
'''
743764
hdr = self._structarr
744765
shape = tuple(shape)
745766

746767
# Apply freesurfer hack for ico7 surface
747-
if shape == (163842, 1, 1):
748-
shape = (27307, 1, 6)
749-
# Apply freesurfer hack for vector
750-
elif (len(shape) == 3 and shape[1:] == (1, 1) and
768+
if shape[:3] == (163842, 1, 1):
769+
shape = (27307, 1, 6) + shape[3:]
770+
# Apply freesurfer hack for large vectors
771+
elif (len(shape) >= 3 and shape[1:3] == (1, 1) and
751772
shape[0] > np.iinfo(hdr['dim'].dtype.base).max):
752773
try:
753774
hdr['glmin'] = shape[0]
@@ -760,7 +781,7 @@ def set_data_shape(self, shape):
760781
'datatype' % shape[0])
761782
warnings.warn('Using large vector Freesurfer hack; header will '
762783
'not be compatible with SPM or FSL', stacklevel=2)
763-
shape = (-1, 1, 1)
784+
shape = (-1, 1, 1) + shape[3:]
764785
super(Nifti1Header, self).set_data_shape(shape)
765786

766787
def get_qform_quaternion(self):

nibabel/tests/test_nifti1.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,19 +261,29 @@ def test_freesurfer_large_vector_hack(self):
261261
hdr.set_data_shape((too_big-1, 1, 1))
262262
assert_equal(hdr.get_data_shape(), (too_big-1, 1, 1))
263263
# The freesurfer case
264+
full_shape = (too_big, 1, 1, 1, 1, 1, 1)
265+
for dim in range(3, 8):
266+
# First element in 'dim' field is number of dimensions
267+
expected_dim = np.array([dim, -1, 1, 1, 1, 1, 1, 1])
268+
with suppress_warnings():
269+
hdr.set_data_shape(full_shape[:dim])
270+
assert_equal(hdr.get_data_shape(), full_shape[:dim])
271+
assert_array_equal(hdr['dim'], expected_dim)
272+
assert_equal(hdr['glmin'], too_big)
273+
# Allow the fourth dimension to vary
264274
with suppress_warnings():
265-
hdr.set_data_shape((too_big, 1, 1))
266-
assert_equal(hdr.get_data_shape(), (too_big, 1, 1))
267-
assert_array_equal(hdr['dim'][:4], [3, -1, 1, 1])
268-
assert_equal(hdr['glmin'], too_big)
269-
# This only works for the case of a 3D with -1, 1, 1
275+
hdr.set_data_shape((too_big, 1, 1, 4))
276+
assert_equal(hdr.get_data_shape(), (too_big, 1, 1, 4))
277+
assert_array_equal(hdr['dim'][:5], np.array([4, -1, 1, 1, 4]))
278+
# This only works when the first 3 dimensions are -1, 1, 1
270279
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,))
271280
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,1))
272281
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,1,2))
273282
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,2,1))
274283
assert_raises(HeaderDataError, hdr.set_data_shape, (1, too_big))
275284
assert_raises(HeaderDataError, hdr.set_data_shape, (1, too_big, 1))
276285
assert_raises(HeaderDataError, hdr.set_data_shape, (1, 1, too_big))
286+
assert_raises(HeaderDataError, hdr.set_data_shape, (1, 1, 1, too_big))
277287
# Outside range of glmin raises error
278288
far_too_big = int(np.iinfo(glmin).max) + 1
279289
with suppress_warnings():
@@ -295,9 +305,22 @@ def test_freesurfer_large_vector_hack(self):
295305
def test_freesurfer_ico7_hack(self):
296306
HC = self.header_class
297307
hdr = HC()
308+
full_shape = (163842, 1, 1, 1, 1, 1, 1)
298309
# Test that using ico7 shape automatically uses factored dimensions
299-
hdr.set_data_shape((163842, 1, 1))
300-
assert_array_equal(hdr._structarr['dim'][1:4], np.array([27307, 1, 6]))
310+
for dim in range(3, 8):
311+
expected_dim = np.array([dim, 27307, 1, 6, 1, 1, 1, 1])
312+
hdr.set_data_shape(full_shape[:dim])
313+
assert_equal(hdr.get_data_shape(), full_shape[:dim])
314+
assert_array_equal(hdr._structarr['dim'], expected_dim)
315+
# Only works on dimensions >= 3
316+
assert_raises(HeaderDataError, hdr.set_data_shape, full_shape[:1])
317+
assert_raises(HeaderDataError, hdr.set_data_shape, full_shape[:2])
318+
# Bad shapes
319+
assert_raises(HeaderDataError, hdr.set_data_shape, (163842, 2, 1))
320+
assert_raises(HeaderDataError, hdr.set_data_shape, (163842, 1, 2))
321+
assert_raises(HeaderDataError, hdr.set_data_shape, (1, 163842, 1))
322+
assert_raises(HeaderDataError, hdr.set_data_shape, (1, 1, 163842))
323+
assert_raises(HeaderDataError, hdr.set_data_shape, (1, 1, 1, 163842))
301324
# Test consistency of data in .mgh and mri_convert produced .nii
302325
nitest_path = os.path.join(get_nibabel_data(), 'nitest-freesurfer')
303326
mgh = mghload(os.path.join(nitest_path, 'fsaverage', 'surf',

0 commit comments

Comments
 (0)