diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a9102c536d131052e048e8ca04a498659cfcdcbb..4ad29971d0ad8acdce7eb7975cdac8e1479860ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,8 @@ Added * New ``firstDot`` option to the :func:`.path.getExt`, :func:`.path.removeExt`, and :func:`.path.splitExt`, functions, offering rudimentary support for double-barrelled filenames. +* The :func:`.nonlinear.applyDeformation` function now accepts a ``premat`` + affine, which is applied to the input image before the deformation field. Changed diff --git a/fsl/transform/nonlinear.py b/fsl/transform/nonlinear.py index 3010cda86f246e1e8697a4cfaee412a000fc91b5..daf13adcf35f85995f1b309acf38dc2d6c145d37 100644 --- a/fsl/transform/nonlinear.py +++ b/fsl/transform/nonlinear.py @@ -679,7 +679,13 @@ def convertDeformationSpace(field, from_, to): defType=field.deformationType) -def applyDeformation(image, field, ref=None, order=1, mode=None, cval=None): +def applyDeformation(image, + field, + ref=None, + order=1, + mode=None, + cval=None, + premat=None): """Applies a :class:`DeformationField` to an :class:`.Image`. The image is transformed into the space of the field's reference image @@ -691,25 +697,30 @@ def applyDeformation(image, field, ref=None, order=1, mode=None, cval=None): the input image. It is therefore assumed that an alternate ``ref`` is aligned in world coordinates with the field's actual reference image. - :arg image: :class:`.Image` to be transformed + :arg image: :class:`.Image` to be transformed - :arg field: :class:`DeformationField` to use + :arg field: :class:`DeformationField` to use - :arg ref: Alternate reference image - if not provided, ``field.ref`` - is used + :arg ref: Alternate reference image - if not provided, ``field.ref`` + 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 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 mode: How to handle regions which are outside of the image FOV. - Defaults to `''nearest'``. + :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'``. + :arg cval: Constant value to use when ``mode='constant'``. - :return: ``numpy.array`` containing the transformed image data. + :arg premat: Optional affine transform which can be used if ``image`` is + not in the same space as ``field.src``. Assumed to transform + from ``image`` **voxel** coordinates into ``field.src`` + **voxel** coordinates. + + :return: ``numpy.array`` containing the transformed image data. """ if order is None: order = 1 @@ -763,12 +774,15 @@ def applyDeformation(image, field, ref=None, order=1, mode=None, cval=None): # We assume world-world alignment # between the original source # and the image to be resampled - if not image.sameSpace(src): - post = affine.concat(image.getAffine('world', 'voxel'), - src .getAffine('voxel', 'world')) + if (premat is not None) or (not image.sameSpace(src)): + if premat is None: + premat = affine.concat(image.getAffine('world', 'voxel'), + src .getAffine('voxel', 'world')) + else: + premat = affine.invert(premat) shape = field.shape field = field.reshape((-1, 3)) - field = affine.transform(field, post) + field = affine.transform(field, premat) field = field.reshape(shape) field = field.transpose((3, 0, 1, 2)) diff --git a/tests/test_transform/test_nonlinear.py b/tests/test_transform/test_nonlinear.py index b161f4f23748eba7f25766dc96edccdadae1bbd9..aaf4e07fec41df2428a7863bae6e8a229a34659a 100644 --- a/tests/test_transform/test_nonlinear.py +++ b/tests/test_transform/test_nonlinear.py @@ -411,6 +411,60 @@ def test_applyDeformation_altsrc(): assert np.all(np.isclose(expect, result)) +def test_applyDeformation_premat(): + + src2ref = affine.compose( + np.random.randint(2, 5, 3), + np.random.randint(1, 10, 3), + [0, 0, 0]) + ref2src = affine.invert(src2ref) + + srcdata = np.random.randint(1, 65536, (10, 10, 10)) + refdata = np.random.randint(1, 65536, (10, 10, 10)) + + src = fslimage.Image(srcdata) + ref = fslimage.Image(refdata, xform=src2ref) + field = _affine_field(src, ref, ref2src, 'world', 'world') + + # First try a down-sampled version + # of the original source image + altsrc, xf = resample.resample(src, (5, 5, 5), origin='corner') + altsrc = fslimage.Image(altsrc, xform=xf, header=src.header) + expect, xf = resample.resampleToReference( + altsrc, ref, matrix=src2ref, order=1, mode='nearest') + premat = affine.concat(src .getAffine('world', 'voxel'), + altsrc.getAffine('voxel', 'world')) + result = nonlinear.applyDeformation( + altsrc, field, order=1, mode='nearest', premat=premat) + assert np.all(np.isclose(expect, result)) + + # Now try a down-sampled ROI + # of the original source image + altsrc = roi.roi(src, [(2, 9), (2, 9), (2, 9)]) + altsrc, xf = resample.resample(altsrc, (4, 4, 4)) + altsrc = fslimage.Image(altsrc, xform=xf, header=src.header) + expect, xf = resample.resampleToReference( + altsrc, ref, matrix=src2ref, order=1, mode='nearest') + premat = affine.concat(src .getAffine('world', 'voxel'), + altsrc.getAffine('voxel', 'world')) + result = nonlinear.applyDeformation( + altsrc, field, order=1, mode='nearest', premat=premat) + assert np.all(np.isclose(expect, result)) + + # down-sampled and offset ROI + # of the original source image + altsrc = roi.roi(src, [(-5, 8), (-5, 8), (-5, 8)]) + altsrc, xf = resample.resample(altsrc, (6, 6, 6)) + altsrc = fslimage.Image(altsrc, xform=xf, header=src.header) + expect, xf = resample.resampleToReference( + altsrc, ref, matrix=src2ref, order=1, mode='nearest') + premat = affine.concat(src .getAffine('world', 'voxel'), + altsrc.getAffine('voxel', 'world')) + result = nonlinear.applyDeformation( + altsrc, field, order=1, mode='nearest', premat=premat) + assert np.all(np.isclose(expect, result)) + + def test_applyDeformation_altref(): src2ref = affine.compose( np.random.randint(2, 5, 3),