Source code for openalea.plantconvert.matrix

import numpy as np
import numpy.linalg as npl

import openalea.plantgl.all as pgl


[docs] class TRSError(Exception): pass
[docs] def random_matrix4(): """Return a random TRS matrix. Implemented for test only. """ A = np.identity(4) A[:3, :] = 1 - 2 * np.random.rand(3, 4) Q, R = npl.qr(A[:3, :3]) if npl.det(Q) > 0: Q[:, 0] = -Q[:, 0] R[0, :] = -R[0, :] A[:3, :3] = Q * R.diagonal() return A
[docs] def numpy_to_mat4(A): """Convert the 2d array A to pgl.Matrix4 object. :param A: a 2d numpy array. Accepted shape = 3*4, 4*4, 3*3 :type A: numpy.ndarray :return: the corresponding Matrix4 object. :rtype: pgl.Matrix4 """ if A.shape not in [(3, 4), (4, 4), (3, 3)]: raise ValueError( "The input's shape %s %s is not accepted. Must be : 3*4, 4*4 or 3*3" % (A.shape) ) if A.shape == (3, 4) or A.shape == (4, 4): return pgl.Matrix4(*A.T.tolist()) else: return pgl.Matrix4(pgl.Matrix3(*A.flatten().tolist()))
[docs] def mat4_to_numpy(A): """Convert the pgl.Matrix4 object to a 2d array. :param A: a pgl.Matrix4 object. :type A: pgl.Matrix4 :return: the corresponding 2d numpy array. :rtype: numpy.ndarray """ return np.array([[A[i, j] for j in range(4)] for i in range(4)])
[docs] def TRS_from_matrix4(A: np.ndarray): """Return the TRS components of a TRS matrix. :param A: a 4x4 TRS matrix :type A: numpy.ndarray :return: t, Q, s: the TRS components of the input matrix. :rtype: tuple of numpy.ndarray vector of length 3, 3x3 matrix, vector of length 3 """ rs = A[:3, :3] t = A[:3, 3] s = npl.norm(rs, axis=0) zero_ind = np.isclose(s, 0) non_zero_ind = np.logical_not(zero_ind) if np.sum(zero_ind) > 1: # more than one zero scaling => can't determine the rotation matrix raise TRSError("There are more than 2 zero scaling.") elif np.sum(zero_ind) == 1: # there is only one zero scaling => deduce the axis by cross product zero_ind = np.nonzero(zero_ind)[0] Q = np.empty((3, 3)) Q[:, non_zero_ind] = rs[:, non_zero_ind] / s[non_zero_ind] v1 = Q[:, non_zero_ind][:, 0] v2 = Q[:, non_zero_ind][:, 1] Q[:, zero_ind] = np.cross(v1, v2).reshape((3, 1)) else: Q = rs / s if npl.det(Q) < 0: first_non_zero = np.nonzero(non_zero_ind)[0] Q[:, first_non_zero] = -Q[:, first_non_zero] s[first_non_zero] = -s[first_non_zero] return t, Q, s
[docs] def inv_TRS(A: np.ndarray): """Return the inverse of a TRS matrix. :param A: a 4x4 TRS matrix :type A: numpy.ndarray :return: inverted matrix of A of size 4x4 :rtype: numpy.ndarray """ t, r, s = TRS_from_matrix4(A) inv = np.identity(4) if np.any(np.isclose(s, 0)): raise TRSError("Can't invert TRS when there is 0 scaling") inv[:3, 3] = -(r.T @ t) / s inv[:3, :3] = (r / s).T return inv
[docs] def is_TRS(A: np.ndarray): """Test if the input matrix A is a 4D TRS matrix. This requires to test if A's linear part is the composition of a rotation and a scaling. If this is the case, the first three columns should be orthogonal. :param A: a 4x4 matrix :type A: numpy.ndarray :return: True if A is a TRS matrix, False otherwise :rtype: bool """ M = A[:3, :3].T @ A[:3, :3] return np.all(M == np.diag(np.diagonal(M)))
[docs] def global_to_local_matrix(mat_c, mat_p=None): """Return the local matrix of mat_c in the reference of mat_p. :param mat_c: a 4x4 matrix :type mat_c: numpy.ndarray :param mat_p: a 4x4 matrix :type mat_p: numpy.ndarray :return: the local matrix of mat_c in the reference of mat_p. :rtype: numpy.ndarray """ if mat_p is None: return global_to_local_matrix(mat_c, np.identity(4)) else: mat_c_np = mat4_to_numpy(mat_c) if not isinstance(mat_p, np.ndarray): mat_p_np = mat4_to_numpy(mat_p) else: mat_p_np = mat_p return inv_TRS(mat_p_np) @ mat_c_np