#![crate_type = "lib"]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_root_url = "https://docs.rs/ros_pointcloud2/1.0.0-rc1")]
#![warn(clippy::print_stderr)]
#![warn(clippy::print_stdout)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::expect_used)]
#![warn(clippy::cargo)]
#![warn(clippy::std_instead_of_core)]
#![warn(clippy::alloc_instead_of_core)]
#![warn(clippy::std_instead_of_alloc)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(doc)]
#[doc = concat!("Custom Field Type Example (docs only).\n\n```rust\n", include_str!("../examples/custom_enum_field_filter.rs"), "\n```")]
pub mod custom_enum_field_filter {}
pub mod points;
pub mod prelude;
pub mod ros;
pub mod iterator;
#[cfg(test)]
mod tests;
use crate::ros::{HeaderMsg, PointFieldMsg};
use core::str::FromStr;
#[macro_use]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug)]
pub enum ConversionError {
InvalidFieldFormat,
UnsupportedFieldType(String),
DataLengthMismatch,
FieldsNotFound(Vec<String>),
UnsupportedFieldCount,
NumberConversion,
TypeMismatch {
stored: FieldDatatype,
requested: FieldDatatype,
},
ExhaustedSource,
UnalignedBuffer,
VecElementSizeMismatch {
element_size: usize,
expected_point_step: usize,
},
UnsupportedSliceView,
}
impl From<core::num::TryFromIntError> for ConversionError {
fn from(_: core::num::TryFromIntError) -> Self {
ConversionError::NumberConversion
}
}
impl core::fmt::Display for ConversionError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ConversionError::InvalidFieldFormat => {
write!(f, "The field does not match the expected datatype.")
}
ConversionError::UnsupportedFieldType(datatype) => {
write!(
f,
"The field datatype is not supported by the ROS message description: {datatype}"
)
}
ConversionError::DataLengthMismatch => {
write!(
f,
"The length of the byte buffer in the message does not match the expected length computed from the fields, indicating a corrupted or malformed message."
)
}
ConversionError::FieldsNotFound(fields) => {
write!(f, "Some fields are not found in the message: {fields:?}")
}
ConversionError::UnsupportedFieldCount => {
write!(
f,
"Only field_count 1 is supported for reading and writing."
)
}
ConversionError::NumberConversion => {
write!(
f,
"The number is too large to be converted into a PointCloud2 supported datatype."
)
}
ConversionError::TypeMismatch { stored, requested } => {
write!(
f,
"Stored datatype {:?} is not compatible with requested datatype {:?}.",
stored, requested
)
}
ConversionError::ExhaustedSource => {
write!(
f,
"The conversion requests more data from the source type than is available."
)
}
ConversionError::UnalignedBuffer => {
write!(
f,
"The underlying byte buffer is not properly aligned for the requested slice type."
)
}
ConversionError::VecElementSizeMismatch {
element_size,
expected_point_step,
} => {
write!(
f,
"The input Vec element size ({element_size}) does not match the expected point_step ({expected_point_step}); ownership cannot be transferred without copying."
)
}
ConversionError::UnsupportedSliceView => {
write!(
f,
"The message layout cannot be viewed as a contiguous slice of the requested point type (stride or layout mismatch)."
)
}
}
}
}
impl core::error::Error for ConversionError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
None
}
}
fn system_endian() -> Endian {
if cfg!(target_endian = "big") {
Endian::Big
} else if cfg!(target_endian = "little") {
Endian::Little
} else {
panic!("Unsupported Endian");
}
}
#[derive(Clone, Debug)]
pub struct LayoutDescription(Vec<LayoutField>);
impl LayoutDescription {
pub fn new(fields: &[LayoutField]) -> Self {
Self(fields.into())
}
}
#[derive(Clone, Debug)]
pub enum LayoutField {
Field {
name: &'static str,
ty: &'static str,
size: usize,
},
Padding {
size: usize,
},
}
impl LayoutField {
pub fn new(name: &'static str, ty: &'static str, size: usize) -> Self {
LayoutField::Field { name, ty, size }
}
pub fn padding(size: usize) -> Self {
LayoutField::Padding { size }
}
}
#[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 PointCloud2Msg {
pub header: HeaderMsg,
pub dimensions: CloudDimensions,
pub fields: Vec<PointFieldMsg>,
pub endian: Endian,
pub point_step: u32,
pub row_step: u32,
pub data: Vec<u8>,
pub dense: Denseness,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PointFieldDescription {
pub offset: u32,
pub datatype: u8,
}
pub trait PointCloud2Source {
fn point_count(&self) -> usize;
fn field(&self, name: &str) -> Option<PointFieldDescription>;
fn endian(&self) -> Endian;
fn point_step(&self) -> u32;
fn data(&self) -> &[u8];
}
impl PointCloud2Source for PointCloud2Msg {
fn point_count(&self) -> usize {
self.dimensions.len()
}
fn field(&self, name: &str) -> Option<PointFieldDescription> {
self.fields
.iter()
.find(|field| field.name == name)
.map(|field| PointFieldDescription {
offset: field.offset,
datatype: field.datatype,
})
}
fn endian(&self) -> Endian {
self.endian
}
fn point_step(&self) -> u32 {
self.point_step
}
fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Clone, Copy, Debug)]
pub struct PointCloud2View<'a> {
pub dimensions: CloudDimensions,
pub fields: &'a [PointFieldMsg],
pub endian: Endian,
pub point_step: u32,
pub data: &'a [u8],
}
impl<'a> From<&'a PointCloud2Msg> for PointCloud2View<'a> {
fn from(cloud: &'a PointCloud2Msg) -> Self {
Self {
dimensions: cloud.dimensions,
fields: &cloud.fields,
endian: cloud.endian,
point_step: cloud.point_step,
data: &cloud.data,
}
}
}
impl PointCloud2Source for PointCloud2View<'_> {
fn point_count(&self) -> usize {
self.dimensions.len()
}
fn field(&self, name: &str) -> Option<PointFieldDescription> {
self.fields
.iter()
.find(|field| field.name == name)
.map(|field| PointFieldDescription {
offset: field.offset,
datatype: field.datatype,
})
}
fn endian(&self) -> Endian {
self.endian
}
fn point_step(&self) -> u32 {
self.point_step
}
fn data(&self) -> &[u8] {
self.data
}
}
impl dyn PointCloud2Source + '_ {
pub fn try_into_iter<'a, const N: usize, C>(
&'a self,
) -> Result<impl Iterator<Item = C> + 'a, ConversionError>
where
C: PointConvertible<N> + 'a,
{
iterator::PointCloudIterator::try_from_source(self)
}
#[cfg(feature = "rayon")]
pub fn try_into_par_iter<'a, const N: usize, C>(
&'a self,
) -> Result<impl rayon::iter::ParallelIterator<Item = C> + 'a, ConversionError>
where
C: PointConvertible<N> + Send + Sync + 'a,
{
iterator::PointCloudIterator::try_from_source(self)
}
}
pub fn try_into_iter<'a, const N: usize, C>(
source: &'a dyn PointCloud2Source,
) -> Result<impl Iterator<Item = C> + 'a, ConversionError>
where
C: PointConvertible<N> + 'a,
{
iterator::PointCloudIterator::try_from_source(source)
}
#[cfg(feature = "rayon")]
pub fn try_into_par_iter<'a, const N: usize, C>(
source: &'a dyn PointCloud2Source,
) -> Result<impl rayon::iter::ParallelIterator<Item = C> + 'a, ConversionError>
where
C: PointConvertible<N> + Send + Sync + 'a,
{
iterator::PointCloudIterator::try_from_source(source)
}
#[derive(Default, Clone, Debug, PartialEq, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub enum Endian {
Big,
#[default]
Little,
}
#[derive(Default, Clone, Debug, PartialEq, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize))]
pub enum Denseness {
#[default]
Dense,
Sparse,
}
#[derive(Clone, Debug, PartialEq)]
enum ByteSimilarity {
Equal,
Overlapping,
Different,
}
#[derive(Clone, Debug)]
pub struct CloudDimensionsBuilder(usize);
impl CloudDimensionsBuilder {
#[must_use]
pub fn new_with_width(width: usize) -> Self {
Self(width)
}
pub fn build(self) -> Result<CloudDimensions, ConversionError> {
let width = match u32::try_from(self.0) {
Ok(w) => w,
Err(_) => return Err(ConversionError::NumberConversion),
};
Ok(CloudDimensions {
width,
height: u32::from(self.0 > 0),
})
}
}
#[derive(Clone, Debug, Default)]
pub struct PointCloud2MsgBuilder {
header: HeaderMsg,
width: u32,
fields: Vec<PointFieldMsg>,
endian: Endian,
point_step: u32,
row_step: u32,
data: Vec<u8>,
dense: Denseness,
}
impl PointCloud2MsgBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_header(mut self, header: HeaderMsg) -> Self {
self.header = header;
self
}
#[deprecated(since = "1.0.0", note = "use `with_header` instead")]
#[doc(hidden)]
pub fn header(self, header: HeaderMsg) -> Self {
self.with_header(header)
}
#[must_use]
pub fn with_width(mut self, width: u32) -> Self {
self.width = width;
self
}
#[deprecated(since = "1.0.0", note = "use `with_width` instead")]
#[doc(hidden)]
pub fn width(self, width: u32) -> Self {
self.with_width(width)
}
#[must_use]
pub fn with_fields(mut self, fields: Vec<PointFieldMsg>) -> Self {
self.fields = fields;
self
}
#[deprecated(since = "1.0.0", note = "use `with_fields` instead")]
#[doc(hidden)]
pub fn fields(self, fields: Vec<PointFieldMsg>) -> Self {
self.with_fields(fields)
}
#[must_use]
pub fn with_endian(mut self, endian: Endian) -> Self {
self.endian = endian;
self
}
#[deprecated(since = "1.0.0", note = "use `with_endian` instead")]
#[doc(hidden)]
pub fn endian(self, is_big_endian: bool) -> Self {
self.with_endian(if is_big_endian {
Endian::Big
} else {
Endian::Little
})
}
#[must_use]
pub fn with_point_step(mut self, point_step: u32) -> Self {
self.point_step = point_step;
self
}
#[deprecated(since = "1.0.0", note = "use `with_point_step` instead")]
#[doc(hidden)]
pub fn point_step(self, point_step: u32) -> Self {
self.with_point_step(point_step)
}
#[must_use]
pub fn with_row_step(mut self, row_step: u32) -> Self {
self.row_step = row_step;
self
}
#[deprecated(since = "1.0.0", note = "use `with_row_step` instead")]
#[doc(hidden)]
pub fn row_step(self, row_step: u32) -> Self {
self.with_row_step(row_step)
}
#[must_use]
pub fn with_data(mut self, data: Vec<u8>) -> Self {
self.data = data;
self
}
#[deprecated(since = "1.0.0", note = "use `with_data` instead")]
#[doc(hidden)]
pub fn data(self, data: Vec<u8>) -> Self {
self.with_data(data)
}
#[must_use]
pub fn with_dense(mut self, dense: Denseness) -> Self {
self.dense = dense;
self
}
#[deprecated(since = "1.0.0", note = "use `with_dense` instead")]
#[doc(hidden)]
pub fn dense(self, is_dense: bool) -> Self {
self.with_dense(if is_dense {
Denseness::Dense
} else {
Denseness::Sparse
})
}
pub fn build(self) -> Result<PointCloud2Msg, ConversionError> {
if self.fields.is_empty() {
return Err(ConversionError::FieldsNotFound(vec![]));
}
if self.fields.iter().any(|f| f.count != 1) {
return Err(ConversionError::UnsupportedFieldCount);
}
let fields_size = self
.fields
.iter()
.map(FieldDatatype::try_from)
.collect::<Result<Vec<_>, _>>()?
.iter()
.map(|f| f.size() as u32)
.sum::<_>();
if self.point_step < fields_size {
return Err(ConversionError::InvalidFieldFormat);
}
if (self.data.len() as u32) % self.point_step != 0 {
return Err(ConversionError::DataLengthMismatch);
}
Ok(PointCloud2Msg {
header: self.header,
dimensions: CloudDimensionsBuilder::new_with_width(self.width as usize).build()?,
fields: self.fields,
endian: self.endian,
point_step: self.point_step,
row_step: self.row_step,
data: self.data,
dense: self.dense,
})
}
}
#[derive(Clone, Debug, Default, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub struct CloudDimensions {
pub width: u32,
pub height: u32,
}
impl CloudDimensions {
#[inline]
pub fn len(&self) -> usize {
(self.width as usize) * (self.height as usize)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
fn ordered_field_names_from_layout(layout: &LayoutDescription) -> Vec<&'static str> {
layout
.0
.iter()
.filter(|field| matches!(field, LayoutField::Field { .. }))
.map(|field| match field {
LayoutField::Field { name, .. } => *name,
_ => unreachable!("Fields must be filtered before."),
})
.collect()
}
impl PointCloud2Msg {
#[inline]
fn byte_similarity<const N: usize, C>(&self) -> Result<ByteSimilarity, ConversionError>
where
C: PointConvertible<N>,
{
let layout = C::layout();
let field_names = ordered_field_names_from_layout(&layout);
let target_layout = KnownLayoutInfo::try_from(layout.clone())?;
debug_assert!(field_names.len() <= target_layout.fields.len());
debug_assert!(self.fields.len() >= field_names.len());
let mut offset: u32 = 0;
let mut field_counter = 0;
for f in target_layout.fields.iter() {
match f {
PointField::Field {
datatype,
size,
count,
} => {
if field_counter >= self.fields.len() || field_counter >= field_names.len() {
return Err(ConversionError::ExhaustedSource);
}
let msg_f = unsafe { self.fields.get_unchecked(field_counter) };
let f_translated = unsafe { field_names.get_unchecked(field_counter) };
field_counter += 1;
if msg_f.name != *f_translated
|| msg_f.offset != offset
|| msg_f.datatype != *datatype
|| msg_f.count != 1
{
return Ok(ByteSimilarity::Different);
}
offset += size * count;
}
PointField::Padding(size) => {
offset += size;
}
}
}
Ok(if offset == self.point_step {
ByteSimilarity::Equal
} else {
ByteSimilarity::Overlapping
})
}
#[inline]
fn message_template_for_type<const N: usize, C>()
-> Result<(PointCloud2MsgBuilder, usize), ConversionError>
where
C: PointConvertible<N>,
{
let layout = C::layout();
let field_names = ordered_field_names_from_layout(&layout);
debug_assert!(field_names.len() == N);
let layout = KnownLayoutInfo::try_from(C::layout())?;
debug_assert!(field_names.len() <= layout.fields.len());
let mut offset: usize = 0;
let mut fields: Vec<PointFieldMsg> = Vec::with_capacity(field_names.len());
for f in layout.fields.into_iter() {
match f {
PointField::Field {
datatype,
size,
count,
} => {
fields.push(PointFieldMsg {
name: crate::ros::make_field_name(field_names[fields.len()]),
offset: offset as u32,
datatype,
..Default::default()
});
offset += (size * count) as usize;
}
PointField::Padding(size) => {
offset += size as usize;
}
}
}
Ok((
PointCloud2MsgBuilder::new()
.with_fields(fields)
.with_point_step(offset as u32),
offset,
))
}
pub fn try_from_iter<'a, const N: usize, C>(
iterable: impl IntoIterator<Item = &'a C>,
) -> Result<Self, ConversionError>
where
C: PointConvertible<N> + 'a,
{
let (mut cloud, point_step) = {
let point: IPoint<N> = C::default().into();
debug_assert!(point.fields.len() == N);
let layout = C::layout();
let field_names = crate::ordered_field_names_from_layout(&layout);
debug_assert!(field_names.len() == N);
let mut pdata_offsets_acc: u32 = 0;
let mut fields = vec![PointFieldMsg::default(); N];
let field_count: u32 = 1;
for ((pdata_entry, field_name), field_val) in point
.fields
.into_iter()
.zip(field_names)
.zip(fields.iter_mut())
{
let datatype_code = pdata_entry.datatype.into();
let _ = FieldDatatype::try_from(datatype_code)?;
*field_val = PointFieldMsg {
name: crate::ros::make_field_name(field_name),
offset: pdata_offsets_acc,
datatype: datatype_code,
count: 1,
};
pdata_offsets_acc += field_count * pdata_entry.datatype.size() as u32;
}
(
PointCloud2MsgBuilder::new()
.with_fields(fields)
.with_point_step(pdata_offsets_acc),
pdata_offsets_acc,
)
};
let mut cloud_width = 0;
iterable.into_iter().for_each(|pointdata| {
let point: IPoint<N> = (*pointdata).into();
point.fields.iter().for_each(|pdata| {
let truncated_bytes = unsafe {
core::slice::from_raw_parts(pdata.bytes.as_ptr(), pdata.datatype.size())
};
cloud.data.extend_from_slice(truncated_bytes);
});
cloud_width += 1;
});
cloud = cloud.with_width(cloud_width);
cloud = cloud.with_row_step(cloud_width * point_step);
cloud.build()
}
#[cfg(feature = "rayon")]
#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
pub fn try_from_par_iter<const N: usize, C>(
iterable: impl rayon::iter::ParallelIterator<Item = C>,
) -> Result<Self, ConversionError>
where
C: PointConvertible<N> + Send + Sync,
{
Self::try_from_slice(&iterable.collect::<Vec<_>>())
}
pub fn try_from_slice<const N: usize, C>(slice: &[C]) -> Result<Self, ConversionError>
where
C: PointConvertible<N>,
{
match (system_endian(), Endian::default()) {
(Endian::Big, Endian::Big) | (Endian::Little, Endian::Little) => {
let (mut cloud, point_step) = {
let point: IPoint<N> = C::default().into();
debug_assert!(point.fields.len() == N);
let layout = C::layout();
let field_names = crate::ordered_field_names_from_layout(&layout);
debug_assert!(field_names.len() == N);
let layout = KnownLayoutInfo::try_from(C::layout())?;
debug_assert!(field_names.len() <= layout.fields.len());
let mut offset = 0;
let mut fields: Vec<PointFieldMsg> = Vec::with_capacity(field_names.len());
for f in layout.fields.into_iter() {
match f {
PointField::Field {
datatype,
size,
count,
} => {
fields.push(PointFieldMsg {
name: crate::ros::make_field_name(field_names[fields.len()]),
offset,
datatype,
..Default::default()
});
offset += size * count;
}
PointField::Padding(size) => {
offset += size;
}
}
}
(
PointCloud2MsgBuilder::new()
.with_fields(fields)
.with_point_step(offset),
offset,
)
};
let bytes_total = slice.len() * point_step as usize;
cloud.data.resize(bytes_total, u8::default());
let raw_data: *mut C = cloud.data.as_mut_ptr() as *mut C;
unsafe {
core::ptr::copy_nonoverlapping(
slice.as_ptr().cast::<u8>(),
raw_data.cast::<u8>(),
bytes_total,
);
}
Ok(cloud
.with_width(slice.len() as u32)
.with_row_step(slice.len() as u32 * point_step)
.build()?)
}
_ => Self::try_from_iter(slice.iter()),
}
}
fn try_from_vec_strict_consuming<const N: usize, C>(
mut vec: Vec<C>,
) -> Result<Self, (ConversionError, Vec<C>)>
where
C: PointConvertible<N> + Copy,
{
let sys_endian = system_endian();
let (cloud, point_step) = match Self::message_template_for_type::<N, C>() {
Ok(v) => v,
Err(e) => return Err((e, vec)),
};
let c_size = core::mem::size_of::<C>();
let vec_len = vec.len();
if c_size != point_step {
return Err((
ConversionError::VecElementSizeMismatch {
element_size: c_size,
expected_point_step: point_step,
},
vec,
));
}
let bytes_total = vec_len * point_step;
let cap_bytes = vec.capacity() * point_step;
let ptr = vec.as_mut_ptr() as *mut u8;
core::mem::forget(vec);
let data = unsafe { Vec::from_raw_parts(ptr, bytes_total, cap_bytes) };
match cloud
.with_endian(sys_endian)
.with_data(data)
.with_width(vec_len as u32)
.with_row_step((vec_len as u32) * (point_step as u32))
.build()
{
Ok(msg) => Ok(msg),
Err(_) => {
unreachable!("The conversion should succeed since the layout matches exactly.")
}
}
}
pub fn try_from_vec_strict<const N: usize, C>(vec: Vec<C>) -> Result<Self, ConversionError>
where
C: PointConvertible<N> + Copy,
{
match Self::try_from_vec_strict_consuming(vec) {
Ok(msg) => Ok(msg),
Err((e, _)) => Err(e),
}
}
pub fn try_from_vec<const N: usize, C>(vec: Vec<C>) -> Result<Self, ConversionError>
where
C: PointConvertible<N> + Copy,
{
if let Ok((_, point_step)) = Self::message_template_for_type::<N, C>() {
let c_size = core::mem::size_of::<C>();
if c_size == point_step {
match Self::try_from_vec_strict_consuming(vec) {
Ok(msg) => return Ok(msg),
Err((_, returned_vec)) => return Self::try_from_slice(&returned_vec),
}
}
}
Self::try_from_slice(&vec)
}
pub fn try_into_vec<const N: usize, C>(&self) -> Result<Vec<C>, ConversionError>
where
C: PointConvertible<N>,
{
match (system_endian(), self.endian) {
(Endian::Big, Endian::Big) | (Endian::Little, Endian::Little) => {
let bytematch = match self.byte_similarity::<N, C>()? {
ByteSimilarity::Equal => true,
ByteSimilarity::Overlapping => false,
ByteSimilarity::Different => return Ok(self.try_into_iter()?.collect()),
};
let cloud_len = self.dimensions.len();
let point_step = self.point_step as usize;
let mut vec: Vec<C> = Vec::with_capacity(cloud_len);
if bytematch {
unsafe {
core::ptr::copy_nonoverlapping(
self.data.as_ptr(),
vec.as_mut_ptr().cast::<u8>(),
self.data.len(),
);
vec.set_len(cloud_len);
}
} else {
unsafe {
for i in 0..cloud_len {
let point_ptr = self.data.as_ptr().add(i * point_step).cast::<C>();
let point = point_ptr.read();
vec.push(point);
}
}
}
Ok(vec)
}
_ => Ok(self.try_into_iter()?.collect()), }
}
pub fn try_into_slice_strict<const N: usize, C>(&self) -> Result<&[C], ConversionError>
where
C: PointConvertible<N> + Copy,
{
if system_endian() != self.endian {
return Err(ConversionError::UnsupportedSliceView);
}
if self.byte_similarity::<N, C>()? != ByteSimilarity::Equal {
return Err(ConversionError::UnsupportedSliceView);
}
let c_size = core::mem::size_of::<C>();
let point_step = self.point_step as usize;
if point_step != c_size {
return Err(ConversionError::UnsupportedSliceView);
}
if self.data.len() % c_size != 0 {
return Err(ConversionError::DataLengthMismatch);
}
let ptr = self.data.as_ptr() as *const C;
if (ptr as usize) % core::mem::align_of::<C>() != 0 {
return Err(ConversionError::UnalignedBuffer);
}
let len = self.data.len() / c_size;
let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
Ok(slice)
}
pub fn try_into_slice<'a, const N: usize, C>(
&'a self,
) -> Result<alloc::borrow::Cow<'a, [C]>, ConversionError>
where
C: PointConvertible<N> + Copy,
{
match self.try_into_slice_strict::<N, C>() {
Ok(slice) => Ok(alloc::borrow::Cow::Borrowed(slice)),
Err(_) => {
let vec = self.try_into_vec::<N, C>()?;
Ok(alloc::borrow::Cow::Owned(vec))
}
}
}
pub fn try_into_iter<'a, const N: usize, C>(
&'a self,
) -> Result<impl Iterator<Item = C> + 'a, ConversionError>
where
C: PointConvertible<N> + 'a,
{
iterator::PointCloudIterator::try_from(self)
}
#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
#[cfg(feature = "rayon")]
pub fn try_into_par_iter<'a, const N: usize, C>(
&'a self,
) -> Result<impl rayon::iter::ParallelIterator<Item = C> + 'a, ConversionError>
where
C: PointConvertible<N> + Send + Sync + 'a,
{
iterator::PointCloudIterator::try_from(self)
}
}
pub struct IPoint<const N: usize> {
fields: [PointData; N],
}
impl<const N: usize> core::ops::Index<usize> for IPoint<N> {
type Output = PointData;
fn index(&self, index: usize) -> &Self::Output {
&self.fields[index]
}
}
impl<const N: usize> From<[PointData; N]> for IPoint<N> {
fn from(fields: [PointData; N]) -> Self {
Self { fields }
}
}
pub unsafe trait PointConvertible<const N: usize>:
From<IPoint<N>> + Into<IPoint<N>> + Default + Sized + Copy
{
fn layout() -> LayoutDescription;
}
#[derive(Debug, Clone)]
enum PointField {
Padding(u32),
Field { size: u32, datatype: u8, count: u32 },
}
#[derive(Debug, Clone)]
struct KnownLayoutInfo {
fields: Vec<PointField>,
}
impl TryFrom<LayoutField> for PointField {
type Error = ConversionError;
fn try_from(f: LayoutField) -> Result<Self, Self::Error> {
match f {
LayoutField::Field { name: _, ty, size } => {
let typename: String = ty.to_lowercase();
let datatype = FieldDatatype::from_str(typename.as_str())?;
Ok(Self::Field {
size: size.try_into()?,
datatype: datatype.into(),
count: 1,
})
}
LayoutField::Padding { size } => Ok(Self::Padding(size.try_into()?)),
}
}
}
impl TryFrom<LayoutDescription> for KnownLayoutInfo {
type Error = ConversionError;
fn try_from(t: LayoutDescription) -> Result<Self, Self::Error> {
let fields: Vec<PointField> =
t.0.into_iter()
.map(PointField::try_from)
.collect::<Result<Vec<_>, _>>()?;
Ok(Self { fields })
}
}
#[derive(Debug, Clone, Copy)]
pub struct PointData {
bytes: [u8; core::mem::size_of::<f64>()],
endian: Endian,
datatype: FieldDatatype,
}
impl Default for PointData {
fn default() -> Self {
Self {
bytes: [u8::default(); core::mem::size_of::<f64>()],
datatype: FieldDatatype::F32,
endian: Endian::default(),
}
}
}
impl PointData {
#[inline]
pub fn new<T: FromBytes>(value: T) -> Self {
Self {
bytes: value.into().raw(),
datatype: T::field_datatype(),
..Default::default()
}
}
#[inline]
fn from_buffer(data: &[u8], offset: usize, datatype: FieldDatatype, endian: Endian) -> Self {
debug_assert!(data.len() >= offset + datatype.size());
let mut bytes = [u8::default(); core::mem::size_of::<f64>()];
unsafe {
let data_ptr = data.as_ptr().add(offset);
core::ptr::copy_nonoverlapping(data_ptr, bytes.as_mut_ptr(), datatype.size());
}
Self {
bytes,
endian,
datatype,
}
}
#[must_use]
pub fn get<T: FromBytes>(&self) -> T {
match self.endian {
Endian::Big => T::from_be_bytes(PointDataBuffer::new(self.bytes)),
Endian::Little => T::from_le_bytes(PointDataBuffer::new(self.bytes)),
}
}
pub fn get_checked<T: FromBytes>(&self) -> Result<T, ConversionError> {
#[cfg(feature = "strict-type-check")]
{
let stored = self.datatype;
let requested = T::field_datatype();
let compatible = stored == requested
|| (matches!(stored, FieldDatatype::RGB) && requested == FieldDatatype::F32)
|| (stored == FieldDatatype::F32 && requested == FieldDatatype::RGB);
if !compatible {
return Err(ConversionError::TypeMismatch { stored, requested });
}
}
let val = match self.endian {
Endian::Big => T::from_be_bytes(PointDataBuffer::new(self.bytes)),
Endian::Little => T::from_le_bytes(PointDataBuffer::new(self.bytes)),
};
Ok(val)
}
}
impl From<f32> for PointData {
fn from(value: f32) -> Self {
Self::new(value)
}
}
impl From<f64> for PointData {
fn from(value: f64) -> Self {
Self::new(value)
}
}
impl From<i32> for PointData {
fn from(value: i32) -> Self {
Self::new(value)
}
}
impl From<u8> for PointData {
fn from(value: u8) -> Self {
Self::new(value)
}
}
impl From<u16> for PointData {
fn from(value: u16) -> Self {
Self::new(value)
}
}
impl From<u32> for PointData {
fn from(value: u32) -> Self {
Self::new(value)
}
}
impl From<i8> for PointData {
fn from(value: i8) -> Self {
Self::new(value)
}
}
impl From<i16> for PointData {
fn from(value: i16) -> Self {
Self::new(value)
}
}
#[derive(Default, Clone, Debug, PartialEq, Copy)]
pub enum FieldDatatype {
F32,
F64,
I32,
U8,
U16,
#[default]
U32,
I8,
I16,
RGB,
}
impl FieldDatatype {
#[must_use]
pub fn size(&self) -> usize {
match self {
FieldDatatype::U8 => core::mem::size_of::<u8>(),
FieldDatatype::U16 => core::mem::size_of::<u16>(),
FieldDatatype::U32 => core::mem::size_of::<u32>(),
FieldDatatype::I8 => core::mem::size_of::<i8>(),
FieldDatatype::I16 => core::mem::size_of::<i16>(),
FieldDatatype::I32 => core::mem::size_of::<i32>(),
FieldDatatype::F32 | FieldDatatype::RGB => core::mem::size_of::<f32>(), FieldDatatype::F64 => core::mem::size_of::<f64>(),
}
}
}
impl core::str::FromStr for FieldDatatype {
type Err = ConversionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"f32" => Ok(FieldDatatype::F32),
"f64" => Ok(FieldDatatype::F64),
"i32" => Ok(FieldDatatype::I32),
"u8" => Ok(FieldDatatype::U8),
"u16" => Ok(FieldDatatype::U16),
"u32" => Ok(FieldDatatype::U32),
"i8" => Ok(FieldDatatype::I8),
"i16" => Ok(FieldDatatype::I16),
"rgb" => Ok(FieldDatatype::RGB),
_ => Err(ConversionError::UnsupportedFieldType(s.into())),
}
}
}
pub trait GetFieldDatatype {
fn field_datatype() -> FieldDatatype;
}
impl GetFieldDatatype for f32 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::F32
}
}
impl GetFieldDatatype for f64 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::F64
}
}
impl GetFieldDatatype for i32 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::I32
}
}
impl GetFieldDatatype for u8 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::U8
}
}
impl GetFieldDatatype for u16 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::U16
}
}
impl GetFieldDatatype for u32 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::U32
}
}
impl GetFieldDatatype for i8 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::I8
}
}
impl GetFieldDatatype for i16 {
fn field_datatype() -> FieldDatatype {
FieldDatatype::I16
}
}
impl GetFieldDatatype for crate::points::RGB {
fn field_datatype() -> FieldDatatype {
FieldDatatype::RGB
}
}
impl TryFrom<u8> for FieldDatatype {
type Error = ConversionError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
use alloc::string::ToString;
match value {
1 => Ok(FieldDatatype::I8),
2 => Ok(FieldDatatype::U8),
3 => Ok(FieldDatatype::I16),
4 => Ok(FieldDatatype::U16),
5 => Ok(FieldDatatype::I32),
6 => Ok(FieldDatatype::U32),
7 => Ok(FieldDatatype::F32),
8 => Ok(FieldDatatype::F64),
_ => Err(ConversionError::UnsupportedFieldType(value.to_string())),
}
}
}
impl From<FieldDatatype> for u8 {
fn from(val: FieldDatatype) -> Self {
match val {
FieldDatatype::I8 => 1,
FieldDatatype::U8 => 2,
FieldDatatype::I16 => 3,
FieldDatatype::U16 => 4,
FieldDatatype::I32 => 5,
FieldDatatype::U32 => 6,
FieldDatatype::F32 | FieldDatatype::RGB => 7, FieldDatatype::F64 => 8,
}
}
}
impl TryFrom<&ros::PointFieldMsg> for FieldDatatype {
type Error = ConversionError;
fn try_from(value: &ros::PointFieldMsg) -> Result<Self, Self::Error> {
Self::try_from(value.datatype)
}
}
pub struct PointDataBuffer([u8; 8]);
impl core::ops::Index<usize> for PointDataBuffer {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl PointDataBuffer {
#[must_use]
pub fn new(data: [u8; 8]) -> Self {
Self(data)
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
#[must_use]
pub fn raw(self) -> [u8; 8] {
self.0
}
#[must_use]
pub fn from_slice(data: &[u8]) -> Self {
let mut buffer = [0; 8];
data.iter().enumerate().for_each(|(i, &v)| buffer[i] = v);
Self(buffer)
}
}
impl From<&[u8]> for PointDataBuffer {
fn from(data: &[u8]) -> Self {
Self::from_slice(data)
}
}
impl<const N: usize> From<[u8; N]> for PointDataBuffer {
fn from(data: [u8; N]) -> Self {
Self::from(data.as_slice())
}
}
impl From<i8> for PointDataBuffer {
fn from(x: i8) -> Self {
x.to_le_bytes().into()
}
}
impl From<i16> for PointDataBuffer {
fn from(x: i16) -> Self {
x.to_le_bytes().into()
}
}
impl From<u16> for PointDataBuffer {
fn from(x: u16) -> Self {
x.to_le_bytes().into()
}
}
impl From<i32> for PointDataBuffer {
fn from(x: i32) -> Self {
x.to_le_bytes().into()
}
}
impl From<u32> for PointDataBuffer {
fn from(x: u32) -> Self {
x.to_le_bytes().into()
}
}
impl From<f32> for PointDataBuffer {
fn from(x: f32) -> Self {
x.to_le_bytes().into()
}
}
impl From<f64> for PointDataBuffer {
fn from(x: f64) -> Self {
x.to_le_bytes().into()
}
}
impl From<u8> for PointDataBuffer {
fn from(x: u8) -> Self {
x.to_le_bytes().into()
}
}
impl From<points::RGB> for PointDataBuffer {
fn from(x: points::RGB) -> Self {
x.raw().to_le_bytes().into()
}
}
pub trait FromBytes: Default + Sized + Copy + GetFieldDatatype + Into<PointDataBuffer> {
fn from_be_bytes(bytes: PointDataBuffer) -> Self;
fn from_le_bytes(bytes: PointDataBuffer) -> Self;
}
impl FromBytes for i8 {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([bytes[0]])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([bytes[0]])
}
}
impl FromBytes for i16 {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([bytes[0], bytes[1]])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([bytes[0], bytes[1]])
}
}
impl FromBytes for u16 {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([bytes[0], bytes[1]])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([bytes[0], bytes[1]])
}
}
impl FromBytes for u32 {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
}
impl FromBytes for f32 {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
}
impl FromBytes for points::RGB {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::new_from_packed_f32(f32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::new_from_packed_f32(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
}
impl FromBytes for i32 {
#[inline]
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
}
impl FromBytes for f64 {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
}
}
impl FromBytes for u8 {
fn from_be_bytes(bytes: PointDataBuffer) -> Self {
Self::from_be_bytes([bytes[0]])
}
fn from_le_bytes(bytes: PointDataBuffer) -> Self {
Self::from_le_bytes([bytes[0]])
}
}