Skip to content

Commit 058af58

Browse files
committed
Merge pull request #50 from matthew-brett/binary-header-class
Abstract handling of binary structures into WrapStruct class
2 parents bbaf70c + 685256d commit 058af58

File tree

11 files changed

+1374
-1085
lines changed

11 files changed

+1374
-1085
lines changed

nibabel/analyze.py

Lines changed: 196 additions & 571 deletions
Large diffs are not rendered by default.

nibabel/nifti1.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,8 @@ def from_fileobj(klass, fileobj, size, byteswap):
504504

505505

506506
class Nifti1Header(SpmAnalyzeHeader):
507-
''' Class for NIFTI1 header
508-
507+
''' Class for NIFTI1 header
508+
509509
The NIFTI1 header has many more coded fields than the simpler Analyze
510510
variants. Nifti1 headers also have extensions.
511511
@@ -517,7 +517,7 @@ class Nifti1Header(SpmAnalyzeHeader):
517517
This class handles the header-preceding-data case.
518518
'''
519519
# Copies of module level definitions
520-
_dtype = header_dtype
520+
template_dtype = header_dtype
521521
_data_type_codes = data_type_codes
522522

523523
# fields with recoders for their values
@@ -563,7 +563,7 @@ def copy(self):
563563

564564
@classmethod
565565
def from_fileobj(klass, fileobj, endianness=None, check=True):
566-
raw_str = fileobj.read(klass._dtype.itemsize)
566+
raw_str = fileobj.read(klass.template_dtype.itemsize)
567567
hdr = klass(raw_str, endianness, check)
568568
# Read next 4 bytes to see if we have extensions. The nifti standard
569569
# has this as a 4 byte string; if the first value is not zero, then we
@@ -575,15 +575,15 @@ def from_fileobj(klass, fileobj, endianness=None, check=True):
575575
if not klass.is_single:
576576
extsize = -1
577577
else: # otherwise read until the beginning of the data
578-
extsize = hdr._header_data['vox_offset'] - fileobj.tell()
578+
extsize = hdr._structarr['vox_offset'] - fileobj.tell()
579579
byteswap = endian_codes['native'] != hdr.endianness
580580
hdr.extensions = klass.exts_klass.from_fileobj(fileobj, extsize, byteswap)
581581
return hdr
582582

583583
def write_to(self, fileobj):
584584
# First check that vox offset is large enough
585585
if self.is_single:
586-
vox_offset = self._header_data['vox_offset']
586+
vox_offset = self._structarr['vox_offset']
587587
min_vox_offset = 352 + self.extensions.get_sizeondisk()
588588
if vox_offset < min_vox_offset:
589589
raise HeaderDataError('vox offset of %d, but need at least %d'
@@ -601,18 +601,18 @@ def write_to(self, fileobj):
601601

602602
def get_best_affine(self):
603603
''' Select best of available transforms '''
604-
hdr = self._header_data
604+
hdr = self._structarr
605605
if hdr['sform_code'] != 0:
606606
return self.get_sform()
607607
if hdr['qform_code'] != 0:
608608
return self.get_qform()
609609
return self.get_base_affine()
610610

611-
def _empty_headerdata(self, endianness=None):
611+
@classmethod
612+
def default_structarr(klass, endianness=None):
612613
''' Create empty header binary block with given endianness '''
613-
hdr_data = analyze.AnalyzeHeader._empty_headerdata(self, endianness)
614-
hdr_data['scl_slope'] = 1
615-
if self.is_single:
614+
hdr_data = super(Nifti1Header, klass).default_structarr(endianness)
615+
if klass.is_single:
616616
hdr_data['magic'] = 'n+1'
617617
hdr_data['vox_offset'] = 352
618618
else:
@@ -625,14 +625,14 @@ def get_qform_quaternion(self):
625625
626626
Fills a value by assuming this is a unit quaternion
627627
'''
628-
hdr = self._header_data
628+
hdr = self._structarr
629629
bcd = [hdr['quatern_b'], hdr['quatern_c'], hdr['quatern_d']]
630630
# Adjust threshold to fact that source data was float32
631631
return fillpositive(bcd, FLOAT32_EPS_3)
632632

633633
def get_qform(self):
634634
''' Return 4x4 affine matrix from qform parameters in header '''
635-
hdr = self._header_data
635+
hdr = self._structarr
636636
quat = self.get_qform_quaternion()
637637
R = quat2mat(quat)
638638
vox = hdr['pixdim'][1:4].copy()
@@ -695,7 +695,7 @@ def set_qform(self, affine, code=None):
695695
>>> int(hdr['qform_code'])
696696
1
697697
'''
698-
hdr = self._header_data
698+
hdr = self._structarr
699699
if code is None:
700700
code = hdr['qform_code']
701701
if code == 0: # default is 'aligned'
@@ -732,7 +732,7 @@ def set_qform(self, affine, code=None):
732732

733733
def get_sform(self):
734734
''' Return sform 4x4 affine matrix from header '''
735-
hdr = self._header_data
735+
hdr = self._structarr
736736
out = np.eye(4)
737737
out[0, :] = hdr['srow_x'][:]
738738
out[1, :] = hdr['srow_y'][:]
@@ -776,7 +776,7 @@ def set_sform(self, affine, code=None):
776776
>>> int(hdr['sform_code'])
777777
1
778778
'''
779-
hdr = self._header_data
779+
hdr = self._structarr
780780
if code is None:
781781
code = hdr['sform_code']
782782
if code == 0:
@@ -853,8 +853,8 @@ def set_slope_inter(self, slope, inter=0.0):
853853
slope = 0.0
854854
if inter is None:
855855
inter = 0.0
856-
self._header_data['scl_slope'] = slope
857-
self._header_data['scl_inter'] = inter
856+
self._structarr['scl_slope'] = slope
857+
self._structarr['scl_inter'] = inter
858858

859859
def get_dim_info(self):
860860
''' Gets nifti MRI slice etc dimension information
@@ -882,7 +882,7 @@ def get_dim_info(self):
882882
See set_dim_info function
883883
884884
'''
885-
hdr = self._header_data
885+
hdr = self._structarr
886886
info = int(hdr['dim_info'])
887887
freq = info & 3
888888
phase = (info >> 2) & 3
@@ -936,7 +936,7 @@ def set_dim_info(self, freq=None, phase=None, slice=None):
936936
info = info | (((phase+1) & 3) << 2)
937937
if not slice is None:
938938
info = info | (((slice+1) & 3) << 4)
939-
self._header_data['dim_info'] = info
939+
self._structarr['dim_info'] = info
940940

941941
def get_intent(self, code_repr='label'):
942942
''' Get intent code, parameters and name
@@ -965,7 +965,7 @@ def get_intent(self, code_repr='label'):
965965
>>> hdr.get_intent('code')
966966
(3, (10.0,), 'some score')
967967
'''
968-
hdr = self._header_data
968+
hdr = self._structarr
969969
recoder = self._field_recoders['intent_code']
970970
code = int(hdr['intent_code'])
971971
if code_repr == 'code':
@@ -1022,7 +1022,7 @@ def set_intent(self, code, params=(), name=''):
10221022
>>> hdr.get_intent()
10231023
('f test', (0.0, 0.0), '')
10241024
'''
1025-
hdr = self._header_data
1025+
hdr = self._structarr
10261026
icode = intent_codes.code[code]
10271027
p_descr = intent_codes.parameters[code]
10281028
if len(params) and len(params) != len(p_descr):
@@ -1060,7 +1060,7 @@ def get_slice_duration(self):
10601060
if slice_dim is None:
10611061
raise HeaderDataError('Slice dimension must be set '
10621062
'for duration to be valid')
1063-
return float(self._header_data['slice_duration'])
1063+
return float(self._structarr['slice_duration'])
10641064

10651065
def set_slice_duration(self, duration):
10661066
''' Set slice duration
@@ -1078,12 +1078,12 @@ def set_slice_duration(self, duration):
10781078
if slice_dim is None:
10791079
raise HeaderDataError('Slice dimension must be set '
10801080
'for duration to be valid')
1081-
self._header_data['slice_duration'] = duration
1081+
self._structarr['slice_duration'] = duration
10821082

10831083
def get_n_slices(self):
10841084
''' Return the number of slices
10851085
'''
1086-
hdr = self._header_data
1086+
hdr = self._structarr
10871087
_, _, slice_dim = self.get_dim_info()
10881088
if slice_dim is None:
10891089
raise HeaderDataError('Slice dimension not set in header '
@@ -1121,7 +1121,7 @@ def get_slice_times(self):
11211121
>>> np.allclose(slice_times, [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
11221122
True
11231123
'''
1124-
hdr = self._header_data
1124+
hdr = self._structarr
11251125
slice_len = self.get_n_slices()
11261126
duration = self.get_slice_duration()
11271127
slabel = self.get_value_label('slice_code')
@@ -1168,7 +1168,7 @@ def set_slice_times(self, slice_times):
11681168
5
11691169
'''
11701170
# Check if number of slices matches header
1171-
hdr = self._header_data
1171+
hdr = self._structarr
11721172
slice_len = self.get_n_slices()
11731173
if slice_len != len(slice_times):
11741174
raise HeaderDataError('Number of slice times does not '
@@ -1258,11 +1258,11 @@ def set_xyzt_units(self, xyz=None, t=None):
12581258
def _set_format_specifics(self):
12591259
''' Utility routine to set format specific header stuff '''
12601260
if self.is_single:
1261-
self._header_data['magic'] = 'n+1'
1262-
if self._header_data['vox_offset'] < 352:
1263-
self._header_data['vox_offset'] = 352
1261+
self._structarr['magic'] = 'n+1'
1262+
if self._structarr['vox_offset'] < 352:
1263+
self._structarr['vox_offset'] = 352
12641264
else:
1265-
self._header_data['magic'] = 'ni1'
1265+
self._structarr['magic'] = 'ni1'
12661266

12671267
''' Checks only below here '''
12681268

nibabel/spm2analyze.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Spm2AnalyzeHeader(spm99.Spm99AnalyzeHeader):
3030
offset for data'''
3131

3232
# Copies of module level definitions
33-
_dtype = header_dtype
33+
template_dtype = header_dtype
3434

3535
def get_slope_inter(self):
3636
''' Get data scaling (slope) and offset (intercept) from header data

nibabel/spm99analyze.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,16 @@
3737
class SpmAnalyzeHeader(analyze.AnalyzeHeader):
3838
''' Basic scaling Spm Analyze header '''
3939
# Copies of module level definitions
40-
_dtype = header_dtype
40+
template_dtype = header_dtype
4141

4242
# data scaling capabilities
4343
has_data_slope = True
4444
has_data_intercept = False
4545

46-
def _empty_headerdata(self, endianness=None):
46+
@classmethod
47+
def default_structarr(klass, endianness=None):
4748
''' Create empty header binary block with given endianness '''
48-
hdr_data = super(SpmAnalyzeHeader, self)._empty_headerdata(endianness)
49+
hdr_data = super(SpmAnalyzeHeader, klass).default_structarr(endianness)
4950
hdr_data['scl_slope'] = 1
5051
return hdr_data
5152

@@ -55,7 +56,7 @@ def get_slope_inter(self):
5556
If scalefactor is 0.0 return None to indicate no scalefactor. Intercept
5657
is always None because SPM99 analyze cannot store intercepts.
5758
'''
58-
slope = self._header_data['scl_slope']
59+
slope = self._structarr['scl_slope']
5960
if slope == 0.0:
6061
return None, None
6162
return slope, None
@@ -82,7 +83,7 @@ def set_slope_inter(self, slope, inter=None):
8283
'''
8384
if slope is None:
8485
slope = 0.0
85-
self._header_data['scl_slope'] = slope
86+
self._structarr['scl_slope'] = slope
8687
if inter is None or inter == 0:
8788
return
8889
raise HeaderTypeError('Cannot set non-zero intercept '
@@ -142,7 +143,7 @@ def get_origin_affine(self):
142143
[ 0., 0., 1., -3.],
143144
[ 0., 0., 0., 1.]])
144145
'''
145-
hdr = self._header_data
146+
hdr = self._structarr
146147
zooms = hdr['pixdim'][1:4].copy()
147148
if self.default_x_flip:
148149
zooms[0] *= -1
@@ -199,8 +200,8 @@ def set_origin_from_affine(self, affine):
199200
>>> affine = np.diag([3,2,1,1])
200201
>>> affine[:3,3] = [-6, -6, -4]
201202
>>> hdr.set_origin_from_affine(affine)
202-
>>> np.all(hdr['origin'][:3] == [3,4,5])
203-
True
203+
>>> np.all(hdr['origin'][:3] == [3,4,5])
204+
True
204205
>>> hdr.get_origin_affine()
205206
array([[-3., 0., 0., 6.],
206207
[ 0., 2., 0., -6.],
@@ -209,7 +210,7 @@ def set_origin_from_affine(self, affine):
209210
'''
210211
if affine.shape != (4, 4):
211212
raise ValueError('Need 4x4 affine to set')
212-
hdr = self._header_data
213+
hdr = self._structarr
213214
RZS = affine[:3, :3]
214215
Z = np.sqrt(np.sum(RZS * RZS, axis=0))
215216
T = affine[:3, 3]

0 commit comments

Comments
 (0)