pub use ros_pointcloud2::ros::{HeaderMsg, PointFieldMsg, TimeMsg};
pub use ros_pointcloud2::{CloudDimensions, Denseness, Endian, FieldDatatype, PointCloud2Msg};
use crate::{
CompressionOption, EncodingInfo, EncodingOptions, FieldType, PointField, PointcloudDecoder,
PointcloudEncoder,
};
#[derive(Clone, Debug)]
pub struct CompressionConfig {
pub encoding: EncodingOptions,
pub compression: CompressionOption,
pub resolution: Option<f32>,
}
impl CompressionConfig {
pub fn lossy_zstd(resolution: f32) -> Self {
Self {
encoding: EncodingOptions::Lossy,
compression: CompressionOption::Zstd,
resolution: Some(resolution),
}
}
pub fn lossy_lz4(resolution: f32) -> Self {
Self {
encoding: EncodingOptions::Lossy,
compression: CompressionOption::Lz4,
resolution: Some(resolution),
}
}
pub fn lossless_zstd() -> Self {
Self {
encoding: EncodingOptions::Lossless,
compression: CompressionOption::Zstd,
resolution: None,
}
}
pub fn lossless_lz4() -> Self {
Self {
encoding: EncodingOptions::Lossless,
compression: CompressionOption::Lz4,
resolution: None,
}
}
pub fn with_resolution(mut self, resolution: f32) -> Self {
self.resolution = Some(resolution);
self
}
fn format_str(&self) -> String {
let enc = match self.encoding {
EncodingOptions::Lossy => "lossy",
EncodingOptions::Lossless => "lossless",
EncodingOptions::None => "none",
};
let comp = match self.compression {
CompressionOption::Zstd => "zstd",
CompressionOption::Lz4 => "lz4",
CompressionOption::None => "none",
};
format!("cloudini/{enc}/{comp}")
}
}
impl Default for CompressionConfig {
fn default() -> Self {
Self::lossy_zstd(0.001)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub struct CompressedPointCloud2 {
pub header: HeaderMsg,
pub height: u32,
pub width: u32,
pub fields: Vec<PointFieldMsg>,
pub is_bigendian: bool,
pub point_step: u32,
pub row_step: u32,
pub compressed_data: Vec<u8>,
pub is_dense: bool,
pub format: String,
}
impl CompressedPointCloud2 {
pub fn compress(msg: PointCloud2Msg, config: CompressionConfig) -> crate::Result<Self> {
let format = config.format_str();
let info = encoding_info_from_msg(&msg, &config);
let encoder = PointcloudEncoder::new(info);
let compressed_data = encoder.encode(&msg.data)?;
let is_bigendian = matches!(msg.endian, Endian::Big);
let is_dense = matches!(msg.dense, Denseness::Dense);
Ok(Self {
header: msg.header,
height: msg.dimensions.height,
width: msg.dimensions.width,
fields: msg.fields,
is_bigendian,
point_step: msg.point_step,
row_step: msg.row_step,
compressed_data,
is_dense,
format,
})
}
pub fn decompress(&self) -> crate::Result<PointCloud2Msg> {
let decoder = PointcloudDecoder::new();
let (info, raw_data) = decoder.decode(&self.compressed_data)?;
let fields = info
.fields
.iter()
.map(|f| PointFieldMsg {
name: f.name.clone().into(),
offset: f.offset,
datatype: field_type_to_datatype(f.field_type),
count: 1,
})
.collect();
Ok(PointCloud2Msg {
header: self.header.clone(),
dimensions: CloudDimensions {
width: info.width,
height: info.height,
},
fields,
endian: if self.is_bigendian {
Endian::Big
} else {
Endian::Little
},
point_step: info.point_step,
row_step: info.point_step * info.width,
data: raw_data,
dense: if self.is_dense {
Denseness::Dense
} else {
Denseness::Sparse
},
})
}
}
pub trait CompressExt {
fn compress(self, config: CompressionConfig) -> crate::Result<CompressedPointCloud2>;
}
impl CompressExt for PointCloud2Msg {
fn compress(self, config: CompressionConfig) -> crate::Result<CompressedPointCloud2> {
CompressedPointCloud2::compress(self, config)
}
}
fn encoding_info_from_msg(msg: &PointCloud2Msg, config: &CompressionConfig) -> EncodingInfo {
let fields = msg
.fields
.iter()
.map(|f| {
let field_type = datatype_to_field_type(f.datatype);
let resolution = match field_type {
FieldType::Float32 | FieldType::Float64 => config.resolution,
_ => None,
};
PointField {
name: f.name.as_str().to_owned(),
offset: f.offset,
field_type,
resolution,
}
})
.collect();
EncodingInfo {
fields,
width: msg.dimensions.width,
height: msg.dimensions.height,
point_step: msg.point_step,
encoding_opt: config.encoding,
compression_opt: config.compression,
..EncodingInfo::default()
}
}
fn datatype_to_field_type(datatype: u8) -> FieldType {
match datatype {
1 => FieldType::Int8,
2 => FieldType::Uint8,
3 => FieldType::Int16,
4 => FieldType::Uint16,
5 => FieldType::Int32,
6 => FieldType::Uint32,
7 => FieldType::Float32, 8 => FieldType::Float64,
_ => FieldType::Unknown,
}
}
fn field_type_to_datatype(ft: FieldType) -> u8 {
match ft {
FieldType::Int8 => 1,
FieldType::Uint8 => 2,
FieldType::Int16 => 3,
FieldType::Uint16 => 4,
FieldType::Int32 => 5,
FieldType::Uint32 => 6,
FieldType::Float32 => 7,
FieldType::Float64 => 8,
FieldType::Int64 | FieldType::Uint64 | FieldType::Unknown => 0,
}
}