def test_bad_so3_input_value_err(self): """ Tests whether `so3_exp_map` and `so3_log_map` correctly return a ValueError if called with an argument of incorrect shape or, in case of `so3_exp_map`, unexpected trace. """ device = torch.device("cuda:0") log_rot = torch.randn(size=[5, 4], device=device) with self.assertRaises(ValueError) as err: so3_exp_map(log_rot) self.assertTrue( "Input tensor shape has to be Nx3." in str(err.exception)) rot = torch.randn(size=[5, 3, 5], device=device) with self.assertRaises(ValueError) as err: so3_log_map(rot) self.assertTrue( "Input has to be a batch of 3x3 Tensors." in str(err.exception)) # trace of rot definitely bigger than 3 or smaller than -1 rot = torch.cat(( torch.rand(size=[5, 3, 3], device=device) + 4.0, torch.rand(size=[5, 3, 3], device=device) - 3.0, )) with self.assertRaises(ValueError) as err: so3_log_map(rot) self.assertTrue( "A matrix has trace outside valid range [-1-eps,3+eps]." in str( err.exception))
def init_equiv_cameras_ndc_screen(cam_type: CamerasBase, batch_size: int): T = torch.randn(batch_size, 3) * 0.03 T[:, 2] = 4 R = so3_exp_map(torch.randn(batch_size, 3) * 3.0) screen_cam_params = {"R": R, "T": T} ndc_cam_params = {"R": R, "T": T} if cam_type in (OrthographicCameras, PerspectiveCameras): fcl = torch.rand((batch_size, 2)) * 3.0 + 0.1 prc = torch.randn((batch_size, 2)) * 0.2 # (height, width) image_size = torch.randint(low=2, high=64, size=(batch_size, 2)) # scale scale = (image_size.min(dim=1, keepdim=True).values - 1.0) / 2.0 ndc_cam_params["focal_length"] = fcl ndc_cam_params["principal_point"] = prc ndc_cam_params["image_size"] = image_size screen_cam_params["image_size"] = image_size screen_cam_params["focal_length"] = fcl * scale screen_cam_params["principal_point"] = (image_size[:, [1, 0]] - 1.0) / 2.0 - prc * scale screen_cam_params["in_ndc"] = False else: raise ValueError(str(cam_type)) return cam_type(**ndc_cam_params), cam_type(**screen_cam_params)
def test_determinant(self): """ Tests whether the determinants of 3x3 rotation matrices produced by `so3_exp_map` are (almost) equal to 1. """ log_rot = TestSO3.init_log_rot(batch_size=30) Rs = so3_exp_map(log_rot) dets = torch.det(Rs) self.assertClose(dets, torch.ones_like(dets), atol=1e-4)
def test_so3_log_to_exp_to_log_to_exp(self, batch_size: int = 100): """ Check that `so3_exp_map(so3_log_map(so3_exp_map(log_rot))) == so3_exp_map(log_rot)` for a randomly generated batch of rotation matrix logarithms `log_rot`. Unlike `test_so3_log_to_exp_to_log`, this test checks the correctness of converting a `log_rot` which contains values > math.pi. """ log_rot = 2.0 * TestSO3.init_log_rot(batch_size=batch_size) # check also the singular cases where rot. angle = {0, 2pi} log_rot[:2] = 0 log_rot[1, 0] = 2.0 * math.pi - 1e-6 rot = so3_exp_map(log_rot, eps=1e-4) rot_ = so3_exp_map(so3_log_map(rot, eps=1e-4, cos_bound=1e-6), eps=1e-6) self.assertClose(rot, rot_, atol=0.01) angles = so3_relative_angle(rot, rot_, cos_bound=1e-6) self.assertClose(angles, torch.zeros_like(angles), atol=0.01)
def test_so3_log_to_exp_to_log(self, batch_size: int = 100): """ Check that `so3_log_map(so3_exp_map(log_rot))==log_rot` for a randomly generated batch of rotation matrix logarithms `log_rot`. """ log_rot = TestSO3.init_log_rot(batch_size=batch_size) # check also the singular cases where rot. angle = 0 log_rot[:1] = 0 log_rot_ = so3_log_map(so3_exp_map(log_rot)) self.assertClose(log_rot, log_rot_, atol=1e-4)
def test_inverse(self, batch_size=5): device = torch.device("cuda:0") log_rot = torch.randn((batch_size, 3), dtype=torch.float32, device=device) R = so3_exp_map(log_rot) t = Rotate(R) im = t.inverse()._matrix im_2 = t._matrix.inverse() im_comp = t.get_matrix().inverse() self.assertTrue(torch.allclose(im, im_comp, atol=1e-4)) self.assertTrue(torch.allclose(im, im_2, atol=1e-4))
def test_inverse(self, batch_size=5): device = torch.device("cuda:0") # generate a random chain of transforms for _ in range(10): # 10 different tries # list of transform matrices ts = [] for i in range(10): choice = float(torch.rand(1)) if choice <= 1.0 / 3.0: t_ = Translate( torch.randn( (batch_size, 3), dtype=torch.float32, device=device ), device=device, ) elif choice <= 2.0 / 3.0: t_ = Rotate( so3_exp_map( torch.randn( (batch_size, 3), dtype=torch.float32, device=device ) ), device=device, ) else: rand_t = torch.randn( (batch_size, 3), dtype=torch.float32, device=device ) rand_t = rand_t.sign() * torch.clamp(rand_t.abs(), 0.2) t_ = Scale(rand_t, device=device) ts.append(t_._matrix.clone()) if i == 0: t = t_ else: t = t.compose(t_) # generate the inverse transformation in several possible ways m1 = t.inverse(invert_composed=True).get_matrix() m2 = t.inverse(invert_composed=True)._matrix m3 = t.inverse(invert_composed=False).get_matrix() m4 = t.get_matrix().inverse() # compute the inverse explicitly ... m5 = torch.eye(4, dtype=torch.float32, device=device) m5 = m5[None].repeat(batch_size, 1, 1) for t_ in ts: m5 = torch.bmm(torch.inverse(t_), m5) # assert all same for m in (m1, m2, m3, m4): self.assertTrue(torch.allclose(m, m5, atol=1e-3))
def init_uniform_y_rotations(batch_size: int, device: torch.device): """ Generate a batch of `batch_size` 3x3 rotation matrices around y-axis whose angles are uniformly distributed between 0 and 2 pi. """ axis = torch.tensor([0.0, 1.0, 0.0], device=device, dtype=torch.float32) angles = torch.linspace(0, 2.0 * np.pi, batch_size + 1, device=device) angles = angles[:batch_size] log_rots = axis[None, :] * angles[:, None] R = so3_exp_map(log_rots) return R
def test_so3_exp_to_log_to_exp(self, batch_size: int = 100): """ Check that `so3_exp_map(so3_log_map(R))==R` for a batch of randomly generated rotation matrices `R`. """ rot = TestSO3.init_rot(batch_size=batch_size) non_singular = (so3_rotation_angle(rot) - math.pi).abs() > 1e-2 rot = rot[non_singular] rot_ = so3_exp_map(so3_log_map(rot, eps=1e-8, cos_bound=1e-8), eps=1e-8) self.assertClose(rot_, rot, atol=0.1) angles = so3_relative_angle(rot, rot_, cos_bound=1e-4) self.assertClose(angles, torch.zeros_like(angles), atol=0.1)
def test_rotate(self): R = so3_exp_map(torch.randn((1, 3))) t = Transform3d().rotate(R) points = torch.tensor([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.5, 0.5, 0.0]]).view(1, 3, 3) normals = torch.tensor([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]).view(1, 3, 3) points_out = t.transform_points(points) normals_out = t.transform_normals(normals) points_out_expected = torch.bmm(points, R) normals_out_expected = torch.bmm(normals, R) self.assertTrue(torch.allclose(points_out, points_out_expected)) self.assertTrue(torch.allclose(normals_out, normals_out_expected))
def test_se3_exp_zero_translation(self, batch_size: int = 100): """ Check that `se3_exp_map` with zero translation gives the same result as corresponding `so3_exp_map`. """ log_transform = TestSE3.init_log_transform(batch_size=batch_size) log_transform[:, :3] *= 0.0 transform = se3_exp_map(log_transform, eps=1e-8) transform_so3 = so3_exp_map(log_transform[:, 3:], eps=1e-8) self.assertClose(transform[:, :3, :3], transform_so3.permute(0, 2, 1), atol=1e-4) self.assertClose(transform[:, 3, :3], torch.zeros_like(transform[:, :3, 3]), atol=1e-4)
def test_blender_camera(self): """ Test BlenderCamera. """ # Test get_world_to_view_transform. T = torch.randn(10, 3) R = so3_exp_map(torch.randn(10, 3) * 3.0) RT = get_world_to_view_transform(R=R, T=T) cam = BlenderCamera(R=R, T=T) RT_class = cam.get_world_to_view_transform() self.assertTrue(torch.allclose(RT.get_matrix(), RT_class.get_matrix())) self.assertTrue(isinstance(RT, Transform3d)) # Test getting camera center. C = cam.get_camera_center() C_ = -torch.bmm(R, T[:, :, None])[:, :, 0] self.assertTrue(torch.allclose(C, C_, atol=1e-05))
def init_random_cameras(cam_type: typing.Type[CamerasBase], batch_size: int, random_z: bool = False): cam_params = {} T = torch.randn(batch_size, 3) * 0.03 if not random_z: T[:, 2] = 4 R = so3_exp_map(torch.randn(batch_size, 3) * 3.0) cam_params = {"R": R, "T": T} if cam_type in (OpenGLPerspectiveCameras, OpenGLOrthographicCameras): cam_params["znear"] = torch.rand(batch_size) * 10 + 0.1 cam_params[ "zfar"] = torch.rand(batch_size) * 4 + 1 + cam_params["znear"] if cam_type == OpenGLPerspectiveCameras: cam_params["fov"] = torch.rand(batch_size) * 60 + 30 cam_params["aspect_ratio"] = torch.rand(batch_size) * 0.5 + 0.5 else: cam_params["top"] = torch.rand(batch_size) * 0.2 + 0.9 cam_params["bottom"] = -(torch.rand(batch_size)) * 0.2 - 0.9 cam_params["left"] = -(torch.rand(batch_size)) * 0.2 - 0.9 cam_params["right"] = torch.rand(batch_size) * 0.2 + 0.9 elif cam_type in (FoVPerspectiveCameras, FoVOrthographicCameras): cam_params["znear"] = torch.rand(batch_size) * 10 + 0.1 cam_params[ "zfar"] = torch.rand(batch_size) * 4 + 1 + cam_params["znear"] if cam_type == FoVPerspectiveCameras: cam_params["fov"] = torch.rand(batch_size) * 60 + 30 cam_params["aspect_ratio"] = torch.rand(batch_size) * 0.5 + 0.5 else: cam_params["max_y"] = torch.rand(batch_size) * 0.2 + 0.9 cam_params["min_y"] = -(torch.rand(batch_size)) * 0.2 - 0.9 cam_params["min_x"] = -(torch.rand(batch_size)) * 0.2 - 0.9 cam_params["max_x"] = torch.rand(batch_size) * 0.2 + 0.9 elif cam_type in ( SfMOrthographicCameras, SfMPerspectiveCameras, OrthographicCameras, PerspectiveCameras, ): cam_params["focal_length"] = torch.rand(batch_size) * 10 + 0.1 cam_params["principal_point"] = torch.randn((batch_size, 2)) else: raise ValueError(str(cam_type)) return cam_type(**cam_params)
def test_so3_exp_singularity(self, batch_size: int = 100): """ Tests whether the `so3_exp_map` is robust to the input vectors the norms of which are close to the numerically unstable region (vectors with low l2-norms). """ # generate random log-rotations with a tiny angle log_rot = TestSO3.init_log_rot(batch_size=batch_size) log_rot_small = log_rot * 1e-6 log_rot_small.requires_grad = True R = so3_exp_map(log_rot_small) # tests whether all outputs are finite self.assertTrue(torch.isfinite(R).all()) # tests whether the gradient is not None and all finite loss = R.sum() loss.backward() self.assertIsNotNone(log_rot_small.grad) self.assertTrue(torch.isfinite(log_rot_small.grad).all())
def compute_rots(): so3_exp_map(log_rot) torch.cuda.synchronize()
def _corresponding_cameras_alignment_test_case( self, cameras, R_align_gt, T_align_gt, s_align_gt, estimate_scale, mode, add_noise, ): batch_size = cameras.R.shape[0] # get target camera centers R_new = torch.bmm(R_align_gt[None].expand_as(cameras.R), cameras.R) T_new = (torch.bmm(T_align_gt[None, None].repeat(batch_size, 1, 1), cameras.R)[:, 0] + cameras.T) * s_align_gt if add_noise != 0.0: R_new = torch.bmm(R_new, so3_exp_map(torch.randn_like(T_new) * add_noise)) T_new += torch.randn_like(T_new) * add_noise # create new cameras from R_new and T_new cameras_tgt = cameras.clone() cameras_tgt.R = R_new cameras_tgt.T = T_new # align cameras and cameras_tgt cameras_aligned = corresponding_cameras_alignment( cameras, cameras_tgt, estimate_scale=estimate_scale, mode=mode) if batch_size <= 2 and mode == "centers": # underdetermined case - check only the center alignment error # since the rotation and translation are ambiguous here self.assertClose( cameras_aligned.get_camera_center(), cameras_tgt.get_camera_center(), atol=max(add_noise * 7.0, 1e-4), ) else: def _rmse(a): return (torch.norm(a, dim=1, p=2)**2).mean().sqrt() if add_noise != 0.0: # in a noisy case check mean rotation/translation error for # extrinsic alignment and root mean center error for center alignment if mode == "centers": self.assertNormsClose( cameras_aligned.get_camera_center(), cameras_tgt.get_camera_center(), _rmse, atol=max(add_noise * 10.0, 1e-4), ) elif mode == "extrinsics": angle_err = so3_relative_angle(cameras_aligned.R, cameras_tgt.R, cos_angle=True).mean() self.assertClose(angle_err, torch.ones_like(angle_err), atol=add_noise * 0.03) self.assertNormsClose(cameras_aligned.T, cameras_tgt.T, _rmse, atol=add_noise * 7.0) else: raise ValueError(mode) else: # compare the rotations and translations of cameras self.assertClose(cameras_aligned.R, cameras_tgt.R, atol=3e-4) self.assertClose(cameras_aligned.T, cameras_tgt.T, atol=3e-4) # compare the centers self.assertClose( cameras_aligned.get_camera_center(), cameras_tgt.get_camera_center(), atol=3e-4, )