From fb7b0162c6efd26430764c222b8e9e9353a20be3 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 13 Oct 2017 16:00:36 -0400 Subject: [PATCH 01/24] Bump dev version --- nibabel/info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nibabel/info.py b/nibabel/info.py index 6082fc1fae..821c1ae3d9 100644 --- a/nibabel/info.py +++ b/nibabel/info.py @@ -17,10 +17,10 @@ # We usually use `dev` as `_version_extra` to label this as a development # (pre-release) version. _version_major = 2 -_version_minor = 2 +_version_minor = 3 _version_micro = 0 -# _version_extra = 'dev' -_version_extra = '' +_version_extra = 'dev' +# _version_extra = '' # Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" __version__ = "%s.%s.%s%s" % (_version_major, From ee7d53653a0c82720f6eb9b9b4d150590282971a Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Sat, 14 Oct 2017 10:04:25 -0400 Subject: [PATCH 02/24] FIX: Set L/R labels in orthoview correctly --- nibabel/viewers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/viewers.py b/nibabel/viewers.py index b99c6328cf..f97f0c3bbb 100644 --- a/nibabel/viewers.py +++ b/nibabel/viewers.py @@ -135,7 +135,7 @@ def __init__(self, data, affine=None, axes=None, title=None): self._scalers[self._order[1]] / self._scalers[self._order[0]]] self._sizes = [self._data.shape[order] for order in self._order] for ii, xax, yax, ratio, label in zip([0, 1, 2], [1, 0, 0], [2, 2, 1], - r, ('SAIP', 'SLIR', 'ALPR')): + r, ('SAIP', 'SRIL', 'ARPL')): ax = self._axes[ii] d = np.zeros((self._sizes[yax], self._sizes[xax])) im = self._axes[ii].imshow( From 6345d21c24436abbeb1818bc0f94a1ff9e0bb019 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 24 Oct 2017 10:55:35 -0400 Subject: [PATCH 03/24] TEST: Check only relevant warnings --- nibabel/tests/test_image_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index d71fbcdb30..be270795e9 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -377,9 +377,9 @@ def validate_shape(self, imaker, params): def validate_shape_deprecated(self, imaker, params): # Check deprecated get_shape API + img = imaker() with clear_and_catch_warnings() as w: warnings.simplefilter('always', DeprecationWarning) - img = imaker() assert_equal(img.get_shape(), params['shape']) assert_equal(len(w), 1) From 0471ffbca12ff30ecb5fc688480dda85a9e81a4f Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 24 Oct 2017 11:00:31 -0400 Subject: [PATCH 04/24] STY: Narrow exception check --- nibabel/ecat.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nibabel/ecat.py b/nibabel/ecat.py index 6b193acdcc..3c0957e11d 100644 --- a/nibabel/ecat.py +++ b/nibabel/ecat.py @@ -468,13 +468,13 @@ def get_series_framenumbers(mlist): mlist_nframes = len(frames_order) trueframenumbers = np.arange(nframes - mlist_nframes, nframes) frame_dict = {} - try: - for frame_stored, (true_order, _) in frames_order.items(): - # frame as stored in file -> true number in series + for frame_stored, (true_order, _) in frames_order.items(): + # frame as stored in file -> true number in series + try: frame_dict[frame_stored] = trueframenumbers[true_order] + 1 - return frame_dict - except: - raise IOError('Error in header or mlist order unknown') + except IndexError: + raise IOError('Error in header or mlist order unknown') + return frame_dict def read_subheaders(fileobj, mlist, endianness): From d0c9e3662e8ea21de701e2124326d8bae560c263 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 24 Oct 2017 11:05:25 -0400 Subject: [PATCH 05/24] STY: No bare excepts --- nibabel/benchmarks/bench_arrayproxy_slicing.py | 2 +- nibabel/tripwire.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/benchmarks/bench_arrayproxy_slicing.py b/nibabel/benchmarks/bench_arrayproxy_slicing.py index a824822d3c..321a0779d5 100644 --- a/nibabel/benchmarks/bench_arrayproxy_slicing.py +++ b/nibabel/benchmarks/bench_arrayproxy_slicing.py @@ -191,7 +191,7 @@ def testfunc(): data[:, 1] = [r[4] for r in results] try: data[:, 2] = [r[3] / r[4] for r in results] - except: + except ZeroDivisionError: data[:, 2] = np.nan data[:, 3] = [r[5] - r[6] for r in results] diff --git a/nibabel/tripwire.py b/nibabel/tripwire.py index 3850281587..e31cfe7258 100644 --- a/nibabel/tripwire.py +++ b/nibabel/tripwire.py @@ -24,7 +24,7 @@ def is_tripwire(obj): obj.any_attribute except TripWireError: return True - except: + except Exception: pass return False From 30f8dddff584440263381ec9fe13499d6126fc99 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 28 Oct 2017 18:23:34 +0100 Subject: [PATCH 06/24] BF: defer use of ufunc / memmap test until testing Avoid doing the ufunc / memmap test until we need it. Might fix gh-571. --- nibabel/testing/__init__.py | 2 +- nibabel/testing/np_features.py | 16 ++++++++++------ nibabel/tests/test_arrayproxy.py | 8 +++++--- nibabel/tests/test_spatialimages.py | 5 +++-- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/nibabel/testing/__init__.py b/nibabel/testing/__init__.py index 2fa7f809f6..0cc34ac62d 100644 --- a/nibabel/testing/__init__.py +++ b/nibabel/testing/__init__.py @@ -32,7 +32,7 @@ data_path = abspath(pjoin(dirname(__file__), '..', 'tests', 'data')) -from .np_features import VIRAL_MEMMAP +from .np_features import memmap_after_ufunc def assert_dt_equal(a, b): """ Assert two numpy dtype specifiers are equal diff --git a/nibabel/testing/np_features.py b/nibabel/testing/np_features.py index 1473cb0c72..8919542d1c 100644 --- a/nibabel/testing/np_features.py +++ b/nibabel/testing/np_features.py @@ -4,16 +4,20 @@ import numpy as np -def _memmap_after_ufunc(): +def memmap_after_ufunc(): """ Return True if ufuncs on memmap arrays always return memmap arrays This should be True for numpy < 1.12, False otherwise. + + Memoize after first call. We do this to avoid having to call this when + importing nibabel.testing, because we cannot depend on the source file + being present - see gh-571. """ + if memmap_after_ufunc.result is not None: + return memmap_after_ufunc.result with open(__file__, 'rb') as fobj: mm_arr = np.memmap(fobj, mode='r', shape=(10,), dtype=np.uint8) - mm_preserved = isinstance(mm_arr + 1, np.memmap) - return mm_preserved - + memmap_after_ufunc.result = isinstance(mm_arr + 1, np.memmap) + return memmap_after_ufunc.result -# True if ufunc on memmap always returns a memmap -VIRAL_MEMMAP = _memmap_after_ufunc() +memmap_after_ufunc.result = None diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py index 70265a5860..9e9e54d0bd 100644 --- a/nibabel/tests/test_arrayproxy.py +++ b/nibabel/tests/test_arrayproxy.py @@ -30,7 +30,7 @@ from numpy.testing import assert_array_equal, assert_array_almost_equal from nose.tools import (assert_true, assert_false, assert_equal, assert_not_equal, assert_raises) -from nibabel.testing import VIRAL_MEMMAP +from nibabel.testing import memmap_after_ufunc from .test_fileslice import slicer_samples from .test_openers import patch_indexed_gzip @@ -298,6 +298,8 @@ def check_mmap(hdr, offset, proxy_class, # Whether scaled array memory backed by memory map (regardless of what # numpy says). scaled_really_mmap = unscaled_really_mmap and not has_scaling + # Whether ufunc on memmap return memmap + viral_memmap = memmap_after_ufunc() with InTemporaryDirectory(): with open(fname, 'wb') as fobj: fobj.write(b' ' * offset) @@ -324,9 +326,9 @@ def check_mmap(hdr, offset, proxy_class, assert_false(back_is_mmap) else: assert_equal(unscaled_is_mmap, - VIRAL_MEMMAP or unscaled_really_mmap) + viral_memmap or unscaled_really_mmap) assert_equal(back_is_mmap, - VIRAL_MEMMAP or scaled_really_mmap) + viral_memmap or scaled_really_mmap) if scaled_really_mmap: assert_equal(back_data.mode, expected_mode) del prox, back_data diff --git a/nibabel/tests/test_spatialimages.py b/nibabel/tests/test_spatialimages.py index 1bcdbd19f7..9cb4759f50 100644 --- a/nibabel/tests/test_spatialimages.py +++ b/nibabel/tests/test_spatialimages.py @@ -25,7 +25,7 @@ from .test_helpers import bytesio_round_trip from ..testing import (clear_and_catch_warnings, suppress_warnings, - VIRAL_MEMMAP) + memmap_after_ufunc) from ..tmpdirs import InTemporaryDirectory from .. import load as top_load @@ -464,6 +464,7 @@ def get_disk_image(self): def test_load_mmap(self): # Test memory mapping when loading images img_klass = self.image_class + viral_memmap = memmap_after_ufunc() with InTemporaryDirectory(): img, fname, has_scaling = self.get_disk_image() file_map = img.file_map.copy() @@ -485,7 +486,7 @@ def test_load_mmap(self): # numpies returned a memmap object, even though the array # has no mmap memory backing. See: # https://github.com/numpy/numpy/pull/7406 - if has_scaling and not VIRAL_MEMMAP: + if has_scaling and not viral_memmap: expected_mode = None kwargs = {} if mmap is not None: From 4e128b8c0f3a8283c080ca2f6706537eace83ec1 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:05:41 +0000 Subject: [PATCH 07/24] DOC: Explicitly describe behaviour when an affine and a header are specified during Nifti1Image creation. --- nibabel/nifti1.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 4d766a76f5..c00d200483 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1764,7 +1764,19 @@ def __init__(self, dataobj, affine, header=None, if header is None and affine is not None: self._affine2header() # Copy docstring - __init__.doc = analyze.AnalyzeImage.__init__.__doc__ + __init__.__doc__ = analyze.AnalyzeImage.__init__.__doc__ + ''' + .. note:: If both a ``header`` and an ``affine`` are specified, and the + ``affine`` does not match the affine that is in the ``header``, + the ``affine`` will be used, but the ``sform_code`` and + ``qform_code`` fields in the header will be re-initialised to + their default values.. This is performed on the basis that, if + you are changing the affine, you are likely to be changing the + space to which the affine is pointing. The :meth:`set_sform` + and :meth:`set_qform` methods can be used to update the codes + after an image has been created - see those methods for more + details. . + ''' + def update_header(self): ''' Harmonize header with image data and affine From 581a143838016ce84e930301d8db768583f2cc68 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:32:16 +0000 Subject: [PATCH 08/24] DOC: Add a new section in the Nifti images user doc page specifying how the sform/qform codes are initialised. --- doc/source/nifti_images.rst | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index 257465f3a9..4ced8dde6f 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -281,6 +281,61 @@ the sform: ``get_qform()``, ``set_qform()``. The qform also has a corresponding ``qform_code`` with the same interpretation as the `sform_code`. +Default sform and qform codes +============================= + +If you create a new image, e.g.: + +>>> data = np.random.random((20, 20, 20)) +>>> xform = np.eye(4) * 2 +>>> img = nib.nifti1.Nifti1Image(data, xform) + +The sform and qform codes will be initialised to 2 (aligned) and 0 (unknown) +respectively: + +>>> img.get_sform(coded=True) +(array([[ 2., 0., 0., 0.], + [ 0., 2., 0., 0.], + [ 0., 0., 2., 0.], + [ 0., 0., 0., 1.]]), array(2, dtype=int16)) +>>> img.get_qform(coded=True) +(None, 0) + +This is based on the assumption that the affine you specify for a newly +created image will align the image to some known coordinate system. According +to the `NIfTI specification `_, the qform is intended to encode a +transformation into scanner coordinates - for a programmatically created +image, we have no way of knowing what the scanner coordinate system is; +furthermore, the qform cannot be used to store an arbitrary affine transform, +as it is unable to encode shears. So the provided affine will be stored in the +sform, and the qform will be left uninitialised. + +If you create a new image and specify an existing header, e.g.: + +>>> example_ni1 = os.path.join(data_path, 'example4d.nii.gz') +>>> n1_img = nib.load(example_ni1) +>>> new_header = header=n1_img.header.copy() +>>> new_data = np.random.random(n1_img.shape[:3]) +>>> new_img = nib.nifti1.Nifti1Image(data, None, header=new_header) + +Then the newly created image will inherit the same sform and qform codes that +are in the provided header. However, if you create a new image with both an +affine and a header specified, e.g.: + +>>> xform = np.eye(4) +>>> new_img = nib.nifti1.Nifti1Image(data, xform, header=new_header) + +Then the sform and qform codes will *only* be preserved if the provided affine +is the same as the affine in the provided header. If the affines do not match, +the sform and qform codes will be set to their default values of 2 and 0 +respectively. This is done on the basis that, if you are changing the affine, +you are likely to be changing the space to which the affine is pointing. So +the original sform and qform codes can no longer be assumed to be valid. + +If you wish to set the sform and qform affines and/or codes to some other +value, you can always set them after creation using the ``set_sform`` and +``set_qform`` methods, as described above. + The fall-back header affine =========================== From 2de656be37f6fa03d804be1a331a6ffb08353be4 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:40:49 +0000 Subject: [PATCH 09/24] DOC: Move that new nifti images section to the bottom --- doc/source/nifti_images.rst | 68 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index 4ced8dde6f..00302a5fee 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -281,6 +281,41 @@ the sform: ``get_qform()``, ``set_qform()``. The qform also has a corresponding ``qform_code`` with the same interpretation as the `sform_code`. +The fall-back header affine +=========================== + +This is the affine of last resort, constructed only from the ``pixdim`` voxel +sizes. The `NIfTI specification `_ says that this should set the +first voxel in the image as [0, 0, 0] in world coordinates, but we nibabblers +follow SPM_ in preferring to set the central voxel to have [0, 0, 0] world +coordinate. The NIfTI spec also implies that the image should be assumed to be +in RAS+ *voxel* orientation for this affine (see :doc:`coordinate_systems`). +Again like SPM, we prefer to assume LAS+ voxel orientation by default. + +You can always get the fall-back affine with ``get_base_affine()``: + +>>> n1_header.get_base_affine() +array([[ -2. , 0. , 0. , 127. ], + [ 0. , 2. , 0. , -95. ], + [ 0. , 0. , 2.2, -25.3], + [ 0. , 0. , 0. , 1. ]]) + +.. _choosing-image-affine: + +Choosing the image affine +========================= + +Given there are three possible affines defined in the NIfTI header, nibabel +has to chose which of these to use for the image ``affine``. + +The algorithm is defined in the ``get_best_affine()`` method. It is: + +#. If ``sform_code`` != 0 ('unknown') use the sform affine; else +#. If ``qform_code`` != 0 ('unknown') use the qform affine; else +#. Use the fall-back affine. + +.. _default-sform-qform-codes: + Default sform and qform codes ============================= @@ -336,39 +371,6 @@ If you wish to set the sform and qform affines and/or codes to some other value, you can always set them after creation using the ``set_sform`` and ``set_qform`` methods, as described above. -The fall-back header affine -=========================== - -This is the affine of last resort, constructed only from the ``pixdim`` voxel -sizes. The `NIfTI specification `_ says that this should set the -first voxel in the image as [0, 0, 0] in world coordinates, but we nibabblers -follow SPM_ in preferring to set the central voxel to have [0, 0, 0] world -coordinate. The NIfTI spec also implies that the image should be assumed to be -in RAS+ *voxel* orientation for this affine (see :doc:`coordinate_systems`). -Again like SPM, we prefer to assume LAS+ voxel orientation by default. - -You can always get the fall-back affine with ``get_base_affine()``: - ->>> n1_header.get_base_affine() -array([[ -2. , 0. , 0. , 127. ], - [ 0. , 2. , 0. , -95. ], - [ 0. , 0. , 2.2, -25.3], - [ 0. , 0. , 0. , 1. ]]) - -.. _choosing-image-affine: - -Choosing the image affine -========================= - -Given there are three possible affines defined in the NIfTI header, nibabel -has to chose which of these to use for the image ``affine``. - -The algorithm is defined in the ``get_best_affine()`` method. It is: - -#. If ``sform_code`` != 0 ('unknown') use the sform affine; else -#. If ``qform_code`` != 0 ('unknown') use the qform affine; else -#. Use the fall-back affine. - ************ Data scaling ************ From 041be2f0681bd23a89d83573c2853d5423ff4c8e Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 14:41:01 +0000 Subject: [PATCH 10/24] DOC: Link to relevant section of user docs from Nifti1Pair docs --- nibabel/nifti1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index c00d200483..407a0b88d9 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1773,8 +1773,8 @@ def __init__(self, dataobj, affine, header=None, you are changing the affine, you are likely to be changing the space to which the affine is pointing. The :meth:`set_sform` and :meth:`set_qform` methods can be used to update the codes - after an image has been created - see those methods for more - details. . + after an image has been created - see those methods, and + the :ref:`manual ` for more details. ''' From d158f50124267a88389a343a7bc04b4224706fb6 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 17:35:08 +0000 Subject: [PATCH 11/24] DOC: typo --- nibabel/nifti1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 407a0b88d9..3a80964de8 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1769,7 +1769,7 @@ def __init__(self, dataobj, affine, header=None, ``affine`` does not match the affine that is in the ``header``, the ``affine`` will be used, but the ``sform_code`` and ``qform_code`` fields in the header will be re-initialised to - their default values.. This is performed on the basis that, if + their default values. This is performed on the basis that, if you are changing the affine, you are likely to be changing the space to which the affine is pointing. The :meth:`set_sform` and :meth:`set_qform` methods can be used to update the codes From 1b8190854787cf84e6a397ab213d34e65cb3a03e Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 20:19:10 +0000 Subject: [PATCH 12/24] DOC: Use numpydoc convention, not .rst --- nibabel/nifti1.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 3a80964de8..29269f3455 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1765,17 +1765,18 @@ def __init__(self, dataobj, affine, header=None, self._affine2header() # Copy docstring __init__.__doc__ = analyze.AnalyzeImage.__init__.__doc__ + ''' - .. note:: If both a ``header`` and an ``affine`` are specified, and the - ``affine`` does not match the affine that is in the ``header``, - the ``affine`` will be used, but the ``sform_code`` and - ``qform_code`` fields in the header will be re-initialised to - their default values. This is performed on the basis that, if - you are changing the affine, you are likely to be changing the - space to which the affine is pointing. The :meth:`set_sform` - and :meth:`set_qform` methods can be used to update the codes - after an image has been created - see those methods, and - the :ref:`manual ` for more details. - ''' + Notes + ----- + + If both a ``header`` and an ``affine`` are specified, andq the ``affine`` + does not match the affine that is in the ``header``, the ``affine`` will + be used, but the ``sform_code`` and ``qform_code`` fields in the header + will be re-initialised to their default values. This is performed on the + basis that, if you are changing the affine, you are likely to be changing + the space to which the affine is pointing. The :meth:`set_sform` and + :meth:`set_qform` methods can be used to update the codes after an image + has been created - see those methods, and the :ref:`manual + ` for more details. ''' def update_header(self): From d42d7ee45a52ef653e3a496902457265e3eb1b6f Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 20:22:34 +0000 Subject: [PATCH 13/24] DOC: doctest clause --- doc/source/nifti_images.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index 00302a5fee..f6fc803d09 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -328,7 +328,7 @@ If you create a new image, e.g.: The sform and qform codes will be initialised to 2 (aligned) and 0 (unknown) respectively: ->>> img.get_sform(coded=True) +>>> img.get_sform(coded=True) # doctest: +NORMALIZE_WHITESPACE (array([[ 2., 0., 0., 0.], [ 0., 2., 0., 0.], [ 0., 0., 2., 0.], From 92a8acb23b9636f15b6f5505e5bbd60a768a1e2c Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 9 Nov 2017 20:24:55 +0000 Subject: [PATCH 14/24] DOC: Typo --- nibabel/nifti1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index 29269f3455..b4f31da843 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1768,7 +1768,7 @@ def __init__(self, dataobj, affine, header=None, Notes ----- - If both a ``header`` and an ``affine`` are specified, andq the ``affine`` + If both a ``header`` and an ``affine`` are specified, and the ``affine`` does not match the affine that is in the ``header``, the ``affine`` will be used, but the ``sform_code`` and ``qform_code`` fields in the header will be re-initialised to their default values. This is performed on the From a6b4d83044f07ec31787727757dad6f3b5bbbd9f Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Sat, 11 Nov 2017 12:17:20 +0000 Subject: [PATCH 15/24] DOC: Small adjustments to grammar/flow --- doc/source/nifti_images.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index f6fc803d09..2f8a7cd0db 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -353,14 +353,14 @@ If you create a new image and specify an existing header, e.g.: >>> new_data = np.random.random(n1_img.shape[:3]) >>> new_img = nib.nifti1.Nifti1Image(data, None, header=new_header) -Then the newly created image will inherit the same sform and qform codes that +then the newly created image will inherit the same sform and qform codes that are in the provided header. However, if you create a new image with both an affine and a header specified, e.g.: >>> xform = np.eye(4) >>> new_img = nib.nifti1.Nifti1Image(data, xform, header=new_header) -Then the sform and qform codes will *only* be preserved if the provided affine +then the sform and qform codes will *only* be preserved if the provided affine is the same as the affine in the provided header. If the affines do not match, the sform and qform codes will be set to their default values of 2 and 0 respectively. This is done on the basis that, if you are changing the affine, From 188b1940793f9aeb4fb84b002598292bc95fefb9 Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Sat, 11 Nov 2017 12:26:28 +0000 Subject: [PATCH 16/24] DOC: Single backticks when referring to parameters in documentation --- nibabel/nifti1.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nibabel/nifti1.py b/nibabel/nifti1.py index b4f31da843..ff3ee32118 100644 --- a/nibabel/nifti1.py +++ b/nibabel/nifti1.py @@ -1768,12 +1768,12 @@ def __init__(self, dataobj, affine, header=None, Notes ----- - If both a ``header`` and an ``affine`` are specified, and the ``affine`` - does not match the affine that is in the ``header``, the ``affine`` will - be used, but the ``sform_code`` and ``qform_code`` fields in the header - will be re-initialised to their default values. This is performed on the - basis that, if you are changing the affine, you are likely to be changing - the space to which the affine is pointing. The :meth:`set_sform` and + If both a `header` and an `affine` are specified, and the `affine` does + not match the affine that is in the `header`, the `affine` will be used, + but the ``sform_code`` and ``qform_code`` fields in the header will be + re-initialised to their default values. This is performed on the basis + that, if you are changing the affine, you are likely to be changing the + space to which the affine is pointing. The :meth:`set_sform` and :meth:`set_qform` methods can be used to update the codes after an image has been created - see those methods, and the :ref:`manual ` for more details. ''' From 15d838e08194fe4abb42b5814d22cf14bfcb8a47 Mon Sep 17 00:00:00 2001 From: Bennet Fauber Date: Sun, 12 Nov 2017 11:52:34 -0500 Subject: [PATCH 17/24] set sform with set_sform not get_sform --- doc/source/nifti_images.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index 2f8a7cd0db..cd3f0a978c 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -239,7 +239,7 @@ You can get the affine and the code using the ``coded=True`` argument to [ 0. , 0.32, 2.17, -7.25], [ 0. , 0. , 0. , 1. ]]), array(1, dtype=int16)) -You can set the sform with with the ``get_sform()`` method of the header and +You can set the sform with with the ``set_sform()`` method of the header and the image. >>> n1_header.set_sform(np.diag([2, 3, 4, 1])) From 93cc5921b53b7388e3301d5f3ab531ca5c5bebe5 Mon Sep 17 00:00:00 2001 From: Bennet Fauber Date: Sun, 12 Nov 2017 12:21:26 -0500 Subject: [PATCH 18/24] two withs become one --- doc/source/nifti_images.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/nifti_images.rst b/doc/source/nifti_images.rst index cd3f0a978c..5bf423571c 100644 --- a/doc/source/nifti_images.rst +++ b/doc/source/nifti_images.rst @@ -239,7 +239,7 @@ You can get the affine and the code using the ``coded=True`` argument to [ 0. , 0.32, 2.17, -7.25], [ 0. , 0. , 0. , 1. ]]), array(1, dtype=int16)) -You can set the sform with with the ``set_sform()`` method of the header and +You can set the sform with the ``set_sform()`` method of the header and the image. >>> n1_header.set_sform(np.diag([2, 3, 4, 1])) From 3793876299edbb61ec957184a53988fb8ae3e50c Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 18 Nov 2017 18:07:23 +0000 Subject: [PATCH 19/24] BF: fix doctest failure with pre-release numpy Numpy output printing has changed in upcoming 1.14 - trigger legacy printing for doctests. --- nibabel/nicom/dwiparams.py | 2 +- nibabel/testing/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/nicom/dwiparams.py b/nibabel/nicom/dwiparams.py index 19ffbafa0f..10fdce0d5b 100644 --- a/nibabel/nicom/dwiparams.py +++ b/nibabel/nicom/dwiparams.py @@ -21,7 +21,7 @@ ''' import numpy as np import numpy.linalg as npl -from ..testing import setup_test # flake8: noqa F401 +from ..testing import setup_test as setup_module # flake8: noqa F401 def B2q(B, tol=None): diff --git a/nibabel/testing/__init__.py b/nibabel/testing/__init__.py index 2fa7f809f6..61ba078cf8 100644 --- a/nibabel/testing/__init__.py +++ b/nibabel/testing/__init__.py @@ -218,4 +218,4 @@ def setup_test(): """ from distutils.version import LooseVersion if LooseVersion(np.__version__) >= LooseVersion('1.14'): - np.set_printoptions(sign='legacy') + np.set_printoptions(legacy="1.13") From ee7183e2aaec860971e5fad0593cbca5b0650c7d Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 18 Nov 2017 18:10:37 +0000 Subject: [PATCH 20/24] BF: skip precision test on macOS, newer numpy Numpy 1.12 blacklisted the macOS `powl` function, meaning that the use of power functions for testing maxexp on float128 doesn't work on macOS. --- nibabel/tests/test_floating.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nibabel/tests/test_floating.py b/nibabel/tests/test_floating.py index 8f6d998a0c..3022265df4 100644 --- a/nibabel/tests/test_floating.py +++ b/nibabel/tests/test_floating.py @@ -4,6 +4,8 @@ PY2 = sys.version_info[0] < 3 +from distutils.version import LooseVersion + import numpy as np from ..casting import (floor_exact, ceil_exact, as_int, FloatingError, @@ -103,7 +105,12 @@ def test_check_nmant_nexp(): ti = type_info(t) if ti['nmant'] != 106: # This check does not work for PPC double pair assert_true(_check_nmant(t, ti['nmant'])) - assert_true(_check_maxexp(t, ti['maxexp'])) + # Test fails for longdouble after blacklisting of OSX powl as of numpy + # 1.12 - see https://github.com/numpy/numpy/issues/8307 + if (t != np.longdouble or + sys.platform != 'darwin' or + LooseVersion(np.__version__) < LooseVersion('1.12')): + assert_true(_check_maxexp(t, ti['maxexp'])) def test_as_int(): From f292f59d41f36d16fd4f087d6ee07360cd7aaf40 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Wed, 22 Nov 2017 22:18:56 +0000 Subject: [PATCH 21/24] MAINT: Remove conda from Appveyor script We can get away without conda, now wheels are available. Simplify file otherwise, by removing unnecessary quotes and backslashes. --- appveyor.yml | 58 +++++++++++++++------------------------------------- 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index ae711f8e97..e41aee90c8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,60 +1,34 @@ # vim ft=yaml # CI on Windows via appveyor -# This file was based on Olivier Grisel's python-appveyor-demo environment: matrix: - - PYTHON: "C:\\Python27-conda32" - PYTHON_VERSION: "2.7" - PYTHON_ARCH: "32" - - - PYTHON: "C:\\Python34-conda32" - PYTHON_VERSION: "3.4" - PYTHON_ARCH: "32" - - - PYTHON: "C:\\Python34-conda64" - PYTHON_VERSION: "3.4" - PYTHON_ARCH: "64" - - - PYTHON: "C:\\Python35-conda64" - PYTHON_VERSION: "3.5" - PYTHON_ARCH: "64" - - - PYTHON: "C:\\Python35-conda32" - PYTHON_VERSION: "3.5" - PYTHON_ARCH: "32" + - PYTHON: C:\Python27 + - PYTHON: C:\Python27-x64 + - PYTHON: C:\Python34 + - PYTHON: C:\Python34-x64 + - PYTHON: C:\Python35 + - PYTHON: C:\Python35-x64 + - PYTHON: C:\Python36 + - PYTHON: C:\Python36-x64 install: - # Install miniconda Python - - "powershell ./tools/install_python.ps1" - # Prepend newly installed Python to the PATH of this build (this cannot be # done from inside the powershell script as it would require to restart # the parent CMD process). - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - # Set up a conda environment: - - conda config --set always_yes yes - - conda update -q conda - - conda info -a - - conda create -q -n test-environment python=%PYTHON_VERSION% - - activate test-environment - - # Check that we have the expected version and architecture for Python - - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + - SET PATH=%PYTHON%;%PYTHON%\Scripts;%PATH% # Install the dependencies of the project. - - "conda install --yes --quiet numpy scipy matplotlib nose h5py mock" - - "pip install pydicom" - - "python setup.py install" - - "SET NIBABEL_DATA_DIR=%CD%\\nibabel-data" + - pip install numpy scipy matplotlib nose h5py mock + - pip install pydicom + - pip install . + - SET NIBABEL_DATA_DIR=%CD%\nibabel-data build: false # Not a C# project, build stuff at the test step instead. test_script: # Change into an innocuous directory and find tests from installation - - "mkdir for_testing" - - "cd for_testing" - - "nosetests --with-doctest nibabel" + - mkdir for_testing + - cd for_testing + - nosetests --with-doctest nibabel From a053e708f0d92339c1d89e34e0c486db2b91fda0 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 22 Nov 2017 19:14:48 -0500 Subject: [PATCH 22/24] Reset maintenance version --- nibabel/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/info.py b/nibabel/info.py index 821c1ae3d9..142a4ebedf 100644 --- a/nibabel/info.py +++ b/nibabel/info.py @@ -17,8 +17,8 @@ # We usually use `dev` as `_version_extra` to label this as a development # (pre-release) version. _version_major = 2 -_version_minor = 3 -_version_micro = 0 +_version_minor = 2 +_version_micro = 1 _version_extra = 'dev' # _version_extra = '' From ac613a8cf5e527d2900164f421df09fed1d2f72a Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 22 Nov 2017 19:29:29 -0500 Subject: [PATCH 23/24] MAINT: Update changelog, authors --- .mailmap | 1 + Changelog | 19 +++++++++++++++++++ doc/source/index.rst | 1 + 3 files changed, 21 insertions(+) diff --git a/.mailmap b/.mailmap index ae1aa78f87..79ce4939e6 100644 --- a/.mailmap +++ b/.mailmap @@ -40,3 +40,4 @@ Satrajit Ghosh Satrajit Ghosh Jasper J.F. van den Bosch Jasper Gregory R. Lee Gregory R. Lee Demian Wassermann Demian Wassermann +Paul McCarthy Paul McCarthy diff --git a/Changelog b/Changelog index 02fd993e0c..4c65cf0bfe 100644 --- a/Changelog +++ b/Changelog @@ -24,6 +24,25 @@ Gerhard (SG) and Eric Larson (EL). References like "pr/298" refer to github pull request numbers. +2.2.1 (Wednesday 22 November 2017) +================================== + +Bug fixes +--------- + +* Set L/R labels in orthoview correctly (pr/564) (CM) +* Defer use of ufunc / memmap test - allows "freezing" (pr/572) (MB, reviewed + by Satra Ghosh) +* Fix doctest failures with pre-release numpy (pr/582) (MB, reviewed by CM) + +Maintenance +----------- + +* Update documentation around NIfTI qform/sform codes (pr/576) (Paul McCarthy, + reviewed by MB, CM) + (pr/580) (Bennet Fauber, reviewed by Paul McCarthy) +* Skip precision test on macOS, newer numpy (pr/583) (MB, reviewed by CM) +* Simplify AppVeyor script, removing conda (pr/584) (MB, reviewed by CM) + 2.2 (Friday 13 October 2017) ============================ diff --git a/doc/source/index.rst b/doc/source/index.rst index 85c847fb9b..f623c931d2 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -79,6 +79,7 @@ contributed code and discussion (in rough order of appearance): * Venky Reddy * Mark Hymers * Jasper J.F. van den Bosch +* Bennet Fauber License reprise =============== From 9b1ead00584115a3d17461cf4a895a96dbc72846 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 22 Nov 2017 19:40:27 -0500 Subject: [PATCH 24/24] MAINT: Remove -dev from version --- nibabel/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/info.py b/nibabel/info.py index 142a4ebedf..602a77c1c9 100644 --- a/nibabel/info.py +++ b/nibabel/info.py @@ -19,8 +19,8 @@ _version_major = 2 _version_minor = 2 _version_micro = 1 -_version_extra = 'dev' -# _version_extra = '' +# _version_extra = 'dev' +_version_extra = '' # Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" __version__ = "%s.%s.%s%s" % (_version_major,