Skip to content

Commit d25308d

Browse files
committed
ENH: Collapse linear and nonlinear transforms chains
Very undertested, but currently there is a test that uses a "collapsed" transform on an ITK's .h5 file with one affine and one nonlinear. BSpline transforms not currently supported. Resolves #89.
1 parent ef5a28f commit d25308d

File tree

4 files changed

+24
-21
lines changed

4 files changed

+24
-21
lines changed

nitransforms/linear.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,17 @@ def __matmul__(self, b):
123123
True
124124
125125
>>> xfm1 = Affine([[1, 0, 0, 4], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
126-
>>> xfm1 @ np.eye(4) == xfm1
126+
>>> xfm1 @ Affine() == xfm1
127127
True
128128
129129
"""
130-
if not isinstance(b, self.__class__):
131-
_b = self.__class__(b)
132-
else:
133-
_b = b
130+
if isinstance(b, self.__class__):
131+
return self.__class__(
132+
b.matrix @ self.matrix,
133+
reference=b.reference,
134+
)
134135

135-
retval = self.__class__(self.matrix.dot(_b.matrix))
136-
if _b.reference:
137-
retval.reference = _b.reference
138-
return retval
136+
return b @ self
139137

140138
@property
141139
def matrix(self):

nitransforms/manip.py

+7-8
Original file line numberDiff line numberDiff line change
@@ -140,17 +140,17 @@ def map(self, x, inverse=False):
140140

141141
return x
142142

143-
def asaffine(self, indices=None):
143+
def collapse(self):
144144
"""
145-
Combine a succession of linear transforms into one.
145+
Combine a succession of transforms into one.
146146
147147
Example
148148
------
149149
>>> chain = TransformChain(transforms=[
150150
... Affine.from_matvec(vec=(2, -10, 3)),
151151
... Affine.from_matvec(vec=(-2, 10, -3)),
152152
... ])
153-
>>> chain.asaffine()
153+
>>> chain.collapse()
154154
array([[1., 0., 0., 0.],
155155
[0., 1., 0., 0.],
156156
[0., 0., 1., 0.],
@@ -160,15 +160,15 @@ def asaffine(self, indices=None):
160160
... Affine.from_matvec(vec=(1, 2, 3)),
161161
... Affine.from_matvec(mat=[[0, 1, 0], [0, 0, 1], [1, 0, 0]]),
162162
... ])
163-
>>> chain.asaffine()
163+
>>> chain.collapse()
164164
array([[0., 1., 0., 2.],
165165
[0., 0., 1., 3.],
166166
[1., 0., 0., 1.],
167167
[0., 0., 0., 1.]])
168168
169169
>>> np.allclose(
170170
... chain.map((4, -2, 1)),
171-
... chain.asaffine().map((4, -2, 1)),
171+
... chain.collapse().map((4, -2, 1)),
172172
... )
173173
True
174174
@@ -178,9 +178,8 @@ def asaffine(self, indices=None):
178178
The indices of the values to extract.
179179
180180
"""
181-
affines = self.transforms if indices is None else np.take(self.transforms, indices)
182-
retval = affines[0]
183-
for xfm in affines[1:]:
181+
retval = self.transforms[-1]
182+
for xfm in reversed(self.transforms[:-1]):
184183
retval = xfm @ retval
185184
return retval
186185

nitransforms/tests/test_linear.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,10 @@ def test_mulmat_operator(testdata_path):
328328
mat2 = from_matvec(np.eye(3), (4, 2, -1))
329329
aff = nitl.Affine(mat1, reference=ref)
330330

331-
composed = aff @ mat2
331+
composed = aff @ nitl.Affine(mat2)
332332
assert composed.reference is None
333-
assert composed == nitl.Affine(mat1.dot(mat2))
333+
assert composed == nitl.Affine(mat2 @ mat1)
334334

335335
composed = nitl.Affine(mat2) @ aff
336336
assert composed.reference == aff.reference
337-
assert composed == nitl.Affine(mat2.dot(mat1), reference=ref)
337+
assert composed == nitl.Affine(mat1 @ mat2, reference=ref)

nitransforms/tests/test_manip.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ def test_itk_h5(tmp_path, testdata_path):
6060
# A certain tolerance is necessary because of resampling at borders
6161
assert (np.abs(diff) > 1e-3).sum() / diff.size < RMSE_TOL
6262

63+
col_moved = xfm.collapse().apply(img_fname, order=0)
64+
col_moved.to_filename("nt_collapse_resampled.nii.gz")
65+
diff = sw_moved.get_fdata() - col_moved.get_fdata()
66+
# A certain tolerance is necessary because of resampling at borders
67+
assert (np.abs(diff) > 1e-3).sum() / diff.size < RMSE_TOL
68+
6369

6470
@pytest.mark.parametrize("ext0", ["lta", "tfm"])
6571
@pytest.mark.parametrize("ext1", ["lta", "tfm"])
@@ -81,7 +87,7 @@ def test_collapse_affines(tmp_path, data_path, ext0, ext1, ext2):
8187
]
8288
)
8389
assert np.allclose(
84-
chain.asaffine().matrix,
90+
chain.collapse().matrix,
8591
Affine.from_filename(
8692
data_path / "regressions" / f"from-fsnative_to-bold_mode-image.{ext2}",
8793
fmt=f"{FMT[ext2]}",

0 commit comments

Comments
 (0)