@@ -373,26 +373,23 @@ def from_header(klass, header=None, check=True):
373
373
obj = klass (check = check )
374
374
if header is None :
375
375
return obj
376
- try : # check if there is a specific conversion routine
376
+ if hasattr (header , 'as_analyze_map' ):
377
+ # header is convertible from a field mapping
377
378
mapping = header .as_analyze_map ()
378
- except AttributeError :
379
- # most basic conversion
380
- obj .set_data_dtype (header .get_data_dtype ())
381
- obj .set_data_shape (header .get_data_shape ())
382
- obj .set_zooms (header .get_zooms ())
383
- return obj
384
- # header is convertible from a field mapping
385
- for key , value in mapping .items ():
386
- try :
387
- obj [key ] = value
388
- except (ValueError , KeyError ):
389
- # the presence of the mapping certifies the fields as
390
- # being of the same meaning as for Analyze types
391
- pass
392
- # set any fields etc that are specific to this format (overriden by
393
- # sub-classes)
394
- obj ._set_format_specifics ()
395
- # Check for unsupported datatypes
379
+ for key in mapping :
380
+ try :
381
+ obj [key ] = mapping [key ]
382
+ except (ValueError , KeyError ):
383
+ # the presence of the mapping certifies the fields as being
384
+ # of the same meaning as for Analyze types, so we can
385
+ # safely discard fields with names not known to this header
386
+ # type on the basis they are from the wrong Analyze dialect
387
+ pass
388
+ # set any fields etc that are specific to this format (overriden by
389
+ # sub-classes)
390
+ obj ._clean_after_mapping ()
391
+ # Fallback basic conversion always done.
392
+ # More specific warning for unsupported datatypes
396
393
orig_code = header .get_data_dtype ()
397
394
try :
398
395
obj .set_data_dtype (orig_code )
@@ -402,13 +399,32 @@ def from_header(klass, header=None, check=True):
402
399
% (header .__class__ ,
403
400
header .get_value_label ('datatype' ),
404
401
klass ))
402
+ obj .set_data_dtype (header .get_data_dtype ())
403
+ obj .set_data_shape (header .get_data_shape ())
404
+ obj .set_zooms (header .get_zooms ())
405
405
if check :
406
406
obj .check_fix ()
407
407
return obj
408
408
409
- def _set_format_specifics (self ):
410
- ''' Utility routine to set format specific header stuff
409
+ def _clean_after_mapping (self ):
410
+ ''' Set format-specific stuff after converting header from mapping
411
+
412
+ This routine cleans up Analyze-type headers that have had their fields
413
+ set from an Analyze map returned by the ``as_analyze_map`` method.
414
+ Nifti 1 / 2, SPM Analyze, Analyze are all Analyze-type headers.
415
+ Because this map can set fields that are illegal for particular
416
+ subtypes of the Analyze header, this routine cleans these up before the
417
+ resulting header is checked and returned.
418
+
419
+ For example, a Nifti1 single (``.nii``) header has magic "n+1".
420
+ Passing the nifti single header for conversion to a Nifti1Pair header
421
+ using the ``as_analyze_map`` method will by default set the header
422
+ magic to "n+1", when it should be "ni1" for the pair header. This
423
+ method is for that kind of case - so the specific header can set fields
424
+ like magic correctly, even though the mapping has given a wrong value.
411
425
'''
426
+ # All current Nifti etc fields that are present in the Analyze header
427
+ # have the same meaning as they do for Analyze.
412
428
pass
413
429
414
430
def raw_data_from_fileobj (self , fileobj ):
@@ -688,6 +704,42 @@ def set_zooms(self, zooms):
688
704
pixdims [1 :ndim + 1 ] = zooms [:]
689
705
690
706
def as_analyze_map (self ):
707
+ """ Return header as mapping for conversion to Analyze types
708
+
709
+ Collect data from custom header type to fill in fields for Analyze and
710
+ derived header types (such as Nifti1 and Nifti2).
711
+
712
+ When Analyze types convert another header type to their own type, they
713
+ call this this method to check if there are other Analyze / Nifti
714
+ fields that the source header would like to set.
715
+
716
+ Returns
717
+ -------
718
+ analyze_map : mapping
719
+ Object that can be used as a mapping thus::
720
+
721
+ for key in analyze_map:
722
+ value = analyze_map[key]
723
+
724
+ where ``key`` is the name of a field that can be set in an Analyze
725
+ header type, such as Nifti1, and ``value`` is a value for the
726
+ field. For example, `analyze_map` might be a something like
727
+ ``dict(regular='y', slice_duration=0.3)`` where ``regular`` is a
728
+ field present in both Analyze and Nifti1, and ``slice_duration`` is
729
+ a field restricted to Nifti1 and Nifti2. If a particular Analyze
730
+ header type does not recognize the field name, it will throw away
731
+ the value without error. See :meth:`Analyze.from_header`.
732
+
733
+ Notes
734
+ -----
735
+ You can also return a Nifti header with the relevant fields set.
736
+
737
+ Your header still needs methods ``get_data_dtype``, ``get_data_shape``
738
+ and ``get_zooms``, for the conversion, and these get called *after*
739
+ using the analyze map, so the methods will override values set in the
740
+ map.
741
+ """
742
+ # In the case of Analyze types, the header is already such a mapping
691
743
return self
692
744
693
745
def set_data_offset (self , offset ):
0 commit comments