cloudini
A Rust implementation of the Cloudini point cloud compression format.
Cloudini uses a two-stage pipeline:
- Field encoding — type-aware per-field compression (delta + varint for integers, quantisation for floats, XOR for lossless floats).
- 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
[]
= "0.1.0"
use ;
use PointXYZI;
let resolution = 0.001_f32; // 1 mm
// Build a PointCloud2Msg from a vec of typed points (simulated Lidar scan)
let points: =
.map
.collect;
let cloud = try_from_vec?;
let raw_size = cloud.data.len;
// Compress — 1 mm lossy + ZSTD
let compressed = cloud.compress?;
// compressed is a CompressedPointCloud2 — ship it over DDS, store to disk, etc.
assert!;
// 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: = restored.try_into_iter?.collect;
for in points.iter.zip
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 ;
// Describe the point layout.
// point_step = sum of all field sizes = 4+4+4+1 = 13 bytes
let info = EncodingInfo ;
// raw_cloud: width * height * point_step bytes of packed point data
let encoder = new;
let compressed: = encoder.encode?;
// compressed is a self-contained buffer: header + chunk data
Decode
use PointcloudDecoder;
let decoder = new;
let : = decoder.decode?;
// 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 / 2e.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.