6 releases
0.3.1 | Aug 22, 2023 |
---|---|
0.3.0 | Jul 25, 2023 |
0.2.2 | Jun 22, 2023 |
0.2.1 | Apr 27, 2023 |
0.1.0 | Apr 25, 2023 |
#67 in Robotics
50 downloads per month
53KB
1K
SLoC
ROS PointCloud2
Customizable conversions to and from the sensor_msgs/PointCloud2
ROS message.
Providing a memory efficient way for message conversion while allowing user defined types without the cost of iterations.
Instead of converting the entire cloud into a Vec
, you get an Iterator
that converts each point from the message on the fly.
An example for using this crate is this filter node. It is also a good starting point for
implementing ROS1 nodes in Rust inside a catkin environment.
To keep the crate a general purpose library for the problem and support ROS1 and ROS2, it uses its own type for the message.
use ros_pointcloud2::fallible_iterator::FallibleIterator;
use ros_pointcloud2::pcl_utils::PointXYZ;
use ros_pointcloud2::ros_types::PointCloud2Msg;
use ros_pointcloud2::ConvertXYZ;
// Your points (here using the predefined type PointXYZ).
let cloud_points = vec![
PointXYZ {
x: 1.3,
y: 1.6,
z: 5.7,
},
PointXYZ {
x: f32::MAX,
y: f32::MIN,
z: f32::MAX,
},
];
let cloud_copy = cloud_points.clone(); // Only for checking equality later.
// Vector -> Convert -> Message
let internal_msg: PointCloud2Msg = ConvertXYZ::try_from(cloud_points)
.unwrap()
.try_into()
.unwrap();
// Convert to your favorite ROS crate message type, we will use rosrust here.
// let msg: rosrust_msg::sensor_msgs::PointCloud2 = internal_msg.into();
// Back to this crate's message type.
// let internal_msg: PointCloud2Msg = msg.into();
// Message -> Convert -> Vector
let convert: ConvertXYZ = ConvertXYZ::try_from(internal_msg).unwrap();
let new_cloud_points = convert
.map(|point: PointXYZ| {
// Insert your point business logic here or use other methods like .for_each().
// I will just copy the points into a vector as an example.
// Also, since we are using a fallible iterator, we need to return a Result.
Ok(point)
})
.collect::<Vec<PointXYZ>>()
.unwrap(); // Handle point conversion or byte errors here.
assert_eq!(new_cloud_points, cloud_copy);
To use ros_pointcloud2
in your favorite ROS crate, you can either use this crate's features (see Integration section below) or implement the Into
and From
traits for PointCloud2Msg
.
Try to avoid cloning the data: Vec<u8>
field.
use ros_pointcloud2::ros_types::PointCloud2Msg;
struct YourROSPointCloud2 {} // Likely to be generated by your ROS crate.
impl Into<YourROSPointCloud2> for PointCloud2Msg {
fn into(self) -> YourROSPointCloud2 {
todo!()
}
}
impl From<YourROSPointCloud2> for PointCloud2Msg {
fn from(msg: YourROSPointCloud2) -> Self {
todo!()
}
}
Integrations
There are currently 2 integrations for common ROS crates such as rosrust for ROS1 and R2R for ROS2 (Galactic).
You can use them by enabling the corresponding feature. Example:
[dependencies]
ros_pointcloud2 = { version = "*", features = ["r2r_msg"]}
Please open an issue or PR if you want to see support for other crates.
Features
- Easy to integrate into your favorite ROS1 or ROS2 crate
- Custom point types
- Predefined types for common conversions (compared to PCL)
- PointXYZ
- PointXYZI
- PointXYZL
- PointXYZRGB
- PointXYZRGBA
- PointXYZRGBL
- PointXYZNormal
- PointXYZINormal
- PointXYZRGBNormal
- 2D and 3D
Custom Types
You can freely convert to your own point types without reiterating the entire cloud.
You just need to implement some traits.
use ros_pointcloud2::mem_macros::size_of;
use ros_pointcloud2::ros_types::PointCloud2Msg;
use ros_pointcloud2::{ConversionError, Convert, MetaNames, PointConvertible, PointMeta};
const DIM: usize = 3;
const METADIM: usize = 4;
#[derive(Debug, PartialEq, Clone)]
struct CustomPoint {
x: f32, // DIM 1
y: f32, // DIM 2
z: f32, // DIM 3
r: u8, // METADIM 1
g: u8, // METADIM 2
b: u8, // METADIM 3
a: u8, // METADIM 4
}
// Converting your custom point to the crate's internal representation
impl From<CustomPoint> for ([f32; DIM], [PointMeta; METADIM]) {
fn from(point: CustomPoint) -> Self {
(
[point.x, point.y, point.z],
[
PointMeta::new(point.r),
PointMeta::new(point.g),
PointMeta::new(point.b),
PointMeta::new(point.a),
],
)
}
}
// The mappings for index of meta idx to field names. Example: 0 -> "r", 1 -> "g", 2 -> "b", 3 -> "a"
impl MetaNames<METADIM> for CustomPoint {
fn meta_names() -> [String; METADIM] {
["r", "g", "b", "a"].map(|s| s.to_string())
}
}
// Converting crate's internal representation to your custom point
impl TryFrom<([f32; DIM], [PointMeta; METADIM])> for CustomPoint {
type Error = ConversionError;
fn try_from(data: ([f32; DIM], [PointMeta; METADIM])) -> Result<Self, Self::Error> {
Ok(Self {
x: data.0[0],
y: data.0[1],
z: data.0[2],
r: data.1[0].get()?,
g: data.1[1].get()?,
b: data.1[2].get()?,
a: data.1[3].get()?,
})
}
}
impl PointConvertible<f32, { size_of!(f32) }, DIM, METADIM> for CustomPoint {}
type MyConverter = Convert<f32, { size_of!(f32) }, DIM, METADIM, CustomPoint>;
// Your custom cloud (Vector)
let custom_cloud = vec![
CustomPoint {
x: f32::MAX,
y: f32::MIN,
z: f32::MAX,
r: u8::MAX,
g: u8::MAX,
b: u8::MAX,
a: u8::MAX,
},
];
// Cloud -> ROS message
let custom_msg: PointCloud2Msg = MyConverter::try_from(custom_cloud)
.unwrap()
.try_into()
.unwrap();
// ROS message -> Cloud
let to_custom_type = MyConverter::try_from(custom_msg).unwrap();
Future Work
- Benchmark vs PCL
- Proper error passing to the iterator result (currently merged into
PointConversionError
) - remove allocations
- introduce no-panic for maximum stability
- Add more predefined types
License
Dependencies
~0.2–41MB
~633K SLoC