|
53 | 53 | #define AUTOWARE__POINTCLOUD_PREPROCESSOR__FILTER_HPP_ |
54 | 54 |
|
55 | 55 | #include "autoware/pointcloud_preprocessor/transform_info.hpp" |
| 56 | +#include "autoware/pointcloud_preprocessor/utility/memory.hpp" |
56 | 57 |
|
57 | 58 | #include <boost/thread/mutex.hpp> |
58 | 59 |
|
@@ -244,17 +245,141 @@ class Filter : public rclcpp::Node |
244 | 245 |
|
245 | 246 | std::unique_ptr<managed_transform_buffer::ManagedTransformBuffer> managed_tf_buffer_{nullptr}; |
246 | 247 |
|
247 | | - inline bool is_valid(const PointCloud2ConstPtr & cloud) |
| 248 | + /** |
| 249 | + * @brief Validate a sensor_msgs::msg::PointCloud2 message for structural consistency and layout. |
| 250 | + * |
| 251 | + * This function performs a series of lightweight sanity checks to determine whether a |
| 252 | + * PointCloud2 message is safe to consume by algorithms expecting an XYZ-compatible, |
| 253 | + * organized (dense) point cloud. |
| 254 | + * |
| 255 | + * Validation errors and warnings are reported using throttled ROS logging to avoid |
| 256 | + * excessive log spam when invalid data is repeatedly received. |
| 257 | + * |
| 258 | + * @details |
| 259 | + * The following checks are performed in order: |
| 260 | + * |
| 261 | + * 1. **Null pointer check** |
| 262 | + * - Ensures the input PointCloud2 pointer is valid. |
| 263 | + * |
| 264 | + * 2. **Point step validation** |
| 265 | + * - Verifies that `point_step` is non-zero. |
| 266 | + * |
| 267 | + * 3. **Dense (organized) cloud requirement** |
| 268 | + * - Warns if `is_dense` is false. |
| 269 | + * - Currently does *not* fail validation, but will become a hard error |
| 270 | + * starting **July 2026**. |
| 271 | + * |
| 272 | + * 4. **Point field layout compatibility** |
| 273 | + * - Ensures the point data layout is compatible with `PointXYZ` |
| 274 | + * (fields must begin with `x`, `y`, `z` of type `FLOAT32`). |
| 275 | + * |
| 276 | + * 5. **Row step consistency** |
| 277 | + * - Validates that `row_step == width * point_step`. |
| 278 | + * - Currently logs a warning on mismatch. |
| 279 | + * - Will become a hard error starting **July 2026**. |
| 280 | + * |
| 281 | + * 6. **Data buffer size consistency** |
| 282 | + * - Ensures `data.size() == height * row_step`. |
| 283 | + * |
| 284 | + * @param cloud |
| 285 | + * Shared pointer to the input PointCloud2 message to validate. |
| 286 | + * |
| 287 | + * @param logger |
| 288 | + * ROS 2 logger used for emitting validation warnings. |
| 289 | + * |
| 290 | + * @param clock |
| 291 | + * ROS 2 clock used for throttled logging. |
| 292 | + * |
| 293 | + * @return true |
| 294 | + * If the point cloud passes all mandatory validation checks. |
| 295 | + * |
| 296 | + * @return false |
| 297 | + * If a critical validation failure is detected (e.g., null pointer, |
| 298 | + * incompatible data layout, or inconsistent buffer size). |
| 299 | + * |
| 300 | + * @note |
| 301 | + * - All warnings are throttled with a fixed duration of 5000 ms. |
| 302 | + * - Some warnings are marked as *future errors* and are expected to |
| 303 | + * become fatal after July 2026. |
| 304 | + * |
| 305 | + * @warning |
| 306 | + * This function assumes consumers expect an XYZ-only point layout. |
| 307 | + * Additional fields (e.g., intensity, color) are allowed only if they |
| 308 | + * appear *after* the XYZ fields and do not alter the layout compatibility. |
| 309 | + */ |
| 310 | + static bool is_valid( |
| 311 | + const PointCloud2ConstPtr & cloud, const rclcpp::Logger & logger, rclcpp::Clock & clock) |
248 | 312 | { |
249 | | - if (cloud->width * cloud->height * cloud->point_step != cloud->data.size()) { |
250 | | - RCLCPP_WARN( |
251 | | - this->get_logger(), |
252 | | - "Invalid PointCloud (data = %zu, width = %d, height = %d, step = %d) with stamp %f, " |
253 | | - "and frame %s received!", |
254 | | - cloud->data.size(), cloud->width, cloud->height, cloud->point_step, |
255 | | - rclcpp::Time(cloud->header.stamp).seconds(), cloud->header.frame_id.c_str()); |
| 313 | + static constexpr rcutils_duration_value_t throttle_duration_ms = 5000; |
| 314 | + |
| 315 | + if (!cloud) { |
| 316 | + RCLCPP_WARN_THROTTLE( |
| 317 | + logger, clock, throttle_duration_ms, "Invalid PointCloud: Null pointer received."); |
| 318 | + return false; |
| 319 | + } |
| 320 | + |
| 321 | + // Check: Point Step |
| 322 | + if (cloud->point_step == 0) { |
| 323 | + RCLCPP_WARN_THROTTLE( |
| 324 | + logger, clock, throttle_duration_ms, |
| 325 | + "Invalid PointCloud: point_step is 0. " |
| 326 | + "Frame: '%s', Stamp: %d.%09u", |
| 327 | + cloud->header.frame_id.c_str(), cloud->header.stamp.sec, cloud->header.stamp.nanosec); |
| 328 | + return false; |
| 329 | + } |
| 330 | + |
| 331 | + // Check: Only accept organized (dense) point clouds |
| 332 | + if (!cloud->is_dense) { |
| 333 | + RCLCPP_WARN_THROTTLE( |
| 334 | + logger, clock, throttle_duration_ms, |
| 335 | + "Invalid PointCloud: is_dense is false. " |
| 336 | + "The point cloud should be organized (dense) and contain only valid points. " |
| 337 | + "This will be an ERROR starting in 2026 July" |
| 338 | + "Frame: '%s', Stamp: %d.%09u", |
| 339 | + cloud->header.frame_id.c_str(), cloud->header.stamp.sec, cloud->header.stamp.nanosec); |
| 340 | + // TODO(mfc): return false; After 2026 July |
| 341 | + } |
| 342 | + |
| 343 | + // Check: Point field layout compatibility |
| 344 | + if (!utils::is_data_layout_compatible_with_point_xyz(*cloud)) { |
| 345 | + RCLCPP_WARN_THROTTLE( |
| 346 | + logger, clock, throttle_duration_ms, |
| 347 | + "The pointcloud layout is not compatible with PointXYZ. Aborting. " |
| 348 | + "Make sure Point fields start with x, y, z as FLOAT32."); |
| 349 | + return false; |
| 350 | + } |
| 351 | + |
| 352 | + // Check: Row step consistency |
| 353 | + size_t expected_row_step = |
| 354 | + static_cast<size_t>(cloud->width) * static_cast<size_t>(cloud->point_step); |
| 355 | + |
| 356 | + if (expected_row_step != static_cast<size_t>(cloud->row_step)) { |
| 357 | + RCLCPP_WARN_THROTTLE( |
| 358 | + logger, clock, throttle_duration_ms, |
| 359 | + "Invalid PointCloud: row_step mismatch. " |
| 360 | + "Expected: %zu (width %u * point_step %u), Got: %u. " |
| 361 | + "Frame: '%s', Stamp: %d.%09u" |
| 362 | + " Please fill in the `cloud->row_step` field accordingly." |
| 363 | + " This will be an ERROR starting in 2026 July", |
| 364 | + expected_row_step, cloud->width, cloud->point_step, cloud->row_step, |
| 365 | + cloud->header.frame_id.c_str(), cloud->header.stamp.sec, cloud->header.stamp.nanosec); |
| 366 | + // TODO(mfc): return false; After 2026 July |
| 367 | + } |
| 368 | + |
| 369 | + // Check: Data buffer size consistency |
| 370 | + size_t expected_data_size = static_cast<size_t>(cloud->height) * expected_row_step; |
| 371 | + |
| 372 | + if (expected_data_size != cloud->data.size()) { |
| 373 | + RCLCPP_WARN_THROTTLE( |
| 374 | + logger, clock, throttle_duration_ms, |
| 375 | + "Invalid PointCloud: data size mismatch. " |
| 376 | + "Expected: %zu (height %u * row_step %u), Got: %zu. " |
| 377 | + "Frame: '%s', Stamp: %d.%09u", |
| 378 | + expected_data_size, cloud->height, cloud->row_step, cloud->data.size(), |
| 379 | + cloud->header.frame_id.c_str(), cloud->header.stamp.sec, cloud->header.stamp.nanosec); |
256 | 380 | return false; |
257 | 381 | } |
| 382 | + |
258 | 383 | return true; |
259 | 384 | } |
260 | 385 |
|
|
0 commit comments