Shortcuts

Source code for pypose.lietensor.convert

import warnings
import torch
from .utils import SO3, so3
from .lietensor import LieTensor


from pypose.lietensor.lietensor import LieTensor, SE3_type, SO3_type, Sim3_type, RxSO3_type
from .utils import SO3, SE3, RxSO3, Sim3


[docs]def mat2SO3(mat, check=True, rtol=1e-5, atol=1e-5): r"""Convert batched rotation or transformation matrices to SO3Type LieTensor. Args: mat (Tensor): the batched matrices to convert. If input is of shape :obj:`(*, 3, 4)` or :obj:`(*, 4, 4)`, only the top left 3x3 submatrix is used. check (bool, optional): flag to check if the input is valid rotation matrices (orthogonal and with a determinant of one). Set to ``False`` if less computation is needed. Default: ``True``. rtol (float, optional): relative tolerance when check is enabled. Default: 1e-05 atol (float, optional): absolute tolerance when check is enabled. Default: 1e-05 Return: LieTensor: the converted SO3Type LieTensor. Shape: Input: :obj:`(*, 3, 3)` or :obj:`(*, 3, 4)` or :obj:`(*, 4, 4)` Output: :obj:`(*, 4)` Let the input be matrix :math:`\mathbf{R}`, :math:`\mathbf{R}_i` represents each individual matrix in the batch. :math:`\mathbf{R}^{m,n}_i` represents the :math:`m^{\mathrm{th}}` row and :math:`n^{\mathrm{th}}` column of :math:`\mathbf{R}_i`, :math:`m,n\geq 1`, then the quaternion can be computed by: .. math:: \left\{\begin{aligned} q^x_i &= \mathrm{sign}(\mathbf{R}^{2,3}_i - \mathbf{R}^{3,2}_i) \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^y_i &= \mathrm{sign}(\mathbf{R}^{3,1}_i - \mathbf{R}^{1,3}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^z_i &= \mathrm{sign}(\mathbf{R}^{1,2}_i - \mathbf{R}^{2,1}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i}\\ q^w_i &= \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i} \end{aligned}\right., In summary, the output LieTensor should be of format: .. math:: \textbf{y}_i = [q^x_i, q^y_i, q^z_i, q^w_i] Warning: Numerically, a transformation matrix is considered legal if: .. math:: |{\rm det}(\mathbf{R}) - 1| \leq \texttt{atol} + \texttt{rtol}\times 1\\ |\mathbf{RR}^{T} - \mathbf{I}| \leq \texttt{atol} + \texttt{rtol}\times \mathbf{I} where :math:`|\cdot |` is element-wise absolute function. When ``check`` is set to ``True``, illegal input will raise a ``ValueError``. Otherwise, no data validation is performed. Illegal input will output an irrelevant result, which likely contains ``nan``. Examples: >>> input = torch.tensor([[0., -1., 0.], ... [1., 0., 0.], ... [0., 0., 1.]]) >>> pp.mat2SO3(input) SO3Type LieTensor: tensor([0.0000, 0.0000, 0.7071, 0.7071]) See :meth:`pypose.SO3` for more details of the output LieTensor format. """ if not torch.is_tensor(mat): mat = torch.tensor(mat) if len(mat.shape) < 2: raise ValueError("Input size must be at least 2 dimensions. Got {}".format(mat.shape)) if not (mat.shape[-2:] == (3, 3) or mat.shape[-2:] == (3, 4) or mat.shape[-2:] == (4, 4)): raise ValueError("Input size must be a * x 3 x 3 or * x 3 x 4 or * x 4 x 4 tensor. \ Got {}".format(mat.shape)) mat = mat[..., :3, :3] shape = mat.shape with torch.no_grad(): if check: e0 = mat @ mat.mT e1 = torch.eye(3, dtype=mat.dtype, device=mat.device) if not torch.allclose(e0, e1.expand_as(e0), rtol=rtol, atol=atol): raise ValueError("Input rotation matrices are not all orthogonal matrix") ones = torch.ones(shape[:-2], dtype=mat.dtype, device=mat.device) if not torch.allclose(torch.det(mat), ones, rtol=rtol, atol=atol): raise ValueError("Input rotation matrices' determinant are not all equal to 1") rmat_t = mat.mT mask_d2 = rmat_t[..., 2, 2] < atol mask_d0_d1 = rmat_t[..., 0, 0] > rmat_t[..., 1, 1] mask_d0_nd1 = rmat_t[..., 0, 0] < -rmat_t[..., 1, 1] t0 = 1 + rmat_t[..., 0, 0] - rmat_t[..., 1, 1] - rmat_t[..., 2, 2] q0 = torch.stack([rmat_t[..., 1, 2] - rmat_t[..., 2, 1], t0, rmat_t[..., 0, 1] + rmat_t[..., 1, 0], rmat_t[..., 2, 0] + rmat_t[..., 0, 2]], -1) t0_rep = t0.unsqueeze(-1).repeat((len(list(shape))-2)*(1,)+(4,)) t1 = 1 - rmat_t[..., 0, 0] + rmat_t[..., 1, 1] - rmat_t[..., 2, 2] q1 = torch.stack([rmat_t[..., 2, 0] - rmat_t[..., 0, 2], rmat_t[..., 0, 1] + rmat_t[..., 1, 0], t1, rmat_t[..., 1, 2] + rmat_t[..., 2, 1]], -1) t1_rep = t1.unsqueeze(-1).repeat((len(list(shape))-2)*(1,)+(4,)) t2 = 1 - rmat_t[..., 0, 0] - rmat_t[..., 1, 1] + rmat_t[..., 2, 2] q2 = torch.stack([rmat_t[..., 0, 1] - rmat_t[..., 1, 0], rmat_t[..., 2, 0] + rmat_t[..., 0, 2], rmat_t[..., 1, 2] + rmat_t[..., 2, 1], t2], -1) t2_rep = t2.unsqueeze(-1).repeat((len(list(shape))-2)*(1,)+(4,)) t3 = 1 + rmat_t[..., 0, 0] + rmat_t[..., 1, 1] + rmat_t[..., 2, 2] q3 = torch.stack([t3, rmat_t[..., 1, 2] - rmat_t[..., 2, 1], rmat_t[..., 2, 0] - rmat_t[..., 0, 2], rmat_t[..., 0, 1] - rmat_t[..., 1, 0]], -1) t3_rep = t3.unsqueeze(-1).repeat((len(list(shape))-2)*(1,)+(4,)) mask_c0 = mask_d2 * mask_d0_d1 mask_c1 = mask_d2 * ~mask_d0_d1 mask_c2 = ~mask_d2 * mask_d0_nd1 mask_c3 = ~mask_d2 * ~mask_d0_nd1 mask_c0 = mask_c0.unsqueeze(-1).type_as(q0) mask_c1 = mask_c1.unsqueeze(-1).type_as(q1) mask_c2 = mask_c2.unsqueeze(-1).type_as(q2) mask_c3 = mask_c3.unsqueeze(-1).type_as(q3) q = q0 * mask_c0 + q1 * mask_c1 + q2 * mask_c2 + q3 * mask_c3 q /= 2*torch.sqrt(t0_rep * mask_c0 + t1_rep * mask_c1 + # noqa t2_rep * mask_c2 + t3_rep * mask_c3) # noqa q = q.view(shape[:-2]+(4,)) # wxyz -> xyzw q = q.index_select(-1, torch.tensor([1, 2, 3, 0], device=q.device)) return SO3(q)
[docs]def mat2SE3(mat, check=True, rtol=1e-5, atol=1e-5): r"""Convert batched rotation or transformation matrices to SE3Type LieTensor. Args: mat (Tensor): the batched matrices to convert. If input is of shape :obj:`(*, 3, 3)`, then translation will be filled with zero. For input with shape :obj:`(*, 3, 4)`, the last row will be treated as ``[0, 0, 0, 1]``. check (bool, optional): flag to check if the input is valid rotation matrices (orthogonal and with a determinant of one). Set to ``False`` if less computation is needed. Default: ``True``. rtol (float, optional): relative tolerance when check is enabled. Default: 1e-05 atol (float, optional): absolute tolerance when check is enabled. Default: 1e-05 Return: LieTensor: the converted SE3Type LieTensor. Shape: Input: :obj:`(*, 3, 3)` or :obj:`(*, 3, 4)` or :obj:`(*, 4, 4)` Output: :obj:`(*, 7)` Let the input be matrix :math:`\mathbf{T}`, :math:`\mathbf{T}_i` represents each individual matrix in the batch. :math:`\mathbf{R}_i\in\mathbb{R}^{3\times 3}` be the top left 3x3 block matrix of :math:`\mathbf{T}_i`. :math:`\mathbf{T}^{m,n}_i` represents the :math:`m^{\mathrm{th}}` row and :math:`n^{\mathrm{th}}` column of :math:`\mathbf{T}_i`, :math:`m,n\geq 1`, then the translation and quaternion can be computed by: .. math:: \left\{\begin{aligned} t^x_i &= \mathbf{T}^{1,4}_i\\ t^y_i &= \mathbf{T}^{2,4}_i\\ t^z_i &= \mathbf{T}^{3,4}_i\\ q^x_i &= \mathrm{sign}(\mathbf{R}^{2,3}_i - \mathbf{R}^{3,2}_i) \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^y_i &= \mathrm{sign}(\mathbf{R}^{3,1}_i - \mathbf{R}^{1,3}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^z_i &= \mathrm{sign}(\mathbf{R}^{1,2}_i - \mathbf{R}^{2,1}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i}\\ q^w_i &= \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i} \end{aligned}\right., In summary, the output LieTensor should be of format: .. math:: \textbf{y}_i = [t^x_i, t^y_i, t^z_i, q^x_i, q^y_i, q^z_i, q^w_i] Warning: Numerically, a transformation matrix is considered legal if: .. math:: |{\rm det}(\mathbf{R}) - 1| \leq \texttt{atol} + \texttt{rtol}\times 1\\ |\mathbf{RR}^{T} - \mathbf{I}| \leq \texttt{atol} + \texttt{rtol}\times \mathbf{I} where :math:`|\cdot |` is element-wise absolute function. When ``check`` is set to ``True``, illegal input will raise a ``ValueError``. Otherwise, no data validation is performed. Illegal input will output an irrelevant result, which likely contains ``nan``. For input with shape :obj:`(*, 4, 4)`, when ``check`` is set to ``True`` and the last row of the each individual matrix is not ``[0, 0, 0, 1]``, a warning will be triggered. Even though the last row is not used in the computation, it is worth noting that a matrix not satisfying this condition is not a valid transformation matrix. Examples: >>> input = torch.tensor([[0., -1., 0., 0.1], ... [1., 0., 0., 0.2], ... [0., 0., 1., 0.3], ... [0., 0., 0., 1.]]) >>> pp.mat2SE3(input) SE3Type LieTensor: tensor([0.1000, 0.2000, 0.3000, 0.0000, 0.0000, 0.7071, 0.7071]) Note: The individual matrix in a batch can be written as: .. math:: \begin{bmatrix} \mathbf{R}_{3\times3} & \mathbf{t}_{3\times1}\\ \textbf{0} & 1 \end{bmatrix}, where :math:`\mathbf{R}` is the rotation matrix. The translation vector :math:`\mathbf{t}` defines the displacement between the original position and the transformed position. See :meth:`pypose.SE3` for more details of the output LieTensor format. """ if not torch.is_tensor(mat): mat = torch.tensor(mat) if len(mat.shape) < 2: raise ValueError("Input size must be at least 2 dimensions. Got {}".format(mat.shape)) if not (mat.shape[-2:] == (3, 3) or mat.shape[-2:] == (3, 4) or mat.shape[-2:] == (4, 4)): raise ValueError("Input size must be a * x 3 x 3 or * x 3 x 4 or * x 4 x 4 tensor. \ Got {}".format(mat.shape)) shape = mat.shape if shape[-2:] == (4, 4) and check == True: zerosone = torch.tensor([0, 0, 0, 1], dtype=mat.dtype, device=mat.device) if not torch.allclose(mat[..., 3, :], zerosone.expand_as(mat[..., 3, :]), rtol=rtol, atol=atol): warnings.warn("input of shape 4x4 last rows are not all equal [0, 0, 0, 1]") q = mat2SO3(mat[..., :3, :3], check=check, rtol=rtol, atol=atol).tensor() if shape[-1] == 3: t = torch.zeros(shape[:-2]+(3,), dtype=mat.dtype, device=mat.device, requires_grad=mat.requires_grad) else: t = mat[..., :3, 3] vec = torch.cat([t, q], dim=-1) return SE3(vec)
[docs]def mat2Sim3(mat, check=True, rtol=1e-5, atol=1e-5): r"""Convert batched rotation or transformation matrices to Sim3Type LieTensor. Args: mat (Tensor): the batched matrices to convert. If input is of shape :obj:`(*, 3, 3)`, then translation will be filled with zero. For input with shape :obj:`(*, 3, 4)`, the last row will be treated as ``[0, 0, 0, 1]``. check (bool, optional): flag to check if the input is valid rotation matrices (orthogonal and with a determinant of one). Set to ``False`` if less computation is needed. Default: ``True``. rtol (float, optional): relative tolerance when check is enabled. Default: 1e-05 atol (float, optional): absolute tolerance when check is enabled. Default: 1e-05 Return: LieTensor: the converted Sim3Type LieTensor. Shape: Input: :obj:`(*, 3, 3)` or :obj:`(*, 3, 4)` or :obj:`(*, 4, 4)` Output: :obj:`(*, 8)` Let the input be matrix :math:`\mathbf{T}`, :math:`\mathbf{T}_i` represents each individual matrix in the batch. :math:`\mathbf{U}_i\in\mathbb{R}^{3\times 3}` be the top left 3x3 block matrix of :math:`\mathbf{T}_i`. Let :math:`\mathbf{T}^{m,n}_i` represents the :math:`m^{\mathrm{th}}` row and :math:`n^{\mathrm{th}}` column of :math:`\mathbf{T}_i`, :math:`m,n\geq 1`, then the scaling factor :math:`s_i\in\mathbb{R}` and the rotation matrix :math:`\mathbf{R}_i\in\mathbb{R}^{3\times 3}` can be computed as: .. math:: \begin{aligned} s_i &= \sqrt[3]{\vert \mathbf{U}_i \vert}\\ \mathbf{R}_i &= \mathbf{U}_i/s_i \end{aligned} the translation and quaternion can be computed by: .. math:: \left\{\begin{aligned} t^x_i &= \mathbf{T}^{1,4}_i\\ t^y_i &= \mathbf{T}^{2,4}_i\\ t^z_i &= \mathbf{T}^{3,4}_i\\ q^x_i &= \mathrm{sign}(\mathbf{R}^{2,3}_i - \mathbf{R}^{3,2}_i) \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^y_i &= \mathrm{sign}(\mathbf{R}^{3,1}_i - \mathbf{R}^{1,3}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^z_i &= \mathrm{sign}(\mathbf{R}^{1,2}_i - \mathbf{R}^{2,1}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i}\\ q^w_i &= \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i} \end{aligned}\right., In summary, the output LieTensor should be of format: .. math:: \textbf{y}_i = [t^x_i, t^y_i, t^z_i, q^x_i, q^y_i, q^z_i, q^w_i, s_i] Warning: Numerically, a transformation matrix is considered legal if: .. math:: \vert s \vert > \texttt{atol} \\ |{\rm det}(\mathbf{R}) - 1| \leq \texttt{atol} + \texttt{rtol}\times 1\\ |\mathbf{RR}^{T} - \mathbf{I}| \leq \texttt{atol} + \texttt{rtol}\times \mathbf{I} where :math:`|\cdot |` is element-wise absolute function. When ``check`` is set to ``True``, illegal input will raise a ``ValueError``. Otherwise, no data validation is performed. Illegal input will output an irrelevant result, which likely contains ``nan``. For input with shape :obj:`(*, 4, 4)`, when ``check`` is set to ``True`` and the last row of the each individual matrix is not ``[0, 0, 0, 1]``, a warning will be triggered. Even though the last row is not used in the computation, it is worth noting that a matrix not satisfying this condition is not a valid transformation matrix. Examples: >>> input = torch.tensor([[ 0.,-0.5, 0., 0.1], ... [0.5, 0., 0., 0.2], ... [ 0., 0., 0.5, 0.3], ... [ 0., 0., 0., 1.]]) >>> pp.mat2Sim3(input) Sim3Type LieTensor: tensor([0.1000, 0.2000, 0.3000, 0.0000, 0.0000, 0.7071, 0.7071, 0.5000]) Note: We follow the convention below to express Sim3: .. math:: \begin{bmatrix} s\mathbf{R}_{3\times3} & \mathbf{t}_{3\times1}\\ \textbf{0} & 1 \end{bmatrix}, referred to this paper: * J. Sola et al., `A micro Lie theory for state estimation in robotics <https://arxiv.org/abs/1812.01537>`_, arXiv preprint arXiv:1812.01537 (2018), where :math:`\mathbf{R}` is the individual matrix in a batch. The scaling factor :math:`s` defines a linear transformation that enlarges or diminishes the object in the same ratio across 3 dimensions, the translation vector :math:`\mathbf{t}` defines the displacement between the original position and the transformed position. We also notice that there is another popular convention: .. math:: \begin{bmatrix} \mathbf{R}_{3\times3} & \mathbf{t}_{3\times1}\\ \textbf{0} & 1/s \end{bmatrix}, referred to this tutorial: * `Lie Groups for 2D and 3D Transformations. <https://www.ethaneade.org/lie.pdf>`_, by Ethan Eade. Please make sure your own convention before using this function. See :meth:`pypose.Sim3` for more details of the output LieTensor format. """ if not torch.is_tensor(mat): mat = torch.tensor(mat) if len(mat.shape) < 2: raise ValueError("Input size must be at least 2 dimensions. Got {}".format(mat.shape)) if not (mat.shape[-2:] == (3, 3) or mat.shape[-2:] == (3, 4) or mat.shape[-2:] == (4, 4)): raise ValueError("Input size must be a * x 3 x 3 or * x 3 x 4 or * x 4 x 4 tensor. \ Got {}".format(mat.shape)) shape = mat.shape if shape[-2:] == (4, 4) and check == True: zerosone = torch.tensor([0, 0, 0, 1], dtype=mat.dtype, device=mat.device) if not torch.allclose(mat[..., 3, :], zerosone.expand_as(mat[..., 3, :]), rtol=rtol, atol=atol): warnings.warn("Input of shape 4x4 last rows are not all equal [0, 0, 0, 1]") shape = mat.shape rot = mat[..., :3, :3] s = torch.pow(torch.det(mat), 1/3).unsqueeze(-1) zeros = torch.zeros(shape[:-2], dtype=mat.dtype, device=mat.device) if torch.allclose(s, zeros, rtol=rtol, atol=atol): raise ValueError("Rotation matrix not full rank.") q = mat2SO3(rot/s.unsqueeze(-1), check=check, rtol=rtol, atol=atol).tensor() if mat.shape[-1] == 3: t = torch.zeros(mat.shape[:-2]+(3,), dtype=mat.dtype, device=mat.device, requires_grad=mat.requires_grad) else: t = mat[..., :3, 3] vec = torch.cat([t, q, s], dim=-1) return Sim3(vec)
[docs]def mat2RxSO3(mat, check=True, rtol=1e-5, atol=1e-5): r"""Convert batched rotation or transformation matrices to RxSO3Type LieTensor. Args: mat (Tensor): the batched matrices to convert. If input is of shape :obj:`(*, 3, 4)` or :obj:`(*, 4, 4)`, only the top left 3x3 submatrix is used. check (bool, optional): flag to check if the input is valid rotation matrices (orthogonal and with a determinant of one). Set to ``False`` if less computation is needed. Default: ``True``. rtol (float, optional): relative tolerance when check is enabled. Default: 1e-05 atol (float, optional): absolute tolerance when check is enabled. Default: 1e-05 Return: LieTensor: the converted RxSO3Type LieTensor. Shape: Input: :obj:`(*, 3, 3)` or :obj:`(*, 3, 4)` or :obj:`(*, 4, 4)` Output: :obj:`(*, 5)` Let the input be matrix :math:`\mathbf{T}`, :math:`\mathbf{T}_i` represents each individual matrix in the batch. :math:`\mathbf{T}^{m,n}_i` represents the :math:`m^{\mathrm{th}}` row and :math:`n^{\mathrm{th}}` column of :math:`\mathbf{T}_i`, :math:`m,n\geq 1`, then the scaling factor :math:`s_i\in\mathbb{R}` and the rotation matrix :math:`\mathbf{R}_i\in\mathbb{R}^{3\times 3}` can be computed as: .. math:: \begin{aligned} s_i &= \sqrt[3]{\vert \mathbf{T_i} \vert}\\ \mathbf{R}_i &= \mathbf{R}_i/s_i \end{aligned}, the translation and quaternion can be computed by: .. math:: \left\{\begin{aligned} q^x_i &= \mathrm{sign}(\mathbf{R}^{2,3}_i - \mathbf{R}^{3,2}_i) \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^y_i &= \mathrm{sign}(\mathbf{R}^{3,1}_i - \mathbf{R}^{1,3}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i - \mathbf{R}^{3,3}_i}\\ q^z_i &= \mathrm{sign}(\mathbf{R}^{1,2}_i - \mathbf{R}^{2,1}_i) \frac{1}{2} \sqrt{1 - \mathbf{R}^{1,1}_i - \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i}\\ q^w_i &= \frac{1}{2} \sqrt{1 + \mathbf{R}^{1,1}_i + \mathbf{R}^{2,2}_i + \mathbf{R}^{3,3}_i} \end{aligned}\right., In summary, the output LieTensor should be of format: .. math:: \textbf{y}_i = [q^x_i, q^y_i, q^z_i, q^w_i, s_i] Warning: Numerically, a transformation matrix is considered legal if: .. math:: \vert s \vert > \texttt{atol} \\ |{\rm det}(\mathbf{R}) - 1| \leq \texttt{atol} + \texttt{rtol}\times 1\\ |\mathbf{RR}^{T} - \mathbf{I}| \leq \texttt{atol} + \texttt{rtol}\times \mathbf{I} where :math:`|\cdot |` is element-wise absolute function. When ``check`` is set to ``True``, illegal input will raise a ``ValueError``. Otherwise, no data validation is performed. Illegal input will output an irrelevant result, which likely contains ``nan``. Examples: >>> input = torch.tensor([[ 0., -0.5, 0.], ... [0.5, 0., 0.], ... [ 0., 0., 0.5]]) >>> pp.mat2RxSO3(input) RxSO3Type LieTensor: tensor([0.0000, 0.0000, 0.7071, 0.7071, 0.5000]) Note: The individual matrix in a batch can be written as: :math:`s\mathbf{R}_{3\times3}`, where :math:`\mathbf{R}` is the rotation matrix. where the scaling factor :math:`s` defines a linear transformation that enlarges or diminishes the object in the same ratio across 3 dimensions. See :meth:`pypose.RxSO3` for more details of the output LieTensor format. """ if not torch.is_tensor(mat): mat = torch.tensor(mat) if len(mat.shape) < 2: raise ValueError("Input size must be at least 2 dimensions. Got {}".format(mat.shape)) if not (mat.shape[-2:] == (3, 3) or mat.shape[-2:] == (3, 4) or mat.shape[-2:] == (4, 4)): raise ValueError("Input size must be a * x 3 x 3 or * x 3 x 4 or * x 4 x 4 tensor. \ Got {}".format(mat.shape)) shape = mat.shape rot = mat[..., :3, :3] s = torch.pow(torch.det(mat), 1/3).unsqueeze(-1) if torch.allclose(s, torch.zeros(shape[:-2], dtype=mat.dtype, device=mat.device), rtol=rtol, atol=atol): raise ValueError("Rotation matrix not full rank.") q = mat2SO3(rot/s.unsqueeze(-1), check=check, rtol=rtol, atol=atol).tensor() vec = torch.cat([q, s], dim=-1) return RxSO3(vec)
[docs]def from_matrix(mat, ltype, check=True, rtol=1e-5, atol=1e-5): r"""Convert batched rotation or transformation matrices to LieTensor. Args: mat (Tensor): the matrix to convert. ltype (ltype): specify the LieTensor type, chosen from :class:`pypose.SO3_type`, :class:`pypose.SE3_type`, :class:`pypose.Sim3_type`, or :class:`pypose.RxSO3_type`. See more details in :meth:`LieTensor` check (bool, optional): flag to check if the input is valid rotation matrices (orthogonal and with a determinant of one). Set to ``False`` if less computation is needed. Default: ``True``. rtol (float, optional): relative tolerance when check is enabled. Default: 1e-05 atol (float, optional): absolute tolerance when check is enabled. Default: 1e-05 Warning: Numerically, a transformation matrix is considered legal if: .. math:: |{\rm det}(\mathbf{R}) - 1| \leq \texttt{atol} + \texttt{rtol}\times 1\\ |\mathbf{RR}^{T} - \mathbf{I}| \leq \texttt{atol} + \texttt{rtol}\times \mathbf{I} where :math:`|\cdot |` is element-wise absolute function. When ``check`` is set to ``True``, illegal input will raise a ``ValueError``. Otherwise, no data validation is performed. Illegal input will output an irrelevant result, which likely contains ``nan``. Return: LieTensor: the converted LieTensor. Examples: - :class:`pypose.SO3_type` >>> pp.from_matrix(torch.tensor([[0., -1., 0.], ... [1., 0., 0.], ... [0., 0., 1.]]), ltype=pp.SO3_type) SO3Type LieTensor: tensor([0.0000, 0.0000, 0.7071, 0.7071]) - :class:`pypose.SE3_type` >>> pp.from_matrix(torch.tensor([[0., -1., 0., 0.1], ... [1., 0., 0., 0.2], ... [0., 0., 1., 0.3], ... [0., 0., 0., 1.]]), ltype=pp.SE3_type) SE3Type LieTensor: tensor([0.1000, 0.2000, 0.3000, 0.0000, 0.0000, 0.7071, 0.7071]) - :class:`pypose.Sim3_type` >>> pp.from_matrix(torch.tensor([[ 0.,-0.5, 0., 0.1], ... [0.5, 0., 0., 0.2], ... [ 0., 0., 0.5, 0.3], ... [ 0., 0., 0., 1.]]), ltype=pp.Sim3_type) Sim3Type LieTensor: tensor([0.1000, 0.2000, 0.3000, 0.0000, 0.0000, 0.7071, 0.7071, 0.5000]) - :class:`pypose.RxSO3_type` >>> pp.from_matrix(torch.tensor([[0., -0.5, 0.], ... [0.5, 0., 0.], ... [0., 0., 0.5]]), ltype=pp.RxSO3_type) RxSO3Type LieTensor: tensor([0.0000, 0.0000, 0.7071, 0.7071, 0.5000]) """ if not torch.is_tensor(mat): mat = torch.tensor(mat) if len(mat.shape) < 2: raise ValueError("Input size must be at least 2 dimensions. Got {}".format(mat.shape)) if not (mat.shape[-2:] == (3, 3) or mat.shape[-2:] == (3, 4) or mat.shape[-2:] == (4, 4)): raise ValueError("Input size must be a * x 3 x 3 or * x 3 x 4 or * x 4 x 4 tensor. \ Got {}".format(mat.shape)) if ltype == SO3_type: return mat2SO3(mat, check=check, rtol=rtol, atol=atol) elif ltype == SE3_type: return mat2SE3(mat, check=check, rtol=rtol, atol=atol) elif ltype == Sim3_type: return mat2Sim3(mat, check=check, rtol=rtol, atol=atol) elif ltype == RxSO3_type: return mat2RxSO3(mat, check=check, rtol=rtol, atol=atol) else: raise ValueError("Input ltype must be one of SO3_type, SE3_type, Sim3_type or RxSO3_type.\ Got {}".format(ltype))
def matrix(lietensor): assert isinstance(lietensor, LieTensor) return lietensor.matrix()
[docs]def euler2SO3(euler: torch.Tensor): r"""Convert batched Euler angles (roll, pitch, and yaw) to SO3Type LieTensor. Args: euler (Tensor): the euler angles in radians to convert. Return: LieTensor: the converted SO3Type LieTensor. Shape: Input: :obj:`(*, 3)` Output: :obj:`(*, 4)` .. math:: {\displaystyle \mathbf{y}_i={ \begin{bmatrix}\, \sin(\alpha_i)\cos(\beta_i)\cos(\gamma_i) - \cos(\alpha_i)\sin(\beta_i)\sin(\gamma_i)\\ \cos(\alpha_i)\sin(\beta_i)\cos(\gamma_i) + \sin(\alpha_i)\cos(\beta_i)\sin(\gamma_i)\\ \cos(\alpha_i)\cos(\beta_i)\sin(\gamma_i) - \sin(\alpha_i)\sin(\beta_i)\cos(\gamma_i)\\ \cos(\alpha_i)\cos(\beta_i)\cos(\gamma_i) + \sin(\alpha_i)\sin(\beta_i)\sin(\gamma_i) \end{bmatrix}}}, where the :math:`i`-th item of input :math:`\mathbf{x}_i = [\alpha_i, \beta_i, \gamma_i]` are roll, pitch, and yaw, respectively. Note: The last dimension of the input tensor has to be 3. The Euler angle takes the rotation sequence of x (roll), y (pitch), then z (yaw) axis (counterclockwise). Warning: Any given rotation has two possible quaternion representations. If one is known, the other is just the negative of all four terms. This function only returns one of them. Examples: >>> input = torch.randn(2, 3, requires_grad=True, dtype=torch.float64) >>> pp.euler2SO3(input) SO3Type LieTensor: tensor([[-0.4873, 0.1162, 0.4829, 0.7182], [ 0.3813, 0.4059, -0.2966, 0.7758]], dtype=torch.float64, grad_fn=<AliasBackward0>) See :obj:`euler` for more information. """ if not torch.is_tensor(euler): euler = torch.tensor(euler) assert euler.shape[-1] == 3 shape, euler = euler.shape, euler.view(-1, 3) roll, pitch, yaw = euler[:, 0], euler[:, 1], euler[:, 2] cy, sy = (yaw * 0.5).cos(), (yaw * 0.5).sin() cp, sp = (pitch * 0.5).cos(), (pitch * 0.5).sin() cr, sr = (roll * 0.5).cos(), (roll * 0.5).sin() q = torch.stack([sr * cp * cy - cr * sp * sy, cr * sp * cy + sr * cp * sy, cr * cp * sy - sr * sp * cy, cr * cp * cy + sr * sp * sy], dim=-1) return SO3(q).lview(*shape[:-1])
[docs]def tensor(inputs): r''' Convert a :obj:`LieTensor` into a :obj:`torch.Tensor` without changing data. Args: inputs (:obj:`LieTensor`): the input LieTensor. Return: Tensor: the torch.Tensor form of LieTensor. Example: >>> x = pp.randn_SO3(2) >>> x.tensor() tensor([[ 0.1196, 0.2339, -0.6824, 0.6822], [ 0.9198, -0.2704, -0.2395, 0.1532]]) ''' return inputs.tensor()
[docs]def translation(inputs): r''' Extract the translation part from a :obj:`LieTensor`. Args: inputs (:obj:`LieTensor`): the input LieTensor. Return: Tensor: the batched translation vectors. Warning: The :obj:`SO3`, :obj:`so3`, :obj:`RxSO3`, and :obj:`rxso3` types do not contain translation. Calling :obj:`translation()` on these types will return zero vector(s). Example: >>> x = pp.randn_SE3(2) >>> x.translation() tensor([[-0.5358, -1.5421, -0.7224], [ 0.8331, -1.4412, 0.0863]]) >>> y = pp.randn_SO3(2) >>> y.translation() tensor([[0., 0., 0.], [0., 0., 0.]]) ''' return inputs.translation()
[docs]def rotation(inputs): r''' Extract the rotation part from a :obj:`LieTensor`. Args: inputs (:obj:`LieTensor`): the input LieTensor. Return: SO3: the batched quaternions. Example: >>> x = pp.randn_SE3(2) >>> x.rotation() SO3Type LieTensor: tensor([[-0.8302, 0.5200, -0.0056, 0.2006], [-0.2541, -0.3184, 0.6305, 0.6607]]) ''' return inputs.rotation()
[docs]def scale(inputs): r''' Extract the scale part from a :obj:`LieTensor`. Args: inputs (:obj:`LieTensor`): the input LieTensor. Return: Tensor: the batched scale scalars. Warning: The :obj:`SO3`, :obj:`so3`, :obj:`SE3`, and :obj:`se3` types do not contain scale. Calling :obj:`scale()` on these types will return one(s). Example: >>> x = pp.randn_Sim3(4) >>> x.scale() tensor([[10.9577], [ 1.0248], [ 0.0947], [ 1.1989]]) >>> y = pp.randn_SE3(2) >>> y.scale() tensor([[1.], [1.]]) ''' return inputs.scale()
[docs]def matrix(inputs): r''' Convert a :obj:`LieTensor` into matrix form. Args: inputs (:obj:`LieTensor`): the input LieTensor. Return: Tensor: the batched matrix form (torch.Tensor) of LieTensor. Example: >>> x = pp.randn_SO3(2) >>> x.matrix() tensor([[[ 0.9285, -0.0040, -0.3713], [ 0.2503, 0.7454, 0.6178], [ 0.2743, -0.6666, 0.6931]], [[ 0.4805, 0.8602, -0.1706], [-0.7465, 0.2991, -0.5944], [-0.4603, 0.4130, 0.7858]]]) ''' return inputs.matrix()
[docs]def euler(inputs, eps=2e-4): r''' Convert batched LieTensor into Euler angles (roll, pitch, yaw). Args: inputs (:obj:`LieTensor`): the input LieTensor. eps (:obj:`float`, optional): the threshold to avoid the sigularity caused by gimbal lock. Default: 2e-4. Return: :obj:`Tensor`: the batched Euler angles in radians. Supported input type: :obj:`so3`, :obj:`SO3`, :obj:`se3`, :obj:`SE3`, :obj:`sim3`, :obj:`Sim3`, :obj:`rxso3`, and :obj:`RxSO3`. Warning: The Euler angle takes the rotation sequence of x (roll), y (pitch), and z (yaw) axis (counterclockwise). There is always more than one solution that can result in the same orientation, while this function only returns one of them. When the pitch angle is around :math:`\pm \frac{\pi}{2}` (north/south pole), there will be sigularity problem due to the `gimbal lock <https://en.wikipedia.org/wiki/Gimbal_lock>`_ and some information can be found in `this pape <https://tinyurl.com/py8frs6v>`_. Example: >>> x = pp.randn_SO3() >>> x.euler() # equivalent to pp.euler(x) tensor([-0.6599, 0.2749, -0.3263]) >>> x = pp.randn_Sim3(2) >>> x.euler() # equivalent to pp.euler(x) tensor([[-0.2701, -0.8006, -0.4150], [ 2.1550, 0.1768, 0.9368]]) >>> x = pp.randn_rxso3() >>> x.euler() # equivalent to pp.euler(x) tensor([ 1.2676, -0.4783, -0.3596]) See :obj:`euler2SO3` for more information. ''' return inputs.euler(eps=eps)

Docs

Access documentation for PyPose

View Docs

Tutorials

Get started with tutorials and examples

View Tutorials

Get Started

Find resources and how to start using pypose

View Resources