1+ import os
12import random
23
34import numpy as np
45import torch
56import random
67import open3d as o3d
78
9+ from tqdm import tqdm
810from pathlib import Path
911from abc import ABC , abstractmethod
1012from typing import List
1113
1214from smart_tree .data_types .cloud import Cloud , LabelledCloud
1315from smart_tree .util .math .maths import euler_angles_to_rotation
14- from smart_tree .util .file import load_o3d_mesh
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
1523from hydra .utils import call , get_original_cwd , instantiate , to_absolute_path
1624
1725
@@ -81,45 +89,81 @@ class RandomMesh(Augmentation):
8189 def __init__ (
8290 self ,
8391 mesh_directory : Path ,
92+ preprocessed_path : Path ,
8493 voxel_size : float ,
8594 number_meshes : int ,
8695 min_size : float ,
8796 max_size : float ,
97+ max_pts : int ,
8898 ):
89- self .mesh_paths = list (mesh_directory .glob ("*" ))
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+
90107 self .voxel_size = voxel_size
91108 self .number_meshes = number_meshes
92109 self .min_size = min_size
93110 self .max_size = max_size
94- self .class_number = 3
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 } " )
95142
96143 def __call__ (self , cloud ):
144+ centre , dimensions = cloud .bbox
145+
97146 for i in range (self .number_meshes ):
98- mesh = load_o3d_mesh (
99- str (self .mesh_paths [random .randint (0 , len (self .mesh_paths ))])
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 ()
100151 )
101152
102- mesh = mesh .scale (0.01 , center = mesh .get_center ())
103-
104- mesh = mesh .translate (- mesh .get_center ())
105-
106- mesh_pts = mesh .sample_points_uniformly (
107- int (1000 * mesh .get_surface_area () / self .voxel_size ),
108- ).paint_uniform_color (np .random .rand (3 ))
109-
110153 lc = LabelledCloud .from_o3d_cld (
111- mesh_pts ,
112- class_l = torch .ones (np .asarray (mesh_pts .points ).shape [0 ])
113- * self .class_number ,
154+ pcd ,
155+ class_l = torch .ones (np .asarray (pcd .points ).shape [0 ]) * self .class_number ,
114156 )
115157
116- cloud += lc
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 ))
117165
118- # load random mesh
119- # voxelize mesh
120- # create labelled cloud
121- # randomly translate / rotate it
122- # return new cloud
166+ cloud += lc
123167
124168 return cloud
125169
@@ -183,10 +227,14 @@ def from_cfg(cfg):
183227
184228 mesh_adder = RandomMesh (
185229 mesh_directory = Path ("/local/Datasets/Thingi10K/raw_meshes/" ),
186- voxel_size = 0.01 ,
187- number_meshes = 5 ,
230+ preprocessed_path = Path (
231+ "/local/uc-vision/smart-tree/data/things10K_sampled_1mm/"
232+ ),
233+ voxel_size = 0.001 ,
234+ number_meshes = 20 ,
188235 min_size = 0.01 ,
189- max_size = 0.5 ,
236+ max_size = 20 ,
237+ max_pts = 50000 ,
190238 )
191239
192240 cld , _ = load_data_npz (Path ("/local/_smart-tree/evaluation-data/gt/apple_12.npz" ))
@@ -195,7 +243,13 @@ def from_cfg(cfg):
195243
196244 cld .view ()
197245
198- # o3d_viewer([cld])
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+ ]
252+ )
199253
200254 quit ()
201255 centre = CentreCloud ()
0 commit comments