Skip to content

Commit 0bfcb72

Browse files
ndem0mtezzele
authored andcommitted
Add IDW deformation technique
- add the IDW class to handle the deformation - add the IDWParameters class - add test about parameters and deformation - some minor changes in rbf deformation
1 parent 32fc9c2 commit 0bfcb72

File tree

11 files changed

+516
-18
lines changed

11 files changed

+516
-18
lines changed

docs/source/code.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ Code Documentation
2020
stephandler
2121
utils
2222
gui
23-
23+
idw
24+
params_idw

docs/source/idw.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Inverse Distance Weighting
2+
==========================
3+
4+
.. currentmodule:: pygem.idw
5+
6+
.. automodule:: pygem.idw
7+
8+
.. autosummary::
9+
:toctree: _summaries
10+
:nosignatures:
11+
12+
IDW.perform
13+
14+
.. autoclass:: IDW
15+
:members:
16+
:private-members:
17+
:undoc-members:
18+
:show-inheritance:
19+
:noindex:
20+

docs/source/params_idw.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
IDWParameters
2+
=================
3+
4+
.. currentmodule:: pygem.params_idw
5+
6+
.. automodule:: pygem.params_idw
7+
8+
.. autosummary::
9+
:toctree: _summaries
10+
:nosignatures:
11+
12+
IDWParameters.__str__
13+
IDWParameters.read_parameters
14+
IDWParameters.write_parameters
15+
16+
.. autoclass:: IDWParameters
17+
:members:
18+
:private-members:
19+
:undoc-members:
20+
:show-inheritance:
21+
:noindex:

pygem/__init__.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1+
"""
2+
"""
13
__all__ = [
24
'affine', 'filehandler', 'freeform', 'radial', 'openfhandler', 'params',
35
'stlhandler', 'unvhandler', 'vtkhandler', 'nurbshandler', 'stephandler',
4-
'igeshandler', 'utils', 'gui', 'khandler'
6+
'igeshandler', 'utils', 'gui', 'khandler', 'idw', 'params_idw'
57
]
68

79
from . import affine
810
from . import filehandler
911
from . import freeform
10-
from . import gui
11-
from . import igeshandler
12-
from . import khandler
13-
from . import nurbshandler
12+
from . import radial
1413
from . import openfhandler
1514
from . import params
16-
from . import radial
17-
from . import stephandler
1815
from . import stlhandler
1916
from . import unvhandler
20-
from . import utils
2117
from . import vtkhandler
18+
from . import nurbshandler
19+
from . import stephandler
20+
from . import igeshandler
21+
from . import utils
22+
from . import gui
23+
from . import khandler
24+
from . import idw
25+
from . import params_idw

pygem/idw.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""
2+
Module focused on the Inverse Distance Weighting interpolation technique.
3+
The IDW algorithm is an average moving interpolation that is usually applied to
4+
highly variable data. The main idea of this interpolation strategy lies in
5+
fact that it is not desirable to honour local high/low values but rather to look
6+
at a moving average of nearby data points and estimate the local trends.
7+
The node value is calculated by averaging the weighted sum of all the points.
8+
Data points that lie progressively farther from the node inuence much less the
9+
computed value than those lying closer to the node.
10+
11+
:Theoretical Insight:
12+
13+
This implementation is based on the simplest form of inverse distance
14+
weighting interpolation, proposed by D. Shepard, A two-dimensional
15+
interpolation function for irregularly-spaced data, Proceedings of the 23 rd
16+
ACM National Conference.
17+
18+
The interpolation value :math:`u` of a given point :math:`\\mathrm{x}`
19+
from a set of samples :math:`u_k = u(\\mathrm{x}_k)`, with
20+
:math:`k = 1,2,\dotsc,\\mathcal{N}`, is given by:
21+
22+
.. math::
23+
u(\\mathrm{x}) = \\displaystyle\\sum_{k=1}^\\mathcal{N}
24+
\\frac{w(\\mathrm{x},\\mathrm{x}_k)}
25+
{\\displaystyle\\sum_{j=1}^\\mathcal{N} w(\\mathrm{x},\\mathrm{x}_j)}
26+
u_k
27+
28+
29+
where, in general, :math:`w(\\mathrm{x}, \\mathrm{x}_i)` represents the
30+
weighting function:
31+
32+
.. math::
33+
w(\\mathrm{x}, \\mathrm{x}_i) = \\| \\mathrm{x} - \\mathrm{x}_i \\|^{-p}
34+
35+
being :math:`\\| \\mathrm{x} - \\mathrm{x}_i \\|^{-p} \\ge 0` is the
36+
Euclidean distance between :math:`\\mathrm{x}` and data point
37+
:math:`\\mathrm{x}_i` and :math:`p` is a power parameter, typically equal to
38+
2.
39+
40+
"""
41+
import numpy as np
42+
43+
from scipy.spatial.distance import cdist
44+
45+
46+
class IDW(object):
47+
"""
48+
Class that handles the IDW technique.
49+
50+
:param idw_parameters: the parameters of the IDW
51+
:type idw_parameters: :class:`IDWParameters`
52+
:param numpy.ndarray original_mesh_points: coordinates of the original
53+
points of the mesh.
54+
55+
:cvar parameters: the parameters of the IDW.
56+
:vartype parameters: :class:`~pygem.params_idw.IDWParameters`
57+
:cvar numpy.ndarray original_mesh_points: coordinates of the original
58+
points of the mesh.
59+
:cvar numpy.ndarray modified_mesh_points: coordinates of the deformed
60+
points of the mesh.
61+
62+
:Example:
63+
64+
>>> from pygem.idw import IDW
65+
>>> from pygem.params_idw import IDWParameters
66+
>>> import numpy as np
67+
>>> params = IDWParameters()
68+
>>> params.read_parameters('tests/test_datasets/parameters_idw_cube.prm')
69+
>>> nx, ny, nz = (20, 20, 20)
70+
>>> mesh = np.zeros((nx * ny * nz, 3))
71+
>>> xv = np.linspace(0, 1, nx)
72+
>>> yv = np.linspace(0, 1, ny)
73+
>>> zv = np.linspace(0, 1, nz)
74+
>>> z, y, x = np.meshgrid(zv, yv, xv)
75+
>>> mesh = np.array([x.ravel(), y.ravel(), z.ravel()])
76+
>>> original_mesh_points = mesh.T
77+
>>> idw = IDW(rbf_parameters, original_mesh_points)
78+
>>> idw.perform()
79+
>>> new_mesh_points = idw.modified_mesh_points
80+
"""
81+
82+
def __init__(self, idw_parameters, original_mesh_points):
83+
self.parameters = idw_parameters
84+
self.original_mesh_points = original_mesh_points
85+
self.modified_mesh_points = None
86+
87+
def perform(self):
88+
"""
89+
This method performs the deformation of the mesh points. After the
90+
execution it sets `self.modified_mesh_points`.
91+
"""
92+
93+
def distance(u, v):
94+
return np.linalg.norm(u - v, self.parameters.power)
95+
96+
# Compute displacement of the control points
97+
displ = (
98+
self.parameters.deformed_control_points -
99+
self.parameters.original_control_points
100+
)
101+
102+
# Compute the distance between the mesh points and the control points
103+
dist = cdist(
104+
self.original_mesh_points,
105+
self.parameters.original_control_points,
106+
distance
107+
)
108+
109+
# Weights are set as the reciprocal of the distance if the distance is
110+
# not zero, otherwise 1.0 where distance is zero.
111+
weights = np.zeros(dist.shape)
112+
for i, d in enumerate(dist):
113+
weights[i] = 1. / d if d.all() else np.where(d == 0.0, 1.0, 0.0)
114+
115+
116+
offset = np.array([
117+
np.sum(displ * wi[:, np.newaxis] / np.sum(wi), axis=0)
118+
for wi in weights
119+
])
120+
121+
self.modified_mesh_points = self.original_mesh_points + offset

pygem/params_idw.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import numpy as np
2+
import os
3+
try:
4+
import configparser as configparser
5+
except ImportError:
6+
import ConfigParser as configparser
7+
8+
class IDWParameters(object):
9+
"""
10+
Class that handles the Inverse Distance Weighting parameters in terms of
11+
control points.
12+
13+
:cvar int p: the power parameter. The default value is 2.
14+
:cvar numpy.ndarray original_control_points: it is an
15+
`n_control_points`-by-3 array with the coordinates of the original
16+
interpolation control points before the deformation. The default is the
17+
vertices of the unit cube.
18+
:cvar numpy.ndarray deformed_control_points: it is an
19+
`n_control_points`-by-3 array with the coordinates of the interpolation
20+
control points after the deformation. The default is the vertices of
21+
the unit cube.
22+
"""
23+
def __init__(self):
24+
self.power = 2
25+
self.original_control_points = np.array([
26+
[0., 0., 0.],
27+
[0., 0., 1.],
28+
[0., 1., 0.],
29+
[1., 0., 0.],
30+
[0., 1., 1.],
31+
[1., 0., 1.],
32+
[1., 1., 0.],
33+
[1., 1., 1.]
34+
])
35+
self.deformed_control_points = np.array([
36+
[0., 0., 0.],
37+
[0., 0., 1.],
38+
[0., 1., 0.],
39+
[1., 0., 0.],
40+
[0., 1., 1.],
41+
[1., 0., 1.],
42+
[1., 1., 0.],
43+
[1., 1., 1.]
44+
])
45+
46+
47+
def read_parameters(self, filename):
48+
"""
49+
Reads in the parameters file and fill the self structure.
50+
51+
:param string filename: parameters file to be read in.
52+
"""
53+
if not isinstance(filename, str):
54+
raise TypeError('filename must be a string')
55+
56+
if not os.path.isfile(filename):
57+
raise IOError('filename does not exist')
58+
59+
config = configparser.RawConfigParser()
60+
config.read(filename)
61+
62+
self.power = config.getint('Inverse Distance Weighting', 'power')
63+
64+
ctrl_points = config.get('Control points', 'original control points')
65+
self.original_control_points = np.array(
66+
[line.split() for line in ctrl_points.split('\n')],
67+
dtype=float
68+
)
69+
70+
defo_points = config.get('Control points', 'deformed control points')
71+
self.deformed_control_points = np.array(
72+
[line.split() for line in defo_points.split('\n')],
73+
dtype=float
74+
)
75+
76+
77+
def write_parameters(self, filename):
78+
"""
79+
This method writes a parameters file (.prm) called `filename` and fills
80+
it with all the parameters class members.
81+
82+
:param string filename: parameters file to be written out.
83+
"""
84+
if not isinstance(filename, str):
85+
raise TypeError("filename must be a string")
86+
87+
output_string = ""
88+
output_string += "\n[Inverse Distance Weighting]\n"
89+
output_string += "# This section describes the settings of idw.\n\n"
90+
output_string += "# the power parameter\n"
91+
output_string += "power = {}\n".format(self.power)
92+
93+
output_string += "\n\n[Control points]\n"
94+
output_string += "# This section describes the IDW control points.\n\n"
95+
output_string += "# original control points collects the coordinates\n"
96+
output_string += "# of the interpolation control points before the\n"
97+
output_string += "# deformation.\n"
98+
99+
output_string += "original control points: "
100+
output_string += (
101+
' '.join(map(str, self.original_control_points[0])) + "\n"
102+
)
103+
for points in self.original_control_points[1:]:
104+
output_string += 25 * ' ' + ' '.join(map(str, points)) + "\n"
105+
output_string += "\n"
106+
output_string += "# deformed control points collects the coordinates\n"
107+
output_string += "# of the interpolation control points after the\n"
108+
output_string += "# deformation.\n"
109+
output_string += "deformed control points: "
110+
output_string += (
111+
' '.join(map(str, self.original_control_points[0])) + "\n"
112+
)
113+
for points in self.deformed_control_points[1:]:
114+
output_string += 25 * ' ' + ' '.join(map(str, points)) + "\n"
115+
116+
with open(filename, 'w') as f:
117+
f.write(output_string)
118+
119+
120+
def __str__(self):
121+
"""
122+
This method prints all the IDW parameters on the screen. Its purpose is
123+
for debugging.
124+
"""
125+
string = ''
126+
string += 'p = {}\n'.format(self.power)
127+
string += '\noriginal_control_points =\n'
128+
string += '{}\n'.format(self.original_control_points)
129+
string += '\ndeformed_control_points =\n'
130+
string += '{}\n'.format(self.deformed_control_points)
131+
return string

pygem/radial.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
"""
4848
import numpy as np
4949

50+
from scipy.spatial.distance import cdist
51+
5052

5153
class RBF(object):
5254
"""
@@ -223,11 +225,9 @@ def _distance_matrix(self, X1, X2):
223225
:return: matrix: the matrix D.
224226
:rtype: numpy.ndarray
225227
"""
226-
m, n = X1.shape[0], X2.shape[0]
227-
matrix = np.zeros(shape=(m, n))
228-
for i in range(0, m):
229-
for j in range(0, n):
230-
matrix[i][j] = self.basis(X1[i] - X2[j], self.parameters.radius)
228+
matrix = cdist(
229+
X1, X2, lambda x, y: self.basis(x-y, self.parameters.radius)
230+
)
231231
return matrix
232232

233233
def _get_weights(self, X, Y):
@@ -246,13 +246,12 @@ def _get_weights(self, X, Y):
246246
"""
247247
n_points = X.shape[0]
248248
dim = X.shape[1]
249-
identity = np.ones(n_points).reshape(n_points, 1)
249+
identity = np.ones((n_points, 1))
250250
dist = self._distance_matrix(X, X)
251251
H = np.bmat([[dist, identity, X], [identity.T, np.zeros((1, 1)), np.zeros((1, dim))], \
252252
[X.T, np.zeros((dim, 1)), np.zeros((dim, dim))]])
253253
rhs = np.bmat([[Y], [np.zeros((1, dim))], [np.zeros((dim, dim))]])
254-
inv_matrix_h = np.linalg.inv(H)
255-
weights = np.dot(inv_matrix_h, rhs)
254+
weights = np.linalg.solve(H, rhs)
256255
return weights
257256

258257
def perform(self):
@@ -264,6 +263,6 @@ def perform(self):
264263
dist = self._distance_matrix(
265264
self.original_mesh_points, self.parameters.original_control_points
266265
)
267-
identity = np.ones(n_points).reshape(n_points, 1)
266+
identity = np.ones((n_points, 1))
268267
H = np.bmat([[dist, identity, self.original_mesh_points]])
269268
self.modified_mesh_points = np.asarray(np.dot(H, self.weights))

0 commit comments

Comments
 (0)