Skip to content

Commit 6fb7538

Browse files
authored
Merge pull request #585 from nipy/rel/2.2.1
2.2.1 bug fix release
2 parents 303a370 + 9b1ead0 commit 6fb7538

18 files changed

+150
-71
lines changed

.mailmap

+1
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ Satrajit Ghosh <[email protected]> Satrajit Ghosh <[email protected]>
4040
Jasper J.F. van den Bosch <[email protected]> Jasper <[email protected]>
4141
Gregory R. Lee <[email protected]> Gregory R. Lee <[email protected]>
4242
Demian Wassermann <[email protected]> Demian Wassermann <[email protected]>
43+
Paul McCarthy <[email protected]> Paul McCarthy <[email protected]>

Changelog

+19
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,25 @@ Gerhard (SG) and Eric Larson (EL).
2424

2525
References like "pr/298" refer to github pull request numbers.
2626

27+
2.2.1 (Wednesday 22 November 2017)
28+
==================================
29+
30+
Bug fixes
31+
---------
32+
33+
* Set L/R labels in orthoview correctly (pr/564) (CM)
34+
* Defer use of ufunc / memmap test - allows "freezing" (pr/572) (MB, reviewed
35+
by Satra Ghosh)
36+
* Fix doctest failures with pre-release numpy (pr/582) (MB, reviewed by CM)
37+
38+
Maintenance
39+
-----------
40+
41+
* Update documentation around NIfTI qform/sform codes (pr/576) (Paul McCarthy,
42+
reviewed by MB, CM) + (pr/580) (Bennet Fauber, reviewed by Paul McCarthy)
43+
* Skip precision test on macOS, newer numpy (pr/583) (MB, reviewed by CM)
44+
* Simplify AppVeyor script, removing conda (pr/584) (MB, reviewed by CM)
45+
2746
2.2 (Friday 13 October 2017)
2847
============================
2948

appveyor.yml

+16-42
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,34 @@
11
# vim ft=yaml
22
# CI on Windows via appveyor
3-
# This file was based on Olivier Grisel's python-appveyor-demo
43

54
environment:
65

76
matrix:
8-
- PYTHON: "C:\\Python27-conda32"
9-
PYTHON_VERSION: "2.7"
10-
PYTHON_ARCH: "32"
11-
12-
- PYTHON: "C:\\Python34-conda32"
13-
PYTHON_VERSION: "3.4"
14-
PYTHON_ARCH: "32"
15-
16-
- PYTHON: "C:\\Python34-conda64"
17-
PYTHON_VERSION: "3.4"
18-
PYTHON_ARCH: "64"
19-
20-
- PYTHON: "C:\\Python35-conda64"
21-
PYTHON_VERSION: "3.5"
22-
PYTHON_ARCH: "64"
23-
24-
- PYTHON: "C:\\Python35-conda32"
25-
PYTHON_VERSION: "3.5"
26-
PYTHON_ARCH: "32"
7+
- PYTHON: C:\Python27
8+
- PYTHON: C:\Python27-x64
9+
- PYTHON: C:\Python34
10+
- PYTHON: C:\Python34-x64
11+
- PYTHON: C:\Python35
12+
- PYTHON: C:\Python35-x64
13+
- PYTHON: C:\Python36
14+
- PYTHON: C:\Python36-x64
2715

2816
install:
29-
# Install miniconda Python
30-
- "powershell ./tools/install_python.ps1"
31-
3217
# Prepend newly installed Python to the PATH of this build (this cannot be
3318
# done from inside the powershell script as it would require to restart
3419
# the parent CMD process).
35-
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
36-
37-
# Set up a conda environment:
38-
- conda config --set always_yes yes
39-
- conda update -q conda
40-
- conda info -a
41-
- conda create -q -n test-environment python=%PYTHON_VERSION%
42-
- activate test-environment
43-
44-
# Check that we have the expected version and architecture for Python
45-
- "python --version"
46-
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
20+
- SET PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
4721

4822
# Install the dependencies of the project.
49-
- "conda install --yes --quiet numpy scipy matplotlib nose h5py mock"
50-
- "pip install pydicom"
51-
- "python setup.py install"
52-
- "SET NIBABEL_DATA_DIR=%CD%\\nibabel-data"
23+
- pip install numpy scipy matplotlib nose h5py mock
24+
- pip install pydicom
25+
- pip install .
26+
- SET NIBABEL_DATA_DIR=%CD%\nibabel-data
5327

5428
build: false # Not a C# project, build stuff at the test step instead.
5529

5630
test_script:
5731
# Change into an innocuous directory and find tests from installation
58-
- "mkdir for_testing"
59-
- "cd for_testing"
60-
- "nosetests --with-doctest nibabel"
32+
- mkdir for_testing
33+
- cd for_testing
34+
- nosetests --with-doctest nibabel

doc/source/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ contributed code and discussion (in rough order of appearance):
7979
* Venky Reddy
8080
* Mark Hymers
8181
* Jasper J.F. van den Bosch
82+
* Bennet Fauber
8283

8384
License reprise
8485
===============

doc/source/nifti_images.rst

+58-1
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ You can get the affine and the code using the ``coded=True`` argument to
239239
[ 0. , 0.32, 2.17, -7.25],
240240
[ 0. , 0. , 0. , 1. ]]), array(1, dtype=int16))
241241

242-
You can set the sform with with the ``get_sform()`` method of the header and
242+
You can set the sform with the ``set_sform()`` method of the header and
243243
the image.
244244

245245
>>> n1_header.set_sform(np.diag([2, 3, 4, 1]))
@@ -314,6 +314,63 @@ The algorithm is defined in the ``get_best_affine()`` method. It is:
314314
#. If ``qform_code`` != 0 ('unknown') use the qform affine; else
315315
#. Use the fall-back affine.
316316

317+
.. _default-sform-qform-codes:
318+
319+
Default sform and qform codes
320+
=============================
321+
322+
If you create a new image, e.g.:
323+
324+
>>> data = np.random.random((20, 20, 20))
325+
>>> xform = np.eye(4) * 2
326+
>>> img = nib.nifti1.Nifti1Image(data, xform)
327+
328+
The sform and qform codes will be initialised to 2 (aligned) and 0 (unknown)
329+
respectively:
330+
331+
>>> img.get_sform(coded=True) # doctest: +NORMALIZE_WHITESPACE
332+
(array([[ 2., 0., 0., 0.],
333+
[ 0., 2., 0., 0.],
334+
[ 0., 0., 2., 0.],
335+
[ 0., 0., 0., 1.]]), array(2, dtype=int16))
336+
>>> img.get_qform(coded=True)
337+
(None, 0)
338+
339+
This is based on the assumption that the affine you specify for a newly
340+
created image will align the image to some known coordinate system. According
341+
to the `NIfTI specification <nifti1>`_, the qform is intended to encode a
342+
transformation into scanner coordinates - for a programmatically created
343+
image, we have no way of knowing what the scanner coordinate system is;
344+
furthermore, the qform cannot be used to store an arbitrary affine transform,
345+
as it is unable to encode shears. So the provided affine will be stored in the
346+
sform, and the qform will be left uninitialised.
347+
348+
If you create a new image and specify an existing header, e.g.:
349+
350+
>>> example_ni1 = os.path.join(data_path, 'example4d.nii.gz')
351+
>>> n1_img = nib.load(example_ni1)
352+
>>> new_header = header=n1_img.header.copy()
353+
>>> new_data = np.random.random(n1_img.shape[:3])
354+
>>> new_img = nib.nifti1.Nifti1Image(data, None, header=new_header)
355+
356+
then the newly created image will inherit the same sform and qform codes that
357+
are in the provided header. However, if you create a new image with both an
358+
affine and a header specified, e.g.:
359+
360+
>>> xform = np.eye(4)
361+
>>> new_img = nib.nifti1.Nifti1Image(data, xform, header=new_header)
362+
363+
then the sform and qform codes will *only* be preserved if the provided affine
364+
is the same as the affine in the provided header. If the affines do not match,
365+
the sform and qform codes will be set to their default values of 2 and 0
366+
respectively. This is done on the basis that, if you are changing the affine,
367+
you are likely to be changing the space to which the affine is pointing. So
368+
the original sform and qform codes can no longer be assumed to be valid.
369+
370+
If you wish to set the sform and qform affines and/or codes to some other
371+
value, you can always set them after creation using the ``set_sform`` and
372+
``set_qform`` methods, as described above.
373+
317374
************
318375
Data scaling
319376
************

nibabel/benchmarks/bench_arrayproxy_slicing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def testfunc():
191191
data[:, 1] = [r[4] for r in results]
192192
try:
193193
data[:, 2] = [r[3] / r[4] for r in results]
194-
except:
194+
except ZeroDivisionError:
195195
data[:, 2] = np.nan
196196
data[:, 3] = [r[5] - r[6] for r in results]
197197

nibabel/ecat.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -468,13 +468,13 @@ def get_series_framenumbers(mlist):
468468
mlist_nframes = len(frames_order)
469469
trueframenumbers = np.arange(nframes - mlist_nframes, nframes)
470470
frame_dict = {}
471-
try:
472-
for frame_stored, (true_order, _) in frames_order.items():
473-
# frame as stored in file -> true number in series
471+
for frame_stored, (true_order, _) in frames_order.items():
472+
# frame as stored in file -> true number in series
473+
try:
474474
frame_dict[frame_stored] = trueframenumbers[true_order] + 1
475-
return frame_dict
476-
except:
477-
raise IOError('Error in header or mlist order unknown')
475+
except IndexError:
476+
raise IOError('Error in header or mlist order unknown')
477+
return frame_dict
478478

479479

480480
def read_subheaders(fileobj, mlist, endianness):

nibabel/info.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
_version_major = 2
2020
_version_minor = 2
2121
_version_micro = 1
22-
_version_extra = 'dev'
23-
# _version_extra = ''
22+
# _version_extra = 'dev'
23+
_version_extra = ''
2424

2525
# Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z"
2626
__version__ = "%s.%s.%s%s" % (_version_major,

nibabel/nicom/dwiparams.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
'''
2222
import numpy as np
2323
import numpy.linalg as npl
24-
from ..testing import setup_test # flake8: noqa F401
24+
from ..testing import setup_test as setup_module # flake8: noqa F401
2525

2626

2727
def B2q(B, tol=None):

nibabel/nifti1.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -1764,7 +1764,20 @@ def __init__(self, dataobj, affine, header=None,
17641764
if header is None and affine is not None:
17651765
self._affine2header()
17661766
# Copy docstring
1767-
__init__.doc = analyze.AnalyzeImage.__init__.__doc__
1767+
__init__.__doc__ = analyze.AnalyzeImage.__init__.__doc__ + '''
1768+
Notes
1769+
-----
1770+
1771+
If both a `header` and an `affine` are specified, and the `affine` does
1772+
not match the affine that is in the `header`, the `affine` will be used,
1773+
but the ``sform_code`` and ``qform_code`` fields in the header will be
1774+
re-initialised to their default values. This is performed on the basis
1775+
that, if you are changing the affine, you are likely to be changing the
1776+
space to which the affine is pointing. The :meth:`set_sform` and
1777+
:meth:`set_qform` methods can be used to update the codes after an image
1778+
has been created - see those methods, and the :ref:`manual
1779+
<default-sform-qform-codes>` for more details. '''
1780+
17681781

17691782
def update_header(self):
17701783
''' Harmonize header with image data and affine

nibabel/testing/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
data_path = abspath(pjoin(dirname(__file__), '..', 'tests', 'data'))
3333

3434

35-
from .np_features import VIRAL_MEMMAP
35+
from .np_features import memmap_after_ufunc
3636

3737
def assert_dt_equal(a, b):
3838
""" Assert two numpy dtype specifiers are equal
@@ -218,4 +218,4 @@ def setup_test():
218218
"""
219219
from distutils.version import LooseVersion
220220
if LooseVersion(np.__version__) >= LooseVersion('1.14'):
221-
np.set_printoptions(sign='legacy')
221+
np.set_printoptions(legacy="1.13")

nibabel/testing/np_features.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
import numpy as np
55

66

7-
def _memmap_after_ufunc():
7+
def memmap_after_ufunc():
88
""" Return True if ufuncs on memmap arrays always return memmap arrays
99
1010
This should be True for numpy < 1.12, False otherwise.
11+
12+
Memoize after first call. We do this to avoid having to call this when
13+
importing nibabel.testing, because we cannot depend on the source file
14+
being present - see gh-571.
1115
"""
16+
if memmap_after_ufunc.result is not None:
17+
return memmap_after_ufunc.result
1218
with open(__file__, 'rb') as fobj:
1319
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-
20+
memmap_after_ufunc.result = isinstance(mm_arr + 1, np.memmap)
21+
return memmap_after_ufunc.result
1722

18-
# True if ufunc on memmap always returns a memmap
19-
VIRAL_MEMMAP = _memmap_after_ufunc()
23+
memmap_after_ufunc.result = None

nibabel/tests/test_arrayproxy.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from numpy.testing import assert_array_equal, assert_array_almost_equal
3131
from nose.tools import (assert_true, assert_false, assert_equal,
3232
assert_not_equal, assert_raises)
33-
from nibabel.testing import VIRAL_MEMMAP
33+
from nibabel.testing import memmap_after_ufunc
3434

3535
from .test_fileslice import slicer_samples
3636
from .test_openers import patch_indexed_gzip
@@ -298,6 +298,8 @@ def check_mmap(hdr, offset, proxy_class,
298298
# Whether scaled array memory backed by memory map (regardless of what
299299
# numpy says).
300300
scaled_really_mmap = unscaled_really_mmap and not has_scaling
301+
# Whether ufunc on memmap return memmap
302+
viral_memmap = memmap_after_ufunc()
301303
with InTemporaryDirectory():
302304
with open(fname, 'wb') as fobj:
303305
fobj.write(b' ' * offset)
@@ -324,9 +326,9 @@ def check_mmap(hdr, offset, proxy_class,
324326
assert_false(back_is_mmap)
325327
else:
326328
assert_equal(unscaled_is_mmap,
327-
VIRAL_MEMMAP or unscaled_really_mmap)
329+
viral_memmap or unscaled_really_mmap)
328330
assert_equal(back_is_mmap,
329-
VIRAL_MEMMAP or scaled_really_mmap)
331+
viral_memmap or scaled_really_mmap)
330332
if scaled_really_mmap:
331333
assert_equal(back_data.mode, expected_mode)
332334
del prox, back_data

nibabel/tests/test_floating.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
PY2 = sys.version_info[0] < 3
66

7+
from distutils.version import LooseVersion
8+
79
import numpy as np
810

911
from ..casting import (floor_exact, ceil_exact, as_int, FloatingError,
@@ -103,7 +105,12 @@ def test_check_nmant_nexp():
103105
ti = type_info(t)
104106
if ti['nmant'] != 106: # This check does not work for PPC double pair
105107
assert_true(_check_nmant(t, ti['nmant']))
106-
assert_true(_check_maxexp(t, ti['maxexp']))
108+
# Test fails for longdouble after blacklisting of OSX powl as of numpy
109+
# 1.12 - see https://github.com/numpy/numpy/issues/8307
110+
if (t != np.longdouble or
111+
sys.platform != 'darwin' or
112+
LooseVersion(np.__version__) < LooseVersion('1.12')):
113+
assert_true(_check_maxexp(t, ti['maxexp']))
107114

108115

109116
def test_as_int():

nibabel/tests/test_image_api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,9 @@ def validate_shape(self, imaker, params):
377377

378378
def validate_shape_deprecated(self, imaker, params):
379379
# Check deprecated get_shape API
380+
img = imaker()
380381
with clear_and_catch_warnings() as w:
381382
warnings.simplefilter('always', DeprecationWarning)
382-
img = imaker()
383383
assert_equal(img.get_shape(), params['shape'])
384384
assert_equal(len(w), 1)
385385

nibabel/tests/test_spatialimages.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
from .test_helpers import bytesio_round_trip
2727
from ..testing import (clear_and_catch_warnings, suppress_warnings,
28-
VIRAL_MEMMAP)
28+
memmap_after_ufunc)
2929
from ..tmpdirs import InTemporaryDirectory
3030
from .. import load as top_load
3131

@@ -464,6 +464,7 @@ def get_disk_image(self):
464464
def test_load_mmap(self):
465465
# Test memory mapping when loading images
466466
img_klass = self.image_class
467+
viral_memmap = memmap_after_ufunc()
467468
with InTemporaryDirectory():
468469
img, fname, has_scaling = self.get_disk_image()
469470
file_map = img.file_map.copy()
@@ -485,7 +486,7 @@ def test_load_mmap(self):
485486
# numpies returned a memmap object, even though the array
486487
# has no mmap memory backing. See:
487488
# https://github.com/numpy/numpy/pull/7406
488-
if has_scaling and not VIRAL_MEMMAP:
489+
if has_scaling and not viral_memmap:
489490
expected_mode = None
490491
kwargs = {}
491492
if mmap is not None:

0 commit comments

Comments
 (0)