├── .gitignore ├── .vscode └── settings.json ├── CMakeLists.txt ├── README.md ├── generate_data.py ├── generate_results.py ├── graph_filter.h ├── output_cube.png ├── sample_point_cloud.py ├── utils.py └── wrapper.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | data/* 3 | 4 | # Desktop service store files 5 | *.DS_Store 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/Users/pmkalshetti/virtualenv/bin/python" 3 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(graph_filter) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | # Pybind11 10 | find_package(pybind11 REQUIRED) 11 | 12 | # OpenMP 13 | find_package(OpenMP REQUIRED) 14 | 15 | # Eigen 16 | find_package(Eigen3 3.3 REQUIRED NO_MODULE) 17 | 18 | # Open3D 19 | list(APPEND CMAKE_INSTALL_PREFIX "~/libraries/") 20 | find_package(Open3D HINTS ${CMAKE_INSTALL_PREFIX}/lib/CMake) 21 | list(APPEND Open3D_LIBRARIES dl) 22 | 23 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Open3D_CXX_FLAGS}") 24 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${Open3D_EXE_LINKER_FLAGS}") 25 | 26 | include_directories(${Open3D_INCLUDE_DIRS}) 27 | link_directories(${Open3D_LIBRARY_DIRS}) 28 | 29 | pybind11_add_module( 30 | graph_filter 31 | wrapper.cpp 32 | ) 33 | target_link_libraries(graph_filter PRIVATE ${Open3D_LIBRARIES} Eigen3::Eigen OpenMP::OpenMP_CXX) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fast Sampling of Point Cloud using Graphs 2 | 3 | The idea is motivated from the paper: [Fast Resampling of Three-Dimensional Point Clouds via Graphs](https://ieeexplore.ieee.org/abstract/document/8101025). 4 | 5 | ## Code 6 | The code is implemented in `Python` and `C++`. 7 | 8 | ### Requirements 9 | - [pybind11](https://pybind11.readthedocs.io/en/stable/) 10 | - [Open3D](http://www.open3d.org/) 11 | - [NumPy](https://numpy.org/) 12 | - [Eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page) 13 | - [OpenMP](https://www.openmp.org/) 14 | 15 | ## Usage 16 | #### Compilation 17 | ```bash 18 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 19 | cmake --build build/ 20 | ``` 21 | ```python 22 | python sample_point_cloud.py data/cube.ply 23 | ``` 24 | 25 | ## Results 26 | #### 1. Cube 27 | ![Output for cube](output_cube.png) 28 | **Input point cloud in black color, filtered point cloud in red color* 29 | 30 | 31 | ## Reference 32 | 33 | @article{chen2017fast, 34 | title={Fast resampling of three-dimensional point clouds via graphs}, 35 | author={Chen, Siheng and Tian, Dong and Feng, Chen and Vetro, Anthony and Kova{\v{c}}evi{\'c}, Jelena}, 36 | journal={IEEE Transactions on Signal Processing}, 37 | volume={66}, 38 | number={3}, 39 | pages={666--681}, 40 | year={2017}, 41 | publisher={IEEE} 42 | } -------------------------------------------------------------------------------- /generate_data.py: -------------------------------------------------------------------------------- 1 | from utils import write_pcd, create_box, create_cone, create_cylinder, create_moebius, create_sphere 2 | 3 | if __name__ == "__main__": 4 | write_pcd(create_box(10000, 10), "data/box.ply") 5 | write_pcd(create_cone(10000, 10, 30), "data/cone.ply") 6 | write_pcd(create_cylinder(10000, 10, 30), "data/cylinder.ply") 7 | write_pcd(create_moebius(10000, 10.0), "data/moebius.ply") 8 | write_pcd(create_sphere(10000, 10.0), "data/sphere.ply") 9 | -------------------------------------------------------------------------------- /generate_results.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | from utils import sample_pcd, draw, read_data, add_noise, write_pcd 4 | 5 | 6 | def generate_noiseless(path_data, n_samples, prefix_save, scale_min_dist, scale_max_dist): 7 | pcd_orig = read_data(path_data) 8 | pcd_orig.paint_uniform_color([0, 0, 0]) # original points are black 9 | 10 | pcd_all = sample_pcd(pcd_orig, "all", n_samples, scale_min_dist, scale_max_dist) 11 | write_pcd(pcd_all, f"{prefix_save}all.ply") 12 | pcd_all.paint_uniform_color([1, 0, 0]) 13 | 14 | # pcd_low = sample_pcd(pcd_orig, "low", n_samples, scale_min_dist, scale_max_dist) 15 | # write_pcd(pcd_low, f"{prefix_save}_low.ply") 16 | # pcd_low.paint_uniform_color([1, 0, 0]) 17 | 18 | pcd_high = sample_pcd(pcd_orig, "high", n_samples, scale_min_dist, scale_max_dist) 19 | write_pcd(pcd_high, f"{prefix_save}high.ply") 20 | pcd_high.paint_uniform_color([1, 0, 0]) 21 | 22 | translate = 25 23 | # draw([pcd_orig, pcd_all.translate([translate, 0, 0]), pcd_low.translate([2*translate, 0, 0]), pcd_high.translate([3*translate, 0, 0])]) 24 | draw([pcd_orig, pcd_all.translate([translate, 0, 0]), pcd_high.translate([2*translate, 0, 0])]) 25 | 26 | 27 | def generate_noisy(std, path_data, n_samples, prefix_save, scale_min_dist, scale_max_dist): 28 | pcd_orig = read_data(path_data) 29 | pcd_orig = add_noise(pcd_orig, std) 30 | pcd_orig.paint_uniform_color([0, 0, 0]) # original points are black 31 | 32 | pcd_all = sample_pcd(pcd_orig, "all", n_samples, scale_min_dist, scale_max_dist) 33 | write_pcd(pcd_all, f"{prefix_save}noisy_all.ply") 34 | pcd_all.paint_uniform_color([1, 0, 0]) 35 | 36 | # pcd_low = sample_pcd(pcd_orig, "low", n_samples, scale_min_dist, scale_max_dist) 37 | # write_pcd(pcd_low, f"{prefix_save}_noisy_low.ply") 38 | # pcd_low.paint_uniform_color([1, 0, 0]) 39 | 40 | pcd_high = sample_pcd(pcd_orig, "high", n_samples, scale_min_dist, scale_max_dist) 41 | write_pcd(pcd_high, f"{prefix_save}noisy_high.ply") 42 | pcd_high.paint_uniform_color([1, 0, 0]) 43 | 44 | translate = 25 45 | # draw([pcd_orig, pcd_all.translate([translate, 0, 0]), pcd_low.translate([2*translate, 0, 0]), pcd_high.translate([3*translate, 0, 0])]) 46 | draw([pcd_orig, pcd_all.translate([translate, 0, 0]), pcd_high.translate([2*translate, 0, 0])]) 47 | 48 | 49 | if __name__ == "__main__": 50 | np.random.seed(1) 51 | generate_noiseless("data/box.ply", 1000, "output/box/", 10, 10) 52 | generate_noiseless("data/cone.ply", 1000, "output/cone/", 10, 10) 53 | generate_noiseless("data/cylinder.ply", 1000, "output/cylinder/", 10, 10) 54 | generate_noiseless("data/moebius.ply", 1000, "output/moebius/", 10, 10) 55 | generate_noiseless("data/sphere.ply", 1000, "output/sphere/", 10, 10) 56 | 57 | 58 | # generate_noisy(0.1, "data/cone.ply", 500, "output/cone/", 10, 10) 59 | # generate_noisy(0.1, "data/box.ply", 500, "output/box/", 5000, 500) -------------------------------------------------------------------------------- /graph_filter.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace Eigen; 14 | 15 | using kdree_t = open3d::geometry::KDTreeFlann; 16 | using MatrixXdR = Eigen::Matrix; 17 | using MatrixX3dR = Eigen::Matrix; 18 | 19 | void compute_resolution(const MatrixXd &points, const kdree_t &kdtree, double &min_dist, double &max_dist) 20 | { 21 | min_dist = std::numeric_limits::max(); 22 | max_dist = std::numeric_limits::min(); 23 | double curr_dist{0.0}; 24 | 25 | #pragma omp parallel for 26 | for (long i = 0; i < points.rows(); ++i) 27 | { 28 | const int k{2}; 29 | std::vector ids; 30 | std::vector dists_squared; 31 | kdtree.SearchKNN(points.row(i), k, ids, dists_squared); 32 | assert(ids[0] == i || dists_squared[1] > 0.00000001); 33 | 34 | #pragma omp critical 35 | { 36 | curr_dist = std::sqrt(dists_squared[1]); 37 | if (curr_dist > max_dist) 38 | max_dist = curr_dist; 39 | if (curr_dist < min_dist) 40 | min_dist = curr_dist; 41 | } 42 | } 43 | } 44 | 45 | SparseMatrix compute_adjacency_matrix(const MatrixXd &points, const kdree_t &kdtree, const double radius, const int max_neighbors) 46 | { 47 | const int n_points = points.rows(); 48 | 49 | struct SparseElem 50 | { 51 | int row; 52 | int col; 53 | double value; 54 | }; 55 | 56 | // init triplets vector array for each point 57 | std::vector> adj_lists(n_points); 58 | for (auto &adj_list : adj_lists) 59 | adj_list.reserve(max_neighbors); 60 | 61 | // loop over each point 62 | #pragma omp parallel for 63 | for (int i = 0; i < n_points; ++i) 64 | { 65 | // find ids and distances of neighbors in radius 66 | std::vector ids; 67 | std::vector dists_squared; 68 | int n_found = kdtree.SearchRadius(points.row(i), radius, ids, dists_squared); 69 | if (!n_found) 70 | continue; 71 | 72 | // matched ids should be greater than id of current point 73 | assert(i == ids[0]); 74 | #pragma omp critical 75 | for (int j = 1; j < n_found; ++j) 76 | { 77 | assert(dists_squared[j] > 0.00000001); 78 | 79 | // ensure id of point 1 < id of point 2 80 | int id_r; 81 | int id_c; 82 | if (i < ids[j]) 83 | { 84 | id_r = i; 85 | id_c = ids[j]; 86 | } 87 | else 88 | { 89 | id_r = ids[j]; 90 | id_c = i; 91 | } 92 | 93 | // check if this pair is already filled 94 | bool pair_visited = false; 95 | for (int k = 0; k < adj_lists[id_r].size() && !pair_visited; ++k) 96 | { 97 | if (adj_lists[id_r][k].row == id_r && adj_lists[id_r][k].col == id_c) 98 | pair_visited = true; 99 | } 100 | 101 | // create triplet and append to ith row 102 | if (!pair_visited && adj_lists[id_r].size() < max_neighbors) 103 | { 104 | adj_lists[id_r].push_back({id_r, id_c, dists_squared[j]}); 105 | } 106 | 107 | // if ith row exceeds max size, then replace with lowest weight 108 | else if (!pair_visited && adj_lists[id_r].size() == max_neighbors) 109 | { 110 | // find farthest negihbor of current point in existing set 111 | double max_dist{0.0}; 112 | int max_id{-1}; 113 | for (int k = 0; k < max_neighbors; ++k) 114 | { 115 | if (adj_lists[id_r][k].value > max_dist) 116 | { 117 | max_dist = adj_lists[id_r][k].value; 118 | max_id = k; 119 | } 120 | } 121 | 122 | // replace with new if closer than farthest 123 | if (max_dist > dists_squared[j]) 124 | { 125 | adj_lists[id_r][max_id].row = id_r; 126 | adj_lists[id_r][max_id].col = id_c; 127 | adj_lists[id_r][max_id].value = dists_squared[j]; 128 | } 129 | } 130 | } 131 | } 132 | 133 | // compute variance 134 | double total_dist_squared{0.0}; 135 | int n_entries{0}; 136 | for (const auto &adj_list : adj_lists) 137 | { 138 | for (const auto &elem : adj_list) 139 | total_dist_squared += elem.value; 140 | n_entries += adj_list.size(); 141 | } 142 | double avg_dist_squared{total_dist_squared / n_entries}; 143 | 144 | double var_dist{0.0}; 145 | for (const auto &adj_list : adj_lists) 146 | { 147 | for (const auto &elem : adj_list) 148 | { 149 | var_dist += std::pow(elem.value - avg_dist_squared, 2); 150 | } 151 | } 152 | var_dist /= n_entries; 153 | 154 | // construct W using above sparse elements (uses exp and variance) 155 | std::vector> triplets; 156 | triplets.reserve(n_entries); 157 | for (const auto &adj_list : adj_lists) 158 | { 159 | for (const auto &elem : adj_list) 160 | { 161 | triplets.push_back(Triplet(elem.row, elem.col, std::exp(-std::pow(elem.value, 2) / (2 * var_dist)))); 162 | } 163 | } 164 | SparseMatrix W(n_points, n_points); 165 | W.setFromTriplets(triplets.begin(), triplets.end()); 166 | W.makeCompressed(); 167 | 168 | // fill the other triangle 169 | SparseMatrix Wt = SparseMatrix(W.transpose()); 170 | W += Wt; 171 | 172 | return W; 173 | } 174 | 175 | VectorXd apply_filter(const MatrixXd &points, const SparseMatrix &L) 176 | { 177 | MatrixXd T = L * points; 178 | VectorXd scores = T.rowwise().norm(); 179 | return scores; 180 | } 181 | 182 | SparseMatrix compute_D(const SparseMatrix &W) 183 | { 184 | const int n_points = W.rows(); // or cols 185 | SparseMatrix D(n_points, n_points); 186 | D.reserve(n_points); // diagonal 187 | for (int i = 0; i < W.outerSize(); ++i) 188 | { 189 | double row_sum{0.0}; 190 | for (SparseMatrix::InnerIterator it(W, i); it; ++it) 191 | { 192 | row_sum += it.value(); 193 | } 194 | D.coeffRef(i, i) = row_sum; 195 | } 196 | 197 | return D; 198 | } 199 | 200 | VectorXd compute_scores(const MatrixXd &points, const std::string filter_type, const int scale_min_dist, const int scale_max_dist) 201 | // VectorXd compute_scores(const MatrixXd &points, const std::string filter_type) 202 | { 203 | const int n_points = points.rows(); 204 | 205 | kdree_t kdtree(points.transpose()); // requires rows = dimension 206 | 207 | double min_dist{0.0}; 208 | double max_dist{0.0}; 209 | compute_resolution(points, kdtree, min_dist, max_dist); 210 | 211 | // const double radius = std::min(min_dist * 10, max_dist * 2); 212 | const double radius = std::min(min_dist * scale_min_dist, max_dist * scale_max_dist); 213 | std::cout << "\nResolution: " << min_dist << ", " << max_dist << ", " << radius << "\n"; 214 | // const double radius = std::min(min_dist, max_dist); 215 | const int max_neighbors = 100; 216 | 217 | // adjacency matrix W 218 | SparseMatrix W = compute_adjacency_matrix(points, kdtree, radius, max_neighbors); 219 | 220 | // // compute D 221 | // SparseMatrix D(n_points, n_points); 222 | // D.reserve(n_points); // diagonal 223 | // for (int i = 0; i < W.outerSize(); ++i) 224 | // { 225 | // double row_sum{0.0}; 226 | // for (SparseMatrix::InnerIterator it(W, i); it; ++it) 227 | // { 228 | // row_sum += it.value(); 229 | // } 230 | // D.coeffRef(i, i) = row_sum; 231 | // } 232 | 233 | // // compute L 234 | // SparseMatrix L(n_points, n_points); 235 | // L = D - W; 236 | 237 | SparseMatrix F(n_points, n_points); 238 | if (filter_type == "all") 239 | { 240 | F.setIdentity(); 241 | } 242 | else if (filter_type == "high") 243 | { 244 | // F = D - W 245 | SparseMatrix D = compute_D(W); 246 | F = D - W; 247 | } 248 | else if (filter_type == "low") 249 | { 250 | // F = D^-1 * W 251 | F = W; 252 | SparseMatrix D = compute_D(W); 253 | for (int i = 0; i < F.outerSize(); ++i) 254 | { 255 | double row_sum{0.0}; 256 | for (SparseMatrix::InnerIterator it(F, i); it; ++it) 257 | { 258 | it.valueRef() *= D.coeff(i, i) + 1; 259 | } 260 | } 261 | } 262 | else 263 | { 264 | std::cout << "ERROR! filter type has to be among {high, low, all}\n"; 265 | std::exit(-1); 266 | } 267 | 268 | // apply filter 269 | VectorXd scores = apply_filter(points, F); 270 | 271 | return scores; 272 | } 273 | 274 | std::vector sample_points(const int n_samples, const std::vector &points_vec, const std::string filter_type, const int scale_min_dist, const int scale_max_dist) 275 | { 276 | const Map points(const_cast(&points_vec[0](0)), points_vec.size(), 3); 277 | const VectorXd scores = compute_scores(points, filter_type, scale_min_dist, scale_max_dist); 278 | std::vector scores_vec(scores.data(), scores.data()+scores.size()); // efficient to delete element in list 279 | std::random_device rd; 280 | std::mt19937 random_generator(rd()); 281 | 282 | // double sum_score = std::accumulate(scores_vec.begin(), scores_vec.end(), 0.0); 283 | // std::for_each(scores_vec.begin(), scores_vec.end(), [sum_score](double &s){s /= sum_score;}); 284 | // for (const auto& score : scores_vec) 285 | // { 286 | // std::cout << score << "\n"; 287 | // } 288 | // const double max_elem = *std::max_element(scores_vec.begin(), scores_vec.end()); 289 | // std::cout << "max value = " << max_elem << "\n"; 290 | // std::cout << "sum scores = " << std::accumulate(scores_vec.begin(), scores_vec.end(), 0.0) << "\n"; 291 | // std::exit(0); 292 | 293 | // sample 294 | std::vector sampled_points(n_samples); 295 | for (int i{0}; i < n_samples; ++i) 296 | { 297 | std::discrete_distribution prob(scores_vec.begin(), scores_vec.end()); // pi in paper 298 | int sampled_id = prob(random_generator); 299 | // if (scores_vec[sampled_id] < 0.0000001) continue; 300 | 301 | // std::cout << scores_vec[sampled_id] << ", "; 302 | scores_vec[sampled_id] = 0.0; // dont sample again 303 | // std::cout << scores_vec[sampled_id] << " "; 304 | // std::cout << std::accumulate(scores_vec.begin(), scores_vec.end(), 0.0) << "\n"; 305 | 306 | sampled_points[i] = points.row(sampled_id); 307 | 308 | } 309 | 310 | return sampled_points; 311 | } 312 | 313 | 314 | std::vector sample_ids(const int n_samples, const std::vector &points_vec, const std::string filter_type, const int scale_min_dist, const int scale_max_dist) 315 | { 316 | const Map points(const_cast(&points_vec[0](0)), points_vec.size(), 3); 317 | VectorXd scores = compute_scores(points, filter_type, scale_min_dist, scale_max_dist); 318 | std::vector scores_vec(scores.data(), scores.data()+scores.size()); // efficient to delete element in list 319 | std::random_device rd; 320 | std::mt19937 random_generator(rd()); 321 | 322 | // sample 323 | std::vector sampled_ids(n_samples); 324 | for (int i{0}; i < n_samples; ++i) 325 | { 326 | std::discrete_distribution prob(scores_vec.begin(), scores_vec.end()); // pi in paper 327 | int sampled_id = prob(random_generator); 328 | scores_vec[sampled_id] = 0.0; // dont sample again 329 | 330 | sampled_ids[i] = sampled_id; 331 | } 332 | 333 | return sampled_ids; 334 | } -------------------------------------------------------------------------------- /output_cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pmkalshetti/fast_point_cloud_sampling/c2dbe804d6734f620191d22145f7e42babd14a06/output_cube.png -------------------------------------------------------------------------------- /sample_point_cloud.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import open3d as o3d 3 | import argparse 4 | from utils import sample_pcd, draw, write_pcd, read_data 5 | 6 | 7 | if __name__ == "__main__": 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("path_ply", help="path to ply file") 10 | parser.add_argument("-n", "--n_samples", default=1000, type=int, help="number of samples") 11 | parser.add_argument("-s", "--save", default="", help="path to save output ply file") 12 | parser.add_argument("-f", "--filter_type", default="high", help="filter type to use") 13 | args = parser.parse_args() 14 | 15 | pcd_orig = read_data(args.path_ply) 16 | pcd_orig.paint_uniform_color([0, 0, 0]) # original points are blacks 17 | 18 | pcd_sampled = sample_pcd(pcd_orig, args.filter_type, args.n_samples, 10, 10) 19 | pcd_sampled.paint_uniform_color([1, 0, 0]) # sampled points are red 20 | 21 | if args.save: 22 | write_pcd(pcd_sampled, args.save) 23 | 24 | draw([pcd_orig.translate([-20, 0, 0]), pcd_sampled]) 25 | 26 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import open3d as o3d 3 | import argparse 4 | from build import graph_filter 5 | import os 6 | 7 | def get_vis(): 8 | vis = o3d.visualization.Visualizer() 9 | cwd = os.getcwd() # to handle issue on OS X 10 | vis.create_window() 11 | os.chdir(cwd) # to handle issue on OS X 12 | return vis 13 | 14 | 15 | def draw(geometries): 16 | vis = get_vis() 17 | if not isinstance(geometries, list): 18 | geometries = [geometries] 19 | for geometry in geometries: 20 | vis.add_geometry(geometry) 21 | vis.run() 22 | vis.destroy_window() 23 | 24 | 25 | def read_data(path): 26 | pcd = o3d.io.read_point_cloud(path) 27 | print(f"Read {np.asarray(pcd.points).shape[0]} points") 28 | return pcd 29 | 30 | 31 | def compute_scores_from_points(points, filter_type, scale_min_dist, scale_max_dist): 32 | scores = graph_filter.compute_scores(points, filter_type, scale_min_dist, scale_max_dist) 33 | 34 | return scores 35 | 36 | 37 | def sample_points(points_orig, scores, n_samples): 38 | # normalize scores 39 | scores = scores / np.sum(scores) 40 | 41 | ids_sampled = np.random.choice( 42 | points_orig.shape[0], n_samples, replace=False, p=scores) 43 | points_sampled = points_orig[ids_sampled] 44 | return points_sampled 45 | 46 | 47 | def sample_pcd(pcd_orig, filter_type, n_samples, scale_min_dist, scale_max_dist): 48 | points_orig = np.asarray(pcd_orig.points) 49 | scores = compute_scores_from_points(points_orig, filter_type, scale_min_dist, scale_max_dist) 50 | points_sampled = sample_points(points_orig, scores, n_samples) 51 | 52 | pcd_sampled = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points_sampled)) 53 | 54 | return pcd_sampled 55 | 56 | 57 | def write_pcd(pcd, path): 58 | dirname = os.path.dirname(path) 59 | if not os.path.exists(dirname): 60 | os.makedirs(dirname) 61 | o3d.io.write_point_cloud(path, pcd, write_ascii=True) 62 | 63 | 64 | def add_noise(pcd_orig, std): 65 | """adds normal noise""" 66 | points_orig = np.asarray(pcd_orig.points) 67 | points_noisy = points_orig + np.random.normal(scale=std, size=points_orig.shape) 68 | pcd_noisy = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points_noisy)) 69 | 70 | return pcd_noisy 71 | 72 | 73 | def create_box(n_points, size): 74 | mesh = o3d.geometry.TriangleMesh.create_box(width=size, height=size, depth=size) 75 | return mesh.sample_points_poisson_disk(n_points) 76 | 77 | def create_cone(n_points, radius, height): 78 | mesh = o3d.geometry.TriangleMesh.create_cone(radius=radius, height=height) 79 | return mesh.sample_points_poisson_disk(n_points) 80 | 81 | def create_cylinder(n_points, radius, height): 82 | mesh = o3d.geometry.TriangleMesh.create_cylinder(radius=radius, height=height) 83 | return mesh.sample_points_poisson_disk(n_points) 84 | 85 | def create_moebius(n_points, radius): 86 | mesh = o3d.geometry.TriangleMesh.create_moebius(raidus=radius, width=10) 87 | return mesh.sample_points_poisson_disk(n_points) 88 | 89 | def create_sphere(n_points, radius): 90 | mesh = o3d.geometry.TriangleMesh.create_sphere(radius=radius) 91 | return mesh.sample_points_poisson_disk(n_points) -------------------------------------------------------------------------------- /wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "graph_filter.h" 2 | 3 | #include 4 | 5 | namespace py = pybind11; 6 | 7 | PYBIND11_MODULE(graph_filter, m) 8 | { 9 | m.doc() = "graph processing on point cloud"; 10 | 11 | m.def("compute_scores", &compute_scores, 12 | "compute score for each point based on neighbors"); 13 | } --------------------------------------------------------------------------------