affines
¶
Compose and decompose homogenous affine - usually 4x4 - matrices
|
Compose translations, rotations, zooms, [shears] to affine |
|
Decompose homogenous affine transformation matrix A into parts. |
|
Decompose 4x4 homogenous affine matrix into parts. |
compose¶
-
transforms3d.affines.
compose
(T, R, Z, S=None)¶ Compose translations, rotations, zooms, [shears] to affine
- Parameters
- Tarray-like shape (N,)
Translations, where N is usually 3 (3D case)
- Rarray-like shape (N,N)
Rotation matrix where N is usually 3 (3D case)
- Zarray-like shape (N,)
Zooms, where N is usually 3 (3D case)
- Sarray-like, shape (P,), optional
Shear vector, such that shears fill upper triangle above diagonal to form shear matrix. P is the (N-2)th Triangular number, which happens to be 3 for a 4x4 affine (3D case)
- Returns
- Aarray, shape (N+1, N+1)
Affine transformation matrix where N usually == 3 (3D case)
Examples
>>> T = [20, 30, 40] # translations >>> R = [[0, -1, 0], [1, 0, 0], [0, 0, 1]] # rotation matrix >>> Z = [2.0, 3.0, 4.0] # zooms >>> A = compose(T, R, Z) >>> A array([[ 0., -3., 0., 20.], [ 2., 0., 0., 30.], [ 0., 0., 4., 40.], [ 0., 0., 0., 1.]]) >>> S = np.zeros(3) >>> B = compose(T, R, Z, S) >>> np.all(A == B) True
A null set
>>> compose(np.zeros(3), np.eye(3), np.ones(3), np.zeros(3)) array([[1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 1., 0.], [0., 0., 0., 1.]])
decompose¶
-
transforms3d.affines.
decompose
(A)¶ Decompose homogenous affine transformation matrix A into parts.
The parts are translations, rotations, zooms, shears.
A can be any square matrix, but is typically shape (4,4).
Decomposes A into
T, R, Z, S
, such that, if A is shape (4,4):Smat = np.array([[1, S[0], S[1]], [0, 1, S[2]], [0, 0, 1]]) RZS = np.dot(R, np.dot(np.diag(Z), Smat)) A = np.eye(4) A[:3,:3] = RZS A[:-1,-1] = T
The order of transformations is therefore shears, followed by zooms, followed by rotations, followed by translations.
The case above (A.shape == (4,4)) is the most common, and corresponds to a 3D affine, but in fact A need only be square.
- Parameters
- Aarray shape (N,N)
- Returns
- Tarray, shape (N-1,)
Translation vector
- Rarray shape (N-1, N-1)
rotation matrix
- Zarray, shape (N-1,)
Zoom vector. May have one negative zoom to prevent need for negative determinant R matrix above
- Sarray, shape (P,)
Shear vector, such that shears fill upper triangle above diagonal to form shear matrix. P is the (N-2)th Triangular number, which happens to be 3 for a 4x4 affine.
Notes
We have used a nice trick from SPM to get the shears. Let us call the starting N-1 by N-1 matrix
RZS
, because it is the composition of the rotations on the zooms on the shears. The rotation matrixR
must have the propertynp.dot(R.T, R) == np.eye(N-1)
. Thusnp.dot(RZS.T, RZS)
will, by the transpose rules, be equal tonp.dot((ZS).T, (ZS))
. Because we are doing shears with the upper right part of the matrix, that means that the Cholesky decomposition ofnp.dot(RZS.T, RZS)
will give us ourZS
matrix, from which we take the zooms from the diagonal, and the shear values from the off-diagonal elements.Examples
>>> T = [20, 30, 40] # translations >>> R = [[0, -1, 0], [1, 0, 0], [0, 0, 1]] # rotation matrix >>> Z = [2.0, 3.0, 4.0] # zooms >>> S = [0.2, 0.1, 0.3] # shears >>> # Now we make an affine matrix >>> A = np.eye(4) >>> Smat = np.array([[1, S[0], S[1]], ... [0, 1, S[2]], ... [0, 0, 1]]) >>> RZS = np.dot(R, np.dot(np.diag(Z), Smat)) >>> A[:3,:3] = RZS >>> A[:-1,-1] = T # set translations >>> Tdash, Rdash, Zdash, Sdash = decompose(A) >>> np.allclose(T, Tdash) True >>> np.allclose(R, Rdash) True >>> np.allclose(Z, Zdash) True >>> np.allclose(S, Sdash) True
decompose44¶
-
transforms3d.affines.
decompose44
(A44)¶ Decompose 4x4 homogenous affine matrix into parts.
The parts are translations, rotations, zooms, shears.
This is the same as
decompose()
but specialized for 4x4 affines.Decomposes A44 into
T, R, Z, S
, such that:Smat = np.array([[1, S[0], S[1]], [0, 1, S[2]], [0, 0, 1]]) RZS = np.dot(R, np.dot(np.diag(Z), Smat)) A44 = np.eye(4) A44[:3,:3] = RZS A44[:-1,-1] = T
The order of transformations is therefore shears, followed by zooms, followed by rotations, followed by translations.
This routine only works for shape (4,4) matrices
- Parameters
- A44array shape (4,4)
- Returns
- Tarray, shape (3,)
Translation vector
- Rarray shape (3,3)
rotation matrix
- Zarray, shape (3,)
Zoom vector. May have one negative zoom to prevent need for negative determinant R matrix above
- Sarray, shape (3,)
Shear vector, such that shears fill upper triangle above diagonal to form shear matrix (type
striu
).
Notes
The implementation inspired by:
Decomposing a matrix into simple transformations by Spencer W. Thomas, pp 320-323 in Graphics Gems II, James Arvo (editor), Academic Press, 1991, ISBN: 0120644819.
The upper left 3x3 of the affine consists of a matrix we’ll call RZS:
RZS = R * Z *S
where R is a rotation matrix, Z is a diagonal matrix of scalings:
Z = diag([sx, sy, sz])
and S is a shear matrix of form:
S = [[1, sxy, sxz], [0, 1, syz], [0, 0, 1]])
Running all this through sympy (see ‘derivations’ folder) gives
RZS
as[R00*sx, R01*sy + R00*sx*sxy, R02*sz + R00*sx*sxz + R01*sy*syz] [R10*sx, R11*sy + R10*sx*sxy, R12*sz + R10*sx*sxz + R11*sy*syz] [R20*sx, R21*sy + R20*sx*sxy, R22*sz + R20*sx*sxz + R21*sy*syz]
R
is defined as being a rotation matrix, so the dot products between the columns ofR
are zero, and the norm of each column is 1. Thus the dot product:R[:,0].T * RZS[:,1]
that results in:
[R00*R01*sy + R10*R11*sy + R20*R21*sy + sx*sxy*R00**2 + sx*sxy*R10**2 + sx*sxy*R20**2]
simplifies to
sy*0 + sx*sxy*1
==sx*sxy
. Therefore:R[:,1] * sy = RZS[:,1] - R[:,0] * (R[:,0].T * RZS[:,1])
allowing us to get
sy
with the norm, and sxy withR[:,0].T * RZS[:,1] / sx
.Similarly
R[:,0].T * RZS[:,2]
simplifies tosx*sxz
, andR[:,1].T * RZS[:,2]
tosy*syz
giving us the remaining unknowns.Examples
>>> T = [20, 30, 40] # translations >>> R = [[0, -1, 0], [1, 0, 0], [0, 0, 1]] # rotation matrix >>> Z = [2.0, 3.0, 4.0] # zooms >>> S = [0.2, 0.1, 0.3] # shears >>> # Now we make an affine matrix >>> A = np.eye(4) >>> Smat = np.array([[1, S[0], S[1]], ... [0, 1, S[2]], ... [0, 0, 1]]) >>> RZS = np.dot(R, np.dot(np.diag(Z), Smat)) >>> A[:3,:3] = RZS >>> A[:-1,-1] = T # set translations >>> Tdash, Rdash, Zdash, Sdash = decompose44(A) >>> np.allclose(T, Tdash) True >>> np.allclose(R, Rdash) True >>> np.allclose(Z, Zdash) True >>> np.allclose(S, Sdash) True