Skip to content

Commit 715e04c

Browse files
committed
BF+BK - fix too many open filehandles
1 parent 4861f6f commit 715e04c

File tree

3 files changed

+78
-47
lines changed

3 files changed

+78
-47
lines changed

nibabel/analyze.py

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,45 +1279,37 @@ def set_data_dtype(self, dtype):
12791279
def get_shape(self):
12801280
return self._data.shape
12811281

1282-
@staticmethod
1283-
def _get_open_files(file_map, mode='rb'):
1284-
''' Utility method to open necessary files for read/write
1285-
1286-
This method is to allow for formats (nifti single in particular)
1287-
that may have the same file for header and image
1288-
'''
1289-
hdrf = file_map['header'].get_prepare_fileobj(mode=mode)
1290-
imgf = file_map['image'].get_prepare_fileobj(mode=mode)
1291-
return hdrf, imgf
1292-
1293-
def _close_filenames(self, file_map, hdrf, imgf):
1294-
''' Utility method to close any files no longer required
1295-
1296-
Called by the image writing routines.
1297-
1298-
This method is to allow for formats (nifti single in particular)
1299-
that may have the same file for header and image
1300-
'''
1301-
if file_map['header'].fileobj is None: # was filename
1302-
hdrf.close()
1303-
if file_map['image'].fileobj is None: # was filename
1304-
imgf.close()
1305-
13061282
@classmethod
13071283
def from_file_map(klass, file_map):
13081284
''' class method to create image from mapping in `file_map ``
13091285
'''
1310-
hdrf, imgf = klass._get_open_files(file_map, 'rb')
1286+
hdr_fh, img_fh = klass._get_fileholders(file_map)
1287+
hdrf = hdr_fh.get_prepare_fileobj(mode='rb')
13111288
header = klass.header_class.from_fileobj(hdrf)
1289+
if hdr_fh.fileobj is None: # was filename
1290+
hdrf.close()
13121291
affine = header.get_best_affine()
13131292
hdr_copy = header.copy()
1293+
imgf = img_fh.fileobj
1294+
if imgf is None:
1295+
imgf = img_fh.filename
13141296
data = klass.ImageArrayProxy(imgf, hdr_copy)
13151297
img = klass(data, affine, header, file_map=file_map)
13161298
img._load_cache = {'header': hdr_copy,
13171299
'affine': affine.copy(),
13181300
'file_map': copy_file_map(file_map)}
13191301
return img
13201302

1303+
@staticmethod
1304+
def _get_fileholders(file_map):
1305+
""" Return fileholder for header and image
1306+
1307+
Allows single-file image types to return one fileholder for both types.
1308+
For Analyze there are two fileholders, one for the header, one for the
1309+
image.
1310+
"""
1311+
return file_map['header'], file_map['image']
1312+
13211313
def _write_header(self, header_file, header, slope, inter):
13221314
''' Utility routine to write header
13231315
@@ -1384,10 +1376,15 @@ def to_file_map(self, file_map=None):
13841376
self.update_header()
13851377
hdr = self.get_header()
13861378
slope, inter, mn, mx = hdr.scaling_from_data(data)
1387-
hdrf, imgf = self._get_open_files(file_map, 'wb')
1379+
hdr_fh, img_fh = self._get_fileholders(file_map)
1380+
hdrf = hdr_fh.get_prepare_fileobj(mode='wb')
1381+
imgf = img_fh.get_prepare_fileobj(mode='wb')
13881382
self._write_header(hdrf, hdr, slope, inter)
13891383
self._write_image(imgf, data, hdr, slope, inter, mn, mx)
1390-
self._close_filenames(file_map, hdrf, imgf)
1384+
if hdr_fh.fileobj is None: # was filename
1385+
hdrf.close()
1386+
if img_fh.fileobj is None: # was filename
1387+
imgf.close()
13911388
self._header = hdr
13921389
self.file_map = file_map
13931390

nibabel/nifti1.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,20 +1334,6 @@ class Nifti1PairHeader(Nifti1Header):
13341334
class Nifti1Pair(analyze.AnalyzeImage):
13351335
header_class = Nifti1PairHeader
13361336

1337-
@classmethod
1338-
def from_file_map(klass, file_map):
1339-
hdrf, imgf = klass._get_open_files(file_map, 'rb')
1340-
header = klass.header_class.from_fileobj(hdrf)
1341-
extra = None
1342-
affine = header.get_best_affine()
1343-
hdr_copy = header.copy()
1344-
data = klass.ImageArrayProxy(imgf, hdr_copy)
1345-
img = klass(data, affine, header, extra, file_map)
1346-
img._load_cache = {'header': hdr_copy,
1347-
'affine': affine.copy(),
1348-
'file_map': copy_file_map(file_map)}
1349-
return img
1350-
13511337
def _write_header(self, header_file, header, slope, inter):
13521338
super(Nifti1Pair, self)._write_header(header_file,
13531339
header,
@@ -1383,13 +1369,13 @@ class Nifti1Image(Nifti1Pair):
13831369
files_types = (('image', '.nii'),)
13841370

13851371
@staticmethod
1386-
def _get_open_files(file_map, mode='rb'):
1387-
hdrf = file_map['image'].get_prepare_fileobj(mode=mode)
1388-
return hdrf, hdrf
1372+
def _get_fileholders(file_map):
1373+
""" Return fileholder for header and image
13891374
1390-
def _close_filenames(self, file_map, hdrf, imgf):
1391-
if file_map['image'].fileobj is None: # was filename
1392-
imgf.close()
1375+
For single-file niftis, the fileholder for the header and the image will
1376+
be the same
1377+
"""
1378+
return file_map['image'], file_map['image']
13931379

13941380
def _write_header(self, header_file, header, slope, inter):
13951381
super(Nifti1Image, self)._write_header(header_file,

nibabel/tests/test_filehandles.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
Check that loading an image does not use up filehandles.
3+
"""
4+
from __future__ import with_statement
5+
6+
from os.path import join as pjoin
7+
import shutil
8+
from tempfile import mkdtemp
9+
from warnings import warn
10+
11+
import numpy as np
12+
13+
try:
14+
import resource as res
15+
except ImportError:
16+
# Not on Unix, guess limit
17+
SOFT_LIMIT = 512
18+
else:
19+
SOFT_LIMIT, HARD_LIMIT = res.getrlimit(res.RLIMIT_NOFILE)
20+
21+
from ..loadsave import load, save
22+
from ..nifti1 import Nifti1Image
23+
24+
from numpy.testing import (assert_array_almost_equal,
25+
assert_array_equal)
26+
27+
from nose.tools import assert_true, assert_equal, assert_raises
28+
29+
30+
def test_multiload():
31+
# Make a tiny image, save, load many times. If we are leaking filehandles,
32+
# this will cause us to run out and generate an error
33+
N = SOFT_LIMIT + 100
34+
if N > 5000:
35+
warn('It would take too long to test file handles, aborting')
36+
return
37+
arr = np.arange(24).reshape((2,3,4))
38+
img = Nifti1Image(arr, np.eye(4))
39+
try:
40+
tmpdir = mkdtemp()
41+
fname = pjoin(tmpdir, 'test.img')
42+
save(img, fname)
43+
imgs = []
44+
for i in range(N):
45+
imgs.append(load(fname))
46+
finally:
47+
del img, imgs
48+
shutil.rmtree(tmpdir)

0 commit comments

Comments
 (0)