From 4c4405bd8abbdc0ea0793724e522e6d7eb896dc2 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 24 Mar 2017 13:12:12 -0400 Subject: [PATCH 01/16] Add ArrayProxy.reshape --- nibabel/arrayproxy.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index d6796e7762..1fbcf31777 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -162,6 +162,17 @@ def __getitem__(self, slicer): # Upcast as necessary for big slopes, intercepts return apply_read_scaling(raw_data, self._slope, self._inter) + def reshape(self, shape): + size = np.prod(self._shape) + if np.prod(shape) != size: + raise ValueError("cannot reshape array of size {:d} into shape " + "{!s}".format(size, shape)) + new_ap = ArrayProxy(file_like=self.file_like, + header=self._header, + mmap=self._mmap) + new_ap.shape = shape + return new_ap + def is_proxy(obj): """ Return True if `obj` is an array proxy From 1eb6fbe0ad0d48c43c7235b2ae520c4e0c5a8593 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 24 Mar 2017 13:26:35 -0400 Subject: [PATCH 02/16] Typo --- nibabel/arrayproxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index 1fbcf31777..fa7f247aa1 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -170,7 +170,7 @@ def reshape(self, shape): new_ap = ArrayProxy(file_like=self.file_like, header=self._header, mmap=self._mmap) - new_ap.shape = shape + new_ap._shape = shape return new_ap From 8b41f5509a7c432d6e65f0ee3b632d09d502e90f Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 24 Mar 2017 14:06:13 -0400 Subject: [PATCH 03/16] Cifti2 arrayproxy tests should be negated or dropped... --- nibabel/cifti2/tests/test_cifti2io.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nibabel/cifti2/tests/test_cifti2io.py b/nibabel/cifti2/tests/test_cifti2io.py index 8052f9ac4c..521e112847 100644 --- a/nibabel/cifti2/tests/test_cifti2io.py +++ b/nibabel/cifti2/tests/test_cifti2io.py @@ -63,12 +63,12 @@ def test_read_and_proxies(): assert_true(isinstance(img2.header, ci.Cifti2Header)) assert_equal(img2.shape, (1, 91282)) # While we cannot reshape arrayproxies, all images are in-memory - assert_true(img2.in_memory) + assert_true(not img2.in_memory) data = img2.get_data() - assert_true(data is img2.dataobj) + assert_true(data is not img2.dataobj) # Uncaching has no effect, images are always array images img2.uncache() - assert_true(data is img2.get_data()) + assert_true(data is not img2.get_data()) @needs_nibabel_data('nitest-cifti2') From c6192ad20b392c13e4f5f1ccdb2a8046f4a2ac0d Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Sat, 25 Mar 2017 06:10:54 -0400 Subject: [PATCH 04/16] RF: Do not automatically load data CIFTI data --- nibabel/cifti2/parse_cifti2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nibabel/cifti2/parse_cifti2.py b/nibabel/cifti2/parse_cifti2.py index 63fc0ddf7b..ee32f7aec8 100644 --- a/nibabel/cifti2/parse_cifti2.py +++ b/nibabel/cifti2/parse_cifti2.py @@ -147,7 +147,6 @@ def __init__(self, dataobj, affine, header=None, if self.cifti_img is None: raise ValueError('Nifti2 header does not contain a CIFTI2 ' 'extension') - self.cifti_img.data = self.get_data() class Cifti2Parser(xml.XmlParser): From d4e152aa84e5bd3ae3fdbe1c95a37dfb7eaf3bc1 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Sat, 25 Mar 2017 06:59:31 -0400 Subject: [PATCH 05/16] Severely simplify _Cifti2AsNiftiImage --- nibabel/cifti2/parse_cifti2.py | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/nibabel/cifti2/parse_cifti2.py b/nibabel/cifti2/parse_cifti2.py index ee32f7aec8..c1046a64d3 100644 --- a/nibabel/cifti2/parse_cifti2.py +++ b/nibabel/cifti2/parse_cifti2.py @@ -121,32 +121,9 @@ def _chk_pixdims(hdr, fix=False): class _Cifti2AsNiftiImage(Nifti2Image): + """ Load a NIfTI2 image with a Cifti2 header """ header_class = _Cifti2AsNiftiHeader - files_types = (('image', '.nii'),) - valid_exts = ('.nii',) makeable = False - rw = True - - def __init__(self, dataobj, affine, header=None, - extra=None, file_map=None): - """Convert NIFTI-2 file to CIFTI2""" - super(_Cifti2AsNiftiImage, self).__init__(dataobj=dataobj, - affine=affine, - header=header, - extra=extra, - file_map=file_map) - - # Get cifti header from extension - for extension in self.header.extensions: - if isinstance(extension, Cifti2Extension): - self.cifti_img = extension - break - else: - self.cifti_img = None - - if self.cifti_img is None: - raise ValueError('Nifti2 header does not contain a CIFTI2 ' - 'extension') class Cifti2Parser(xml.XmlParser): From 49725c778912c06a8c285eb6054662e7c7ca2b65 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 13:35:01 -0400 Subject: [PATCH 06/16] Permit tuple ArrayProxy spec in place of header --- nibabel/arrayproxy.py | 52 +++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index fa7f247aa1..e5ee8d5b94 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -45,14 +45,17 @@ class ArrayProxy(object): of the numpy dtypes, starting at a given file position ``offset`` with single ``slope`` and ``intercept`` scaling to produce output values. - The class ``__init__`` requires a ``header`` object with methods: + The class ``__init__`` requires a spec which defines how the data will be + read and rescaled. The spec may be a tuple of length 2 - 5, containing the + shape, storage dtype, offset, slope and intercept, or a ``header`` object + with methods: * get_data_shape * get_data_dtype * get_data_offset * get_slope_inter - The header should also have a 'copy' method. This requirement will go away + A header should also have a 'copy' method. This requirement will go away when the deprecated 'header' propoerty goes away. This implementation allows us to deal with Analyze and its variants, @@ -64,9 +67,10 @@ class ArrayProxy(object): """ # Assume Fortran array memory layout order = 'F' + _header = None @kw_only_meth(2) - def __init__(self, file_like, header, mmap=True): + def __init__(self, file_like, spec, mmap=True): """ Initialize array proxy instance Parameters @@ -74,7 +78,21 @@ def __init__(self, file_like, header, mmap=True): file_like : object File-like object or filename. If file-like object, should implement at least ``read`` and ``seek``. - header : object + spec : object or tuple + Tuple must have length 2-5, with the following fields: + - shape : tuple + tuple of ints describing shape of data + - storage_dtype : dtype specifier + dtype of array inside proxied file, or input to ``numpy.dtype`` + to specify array dtype + - offset : int + Offset, in bytes, of data array from start of file + (default: 0) + - slope : float + Scaling factor for resulting data (default: 1.0) + - inter : float + Intercept for rescaled dadta (default: 0.0) + OR Header object implementing ``get_data_shape``, ``get_data_dtype``, ``get_data_offset``, ``get_slope_inter`` mmap : {True, False, 'c', 'r'}, optional, keyword only @@ -90,16 +108,26 @@ def __init__(self, file_like, header, mmap=True): if mmap not in (True, False, 'c', 'r'): raise ValueError("mmap should be one of {True, False, 'c', 'r'}") self.file_like = file_like + if hasattr(spec, 'get_data_shape'): + slope, inter = spec.get_slope_inter() + par = (spec.get_data_shape(), + spec.get_data_dtype(), + spec.get_data_offset(), + 1. if slope is None else slope, + 0. if inter is None else inter) + # Reference to original header; we will remove this soon + self._header = spec.copy() + elif 2 <= len(spec) <= 5: + optional = (0, 1., 0.) + par = spec + optional[len(spec) - 2:] + else: + raise TypeError('spec must be tuple of length 2-5 or header object') + # Copies of values needed to read array - self._shape = header.get_data_shape() - self._dtype = header.get_data_dtype() - self._offset = header.get_data_offset() - self._slope, self._inter = header.get_slope_inter() - self._slope = 1.0 if self._slope is None else self._slope - self._inter = 0.0 if self._inter is None else self._inter + self._shape, self._dtype, self._offset, self._slope, self._inter = par + # Permit any specifier that can be interpreted as a numpy dtype + self._dtype = np.dtype(self._dtype) self._mmap = mmap - # Reference to original header; we will remove this soon - self._header = header.copy() @property def header(self): From 0c62d74c72ebda5a1204b38adbefcf98a6e4aa03 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 13:44:59 -0400 Subject: [PATCH 07/16] ArrayProxy.reshape generates headerless ArrayProxy --- nibabel/arrayproxy.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index e5ee8d5b94..1bcf43b739 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -195,11 +195,10 @@ def reshape(self, shape): if np.prod(shape) != size: raise ValueError("cannot reshape array of size {:d} into shape " "{!s}".format(size, shape)) - new_ap = ArrayProxy(file_like=self.file_like, - header=self._header, - mmap=self._mmap) - new_ap._shape = shape - return new_ap + return ArrayProxy(file_like=self.file_like, + spec=(shape, self._dtype, self._offset, + self._slope, self._inter), + mmap=self._mmap) def is_proxy(obj): From 1626cfbdd04c07b615fc3e16b43369d5d32ec05f Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 13:49:06 -0400 Subject: [PATCH 08/16] RF: Deprecate ArrayProxy.header to be removed in 3.0 --- nibabel/arrayproxy.py | 7 ++----- nibabel/tests/test_proxy_api.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index 1bcf43b739..6b3170ffee 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -25,10 +25,9 @@ See :mod:`nibabel.tests.test_proxy_api` for proxy API conformance checks. """ -import warnings - import numpy as np +from .deprecated import deprecate_with_version from .volumeutils import array_from_file, apply_read_scaling from .fileslice import fileslice from .keywordonly import kw_only_meth @@ -130,10 +129,8 @@ def __init__(self, file_like, spec, mmap=True): self._mmap = mmap @property + @deprecate_with_version('ArrayProxy.header deprecated', '2.2', '3.0') def header(self): - warnings.warn('We will remove the header property from proxies soon', - FutureWarning, - stacklevel=2) return self._header @property diff --git a/nibabel/tests/test_proxy_api.py b/nibabel/tests/test_proxy_api.py index fce79dcc60..285674083b 100644 --- a/nibabel/tests/test_proxy_api.py +++ b/nibabel/tests/test_proxy_api.py @@ -288,7 +288,7 @@ def validate_deprecated_header(self, pmaker, params): # Header is a copy of original assert_false(prox.header is hdr) assert_equal(prox.header, hdr) - assert_equal(warns.pop(0).category, FutureWarning) + assert_equal(warns.pop(0).category, DeprecationWarning) class TestSpm99AnalyzeProxyAPI(TestAnalyzeProxyAPI): From c6dfa6abd9c613b75654c10fb589e96e6cb1a330 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 15:16:25 -0400 Subject: [PATCH 09/16] TEST: Test tuple specs and reshaped APs --- nibabel/tests/test_arrayproxy.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py index 9e4ceb9f4b..265c2b01c7 100644 --- a/nibabel/tests/test_arrayproxy.py +++ b/nibabel/tests/test_arrayproxy.py @@ -83,6 +83,27 @@ def test_init(): assert_array_equal(np.asarray(ap), arr) +def test_tuplespec(): + bio = BytesIO() + shape = [2, 3, 4] + dtype = np.int32 + arr = np.arange(24, dtype=dtype).reshape(shape) + bio.seek(16) + bio.write(arr.tostring(order='F')) + hdr = FunkyHeader(shape) + tuple_spec = (hdr.get_data_shape(), hdr.get_data_dtype(), + hdr.get_data_offset(), 1., 0.) + ap_header = ArrayProxy(bio, hdr) + ap_tuple = ArrayProxy(bio, tuple_spec) + for prop in ('shape', 'dtype', 'offset', 'slope', 'inter', 'is_proxy'): + assert_equal(getattr(ap_header, prop), getattr(ap_tuple, prop)) + for method, args in (('get_unscaled', ()), ('__array__', ()), + ('__getitem__', ((0, 2, 1), )) + ): + assert_array_equal(getattr(ap_header, method)(*args), + getattr(ap_tuple, method)(*args)) + + def write_raw_data(arr, hdr, fileobj): hdr.set_data_shape(arr.shape) hdr.set_data_dtype(arr.dtype) @@ -185,6 +206,14 @@ def __array__(self): assert_equal(arr.shape, shape) +def test_reshaped_is_proxy(): + shape = (1, 2, 3, 4) + hdr = FunkyHeader(shape) + bio = BytesIO() + prox = ArrayProxy(bio, hdr) + assert_true(isinstance(prox.reshape((2, 3, 4)), ArrayProxy)) + + def test_get_unscaled(): # Test fetch of raw array class FunkyHeader2(FunkyHeader): From ad353403ef2747e9813c4ea4c8a666742bf0817e Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 15:16:40 -0400 Subject: [PATCH 10/16] Update Changelog --- Changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog b/Changelog index 0d6b0a4550..378d280c7e 100644 --- a/Changelog +++ b/Changelog @@ -43,6 +43,8 @@ Enhancements (pr/495); function to concatenate multiple ArraySequence objects (pr/494) * Support for numpy 1.12 (pr/500, pr/502) (MC, MB) * Allow dtype specifiers as fileslice input (pr/485) (MB) +* Support "headerless" ArrayProxy specification, enabling memory-efficient + ArrayProxy reshaping (pr/521) (CM) Bug fixes --------- @@ -60,6 +62,10 @@ Maintenance API changes and deprecations ---------------------------- +* ``header`` argument to ``ArrayProxy.__init__`` is renamed to ``spec`` +* Deprecation of ``header`` property of ``ArrayProxy`` object, for removal in + 3.0 + 2.1 (Monday 22 August 2016) =========================== From c6950f58d4f4634fecaa64353439cf24661ef3c9 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 17:46:54 -0400 Subject: [PATCH 11/16] DOC: Update ArrayProxy docstrings --- nibabel/arrayproxy.py | 9 +++++++-- nibabel/tests/test_arrayproxy.py | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index 6b3170ffee..40ab563bc5 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -78,7 +78,7 @@ def __init__(self, file_like, spec, mmap=True): File-like object or filename. If file-like object, should implement at least ``read`` and ``seek``. spec : object or tuple - Tuple must have length 2-5, with the following fields: + Tuple must have length 2-5, with the following values. - shape : tuple tuple of ints describing shape of data - storage_dtype : dtype specifier @@ -90,7 +90,7 @@ def __init__(self, file_like, spec, mmap=True): - slope : float Scaling factor for resulting data (default: 1.0) - inter : float - Intercept for rescaled dadta (default: 0.0) + Intercept for rescaled data (default: 0.0) OR Header object implementing ``get_data_shape``, ``get_data_dtype``, ``get_data_offset``, ``get_slope_inter`` @@ -188,6 +188,11 @@ def __getitem__(self, slicer): return apply_read_scaling(raw_data, self._slope, self._inter) def reshape(self, shape): + ''' Return an ArrayProxy with a new shape, without modifying data + + ``array_proxy.reshape(shape)`` is equivalent to + ``np.reshape(array_proxy, shape)`` + ''' size = np.prod(self._shape) if np.prod(shape) != size: raise ValueError("cannot reshape array of size {:d} into shape " diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py index 265c2b01c7..6402cb4bee 100644 --- a/nibabel/tests/test_arrayproxy.py +++ b/nibabel/tests/test_arrayproxy.py @@ -90,11 +90,13 @@ def test_tuplespec(): arr = np.arange(24, dtype=dtype).reshape(shape) bio.seek(16) bio.write(arr.tostring(order='F')) + # Create equivalent header and tuple specs hdr = FunkyHeader(shape) tuple_spec = (hdr.get_data_shape(), hdr.get_data_dtype(), hdr.get_data_offset(), 1., 0.) ap_header = ArrayProxy(bio, hdr) ap_tuple = ArrayProxy(bio, tuple_spec) + # Header and tuple specs produce identical behavior for prop in ('shape', 'dtype', 'offset', 'slope', 'inter', 'is_proxy'): assert_equal(getattr(ap_header, prop), getattr(ap_tuple, prop)) for method, args in (('get_unscaled', ()), ('__array__', ()), @@ -102,6 +104,8 @@ def test_tuplespec(): ): assert_array_equal(getattr(ap_header, method)(*args), getattr(ap_tuple, method)(*args)) + # Tuple-defined ArrayProxies have no header to store + assert_true(ap_tuple.header is None) def write_raw_data(arr, hdr, fileobj): From e35f327316f8b92d7fa6af5a06a9ade801bcf54c Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 17:50:42 -0400 Subject: [PATCH 12/16] RF: Update ArrayProxy.reshape to take -1 entries --- nibabel/arrayproxy.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index 40ab563bc5..352b276e8f 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -194,13 +194,23 @@ def reshape(self, shape): ``np.reshape(array_proxy, shape)`` ''' size = np.prod(self._shape) - if np.prod(shape) != size: + + # Calculate new shape if not fully specified + shape_arr = np.asarray(shape) + unknowns = shape_arr == -1 + if len(unknowns) > 1: + raise ValueError("can only specify one unknown dimension") + elif len(unknowns) == 1: + uk_val = size // np.prod(shape_arr[~unknowns]) + shape_arr[unknowns] = uk_val + + if shape_arr.prod() != size: raise ValueError("cannot reshape array of size {:d} into shape " "{!s}".format(size, shape)) - return ArrayProxy(file_like=self.file_like, - spec=(shape, self._dtype, self._offset, - self._slope, self._inter), - mmap=self._mmap) + return self.__class__(file_like=self.file_like, + spec=(shape, self._dtype, self._offset, + self._slope, self._inter), + mmap=self._mmap) def is_proxy(obj): From fbac7e405ac0d26ed20567d226e8f7a8435dde7b Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 20:55:43 -0400 Subject: [PATCH 13/16] FIX: Correct reshape method --- nibabel/arrayproxy.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index 352b276e8f..bafc975314 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -188,23 +188,21 @@ def __getitem__(self, slicer): return apply_read_scaling(raw_data, self._slope, self._inter) def reshape(self, shape): - ''' Return an ArrayProxy with a new shape, without modifying data - - ``array_proxy.reshape(shape)`` is equivalent to - ``np.reshape(array_proxy, shape)`` - ''' + ''' Return an ArrayProxy with a new shape, without modifying data ''' size = np.prod(self._shape) # Calculate new shape if not fully specified - shape_arr = np.asarray(shape) - unknowns = shape_arr == -1 - if len(unknowns) > 1: + from operator import mul + from functools import reduce + n_unknowns = len([e for e in shape if e == -1]) + if n_unknowns > 1: raise ValueError("can only specify one unknown dimension") - elif len(unknowns) == 1: - uk_val = size // np.prod(shape_arr[~unknowns]) - shape_arr[unknowns] = uk_val + elif n_unknowns == 1: + known_size = reduce(mul, shape, -1) + unknown_size = size // known_size + shape = tuple(unknown_size if e == -1 else e for e in shape) - if shape_arr.prod() != size: + if np.prod(shape) != size: raise ValueError("cannot reshape array of size {:d} into shape " "{!s}".format(size, shape)) return self.__class__(file_like=self.file_like, From 8ad9c32fdebec9e739056ec14a7f676105925d34 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 20:55:56 -0400 Subject: [PATCH 14/16] TEST: Catch warning, test -1 reshaping --- nibabel/tests/test_arrayproxy.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py index 6402cb4bee..357109fb25 100644 --- a/nibabel/tests/test_arrayproxy.py +++ b/nibabel/tests/test_arrayproxy.py @@ -105,7 +105,8 @@ def test_tuplespec(): assert_array_equal(getattr(ap_header, method)(*args), getattr(ap_tuple, method)(*args)) # Tuple-defined ArrayProxies have no header to store - assert_true(ap_tuple.header is None) + with warnings.catch_warnings(): + assert_true(ap_tuple.header is None) def write_raw_data(arr, hdr, fileobj): @@ -216,6 +217,10 @@ def test_reshaped_is_proxy(): bio = BytesIO() prox = ArrayProxy(bio, hdr) assert_true(isinstance(prox.reshape((2, 3, 4)), ArrayProxy)) + minus1 = prox.reshape((2, -1, 4)) + assert_true(isinstance(minus1, ArrayProxy)) + assert_equal(minus1.shape, (2, 3, 4)) + assert_raises(ValueError, prox.reshape, (-1, -1, 4)) def test_get_unscaled(): From f4f38942b60d29b5c696aa6843ad745f346254fc Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 21:26:29 -0400 Subject: [PATCH 15/16] TEST: Check for illegal specs --- nibabel/tests/test_arrayproxy.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py index 357109fb25..f4d07fa1a0 100644 --- a/nibabel/tests/test_arrayproxy.py +++ b/nibabel/tests/test_arrayproxy.py @@ -81,6 +81,8 @@ def test_init(): bio.write(arr.tostring(order='C')) ap = CArrayProxy(bio, FunkyHeader((2, 3, 4))) assert_array_equal(np.asarray(ap), arr) + # Illegal init + assert_raises(TypeError, ArrayProxy, bio, object()) def test_tuplespec(): @@ -107,6 +109,13 @@ def test_tuplespec(): # Tuple-defined ArrayProxies have no header to store with warnings.catch_warnings(): assert_true(ap_tuple.header is None) + # Partial tuples of length 2-4 are also valid + for n in range(2, 5): + ArrayProxy(bio, tuple_spec[:n]) + # Bad tuple lengths + assert_raises(TypeError, ArrayProxy, bio, ()) + assert_raises(TypeError, ArrayProxy, bio, tuple_spec[:1]) + assert_raises(TypeError, ArrayProxy, bio, tuple_spec + ('error',)) def write_raw_data(arr, hdr, fileobj): From 37405c3d01e0276bbee1c64a4fb964de03080112 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 28 Mar 2017 21:28:56 -0400 Subject: [PATCH 16/16] TEST: Check legal but incompatible shapes --- nibabel/tests/test_arrayproxy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nibabel/tests/test_arrayproxy.py b/nibabel/tests/test_arrayproxy.py index f4d07fa1a0..dfc16fb48e 100644 --- a/nibabel/tests/test_arrayproxy.py +++ b/nibabel/tests/test_arrayproxy.py @@ -230,6 +230,8 @@ def test_reshaped_is_proxy(): assert_true(isinstance(minus1, ArrayProxy)) assert_equal(minus1.shape, (2, 3, 4)) assert_raises(ValueError, prox.reshape, (-1, -1, 4)) + assert_raises(ValueError, prox.reshape, (2, 3, 5)) + assert_raises(ValueError, prox.reshape, (2, -1, 5)) def test_get_unscaled():