|
a |
|
b/ants/registration/apply_transforms.py |
|
|
1 |
|
|
|
2 |
|
|
|
3 |
__all__ = ['apply_transforms', |
|
|
4 |
'apply_transforms_to_points'] |
|
|
5 |
|
|
|
6 |
import os |
|
|
7 |
|
|
|
8 |
import ants |
|
|
9 |
from ants.internal import get_lib_fn, process_arguments |
|
|
10 |
|
|
|
11 |
|
|
|
12 |
def apply_transforms(fixed, moving, transformlist, |
|
|
13 |
interpolator='linear', imagetype=0, |
|
|
14 |
whichtoinvert=None, compose=None, |
|
|
15 |
defaultvalue=0, singleprecision=False, verbose=False, **kwargs): |
|
|
16 |
""" |
|
|
17 |
Apply a transform list to map an image from one domain to another. |
|
|
18 |
In image registration, one computes mappings between (usually) pairs |
|
|
19 |
of images. These transforms are often a sequence of increasingly |
|
|
20 |
complex maps, e.g. from translation, to rigid, to affine to deformation. |
|
|
21 |
The list of such transforms is passed to this function to interpolate one |
|
|
22 |
image domain into the next image domain, as below. The order matters |
|
|
23 |
strongly and the user is advised to familiarize with the standards |
|
|
24 |
established in examples. |
|
|
25 |
|
|
|
26 |
ANTsR function: `antsApplyTransforms` |
|
|
27 |
|
|
|
28 |
Arguments |
|
|
29 |
--------- |
|
|
30 |
fixed : ANTsImage |
|
|
31 |
fixed image defining domain into which the moving image is transformed. The output will |
|
|
32 |
have the same pixel type as this image. |
|
|
33 |
|
|
|
34 |
moving : AntsImage |
|
|
35 |
moving image to be mapped to fixed space. |
|
|
36 |
|
|
|
37 |
transformlist : list of strings |
|
|
38 |
list of transforms generated by ants.registration where each transform is a filename. |
|
|
39 |
|
|
|
40 |
interpolator : string |
|
|
41 |
Choice of interpolator. Supports partial matching. |
|
|
42 |
linear |
|
|
43 |
nearestNeighbor |
|
|
44 |
multiLabel for label images (deprecated, prefer genericLabel) |
|
|
45 |
gaussian |
|
|
46 |
bSpline |
|
|
47 |
cosineWindowedSinc |
|
|
48 |
welchWindowedSinc |
|
|
49 |
hammingWindowedSinc |
|
|
50 |
lanczosWindowedSinc |
|
|
51 |
genericLabel use this for label images |
|
|
52 |
|
|
|
53 |
imagetype : integer |
|
|
54 |
choose 0/1/2/3 mapping to scalar/vector/tensor/time-series |
|
|
55 |
|
|
|
56 |
whichtoinvert : list of booleans (optional) |
|
|
57 |
Must be same length as transformlist. |
|
|
58 |
whichtoinvert[i] is True if transformlist[i] is a matrix, |
|
|
59 |
and the matrix should be inverted. If transformlist[i] is a |
|
|
60 |
warp field, whichtoinvert[i] must be False. |
|
|
61 |
If the transform list is a matrix followed by a warp field, |
|
|
62 |
whichtoinvert defaults to (True,False). Otherwise it defaults |
|
|
63 |
to [False]*len(transformlist)). |
|
|
64 |
|
|
|
65 |
compose : string (optional) |
|
|
66 |
if it is a string pointing to a valid file location, |
|
|
67 |
this will force the function to return a composite transformation filename. |
|
|
68 |
|
|
|
69 |
defaultvalue : scalar |
|
|
70 |
Default voxel value for mappings outside the image domain. |
|
|
71 |
|
|
|
72 |
singleprecision : boolean |
|
|
73 |
if True, use float32 for computations. This is useful for reducing memory |
|
|
74 |
usage for large datasets, at the cost of precision. |
|
|
75 |
|
|
|
76 |
verbose : boolean |
|
|
77 |
print command and run verbose application of transform. |
|
|
78 |
|
|
|
79 |
kwargs : keyword arguments |
|
|
80 |
extra parameters |
|
|
81 |
|
|
|
82 |
Returns |
|
|
83 |
------- |
|
|
84 |
ANTsImage or string (transformation filename) |
|
|
85 |
|
|
|
86 |
Example |
|
|
87 |
------- |
|
|
88 |
>>> import ants |
|
|
89 |
>>> fixed = ants.image_read( ants.get_ants_data('r16') ) |
|
|
90 |
>>> moving = ants.image_read( ants.get_ants_data('r64') ) |
|
|
91 |
>>> fixed = ants.resample_image(fixed, (64,64), 1, 0) |
|
|
92 |
>>> moving = ants.resample_image(moving, (64,64), 1, 0) |
|
|
93 |
>>> mytx = ants.registration(fixed=fixed , moving=moving , |
|
|
94 |
type_of_transform = 'SyN' ) |
|
|
95 |
>>> mywarpedimage = ants.apply_transforms( fixed=fixed, moving=moving, |
|
|
96 |
transformlist=mytx['fwdtransforms'] ) |
|
|
97 |
""" |
|
|
98 |
|
|
|
99 |
if not isinstance(transformlist, (tuple, list)) and (transformlist is not None): |
|
|
100 |
transformlist = [transformlist] |
|
|
101 |
|
|
|
102 |
accepted_interpolators = {"linear", "nearestNeighbor", "multiLabel", "gaussian", |
|
|
103 |
"bSpline", "cosineWindowedSinc", "welchWindowedSinc", |
|
|
104 |
"hammingWindowedSinc", "lanczosWindowedSinc", "genericLabel"} |
|
|
105 |
|
|
|
106 |
if interpolator not in accepted_interpolators: |
|
|
107 |
raise ValueError('interpolator not supported - see %s' % accepted_interpolators) |
|
|
108 |
|
|
|
109 |
args = [fixed, moving, transformlist, interpolator] |
|
|
110 |
|
|
|
111 |
output_pixel_type = 'float' if singleprecision else 'double' |
|
|
112 |
|
|
|
113 |
if not isinstance(fixed, str): |
|
|
114 |
if ants.is_image(fixed) and ants.is_image(moving): |
|
|
115 |
for tl_path in transformlist: |
|
|
116 |
if not os.path.exists(tl_path): |
|
|
117 |
raise Exception('Transform %s does not exist' % tl_path) |
|
|
118 |
|
|
|
119 |
inpixeltype = fixed.pixeltype |
|
|
120 |
fixed = fixed.clone(output_pixel_type) |
|
|
121 |
moving = moving.clone(output_pixel_type) |
|
|
122 |
warpedmovout = moving.clone(output_pixel_type) |
|
|
123 |
f = fixed |
|
|
124 |
m = moving |
|
|
125 |
if (moving.dimension == 4) and (fixed.dimension == 3) and (imagetype == 0): |
|
|
126 |
raise Exception('Set imagetype 3 to transform time series images.') |
|
|
127 |
|
|
|
128 |
wmo = warpedmovout |
|
|
129 |
mytx = [] |
|
|
130 |
if whichtoinvert is None or (isinstance(whichtoinvert, (tuple,list)) and (sum([w is not None for w in whichtoinvert])==0)): |
|
|
131 |
if (len(transformlist) == 2) and ('.mat' in transformlist[0]) and ('.mat' not in transformlist[1]): |
|
|
132 |
whichtoinvert = (True, False) |
|
|
133 |
else: |
|
|
134 |
whichtoinvert = tuple([False]*len(transformlist)) |
|
|
135 |
|
|
|
136 |
if len(whichtoinvert) != len(transformlist): |
|
|
137 |
raise ValueError('Transform list and inversion list must be the same length') |
|
|
138 |
|
|
|
139 |
for i in range(len(transformlist)): |
|
|
140 |
ismat = False |
|
|
141 |
if '.mat' in transformlist[i]: |
|
|
142 |
ismat = True |
|
|
143 |
if whichtoinvert[i] and (not ismat): |
|
|
144 |
raise ValueError('Cannot invert transform %i (%s) because it is not a matrix' % (i, transformlist[i])) |
|
|
145 |
if whichtoinvert[i]: |
|
|
146 |
mytx = mytx + ['-t', '[%s,1]' % (transformlist[i])] |
|
|
147 |
else: |
|
|
148 |
mytx = mytx + ['-t', transformlist[i]] |
|
|
149 |
|
|
|
150 |
if compose is None: |
|
|
151 |
args = ['-d', fixed.dimension, |
|
|
152 |
'-i', m, |
|
|
153 |
'-o', wmo, |
|
|
154 |
'-r', f, |
|
|
155 |
'-n', interpolator] |
|
|
156 |
args = args + mytx |
|
|
157 |
if compose: |
|
|
158 |
tfn = '%scomptx.nii.gz' % compose if not compose.endswith('.h5') else compose |
|
|
159 |
else: |
|
|
160 |
tfn = 'NA' |
|
|
161 |
if compose is not None: |
|
|
162 |
mycompo = '[%s,1]' % tfn |
|
|
163 |
args = ['-d', fixed.dimension, |
|
|
164 |
'-i', m, |
|
|
165 |
'-o', mycompo, |
|
|
166 |
'-r', f, |
|
|
167 |
'-n', interpolator] |
|
|
168 |
args = args + mytx |
|
|
169 |
|
|
|
170 |
myargs = process_arguments(args) |
|
|
171 |
|
|
|
172 |
myverb = int(verbose) |
|
|
173 |
if verbose: |
|
|
174 |
print(myargs) |
|
|
175 |
|
|
|
176 |
processed_args = myargs + ['-z', str(1), '-v', str(myverb), '--float', str(int(singleprecision)), '-e', str(imagetype), '-f', str(defaultvalue)] |
|
|
177 |
libfn = get_lib_fn('antsApplyTransforms') |
|
|
178 |
libfn(processed_args) |
|
|
179 |
|
|
|
180 |
if compose is None: |
|
|
181 |
return warpedmovout.clone(inpixeltype) |
|
|
182 |
else: |
|
|
183 |
if os.path.exists(tfn): |
|
|
184 |
return tfn |
|
|
185 |
else: |
|
|
186 |
return None |
|
|
187 |
|
|
|
188 |
else: |
|
|
189 |
return 1 |
|
|
190 |
else: |
|
|
191 |
args = args + ['-z', str(1), '--float', str(int(singleprecision)), '-e', imagetype, '-f', defaultvalue] |
|
|
192 |
processed_args = process_arguments(args) |
|
|
193 |
libfn = get_lib_fn('antsApplyTransforms') |
|
|
194 |
libfn(processed_args) |
|
|
195 |
|
|
|
196 |
|
|
|
197 |
|
|
|
198 |
|
|
|
199 |
|
|
|
200 |
|
|
|
201 |
def apply_transforms_to_points( dim, points, transformlist, |
|
|
202 |
whichtoinvert=None, verbose=False ): |
|
|
203 |
""" |
|
|
204 |
Apply a transform list to map a pointset from one domain to |
|
|
205 |
another. In registration, one computes mappings between pairs of |
|
|
206 |
domains. These transforms are often a sequence of increasingly |
|
|
207 |
complex maps, e.g. from translation, to rigid, to affine to |
|
|
208 |
deformation. The list of such transforms is passed to this |
|
|
209 |
function to interpolate one image domain into the next image |
|
|
210 |
domain, as below. The order matters strongly and the user is |
|
|
211 |
advised to familiarize with the standards established in examples. |
|
|
212 |
Importantly, point mapping goes the opposite direction of image |
|
|
213 |
mapping, for both reasons of convention and engineering. |
|
|
214 |
|
|
|
215 |
ANTsR function: `antsApplyTransformsToPoints` |
|
|
216 |
|
|
|
217 |
Arguments |
|
|
218 |
--------- |
|
|
219 |
dim: integer |
|
|
220 |
dimensionality of the transformation. |
|
|
221 |
|
|
|
222 |
points: data frame |
|
|
223 |
moving point set with n-points in rows of at least dim |
|
|
224 |
columns - we maintain extra information in additional |
|
|
225 |
columns. this should be a data frame with columns names x, y, z, t. |
|
|
226 |
|
|
|
227 |
transformlist : list of strings |
|
|
228 |
list of transforms generated by ants.registration where each transform is a filename. |
|
|
229 |
|
|
|
230 |
whichtoinvert : list of booleans (optional) |
|
|
231 |
Must be same length as transformlist. |
|
|
232 |
whichtoinvert[i] is True if transformlist[i] is a matrix, |
|
|
233 |
and the matrix should be inverted. If transformlist[i] is a |
|
|
234 |
warp field, whichtoinvert[i] must be False. |
|
|
235 |
If the transform list is a matrix followed by a warp field, |
|
|
236 |
whichtoinvert defaults to (True,False). Otherwise it defaults |
|
|
237 |
to [False]*len(transformlist)). |
|
|
238 |
|
|
|
239 |
verbose : boolean |
|
|
240 |
|
|
|
241 |
Returns |
|
|
242 |
------- |
|
|
243 |
data frame of transformed points |
|
|
244 |
|
|
|
245 |
Example |
|
|
246 |
------- |
|
|
247 |
>>> import ants |
|
|
248 |
>>> fixed = ants.image_read( ants.get_ants_data('r16') ) |
|
|
249 |
>>> moving = ants.image_read( ants.get_ants_data('r27') ) |
|
|
250 |
>>> reg = ants.registration( fixed, moving, 'Affine' ) |
|
|
251 |
>>> d = {'x': [128, 127], 'y': [101, 111]} |
|
|
252 |
>>> pts = pd.DataFrame(data=d) |
|
|
253 |
>>> ptsw = ants.apply_transforms_to_points( 2, pts, reg['fwdtransforms']) |
|
|
254 |
""" |
|
|
255 |
|
|
|
256 |
if not isinstance(transformlist, (tuple, list)) and (transformlist is not None): |
|
|
257 |
transformlist = [transformlist] |
|
|
258 |
|
|
|
259 |
args = [dim, points, transformlist, whichtoinvert] |
|
|
260 |
|
|
|
261 |
for tl_path in transformlist: |
|
|
262 |
if not os.path.exists(tl_path): |
|
|
263 |
raise Exception('Transform %s does not exist' % tl_path) |
|
|
264 |
|
|
|
265 |
mytx = [] |
|
|
266 |
|
|
|
267 |
if whichtoinvert is None or (isinstance(whichtoinvert, (tuple,list)) and (sum([w is not None for w in whichtoinvert])==0)): |
|
|
268 |
if (len(transformlist) == 2) and ('.mat' in transformlist[0]) and ('.mat' not in transformlist[1]): |
|
|
269 |
whichtoinvert = (True, False) |
|
|
270 |
else: |
|
|
271 |
whichtoinvert = tuple([False]*len(transformlist)) |
|
|
272 |
|
|
|
273 |
if len(whichtoinvert) != len(transformlist): |
|
|
274 |
raise ValueError('Transform list and inversion list must be the same length') |
|
|
275 |
|
|
|
276 |
for i in range(len(transformlist)): |
|
|
277 |
ismat = False |
|
|
278 |
if '.mat' in transformlist[i]: |
|
|
279 |
ismat = True |
|
|
280 |
if whichtoinvert[i] and (not ismat): |
|
|
281 |
raise ValueError('Cannot invert transform %i (%s) because it is not a matrix' % (i, transformlist[i])) |
|
|
282 |
if whichtoinvert[i]: |
|
|
283 |
mytx = mytx + ['-t', '[%s,1]' % (transformlist[i])] |
|
|
284 |
else: |
|
|
285 |
mytx = mytx + ['-t', transformlist[i]] |
|
|
286 |
if dim == 2: |
|
|
287 |
pointsSub = points[['x','y']] |
|
|
288 |
if dim == 3: |
|
|
289 |
pointsSub = points[['x','y','z']] |
|
|
290 |
if dim == 4: |
|
|
291 |
pointsSub = points[['x','y','z','t']] |
|
|
292 |
pointImage = ants.make_image( pointsSub.shape, pointsSub.values.flatten()) |
|
|
293 |
pointsOut = pointImage.clone() |
|
|
294 |
args = ['-d', dim, |
|
|
295 |
'-i', pointImage, |
|
|
296 |
'-o', pointsOut ] |
|
|
297 |
args = args + mytx |
|
|
298 |
myargs = process_arguments(args) |
|
|
299 |
|
|
|
300 |
myverb = int(verbose) |
|
|
301 |
if verbose: |
|
|
302 |
print(myargs) |
|
|
303 |
|
|
|
304 |
processed_args = myargs + [ '-f', str(1), '--precision', str(0)] |
|
|
305 |
libfn = get_lib_fn('antsApplyTransformsToPoints') |
|
|
306 |
libfn(processed_args) |
|
|
307 |
mynp = pointsOut.numpy() |
|
|
308 |
pointsOutDF = points.copy() |
|
|
309 |
pointsOutDF['x'] = mynp[:,0] |
|
|
310 |
if dim >= 2: |
|
|
311 |
pointsOutDF['y'] = mynp[:,1] |
|
|
312 |
if dim >= 3: |
|
|
313 |
pointsOutDF['z'] = mynp[:,2] |
|
|
314 |
if dim >= 4: |
|
|
315 |
pointsOutDF['t'] = mynp[:,3] |
|
|
316 |
return pointsOutDF |