Easy Navigation
Loading...
Searching...
No Matches
PointPerception.hpp
Go to the documentation of this file.
1// Copyright 2025 Intelligent Robotics Lab
2//
3// This file is part of the project Easy Navigation (EasyNav in short)
4// licensed under the GNU General Public License v3.0.
5// See <http://www.gnu.org/licenses/> for details.
6//
7// Easy Navigation program is free software: you can redistribute it and/or modify
8// it under the terms of the GNU General Public License as published by
9// the Free Software Foundation, either version 3 of the License, or
10// (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15// GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program. If not, see <http://www.gnu.org/licenses/>.
19
30
31#ifndef EASYNAV_COMMON_TYPES__POINTPERCEPTIONS_HPP_
32#define EASYNAV_COMMON_TYPES__POINTPERCEPTIONS_HPP_
33
34#include <string>
35#include <vector>
36#include <optional>
37
38#include "tf2/LinearMath/Transform.hpp"
39#include "pcl/point_cloud.h"
40#include "pcl/point_types.h"
41#include "pcl/PointIndices.h"
42
43#include "sensor_msgs/msg/laser_scan.hpp"
44#include "sensor_msgs/msg/point_cloud2.hpp"
45
46#include "rclcpp/time.hpp"
47#include "rclcpp_lifecycle/lifecycle_node.hpp"
48
52
53namespace std
54{
55
57template<>
58struct hash<std::tuple<int, int, int>>
59{
63 std::size_t operator()(const std::tuple<int, int, int> & key) const
64 {
65 std::size_t h1 = std::hash<int>()(std::get<0>(key));
66 std::size_t h2 = std::hash<int>()(std::get<1>(key));
67 std::size_t h3 = std::hash<int>()(std::get<2>(key));
68 return h1 ^ (h2 << 1) ^ (h3 << 2);
69 }
70};
71
72} // namespace std
73
74namespace easynav
75{
76
78{
79 pcl::PointCloud<pcl::PointXYZ> data;
80 std::string frame;
81 rclcpp::Time stamp;
82};
83
90{
91public:
93 static constexpr std::string_view default_group_ = "points";
94
98 static inline bool supports_msg_type(std::string_view t)
99 {
100 return t == "sensor_msgs/msg/LaserScan" ||
101 t == "sensor_msgs/msg/PointCloud2";
102 }
103
105 pcl::PointCloud<pcl::PointXYZ> data;
106
108 pcl::PointCloud<pcl::PointXYZ> pending_cloud_;
109 std::string pending_frame_;
110 rclcpp::Time pending_stamp_;
111
112
115 void resize(std::size_t size)
116 {
117 data.points.resize(size);
118 }
119
122 {
123 return buffer.latest_ref();
124 }
125
127 {
128 // Access TF buffer singleton (already initialized somewhere with a clock)
129 auto tf_buffer_ptr = RTTFBuffer::getInstance();
130 auto & tf_buffer = *tf_buffer_ptr;
131 const auto tf_info = tf_buffer.get_tf_info();
132 const std::string & robot_frame = tf_info.robot_frame;
133
134 // ------------------------------------------------------------------
135 // 1. Push pending perception into the circular buffer exactly once.
136 // ------------------------------------------------------------------
137 if (pending_available_) {
138 PointPerceptionBufferType pending_item;
139 pending_item.data = std::move(pending_cloud_); // avoid deep copy
140 pending_item.frame = pending_frame_;
141 pending_item.stamp = pending_stamp_;
142
143 buffer.push(std::move(pending_item));
144 pending_available_ = false;
145 }
146
147 const std::size_t count = buffer.size();
148 if (count == 0) {
149 // No candidates at all: keep current visible state as is.
150 return;
151 }
152
153 // ------------------------------------------------------------------
154 // 2. Drain the circular buffer into a temporary vector so we can
155 // inspect all items and then rebuild the buffer.
156 // ------------------------------------------------------------------
157 std::vector<PointPerceptionBufferType> items;
158 items.reserve(count);
159
160 for (std::size_t i = 0; i < count; ++i) {
162 if (!buffer.pop(item)) {
163 break; // Defensive guard if pop() fails unexpectedly.
164 }
165 items.push_back(std::move(item));
166 }
167
168 if (items.empty()) {
169 // Nothing recovered from buffer: keep visible state untouched.
170 return;
171 }
172
173 // ------------------------------------------------------------------
174 // 3. Find indices:
175 // - newest_idx: newest perception by timestamp (regardless of TF),
176 // - newest_valid_idx: newest perception that has a valid TF.
177 //
178 // IMPORTANT: store only indices to avoid copying point clouds
179 // during the scan.
180 // ------------------------------------------------------------------
181 std::optional<std::size_t> newest_idx;
182 std::optional<std::size_t> newest_valid_idx;
183
184 for (std::size_t i = 0; i < items.size(); ++i) {
185 const auto & item = items[i];
186
187 // Track newest item overall (used when no TF is valid).
188 if (!newest_idx || item.stamp > items[*newest_idx].stamp) {
189 newest_idx = i;
190 }
191
192 bool has_tf = false;
193 try {
194 has_tf = tf_buffer.canTransform(
195 robot_frame,
196 item.frame,
197 tf2_ros::fromMsg(item.stamp),
198 tf2::durationFromSec(0.0));
199 } catch (...) {
200 // Any TF exception is treated as "no valid TF" for this item.
201 has_tf = false;
202 }
203
204 if (has_tf) {
205 if (!newest_valid_idx || item.stamp > items[*newest_valid_idx].stamp) {
206 newest_valid_idx = i;
207 }
208 }
209 }
210
211 // ------------------------------------------------------------------
212 // 4. Update visible state BEFORE moving items back into the buffer.
213 // This guarantees that `data` corresponds to:
214 // - the newest TF-valid item if any exists, otherwise
215 // - the newest item overall.
216 // ------------------------------------------------------------------
217 if (newest_valid_idx) {
218 const auto & sel = items[*newest_valid_idx];
219 data = sel.data; // single deep copy (intentional)
220 frame_id = sel.frame;
221 stamp = sel.stamp;
222 valid = true; // "valid" means "usable / not too old", not "TF ok"
223 new_data = true;
224 } else if (newest_idx) {
225 const auto & sel = items[*newest_idx];
226 data = sel.data; // single deep copy (intentional)
227 frame_id = sel.frame;
228 stamp = sel.stamp;
229 valid = true;
230 new_data = true;
231 } else {
232 // Defensive: should not happen because items is non-empty.
233 return;
234 }
235
236 // ------------------------------------------------------------------
237 // 5. Rebuild the circular buffer from scratch (move-only, no copies).
238 // ------------------------------------------------------------------
239 buffer.clear();
240
241 if (newest_valid_idx) {
242 // Keep the newest TF-valid item and any newer items (even if TF is not yet available).
243 const rclcpp::Time cutoff_stamp = items[*newest_valid_idx].stamp;
244
245 for (auto & item : items) {
246 if (item.stamp >= cutoff_stamp) {
247 buffer.push(std::move(item));
248 }
249 }
250 } else {
251 // No TF-valid items: keep everything in case TF arrives later.
252 for (auto & item : items) {
253 buffer.push(std::move(item));
254 }
255 }
256 }
257
258protected:
260};
261
268{
269public:
272 std::string group() const override {return "points";}
273
277 std::shared_ptr<PerceptionBase> create(const std::string &) override
278 {
279 return std::make_shared<PointPerception>();
280 }
281
294 rclcpp::SubscriptionBase::SharedPtr create_subscription(
295 rclcpp_lifecycle::LifecycleNode & node,
296 const std::string & topic,
297 const std::string & type,
298 std::shared_ptr<PerceptionBase> target,
299 rclcpp::CallbackGroup::SharedPtr cb_group) override;
300};
301
305void convert(const sensor_msgs::msg::LaserScan & scan, pcl::PointCloud<pcl::PointXYZ> & pc);
306
310sensor_msgs::msg::PointCloud2 perception_to_rosmsg(const PointPerception & perception);
311
315sensor_msgs::msg::PointCloud2 points_to_rosmsg(const pcl::PointCloud<pcl::PointXYZ> & points);
316
320 std::vector<std::shared_ptr<PointPerception>>;
321
325PointPerceptions get_point_perceptions(std::vector<PerceptionPtr> & perceptionptr);
326
330rclcpp::Time get_latest_point_perceptions_stamp(const PointPerceptions & perceptions);
331
339{
340public:
343 struct VoxelKey
344 {
346 int x, y, z;
347
351 bool operator==(const VoxelKey & other) const
352 {
353 return x == other.x && y == other.y && z == other.z;
354 }
355 };
356
360 {
364 std::size_t operator()(const VoxelKey & key) const
365 {
366 std::size_t h1 = std::hash<int>{}(key.x);
367 std::size_t h2 = std::hash<int>{}(key.y);
368 std::size_t h3 = std::hash<int>{}(key.z);
369 return h1 ^ (h2 << 1) ^ (h3 << 2);
370 }
371 };
372
375 explicit PointPerceptionsOpsView(const PointPerceptions & perceptions);
376
383 explicit PointPerceptionsOpsView(const PointPerception & perception);
384
387 explicit PointPerceptionsOpsView(PointPerceptions && perceptions);
388
391
393
419 const std::vector<double> & min_bounds,
420 const std::vector<double> & max_bounds,
421 bool lazy_post_fuse = true);
422
426 PointPerceptionsOpsView & downsample(double resolution);
427
446 collapse(const std::vector<double> & collapse_dims, bool lazy = true);
447
450 pcl::PointCloud<pcl::PointXYZ> as_points() const;
451
455 const pcl::PointCloud<pcl::PointXYZ> & as_points(int idx) const;
456
468 PointPerceptionsOpsView & fuse(const std::string & target_frame, bool exact_time = true);
469
486 const std::string & target_frame,
487 rclcpp::Time & stamp,
488 bool exact_time = true);
489
505 add(
506 const pcl::PointCloud<pcl::PointXYZ> points,
507 const std::string & frame,
508 rclcpp::Time stamp);
509
512 const PointPerceptions & get_perceptions() const {return perceptions_;}
513
516 rclcpp::Time get_latest_stamp() const;
517
518private:
519 std::optional<PointPerceptions> owned_;
520 const PointPerceptions & perceptions_;
521 std::vector<pcl::PointIndices> indices_;
522
523 // Lazy fusion state
524 bool has_target_frame_ {false};
525 std::string target_frame_;
526 std::vector<tf2::Transform> tf_transforms_;
527 std::vector<bool> tf_valid_;
528
529 // Lazy collapse state
530 bool collapse_x_ {false};
531 bool collapse_y_ {false};
532 bool collapse_z_ {false};
533 float collapse_val_x_ {0.0f};
534 float collapse_val_y_ {0.0f};
535 float collapse_val_z_ {0.0f};
536
537 bool has_post_filter_ {false};
538 double post_min_[3] {0.0, 0.0, 0.0};
539 double post_max_[3] {0.0, 0.0, 0.0};
540 bool use_post_min_[3] {false, false, false};
541 bool use_post_max_[3] {false, false, false};
542
543 // Temporary storage for as_points(int)
544 mutable pcl::PointCloud<pcl::PointXYZ> tmp_single_cloud_;
545};
546
547} // namespace easynav
548
549#endif // EASYNAV_COMMON_TYPES__POINTPERCEPTIONS_HPP_
Defines data structures and utilities for representing and processing sensor perceptions.
Fixed-size circular buffer, thread-safe (mutex-based), copyable.
Definition CircularBuffer.hpp:38
Abstract base class for representing a single sensor perception.
Definition Perceptions.hpp:45
bool valid
Whether the perception contains valid data.
Definition Perceptions.hpp:56
rclcpp::Time stamp
Timestamp of the perception (ROS time).
Definition Perceptions.hpp:50
bool new_data
Whether the data has changed since the last observation.
Definition Perceptions.hpp:59
std::string frame_id
Coordinate frame associated with the perception.
Definition Perceptions.hpp:53
Abstract base interface for group-specific perception handlers.
Definition Perceptions.hpp:121
PerceptionHandler implementation for sensors producing point-based data.
Definition PointPerception.hpp:268
std::shared_ptr< PerceptionBase > create(const std::string &) override
Creates a new PointPerception instance.
Definition PointPerception.hpp:277
std::string group() const override
Returns the sensor group handled by this handler.
Definition PointPerception.hpp:272
rclcpp::SubscriptionBase::SharedPtr create_subscription(rclcpp_lifecycle::LifecycleNode &node, const std::string &topic, const std::string &type, std::shared_ptr< PerceptionBase > target, rclcpp::CallbackGroup::SharedPtr cb_group) override
Creates a subscription to LaserScan or PointCloud2 messages and updates the perception.
Definition PointPerception.cpp:97
Concrete perception class for 3D point cloud data.
Definition PointPerception.hpp:90
pcl::PointCloud< pcl::PointXYZ > pending_cloud_
Definition PointPerception.hpp:108
static bool supports_msg_type(std::string_view t)
Checks if a ROS message type is supported by this perception.
Definition PointPerception.hpp:98
CircularBuffer< PointPerceptionBufferType > buffer
Definition PointPerception.hpp:259
static constexpr std::string_view default_group_
Group identifier for point perceptions.
Definition PointPerception.hpp:93
std::string pending_frame_
Definition PointPerception.hpp:109
bool pending_available_
Definition PointPerception.hpp:107
pcl::PointCloud< pcl::PointXYZ > data
The 3D point cloud data associated with this perception.
Definition PointPerception.hpp:105
const PointPerceptionBufferType & get_last_perception() const
Retrieves the most recent buffered perception (independently of it has a valid TF) without removing i...
Definition PointPerception.hpp:121
rclcpp::Time pending_stamp_
Definition PointPerception.hpp:110
void resize(std::size_t size)
Resizes the internal point cloud storage.
Definition PointPerception.hpp:115
void integrate_pending_perceptions()
Definition PointPerception.hpp:126
PointPerceptionsOpsView & downsample(double resolution)
Downsamples each perception using a voxel grid.
Definition PointPerception.cpp:361
PointPerceptionsOpsView & add(const pcl::PointCloud< pcl::PointXYZ > points, const std::string &frame, rclcpp::Time stamp)
Adds a new perception to the current view.
Definition PointPerception.cpp:671
PointPerceptionsOpsView & fuse(const std::string &target_frame, bool exact_time=true)
Configures fusion of all perceptions into a common frame.
Definition PointPerception.cpp:603
PointPerceptionsOpsView(PointPerceptionsOpsView &&)=default
const PointPerceptions & get_perceptions() const
Provides a constant reference to the underlying perceptions container.
Definition PointPerception.hpp:512
pcl::PointCloud< pcl::PointXYZ > as_points() const
Retrieves all selected points across perceptions as a single concatenated cloud.
Definition PointPerception.cpp:473
PointPerceptionsOpsView(const PointPerceptions &perceptions)
Constructs a view over an external container of perceptions.
Definition PointPerception.cpp:197
rclcpp::Time get_latest_stamp() const
Retrieves the latest timestamp across all perceptions.
Definition PointPerception.cpp:725
PointPerceptionsOpsView & collapse(const std::vector< double > &collapse_dims, bool lazy=true)
Collapses dimensions to fixed values (for example, projection onto a plane).
Definition PointPerception.cpp:412
PointPerceptionsOpsView(const PointPerceptionsOpsView &)=delete
PointPerceptionsOpsView & operator=(const PointPerceptionsOpsView &)=delete
PointPerceptionsOpsView & filter(const std::vector< double > &min_bounds, const std::vector< double > &max_bounds, bool lazy_post_fuse=true)
Filters all point clouds by axis-aligned bounds.
Definition PointPerception.cpp:257
static RTTFBuffer * getInstance(Args &&... args)
Definition Singleton.hpp:36
Definition CircularBuffer.hpp:28
rclcpp::Time get_latest_point_perceptions_stamp(const PointPerceptions &perceptions)
Retrieves the latest timestamp among a set of point-based perceptions.
Definition PointPerception.cpp:710
sensor_msgs::msg::PointCloud2 points_to_rosmsg(const pcl::PointCloud< pcl::PointXYZ > &points)
Converts a PCL point cloud into a sensor_msgs::msg::PointCloud2 message.
Definition PointPerception.cpp:189
sensor_msgs::msg::PointCloud2 perception_to_rosmsg(const PointPerception &perception)
Converts a PointPerception into a sensor_msgs::msg::PointCloud2 message.
Definition PointPerception.cpp:179
void convert(const sensor_msgs::msg::LaserScan &scan, pcl::PointCloud< pcl::PointXYZ > &pc)
Converts a LaserScan message into a point cloud.
Definition PointPerception.cpp:147
std::vector< std::shared_ptr< PointPerception > > PointPerceptions
Alias for a vector of shared pointers to PointPerception objects.
Definition PointPerception.hpp:319
PointPerceptions get_point_perceptions(std::vector< PerceptionPtr > &perceptionptr)
Extracts all PointPerception objects from a heterogeneous collection.
Definition PointPerception.cpp:705
Definition PointPerception.hpp:54
Definition PointPerception.hpp:78
rclcpp::Time stamp
Definition PointPerception.hpp:81
pcl::PointCloud< pcl::PointXYZ > data
Definition PointPerception.hpp:79
std::string frame
Definition PointPerception.hpp:80
Hash functor for VoxelKey.
Definition PointPerception.hpp:360
std::size_t operator()(const VoxelKey &key) const
Computes the hash value.
Definition PointPerception.hpp:364
Discrete 3D voxel index used for downsampling.
Definition PointPerception.hpp:344
int y
Definition PointPerception.hpp:346
int z
Definition PointPerception.hpp:346
int x
Discrete coordinates (voxel indices).
Definition PointPerception.hpp:346
bool operator==(const VoxelKey &other) const
Equality comparison.
Definition PointPerception.hpp:351
std::size_t operator()(const std::tuple< int, int, int > &key) const
Computes the hash value.
Definition PointPerception.hpp:63