|
| 1 | +import os |
1 | 2 | import random |
2 | 3 |
|
3 | 4 | import numpy as np |
4 | 5 | import torch |
| 6 | +import random |
| 7 | +import open3d as o3d |
5 | 8 |
|
| 9 | +from tqdm import tqdm |
| 10 | +from pathlib import Path |
6 | 11 | from abc import ABC, abstractmethod |
7 | 12 | from typing import List |
8 | 13 |
|
9 | | -from smart_tree.data_types.cloud import Cloud |
| 14 | +from smart_tree.data_types.cloud import Cloud, LabelledCloud |
10 | 15 | from smart_tree.util.math.maths import euler_angles_to_rotation |
| 16 | +from smart_tree.util.file import ( |
| 17 | + load_o3d_mesh, |
| 18 | + save_o3d_mesh, |
| 19 | + save_o3d_cloud, |
| 20 | + load_o3d_cloud, |
| 21 | +) |
| 22 | +from smart_tree.util.mesh.geometries import o3d_cloud, o3d_sphere |
11 | 23 | from hydra.utils import call, get_original_cwd, instantiate, to_absolute_path |
12 | 24 |
|
13 | 25 |
|
@@ -73,6 +85,89 @@ def __call__(self, cloud): |
73 | 85 | ) |
74 | 86 |
|
75 | 87 |
|
| 88 | +class RandomMesh(Augmentation): |
| 89 | + def __init__( |
| 90 | + self, |
| 91 | + mesh_directory: Path, |
| 92 | + preprocessed_path: Path, |
| 93 | + voxel_size: float, |
| 94 | + number_meshes: int, |
| 95 | + min_size: float, |
| 96 | + max_size: float, |
| 97 | + max_pts: int, |
| 98 | + ): |
| 99 | + """We want to preprocess the meshes by getting them to the right scale, |
| 100 | + which is done by first converting them from mm to metres, we then |
| 101 | + scale them up to the max_size and then do a point sample, based on target voxel_size |
| 102 | + (ensure we have enough point density at the max size), then revert |
| 103 | + the scale back normal scale in metres. During inference we randomly scale based on the |
| 104 | + min_size and max_size and then translate the points and merge with the input cloud |
| 105 | + """ |
| 106 | + |
| 107 | + self.voxel_size = voxel_size |
| 108 | + self.number_meshes = number_meshes |
| 109 | + self.min_size = min_size |
| 110 | + self.max_size = max_size |
| 111 | + self.class_number = 2 |
| 112 | + self.preprocessed_path = preprocessed_path |
| 113 | + |
| 114 | + if not (os.path.exists(preprocessed_path)): |
| 115 | + os.makedirs(preprocessed_path) |
| 116 | + |
| 117 | + for mesh_path in tqdm( |
| 118 | + Path(mesh_directory).glob("*.stl"), |
| 119 | + desc="Preprocessing Meshes", |
| 120 | + leave=False, |
| 121 | + ): |
| 122 | + if os.path.isfile(f"{preprocessed_path}/{mesh_path.stem}.pcd"): |
| 123 | + continue |
| 124 | + try: |
| 125 | + mesh = load_o3d_mesh(str(mesh_path)) |
| 126 | + pcd = ( |
| 127 | + mesh.scale(0.001, mesh.get_center()) |
| 128 | + .translate(-mesh.get_center()) |
| 129 | + .paint_uniform_color(np.random.rand(3)) |
| 130 | + .scale(max_size, mesh.get_center()) |
| 131 | + .sample_points_uniformly( |
| 132 | + min( |
| 133 | + max(int(mesh.get_surface_area() / (voxel_size**2)), 10), |
| 134 | + max_pts, |
| 135 | + ) |
| 136 | + ) |
| 137 | + .scale(1 / max_size, mesh.get_center()) |
| 138 | + ) |
| 139 | + save_o3d_cloud(f"{preprocessed_path}/{mesh_path.stem}.pcd", pcd) |
| 140 | + except: |
| 141 | + print(f"Cloud Generation Failed on {mesh_path}") |
| 142 | + |
| 143 | + def __call__(self, cloud): |
| 144 | + centre, dimensions = cloud.bbox |
| 145 | + |
| 146 | + for i in range(self.number_meshes): |
| 147 | + random_pcd_path = random.choice(list(self.preprocessed_path.glob("*.pcd"))) |
| 148 | + pcd = load_o3d_cloud(str(random_pcd_path)) |
| 149 | + scaled_pcd = pcd.scale( |
| 150 | + random.uniform(self.min_size, self.max_size), pcd.get_center() |
| 151 | + ) |
| 152 | + |
| 153 | + lc = LabelledCloud.from_o3d_cld( |
| 154 | + pcd, |
| 155 | + class_l=torch.ones(np.asarray(pcd.points).shape[0]) * self.class_number, |
| 156 | + ) |
| 157 | + |
| 158 | + lc = lc.rotate( |
| 159 | + euler_angles_to_rotation(torch.rand(3) * torch.pi * 2).to( |
| 160 | + cloud.xyz.device |
| 161 | + ) |
| 162 | + ) |
| 163 | + lc = lc.translate(cloud.min_xyz - lc.centre) |
| 164 | + lc = lc.translate(dimensions * torch.rand(3)) |
| 165 | + |
| 166 | + cloud += lc |
| 167 | + |
| 168 | + return cloud |
| 169 | + |
| 170 | + |
76 | 171 | class RandomDropout(Augmentation): |
77 | 172 | def __init__(self, max_drop_out): |
78 | 173 | self.max_drop_out = max_drop_out |
@@ -127,13 +222,36 @@ def from_cfg(cfg): |
127 | 222 |
|
128 | 223 | if __name__ == "__main__": |
129 | 224 | from pathlib import Path |
130 | | - from smart_tree.util.file import load_cloud |
| 225 | + from smart_tree.util.file import load_data_npz |
131 | 226 | from smart_tree.util.visualizer.view import o3d_viewer |
132 | 227 |
|
133 | | - cld = load_cloud( |
134 | | - Path("/media/harry/harry's-data/PhD/training-data/apple/apple_1.npz") |
| 228 | + mesh_adder = RandomMesh( |
| 229 | + mesh_directory=Path("/local/Datasets/Thingi10K/raw_meshes/"), |
| 230 | + preprocessed_path=Path( |
| 231 | + "/local/uc-vision/smart-tree/data/things10K_sampled_1mm/" |
| 232 | + ), |
| 233 | + voxel_size=0.001, |
| 234 | + number_meshes=20, |
| 235 | + min_size=0.01, |
| 236 | + max_size=20, |
| 237 | + max_pts=50000, |
| 238 | + ) |
| 239 | + |
| 240 | + cld, _ = load_data_npz(Path("/local/_smart-tree/evaluation-data/gt/apple_12.npz")) |
| 241 | + |
| 242 | + cld = mesh_adder(cld) |
| 243 | + |
| 244 | + cld.view() |
| 245 | + |
| 246 | + o3d_viewer( |
| 247 | + [ |
| 248 | + cld.to_o3d_cld(), |
| 249 | + o3d_sphere(cld.min_xyz, radius=0.1, colour=(0, 1, 0)), |
| 250 | + o3d_sphere(cld.max_xyz, radius=0.1), |
| 251 | + ] |
135 | 252 | ) |
136 | 253 |
|
| 254 | + quit() |
137 | 255 | centre = CentreCloud() |
138 | 256 | rotater = FixedRotate(torch.tensor([torch.pi / 2, torch.pi / 2, torch.pi * 2])) |
139 | 257 | do = RandomDropout(0.5) |
|
0 commit comments