Skip to content

Commit b3bfcec

Browse files
committed
Merge pull request #441 from matthew-brett/fix-travis-tests
MRG: change to get travis tests passing again. Exclude shim module from flake8 tests. Adapt to new numpy 1.12 memmap behavior.
2 parents 7397e60 + a4cc417 commit b3bfcec

File tree

6 files changed

+93
-15
lines changed

6 files changed

+93
-15
lines changed

nibabel/pydicom_compat.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
* dicom_test : test decorator that skips test if dicom not available.
1414
"""
1515

16+
# Module does (apparently) unused imports; stop flake8 complaining
17+
# flake8: noqa
18+
1619
import numpy as np
1720

1821
have_dicom = True

nibabel/testing/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
data_path = abspath(pjoin(dirname(__file__), '..', 'tests', 'data'))
3030

3131

32+
from .np_features import VIRAL_MEMMAP
33+
3234
def assert_dt_equal(a, b):
3335
""" Assert two numpy dtype specifiers are equal
3436

nibabel/testing/np_features.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
""" Look for changes in numpy behavior over versions
2+
"""
3+
4+
import numpy as np
5+
6+
7+
def _memmap_after_ufunc():
8+
""" Return True if ufuncs on memmap arrays always return memmap arrays
9+
10+
This should be True for numpy < 1.12, False otherwise.
11+
"""
12+
with open(__file__, 'rb') as fobj:
13+
mm_arr = np.memmap(fobj, mode='r', shape=(10,), dtype=np.uint8)
14+
mm_preserved = isinstance(mm_arr + 1, np.memmap)
15+
return mm_preserved
16+
17+
18+
# True if ufunc on memmap always returns a memmap
19+
VIRAL_MEMMAP = _memmap_after_ufunc()

nibabel/tests/test_arrayproxy.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from numpy.testing import assert_array_equal, assert_array_almost_equal
2424
from nose.tools import (assert_true, assert_false, assert_equal,
2525
assert_not_equal, assert_raises)
26+
from nibabel.testing import VIRAL_MEMMAP
2627

2728
from .test_fileslice import slicer_samples
2829

@@ -182,10 +183,37 @@ def test_mmap():
182183
check_mmap(hdr, hdr.get_data_offset(), ArrayProxy)
183184

184185

185-
def check_mmap(hdr, offset, proxy_class, check_mode=True):
186+
def check_mmap(hdr, offset, proxy_class,
187+
has_scaling=False,
188+
unscaled_is_view=True):
189+
""" Assert that array proxies return memory maps as expected
190+
191+
Parameters
192+
----------
193+
hdr : object
194+
Image header instance
195+
offset : int
196+
Offset in bytes of image data in file (that we will write)
197+
proxy_class : class
198+
Class of image array proxy to test
199+
has_scaling : {False, True}
200+
True if the `hdr` says to apply scaling to the output data, False
201+
otherwise.
202+
unscaled_is_view : {True, False}
203+
True if getting the unscaled data returns a view of the array. If
204+
False, then type of returned array will depend on whether numpy has the
205+
old viral (< 1.12) memmap behavior (returns memmap) or the new behavior
206+
(returns ndarray). See: https://github.com/numpy/numpy/pull/7406
207+
"""
186208
shape = hdr.get_data_shape()
187209
arr = np.arange(np.prod(shape), dtype=hdr.get_data_dtype()).reshape(shape)
188210
fname = 'test.bin'
211+
# Whether unscaled array memory backed by memory map (regardless of what
212+
# numpy says).
213+
unscaled_really_mmap = unscaled_is_view
214+
# Whether scaled array memory backed by memory map (regardless of what
215+
# numpy says).
216+
scaled_really_mmap = unscaled_really_mmap and not has_scaling
189217
with InTemporaryDirectory():
190218
with open(fname, 'wb') as fobj:
191219
fobj.write(b' ' * offset)
@@ -205,13 +233,17 @@ def check_mmap(hdr, offset, proxy_class, check_mode=True):
205233
prox = proxy_class(fname, hdr, **kwargs)
206234
unscaled = prox.get_unscaled()
207235
back_data = np.asanyarray(prox)
236+
unscaled_is_mmap = isinstance(unscaled, np.memmap)
237+
back_is_mmap = isinstance(back_data, np.memmap)
208238
if expected_mode is None:
209-
assert_false(isinstance(unscaled, np.memmap))
210-
assert_false(isinstance(back_data, np.memmap))
239+
assert_false(unscaled_is_mmap)
240+
assert_false(back_is_mmap)
211241
else:
212-
assert_true(isinstance(unscaled, np.memmap))
213-
assert_true(isinstance(back_data, np.memmap))
214-
if check_mode:
242+
assert_equal(unscaled_is_mmap,
243+
VIRAL_MEMMAP or unscaled_really_mmap)
244+
assert_equal(back_is_mmap,
245+
VIRAL_MEMMAP or scaled_really_mmap)
246+
if scaled_really_mmap:
215247
assert_equal(back_data.mode, expected_mode)
216248
del prox, back_data
217249
# Check that mmap is keyword-only

nibabel/tests/test_parrec.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -728,15 +728,18 @@ def test_parrec_proxy():
728728
# Test PAR / REC proxy class, including mmap flags
729729
shape = (10, 20, 30, 5)
730730
hdr = FakeHeader(shape, np.int32)
731-
check_mmap(hdr, 0, PARRECArrayProxy, check_mode=False)
731+
check_mmap(hdr, 0, PARRECArrayProxy,
732+
has_scaling=True,
733+
unscaled_is_view=False)
732734

733735

734736
class TestPARRECImage(tsi.MmapImageMixin):
735737
image_class = PARRECImage
736738
check_mmap_mode = False
737739

738-
def write_image(self):
739-
return parrec.load(EG_PAR), EG_PAR
740+
def get_disk_image(self):
741+
# The example image does have image scaling to apply
742+
return parrec.load(EG_PAR), EG_PAR, True
740743

741744

742745
def test_bitpix():

nibabel/tests/test_spatialimages.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
from numpy.testing import assert_array_equal, assert_array_almost_equal
2525

2626
from .test_helpers import bytesio_round_trip
27-
from ..testing import clear_and_catch_warnings, suppress_warnings
27+
from ..testing import (clear_and_catch_warnings, suppress_warnings,
28+
VIRAL_MEMMAP)
2829
from ..tmpdirs import InTemporaryDirectory
2930
from .. import load as top_load
3031

@@ -340,23 +341,34 @@ class MmapImageMixin(object):
340341
#: whether to test mode of returned memory map
341342
check_mmap_mode = True
342343

343-
def write_image(self):
344-
""" Return an image and an image filname to test against
344+
def get_disk_image(self):
345+
""" Return image, image filename, and flag for required scaling
346+
347+
Subclasses can do anything to return an image, including loading a
348+
pre-existing image from disk.
349+
350+
Returns
351+
-------
352+
img : class:`SpatialImage` instance
353+
fname : str
354+
Image filename.
355+
has_scaling : bool
356+
True if the image array has scaling to apply to the raw image array
357+
data, False otherwise.
345358
"""
346359
img_klass = self.image_class
347360
shape = (3, 4, 2)
348361
data = np.arange(np.prod(shape), dtype=np.int16).reshape(shape)
349362
img = img_klass(data, None)
350363
fname = 'test' + img_klass.files_types[0][1]
351364
img.to_filename(fname)
352-
return img, fname
365+
return img, fname, False
353366

354367
def test_load_mmap(self):
355368
# Test memory mapping when loading images
356369
img_klass = self.image_class
357370
with InTemporaryDirectory():
358-
# This should have no scaling, can be mmapped
359-
img, fname = self.write_image()
371+
img, fname, has_scaling = self.get_disk_image()
360372
file_map = img.file_map.copy()
361373
for func, param1 in ((img_klass.from_filename, fname),
362374
(img_klass.load, fname),
@@ -371,6 +383,13 @@ def test_load_mmap(self):
371383
('c', 'c'),
372384
('r', 'r'),
373385
(False, None)):
386+
# If the image has scaling, then numpy 1.12 will not return
387+
# a memmap, regardless of the input flags. Previous
388+
# numpies returned a memmap object, even though the array
389+
# has no mmap memory backing. See:
390+
# https://github.com/numpy/numpy/pull/7406
391+
if has_scaling and not VIRAL_MEMMAP:
392+
expected_mode = None
374393
kwargs = {}
375394
if mmap is not None:
376395
kwargs['mmap'] = mmap

0 commit comments

Comments
 (0)