Skip to content

Commit 33afc2c

Browse files
committed
refactoring P2PDistance
1 parent 1635dc3 commit 33afc2c

File tree

3 files changed

+108
-22
lines changed

3 files changed

+108
-22
lines changed

nipype/algorithms/mesh.py

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,47 +23,59 @@
2323
iflogger = logging.getLogger('interface')
2424

2525

26-
class P2PDistanceInputSpec(BaseInterfaceInputSpec):
26+
class ComputeMeshWarpInputSpec(BaseInterfaceInputSpec):
2727
surface1 = File(exists=True, mandatory=True,
28-
desc=("Reference surface (vtk format) to which compute "
29-
"distance."))
28+
desc=('Reference surface (vtk format) to which compute '
29+
'distance.'))
3030
surface2 = File(exists=True, mandatory=True,
31-
desc=("Test surface (vtk format) from which compute "
32-
"distance."))
31+
desc=('Test surface (vtk format) from which compute '
32+
'distance.'))
33+
metric = traits.Enum('euclidean', 'sqeuclidean', usedefault=True,
34+
desc=('norm used to report distance'))
3335
weighting = traits.Enum(
34-
"none", "area", usedefault=True,
35-
desc=('"none": no weighting is performed, "area": vertex distances are'
36-
'weighted by the total area of faces corresponding to the '
37-
'vertex'))
36+
'none', 'area', usedefault=True,
37+
desc=('"none": no weighting is performed, surface": edge distance is '
38+
'weighted by the corresponding surface area'))
39+
out_warp = File('surfwarp.vtk', usedefault=True,
40+
desc='vtk file based on surface1 and warpings mapping it '
41+
'to surface2')
3842
out_file = File('distance.npy', usedefault=True,
3943
desc='numpy file keeping computed distances and weights')
4044

4145

42-
class P2PDistanceOutputSpec(TraitedSpec):
46+
class ComputeMeshWarpOutputSpec(TraitedSpec):
4347
distance = traits.Float(desc="computed distance")
48+
out_warp = File(exists=True, desc=('vtk file with the vertex-wise '
49+
'mapping of surface1 to surface2'))
4450
out_file = File(exists=True,
4551
desc='numpy file keeping computed distances and weights')
4652

4753

48-
class P2PDistance(BaseInterface):
54+
class ComputeMeshWarp(BaseInterface):
4955

50-
"""Calculates a point-to-point (p2p) distance between two corresponding
51-
VTK-readable meshes or contours.
56+
"""
57+
Calculates a the vertex-wise warping to get surface2 from surface1.
58+
It also reports the average distance of vertices, using the norm specified
59+
as input.
60+
61+
.. warning:
62+
63+
A point-to-point correspondence between surfaces is required
5264
53-
A point-to-point correspondence between nodes is required
5465
5566
Example
5667
-------
5768
5869
>>> import nipype.algorithms.mesh as mesh
59-
>>> dist = mesh.P2PDistance()
70+
>>> dist = mesh.ComputeMeshWarp()
6071
>>> dist.inputs.surface1 = 'surf1.vtk'
6172
>>> dist.inputs.surface2 = 'surf2.vtk'
6273
>>> res = dist.run() # doctest: +SKIP
74+
6375
"""
6476

65-
input_spec = P2PDistanceInputSpec
66-
output_spec = P2PDistanceOutputSpec
77+
input_spec = ComputeMeshWarpInputSpec
78+
output_spec = ComputeMeshWarpOutputSpec
6779

6880
def _triangle_area(self, A, B, C):
6981
ABxAC = euclidean(A, B) * euclidean(A, C)
@@ -73,10 +85,11 @@ def _triangle_area(self, A, B, C):
7385
return area
7486

7587
def _run_interface(self, runtime):
88+
from numpy import linalg as nla
7689
try:
77-
from tvtk.api import tvtk
90+
from tvtk.api import tvtk, write_data
7891
except ImportError:
79-
raise ImportError('Interface P2PDistance requires tvtk')
92+
raise ImportError('Interface ComputeMeshWarp requires tvtk')
8093

8194
try:
8295
from enthought.etsconfig.api import ETSConfig
@@ -99,9 +112,13 @@ def _run_interface(self, runtime):
99112
points1 = np.array(vtk1.points)
100113
points2 = np.array(vtk2.points)
101114

102-
diff = np.linalg.norm(points1 - points2, axis=1)
115+
diff = points2 - points1
103116
weights = np.ones(len(diff))
104117

118+
errvector = nla.norm(diff, axis=1)
119+
if self.inputs.metric == 'sqeuclidean':
120+
errvector = errvector ** 2
121+
105122
if (self.inputs.weighting == 'area'):
106123
faces = vtk1.polys.to_array().reshape(-1, 4).astype(int)[:, 1:]
107124

@@ -117,14 +134,40 @@ def _run_interface(self, runtime):
117134
w += self._triangle_area(fp1, fp2, fp3)
118135
weights[i] = w
119136

120-
result = np.vstack([diff, weights])
137+
result = np.vstack([errvector, weights])
121138
np.save(op.abspath(self.inputs.out_file), result.transpose())
122139

123-
self._distance = np.average(diff, weights=weights)
140+
out_mesh = tvtk.PolyData()
141+
out_mesh.points = vtk1.points
142+
out_mesh.polys = vtk1.polys
143+
out_mesh.point_data.warpings = [tuple(d) for d in diff]
144+
145+
write_data(out_mesh, op.abspath(self.inputs.out_warp))
146+
147+
self._distance = np.average(errvector, weights=weights)
124148
return runtime
125149

126150
def _list_outputs(self):
127151
outputs = self._outputs().get()
128152
outputs['out_file'] = op.abspath(self.inputs.out_file)
129153
outputs['distance'] = self._distance
130154
return outputs
155+
156+
157+
class P2PDistance(ComputeMeshWarp):
158+
159+
"""
160+
Calculates a point-to-point (p2p) distance between two corresponding
161+
VTK-readable meshes or contours.
162+
163+
A point-to-point correspondence between nodes is required
164+
165+
.. deprecated:: 1.0-dev
166+
Use :py:class:`ComputeMeshWarp` instead.
167+
"""
168+
169+
def __init__(self, **inputs):
170+
super(P2PDistance, self).__init__(**inputs)
171+
warnings.warn(("This interface has been deprecated since 1.0,"
172+
" please use nipype.algorithms.metrics.Distance"),
173+
DeprecationWarning)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
2+
from nipype.testing import assert_equal
3+
from nipype.algorithms.mesh import ComputeMeshWarp
4+
5+
def test_ComputeMeshWarp_inputs():
6+
input_map = dict(ignore_exception=dict(nohash=True,
7+
usedefault=True,
8+
),
9+
metric=dict(usedefault=True,
10+
),
11+
out_file=dict(usedefault=True,
12+
),
13+
out_warp=dict(usedefault=True,
14+
),
15+
surface1=dict(mandatory=True,
16+
),
17+
surface2=dict(mandatory=True,
18+
),
19+
weighting=dict(usedefault=True,
20+
),
21+
)
22+
inputs = ComputeMeshWarp.input_spec()
23+
24+
for key, metadata in input_map.items():
25+
for metakey, value in metadata.items():
26+
yield assert_equal, getattr(inputs.traits()[key], metakey), value
27+
28+
def test_ComputeMeshWarp_outputs():
29+
output_map = dict(distance=dict(),
30+
out_file=dict(),
31+
out_warp=dict(),
32+
)
33+
outputs = ComputeMeshWarp.output_spec()
34+
35+
for key, metadata in output_map.items():
36+
for metakey, value in metadata.items():
37+
yield assert_equal, getattr(outputs.traits()[key], metakey), value
38+

nipype/algorithms/tests/test_auto_P2PDistance.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ def test_P2PDistance_inputs():
66
input_map = dict(ignore_exception=dict(nohash=True,
77
usedefault=True,
88
),
9+
metric=dict(usedefault=True,
10+
),
911
out_file=dict(usedefault=True,
1012
),
13+
out_warp=dict(usedefault=True,
14+
),
1115
surface1=dict(mandatory=True,
1216
),
1317
surface2=dict(mandatory=True,
@@ -24,6 +28,7 @@ def test_P2PDistance_inputs():
2428
def test_P2PDistance_outputs():
2529
output_map = dict(distance=dict(),
2630
out_file=dict(),
31+
out_warp=dict(),
2732
)
2833
outputs = P2PDistance.output_spec()
2934

0 commit comments

Comments
 (0)