Skip to content

Commit 453dcc3

Browse files
reformat code, fixed sphinx, add test, simplied interpolation function with comments
1 parent 43bd627 commit 453dcc3

File tree

14 files changed

+16354
-94
lines changed

14 files changed

+16354
-94
lines changed

docs/source/_tutorials/tutorial-8-cffd.html

Lines changed: 15958 additions & 0 deletions
Large diffs are not rendered by default.

docs/source/bffd.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Barycenter Free-form Deformation
2+
=====================
3+
4+
.. currentmodule:: pygem.bffd
5+
6+
.. automodule:: pygem.bffd
7+
8+
.. autosummary::
9+
:toctree: _summaries
10+
:nosignatures:
11+
12+
.. autoclass:: pygem.bffd.BFFD
13+
:members:
14+
:special-members: __call__
15+
:private-members:
16+
:exclude-members: _abd_impl
17+
:show-inheritance:
18+
:noindex:

docs/source/cffd.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Constrained Free-form Deformation
2+
=====================
3+
4+
.. currentmodule:: pygem.cffd
5+
6+
.. automodule:: pygem.cffd
7+
8+
.. autosummary::
9+
:toctree: _summaries
10+
:nosignatures:
11+
12+
.. autoclass:: pygem.cffd.CFFD
13+
:members:
14+
:special-members: __call__
15+
:private-members:
16+
:exclude-members: _abd_impl
17+
:show-inheritance:
18+
:noindex:

docs/source/code.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Code Documentation
55
:maxdepth: 2
66

77
ffd
8+
cffd
9+
bffd
10+
vffd
811
rbf
912
rbf_factory
1013
idw

docs/source/tutorials.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ Tutorials
44
We made some tutorial examples:
55

66
- `Tutorial 1 <tutorial-1-ffd.html>`_ shows how to apply the free-form deformation to mesh nodes.
7-
- `Turorial 2 <tutorial-2-iges.html>`_ shows how to deal with iges (CAD) files and FFD.
8-
- `Turorial 3 <tutorial-3-rbf.html>`_ shows how to apply the radial basis function to mesh nodes.
9-
- `Turorial 4 <tutorial-4-idw.html>`_ shows how to perform a deformation using the inverse distance weighting method to mesh nodes.
10-
- `Turorial 5 <tutorial-5-file.html>`_ shows how to perform a deformation to an object stored in a file.
11-
- `Turorial 6 <tutorial-6-ffd-rbf.html>`_ shows deforming a computational mesh.
12-
7+
- `Tutorial 2 <tutorial-2-iges.html>`_ shows how to deal with iges (CAD) files and FFD.
8+
- `Tutorial 3 <tutorial-3-rbf.html>`_ shows how to apply the radial basis function to mesh nodes.
9+
- `Tutorial 4 <tutorial-4-idw.html>`_ shows how to perform a deformation using the inverse distance weighting method to mesh nodes.
10+
- `Tutorial 5 <tutorial-5-file.html>`_ shows how to perform a deformation to an object stored in a file.
11+
- `Tutorial 6 <tutorial-6-ffd-rbf.html>`_ shows deforming a computational mesh.
12+
- `Tutorial 8 <tutorial-8-cffd.html>`_ shows deforming a computational mesh.

docs/source/vffd.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Volume Free-form Deformation
2+
=====================
3+
4+
.. currentmodule:: pygem.vffd
5+
6+
.. automodule:: pygem.vffd
7+
8+
.. autosummary::
9+
:toctree: _summaries
10+
:nosignatures:
11+
12+
.. autoclass:: pygem.vffd.VFFD
13+
:members:
14+
:special-members: __call__
15+
:private-members:
16+
:exclude-members: _abd_impl
17+
:show-inheritance:
18+
:noindex:

pygem/bffd.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,45 @@
1-
from pygem.cffd import CFFD
2-
import numpy as np
3-
41
"""
52
Utilities for performing Barycenter Free Form Deformation (BFFD).
63
74
:Theoretical Insight:
85
It performs Free Form Deformation while trying to enforce the barycenter to be a certain value specified by the user.
96
The constraint is enforced exactly (up to numerical errors).
10-
For details see the mother and the grandmother clases.
7+
For details see the mother and the grandmother classes.
118
12-
139
"""
1410

11+
from pygem.cffd import CFFD
12+
import numpy as np
1513

1614

1715
class BFFD(CFFD):
1816
'''
1917
Class that handles the Barycenter Free Form Deformation on the mesh points.
2018
19+
:param list n_control_points: number of control points in the x, y, and z
20+
direction. Default is [2, 2, 2].
21+
22+
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
23+
x, y and z direction (local coordinate system).
24+
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
25+
the FFD bounding box.
26+
:cvar numpy.ndarray rot_angle: rotation angle around x, y and z axis of the
27+
FFD bounding box.
28+
:cvar numpy.ndarray n_control_points: the number of control points in the
29+
x, y, and z direction.
30+
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
31+
x, normalized with the box length x.
32+
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
33+
y, normalized with the box length y.
34+
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
35+
z, normalized with the box length z.
36+
:cvar callable linconstraint: it defines the F of the constraint F(x)=c.
37+
:cvar numpy.ndarray valconstraint: it defines the c of the constraint F(x)=c.
38+
:cvar list indices: it defines the indices of the control points
39+
that are moved to enforce the constraint. The control index is obtained by doing:
40+
all_indices=np.arange(n_x*n_y*n_z*3).reshape(n_x,n_y,n_z,3).tolist().
41+
:cvar numpy.ndarray M: a SDP weigth matrix. It must be of size len(indices) x len(indices).
42+
2143
:Example:
2244
2345
>>> from pygem import BFFD
@@ -31,8 +53,6 @@ class BFFD(CFFD):
3153
>>> bffd.M=np.eye(len(bffd.indices))
3254
>>> new_mesh_points = bffd(original_mesh_points)
3355
>>> assert np.isclose(np.linalg.norm(bffd.linconstraint(new_mesh_points)-b),np.array([0.]))
34-
35-
3656
'''
3757
def __init__(self, n_control_points=None):
3858
super().__init__(n_control_points)

pygem/cffd.py

Lines changed: 69 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
1-
from pygem.ffd import FFD
2-
import numpy as np
3-
41
"""
52
Utilities for performing Constrained Free Form Deformation (CFFD).
63
74
:Theoretical Insight:
5+
86
It performs Free Form Deformation while trying to enforce a costraint of the form F(x)=c.
97
The constraint is enforced exactly (up to numerical errors) if and only if the function is linear.
108
For details on Free Form Deformation see the mother class.
119
12-
1310
"""
1411

12+
from pygem.ffd import FFD
13+
import numpy as np
14+
1515

1616
class CFFD(FFD):
1717
"""
1818
Class that handles the Constrained Free Form Deformation on the mesh points.
1919
20+
:param list n_control_points: number of control points in the x, y, and z
21+
direction. Default is [2, 2, 2].
22+
23+
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
24+
x, y and z direction (local coordinate system).
25+
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
26+
the FFD bounding box.
27+
:cvar numpy.ndarray rot_angle: rotation angle around x, y and z axis of the
28+
FFD bounding box.
29+
:cvar numpy.ndarray n_control_points: the number of control points in the
30+
x, y, and z direction.
31+
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
32+
x, normalized with the box length x.
33+
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
34+
y, normalized with the box length y.
35+
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
36+
z, normalized with the box length z.
2037
:cvar callable linconstraint: it defines the F of the constraint F(x)=c.
21-
2238
:cvar numpy.ndarray valconstraint: it defines the c of the constraint F(x)=c.
23-
:param list indices: it defines the indices of the control points
39+
:cvar list indices: it defines the indices of the control points
2440
that are moved to enforce the constraint. The control index is obtained by doing:
2541
all_indices=np.arange(n_x*n_y*n_z*3).reshape(n_x,n_y,n_z,3).tolist().
2642
:cvar numpy.ndarray M: a SDP weigth matrix. It must be of size len(indices) x len(indices).
2743
28-
2944
:Example:
3045
3146
>>> from pygem import CFFD
@@ -44,93 +59,86 @@ class CFFD(FFD):
4459
>>> cffd.M=np.eye(len(cffd.indices))
4560
>>> new_mesh_points = cffd(original_mesh_points)
4661
>>> assert np.isclose(np.linalg.norm(fun(new_mesh_points)-b),np.array([0.]))
47-
4862
"""
63+
4964
def __init__(self, n_control_points=None):
5065
super().__init__(n_control_points)
51-
self.linconstraint=None
52-
self.valconstraint=None
53-
self.indices=None
54-
self.M=None
66+
self.linconstraint = None
67+
self.valconstraint = None
68+
self.indices = None
69+
self.M = None
5570

5671
def __call__(self, src_pts):
5772
saved_parameters=self._save_parameters()
58-
A,b=self._compute_linear_map(src_pts,saved_parameters.copy())
59-
d=A@saved_parameters[self.indices]+b
60-
deltax=np.linalg.inv(self.M)@A.T@np.linalg.inv((A@np.linalg.inv(self.M)@A.T))@(self.valconstraint-d)
61-
saved_parameters[self.indices]=saved_parameters[self.indices]+deltax
73+
A, b = self._compute_linear_map(src_pts, saved_parameters.copy())
74+
d = A @ saved_parameters[self.indices] + b
75+
deltax = np.linalg.inv(self.M) @ A.T @ np.linalg.inv((A @ np.linalg.inv(self.M)@ A.T)) @ (self.valconstraint - d)
76+
saved_parameters[self.indices] = saved_parameters[self.indices] + deltax
6277
self._load_parameters(saved_parameters)
6378
return self.ffd(src_pts)
6479

6580
def ffd(self,src_pts):
6681
'''
67-
Performs Classic Free Form Deformation.
82+
Performs Classic Free Form Deformation.
6883
69-
:param np.ndarray src_pts
70-
:return the deformed points
71-
:rtype: numpy.ndarray
84+
:param np.ndarray src_pts: the points to deform.
85+
:return: the deformed points.
86+
:rtype: numpy.ndarray
7287
'''
7388
return super().__call__(src_pts)
7489

7590

7691
def _save_parameters(self):
7792
'''
78-
Saves the FFD control points in an array of shape [n_x,ny,nz,3]
79-
:return the FFD control points in an array of shape [n_x,ny,nz,3]
80-
:rtype: numpy.ndarray
93+
Saves the FFD control points in an array of shape [n_x,ny,nz,3].
94+
95+
:return: the FFD control points in an array of shape [n_x,ny,nz,3].
96+
:rtype: numpy.ndarray
8197
'''
82-
tmp=np.zeros([*self.n_control_points,3])
83-
tmp[:,:,:,0]=self.array_mu_x
84-
tmp[:,:,:,1]=self.array_mu_y
85-
tmp[:,:,:,2]=self.array_mu_z
98+
tmp = np.zeros([*self.n_control_points,3])
99+
tmp[:, :, :, 0] = self.array_mu_x
100+
tmp[:, :, :, 1] = self.array_mu_y
101+
tmp[:, :, :, 2] = self.array_mu_z
86102
return tmp.reshape(-1)
87103

88104
def _load_parameters(self,tmp):
89105
'''
90-
Loads the FFD control points from an array of shape [n_x,ny,nz,3]
91-
:param np.ndarray tmp
92-
:rtype: None
106+
Loads the FFD control points from an array of shape [n_x,ny,nz,3].
107+
108+
:param np.ndarray tmp: the array of FFD control points.
109+
:rtype: None
93110
'''
94-
tmp=tmp.reshape(*self.n_control_points,3)
95-
self.array_mu_x=tmp[:,:,:,0]
96-
self.array_mu_y=tmp[:,:,:,1]
97-
self.array_mu_z=tmp[:,:,:,2]
111+
tmp = tmp.reshape(*self.n_control_points,3)
112+
self.array_mu_x = tmp[:, :, :, 0]
113+
self.array_mu_y = tmp[:, :, :, 1]
114+
self.array_mu_z = tmp[:, :, :, 2]
98115

99116

100117
# I see that a similar function already exists in pygem.utils, but it does not work for inputs and outputs of different dimensions
101118
def _compute_linear_map(self,src_pts,saved_parameters):
102119
'''
103-
Computes the coefficient and the intercept of the linear map from the control points to the output
120+
Computes the coefficient and the intercept of the linear map from the control points to the output.
104121
105-
:param np.ndarray src_pts
106-
:param np.ndarray saved_parameters
107-
:return a tuple containing the coefficient and the intercept
108-
:rtype tuple(np.ndarray,np.ndarray)
109-
122+
:param np.ndarray src_pts: the points to deform.
123+
:param np.ndarray saved_parameters: the array of FFD control points.
124+
:return: a tuple containing the coefficient and the intercept.
125+
:rtype: tuple(np.ndarray,np.ndarray)
110126
'''
111-
112-
113-
saved_parameters_bak=saved_parameters.copy()
127+
saved_parameters_bak=saved_parameters.copy() #saving ffd parameters
114128
n_indices=len(self.indices)
115129
inputs=np.zeros([n_indices+1,n_indices+1])
116130
outputs=np.zeros([n_indices+1,self.valconstraint.shape[0]])
117-
tmp=saved_parameters_bak[self.indices].reshape(1,-1)
118-
inputs[0]=np.hstack([tmp, np.ones((tmp.shape[0], 1))])
119-
saved_parameters[self.indices]=tmp
120-
self._load_parameters(saved_parameters)
121-
def_pts=super().__call__(src_pts)
122-
outputs[0]=self.linconstraint(def_pts)
123-
for i in range(1,n_indices+1):
124-
tmp=np.eye(n_indices)[i%n_indices]
131+
np.random.seed(0)
132+
for i in range(n_indices+1): ##now we generate the interpolation points
133+
tmp=np.random.rand(1,n_indices)
125134
tmp=tmp.reshape(1,-1)
126-
inputs[i]=np.hstack([tmp, np.ones((tmp.shape[0], 1))])
127-
saved_parameters[self.indices]=tmp
128-
self._load_parameters(saved_parameters)
129-
def_pts=super().__call__(src_pts)
130-
outputs[i]=self.linconstraint(def_pts)
131-
self._load_parameters(saved_parameters_bak)
132-
sol=np.linalg.lstsq(inputs,outputs,rcond=None)
133-
A=sol[0].T[:,:-1]
134-
b=sol[0].T[:,-1]
135-
self._load_parameters(saved_parameters_bak)
135+
inputs[i]=np.hstack([tmp, np.ones((tmp.shape[0], 1))]) #dependent variable
136+
saved_parameters[self.indices]=tmp
137+
self._load_parameters(saved_parameters) #loading the depent variable as a control point
138+
def_pts=super().__call__(src_pts) #computing the deformation with the dependent variable
139+
outputs[i]=self.linconstraint(def_pts) #computing the independent variable
140+
sol=np.linalg.lstsq(inputs,outputs,rcond=None) #computation of the linear map
141+
A=sol[0].T[:,:-1] #coefficient
142+
b=sol[0].T[:,-1] #intercept
143+
self._load_parameters(saved_parameters_bak) #restoring the original FFD parameters
136144
return A,b

0 commit comments

Comments
 (0)