Skip to content

Commit cc93272

Browse files
committed
Merge pull request #268 from matthew-brett/mmap-api
MRG: mmap keyword for loading images Add mmap keyword to control memory mapping on image load. API as discussed on the nipy list.
2 parents 5c15ed8 + cca7371 commit cc93272

File tree

12 files changed

+411
-55
lines changed

12 files changed

+411
-55
lines changed

nibabel/analyze.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
from .fileholders import copy_file_map
9696
from .batteryrunners import Report
9797
from .arrayproxy import ArrayProxy
98+
from .keywordonly import kw_only_meth
9899

99100
# Sub-parts of standard analyze header from
100101
# Mayo dbh.h file
@@ -915,17 +916,38 @@ def set_data_dtype(self, dtype):
915916
self._header.set_data_dtype(dtype)
916917

917918
@classmethod
918-
def from_file_map(klass, file_map):
919+
@kw_only_meth(1)
920+
def from_file_map(klass, file_map, mmap=True):
919921
''' class method to create image from mapping in `file_map ``
922+
923+
Parameters
924+
----------
925+
file_map : dict
926+
Mapping with (kay, value) pairs of (``file_type``, FileHolder
927+
instance giving file-likes for each file needed for this image
928+
type.
929+
mmap : {True, False, 'c', 'r'}, optional, keyword only
930+
`mmap` controls the use of numpy memory mapping for reading image
931+
array data. If False, do not try numpy ``memmap`` for data array.
932+
If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap`
933+
value of True gives the same behavior as ``mmap='c'``. If image
934+
data file cannot be memory-mapped, ignore `mmap` value and read
935+
array from file.
936+
937+
Returns
938+
-------
939+
img : AnalyzeImage instance
920940
'''
941+
if mmap not in (True, False, 'c', 'r'):
942+
raise ValueError("mmap should be one of {True, False, 'c', 'r'}")
921943
hdr_fh, img_fh = klass._get_fileholders(file_map)
922944
with hdr_fh.get_prepare_fileobj(mode='rb') as hdrf:
923945
header = klass.header_class.from_fileobj(hdrf)
924946
hdr_copy = header.copy()
925947
imgf = img_fh.fileobj
926948
if imgf is None:
927949
imgf = img_fh.filename
928-
data = klass.ImageArrayProxy(imgf, hdr_copy)
950+
data = klass.ImageArrayProxy(imgf, hdr_copy, mmap=mmap)
929951
# Initialize without affine to allow header to pass through unmodified
930952
img = klass(data, None, header, file_map=file_map)
931953
# set affine from header though
@@ -935,6 +957,34 @@ def from_file_map(klass, file_map):
935957
'file_map': copy_file_map(file_map)}
936958
return img
937959

960+
@classmethod
961+
@kw_only_meth(1)
962+
def from_filename(klass, filename, mmap=True):
963+
''' class method to create image from filename `filename`
964+
965+
Parameters
966+
----------
967+
filename : str
968+
Filename of image to load
969+
mmap : {True, False, 'c', 'r'}, optional, keyword only
970+
`mmap` controls the use of numpy memory mapping for reading image
971+
array data. If False, do not try numpy ``memmap`` for data array.
972+
If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap`
973+
value of True gives the same behavior as ``mmap='c'``. If image
974+
data file cannot be memory-mapped, ignore `mmap` value and read
975+
array from file.
976+
977+
Returns
978+
-------
979+
img : Analyze Image instance
980+
'''
981+
if mmap not in (True, False, 'c', 'r'):
982+
raise ValueError("mmap should be one of {True, False, 'c', 'r'}")
983+
file_map = klass.filespec_to_file_map(filename)
984+
return klass.from_file_map(file_map, mmap=mmap)
985+
986+
load = from_filename
987+
938988
@staticmethod
939989
def _get_fileholders(file_map):
940990
""" Return fileholder for header and image

nibabel/arrayproxy.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from .volumeutils import BinOpener, array_from_file, apply_read_scaling
3131
from .fileslice import fileslice
32+
from .keywordonly import kw_only_meth
3233

3334

3435
class ArrayProxy(object):
@@ -55,12 +56,36 @@ class ArrayProxy(object):
5556
including Nifti1, and with the MGH format.
5657
5758
Other image types might need more specific classes to implement the API.
58-
API. See :mod:`nibabel.minc1` and :mod:`nibabel.ecat` for examples.
59+
See :mod:`nibabel.minc1`, :mod:`nibabel.ecat` and :mod:`nibabel.parrec` for
60+
examples.
5961
"""
6062
# Assume Fortran array memory layout
6163
order = 'F'
6264

63-
def __init__(self, file_like, header):
65+
@kw_only_meth(2)
66+
def __init__(self, file_like, header, mmap=True):
67+
""" Initialize array proxy instance
68+
69+
Parameters
70+
----------
71+
file_like : object
72+
File-like object or filename. If file-like object, should implement
73+
at least ``read`` and ``seek``.
74+
header : object
75+
Header object implementing ``get_data_shape``, ``get_data_dtype``,
76+
``get_data_offset``, ``get_slope_inter``
77+
mmap : {True, False, 'c', 'r'}, optional, keyword only
78+
`mmap` controls the use of numpy memory mapping for reading data.
79+
If False, do not try numpy ``memmap`` for data array. If one of
80+
{'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap` value of
81+
True gives the same behavior as ``mmap='c'``. If `file_like`
82+
cannot be memory-mapped, ignore `mmap` value and read array from
83+
file.
84+
scaling : {'fp', 'dv'}, optional, keyword only
85+
Type of scaling to use - see header ``get_data_scaling`` method.
86+
"""
87+
if mmap not in (True, False, 'c', 'r'):
88+
raise ValueError("mmap should be one of {True, False, 'c', 'r'}")
6489
self.file_like = file_like
6590
# Copies of values needed to read array
6691
self._shape = header.get_data_shape()
@@ -69,6 +94,7 @@ def __init__(self, file_like, header):
6994
self._slope, self._inter = header.get_slope_inter()
7095
self._slope = 1.0 if self._slope is None else self._slope
7196
self._inter = 0.0 if self._inter is None else self._inter
97+
self._mmap = mmap
7298
# Reference to original header; we will remove this soon
7399
self._header = header.copy()
74100

@@ -109,7 +135,8 @@ def get_unscaled(self):
109135
self._dtype,
110136
fileobj,
111137
offset=self._offset,
112-
order=self.order)
138+
order=self.order,
139+
mmap=self._mmap)
113140
return raw_data
114141

115142
def __array__(self):

nibabel/freesurfer/mghformat.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
from os.path import splitext
1414
import numpy as np
1515

16-
from nibabel.volumeutils import (array_to_file, array_from_file, Recoder)
17-
from nibabel.spatialimages import HeaderDataError, ImageFileError, SpatialImage
18-
from nibabel.fileholders import FileHolder, copy_file_map
19-
from nibabel.filename_parser import types_filenames, TypesFilenamesError
20-
from nibabel.arrayproxy import ArrayProxy
16+
from ..volumeutils import (array_to_file, array_from_file, Recoder)
17+
from ..spatialimages import HeaderDataError, SpatialImage
18+
from ..fileholders import FileHolder, copy_file_map
19+
from ..arrayproxy import ArrayProxy
20+
from ..keywordonly import kw_only_meth
2121

2222
# mgh header
2323
# See http://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat
@@ -468,26 +468,64 @@ def filespec_to_file_map(klass, filespec):
468468
return super(MGHImage, klass).filespec_to_file_map(filespec)
469469

470470
@classmethod
471-
def from_file_map(klass, file_map):
471+
@kw_only_meth(1)
472+
def from_file_map(klass, file_map, mmap=True):
472473
'''Load image from `file_map`
473474
474475
Parameters
475476
----------
476477
file_map : None or mapping, optional
477478
files mapping. If None (default) use object's ``file_map``
478479
attribute instead
479-
'''
480+
mmap : {True, False, 'c', 'r'}, optional, keyword only
481+
`mmap` controls the use of numpy memory mapping for reading image
482+
array data. If False, do not try numpy ``memmap`` for data array.
483+
If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap`
484+
value of True gives the same behavior as ``mmap='c'``. If image
485+
data file cannot be memory-mapped, ignore `mmap` value and read
486+
array from file.
487+
'''
488+
if not mmap in (True, False, 'c', 'r'):
489+
raise ValueError("mmap should be one of {True, False, 'c', 'r'}")
480490
mghf = file_map['image'].get_prepare_fileobj('rb')
481491
header = klass.header_class.from_fileobj(mghf)
482492
affine = header.get_affine()
483493
hdr_copy = header.copy()
484-
data = klass.ImageArrayProxy(mghf, hdr_copy)
494+
data = klass.ImageArrayProxy(mghf, hdr_copy, mmap=mmap)
485495
img = klass(data, affine, header, file_map=file_map)
486496
img._load_cache = {'header': hdr_copy,
487497
'affine': affine.copy(),
488498
'file_map': copy_file_map(file_map)}
489499
return img
490500

501+
@classmethod
502+
@kw_only_meth(1)
503+
def from_filename(klass, filename, mmap=True):
504+
''' class method to create image from filename `filename`
505+
506+
Parameters
507+
----------
508+
filename : str
509+
Filename of image to load
510+
mmap : {True, False, 'c', 'r'}, optional, keyword only
511+
`mmap` controls the use of numpy memory mapping for reading image
512+
array data. If False, do not try numpy ``memmap`` for data array.
513+
If one of {'c', 'r'}, try numpy memmap with ``mode=mmap``. A `mmap`
514+
value of True gives the same behavior as ``mmap='c'``. If image
515+
data file cannot be memory-mapped, ignore `mmap` value and read
516+
array from file.
517+
518+
Returns
519+
-------
520+
img : MGHImage instance
521+
'''
522+
if not mmap in (True, False, 'c', 'r'):
523+
raise ValueError("mmap should be one of {True, False, 'c', 'r'}")
524+
file_map = klass.filespec_to_file_map(filename)
525+
return klass.from_file_map(file_map, mmap=mmap)
526+
527+
load = from_filename
528+
491529
def to_file_map(self, file_map=None):
492530
''' Write image to `file_map` or contained ``self.file_map``
493531

nibabel/loadsave.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,22 @@
2626
from .arrayproxy import is_proxy
2727

2828

29-
def load(filename):
29+
def load(filename, **kwargs):
3030
''' Load file given filename, guessing at file type
3131
3232
Parameters
3333
----------
3434
filename : string
3535
specification of file to load
36+
\*\*kwargs : keyword arguments
37+
Keyword arguments to format-specific load
3638
3739
Returns
3840
-------
3941
img : ``SpatialImage``
4042
Image of guessed type
4143
'''
42-
return guessed_image_type(filename).from_filename(filename)
44+
return guessed_image_type(filename).from_filename(filename, **kwargs)
4345

4446

4547
def guessed_image_type(filename):

0 commit comments

Comments
 (0)