25
25
26
26
See :mod:`nibabel.tests.test_proxy_api` for proxy API conformance checks.
27
27
"""
28
+ from contextlib import contextmanager
29
+
28
30
import numpy as np
29
31
30
32
from .deprecated import deprecate_with_version
@@ -69,8 +71,8 @@ class ArrayProxy(object):
69
71
_header = None
70
72
71
73
@kw_only_meth (2 )
72
- def __init__ (self , file_like , spec , mmap = True ):
73
- """ Initialize array proxy instance
74
+ def __init__ (self , file_like , spec , mmap = True , keep_file_open = False ):
75
+ """Initialize array proxy instance
74
76
75
77
Parameters
76
78
----------
@@ -99,12 +101,16 @@ def __init__(self, file_like, spec, mmap=True):
99
101
True gives the same behavior as ``mmap='c'``. If `file_like`
100
102
cannot be memory-mapped, ignore `mmap` value and read array from
101
103
file.
102
- scaling : {'fp', 'dv'}, optional, keyword only
103
- Type of scaling to use - see header ``get_data_scaling`` method.
104
+ keep_file_open: If ``file_like`` is a file name, the default behaviour
105
+ is to open a new file handle every time the data is accessed. If
106
+ this flag is set to `True``, the file handle will be opened on the
107
+ first access, and kept open until this ``ArrayProxy`` is garbage-
108
+ collected.
104
109
"""
105
110
if mmap not in (True , False , 'c' , 'r' ):
106
111
raise ValueError ("mmap should be one of {True, False, 'c', 'r'}" )
107
112
self .file_like = file_like
113
+ self ._keep_file_open = keep_file_open
108
114
if hasattr (spec , 'get_data_shape' ):
109
115
slope , inter = spec .get_slope_inter ()
110
116
par = (spec .get_data_shape (),
@@ -126,6 +132,15 @@ def __init__(self, file_like, spec, mmap=True):
126
132
self ._dtype = np .dtype (self ._dtype )
127
133
self ._mmap = mmap
128
134
135
+ def __del__ (self ):
136
+ '''If this ``ArrayProxy`` was created with ``keep_file_open=True``,
137
+ the open file object is closed if necessary.
138
+ '''
139
+ if self ._keep_file_open and hasattr (self , '_opener' ):
140
+ if not self ._opener .closed :
141
+ self ._opener .close ()
142
+ self ._opener = None
143
+
129
144
@property
130
145
@deprecate_with_version ('ArrayProxy.header deprecated' , '2.2' , '3.0' )
131
146
def header (self ):
@@ -155,12 +170,26 @@ def inter(self):
155
170
def is_proxy (self ):
156
171
return True
157
172
173
+ @contextmanager
174
+ def _get_fileobj (self ):
175
+ '''Create and return a new ``ImageOpener``, or return an existing one.
176
+ one. The specific behaviour depends on the value of the
177
+ ``keep_file_open`` flag that was passed to ``__init__``.
178
+ '''
179
+ if self ._keep_file_open :
180
+ if not hasattr (self , '_opener' ):
181
+ self ._opener = ImageOpener (self .file_like )
182
+ yield self ._opener
183
+ else :
184
+ with ImageOpener (self .file_like ) as opener :
185
+ yield opener
186
+
158
187
def get_unscaled (self ):
159
188
''' Read of data from file
160
189
161
190
This is an optional part of the proxy API
162
191
'''
163
- with ImageOpener ( self .file_like ) as fileobj :
192
+ with self ._get_fileobj ( ) as fileobj :
164
193
raw_data = array_from_file (self ._shape ,
165
194
self ._dtype ,
166
195
fileobj ,
@@ -175,7 +204,7 @@ def __array__(self):
175
204
return apply_read_scaling (raw_data , self ._slope , self ._inter )
176
205
177
206
def __getitem__ (self , slicer ):
178
- with ImageOpener ( self .file_like ) as fileobj :
207
+ with self ._get_fileobj ( ) as fileobj :
179
208
raw_data = fileslice (fileobj ,
180
209
slicer ,
181
210
self ._shape ,
0 commit comments