Skip to content

Latest commit

 

History

History
705 lines (557 loc) · 25.9 KB

File metadata and controls

705 lines (557 loc) · 25.9 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Cloudini is a high-performance pointcloud compression library with bindings for ROS, PCL, and WebAssembly. It implements a two-stage compression approach: custom encoding for pointcloud fields followed by general-purpose compression (LZ4/ZSTD).

Architecture

The project consists of three main components:

  • cloudini_lib/: Core compression library (C++20) with field encoders/decoders
  • cloudini_ros/: ROS2 integration with point_cloud_transport plugins and conversion utilities
  • cloudini_web/: WebAssembly interface for browser-based compression

Core Library (cloudini_lib)

Key components:

  • PointcloudEncoder: Multi-threaded encoder with double-buffering pattern for LZ4/ZSTD compression
  • PointcloudDecoder: Decoder supporting chunked decompression
  • Field Encoders/Decoders: Type-specific encoders for efficient field compression
    • FieldEncoderFloatN_Lossy: SIMD-optimized encoder for 3-4 consecutive FLOAT32 fields (typically XYZ/XYZRGB)
    • FieldEncoderInt: Delta + varint encoding for integer types
    • FieldEncoderFloat_XOR: XOR-based lossless float compression
    • FieldEncoderCopy: Pass-through for unsupported types

Threading Model:

  • Encoder uses a worker thread (compressionWorker()) for LZ4/ZSTD compression
  • Double-buffering pattern: main thread encodes fields while worker compresses previous chunk
  • Condition variables cv_ready_to_compress_ and cv_done_compressing_ coordinate work
  • CRITICAL: All shared state modifications (compression_done_, compressed_size_) MUST be protected by mutex_

ROS2 Integration (cloudini_ros)

Key Classes:

  1. CloudiniSubscriberPCL (cloudini_ros/include/cloudini_ros/cloudini_subscriber_pcl.hpp)

    • High-performance subscriber that converts CompressedPointCloud2 directly to pcl::PCLPointCloud2
    • Uses rclcpp::GenericSubscription for zero-copy raw DDS message access
    • Implements object pool pattern (max 4 objects) to avoid repeated allocations
    • Thread-safe with mutex-protected pool
    • Bypasses intermediate sensor_msgs::PointCloud2 conversion
  2. Topic Converter Node (cloudini_ros/src/topic_converter.cpp)

    • Converts between sensor_msgs/PointCloud2 and point_cloud_interfaces/CompressedPointCloud2
    • Supports both compression and decompression modes
    • Optimizations:
      • Skips processing when get_subscription_count() == 0
      • Auto-detects publisher QoS settings via adapt_request_to_offers()
      • Zero-copy via raw DDS message manipulation
  3. Point Cloud Transport Plugin (cloudini_ros/src/cloudini_publisher_plugin.cpp)

    • Integrates with ROS2 point_cloud_transport framework (similar to image_transport)
    • Automatically creates compressed topic variants (e.g., /points/cloudini)
    • Configurable via transport hints: TransportHints("cloudini")

Message Parsing Utilities (cloudini_lib/include/cloudini_lib/ros_msg_utils.hpp):

  • getDeserializedPointCloudMessage(): Parse raw DDS messages without full deserialization
  • convertPointCloud2ToCompressedCloud(): Convert + compress in single pass
  • convertCompressedCloudToPointCloud2(): Decompress + convert
  • toEncodingInfo(): Extract encoding metadata from point cloud

PCL Integration (cloudini_lib/include/cloudini_lib/pcl_conversion.hpp):

  • PCLPointCloudEncode(): Encode pcl::PCLPointCloud2 to Cloudini format
  • PCLPointCloudDecode(): Decode Cloudini format to pcl::PCLPointCloud2
  • Direct conversion without intermediate ROS message types

Build Commands

Core Library (Standalone)

cmake -B build_release -S cloudini_lib -DCMAKE_BUILD_TYPE=Release
cmake --build build_release --parallel

Debug Build with Sanitizers

cmake -B build_debug -S cloudini_lib -DCMAKE_BUILD_TYPE=Debug
cmake --build build_debug --parallel

ROS Build

colcon build --packages-select cloudini_lib cloudini_ros

WebAssembly Build

# Requires Emscripten
cmake -B build_wasm -S cloudini_lib -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
cmake --build build_wasm

Web Interface

cd cloudini_web
npm install
npm run dev  # Development server
npm run build  # Production build

Testing

Core Library Tests

# Core library tests
cd build_debug && ctest

# Run specific benchmark
./build_release/benchmarks/pcd_benchmark

ROS2 Test Nodes

Test Publisher (cloudini_ros/test/test_plugin_publisher.cpp):

ros2 run cloudini_ros test_plugin_publisher --ros-args -p input_topic:=/points -p output_topic:=/points_pct
  • Subscribes to standard PointCloud2 topic
  • Publishes via point_cloud_transport, automatically creating compressed variants
  • Creates topics: /points_pct (raw) and /points_pct/cloudini (compressed)

Test Subscriber (cloudini_ros/test/test_plugin_subscriber.cpp):

ros2 run cloudini_ros test_plugin_subscriber --ros-args -p topic:=/points_pct
  • Uses point_cloud_transport to subscribe with transport hint "cloudini"
  • Manually converts to PCL format in callback

Direct PCL Subscriber (cloudini_ros/test/test_cloudini_subscriber.cpp):

ros2 run cloudini_ros test_cloudini_subscriber --ros-args -p topic:=/points/cloudini
  • Uses CloudiniSubscriberPCL for most efficient direct PCL conversion
  • Bypasses point_cloud_transport framework for minimal overhead

Topic Converter:

# Compress: sensor_msgs/PointCloud2 -> CompressedPointCloud2
ros2 run cloudini_ros cloudini_topic_converter --ros-args \
  -p compressing:=true \
  -p topic_input:=/points \
  -p topic_output:=/points/compressed \
  -p resolution:=0.001

# Decompress: CompressedPointCloud2 -> sensor_msgs/PointCloud2
ros2 run cloudini_ros cloudini_topic_converter --ros-args \
  -p compressing:=false \
  -p topic_input:=/points/compressed \
  -p topic_output:=/points/decompressed

Direct Compression Publisher (cloudini_ros/test/test_direct_publisher.cpp):

ros2 run cloudini_ros test_direct_publisher --ros-args \
  -p input_topic:=/points \
  -p output_topic:=/points/compressed \
  -p resolution:=0.001
  • Compresses PointCloud2 directly using SerializeCompressedPointCloud2
  • Publishes as serialized CompressedPointCloud2 via generic publisher
  • No topic_converter node needed

Benchmarking

MCAP Rosbag Conversion:

# Convert a ROS2 bag directory (creates <bag_dir>_encoded/ with .mcap + metadata.yaml)
./build_release/tools/cloudini_rosbag_converter -c -y -f DATA/my_bag/

# Convert a bare .mcap file (if sibling metadata.yaml exists, it is also transformed)
./build_release/tools/cloudini_rosbag_converter -c -y -f DATA/my_bag/my_bag_0.mcap

# Convert with --viz: visualization-oriented lossy preprocessing
# (drop NaN points, voxel-dedupe at xyz resolution, quantize FLOAT64 to 1us).
# Roughly halves output size on real LIDAR with stage-2 ZSTD.
./build_release/tools/cloudini_rosbag_converter -c -y --viz -f DATA/my_bag/

# Decode back to PointCloud2
./build_release/tools/cloudini_rosbag_converter -d -y -f DATA/my_bag_encoded/

Visualization workflow (--viz):

The --viz flag bundles three lossy preprocessing operations applied per message before V4 encoding:

  1. Drop points whose geometry triple (xyz) contains NaN/inf.
  2. Voxel-dedupe at the xyz resolution (default 1mm). Hash-based, order-preserving — first occurrence of each voxel wins.
  3. Quantize FLOAT64 fields without an explicit resolution to 1µs (typically per-point timestamps stored as seconds-since-epoch).

The wire format produced is plain V4 — any standard mcap / ROS2 consumer reads it without changes. The flag is for compression workflows where the original NaN/duplicate/sub-µs-precision data isn't needed by downstream visualizers. Empirical results on real LIDAR bags: ratio drops from ~30-40% (V4 lossless) to ~13-30% codec-only, ~15-17% after stage-2 ZSTD.

Implementation: cloudini_ros::applyVizLossyPreprocessing in cloudini_lib/src/ros_msg_utils.cpp. Public API in ros_msg_utils.hpp.

Codec benchmark:

# Per-topic ratio + encode/decode speed for V4 vs V4+viz
./build_release/tools/mcap_codec_benchmark DATA/my_bag.mcap --max-messages 100

# Add --zstd for after-ZSTD-3 sizes (production-equivalent)
./build_release/tools/mcap_codec_benchmark DATA/my_bag.mcap --max-messages 100 --zstd

# --explain prints field schema + viz-preprocessing effect for the first
# message of each topic (NaN count, dedup count, FLOAT64 fields quantized)
./build_release/tools/mcap_codec_benchmark DATA/my_bag.mcap --explain

V5 naming note: A previous research branch (feat/lossy-v2-bitpacked-default, git tag v5-reference-2026-05) used "V5" for a GPU-decodable bit-packed format. That format is archived and should not be confused with the current V5 wire version on this branch. Current V5 keeps the V4 float paths and adds adaptive integer sections per chunk: integer fields can choose V4 delta-varint, palette indexes, raw-value RLE, or Delta-RLE for repeated increments. Use mcap_codec_benchmark to compare V4/V5 and V4-viz/V5-viz.

Debugging with ROS2 CLI

# List available topics
ros2 topic list

# Check topic type
ros2 topic info /points/cloudini

# Monitor message rate
ros2 topic hz /points/cloudini

# Echo messages (won't work for compressed - binary data)
ros2 topic echo /points

Key Dependencies

  • Required: CMake 3.16+, C++20 compiler
  • Auto-downloaded via CPM: LZ4, ZSTD, cxxopts, benchmark, googletest
  • Optional: PCL (for pointcloud utilities), ROS 2 (for integration)
  • Web: Node.js 18+, Vite
  • ROS2 Packages:
    • rclcpp - Core ROS2 C++ API
    • sensor_msgs - Standard PointCloud2 messages
    • point_cloud_interfaces - CompressedPointCloud2 message definition
    • point_cloud_transport - Transport framework for point cloud plugins
    • pcl_conversions - PCL/ROS message conversion utilities (apt: ros-${ROS_DISTRO}-pcl-conversions)
    • pluginlib - Plugin loading framework

API Usage Patterns

Using CloudiniSubscriberPCL (Most Efficient)

#include <cloudini_ros/cloudini_subscriber_pcl.hpp>

auto node = std::make_shared<rclcpp::Node>("my_node");

// Create subscriber with callback
auto subscriber = std::make_shared<cloudini_ros::CloudiniSubscriberPCL>(
    node,  // Can also use node->shared_from_this() if inside node class
    "/points/cloudini",  // Topic name
    [](const pcl::PCLPointCloud2::Ptr& cloud) {
        // Process PCL cloud - object is from pool, will be returned when lambda exits
        std::cout << "Received cloud with " << cloud->width * cloud->height << " points\n";
    },
    rclcpp::QoS(10)  // Optional QoS settings
);

rclcpp::spin(node);

IMPORTANT: Do NOT call shared_from_this() in node constructors - use raw this pointer instead, or call from init() method after node construction.

Using Point Cloud Transport

#include <point_cloud_transport/point_cloud_transport.hpp>

// Publisher side
auto pct = std::make_shared<point_cloud_transport::PointCloudTransport>(node);
auto pub = pct->advertise("/points", 10);
// Automatically creates /points/cloudini compressed topic

pub.publish(cloud_msg);

// Subscriber side with transport hint
auto transport_hint = std::make_shared<point_cloud_transport::TransportHints>("cloudini");
auto sub = pct->subscribe("/points", 10, callback, {}, transport_hint.get());

Raw DDS Message Access Pattern

#include <rclcpp/generic_subscription.hpp>
#include <cloudini_lib/ros_msg_utils.hpp>

// Generic subscription for zero-copy access
auto callback = [](std::shared_ptr<rclcpp::SerializedMessage> msg) {
    const auto& input_msg = msg->get_rcl_serialized_message();
    const Cloudini::ConstBufferView raw_dds_msg(input_msg.buffer, input_msg.buffer_length);

    // Parse without full deserialization
    auto pc_info = cloudini_ros::getDeserializedPointCloudMessage(raw_dds_msg);

    // Process compressed data directly
    // pc_info.data contains the cloudini-compressed payload
};

auto subscription = node->create_generic_subscription(
    topic, "point_cloud_interfaces/msg/CompressedPointCloud2",
    rclcpp::QoS(10), callback
);

Resolution Configuration

#include <cloudini_lib/ros_msg_utils.hpp>

// Create encoding info from PointCloud2
auto pc_info = cloudini_ros::getDeserializedPointCloudMessage(raw_msg);
auto encoding_info = cloudini_ros::toEncodingInfo(pc_info);

// Apply resolution profile (quantization for lossy compression)
cloudini_ros::applyResolutionProfile(
    cloudini_ros::ResolutionProfile{},  // Default profile
    encoding_info.fields,
    0.001  // 1mm resolution
);

// Convert with custom encoding
std::vector<uint8_t> output;
cloudini_ros::convertPointCloud2ToCompressedCloud(pc_info, encoding_info, output);

Direct Compression (Without Topic Converter)

#include <cloudini_ros/conversion_utils.hpp>
#include <rclcpp/rclcpp.hpp>

// Setup: create a generic publisher for CompressedPointCloud2
auto publisher = node->create_generic_publisher(
    "/points/compressed",
    "point_cloud_interfaces/msg/CompressedPointCloud2",
    rclcpp::QoS(10));

// Compress a PointCloud2 message
std::vector<uint8_t> buffer;
Cloudini::SerializeCompressedPointCloud2(pcd_msg, 0.001, buffer);

// Publish using zero-copy pointer swap
rclcpp::SerializedMessage ser_msg;
ser_msg.get_rcl_serialized_message().buffer = buffer.data();
ser_msg.get_rcl_serialized_message().buffer_length = buffer.size();
publisher->publish(ser_msg);
// NOTE: null out ser_msg buffer before destruction to prevent double-free
ser_msg.get_rcl_serialized_message().buffer = nullptr;
ser_msg.get_rcl_serialized_message().buffer_length = 0;

Performance Optimization Patterns

Object Pool Pattern (CloudiniSubscriberPCL)

CloudiniSubscriberPCL implements an object pool to avoid repeated PCL cloud allocations:

pcl::PCLPointCloud2::Ptr CloudiniSubscriberPCL::acquireCloudFromPool() {
    pcl::PCLPointCloud2* raw_ptr = nullptr;

    {
        std::lock_guard<std::mutex> lock(pool_mutex_);
        if (!cloud_pool_.empty()) {
            raw_ptr = cloud_pool_.back();
            cloud_pool_.pop_back();
        } else {
            raw_ptr = new pcl::PCLPointCloud2();
        }
    }

    // Custom deleter returns to pool
    return pcl::PCLPointCloud2::Ptr(raw_ptr, [this](pcl::PCLPointCloud2* ptr) {
        std::lock_guard<std::mutex> lock(pool_mutex_);
        if (cloud_pool_.size() < MAX_POOL_SIZE) {
            ptr->data.clear();  // Clear but keep capacity
            ptr->fields.clear();
            cloud_pool_.push_back(ptr);
        } else {
            delete ptr;
        }
    });
}

Benefits: ~75% reduction in allocations during steady-state operation.

Skip Processing When No Subscribers

void callback(std::shared_ptr<rclcpp::SerializedMessage> msg) {
    // Skip expensive processing if no one is listening
    if (publisher_->get_subscription_count() == 0) {
        return;
    }

    // Proceed with compression/decompression
    // ...
}

Zero-Copy via Raw DDS Access

Instead of deserializing to sensor_msgs::PointCloud2, parse the raw DDS buffer:

// AVOID THIS (requires full deserialization + copy):
void callback(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
    auto pcl_cloud = std::make_shared<pcl::PCLPointCloud2>();
    pcl_conversions::toPCL(*msg, *pcl_cloud);
    // ...
}

// PREFER THIS (zero-copy):
void callback(std::shared_ptr<rclcpp::SerializedMessage> msg) {
    const auto& raw = msg->get_rcl_serialized_message();
    const Cloudini::ConstBufferView view(raw.buffer, raw.buffer_length);
    auto pc_info = cloudini_ros::getDeserializedPointCloudMessage(view);
    // Access fields directly from buffer without intermediate copies
}

Development Notes

  • Uses CPM for dependency management - set CPM_SOURCE_CACHE environment variable to avoid re-downloading
  • Supports SSE4.1 optimizations on x86_64
  • Address sanitizer enabled automatically in Debug builds on GCC/Clang
  • Custom intrinsics for SIMD operations in include/cloudini_lib/intrinsics.hpp
  • Field encoders support both lossy (quantized floats) and lossless compression modes

Threading Safety Guidelines

CRITICAL: When modifying shared state accessed by multiple threads:

  1. Always acquire mutex before modifying condition variable predicates
  2. Release mutex before calling notify_one() or notify_all()
  3. Use std::lock_guard for automatic RAII-style locking

Example from PointcloudEncoder::compressionWorker():

// CORRECT:
{
    std::lock_guard<std::mutex> lock(mutex_);
    compressed_size_ += chunk_size + sizeof(uint32_t);
    compression_done_ = true;
}
cv_done_compressing_.notify_one();  // Notify after releasing lock

// WRONG - DATA RACE:
compressed_size_ += chunk_size;  // No mutex!
compression_done_ = true;         // Race condition!
cv_done_compressing_.notify_one();

ROS2 Node Lifecycle Safety

IMPORTANT: Do NOT call shared_from_this() in constructors:

// WRONG - crashes with bad_weak_ptr:
MyNode::MyNode() : Node("my_node") {
    subscriber_ = std::make_shared<CloudiniSubscriberPCL>(
        this->shared_from_this(),  // BAD - shared_ptr not yet constructed
        "/topic", callback
    );
}

// CORRECT Option 1 - Use raw pointer:
MyNode::MyNode() : Node("my_node") {
    subscriber_ = std::make_shared<CloudiniSubscriberPCL>(
        this,  // GOOD - raw pointer
        "/topic", callback
    );
}

// CORRECT Option 2 - Separate init():
MyNode::MyNode() : Node("my_node") {}

void MyNode::init() {
    subscriber_ = std::make_shared<CloudiniSubscriberPCL>(
        this->shared_from_this(),  // GOOD - called after construction
        "/topic", callback
    );
}

Common Issues and Fixes

Issue: Timeout waiting for compression to complete

Symptom: Exception "Timeout waiting for compression to complete" during encoding.

Cause: Data race or lost notification in multi-threaded compression worker.

Fix: Ensure all modifications to compression_done_ and compressed_size_ are protected by mutex. Fixed in recent commit to cloudini_lib/src/cloudini.cpp.

Issue: Point cloud transport subscriber not receiving messages

Symptom: Subscriber callback never fires despite topic being published.

Cause: Topic naming mismatch. Point cloud transport expects base topic name (e.g., /points), not the compressed variant (e.g., /points/cloudini).

Solution:

  • Publisher: Use base topic name with advertise("/points", 10)
  • Subscriber: Use base topic + transport hint subscribe("/points", 10, callback, {}, TransportHints("cloudini"))

Issue: PCL conversions header not found

Symptom: Compilation error pcl_conversions/pcl_conversions.h: No such file or directory

Solution:

  1. Install package: sudo apt install ros-${ROS_DISTRO}-pcl-conversions
  2. Add to CMakeLists.txt:
    find_package(pcl_conversions REQUIRED)
    target_link_libraries(target ${pcl_conversions_TARGETS})
  3. Export in package.xml: <depend>pcl_conversions</depend>

Issue: Generic subscription crashes with deserialization error

Symptom: Runtime crash when accessing serialized message buffer.

Cause: Incorrect topic type string or attempting to deserialize incompatible message.

Solution: Verify topic type matches exactly:

ros2 topic info /topic_name  # Check actual type

Use correct type string:

  • "sensor_msgs/msg/PointCloud2" (not sensor_msgs::msg::PointCloud2)
  • "point_cloud_interfaces/msg/CompressedPointCloud2"

Tools

  • cloudini_rosbag_converter: Convert MCAP rosbags between compressed/uncompressed pointclouds. Accepts bag directories or bare .mcap files; generates a transformed metadata.yaml when one is present
  • mcap_cutter: Extract portions of MCAP files
  • pcd_benchmark: Benchmark compression on PCD files
  • run_encoder.sh: Batch processing script for test data

File Structure Reference

cloudini_lib/

include/cloudini_lib/
├── cloudini.hpp              # Core encoder/decoder classes
├── encoding_utils.hpp        # Buffer views, encode/decode helpers
├── field_encoder.hpp         # Field-specific encoder implementations
├── field_decoder.hpp         # Field-specific decoder implementations
├── intrinsics.hpp            # SIMD intrinsics wrappers (Vector4f, Vector4i)
├── pcl_conversion.hpp        # PCL ↔ Cloudini conversion
└── ros_msg_utils.hpp         # ROS2 message parsing/conversion utilities

src/
├── cloudini.cpp              # PointcloudEncoder/Decoder implementation
├── field_encoder.cpp         # Encoder implementations (FloatN, Int, XOR)
├── field_decoder.cpp         # Decoder implementations
├── pcl_conversion.cpp        # PCL integration
└── ros_msg_utils.cpp         # ROS message utilities

tools/
├── cloudini_rosbag_converter # MCAP conversion tool
└── mcap_cutter              # MCAP slicing utility

cloudini_ros/

include/cloudini_ros/
└── cloudini_subscriber_pcl.hpp  # High-performance PCL subscriber with object pool

src/
├── topic_converter.cpp          # Standalone compression/decompression node
├── cloudini_subscriber_pcl.cpp  # PCL subscriber implementation
├── cloudini_publisher_plugin.cpp # point_cloud_transport publisher plugin
└── cloudini_subscriber_plugin.cpp # point_cloud_transport subscriber plugin

test/
├── test_cloudini_subscriber.cpp # Direct CloudiniSubscriberPCL usage example
├── test_direct_publisher.cpp    # Direct compression publisher example
├── test_plugin_publisher.cpp    # point_cloud_transport publisher example
└── test_plugin_subscriber.cpp   # point_cloud_transport subscriber example

Quick Reference

Compression Modes

  1. None (EncodingOptions::NONE + CompressionOption::NONE)

    • No encoding, no compression (for debugging)
  2. Lossless (EncodingOptions::LOSSLESS + CompressionOption::LZ4/ZSTD)

    • Delta encoding for integers
    • XOR encoding for floats
    • LZ4 or ZSTD compression
  3. Lossy (EncodingOptions::LOSSY + CompressionOption::LZ4/ZSTD)

    • Quantized floats based on resolution parameter
    • SIMD-optimized for XYZ/XYZRGB fields
    • Highest compression ratio
    • Typical resolution: 0.001 (1mm) for geometry

QoS Profile Auto-Detection

topic_converter.cpp uses adapt_request_to_offers() to auto-detect publisher QoS:

  • Reliability: Uses RELIABLE if all publishers are RELIABLE, else BEST_EFFORT
  • Durability: Uses TRANSIENT_LOCAL if all publishers are TRANSIENT_LOCAL (for latched topics), else VOLATILE

Typical Compression Ratios

Based on empirical data:

  • Lossy (1mm resolution): 5-10x compression
  • Lossless: 2-4x compression
  • Depends on: Point cloud structure, field types, data entropy

Message Flow Diagram

Standard ROS2 Workflow:
┌─────────────────┐
│ Sensor/Driver   │
│ PointCloud2     │
└────────┬────────┘
         │
         ▼
┌─────────────────┐      ┌──────────────────┐
│ point_cloud_    │─────▶│ cloudini plugin  │
│ transport       │      │ (compress)       │
└─────────────────┘      └────────┬─────────┘
                                  │
                                  ▼
                         ┌─────────────────┐
                         │ Compressed      │
                         │ PointCloud2     │
                         └────────┬────────┘
                                  │
                                  ▼
┌─────────────────┐      ┌──────────────────┐
│ CloudiniSub     │◀─────│ Generic Sub      │
│ PCL (fastest)   │      │ (zero-copy DDS)  │
└─────────────────┘      └──────────────────┘

Alternative (Manual Conversion):
┌─────────────────┐
│ Sensor          │
│ PointCloud2     │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ topic_converter │ (standalone node)
│ compressing=true│
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Compressed      │
│ PointCloud2     │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ CloudiniSub     │
│ PCL             │
└─────────────────┘

Performance Tips

  1. Use CloudiniSubscriberPCL for PCL workflows - bypasses intermediate ROS conversions
  2. Enable intra-process comms when publisher/subscriber in same process:
    rclcpp::NodeOptions options;
    options.use_intra_process_comms(true);
    auto node = std::make_shared<MyNode>(options);
  3. Set resolution based on needs:
    • Indoor robotics: 0.001 (1mm)
    • Outdoor/autonomous driving: 0.01 (1cm)
    • Architecture/mapping: 0.0001 (0.1mm)
  4. Use ZSTD for maximum compression (slower), LZ4 for real-time (faster)
  5. Monitor with ros2 topic hz and ros2 topic bw to verify compression gains

Recent Fixes (as of 2025)

  • Threading fix in PointcloudEncoder: Protected compression_done_ and compressed_size_ with mutex to prevent data races and deadlocks
  • Object pool optimization: Added to CloudiniSubscriberPCL for ~75% allocation reduction
  • Subscriber-aware processing: topic_converter now skips processing when no subscribers present
  • QoS auto-detection: topic_converter automatically adapts to publisher QoS settings
  • pcl_conversions linking: Added to CMakeLists.txt for proper PCL integration