# cloudini
The [cloudini](https://github.com/facontidavide/cloudini) point cloud compression library as a Rust crate.
The crate provides Rust bindings of the original C++ library by default using the feature flag `use_cpp`. It requires CMake and a C++ 20 compiler to be installed. There is a Rust-only rewrite available by disabling the feature aforementioned feature (`no-default-features = true`).
## Quickstart
Using the `ros_pointcloud2` integration via the standard feature `ros`:
```toml
[dependencies]
cloudini = "0.2.0"
ros_pointcloud2 = "1.0.0-rc.3"
```
```rust
use cloudini::ros::{CompressExt, CompressionConfig, PointCloud2Msg};
use ros_pointcloud2::points::PointXYZI;
let resolution = 0.001_f32; // 1 mm
// Simulate a 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
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);
}
```
## Raw
You can disable the `ros_pointcloud2` convenience wrapper by setting `no-default-features = true` and use the underlying enoder and decoder.
### Encode
```rust
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()
};
let encoder = PointcloudEncoder::new(info);
let compressed: Vec<u8> = encoder.encode(&raw_cloud)?;
// compressed is a self-contained buffer: header + chunk data
```
### Decode
```rust
use cloudini::PointcloudDecoder;
let decoder = PointcloudDecoder::new();
let (info, decoded): (_, Vec<u8>) = decoder.decode(&compressed)?;
// decoded.len() == info.width * info.height * info.point_step
```
### License
The original Cloudini library is bundled and licensed under the Apache 2 License. See `./cloudini-cpp/LICENSE`.
The Rust code follows the typical dual-license model:
<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>
<br>
<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>