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 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 diff --git a/nibabel/tests/test_proxy_api.py b/nibabel/tests/test_proxy_api.py index b8316d7291..1694254a0c 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 @@ -54,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 @@ -142,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']) @@ -192,6 +196,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 @@ -216,11 +221,12 @@ 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.,) - dtypes = (np.uint8, np.int16, np.float32) + # 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, - dtypes, + self.data_dtypes, offsets, slopes, inters): @@ -262,7 +268,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, @@ -325,6 +331,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 +344,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):