-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add pointcloud API contract and ROS interface (#431)
### Proposed changes Added a ROS point cloud interface for float data type and it respective testcase #### Type of change - [ ] 🐛 Bugfix (change which fixes an issue) - [x] 🚀 Feature (change which adds functionality) - [ ] 📚 Documentation (change which fixes or extends documentation) ### Checklist _Put an `x` in the boxes that apply. This is simply a reminder of what we will require before merging your code._ - [x] Lint and unit tests (if any) pass locally with my changes - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have added necessary documentation (if appropriate) - [x] All commits have been signed for [DCO](https://developercertificate.org/) --------- Signed-off-by: Pablo Vela <[email protected]> Co-authored-by: Michel Hidalgo <[email protected]>
- Loading branch information
Showing
11 changed files
with
1,240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// Copyright 2024 Ekumen, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#ifndef BELUGA_SENSOR_DATA_POINT_CLOUD_HPP | ||
#define BELUGA_SENSOR_DATA_POINT_CLOUD_HPP | ||
|
||
#include <cmath> | ||
|
||
#include <ciabatta/ciabatta.hpp> | ||
|
||
#include <sophus/types.hpp> | ||
|
||
/** | ||
* \file | ||
* \brief Implementation of a point cloud interface with memory-aligned data. | ||
* | ||
* \details | ||
* Assumes data is aligned as X, Y, Z, other datafields . | ||
* All datafields are the same type (float or double). | ||
* The result of the data stide division by the type of data must be an integer. | ||
*/ | ||
|
||
namespace beluga { | ||
|
||
/** | ||
* \page PointCloudPage Beluga named requirements: PointCloud | ||
* | ||
* A type `P` satisfies `PointCloud` requirements if given `p` a possibly | ||
* const instance of `P`: | ||
* - `P::Scalar` is defined and is a scalar type to be used for x, y and z coordinates values, | ||
* - p.points()` returns a view to the unorganized 3D point collection of `Eigen::Map<Eigen::Matrix3X>` type, | ||
* - `p.origin()` returns the transform, of `Sophus::SE3d` type, from the global to local | ||
* frame in the sensor space; | ||
*/ | ||
|
||
/// Point Cloud 3D base type. | ||
/** | ||
* When instantiated, it satisfies \ref PointCloudPage. | ||
* | ||
* \tparam Derived Concrete point cloud type. It must define | ||
* `Derived::ranges()`, `Derived::angles()`, | ||
* as described in \ref PointCloudPage. | ||
*/ | ||
template <typename Derived> | ||
class BasePointCloud : public ciabatta::ciabatta_top<Derived> {}; | ||
|
||
} // namespace beluga | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// Copyright 2024 Ekumen, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#ifndef BELUGA_SENSOR_DATA_SPARSE_POINT_CLOUD_HPP | ||
#define BELUGA_SENSOR_DATA_SPARSE_POINT_CLOUD_HPP | ||
|
||
#include <cmath> | ||
|
||
#include <ciabatta/ciabatta.hpp> | ||
|
||
#include <sophus/types.hpp> | ||
|
||
/** | ||
* \file | ||
* \brief Implementation of a sparse point cloud interface without memory-aligned data. | ||
* | ||
* \details | ||
* Assumes data is aligned as X, Y, Z, other datafields . | ||
* Only X, Y & Z must be the same type (float or double). | ||
* Other datafields can be different types. | ||
*/ | ||
|
||
namespace beluga { | ||
|
||
/** | ||
* \page SparsePointCloudPage Beluga named requirements: PointCloud | ||
* | ||
* A type `P` satisfies `PointCloud` requirements if given `p` a possibly | ||
* const instance of `P`: | ||
* - `P::Scalar` is defined and is a scalar type to be used for x, y and z coordinates values, | ||
* - p.points()` returns a view to the unorganized 3D point collection of `Eigen::Map<Eigen::Vector3>` type, | ||
* - `p.origin()` returns the transform, of `Sophus::SE3d` type, from the global to local | ||
* frame in the sensor space; | ||
*/ | ||
|
||
/// Sparse Point Cloud 3D base type. | ||
/** | ||
* When instantiated, it satisfies \ref PointCloudPage. | ||
* | ||
* \tparam Derived Concrete point cloud type. It must define | ||
* `Derived::ranges()`, `Derived::angles()`, | ||
* as described in \ref PointCloudPage. | ||
*/ | ||
template <typename Derived> | ||
class BaseSparsePointCloud : public ciabatta::ciabatta_top<Derived> {}; | ||
|
||
} // namespace beluga | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright 2024 Ekumen, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#ifndef BELUGA_ROS_POINT_CLOUD_HPP | ||
#define BELUGA_ROS_POINT_CLOUD_HPP | ||
|
||
#include <range/v3/view/iota.hpp> | ||
|
||
#include <beluga/sensor/data/point_cloud.hpp> | ||
#include <beluga/views/take_evenly.hpp> | ||
#include <beluga_ros/messages.hpp> | ||
|
||
#include <sophus/se3.hpp> | ||
|
||
#include <Eigen/Dense> | ||
|
||
#include "beluga/eigen_compatibility.hpp" | ||
|
||
/** | ||
* \file | ||
* \brief Implementation of `sensor_msgs/PointCloud2` wrapper type for messages with memory-aligned strides. | ||
* | ||
* \details | ||
* The stride calculation ensures that the memory layout of the point cloud data is aligned with the size of the data | ||
* type used for iteration (`iteratorType`). To maintain proper memory alignment, the stride must satisfy the condition: | ||
* | ||
* `cloud_->point_step % sizeof(iteratorType) == 0` | ||
* | ||
* This condition guarantees that `point_step` is a multiple of the size of the `iteratorType`, ensuring efficient | ||
* access to each point in the cloud. | ||
*/ | ||
|
||
namespace beluga_ros { | ||
|
||
/// Thin wrapper type for 3D `sensor_msgs/PointCloud2` messages. | ||
template <typename T> | ||
class PointCloud3 : public beluga::BasePointCloud<PointCloud3<T>> { | ||
public: | ||
/// PointCloud type | ||
using Scalar = T; | ||
|
||
/// Check type is float or double | ||
static_assert( | ||
std::is_same_v<Scalar, float> || std::is_same_v<Scalar, double>, | ||
"Pointcloud3 only supports float or double datatype"); | ||
|
||
/// Constructor. | ||
/// | ||
/// \param cloud Point cloud message. | ||
/// \param origin Point cloud frame origin in the filter frame. | ||
explicit PointCloud3(beluga_ros::msg::PointCloud2ConstSharedPtr cloud, Sophus::SE3d origin = Sophus::SE3d()) | ||
: cloud_(std::move(cloud)), origin_(std::move(origin)) { | ||
assert(cloud_ != nullptr); | ||
constexpr uint8_t fieldType = sensor_msgs::typeAsPointFieldType<T>::value; | ||
// Check if point cloud is 3D | ||
if (cloud_->fields.size() < 3) { | ||
throw std::invalid_argument("PointCloud is not 3D"); | ||
} | ||
// Check point cloud is XYZ... type | ||
if (cloud_->fields.at(0).name != "x" || cloud_->fields.at(1).name != "y" || cloud_->fields.at(2).name != "z") { | ||
throw std::invalid_argument("PointCloud not XYZ..."); | ||
} | ||
// Check XYZ datatype is the same | ||
if (cloud_->fields.at(0).datatype != fieldType || cloud_->fields.at(1).datatype != fieldType || | ||
cloud_->fields.at(2).datatype != fieldType) { | ||
throw std::invalid_argument("XYZ datatype are not same"); | ||
} | ||
// Check stride is divisible | ||
if (cloud_->point_step % sizeof(Scalar) != 0) { | ||
throw std::invalid_argument("Data is not memory-aligned"); | ||
} | ||
stride_ = static_cast<int>(cloud_->point_step / sizeof(Scalar)); | ||
} | ||
|
||
/// Get the point cloud frame origin in the filter frame. | ||
[[nodiscard]] const auto& origin() const { return origin_; } | ||
|
||
/// Get the unorganized 3D point collection as an Eigen Map<Eigen::Matrix3X>. | ||
[[nodiscard]] auto points() const { | ||
const beluga_ros::msg::PointCloud2ConstIterator<Scalar> iter_points(*cloud_, "x"); | ||
return Eigen::Map<const Eigen::Matrix3X<Scalar>, 0, Eigen::OuterStride<>>( | ||
&iter_points[0], 3, cloud_->width * cloud_->height, stride_); | ||
} | ||
|
||
private: | ||
beluga_ros::msg::PointCloud2ConstSharedPtr cloud_; | ||
int stride_; | ||
Sophus::SE3d origin_; | ||
}; | ||
|
||
} // namespace beluga_ros | ||
|
||
#endif // BELUGA_ROS_POINT_CLOUD_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright 2024 Ekumen, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#ifndef BELUGA_ROS_SPARSE_POINT_CLOUD_HPP | ||
#define BELUGA_ROS_SPARSE_POINT_CLOUD_HPP | ||
|
||
#include <range/v3/view/iota.hpp> | ||
|
||
#include <beluga/sensor/data/sparse_point_cloud.hpp> | ||
#include <beluga/views/take_evenly.hpp> | ||
#include <beluga_ros/messages.hpp> | ||
|
||
#include <sophus/se3.hpp> | ||
|
||
#include <Eigen/Dense> | ||
|
||
#include "beluga/eigen_compatibility.hpp" | ||
|
||
/** | ||
* \file | ||
* \brief Implementation of `sensor_msgs/PointCloud2` wrapper type for messages without alignment constraints on the | ||
* stride. | ||
* | ||
* \details | ||
* Since alignment is not enforced, this allows for more flexible messages layouts, though it may lead to less efficient | ||
* access patterns in certain cases. | ||
*/ | ||
|
||
namespace beluga_ros { | ||
|
||
/// Thin wrapper type for 3D `sensor_msgs/PointCloud2` messages. | ||
/// Assumes an XYZ... type message. | ||
/// XYZ datafields must be the same type (float or double). | ||
/// Other datafields can be different types. | ||
template <typename T> | ||
class SparsePointCloud3 : public beluga::BaseSparsePointCloud<SparsePointCloud3<T>> { | ||
public: | ||
/// PointCloud type | ||
using Scalar = T; | ||
|
||
/// Check type is float or double | ||
static_assert( | ||
std::is_same_v<Scalar, float> || std::is_same_v<Scalar, double>, | ||
"PointcloudSparse3 only supports float or double datatype"); | ||
|
||
/// Constructor. | ||
/// | ||
/// \param cloud Point cloud message. | ||
/// \param origin Point cloud frame origin in the filter frame. | ||
explicit SparsePointCloud3(beluga_ros::msg::PointCloud2ConstSharedPtr cloud, Sophus::SE3d origin = Sophus::SE3d()) | ||
: cloud_(std::move(cloud)), origin_(std::move(origin)) { | ||
assert(cloud_ != nullptr); | ||
constexpr uint8_t fieldType = sensor_msgs::typeAsPointFieldType<T>::value; | ||
// Check if point cloud is 3D | ||
if (cloud_->fields.size() < 3) { | ||
throw std::invalid_argument("PointCloud is not 3D"); | ||
} | ||
// Check point cloud is XYZ... type | ||
if (cloud_->fields.at(0).name != "x" && cloud_->fields.at(1).name != "y" && cloud_->fields.at(2).name != "z") { | ||
throw std::invalid_argument("PointCloud not XYZ..."); | ||
} | ||
// Check XYZ datatype is the same | ||
if (cloud_->fields.at(0).datatype != fieldType || cloud_->fields.at(1).datatype != fieldType || | ||
cloud_->fields.at(2).datatype != fieldType) { | ||
throw std::invalid_argument("XYZ datatype are not same"); | ||
} | ||
} | ||
|
||
/// Get the point cloud frame origin in the filter frame. | ||
[[nodiscard]] const auto& origin() const { return origin_; } | ||
|
||
/// Get the unorganized 3D point collection as an Eigen Map<Eigen::Vector3>. | ||
[[nodiscard]] auto points() const { | ||
beluga_ros::msg::PointCloud2ConstIterator<Scalar> iter_points(*cloud_, "x"); | ||
return ranges::views::iota(0, static_cast<int>(cloud_->width * cloud_->height)) | | ||
ranges::views::transform([iter_points](int i) mutable { | ||
return Eigen::Map<const Eigen::Vector3<Scalar>>(&(iter_points + i)[0]); | ||
}); | ||
} | ||
|
||
private: | ||
beluga_ros::msg::PointCloud2ConstSharedPtr cloud_; | ||
Sophus::SE3d origin_; | ||
}; | ||
|
||
} // namespace beluga_ros | ||
|
||
#endif // BELUGA_ROS_SPARSE_POINT_CLOUD_HPP |
Oops, something went wrong.