77#
88### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
99
10- # https://github.com/florisvanvugt/afnipy/blob/master/afni.py
11-
1210from __future__ import print_function , division
1311
1412from copy import deepcopy
1715
1816import numpy as np
1917
20- from .fileslice import fileslice , strided_scalar
18+ from .arrayproxy import ArrayProxy
19+ from .fileslice import strided_scalar
2120from .keywordonly import kw_only_meth
22- from .openers import ImageOpener
2321from .spatialimages import SpatialImage , SpatialHeader
24- from .volumeutils import Recoder , array_from_file
22+ from .volumeutils import Recoder
2523
2624_attr_dic = {
2725 'string' : str ,
@@ -62,6 +60,7 @@ class AFNIError(Exception):
6260 """
6361
6462
63+ DATA_OFFSET = 0
6564TYPE_RE = re .compile ('type\s*=\s*(string|integer|float)-attribute\s*\n ' )
6665NAME_RE = re .compile ('name\s*=\s*(\w+)\s*\n ' )
6766
@@ -79,14 +78,11 @@ def _unpack_var(var):
7978 (key, value)
8079 Example: ('BRICK_TYPES', [1])
8180 """
82-
8381 # data type and key
8482 atype = TYPE_RE .findall (var )[0 ]
8583 aname = NAME_RE .findall (var )[0 ]
86-
8784 atype = _attr_dic .get (atype , str )
8885 attr = ' ' .join (var .strip ().split ('\n ' )[3 :])
89-
9086 if atype is not str :
9187 attr = [atype (f ) for f in attr .split ()]
9288 if len (attr ) == 1 :
@@ -100,19 +96,15 @@ def _unpack_var(var):
10096def _get_datatype (info ):
10197 """ Gets datatype from `info` header information
10298 """
103-
10499 bo = info ['BYTEORDER_STRING' ]
105100 bt = info ['BRICK_TYPES' ]
106-
107101 if isinstance (bt , list ):
108102 if len (np .unique (bt )) > 1 :
109103 raise AFNIError ('Can\' t load dataset with multiple data types.' )
110104 else :
111105 bt = bt [0 ]
112-
113106 bo = _endian_dict .get (bo , '=' )
114107 bt = _dtype_dict .get (bt , None )
115-
116108 if bt is None :
117109 raise AFNIError ('Can\' t deduce image data type.' )
118110
@@ -125,104 +117,71 @@ def parse_AFNI_header(fobj):
125117 Parameters
126118 ----------
127119 fobj : file-object
128- AFNI HEAD file objects
120+ AFNI HEAD file object
129121
130122 Returns
131123 -------
132124 all_info : dict
133125 Contains all the information from the HEAD file
134126 """
135-
136127 head = fobj .read ().split ('\n \n ' )
137-
138128 all_info = {key : value for key , value in map (_unpack_var , head )}
139129
140130 return all_info
141131
142132
143- def _data_from_brik (fobj , shape , dtype , scalings = None , mmap = True ):
144- """ Load and return array data from BRIK file
145-
146- Parameters
147- ----------
148- fobj : file-like
149- The file to process.
150- shape : tuple
151- The data shape as specified from the HEAD file.
152- dtype : dtype
153- The datatype.
154- scalings : {None, sequence}, optional
155- Scalings to use. If not None, a length N sequence, where N is equal to
156- `shape[-1]`
157- mmap : {True, False, 'c', 'r', 'r+'}, optional
158- `mmap` controls the use of numpy memory mapping for reading data. If
159- False, do not try numpy ``memmap`` for data array. If one of {'c',
160- 'r', 'r+'}, try numpy memmap with ``mode=mmap``. A `mmap` value of
161- True gives the same behavior as ``mmap='c'``. If `rec_fileobj` cannot
162- be memory-mapped, ignore `mmap` value and read array from file.
163-
164- Returns
165- -------
166- data : array
167- The scaled and sorted array.
168- """
169- brik_data = array_from_file (shape , dtype , fobj , mmap = mmap )
170- if scalings is not None :
171- brik_data = brik_data * scalings .astype (dtype )
172- return brik_data
173-
174-
175- class AFNIArrayProxy (object ):
176-
177- def __init__ (self , file_like , header , mmap = True ):
133+ class AFNIArrayProxy (ArrayProxy ):
134+ @kw_only_meth (2 )
135+ def __init__ (self , file_like , header , mmap = True , keep_file_open = None ):
178136 """ Initialize AFNI array proxy
179137
180138 Parameters
181139 ----------
182140 file_like : file-like object
183- Filename or object implementing ``read, seek, tell``
184- header : AFNIHeader instance
185- Implementing ``get_data_shape, get_data_dtype``,
186- ``get_data_scaling``.
141+ File-like object or filename. If file-like object, should implement
142+ at least ``read`` and ``seek``.
143+ header : AFNIHeader object
187144 mmap : {True, False, 'c', 'r'}, optional, keyword only
188145 `mmap` controls the use of numpy memory mapping for reading data.
189146 If False, do not try numpy ``memmap`` for data array. If one of
190147 {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap` value of
191148 True gives the same behavior as ``mmap='c'``. If `file_like`
192149 cannot be memory-mapped, ignore `mmap` value and read array from
193150 file.
151+ keep_file_open : { None, 'auto', True, False }, optional, keyword only
152+ `keep_file_open` controls whether a new file handle is created
153+ every time the image is accessed, or a single file handle is
154+ created and used for the lifetime of this ``ArrayProxy``. If
155+ ``True``, a single file handle is created and used. If ``False``,
156+ a new file handle is created every time the image is accessed. If
157+ ``'auto'``, and the optional ``indexed_gzip`` dependency is
158+ present, a single file handle is created and persisted. If
159+ ``indexed_gzip`` is not available, behaviour is the same as if
160+ ``keep_file_open is False``. If ``file_like`` is an open file
161+ handle, this setting has no effect. The default value (``None``)
162+ will result in the value of ``KEEP_FILE_OPEN_DEFAULT`` being used.
194163 """
195- self . file_like = file_like
196- self . _shape = header . get_data_shape ()
197- self . _dtype = header . get_data_dtype ()
198- self . _mmap = mmap
164+ super ( AFNIArrayProxy , self ). __init__ ( file_like ,
165+ header ,
166+ mmap = mmap ,
167+ keep_file_open = keep_file_open )
199168 self ._scaling = header .get_data_scaling ()
200169
201170 @property
202- def shape (self ):
203- return self ._shape
204-
205- @property
206- def dtype (self ):
207- return self ._dtype
208-
209- @property
210- def is_proxy (self ):
211- return True
171+ def scaling (self ):
172+ return self ._scaling
212173
213174 def __array__ (self ):
214- with ImageOpener (self .file_like ) as fileobj :
215- return _data_from_brik (fileobj ,
216- self ._shape ,
217- self ._dtype ,
218- scalings = self ._scaling ,
219- mmap = self ._mmap )
175+ raw_data = self .get_unscaled ()
176+ # apply volume specific scaling
177+ if self ._scaling is not None :
178+ return raw_data * self ._scaling .astype (self .dtype )
220179
221- def __getitem__ (self , slicer ):
222- with ImageOpener (self .file_like ) as fileobj :
223- raw_data = fileslice (fileobj , slicer , self ._shape , self ._dtype , 0 ,
224- 'F' )
180+ return raw_data
225181
182+ def __getitem__ (self , slicer ):
183+ raw_data = super (AFNIArrayProxy , self ).__getitem__ (slicer )
184+ # apply volume specific scaling
226185 if self ._scaling is not None :
227186 scaling = self ._scaling .copy ()
228187 fake_data = strided_scalar (self ._shape )
@@ -235,7 +194,6 @@ def __getitem__(self, slicer):
235194class AFNIHeader (SpatialHeader ):
236195 """ Class for AFNI header
237196 """
238-
239197 def __init__ (self , info ):
240198 """
241199 Parameters
@@ -246,7 +204,6 @@ def __init__(self, info):
246204 """
247205 self .info = info
248206 dt = _get_datatype (self .info )
249-
250207 super (AFNIHeader , self ).__init__ (data_dtype = dt ,
251208 shape = self ._calc_data_shape (),
252209 zooms = self ._calc_zooms ())
@@ -293,7 +250,6 @@ def _calc_zooms(self):
293250 """
294251 xyz_step = tuple (np .abs (self .info ['DELTA' ]))
295252 t_step = self .info .get ('TAXIS_FLOATS' , ())
296-
297253 if len (t_step ) > 0 :
298254 t_step = (t_step [1 ],)
299255
@@ -320,13 +276,14 @@ def get_space(self):
320276 -------
321277 space : str
322278 """
323-
324279 listed_space = self .info .get ('TEMPLATE_SPACE' , 0 )
325280 space = space_codes .label [listed_space ]
326281
327282 return space
328283
329284 def get_affine (self ):
285+ """ Returns affine of dataset
286+ """
330287 # AFNI default is RAI/DICOM order (i.e., RAI are - axis)
331288 # need to flip RA sign to align with nibabel RAS+ system
332289 affine = np .asarray (self .info ['IJK_TO_DICOM_REAL' ]).reshape (3 , 4 )
@@ -336,17 +293,25 @@ def get_affine(self):
336293 return affine
337294
338295 def get_data_scaling (self ):
296+ """ AFNI applies volume-specific data scaling
297+ """
339298 floatfacs = self .info .get ('BRICK_FLOAT_FACS' , None )
340-
341299 if floatfacs is None or not np .any (floatfacs ):
342300 return None
343-
344301 scale = np .ones (self .info ['DATASET_RANK' ][1 ])
345302 floatfacs = np .asarray (floatfacs )
346303 scale [floatfacs .nonzero ()] = floatfacs [floatfacs .nonzero ()]
347304
348305 return scale
349306
307+ def get_slope_inter (self ):
308+ """ Use `self.get_data_scaling()` instead
309+ """
310+ return None , None
311+
312+ def get_data_offset (self ):
313+ return DATA_OFFSET
314+
350315 def get_volume_labels (self ):
351316 """ Returns volume labels
352317
@@ -355,7 +320,6 @@ def get_volume_labels(self):
355320 labels : list of str
356321 """
357322 labels = self .info .get ('BRICK_LABS' , None )
358-
359323 if labels is not None :
360324 labels = labels .split ('~' )
361325
@@ -370,10 +334,8 @@ class AFNIImage(SpatialImage):
370334 valid_exts = ('.brik' , '.head' )
371335 files_types = (('image' , '.brik' ), ('header' , '.head' ))
372336 _compressed_suffixes = ('.gz' , '.bz2' )
373-
374337 makeable = False
375338 rw = False
376-
377339 ImageArrayProxy = AFNIArrayProxy
378340
379341 @classmethod
0 commit comments