Skip to content

Commit 4cc9d55

Browse files
committed
Merge pull request #101 from matthew-brett/freesurfer-hack
Freesurfer large vector hack Add hack to allow Freesurfer files that put large vector sizes in glmin. Alexandre Gramfort spotted files using this hack.
2 parents 953ea8b + 880bdaa commit 4cc9d55

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

nibabel/analyze.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ def set_data_shape(self, shape):
626626
# Check that dimensions fit
627627
if not np.all(dims[1:ndims+1] == shape):
628628
raise HeaderDataError('shape %s does not fit in dim datatype' %
629-
shape)
629+
(shape,))
630630
self._structarr['pixdim'][ndims+1:] = 1.0
631631

632632
def get_base_affine(self):
@@ -974,7 +974,7 @@ def update_header(self):
974974
# We need to update the header if the data shape has changed. It's a
975975
# bit difficult to change the data shape using the standard API, but
976976
# maybe it happened
977-
if not self._data is None:
977+
if not self._data is None and hdr.get_data_shape() != self._data.shape:
978978
hdr.set_data_shape(self._data.shape)
979979
# If the affine is not None, and it is different from the main affine in
980980
# the header, update the heaader

nibabel/nifti1.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
99
''' Header reading / writing functions for nifti1 image format
1010
'''
11+
import warnings
12+
1113
import numpy as np
1214
import numpy.linalg as npl
1315

@@ -620,6 +622,75 @@ def default_structarr(klass, endianness=None):
620622
hdr_data['vox_offset'] = 0
621623
return hdr_data
622624

625+
def get_data_shape(self):
626+
''' Get shape of data
627+
628+
Examples
629+
--------
630+
>>> hdr = Nifti1Header()
631+
>>> hdr.get_data_shape()
632+
(0,)
633+
>>> hdr.set_data_shape((1,2,3))
634+
>>> hdr.get_data_shape()
635+
(1, 2, 3)
636+
637+
Expanding number of dimensions gets default zooms
638+
639+
>>> hdr.get_zooms()
640+
(1.0, 1.0, 1.0)
641+
642+
Notes
643+
-----
644+
Allows for freesurfer hack for large vectors described in
645+
https://github.com/nipy/nibabel/issues/100 and
646+
http://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn5022&r=5022#77
647+
'''
648+
shape = super(Nifti1Header, self).get_data_shape()
649+
# Apply freesurfer hack for vector
650+
if shape != (-1, 1, 1): # Normal case
651+
return shape
652+
vec_len = int(self._structarr['glmin'])
653+
if vec_len == 0:
654+
raise HeaderDataError('-1 in dim[1] but 0 in glmin; inconsistent '
655+
'freesurfer type header?')
656+
return (vec_len, 1, 1)
657+
658+
def set_data_shape(self, shape):
659+
''' Set shape of data
660+
661+
If ``ndims == len(shape)`` then we set zooms for dimensions higher than
662+
``ndims`` to 1.0
663+
664+
Parameters
665+
----------
666+
shape : sequence
667+
sequence of integers specifying data array shape
668+
669+
Notes
670+
-----
671+
Applies freesurfer hack for large vectors described in
672+
https://github.com/nipy/nibabel/issues/100 and
673+
http://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn5022&r=5022#77
674+
'''
675+
# Apply freesurfer hack for vector
676+
hdr = self._structarr
677+
shape = tuple(shape)
678+
if (len(shape) == 3 and shape[1:] == (1, 1) and
679+
shape[0] > np.iinfo(hdr['dim'].dtype.base).max): # Freesurfer case
680+
try:
681+
hdr['glmin'] = shape[0]
682+
except OverflowError:
683+
overflow = True
684+
else:
685+
overflow = hdr['glmin'] != shape[0]
686+
if overflow:
687+
raise HeaderDataError('shape[0] %s does not fit in glmax datatype' %
688+
shape[0])
689+
warnings.warn('Using large vector Freesurfer hack; header will '
690+
'not be compatible with SPM or FSL', stacklevel=2)
691+
shape = (-1, 1, 1)
692+
super(Nifti1Header, self).set_data_shape(shape)
693+
623694
def get_qform_quaternion(self):
624695
''' Compute quaternion from b, c, d of quaternion
625696

nibabel/tests/test_analyze.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ def test_shapes(self):
234234
assert_equal(hdr.get_data_shape(), shape)
235235
shape = (mx+1,)
236236
assert_raises(HeaderDataError, hdr.set_data_shape, shape)
237+
# Lists or tuples or arrays will work for setting shape
238+
shape = (2, 3, 4)
239+
for constructor in (list, tuple, np.array):
240+
hdr.set_data_shape(constructor(shape))
241+
assert_equal(hdr.get_data_shape(), shape)
237242

238243
def test_read_write_data(self):
239244
# Check reading and writing of data

nibabel/tests/test_nifti1.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,49 @@ def test_nifti_log_checks(self):
169169
assert_equal(message, 'sform_code -1 not valid; '
170170
'setting to 0')
171171

172+
def test_freesurfer_hack(self):
173+
# For large vector images, Freesurfer appears to set dim[1] to -1 and
174+
# then use glmin for the vector length (an i4)
175+
HC = self.header_class
176+
# The standard case
177+
hdr = HC()
178+
hdr.set_data_shape((2, 3, 4))
179+
assert_equal(hdr.get_data_shape(), (2, 3, 4))
180+
assert_equal(hdr['glmin'], 0)
181+
# Just left of the freesurfer case
182+
dim_type = hdr.template_dtype['dim'].base
183+
glmin = hdr.template_dtype['glmin'].base
184+
too_big = int(np.iinfo(dim_type).max) + 1
185+
hdr.set_data_shape((too_big-1, 1, 1))
186+
assert_equal(hdr.get_data_shape(), (too_big-1, 1, 1))
187+
# The freesurfer case
188+
hdr.set_data_shape((too_big, 1, 1))
189+
assert_equal(hdr.get_data_shape(), (too_big, 1, 1))
190+
assert_array_equal(hdr['dim'][:4], [3, -1, 1, 1])
191+
assert_equal(hdr['glmin'], too_big)
192+
# This only works for the case of a 3D with -1, 1, 1
193+
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,))
194+
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,1))
195+
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,1,2))
196+
assert_raises(HeaderDataError, hdr.set_data_shape, (too_big,2,1))
197+
assert_raises(HeaderDataError, hdr.set_data_shape, (1, too_big))
198+
assert_raises(HeaderDataError, hdr.set_data_shape, (1, too_big, 1))
199+
assert_raises(HeaderDataError, hdr.set_data_shape, (1, 1, too_big))
200+
# Outside range of glmin raises error
201+
far_too_big = int(np.iinfo(glmin).max) + 1
202+
hdr.set_data_shape((far_too_big-1, 1, 1))
203+
assert_equal(hdr.get_data_shape(), (far_too_big-1, 1, 1))
204+
assert_raises(HeaderDataError, hdr.set_data_shape, (far_too_big,1,1))
205+
# glmin of zero raises error (implausible vector length)
206+
hdr.set_data_shape((-1,1,1))
207+
hdr['glmin'] = 0
208+
assert_raises(HeaderDataError, hdr.get_data_shape)
209+
# Lists or tuples or arrays will work for setting shape
210+
for shape in ((too_big-1, 1, 1), (too_big, 1, 1)):
211+
for constructor in (list, tuple, np.array):
212+
hdr.set_data_shape(constructor(shape))
213+
assert_equal(hdr.get_data_shape(), shape)
214+
172215

173216
class TestNifti1SingleHeader(TestNifti1PairHeader):
174217

0 commit comments

Comments
 (0)