Skip to content

Commit 4a9d160

Browse files
separate things again, fixed docs and tutorialx
1 parent 7ae6bf5 commit 4a9d160

File tree

14 files changed

+4786
-13237
lines changed

14 files changed

+4786
-13237
lines changed

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

Lines changed: 4372 additions & 12305 deletions
Large diffs are not rendered by default.

docs/source/bffd.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
Barycenter Free-form Deformation
22
=====================
33

4-
.. currentmodule:: pygem.cffd
4+
.. currentmodule:: pygem.bffd
55

6-
.. automodule:: pygem.cffd
6+
.. automodule:: pygem.bffd
77

88
.. autosummary::
99
:toctree: _summaries
1010
:nosignatures:
1111

12-
.. autoclass:: pygem.cffd.BFFD
12+
.. autoclass:: pygem.bffd.BFFD
1313
:members:
1414
:special-members: __call__
1515
:private-members:

docs/source/vffd.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
Volume Free-form Deformation
22
=====================
33

4-
.. currentmodule:: pygem.cffd
4+
.. currentmodule:: pygem.vffd
55

6-
.. automodule:: pygem.cffd
6+
.. automodule:: pygem.vffd
77

88
.. autosummary::
99
:toctree: _summaries
1010
:nosignatures:
1111

12-
.. autoclass:: pygem.cffd.VFFD
12+
.. autoclass:: pygem.vffd.VFFD
1313
:members:
1414
:special-members: __call__
1515
:private-members:

pygem/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515

1616
from .deformation import Deformation
1717
from .ffd import FFD
18-
from .cffd import CFFD,BFFD,VFFD
18+
from .cffd import CFFD
1919
from .rbf import RBF
2020
from .idw import IDW
2121
from .rbf_factory import RBFFactory
2222
from .custom_deformation import CustomDeformation
2323
from .meta import *
24+
from .bffd import BFFD
25+
from .vffd import VFFD

pygem/bffd.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from pygem.cffd import CFFD
2+
import numpy as np
3+
class BFFD(CFFD):
4+
'''
5+
Class that handles the Barycenter Free Form Deformation on the mesh points.
6+
7+
:param list n_control_points: number of control points in the x, y, and z
8+
direction. Default is [2, 2, 2].
9+
10+
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
11+
x, y and z direction (local coordinate system).
12+
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
13+
the FFD bounding box.
14+
:cvar numpy.ndarray n_control_points: the number of control points in the
15+
x, y, and z direction.
16+
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
17+
x, normalized with the box length x.
18+
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
19+
y, normalized with the box length y.
20+
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
21+
z, normalized with the box length z.
22+
:cvar callable fun: it defines the F of the constraint F(x)=c. Default is the constant 1 function.
23+
:cvar numpy.ndarray fixval: it defines the c of the constraint F(x)=c. Default is 1.
24+
:cvar numpy.ndarray mask: a boolean tensor that tells to the class
25+
which control points can be moved, and in what direction, to enforce the constraint.
26+
The tensor has shape (n_x,n_y,n_z,3), where the last dimension indicates movement
27+
on x,y,z respectively. Default is all true.
28+
29+
:Example:
30+
31+
>>> from pygem import BFFD
32+
>>> b = np.random.rand(3)
33+
>>> bffd = BFFD(b, [2, 2, 2])
34+
>>> bffd.read_parameters('tests/test_datasets/parameters_test_cffd')
35+
>>> original_mesh_points = np.load("tests/test_datasets/test_sphere_cffd.npy")
36+
>>> bffd.adjust_control_points(original_mesh_points[:-4])
37+
>>> assert np.isclose(np.linalg.norm(bffd.fun(bffd.ffd(original_mesh_points[:-4])) - b), np.array([0.]))
38+
>>> new_mesh_points = bffd.ffd(original_mesh_points)
39+
'''
40+
41+
def __init__(self, fixval=None, n_control_points=None, ffd_mask=None):
42+
super().__init__(fixval, None, n_control_points, ffd_mask, None)
43+
44+
def linfun(x):
45+
return np.mean(x.reshape(-1, 3), axis=0)
46+
47+
self.fun = linfun
48+
self.fixval = fixval
49+
self.fun_mask = np.array([[True, False, False], [False, True, False],
50+
[False, False, True]])
51+
52+

pygem/cffd.py

Lines changed: 2 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class CFFD(FFD):
4747
:Example:
4848
>>> from pygem import CFFD
4949
>>> import numpy as np
50-
>>> original_mesh_points = np.random.rand(100, 3)
50+
>>> original_mesh_points = np.load("tests/test_datasets/test_sphere_cffd.npy")
5151
>>> A = np.random.rand(3, original_mesh_points[:-4].reshape(-1).shape[0])
5252
>>> fun = lambda x: A @ x.reshape(-1)
5353
>>> b = np.random.rand(3)
@@ -184,121 +184,4 @@ def _compute_linear_map(self, src_pts, saved_parameters, indices):
184184
rcond=None) #computation of the linear map
185185
A = sol[0].T[:, :-1] #coefficient
186186
b = sol[0].T[:, -1] #intercept
187-
return A, b
188-
189-
190-
class BFFD(CFFD):
191-
'''
192-
Class that handles the Barycenter Free Form Deformation on the mesh points.
193-
194-
:param list n_control_points: number of control points in the x, y, and z
195-
direction. Default is [2, 2, 2].
196-
197-
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
198-
x, y and z direction (local coordinate system).
199-
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
200-
the FFD bounding box.
201-
:cvar numpy.ndarray n_control_points: the number of control points in the
202-
x, y, and z direction.
203-
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
204-
x, normalized with the box length x.
205-
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
206-
y, normalized with the box length y.
207-
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
208-
z, normalized with the box length z.
209-
:cvar callable fun: it defines the F of the constraint F(x)=c. Default is the constant 1 function.
210-
:cvar numpy.ndarray fixval: it defines the c of the constraint F(x)=c. Default is 1.
211-
:cvar numpy.ndarray mask: a boolean tensor that tells to the class
212-
which control points can be moved, and in what direction, to enforce the constraint.
213-
The tensor has shape (n_x,n_y,n_z,3), where the last dimension indicates movement
214-
on x,y,z respectively. Default is all true.
215-
216-
:Example:
217-
218-
>>> from pygem import BFFD
219-
>>> b = np.random.rand(3)
220-
>>> bffd = BFFD(b, [2, 2, 2])
221-
>>> bffd.read_parameters('tests/test_datasets/parameters_test_cffd')
222-
>>> original_mesh_points = np.random.rand(100, 3)
223-
>>> bffd.adjust_control_points(original_mesh_points[:-4])
224-
>>> assert np.isclose(np.linalg.norm(bffd.fun(bffd.ffd(original_mesh_points[:-4])) - b), np.array([0.]))
225-
new_mesh_points = bffd.ffd(original_mesh_points)
226-
'''
227-
228-
def __init__(self, fixval=None, n_control_points=None, ffd_mask=None):
229-
super().__init__(fixval, None, n_control_points, ffd_mask, None)
230-
231-
def linfun(x):
232-
return np.mean(x.reshape(-1, 3), axis=0)
233-
234-
self.fun = linfun
235-
self.fixval = fixval
236-
self.fun_mask = np.array([[True, False, False], [False, True, False],
237-
[False, False, True]])
238-
239-
240-
class VFFD(CFFD):
241-
'''
242-
Class that handles the Volumetric Free Form Deformation on the mesh points.
243-
244-
:param list n_control_points: number of control points in the x, y, and z
245-
direction. Default is [2, 2, 2].
246-
:param string mode: it can be ``affine`` or ``triaffine``. The first option is for the F that are affine in all the coordinates of the points.
247-
The second one is for functions that are F in the coordinates of the points. The first option implies the second, but is optimal for that class of functions.
248-
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
249-
x, y and z direction (local coordinate system).
250-
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
251-
the FFD bounding box.
252-
:cvar numpy.ndarray n_control_points: the number of control points in the
253-
x, y, and z direction.
254-
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
255-
x, normalized with the box length x.
256-
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
257-
y, normalized with the box length y.
258-
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
259-
z, normalized with the box length z.
260-
:cvar callable fun: it defines the F of the constraint F(x)=c. Default is the constant 1 function.
261-
:cvar numpy.ndarray fixval: it defines the c of the constraint F(x)=c. Default is 1.
262-
:cvar numpy.ndarray ffd_mask: a boolean tensor that tells to the class
263-
which control points can be moved, and in what direction, to enforce the constraint.
264-
The tensor has shape (n_x,n_y,n_z,3), where the last dimension indicates movement
265-
on x,y,z respectively. Default is all true.
266-
:cvar numpy.ndarray fun_mask: a boolean tensor that tells to the class
267-
on which axis which constraint depends on. The tensor has shape (n_cons,3), where the last dimension indicates dependency on
268-
on x,y,z respectively. Default is all true. It used only in the triaffine mode.
269-
270-
:Example:
271-
272-
>>> from pygem import VFFD
273-
>>> import numpy as np
274-
>>> import meshio
275-
>>> mesh = meshio.read('tests/test_datasets/test_sphere_cffd.stl')
276-
>>> original_mesh_points = mesh.points
277-
>>> triangles = mesh.cells_dict["triangle"]
278-
>>> b = np.random.rand(1)
279-
>>> vffd = VFFD(triangles, b, [2, 2, 2])
280-
>>> vffd.read_parameters('tests/test_datasets/parameters_test_cffd.prm')
281-
>>> vffd.adjust_control_points(original_mesh_points)
282-
>>> new_mesh_points = vffd(original_mesh_points)
283-
>>> assert np.isclose(np.linalg.norm(vffd.fun(new_mesh_points) - b), np.array([0.]), atol=1e-07)
284-
285-
'''
286-
def __init__(self, triangles, fixval, n_control_points=None, ffd_mask=None):
287-
super().__init__(fixval, None, n_control_points, ffd_mask, None)
288-
289-
self.triangles = triangles
290-
291-
def volume_inn(x):
292-
return _volume(x, self.triangles)
293-
294-
self.fun = volume_inn
295-
self.fixval = fixval
296-
self.fun_mask = np.array([[True, True, True]])
297-
298-
299-
def _volume(x, triangles):
300-
x = x.reshape(-1, 3)
301-
mesh = x[triangles]
302-
return np.array([np.sum(np.linalg.det(mesh))])
303-
304-
187+
return A, b

pygem/vffd.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from pygem.cffd import CFFD
2+
import numpy as np
3+
class VFFD(CFFD):
4+
'''
5+
Class that handles the Volumetric Free Form Deformation on the mesh points.
6+
7+
:param list n_control_points: number of control points in the x, y, and z
8+
direction. Default is [2, 2, 2].
9+
:param string mode: it can be ``affine`` or ``triaffine``. The first option is for the F that are affine in all the coordinates of the points.
10+
The second one is for functions that are F in the coordinates of the points. The first option implies the second, but is optimal for that class of functions.
11+
:cvar numpy.ndarray box_length: dimension of the FFD bounding box, in the
12+
x, y and z direction (local coordinate system).
13+
:cvar numpy.ndarray box_origin: the x, y and z coordinates of the origin of
14+
the FFD bounding box.
15+
:cvar numpy.ndarray n_control_points: the number of control points in the
16+
x, y, and z direction.
17+
:cvar numpy.ndarray array_mu_x: collects the displacements (weights) along
18+
x, normalized with the box length x.
19+
:cvar numpy.ndarray array_mu_y: collects the displacements (weights) along
20+
y, normalized with the box length y.
21+
:cvar numpy.ndarray array_mu_z: collects the displacements (weights) along
22+
z, normalized with the box length z.
23+
:cvar callable fun: it defines the F of the constraint F(x)=c. Default is the constant 1 function.
24+
:cvar numpy.ndarray fixval: it defines the c of the constraint F(x)=c. Default is 1.
25+
:cvar numpy.ndarray ffd_mask: a boolean tensor that tells to the class
26+
which control points can be moved, and in what direction, to enforce the constraint.
27+
The tensor has shape (n_x,n_y,n_z,3), where the last dimension indicates movement
28+
on x,y,z respectively. Default is all true.
29+
:cvar numpy.ndarray fun_mask: a boolean tensor that tells to the class
30+
on which axis which constraint depends on. The tensor has shape (n_cons,3), where the last dimension indicates dependency on
31+
on x,y,z respectively. Default is all true. It used only in the triaffine mode.
32+
33+
:Example:
34+
35+
>>> from pygem import VFFD
36+
>>> import numpy as np
37+
>>> import meshio
38+
>>> mesh = meshio.read('tests/test_datasets/test_sphere_cffd.stl')
39+
>>> original_mesh_points = mesh.points
40+
>>> triangles = mesh.cells_dict["triangle"]
41+
>>> b = np.random.rand(1)
42+
>>> vffd = VFFD(triangles, b, [2, 2, 2])
43+
>>> vffd.read_parameters('tests/test_datasets/parameters_test_cffd.prm')
44+
>>> vffd.adjust_control_points(original_mesh_points)
45+
>>> new_mesh_points = vffd(original_mesh_points)
46+
>>> assert np.isclose(np.linalg.norm(vffd.fun(new_mesh_points) - b), np.array([0.]), atol=1e-07)
47+
48+
'''
49+
def __init__(self, triangles, fixval, n_control_points=None, ffd_mask=None):
50+
super().__init__(fixval, None, n_control_points, ffd_mask, None)
51+
52+
self.triangles = triangles
53+
54+
def volume_inn(x):
55+
return _volume(x, self.triangles)
56+
57+
self.fun = volume_inn
58+
self.fixval = fixval
59+
self.fun_mask = np.array([[True, True, True]])
60+
61+
62+
def _volume(x, triangles):
63+
x = x.reshape(-1, 3)
64+
mesh = x[triangles]
65+
return np.array([np.sum(np.linalg.det(mesh))])
66+
67+

tests/test_bffd.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import filecmp
2+
import os
3+
from unittest import TestCase
4+
import numpy as np
5+
from pygem import BFFD
6+
7+
8+
class TestBFFD(TestCase):
9+
10+
def test_nothing_happens_bffd(self):
11+
np.random.seed(0)
12+
original_mesh_points = np.random.rand(100, 3)
13+
A = np.random.rand(3, original_mesh_points.reshape(-1).shape[0])
14+
15+
b = np.mean(original_mesh_points, axis=0)
16+
cffd = BFFD(b)
17+
cffd.adjust_control_points(original_mesh_points)
18+
new_mesh_points = cffd.ffd(original_mesh_points)
19+
assert np.linalg.norm(original_mesh_points - new_mesh_points
20+
) / np.linalg.norm(original_mesh_points) < 1e-02
21+
22+
def test_constraint_bffd(self):
23+
np.random.seed(0)
24+
original_mesh_points = np.random.rand(100, 3)
25+
A = np.random.rand(3, original_mesh_points.reshape(-1).shape[0])
26+
b = np.mean(original_mesh_points, axis=0) + 0.02 * np.random.rand(3)
27+
cffd = BFFD(b)
28+
cffd.read_parameters('tests/test_datasets/parameters_test_cffd.prm')
29+
cffd.adjust_control_points(original_mesh_points)
30+
new_mesh_points = cffd.ffd(original_mesh_points)
31+
assert np.linalg.norm(
32+
b - np.mean(new_mesh_points, axis=0)) / np.linalg.norm(b) < 1e-02

0 commit comments

Comments
 (0)