19
19
from ..arrayproxy import ArrayProxy
20
20
from ..keywordonly import kw_only_meth
21
21
from ..openers import ImageOpener
22
+ from ..wrapstruct import LabeledWrapStruct
22
23
23
24
# mgh header
24
25
# See https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat
@@ -70,7 +71,7 @@ class MGHError(Exception):
70
71
"""
71
72
72
73
73
- class MGHHeader (object ):
74
+ class MGHHeader (LabeledWrapStruct ):
74
75
''' Class for MGH format header
75
76
76
77
The header also consists of the footer data which MGH places after the data
@@ -84,6 +85,7 @@ class MGHHeader(object):
84
85
85
86
def __init__ (self ,
86
87
binaryblock = None ,
88
+ endianness = '>' ,
87
89
check = True ):
88
90
''' Initialize header from binary data block
89
91
@@ -96,64 +98,16 @@ def __init__(self,
96
98
Whether to check content of header in initialization.
97
99
Default is True.
98
100
'''
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 ()
112
109
if check :
113
110
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 ())
157
111
158
112
@classmethod
159
113
def from_header (klass , header = None , check = True ):
@@ -188,50 +142,15 @@ def from_fileobj(klass, fileobj, check=True):
188
142
int (klass ._data_type_codes .bytespervox [tp ]) *
189
143
np .prod (hdr_str_to_np ['dims' ]))
190
144
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 )
227
146
228
147
def get_affine (self ):
229
148
''' Get the affine transform from the header information.
230
149
MGH format doesn't store the transform directly. Instead it's gleaned
231
150
from the zooms ( delta ), direction cosines ( Mdc ), RAS centers (
232
151
Pxyz_c ) and the dimensions.
233
152
'''
234
- hdr = self ._header_data
153
+ hdr = self ._structarr
235
154
d = np .diag (hdr ['delta' ])
236
155
pcrs_c = hdr ['dims' ][:3 ] / 2.0
237
156
Mdc = hdr ['Mdc' ].T
@@ -253,8 +172,8 @@ def get_vox2ras_tkr(self):
253
172
''' Get the vox2ras-tkr transform. See "Torig" here:
254
173
https://surfer.nmr.mgh.harvard.edu/fswiki/CoordinateSystems
255
174
'''
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
258
177
v2rtkr = np .array ([[- ds [0 ], 0 , 0 , ns [0 ]],
259
178
[0 , 0 , ds [2 ], - ns [2 ]],
260
179
[0 , - ds [1 ], 0 , ns [1 ]],
@@ -271,7 +190,7 @@ def get_data_dtype(self):
271
190
272
191
For examples see ``set_data_dtype``
273
192
'''
274
- code = int (self ._header_data ['type' ])
193
+ code = int (self ._structarr ['type' ])
275
194
dtype = self ._data_type_codes .numpy_dtype [code ]
276
195
return dtype
277
196
@@ -282,7 +201,7 @@ def set_data_dtype(self, datatype):
282
201
code = self ._data_type_codes [datatype ]
283
202
except KeyError :
284
203
raise MGHError ('datatype dtype "%s" not recognized' % datatype )
285
- self ._header_data ['type' ] = code
204
+ self ._structarr ['type' ] = code
286
205
287
206
def get_zooms (self ):
288
207
''' Get zooms from header
@@ -292,7 +211,7 @@ def get_zooms(self):
292
211
z : tuple
293
212
tuple of header zoom values
294
213
'''
295
- hdr = self ._header_data
214
+ hdr = self ._structarr
296
215
zooms = hdr ['delta' ]
297
216
return tuple (zooms [:])
298
217
@@ -301,7 +220,7 @@ def set_zooms(self, zooms):
301
220
302
221
See docstring for ``get_zooms`` for examples
303
222
'''
304
- hdr = self ._header_data
223
+ hdr = self ._structarr
305
224
zooms = np .asarray (zooms )
306
225
if len (zooms ) != len (hdr ['delta' ]):
307
226
raise HeaderDataError ('Expecting %d zoom values for ndim'
@@ -314,7 +233,7 @@ def set_zooms(self, zooms):
314
233
def get_data_shape (self ):
315
234
''' Get shape of data
316
235
'''
317
- shape = tuple (self ._header_data ['dims' ])
236
+ shape = tuple (self ._structarr ['dims' ])
318
237
# If last dimension (nframes) is 1, remove it because
319
238
# we want to maintain 3D and it's redundant
320
239
if shape [3 ] == 1 :
@@ -332,18 +251,18 @@ def set_data_shape(self, shape):
332
251
shape = tuple (shape )
333
252
if len (shape ) > 4 :
334
253
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 ))
336
255
337
256
def get_data_bytespervox (self ):
338
257
''' Get the number of bytes per voxel of the data
339
258
'''
340
259
return int (self ._data_type_codes .bytespervox [
341
- int (self ._header_data ['type' ])])
260
+ int (self ._structarr ['type' ])])
342
261
343
262
def get_data_size (self ):
344
263
''' Get the number of bytes the data chunk occupies.
345
264
'''
346
- return self .get_data_bytespervox () * np .prod (self ._header_data ['dims' ])
265
+ return self .get_data_bytespervox () * np .prod (self ._structarr ['dims' ])
347
266
348
267
def get_data_offset (self ):
349
268
''' Return offset into data file to read data
@@ -379,11 +298,18 @@ def get_slope_inter(self):
379
298
"""
380
299
return None , None
381
300
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 ):
383
308
''' Return header data for empty header
309
+
310
+ Ignores byte order; always big endian
384
311
'''
385
- dt = self .template_dtype
386
- hdr_data = np .zeros ((), dtype = dt )
312
+ hdr_data = super (MGHHeader , klass ).default_structarr ()
387
313
hdr_data ['version' ] = 1
388
314
hdr_data ['dims' ][:] = np .array ([1 , 1 , 1 , 1 ])
389
315
hdr_data ['type' ] = 3
@@ -396,15 +322,14 @@ def _empty_headerdata(self):
396
322
hdr_data ['mrparms' ] = np .array ([0 , 0 , 0 , 0 ])
397
323
return hdr_data
398
324
399
- def _set_affine_default (self , hdr ):
325
+ def _set_affine_default (self ):
400
326
''' If goodRASFlag is 0, return the default delta, Mdc and Pxyz_c
401
327
'''
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
408
333
409
334
def writehdr_to (self , fileobj ):
410
335
''' Write header to fileobj
0 commit comments