Skip to content

Commit

Permalink
enh: apply some early comments from @effigies.
Browse files Browse the repository at this point in the history
  • Loading branch information
oesteban committed Sep 23, 2019
1 parent 5b63fc0 commit 77de318
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 56 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,3 @@ Thumbs.db
doc/source/reference
venv/
.buildbot.patch
nibabel/maths/bspline.c
4 changes: 2 additions & 2 deletions nibabel/transform/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Geometric transforms
"""
Geometric transforms.
.. currentmodule:: nibabel.transform
Expand All @@ -15,7 +16,6 @@
transform
"""
from __future__ import absolute_import
from .linear import Affine
from .nonlinear import DeformationFieldTransform

Expand Down
34 changes: 14 additions & 20 deletions nibabel/transform/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
''' Common interface for transforms '''
from __future__ import division, print_function, absolute_import
"""Common interface for transforms."""
import numpy as np
import h5py

from scipy import ndimage as ndi


class ImageSpace(object):
'''Class to represent spaces of gridded data (images)'''
"""Class to represent spaces of gridded data (images)."""

__slots__ = ['_affine', '_shape', '_ndim', '_ndindex', '_coords', '_nvox',
'_inverse']

Expand Down Expand Up @@ -92,9 +92,8 @@ def __eq__(self, other):


class TransformBase(object):
'''
Abstract image class to represent transforms
'''
"""Abstract image class to represent transforms."""

__slots__ = ['_reference']

def __init__(self):
Expand All @@ -117,11 +116,11 @@ def ndim(self):

def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
output_dtype=None):
'''Resample the moving image in reference space
"""
Resample the moving image in reference space.
Parameters
----------
moving : `spatialimage`
The image object containing the data to be resampled in reference
space
Expand All @@ -144,16 +143,14 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
Returns
-------
moved_image : `spatialimage`
The moving imaged after resampling to reference space.
'''

"""
moving_data = np.asanyarray(moving.dataobj)
if output_dtype is None:
output_dtype = moving.header.get_data_dtype()
output_dtype = moving_data.dtype

moving_data = moving.get_data()
moved = ndi.geometric_transform(
moving_data,
mapping=self.map_voxel,
Expand All @@ -171,22 +168,19 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
return moved_image

def map_point(self, coords):
'''Find the coordinates in moving space corresponding to the
input reference coordinates'''
"""Apply y = f(x), where x is the argument `coords`."""
raise NotImplementedError

def map_voxel(self, index, moving=None):
'''Find the voxel indices in the moving image corresponding to the
input reference voxel'''
"""Apply ijk' = f_ijk((i, j, k)), equivalent to the above with indexes."""
raise NotImplementedError

def _to_hdf5(self, x5_root):
'''Serialize this object into the x5 file format'''
"""Serialize this object into the x5 file format."""
raise NotImplementedError

def to_filename(self, filename, fmt='X5'):
'''Store the transform in BIDS-Transforms HDF5 file format (.x5).
'''
"""Store the transform in BIDS-Transforms HDF5 file format (.x5)."""
with h5py.File(filename, 'w') as out_file:
out_file.attrs['Format'] = 'X5'
out_file.attrs['Version'] = np.uint16(1)
Expand Down
38 changes: 19 additions & 19 deletions nibabel/transform/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
''' Linear transforms '''
from __future__ import division, print_function, absolute_import
"""Linear transforms."""
import sys
import numpy as np
from scipy import ndimage as ndi
Expand All @@ -21,15 +20,16 @@


class Affine(TransformBase):
'''Represents linear transforms on image data'''
"""Represents linear transforms on image data."""

__slots__ = ['_matrix']

def __init__(self, matrix=None, reference=None):
'''Initialize a transform
"""
Initialize a linear transform.
Parameters
----------
matrix : ndarray
The inverse coordinate transformation matrix **in physical
coordinates**, mapping coordinates from *reference* space
Expand All @@ -38,15 +38,15 @@ def __init__(self, matrix=None, reference=None):
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]])
'''
"""
super(Affine, self).__init__()
if matrix is None:
matrix = [np.eye(4)]

Expand All @@ -56,7 +56,6 @@ def __init__(self, matrix=None, reference=None):
self._matrix = np.array(matrix)
assert self._matrix.ndim == 3, 'affine matrix should be 3D'
assert self._matrix.shape[-2] == self._matrix.shape[-1], 'affine matrix is not square'
super(Affine, self).__init__()

if reference:
if isinstance(reference, str):
Expand All @@ -69,11 +68,11 @@ def matrix(self):

def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
output_dtype=None):
'''Resample the moving image in reference space
"""
Resample the moving image in reference space.
Parameters
----------
moving : `spatialimage`
The image object containing the data to be resampled in reference
space
Expand All @@ -96,21 +95,19 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
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()

Expand Down Expand Up @@ -163,13 +160,15 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
return moved_image

def map_point(self, coords, index=0, forward=True):
"""Apply y = f(x), where x is the argument `coords`."""
coords = np.array(coords)
if coords.shape[0] == self._matrix[index].shape[0] - 1:
coords = np.append(coords, [1])
affine = self._matrix[index] if forward else np.linalg.inv(self._matrix[index])
return affine.dot(coords)[:-1]

def map_voxel(self, index, nindex=0, moving=None):
"""Apply ijk' = f_ijk((i, j, k)), equivalent to the above with indexes."""
try:
reference = self.reference
except ValueError:
Expand All @@ -191,6 +190,7 @@ def map_voxel(self, index, nindex=0, moving=None):
return tuple(matrix.dot(index)[:-1])

def _to_hdf5(self, x5_root):
"""Serialize this object into the x5 file format."""
xform = x5_root.create_dataset('Transform', data=self._matrix)
xform.attrs['Type'] = 'affine'
x5_root.create_dataset('Inverse', data=np.linalg.inv(self._matrix))
Expand All @@ -199,9 +199,7 @@ def _to_hdf5(self, x5_root):
self.reference._to_hdf5(x5_root.create_group('Reference'))

def to_filename(self, filename, fmt='X5', moving=None):
'''Store the transform in BIDS-Transforms HDF5 file format (.x5).
'''

"""Store the transform in BIDS-Transforms HDF5 file format (.x5)."""
if fmt.lower() in ['itk', 'ants', 'elastix', 'nifty']:
with open(filename, 'w') as f:
f.write('#Insight Transform File V1.0\n')
Expand Down Expand Up @@ -257,8 +255,7 @@ def to_filename(self, filename, fmt='X5', moving=None):


def load(filename, fmt='X5', reference=None):
''' Load a linear transform '''

"""Load a linear transform."""
if fmt.lower() in ['itk', 'ants', 'elastix', 'nifty']:
with open(filename) as itkfile:
itkxfm = itkfile.read().splitlines()
Expand Down Expand Up @@ -293,7 +290,10 @@ def load(filename, fmt='X5', reference=None):


def _fsl_aff_adapt(space):
"""Calculates a matrix to convert from the original RAS image
"""
Adapt FSL affines.
Calculates a matrix to convert from the original RAS image
coordinates to FSL's internal coordinate system of transforms
"""
aff = space.affine
Expand Down
30 changes: 16 additions & 14 deletions nibabel/transform/nonlinear.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
''' Common interface for transforms '''
from __future__ import division, print_function, absolute_import
"""Nonlinear transforms."""
import numpy as np
from scipy import ndimage as ndi
# from gridbspline.maths import cubic
Expand All @@ -19,14 +18,13 @@


class DeformationFieldTransform(TransformBase):
'''Represents a dense field of displacements (one vector per voxel)'''
"""Represents a dense field of displacements (one vector per voxel)."""

__slots__ = ['_field', '_moving', '_moving_space']
__s = (slice(None), )

def __init__(self, field, reference=None):
'''
Create a dense deformation field transform
'''
"""Create a dense deformation field transform."""
super(DeformationFieldTransform, self).__init__()
self._field = field.get_data()

Expand Down Expand Up @@ -103,11 +101,11 @@ def _cache_moving(self, moving):

def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
output_dtype=None):
'''
"""
Resample the `moving` image applying the deformation field.
Examples
--------
>>> import numpy as np
>>> import nibabel as nb
>>> ref = nb.load('t1_weighted.nii.gz')
Expand All @@ -118,16 +116,18 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
>>> new = xfm.resample(ref)
>>> new.to_filename('deffield.nii.gz')
'''
"""
self._cache_moving(moving)
return super(DeformationFieldTransform, self).resample(
moving, order=order, mode=mode, cval=cval, prefilter=prefilter)

def map_voxel(self, index, moving=None):
"""Apply ijk' = f_ijk((i, j, k)), equivalent to the above with indexes."""
return tuple(self._moving[index + self.__s])

def map_coordinates(self, coordinates, order=3, mode='mirror', cval=0.0,
prefilter=True):
"""Apply y = f(x), where x is the argument `coords`."""
coordinates = np.array(coordinates)
# Extract shapes and dimensions, then flatten
ndim = coordinates.shape[-1]
Expand All @@ -154,11 +154,13 @@ def map_coordinates(self, coordinates, order=3, mode='mirror', cval=0.0,


class BSplineFieldTransform(TransformBase):
"""Represent a nonlinear transform parameterized by BSpline basis."""

__slots__ = ['_coeffs', '_knots', '_refknots', '_order', '_moving']
__s = (slice(None), )

def __init__(self, reference, coefficients, order=3):
'''Create a smooth deformation field using B-Spline basis'''
"""Create a smooth deformation field using B-Spline basis."""
super(BSplineFieldTransform, self).__init__()
self._order = order
self.reference = reference
Expand Down Expand Up @@ -216,16 +218,16 @@ def _interp_transform(self, coords):
return self.reference.inverse.dot(np.hstack((coords, 1)))[:3]

def map_voxel(self, index, moving=None):
'''Find the corresponding coordinates for a voxel in reference space'''
"""Apply ijk' = f_ijk((i, j, k)), equivalent to the above with indexes."""
return tuple(self._moving[index + self.__s])

def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
output_dtype=None):
'''
"""
Resample the `moving` image applying the deformation field.
Examples
--------
>>> import numpy as np
>>> import nibabel as nb
>>> ref = nb.load('t1_weighted.nii.gz')
Expand All @@ -238,7 +240,7 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
>>> new = xfm.resample(ref)
>>> new.to_filename('deffield.nii.gz')
'''
"""
self._cache_moving()
return super(BSplineFieldTransform, self).resample(
moving, order=order, mode=mode, cval=cval, prefilter=prefilter)
Expand Down

0 comments on commit 77de318

Please sign in to comment.