Skip to content

Commit 59a27c6

Browse files
authored
Merge pull request #115 from ndem0/cleancode
Refactor `affine` and `freeform`
2 parents 49c8c1a + ef8dc2a commit 59a27c6

File tree

2 files changed

+61
-68
lines changed

2 files changed

+61
-68
lines changed

pygem/affine.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""
2-
Utilities for the affine transformations of the bounding box of the Free Form Deformation.
2+
Utilities for the affine transformations of the bounding box of the Free Form
3+
Deformation.
34
"""
45
import math
56
import sys
@@ -9,22 +10,23 @@
910

1011
def angles2matrix(rot_z=0, rot_y=0, rot_x=0):
1112
"""
12-
This method returns the rotation matrix for given rotations around z, y and x axes.
13-
The output rotation matrix is equal to the composition of the individual rotations.
14-
Rotations are counter-clockwise. The default value of the three rotations is zero.
13+
This method returns the rotation matrix for given rotations around z, y and
14+
x axes. The output rotation matrix is equal to the composition of the
15+
individual rotations. Rotations are counter-clockwise. The default value of
16+
the three rotations is zero.
1517
1618
:param float rot_z: rotation angle (in radians) around z-axis.
1719
:param float rot_y: rotation angle (in radians) around y-axis.
1820
:param float rot_x: rotation angle (in radians) around x-axis.
1921
20-
:return: rot_matrix: rotation matrix for the given angles. The matrix shape is always (3, 3).
22+
:return: rot_matrix: rotation matrix for the given angles. The matrix shape
23+
is always (3, 3).
2124
:rtype: numpy.ndarray
2225
2326
:Example:
2427
2528
>>> import pygem.affine as at
2629
>>> import numpy as np
27-
2830
>>> # Example of a rotation around x, y, z axis
2931
>>> rotz = 10*np.pi/180
3032
>>> roty = 20*np.pi/180
@@ -34,8 +36,8 @@ def angles2matrix(rot_z=0, rot_y=0, rot_x=0):
3436
.. note::
3537
3638
- The direction of rotation is given by the right-hand rule.
37-
- When applying the rotation to a vector, the vector should be column vector
38-
to the right of the rotation matrix.
39+
- When applying the rotation to a vector, the vector should be column
40+
vector to the right of the rotation matrix.
3941
"""
4042
rot_matrix = []
4143
if rot_z:
@@ -63,9 +65,10 @@ def angles2matrix(rot_z=0, rot_y=0, rot_x=0):
6365

6466
def to_reduced_row_echelon_form(matrix):
6567
"""
66-
This method computes the reduced row echelon form (a.k.a. row canonical form) of a matrix.
67-
The code is taken from https://rosettacode.org/wiki/Reduced_row_echelon_form#Python and
68-
edited with minor changes.
68+
This method computes the reduced row echelon form (a.k.a. row canonical
69+
form) of a matrix. The code is taken from
70+
https://rosettacode.org/wiki/Reduced_row_echelon_form#Python and edited with
71+
minor changes.
6972
7073
:param matrix matrix: matrix to be reduced.
7174
@@ -75,7 +78,6 @@ def to_reduced_row_echelon_form(matrix):
7578
:Example:
7679
7780
>>> import pygem.affine as at
78-
7981
>>> matrix = [[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]
8082
>>> rref_matrix = at.to_reduced_row_echelon_form(matrix)
8183
@@ -118,9 +120,9 @@ def affine_points_fit(points_start, points_end):
118120
:param numpy.ndarray points_start: set of starting points.
119121
:param numpy.ndarray points_end: set of ending points.
120122
121-
:return: transform_vector: function that transforms a vector according to the
122-
affine map. It takes a source vector and return a vector transformed
123-
by the reduced row echelon form of the map.
123+
:return: transform_vector: function that transforms a vector according to
124+
the affine map. It takes a source vector and return a vector transformed
125+
by the reduced row echelon form of the map.
124126
:rtype: function
125127
126128
:Example:
@@ -164,7 +166,7 @@ def affine_points_fit(points_start, points_end):
164166

165167
if np.linalg.cond(affine_matrix) < 1 / sys.float_info.epsilon:
166168
rref_aff_matrix = to_reduced_row_echelon_form(affine_matrix)
167-
rref_aff_matrix = np.array(rref_aff_matrix)
169+
rref_aff_matrix = np.array(rref_aff_matrix)[:, 4:]
168170
else:
169171
raise RuntimeError(
170172
"Error: singular matrix. Points are probably coplanar."
@@ -179,12 +181,7 @@ def transform_vector(source):
179181
:return destination: numpy.ndarray representing the transformed vector.
180182
:rtype: numpy.ndarray
181183
"""
182-
destination = np.zeros(dim)
183-
for i in range(dim):
184-
for j in range(dim):
185-
destination[j] += source[i] * rref_aff_matrix[i][j + dim + 1]
186-
# Add the last line of the rref
187-
destination[i] += rref_aff_matrix[dim][i + dim + 1]
184+
destination = source.dot(rref_aff_matrix[:-1]) + rref_aff_matrix[-1]
188185
return destination
189186

190187
return transform_vector

pygem/freeform.py

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,65 +3,67 @@
33
44
:Theoretical Insight:
55
6-
Free Form Deformation is a technique for the efficient, smooth and accurate geometrical
7-
parametrization. It has been proposed the first time in *Sederberg, Thomas W., and Scott
8-
R. Parry. "Free-form deformation of solid geometric models." ACM SIGGRAPH computer
9-
graphics 20.4 (1986): 151-160*. It consists in three different step:
6+
Free Form Deformation is a technique for the efficient, smooth and accurate
7+
geometrical parametrization. It has been proposed the first time in
8+
*Sederberg, Thomas W., and Scott R. Parry. "Free-form deformation of solid
9+
geometric models." ACM SIGGRAPH computer graphics 20.4 (1986): 151-160*. It
10+
consists in three different step:
1011
11-
- Mapping the physical domain to the reference one with map :math:`\\boldsymbol{\psi}`.
12-
In the code it is named *transformation*.
12+
- Mapping the physical domain to the reference one with map
13+
:math:`\\boldsymbol{\psi}`. In the code it is named *transformation*.
1314
1415
- Moving some control points to deform the lattice with :math:`\\hat{T}`.
15-
The movement of the control points is basically the weight (or displacement)
16-
:math:`\\boldsymbol{\mu}` we set in the *parameters file*.
16+
The movement of the control points is basically the weight (or displacement)
17+
:math:`\\boldsymbol{\mu}` we set in the *parameters file*.
1718
18-
- Mapping back to the physical domain with map :math:`\\boldsymbol{\psi}^-1`.
19-
In the code it is named *inverse_transformation*.
19+
- Mapping back to the physical domain with map
20+
:math:`\\boldsymbol{\psi}^-1`. In the code it is named
21+
*inverse_transformation*.
2022
2123
FFD map (:math:`T`) is the composition of the three maps, that is
2224
23-
.. math::
24-
T(\\cdot, \\boldsymbol{\\mu}) = (\\Psi^{-1} \\circ \\hat{T} \\circ \\Psi)
25-
(\\cdot, \\boldsymbol{\\mu})
25+
.. math:: T(\\cdot, \\boldsymbol{\\mu}) = (\\Psi^{-1} \\circ \\hat{T} \\circ
26+
\\Psi) (\\cdot, \\boldsymbol{\\mu})
2627
2728
In this way, every point inside the FFD box changes it position according to
2829
29-
.. math::
30-
\\boldsymbol{P} = \\boldsymbol{\psi}^-1 \\left( \\sum_{l=0} ^L \\sum_{m=0} ^M
31-
\\sum_{n=0} ^N \\mathsf{b}_{lmn}(\\boldsymbol{\\psi}(\\boldsymbol{P}_0))
32-
\\boldsymbol{\\mu}_{lmn} \\right)
30+
.. math:: \\boldsymbol{P} = \\boldsymbol{\psi}^-1 \\left( \\sum_{l=0} ^L
31+
\\sum_{m=0} ^M \\sum_{n=0} ^N
32+
\\mathsf{b}_{lmn}(\\boldsymbol{\\psi}(\\boldsymbol{P}_0))
33+
\\boldsymbol{\\mu}_{lmn} \\right)
3334
34-
where :math:`\\mathsf{b}_{lmn}` are Bernstein polynomials.
35-
We improve the traditional version by allowing a rotation of the FFD lattice in order
36-
to give more flexibility to the tool.
35+
where :math:`\\mathsf{b}_{lmn}` are Bernstein polynomials. We improve the
36+
traditional version by allowing a rotation of the FFD lattice in order to
37+
give more flexibility to the tool.
3738
38-
You can try to add more shapes to the lattice to allow more and more involved transformations.
39+
You can try to add more shapes to the lattice to allow more and more
40+
involved transformations.
3941
4042
"""
4143
import numpy as np
4244
from scipy import special
4345
import pygem.affine as at
4446

45-
4647
class FFD(object):
4748
"""
4849
Class that handles the Free Form Deformation on the mesh points.
4950
50-
:param FFDParameters ffd_parameters: parameters of the Free Form Deformation.
51-
:param numpy.ndarray original_mesh_points: coordinates of the original points of the mesh.
51+
:param FFDParameters ffd_parameters: parameters of the Free Form
52+
Deformation.
53+
:param numpy.ndarray original_mesh_points: coordinates of the original
54+
points of the mesh.
5255
5356
:cvar FFDParameters parameters: parameters of the Free Form Deformation.
54-
:cvar numpy.ndarray original_mesh_points: coordinates of the original points of the mesh.
55-
The shape is `n_points`-by-3.
56-
:cvar numpy.ndarray modified_mesh_points: coordinates of the points of the deformed mesh.
57-
The shape is `n_points`-by-3.
57+
:cvar numpy.ndarray original_mesh_points: coordinates of the original points
58+
of the mesh. The shape is `n_points`-by-3.
59+
:cvar numpy.ndarray modified_mesh_points: coordinates of the points of the
60+
deformed mesh. The shape is `n_points`-by-3.
5861
5962
:Example:
6063
6164
>>> import pygem.freeform as ffd
6265
>>> import pygem.params as ffdp
6366
>>> import numpy as np
64-
6567
>>> ffd_parameters = ffdp.FFDParameters()
6668
>>> ffd_parameters.read_parameters('tests/test_datasets/parameters_test_ffd_sphere.prm')
6769
>>> original_mesh_points = np.load('tests/test_datasets/meshpoints_sphere_orig.npy')
@@ -77,11 +79,8 @@ def __init__(self, ffd_parameters, original_mesh_points):
7779

7880
def perform(self):
7981
"""
80-
This method performs the deformation on the mesh points. After the execution
81-
it sets `self.modified_mesh_points`.
82-
83-
.. todo::
84-
82+
This method performs the deformation on the mesh points. After the
83+
execution it sets `self.modified_mesh_points`.
8584
"""
8685
# translation and then affine transformation
8786
translation = self.parameters.origin_box
@@ -113,7 +112,8 @@ def perform(self):
113112
) & (reference_frame_mesh_points[:, 2] <= 1.)]
114113
(n_rows_mesh, n_cols_mesh) = mesh_points.shape
115114

116-
# Initialization. In order to exploit the contiguity in memory the following are transposed
115+
# Initialization. In order to exploit the contiguity in memory the
116+
# following are transposed
117117
(dim_n_mu, dim_m_mu, dim_t_mu) = self.parameters.array_mu_x.shape
118118
bernstein_x = np.zeros((dim_n_mu, n_rows_mesh))
119119
bernstein_y = np.zeros((dim_m_mu, n_rows_mesh))
@@ -172,19 +172,15 @@ def perform(self):
172172
@staticmethod
173173
def _transform_points(original_points, transformation):
174174
"""
175-
This private static method transforms the points according to the affine transformation taken from
176-
affine_points_fit method.
175+
This private static method transforms the points according to the affine
176+
transformation taken from affine_points_fit method.
177177
178-
:param numpy.ndarray original_points: coordinates of the original points.
179-
:param function transformation: affine transformation taken from affine_points_fit method.
178+
:param numpy.ndarray original_points: coordinates of the original
179+
points.
180+
:param function transformation: affine transformation taken from
181+
affine_points_fit method.
180182
181183
:return: modified_points: coordinates of the modified points.
182184
:rtype: numpy.ndarray
183185
"""
184-
n_rows_mesh, n_cols_mesh = original_points.shape
185-
modified_points = np.zeros((n_rows_mesh, n_cols_mesh))
186-
187-
for i in range(0, n_rows_mesh):
188-
modified_points[i, :] = transformation(original_points[i])
189-
190-
return modified_points
186+
return transformation(original_points)

0 commit comments

Comments
 (0)