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
예제 #3
0
    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)
예제 #4
0
    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()
예제 #6
0
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
예제 #7
0
 def face_areas_normals():
     _C.face_areas_normals(verts, faces)
     torch.cuda.synchronize()