cloudini 0.1.0

A point cloud compression library implementing the Cloudini format.
Documentation

cloudini

A Rust implementation of the Cloudini point cloud compression format.

Cloudini uses a two-stage pipeline:

  1. Field encoding — type-aware per-field compression (delta + varint for integers, quantisation for floats, XOR for lossless floats).
  2. Block compression — the encoded stream is compressed with LZ4 or ZSTD.

Typical results on Lidar geometry (XYZ @ 1 mm + intensity):

Mode Ratio
Lossy + ZSTD ~34%
Lossy + LZ4 ~40%

Quickstart

[dependencies]
cloudini = "0.1.0"
use cloudini::ros::{CompressExt, CompressionConfig, PointCloud2Msg};
use ros_pointcloud2::points::PointXYZI;

let resolution = 0.001_f32; // 1 mm

// Build a PointCloud2Msg from a vec of typed points (simulated Lidar scan)
let points: Vec<PointXYZI> = (0..200)
    .map(|i| {
        let t = i as f32 * 0.05;
        PointXYZI::new(t.sin() * 5.0, t.cos() * 5.0, t * 0.1, i as f32)
    })
    .collect();
let cloud = PointCloud2Msg::try_from_vec(points.clone())?;
let raw_size = cloud.data.len();

// Compress — 1 mm lossy + ZSTD
let compressed = cloud.compress(CompressionConfig::lossy_zstd(resolution))?;
// compressed is a CompressedPointCloud2 — ship it over DDS, store to disk, etc.
assert!(compressed.compressed_data.len() < raw_size);

// Decompress — no metadata needed, it is embedded in the buffer
let restored = compressed.decompress()?;

// Each float component is within resolution / 2 of the original
let tol = resolution / 2.0 + 1e-6;
let restored_points: Vec<PointXYZI> = restored.try_into_iter()?.collect();
for (orig, rest) in points.iter().zip(restored_points.iter()) {
    assert!((orig.x         - rest.x        ).abs() <= tol);
    assert!((orig.y         - rest.y        ).abs() <= tol);
    assert!((orig.z         - rest.z        ).abs() <= tol);
    assert!((orig.intensity - rest.intensity).abs() <= tol);
}

Compression presets

Constructor Encoding Compression Max float error
CompressionConfig::lossy_zstd(res) Lossy ZSTD res / 2
CompressionConfig::lossy_lz4(res) Lossy LZ4 res / 2
CompressionConfig::lossless_zstd() Lossless (XOR) ZSTD 0
CompressionConfig::lossless_lz4() Lossless (XOR) LZ4 0

CompressionConfig::default() is lossy_zstd(0.001) — suitable for most Lidar use cases.

Raw

You can disable the ros_pointcloud2 convenience wrapper by setting no-default-features = true and use the underlying enoder and decoder.

Encode

use cloudini::{
    CompressionOption, EncodingInfo, EncodingOptions, FieldType, PointField,
    PointcloudEncoder,
};

// Describe the point layout.
// point_step = sum of all field sizes = 4+4+4+1 = 13 bytes
let info = EncodingInfo {
    fields: vec![
        PointField { name: "x".into(), offset: 0,  field_type: FieldType::Float32, resolution: Some(0.001) },
        PointField { name: "y".into(), offset: 4,  field_type: FieldType::Float32, resolution: Some(0.001) },
        PointField { name: "z".into(), offset: 8,  field_type: FieldType::Float32, resolution: Some(0.001) },
        PointField { name: "intensity".into(), offset: 12, field_type: FieldType::Uint8, resolution: None },
    ],
    width: 1000,        // number of points
    height: 1,          // 1 for unorganised clouds
    point_step: 13,
    encoding_opt: EncodingOptions::Lossy,
    compression_opt: CompressionOption::Zstd,
    ..EncodingInfo::default()
};

// raw_cloud: width * height * point_step bytes of packed point data
let encoder = PointcloudEncoder::new(info);
let compressed: Vec<u8> = encoder.encode(&raw_cloud)?;
// compressed is a self-contained buffer: header + chunk data

Decode

use cloudini::PointcloudDecoder;

let decoder = PointcloudDecoder::new();
let (info, decoded): (_, Vec<u8>) = decoder.decode(&compressed)?;

// decoded.len() == info.width * info.height * info.point_step
// For lossy float fields the max error per component is resolution / 2

Encoding modes

EncodingOptions Float32/64 with resolution Float64 without resolution Integers
None raw copy raw copy raw copy
Lossy quantise → delta → varint raw copy delta → varint
Lossless raw copy (f32) / XOR (f64) XOR with prev bits delta → varint

Lossy float error: max_error = resolution / 2 e.g. resolution: Some(0.001) → at most 0.5 mm error per component.


Compression backends

CompressionOption Notes
None field encoding only, no block compression
Lz4 fast (good for real-time), moderate ratio
Zstd higher ratio, higher CPU cost

Field types

FieldType Size Notes
Int8 / Uint8 1 byte always copied verbatim
Int16 / Uint16 2 bytes delta + varint
Int32 / Uint32 4 bytes delta + varint
Int64 / Uint64 8 bytes delta + varint
Float32 4 bytes lossy (with resolution) or copy
Float64 8 bytes lossy (with resolution) or XOR lossless

Format compatibility

Encoded buffers start with CLOUDINI_V03\n followed by a null-terminated YAML header and then the compressed chunks. The format is compatible with the original C++ Cloudini library.