#[cfg(feature = "ros")]
mod ros_tests {
use cloudini::ros::{CompressExt, CompressedPointCloud2, CompressionConfig};
use ros_pointcloud2::{PointCloud2Msg, points::PointXYZI};
fn make_cloud(n: usize, _resolution: f32) -> PointCloud2Msg {
let points: Vec<PointXYZI> = (0..n)
.map(|i| {
let t = i as f32 * 0.01;
PointXYZI::new(t.sin() * 5.0, t.cos() * 5.0, t * 0.1, (i % 256) as f32)
})
.collect();
PointCloud2Msg::try_from_iter(&points).unwrap()
}
fn approx_eq_f32(a: f32, b: f32, tol: f32) -> bool {
(a - b).abs() <= tol
}
fn check_roundtrip(original: &PointCloud2Msg, restored: &PointCloud2Msg, tol: f32) {
assert_eq!(original.dimensions.width, restored.dimensions.width);
assert_eq!(original.dimensions.height, restored.dimensions.height);
assert_eq!(original.point_step, restored.point_step);
assert_eq!(original.data.len(), restored.data.len());
let pts_orig: Vec<PointXYZI> = original.clone().try_into_iter().unwrap().collect();
let pts_rest: Vec<PointXYZI> = restored.clone().try_into_iter().unwrap().collect();
for (a, b) in pts_orig.iter().zip(pts_rest.iter()) {
assert!(
approx_eq_f32(a.x, b.x, tol),
"x mismatch: {} vs {}",
a.x,
b.x
);
assert!(
approx_eq_f32(a.y, b.y, tol),
"y mismatch: {} vs {}",
a.y,
b.y
);
assert!(
approx_eq_f32(a.z, b.z, tol),
"z mismatch: {} vs {}",
a.z,
b.z
);
assert!(
approx_eq_f32(a.intensity, b.intensity, tol),
"intensity mismatch: {} vs {}",
a.intensity,
b.intensity
);
}
}
#[test]
fn compress_ext_lossy_zstd_roundtrip() {
let resolution = 0.001_f32;
let cloud = make_cloud(500, resolution);
let compressed = cloud
.clone()
.compress(CompressionConfig::lossy_zstd(resolution))
.unwrap();
assert_eq!(compressed.format, "cloudini/lossy/zstd");
let restored = compressed.decompress().unwrap();
check_roundtrip(&cloud, &restored, resolution * 0.5 + 1e-5);
}
#[test]
fn compress_ext_lossy_lz4_roundtrip() {
let resolution = 0.01_f32;
let cloud = make_cloud(500, resolution);
let compressed = cloud
.clone()
.compress(CompressionConfig::lossy_lz4(resolution))
.unwrap();
assert_eq!(compressed.format, "cloudini/lossy/lz4");
let restored = compressed.decompress().unwrap();
check_roundtrip(&cloud, &restored, resolution * 0.5 + 1e-5);
}
#[test]
fn compressed_pointcloud2_lossless_zstd() {
let cloud = make_cloud(500, 0.001);
let compressed =
CompressedPointCloud2::compress(cloud.clone(), CompressionConfig::lossless_zstd())
.unwrap();
assert_eq!(compressed.format, "cloudini/lossless/zstd");
let restored = compressed.decompress().unwrap();
assert_eq!(cloud.data, restored.data);
}
#[test]
fn compressed_pointcloud2_lossless_lz4() {
let cloud = make_cloud(200, 0.001);
let compressed =
CompressedPointCloud2::compress(cloud.clone(), CompressionConfig::lossless_lz4())
.unwrap();
assert_eq!(compressed.format, "cloudini/lossless/lz4");
let restored = compressed.decompress().unwrap();
assert_eq!(cloud.data, restored.data);
}
#[test]
fn metadata_preserved_through_roundtrip() {
let cloud = make_cloud(100, 0.001);
let compressed = cloud
.clone()
.compress(CompressionConfig::default())
.unwrap();
assert_eq!(compressed.height, cloud.dimensions.height);
assert_eq!(compressed.width, cloud.dimensions.width);
assert!(!compressed.is_bigendian);
assert!(compressed.is_dense);
let restored = compressed.decompress().unwrap();
assert_eq!(restored.dimensions.width, cloud.dimensions.width);
assert_eq!(restored.dimensions.height, cloud.dimensions.height);
assert_eq!(restored.point_step, cloud.point_step);
}
#[test]
fn large_cloud_multi_chunk_roundtrip() {
let resolution = 0.001_f32;
let cloud = make_cloud(40_000, resolution);
let compressed = cloud
.clone()
.compress(CompressionConfig::lossy_zstd(resolution))
.unwrap();
let restored = compressed.decompress().unwrap();
check_roundtrip(&cloud, &restored, resolution * 0.5 + 1e-5);
}
#[cfg(feature = "serde")]
mod serde_tests {
use super::*;
#[test]
fn serde_json_roundtrip() {
let cloud = make_cloud(100, 0.001);
let compressed = cloud
.compress(CompressionConfig::lossy_zstd(0.001))
.unwrap();
let json = serde_json::to_string(&compressed).unwrap();
let restored: CompressedPointCloud2 = serde_json::from_str(&json).unwrap();
assert_eq!(compressed.width, restored.width);
assert_eq!(compressed.height, restored.height);
assert_eq!(compressed.compressed_data, restored.compressed_data);
assert_eq!(compressed.format, restored.format);
assert_eq!(compressed.point_step, restored.point_step);
}
#[test]
fn serde_bincode_roundtrip() {
let cloud = make_cloud(100, 0.001);
let compressed = cloud.compress(CompressionConfig::lossless_zstd()).unwrap();
let bytes = bincode::serialize(&compressed).unwrap();
let restored: CompressedPointCloud2 = bincode::deserialize(&bytes).unwrap();
assert_eq!(compressed.compressed_data, restored.compressed_data);
assert_eq!(compressed.format, restored.format);
}
}
#[cfg(feature = "rkyv")]
mod rkyv_tests {
use super::*;
use rkyv::rancor::Error;
#[test]
fn rkyv_zero_copy_access() {
let cloud = make_cloud(100, 0.001);
let compressed = cloud
.compress(CompressionConfig::lossy_zstd(0.001))
.unwrap();
let bytes = rkyv::to_bytes::<Error>(&compressed).unwrap();
let archived =
rkyv::access::<rkyv::Archived<CompressedPointCloud2>, Error>(&bytes).unwrap();
assert_eq!(u32::from(archived.width), compressed.width);
assert_eq!(u32::from(archived.height), compressed.height);
assert_eq!(archived.format.as_str(), compressed.format.as_str());
assert_eq!(
archived.compressed_data.as_slice(),
compressed.compressed_data.as_slice()
);
let decoder = cloudini::PointcloudDecoder::new();
let (_info, _raw) = decoder.decode(archived.compressed_data.as_slice()).unwrap();
}
}
}