ReUseX  0.0.5
3D Point Cloud Processing for Building Reuse
Loading...
Searching...
No Matches
CellComplexRoomProbabilities.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 Povl Filip Sonne-Frederiksen
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4#pragma once
5#include "reusex/core/label_semantics.hpp"
6#include "reusex/core/logging.hpp"
7#include "reusex/core/processing_observer.hpp"
8
9#include <Eigen/Core>
10#include <embree4/rtcore.h>
11#include <fmt/color.h>
12#include <fmt/core.h>
13#include <fmt/ranges.h>
14#include <pcl/filters/uniform_sampling.h>
15
16#include <cmath>
17#include <vector>
18
19// Spherical Fibonacci
20static std::vector<Eigen::Vector3d> sampleSphericalFibonacci(size_t N) {
21 std::vector<Eigen::Vector3d> out;
22 out.reserve(N);
23 if (N == 0)
24 return out;
25 constexpr double golden_angle =
26 M_PI * (3.0 - std::sqrt(5.0)); // ~2.3999632297
27
28 for (size_t i = 0; i < N; ++i) {
29 double t = (double)i;
30 double z = 1.0 - 2.0 * t / (double)(N - 1); // maps to [1, -1] if N>1
31 double phi = golden_angle * t;
32 double r = std::sqrt(std::max(0.0, 1.0 - z * z));
33 double x = std::cos(phi) * r;
34 double y = std::sin(phi) * r;
35 out.emplace_back(x, y, z);
36 }
37 return out;
38}
39
40namespace reusex::geometry {
41template <typename PointT, typename PointN, typename PointL>
43 pcl::PointCloud<PointT>::ConstPtr cloud_,
44 pcl::PointCloud<PointN>::ConstPtr normals_,
45 pcl::PointCloud<PointL>::ConstPtr labels_, const double grid_size) -> void {
46
47 struct RTCVertex {
48 float x, y, z, r; // x, y, z coordinates and radius
49 };
50
51 struct RTCNormal {
52 float x, y, z; // x, y, z components of the normal vector
53 };
54
55 struct RTCData {
56 size_t label_index;
57 };
58
59 reusex::trace("calling compute_room_probabilities");
60
61 assert(cloud_->size() == labels_->size() &&
62 "Point cloud and label cloud must have the same size");
63 assert(cloud_->size() == normals_->size() &&
64 "Point cloud and normal cloud must have the same size");
65
66 const double radius_ = grid_size * M_SQRT2;
67
69 "Using ray tracing for {} points with grid size {:.3} and radius {:.3}",
70 cloud_->size(), grid_size, radius_);
71
73 "c:room_probabilities")
74 .first;
75
76 // Get unique labels
77 std::vector<unsigned int> labels;
78 labels.reserve(labels_->points.size());
79
80 // Rooms follow the point-label convention: 0 = unlabeled, valid rooms 1..N.
81 for (const auto &p : labels_->points)
83 labels.push_back(p.label);
84
85 std::sort(labels.begin(), labels.end());
86 labels.erase(std::unique(labels.begin(), labels.end()), labels.end());
87 reusex::debug("Room labels: {}", fmt::join(labels, ", "));
88
89 for (auto cit = this->cells_begin(); cit != this->cells_end(); ++cit)
90 c_rp[*cit] = std::vector<double>(labels.size() + 1, 0.0);
91 this->n_rooms = labels.size();
92 reusex::debug("Number of rooms (labels): {}", labels.size());
93
94 unsigned int old_mxcsr = _mm_getcsr(); // save current flagsq
95 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
96 _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
97
98 reusex::trace("Creating device and scene for ray tracing");
99 RTCDevice device_ = rtcNewDevice("verbose=0"); // 0-3
100 RTCScene scene_ = rtcNewScene(device_);
101 rtcSetSceneFlags(scene_, RTC_SCENE_FLAG_ROBUST);
102 rtcSetSceneBuildQuality(scene_, RTC_BUILD_QUALITY_HIGH);
103 assert(device_ != nullptr && "Error creating Embree device");
104 assert(scene_ != nullptr && "Error creating Embree scene");
105 rtcSetDeviceErrorFunction(
106 device_,
107 [](void *, RTCError err, const char *str) {
108 reusex::error("Embree error {}:", str);
109 switch (err) {
110 case RTC_ERROR_NONE:
111 break;
112 case RTC_ERROR_UNKNOWN:
113 throw std::runtime_error("Embree: An unknown error has occurred.");
114 break;
115 case RTC_ERROR_INVALID_ARGUMENT:
116 throw std::runtime_error(
117 "Embree: An invalid argument was specified.");
118 break;
119 case RTC_ERROR_INVALID_OPERATION:
120 throw std::runtime_error(
121 "Embree: The operation is not allowed for the specified object.");
122 break;
123 case RTC_ERROR_OUT_OF_MEMORY:
124 throw std::runtime_error("Embree: There is not enough memory left to "
125 "complete the operation.");
126 break;
127 case RTC_ERROR_UNSUPPORTED_CPU:
128 throw std::runtime_error(
129 "Embree: The CPU is not supported as it does not support SSE2.");
130 break;
131 case RTC_ERROR_CANCELLED:
132 throw std::runtime_error(
133 "Embree: The operation got cancelled by an Memory Monitor "
134 "Callback or Progress Monitor Callback function.");
135 break;
136 default:
137 throw std::runtime_error("Embree: An invalid error has occurred.");
138 break;
139 }
140 },
141 nullptr);
142
143 // Intersect with ground plane to get line/ INFO: Create Embree scene
144 reusex::trace("Creating scene geometry for ray tracing");
145
146 pcl::UniformSampling<PointT> us;
147 us.setInputCloud(cloud_);
148 us.setRadiusSearch(grid_size);
149
150 using RTCDataPtr = std::shared_ptr<RTCData>;
151 std::vector<RTCDataPtr> rtc_data_vec{}; // To keep data alive
152 for (size_t i = 0; i < labels.size(); ++i) {
153
154 pcl::IndicesPtr indices(new pcl::Indices);
155 for (size_t j = 0; j < cloud_->size(); ++j)
156 if (labels_->points[j].label == labels[i])
157 indices->push_back(static_cast<int>(j));
158
159 reusex::trace("Room label {}: {} points", labels[i], indices->size());
160
161 // size_t n_points_before = indices->size();
162 us.setIndices(indices);
163 us.filter(*indices);
164 // reusex::trace(
165 // "Label {}: Reduced from {} to {} points after uniform sampling",
166 // labels[i], n_points_before, indices->size());
167
168 RTCGeometry geometry_ =
169 rtcNewGeometry(device_, RTC_GEOMETRY_TYPE_ORIENTED_DISC_POINT);
170
171 auto *vb = static_cast<RTCVertex *>(rtcSetNewGeometryBuffer(
172 geometry_, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT4,
173 sizeof(RTCVertex), indices->size()));
174
175 auto *nb = static_cast<RTCNormal *>(rtcSetNewGeometryBuffer(
176 geometry_, RTC_BUFFER_TYPE_NORMAL, 0, RTC_FORMAT_FLOAT3,
177 sizeof(RTCNormal), indices->size()));
178
179 for (size_t k = 0; k < indices->size(); ++k) {
180 const auto &p = cloud_->points[indices->at(k)];
181 const auto &n = normals_->points[indices->at(k)];
182
183 vb[k].x = static_cast<float>(p.x);
184 vb[k].y = static_cast<float>(p.y);
185 vb[k].z = static_cast<float>(p.z);
186 vb[k].r = static_cast<float>(radius_) * 1.05f;
187
188 nb[k].x = static_cast<float>(n.normal_x);
189 nb[k].y = static_cast<float>(n.normal_y);
190 nb[k].z = static_cast<float>(n.normal_z);
191 }
192
193 rtc_data_vec.emplace_back(new RTCData{i});
194 rtcSetGeometryUserData(geometry_, rtc_data_vec.back().get());
195
196 rtcCommitGeometry(geometry_);
197 rtcAttachGeometry(scene_, geometry_);
198 rtcReleaseGeometry(geometry_);
199 }
200
201 reusex::trace("Committing scene");
202 rtcCommitScene(scene_);
203
204 {
205 const auto dirs = sampleSphericalFibonacci(100);
206
207 auto observer = reusex::core::ProgressObserver(
209
210#pragma omp parallel for schedule(dynamic)
211 for (size_t cell_idx = 0; cell_idx < this->num_cells(); ++cell_idx) {
212 auto cit = this->cells_begin();
213 std::advance(cit, cell_idx);
214 // const size_t idx = (*this)[*cit].id;
215 // For each cell create n random rays
216 // Count the number of intersections per id nad normalize
217
218 std::vector<double> local_accum(c_rp[*cit].size(), 0.0);
219 double *accum_ptr = local_accum.data();
220 // cppcheck-suppress unreadVariable
221 const int N = static_cast<int>(c_rp[*cit].size());
222
223#pragma omp parallel for reduction(+ : accum_ptr[ : N])
224 for (size_t i = 0; i < dirs.size(); ++i) {
225 const auto dir = dirs[i];
226 auto c = (*this)[*cit].pos;
227
228 RTCRayHit rayhit;
229 rayhit.ray.org_x = static_cast<float>(c.x());
230 rayhit.ray.org_y = static_cast<float>(c.y());
231 rayhit.ray.org_z = static_cast<float>(c.z());
232 rayhit.ray.dir_x = static_cast<float>(dir.x());
233 rayhit.ray.dir_y = static_cast<float>(dir.y());
234 rayhit.ray.dir_z = static_cast<float>(dir.z());
235
236 rayhit.ray.tnear = 0.001f; // Start a bit away from the origin
237 rayhit.ray.tfar = std::numeric_limits<float>::infinity();
238 rayhit.hit.geomID = RTC_INVALID_GEOMETRY_ID;
239
240 rayhit.ray.mask = -1;
241 rayhit.ray.flags = 0;
242 rayhit.hit.instID[0] = RTC_INVALID_GEOMETRY_ID;
243
244 rtcIntersect1(scene_, &rayhit);
245
246 if (rayhit.hit.geomID == RTC_INVALID_GEOMETRY_ID) { // No hit
247 accum_ptr[0] += 1;
248 // reusex::trace(fmt::format(fmt::fg(fmt::color::red), "No
249 // hit"));
250 continue;
251 }
252
253 // Check if backside
254 const auto normal =
255 Eigen::Vector3f(rayhit.hit.Ng_x, rayhit.hit.Ng_y, rayhit.hit.Ng_z)
256 .normalized();
257 const Eigen::Vector3f dir_vec(dir.x(), dir.y(), dir.z());
258 if (normal.dot(dir_vec) > 0) {
259 accum_ptr[0] += 1;
260 // reusex::trace(
261 // fmt::format(fmt::fg(fmt::color::yellow), "Backside hit"));
262 continue; // Backside
263 }
264 // reusex::trace(fmt::format(fmt::fg(fmt::color::green),
265 // "Frontside hit"));
266
267 auto geometry = rtcGetGeometry(scene_, rayhit.hit.geomID);
268 auto *data = static_cast<RTCData *>(rtcGetGeometryUserData(geometry));
269
270 // const auto label = labels[data->label_index];
271 // reusex::trace("Cell {:>3} ray {} hit label {} (index {})",
272 // (*this)[*cit].id, i, label, data->label_index);
273
274 // #pragma omp critical
275 accum_ptr[data->label_index + 1] += 1;
276 // reusex::trace("Cell {:>3} hit label {} (index {})", idx,
277 // labels[data->label_index], data->label_index);
278 }
279
280 // Compute probabilities
281 // double sum = std::accumulate(c_rp[*cit].begin(), c_rp[*cit].end(),
282 // 0.0); reusex::trace("Cell {:>3} sum = {:.3f}", idx, sum); if (sum
283 // > 0)
284 // for (size_t j = 0; j < c_rp[*cit].size(); ++j)
285 // c_rp[*cit][j] /= sum;
286 //
287 for (auto &p : local_accum)
288 p /= dirs.size();
289 c_rp[*cit] = std::move(local_accum);
290
291 ++observer;
292 }
293 }
294 rtcReleaseScene(scene_);
295 rtcReleaseDevice(device_);
296
297 _mm_setcsr(old_mxcsr); // restore old flags
298
299 // Log sum of probabilities
300 auto sum_results = std::vector<double>(labels.size() + 1, 0.0);
301 for (auto cit = this->cells_begin(); cit != this->cells_end(); ++cit)
302 for (size_t i = 0; i < c_rp[*cit].size(); ++i)
303 sum_results[i] += c_rp[*cit][i];
304 reusex::trace("Sum probabilities => [{:.3f}]", fmt::join(sum_results, ", "));
305}
306} // namespace reusex::geometry
static std::vector< Eigen::Vector3d > sampleSphericalFibonacci(size_t N)
auto cells_begin() const -> CellIterator
auto compute_room_probabilities(pcl::PointCloud< PointT >::ConstPtr cloud, pcl::PointCloud< PointN >::ConstPtr normals, pcl::PointCloud< PointL >::ConstPtr labels, const double grid_size=0.2) -> void
auto cells_end() const -> CellIterator
std::pair< boost::associative_property_map< std::map< Key, T > >, bool > add_property_map(const std::string &name)
Definition Registry.hpp:27
constexpr bool is_valid_label(uint32_t label) noexcept
void debug(fmt::format_string< Args... > format, Args &&...args)
Definition logging.hpp:79
void trace(fmt::format_string< Args... > format, Args &&...args)
Definition logging.hpp:72
void error(fmt::format_string< Args... > format, Args &&...args)
Definition logging.hpp:100