def test_face_areas(self): """ Check the results from face_areas cuda and PyTorch implementions are the same. Check that face_areas throws an error if cpu tensors are given as input. """ meshes = self.init_meshes(10, 1000, 3000, device="cuda:0") verts = meshes.verts_packed() faces = meshes.faces_packed() areas_torch = self.face_areas(verts, faces).squeeze() areas_cuda, _ = _C.face_areas_normals(verts, faces) self.assertTrue(torch.allclose(areas_torch, areas_cuda, atol=5e-8)) with self.assertRaises(Exception) as err: _C.face_areas_normals(verts.cpu(), faces.cpu()) self.assertTrue("Not implemented on the CPU" in str(err.exception))
def packed_to_padded_with_init( num_meshes: int, num_verts: int, num_faces: int, cuda: str = True ): device = "cuda" if cuda else "cpu" meshes = TestSamplePoints.init_meshes( num_meshes, num_verts, num_faces, device ) verts = meshes.verts_packed() faces = meshes.faces_packed() mesh_to_faces_packed_first_idx = meshes.mesh_to_faces_packed_first_idx() max_faces = meshes.num_faces_per_mesh().max().item() if cuda: areas, _ = _C.face_areas_normals(verts, faces) else: areas = TestSamplePoints.face_areas(verts, faces) torch.cuda.synchronize() def packed_to_padded(): if cuda: _C.packed_to_padded_tensor( areas, mesh_to_faces_packed_first_idx, max_faces ) else: TestSamplePoints.packed_to_padded_tensor( areas, mesh_to_faces_packed_first_idx, max_faces ) torch.cuda.synchronize() return packed_to_padded
def _test_face_areas_normals_helper(self, device): """ Check the results from face_areas cuda/cpp and PyTorch implementation are the same. """ meshes = self.init_meshes(10, 1000, 3000, device=device) verts = meshes.verts_packed() faces = meshes.faces_packed() areas_torch, normals_torch = self.face_areas_normals(verts, faces) areas, normals = _C.face_areas_normals(verts, faces) self.assertClose(areas_torch, areas, atol=1e-7) # normals get normalized by area thus sensitivity increases as areas # in our tests can be arbitrarily small. Thus we compare normals after # multiplying with areas unnormals = normals * areas.view(-1, 1) unnormals_torch = normals_torch * areas_torch.view(-1, 1) self.assertClose(unnormals_torch, unnormals, atol=1e-7)
def test_packed_to_padded_tensor(self): """ Check the results from packed_to_padded cuda and PyTorch implementions are the same. """ meshes = self.init_meshes(1, 3, 5, device="cuda:0") verts = meshes.verts_packed() faces = meshes.faces_packed() mesh_to_faces_packed_first_idx = meshes.mesh_to_faces_packed_first_idx( ) max_faces = meshes.num_faces_per_mesh().max().item() areas, _ = _C.face_areas_normals(verts, faces) areas_padded = _C.packed_to_padded_tensor( areas, mesh_to_faces_packed_first_idx, max_faces).cpu() areas_padded_cpu = TestSamplePoints.packed_to_padded_tensor( areas, mesh_to_faces_packed_first_idx, max_faces) self.assertTrue(torch.allclose(areas_padded, areas_padded_cpu)) with self.assertRaises(Exception) as err: _C.packed_to_padded_tensor(areas.cpu(), mesh_to_faces_packed_first_idx, max_faces) self.assertTrue("Not implemented on the CPU" in str(err.exception))
def face_areas(): if cuda: _C.face_areas_normals(verts, faces) else: TestSamplePoints.face_areas(verts, faces) torch.cuda.synchronize()
def sample_points_from_meshes( meshes, num_samples: int = 10000, return_normals: bool = False ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: """ Convert a batch of meshes to a pointcloud by uniformly sampling points on the surface of the mesh with probability proportional to the face area. Args: meshes: A Meshes object with a batch of N meshes. num_samples: Integer giving the number of point samples per mesh. return_normals: If True, return normals for the sampled points. eps: (float) used to clamp the norm of the normals to avoid dividing by 0. Returns: 2-element tuple containing - **samples**: FloatTensor of shape (N, num_samples, 3) giving the coordinates of sampled points for each mesh in the batch. For empty meshes the corresponding row in the samples array will be filled with 0. - **normals**: FloatTensor of shape (N, num_samples, 3) giving a normal vector to each sampled point. Only returned if return_normals is True. For empty meshes the corresponding row in the normals array will be filled with 0. """ if meshes.isempty(): raise ValueError("Meshes are empty.") verts = meshes.verts_packed() faces = meshes.faces_packed() mesh_to_face = meshes.mesh_to_faces_packed_first_idx() num_meshes = len(meshes) num_valid_meshes = torch.sum(meshes.valid) # Non empty meshes. # Intialize samples tensor with fill value 0 for empty meshes. samples = torch.zeros((num_meshes, num_samples, 3), device=meshes.device) # Only compute samples for non empty meshes with torch.no_grad(): areas, _ = _C.face_areas_normals(verts, faces) # Face areas can be zero. max_faces = meshes.num_faces_per_mesh().max().item() areas_padded = _C.packed_to_padded_tensor(areas, mesh_to_face[meshes.valid], max_faces) # (N, F) # TODO (gkioxari) Confirm multinomial bug is not present with real data. sample_face_idxs = areas_padded.multinomial( num_samples, replacement=True) # (N, num_samples) sample_face_idxs += mesh_to_face[meshes.valid].view( num_valid_meshes, 1) # Get the vertex coordinates of the sampled faces. face_verts = verts[faces.long()] v0, v1, v2 = face_verts[:, 0], face_verts[:, 1], face_verts[:, 2] # Randomly generate barycentric coords. w0, w1, w2 = _rand_barycentric_coords(num_valid_meshes, num_samples, verts.dtype, verts.device) # Use the barycentric coords to get a point on each sampled face. a = v0[sample_face_idxs] # (N, num_samples, 3) b = v1[sample_face_idxs] c = v2[sample_face_idxs] samples[meshes.valid] = (w0[:, :, None] * a + w1[:, :, None] * b + w2[:, :, None] * c) if return_normals: # Intialize normals tensor with fill value 0 for empty meshes. # Normals for the sampled points are face normals computed from # the vertices of the face in which the sampled point lies. normals = torch.zeros((num_meshes, num_samples, 3), device=meshes.device) vert_normals = (v1 - v0).cross(v2 - v1, dim=1) vert_normals = vert_normals / vert_normals.norm( dim=1, p=2, keepdim=True).clamp(min=sys.float_info.epsilon) vert_normals = vert_normals[sample_face_idxs] normals[meshes.valid] = vert_normals return samples, normals else: return samples
def face_areas_normals(): _C.face_areas_normals(verts, faces) torch.cuda.synchronize()