-
Notifications
You must be signed in to change notification settings - Fork 264
ENH,WIP: New transforms module #656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
3cfff0d
c92d560
da8da56
253a256
04584b6
70d9620
36a1764
2a700c9
fda2bfe
3ef5adc
189a662
2619ae4
4e7c19b
23d7002
2acd9f3
385ceff
fa030a4
fd418fe
13d722f
253aed5
47c2a57
4478c06
99fa15d
5eed01d
632e068
f2c062e
8eb670d
799fb6e
ce36f06
8e1bf44
e2df7cc
c5d10ed
a18ce1d
9902f50
fe74efb
596994c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…of linear and nonlinear transforms
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*- | ||
# vi: set ft=python sts=4 ts=4 sw=4 et: | ||
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## | ||
# | ||
# See COPYING file distributed along with the NiBabel package for the | ||
# copyright and license terms. | ||
# | ||
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ## | ||
''' Linear transforms ''' | ||
from __future__ import division, print_function, absolute_import | ||
import numpy as np | ||
from scipy import ndimage as ndi | ||
|
||
from .base import ImageSpace, TransformBase | ||
from ..funcs import four_to_three | ||
|
||
|
||
class Affine(TransformBase): | ||
'''Represents linear transforms on image data''' | ||
__slots__ = ['_matrix'] | ||
|
||
def __init__(self, matrix): | ||
'''Initialize a transform | ||
|
||
Parameters | ||
---------- | ||
|
||
matrix : ndarray | ||
The inverse coordinate transformation matrix **in physical | ||
coordinates**, mapping coordinates from *reference* space | ||
into *moving* space. | ||
This matrix should be provided in homogeneous coordinates. | ||
|
||
Examples | ||
-------- | ||
|
||
>>> xfm = Affine([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) | ||
>>> xfm.matrix # doctest: +NORMALIZE_WHITESPACE | ||
array([[1, 0, 0, 4], | ||
[0, 1, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1]]) | ||
|
||
''' | ||
self._matrix = np.array(matrix) | ||
assert self._matrix.ndim == 2, 'affine matrix should be 2D' | ||
assert self._matrix.shape[0] == self._matrix.shape[1], 'affine matrix is not square' | ||
super(Affine, self).__init__() | ||
oesteban marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@property | ||
def matrix(self): | ||
return self._matrix | ||
|
||
def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True, | ||
output_dtype=None): | ||
'''Resample the moving image in reference space | ||
|
||
Parameters | ||
---------- | ||
|
||
moving : `spatialimage` | ||
The image object containing the data to be resampled in reference | ||
space | ||
order : int, optional | ||
The order of the spline interpolation, default is 3. | ||
The order has to be in the range 0-5. | ||
mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional | ||
Determines how the input image is extended when the resamplings overflows | ||
a border. Default is 'constant'. | ||
cval : float, optional | ||
Constant value for ``mode='constant'``. Default is 0.0. | ||
prefilter: bool, optional | ||
Determines if the moving image's data array is prefiltered with | ||
a spline filter before interpolation. The default is ``True``, | ||
which will create a temporary *float64* array of filtered values | ||
if *order > 1*. If setting this to ``False``, the output will be | ||
slightly blurred if *order > 1*, unless the input is prefiltered, | ||
i.e. it is the result of calling the spline filter on the original | ||
input. | ||
|
||
Returns | ||
------- | ||
|
||
moved_image : `spatialimage` | ||
The moving imaged after resampling to reference space. | ||
|
||
|
||
Examples | ||
-------- | ||
|
||
>>> import nibabel as nib | ||
>>> xfm = Affine([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) | ||
>>> ref = nib.load('image.nii.gz') | ||
>>> xfm.reference = ref | ||
>>> xfm.resample(ref, order=0) | ||
|
||
''' | ||
if output_dtype is None: | ||
output_dtype = moving.header.get_data_dtype() | ||
|
||
try: | ||
reference = self.reference | ||
except ValueError: | ||
print('Warning: no reference space defined, using moving as reference') | ||
reference = moving | ||
|
||
# Compose an index to index affine matrix | ||
matrix = reference.affine.dot(self._matrix.dot(np.linalg.inv(moving.affine))) | ||
mdata = moving.get_data() | ||
moved = ndi.affine_transform( | ||
mdata, matrix=matrix[:mdata.ndim, :mdata.ndim], | ||
offset=matrix[:mdata.ndim, mdata.ndim], | ||
output_sha 8000 pe=reference.shape, order=order, mode=mode, | ||
cval=cval, prefilter=prefilter) | ||
|
||
moved_image = moving.__class__(moved, reference.affine, moving.header) | ||
moved_image.header.set_data_dtype(output_dtype) | ||
|
||
return moved_image | ||
|
||
def map_point(self, coords, forward=True): | ||
coords = np.array(coords) | ||
if coords.shape[0] == self._matrix.shape[0] - 1: | ||
coords = np.append(coords, [1]) | ||
affine = self._matrix if forward else np.linalg.inv(self._matrix) | ||
return affine.dot(coords)[:-1] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pushing this to the backlog checks that need be done before merge. For now I'm just working on a very much needed rebase and implementing all the functionalities. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I've just realized why this |
||
|
||
def map_voxel(self, index, moving=None): | ||
try: | ||
reference = self.reference | ||
except ValueError: | ||
print('Warning: no reference space defined, using moving as reference') | ||
reference = moving | ||
else: | ||
if moving is None: | ||
moving = reference | ||
finally: | ||
if reference is None: | ||
raise ValueError('Reference and moving spaces are both undefined') | ||
|
||
index = np.array(index) | ||
if index.shape[0] == self._matrix.shape[0] - 1: | ||
index = np.append(index, [1]) | ||
|
||
matrix = reference.affine.dot(self._matrix.dot(np.linalg.inv(moving.affine))) | ||
return tuple(matrix.dot(index)[:-1]) | ||
|
||
def to_filename(self, filename): | ||
'''Store the transform in BIDS-Transforms HDF5 file format (.x5). | ||
''' | ||
raise NotImplementedError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realigning 4D series seems like a pretty common use case that you're precluding here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think re-reading this, it's actually just that the reference image has to be 2 or 3D. Still, there's no reason I couldn't pass in a 4D image; it's just all in the same 3D space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I'll work on generalization later down the line.