Something went wrong on our end
Forked from
FSL / fslpy
1026 commits behind the upstream repository.
-
Paul McCarthy authored
affine.rescale function, for reuse.
Paul McCarthy authoredaffine.rescale function, for reuse.
resample.py 10.73 KiB
#!/usr/bin/env python
#
# resample.py - The resample functino
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module defines the :func:`resample` function, which can be used
to resample an :class:`.Image` object to a different resolution.
The :func:`resampleToPixdims` and :func:`resampleToReference` functions
are convenience wrappers around :func:`resample`.
The :func:`applySmoothing` function is a sub-function of :func:`resample`.
"""
import numpy as np
import scipy.ndimage as ndimage
import fsl.transform.affine as affine
import fsl.utils.deprecated as deprecated
def resampleToPixdims(image, newPixdims, **kwargs):
"""Resample ``image`` so that it has the specified voxel dimensions.
This is a wrapper around :func:`resample` - refer to its documenttion
for details on the other arguments and the return values.
:arg image: :class:`.Image` to resample
:arg pixdims: New voxel dimensions to resample ``image`` to.
"""
newPixdims = np.array(newPixdims)
oldShape = np.array(image.shape)
oldPixdims = np.array(image.pixdim)
newShape = oldShape * (oldPixdims / newPixdims)
return resample(image, newShape, **kwargs)
def resampleToReference(image, reference, matrix=None, **kwargs):
"""Resample ``image`` into the space of the ``reference``.
This is a wrapper around :func:`resample` - refer to its documenttion
for details on the other arguments and the return values.
When resampling to a reference image, resampling will only be applied
along the spatial (first three) dimensions.
:arg image: :class:`.Image` to resample
:arg matrix: Optional world-to-world affine alignment matrix
:arg reference: :class:`.Nifti` defining the space to resample ``image``
into
"""
oldShape = list(image.shape)
newShape = list(reference.shape[:3])
if matrix is None:
matrix = np.eye(4)
# If the input image is >3D, pad the
# new shape so that we only resample
# along the first 3 dimensions.
if len(newShape) < len(oldShape):
newShape = newShape + oldShape[len(newShape):]
# Align the two images together
# via their vox-to-world affines,
# and the world-to-world affine
# if provided
matrix = affine.concat(image.worldToVoxMat,
affine.invert(matrix),
reference.voxToWorldMat)
# If the input image is >3D, we
# have to adjust the resampling
# matrix to take into account the
# additional dimensions (see scipy.
# ndimage.affine_transform)
if len(newShape) > 3:
rotmat = matrix[:3, :3]
offsets = matrix[:3, 3]
matrix = np.eye(len(newShape) + 1)
matrix[:3, :3] = rotmat
matrix[:3, -1] = offsets
kwargs['mode'] = kwargs.get('mode', 'constant')
kwargs['newShape'] = newShape
kwargs['matrix'] = matrix
data = resample(image, **kwargs)[0]
# The image is now in the same space
# as the reference, so it inherits
# ref's voxel-to-world affine
return data, reference.voxToWorldMat
def resample(image,
newShape,
sliceobj=None,
dtype=None,
order=1,
smooth=True,
origin=None,
matrix=None,
mode=None,
cval=0):
"""Returns a copy of the data in the ``image``, resampled to the specified
``newShape``.
The space that the image is resampled into can be defined in one of the
following ways, in decreasing order of precedence:
1. If a ``matrix`` is provided, it is applied to the voxel coordinates
when retrieving values from the ``image``
2. Otherwise the image is simply scaled according to the ratio calculated
by ``image.shape / newShape``. In this case the ``origin`` argument
may be used to adjust the alignemnt of the original and resampled
voxel grids.
See the ``scipy.ndimage.affine_transform`` function for more details,
particularly on the ``order``, ``matrix``, ``mode`` and
``cval`` arguments.
.. note:: If a custom resampling ``matrix`` is specified, the adjusted
voxel-to-world afffine cannot be calculated by this function,
so ``None`` will be returned instead.
:arg image: :class:`.Image` object to resample
:arg newShape: Desired shape. May containg floating point values, in which
case the resampled image will have shape
``round(newShape)``, but the voxel sizes will have scales
``self.shape / newShape`` (unless ``matrix`` is specified).
: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 ``newShape``. A
:exc:`ValueError` is raised if this is not the case.
:arg dtype: ``numpy`` data type of the resampled data. If ``None``,
the :meth:`dtype` of this ``Image`` is used.
:arg order: Spline interpolation order, passed through to the
``scipy.ndimage.affine_transform`` function - ``0``
corresponds to nearest neighbour interpolation, ``1``
(the default) to linear interpolation, and ``3`` to
cubic interpolation.
:arg smooth: If ``True`` (the default), the data is smoothed before
being resampled, but only along axes which are being
down-sampled (i.e. where ``newShape[i] < self.shape[i]``).
:arg origin: ``'centre'`` (the default) or ``'corner'``. ``'centre'``
resamples the image such that the centre of the corner
voxels of this image and the resampled data are
aligned. ``'corner'`` resamples the image such that
the corner of the corner voxels are aligned (and
therefore the voxel grids are aligned).
Ignored if ``offset`` or ``matrix`` is specified.
:arg matrix: Arbitrary affine transformation matrix to apply to the
voxel coordinates of ``image`` when resampling.
:arg mode: How to handle regions which are outside of the image FOV.
Defaults to `''nearest'``.
:arg cval: Constant value to use when ``mode='constant'``.
:returns: A tuple containing:
- 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 spatial
dimensions of the resampled data, or ``None`` if a ``matrix``
was provided.
"""
if sliceobj is None: sliceobj = slice(None)
if dtype is None: dtype = image.dtype
if origin is None: origin = 'centre'
if mode is None: mode = 'nearest'
if origin == 'center': origin = 'centre'
ownMatrix = matrix is None
if origin not in ('centre', 'corner'):
raise ValueError('Invalid value for origin: {}'.format(origin))
data = np.array(image[sliceobj], dtype=dtype, copy=False)
if len(data.shape) != len(newShape):
raise ValueError('Data dimensions do not match new shape: '
'len({}) != len({})'.format(data.shape, newShape))
# If matrix not provided, calculate
# a scaling/offset matrix from the
# old/new shape ratio and the origin
# setting.
if matrix is None:
matrix = affine.rescale(data.shape, newShape, origin)
# identity matrix? the image
# doesn't need to be resampled
if np.all(np.isclose(matrix, np.eye(len(newShape) + 1))):
return data, image.voxToWorldMat
newShape = np.array(np.round(newShape), dtype=np.int)
# Apply smoothing if requested,
# and if not using nn interp
if order > 0 and smooth:
data = applySmoothing(data, matrix, newShape)
# Do the resample thing
data = ndimage.affine_transform(data,
matrix,
output_shape=newShape,
order=order,
mode=mode,
cval=cval)
# Construct an affine transform which
# puts the resampled image into the
# same world coordinate system as this
# image. We may be working with >3D data,
# so here we discard the non-spatial
# parts of the resampling matrix
if matrix.shape != (4, 4):
rotmat = matrix[:3, :3]
offsets = matrix[:3, -1]
matrix = np.eye(4)
matrix[:3, :3] = rotmat
matrix[:3, -1] = offsets
# We can only adjust the v2w affine if
# the input space and resampling space
# are aligned (e.g. if we're just
# resampling to different dimensions).
# We can't assume this when a custom
# matrix is specified, so we just give
# up and return None.
if ownMatrix: matrix = affine.concat(image.voxToWorldMat, matrix)
else: matrix = None
return data, matrix
def applySmoothing(data, matrix, newShape):
"""Called by the :func:`resample` function.
If interpolating and smoothing, we apply a gaussian filter along axes with
a resampling ratio greater than 1.1. We do this so that interpolation has
an effect when down-sampling to a resolution where the voxel centres are
aligned (as otherwise any interpolation regime will be equivalent to
nearest neighbour). This more-or-less mimics the behaviour of FLIRT.
See the ``scipy.ndimage.gaussian_filter`` function for more details.
:arg data: Data to be smoothed.
:arg matrix: Affine matrix to be used during resampling. The voxel
scaling factors are extracted from this.
:arg newShape: Shape the data is to be resampled into.
:returns: A smoothed copy of ``data``.
"""
ratio = affine.decompose(matrix[:3, :3])[0]
if len(newShape) > 3:
ratio = np.concatenate((
ratio,
[float(o) / float(s)
for o, s in zip(data.shape[3:], newShape[3:])]))
sigma = np.array(ratio)
sigma[ratio < 1.1] = 0
sigma[ratio >= 1.1] *= 0.425
return ndimage.gaussian_filter(data, sigma)
@deprecated.deprecated('2.9.0', '3.0.0',
'Use fsl.transform.affine.rescale instead')
def calculateMatrix(oldShape, newShape, origin):
"""Deprecated - use :func:`.affine.rescale` instead. """
xform = affine.rescale(oldShape, newShape, origin)
if np.all(np.isclose(xform, np.eye(len(oldShape) + 1))):
return None
return xform[:-1, :]