def test_orthographic_kwargs(self): cameras = FoVOrthographicCameras(znear=5.0, zfar=100.0) far = 10.0 P = cameras.get_projection_transform(znear=1.0, zfar=far) vertices = torch.tensor([1, 2, far], dtype=torch.float32) projected_verts = torch.tensor([1, 2, 1], dtype=torch.float32) vertices = vertices[None, None, :] v1 = P.transform_points(vertices) self.assertClose(v1.squeeze(), projected_verts)
def test_orthographic_scaled(self): vertices = torch.tensor([1, 2, 0.5], dtype=torch.float32) vertices = vertices[None, None, :] scale = torch.tensor([[2.0, 0.5, 20]]) # applying the scale puts the z coordinate at the far clipping plane # so the z is mapped to 1.0 projected_verts = torch.tensor([2, 1, 1], dtype=torch.float32) cameras = FoVOrthographicCameras(znear=1.0, zfar=10.0, scale_xyz=scale) P = cameras.get_projection_transform() v1 = P.transform_points(vertices) v2 = orthographic_project_naive(vertices, scale) self.assertClose(v1[..., :2], v2[..., :2]) self.assertClose(v1, projected_verts[None, None])
def test_orthographic_mixed_inputs_broadcast(self): far = torch.tensor([10.0, 20.0]) near = 1.0 cameras = FoVOrthographicCameras(znear=near, zfar=far) P = cameras.get_projection_transform() vertices = torch.tensor([1.0, 2.0, 10.0], dtype=torch.float32) z2 = 1.0 / (20.0 - 1.0) * 10.0 + -1.0 / (20.0 - 1.0) projected_verts = torch.tensor([[1.0, 2.0, 1.0], [1.0, 2.0, z2]], dtype=torch.float32) vertices = vertices[None, None, :] v1 = P.transform_points(vertices) v2 = orthographic_project_naive(vertices) self.assertClose(v1[..., :2], torch.cat([v2, v2])[..., :2]) self.assertClose(v1.squeeze(), projected_verts)
def test_orthographic_mixed_inputs_grad(self): far = torch.tensor([10.0]) near = 1.0 scale = torch.tensor([[1.0, 1.0, 1.0]], requires_grad=True) cameras = FoVOrthographicCameras(znear=near, zfar=far, scale_xyz=scale) P = cameras.get_projection_transform() vertices = torch.tensor([1.0, 2.0, 10.0], dtype=torch.float32) vertices_batch = vertices[None, None, :] v1 = P.transform_points(vertices_batch) v1.sum().backward() self.assertTrue(hasattr(scale, "grad")) scale_grad = scale.grad.clone() grad_scale = torch.tensor([[ vertices[0] * P._matrix[:, 0, 0], vertices[1] * P._matrix[:, 1, 1], vertices[2] * P._matrix[:, 2, 2], ]]) self.assertClose(scale_grad, grad_scale)
def test_orthographic(self): far = 10.0 near = 1.0 cameras = FoVOrthographicCameras(znear=near, zfar=far) P = cameras.get_projection_transform() vertices = torch.tensor([1, 2, far], dtype=torch.float32) projected_verts = torch.tensor([1, 2, 1], dtype=torch.float32) vertices = vertices[None, None, :] v1 = P.transform_points(vertices) v2 = orthographic_project_naive(vertices) self.assertClose(v1[..., :2], v2[..., :2]) self.assertClose(v1.squeeze(), projected_verts) vertices[..., 2] = near projected_verts[2] = 0.0 v1 = P.transform_points(vertices) v2 = orthographic_project_naive(vertices) self.assertClose(v1[..., :2], v2[..., :2]) self.assertClose(v1.squeeze(), projected_verts)
def test_simple_sphere_pulsar(self): for device in [torch.device("cpu"), torch.device("cuda")]: sphere_mesh = ico_sphere(1, device) verts_padded = sphere_mesh.verts_padded() # Shift vertices to check coordinate frames are correct. verts_padded[..., 1] += 0.2 verts_padded[..., 0] += 0.2 pointclouds = Pointclouds( points=verts_padded, features=torch.ones_like(verts_padded) ) for azimuth in [0.0, 90.0]: R, T = look_at_view_transform(2.7, 0.0, azimuth) for camera_name, cameras in [ ("fovperspective", FoVPerspectiveCameras(device=device, R=R, T=T)), ( "fovorthographic", FoVOrthographicCameras(device=device, R=R, T=T), ), ("perspective", PerspectiveCameras(device=device, R=R, T=T)), ("orthographic", OrthographicCameras(device=device, R=R, T=T)), ]: raster_settings = PointsRasterizationSettings( image_size=256, radius=5e-2, points_per_pixel=1 ) rasterizer = PointsRasterizer( cameras=cameras, raster_settings=raster_settings ) renderer = PulsarPointsRenderer(rasterizer=rasterizer).to(device) # Load reference image filename = ( "pulsar_simple_pointcloud_sphere_" f"azimuth{azimuth}_{camera_name}.png" ) image_ref = load_rgb_image("test_%s" % filename, DATA_DIR) images = renderer( pointclouds, gamma=(1e-3,), znear=(1.0,), zfar=(100.0,) ) rgb = images[0, ..., :3].squeeze().cpu() if DEBUG: filename = "DEBUG_%s" % filename Image.fromarray((rgb.numpy() * 255).astype(np.uint8)).save( DATA_DIR / filename ) self.assertClose(rgb, image_ref, rtol=7e-3, atol=5e-3)
def test_getitem(self): R_matrix = torch.randn((6, 3, 3)) scale = torch.tensor([[1.0, 1.0, 1.0]], requires_grad=True) cam = FoVOrthographicCameras( znear=10.0, zfar=100.0, R=R_matrix, scale_xyz=scale ) # Check get item returns an instance of the same class # with all the same keys c0 = cam[0] self.assertTrue(isinstance(c0, FoVOrthographicCameras)) self.assertEqual(cam.__dict__.keys(), c0.__dict__.keys()) # Check torch.LongTensor index index = torch.tensor([1, 3, 5], dtype=torch.int64) c135 = cam[index] self.assertEqual(len(c135), 3) self.assertClose(c135.zfar, torch.tensor([100.0] * 3)) self.assertClose(c135.znear, torch.tensor([10.0] * 3)) self.assertClose(c135.min_x, torch.tensor([-1.0] * 3)) self.assertClose(c135.max_x, torch.tensor([1.0] * 3)) self.assertClose(c135.R, R_matrix[[1, 3, 5], ...]) self.assertClose(c135.scale_xyz, scale.expand(3, -1))
def test_pointcloud_with_features(self): device = torch.device("cuda:0") file_dir = Path(__file__).resolve().parent.parent / "docs/tutorials/data" pointcloud_filename = file_dir / "PittsburghBridge/pointcloud.npz" # Note, this file is too large to check in to the repo. # Download the file to run the test locally. if not path.exists(pointcloud_filename): url = "https://dl.fbaipublicfiles.com/pytorch3d/data/PittsburghBridge/pointcloud.npz" msg = ( "pointcloud.npz not found, download from %s, save it at the path %s, and rerun" % (url, pointcloud_filename) ) warnings.warn(msg) return True # Load point cloud pointcloud = np.load(pointcloud_filename) verts = torch.Tensor(pointcloud["verts"]).to(device) rgb_feats = torch.Tensor(pointcloud["rgb"]).to(device) verts.requires_grad = True rgb_feats.requires_grad = True point_cloud = Pointclouds(points=[verts], features=[rgb_feats]) R, T = look_at_view_transform(20, 10, 0) cameras = FoVOrthographicCameras(device=device, R=R, T=T, znear=0.01) raster_settings = PointsRasterizationSettings( # Set image_size so it is not a multiple of 16 (min bin_size) # in order to confirm that there are no errors in coarse rasterization. image_size=500, radius=0.003, points_per_pixel=10, ) renderer = PointsRenderer( rasterizer=PointsRasterizer( cameras=cameras, raster_settings=raster_settings ), compositor=AlphaCompositor(), ) images = renderer(point_cloud) # Load reference image filename = "bridge_pointcloud.png" image_ref = load_rgb_image("test_%s" % filename, DATA_DIR) for bin_size in [0, None]: # Check both naive and coarse to fine produce the same output. renderer.rasterizer.raster_settings.bin_size = bin_size images = renderer(point_cloud) rgb = images[0, ..., :3].squeeze().cpu() if DEBUG: filename = "DEBUG_%s" % filename Image.fromarray((rgb.detach().numpy() * 255).astype(np.uint8)).save( DATA_DIR / filename ) self.assertClose(rgb, image_ref, atol=0.015) # Check grad exists. grad_images = torch.randn_like(images) images.backward(grad_images) self.assertIsNotNone(verts.grad) self.assertIsNotNone(rgb_feats.grad)
def test_perspective_type(self): cam = FoVOrthographicCameras(znear=1.0, zfar=10.0) self.assertFalse(cam.is_perspective()) self.assertEquals(cam.get_znear(), 1.0)
def test_unified_inputs_pulsar(self): # Test data on different devices. for device in [torch.device("cpu"), torch.device("cuda")]: sphere_mesh = ico_sphere(1, device) verts_padded = sphere_mesh.verts_padded() pointclouds = Pointclouds( points=verts_padded, features=torch.ones_like(verts_padded) ) R, T = look_at_view_transform(2.7, 0.0, 0.0) # Test the different camera types. for _, cameras in [ ("fovperspective", FoVPerspectiveCameras(device=device, R=R, T=T)), ( "fovorthographic", FoVOrthographicCameras(device=device, R=R, T=T), ), ("perspective", PerspectiveCameras(device=device, R=R, T=T)), ("orthographic", OrthographicCameras(device=device, R=R, T=T)), ]: # Test different ways for image size specification. for image_size in (256, (256, 256)): raster_settings = PointsRasterizationSettings( image_size=image_size, radius=5e-2, points_per_pixel=1 ) rasterizer = PointsRasterizer( cameras=cameras, raster_settings=raster_settings ) # Test that the compositor can be provided. It's value is ignored # so use a dummy. _ = PulsarPointsRenderer(rasterizer=rasterizer, compositor=1).to( device ) # Constructor without compositor. _ = PulsarPointsRenderer(rasterizer=rasterizer).to(device) # Constructor with n_channels. _ = PulsarPointsRenderer(rasterizer=rasterizer, n_channels=3).to( device ) # Constructor with max_num_spheres. renderer = PulsarPointsRenderer( rasterizer=rasterizer, max_num_spheres=1000 ).to(device) # Test the forward function. if isinstance(cameras, (PerspectiveCameras, OrthographicCameras)): # znear and zfar is required in this case. self.assertRaises( ValueError, lambda: renderer.forward( point_clouds=pointclouds, gamma=(1e-4,) ), ) renderer.forward( point_clouds=pointclouds, gamma=(1e-4,), znear=(1.0,), zfar=(2.0,), ) # znear and zfar must be batched. self.assertRaises( TypeError, lambda: renderer.forward( point_clouds=pointclouds, gamma=(1e-4,), znear=1.0, zfar=(2.0,), ), ) self.assertRaises( TypeError, lambda: renderer.forward( point_clouds=pointclouds, gamma=(1e-4,), znear=(1.0,), zfar=2.0, ), ) else: # gamma must be batched. self.assertRaises( TypeError, lambda: renderer.forward( point_clouds=pointclouds, gamma=1e-4 ), ) renderer.forward(point_clouds=pointclouds, gamma=(1e-4,)) # rasterizer width and height change. renderer.rasterizer.raster_settings.image_size = 0 self.assertRaises( ValueError, lambda: renderer.forward( point_clouds=pointclouds, gamma=(1e-4,) ), )