Program Listing for File datacontainerbase.hpp
↰ Return to documentation for file (include/velodyne_pointcloud/datacontainerbase.hpp)
// Copyright 2012, 2019 Austin Robot Technology, Jack O'Quin, Joshua Whitley, Sebastian Pütz // NOLINT
// All rights reserved.
//
// Software License Agreement (BSD License 2.0)
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of {copyright_holder} nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#ifndef VELODYNE_POINTCLOUD__DATACONTAINERBASE_HPP_
#define VELODYNE_POINTCLOUD__DATACONTAINERBASE_HPP_
#include <tf2/LinearMath/Quaternion.h>
#include <tf2/LinearMath/Vector3.h>
#include <tf2/buffer_core.h>
#include <tf2/exceptions.h>
#include <tf2_ros/buffer.h>
#include <tf2_ros/transform_listener.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <rclcpp/time.hpp>
#include <geometry_msgs/msg/transform_stamped.hpp>
#include <sensor_msgs/msg/point_cloud2.hpp>
#include <sensor_msgs/point_cloud2_iterator.hpp>
#include <velodyne_msgs/msg/velodyne_scan.hpp>
namespace velodyne_rawdata
{
class DataContainerBase
{
public:
explicit DataContainerBase(
const double min_range, const double max_range, const std::string & target_frame,
const std::string & fixed_frame, const unsigned int init_width, const unsigned int init_height,
const bool is_dense, const unsigned int scans_per_packet, rclcpp::Clock::SharedPtr clock,
int fields, ...)
: config_(min_range, max_range, target_frame, fixed_frame,
init_width, init_height, is_dense, scans_per_packet),
clock_(clock)
{
va_list vl;
cloud.fields.clear();
cloud.fields.reserve(fields);
va_start(vl, fields);
int offset = 0;
for (int i = 0; i < fields; ++i) {
// Create the corresponding PointField
std::string name(va_arg(vl, char *));
int count(va_arg(vl, int));
int datatype(va_arg(vl, int));
offset = addPointField(cloud, name, count, datatype, offset);
}
va_end(vl);
cloud.point_step = offset;
cloud.width = config_.init_width;
cloud.height = config_.init_height;
cloud.is_dense = static_cast<uint8_t>(config_.is_dense);
cloud.row_step = cloud.width * cloud.point_step;
}
virtual ~DataContainerBase() {}
struct Config final
{
double min_range;
double max_range;
std::string target_frame;
std::string fixed_frame;
unsigned int init_width;
unsigned int init_height;
bool is_dense;
unsigned int scans_per_packet;
Config(
double min_range, double max_range, const std::string & target_frame,
const std::string & fixed_frame, unsigned int init_width,
unsigned int init_height, bool is_dense, unsigned int scans_per_packet)
: min_range(min_range),
max_range(max_range),
target_frame(target_frame),
fixed_frame(fixed_frame),
init_width(init_width),
init_height(init_height),
is_dense(is_dense),
scans_per_packet(scans_per_packet)
{
}
};
virtual void setup(const velodyne_msgs::msg::VelodyneScan::ConstSharedPtr scan_msg)
{
sensor_frame_ = scan_msg->header.frame_id;
manage_tf_buffer();
cloud.header.stamp = scan_msg->header.stamp;
cloud.width = config_.init_width;
cloud.height = config_.init_height;
cloud.is_dense = static_cast<uint8_t>(config_.is_dense);
cloud.row_step = cloud.width * cloud.point_step;
cloud.data.resize(scan_msg->packets.size() * config_.scans_per_packet * cloud.point_step);
// Clear out the last data; this is important in the organized cloud case
std::fill(cloud.data.begin(), cloud.data.end(), 0);
}
virtual void addPoint(
float x, float y, float z, const uint16_t ring, const float distance,
const float intensity, const float time) = 0;
virtual void newLine() = 0;
const sensor_msgs::msg::PointCloud2 & finishCloud()
{
cloud.data.resize(cloud.point_step * cloud.width * cloud.height);
cloud.row_step = cloud.point_step * cloud.width;
// If config_.target_frame is empty (the default), we use the frame_id that
// came in during the initial VelodyneScan (set by setup()). If it is
// set to something, then we override that value.
if (!config_.target_frame.empty()) {
cloud.header.frame_id = config_.target_frame;
} else if (!config_.fixed_frame.empty()) {
cloud.header.frame_id = config_.fixed_frame;
} else {
cloud.header.frame_id = sensor_frame_;
}
return cloud;
}
void manage_tf_buffer()
{
// check if sensor frame is already known, if not don't prepare tf buffer until setup was called
if (sensor_frame_.empty()) {
return;
}
// avoid doing transformation when sensor_frame equals target frame
// and no ego motion compensation is perfomed
if (config_.fixed_frame.empty() && sensor_frame_ == config_.target_frame) {
// when the string is empty the points will not be transformed later on
config_.target_frame = "";
return;
}
// only use somewhat resource intensive tf listener when transformations are necessary
if (!config_.fixed_frame.empty() || !config_.target_frame.empty()) {
if (!tf_buffer_) {
tf_buffer_ = std::make_shared<tf2_ros::Buffer>(clock_);
tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
}
} else {
tf_listener_.reset();
tf_buffer_.reset();
}
}
void configure(
const double min_range, const double max_range, const std::string & fixed_frame,
const std::string & target_frame)
{
config_.min_range = min_range;
config_.max_range = max_range;
config_.fixed_frame = fixed_frame;
config_.target_frame = target_frame;
manage_tf_buffer();
}
inline bool calculateTransformMatrix(
Eigen::Affine3f & matrix, const std::string & target_frame,
const std::string & source_frame, const rclcpp::Time & time)
{
if (!tf_buffer_) {
RCLCPP_ERROR(rclcpp::get_logger("velodyne_pointcloud"), "tf buffer was not initialized yet");
return false;
}
geometry_msgs::msg::TransformStamped msg;
try {
msg = tf_buffer_->lookupTransform(
target_frame, source_frame, time, rclcpp::Duration::from_seconds(0.2));
} catch (tf2::LookupException & e) {
RCLCPP_ERROR(rclcpp::get_logger("velodyne_pointcloud"), "%s", e.what());
return false;
} catch (tf2::ExtrapolationException & e) {
RCLCPP_ERROR(rclcpp::get_logger("velodyne_pointcloud"), "%s", e.what());
return false;
}
const auto & quaternion = msg.transform.rotation;
Eigen::Quaternionf rotation(quaternion.w, quaternion.x, quaternion.y, quaternion.z);
const auto & origin = msg.transform.translation;
Eigen::Translation3f translation(origin.x, origin.y, origin.z);
matrix = translation * rotation;
return true;
}
inline bool computeTransformToTarget(const rclcpp::Time & scan_time)
{
if (config_.target_frame.empty()) {
// no need to calculate transform -> success
return true;
}
std::string & source_frame = config_.fixed_frame.empty() ? sensor_frame_ : config_.fixed_frame;
return calculateTransformMatrix(
tf_matrix_to_target_, config_.target_frame, source_frame, scan_time);
}
inline bool computeTransformToFixed(const rclcpp::Time & packet_time)
{
if (config_.fixed_frame.empty()) {
// no need to calculate transform -> success
return true;
}
std::string & source_frame = sensor_frame_;
return calculateTransformMatrix(
tf_matrix_to_fixed_, config_.fixed_frame, source_frame, packet_time);
}
protected:
sensor_msgs::msg::PointCloud2 cloud;
inline void vectorTfToEigen(tf2::Vector3 & tf_vec, Eigen::Vector3f & eigen_vec)
{
eigen_vec(0) = tf_vec[0];
eigen_vec(1) = tf_vec[1];
eigen_vec(2) = tf_vec[2];
}
inline void transformPoint(float & x, float & y, float & z)
{
Eigen::Vector3f p = Eigen::Vector3f(x, y, z);
if (!config_.fixed_frame.empty()) {
p = tf_matrix_to_fixed_ * p;
}
if (!config_.target_frame.empty()) {
p = tf_matrix_to_target_ * p;
}
x = p.x();
y = p.y();
z = p.z();
}
inline bool pointInRange(float range)
{
return range >= config_.min_range && range <= config_.max_range;
}
Config config_;
rclcpp::Clock::SharedPtr clock_;
std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
Eigen::Affine3f tf_matrix_to_fixed_;
Eigen::Affine3f tf_matrix_to_target_;
std::string sensor_frame_;
};
} // namespace velodyne_rawdata
#endif // VELODYNE_POINTCLOUD__DATACONTAINERBASE_HPP_