1- from pygem .ffd import FFD
2- import numpy as np
3-
41"""
52Utilities 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
1616class 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