Skip to content

Commit cf77776

Browse files
committed
Merge pull request #389 from matthew-brett/fix-freesurfer-load
MRG: fix for Freesurfer mmap bug We were accidentally passing an ImageOpener instance as the file object to the array proxy, and Pyton 3.5 pointed this out by failing: http://nipy.bic.berkeley.edu/builders/nibabel-bdist32-35/builds/9/steps/shell_6/logs/stdio This branch fixes the Python 3.5 tests: http://nipy.bic.berkeley.edu/builders/nibabel-bdist32-35/builds/11/steps/shell_6/logs/stdio
2 parents 4ffa80c + 6b1a51b commit cf77776

File tree

4 files changed

+48
-9
lines changed

4 files changed

+48
-9
lines changed

nibabel/fileholders.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ def same_file_as(self, other):
9090
return ((self.filename == other.filename) and
9191
(self.fileobj == other.fileobj))
9292

93+
@property
94+
def file_like(self):
95+
""" Return ``self.fileobj`` if not None, otherwise ``self.filename``
96+
"""
97+
return self.fileobj if self.fileobj is not None else self.filename
98+
9399

94100
def copy_file_map(file_map):
95101
''' Copy mapping of fileholders given by `file_map`

nibabel/freesurfer/mghformat.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,11 +496,13 @@ def from_file_map(klass, file_map, mmap=True):
496496
'''
497497
if not mmap in (True, False, 'c', 'r'):
498498
raise ValueError("mmap should be one of {True, False, 'c', 'r'}")
499-
mghf = file_map['image'].get_prepare_fileobj('rb')
499+
img_fh = file_map['image']
500+
mghf = img_fh.get_prepare_fileobj('rb')
500501
header = klass.header_class.from_fileobj(mghf)
501502
affine = header.get_affine()
502503
hdr_copy = header.copy()
503-
data = klass.ImageArrayProxy(mghf, hdr_copy, mmap=mmap)
504+
# Pass original image fileobj / filename to array proxy
505+
data = klass.ImageArrayProxy(img_fh.file_like, hdr_copy, mmap=mmap)
504506
img = klass(data, affine, header, file_map=file_map)
505507
img._load_cache = {'header': hdr_copy,
506508
'affine': affine.copy(),

nibabel/freesurfer/tests/test_mghformat.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99
'''Tests for mghformat reading writing'''
1010

1111
import os
12+
import io
13+
import gzip
1214

1315
import numpy as np
1416

1517
from ...externals.six import BytesIO
16-
from .. import load, save, MGHImage
17-
from ..mghformat import MGHHeader, MGHError
18+
from .. import load, save
19+
from ...openers import ImageOpener
20+
from ..mghformat import MGHHeader, MGHError, MGHImage
1821
from ...tmpdirs import InTemporaryDirectory
1922
from ...fileholders import FileHolder
2023

@@ -28,6 +31,8 @@
2831

2932
from ...tests import test_spatialimages as tsi
3033

34+
MGZ_FNAME = os.path.join(data_path, 'test.mgz')
35+
3136
# sample voxel to ras matrix (mri_info --vox2ras)
3237
v2r = np.array([[1, 2, 3, -13], [2, 3, 1, -11.5],
3338
[3, 1, 2, -11.5], [0, 0, 0, 1]], dtype=np.float32)
@@ -43,8 +48,7 @@ def test_read_mgh():
4348
# mri_volsynth --dim 3 4 5 2 --vol test.mgz
4449
# --cdircos 1 2 3 --rdircos 2 3 1 --sdircos 3 1 2
4550
# mri_volsynth is a FreeSurfer command
46-
mgz_path = os.path.join(data_path, 'test.mgz')
47-
mgz = load(mgz_path)
51+
mgz = load(MGZ_FNAME)
4852

4953
# header
5054
h = mgz.header
@@ -165,8 +169,7 @@ def test_header_updating():
165169
# Don't update the header information if the affine doesn't change.
166170
# Luckily the test.mgz dataset had a bad set of cosine vectors, so these
167171
# will be changed if the affine gets updated
168-
mgz_path = os.path.join(data_path, 'test.mgz')
169-
mgz = load(mgz_path)
172+
mgz = load(MGZ_FNAME)
170173
hdr = mgz.header
171174
# Test against mri_info output
172175
exp_aff = np.loadtxt(BytesIO(b"""
@@ -230,6 +233,26 @@ def test_header_slope_inter():
230233
assert_equal(hdr.get_slope_inter(), (None, None))
231234

232235

236+
def test_mgh_load_fileobj():
237+
# Checks the filename gets passed to array proxy
238+
#
239+
# This is a bit of an implementation detail, but the test is to make sure
240+
# that we aren't passing ImageOpener objects to the array proxy, as these
241+
# were confusing mmap on Python 3. If there's some sensible reason not to
242+
# pass the filename to the array proxy, please feel free to change this
243+
# test.
244+
img = MGHImage.load(MGZ_FNAME)
245+
assert_equal(img.dataobj.file_like, MGZ_FNAME)
246+
# Check fileobj also passed into dataobj
247+
with ImageOpener(MGZ_FNAME) as fobj:
248+
contents = fobj.read()
249+
bio = io.BytesIO(contents)
250+
fm = MGHImage.make_file_map(mapping=dict(image=bio))
251+
img2 = MGHImage.from_file_map(fm)
252+
assert_true(img2.dataobj.file_like is bio)
253+
assert_array_equal(img.get_data(), img2.get_data())
254+
255+
233256
class TestMGHImage(tsi.TestSpatialImage, tsi.MmapImageMixin):
234257
""" Apply general image tests to MGHImage
235258
"""

nibabel/tests/test_fileholders.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,12 @@ def test_same_file_as():
5050
assert_true(fh3.same_file_as(fh4_again))
5151

5252

53-
53+
def test_file_like():
54+
# Test returning file object or filename
55+
fh = FileHolder('a_fname')
56+
assert_equal(fh.file_like, 'a_fname')
57+
bio = BytesIO()
58+
fh = FileHolder(fileobj=bio)
59+
assert_true(fh.file_like is bio)
60+
fh = FileHolder('a_fname', fileobj=bio)
61+
assert_true(fh.file_like is bio)

0 commit comments

Comments
 (0)