From 0f8842c99ef38ad75e562127b6eedd6a3c2db863 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 28 Nov 2019 08:51:50 -0500 Subject: [PATCH 1/6] TEST: Check non-integral slopes, intercepts --- nibabel/tests/test_proxy_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/tests/test_proxy_api.py b/nibabel/tests/test_proxy_api.py index b8316d7291..1e010055e0 100644 --- a/nibabel/tests/test_proxy_api.py +++ b/nibabel/tests/test_proxy_api.py @@ -216,8 +216,8 @@ def obj_params(self): offsets = (self.header_class().get_data_offset(),) else: offsets = (0, 16) - slopes = (1., 2.) if self.has_slope else (1.,) - inters = (0., 10.) if self.has_inter else (0.,) + slopes = (1., 2., 3.1416) if self.has_slope else (1.,) + inters = (0., 10., 2.7183) if self.has_inter else (0.,) dtypes = (np.uint8, np.int16, np.float32) for shape, dtype, offset, slope, inter in product(self.shapes, dtypes, From 460c8d9386a02eefbc073c03d1ee1e45197b92e3 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 28 Nov 2019 09:18:41 -0500 Subject: [PATCH 2/6] CI: Disable pytest for now --- .azure-pipelines/windows.yml | 4 +--- .travis.yml | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.azure-pipelines/windows.yml b/.azure-pipelines/windows.yml index 2d63db68e0..f825bef612 100644 --- a/.azure-pipelines/windows.yml +++ b/.azure-pipelines/windows.yml @@ -29,8 +29,7 @@ jobs: displayName: 'Update build tools' - script: | python -m pip install --find-links %EXTRA_WHEELS% %DEPENDS% - python -m pip install nose mock coverage codecov - python -m pip install pytest + python -m pip install nose mock coverage codecov pytest displayName: 'Install dependencies' - script: | python -m pip install . @@ -41,7 +40,6 @@ jobs: cd for_testing cp ../.coveragerc . nosetests --with-doctest --with-coverage --cover-package nibabel nibabel - pytest -v ../nibabel/tests/test_affines.py ../nibabel/tests/test_volumeutils.py displayName: 'Nose tests' - script: | cd for_testing diff --git a/.travis.yml b/.travis.yml index 804e198b90..81d3589769 100644 --- a/.travis.yml +++ b/.travis.yml @@ -130,7 +130,6 @@ script: cd for_testing cp ../.coveragerc . nosetests --with-doctest --with-coverage --cover-package nibabel nibabel - pytest -v ../nibabel/tests/test_affines.py ../nibabel/tests/test_volumeutils.py else false fi From 381ad1c0a98f5d4d7df5e34cc803811e952d3784 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 2 Dec 2019 09:59:29 -0500 Subject: [PATCH 3/6] TEST: Test supported dtypes for Analyze/NIfTI/MGH --- nibabel/tests/test_proxy_api.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nibabel/tests/test_proxy_api.py b/nibabel/tests/test_proxy_api.py index 1e010055e0..dd48e95506 100644 --- a/nibabel/tests/test_proxy_api.py +++ b/nibabel/tests/test_proxy_api.py @@ -47,6 +47,7 @@ from .._h5py_compat import h5py, have_h5py from .. import ecat from .. import parrec +from ..casting import have_binary128 from ..arrayproxy import ArrayProxy, is_proxy @@ -192,6 +193,7 @@ class TestAnalyzeProxyAPI(_TestProxyAPI): shapes = ((2,), (2, 3), (2, 3, 4), (2, 3, 4, 5)) has_slope = False has_inter = False + data_dtypes = (np.uint8, np.int16, np.int32, np.float32, np.complex64, np.float64) array_order = 'F' # Cannot set offset for Freesurfer settable_offset = True @@ -218,9 +220,8 @@ def obj_params(self): offsets = (0, 16) slopes = (1., 2., 3.1416) if self.has_slope else (1.,) inters = (0., 10., 2.7183) if self.has_inter else (0.,) - dtypes = (np.uint8, np.int16, np.float32) for shape, dtype, offset, slope, inter in product(self.shapes, - dtypes, + self.data_dtypes, offsets, slopes, inters): @@ -325,6 +326,10 @@ class TestSpm2AnalyzeProxyAPI(TestSpm99AnalyzeProxyAPI): class TestNifti1ProxyAPI(TestSpm99AnalyzeProxyAPI): header_class = Nifti1Header has_inter = True + data_dtypes = (np.uint8, np.int16, np.int32, np.float32, np.complex64, np.float64, + np.int8, np.uint16, np.uint32, np.int64, np.uint64, np.complex128) + if have_binary128(): + data_dtypes.extend(np.float128, np.complex256) class TestMGHAPI(TestAnalyzeProxyAPI): @@ -334,6 +339,7 @@ class TestMGHAPI(TestAnalyzeProxyAPI): has_inter = False settable_offset = False data_endian = '>' + data_dtypes = (np.uint8, np.int16, np.int32, np.float32) class TestMinc1API(_TestProxyAPI): From 8cc2629613656de328f1ffcdbc601bf641db0a2f Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 2 Dec 2019 10:22:49 -0500 Subject: [PATCH 4/6] TEST: Refine parameter and assertion precision --- nibabel/tests/test_proxy_api.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/nibabel/tests/test_proxy_api.py b/nibabel/tests/test_proxy_api.py index dd48e95506..51edf2af7f 100644 --- a/nibabel/tests/test_proxy_api.py +++ b/nibabel/tests/test_proxy_api.py @@ -55,7 +55,7 @@ from nose.tools import (assert_true, assert_false, assert_raises, assert_equal, assert_not_equal, assert_greater_equal) -from numpy.testing import (assert_almost_equal, assert_array_equal) +from numpy.testing import assert_almost_equal, assert_array_equal, assert_allclose from ..testing import data_path as DATA_PATH, assert_dt_equal @@ -143,7 +143,10 @@ def validate_get_scaled(self, pmaker, params): for dtype in np.sctypes['float'] + np.sctypes['int'] + np.sctypes['uint']: out = prox.get_scaled(dtype=dtype) - assert_almost_equal(out, params['arr_out']) + # Half-precision is imprecise. Obviously. It's a bad idea, but don't break + # the test over it. + rtol = 1e-03 if dtype == np.float16 else 1e-05 + assert_allclose(out, params['arr_out'].astype(out.dtype), rtol=rtol, atol=1e-08) assert_greater_equal(out.dtype, np.dtype(dtype)) # Shape matches expected shape assert_equal(out.shape, params['shape']) @@ -218,8 +221,8 @@ def obj_params(self): offsets = (self.header_class().get_data_offset(),) else: offsets = (0, 16) - slopes = (1., 2., 3.1416) if self.has_slope else (1.,) - inters = (0., 10., 2.7183) if self.has_inter else (0.,) + slopes = (1., 2., float(np.float32(3.1416))) if self.has_slope else (1.,) + inters = (0., 10., float(np.float32(2.7183))) if self.has_inter else (0.,) for shape, dtype, offset, slope, inter in product(self.shapes, self.data_dtypes, offsets, @@ -263,7 +266,7 @@ def sio_func(): dtype=dtype, dtype_out=dtype_out, arr=arr.copy(), - arr_out=arr * slope + inter, + arr_out=arr.astype(dtype_out) * slope + inter, shape=shape, offset=offset, slope=slope, From 779f27b0b1a2b73978b01db1c9c38f2e898c66b8 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Mon, 2 Dec 2019 10:24:27 -0500 Subject: [PATCH 5/6] FIX: Cast scaling parameters only when safe --- nibabel/arrayproxy.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nibabel/arrayproxy.py b/nibabel/arrayproxy.py index eda456c4b7..b8313e8ae4 100644 --- a/nibabel/arrayproxy.py +++ b/nibabel/arrayproxy.py @@ -360,10 +360,13 @@ def _get_scaled(self, dtype, slicer): scl_slope = np.asanyarray(self._slope) scl_inter = np.asanyarray(self._inter) use_dtype = scl_slope.dtype if dtype is None else dtype - slope = scl_slope.astype(use_dtype) - inter = scl_inter.astype(use_dtype) + + if np.can_cast(scl_slope, use_dtype): + scl_slope = scl_slope.astype(use_dtype) + if np.can_cast(scl_inter, use_dtype): + scl_inter = scl_inter.astype(use_dtype) # Read array and upcast as necessary for big slopes, intercepts - scaled = apply_read_scaling(self._get_unscaled(slicer=slicer), slope, inter) + scaled = apply_read_scaling(self._get_unscaled(slicer=slicer), scl_slope, scl_inter) if dtype is not None: scaled = scaled.astype(np.promote_types(scaled.dtype, dtype), copy=False) return scaled From b1f6e2cc4e53af880e70d9de5436c05e4aea7627 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Tue, 3 Dec 2019 12:13:30 -0500 Subject: [PATCH 6/6] TEST: Add comment to explain parameters --- nibabel/tests/test_proxy_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nibabel/tests/test_proxy_api.py b/nibabel/tests/test_proxy_api.py index 51edf2af7f..1694254a0c 100644 --- a/nibabel/tests/test_proxy_api.py +++ b/nibabel/tests/test_proxy_api.py @@ -221,6 +221,8 @@ def obj_params(self): offsets = (self.header_class().get_data_offset(),) else: offsets = (0, 16) + # For non-integral parameters, cast to float32 value can be losslessly cast + # later, enabling exact checks, then back to float for consistency slopes = (1., 2., float(np.float32(3.1416))) if self.has_slope else (1.,) inters = (0., 10., float(np.float32(2.7183))) if self.has_inter else (0.,) for shape, dtype, offset, slope, inter in product(self.shapes,