Skip to content

Commit

Permalink
Merge eff6b31 into 4c2ffe8
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-brett committed Sep 2, 2014
2 parents 4c2ffe8 + eff6b31 commit 9451cf0
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 33 deletions.
94 changes: 73 additions & 21 deletions nibabel/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,26 +373,23 @@ def from_header(klass, header=None, check=True):
obj = klass(check=check)
if header is None:
return obj
try: # check if there is a specific conversion routine
if hasattr(header, 'as_analyze_map'):
# header is convertible from a field mapping
mapping = header.as_analyze_map()
except AttributeError:
# most basic conversion
obj.set_data_dtype(header.get_data_dtype())
obj.set_data_shape(header.get_data_shape())
obj.set_zooms(header.get_zooms())
return obj
# header is convertible from a field mapping
for key, value in mapping.items():
try:
obj[key] = value
except (ValueError, KeyError):
# the presence of the mapping certifies the fields as
# being of the same meaning as for Analyze types
pass
# set any fields etc that are specific to this format (overriden by
# sub-classes)
obj._set_format_specifics()
# Check for unsupported datatypes
for key in mapping:
try:
obj[key] = mapping[key]
except (ValueError, KeyError):
# the presence of the mapping certifies the fields as being
# of the same meaning as for Analyze types, so we can
# safely discard fields with names not known to this header
# type on the basis they are from the wrong Analyze dialect
pass
# set any fields etc that are specific to this format (overriden by
# sub-classes)
obj._clean_after_mapping()
# Fallback basic conversion always done.
# More specific warning for unsupported datatypes
orig_code = header.get_data_dtype()
try:
obj.set_data_dtype(orig_code)
Expand All @@ -402,13 +399,32 @@ def from_header(klass, header=None, check=True):
% (header.__class__,
header.get_value_label('datatype'),
klass))
obj.set_data_dtype(header.get_data_dtype())
obj.set_data_shape(header.get_data_shape())
obj.set_zooms(header.get_zooms())
if check:
obj.check_fix()
return obj

def _set_format_specifics(self):
''' Utility routine to set format specific header stuff
def _clean_after_mapping(self):
''' Set format-specific stuff after converting header from mapping
This routine cleans up Analyze-type headers that have had their fields
set from an Analyze map returned by the ``as_analyze_map`` method.
Nifti 1 / 2, SPM Analyze, Analyze are all Analyze-type headers.
Because this map can set fields that are illegal for particular
subtypes of the Analyze header, this routine cleans these up before the
resulting header is checked and returned.
For example, a Nifti1 single (``.nii``) header has magic "n+1".
Passing the nifti single header for conversion to a Nifti1Pair header
using the ``as_analyze_map`` method will by default set the header
magic to "n+1", when it should be "ni1" for the pair header. This
method is for that kind of case - so the specific header can set fields
like magic correctly, even though the mapping has given a wrong value.
'''
# All current Nifti etc fields that are present in the Analyze header
# have the same meaning as they do for Analyze.
pass

def raw_data_from_fileobj(self, fileobj):
Expand Down Expand Up @@ -688,6 +704,42 @@ def set_zooms(self, zooms):
pixdims[1:ndim+1] = zooms[:]

def as_analyze_map(self):
""" Return header as mapping for conversion to Analyze types
Collect data from custom header type to fill in fields for Analyze and
derived header types (such as Nifti1 and Nifti2).
When Analyze types convert another header type to their own type, they
call this this method to check if there are other Analyze / Nifti
fields that the source header would like to set.
Returns
-------
analyze_map : mapping
Object that can be used as a mapping thus::
for key in analyze_map:
value = analyze_map[key]
where ``key`` is the name of a field that can be set in an Analyze
header type, such as Nifti1, and ``value`` is a value for the
field. For example, `analyze_map` might be a something like
``dict(regular='y', slice_duration=0.3)`` where ``regular`` is a
field present in both Analyze and Nifti1, and ``slice_duration`` is
a field restricted to Nifti1 and Nifti2. If a particular Analyze
header type does not recognize the field name, it will throw away
the value without error. See :meth:`Analyze.from_header`.
Notes
-----
You can also return a Nifti header with the relevant fields set.
Your header still needs methods ``get_data_dtype``, ``get_data_shape``
and ``get_zooms``, for the conversion, and these get called *after*
using the analyze map, so the methods will override values set in the
map.
"""
# In the case of Analyze types, the header is already such a mapping
return self

def set_data_offset(self, offset):
Expand Down
4 changes: 0 additions & 4 deletions nibabel/freesurfer/mghformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,6 @@ def _empty_headerdata(self):
hdr_data['mrparms'] = np.array([0, 0, 0, 0])
return hdr_data

def _set_format_specifics(self):
''' Set MGH specific header stuff'''
self._header_data['version'] = 1

def _set_affine_default(self, hdr):
''' If goodRASFlag is 0, return the default delta, Mdc and Pxyz_c
'''
Expand Down
19 changes: 11 additions & 8 deletions nibabel/nifti1.py
Original file line number Diff line number Diff line change
Expand Up @@ -1466,14 +1466,17 @@ def set_xyzt_units(self, xyz=None, t=None):
t_code = unit_codes[t]
self.structarr['xyzt_units'] = xyz_code + t_code

def _set_format_specifics(self):
''' Utility routine to set format specific header stuff '''
if self.is_single:
self._structarr['magic'] = self.single_magic
if self._structarr['vox_offset'] < self.single_vox_offset:
self._structarr['vox_offset'] = self.single_vox_offset
else:
self._structarr['magic'] = self.pair_magic
def _clean_after_mapping(self):
''' Set format-specific stuff after converting header from mapping
Clean up header after it has been initialized from an
``as_analyze_map`` method of another header type
See :meth:`nibabel.analyze.AnalyzeHeader._clean_after_mapping` for a
more detailed description.
'''
self._structarr['magic'] = (self.single_magic if self.is_single
else self.pair_magic)

''' Checks only below here '''

Expand Down
47 changes: 47 additions & 0 deletions nibabel/tests/test_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,53 @@ def test_slope_inter(self):
assert_raises(HeaderTypeError, hdr.set_slope_inter, 1.1)
assert_raises(HeaderTypeError, hdr.set_slope_inter, 1.0, 0.1)

def test_from_analyze_map(self):
# Test that any header can pass values from a mapping
klass = self.header_class
# Header needs to implement data_dtype, data_shape, zooms
class H1(object): pass
assert_raises(AttributeError, klass.from_header, H1())
class H2(object):
def get_data_dtype(self):
return np.dtype('u1')
assert_raises(AttributeError, klass.from_header, H2())
class H3(H2):
def get_data_shape(self):
return (2, 3, 4)
assert_raises(AttributeError, klass.from_header, H3())
class H4(H3):
def get_zooms(self):
return 4., 5., 6.
exp_hdr = klass()
exp_hdr.set_data_dtype(np.dtype('u1'))
exp_hdr.set_data_shape((2, 3, 4))
exp_hdr.set_zooms((4, 5, 6))
assert_equal(klass.from_header(H4()), exp_hdr)
# cal_max, cal_min get properly set from ``as_analyze_map``
class H5(H4):
def as_analyze_map(self):
return dict(cal_min=-100, cal_max=100)
exp_hdr['cal_min'] = -100
exp_hdr['cal_max'] = 100
assert_equal(klass.from_header(H5()), exp_hdr)
# set_* methods override fields fron header
class H6(H5):
def as_analyze_map(self):
return dict(datatype=4, bitpix=32,
cal_min=-100, cal_max=100)
assert_equal(klass.from_header(H6()), exp_hdr)
# Any mapping will do, including a Nifti header
class H7(H5):
def as_analyze_map(self):
n_hdr = Nifti1Header()
n_hdr.set_data_dtype(np.dtype('i2'))
n_hdr['cal_min'] = -100
n_hdr['cal_max'] = 100
return n_hdr
# Values from methods still override values from header (shape, dtype,
# zooms still at defaults from n_hdr header fields above)
assert_equal(klass.from_header(H7()), exp_hdr)


def test_best_affine():
hdr = AnalyzeHeader()
Expand Down

0 comments on commit 9451cf0

Please sign in to comment.