Closed
Show file tree
Hide file tree
Changes from 1 commit
Show all changes
36 commits
Select commit Hold shift + click to select a range
3cfff0d
ENH: Add an utility to calculate obliquity of affines
oestebanSep 20, 2019
c92d560
enh(tests): add not-oblique test, move tests to ``test_affines.py``
oestebanSep 20, 2019
da8da56
enh: return radians unless degrees=True
oestebanSep 22, 2019
253a256
enh: address @matthew-brett's comments
oestebanSep 23, 2019
04584b6
doc: add link to AFNI's documentation about *obliquity* [skip ci]
oestebanSep 24, 2019
70d9620
Initial commit
oestebanAug 1, 2018
36a1764
add TransformBase
oestebanAug 1, 2018
2a700c9
add new module
oestebanAug 1, 2018
fda2bfe
add documentation, core functionality
oestebanAug 1, 2018
3ef5adc
add a deformation field transform
oestebanAug 1, 2018
189a662
optimize deformation field
oestebanAug 2, 2018
2619ae4
pre-cache transformed indexes
oestebanAug 2, 2018
4e7c19b
used cached field
oestebanAug 2, 2018
23d7002
add caching of deltas in voxel coordinates
oestebanAug 3, 2018
2acd9f3
add bspline cython extension
oestebanAug 3, 2018
385ceff
a smarter ImageSpace
oestebanAug 4, 2018
fa030a4
add comment
oestebanAug 10, 2018
fd418fe
remove cython module
oestebanAug 10, 2018
13d722f
cleanup
oestebanAug 11, 2018
253aed5
starting with bspline transform
oestebanAug 11, 2018
47c2a57
finishing b-spline interpolation
oestebanAug 13, 2018
4478c06
Add base implementation of transforms and change base implementation …
oestebanMar 12, 2019
99fa15d
export to hdf5
oestebanMar 13, 2019
5eed01d
corrections to adhere the current x5 format draft
oestebanMar 14, 2019
632e068
fix coordinates translation in affines, import itk affines
oestebanMar 15, 2019
f2c062e
wip: reads & writes ITK's MatrixOffsetTransformBase transforms, with …
oestebanMar 16, 2019
8eb670d
fix: print warnings to stderr, sty: minimal fixes
oestebanMar 16, 2019
799fb6e
enh(affines): write out AFNI 12-parameters files
oestebanMar 19, 2019
ce36f06
enh(transforms): finish up a x5-to-fsl writer of affines
oestebanMar 21, 2019
8e1bf44
enh(transforms): improve generation of x5 structures of reference spaces
oestebanMar 21, 2019
e2df7cc
fix(transforms): string formating forgotten when writing ITK transforms
oestebanMar 22, 2019
c5d10ed
wip(transforms): writing up some linear transform readers
oestebanMar 22, 2019
a18ce1d
enh(transform): HDF5 - set values as attributes; generalize affines t…
oestebanMar 22, 2019
9902f50
enh: apply some early comments from @effigies.
oestebanSep 19, 2019
fe74efb
fix: add a first battery of tests
oestebanSep 26, 2019
596994c
enh: add tests for affines stored in AFNI format (non-oblique images)
oestebanSep 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Failed to load files.
PrevPrevious commit
Next Next commit
starting with bspline transform
  • Loading branch information
@oesteban
oesteban committedSep 27, 2019
commit 253aed5a70f5863e995836fc49d52bb89891c27a
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,6 +97,10 @@ def reference(self):
def reference(self, image):
self._reference = ImageSpace(image)

@property
def ndim(self):
return self.reference.ndim

def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
output_dtype=None):
'''Resample the moving image in reference space
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,23 +17,40 @@


class DeformationFieldTransform(TransformBase):
'''Represents linear transforms on image data'''
'''Represents a dense field of displacements (one vector per voxel)'''
__slots__ = ['_field', '_moving', '_moving_space']
__s = (slice(None), )

def __init__(self, field):
def __init__(self, field, reference=None):
'''
Create a dense deformation field transform
'''
super(DeformationFieldTransform, self).__init__()
# By definition, a free deformation field has a
# displacement vector per voxel in output (reference)
# space
self.reference = four_to_three(field)[0]
self._field = field.get_data()

ndim = self._field.ndim - 1
if self._field.shape[:-1] != ndim:
raise ValueError(
'Number of components of the deformation field does '
'not match the number of dimensions')

self._moving = None # Where each voxel maps to
self._moving_space = None # Input space cache

# By definition, a free deformation field has a
# displacement vector per voxel in output (reference)
# space
if reference is None:
reference = four_to_three(field)[0]
elif reference.shape[:ndim] != field.shape[:ndim]:
raise ValueError(
'Reference ({}) and field ({}) must have the same '
'grid.'.format(
_pprint(reference.shape[:ndim]),
_pprint(field.shape[:ndim])))

self.reference = reference

def _cache_moving(self, moving):
# Check whether input (moving) space is cached
moving_space = ImageSpace(moving)
Expand DownExpand Up@@ -132,3 +149,91 @@ def map_coordinates(self, coordinates, order=3, mode='mirror', cval=0.0,
0, -1)

return coordinates + deltas


class BSplineFieldTransform(TransformBase):
__slots__ = ['_coeffs', '_knots', '_refknots']

def __init__(self, reference, coefficients, order=3):
'''Create a smooth deformation field using B-Spline basis'''
super(BSplineFieldTransform, self).__init__()
self.reference = reference

if coefficients.shape[-1] != self.ndim:
raise ValueError(
'Number of components of the coefficients does '
'not match the number of dimensions')

self._coeffs = coefficients.get_data().reshape(-1, self.ndim)
self._knots = ImageSpace(four_to_three(coefficients)[0])

# Calculate the voxel coordinates of the reference image
# in the B-spline basis space.
self._refknots = np.tensordot(
np.linalg.inv(self.knots.affine),
np.vstack((self.reference.ndcoords, np.ones((1, self.reference.nvox)))),
axes=1)[..., :3].reshape(self.reference.shape + (self._knots.ndim, ))


def map_voxel(self, index, moving=None):

indexes = []
offset = 0.0 if self._order & 1 else 0.5
for dim in range(self.ndim):
first = int(np.floor(xi[dim] + offset) - self._order // 2)
indexes.append(list(range(first, first + self._order + 1)))

ndindex = np.moveaxis(
np.array(np.meshgrid(*indexes, indexing='ij')), 0, -1).reshape(
-1, self.ndim)

vbspl = np.vectorize(cubic)
weights = np.prod(vbspl(ndindex - xi), axis=-1)
ndindex = [tuple(v) for v in ndindex]

zero = np.zeros(self.ndim)
shape = np.array(self.shape)
coeffs = []
for ijk in ndindex:
offbounds = (zero > ijk) | (shape <= ijk)
if np.any(offbounds):
# Deal with offbounds samples
if self._off_bounds == 'constant':
coeffs.append([self._fill_value] * self.ncomp)
continue
ijk = np.array(ijk, dtype=int)
ijk[ijk < 0] *= -1
ijk[ijk >= shape] = (2 * shape[ijk >= shape] - ijk[ijk >= shape] - 1).astype(int)
ijk = tuple(ijk.tolist())

coeffs.append(self._coeffs[ijk])
return weights.dot(np.array(coeffs, dtype=float))


# def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
# output_dtype=None):
# '''

# Examples
# --------

# >>> import numpy as np
# >>> import nibabel as nb
# >>> ref = nb.load('t1_weighted.nii.gz')
# >>> coeffs = np.zeros((6, 6, 6, 3))
# >>> coeffs[2, 2, 2, ...] = [10.0, -20.0, 0]
# >>> aff = ref.affine
# >>> aff[:3, :3] = aff.dot(np.eye(3) * np.array(ref.header.get_zooms()[:3] / 6.0)
# >>> coeffsimg = nb.Nifti1Image(coeffs, ref.affine, ref.header)
# >>> xfm = nb.transform.BSplineFieldTransform(ref, coeffsimg)
# >>> new = xfm.resample(ref)
# >>> new.to_filename('deffield.nii.gz')

# '''

# return super(BSplineFieldTransform, self).resample(
# moving, order=order, mode=mode, cval=cval, prefilter=prefilter)


def _pprint(inlist):
return 'x'.join(['%d' % s for s in inlist])