From b155f416d1c65a8d6d607675935cd159164f8b00 Mon Sep 17 00:00:00 2001 From: stelzo Date: Tue, 30 Apr 2024 18:06:02 +0200 Subject: [PATCH 01/10] reduce to functions --- Cargo.toml | 8 + README.md | 50 ++-- benches/roundtrip.rs | 55 +++++ examples/custom_label_filter.rs | 133 ++++++++++ examples/distance_filter.rs | 49 ++++ src/convert.rs | 2 +- src/iterator.rs | 238 ++++++++++++++++++ src/lib.rs | 271 +++++++++++++------- src/reader.rs | 422 -------------------------------- src/ros_types.rs | 13 - src/writer.rs | 346 -------------------------- tests/e2e_test.rs | 254 ++++++------------- tests/r2r_msg_test.rs | 8 +- tests/rosrust_msg_test.rs | 8 +- 14 files changed, 765 insertions(+), 1092 deletions(-) create mode 100644 benches/roundtrip.rs create mode 100644 examples/custom_label_filter.rs create mode 100644 examples/distance_filter.rs create mode 100644 src/iterator.rs delete mode 100644 src/reader.rs delete mode 100644 src/writer.rs diff --git a/Cargo.toml b/Cargo.toml index a70ded8..cb7734d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,14 @@ rosrust_msg = { version = "0.1", optional = true } rosrust = { version = "0.9.11", optional = true } r2r = { version = "0.8.4", optional = true } +[dev-dependencies] +rand = "0.8" +criterion = { version = "0.4", features = ["html_reports"] } + +[[bench]] +name = "roundtrip" +harness = false + [features] rosrust_msg = ["dep:rosrust_msg", "dep:rosrust"] r2r_msg = ["dep:r2r"] \ No newline at end of file diff --git a/README.md b/README.md index 4bcd698..96eb8cc 100644 --- a/README.md +++ b/README.md @@ -7,48 +7,34 @@ Providing an easy to use, generics defined, point-wise iterator abstraction over the byte buffer in `PointCloud2` to minimize iterations in your processing pipeline. -To keep the crate a general purpose library for the problem and support ROS1 and ROS2, it uses its own type for the message `PointCloud2Msg`. +To keep the crate a general purpose library for the problem, it uses its own type for the message `PointCloud2Msg`. ROS1 and ROS2 support is added with feature flags. -## Examples +## Quickstart ```rust -use ros_pointcloud2::{ - pcl_utils::PointXYZ, reader::ReaderXYZ, writer::WriterXYZ, PointCloud2Msg, -}; +use ros_pointcloud2::{PointCloud2Msg, pcl_utils::PointXYZ}; -// Your points (here using the predefined type PointXYZ). +// Your points (here using a predefined type PointXYZ). let cloud_points = vec![ PointXYZ {x: 91.486, y: -4.1, z: 42.0001,}, PointXYZ {x: f32::MAX, y: f32::MIN, z: f32::MAX,}, ]; -// For equality test later -let cloud_copy = cloud_points.clone(); - -// Vector -> Writer -> Message. -// You can also just give the Vec or anything that implements `IntoIterator`. -let internal_msg: PointCloud2Msg = WriterXYZ::from(cloud_points.into_iter()) - .try_into() // iterating points here O(n) - .unwrap(); - -// Convert to your ROS crate message type, we will use r2r here. -// let msg: r2r::sensor_msgs::msg::PointCloud2 = internal_msg.into(); +// Give the Vec or anything that implements `IntoIterator`. +let in_msg = PointCloud2Msg::try_from_iterable(cloud_points).unwrap(); +// Convert the ROS crate message type, we will use r2r here. +// let msg: r2r::sensor_msgs::msg::PointCloud2 = in_msg.into(); // Publish ... - // ... now incoming from a topic. -// let internal_msg: PointCloud2Msg = msg.into(); +// let in_msg: PointCloud2Msg = msg.into(); -// Message -> Reader -> your pipeline. The Reader implements the Iterator trait. -let reader = ReaderXYZ::try_from(internal_msg).unwrap(); -let new_cloud_points = reader - .map(|point: PointXYZ| { - // Some logic here +let new_pcl = in_msg.try_into_iterable().unwrap() + .map(|point: PointXYZ| { // Define the type of point here. + // Some logic here ... - point - }) - .collect::>(); // iterating points here O(n) - -assert_eq!(new_cloud_points, cloud_copy); + point + }) + .collect::>(); ``` ## Integrations @@ -87,11 +73,5 @@ Also, indicate the following dependencies to your linker inside the `package.xml Please open an issue or PR if you want to see support for other crates. -## Future Work -- Benchmark vs PCL -- Add more predefined types -- Optional derive macros for custom point implementations - - ## License [MIT](https://choosealicense.com/licenses/mit/) diff --git a/benches/roundtrip.rs b/benches/roundtrip.rs new file mode 100644 index 0000000..4ebc753 --- /dev/null +++ b/benches/roundtrip.rs @@ -0,0 +1,55 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use ros_pointcloud2::{pcl_utils::PointXYZ, PointCloud2Msg}; + +use rand::Rng; + +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 { + x: rng.gen_range(min..max), + y: rng.gen_range(min..max), + z: rng.gen_range(min..max), + }; + pointcloud.push(point); + } + pointcloud +} + +fn roundtrip(cloud: Vec) -> bool { + let orig_len = cloud.len(); + let internal_msg = PointCloud2Msg::try_from_iterable(cloud).unwrap(); + let total = internal_msg + .try_into_iter() + .unwrap() + .collect::>(); + orig_len == total.len() +} + +fn roundtrip_benchmark(c: &mut Criterion) { + let cloud_points_10k = generate_random_pointcloud(10_000, f32::MIN / 2.0, f32::MAX / 2.0); + let cloud_points_100k = generate_random_pointcloud(100_000, f32::MIN / 2.0, f32::MAX / 2.0); + let cloud_points_1m = generate_random_pointcloud(1_000_000, f32::MIN / 2.0, f32::MAX / 2.0); + + c.bench_function("roundtrip 10k", |b| { + b.iter(|| { + black_box(roundtrip(cloud_points_10k.clone())); + }) + }); + + c.bench_function("roundtrip 100k", |b| { + b.iter(|| { + black_box(roundtrip(cloud_points_100k.clone())); + }) + }); + + c.bench_function("roundtrip 1m", |b| { + b.iter(|| { + black_box(roundtrip(cloud_points_1m.clone())); + }) + }); +} + +criterion_group!(benches, roundtrip_benchmark); +criterion_main!(benches); diff --git a/examples/custom_label_filter.rs b/examples/custom_label_filter.rs new file mode 100644 index 0000000..19b92ed --- /dev/null +++ b/examples/custom_label_filter.rs @@ -0,0 +1,133 @@ +// This example demonstrates how to use custom point types with custom metadata. +// The use case is a segmentation point cloud where each point holds a label +// with a custom enum type we want to filter. + +use ros_pointcloud2::{size_of, MetaNames, Point, PointCloud2Msg, PointConvertible}; + +#[derive(Debug, PartialEq, Clone)] +enum Label { + Human, + Deer, + Car, +} + +impl From