def test_load_pointcloud_bad_order(self): """ Ply file with a strange property order """ file = "\n".join([ "ply", "format ascii 1.0", "element vertex 1", "property uchar green", "property float x", "property float z", "property uchar red", "property float y", "property uchar blue", "end_header", "1 2 3 4 5 6", ]) io = IO() pointcloud_gpu = io.load_pointcloud(StringIO(file), device="cuda:0") self.assertEqual(pointcloud_gpu.device, torch.device("cuda:0")) pointcloud = pointcloud_gpu.to(torch.device("cpu")) expected_points = torch.tensor([[[2, 5, 3]]], dtype=torch.float32) expected_features = torch.tensor([[[4, 1, 6]]], dtype=torch.float32) self.assertClose(pointcloud.points_padded(), expected_points) self.assertClose(pointcloud.features_padded(), expected_features)
def test_save_load_with_normals(self): points = torch.tensor([[0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]], dtype=torch.float32) normals = torch.tensor([[0, 1, 0], [1, 0, 0], [1, 4, 1], [1, 0, 0]], dtype=torch.float32) features = torch.rand_like(points) for do_features, do_normals in itertools.product([True, False], [True, False]): cloud = Pointclouds( points=[points], features=[features] if do_features else None, normals=[normals] if do_normals else None, ) device = torch.device("cuda:0") io = IO() with NamedTemporaryFile(mode="w", suffix=".ply") as f: io.save_pointcloud(cloud.cuda(), f.name) f.flush() cloud2 = io.load_pointcloud(f.name, device=device) self.assertEqual(cloud2.device, device) cloud2 = cloud2.cpu() self.assertClose(cloud2.points_padded(), cloud.points_padded()) if do_normals: self.assertClose(cloud2.normals_padded(), cloud.normals_padded()) else: self.assertIsNone(cloud.normals_padded()) self.assertIsNone(cloud2.normals_padded()) if do_features: self.assertClose(cloud2.features_packed(), features) else: self.assertIsNone(cloud2.features_packed())
def test_save_pointcloud(self): header = "\n".join([ "ply", "format binary_little_endian 1.0", "element vertex 8", "property float x", "property float y", "property float z", "property float red", "property float green", "property float blue", "end_header", "", ]).encode("ascii") data = struct.pack("<" + "f" * 48, *range(48)) points = torch.FloatTensor([0, 1, 2]) + 6 * torch.arange(8)[:, None] features = torch.FloatTensor([3, 4, 5]) + 6 * torch.arange(8)[:, None] pointcloud = Pointclouds(points=[points], features=[features]) io = IO() with NamedTemporaryFile(mode="rb", suffix=".ply") as f: io.save_pointcloud(data=pointcloud, path=f.name) f.flush() f.seek(0) actual_data = f.read() reloaded_pointcloud = io.load_pointcloud(f.name) self.assertEqual(header + data, actual_data) self.assertClose(reloaded_pointcloud.points_list()[0], points) self.assertClose(reloaded_pointcloud.features_list()[0], features) with NamedTemporaryFile(mode="r", suffix=".ply") as f: io.save_pointcloud(data=pointcloud, path=f.name, binary=False) reloaded_pointcloud2 = io.load_pointcloud(f.name) self.assertEqual(f.readline(), "ply\n") self.assertEqual(f.readline(), "format ascii 1.0\n") self.assertClose(reloaded_pointcloud2.points_list()[0], points) self.assertClose(reloaded_pointcloud2.features_list()[0], features)
def test_load_cloudcompare_pointcloud(self): """ Test loading a pointcloud styled like some cloudcompare output. cloudcompare is an open source 3D point cloud processing software. """ header = "\n".join([ "ply", "format binary_little_endian 1.0", "obj_info Not a key-value pair!", "element vertex 8", "property double x", "property double y", "property double z", "property uchar red", "property uchar green", "property uchar blue", "property float my_Favorite", "end_header", "", ]).encode("ascii") data = struct.pack("<" + "dddBBBf" * 8, *range(56)) io = IO() with NamedTemporaryFile(mode="wb", suffix=".ply") as f: f.write(header) f.write(data) f.flush() pointcloud = io.load_pointcloud(f.name) self.assertClose( pointcloud.points_padded()[0], torch.FloatTensor([0, 1, 2]) + 7 * torch.arange(8)[:, None], ) self.assertClose( pointcloud.features_padded()[0], torch.FloatTensor([3, 4, 5]) + 7 * torch.arange(8)[:, None], )
def test_save_pointcloud(self): header = "\n".join([ "ply", "format binary_little_endian 1.0", "element vertex 8", "property float x", "property float y", "property float z", "property float red", "property float green", "property float blue", "end_header", "", ]).encode("ascii") data = struct.pack("<" + "f" * 48, *range(48)) points = torch.FloatTensor([0, 1, 2]) + 6 * torch.arange(8)[:, None] features_large = torch.FloatTensor([3, 4, 5 ]) + 6 * torch.arange(8)[:, None] features = features_large / 255.0 pointcloud_largefeatures = Pointclouds(points=[points], features=[features_large]) pointcloud = Pointclouds(points=[points], features=[features]) io = IO() with NamedTemporaryFile(mode="rb", suffix=".ply") as f: io.save_pointcloud(data=pointcloud_largefeatures, path=f.name) f.flush() f.seek(0) actual_data = f.read() reloaded_pointcloud = io.load_pointcloud(f.name) self.assertEqual(header + data, actual_data) self.assertClose(reloaded_pointcloud.points_list()[0], points) self.assertClose(reloaded_pointcloud.features_list()[0], features_large) # Test the load-save cycle leaves file completely unchanged with NamedTemporaryFile(mode="rb", suffix=".ply") as f: io.save_pointcloud( data=reloaded_pointcloud, path=f.name, ) f.flush() f.seek(0) data2 = f.read() self.assertEqual(data2, actual_data) with NamedTemporaryFile(mode="r", suffix=".ply") as f: io.save_pointcloud(data=pointcloud, path=f.name, binary=False, decimal_places=9) reloaded_pointcloud2 = io.load_pointcloud(f.name) self.assertEqual(f.readline(), "ply\n") self.assertEqual(f.readline(), "format ascii 1.0\n") self.assertClose(reloaded_pointcloud2.points_list()[0], points) self.assertClose(reloaded_pointcloud2.features_list()[0], features) for binary in [True, False]: with NamedTemporaryFile(mode="rb", suffix=".ply") as f: io.save_pointcloud(data=pointcloud, path=f.name, colors_as_uint8=True, binary=binary) f.flush() f.seek(0) actual_data = f.read() reloaded_pointcloud3 = io.load_pointcloud(f.name) self.assertClose(reloaded_pointcloud3.features_list()[0], features) self.assertIn(b"property uchar green", actual_data) # Test the load-save cycle leaves file completely unchanged with NamedTemporaryFile(mode="rb", suffix=".ply") as f: io.save_pointcloud( data=reloaded_pointcloud3, path=f.name, binary=binary, colors_as_uint8=True, ) f.flush() f.seek(0) data2 = f.read() self.assertEqual(data2, actual_data)