Skip to content
Snippets Groups Projects
Commit dfb45639 authored by Paul McCarthy's avatar Paul McCarthy :mountain_bicyclist:
Browse files

Merge branch 'bf/coefficient_field' into 'master'

Bf/coefficient field

See merge request fsl/fslpy!252
parents be82aa76 e8eb9a49
No related branches found
No related tags found
No related merge requests found
Pipeline #5624 canceled
...@@ -2,8 +2,8 @@ This document contains the ``fslpy`` release history in reverse chronological ...@@ -2,8 +2,8 @@ This document contains the ``fslpy`` release history in reverse chronological
order. order.
3.3.0 (Under development) 3.3.0 (Tuesday 22nd September 2020)
------------------------- -----------------------------------
Added Added
...@@ -13,6 +13,7 @@ Added ...@@ -13,6 +13,7 @@ Added
``imtest``, ``fsl_abspath``, ``remove_ext``, ``Text2Vest``, and ``imtest``, ``fsl_abspath``, ``remove_ext``, ``Text2Vest``, and
``Vest2Text``. ``Vest2Text``.
* New :func:`.gps` function, wrapping the FSL ``gps`` command. * New :func:`.gps` function, wrapping the FSL ``gps`` command.
* New :func:`.vest.loadVestFile` and :func:`.vest.generateVest` functions.
Changed Changed
...@@ -22,11 +23,13 @@ Changed ...@@ -22,11 +23,13 @@ Changed
* Updates to the BIDS filetree specification. * Updates to the BIDS filetree specification.
Changed Fixed
^^^^^^^ ^^^^^
* Adjustments to tests and documentation * The :class:`.CoefficientField` class now works with alternate reference
images (i.e. a reference image with different dimensions to that which
was originally used when the non-linear transformation was calculated).
3.2.2 (Thursday 9th July 2020) 3.2.2 (Thursday 9th July 2020)
......
...@@ -225,6 +225,27 @@ def _readFnirtCoefficientField(fname, img, src, ref): ...@@ -225,6 +225,27 @@ def _readFnirtCoefficientField(fname, img, src, ref):
# image voxel coordinates. # image voxel coordinates.
fieldToRefMat = affine.scaleOffsetXform(knotSpacing, 0) fieldToRefMat = affine.scaleOffsetXform(knotSpacing, 0)
# But if the provided reference has
# different resolution to the
# reference that was originally
# used to generate the warp field,
# we need to adjust the field
# accordingly. We assume that the
# references are aligned in the FSL
# coordinate system, so simply apply
# a scaling factor calculated by
# dividing the original reference
# pixdims by the provided reference
# pixdims.
refPixdims = np.array([img.header['intent_p1'],
img.header['intent_p2'],
img.header['intent_p3']])
if not np.all(np.isclose(ref.pixdim[:3], refPixdims)):
fieldToRefMat = affine.concat(
affine.scaleOffsetXform(refPixdims / ref.pixdim[:3], 0),
fieldToRefMat)
return nonlinear.CoefficientField(fname, return nonlinear.CoefficientField(fname,
src, src,
ref, ref,
...@@ -236,7 +257,7 @@ def _readFnirtCoefficientField(fname, img, src, ref): ...@@ -236,7 +257,7 @@ def _readFnirtCoefficientField(fname, img, src, ref):
fieldToRefMat=fieldToRefMat) fieldToRefMat=fieldToRefMat)
def readFnirt(fname, src, ref, defType=None): def readFnirt(fname, src, ref, defType=None, intent=None):
"""Reads a non-linear FNIRT transformation image, returning """Reads a non-linear FNIRT transformation image, returning
a :class:`.DeformationField` or :class:`.CoefficientField` depending a :class:`.DeformationField` or :class:`.CoefficientField` depending
on the file type. on the file type.
...@@ -247,28 +268,38 @@ def readFnirt(fname, src, ref, defType=None): ...@@ -247,28 +268,38 @@ def readFnirt(fname, src, ref, defType=None):
:arg defType: Deformation type - either ``'absolute'`` or ``'relative'``. :arg defType: Deformation type - either ``'absolute'`` or ``'relative'``.
Only used if the file is a deformation field. If not Only used if the file is a deformation field. If not
provided, is automatically inferred from the data. provided, is automatically inferred from the data.
:arg intent: NIFTI intent code of ``fname``. e.g.
:attr:`.constants.FSL_FNIRT_DISPLACEMENT_FIELD`. If not
provided, the intent is read from the image header.
""" """
# Figure out whether the file if defType not in (None, 'absolute', 'relative'):
# is a deformation field or raise ValueError('defType must be None, "absolute" or "relative" '
# a coefficient field '(passed in as {})'.format(defType))
img = fslimage.Image(fname, loadData=False) # Figure out whether the file is a
disps = (constants.FSL_FNIRT_DISPLACEMENT_FIELD, # deformation field or a coefficient
constants.FSL_TOPUP_FIELD) # field by checking the intent code.
coefs = (constants.FSL_CUBIC_SPLINE_COEFFICIENTS, # If the intent is provided, assume
constants.FSL_DCT_COEFFICIENTS, # that the caller knows the type of
constants.FSL_QUADRATIC_SPLINE_COEFFICIENTS, # the field.
constants.FSL_TOPUP_CUBIC_SPLINE_COEFFICIENTS, img = fslimage.Image(fname, loadData=False)
constants.FSL_TOPUP_QUADRATIC_SPLINE_COEFFICIENTS) intent = intent or img.intent
disps = (constants.FSL_FNIRT_DISPLACEMENT_FIELD,
constants.FSL_TOPUP_FIELD)
coefs = (constants.FSL_CUBIC_SPLINE_COEFFICIENTS,
constants.FSL_DCT_COEFFICIENTS,
constants.FSL_QUADRATIC_SPLINE_COEFFICIENTS,
constants.FSL_TOPUP_CUBIC_SPLINE_COEFFICIENTS,
constants.FSL_TOPUP_QUADRATIC_SPLINE_COEFFICIENTS)
if img.intent in disps: if intent in disps:
return _readFnirtDeformationField(fname, img, src, ref, defType) return _readFnirtDeformationField(fname, img, src, ref, defType)
elif img.intent in coefs: elif intent in coefs:
return _readFnirtCoefficientField(fname, img, src, ref) return _readFnirtCoefficientField(fname, img, src, ref)
else: else:
raise ValueError('Cannot determine type of nonlinear ' raise ValueError('Cannot determine type of nonlinear warp field '
'file {}'.format(fname)) '{} (intent code: {})'.format(fname, intent))
def toFnirt(field): def toFnirt(field):
......
...@@ -496,13 +496,11 @@ class CoefficientField(NonLinearTransform): ...@@ -496,13 +496,11 @@ class CoefficientField(NonLinearTransform):
fdata = self.data fdata = self.data
nx, ny, nz = self.shape[:3] nx, ny, nz = self.shape[:3]
ix, iy, iz = self.ref.shape[:3]
# Convert the given voxel coordinates # Convert the given voxel coordinates
# into the corresponding coefficient # into the corresponding coefficient
# field voxel coordinates # field voxel coordinates
x, y, z = coords.T i, j, k = affine.transform(coords, self.refToFieldMat).T
i, j, k = affine.transform(coords, self.refToFieldMat).T
# i, j, k: coefficient field indices # i, j, k: coefficient field indices
# u, v, w: position of the ref voxel # u, v, w: position of the ref voxel
...@@ -582,6 +580,10 @@ def convertDeformationType(field, defType=None): ...@@ -582,6 +580,10 @@ def convertDeformationType(field, defType=None):
if field.deformationType == 'absolute': defType = 'relative' if field.deformationType == 'absolute': defType = 'relative'
else: defType = 'absolute' else: defType = 'absolute'
if defType not in ('absolute', 'relative'):
raise ValueError('defType must be "absolute" or "relative" '
'("{}" passed)'.format(defType))
# Regardless of the conversion direction, # Regardless of the conversion direction,
# we need the coordinates of every voxel # we need the coordinates of every voxel
# in the reference coordinate system. # in the reference coordinate system.
...@@ -601,8 +603,8 @@ def convertDeformationType(field, defType=None): ...@@ -601,8 +603,8 @@ def convertDeformationType(field, defType=None):
# assumed to be) the relative shift. Or, # assumed to be) the relative shift. Or,
# to convert from absolute to relative, # to convert from absolute to relative,
# we subtract the reference image voxels. # we subtract the reference image voxels.
if defType == 'absolute': return field.data + coords if defType == 'absolute': return field.data + coords
elif defType == 'relative': return field.data - coords else: return field.data - coords
def convertDeformationSpace(field, from_, to): def convertDeformationSpace(field, from_, to):
......
...@@ -9,10 +9,13 @@ import os.path as op ...@@ -9,10 +9,13 @@ import os.path as op
import itertools as it import itertools as it
import numpy as np import numpy as np
import nibabel as nib
import pytest import pytest
import fsl.data.image as fslimage import fsl.data.image as fslimage
import fsl.utils.tempdir as tempdir
import fsl.data.constants as constants
import fsl.transform.affine as affine import fsl.transform.affine as affine
import fsl.transform.nonlinear as nonlinear import fsl.transform.nonlinear as nonlinear
import fsl.transform.fnirt as fnirt import fsl.transform.fnirt as fnirt
...@@ -51,6 +54,39 @@ def test_readFnirt(): ...@@ -51,6 +54,39 @@ def test_readFnirt():
assert disp.refSpace == 'fsl' assert disp.refSpace == 'fsl'
def test_readFnirt_defType_intent():
src = op.join(datadir, 'src.nii.gz')
ref = op.join(datadir, 'ref.nii.gz')
coef = op.join(datadir, 'coefficientfield.nii.gz')
disp = op.join(datadir, 'displacementfield.nii.gz')
src = fslimage.Image(src)
ref = fslimage.Image(ref)
field = fnirt.readFnirt(disp, src, ref, defType='absolute')
assert field.deformationType == 'absolute'
field = fnirt.readFnirt(disp, src, ref, defType='relative')
assert field.deformationType == 'relative'
img = nib.load(coef)
img.header['intent_code'] = 0
with tempdir.tempdir():
img.to_filename('field.nii.gz')
with pytest.raises(ValueError):
fnirt.readFnirt('field', src, ref)
field = fnirt.readFnirt(
'field', src, ref,
intent=constants.FSL_CUBIC_SPLINE_COEFFICIENTS)
assert isinstance(field, nonlinear.CoefficientField)
field = fnirt.readFnirt(
'field', src, ref,
intent=constants.FSL_FNIRT_DISPLACEMENT_FIELD)
assert isinstance(field, nonlinear.DeformationField)
def test_toFnirt(): def test_toFnirt():
def check(got, exp): def check(got, exp):
......
...@@ -311,6 +311,38 @@ def test_CoefficientField_transform(): ...@@ -311,6 +311,38 @@ def test_CoefficientField_transform():
assert np.all(np.isclose(gotnp, srccoordsnp[srcspace], **tol)) assert np.all(np.isclose(gotnp, srccoordsnp[srcspace], **tol))
def test_coefficientField_transform_altref():
# test coordinates (manually determined).
# original ref image is 2mm isotropic,
# resampled is 1mm. Each tuple contains:
#
# (src, ref2mm, ref1mm)
coords = [
((18.414, 26.579, 25.599), (11, 19, 11), (22, 38, 22)),
((14.727, 22.480, 20.340), ( 8, 17, 8), (16, 34, 16)),
((19.932, 75.616, 27.747), (11, 45, 5), (22, 90, 10))
]
nldir = op.join(datadir, 'nonlinear')
src = op.join(nldir, 'src.nii.gz')
ref = op.join(nldir, 'ref.nii.gz')
cf = op.join(nldir, 'coefficientfield.nii.gz')
src = fslimage.Image(src)
ref2mm = fslimage.Image(ref)
ref1mm = ref2mm.adjust((1, 1, 1))
cfref2mm = fnirt.readFnirt(cf, src, ref2mm)
cfref1mm = fnirt.readFnirt(cf, src, ref1mm)
for srcc, ref2mmc, ref1mmc in coords:
ref2mmc = cfref2mm.transform(ref2mmc, 'voxel', 'voxel')
ref1mmc = cfref1mm.transform(ref1mmc, 'voxel', 'voxel')
assert np.all(np.isclose(ref2mmc, srcc, 1e-4))
assert np.all(np.isclose(ref1mmc, srcc, 1e-4))
def test_coefficientFieldToDeformationField(): def test_coefficientFieldToDeformationField():
nldir = op.join(datadir, 'nonlinear') nldir = op.join(datadir, 'nonlinear')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment