Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API changes for v1 #14

Merged
merged 10 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/r2r_galactic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: docker build . --file ./tests/Dockerfile_r2r_galactic --tag r2r_galactic
- run: docker run r2r_galactic cargo test --features r2r_msg
- run: docker run r2r_galactic cargo test --features r2r_msg,derive,nalgebra,rayon
2 changes: 1 addition & 1 deletion .github/workflows/r2r_humble.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: docker build . --file ./tests/Dockerfile_r2r_humble --tag r2r_humble
- run: docker run r2r_humble cargo test --features r2r_msg
- run: docker run r2r_humble cargo test --features r2r_msg,derive,nalgebra,rayon
2 changes: 1 addition & 1 deletion .github/workflows/r2r_iron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: docker build . --file ./tests/Dockerfile_r2r_iron --tag r2r_iron
- run: docker run r2r_iron cargo test --features r2r_msg
- run: docker run r2r_iron cargo test --features r2r_msg,derive,nalgebra,rayon
2 changes: 1 addition & 1 deletion .github/workflows/rclrs_humble.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: docker build . --file ./tests/Dockerfile_rclrs_humble --tag rclrs_humble
- run: docker run rclrs_humble cargo test
- run: docker run rclrs_humble cargo test --features derive,nalgebra,rayon
2 changes: 1 addition & 1 deletion .github/workflows/rclrs_iron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: docker build . --file ./tests/Dockerfile_rclrs_iron --tag rclrs_iron
- run: docker run rclrs_iron cargo test
- run: docker run rclrs_iron cargo test --features derive,nalgebra,rayon
2 changes: 1 addition & 1 deletion .github/workflows/rosrust_noetic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@ jobs:
- name: test
run: |
source /opt/ros/$ROS_DISTRO/setup.bash
cargo test --features rosrust_msg
cargo test --features rosrust_msg,derive,nalgebra,rayon
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ jobs:
- name: Linting
run: cargo clippy --all-targets -- -D warnings
- name: Tests
run: cargo test
run: cargo test --features derive,nalgebra,rayon
37 changes: 34 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,48 @@ description = "Customizable conversions for working with sensor_msgs/PointCloud2
repository = "https://github.com/stelzo/ros_pointcloud2"
license = "MIT"
keywords = ["ros", "pointcloud2", "pointcloud", "message"]
categories = ["science::robotics", "encoding", "data-structures", "api-bindings"]
categories = [
"science::robotics",
"encoding",
"data-structures",
"api-bindings",
]
readme = "README.md"
documentation = "https://docs.rs/ros_pointcloud2"
homepage = "https://github.com/stelzo/ros_pointcloud2"
exclude = ["**/tests/**", "**/examples/**", "**/benches/**", "**/target/**", "**/build/**", "**/dist/**", "**/docs/**", "**/doc/**"]
exclude = [
"**/tests/**",
"**/examples/**",
"**/benches/**",
"**/target/**",
"**/build/**",
"**/dist/**",
"**/docs/**",
"**/doc/**",
]

[dependencies]
rosrust_msg = { version = "0.1", optional = true }
rosrust = { version = "0.9.11", optional = true }
r2r = { version = "0.8.4", optional = true }
rayon = { version = "1", optional = true }
nalgebra = { version = "0", optional = true }
rpcl2_derive = { path = "./rpcl2_derive", optional = true }
type-layout = { version = "0.2", 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"]
r2r_msg = ["dep:r2r"]
rayon = ["dep:rayon"]
derive = ["dep:rpcl2_derive", "dep:type-layout"]
nalgebra = ["dep:nalgebra"]

default = ["rayon", "derive", "nalgebra"]
72 changes: 33 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,53 @@
## !! Note !!
This library is currently in development for v1.0.0, for the documentation of v0.4.0 on crates.io, visit the [docs](https://docs.rs/ros_pointcloud2/0.4.0/ros_pointcloud2/).

<p align="center">
<h3 align="center">ROS PointCloud2</h3>
<p align="center">Customizable conversions to and from the PointCloud2 ROS message.</p>
<p align="center">A complete and versatile abstraction of PointCloud2.</p>
<p align="center"><a href="https://crates.io/crates/ros_pointcloud2"><img src="https://img.shields.io/crates/v/ros_pointcloud2.svg" alt=""></a> <a href="https://github.com/stelzo/ros_pointcloud2/tree/main/tests"><img src="https://github.com/stelzo/ros_pointcloud2/actions/workflows/tests.yml/badge.svg" alt=""></a>
</p>
</p>

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, it uses its own type for the message `PointCloud2Msg`.
ROS1 and ROS2 is supported with feature flags.

Get started with the example below, check out the other use cases in the `examples` folder or see the [Documentation](https://docs.rs/ros_pointcloud2/1.0.0/ros_pointcloud2/) for a complete guide.

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`.
## Quickstart

The following example uses the iterator APIs. For memory bound pipelines, you may also try the `try_from_vec` or `try_into_vec` functions by enabling the `derive` feature.

## Examples
```rust
use ros_pointcloud2::{
pcl_utils::PointXYZ, reader::ReaderXYZ, writer::WriterXYZ, PointCloud2Msg,
};
use ros_pointcloud2::prelude::*;

// Your points (here using the predefined type PointXYZ).
// PointXYZ (and many others) are provided by the crate.
let cloud_points = vec![
PointXYZ {x: 91.486, y: -4.1, z: 42.0001,},
PointXYZ {x: f32::MAX, y: f32::MIN, z: f32::MAX,},
PointXYZ::new(91.486, -4.1, 42.0001),
PointXYZ::new(f32::MAX, f32::MIN, 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_iter(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_iter().unwrap()
.map(|point: PointXYZ| { // Define the type of point here.
// Some logic here ...

point
})
.collect::<Vec<PointXYZ>>(); // iterating points here O(n)

assert_eq!(new_cloud_points, cloud_copy);
point
})
.collect::<Vec<_>>();
```

## Integrations

There are currently 3 integrations for common ROS crates.

- [rosrust_msg](https://github.com/adnanademovic/rosrust)
- [![Tests](https://github.com/stelzo/ros_pointcloud2/actions/workflows/rosrust_noetic.yml/badge.svg)](https://github.com/stelzo/ros_pointcloud2/actions/workflows/rosrust_noetic.yml)
- [r2r_msg](https://github.com/sequenceplanner/r2r)
Expand All @@ -65,6 +59,7 @@ There are currently 3 integrations for common ROS crates.
- [![Tests](https://github.com/stelzo/ros_pointcloud2/actions/workflows/rclrs_iron.yml/badge.svg)](https://github.com/stelzo/ros_pointcloud2/actions/workflows/rclrs_iron.yml)

You can use `rosrust` and `r2r` by enabling the respective feature:

```toml
[dependencies]
ros_pointcloud2 = { version = "*", features = ["r2r_msg"]}
Expand All @@ -73,12 +68,16 @@ ros_pointcloud2 = { version = "*", features = ["rosrust_msg"]}
```

### rclrs (ros2_rust)

Features do not work properly with `rcrls` because the messages are linked externally. You need to use tags instead:

```toml
[dependencies]
ros_pointcloud2 = { git = "https://github.com/stelzo/ros_pointcloud2", tag = "v0.4.0_rclrs" }
```

Also, indicate the following dependencies to your linker inside the `package.xml` of your package.

```xml
<depend>std_msgs</depend>
<depend>sensor_msgs</depend>
Expand All @@ -87,11 +86,6 @@ 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/)
164 changes: 164 additions & 0 deletions benches/roundtrip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ros_pointcloud2::prelude::*;

use rand::Rng;

pub fn generate_random_pointcloud(num_points: usize, min: f32, max: f32) -> Vec<PointXYZ> {
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
}

#[cfg(feature = "derive")]
fn roundtrip_vec(cloud: Vec<PointXYZ>) -> bool {
let orig_len = cloud.len();
let internal_msg = PointCloud2Msg::try_from_vec(cloud).unwrap();
let total = internal_msg
.try_into_iter()
.unwrap()
.collect::<Vec<PointXYZ>>();
orig_len == total.len()
}

fn roundtrip(cloud: Vec<PointXYZ>) -> bool {
let orig_len = cloud.len();
let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap();
let total = internal_msg
.try_into_iter()
.unwrap()
.collect::<Vec<PointXYZ>>();
orig_len == total.len()
}

#[cfg(feature = "derive")]
fn roundtrip_filter_vec(cloud: Vec<PointXYZ>) -> bool {
let orig_len = cloud.len();
let internal_msg = PointCloud2Msg::try_from_vec(cloud).unwrap();
let total = internal_msg
.try_into_iter()
.unwrap()
.filter(|point: &PointXYZ| {
(point.x.powi(2) + point.y.powi(2) + point.z.powi(2)).sqrt() < 1.9
})
.fold(PointXYZ::default(), |acc, point| PointXYZ {
x: acc.x + point.x,
y: acc.y + point.y,
z: acc.z + point.z,
});
orig_len == total.x as usize
}

fn roundtrip_filter(cloud: Vec<PointXYZ>) -> bool {
let orig_len = cloud.len();
let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap();
let total = internal_msg
.try_into_iter()
.unwrap()
.filter(|point: &PointXYZ| {
(point.x.powi(2) + point.y.powi(2) + point.z.powi(2)).sqrt() < 1.9
})
.fold(PointXYZ::default(), |acc, point| PointXYZ {
x: acc.x + point.x,
y: acc.y + point.y,
z: acc.z + point.z,
});
orig_len == total.x as usize
}

#[cfg(feature = "rayon")]
fn roundtrip_par(cloud: Vec<PointXYZ>) -> bool {
let orig_len = cloud.len();
let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap();
let total = internal_msg
.try_into_par_iter()
.unwrap()
.collect::<Vec<PointXYZ>>();
orig_len != total.len()
}

#[cfg(feature = "rayon")]
fn roundtrip_filter_par(cloud: Vec<PointXYZ>) -> bool {
let orig_len: usize = cloud.len();
let internal_msg = PointCloud2Msg::try_from_iter(cloud).unwrap();
let total = internal_msg
.try_into_par_iter()
.unwrap()
.filter(|point: &PointXYZ| {
(point.x.powi(2) + point.y.powi(2) + point.z.powi(2)).sqrt() < 1.9
})
.reduce(PointXYZ::default, |acc, point| PointXYZ {
x: acc.x + point.x,
y: acc.y + point.y,
z: acc.z + point.z,
});
orig_len == total.x as usize
}

fn roundtrip_benchmark(c: &mut Criterion) {
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);

c.bench_function("roundtrip 500k", |b| {
b.iter(|| {
black_box(roundtrip(cloud_points_500k.clone()));
})
});

#[cfg(feature = "rayon")]
c.bench_function("roundtrip_par 500k", |b| {
b.iter(|| {
black_box(roundtrip_par(cloud_points_500k.clone()));
})
});

#[cfg(feature = "derive")]
c.bench_function("roundtrip_vec 500k", |b| {
b.iter(|| {
black_box(roundtrip_vec(cloud_points_500k.clone()));
})
});

c.bench_function("roundtrip_filter 500k", |b| {
b.iter(|| {
roundtrip_filter(black_box(cloud_points_500k.clone()));
})
});

#[cfg(feature = "rayon")]
c.bench_function("roundtrip_filter_par 500k", |b| {
b.iter(|| {
roundtrip_filter_par(black_box(cloud_points_500k.clone()));
})
});

c.bench_function("roundtrip_filter 1.5m", |b| {
b.iter(|| {
roundtrip_filter(black_box(cloud_points_1_5m.clone()));
})
});

#[cfg(feature = "rayon")]
c.bench_function("roundtrip_filter_par 1.5m", |b| {
b.iter(|| {
black_box(roundtrip_filter_par(cloud_points_1_5m.clone()));
})
});

#[cfg(feature = "derive")]
c.bench_function("roundtrip_filter_vec 1.5m", |b| {
b.iter(|| {
roundtrip_filter_vec(black_box(cloud_points_1_5m.clone()));
})
});
}

criterion_group!(benches, roundtrip_benchmark);
criterion_main!(benches);
Loading
Loading