12
12
See https://afni.nimh.nih.gov/pub/dist/doc/program_help/README.attributes.html
13
13
for information on what is required to have a valid BRIK/HEAD dataset.
14
14
15
- Some notes on the AFNI BRIK/HEAD format:
15
+ Unless otherwise noted, descriptions AFNI attributes in the code refer to this
16
+ document.
16
17
17
- * In the AFNI HEAD file, the first two values of the attribute DATASET_RANK
18
+ Notes
19
+ -----
20
+
21
+ In the AFNI HEAD file, the first two values of the attribute DATASET_RANK
18
22
determine the shape of the data array stored in the corresponding BRIK file.
19
23
The first value, DATASET_RANK[0], must be set to 3 denoting a 3D image. The
20
24
second value, DATASET_RANK[1], determines how many "sub-bricks" (in AFNI
@@ -152,6 +156,15 @@ def _get_datatype(info):
152
156
-------
153
157
dt : np.dtype
154
158
Datatype of BRIK file associated with HEAD
159
+
160
+ Notes
161
+ -----
162
+ ``BYTEORDER_STRING`` may be absent, signifying platform native byte order,
163
+ or contain one of "LSB_FIRST" or "MSB_FIRST".
164
+
165
+ ``BRICK_TYPES`` gives the storage data type for each sub-brick, with
166
+ 0=uint, 1=int16, 3=float32, 5=complex64 (see ``_dtype_dict``). This should
167
+ generally be the same value for each sub-brick in the dataset.
155
168
"""
156
169
bo = info ['BYTEORDER_STRING' ]
157
170
bt = info ['BRICK_TYPES' ]
@@ -196,17 +209,19 @@ def parse_AFNI_header(fobj):
196
209
return parse_AFNI_header (src )
197
210
# unpack variables in HEAD file
198
211
head = fobj .read ().split ('\n \n ' )
199
- info = {key : value for key , value in map (_unpack_var , head )}
200
- return info
212
+ return {key : value for key , value in map (_unpack_var , head )}
201
213
202
214
203
215
class AFNIArrayProxy (ArrayProxy ):
204
- """
216
+ """ Proxy object for AFNI image array.
217
+
205
218
Attributes
206
219
----------
207
220
scaling : np.ndarray
208
- Scaling factor (one factor per volume/subbrick) for data. Default: None
221
+ Scaling factor (one factor per volume/sub-brick) for data. Default is
222
+ None
209
223
"""
224
+
210
225
@kw_only_meth (2 )
211
226
def __init__ (self , file_like , header , mmap = True , keep_file_open = None ):
212
227
"""
@@ -233,7 +248,7 @@ def __init__(self, file_like, header, mmap=True, keep_file_open=None):
233
248
a new file handle is created every time the image is accessed. If
234
249
``'auto'``, and the optional ``indexed_gzip`` dependency is
235
250
present, a single file handle is created and persisted. If
236
- ``indexed_gzip`` is not available, behaviour is the same as if
251
+ ``indexed_gzip`` is not available, behavior is the same as if
237
252
``keep_file_open is False``. If ``file_like`` refers to an open
238
253
file handle, this setting has no effect. The default value
239
254
(``None``) will result in the value of
@@ -319,6 +334,13 @@ def _calc_data_shape(self):
319
334
Returns
320
335
-------
321
336
(x, y, z, t) : tuple of int
337
+
338
+ Notes
339
+ -----
340
+ ``DATASET_RANK[0]`` gives number of spatial dimensions (and apparently
341
+ must be 3). ``DATASET_RANK[1]`` gives the number of sub-bricks.
342
+ ``DATASET_DIMENSIONS`` is length 3, giving the number of voxels in i,
343
+ j, k.
322
344
"""
323
345
dset_rank = self .info ['DATASET_RANK' ]
324
346
shape = tuple (self .info ['DATASET_DIMENSIONS' ][:dset_rank [0 ]])
@@ -335,6 +357,15 @@ def _calc_zooms(self):
335
357
Returns
336
358
-------
337
359
zooms : tuple
360
+
361
+ Notes
362
+ -----
363
+ Gets zooms from attributes ``DELTA`` and ``TAXIS_FLOATS``.
364
+
365
+ ``DELTA`` gives (x,y,z) voxel sizes.
366
+
367
+ ``TAXIS_FLOATS`` should be length 5, with first entry giving "Time
368
+ origin", and second giving "Time step (TR)".
338
369
"""
339
370
xyz_step = tuple (np .abs (self .info ['DELTA' ]))
340
371
t_step = self .info .get ('TAXIS_FLOATS' , (0 , 0 ,))
@@ -344,12 +375,17 @@ def _calc_zooms(self):
344
375
345
376
def get_space (self ):
346
377
"""
347
- Returns space of dataset
378
+ Return label for anatomical space to which this dataset is aligned.
348
379
349
380
Returns
350
381
-------
351
382
space : str
352
383
AFNI "space" designation; one of [ORIG, ANAT, TLRC, MNI]
384
+
385
+ Notes
386
+ -----
387
+ There appears to be documentation for these spaces at
388
+ https://afni.nimh.nih.gov/pub/dist/atlases/elsedemo/AFNI_atlas_spaces.niml
353
389
"""
354
390
listed_space = self .info .get ('TEMPLATE_SPACE' , 0 )
355
391
space = space_codes .space [listed_space ]
@@ -369,8 +405,8 @@ def get_affine(self):
369
405
[ 0. , 0. , 3. , -52.3511],
370
406
[ 0. , 0. , 0. , 1. ]])
371
407
"""
372
- # AFNI default is RAI-/ DICOM order (i.e., RAI are - axis)
373
- # need to flip RA sign to align with nibabel RAS+ system
408
+ # AFNI default is RAI- == LPS+ == DICOM order. We need to flip RA sign
409
+ # to align with nibabel RAS+ system
374
410
affine = np .asarray (self .info ['IJK_TO_DICOM_REAL' ]).reshape (3 , 4 )
375
411
affine = np .row_stack ((affine * [[- 1 ], [- 1 ], [1 ]],
376
412
[0 , 0 , 0 , 1 ]))
@@ -387,6 +423,9 @@ def get_data_scaling(self):
387
423
>>> header.get_data_scaling()
388
424
array([ 3.88336300e-08])
389
425
"""
426
+ # BRICK_FLOAT_FACS has one value per sub-brick, such that the scaled
427
+ # values for sub-brick array [n] are the values read from disk *
428
+ # BRICK_FLOAT_FACS[n]
390
429
floatfacs = self .info .get ('BRICK_FLOAT_FACS' , None )
391
430
if floatfacs is None or not np .any (floatfacs ):
392
431
return None
@@ -405,7 +444,10 @@ def get_slope_inter(self):
405
444
return None , None
406
445
407
446
def get_data_offset (self ):
408
- """Data offset in BRIK file"""
447
+ """Data offset in BRIK file
448
+
449
+ Offset is always 0.
450
+ """
409
451
return DATA_OFFSET
410
452
411
453
def get_volume_labels (self ):
@@ -461,7 +503,7 @@ class AFNIImage(SpatialImage):
461
503
462
504
@classmethod
463
505
@kw_only_meth (1 )
464
- def from_file_map (klass , file_map , mmap = True ):
506
+ def from_file_map (klass , file_map , mmap = True , keep_file_open = None ):
465
507
"""
466
508
Creates an AFNIImage instance from `file_map`
467
509
@@ -477,19 +519,32 @@ def from_file_map(klass, file_map, mmap=True):
477
519
`mmap` value of True gives the same behavior as ``mmap='c'``. If
478
520
image data file cannot be memory-mapped, ignore `mmap` value and
479
521
read array from file.
522
+ keep_file_open : {None, 'auto', True, False}, optional, keyword only
523
+ `keep_file_open` controls whether a new file handle is created
524
+ every time the image is accessed, or a single file handle is
525
+ created and used for the lifetime of this ``ArrayProxy``. If
526
+ ``True``, a single file handle is created and used. If ``False``,
527
+ a new file handle is created every time the image is accessed. If
528
+ ``'auto'``, and the optional ``indexed_gzip`` dependency is
529
+ present, a single file handle is created and persisted. If
530
+ ``indexed_gzip`` is not available, behavior is the same as if
531
+ ``keep_file_open is False``. If ``file_like`` refers to an open
532
+ file handle, this setting has no effect. The default value
533
+ (``None``) will result in the value of
534
+ ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT` being used.
480
535
"""
481
536
with file_map ['header' ].get_prepare_fileobj ('rt' ) as hdr_fobj :
482
537
hdr = klass .header_class .from_fileobj (hdr_fobj )
483
538
imgf = file_map ['image' ].fileobj
484
- if imgf is None :
485
- imgf = file_map [ 'image' ]. filename
486
- data = klass . ImageArrayProxy ( imgf , hdr . copy (), mmap = mmap )
539
+ imgf = file_map [ 'image' ]. filename if imgf is None else imgf
540
+ data = klass . ImageArrayProxy ( imgf , hdr . copy (), mmap = mmap ,
541
+ keep_file_open = keep_file_open )
487
542
return klass (data , hdr .get_affine (), header = hdr , extra = None ,
488
543
file_map = file_map )
489
544
490
545
@classmethod
491
546
@kw_only_meth (1 )
492
- def from_filename (klass , filename , mmap = True ):
547
+ def from_filename (klass , filename , mmap = True , keep_file_open = None ):
493
548
"""
494
549
Creates an AFNIImage instance from `filename`
495
550
@@ -504,9 +559,23 @@ def from_filename(klass, filename, mmap=True):
504
559
`mmap` value of True gives the same behavior as ``mmap='c'``. If
505
560
image data file cannot be memory-mapped, ignore `mmap` value and
506
561
read array from file.
562
+ keep_file_open : {None, 'auto', True, False}, optional, keyword only
563
+ `keep_file_open` controls whether a new file handle is created
564
+ every time the image is accessed, or a single file handle is
565
+ created and used for the lifetime of this ``ArrayProxy``. If
566
+ ``True``, a single file handle is created and used. If ``False``,
567
+ a new file handle is created every time the image is accessed. If
568
+ ``'auto'``, and the optional ``indexed_gzip`` dependency is
569
+ present, a single file handle is created and persisted. If
570
+ ``indexed_gzip`` is not available, behavior is the same as if
571
+ ``keep_file_open is False``. If ``file_like`` refers to an open
572
+ file handle, this setting has no effect. The default value
573
+ (``None``) will result in the value of
574
+ ``nibabel.arrayproxy.KEEP_FILE_OPEN_DEFAULT` being used.
507
575
"""
508
576
file_map = klass .filespec_to_file_map (filename )
509
- return klass .from_file_map (file_map , mmap = mmap )
577
+ return klass .from_file_map (file_map , mmap = mmap ,
578
+ keep_file_open = keep_file_open )
510
579
511
580
@classmethod
512
581
def filespec_to_file_map (klass , filespec ):
@@ -539,7 +608,7 @@ def filespec_to_file_map(klass, filespec):
539
608
image type.
540
609
"""
541
610
file_map = super (AFNIImage , klass ).filespec_to_file_map (filespec )
542
- # check for AFNI-specific BRIK/HEAD compression idiosyncracies
611
+ # check for AFNI-specific BRIK/HEAD compression idiosyncrasies
543
612
for key , fholder in file_map .items ():
544
613
fname = fholder .filename
545
614
if key == 'header' and not os .path .exists (fname ):
0 commit comments