pyvista.Transform.decompose

pyvista.Transform.decompose#

Transform.decompose(
*,
homogeneous: bool = False,
) tuple[NumpyArray[float], NumpyArray[float], NumpyArray[float], NumpyArray[float], NumpyArray[float]][source]#

Decompose the current transformation into its components.

Decompose the matrix M into

  • translation T

  • rotation R

  • reflection N

  • scaling S

  • shearing K

such that, when represented as 4x4 matrices, M = TRNSK. The decomposition is unique and is computed with polar matrix decomposition.

By default, compact representations of the transformations are returned (e.g. as a 3-element vector or a 3x3 matrix). Optionally, 4x4 matrices may be returned instead.

Note

  • The rotation is orthonormal and right-handed with positive determinant.

  • The scaling factors are positive.

  • The reflection is either 1 (no reflection) or -1 (has reflection) and can be used like a scaling factor.

Parameters:
homogeneousbool, default: False

If True, return the components (translation, rotation, etc.) as 4x4 homogeneous matrices. By default, reflection is a scalar, translation and scaling are length-3 vectors, and rotation and shear are 3x3 matrices.

Returns:
numpy.ndarray

Translation component T. Returned as a 3-element vector (or a 4x4 translation matrix if homogeneous is True).

numpy.ndarray

Rotation component R. Returned as a 3x3 orthonormal rotation matrix of row vectors (or a 4x4 rotation matrix if homogeneous is True).

numpy.ndarray

Reflection component N. Returned as a NumPy scalar (or a 4x4 reflection matrix if homogeneous is True).

numpy.ndarray

Scaling component S. Returned as a 3-element vector (or a 4x4 scaling matrix if homogeneous is True).

numpy.ndarray

Shear component K. Returned as a 3x3 matrix with ones on the diagonal and shear values in the off-diagonals (or as a 4x4 shearing matrix if homogeneous is True).

Examples

Create a transform by concatenating scaling, rotation, and translation matrices.

>>> import numpy as np
>>> import pyvista as pv
>>> transform = pv.Transform()
>>> _ = transform.scale(1, 2, 3)
>>> _ = transform.rotate_z(90)
>>> _ = transform.translate(4, 5, 6)
>>> transform
Transform (...)
  Num Transformations: 3
  Matrix:  [[ 0., -2.,  0.,  4.],
            [ 1.,  0.,  0.,  5.],
            [ 0.,  0.,  3.,  6.],
            [ 0.,  0.,  0.,  1.]]

Decompose the matrix.

>>> T, R, N, S, K = transform.decompose()

Since the input has no shear this component is the identity matrix. Similarly, there are no reflections so its value is 1. All other components are recovered perfectly and match the input.

>>> K  # shear
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
>>> S  # scale
array([1., 2., 3.])
>>> N  # reflection
array(1.)
>>> R  # rotation
array([[ 0., -1.,  0.],
       [ 1.,  0.,  0.],
       [ 0.,  0.,  1.]])
>>> T  # translation
array([4., 5., 6.])

Concatenate a shear component using pre-multiplication so that shearing is the first transformation.

>>> shear = np.eye(4)
>>> shear[0, 1] = 0.1  # xy shear
>>> _ = transform.concatenate(shear, multiply_mode='pre')

Repeat the decomposition and show its components. Note how the decomposed shear does not perfectly match the input shear matrix values. The values of the scaling and rotation components are also affected and do not exactly match the input. This is expected, because the shear can be partially factored as a combination of rotation and scaling.

>>> T, R, N, S, K = transform.decompose()
>>> K  # shear
array([[1.        , 0.03333333, 0.        ],
       [0.01663894, 1.        , 0.        ],
       [0.        , 0.        , 1.        ]])
>>> S  # scale
array([0.99944491, 2.0022213 , 3.        ])
>>> N  # reflection
array(1.)
>>> R  # rotation
array([[ 0.03331483, -0.99944491,  0.        ],
       [ 0.99944491,  0.03331483,  0.        ],
       [ 0.        ,  0.        ,  1.        ]])
>>> T  # translation
array([4., 5., 6.])

Although the values may not match the input exactly, the decomposition is nevertheless valid and can be used to re-compose the original transformation.

>>> T, R, N, S, K = transform.decompose(homogeneous=True)
>>> T @ R @ N @ S @ K
array([[-5.76153045e-17, -2.00000000e+00,  0.00000000e+00,
         4.00000000e+00],
       [ 1.00000000e+00,  1.00000000e-01,  0.00000000e+00,
         5.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  3.00000000e+00,
         6.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
         1.00000000e+00]])

Alternatively, re-compose the transformation as a new Transform with pre-multiplication.

>>> recomposed = pv.Transform([T, R, N, S, K], multiply_mode='pre')
>>> np.allclose(recomposed.matrix, transform.matrix)
True

Concatenate a reflection and decompose the transform again.

>>> _ = transform.flip_x()
>>> T, R, N, S, K = transform.decompose()

The reflection component is now -1.

>>> N  # reflection
array(-1.)

The decomposition may be simplified to a TRSK decomposition by combining the reflection component with either the rotation or the scaling term.

Multiplying the reflection with the rotation will make it a left-handed rotation with negative determinant:

>>> R = R * N
>>> np.linalg.det(R) < 0
np.True_

Alternatively, keep the rotation right-handed but make the scaling factors negative:

>>> S = S * N
>>> S  # scale
array([-0.99944491, -2.0022213 , -3.        ])