Easy Navigation
Loading...
Searching...
No Matches
PointPerception.hpp
Go to the documentation of this file.
1// Copyright 2025 Intelligent Robotics Lab
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
26
27#ifndef EASYNAV_SENSORS__TYPES__POINTPERCEPTION_HPP_
28#define EASYNAV_SENSORS__TYPES__POINTPERCEPTION_HPP_
29
30#include <string>
31#include <vector>
32#include <optional>
33#include <mutex>
34#include <memory>
35#include <tuple>
36#include <utility>
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#include "sensor_msgs/msg/laser_scan.hpp"
43#include "sensor_msgs/msg/point_cloud2.hpp"
44#include "rclcpp/time.hpp"
45#include "rclcpp_lifecycle/lifecycle_node.hpp"
49
50namespace std
51{
52
54template<>
55struct hash<std::tuple<int, int, int>>
56{
60 std::size_t operator()(const std::tuple<int, int, int> & key) const
61 {
62 std::size_t h1 = std::hash<int>()(std::get<0>(key));
63 std::size_t h2 = std::hash<int>()(std::get<1>(key));
64 std::size_t h3 = std::hash<int>()(std::get<2>(key));
65 return h1 ^ (h2 << 1) ^ (h3 << 2);
66 }
67};
68
69} // namespace std
70
71namespace easynav
72{
73
75{
76 pcl::PointCloud<pcl::PointXYZ> data;
77 std::string frame;
78 rclcpp::Time stamp;
79};
80
87{
88public:
90 static constexpr std::string_view default_group_ = "points";
91
96 static inline bool supports_msg_type(std::string_view t)
97 {
98 return t == "sensor_msgs/msg/LaserScan" ||
99 t == "sensor_msgs/msg/PointCloud2";
100 }
101
103 {
104 [[maybe_unused]] static const bool _ = [] {
106 [](const PointPerception & perception) {
107 std::ostringstream ret;
108 ret << "{ " << perception.stamp.seconds()
109 << " } PointPerception with " << perception.data.size()
110 << " points in frame [" << perception.frame_id
111 << "] with ts " << perception.stamp.seconds() << "\n";
112 return ret.str();
113 });
114 return true;
115 }();
116 }
117
119 {
120 std::lock_guard<std::mutex> lock(other.mutex_);
121 stamp = other.stamp;
122 frame_id = other.frame_id;
123 valid = other.valid;
124 new_data = other.new_data;
125
126 data = other.data;
131 buffer = other.buffer;
132 }
133
135 {
136 if (this == &other) {
137 return *this;
138 }
139
140 std::scoped_lock lock(mutex_, other.mutex_);
141 stamp = other.stamp;
142 frame_id = other.frame_id;
143 valid = other.valid;
144 new_data = other.new_data;
145
146 data = other.data;
151 buffer = other.buffer;
152
153 return *this;
154 }
155
157 pcl::PointCloud<pcl::PointXYZ> data;
158
160 pcl::PointCloud<pcl::PointXYZ> pending_cloud_;
161 std::string pending_frame_;
162 rclcpp::Time pending_stamp_;
163
166 pcl::PointCloud<pcl::PointXYZ> && cloud,
167 std::string && frame,
168 const rclcpp::Time & stamp)
169 {
170 std::lock_guard<std::mutex> lock(mutex_);
171 pending_cloud_ = std::move(cloud);
172 pending_frame_ = std::move(frame);
174 pending_available_ = true;
175 }
176
177
180 void resize(std::size_t size)
181 {
182 data.points.resize(size);
183 }
184
186 {
187 std::lock_guard<std::mutex> lock(mutex_);
188 // Access TF buffer singleton (already initialized somewhere with a clock)
189 auto tf_buffer_ptr = RTTFBuffer::getInstance();
190 auto & tf_buffer = *tf_buffer_ptr;
191 const auto tf_info = tf_buffer.get_tf_info();
192 const std::string & robot_frame = tf_info.robot_frame;
193
194 // ------------------------------------------------------------------
195 // 1. Push pending perception into the circular buffer exactly once.
196 // ------------------------------------------------------------------
197 if (pending_available_) {
198 PointPerceptionBufferType pending_item;
199 pending_item.data = std::move(pending_cloud_); // avoid deep copy
200 pending_item.frame = pending_frame_;
201 pending_item.stamp = pending_stamp_;
202
203 buffer.push(std::move(pending_item));
204 pending_available_ = false;
205 }
206
207 const std::size_t count = buffer.size();
208 if (count == 0) {
209 // No candidates at all: keep current visible state as is.
210 return;
211 }
212
213 // ------------------------------------------------------------------
214 // 2. Drain the circular buffer into a temporary vector so we can
215 // inspect all items and then rebuild the buffer.
216 // ------------------------------------------------------------------
217 std::vector<PointPerceptionBufferType> items;
218 items.reserve(count);
219
220 for (std::size_t i = 0; i < count; ++i) {
222 if (!buffer.pop(item)) {
223 break; // Defensive guard if pop() fails unexpectedly.
224 }
225 items.push_back(std::move(item));
226 }
227
228 if (items.empty()) {
229 // Nothing recovered from buffer: keep visible state untouched.
230 return;
231 }
232
233 // ------------------------------------------------------------------
234 // 3. Find indices:
235 // - newest_idx: newest perception by timestamp (regardless of TF),
236 // - newest_valid_idx: newest perception that has a valid TF.
237 //
238 // IMPORTANT: store only indices to avoid copying point clouds
239 // during the scan.
240 // ------------------------------------------------------------------
241 std::optional<std::size_t> newest_idx;
242 std::optional<std::size_t> newest_valid_idx;
243
244 for (std::size_t i = 0; i < items.size(); ++i) {
245 const auto & item = items[i];
246
247 // Track newest item overall (used when no TF is valid).
248 if (!newest_idx || item.stamp > items[*newest_idx].stamp) {
249 newest_idx = i;
250 }
251
252 bool has_tf = false;
253 try {
254 has_tf = tf_buffer.canTransform(
255 robot_frame,
256 item.frame,
257 tf2_ros::fromMsg(item.stamp));
258 } catch (...) {
259 // Any TF exception is treated as "no valid TF" for this item.
260 has_tf = false;
261 }
262
263 if (has_tf) {
264 if (!newest_valid_idx || item.stamp > items[*newest_valid_idx].stamp) {
265 newest_valid_idx = i;
266 }
267 }
268 }
269
270 // ------------------------------------------------------------------
271 // 4. Update visible state BEFORE moving items back into the buffer.
272 // This guarantees that `data` corresponds to:
273 // - the newest TF-valid item if any exists, otherwise
274 // - the newest item overall.
275 // ------------------------------------------------------------------
276 if (newest_valid_idx) {
277 const auto & sel = items[*newest_valid_idx];
278 data = sel.data; // single deep copy (intentional)
279 frame_id = sel.frame;
280 stamp = sel.stamp;
281 valid = true; // "valid" means "usable / not too old", not "TF ok"
282 new_data = true;
283 } else if (newest_idx) {
284 const auto & sel = items[*newest_idx];
285 data = sel.data; // single deep copy (intentional)
286 frame_id = sel.frame;
287 stamp = sel.stamp;
288 valid = true;
289 new_data = true;
290 } else {
291 // Defensive: should not happen because items is non-empty.
292 return;
293 }
294
295 // ------------------------------------------------------------------
296 // 5. Rebuild the circular buffer from scratch (move-only, no copies).
297 // ------------------------------------------------------------------
298 buffer.clear();
299
300 if (newest_valid_idx) {
301 // Keep the newest TF-valid item and any newer items (even if TF is not yet available).
302 const rclcpp::Time cutoff_stamp = items[*newest_valid_idx].stamp;
303
304 for (auto & item : items) {
305 if (item.stamp >= cutoff_stamp) {
306 buffer.push(std::move(item));
307 }
308 }
309 } else {
310 // No TF-valid items: keep everything in case TF arrives later.
311 for (auto & item : items) {
312 buffer.push(std::move(item));
313 }
314 }
315 }
316
317protected:
318 mutable std::mutex mutex_;
320};
321
328{
329public:
333 void on_initialize() override;
334
342 bool cycle_rt([[maybe_unused]] std::shared_ptr<NavState> nav_state) override;
343
345 std::shared_ptr<PerceptionBase> get_perception() const override {return perception_data_;}
346
347private:
349 std::shared_ptr<PointPerception> perception_data_ {nullptr};
350
352 rclcpp::SubscriptionBase::SharedPtr perception_sub_;
353};
354
358void convert(const sensor_msgs::msg::LaserScan & scan, pcl::PointCloud<pcl::PointXYZ> & pc);
359
363sensor_msgs::msg::PointCloud2 perception_to_rosmsg(const PointPerception & perception);
364
368sensor_msgs::msg::PointCloud2 points_to_rosmsg(const pcl::PointCloud<pcl::PointXYZ> & points);
369
373 std::vector<std::shared_ptr<PointPerception>>;
374
379rclcpp::Time get_latest_point_perceptions_stamp(const PointPerceptions & perceptions);
380
390{
391public:
394 struct VoxelKey
395 {
397 int x, y, z;
398
402 bool operator==(const VoxelKey & other) const
403 {
404 return x == other.x && y == other.y && z == other.z;
405 }
406 };
407
411 {
415 std::size_t operator()(const VoxelKey & key) const
416 {
417 std::size_t h1 = std::hash<int>{}(key.x);
418 std::size_t h2 = std::hash<int>{}(key.y);
419 std::size_t h3 = std::hash<int>{}(key.z);
420 return h1 ^ (h2 << 1) ^ (h3 << 2);
421 }
422 };
423
426 explicit PointPerceptionsOpsView(const PointPerceptions & perceptions);
427
434 explicit PointPerceptionsOpsView(const PointPerception & perception);
435
438 explicit PointPerceptionsOpsView(PointPerceptions && perceptions);
439
442
444
470 const std::vector<double> & min_bounds,
471 const std::vector<double> & max_bounds,
472 bool lazy_post_fuse = true);
473
477 PointPerceptionsOpsView & downsample(double resolution);
478
497 collapse(const std::vector<double> & collapse_dims, bool lazy = true);
498
501 pcl::PointCloud<pcl::PointXYZ> as_points() const;
502
514 PointPerceptionsOpsView & fuse(const std::string & target_frame, bool exact_time = false);
515
534 const std::string & target_frame,
535 rclcpp::Time & stamp,
536 bool exact_time = false);
537
553 add(
554 const pcl::PointCloud<pcl::PointXYZ> points,
555 const std::string & frame,
556 rclcpp::Time stamp);
557
560 const PointPerceptions & get_perceptions() const {return perceptions_;}
561
564 rclcpp::Time get_latest_stamp() const;
565
566private:
567 std::optional<PointPerceptions> owned_;
568 const PointPerceptions & perceptions_;
569 std::vector<pcl::PointIndices> indices_;
570
571 // Lazy fusion state
573 bool has_target_frame_ {false};
575 std::string target_frame_;
577 std::vector<tf2::Transform> tf_transforms_;
579 std::vector<bool> tf_valid_;
580
581 // Lazy collapse state
582 bool collapse_x_ {false};
583 bool collapse_y_ {false};
584 bool collapse_z_ {false};
585 float collapse_val_x_ {0.0f};
586 float collapse_val_y_ {0.0f};
587 float collapse_val_z_ {0.0f};
588
589 bool has_post_filter_ {false};
590 double post_min_[3] {0.0, 0.0, 0.0};
591 double post_max_[3] {0.0, 0.0, 0.0};
592 bool use_post_min_[3] {false, false, false};
593 bool use_post_max_[3] {false, false, false};
594};
595
596} // namespace easynav
597
598#endif // EASYNAV_SENSORS__TYPES__POINTPERCEPTION_HPP_
Defines data structures and utilities for representing and processing sensor perceptions.
Fixed-size circular buffer, thread-safe (mutex-based), copyable.
Definition CircularBuffer.hpp:34
static void register_printer(std::function< std::string(const T &)> printer)
Registers a pretty-printer for type T used by debug_string().
Definition NavState.hpp:458
Abstract base class for representing a single sensor perception.
Definition Perceptions.hpp:48
bool valid
Whether the perception holds usable data: set with new data, cleared by SensorsNode once it is older ...
Definition Perceptions.hpp:60
rclcpp::Time stamp
Timestamp of the perception (ROS time).
Definition Perceptions.hpp:53
bool new_data
Whether the data has changed since the last observation.
Definition Perceptions.hpp:63
std::string frame_id
Coordinate frame associated with the perception.
Definition Perceptions.hpp:56
Abstract base class for pluginlib-based sensor perception handlers.
Definition Perceptions.hpp:117
PerceptionHandler implementation for sensors producing point-based data.
Definition PointPerception.hpp:328
bool cycle_rt(std::shared_ptr< NavState > nav_state) override
Run one real-time sensor processing cycle.
Definition PointPerception.cpp:143
void on_initialize() override
Optional post-initialization hook for subclasses.
Definition PointPerception.cpp:88
std::shared_ptr< PerceptionBase > get_perception() const override
The perception this handler keeps up to date.
Definition PointPerception.hpp:345
Concrete perception class for 3D point cloud data.
Definition PointPerception.hpp:87
pcl::PointCloud< pcl::PointXYZ > pending_cloud_
Definition PointPerception.hpp:160
std::mutex mutex_
Definition PointPerception.hpp:318
static bool supports_msg_type(std::string_view t)
Checks if a ROS message type is supported by this perception.
Definition PointPerception.hpp:96
PointPerception()
Definition PointPerception.hpp:102
void set_pending_cloud(pcl::PointCloud< pcl::PointXYZ > &&cloud, std::string &&frame, const rclcpp::Time &stamp)
Stores a pending cloud atomically for later integration.
Definition PointPerception.hpp:165
CircularBuffer< PointPerceptionBufferType > buffer
Definition PointPerception.hpp:319
static constexpr std::string_view default_group_
Group identifier for point perceptions.
Definition PointPerception.hpp:90
std::string pending_frame_
Definition PointPerception.hpp:161
bool pending_available_
Definition PointPerception.hpp:159
pcl::PointCloud< pcl::PointXYZ > data
The 3D point cloud data associated with this perception.
Definition PointPerception.hpp:157
PointPerception & operator=(const PointPerception &other)
Definition PointPerception.hpp:134
rclcpp::Time pending_stamp_
Definition PointPerception.hpp:162
PointPerception(const PointPerception &other)
Definition PointPerception.hpp:118
void resize(std::size_t size)
Resizes the internal point cloud storage.
Definition PointPerception.hpp:180
void integrate_pending_perceptions()
Definition PointPerception.hpp:185
PointPerceptionsOpsView & downsample(double resolution)
Downsamples each perception using a voxel grid.
Definition PointPerception.cpp:366
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:697
PointPerceptionsOpsView & fuse(const std::string &target_frame, bool exact_time=false)
Configures fusion of all perceptions into a common frame.
Definition PointPerception.cpp:551
PointPerceptionsOpsView(PointPerceptionsOpsView &&)=default
const PointPerceptions & get_perceptions() const
Provides a constant reference to the underlying perceptions container.
Definition PointPerception.hpp:560
pcl::PointCloud< pcl::PointXYZ > as_points() const
Retrieves all selected points across perceptions as a single concatenated cloud.
Definition PointPerception.cpp:478
PointPerceptionsOpsView(const PointPerceptions &perceptions)
Constructs a view over an external container of perceptions.
Definition PointPerception.cpp:204
rclcpp::Time get_latest_stamp() const
Retrieves the latest timestamp across all perceptions.
Definition PointPerception.cpp:753
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:417
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:264
static RTTFBuffer * getInstance(Args &&... args)
Definition Singleton.hpp:30
Definition CircularBuffer.hpp:24
rclcpp::Time get_latest_point_perceptions_stamp(const PointPerceptions &perceptions)
Retrieves the latest timestamp among a set of point-based perceptions.
Definition PointPerception.cpp:730
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:196
sensor_msgs::msg::PointCloud2 perception_to_rosmsg(const PointPerception &perception)
Converts a PointPerception into a sensor_msgs::msg::PointCloud2 message.
Definition PointPerception.cpp:186
void convert(const sensor_msgs::msg::LaserScan &scan, pcl::PointCloud< pcl::PointXYZ > &pc)
Converts a LaserScan message into a point cloud.
Definition PointPerception.cpp:154
std::vector< std::shared_ptr< PointPerception > > PointPerceptions
Alias for a vector of shared pointers to PointPerception objects.
Definition PointPerception.hpp:372
Definition PointPerception.hpp:51
Definition PointPerception.hpp:75
rclcpp::Time stamp
Definition PointPerception.hpp:78
pcl::PointCloud< pcl::PointXYZ > data
Definition PointPerception.hpp:76
std::string frame
Definition PointPerception.hpp:77
Hash functor for VoxelKey.
Definition PointPerception.hpp:411
std::size_t operator()(const VoxelKey &key) const
Computes the hash value.
Definition PointPerception.hpp:415
Discrete 3D voxel index used for downsampling.
Definition PointPerception.hpp:395
int y
Definition PointPerception.hpp:397
int z
Definition PointPerception.hpp:397
int x
Discrete coordinates (voxel indices).
Definition PointPerception.hpp:397
bool operator==(const VoxelKey &other) const
Equality comparison.
Definition PointPerception.hpp:402
std::size_t operator()(const std::tuple< int, int, int > &key) const
Computes the hash value.
Definition PointPerception.hpp:60