Example #1
0
 def _bm_load_ply(verts: torch.Tensor, faces: torch.Tensor,
                  decimal_places: int):
     f = StringIO()
     save_ply(f, verts, faces, decimal_places)
     s = f.getvalue()
     # Recreate stream so it's unaffected by how it was created.
     return lambda: load_ply(StringIO(s))
Example #2
0
    def test_simple_save(self):
        verts = torch.tensor(
            [[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [1, 2, 0]],
            dtype=torch.float32)
        faces = torch.tensor([[0, 1, 2], [0, 3, 4]])
        for filetype in BytesIO, TemporaryFile:
            lengths = {}
            for ascii in [True, False]:
                file = filetype()
                save_ply(file, verts=verts, faces=faces, ascii=ascii)
                lengths[ascii] = file.tell()

                file.seek(0)
                verts2, faces2 = load_ply(file)
                self.assertClose(verts, verts2)
                self.assertClose(faces, faces2)

                file.seek(0)
                if ascii:
                    file.read().decode("ascii")
                else:
                    with self.assertRaises(UnicodeDecodeError):
                        file.read().decode("ascii")

                if filetype is TemporaryFile:
                    file.close()
            self.assertLess(lengths[False], lengths[True],
                            "ascii should be longer")
Example #3
0
 def bm_load_simple_ply_with_init(V: int, F: int):
     verts = torch.tensor([[0.1, 0.2, 0.3]]).expand(V, 3)
     faces = torch.tensor([[0, 1, 2]], dtype=torch.int64).expand(F, 3)
     ply_file = StringIO()
     save_ply(ply_file, verts=verts, faces=faces)
     ply = ply_file.getvalue()
     # Recreate stream so it's unaffected by how it was created.
     return lambda: load_ply(StringIO(ply))
Example #4
0
 def test_normals_save(self):
     verts = torch.tensor([[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]],
                          dtype=torch.float32)
     faces = torch.tensor([[0, 1, 2], [0, 2, 3]])
     normals = torch.tensor([[0, 1, 0], [1, 0, 0], [0, 0, 1], [1, 0, 0]],
                            dtype=torch.float32)
     file = StringIO()
     save_ply(file, verts=verts, faces=faces, verts_normals=normals)
     file.close()
Example #5
0
 def test_simple_save(self):
     verts = torch.tensor([[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]],
                          dtype=torch.float32)
     faces = torch.tensor([[0, 1, 2], [0, 3, 4]])
     file = StringIO()
     save_ply(file, verts=verts, faces=faces)
     file.seek(0)
     verts2, faces2 = load_ply(file)
     self.assertClose(verts, verts2)
     self.assertClose(faces, faces2)
Example #6
0
    def test_save_ply_invalid_indices(self):
        message_regex = "Faces have invalid indices"
        verts = torch.FloatTensor([[0.1, 0.2, 0.3]])
        faces = torch.LongTensor([[0, 1, 2]])
        with self.assertWarnsRegex(UserWarning, message_regex):
            save_ply(StringIO(), verts, faces)

        faces = torch.LongTensor([[-1, 0, 1]])
        with self.assertWarnsRegex(UserWarning, message_regex):
            save_ply(StringIO(), verts, faces)
Example #7
0
 def _test_save_load(self, verts, faces):
     f = StringIO()
     save_ply(f, verts, faces)
     f.seek(0)
     # raise Exception(f.getvalue())
     expected_verts, expected_faces = verts, faces
     if not len(expected_verts):  # Always compare with a (V, 3) tensor
         expected_verts = torch.zeros(size=(0, 3), dtype=torch.float32)
     if not len(expected_faces):  # Always compare with an (F, 3) tensor
         expected_faces = torch.zeros(size=(0, 3), dtype=torch.int64)
     actual_verts, actual_faces = load_ply(f)
     self.assertClose(expected_verts, actual_verts)
     self.assertClose(expected_faces, actual_faces)
Example #8
0
    def load_ply_bm(V: int, F: int):
        verts = torch.tensor([[0.1, 0.2, 0.3]]).expand(V, 3)
        faces = torch.tensor([[0, 1, 2]], dtype=torch.int64).expand(F, 3)
        ply_file = StringIO()
        save_ply(ply_file, verts=verts, faces=faces)
        ply = ply_file.getvalue()

        # Recreate stream so it's unaffected by how it was created.

        def load_mesh():
            ply_file = StringIO(ply)
            verts, faces = load_ply(ply_file)

        return load_mesh
Example #9
0
    def test_save_ply_invalid_shapes(self):
        # Invalid vertices shape
        with self.assertRaises(ValueError) as error:
            verts = torch.FloatTensor([[0.1, 0.2, 0.3, 0.4]])  # (V, 4)
            faces = torch.LongTensor([[0, 1, 2]])
            save_ply(StringIO(), verts, faces)
        expected_message = "Argument 'verts' should either be empty or of shape (num_verts, 3)."
        self.assertTrue(expected_message, error.exception)

        # Invalid faces shape
        with self.assertRaises(ValueError) as error:
            verts = torch.FloatTensor([[0.1, 0.2, 0.3]])
            faces = torch.LongTensor([[0, 1, 2, 3]])  # (F, 4)
            save_ply(StringIO(), verts, faces)
        expected_message = "Argument 'faces' should either be empty or of shape (num_faces, 3)."
        self.assertTrue(expected_message, error.exception)
Example #10
0
 def _bm_save_ply(verts: torch.Tensor, faces: torch.Tensor,
                  decimal_places: int):
     return lambda: save_ply(
         BytesIO(),
         verts=verts,
         faces=faces,
         ascii=True,
         decimal_places=decimal_places,
     )
Example #11
0
 def _bm_save_ply(verts: torch.Tensor, faces: torch.Tensor,
                  decimal_places: int):
     return lambda: save_ply(
         StringIO(), verts, faces, decimal_places=decimal_places)
Example #12
0
 def save_mesh():
     file = StringIO()
     save_ply(file, verts_list, faces_list, 2)
Example #13
0
    def test_pcl_normals(self,
                         batch_size=3,
                         num_points=300,
                         neighborhood_size=50):
        """
        Tests the normal estimation on a spherical point cloud, where
        we know the ground truth normals.
        """
        device = torch.device("cuda:0")
        # run several times for different random point clouds
        for run_idx in range(3):
            # either use tensors or Pointclouds as input
            for use_pointclouds in (True, False):
                # get a spherical point cloud
                pcl, normals_gt = TestPCLNormals.init_spherical_pcl(
                    num_points=num_points,
                    batch_size=batch_size,
                    device=device,
                    use_pointclouds=use_pointclouds,
                )
                if use_pointclouds:
                    normals_gt = pcl.normals_padded()
                    num_pcl_points = pcl.num_points_per_cloud()
                else:
                    num_pcl_points = [pcl.shape[1]] * batch_size

                # check for both disambiguation options
                for disambiguate_directions in (True, False):
                    (
                        curvatures,
                        local_coord_frames,
                    ) = estimate_pointcloud_local_coord_frames(
                        pcl,
                        neighborhood_size=neighborhood_size,
                        disambiguate_directions=disambiguate_directions,
                    )

                    # estimate the normals
                    normals = estimate_pointcloud_normals(
                        pcl,
                        neighborhood_size=neighborhood_size,
                        disambiguate_directions=disambiguate_directions,
                    )

                    # TODO: temporarily disabled
                    if use_pointclouds:
                        # test that the class method gives the same output
                        normals_pcl = pcl.estimate_normals(
                            neighborhood_size=neighborhood_size,
                            disambiguate_directions=disambiguate_directions,
                            assign_to_self=True,
                        )
                        normals_from_pcl = pcl.normals_padded()
                        for nrm, nrm_from_pcl, nrm_pcl, np in zip(
                                normals, normals_from_pcl, normals_pcl,
                                num_pcl_points):
                            self.assertClose(nrm[:np], nrm_pcl[:np], atol=1e-5)
                            self.assertClose(nrm[:np],
                                             nrm_from_pcl[:np],
                                             atol=1e-5)

                    # check that local coord frames give the same normal
                    # as normals
                    for nrm, lcoord, np in zip(normals, local_coord_frames,
                                               num_pcl_points):
                        self.assertClose(nrm[:np],
                                         lcoord[:np, :, 0],
                                         atol=1e-5)

                    # dotp between normals and normals_gt
                    normal_parallel = (normals_gt * normals).sum(2)

                    # check that normals are on average
                    # parallel to the expected ones
                    for normp, np in zip(normal_parallel, num_pcl_points):
                        abs_parallel = normp[:np].abs()
                        avg_parallel = abs_parallel.mean()
                        std_parallel = abs_parallel.std()
                        self.assertClose(avg_parallel,
                                         torch.ones_like(avg_parallel),
                                         atol=1e-2)
                        self.assertClose(std_parallel,
                                         torch.zeros_like(std_parallel),
                                         atol=1e-2)

                    if disambiguate_directions:
                        # check that 95% of normal dot products
                        # have the same sign
                        for normp, np in zip(normal_parallel, num_pcl_points):
                            n_pos = (normp[:np] > 0).sum()
                            self.assertTrue((n_pos > np * 0.95)
                                            or (n_pos < np * 0.05))

                    if DEBUG and run_idx == 0 and not use_pointclouds:
                        import os
                        from pytorch3d.io.ply_io import save_ply

                        # export to .ply
                        outdir = "/tmp/pt3d_pcl_normals_test/"
                        os.makedirs(outdir, exist_ok=True)
                        plyfile = os.path.join(
                            outdir,
                            f"pcl_disamb={disambiguate_directions}.ply")
                        print(
                            f"Storing point cloud with normals to {plyfile}.")
                        pcl_idx = 0
                        save_ply(
                            plyfile,
                            pcl[pcl_idx].cpu(),
                            faces=None,
                            verts_normals=normals[pcl_idx].cpu(),
                        )
Example #14
0
 def bm_save_simple_ply_with_init(V: int, F: int):
     verts_list = torch.tensor(V * [[0.11, 0.22, 0.33]]).view(-1, 3)
     faces_list = torch.tensor(F * [[0, 1, 2]]).view(-1, 3)
     return lambda: save_ply(
         StringIO(), verts_list, faces_list, decimal_places=2)