From a09b2c3e91260687f02f735d7937b5abd2edd7e0 Mon Sep 17 00:00:00 2001 From: stelzo Date: Fri, 10 May 2024 01:10:17 +0200 Subject: [PATCH] intro docs --- benches/roundtrip.rs | 129 +++++++++++++++++++++++++++++++++++++------ src/lib.rs | 41 +++++++------- 2 files changed, 135 insertions(+), 35 deletions(-) diff --git a/benches/roundtrip.rs b/benches/roundtrip.rs index 99e5aac..8d56bd1 100644 --- a/benches/roundtrip.rs +++ b/benches/roundtrip.rs @@ -3,6 +3,8 @@ use ros_pointcloud2::prelude::*; use rand::Rng; +pub type PointXYZB = PointXYZINormal; + pub fn distance_to_origin(point: &PointXYZ) -> f32 { ((point.x.powi(2)) + (point.y.powi(2)) + (point.z.powi(2))).sqrt() } @@ -97,14 +99,15 @@ fn minus(point1: &PointXYZ, point2: &PointXYZ) -> PointXYZ { } } -pub fn generate_random_pointcloud(num_points: usize, min: f32, max: f32) -> Vec { +pub fn generate_random_pointcloud(num_points: usize, min: f32, max: f32) -> Vec { let mut rng = rand::thread_rng(); let mut pointcloud = Vec::with_capacity(num_points); for _ in 0..num_points { - let point = PointXYZ { + let point = PointXYZB { x: rng.gen_range(min..max), y: rng.gen_range(min..max), z: rng.gen_range(min..max), + ..Default::default() }; pointcloud.push(point); } @@ -160,14 +163,14 @@ pub fn heavy_computing(point: &PointXYZ, iterations: u32) -> f32 { } #[cfg(feature = "derive")] -fn roundtrip_vec(cloud: Vec) -> bool { +fn roundtrip_vec(cloud: Vec) -> bool { let orig_len = cloud.len(); let internal_msg = PointCloud2Msg::try_from_vec(cloud).unwrap(); let total: Vec = internal_msg.try_into_vec().unwrap(); orig_len == total.len() } -fn roundtrip(cloud: Vec) -> bool { +fn roundtrip(cloud: Vec) -> bool { let orig_len = cloud.len(); let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap(); let total = internal_msg @@ -178,7 +181,7 @@ fn roundtrip(cloud: Vec) -> bool { } #[cfg(feature = "derive")] -fn roundtrip_filter_vec(cloud: Vec) -> bool { +fn roundtrip_filter_vec(cloud: Vec) -> bool { let orig_len = cloud.len(); let internal_msg = PointCloud2Msg::try_from_vec(cloud).unwrap(); let total = internal_msg @@ -193,7 +196,7 @@ fn roundtrip_filter_vec(cloud: Vec) -> bool { orig_len == total.x as usize } -fn roundtrip_filter(cloud: Vec) -> bool { +fn roundtrip_filter(cloud: Vec) -> bool { let orig_len = cloud.len(); let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap(); let total = internal_msg @@ -208,7 +211,7 @@ fn roundtrip_filter(cloud: Vec) -> bool { orig_len == total.x as usize } -fn roundtrip_computing(cloud: Vec) -> bool { +fn roundtrip_computing(cloud: Vec) -> bool { let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap(); let total = internal_msg .try_into_iter() @@ -219,7 +222,7 @@ fn roundtrip_computing(cloud: Vec) -> bool { } #[cfg(feature = "rayon")] -fn roundtrip_computing_par(cloud: Vec) -> bool { +fn roundtrip_computing_par(cloud: Vec) -> bool { let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap(); let total = internal_msg .try_into_par_iter() @@ -230,7 +233,7 @@ fn roundtrip_computing_par(cloud: Vec) -> bool { } #[cfg(feature = "derive")] -fn roundtrip_computing_par_par(cloud: Vec) -> bool { +fn roundtrip_computing_par_par(cloud: Vec) -> bool { let internal_msg = PointCloud2Msg::try_from_par_iter(cloud.into_par_iter()).unwrap(); let total = internal_msg .try_into_par_iter() @@ -241,7 +244,7 @@ fn roundtrip_computing_par_par(cloud: Vec) -> bool { } #[cfg(feature = "derive")] -fn roundtrip_computing_vec(cloud: Vec) -> bool { +fn roundtrip_computing_vec(cloud: Vec) -> bool { let internal_msg = PointCloud2Msg::try_from_vec(cloud).unwrap(); let total: f32 = internal_msg .try_into_vec() @@ -253,7 +256,7 @@ fn roundtrip_computing_vec(cloud: Vec) -> bool { } #[cfg(feature = "rayon")] -fn roundtrip_par(cloud: Vec) -> bool { +fn roundtrip_par(cloud: Vec) -> bool { let orig_len = cloud.len(); let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap(); let total = internal_msg @@ -264,7 +267,7 @@ fn roundtrip_par(cloud: Vec) -> bool { } #[cfg(feature = "rayon")] -fn roundtrip_par_par(cloud: Vec) -> bool { +fn roundtrip_par_par(cloud: Vec) -> bool { let orig_len = cloud.len(); let internal_msg = PointCloud2Msg::try_from_par_iter(cloud.into_par_iter()).unwrap(); let total = internal_msg @@ -275,7 +278,7 @@ fn roundtrip_par_par(cloud: Vec) -> bool { } #[cfg(feature = "rayon")] -fn roundtrip_filter_par(cloud: Vec) -> bool { +fn roundtrip_filter_par(cloud: Vec) -> bool { let orig_len: usize = cloud.len(); let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap(); let total = internal_msg @@ -291,7 +294,7 @@ fn roundtrip_filter_par(cloud: Vec) -> bool { } #[cfg(feature = "rayon")] -fn roundtrip_filter_par_par(cloud: Vec) -> bool { +fn roundtrip_filter_par_par(cloud: Vec) -> bool { let orig_len: usize = cloud.len(); let internal_msg = PointCloud2Msg::try_from_par_iter(cloud.into_par_iter()).unwrap(); let total = internal_msg @@ -307,12 +310,99 @@ fn roundtrip_filter_par_par(cloud: Vec) -> bool { } fn roundtrip_benchmark(c: &mut Criterion) { + let cloud_points_16k = generate_random_pointcloud(16_000, f32::MIN / 2.0, f32::MAX / 2.0); let cloud_points_60k = generate_random_pointcloud(60_000, f32::MIN / 2.0, f32::MAX / 2.0); let cloud_points_120k = generate_random_pointcloud(120_000, f32::MIN / 2.0, f32::MAX / 2.0); let cloud_points_500k = generate_random_pointcloud(500_000, f32::MIN / 2.0, f32::MAX / 2.0); let cloud_points_1_5m = generate_random_pointcloud(1_500_000, f32::MIN / 2.0, f32::MAX / 2.0); - // 60k points (Ouster OS with 64 beams) + // 16k points (Velodyne with 16 beams) + + // Moving memory + c.bench_function("16k iter", |b| { + b.iter(|| { + black_box(roundtrip(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "rayon")] + c.bench_function("16k iter_par", |b| { + b.iter(|| { + black_box(roundtrip_par(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "rayon")] + c.bench_function("16k iter_par_par", |b| { + b.iter(|| { + black_box(roundtrip_par_par(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "derive")] + c.bench_function("16k vec", |b| { + b.iter(|| { + black_box(roundtrip_vec(cloud_points_16k.clone())); + }) + }); + + // Simple distance filter + c.bench_function("16k iter_filter", |b| { + b.iter(|| { + roundtrip_filter(black_box(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "rayon")] + c.bench_function("16k filter_par", |b| { + b.iter(|| { + roundtrip_filter_par(black_box(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "rayon")] + c.bench_function("16k filter_par_par", |b| { + b.iter(|| { + black_box(roundtrip_filter_par_par(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "derive")] + c.bench_function("16k vec_filter", |b| { + b.iter(|| { + roundtrip_filter_vec(black_box(cloud_points_16k.clone())); + }) + }); + + // Heavy computing + c.bench_function("16k iter_compute", |b| { + b.iter(|| { + roundtrip_computing(black_box(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "rayon")] + c.bench_function("16k iter_compute_par", |b| { + b.iter(|| { + roundtrip_computing_par(black_box(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "rayon")] + c.bench_function("16k iter_compute_par_par", |b| { + b.iter(|| { + roundtrip_computing_par_par(black_box(cloud_points_16k.clone())); + }) + }); + + #[cfg(feature = "derive")] + c.bench_function("16k vec_compute", |b| { + b.iter(|| { + roundtrip_computing_vec(black_box(cloud_points_16k.clone())); + }) + }); + + // 60k points (Ouster with 64 beams) // Moving memory c.bench_function("60k iter", |b| { @@ -335,7 +425,14 @@ fn roundtrip_benchmark(c: &mut Criterion) { }) }); - // 120k points (Ouster OS with 128 beams) + #[cfg(feature = "derive")] + c.bench_function("60k vec", |b| { + b.iter(|| { + black_box(roundtrip_vec(cloud_points_60k.clone())); + }) + }); + + // 120k points (Ouster with 128 beams) // Moving memory c.bench_function("120k iter", |b| { diff --git a/src/lib.rs b/src/lib.rs index d32088f..75d084a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,36 +1,43 @@ //! A library to work with the PointCloud2 message type from ROS. //! -//! ros_pointcloud2 mainly provides a [`ros_pointcloud2::PointCloud2Msg`] type that implements -//! functions for conversion to and from iterators. +//! Per default, ros_pointcloud2 provides a [`ros_pointcloud2::PointCloud2Msg`] type that implements conversions to and from iterators. +//! These are very flexible and faster than C++ PCL conversions for small to medium point clouds making them a good default for prototyping. +//! The meaning of small, medium and large depends on your device and the application. //! //! - [`ros_pointcloud2::PointCloud2Msg::try_from_iter`] //! - [`ros_pointcloud2::PointCloud2Msg::try_into_iter`] //! -//! For memory heavy applications, you can use the `derive` feature that allows you to copy -//! the data from and into a typed Vec. This increases compile times but is faster at runtime if you -//! only move the data around without any per-point processing. +//! For memory-heavy applications, you can use the `derive` feature that allows you to copy +//! the data from and into a typed Vec. When reading a Msg, your point struct must be an ordered subset of the type coming in at the Msg. +//! This info is not inside the source code, it is knowledge you have to provide to the library for gaining the speedup. +//! An example would be PointXYZINormal coming from some source as Msg and try_into_vec to Vec. They are the same up to the data you need. +//! Creating a Msg does not need to be ordered, since there is no conversion needed. +//! `derive` increases compile times but is faster for just moving data around without per-point processing. //! //! - [`ros_pointcloud2::PointCloud2Msg::try_from_vec`] (requires `derive` feature) //! - [`ros_pointcloud2::PointCloud2Msg::try_into_vec`] (requires `derive` feature) //! -//! For processing heavy applications, you can use the `rayon` feature to get a parallel iterator over the points. +//! For processing-heavy applications over point clouds, you may add the `rayon` feature. +//! It provides an implementation of Rayon's parallel iterator, which allows fully parallel processing of the point cloud. +//! Note that the `derive` feature is also required for creating a Msg from a parallel iterator. //! //! - [`ros_pointcloud2::PointCloud2Msg::try_into_par_iter`] (requires `rayon` feature) +//! - [`ros_pointcloud2::PointCloud2Msg::try_from_par_iter`] (requires `rayon` + `derive` feature) //! -//! Note though, that writing in parallel to the same point cloud is not supported. +//! If you are not sure which feature makes sense for your application and device, you should try out the benchmark in the `benches` directory. //! //! For ROS interoperability, the message type generated by the respective crate must be converted to -//! the [`ros_pointcloud2::PointCloud2Msg`] using the `From` trait, -//! which mostly does ownership transfers without copying the point data. +//! the [`ros_pointcloud2::PointCloud2Msg`] using the `From` trait. +//! This mostly does ownership transfers and avoids copies of point data. //! -//! There are implementations for the `r2r`, `rclrs` (ros2_rust) and `rosrust_msg` message types +//! There are implementations for the `r2r`, `rclrs` (ros2_rust) and `rosrust` message types //! available with feature flags. If you miss a message type, please open an issue or a PR. //! See the [`ros_pointcloud2::ros_types`] module for more information. //! //! Common point types like [`ros_pointcloud2::pcl_utils::PointXYZ`] or //! [`ros_pointcloud2::pcl_utils::PointXYZI`] are provided in the -//! [`ros_pointcloud2::pcl_utils`] module. You can implement any custom point type -//! that can be described by the specification. +//! [`ros_pointcloud2::pcl_utils`] module. You can implement any additional custom point type. +//! See `examples/custom_enum_field_filter.rs` for an example. //! //! # Example PointXYZ //! ``` @@ -258,11 +265,7 @@ impl PointCloud2Msg { } } - if offset == self.point_step { - Ok(true) - } else { - Err(MsgConversionError::DataLengthMismatch) - } + Ok(offset == self.point_step) } #[inline(always)] @@ -347,7 +350,7 @@ impl PointCloud2Msg { Ok(cloud) } - #[cfg(all(feature = "rayon", not(feature = "derive")))] + /*#[cfg(all(feature = "rayon", not(feature = "derive")))] pub fn try_from_par_iter( iterable: impl rayon::iter::ParallelIterator, ) -> Result @@ -373,7 +376,7 @@ impl PointCloud2Msg { cloud.row_step = cloud.width * cloud.point_step; Ok(cloud) - } + }*/ #[cfg(all(feature = "rayon", feature = "derive"))] pub fn try_from_par_iter(