use cloudini::{
CompressionOption, EncodingInfo, EncodingOptions, FieldType, PointField, PointcloudDecoder,
PointcloudEncoder,
};
fn make_xyz_intensity_info(
n_points: u32,
encoding: EncodingOptions,
compression: CompressionOption,
) -> EncodingInfo {
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: n_points,
height: 1,
point_step: 13,
encoding_opt: encoding,
compression_opt: compression,
..EncodingInfo::default()
}
}
fn gen_lidar_cloud(n: usize) -> Vec<u8> {
let mut data = vec![0u8; n * 13];
for i in 0..n {
let t = i as f32 * 0.005;
let x = t.sin() * 20.0 + (t * 0.3).cos() * 5.0;
let y = t.cos() * 15.0 + (t * 0.7).sin() * 3.0;
let z = (t * 0.2).sin() * 8.0 + 1.0;
let intensity = (i % 256) as u8;
let base = i * 13;
data[base..base + 4].copy_from_slice(&x.to_le_bytes());
data[base + 4..base + 8].copy_from_slice(&y.to_le_bytes());
data[base + 8..base + 12].copy_from_slice(&z.to_le_bytes());
data[base + 12] = intensity;
}
data
}
fn decode_xyz(data: &[u8], i: usize) -> (f32, f32, f32, u8) {
let base = i * 13;
let x = f32::from_le_bytes(data[base..base + 4].try_into().unwrap());
let y = f32::from_le_bytes(data[base + 4..base + 8].try_into().unwrap());
let z = f32::from_le_bytes(data[base + 8..base + 12].try_into().unwrap());
let intensity = data[base + 12];
(x, y, z, intensity)
}
fn assert_lossy_roundtrip(original: &[u8], decoded: &[u8], n: usize, resolution: f32) {
assert_eq!(
decoded.len(),
original.len(),
"Decoded length mismatch: {} vs {}",
decoded.len(),
original.len()
);
let tol = resolution * 0.5 + 1e-5;
for i in 0..n {
let (ox, oy, oz, oi) = decode_xyz(original, i);
let (dx, dy, dz, di) = decode_xyz(decoded, i);
if ox.is_nan() {
assert!(dx.is_nan(), "point {}: x: expected NaN, got {}", i, dx);
} else {
assert!(
(ox - dx).abs() <= tol,
"point {}: x: |{} - {}| = {} > tol {}",
i,
ox,
dx,
(ox - dx).abs(),
tol
);
}
if oy.is_nan() {
assert!(dy.is_nan(), "point {}: y: expected NaN, got {}", i, dy);
} else {
assert!(
(oy - dy).abs() <= tol,
"point {}: y: |{} - {}| = {} > tol {}",
i,
oy,
dy,
(oy - dy).abs(),
tol
);
}
if oz.is_nan() {
assert!(dz.is_nan(), "point {}: z: expected NaN, got {}", i, dz);
} else {
assert!(
(oz - dz).abs() <= tol,
"point {}: z: |{} - {}| = {} > tol {}",
i,
oz,
dz,
(oz - dz).abs(),
tol
);
}
assert_eq!(oi, di, "point {}: intensity mismatch", i);
}
}
#[test]
fn test_lossy_lz4_roundtrip() {
let n = 1000;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Lz4);
let enc = PointcloudEncoder::new(info.clone());
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_lossy_roundtrip(&cloud, &decoded, n, 0.001);
println!(
"[lz4-lossy] {} → {} bytes ({:.1}%)",
cloud.len(),
compressed.len(),
100.0 * compressed.len() as f64 / cloud.len() as f64
);
}
#[test]
fn test_lossy_zstd_roundtrip() {
let n = 1000;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Zstd);
let enc = PointcloudEncoder::new(info.clone());
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_lossy_roundtrip(&cloud, &decoded, n, 0.001);
println!(
"[zstd-lossy] {} → {} bytes ({:.1}%)",
cloud.len(),
compressed.len(),
100.0 * compressed.len() as f64 / cloud.len() as f64
);
}
#[test]
fn test_lossy_none_compression_roundtrip() {
let n = 500;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::None);
let enc = PointcloudEncoder::new(info.clone());
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_lossy_roundtrip(&cloud, &decoded, n, 0.001);
}
#[test]
fn test_none_encoding_roundtrip() {
let n = 100;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::None, CompressionOption::Lz4);
let enc = PointcloudEncoder::new(info.clone());
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_eq!(
decoded, cloud,
"NONE encoding should preserve bytes exactly"
);
}
#[test]
fn test_none_encoding_none_compression_roundtrip() {
let n = 100;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::None, CompressionOption::None);
let enc = PointcloudEncoder::new(info.clone());
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_eq!(decoded, cloud);
}
#[test]
fn test_compression_ratio_lossy_zstd() {
let n = 10000;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Zstd);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let ratio = compressed.len() as f64 / cloud.len() as f64;
println!("[ratio] lossy+zstd: {:.3}", ratio);
assert!(
ratio < 0.7,
"Expected compression ratio < 0.7, got {:.3}",
ratio
);
}
#[test]
fn test_compression_ratio_none_compression() {
let n = 10000;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::None);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let ratio = compressed.len() as f64 / cloud.len() as f64;
println!("[ratio] lossy+none: {:.3}", ratio);
assert!(ratio < 5.0, "Unexpected expansion ratio: {:.3}", ratio);
}
#[test]
fn test_multi_chunk() {
let n = 100_000;
let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Lz4);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_lossy_roundtrip(&cloud, &decoded, n, 0.001);
}
#[test]
fn test_single_point() {
let cloud = gen_lidar_cloud(1);
let info = make_xyz_intensity_info(1, EncodingOptions::Lossy, CompressionOption::Zstd);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_lossy_roundtrip(&cloud, &decoded, 1, 0.001);
}
#[test]
fn test_nan_values() {
let n = 10;
let mut cloud = gen_lidar_cloud(n);
cloud[0..4].copy_from_slice(&f32::NAN.to_le_bytes());
cloud[13 * 3 + 4..13 * 3 + 8].copy_from_slice(&f32::NAN.to_le_bytes());
cloud[13 * 7 + 8..13 * 7 + 12].copy_from_slice(&f32::NAN.to_le_bytes());
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Zstd);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
let x0 = f32::from_le_bytes(decoded[0..4].try_into().unwrap());
assert!(x0.is_nan(), "x[0] should be NaN");
let y3 = f32::from_le_bytes(decoded[13 * 3 + 4..13 * 3 + 8].try_into().unwrap());
assert!(y3.is_nan(), "y[3] should be NaN");
let z7 = f32::from_le_bytes(decoded[13 * 7 + 8..13 * 7 + 12].try_into().unwrap());
assert!(z7.is_nan(), "z[7] should be NaN");
}
#[test]
fn test_integer_field_types() {
let n = 500u32;
let point_step = 4 + 2 + 4 + 8; let info = EncodingInfo {
fields: vec![
PointField {
name: "a".into(),
offset: 0,
field_type: FieldType::Uint32,
resolution: None,
},
PointField {
name: "b".into(),
offset: 4,
field_type: FieldType::Uint16,
resolution: None,
},
PointField {
name: "c".into(),
offset: 6,
field_type: FieldType::Int32,
resolution: None,
},
PointField {
name: "d".into(),
offset: 10,
field_type: FieldType::Int64,
resolution: None,
},
],
width: n,
height: 1,
point_step,
encoding_opt: EncodingOptions::Lossy,
compression_opt: CompressionOption::Zstd,
..EncodingInfo::default()
};
let mut cloud = vec![0u8; n as usize * point_step as usize];
for i in 0..n as usize {
let base = i * point_step as usize;
let a = (i * 1000) as u32;
let b = (i % 65536) as u16;
let c = -(i as i32) * 100;
let d = (i as i64) * 1_000_000;
cloud[base..base + 4].copy_from_slice(&a.to_le_bytes());
cloud[base + 4..base + 6].copy_from_slice(&b.to_le_bytes());
cloud[base + 6..base + 10].copy_from_slice(&c.to_le_bytes());
cloud[base + 10..base + 18].copy_from_slice(&d.to_le_bytes());
}
let enc = PointcloudEncoder::new(info.clone());
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_eq!(decoded, cloud, "Integer fields must round-trip exactly");
}
#[test]
fn test_float64_xor_lossless() {
let n = 200u32;
let point_step = 8u32;
let info = EncodingInfo {
fields: vec![PointField {
name: "value".into(),
offset: 0,
field_type: FieldType::Float64,
resolution: None,
}],
width: n,
height: 1,
point_step,
encoding_opt: EncodingOptions::Lossless,
compression_opt: CompressionOption::Lz4,
..EncodingInfo::default()
};
let mut cloud = vec![0u8; n as usize * 8];
for i in 0..n as usize {
let v = (i as f64) * 0.123456789 + 1000.0;
cloud[i * 8..(i + 1) * 8].copy_from_slice(&v.to_le_bytes());
}
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_eq!(decoded, cloud, "Float64 XOR should be lossless");
}
#[test]
fn test_float64_lossy() {
let n = 200u32;
let point_step = 8u32;
let resolution = 0.001f32;
let info = EncodingInfo {
fields: vec![PointField {
name: "value".into(),
offset: 0,
field_type: FieldType::Float64,
resolution: Some(resolution),
}],
width: n,
height: 1,
point_step,
encoding_opt: EncodingOptions::Lossy,
compression_opt: CompressionOption::Zstd,
..EncodingInfo::default()
};
let mut cloud = vec![0u8; n as usize * 8];
for i in 0..n as usize {
let v = (i as f64) * 0.1 - 10.0;
cloud[i * 8..(i + 1) * 8].copy_from_slice(&v.to_le_bytes());
}
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
let tol = resolution as f64 * 0.5 + 1e-9;
for i in 0..n as usize {
let orig = f64::from_le_bytes(cloud[i * 8..(i + 1) * 8].try_into().unwrap());
let dec_v = f64::from_le_bytes(decoded[i * 8..(i + 1) * 8].try_into().unwrap());
assert!(
(orig - dec_v).abs() <= tol,
"point {}: |{} - {}| = {} > tol {}",
i,
orig,
dec_v,
(orig - dec_v).abs(),
tol
);
}
}
#[test]
fn test_4_float32_fields_floatn_path() {
let n = 1000u32;
let point_step = 16u32; 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: "w".into(),
offset: 12,
field_type: FieldType::Float32,
resolution: Some(0.001),
},
],
width: n,
height: 1,
point_step,
encoding_opt: EncodingOptions::Lossy,
compression_opt: CompressionOption::Zstd,
..EncodingInfo::default()
};
let mut cloud = vec![0u8; n as usize * 16];
for i in 0..n as usize {
for j in 0..4 {
let v = (i as f32) * 0.1 + j as f32 * 10.0;
let base = i * 16 + j * 4;
cloud[base..base + 4].copy_from_slice(&v.to_le_bytes());
}
}
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
let tol = 0.001 * 0.5 + 1e-6;
for i in 0..n as usize {
for j in 0..4 {
let base = i * 16 + j * 4;
let orig = f32::from_le_bytes(cloud[base..base + 4].try_into().unwrap());
let dec_v = f32::from_le_bytes(decoded[base..base + 4].try_into().unwrap());
assert!(
(orig - dec_v).abs() <= tol,
"point {} field {}: |{} - {}| > tol",
i,
j,
orig,
dec_v
);
}
}
}
#[test]
fn test_2_float32_no_floatn_path() {
let n = 500u32;
let point_step = 8u32;
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),
},
],
width: n,
height: 1,
point_step,
encoding_opt: EncodingOptions::Lossy,
compression_opt: CompressionOption::Zstd,
..EncodingInfo::default()
};
let mut cloud = vec![0u8; n as usize * 8];
for i in 0..n as usize {
let x = i as f32 * 0.1;
let y = i as f32 * 0.05;
cloud[i * 8..i * 8 + 4].copy_from_slice(&x.to_le_bytes());
cloud[i * 8 + 4..i * 8 + 8].copy_from_slice(&y.to_le_bytes());
}
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
let tol = 0.001 * 0.5 + 1e-6;
for i in 0..n as usize {
let ox = f32::from_le_bytes(cloud[i * 8..i * 8 + 4].try_into().unwrap());
let dx = f32::from_le_bytes(decoded[i * 8..i * 8 + 4].try_into().unwrap());
let oy = f32::from_le_bytes(cloud[i * 8 + 4..i * 8 + 8].try_into().unwrap());
let dy = f32::from_le_bytes(decoded[i * 8 + 4..i * 8 + 8].try_into().unwrap());
assert!((ox - dx).abs() <= tol, "x mismatch at point {}", i);
assert!((oy - dy).abs() <= tol, "y mismatch at point {}", i);
}
}
#[test]
fn test_header_preserved_in_output() {
let n = 10u32;
let cloud = gen_lidar_cloud(n as usize);
let info = make_xyz_intensity_info(n, EncodingOptions::Lossy, CompressionOption::None);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
assert!(
compressed.starts_with(b"CLOUDINI_V03"),
"Output should start with magic header"
);
}
#[test]
fn test_different_resolutions() {
let n = 200u32;
let point_step = 12u32;
let info = EncodingInfo {
fields: vec![
PointField {
name: "x".into(),
offset: 0,
field_type: FieldType::Float32,
resolution: Some(0.0001),
},
PointField {
name: "y".into(),
offset: 4,
field_type: FieldType::Float32,
resolution: Some(0.01),
},
PointField {
name: "z".into(),
offset: 8,
field_type: FieldType::Float32,
resolution: Some(0.1),
},
],
width: n,
height: 1,
point_step,
encoding_opt: EncodingOptions::Lossy,
compression_opt: CompressionOption::Zstd,
..EncodingInfo::default()
};
let mut cloud = vec![0u8; n as usize * 12];
for i in 0..n as usize {
let x = i as f32 * 0.001;
let y = i as f32 * 0.1;
let z = i as f32 * 1.0;
cloud[i * 12..i * 12 + 4].copy_from_slice(&x.to_le_bytes());
cloud[i * 12 + 4..i * 12 + 8].copy_from_slice(&y.to_le_bytes());
cloud[i * 12 + 8..i * 12 + 12].copy_from_slice(&z.to_le_bytes());
}
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
let resolutions = [0.0001f32, 0.01, 0.1];
for i in 0..n as usize {
for (j, &res) in resolutions.iter().enumerate() {
let base = i * 12 + j * 4;
let orig = f32::from_le_bytes(cloud[base..base + 4].try_into().unwrap());
let dec_v = f32::from_le_bytes(decoded[base..base + 4].try_into().unwrap());
let tol = res * 0.5 + 1e-6;
assert!(
(orig - dec_v).abs() <= tol,
"point {} field {}: |{} - {}| = {} > tol {}",
i,
j,
orig,
dec_v,
(orig - dec_v).abs(),
tol
);
}
}
}
#[test]
fn test_height_gt_1() {
let width = 100u32;
let height = 10u32;
let n = (width * height) as usize;
let cloud = gen_lidar_cloud(n);
let info = EncodingInfo {
width,
height,
..make_xyz_intensity_info(0, EncodingOptions::Lossy, CompressionOption::Zstd)
};
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_lossy_roundtrip(&cloud, &decoded, n, 0.001);
}
#[test]
fn test_exact_chunk_boundary() {
let n = 32768usize; let cloud = gen_lidar_cloud(n);
let info = make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Lz4);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_lossy_roundtrip(&cloud, &decoded, n, 0.001);
}
#[test]
fn test_float32_no_resolution_copy() {
let n = 100u32;
let point_step = 8u32;
let info = EncodingInfo {
fields: vec![
PointField {
name: "a".into(),
offset: 0,
field_type: FieldType::Float32,
resolution: None,
},
PointField {
name: "b".into(),
offset: 4,
field_type: FieldType::Float32,
resolution: None,
},
],
width: n,
height: 1,
point_step,
encoding_opt: EncodingOptions::Lossy,
compression_opt: CompressionOption::Lz4,
..EncodingInfo::default()
};
let mut cloud = vec![0u8; n as usize * 8];
for i in 0..n as usize {
let a = (i as f32) * 1.234_568;
let b = (i as f32) * 9.876_543;
cloud[i * 8..i * 8 + 4].copy_from_slice(&a.to_le_bytes());
cloud[i * 8 + 4..i * 8 + 8].copy_from_slice(&b.to_le_bytes());
}
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let dec = PointcloudDecoder::new();
let (_, decoded) = dec.decode(&compressed).unwrap();
assert_eq!(
decoded, cloud,
"FLOAT32 without resolution should be copied exactly"
);
}
#[test]
fn test_compression_ratio_large_cloud() {
let n = 50_000usize;
let cloud = gen_lidar_cloud(n);
let original_size = cloud.len();
for (enc_opt, comp_opt, max_ratio, label) in [
(
EncodingOptions::Lossy,
CompressionOption::Zstd,
0.70f64,
"lossy+zstd",
),
(
EncodingOptions::Lossy,
CompressionOption::Lz4,
0.85f64,
"lossy+lz4",
),
] {
let info = make_xyz_intensity_info(n as u32, enc_opt, comp_opt);
let enc = PointcloudEncoder::new(info);
let compressed = enc.encode(&cloud).unwrap();
let ratio = compressed.len() as f64 / original_size as f64;
println!(
"[ratio-{label}] {original_size} → {} bytes ({:.1}%)",
compressed.len(),
ratio * 100.0
);
assert!(
ratio < max_ratio,
"{label}: expected ratio < {max_ratio:.2}, got {ratio:.3}"
);
}
}
#[cfg(feature = "parallel")]
mod parallel_tests {
use super::*;
#[test]
fn parallel_encode_matches_sequential_lz4() {
let n = 70_000usize; let cloud = gen_lidar_cloud(n);
let info =
make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Lz4);
let st = PointcloudEncoder::new(info.clone())
.with_threads(false)
.encode(&cloud)
.unwrap();
let mt = PointcloudEncoder::new(info)
.with_threads(true)
.encode(&cloud)
.unwrap();
assert_eq!(
st, mt,
"parallel encoder output must be byte-identical to sequential"
);
}
#[test]
fn parallel_encode_matches_sequential_zstd() {
let n = 70_000usize;
let cloud = gen_lidar_cloud(n);
let info =
make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Zstd);
let st = PointcloudEncoder::new(info.clone())
.with_threads(false)
.encode(&cloud)
.unwrap();
let mt = PointcloudEncoder::new(info)
.with_threads(true)
.encode(&cloud)
.unwrap();
assert_eq!(
st, mt,
"parallel encoder output must be byte-identical to sequential"
);
}
#[test]
fn parallel_decode_matches_sequential_lz4() {
let n = 70_000usize;
let cloud = gen_lidar_cloud(n);
let info =
make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Lz4);
let compressed = PointcloudEncoder::new(info).encode(&cloud).unwrap();
let (_, st) = PointcloudDecoder::new()
.with_threads(false)
.decode(&compressed)
.unwrap();
let (_, mt) = PointcloudDecoder::new()
.with_threads(true)
.decode(&compressed)
.unwrap();
assert_eq!(
st, mt,
"parallel decoder output must be byte-identical to sequential"
);
}
#[test]
fn parallel_decode_matches_sequential_zstd() {
let n = 70_000usize;
let cloud = gen_lidar_cloud(n);
let info =
make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Zstd);
let compressed = PointcloudEncoder::new(info).encode(&cloud).unwrap();
let (_, st) = PointcloudDecoder::new()
.with_threads(false)
.decode(&compressed)
.unwrap();
let (_, mt) = PointcloudDecoder::new()
.with_threads(true)
.decode(&compressed)
.unwrap();
assert_eq!(
st, mt,
"parallel decoder output must be byte-identical to sequential"
);
}
#[test]
fn parallel_encode_decode_roundtrip() {
let n = 70_000usize;
let cloud = gen_lidar_cloud(n);
let info =
make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Lz4);
let compressed = PointcloudEncoder::new(info)
.with_threads(true)
.encode(&cloud)
.unwrap();
let (_, decoded) = PointcloudDecoder::new()
.with_threads(true)
.decode(&compressed)
.unwrap();
assert_lossy_roundtrip(&cloud, &decoded, n, 0.001);
}
#[test]
fn parallel_encode_exact_chunk_boundary() {
let n = 32_768usize;
let cloud = gen_lidar_cloud(n);
let info =
make_xyz_intensity_info(n as u32, EncodingOptions::Lossy, CompressionOption::Lz4);
let st = PointcloudEncoder::new(info.clone())
.with_threads(false)
.encode(&cloud)
.unwrap();
let mt = PointcloudEncoder::new(info)
.with_threads(true)
.encode(&cloud)
.unwrap();
assert_eq!(
st, mt,
"parallel encode at exact chunk boundary must match sequential"
);
}
}