From b08383671d609caeb22608ff397fcd0fc69623f8 Mon Sep 17 00:00:00 2001 From: edgarriba Date: Thu, 21 Feb 2019 19:36:03 +0100 Subject: [PATCH 1/8] first commit to refactor homogeneous transforms module --- test/test_transformations.py | 40 ++++--- torchgeometry/core/depth_warper.py | 4 +- torchgeometry/core/pinhole.py | 4 +- torchgeometry/core/transformations.py | 148 +++++++++++++++++--------- 4 files changed, 127 insertions(+), 69 deletions(-) diff --git a/test/test_transformations.py b/test/test_transformations.py index c98bab805b..c4b1d0c8e7 100644 --- a/test/test_transformations.py +++ b/test/test_transformations.py @@ -8,7 +8,23 @@ from common import TEST_DEVICES -class TestTransformPose: +class TestComposeTransforms: + + def _identity_matrix(self, batch_size): + return torch.eye(4).repeat(batch_size, 1, 1) # Nx4x4 + + def test_compose_transforms(self): + batch_size = 1 + trans_1 = self._identity_matrix(batch_size) + trans_2 = self._identity_matrix(batch_size) + trans_2[..., :3, -1] += 10 # add offset to translation vector + + trans_21 = tgm.compose_transformations(trans_1, trans_2) + assert utils.check_equal_torch(trans_21, trans_2) + + + +class TestRelativeTransform: def _generate_identity_matrix(self, batch_size, device_type): eye = torch.eye(4).repeat(batch_size, 1, 1) # Nx4x4 @@ -17,7 +33,7 @@ def _generate_identity_matrix(self, batch_size, device_type): def _test_identity(self): pose_1 = self.pose_1.clone() pose_2 = self.pose_2.clone() - pose_21 = tgm.relative_pose(pose_1, pose_2) + pose_21 = tgm.relative_transformation(pose_1, pose_2) assert utils.check_equal_torch(pose_21, torch.eye(4).unsqueeze(0)) def _test_translation(self): @@ -27,7 +43,7 @@ def _test_translation(self): pose_2[..., :3, -1:] += offset # add translation # compute relative pose - pose_21 = tgm.relative_pose(pose_1, pose_2) + pose_21 = tgm.relative_transformation(pose_1, pose_2) assert utils.check_equal_torch(pose_21[..., :3, -1:], offset) def _test_rotation(self): @@ -39,7 +55,7 @@ def _test_rotation(self): pose_2[..., 3, 3] = 1.0 # compute relative pose - pose_21 = tgm.relative_pose(pose_1, pose_2) + pose_21 = tgm.relative_transformation(pose_1, pose_2) assert utils.check_equal_torch(pose_21, pose_2) def _test_integration(self): @@ -51,7 +67,7 @@ def _test_integration(self): pose_2[..., :3, :3] = torch.rand(batch_size, 3, 3, device=device) pose_2[..., :3, -1:] = torch.rand(batch_size, 3, 1, device=device) - pose_21 = tgm.relative_pose(pose_1, pose_2) + pose_21 = tgm.relative_transformation(pose_1, pose_2) assert utils.check_equal_torch( torch.matmul(pose_21, pose_1), pose_2) @@ -60,9 +76,9 @@ def test_jit(self): pose_1 = self.pose_1.clone() pose_2 = self.pose_2.clone() - pose_21 = tgm.relative_pose(pose_1, pose_2) + pose_21 = tgm.relative_transformation(pose_1, pose_2) pose_21_jit = torch.jit.trace( - tgm.relative_pose, (pose_1, pose_2,))(pose_1, pose_2) + tgm.relative_transformation, (pose_1, pose_2,))(pose_1, pose_2) assert utils.check_equal_torch(pose_21, pose_21_jit) def _test_gradcheck(self): @@ -71,7 +87,7 @@ def _test_gradcheck(self): pose_1 = utils.tensor_to_gradcheck_var(pose_1) # to var pose_2 = utils.tensor_to_gradcheck_var(pose_2) # to var - assert gradcheck(tgm.relative_pose, (pose_1, pose_2,), + assert gradcheck(tgm.relative_transformation, (pose_1, pose_2,), raise_exception=True) @pytest.mark.parametrize("device_type", TEST_DEVICES) @@ -83,11 +99,11 @@ def test_run_all(self, batch_size, device_type): self.pose_2 = self.pose_1.clone() # run tests - self._test_identity() + #self._test_identity() self._test_translation() - self._test_rotation() - self._test_integration() - self._test_gradcheck() + #self._test_rotation() + #self._test_integration() + #self._test_gradcheck() # TODO: embedd to a class diff --git a/torchgeometry/core/depth_warper.py b/torchgeometry/core/depth_warper.py index cb3e67f8a3..63c7e89f66 100644 --- a/torchgeometry/core/depth_warper.py +++ b/torchgeometry/core/depth_warper.py @@ -3,7 +3,7 @@ import torch.nn as nn import torch.nn.functional as F -from torchgeometry.core.transformations import relative_pose +from torchgeometry.core.transformations import relative_transformation from torchgeometry.core.conversions import transform_points from torchgeometry.core.conversions import convert_points_to_homogeneous from torchgeometry.core.pinhole import PinholeCamera, PinholeCamerasList @@ -80,7 +80,7 @@ def compute_projection_matrix( extrinsics_src = extrinsics_src.contiguous().view(-1, 4, 4) # (B*N)x4x4 extrinsics_dst: torch.Tensor = self._pinhole_dst.extrinsics.view( -1, 4, 4) # (B*N)x4x4 - dst_trans_src: torch.Tensor = relative_pose( + dst_trans_src: torch.Tensor = relative_transformation( extrinsics_src, extrinsics_dst) # compute the projection matrix between the non reference cameras and diff --git a/torchgeometry/core/pinhole.py b/torchgeometry/core/pinhole.py index 2937a07346..d999c5f9f3 100644 --- a/torchgeometry/core/pinhole.py +++ b/torchgeometry/core/pinhole.py @@ -4,7 +4,7 @@ import torch import torch.nn as nn -from torchgeometry.core.transformations import inverse_pose +from torchgeometry.core.transformations import inverse_transformation from torchgeometry.core.conversions import rtvec_to_pose, transform_points @@ -558,7 +558,7 @@ def homography_i_H_ref(pinhole_i, pinhole_ref): assert pinhole_i.shape == pinhole_ref.shape, pinhole_ref.shape i_pose_base = get_optical_pose_base(pinhole_i) ref_pose_base = get_optical_pose_base(pinhole_ref) - i_pose_ref = torch.matmul(i_pose_base, inverse_pose(ref_pose_base)) + i_pose_ref = torch.matmul(i_pose_base, inverse_transformation(ref_pose_base)) return torch.matmul( pinhole_matrix(pinhole_i), torch.matmul(i_pose_ref, inverse_pinhole_matrix(pinhole_ref))) diff --git a/torchgeometry/core/transformations.py b/torchgeometry/core/transformations.py index 887b894145..0a1abd038f 100644 --- a/torchgeometry/core/transformations.py +++ b/torchgeometry/core/transformations.py @@ -5,17 +5,53 @@ __all__ = [ - "inverse_pose", - "relative_pose", - "InversePose", + "compose_transformations", + "inverse_transformation", + "relative_transformation", ] -def inverse_pose(pose, eps=1e-6): - r"""Function that inverts a 4x4 pose :math:`P = - \begin{bmatrix} R & t \\ \mathbf{0} & 1 \end{bmatrix}` +def compose_transformations( + trans_1: torch.Tensor, trans_2: torch.Tensor, + eps: Optional[float] = 1e-6) -> torch.Tensor: + r"""Functions that composes two homogeneous transformations.""" + if not torch.is_tensor(trans_1): + raise TypeError("Input trans_1 type is not a torch.Tensor. Got {}" + .format(type(trans_1))) + if not torch.is_tensor(trans_2): + raise TypeError("Input trans_2 type is not a torch.Tensor. Got {}" + .format(type(trans_2))) + if not (len(trans_1.shape) == 3 and trans_1.shape[-2:] == (4, 4)): + raise ValueError("Input trans_1 must be a of the shape Nx4x4." + " Got {}".format(trans_1.shape)) + if not (len(trans_2.shape) == 3 and trans_2.shape[-2:] == (4, 4)): + raise ValueError("Input trans_2 must be a of the shape Nx4x4." + " Got {}".format(trans_2.shape)) + # unpack input data + rmat_1: torch.Tensor = trans_1[..., :3, :3] # Nx3x3 + rmat_2: torch.Tensor = trans_2[..., :3, :3] # Nx3x3 + tvec_1: torch.Tensor = trans_1[..., :3, -1:] # Nx3x1 + tvec_2: torch.Tensor = trans_2[..., :3, -1:] # Nx3x1 + + # compute the actual transforms composition, return the transform + # from frame two to one. The variable should be trans_2_to_1, but for + # simplicity it's just trans_21. + rmat_21: torch.Tensor = torch.matmul(rmat_1, rmat_2) + tvec_21: torch.Tensor = torch.matmul(rmat_1, tvec_2) + tvec_1 + + # pack output tensor + trans_21: torch.Tensor = torch.zeros_like(trans_1) + trans_21[..., :3, 0:3] += rmat_21 + trans_21[..., :3, -1:] += tvec_21 + trans_21[..., -1, -1:] += 1.0 + return trans_21 + eps - The inverse pose is computed as follows: + +def inverse_transformation(trans_12, eps=1e-6): + r"""Function that inverts a 4x4 homogeneous transformation + :math:`P = \begin{bmatrix} R & t \\ \mathbf{0} & 1 \end{bmatrix}` + + The inverse transformation is computed as follows: .. math:: @@ -23,39 +59,44 @@ def inverse_pose(pose, eps=1e-6): 1\end{bmatrix} Args: - points (Tensor): tensor with poses. + points (torch.Tensor): tensor with transformations. Returns: - Tensor: tensor with inverted poses. + torch.Tensor: tensor with inverted transformations. Shape: - Input: :math:`(N, 4, 4)` - Output: :math:`(N, 4, 4)` Example: - >>> pose = torch.rand(1, 4, 4) # Nx4x4 - >>> pose_inv = tgm.inverse_pose(pose) # Nx4x4 + >>> trans_12 = torch.rand(1, 4, 4) # Nx4x4 + >>> trans_21 = tgm.inverse_transformation(trans_12) # Nx4x4 """ - if not torch.is_tensor(pose): + if not torch.is_tensor(trans_12): raise TypeError("Input type is not a torch.Tensor. Got {}".format( - type(pose))) - if not len(pose.shape) == 3 and pose.shape[-2:] == (4, 4): + type(trans_12))) + if not len(trans_12.shape) == 3 and trans_12.shape[-2:] == (4, 4): raise ValueError("Input size must be a Nx4x4 tensor. Got {}" - .format(pose.shape)) - - r_mat = pose[..., :3, 0:3] # Nx3x3 - t_vec = pose[..., :3, 3:4] # Nx3x1 - r_mat_trans = torch.transpose(r_mat, 1, 2) - - pose_inv = pose.new_zeros(pose.shape) + eps - pose_inv[..., :3, 0:3] = r_mat_trans - pose_inv[..., :3, 3:4] = torch.matmul(-1.0 * r_mat_trans, t_vec) - pose_inv[..., 3, 3] = 1.0 - return pose_inv - - -def relative_pose(pose_1: torch.Tensor, pose_2: torch.Tensor, - eps: Optional[float] = 1e-6) -> torch.Tensor: + .format(trans_12.shape)) + # unpack input tensor + rmat_12: torch.Tensor = trans_12[..., :3, 0:3] # Nx3x3 + tvec_12: torch.Tensor = trans_12[..., :3, 3:4] # Nx3x1 + + # compute the actual inverse + rmat_21: torch.Tensor = torch.transpose(rmat_12, 1, 2) + tvec_21: torch.Tensor = torch.matmul(-rmat_21, tvec_12) + + # pack to output tensor + trans_21: torch.Tensor = torch.zeros_like(trans_12) + trans_21[..., :3, 0:3] += rmat_21 + trans_21[..., :3, -1:] += tvec_21 + trans_21[..., -1, -1:] += 1.0 + return trans_21 + eps + + +def relative_transformation( + trans_1: torch.Tensor, trans_2: torch.Tensor, + eps: Optional[float] = 1e-6) -> torch.Tensor: r"""Function that computes the relative transformation from a reference pose :math:`P_1^{\{W\}} = \begin{bmatrix} R_1 & t_1 \\ \mathbf{0} & 1 \end{bmatrix}` to destination :math:`P_2^{\{W\}} = \begin{bmatrix} R_2 & @@ -86,40 +127,41 @@ def relative_pose(pose_1: torch.Tensor, pose_2: torch.Tensor, >>> pose_2 = torch.eye(4).unsqueeze(0) # 1x4x4 >>> pose_21 = tgm.relative_pose(pose_1, pose_2) # 1x4x4 """ - if not torch.is_tensor(pose_1): - raise TypeError("Input pose_1 type is not a torch.Tensor. Got {}" + if not torch.is_tensor(trans_1): + raise TypeError("Input trans_1 type is not a torch.Tensor. Got {}" .format(type(pose_1))) - if not torch.is_tensor(pose_2): - raise TypeError("Input pose_2 type is not a torch.Tensor. Got {}" - .format(type(pose_2))) - if not (len(pose_1.shape) == 3 and pose_1.shape[-2:] == (4, 4)): + if not torch.is_tensor(trans_2): + raise TypeError("Input trans_2 type is not a torch.Tensor. Got {}" + .format(type(trans_2))) + if not (len(trans_1.shape) == 3 and trans_1.shape[-2:] == (4, 4)): + raise ValueError("Input must be a of the shape Nx4x4." + " Got {}".format(trans_1.shape)) + if not (len(trans_2.shape) == 3 and trans_2.shape[-2:] == (4, 4)): raise ValueError("Input must be a of the shape Nx4x4." - " Got {}".format(pose_1.shape, pose_2.shape)) + " Got {}".format(trans_2.shape)) # unpack input data - r_mat_1 = pose_1[..., :3, :3] # Nx3x3 - r_mat_2 = pose_2[..., :3, :3] # Nx3x3 - t_vec_1 = pose_1[..., :3, -1:] # Nx3x1 - t_vec_2 = pose_2[..., :3, -1:] # Nx3x1 + rmat_1: torch.Tensor = trans_1[..., :3, :3] # Nx3x3 + rmat_2: torch.Tensor = trans_2[..., :3, :3] # Nx3x3 + tvec_1: torch.Tensor = trans_1[..., :3, -1:] # Nx3x1 + tvec_2: torch.Tensor = trans_2[..., :3, -1:] # Nx3x1 - # compute relative pose - r_mat_1_trans = r_mat_1.transpose(1, 2) - r_mat_21 = torch.matmul(r_mat_2, r_mat_1_trans) - t_vec_21 = torch.matmul(r_mat_1_trans, t_vec_2 - t_vec_1) + # compute relative transformation + rmat_2_trans: torch.Tensor = rmat_2.transpose(1, 2) + rmat_12: torch.Tensor = torch.matmul(rmat_2_trans, rmat_1) + tvec_12: torch.Tensor = torch.matmul(rmat_2_trans, tvec_1 - tvec_2) # pack output data - batch_size = r_mat_21.shape[0] - pose_21 = torch.zeros(batch_size, 4, 4, - device=r_mat_21.device, dtype=r_mat_21.dtype) - pose_21[..., :3, :3] = r_mat_21 - pose_21[..., :3, -1:] = t_vec_21 - pose_21[..., -1, -1] += 1.0 - return pose_21 + eps + trans_12: torch.Tensor = torch.zeros_like(trans_1) + trans_12[..., :3, 0:3] += rmat_12 + trans_12[..., :3, -1:] += tvec_12 + trans_12[..., -1, -1:] += 1.0 + return trans_12 + eps # layer api -class InversePose(nn.Module): +'''class InversePose(nn.Module): r"""Creates a transformation that inverts a 4x4 pose. Args: @@ -142,4 +184,4 @@ def __init__(self): super(InversePose, self).__init__() def forward(self, input): - return inverse_pose(input) + return inverse_pose(input)''' From 1de91426a4af797427c3338818e4b3beda7e4cf6 Mon Sep 17 00:00:00 2001 From: edgarriba Date: Mon, 25 Feb 2019 17:47:26 +0100 Subject: [PATCH 2/8] refactor compose_transformations --- docs/source/transformations.rst | 7 +- test/test_transformations.py | 41 +++++--- torchgeometry/core/transformations.py | 137 +++++++++++++------------- 3 files changed, 101 insertions(+), 84 deletions(-) diff --git a/docs/source/transformations.rst b/docs/source/transformations.rst index ff21e365b7..15959c83f8 100644 --- a/docs/source/transformations.rst +++ b/docs/source/transformations.rst @@ -3,7 +3,6 @@ Linear Transformations .. currentmodule:: torchgeometry -.. autofunction:: relative_pose -.. autofunction:: inverse_pose - -.. autoclass:: InversePose +.. autofunction:: relative_transformation +.. autofunction:: inverse_transformation +.. autofunction:: compose_transformations diff --git a/test/test_transformations.py b/test/test_transformations.py index c4b1d0c8e7..42a9cb1661 100644 --- a/test/test_transformations.py +++ b/test/test_transformations.py @@ -13,15 +13,34 @@ class TestComposeTransforms: def _identity_matrix(self, batch_size): return torch.eye(4).repeat(batch_size, 1, 1) # Nx4x4 - def test_compose_transforms(self): - batch_size = 1 - trans_1 = self._identity_matrix(batch_size) - trans_2 = self._identity_matrix(batch_size) - trans_2[..., :3, -1] += 10 # add offset to translation vector + def test_compose_transforms_4x4(self): + offset = 10 + trans_01 = self._identity_matrix(batch_size=1)[0] + trans_12 = self._identity_matrix(batch_size=1)[0] + trans_12[..., :3, -1] += offset # add offset to translation vector - trans_21 = tgm.compose_transformations(trans_1, trans_2) - assert utils.check_equal_torch(trans_21, trans_2) + trans_02 = tgm.compose_transformations(trans_01, trans_12) + assert utils.check_equal_torch(trans_02, trans_12) + @pytest.mark.parametrize("batch_size", [1, 2, 5]) + def test_compose_transforms_Bx4x4(self, batch_size): + offset = 10 + trans_01 = self._identity_matrix(batch_size) + trans_12 = self._identity_matrix(batch_size) + trans_12[..., :3, -1] += offset # add offset to translation vector + + trans_02 = tgm.compose_transformations(trans_01, trans_12) + assert utils.check_equal_torch(trans_02, trans_12) + + @pytest.mark.parametrize("batch_size", [1, 2, 5]) + def test_gradcheck(self, batch_size): + trans_01 = self._identity_matrix(batch_size) + trans_12 = self._identity_matrix(batch_size) + + trans_01 = utils.tensor_to_gradcheck_var(trans_01) # to var + trans_12 = utils.tensor_to_gradcheck_var(trans_12) # to var + assert gradcheck(tgm.compose_transformations, (trans_01, trans_12,), + raise_exception=True) class TestRelativeTransform: @@ -118,17 +137,13 @@ def test_inverse_pose(batch_size, device_type): dst_pose_src[:, -1, -1] = 1.0 # compute the inverse of the pose - src_pose_dst = tgm.inverse_pose(dst_pose_src) + src_pose_dst = tgm.inverse_transformation(dst_pose_src) # H_inv * H == I eye = torch.matmul(src_pose_dst, dst_pose_src) assert utils.check_equal_torch(eye, torch.eye(4), eps=1e-3) - # functional - eye = torch.matmul(tgm.InversePose()(dst_pose_src), dst_pose_src) - assert utils.check_equal_torch(eye, torch.eye(4), eps=1e-3) - # evaluate function gradient dst_pose_src = utils.tensor_to_gradcheck_var(dst_pose_src) # to var - assert gradcheck(tgm.inverse_pose, (dst_pose_src,), + assert gradcheck(tgm.inverse_transformation, (dst_pose_src,), raise_exception=True) diff --git a/torchgeometry/core/transformations.py b/torchgeometry/core/transformations.py index 0a1abd038f..b800a270ab 100644 --- a/torchgeometry/core/transformations.py +++ b/torchgeometry/core/transformations.py @@ -12,42 +12,57 @@ def compose_transformations( - trans_1: torch.Tensor, trans_2: torch.Tensor, - eps: Optional[float] = 1e-6) -> torch.Tensor: - r"""Functions that composes two homogeneous transformations.""" - if not torch.is_tensor(trans_1): - raise TypeError("Input trans_1 type is not a torch.Tensor. Got {}" - .format(type(trans_1))) - if not torch.is_tensor(trans_2): - raise TypeError("Input trans_2 type is not a torch.Tensor. Got {}" - .format(type(trans_2))) - if not (len(trans_1.shape) == 3 and trans_1.shape[-2:] == (4, 4)): - raise ValueError("Input trans_1 must be a of the shape Nx4x4." - " Got {}".format(trans_1.shape)) - if not (len(trans_2.shape) == 3 and trans_2.shape[-2:] == (4, 4)): - raise ValueError("Input trans_2 must be a of the shape Nx4x4." - " Got {}".format(trans_2.shape)) + trans_01: torch.Tensor, trans_12: torch.Tensor) -> torch.Tensor: + r"""Functions that composes two homogeneous transformations. + + Args: + trans_01 (torch.Tensor): tensor with the homogenous transformation from + a reference frame 1 respect to a frame 0. The tensor has must have a + shape of :math:`(B, 4, 4)` or :math:`(4, 4)`. + trans_12 (torch.Tensor): tensor with the homogenous transformation from + a reference frame 2 respect to a frame 1. The tensor has must have a + shape of :math:`(B, 4, 4)` or :math:`(4, 4)`. + + Returns: + torch.Tensor: the transformation between the reference frame 1 respect + to frame 2. The output shape will be :math:`(B, 4, 4)` or + :math:`(4 ,4)`. + + """ + if not torch.is_tensor(trans_01): + raise TypeError("Input trans_01 type is not a torch.Tensor. Got {}" + .format(type(trans_01))) + if not torch.is_tensor(trans_12): + raise TypeError("Input trans_12 type is not a torch.Tensor. Got {}" + .format(type(trans_12))) + if not trans_01.dim() in (2, 3) and trans_01.shape[-2:] == (4, 4): + raise ValueError("Input trans_01 must be a of the shape Nx4x4 or 4x4." + " Got {}".format(trans_01.shape)) + if not trans_12.dim() in (2, 3) and trans_12.shape[-2:] == (4, 4): + raise ValueError("Input trans_12 must be a of the shape Nx4x4 or 4x4." + " Got {}".format(trans_12.shape)) + if not trans_01.dim() == trans_12.dim(): + raise ValueError("Input number of dims must match. Got {} and {}" + .format(trans_01.dim(), trans_12.dim())) # unpack input data - rmat_1: torch.Tensor = trans_1[..., :3, :3] # Nx3x3 - rmat_2: torch.Tensor = trans_2[..., :3, :3] # Nx3x3 - tvec_1: torch.Tensor = trans_1[..., :3, -1:] # Nx3x1 - tvec_2: torch.Tensor = trans_2[..., :3, -1:] # Nx3x1 + rmat_01: torch.Tensor = trans_01[..., :3, :3] # Nx3x3 + rmat_12: torch.Tensor = trans_12[..., :3, :3] # Nx3x3 + tvec_01: torch.Tensor = trans_01[..., :3, -1:] # Nx3x1 + tvec_12: torch.Tensor = trans_12[..., :3, -1:] # Nx3x1 - # compute the actual transforms composition, return the transform - # from frame two to one. The variable should be trans_2_to_1, but for - # simplicity it's just trans_21. - rmat_21: torch.Tensor = torch.matmul(rmat_1, rmat_2) - tvec_21: torch.Tensor = torch.matmul(rmat_1, tvec_2) + tvec_1 + # compute the actual transforms composition + rmat_02: torch.Tensor = torch.matmul(rmat_01, rmat_12) + tvec_02: torch.Tensor = torch.matmul(rmat_01, tvec_12) + tvec_01 # pack output tensor - trans_21: torch.Tensor = torch.zeros_like(trans_1) - trans_21[..., :3, 0:3] += rmat_21 - trans_21[..., :3, -1:] += tvec_21 - trans_21[..., -1, -1:] += 1.0 - return trans_21 + eps + trans_02: torch.Tensor = torch.zeros_like(trans_01) + trans_02[..., :3, 0:3] += rmat_02 + trans_02[..., :3, -1:] += tvec_02 + trans_02[..., -1, -1:] += 1.0 + return trans_02 -def inverse_transformation(trans_12, eps=1e-6): +def inverse_transformation(trans_12): r"""Function that inverts a 4x4 homogeneous transformation :math:`P = \begin{bmatrix} R & t \\ \mathbf{0} & 1 \end{bmatrix}` @@ -73,17 +88,17 @@ def inverse_transformation(trans_12, eps=1e-6): >>> trans_21 = tgm.inverse_transformation(trans_12) # Nx4x4 """ if not torch.is_tensor(trans_12): - raise TypeError("Input type is not a torch.Tensor. Got {}".format( - type(trans_12))) - if not len(trans_12.shape) == 3 and trans_12.shape[-2:] == (4, 4): - raise ValueError("Input size must be a Nx4x4 tensor. Got {}" + raise TypeError("Input type is not a torch.Tensor. Got {}" + .format(type(trans_12))) + if not trans_12.dim() in (2, 3) and trans_12.shape[-2:] == (4, 4): + raise ValueError("Input size must be a Nx4x4 or 4x4. Got {}" .format(trans_12.shape)) # unpack input tensor rmat_12: torch.Tensor = trans_12[..., :3, 0:3] # Nx3x3 tvec_12: torch.Tensor = trans_12[..., :3, 3:4] # Nx3x1 # compute the actual inverse - rmat_21: torch.Tensor = torch.transpose(rmat_12, 1, 2) + rmat_21: torch.Tensor = torch.transpose(rmat_12, -1, -2) tvec_21: torch.Tensor = torch.matmul(-rmat_21, tvec_12) # pack to output tensor @@ -91,12 +106,11 @@ def inverse_transformation(trans_12, eps=1e-6): trans_21[..., :3, 0:3] += rmat_21 trans_21[..., :3, -1:] += tvec_21 trans_21[..., -1, -1:] += 1.0 - return trans_21 + eps + return trans_21 def relative_transformation( - trans_1: torch.Tensor, trans_2: torch.Tensor, - eps: Optional[float] = 1e-6) -> torch.Tensor: + trans_01: torch.Tensor, trans_02: torch.Tensor) -> torch.Tensor: r"""Function that computes the relative transformation from a reference pose :math:`P_1^{\{W\}} = \begin{bmatrix} R_1 & t_1 \\ \mathbf{0} & 1 \end{bmatrix}` to destination :math:`P_2^{\{W\}} = \begin{bmatrix} R_2 & @@ -127,35 +141,24 @@ def relative_transformation( >>> pose_2 = torch.eye(4).unsqueeze(0) # 1x4x4 >>> pose_21 = tgm.relative_pose(pose_1, pose_2) # 1x4x4 """ - if not torch.is_tensor(trans_1): - raise TypeError("Input trans_1 type is not a torch.Tensor. Got {}" - .format(type(pose_1))) - if not torch.is_tensor(trans_2): - raise TypeError("Input trans_2 type is not a torch.Tensor. Got {}" - .format(type(trans_2))) - if not (len(trans_1.shape) == 3 and trans_1.shape[-2:] == (4, 4)): - raise ValueError("Input must be a of the shape Nx4x4." - " Got {}".format(trans_1.shape)) - if not (len(trans_2.shape) == 3 and trans_2.shape[-2:] == (4, 4)): - raise ValueError("Input must be a of the shape Nx4x4." - " Got {}".format(trans_2.shape)) - # unpack input data - rmat_1: torch.Tensor = trans_1[..., :3, :3] # Nx3x3 - rmat_2: torch.Tensor = trans_2[..., :3, :3] # Nx3x3 - tvec_1: torch.Tensor = trans_1[..., :3, -1:] # Nx3x1 - tvec_2: torch.Tensor = trans_2[..., :3, -1:] # Nx3x1 - - # compute relative transformation - rmat_2_trans: torch.Tensor = rmat_2.transpose(1, 2) - rmat_12: torch.Tensor = torch.matmul(rmat_2_trans, rmat_1) - tvec_12: torch.Tensor = torch.matmul(rmat_2_trans, tvec_1 - tvec_2) - - # pack output data - trans_12: torch.Tensor = torch.zeros_like(trans_1) - trans_12[..., :3, 0:3] += rmat_12 - trans_12[..., :3, -1:] += tvec_12 - trans_12[..., -1, -1:] += 1.0 - return trans_12 + eps + if not torch.is_tensor(trans_01): + raise TypeError("Input trans_01 type is not a torch.Tensor. Got {}" + .format(type(pose_01))) + if not torch.is_tensor(trans_02): + raise TypeError("Input trans_02 type is not a torch.Tensor. Got {}" + .format(type(trans_02))) + if not (len(trans_01.shape) in (2, 3) and trans_01.shape[-2:] == (4, 4)): + raise ValueError("Input must be a of the shape Nx4x4 or 4x4." + " Got {}".format(trans_01.shape)) + if not (len(trans_02.shape) in (2, 3) and trans_02.shape[-2:] == (4, 4)): + raise ValueError("Input must be a of the shape Nx4x4 or 4x4." + " Got {}".format(trans_02.shape)) + if not trans_01.dim() == trans_02.dim(): + raise ValueError("Input number of dims must match. Got {} and {}" + .format(trans_01.dim(), trans_02.dim())) + trans_10: torch.Tensor = inverse_transformation(trans_01) + trans_12: torch.Tensor = compose_transformations(trans_10, trans_02) + return trans_12 # layer api From d77bcd06eda51d93bdc371d660722eae8684e1db Mon Sep 17 00:00:00 2001 From: edgarriba Date: Mon, 25 Feb 2019 23:35:00 +0100 Subject: [PATCH 3/8] refactor test inverse_transformation --- test/test_transformations.py | 119 ++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 35 deletions(-) diff --git a/test/test_transformations.py b/test/test_transformations.py index 42a9cb1661..59a2ff166e 100644 --- a/test/test_transformations.py +++ b/test/test_transformations.py @@ -8,25 +8,54 @@ from common import TEST_DEVICES -class TestComposeTransforms: +def identity_matrix(batch_size): + return torch.eye(4).repeat(batch_size, 1, 1) # Nx4x4 + + +def euler_angles_to_rotation_matrix(x, y, z): + assert x.dim() == 1, x.shape + assert x.shape == y.shape == z.shape + ones, zeros = torch.ones_like(x), torch.zeros_like(x) + + rx_tmp = [ + ones, zeros, zeros, zeros, + zeros, torch.cos(x), -torch.sin(x), zeros, + zeros, torch.sin(x), torch.cos(x), zeros, + zeros, zeros, zeros, ones] + rx = torch.stack(rx_tmp, dim=-1).view(-1, 4, 4) + + ry_tmp = [ + torch.cos(y), zeros, torch.sin(y), zeros, + zeros, ones, zeros, zeros, + -torch.sin(y), zeros, torch.cos(y), zeros, + zeros, zeros, zeros, ones] + ry = torch.stack(ry_tmp, dim=-1).view(-1, 4, 4) + + rz_tmp = [ + torch.cos(z), -torch.sin(z), zeros, zeros, + torch.sin(z), torch.cos(z), zeros, zeros, + zeros, zeros, ones, zeros, + zeros, zeros, zeros, ones] + rz = torch.stack(rz_tmp, dim=-1).view(-1, 4, 4) + return torch.matmul(rz, torch.matmul(ry, rx)) + - def _identity_matrix(self, batch_size): - return torch.eye(4).repeat(batch_size, 1, 1) # Nx4x4 +class TestComposeTransforms: - def test_compose_transforms_4x4(self): + def test_translation_4x4(self): offset = 10 - trans_01 = self._identity_matrix(batch_size=1)[0] - trans_12 = self._identity_matrix(batch_size=1)[0] + trans_01 = identity_matrix(batch_size=1)[0] + trans_12 = identity_matrix(batch_size=1)[0] trans_12[..., :3, -1] += offset # add offset to translation vector trans_02 = tgm.compose_transformations(trans_01, trans_12) assert utils.check_equal_torch(trans_02, trans_12) @pytest.mark.parametrize("batch_size", [1, 2, 5]) - def test_compose_transforms_Bx4x4(self, batch_size): + def test_translation_Bx4x4(self, batch_size): offset = 10 - trans_01 = self._identity_matrix(batch_size) - trans_12 = self._identity_matrix(batch_size) + trans_01 = identity_matrix(batch_size) + trans_12 = identity_matrix(batch_size) trans_12[..., :3, -1] += offset # add offset to translation vector trans_02 = tgm.compose_transformations(trans_01, trans_12) @@ -34,8 +63,8 @@ def test_compose_transforms_Bx4x4(self, batch_size): @pytest.mark.parametrize("batch_size", [1, 2, 5]) def test_gradcheck(self, batch_size): - trans_01 = self._identity_matrix(batch_size) - trans_12 = self._identity_matrix(batch_size) + trans_01 = identity_matrix(batch_size) + trans_12 = identity_matrix(batch_size) trans_01 = utils.tensor_to_gradcheck_var(trans_01) # to var trans_12 = utils.tensor_to_gradcheck_var(trans_12) # to var @@ -43,6 +72,50 @@ def test_gradcheck(self, batch_size): raise_exception=True) +class TestInverseTransformation: + + def test_translation_4x4(self): + offset = 10 + trans_01 = identity_matrix(batch_size=1)[0] + trans_01[..., :3, -1] += offset # add offset to translation vector + + trans_10 = tgm.inverse_transformation(trans_01) + trans_01_hat = tgm.inverse_transformation(trans_10) + assert utils.check_equal_torch(trans_01, trans_01_hat) + + @pytest.mark.parametrize("batch_size", [1, 2, 5]) + def test_translation_Bx4x4(self, batch_size): + offset = 10 + trans_01 = identity_matrix(batch_size) + trans_01[..., :3, -1] += offset # add offset to translation vector + + trans_10 = tgm.inverse_transformation(trans_01) + trans_01_hat = tgm.inverse_transformation(trans_10) + assert utils.check_equal_torch(trans_01, trans_01_hat) + + @pytest.mark.parametrize("batch_size", [1, 2, 5]) + def test_rotation_translation_Bx4x4(self, batch_size): + offset = 10 + x, y, z = 0, 0, tgm.pi + ones = torch.ones(batch_size) + rmat_01 = euler_angles_to_rotation_matrix(x * ones, y * ones, z * ones) + + trans_01 = identity_matrix(batch_size) + trans_01[..., :3, -1] += offset # add offset to translation vector + trans_01[..., :3, :3] = rmat_01[..., :3, :3] + + trans_10 = tgm.inverse_transformation(trans_01) + trans_01_hat = tgm.inverse_transformation(trans_10) + assert utils.check_equal_torch(trans_01, trans_01_hat) + + @pytest.mark.parametrize("batch_size", [1, 2, 5]) + def test_gradcheck(self, batch_size): + trans_01 = identity_matrix(batch_size) + trans_01 = utils.tensor_to_gradcheck_var(trans_01) # to var + assert gradcheck(tgm.inverse_transformation, (trans_01,), + raise_exception=True) + + class TestRelativeTransform: def _generate_identity_matrix(self, batch_size, device_type): @@ -123,27 +196,3 @@ def test_run_all(self, batch_size, device_type): #self._test_rotation() #self._test_integration() #self._test_gradcheck() - - -# TODO: embedd to a class -@pytest.mark.parametrize("device_type", TEST_DEVICES) -@pytest.mark.parametrize("batch_size", [1, 2, 5, 6]) -def test_inverse_pose(batch_size, device_type): - # generate input data - eye_size = 4 # identity 4x4 - dst_pose_src = utils.create_random_homography(batch_size, eye_size) - dst_pose_src = dst_pose_src.to(torch.device(device_type)) - dst_pose_src[:, -1] = 0.0 - dst_pose_src[:, -1, -1] = 1.0 - - # compute the inverse of the pose - src_pose_dst = tgm.inverse_transformation(dst_pose_src) - - # H_inv * H == I - eye = torch.matmul(src_pose_dst, dst_pose_src) - assert utils.check_equal_torch(eye, torch.eye(4), eps=1e-3) - - # evaluate function gradient - dst_pose_src = utils.tensor_to_gradcheck_var(dst_pose_src) # to var - assert gradcheck(tgm.inverse_transformation, (dst_pose_src,), - raise_exception=True) From fa951ddd68cd38a0ac225bc540b547c10dd23970 Mon Sep 17 00:00:00 2001 From: edgarriba Date: Mon, 25 Feb 2019 23:54:04 +0100 Subject: [PATCH 4/8] refactor test relative transformation --- test/test_transformations.py | 115 +++++++++----------------- torchgeometry/core/transformations.py | 2 +- 2 files changed, 38 insertions(+), 79 deletions(-) diff --git a/test/test_transformations.py b/test/test_transformations.py index 59a2ff166e..c1a2a8f431 100644 --- a/test/test_transformations.py +++ b/test/test_transformations.py @@ -9,35 +9,37 @@ def identity_matrix(batch_size): + r"""Creates a batched homogeneous identity matrix""" return torch.eye(4).repeat(batch_size, 1, 1) # Nx4x4 def euler_angles_to_rotation_matrix(x, y, z): + r"""Create a rotation matrix from x, y, z angles""" assert x.dim() == 1, x.shape assert x.shape == y.shape == z.shape ones, zeros = torch.ones_like(x), torch.zeros_like(x) - + # the rotation matrix for the x-axis rx_tmp = [ ones, zeros, zeros, zeros, zeros, torch.cos(x), -torch.sin(x), zeros, zeros, torch.sin(x), torch.cos(x), zeros, zeros, zeros, zeros, ones] rx = torch.stack(rx_tmp, dim=-1).view(-1, 4, 4) - + # the rotation matrix for the y-axis ry_tmp = [ torch.cos(y), zeros, torch.sin(y), zeros, zeros, ones, zeros, zeros, -torch.sin(y), zeros, torch.cos(y), zeros, zeros, zeros, zeros, ones] ry = torch.stack(ry_tmp, dim=-1).view(-1, 4, 4) - + # the rotation matrix for the z-axis rz_tmp = [ torch.cos(z), -torch.sin(z), zeros, zeros, torch.sin(z), torch.cos(z), zeros, zeros, zeros, zeros, ones, zeros, zeros, zeros, zeros, ones] rz = torch.stack(rz_tmp, dim=-1).view(-1, 4, 4) - return torch.matmul(rz, torch.matmul(ry, rx)) + return torch.matmul(rz, torch.matmul(ry, rx)) # Bx4x4 class TestComposeTransforms: @@ -116,83 +118,40 @@ def test_gradcheck(self, batch_size): raise_exception=True) -class TestRelativeTransform: +class TestRelativeTransformation: - def _generate_identity_matrix(self, batch_size, device_type): - eye = torch.eye(4).repeat(batch_size, 1, 1) # Nx4x4 - return eye.to(torch.device(device_type)) + def test_translation_4x4(self): + offset = 10. + trans_01 = identity_matrix(batch_size=1)[0] + trans_02 = identity_matrix(batch_size=1)[0] + trans_02[..., :3, -1] += offset # add offset to translation vector - def _test_identity(self): - pose_1 = self.pose_1.clone() - pose_2 = self.pose_2.clone() - pose_21 = tgm.relative_transformation(pose_1, pose_2) - assert utils.check_equal_torch(pose_21, torch.eye(4).unsqueeze(0)) + trans_12 = tgm.relative_transformation(trans_01, trans_02) + trans_02_hat = tgm.compose_transformations(trans_01, trans_12) + assert utils.check_equal_torch(trans_02_hat, trans_02) - def _test_translation(self): + @pytest.mark.parametrize("batch_size", [1, 2, 5]) + def test_rotation_translation_Bx4x4(self, batch_size): offset = 10. - pose_1 = self.pose_1.clone() - pose_2 = self.pose_2.clone() - pose_2[..., :3, -1:] += offset # add translation - - # compute relative pose - pose_21 = tgm.relative_transformation(pose_1, pose_2) - assert utils.check_equal_torch(pose_21[..., :3, -1:], offset) - - def _test_rotation(self): - pose_1 = self.pose_1.clone() - pose_2 = torch.zeros_like(pose_1) # Rz (90deg) - pose_2[..., 0, 1] = -1.0 - pose_2[..., 1, 0] = 1.0 - pose_2[..., 2, 2] = 1.0 - pose_2[..., 3, 3] = 1.0 - - # compute relative pose - pose_21 = tgm.relative_transformation(pose_1, pose_2) - assert utils.check_equal_torch(pose_21, pose_2) - - def _test_integration(self): - pose_1 = self.pose_1.clone() - pose_2 = self.pose_2.clone() - - # apply random rotations and translations - batch_size, device = pose_2.shape[0], pose_2.device - pose_2[..., :3, :3] = torch.rand(batch_size, 3, 3, device=device) - pose_2[..., :3, -1:] = torch.rand(batch_size, 3, 1, device=device) - - pose_21 = tgm.relative_transformation(pose_1, pose_2) - assert utils.check_equal_torch( - torch.matmul(pose_21, pose_1), pose_2) - - @pytest.mark.skip("Converting a tensor to a Python boolean ...") - def test_jit(self): - pose_1 = self.pose_1.clone() - pose_2 = self.pose_2.clone() - - pose_21 = tgm.relative_transformation(pose_1, pose_2) - pose_21_jit = torch.jit.trace( - tgm.relative_transformation, (pose_1, pose_2,))(pose_1, pose_2) - assert utils.check_equal_torch(pose_21, pose_21_jit) - - def _test_gradcheck(self): - pose_1 = self.pose_1.clone() - pose_2 = self.pose_2.clone() - - pose_1 = utils.tensor_to_gradcheck_var(pose_1) # to var - pose_2 = utils.tensor_to_gradcheck_var(pose_2) # to var - assert gradcheck(tgm.relative_transformation, (pose_1, pose_2,), - raise_exception=True) + x, y, z = 0., 0., tgm.pi + ones = torch.ones(batch_size) + rmat_02 = euler_angles_to_rotation_matrix(x * ones, y * ones, z * ones) + + trans_01 = identity_matrix(batch_size) + trans_02 = identity_matrix(batch_size) + trans_02[..., :3, -1] += offset # add offset to translation vector + trans_02[..., :3, :3] = rmat_02[..., :3, :3] + + trans_12 = tgm.relative_transformation(trans_01, trans_02) + trans_02_hat = tgm.compose_transformations(trans_01, trans_12) + assert utils.check_equal_torch(trans_02_hat, trans_02) - @pytest.mark.parametrize("device_type", TEST_DEVICES) @pytest.mark.parametrize("batch_size", [1, 2, 5]) - def test_run_all(self, batch_size, device_type): - # generate identity matrices - self.pose_1 = self._generate_identity_matrix( - batch_size, device_type) - self.pose_2 = self.pose_1.clone() - - # run tests - #self._test_identity() - self._test_translation() - #self._test_rotation() - #self._test_integration() - #self._test_gradcheck() + def test_gradcheck(self, batch_size): + trans_01 = identity_matrix(batch_size) + trans_02 = identity_matrix(batch_size) + + trans_01 = utils.tensor_to_gradcheck_var(trans_01) # to var + trans_02 = utils.tensor_to_gradcheck_var(trans_02) # to var + assert gradcheck(tgm.relative_transformation, (trans_01, trans_02,), + raise_exception=True) diff --git a/torchgeometry/core/transformations.py b/torchgeometry/core/transformations.py index b800a270ab..f3ff3f71f6 100644 --- a/torchgeometry/core/transformations.py +++ b/torchgeometry/core/transformations.py @@ -143,7 +143,7 @@ def relative_transformation( """ if not torch.is_tensor(trans_01): raise TypeError("Input trans_01 type is not a torch.Tensor. Got {}" - .format(type(pose_01))) + .format(type(trans_01))) if not torch.is_tensor(trans_02): raise TypeError("Input trans_02 type is not a torch.Tensor. Got {}" .format(type(trans_02))) From d23b7d6ec45d238dd9e511bd342e9641110bfca8 Mon Sep 17 00:00:00 2001 From: edgarriba Date: Wed, 27 Feb 2019 00:13:00 +0100 Subject: [PATCH 5/8] improve transformations module docs --- test/test_transformations.py | 20 ++++----- torchgeometry/core/pinhole.py | 3 +- torchgeometry/core/transformations.py | 65 +++++++++++++++------------ 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/test/test_transformations.py b/test/test_transformations.py index c1a2a8f431..1a9fec6ede 100644 --- a/test/test_transformations.py +++ b/test/test_transformations.py @@ -20,24 +20,24 @@ def euler_angles_to_rotation_matrix(x, y, z): ones, zeros = torch.ones_like(x), torch.zeros_like(x) # the rotation matrix for the x-axis rx_tmp = [ - ones, zeros, zeros, zeros, - zeros, torch.cos(x), -torch.sin(x), zeros, - zeros, torch.sin(x), torch.cos(x), zeros, - zeros, zeros, zeros, ones] + ones, zeros, zeros, zeros, + zeros, torch.cos(x), -torch.sin(x), zeros, + zeros, torch.sin(x), torch.cos(x), zeros, + zeros, zeros, zeros, ones] rx = torch.stack(rx_tmp, dim=-1).view(-1, 4, 4) # the rotation matrix for the y-axis ry_tmp = [ - torch.cos(y), zeros, torch.sin(y), zeros, - zeros, ones, zeros, zeros, + torch.cos(y), zeros, torch.sin(y), zeros, + zeros, ones, zeros, zeros, -torch.sin(y), zeros, torch.cos(y), zeros, - zeros, zeros, zeros, ones] + zeros, zeros, zeros, ones] ry = torch.stack(ry_tmp, dim=-1).view(-1, 4, 4) # the rotation matrix for the z-axis rz_tmp = [ torch.cos(z), -torch.sin(z), zeros, zeros, - torch.sin(z), torch.cos(z), zeros, zeros, - zeros, zeros, ones, zeros, - zeros, zeros, zeros, ones] + torch.sin(z), torch.cos(z), zeros, zeros, + zeros, zeros, ones, zeros, + zeros, zeros, zeros, ones] rz = torch.stack(rz_tmp, dim=-1).view(-1, 4, 4) return torch.matmul(rz, torch.matmul(ry, rx)) # Bx4x4 diff --git a/torchgeometry/core/pinhole.py b/torchgeometry/core/pinhole.py index d999c5f9f3..fd81de3e8c 100644 --- a/torchgeometry/core/pinhole.py +++ b/torchgeometry/core/pinhole.py @@ -558,7 +558,8 @@ def homography_i_H_ref(pinhole_i, pinhole_ref): assert pinhole_i.shape == pinhole_ref.shape, pinhole_ref.shape i_pose_base = get_optical_pose_base(pinhole_i) ref_pose_base = get_optical_pose_base(pinhole_ref) - i_pose_ref = torch.matmul(i_pose_base, inverse_transformation(ref_pose_base)) + i_pose_ref = torch.matmul(i_pose_base, + inverse_transformation(ref_pose_base)) return torch.matmul( pinhole_matrix(pinhole_i), torch.matmul(i_pose_ref, inverse_pinhole_matrix(pinhole_ref))) diff --git a/torchgeometry/core/transformations.py b/torchgeometry/core/transformations.py index f3ff3f71f6..e129c80eb7 100644 --- a/torchgeometry/core/transformations.py +++ b/torchgeometry/core/transformations.py @@ -15,6 +15,11 @@ def compose_transformations( trans_01: torch.Tensor, trans_12: torch.Tensor) -> torch.Tensor: r"""Functions that composes two homogeneous transformations. + .. math:: + + T_0^{2} = \begin{bmatrix} R_0^1 R_1^{2} & R_0^{1} t_1^{2} + t_0^{1} \\ + \mathbf{0} & 1\end{bmatrix} + Args: trans_01 (torch.Tensor): tensor with the homogenous transformation from a reference frame 1 respect to a frame 0. The tensor has must have a @@ -23,11 +28,17 @@ def compose_transformations( a reference frame 2 respect to a frame 1. The tensor has must have a shape of :math:`(B, 4, 4)` or :math:`(4, 4)`. + Shape: + - Output: :math:`(N, 4, 4)` or :math:`(4, 4)` + Returns: - torch.Tensor: the transformation between the reference frame 1 respect - to frame 2. The output shape will be :math:`(B, 4, 4)` or - :math:`(4 ,4)`. - + torch.Tensor: the transformation between the two frames. + + Example:: + >>> trans_01 = torch.eye(4) # 4x4 + >>> trans_12 = torch.eye(4) # 4x4 + >>> trans_02 = tgm.compose_transformations(trans_01, trans_12) # 4x4 + """ if not torch.is_tensor(trans_01): raise TypeError("Input trans_01 type is not a torch.Tensor. Got {}" @@ -64,24 +75,24 @@ def compose_transformations( def inverse_transformation(trans_12): r"""Function that inverts a 4x4 homogeneous transformation - :math:`P = \begin{bmatrix} R & t \\ \mathbf{0} & 1 \end{bmatrix}` + :math:`T_1^{2} = \begin{bmatrix} R_1 & t_1 \\ \mathbf{0} & 1 \end{bmatrix}` The inverse transformation is computed as follows: .. math:: - P^{-1} = \begin{bmatrix} R^T & -R^T t \\ \mathbf{0} & - 1\end{bmatrix} + T_2^{1} = (T_1^{2})^{-1} = \begin{bmatrix} R_1^T & -R_1^T t_1 \\ + \mathbf{0} & 1\end{bmatrix} Args: - points (torch.Tensor): tensor with transformations. + trans_12 (torch.Tensor): transformation tensor of shape + :math:`(N, 4, 4)` or :math:`(4, 4)`. Returns: torch.Tensor: tensor with inverted transformations. Shape: - - Input: :math:`(N, 4, 4)` - - Output: :math:`(N, 4, 4)` + - Output: :math:`(N, 4, 4)` or :math:`(4, 4)` Example: >>> trans_12 = torch.rand(1, 4, 4) # Nx4x4 @@ -111,35 +122,33 @@ def inverse_transformation(trans_12): def relative_transformation( trans_01: torch.Tensor, trans_02: torch.Tensor) -> torch.Tensor: - r"""Function that computes the relative transformation from a reference - pose :math:`P_1^{\{W\}} = \begin{bmatrix} R_1 & t_1 \\ \mathbf{0} & 1 - \end{bmatrix}` to destination :math:`P_2^{\{W\}} = \begin{bmatrix} R_2 & - t_2 \\ \mathbf{0} & 1 \end{bmatrix}`. + r"""Function that computes the relative homogenous transformation from a + reference transformation :math:`T_1^{0} = \begin{bmatrix} R_1 & t_1 \\ + \mathbf{0} & 1 \end{bmatrix}` to destination :math:`T_2^{0} = + \begin{bmatrix} R_2 & t_2 \\ \mathbf{0} & 1 \end{bmatrix}`. The relative transformation is computed as follows: .. math:: - P_1^{2} = \begin{bmatrix} R_2 R_1^T & R_1^T (t_2 - t_1) \\ \mathbf{0} & - 1\end{bmatrix} + T_1^{2} = (T_0^{1})^{-1} \cdot T_0^{2} Arguments: - pose_1 (torch.Tensor): reference pose tensor of shape - :math:`(N, 4, 4)`. - pose_2 (torch.Tensor): destination pose tensor of shape - :math:`(N, 4, 4)`. + trans_01 (torch.Tensor): reference transformation tensor of shape + :math:`(N, 4, 4)` or :math:`(4, 4)`. + trans_02 (torch.Tensor): destination transformation tensor of shape + :math:`(N, 4, 4)` or :math:`(4, 4). Shape: - - Output: :math:`(N, 4, 4)` + - Output: :math:`(N, 4, 4)` or :math:`(4, 4)`. Returns: - torch.Tensor: the relative transformation between the poses. + torch.Tensor: the relative transformation between the transformations. Example:: - - >>> pose_1 = torch.eye(4).unsqueeze(0) # 1x4x4 - >>> pose_2 = torch.eye(4).unsqueeze(0) # 1x4x4 - >>> pose_21 = tgm.relative_pose(pose_1, pose_2) # 1x4x4 + >>> trans_01 = torch.eye(4) # 4x4 + >>> trans_02 = torch.eye(4) # 4x4 + >>> trans_12 = tgm.relative_transformation(trans_01, trans_02) # 4x4 """ if not torch.is_tensor(trans_01): raise TypeError("Input trans_01 type is not a torch.Tensor. Got {}" @@ -147,10 +156,10 @@ def relative_transformation( if not torch.is_tensor(trans_02): raise TypeError("Input trans_02 type is not a torch.Tensor. Got {}" .format(type(trans_02))) - if not (len(trans_01.shape) in (2, 3) and trans_01.shape[-2:] == (4, 4)): + if not trans_01.dim() in (2, 3) and trans_01.shape[-2:] == (4, 4): raise ValueError("Input must be a of the shape Nx4x4 or 4x4." " Got {}".format(trans_01.shape)) - if not (len(trans_02.shape) in (2, 3) and trans_02.shape[-2:] == (4, 4)): + if not trans_02.dim() in (2, 3) and trans_02.shape[-2:] == (4, 4): raise ValueError("Input must be a of the shape Nx4x4 or 4x4." " Got {}".format(trans_02.shape)) if not trans_01.dim() == trans_02.dim(): From e7def55997a02d9b5fdb0a5f8cf5989c5ed853c6 Mon Sep 17 00:00:00 2001 From: edgarriba Date: Wed, 27 Feb 2019 00:36:52 +0100 Subject: [PATCH 6/8] move transform_points to transformation submodule --- docs/source/conversions.rst | 2 - docs/source/transformations.rst | 1 + torchgeometry/core/conversions.py | 77 +------------------------ torchgeometry/core/depth_warper.py | 2 +- torchgeometry/core/homography_warper.py | 2 +- torchgeometry/core/pinhole.py | 3 +- torchgeometry/core/transformations.py | 77 ++++++++++++++++++++++++- 7 files changed, 83 insertions(+), 81 deletions(-) diff --git a/docs/source/conversions.rst b/docs/source/conversions.rst index 42742d89e5..1a9771a596 100644 --- a/docs/source/conversions.rst +++ b/docs/source/conversions.rst @@ -7,7 +7,6 @@ Conversions .. autofunction:: deg2rad .. autofunction:: convert_points_from_homogeneous .. autofunction:: convert_points_to_homogeneous -.. autofunction:: transform_points .. autofunction:: angle_axis_to_rotation_matrix .. autofunction:: rotation_matrix_to_angle_axis .. autofunction:: rotation_matrix_to_quaternion @@ -18,4 +17,3 @@ Conversions .. autoclass:: DegToRad .. autoclass:: ConvertPointsFromHomogeneous .. autoclass:: ConvertPointsToHomogeneous -.. autoclass:: TransformPoints diff --git a/docs/source/transformations.rst b/docs/source/transformations.rst index 15959c83f8..e5c6a0893e 100644 --- a/docs/source/transformations.rst +++ b/docs/source/transformations.rst @@ -6,3 +6,4 @@ Linear Transformations .. autofunction:: relative_transformation .. autofunction:: inverse_transformation .. autofunction:: compose_transformations +.. autofunction:: transform_points diff --git a/torchgeometry/core/conversions.py b/torchgeometry/core/conversions.py index 98fba1953f..aa67143a50 100644 --- a/torchgeometry/core/conversions.py +++ b/torchgeometry/core/conversions.py @@ -8,7 +8,6 @@ "deg2rad", "convert_points_from_homogeneous", "convert_points_to_homogeneous", - "transform_points", "angle_axis_to_rotation_matrix", "rotation_matrix_to_angle_axis", "rotation_matrix_to_quaternion", @@ -19,7 +18,6 @@ "DegToRad", "ConvertPointsFromHomogeneous", "ConvertPointsToHomogeneous", - "TransformPoints", ] @@ -113,47 +111,6 @@ def convert_points_to_homogeneous(points): return nn.functional.pad(points, (0, 1), "constant", 1.0) -def transform_points(dst_pose_src, points_src): - r"""Function that applies transformations to a set of points. - - See :class:`~torchgeometry.TransformPoints` for details. - - Args: - dst_pose_src (Tensor): tensor for transformations. - points_src (Tensor): tensor of points. - - Returns: - Tensor: tensor of N-dimensional points. - - Shape: - - Input: :math:`(B, D+1, D+1)` and :math:`(B, D, N)` - - Output: :math:`(B, N, D)` - - Examples:: - - >>> input = torch.rand(2, 4, 3) # BxNx3 - >>> pose = torch.eye(4).view(1, 4, 4) # Bx4x4 - >>> output = tgm.transform_points(pose, input) # BxNx3 - """ - if not torch.is_tensor(dst_pose_src) or not torch.is_tensor(points_src): - raise TypeError("Input type is not a torch.Tensor") - if not dst_pose_src.device == points_src.device: - raise TypeError("Tensor must be in the same device") - if not dst_pose_src.shape[0] == points_src.shape[0]: - raise ValueError("Input batch size must be the same for both tensors") - if not dst_pose_src.shape[-1] == (points_src.shape[-1] + 1): - raise ValueError("Last input dimensions must differe by one unit") - # to homogeneous - points_src_h = convert_points_to_homogeneous(points_src) # BxNxD+1 - # transform coordinates - points_dst_h = torch.matmul( - dst_pose_src.unsqueeze(1), points_src_h.unsqueeze(-1)) - points_dst_h = torch.squeeze(points_dst_h, dim=-1) - # to euclidean - points_dst = convert_points_from_homogeneous(points_dst_h) # BxNxD - return points_dst - - def angle_axis_to_rotation_matrix(angle_axis): """Convert 3d vector of axis-angle rotation to 4x4 rotation matrix @@ -348,8 +305,8 @@ def rotation_matrix_to_quaternion(rotation_matrix, eps=1e-6): mask_c3 = mask_c3.view(-1, 1).type_as(q3) q = q0 * mask_c0 + q1 * mask_c1 + q2 * mask_c2 + q3 * mask_c3 - q /= torch.sqrt(t0_rep * mask_c0 + t1_rep * mask_c1 # noqa - + t2_rep * mask_c2 + t3_rep * mask_c3) # noqa + q /= torch.sqrt(t0_rep * mask_c0 + t1_rep * mask_c1 + # noqa + t2_rep * mask_c2 + t3_rep * mask_c3) # noqa q *= 0.5 return q @@ -520,33 +477,3 @@ def __init__(self): def forward(self, input): return convert_points_to_homogeneous(input) - - -class TransformPoints(nn.Module): - r"""Creates an object to transform a set of points. - - Args: - dst_pose_src (Tensor): tensor for transformations of - shape :math:`(B, D+1, D+1)`. - - Returns: - Tensor: tensor of N-dimensional points. - - Shape: - - Input: :math:`(B, D, N)` - - Output: :math:`(B, N, D)` - - Examples:: - - >>> input = torch.rand(2, 4, 3) # BxNx3 - >>> transform = torch.eye(4).view(1, 4, 4) # Bx4x4 - >>> transform_op = tgm.TransformPoints(transform) - >>> output = transform_op(input) # BxNx3 - """ - - def __init__(self, dst_homo_src): - super(TransformPoints, self).__init__() - self.dst_homo_src = dst_homo_src - - def forward(self, points_src): - return transform_points(self.dst_homo_src, points_src) diff --git a/torchgeometry/core/depth_warper.py b/torchgeometry/core/depth_warper.py index 63c7e89f66..29abf94680 100644 --- a/torchgeometry/core/depth_warper.py +++ b/torchgeometry/core/depth_warper.py @@ -3,8 +3,8 @@ import torch.nn as nn import torch.nn.functional as F +from torchgeometry.core.transformations import transform_points from torchgeometry.core.transformations import relative_transformation -from torchgeometry.core.conversions import transform_points from torchgeometry.core.conversions import convert_points_to_homogeneous from torchgeometry.core.pinhole import PinholeCamera, PinholeCamerasList from torchgeometry.core.pinhole import normalize_pixel_coordinates diff --git a/torchgeometry/core/homography_warper.py b/torchgeometry/core/homography_warper.py index 1f41fd0c4a..1a886fb7ef 100644 --- a/torchgeometry/core/homography_warper.py +++ b/torchgeometry/core/homography_warper.py @@ -5,7 +5,7 @@ import torch.nn.functional as F from torchgeometry.utils import create_meshgrid -from torchgeometry.core.conversions import transform_points +from torchgeometry.core.transformations import transform_points __all__ = [ diff --git a/torchgeometry/core/pinhole.py b/torchgeometry/core/pinhole.py index fd81de3e8c..7b6870bf03 100644 --- a/torchgeometry/core/pinhole.py +++ b/torchgeometry/core/pinhole.py @@ -4,8 +4,9 @@ import torch import torch.nn as nn +from torchgeometry.core.conversions import rtvec_to_pose +from torchgeometry.core.transformations import transform_points from torchgeometry.core.transformations import inverse_transformation -from torchgeometry.core.conversions import rtvec_to_pose, transform_points __all__ = [ diff --git a/torchgeometry/core/transformations.py b/torchgeometry/core/transformations.py index e129c80eb7..4c43e4d132 100644 --- a/torchgeometry/core/transformations.py +++ b/torchgeometry/core/transformations.py @@ -3,11 +3,16 @@ import torch import torch.nn as nn +from torchgeometry.core.conversions import convert_points_to_homogeneous +from torchgeometry.core.conversions import convert_points_from_homogeneous + __all__ = [ "compose_transformations", "inverse_transformation", "relative_transformation", + "transform_points", + "TransformPoints", ] @@ -137,7 +142,7 @@ def relative_transformation( trans_01 (torch.Tensor): reference transformation tensor of shape :math:`(N, 4, 4)` or :math:`(4, 4)`. trans_02 (torch.Tensor): destination transformation tensor of shape - :math:`(N, 4, 4)` or :math:`(4, 4). + :math:`(N, 4, 4)` or :math:`(4, 4)`. Shape: - Output: :math:`(N, 4, 4)` or :math:`(4, 4)`. @@ -170,9 +175,79 @@ def relative_transformation( return trans_12 +def transform_points(dst_trans_src: torch.Tensor, + points_src: torch.Tensor) -> torch.Tensor: + r"""Function that applies transformations to a set of points. + + Args: + dst_trans_src (torch.Tensor): tensor for transformations of shape + :math:`(B, D+1, D+1)`. + points_src (torch.Tensor): tensor of points of shape :math:`(B, N, D)`. + Returns: + torch.Tensor: tensor of N-dimensional points. + + Shape: + - Output: :math:`(B, N, D)` + + Examples: + + >>> x_src = torch.rand(2, 4, 3) # BxNx3 + >>> dst_trans_src = torch.eye(4).view(1, 4, 4) # Bx4x4 + >>> x_dst = tgm.transform_points(dst_trans_src, x_src) # BxNx3 + """ + if not torch.is_tensor(dst_trans_src) or not torch.is_tensor(points_src): + raise TypeError("Input type is not a torch.Tensor") + if not dst_trans_src.device == points_src.device: + raise TypeError("Tensor must be in the same device") + if not dst_trans_src.shape[0] == points_src.shape[0]: + raise ValueError("Input batch size must be the same for both tensors") + if not dst_trans_src.shape[-1] == (points_src.shape[-1] + 1): + raise ValueError("Last input dimensions must differe by one unit") + # to homogeneous + points_src_h: torch.Tensor = convert_points_to_homogeneous( + points_src) # BxNxD+1 + # transform coordinates + points_dst_h: torch.Tensor = torch.matmul( + dst_trans_src.unsqueeze(1), points_src_h.unsqueeze(-1)) + points_dst_h = torch.squeeze(points_dst_h, dim=-1) + # to euclidean + points_dst: torch.Tensor = convert_points_from_homogeneous( + points_dst_h) # BxNxD + return points_dst + + # layer api +class TransformPoints(nn.Module): + r"""Creates an object to transform a set of points. + + Args: + dst_pose_src (torhc.Tensor): tensor for transformations of + shape :math:`(B, D+1, D+1)`. + + Returns: + torch.Tensor: tensor of N-dimensional points. + + Shape: + - Input: :math:`(B, D, N)` + - Output: :math:`(B, N, D)` + + Examples: + >>> input = torch.rand(2, 4, 3) # BxNx3 + >>> transform = torch.eye(4).view(1, 4, 4) # Bx4x4 + >>> transform_op = tgm.TransformPoints(transform) + >>> output = transform_op(input) # BxNx3 + """ + + def __init__(self, dst_homo_src: torch.Tensor) -> None: + super(TransformPoints, self).__init__() + self.dst_homo_src: torch.Tensor = dst_homo_src + + def forward(self, points_src: torch.Tensor) -> torch.Tensor: + return transform_points(self.dst_homo_src, points_src) + + '''class InversePose(nn.Module): r"""Creates a transformation that inverts a 4x4 pose. From 01df72ef93b6e17b533176e97d1af8baa2108936 Mon Sep 17 00:00:00 2001 From: edgarriba Date: Wed, 27 Feb 2019 17:43:27 +0100 Subject: [PATCH 7/8] fix transform_points docs and move test --- test/test_conversions.py | 35 ----------------------- test/test_transformations.py | 40 +++++++++++++++++++++++++++ torchgeometry/core/conversions.py | 4 +-- torchgeometry/core/transformations.py | 36 ++++++++++++------------ 4 files changed, 59 insertions(+), 56 deletions(-) diff --git a/test/test_conversions.py b/test/test_conversions.py index 921b6ddaad..d7098d9c74 100644 --- a/test/test_conversions.py +++ b/test/test_conversions.py @@ -107,41 +107,6 @@ def test_convert_points_from_homogeneous(batch_shape, device_type): raise_exception=True) -@pytest.mark.parametrize("device_type", TEST_DEVICES) -@pytest.mark.parametrize("batch_size", [1, 2, 5]) -@pytest.mark.parametrize("num_points", [2, 3, 5]) -@pytest.mark.parametrize("num_dims", [2, 3]) -def test_transform_points(batch_size, num_points, num_dims, device_type): - # generate input data - eye_size = num_dims + 1 - points_src = torch.rand(batch_size, num_points, num_dims) - points_src = points_src.to(torch.device(device_type)) - - dst_homo_src = utils.create_random_homography(batch_size, eye_size) - dst_homo_src = dst_homo_src.to(torch.device(device_type)) - - # transform the points from dst to ref - points_dst = tgm.transform_points(dst_homo_src, points_src) - - # transform the points from ref to dst - src_homo_dst = torch.inverse(dst_homo_src) - points_dst_to_src = tgm.transform_points(src_homo_dst, points_dst) - - # projected should be equal as initial - error = utils.compute_mse(points_src, points_dst_to_src) - assert pytest.approx(error.item(), 0.0) - - # functional - assert torch.allclose(points_dst, - tgm.TransformPoints(dst_homo_src)(points_src)) - - # evaluate function gradient - points_src = utils.tensor_to_gradcheck_var(points_src) # to var - dst_homo_src = utils.tensor_to_gradcheck_var(dst_homo_src) # to var - assert gradcheck(tgm.transform_points, (dst_homo_src, points_src,), - raise_exception=True) - - @pytest.mark.parametrize("device_type", TEST_DEVICES) @pytest.mark.parametrize("batch_size", [1, 2, 5]) def test_angle_axis_to_rotation_matrix(batch_size, device_type): diff --git a/test/test_transformations.py b/test/test_transformations.py index 1a9fec6ede..99d4e050f2 100644 --- a/test/test_transformations.py +++ b/test/test_transformations.py @@ -42,6 +42,46 @@ def euler_angles_to_rotation_matrix(x, y, z): return torch.matmul(rz, torch.matmul(ry, rx)) # Bx4x4 +class TestTransformPoints: + + @pytest.mark.parametrize("device_type", TEST_DEVICES) + @pytest.mark.parametrize("batch_size", [1, 2, 5]) + @pytest.mark.parametrize("num_points", [2, 3, 5]) + @pytest.mark.parametrize("num_dims", [2, 3]) + def test_transform_points( + self, batch_size, num_points, num_dims, device_type): + # generate input data + eye_size = num_dims + 1 + points_src = torch.rand(batch_size, num_points, num_dims) + points_src = points_src.to(torch.device(device_type)) + + dst_homo_src = utils.create_random_homography(batch_size, eye_size) + dst_homo_src = dst_homo_src.to(torch.device(device_type)) + + # transform the points from dst to ref + points_dst = tgm.transform_points(dst_homo_src, points_src) + + # transform the points from ref to dst + src_homo_dst = torch.inverse(dst_homo_src) + points_dst_to_src = tgm.transform_points(src_homo_dst, points_dst) + + # projected should be equal as initial + error = utils.compute_mse(points_src, points_dst_to_src) + assert pytest.approx(error.item(), 0.0) + + def test_gradcheck(self): + # generate input data + batch_size, num_points, num_dims = 2, 3, 2 + eye_size = num_dims + 1 + points_src = torch.rand(batch_size, num_points, num_dims) + dst_homo_src = utils.create_random_homography(batch_size, eye_size) + # evaluate function gradient + points_src = utils.tensor_to_gradcheck_var(points_src) # to var + dst_homo_src = utils.tensor_to_gradcheck_var(dst_homo_src) # to var + assert gradcheck(tgm.transform_points, (dst_homo_src, points_src,), + raise_exception=True) + + class TestComposeTransforms: def test_translation_4x4(self): diff --git a/torchgeometry/core/conversions.py b/torchgeometry/core/conversions.py index aa67143a50..4fd464c2b3 100644 --- a/torchgeometry/core/conversions.py +++ b/torchgeometry/core/conversions.py @@ -71,7 +71,7 @@ def deg2rad(tensor): return tensor * pi.to(tensor.device).type(tensor.dtype) / 180. -def convert_points_from_homogeneous(points, eps=1e-6): +def convert_points_from_homogeneous(points): r"""Function that converts points from homogeneous to Euclidean space. See :class:`~torchgeometry.ConvertPointsFromHomogeneous` for details. @@ -88,7 +88,7 @@ def convert_points_from_homogeneous(points, eps=1e-6): raise ValueError("Input must be at least a 2D tensor. Got {}".format( points.shape)) - return points[..., :-1] / (points[..., -1:] + eps) + return points[..., :-1] / points[..., -1:] def convert_points_to_homogeneous(points): diff --git a/torchgeometry/core/transformations.py b/torchgeometry/core/transformations.py index 4c43e4d132..c306c3f350 100644 --- a/torchgeometry/core/transformations.py +++ b/torchgeometry/core/transformations.py @@ -175,14 +175,14 @@ def relative_transformation( return trans_12 -def transform_points(dst_trans_src: torch.Tensor, - points_src: torch.Tensor) -> torch.Tensor: +def transform_points(trans_01: torch.Tensor, + points_1: torch.Tensor) -> torch.Tensor: r"""Function that applies transformations to a set of points. Args: - dst_trans_src (torch.Tensor): tensor for transformations of shape + trans_01 (torch.Tensor): tensor for transformations of shape :math:`(B, D+1, D+1)`. - points_src (torch.Tensor): tensor of points of shape :math:`(B, N, D)`. + points_1 (torch.Tensor): tensor of points of shape :math:`(B, N, D)`. Returns: torch.Tensor: tensor of N-dimensional points. @@ -191,29 +191,27 @@ def transform_points(dst_trans_src: torch.Tensor, Examples: - >>> x_src = torch.rand(2, 4, 3) # BxNx3 - >>> dst_trans_src = torch.eye(4).view(1, 4, 4) # Bx4x4 - >>> x_dst = tgm.transform_points(dst_trans_src, x_src) # BxNx3 + >>> points_1 = torch.rand(2, 4, 3) # BxNx3 + >>> trans_01 = torch.eye(4).view(1, 4, 4) # Bx4x4 + >>> points_0 = tgm.transform_points(trans_01, points_1) # BxNx3 """ - if not torch.is_tensor(dst_trans_src) or not torch.is_tensor(points_src): + if not torch.is_tensor(trans_01) or not torch.is_tensor(points_1): raise TypeError("Input type is not a torch.Tensor") - if not dst_trans_src.device == points_src.device: + if not trans_01.device == points_1.device: raise TypeError("Tensor must be in the same device") - if not dst_trans_src.shape[0] == points_src.shape[0]: + if not trans_01.shape[0] == points_1.shape[0]: raise ValueError("Input batch size must be the same for both tensors") - if not dst_trans_src.shape[-1] == (points_src.shape[-1] + 1): + if not trans_01.shape[-1] == (points_1.shape[-1] + 1): raise ValueError("Last input dimensions must differe by one unit") # to homogeneous - points_src_h: torch.Tensor = convert_points_to_homogeneous( - points_src) # BxNxD+1 + points_1_h = convert_points_to_homogeneous(points_1) # BxNxD+1 # transform coordinates - points_dst_h: torch.Tensor = torch.matmul( - dst_trans_src.unsqueeze(1), points_src_h.unsqueeze(-1)) - points_dst_h = torch.squeeze(points_dst_h, dim=-1) + points_0_h = torch.matmul( + trans_01.unsqueeze(1), points_1_h.unsqueeze(-1)) + points_0_h = torch.squeeze(points_0_h, dim=-1) # to euclidean - points_dst: torch.Tensor = convert_points_from_homogeneous( - points_dst_h) # BxNxD - return points_dst + points_0 = convert_points_from_homogeneous(points_0_h) # BxNxD + return points_0 # layer api From f8f19499ed83942c3c34eb975fa3db678a4d7fb1 Mon Sep 17 00:00:00 2001 From: edgarriba Date: Thu, 28 Feb 2019 13:38:28 +0100 Subject: [PATCH 8/8] test transform points module --- test/test_transformations.py | 2 +- torchgeometry/core/depth_warper.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_transformations.py b/test/test_transformations.py index 99d4e050f2..2a2da79122 100644 --- a/test/test_transformations.py +++ b/test/test_transformations.py @@ -78,7 +78,7 @@ def test_gradcheck(self): # evaluate function gradient points_src = utils.tensor_to_gradcheck_var(points_src) # to var dst_homo_src = utils.tensor_to_gradcheck_var(dst_homo_src) # to var - assert gradcheck(tgm.transform_points, (dst_homo_src, points_src,), + assert gradcheck(tgm.TransformPoints(dst_homo_src), (points_src,), raise_exception=True) diff --git a/torchgeometry/core/depth_warper.py b/torchgeometry/core/depth_warper.py index 29abf94680..8aa76b3b2c 100644 --- a/torchgeometry/core/depth_warper.py +++ b/torchgeometry/core/depth_warper.py @@ -19,7 +19,7 @@ class DepthWarper(nn.Module): - """Warps a patch by depth. + r"""Warps a patch by depth. .. math:: P_{src}^{\{dst\}} = K_{dst} * T_{src}^{\{dst\}}