UFO 1.0.0
An Efficient Probabilistic 3D Mapping Framework That Embraces the Unknown
Loading...
Searching...
No Matches
angular_integrator.hpp
1
42#ifndef UFO_MAP_INTEGRATOR_ANGULAR_INTEGRATOR_HPP
43#define UFO_MAP_INTEGRATOR_ANGULAR_INTEGRATOR_HPP
44
45// UFO
46#include <ufo/cloud/point_cloud.hpp>
47#include <ufo/container/tree/code.hpp>
48#include <ufo/container/tree/index.hpp>
49#include <ufo/container/tree/key.hpp>
50#include <ufo/execution/algorithm.hpp>
51#include <ufo/execution/execution.hpp>
52#include <ufo/geometry/aabb.hpp>
53#include <ufo/geometry/geometry.hpp>
54#include <ufo/map/integrator/integrator.hpp>
55#include <ufo/map/integrator/sensor_error.hpp>
56#include <ufo/numeric/math.hpp>
57#include <ufo/numeric/numbers.hpp>
58#include <ufo/numeric/transform.hpp>
59#include <ufo/numeric/vec.hpp>
60#include <ufo/utility/spinlock.hpp>
61
62// STL
63#include <algorithm>
64#include <cstddef>
65#include <cstdint>
66#include <deque>
67#include <future>
68#include <numeric>
69#include <thread>
70#include <vector>
71
72namespace ufo
73{
74template <std::size_t Dim, class SensorErrorFun = FixedSensorError>
75class AngularIntegrator final : public Integrator<Dim>
76{
77 public:
78 // Min range to integrate
79 float min_distance = 0.0f;
80 // Max range to integrate, negative value is infinity range
81 float max_distance = std::numeric_limits<float>::infinity();
82
83 float sensor_angular_resolution = radians(0.0f);
84
85 float translation_error = 0.0f;
86 float orientation_error = radians(0.0f);
87
88 public:
89 AngularIntegrator(SensorErrorFun sensor_error_f = SensorErrorFun())
90 : sensor_error_f_(sensor_error_f)
91 {
92 angularResolution(radians(1.0f));
93 }
94
95 template <class Map, class T, class... Rest>
96 void operator()(Map& map, PointCloud<Dim, T, Rest...> cloud,
97 Transform<Dim, T> const& frame_origin = {},
98 Vec<Dim, T> sensor_origin = {}) const
99 {
100 // TODO: Fill in
101 }
102
103 template <
104 class ExecutionPolicy, class Map, class T, class... Rest,
105 std::enable_if_t<execution::is_execution_policy_v<ExecutionPolicy>, bool> = true>
106 void operator()(ExecutionPolicy&& policy, Map& map, PointCloud<Dim, T, Rest...> cloud,
107 Transform<Dim, T> const& frame_origin = {},
108 Vec<Dim, T> sensor_origin = {},
109 std::future<void> const& finish_before_touch = {}) const
110 {
111 // TODO: Filter distances
112
113 if constexpr (execution::is_seq_v<ExecutionPolicy> ||
114 execution::is_unseq_v<ExecutionPolicy>) {
115 return operator()(map, cloud, transform);
116 }
117
118 auto const t0 = std::chrono::high_resolution_clock::now();
119
120 fillDistances(policy, cloud.template view<0>(), sensor_origin);
121
122 removeNanAndMinDistance(cloud);
123
124 auto const t1 = std::chrono::high_resolution_clock::now();
125
126 if (Transform<Dim, T>{} != frame_origin) {
127 transformInPlace(policy, frame_origin, cloud);
128 sensor_origin = frame_origin(sensor_origin);
129 }
130
131 auto const t2 = std::chrono::high_resolution_clock::now();
132
133 fillData(policy, cloud.template view<0>(), sensor_origin);
134
135 auto const t3 = std::chrono::high_resolution_clock::now();
136
137 fillMisses(policy, map, cloud.template view<0>(), sensor_origin);
138
139 auto const t4 = std::chrono::high_resolution_clock::now();
140
141 if (finish_before_touch.valid()) {
142 finish_before_touch.wait();
143 }
144
145 auto const t5 = std::chrono::high_resolution_clock::now();
146
147 this->insertMisses(policy, map, cached_misses_);
148
149 auto const t6 = std::chrono::high_resolution_clock::now();
150
151 resetData(policy);
152
153 auto const t7 = std::chrono::high_resolution_clock::now();
154
155 filterHits(policy, cloud, sensor_origin);
156
157 auto const t8 = std::chrono::high_resolution_clock::now();
158
159 this->insertHits(policy, map, cloud, sensor_origin);
160
161 auto const t9 = std::chrono::high_resolution_clock::now();
162
163 // TODO: Implement
164
165 if (this->propagate) {
166 map.propagate(policy);
167 // TODO: Reset modified?
168 }
169
170 auto const t10 = std::chrono::high_resolution_clock::now();
171
172 std::chrono::duration<double, std::milli> const remove_nan_ms = t1 - t0;
173 std::chrono::duration<double, std::milli> const transform_ms = t2 - t1;
174 std::chrono::duration<double, std::milli> const fill_data_ms = t3 - t2;
175 std::chrono::duration<double, std::milli> const fill_misses_ms = t4 - t3;
176 std::chrono::duration<double, std::milli> const wait_to_touch_ms = t5 - t4;
177 std::chrono::duration<double, std::milli> const insert_misses_ms = t6 - t5;
178 std::chrono::duration<double, std::milli> const reset_data_ms = t7 - t6;
179 std::chrono::duration<double, std::milli> const filter_hits_ms = t8 - t7;
180 std::chrono::duration<double, std::milli> const insert_hits_ms = t9 - t8;
181 std::chrono::duration<double, std::milli> const propagate_ms = t10 - t9;
182 std::chrono::duration<double, std::milli> const total_ms = t10 - t0;
183
184 std::cout << "Remove NaN: " << remove_nan_ms.count() << " ms\n";
185 std::cout << "Transform: " << transform_ms.count() << " ms\n";
186 std::cout << "Fill data: " << fill_data_ms.count() << " ms\n";
187 std::cout << "Fill misses: " << fill_misses_ms.count() << " ms\n";
188 std::cout << "Wait to touch: " << wait_to_touch_ms.count() << " ms\n";
189 std::cout << "Insert misses: " << insert_misses_ms.count() << " ms\n";
190 std::cout << "Reset data: " << reset_data_ms.count() << " ms\n";
191 std::cout << "Filter hits: " << filter_hits_ms.count() << " ms\n";
192 std::cout << "Insert hits: " << insert_hits_ms.count() << " ms\n";
193 std::cout << "Propagate: " << propagate_ms.count() << " ms\n";
194 std::cout << "Total: " << total_ms.count() << " ms\n\n";
195 }
196
197 [[nodiscard]] constexpr float angularResolution() const noexcept
198 {
199 return 1.0f / angular_resolution_factor_;
200 }
201
202 constexpr void angularResolution(float resolution) noexcept
203 {
204 angular_resolution_factor_ = 1.0f / resolution;
205 auto const arf = angular_resolution_factor_;
206
207 assert(0.0f < resolution && 0.0f < angular_resolution_factor_);
208
209 columns_ = static_cast<std::uint_fast32_t>(2.0f * numbers::pi_v<float> * arf) + 1;
210
211 if constexpr (2 == Dim) {
212 rows_ = 1u;
213 } else if constexpr (3 == Dim) {
214 rows_ = static_cast<std::uint_fast32_t>(numbers::pi_v<float> * arf) + 1;
215 }
216
217 data_.resize(rows_ * columns_);
218 // TODO: Is this correct?
219 data_2_.resize(rows_ * columns_ / (ds_ * ds_));
220 }
221
222 [[nodiscard]] SensorErrorFun& sensorErrorFunction() { return sensor_error_f_; }
223
224 [[nodiscard]] SensorErrorFun const& sensorErrorFunction() const
225 {
226 return sensor_error_f_;
227 }
228
229 private:
230 /**************************************************************************************
231 | |
232 | Utility |
233 | |
234 **************************************************************************************/
235
236 template <class T>
237 void fillDistances(SoAView<Vec<Dim, T>> const& points, Vec<Dim, T> const& origin) const
238 {
239 cached_distances_.resize(points.size());
240 ufo::transform(points.begin(), points.end(), cached_distances_.begin(),
241 [&origin](auto const& p) { return distance(origin, p); });
242 }
243
244 template <
245 class ExecutionPolicy, class T,
246 std::enable_if_t<execution::is_execution_policy_v<ExecutionPolicy>, bool> = true>
247 void fillDistances(ExecutionPolicy&& policy, SoAView<Vec<Dim, T>> const& points,
248 Vec<Dim, T> const& origin) const
249 {
250 cached_distances_.resize(points.size());
251 ufo::transform(std::forward<ExecutionPolicy>(policy), points.begin(), points.end(),
252 cached_distances_.begin(),
253 [&origin](auto const& p) { return distance(origin, p); });
254 }
255
256 template <class T, class... Rest>
257 void removeNanAndMinDistance(PointCloud<Dim, T, Rest...>& cloud) const
258 {
259 // TODO: Implement
260
261 // auto first_d = cached_distances_.begin();
262 // auto const last_d = cached_distances_.end();
263 // auto first_c = cloud.begin();
264
265 // for (; last_d != first_d; ++first_d, ++first_c) {
266 // if (std::isnan(*first_d) || this->min_distance > *first_d) {
267 // break;
268 // }
269 // }
270
271 // if (last_d == first_d) {
272 // return;
273 // }
274
275 // auto i_d = std::next(first_d);
276 // auto i_c = std::next(first_c);
277 // for (; last_d != i_d; ++i_d, i_c) {
278 // if (!std::isnan(*i_d) && this->min_distance <= *i_d) {
279 // std::iter_swap(i_d, first_d);
280 // std::iter_swap(i_c, first_c);
281 // ++first_d;
282 // ++first_c;
283 // }
284 // }
285
286 // cached_distances_.erase(first_d, cached_distances_.end());
287 // cloud.erase(first_c, cloud.end());
288 }
289
290 template <class T>
291 [[nodiscard]] AABB<Dim, T> bounds(SoAView<Vec<Dim, T>> points,
292 Vec<Dim, T> const& origin) const
293 {
294 // TODO: Optimize
295
296 Vec<Dim, T> min = origin;
297 Vec<Dim, T> max = origin;
298 for (std::size_t i{}; points.size() > i; ++i) {
299 auto p = points[i];
300 auto dir = p - origin;
301 T dist = static_cast<T>(cached_distances_[i]);
302 dir /= dist;
303 dist = std::min(static_cast<T>(this->max_distance), dist);
304 // FIXME: Only need to call this two times
305 dist += sensor_error_f_(dist);
306 p = origin + dir * dist;
307 min = ufo::min(min, p);
308 max = ufo::max(max, p);
309 }
310
311 return AABB<Dim, T>{min, max};
312 }
313
314 template <class T>
315 [[nodiscard]] std::int_fast32_t polarIndex(T polar_angle) const
316 {
317 if constexpr (2u == Dim) {
318 return 0;
319 } else if constexpr (3u == Dim) {
320 return static_cast<std::int_fast32_t>(
321 std::floor(polar_angle * angular_resolution_factor_));
322 }
323 }
324
325 template <class T>
326 [[nodiscard]] std::int_fast32_t azimuthalIndex(T azimuthal_angle) const
327 {
328 azimuthal_angle += numbers::pi_v<T>;
329 return static_cast<std::int_fast32_t>(
330 std::floor(azimuthal_angle * angular_resolution_factor_));
331 }
332
333 /**************************************************************************************
334 | |
335 | Data |
336 | |
337 **************************************************************************************/
338
339 template <class T>
340 void fillData(SoAView<Vec<Dim, T>> points, Vec<Dim, T> const& origin) const
341 {
342 // TODO: Implement
343 }
344
345 template <
346 class ExecutionPolicy, class T,
347 std::enable_if_t<execution::is_execution_policy_v<ExecutionPolicy>, bool> = true>
348 void fillData(ExecutionPolicy&& policy, SoAView<Vec<Dim, T>> points,
349 Vec<Dim, T> const& origin) const
350 {
351 // Spherical coordinate system
352
353 assert(0.0f <= sensor_angular_resolution);
354 auto const hsar = sensor_angular_resolution / 2.0f;
355
356 ufo::for_each(std::forward<ExecutionPolicy>(policy), std::size_t(0), points.size(),
357 [this, points, &origin, hsar](std::size_t i) {
358 auto p = points[i];
359 p -= origin;
360 float const radial_distance = cached_distances_[i];
361 float const azimuthal_angle = azimuthalAngle(Vec<2, T>(p));
362 float polar_angle;
363 if constexpr (2 == Dim) {
364 polar_angle = 0.0f;
365 } else if constexpr (3 == Dim) {
366 polar_angle = polarAngle(p.z, radial_distance);
367 }
368
369 // TODO: Need to check if `polar_angle +- hsar` is between [0, pi]
370
371 auto const min_ai = azimuthalIndex(azimuthal_angle - hsar) + columns_;
372 auto const max_ai = azimuthalIndex(azimuthal_angle + hsar) + columns_;
373 auto const min_pi = polarIndex(polar_angle - hsar) + rows_;
374 auto const max_pi = polarIndex(polar_angle + hsar) + rows_;
375
376 for (auto ai = min_ai; max_ai >= ai; ++ai) {
377 auto const first_ai = (ai % columns_) * rows_;
378 for (auto pi = min_pi; max_pi >= pi; ++pi) {
379 auto index = first_ai + (pi % rows_);
380 assert(data_.size() > index);
381 Data& data = data_[index];
382 std::scoped_lock lock(data.lock);
383 if (0u == data.count || data.distance > radial_distance) {
384 data.distance = radial_distance;
385 data.distance_error = sensor_error_f_(radial_distance);
386 }
387 ++data.count;
388 }
389 }
390 });
391
392 for (std::uint_fast32_t ai{}; columns_ > ai; ++ai) {
393 auto ai_2 = ai / ds_;
394 auto const first_ai = ai * rows_;
395 auto const first_ai_2 = ai_2 * (rows_ / ds_);
396 for (std::uint_fast32_t pi{}; rows_ > pi; ++pi) {
397 auto const pi_2 = pi / ds_;
398 auto const index = first_ai + pi;
399 auto const index_2 = first_ai_2 + pi_2;
400 assert(data_.size() > index);
401 // TODO: This is triggered
402 assert(data_2_.size() > index_2);
403 auto& data = data_[index];
404 auto& data_2 = data_2_[index_2];
405
406 if (data.distance < data_2.min_distance) {
407 data_2.min_distance = data.distance;
408 data_2.min_distance_error = data.distance_error;
409 }
410 if (data.distance > data_2.max_distance) {
411 data_2.max_distance = data.distance;
412 data_2.max_distance_error = data.distance_error;
413 }
414 data_2.count += data.count;
415 }
416 }
417 }
418
419 void resetData() const
420 {
421 ufo::transform(data_.begin(), data_.end(), data_.begin(),
422 [](auto const&) { return Data{}; });
423 ufo::transform(data_2_.begin(), data_2_.end(), data_2_.begin(),
424 [](auto const&) { return Data2{}; });
425 }
426
427 template <
428 class ExecutionPolicy,
429 std::enable_if_t<execution::is_execution_policy_v<ExecutionPolicy>, bool> = true>
430 void resetData(ExecutionPolicy&& policy) const
431 {
432 ufo::transform(policy, data_.begin(), data_.end(), data_.begin(),
433 [](auto const&) { return Data{}; });
434 ufo::transform(std::forward<ExecutionPolicy>(policy), data_2_.begin(), data_2_.end(),
435 data_2_.begin(), [](auto const&) { return Data2{}; });
436 }
437
438 /**************************************************************************************
439 | |
440 | Misses |
441 | |
442 **************************************************************************************/
443
444 template <class Map, class T>
445 std::vector<TreeKey<Dim>> missesKeys(Map const& map, SoAView<Vec<Dim, T>> points,
446 Vec<Dim, T> const& origin) const
447 {
448 auto const bb = bounds(points, origin);
449
450 // TODO: Make parameter
451 auto const depth = this->miss_depth + 5;
452
453 auto const min = map.key(TreeCoord<Dim>(ufo::min(bb), depth));
454 auto const max = map.key(TreeCoord<Dim>(ufo::max(bb), depth));
455
456 using key_t = typename TreeKey<Dim>::Key;
457 auto const diff = (static_cast<key_t>(max) - static_cast<key_t>(min)) + 1u;
458
459 std::vector<TreeKey<Dim>> keys;
460 keys.reserve(std::accumulate(begin(diff), end(diff), 0u, std::multiplies<>()));
461
462 if constexpr (2 == Dim) {
463 for (auto k = min; max.x >= k.x; ++k.x) {
464 for (k.y = min.y; max.y >= k.y; ++k.y) {
465 keys.push_back(k);
466 }
467 }
468 } else if constexpr (3 == Dim) {
469 for (auto k = min; max.x >= k.x; ++k.x) {
470 for (k.y = min.y; max.y >= k.y; ++k.y) {
471 for (k.z = min.z; max.z >= k.z; ++k.z) {
472 keys.push_back(k);
473 }
474 }
475 }
476 } else {
477 // TODO: Error
478 }
479
480 return keys;
481 }
482
483 template <class Map, class T>
484 void fillMisses(Map const& map, Vec<Dim, T> const& origin) const
485 {
486 // TODO: Implement
487 }
488
489 template <
490 class ExecutionPolicy, class Map, class T,
491 std::enable_if_t<execution::is_execution_policy_v<ExecutionPolicy>, bool> = true>
492 void fillMisses(ExecutionPolicy&& policy, Map const& map, SoAView<Vec<Dim, T>> points,
493 Vec<Dim, T> const& origin) const
494 {
495 auto const keys = missesKeys(map, points, origin);
496
497 std::mutex mutex;
498 std::size_t size{};
499
500 ufo::for_each(policy, keys.begin(), keys.end(),
501 [this, &mutex, &size, &map, &origin](auto const& key) {
502 thread_local std::size_t idx;
503 auto const id = std::this_thread::get_id();
504 if (size <= idx || id != thread_misses_[idx].id) {
505 std::scoped_lock lock(mutex);
506 idx = size++;
507 if (thread_misses_.size() < size) {
508 thread_misses_.emplace_back();
509 assert(thread_misses_.size() == size);
510 }
511 thread_misses_[idx].id = id;
512 }
513
514 auto& misses = thread_misses_[idx].misses;
515
516 auto const code = map.code(key);
517 auto const center = cast<T>(map.center(key)) - origin;
518
519 fillMissesRecurs(misses, map, code, center);
520 });
521
522 std::vector<std::size_t> offset(size);
523 std::size_t total_size{};
524 for (std::size_t i{}; size > i; ++i) {
525 offset[i] = total_size;
526 total_size += thread_misses_[i].misses.size();
527 }
528
529 std::cout << total_size << '\n';
530
531 cached_misses_.resize(total_size);
532 ufo::for_each(policy, std::size_t(0), size, [this, &offset](std::size_t i) {
533 auto& data = thread_misses_[i];
534 auto const& m = data.misses;
535 auto const o = offset[i];
536
537 std::copy(m.begin(), m.end(), cached_misses_.begin() + o);
538
539 data.clear();
540 });
541 }
542
543 template <class Map, class T>
544 std::pair<bool, bool> fillMissesRecurs(std::vector<detail::Miss<Dim>>& misses,
545 Map const& map, TreeCode<Dim> const& code,
546 Vec<Dim, T> const& center) const
547 {
548 auto const ca = abs(center);
549 auto const hl = cast<T>(map.halfLength(code));
550
551 auto const return_distance = norm(ca + hl);
552 auto const inner_distance = norm(ca - hl);
553
554 auto const [min_polar, max_polar] = polarAngles(center, hl);
555 auto const [min_azimuthal, max_azimuthal] = azimuthalAngles(center, hl);
556
557 // TODO: Add angular error
558
559 // + columns_ so they are positive
560 auto const min_ai = azimuthalIndex(min_azimuthal) + columns_;
561 auto const max_ai = azimuthalIndex(max_azimuthal) + columns_;
562 // + rows_ so they are positive
563 auto const min_pi = polarIndex(min_polar) + rows_;
564 auto const max_pi = polarIndex(max_polar) + rows_;
565
566 bool valid_return = false;
567 bool valid_parent = false;
568 bool valid_void_region = false;
569 std::uint_fast32_t count{};
570
571 if (this->miss_depth == code.depth()) {
572 valid_return = validReturn(return_distance, min_ai, max_ai, min_pi, max_pi);
573 valid_void_region = valid_return && validVoidRegion(return_distance, min_ai, max_ai,
574 min_pi, max_pi);
575
576 if (valid_return) {
577 misses.emplace_back(code, center, count, valid_void_region);
578 }
579 } else {
580 valid_parent = validParent(inner_distance, min_ai, max_ai, min_pi, max_pi);
581 valid_return =
582 valid_parent && validReturn(return_distance, min_ai, max_ai, min_pi, max_pi);
583 valid_void_region = valid_return && validVoidRegion(return_distance, min_ai, max_ai,
584 min_pi, max_pi);
585
586 if (valid_return && valid_void_region) {
587 misses.emplace_back(code, center, count, valid_void_region);
588 } else if (valid_parent) {
589 auto size_before = misses.size();
590
591 auto child_code = code.firstborn();
592 valid_return = true;
593 valid_void_region = true;
594 bool any_void_region = false;
595 for (std::uint_fast32_t i{}; map.branchingFactor() > i; ++i) {
596 auto [vr, vvr] =
597 fillMissesRecurs(misses, map, child_code.firstbornSibling(i),
598 map.child(TreeCoord<Dim, T>(center, code.depth()), i));
599 valid_return = valid_return && vr;
600 valid_void_region = valid_void_region && vvr;
601 any_void_region = any_void_region || vvr;
602 }
603
604 if (valid_return && (valid_void_region || !any_void_region)) {
605 misses.resize(size_before);
606 misses.emplace_back(code, center, count, valid_void_region);
607 }
608 }
609 }
610
611 return std::pair{valid_return, valid_void_region};
612 }
613
614 [[nodiscard]] bool validReturn(float const distance, std::int_fast32_t min_ai,
615 std::int_fast32_t max_ai, std::int_fast32_t min_pi,
616 std::int_fast32_t max_pi) const
617 {
618 // TODO: Optimize
619
620 for (auto ai = min_ai; max_ai >= ai; ++ai) {
621 auto const first_ai = (ai % columns_) * rows_;
622 for (auto pi = min_pi; max_pi >= pi; ++pi) {
623 auto const& data = data_[first_ai + (pi % rows_)];
624 auto const d = data.distance + data.distance_error;
625 if (d <= distance) {
626 return false;
627 }
628 }
629 }
630 return true;
631 }
632
633 [[nodiscard]] bool validVoidRegion(float const distance, std::int_fast32_t min_ai,
634 std::int_fast32_t max_ai, std::int_fast32_t min_pi,
635 std::int_fast32_t max_pi) const
636 {
637 // TODO: Optimize
638
639 for (auto ai = min_ai; max_ai >= ai; ++ai) {
640 auto const first_ai = (ai % columns_) * rows_;
641 for (auto pi = min_pi; max_pi >= pi; ++pi) {
642 auto const& data = data_[first_ai + (pi % rows_)];
643 auto const d = data.distance - data.distance_error;
644 if (d <= distance) {
645 return false;
646 }
647 }
648 }
649 return true;
650 }
651
652 [[nodiscard]] bool validParent(float const distance, std::int_fast32_t min_ai,
653 std::int_fast32_t max_ai, std::int_fast32_t min_pi,
654 std::int_fast32_t max_pi) const
655 {
656 // TODO: Optimize
657
658 for (auto ai = min_ai; max_ai >= ai; ++ai) {
659 auto const first_ai = (ai % columns_) * rows_;
660 for (auto pi = min_pi; max_pi >= pi; ++pi) {
661 auto const& data = data_[first_ai + (pi % rows_)];
662 auto const d = data.distance + data.distance_error;
663 if (d > distance) {
664 return true;
665 }
666 }
667 }
668 return false;
669
670 // min_ai /= ds_;
671 // max_ai /= ds_;
672 // min_pi /= ds_;
673 // max_pi /= ds_;
674
675 // auto const columns = columns_ / ds_;
676 // auto const rows = rows_ / ds_;
677
678 // for (auto ai = min_ai; max_ai >= ai; ++ai) {
679 // auto const first_ai = (ai % columns) * rows;
680 // for (auto pi = min_pi; max_pi >= pi; ++pi) {
681 // auto const index = first_ai + (pi % rows);
682 // auto const& data = data_2_[index];
683 // auto const d = data.max_distance + data.max_distance_error;
684 // if (d > distance) {
685 // auto const min_ai_hr = ai * ds_;
686 // auto const max_ai_hr = (ai + 1u) * ds_;
687 // auto const min_pi_hr = pi * ds_;
688 // auto const max_pi_hr = (pi + 1u) * ds_;
689 // for (auto ai_hr = min_ai_hr; max_ai_hr > ai_hr; ++ai_hr) {
690 // auto const first_ai_hr = (ai_hr % columns_) * rows_;
691 // for (auto pi_hr = min_pi_hr; max_pi_hr >= pi_hr; ++pi_hr) {
692 // auto const index_hr = first_ai_hr + (pi_hr % rows_);
693 // auto const& data = data_[index_hr];
694 // auto const d_hr = data.distance + data.distance_error;
695
696 // if (d_hr > distance) {
697 // return true;
698 // }
699 // }
700 // }
701 // }
702 // }
703 // }
704 // return false;
705
706 // for (std::uint_fast32_t ai{}; columns_ > ai; ++ai) {
707 // auto ai_2 = ai / ds_;
708 // auto const first_ai = ai * rows_;
709 // auto const first_ai_2 = ai_2 * (rows_ / ds_);
710 // for (std::uint_fast32_t pi{}; rows_ > pi; ++pi) {
711 // auto const pi_2 = pi / ds_;
712 // auto const index = first_ai + pi;
713 // auto const index_2 = first_ai_2 + pi_2;
714 // assert(data_.size() > index);
715 // assert(data_2_.size() > index_2);
716 // auto& data = data_[index];
717 // auto& data_2 = data_2_[index_2];
718
719 // if (data.distance < data_2.min_distance) {
720 // data_2.min_distance = data.distance;
721 // data_2.min_distance_error = data.distance_error;
722 // }
723 // if (data.distance > data_2.max_distance) {
724 // data_2.max_distance = data.distance;
725 // data_2.max_distance_error = data.distance_error;
726 // }
727 // data_2.count += data.count;
728 // }
729 // }
730 }
731
732 template <class T>
733 [[nodiscard]] T polarAngle(T const& z, T radial_distance) const
734 {
735 return std::acos(z / radial_distance);
736 }
737
738 template <class T>
739 [[nodiscard]] T polarAngle(Vec<Dim, T> const& point) const
740 {
741 if constexpr (2u == Dim) {
742 return T(0);
743 } else if constexpr (3u == Dim) {
744 return polarAngle(point.z, norm(point));
745 } else {
746 // TODO: Error
747 }
748 }
749
750 template <class T>
751 [[nodiscard]] T azimuthalAngle(Vec<2, T> const& point) const
752 {
753 return std::atan2(point.y, point.x);
754 }
755
756 // template <class T>
757 // [[nodiscard]] std::pair<T, T> polarAngles(Vec<Dim, T> const& center,
758 // Vec<Dim, T> const& half_length) const
759 // {
760 // if constexpr (2 == Dim) {
761 // return std::pair{T(0), T(0)};
762 // } else {
763 // auto const ca = abs(Vec<2, T>(center));
764 // auto const min_z = center.z - half_length.z;
765 // auto const max_z = center.z + half_length.z;
766
767 // auto max = T(0) < min_z ? ca + Vec2<T>(half_length) : ca - Vec2<T>(half_length);
768 // auto min = T(0) > max_z ? ca + Vec2<T>(half_length) : ca - Vec2<T>(half_length);
769
770 // return std::pair{polarAngle(Vec<3, T>(min, max_z)),
771 // polarAngle(Vec<3, T>(max, min_z))};
772 // }
773 // }
774
775 // /**
776 // * @brief Minimum and maximum azimuthal angles between [-pi, 2 * pi] such that first
777 // is
778 // * always greater than second.
779 // *
780 // * @param offset
781 // * @param point
782 // * @param half_length
783 // * @return std::pair<T, T>
784 // */
785 // template <class T>
786 // [[nodiscard]] std::pair<T, T> azimuthalAngles(Vec<Dim, T> const& center,
787 // Vec<Dim, T> const& half_length) const
788 // {
789 // Vec<2, T> min_p;
790 // Vec<2, T> max_p;
791
792 // if (T(0) > center.x) {
793 // min_p.y = center.y + half_length.y;
794 // max_p.y = center.y - half_length.y;
795 // } else {
796 // min_p.y = center.y - half_length.y;
797 // max_p.y = center.y + half_length.y;
798 // }
799
800 // if (T(0) > center.y) {
801 // min_p.x = center.x - half_length.x;
802 // max_p.x = center.x + half_length.x;
803 // } else {
804 // min_p.x = center.x + half_length.x;
805 // max_p.x = center.x - half_length.x;
806 // }
807
808 // return std::pair{azimuthalAngle(min_p), azimuthalAngle(max_p)};
809 // }
810
811 template <class T>
812 [[nodiscard]] std::pair<T, T> polarAngles(Vec<Dim, T> const& center,
813 Vec<Dim, T> const& half_length) const
814 {
815 if constexpr (2 == Dim) {
816 return std::pair{T(0), T(0)};
817 } else {
818 auto const ca = abs(Vec<2, T>(center));
819 auto const min_z = center.z - half_length.z;
820 auto const max_z = center.z + half_length.z;
821
822 T max;
823 T min;
824 if (T(0) <= min_z) {
825 max = polarAngle(Vec<3, T>(ca + Vec2<T>(half_length), min_z));
826 } else if (ca.x >= half_length.x || ca.y >= half_length.y) {
827 max = polarAngle(Vec<3, T>(std::max(T(0), ca.x - half_length.x),
828 std::max(T(0), ca.y - half_length.y), min_z));
829 } else {
830 max = numbers::pi_v<float>;
831 }
832 if (T(0) >= max_z) {
833 min = polarAngle(Vec<3, T>(ca + Vec2<T>(half_length), max_z));
834 } else if (ca.x >= half_length.x || ca.y >= half_length.y) {
835 min = polarAngle(Vec<3, T>(std::max(T(0), ca.x - half_length.x),
836 std::max(T(0), ca.y - half_length.y), max_z));
837 } else {
838 min = T(0);
839 }
840
841 return std::pair{min, max};
842 }
843 }
844
854 template <class T>
855 [[nodiscard]] std::pair<T, T> azimuthalAngles(Vec<Dim, T> const& center,
856 Vec<Dim, T> const& half_length) const
857 {
858 auto const ca = abs(Vec<2, T>(center));
859
860 Vec<2, int> const offset{ca.x <= half_length.x ? 0 : (T(0) < center.x ? 1 : -1),
861 ca.y <= half_length.y ? 0 : (T(0) < center.y ? 1 : -1)};
862
863 if (0 == offset.x && 0 == offset.y) {
864 return std::pair(-numbers::pi_v<T>, numbers::pi_v<T>);
865 }
866
867 Vec<2, T> min_p;
868 Vec<2, T> max_p;
869 T a{};
870
871 if (-1 == offset.x) {
872 min_p.y = center.y + half_length.y;
873 max_p.y = center.y - half_length.y;
874 } else if (1 == offset.x) {
875 min_p.y = center.y - half_length.y;
876 max_p.y = center.y + half_length.y;
877 } else if (-1 == offset.y) {
878 min_p.y = center.y + half_length.y;
879 max_p.y = center.y + half_length.y;
880 } else {
881 min_p.y = center.y - half_length.y;
882 max_p.y = center.y - half_length.y;
883 }
884
885 if (-1 == offset.y) {
886 min_p.x = center.x - half_length.x;
887 max_p.x = center.x + half_length.x;
888 } else if (1 == offset.y) {
889 min_p.x = center.x + half_length.x;
890 max_p.x = center.x - half_length.x;
891 } else if (-1 == offset.x) {
892 min_p.x = center.x + half_length.x;
893 max_p.x = center.x + half_length.x;
894 a = T(2) * numbers::pi_v<T>;
895 } else {
896 min_p.x = center.x - half_length.x;
897 max_p.x = center.x - half_length.x;
898 }
899
900 return std::pair{azimuthalAngle(min_p), azimuthalAngle(max_p) + a};
901 }
902
903 /**************************************************************************************
904 | |
905 | Hits |
906 | |
907 **************************************************************************************/
908
909 template <class T, class... Rest>
910 void filterHits(PointCloud<Dim, T, Rest...>& cloud, Vec<Dim, T> const& origin) const
911 {
912 cloud.erase_if(
913 [&origin, max_sq = max_distance * max_distance](Vec<Dim, T> const& p) mutable {
914 return max_sq < distanceSquared(origin, p);
915 });
916 }
917
918 template <
919 class ExecutionPolicy, class T, class... Rest,
920 std::enable_if_t<execution::is_execution_policy_v<ExecutionPolicy>, bool> = true>
921 void filterHits(ExecutionPolicy&& policy, PointCloud<Dim, T, Rest...>& cloud,
922 Vec<Dim, T> const& origin) const
923 {
924 filterHits(cloud, origin);
925 }
926
927 public:
928 struct Data {
929 float distance{};
930 float distance_error{};
931 std::uint_fast32_t count{};
932 Spinlock lock{};
933
934 Data() = default;
935
936 Data(Data const& other)
937 : distance(other.distance)
938 , distance_error(other.distance_error)
939 , count(other.count)
940 {
941 }
942
943 Data& operator=(Data const& rhs)
944 {
945 distance = rhs.distance;
946 distance_error = rhs.distance_error;
947 count = rhs.count;
948 return *this;
949 }
950 };
951
952 struct Data2 {
953 float min_distance = std::numeric_limits<float>::max();
954 float min_distance_error{};
955
956 float max_distance = std::numeric_limits<float>::lowest();
957 float max_distance_error{};
958
959 std::uint_fast32_t count{};
960 };
961
963 std::thread::id id;
964 std::vector<detail::Miss<Dim>> misses;
965
966 void clear()
967 {
968 id = {};
969 misses.clear();
970 }
971 };
972
973 SensorErrorFun sensor_error_f_;
974
975 float angular_resolution_factor_{};
976
977 std::uint_fast32_t rows_{};
978 std::uint_fast32_t columns_{};
979 mutable __block std::vector<Data> data_;
980 std::uint_fast32_t ds_ = 10u;
981 mutable __block std::vector<Data2> data_2_; // TODO: Use this
982
983 mutable __block std::vector<float> cached_distances_;
984
985 mutable __block std::vector<detail::Miss<Dim>> cached_misses_;
986
987 mutable __block std::deque<ThreadMisses> thread_misses_;
988};
989} // namespace ufo
990
991#endif // UFO_MAP_INTEGRATOR_ANGULAR_INTEGRATOR_HPP
void propagate(MapType map_types=MapType::ALL)
Propagate modified information up the tree.
Definition map.hpp:236
A simple spinlock implementation using std::atomic_flag.
Definition spinlock.hpp:68
constexpr T radians(T deg) noexcept
Converts degrees to radians.
Definition math.hpp:86
All vision-related classes and functions.
Definition cloud.hpp:49
constexpr UnaryFunc for_each(Index first, Index last, UnaryFunc f, Proj proj={})
Applies f to each integer index in [first, last) sequentially.
Definition algorithm.hpp:80
constexpr T a(Lab< T, Flags > color) noexcept
Returns the un-weighted green–red axis value.
Definition lab.hpp:310
constexpr auto distanceSquared(A const &a, B const &b)
Computes the minimum squared distance between two shapes.
Definition distance.hpp:80
constexpr Vec< Geometry::dimension(), typename Geometry::value_type > max(Geometry const &g)
Returns the maximum coordinate of the minimum spanning axis-aligned bounding box of a geometry.
Definition fun.hpp:72
void transformInPlace(Transform< Dim, T > const &t, PointCloud< Dim, T, Rest... > &pc)
Applies a rigid transform to every point position in-place.
constexpr Vec< Dim, T > abs(Vec< Dim, T > v) noexcept
Returns the component-wise absolute value of a vector.
Definition vec.hpp:1351
constexpr T norm(Quat< T > const &q) noexcept
Returns the Euclidean norm sqrt(w² + x² + y² + z²).
Definition quat.hpp:667
constexpr Vec< Geometry::dimension(), typename Geometry::value_type > min(Geometry const &g)
Returns the minimum coordinate of the minimum spanning axis-aligned bounding box of a geometry.
Definition fun.hpp:58
PointCloud< Dim, T, Rest... > transform(Transform< Dim, T > const &t, PointCloud< Dim, T, Rest... > pc)
Applies a rigid transform to every point position and returns the result.
constexpr auto distance(A const &a, B const &b)
Computes the minimum distance between two shapes.
Definition distance.hpp:61
Axis-Aligned Bounding Box (AABB) in Dim-dimensional space.
Definition aabb.hpp:70
Rigid-body transform: a rotation matrix plus a translation vector.
Definition transform.hpp:81
A fixed-size arithmetic vector of up to 4 dimensions.
Definition vec.hpp:76