gtsam_points
Loading...
Searching...
No Matches
region_growing_impl.hpp
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2025 Kenji Koide (k.koide@aist.go.jp)
3
4#pragma once
5
6#include <mutex>
7#include <numeric>
8#include <unordered_set>
9#include <gtsam_points/util/easy_profiler.hpp>
10#include <gtsam_points/segmentation/region_growing.hpp>
11
12namespace gtsam_points {
13
14template <typename PointCloud>
15RegionGrowingContext region_growing_init_(
16 const PointCloud& points,
17 const NearestNeighborSearch& search,
18 const Eigen::Vector4d& seed_point,
19 const RegionGrowingParams& params) {
20 //
21 size_t index;
22 double sq_distance;
23 // Find the closest point to be the seed
24 if (!search.knn_search(seed_point.data(), 1, &index, &sq_distance)) {
25 return RegionGrowingContext();
26 }
27
28 RegionGrowingContext context;
29 context.seed_points.emplace_back(index);
30 context.visited_seeds.resize(frame::size(points), 0);
31 return context;
32}
33
34template <typename PointCloud>
35bool region_growing_step_(
36 RegionGrowingContext& context,
37 const PointCloud& points,
38 const NearestNeighborSearch& search,
39 const RegionGrowingParams& params) {
40 //
41 if (frame::has_normals(points) == false) {
42 std::cerr << "warning: normals are required for region growing" << std::endl;
43 return true;
44 }
45
46 if (context.visited_seeds.size() != frame::size(points)) {
47 std::cerr << "error: context.visited_seeds.size() != frame::size(points)" << std::endl;
48 return true;
49 }
50
51 if (context.seed_points.empty()) {
52 return true;
53 }
54
55 if (context.cluster_indices.size() > params.max_cluster_size) {
56 context.seed_points.clear();
57 return true;
58 }
59
60 const int seed_index = context.seed_points.front();
61 context.seed_points.pop_front();
62
63 if (context.visited_seeds[seed_index]) {
64 // Skip if the seed has been visited
65 return region_growing_step_(context, points, search, params);
66 }
67
68 // Add the seed to the cluster
69 context.visited_seeds[seed_index] = 1;
70 context.cluster_indices.emplace_back(seed_index);
71
72 // Find neighbors of the seed
73 std::vector<size_t> neighbor_indices;
74 std::vector<double> neighbor_sq_dists;
75 search
76 .radius_search(frame::point(points, seed_index).data(), params.distance_threshold, neighbor_indices, neighbor_sq_dists, params.max_cluster_size);
77
78 const double sq_distance_threshold = params.distance_threshold * params.distance_threshold;
79 const double cosine_threshold = std::cos(params.angle_threshold);
80 for (size_t i = 0; i < neighbor_indices.size(); i++) {
81 // Skip if the neighbor has been visited
82 if (context.visited_seeds[neighbor_indices[i]]) {
83 continue;
84 }
85
86 // Distance check
87 if (neighbor_sq_dists[i] > sq_distance_threshold) {
88 continue;
89 }
90
91 // Angle check
92 const auto& seed_normal = frame::normal(points, seed_index);
93 const auto& pt_normal = frame::normal(points, neighbor_indices[i]);
94 if (seed_normal.dot(pt_normal) < cosine_threshold) {
95 continue;
96 }
97
98 // Add the neighbor to the seed list
99 context.seed_points.emplace_back(neighbor_indices[i]);
100 }
101
102 return false;
103}
104
105template <typename PointCloud>
106void region_growing_dilation_(
107 RegionGrowingContext& context,
108 const PointCloud& points,
109 const NearestNeighborSearch& search,
110 const RegionGrowingParams& params) {
111 //
112 std::vector<std::unordered_set<size_t>> new_indices(params.num_threads);
113
114 // Find points within the dilation radius of the cluster
115#pragma omp parallel for num_threads(params.num_threads) schedule(guided, 4)
116 for (size_t i = 0; i < context.cluster_indices.size(); i++) {
117 std::vector<size_t> indices;
118 std::vector<double> sq_dists;
119 search.radius_search(frame::point(points, context.cluster_indices[i]).data(), params.dilation_radius, indices, sq_dists);
120
121#ifdef _OPENMP
122 const int thread_num = omp_get_thread_num();
123#else
124 const int thread_num = 0; // Single-threaded execution
125#endif
126
127 new_indices[thread_num].insert(indices.begin(), indices.end());
128 }
129
130 // Merge and sort the indices
131 for (size_t i = 1; i < new_indices.size(); i++) {
132 new_indices[0].insert(new_indices[i].begin(), new_indices[i].end());
133 }
134
135 context.cluster_indices.assign(new_indices[0].begin(), new_indices[0].end());
136 std::sort(context.cluster_indices.begin(), context.cluster_indices.end());
137}
138
139template <typename PointCloud>
140bool region_growing_update_(
141 RegionGrowingContext& context,
142 const PointCloud& points,
143 const NearestNeighborSearch& search,
144 const RegionGrowingParams& params) {
145 //
146 if (context.seed_points.empty()) {
147 return true;
148 }
149
150 for (int i = 0; i < params.max_steps; i++) {
151 if (context.seed_points.empty()) {
152 break;
153 }
154
155 region_growing_step_(context, points, search, params);
156 }
157
158 if (context.seed_points.empty() && params.dilation_radius > 0.0) {
159 region_growing_dilation_(context, points, search, params);
160 }
161
162 return context.seed_points.empty();
163}
164
165} // namespace gtsam_points