diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 31b62178cd2d711fae19645e813e666be314e467..b6dad1cea394714ff0a04fe33fd78a207858b388 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,8 @@ Changed * Minimum required version of ``nibabel`` is now 2.3. * The :class:`.Image` class now fully delegates to ``nibabel`` for managing file handles. +* The :meth:`.Image.resample` method now supports images with more than three + dimensions. Removed @@ -38,6 +40,7 @@ Fixed * Make sure that FEAT ``Cluster`` objects (created by the :func:`.loadClusterResults` function) contain ``p`` and ``logp`` attributes, even when cluster thresholding was not used. +* Fix to the :class:`.ImageWrapper` regarding complex data types. 1.13.0 (Thursday 22nd November 2018) diff --git a/fsl/data/image.py b/fsl/data/image.py index a1e695b59bd81a36a845d3f64167efbf6222ecc5..24e129c8bfc1ecc42d56d5c77d241e3912c5bd67 100644 --- a/fsl/data/image.py +++ b/fsl/data/image.py @@ -1173,7 +1173,7 @@ class Image(Nifti): order=1, smooth=True): """Returns a copy of the data in this ``Image``, resampled to the - specified ``shape``. + specified ``newShape``. :arg newShape: Desired shape. May containg floating point values, in which case the resampled image will have shape @@ -1182,7 +1182,7 @@ class Image(Nifti): :arg sliceobj: Slice into this ``Image``. If ``None``, the whole image is resampled, and it is assumed that it has the - same number of dimensions as ``shape``. A + same number of dimensions as ``newShape``. A :exc:`ValueError` is raised if this is not the case. :arg dtype: ``numpy`` data type of the resampled data. If ``None``, @@ -1201,12 +1201,12 @@ class Image(Nifti): :returns: A tuple containing: - - A ``numpy`` array of shape ``shape``, containing an - interpolated copy of the data in this ``Image``. + - A ``numpy`` array of shape ``newShape``, containing + an interpolated copy of the data in this ``Image``. - A ``numpy`` array of shape ``(4, 4)``, containing the - adjusted voxel-to-world transformation for the resampled - data. + adjusted voxel-to-world transformation for the spatial + dimensions of the resampled data. """ if sliceobj is None: sliceobj = slice(None) @@ -1225,7 +1225,7 @@ class Image(Nifti): ratio = oldShape / newShape newShape = np.array(np.round(newShape), dtype=np.int) - scale = transform.scaleOffsetXform(ratio, 0) + scale = np.diag(ratio) # If interpolating and smoothing, we apply a # gaussian filter along axes with a resampling @@ -1242,7 +1242,7 @@ class Image(Nifti): data = ndimage.gaussian_filter(data, sigma) data = ndimage.affine_transform(data, - scale[:3, :3], + scale, output_shape=newShape, order=order) @@ -1250,6 +1250,7 @@ class Image(Nifti): # puts the resampled image into the # same world coordinate system as this # image. + scale = transform.scaleOffsetXform(ratio[:3], 0) xform = transform.concat(self.voxToWorldMat, scale) else: xform = self.voxToWorldMat diff --git a/fsl/data/imagewrapper.py b/fsl/data/imagewrapper.py index b73cb879aaf88ffa1e742477b3625fd7da91a590..2b49e4af594268f49a48a5dc280c323bc45d8eda 100644 --- a/fsl/data/imagewrapper.py +++ b/fsl/data/imagewrapper.py @@ -301,7 +301,14 @@ class ImageWrapper(notifier.Notifier): # Internally, we calculate and store the # data range for each volume/slice/vector - self.__volRanges = np.zeros((nvols, 2), dtype=np.float32) + # + # We use nan as a placeholder, so the + # dtype must be non-integral + dtype = self.__image.get_data_dtype() + if np.issubdtype(dtype, np.integer): + dtype = np.float32 + self.__volRanges = np.zeros((nvols, 2), + dtype=dtype) self.__coverage[ :] = np.nan self.__volRanges[:] = np.nan diff --git a/tests/test_image.py b/tests/test_image.py index 2786cc501e3a3eee2bb387cd78c33fd303d79b27..3290ed3619e6d13c713977cf851dd80bd37843c8 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1078,6 +1078,12 @@ def test_image_resample(seed): make_random_image(fname, shape) img = fslimage.Image(fname, mmap=False) + # bad shape + with pytest.raises(ValueError): + img.resample((10, 10)) + with pytest.raises(ValueError): + img.resample((10, 10, 10, 10)) + # resampling to the same shape should be a no-op samei, samex = img.resample(shape) assert np.all(samei == img[:]) @@ -1134,18 +1140,50 @@ def test_image_resample(seed): assert np.all(np.isclose(resvals, origvals)) - # Test a 4D image + del img + img = None + + +def test_image_resample_4d(seed): + + fname = 'test.nii.gz' + + with tempdir(): + make_random_image(fname, (10, 10, 10, 10)) + + # resample one volume img = fslimage.Image(fname) slc = (slice(None), slice(None), slice(None), 3) - resampled = img.resample(img.shape[:3], slc)[0] assert np.all(resampled == img[..., 3]) + # resample up resampled = img.resample((15, 15, 15), slc)[0] assert tuple(resampled.shape) == (15, 15, 15) + + # resample down + resampled = img.resample((5, 5, 5), slc)[0] + assert tuple(resampled.shape) == (5, 5, 5) + + # resample the entire image + resampled = img.resample((15, 15, 15, 10), None)[0] + assert tuple(resampled.shape) == (15, 15, 15, 10) + + resampled = img.resample((5, 5, 5, 10), None)[0] + assert tuple(resampled.shape) == (5, 5, 5, 10) + + # resample along the fourth dim + resampled = img.resample((15, 15, 15, 15), None)[0] + assert tuple(resampled.shape) == (15, 15, 15, 15) + + resampled = img.resample((5, 5, 5, 15), None)[0] + assert tuple(resampled.shape) == (5, 5, 5, 15) + del img + del resampled img = None + resampled = None def test_Image_init_xform_nifti1(): _test_Image_init_xform(1)