7
7
#
8
8
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
9
9
10
- # https://github.com/florisvanvugt/afnipy/blob/master/afni.py
11
-
12
10
from __future__ import print_function , division
13
11
14
12
from copy import deepcopy
17
15
18
16
import numpy as np
19
17
20
- from .fileslice import fileslice , strided_scalar
18
+ from .arrayproxy import ArrayProxy
19
+ from .fileslice import strided_scalar
21
20
from .keywordonly import kw_only_meth
22
- from .openers import ImageOpener
23
21
from .spatialimages import SpatialImage , SpatialHeader
24
- from .volumeutils import Recoder , array_from_file
22
+ from .volumeutils import Recoder
25
23
26
24
_attr_dic = {
27
25
'string' : str ,
@@ -62,6 +60,7 @@ class AFNIError(Exception):
62
60
"""
63
61
64
62
63
+ DATA_OFFSET = 0
65
64
TYPE_RE = re .compile ('type\s*=\s*(string|integer|float)-attribute\s*\n ' )
66
65
NAME_RE = re .compile ('name\s*=\s*(\w+)\s*\n ' )
67
66
@@ -79,14 +78,11 @@ def _unpack_var(var):
79
78
(key, value)
80
79
Example: ('BRICK_TYPES', [1])
81
80
"""
82
-
83
81
# data type and key
84
82
atype = TYPE_RE .findall (var )[0 ]
85
83
aname = NAME_RE .findall (var )[0 ]
86
-
87
84
atype = _attr_dic .get (atype , str )
88
85
attr = ' ' .join (var .strip ().split ('\n ' )[3 :])
89
-
90
86
if atype is not str :
91
87
attr = [atype (f ) for f in attr .split ()]
92
88
if len (attr ) == 1 :
@@ -100,19 +96,15 @@ def _unpack_var(var):
100
96
def _get_datatype (info ):
101
97
""" Gets datatype from `info` header information
102
98
"""
103
-
104
99
bo = info ['BYTEORDER_STRING' ]
105
100
bt = info ['BRICK_TYPES' ]
106
-
107
101
if isinstance (bt , list ):
108
102
if len (np .unique (bt )) > 1 :
109
103
raise AFNIError ('Can\' t load dataset with multiple data types.' )
110
104
else :
111
105
bt = bt [0 ]
112
-
113
106
bo = _endian_dict .get (bo , '=' )
114
107
bt = _dtype_dict .get (bt , None )
115
-
116
108
if bt is None :
117
109
raise AFNIError ('Can\' t deduce image data type.' )
118
110
@@ -125,104 +117,71 @@ def parse_AFNI_header(fobj):
125
117
Parameters
126
118
----------
127
119
fobj : file-object
128
- AFNI HEAD file objects
120
+ AFNI HEAD file object
129
121
130
122
Returns
131
123
-------
132
124
all_info : dict
133
125
Contains all the information from the HEAD file
134
126
"""
135
-
136
127
head = fobj .read ().split ('\n \n ' )
137
-
138
128
all_info = {key : value for key , value in map (_unpack_var , head )}
139
129
140
130
return all_info
141
131
142
132
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 ):
178
136
""" Initialize AFNI array proxy
179
137
180
138
Parameters
181
139
----------
182
140
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
187
144
mmap : {True, False, 'c', 'r'}, optional, keyword only
188
145
`mmap` controls the use of numpy memory mapping for reading data.
189
146
If False, do not try numpy ``memmap`` for data array. If one of
190
147
{'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap` value of
191
148
True gives the same behavior as ``mmap='c'``. If `file_like`
192
149
cannot be memory-mapped, ignore `mmap` value and read array from
193
150
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.
194
163
"""
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 )
199
168
self ._scaling = header .get_data_scaling ()
200
169
201
170
@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
212
173
213
174
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 )
220
179
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
225
181
182
+ def __getitem__ (self , slicer ):
183
+ raw_data = super (AFNIArrayProxy , self ).__getitem__ (slicer )
184
+ # apply volume specific scaling
226
185
if self ._scaling is not None :
227
186
scaling = self ._scaling .copy ()
228
187
fake_data = strided_scalar (self ._shape )
@@ -235,7 +194,6 @@ def __getitem__(self, slicer):
235
194
class AFNIHeader (SpatialHeader ):
236
195
""" Class for AFNI header
237
196
"""
238
-
239
197
def __init__ (self , info ):
240
198
"""
241
199
Parameters
@@ -246,7 +204,6 @@ def __init__(self, info):
246
204
"""
247
205
self .info = info
248
206
dt = _get_datatype (self .info )
249
-
250
207
super (AFNIHeader , self ).__init__ (data_dtype = dt ,
251
208
shape = self ._calc_data_shape (),
252
209
zooms = self ._calc_zooms ())
@@ -293,7 +250,6 @@ def _calc_zooms(self):
293
250
"""
294
251
xyz_step = tuple (np .abs (self .info ['DELTA' ]))
295
252
t_step = self .info .get ('TAXIS_FLOATS' , ())
296
-
297
253
if len (t_step ) > 0 :
298
254
t_step = (t_step [1 ],)
299
255
@@ -320,13 +276,14 @@ def get_space(self):
320
276
-------
321
277
space : str
322
278
"""
323
-
324
279
listed_space = self .info .get ('TEMPLATE_SPACE' , 0 )
325
280
space = space_codes .label [listed_space ]
326
281
327
282
return space
328
283
329
284
def get_affine (self ):
285
+ """ Returns affine of dataset
286
+ """
330
287
# AFNI default is RAI/DICOM order (i.e., RAI are - axis)
331
288
# need to flip RA sign to align with nibabel RAS+ system
332
289
affine = np .asarray (self .info ['IJK_TO_DICOM_REAL' ]).reshape (3 , 4 )
@@ -336,17 +293,25 @@ def get_affine(self):
336
293
return affine
337
294
338
295
def get_data_scaling (self ):
296
+ """ AFNI applies volume-specific data scaling
297
+ """
339
298
floatfacs = self .info .get ('BRICK_FLOAT_FACS' , None )
340
-
341
299
if floatfacs is None or not np .any (floatfacs ):
342
300
return None
343
-
344
301
scale = np .ones (self .info ['DATASET_RANK' ][1 ])
345
302
floatfacs = np .asarray (floatfacs )
346
303
scale [floatfacs .nonzero ()] = floatfacs [floatfacs .nonzero ()]
347
304
348
305
return scale
349
306
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
+
350
315
def get_volume_labels (self ):
351
316
""" Returns volume labels
352
317
@@ -355,7 +320,6 @@ def get_volume_labels(self):
355
320
labels : list of str
356
321
"""
357
322
labels = self .info .get ('BRICK_LABS' , None )
358
-
359
323
if labels is not None :
360
324
labels = labels .split ('~' )
361
325
@@ -370,10 +334,8 @@ class AFNIImage(SpatialImage):
370
334
valid_exts = ('.brik' , '.head' )
371
335
files_types = (('image' , '.brik' ), ('header' , '.head' ))
372
336
_compressed_suffixes = ('.gz' , '.bz2' )
373
-
374
337
makeable = False
375
338
rw = False
376
-
377
339
ImageArrayProxy = AFNIArrayProxy
378
340
379
341
@classmethod
0 commit comments