1919from ..arrayproxy import ArrayProxy
2020from ..keywordonly import kw_only_meth
2121from ..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