13
13
14
14
15
15
import numpy as np
16
- from scipy .spatial .distance import euclidean
16
+ from numpy import linalg as nla
17
+ import os .path as op
18
+ from nipype .external import six
17
19
18
20
from .. import logging
19
21
20
22
from ..interfaces .base import (BaseInterface , traits , TraitedSpec , File ,
21
23
BaseInterfaceInputSpec )
24
+ from warnings import warn
22
25
iflogger = logging .getLogger ('interface' )
23
26
24
27
25
- class P2PDistanceInputSpec (BaseInterfaceInputSpec ):
28
+ class ComputeMeshWarpInputSpec (BaseInterfaceInputSpec ):
26
29
surface1 = File (exists = True , mandatory = True ,
27
- desc = (" Reference surface (vtk format) to which compute "
28
- " distance." ))
30
+ desc = (' Reference surface (vtk format) to which compute '
31
+ ' distance.' ))
29
32
surface2 = File (exists = True , mandatory = True ,
30
- desc = ("Test surface (vtk format) from which compute "
31
- "distance." ))
32
- weighting = traits .Enum ("none" , "surface" , usedefault = True ,
33
- desc = ('"none": no weighting is performed, '
34
- '"surface": edge distance is weighted by the '
35
- 'corresponding surface area' ))
33
+ desc = ('Test surface (vtk format) from which compute '
34
+ 'distance.' ))
35
+ metric = traits .Enum ('euclidean' , 'sqeuclidean' , usedefault = True ,
36
+ desc = ('norm used to report distance' ))
37
+ weighting = traits .Enum (
38
+ 'none' , 'area' , usedefault = True ,
39
+ desc = ('"none": no weighting is performed, surface": edge distance is '
40
+ 'weighted by the corresponding surface area' ))
41
+ out_warp = File ('surfwarp.vtk' , usedefault = True ,
42
+ desc = 'vtk file based on surface1 and warpings mapping it '
43
+ 'to surface2' )
44
+ out_file = File ('distance.npy' , usedefault = True ,
45
+ desc = 'numpy file keeping computed distances and weights' )
36
46
37
47
38
- class P2PDistanceOutputSpec (TraitedSpec ):
48
+ class ComputeMeshWarpOutputSpec (TraitedSpec ):
39
49
distance = traits .Float (desc = "computed distance" )
50
+ out_warp = File (exists = True , desc = ('vtk file with the vertex-wise '
51
+ 'mapping of surface1 to surface2' ))
52
+ out_file = File (exists = True ,
53
+ desc = 'numpy file keeping computed distances and weights' )
40
54
41
55
42
- class P2PDistance (BaseInterface ):
56
+ class ComputeMeshWarp (BaseInterface ):
43
57
44
- """Calculates a point-to-point (p2p) distance between two corresponding
45
- VTK-readable meshes or contours.
58
+ """
59
+ Calculates a the vertex-wise warping to get surface2 from surface1.
60
+ It also reports the average distance of vertices, using the norm specified
61
+ as input.
62
+
63
+ .. warning:
64
+
65
+ A point-to-point correspondence between surfaces is required
46
66
47
- A point-to-point correspondence between nodes is required
48
67
49
68
Example
50
69
-------
51
70
52
- >>> import nipype.algorithms.mesh as mesh
53
- >>> dist = mesh.P2PDistance ()
71
+ >>> import nipype.algorithms.mesh as m
72
+ >>> dist = m.ComputeMeshWarp ()
54
73
>>> dist.inputs.surface1 = 'surf1.vtk'
55
74
>>> dist.inputs.surface2 = 'surf2.vtk'
56
75
>>> res = dist.run() # doctest: +SKIP
76
+
57
77
"""
58
78
59
- input_spec = P2PDistanceInputSpec
60
- output_spec = P2PDistanceOutputSpec
79
+ input_spec = ComputeMeshWarpInputSpec
80
+ output_spec = ComputeMeshWarpOutputSpec
61
81
62
82
def _triangle_area (self , A , B , C ):
63
- ABxAC = euclidean (A , B ) * euclidean (A , C )
64
- prod = np .dot (np .array (B ) - np .array (A ), np .array (C ) - np .array (A ))
83
+ A = np .array (A )
84
+ B = np .array (B )
85
+ C = np .array (C )
86
+ ABxAC = nla .norm (A - B ) * nla .norm (A - C )
87
+ prod = np .dot (B - A , C - A )
65
88
angle = np .arccos (prod / ABxAC )
66
89
area = 0.5 * ABxAC * np .sin (angle )
67
90
return area
@@ -70,14 +93,17 @@ def _run_interface(self, runtime):
70
93
try :
71
94
from tvtk .api import tvtk
72
95
except ImportError :
73
- raise ImportError ('Interface P2PDistance requires tvtk' )
96
+ raise ImportError ('Interface ComputeMeshWarp requires tvtk' )
74
97
75
98
try :
76
99
from enthought .etsconfig .api import ETSConfig
77
100
ETSConfig .toolkit = 'null'
78
101
except ImportError :
79
102
iflogger .warn (('ETS toolkit could not be imported' ))
80
103
pass
104
+ except ValueError :
105
+ iflogger .warn (('ETS toolkit is already set' ))
106
+ pass
81
107
82
108
r1 = tvtk .PolyDataReader (file_name = self .inputs .surface1 )
83
109
r2 = tvtk .PolyDataReader (file_name = self .inputs .surface2 )
@@ -86,32 +112,213 @@ def _run_interface(self, runtime):
86
112
r1 .update ()
87
113
r2 .update ()
88
114
assert (len (vtk1 .points ) == len (vtk2 .points ))
89
- d = 0.0
90
- totalWeight = 0.0
91
115
92
- points = vtk1 .points
93
- faces = vtk1 .polys .to_array ().reshape (- 1 , 4 ).astype (int )[:, 1 :]
116
+ points1 = np .array (vtk1 .points )
117
+ points2 = np .array (vtk2 .points )
118
+
119
+ diff = points2 - points1
120
+ weights = np .ones (len (diff ))
121
+
122
+ try :
123
+ errvector = nla .norm (diff , axis = 1 )
124
+ except TypeError : # numpy < 1.9
125
+ errvector = np .apply_along_axis (nla .norm , 1 , diff )
126
+ pass
127
+
128
+ if self .inputs .metric == 'sqeuclidean' :
129
+ errvector = errvector ** 2
130
+
131
+ if (self .inputs .weighting == 'area' ):
132
+ faces = vtk1 .polys .to_array ().reshape (- 1 , 4 ).astype (int )[:, 1 :]
94
133
95
- for p1 , p2 in zip (points , vtk2 .points ):
96
- weight = 1.0
97
- if (self .inputs .weighting == 'surface' ):
134
+ for i , p1 in enumerate (points2 ):
98
135
# compute surfaces, set in weight
99
- weight = 0.0
100
- point_faces = faces [(faces [:, :] == 0 ).any (axis = 1 )]
136
+ w = 0.0
137
+ point_faces = faces [(faces [:, :] == i ).any (axis = 1 )]
101
138
102
139
for idset in point_faces :
103
- p1 = points [int (idset [0 ])]
104
- p2 = points [int (idset [1 ])]
105
- p3 = points [int (idset [2 ])]
106
- weight = weight + self ._triangle_area (p1 , p2 , p3 )
140
+ fp1 = points1 [int (idset [0 ])]
141
+ fp2 = points1 [int (idset [1 ])]
142
+ fp3 = points1 [int (idset [2 ])]
143
+ w += self ._triangle_area (fp1 , fp2 , fp3 )
144
+ weights [i ] = w
107
145
108
- d += weight * euclidean ( p1 , p2 )
109
- totalWeight = totalWeight + weight
146
+ result = np . vstack ([ errvector , weights ] )
147
+ np . save ( op . abspath ( self . inputs . out_file ), result . transpose ())
110
148
111
- self ._distance = d / totalWeight
149
+ out_mesh = tvtk .PolyData ()
150
+ out_mesh .points = vtk1 .points
151
+ out_mesh .polys = vtk1 .polys
152
+ out_mesh .point_data .vectors = diff
153
+ out_mesh .point_data .vectors .name = 'warpings'
154
+ writer = tvtk .PolyDataWriter (
155
+ file_name = op .abspath (self .inputs .out_warp ))
156
+ writer .set_input_data (out_mesh )
157
+ writer .write ()
158
+
159
+ self ._distance = np .average (errvector , weights = weights )
112
160
return runtime
113
161
114
162
def _list_outputs (self ):
115
163
outputs = self ._outputs ().get ()
164
+ outputs ['out_file' ] = op .abspath (self .inputs .out_file )
165
+ outputs ['out_warp' ] = op .abspath (self .inputs .out_warp )
116
166
outputs ['distance' ] = self ._distance
117
167
return outputs
168
+
169
+
170
+ class MeshWarpMathsInputSpec (BaseInterfaceInputSpec ):
171
+ in_surf = File (exists = True , mandatory = True ,
172
+ desc = ('Input surface in vtk format, with associated warp '
173
+ 'field as point data (ie. from ComputeMeshWarp' ))
174
+ float_trait = traits .Either (traits .Float (1.0 ), traits .Tuple (
175
+ traits .Float (1.0 ), traits .Float (1.0 ), traits .Float (1.0 )))
176
+
177
+ operator = traits .Either (
178
+ float_trait , File (exists = True ), default = 1.0 , mandatory = True ,
179
+ desc = ('image, float or tuple of floats to act as operator' ))
180
+
181
+ operation = traits .Enum ('sum' , 'sub' , 'mul' , 'div' , usedefault = True ,
182
+ desc = ('operation to be performed' ))
183
+
184
+ out_warp = File ('warp_maths.vtk' , usedefault = True ,
185
+ desc = 'vtk file based on in_surf and warpings mapping it '
186
+ 'to out_file' )
187
+ out_file = File ('warped_surf.vtk' , usedefault = True ,
188
+ desc = 'vtk with surface warped' )
189
+
190
+
191
+ class MeshWarpMathsOutputSpec (TraitedSpec ):
192
+ out_warp = File (exists = True , desc = ('vtk file with the vertex-wise '
193
+ 'mapping of surface1 to surface2' ))
194
+ out_file = File (exists = True ,
195
+ desc = 'vtk with surface warped' )
196
+
197
+
198
+ class MeshWarpMaths (BaseInterface ):
199
+
200
+ """
201
+ Performs the most basic mathematical operations on the warping field
202
+ defined at each vertex of the input surface. A surface with scalar
203
+ or vector data can be used as operator for non-uniform operations.
204
+
205
+ .. warning:
206
+
207
+ A point-to-point correspondence between surfaces is required
208
+
209
+
210
+ Example
211
+ -------
212
+
213
+ >>> import nipype.algorithms.mesh as m
214
+ >>> mmath = m.MeshWarpMaths()
215
+ >>> mmath.inputs.in_surf = 'surf1.vtk'
216
+ >>> mmath.inputs.operator = 'surf2.vtk'
217
+ >>> mmath.inputs.operation = 'mul'
218
+ >>> res = mmath.run() # doctest: +SKIP
219
+
220
+ """
221
+
222
+ input_spec = MeshWarpMathsInputSpec
223
+ output_spec = MeshWarpMathsOutputSpec
224
+
225
+ def _run_interface (self , runtime ):
226
+ try :
227
+ from tvtk .api import tvtk
228
+ except ImportError :
229
+ raise ImportError ('Interface ComputeMeshWarp requires tvtk' )
230
+
231
+ try :
232
+ from enthought .etsconfig .api import ETSConfig
233
+ ETSConfig .toolkit = 'null'
234
+ except ImportError :
235
+ iflogger .warn (('ETS toolkit could not be imported' ))
236
+ pass
237
+ except ValueError :
238
+ iflogger .warn (('ETS toolkit is already set' ))
239
+ pass
240
+
241
+ r1 = tvtk .PolyDataReader (file_name = self .inputs .in_surf )
242
+ vtk1 = r1 .output
243
+ r1 .update ()
244
+ points1 = np .array (vtk1 .points )
245
+
246
+ if vtk1 .point_data .vectors is None :
247
+ raise RuntimeError (('No warping field was found in in_surf' ))
248
+
249
+ operator = self .inputs .operator
250
+ opfield = np .ones_like (points1 )
251
+
252
+ if isinstance (operator , six .string_types ):
253
+ r2 = tvtk .PolyDataReader (file_name = self .inputs .surface2 )
254
+ vtk2 = r2 .output
255
+ r2 .update ()
256
+ assert (len (points1 ) == len (vtk2 .points ))
257
+
258
+ opfield = vtk2 .point_data .vectors
259
+
260
+ if opfield is None :
261
+ opfield = vtk2 .point_data .scalars
262
+
263
+ if opfield is None :
264
+ raise RuntimeError (
265
+ ('No operator values found in operator file' ))
266
+
267
+ opfield = np .array (opfield )
268
+
269
+ if opfield .shape [1 ] < points1 .shape [1 ]:
270
+ opfield = np .array ([opfield .tolist ()] * points1 .shape [1 ]).T
271
+ else :
272
+ operator = np .atleast_1d (operator )
273
+ opfield *= operator
274
+
275
+ warping = np .array (vtk1 .point_data .vectors )
276
+
277
+ if self .inputs .operation == 'sum' :
278
+ warping += opfield
279
+ elif self .inputs .operation == 'sub' :
280
+ warping -= opfield
281
+ elif self .inputs .operation == 'mul' :
282
+ warping *= opfield
283
+ elif self .inputs .operation == 'div' :
284
+ warping /= opfield
285
+
286
+ vtk1 .point_data .vectors = warping
287
+ writer = tvtk .PolyDataWriter (
288
+ file_name = op .abspath (self .inputs .out_warp ))
289
+ writer .set_input_data (vtk1 )
290
+ writer .write ()
291
+
292
+ vtk1 .point_data .vectors = None
293
+ vtk1 .points = points1 + warping
294
+ writer = tvtk .PolyDataWriter (
295
+ file_name = op .abspath (self .inputs .out_file ))
296
+ writer .set_input_data (vtk1 )
297
+ writer .write ()
298
+
299
+ return runtime
300
+
301
+ def _list_outputs (self ):
302
+ outputs = self ._outputs ().get ()
303
+ outputs ['out_file' ] = op .abspath (self .inputs .out_file )
304
+ outputs ['out_warp' ] = op .abspath (self .inputs .out_warp )
305
+ return outputs
306
+
307
+
308
+ class P2PDistance (ComputeMeshWarp ):
309
+
310
+ """
311
+ Calculates a point-to-point (p2p) distance between two corresponding
312
+ VTK-readable meshes or contours.
313
+
314
+ A point-to-point correspondence between nodes is required
315
+
316
+ .. deprecated:: 1.0-dev
317
+ Use :py:class:`ComputeMeshWarp` instead.
318
+ """
319
+
320
+ def __init__ (self , ** inputs ):
321
+ super (P2PDistance , self ).__init__ (** inputs )
322
+ warn (('This interface has been deprecated since 1.0, please use '
323
+ 'ComputeMeshWarp' ),
324
+ DeprecationWarning )
0 commit comments