Skip to content

Commit

Permalink
API changes for v1 (#14)
Browse files Browse the repository at this point in the history
* reduce to functions

* fix msg not found

* impl par iterator

* clippy

* pre endian

* derive as feat only

* custom conversions

* tidy

* test with all feats

* indent
  • Loading branch information
stelzo authored May 7, 2024
1 parent a0ca014 commit 89a1dbb
Show file tree
Hide file tree
Showing 27 changed files with 2,730 additions and 2,049 deletions.
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

0 comments on commit 89a1dbb

Please sign in to comment.