Skip to content

Commit 6104b47

Browse files
committed
ENH: Uber-refactor of code style, method names, etc.
1 parent 94445d9 commit 6104b47

File tree

4 files changed

+57
-42
lines changed

4 files changed

+57
-42
lines changed

nitransforms/base.py

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
EQUALITY_TOL = 1e-5
1616

1717

18-
class ImageSpace(object):
18+
class ImageGrid(object):
1919
"""Class to represent spaces of gridded data (images)."""
2020

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

2424
def __init__(self, image):
25+
"""Create a gridded sampling reference."""
2526
self._affine = image.affine
2627
self._shape = image.shape
2728
self._ndim = len(image.shape)
@@ -34,26 +35,32 @@ def __init__(self, image):
3435

3536
@property
3637
def affine(self):
38+
"""Access the indexes-to-RAS affine."""
3739
return self._affine
3840

3941
@property
4042
def inverse(self):
43+
"""Access the RAS-to-indexes affine."""
4144
return self._inverse
4245

4346
@property
4447
def shape(self):
48+
"""Access the space's size of each dimension."""
4549
return self._shape
4650

4751
@property
4852
def ndim(self):
53+
"""Access the number of dimensions."""
4954
return self._ndim
5055

5156
@property
5257
def nvox(self):
58+
"""Access the total number of voxels."""
5359
return self._nvox
5460

5561
@property
5662
def ndindex(self):
63+
"""List the indexes corresponding to the space grid."""
5764
if self._ndindex is None:
5865
indexes = tuple([np.arange(s) for s in self._shape])
5966
self._ndindex = np.array(np.meshgrid(
@@ -62,6 +69,7 @@ def ndindex(self):
6269

6370
@property
6471
def ndcoords(self):
72+
"""List the physical coordinates of this gridded space samples."""
6573
if self._coords is None:
6674
self._coords = np.tensordot(
6775
self._affine,
@@ -70,14 +78,14 @@ def ndcoords(self):
7078
)[:3, ...]
7179
return self._coords
7280

73-
def map_voxels(self, coordinates):
81+
def index(self, coordinates):
82+
"""Get the image array's indexes corresponding to coordinates."""
7483
coordinates = np.array(coordinates)
7584
ncoords = coordinates.shape[-1]
7685
coordinates = np.vstack((coordinates, np.ones((1, ncoords))))
7786

7887
# Back to grid coordinates
79-
return np.tensordot(np.linalg.inv(self._affine),
80-
coordinates, axes=1)[:3, ...]
88+
return np.tensordot(self._inverse, coordinates, axes=1)[:3, ...]
8189

8290
def _to_hdf5(self, group):
8391
group.attrs['Type'] = 'image'
@@ -86,14 +94,9 @@ def _to_hdf5(self, group):
8694
group.create_dataset('shape', data=self.shape)
8795

8896
def __eq__(self, other):
89-
try:
90-
return (
91-
np.allclose(self.affine, other.affine, rtol=EQUALITY_TOL)
92-
and self.shape == other.shape
93-
)
94-
except AttributeError:
95-
pass
96-
return False
97+
"""Overload equals operator."""
98+
return (np.allclose(self.affine, other.affine, rtol=EQUALITY_TOL) and
99+
self.shape == other.shape)
97100

98101

99102
class TransformBase(object):
@@ -102,6 +105,7 @@ class TransformBase(object):
102105
__slots__ = ['_reference']
103106

104107
def __init__(self):
108+
"""Instantiate a transform."""
105109
self._reference = None
106110

107111
def __eq__(self, other):
@@ -110,25 +114,31 @@ def __eq__(self, other):
110114
return False
111115
return np.allclose(self.matrix, other.matrix, rtol=EQUALITY_TOL)
112116

117+
def __call__(self, x):
118+
"""Apply y = f(x)."""
119+
return self.map(x)
120+
113121
@property
114122
def reference(self):
115-
'''A reference space where data will be resampled onto'''
123+
"""Access a reference space where data will be resampled onto."""
116124
if self._reference is None:
117125
raise ValueError('Reference space not set')
118126
return self._reference
119127

120128
@reference.setter
121129
def reference(self, image):
122-
self._reference = ImageSpace(image)
130+
self._reference = ImageGrid(image)
123131

124132
@property
125133
def ndim(self):
134+
"""Access the dimensions of the reference space."""
126135
return self.reference.ndim
127136

128137
def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
129138
output_dtype=None):
130139
"""
131140
Resample the moving image in reference space.
141+
132142
Parameters
133143
----------
134144
moving : `spatialimage`
@@ -150,18 +160,25 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
150160
slightly blurred if *order > 1*, unless the input is prefiltered,
151161
i.e. it is the result of calling the spline filter on the original
152162
input.
163+
153164
Returns
154165
-------
155166
moved_image : `spatialimage`
156167
The moving imaged after resampling to reference space.
168+
157169
"""
158170
moving_data = np.asanyarray(moving.dataobj)
159171
if output_dtype is None:
160172
output_dtype = moving_data.dtype
161173

174+
moving_grid = ImageGrid(moving)
175+
176+
def _map_indexes(ijk):
177+
return moving_grid.inverse.dot(self.map(self.reference.affine.dot(ijk)))
178+
162179
moved = ndi.geometric_transform(
163180
moving_data,
164-
mapping=self.map_voxel,
181+
mapping=_map_indexes,
165182
output_shape=self.reference.shape,
166183
output=output_dtype,
167184
order=order,
@@ -175,16 +192,8 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
175192
moved_image.header.set_data_dtype(output_dtype)
176193
return moved_image
177194

178-
def map_point(self, coords):
179-
"""Apply y = f(x), where x is the argument `coords`."""
180-
raise NotImplementedError
181-
182-
def map_voxel(self, index, moving=None):
183-
"""Apply ijk' = f_ijk((i, j, k)), equivalent to the above with indexes."""
184-
raise NotImplementedError
185-
186-
def _to_hdf5(self, x5_root):
187-
"""Serialize this object into the x5 file format."""
195+
def map(self, x):
196+
"""Apply y = f(x)."""
188197
raise NotImplementedError
189198

190199
def to_filename(self, filename, fmt='X5'):
@@ -196,3 +205,7 @@ def to_filename(self, filename, fmt='X5'):
196205
self._to_hdf5(root)
197206

198207
return filename
208+
209+
def _to_hdf5(self, x5_root):
210+
"""Serialize this object into the x5 file format."""
211+
raise NotImplementedError

nitransforms/linear.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
178178

179179
return moved_image
180180

181-
def map_point(self, coords, index=0, forward=True):
181+
def map(self, coords, index=0, forward=True):
182182
"""
183183
Apply y = f(x), where x is the argument `coords`.
184184
@@ -200,10 +200,10 @@ def map_point(self, coords, index=0, forward=True):
200200
Examples
201201
--------
202202
>>> xfm = Affine([[1, 0, 0, 1], [0, 1, 0, 2], [0, 0, 1, 3], [0, 0, 0, 1]])
203-
>>> xfm.map_point((0,0,0))
203+
>>> xfm((0,0,0))
204204
array([1, 2, 3])
205205
206-
>>> xfm.map_point((0,0,0), forward=False)
206+
>>> xfm((0,0,0), forward=False)
207207
array([-1., -2., -3.])
208208
209209
"""
@@ -213,7 +213,7 @@ def map_point(self, coords, index=0, forward=True):
213213
affine = self._matrix[index] if forward else np.linalg.inv(self._matrix[index])
214214
return affine.dot(coords)[:-1]
215215

216-
def map_voxel(self, index, nindex=0, moving=None):
216+
def _map_voxel(self, index, nindex=0, moving=None):
217217
"""Apply ijk' = f_ijk((i, j, k)), equivalent to the above with indexes."""
218218
try:
219219
reference = self.reference

nitransforms/nonlinear.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from scipy import ndimage as ndi
1212
# from gridbspline.maths import cubic
1313

14-
from .base import ImageSpace, TransformBase
14+
from .base import ImageGrid, TransformBase
1515
from nibabel.funcs import four_to_three
1616

1717
# vbspl = np.vectorize(cubic)
@@ -53,7 +53,7 @@ def __init__(self, field, reference=None):
5353

5454
def _cache_moving(self, moving):
5555
# Check whether input (moving) space is cached
56-
moving_space = ImageSpace(moving)
56+
moving_space = ImageGrid(moving)
5757
if self._moving_space == moving_space:
5858
return
5959

@@ -126,14 +126,13 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
126126
return super(DeformationFieldTransform, self).resample(
127127
moving, order=order, mode=mode, cval=cval, prefilter=prefilter)
128128

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

133-
def map_coordinates(self, coordinates, order=3, mode='mirror', cval=0.0,
134-
prefilter=True):
133+
def map(self, x, order=3, mode='mirror', cval=0.0, prefilter=True):
135134
"""Apply y = f(x), where x is the argument `coords`."""
136-
coordinates = np.array(coordinates)
135+
coordinates = np.array(x)
137136
# Extract shapes and dimensions, then flatten
138137
ndim = coordinates.shape[-1]
139138
output_shape = coordinates.shape[:-1]
@@ -176,7 +175,7 @@ def __init__(self, reference, coefficients, order=3):
176175
'not match the number of dimensions')
177176

178177
self._coeffs = coefficients.get_data()
179-
self._knots = ImageSpace(four_to_three(coefficients)[0])
178+
self._knots = ImageGrid(four_to_three(coefficients)[0])
180179
self._cache_moving()
181180

182181
def _cache_moving(self):
@@ -222,7 +221,7 @@ def _interp_transform(self, coords):
222221
# coords[:3] += weights.dot(np.array(coeffs, dtype=float))
223222
return self.reference.inverse.dot(np.hstack((coords, 1)))[:3]
224223

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

nitransforms/tests/test_base.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
"""Tests of the base module"""
1+
"""Tests of the base module."""
22
import numpy as np
3+
import pytest
34

4-
from ..base import ImageSpace
5+
from ..base import ImageGrid
56

67

7-
def test_ImageSpace(get_data):
8-
im = get_data['RAS']
8+
@pytest.mark.parametrize('image_orientation', ['RAS', 'LAS', 'LPS', 'oblique'])
9+
def test_ImageGrid(get_data, image_orientation):
10+
"""Check the grid object."""
11+
im = get_data[image_orientation]
912

10-
img = ImageSpace(im)
13+
img = ImageGrid(im)
1114
assert np.all(img.affine == np.linalg.inv(img.inverse))
1215

1316
# nd index / coords

0 commit comments

Comments
 (0)