Skip to content

Commit d7b472a

Browse files
committed
RF: Subclass MGHHeader from LabeledWrapStruct
1 parent bb56000 commit d7b472a

File tree

1 file changed

+39
-114
lines changed

1 file changed

+39
-114
lines changed

nibabel/freesurfer/mghformat.py

Lines changed: 39 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ..arrayproxy import ArrayProxy
2020
from ..keywordonly import kw_only_meth
2121
from ..openers import ImageOpener
22+
from ..wrapstruct import LabeledWrapStruct
2223

2324
# mgh header
2425
# See https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat
@@ -70,7 +71,7 @@ class MGHError(Exception):
7071
"""
7172

7273

73-
class MGHHeader(object):
74+
class MGHHeader(LabeledWrapStruct):
7475
''' Class for MGH format header
7576
7677
The header also consists of the footer data which MGH places after the data
@@ -84,6 +85,7 @@ class MGHHeader(object):
8485

8586
def __init__(self,
8687
binaryblock=None,
88+
endianness='>',
8789
check=True):
8890
''' Initialize header from binary data block
8991
@@ -96,64 +98,16 @@ def __init__(self,
9698
Whether to check content of header in initialization.
9799
Default is True.
98100
'''
99-
if binaryblock is None:
100-
self._header_data = self._empty_headerdata()
101-
return
102-
# check size
103-
if len(binaryblock) != self.template_dtype.itemsize:
104-
raise HeaderDataError('Binary block is wrong size')
105-
hdr = np.ndarray(shape=(),
106-
dtype=self.template_dtype,
107-
buffer=binaryblock)
108-
# if goodRASFlag, discard delta, Mdc and c_ras stuff
109-
if int(hdr['goodRASFlag']) < 0:
110-
hdr = self._set_affine_default(hdr)
111-
self._header_data = hdr.copy()
101+
if endianness != '>':
102+
raise ValueError("MGHHeader is big-endian")
103+
104+
super(MGHHeader, self).__init__(binaryblock=binaryblock,
105+
endianness=endianness,
106+
check=False)
107+
if int(self._structarr['goodRASFlag']) < 0:
108+
self._set_affine_default()
112109
if check:
113110
self.check_fix()
114-
return
115-
116-
def __str__(self):
117-
''' Print the MGH header object information
118-
'''
119-
txt = []
120-
txt.append(str(self.__class__))
121-
txt.append('Dims: ' + str(self.get_data_shape()))
122-
code = int(self._header_data['type'])
123-
txt.append('MRI Type: ' + self._data_type_codes.mritype[code])
124-
txt.append('goodRASFlag: ' + str(self._header_data['goodRASFlag']))
125-
txt.append('delta: ' + str(self._header_data['delta']))
126-
txt.append('Mdc: ')
127-
txt.append(str(self._header_data['Mdc']))
128-
txt.append('Pxyz_c: ' + str(self._header_data['Pxyz_c']))
129-
txt.append('mrparms: ' + str(self._header_data['mrparms']))
130-
return '\n'.join(txt)
131-
132-
def __getitem__(self, item):
133-
''' Return values from header data
134-
'''
135-
return self._header_data[item]
136-
137-
def __setitem__(self, item, value):
138-
''' Set values in header data
139-
'''
140-
self._header_data[item] = value
141-
142-
def __iter__(self):
143-
return iter(self.keys())
144-
145-
def keys(self):
146-
''' Return keys from header data'''
147-
return list(self.template_dtype.names)
148-
149-
def values(self):
150-
''' Return values from header data'''
151-
data = self._header_data
152-
return [data[key] for key in self.template_dtype.names]
153-
154-
def items(self):
155-
''' Return items from header data'''
156-
return zip(self.keys(), self.values())
157111

158112
@classmethod
159113
def from_header(klass, header=None, check=True):
@@ -188,50 +142,15 @@ def from_fileobj(klass, fileobj, check=True):
188142
int(klass._data_type_codes.bytespervox[tp]) *
189143
np.prod(hdr_str_to_np['dims']))
190144
ftr_str = fileobj.read(klass._ftrdtype.itemsize)
191-
return klass(hdr_str + ftr_str, check)
192-
193-
@property
194-
def binaryblock(self):
195-
''' binary block of data as string
196-
197-
Returns
198-
-------
199-
binaryblock : string
200-
string giving binary data block
201-
202-
'''
203-
return self._header_data.tostring()
204-
205-
def copy(self):
206-
''' Return copy of header
207-
'''
208-
return self.__class__(self.binaryblock, check=False)
209-
210-
def __eq__(self, other):
211-
''' equality between two MGH format headers
212-
213-
Examples
214-
--------
215-
>>> wstr = MGHHeader()
216-
>>> wstr2 = MGHHeader()
217-
>>> wstr == wstr2
218-
True
219-
'''
220-
return self.binaryblock == other.binaryblock
221-
222-
def __ne__(self, other):
223-
return not self == other
224-
225-
def check_fix(self):
226-
''' Pass. maybe for now'''
145+
return klass(hdr_str + ftr_str, check=check)
227146

228147
def get_affine(self):
229148
''' Get the affine transform from the header information.
230149
MGH format doesn't store the transform directly. Instead it's gleaned
231150
from the zooms ( delta ), direction cosines ( Mdc ), RAS centers (
232151
Pxyz_c ) and the dimensions.
233152
'''
234-
hdr = self._header_data
153+
hdr = self._structarr
235154
d = np.diag(hdr['delta'])
236155
pcrs_c = hdr['dims'][:3] / 2.0
237156
Mdc = hdr['Mdc'].T
@@ -253,8 +172,8 @@ def get_vox2ras_tkr(self):
253172
''' Get the vox2ras-tkr transform. See "Torig" here:
254173
https://surfer.nmr.mgh.harvard.edu/fswiki/CoordinateSystems
255174
'''
256-
ds = np.array(self._header_data['delta'])
257-
ns = (np.array(self._header_data['dims'][:3]) * ds) / 2.0
175+
ds = np.array(self._structarr['delta'])
176+
ns = (np.array(self._structarr['dims'][:3]) * ds) / 2.0
258177
v2rtkr = np.array([[-ds[0], 0, 0, ns[0]],
259178
[0, 0, ds[2], -ns[2]],
260179
[0, -ds[1], 0, ns[1]],
@@ -271,7 +190,7 @@ def get_data_dtype(self):
271190
272191
For examples see ``set_data_dtype``
273192
'''
274-
code = int(self._header_data['type'])
193+
code = int(self._structarr['type'])
275194
dtype = self._data_type_codes.numpy_dtype[code]
276195
return dtype
277196

@@ -282,7 +201,7 @@ def set_data_dtype(self, datatype):
282201
code = self._data_type_codes[datatype]
283202
except KeyError:
284203
raise MGHError('datatype dtype "%s" not recognized' % datatype)
285-
self._header_data['type'] = code
204+
self._structarr['type'] = code
286205

287206
def get_zooms(self):
288207
''' Get zooms from header
@@ -292,7 +211,7 @@ def get_zooms(self):
292211
z : tuple
293212
tuple of header zoom values
294213
'''
295-
hdr = self._header_data
214+
hdr = self._structarr
296215
zooms = hdr['delta']
297216
return tuple(zooms[:])
298217

@@ -301,7 +220,7 @@ def set_zooms(self, zooms):
301220
302221
See docstring for ``get_zooms`` for examples
303222
'''
304-
hdr = self._header_data
223+
hdr = self._structarr
305224
zooms = np.asarray(zooms)
306225
if len(zooms) != len(hdr['delta']):
307226
raise HeaderDataError('Expecting %d zoom values for ndim'
@@ -314,7 +233,7 @@ def set_zooms(self, zooms):
314233
def get_data_shape(self):
315234
''' Get shape of data
316235
'''
317-
shape = tuple(self._header_data['dims'])
236+
shape = tuple(self._structarr['dims'])
318237
# If last dimension (nframes) is 1, remove it because
319238
# we want to maintain 3D and it's redundant
320239
if shape[3] == 1:
@@ -332,18 +251,18 @@ def set_data_shape(self, shape):
332251
shape = tuple(shape)
333252
if len(shape) > 4:
334253
raise ValueError("Shape may be at most 4 dimensional")
335-
self._header_data['dims'] = shape + (1,) * (4 - len(shape))
254+
self._structarr['dims'] = shape + (1,) * (4 - len(shape))
336255

337256
def get_data_bytespervox(self):
338257
''' Get the number of bytes per voxel of the data
339258
'''
340259
return int(self._data_type_codes.bytespervox[
341-
int(self._header_data['type'])])
260+
int(self._structarr['type'])])
342261

343262
def get_data_size(self):
344263
''' Get the number of bytes the data chunk occupies.
345264
'''
346-
return self.get_data_bytespervox() * np.prod(self._header_data['dims'])
265+
return self.get_data_bytespervox() * np.prod(self._structarr['dims'])
347266

348267
def get_data_offset(self):
349268
''' Return offset into data file to read data
@@ -379,11 +298,18 @@ def get_slope_inter(self):
379298
"""
380299
return None, None
381300

382-
def _empty_headerdata(self):
301+
@classmethod
302+
def guessed_endian(klass, mapping):
303+
""" MGHHeader data must be big-endian """
304+
return '>'
305+
306+
@classmethod
307+
def default_structarr(klass, endianness=None):
383308
''' Return header data for empty header
309+
310+
Ignores byte order; always big endian
384311
'''
385-
dt = self.template_dtype
386-
hdr_data = np.zeros((), dtype=dt)
312+
hdr_data = super(MGHHeader, klass).default_structarr()
387313
hdr_data['version'] = 1
388314
hdr_data['dims'][:] = np.array([1, 1, 1, 1])
389315
hdr_data['type'] = 3
@@ -396,15 +322,14 @@ def _empty_headerdata(self):
396322
hdr_data['mrparms'] = np.array([0, 0, 0, 0])
397323
return hdr_data
398324

399-
def _set_affine_default(self, hdr):
325+
def _set_affine_default(self):
400326
''' If goodRASFlag is 0, return the default delta, Mdc and Pxyz_c
401327
'''
402-
hdr['delta'][:] = np.array([1, 1, 1])
403-
hdr['Mdc'][0][:] = np.array([-1, 0, 0]) # x_ras
404-
hdr['Mdc'][1][:] = np.array([0, 0, -1]) # y_ras
405-
hdr['Mdc'][2][:] = np.array([0, 1, 0]) # z_ras
406-
hdr['Pxyz_c'][:] = np.array([0, 0, 0]) # c_ras
407-
return hdr
328+
self._structarr['delta'][:] = np.array([1, 1, 1])
329+
self._structarr['Mdc'][0][:] = np.array([-1, 0, 0]) # x_ras
330+
self._structarr['Mdc'][1][:] = np.array([0, 0, -1]) # y_ras
331+
self._structarr['Mdc'][2][:] = np.array([0, 1, 0]) # z_ras
332+
self._structarr['Pxyz_c'][:] = np.array([0, 0, 0]) # c_ras
408333

409334
def writehdr_to(self, fileobj):
410335
''' Write header to fileobj

0 commit comments

Comments
 (0)