1400 lines
54 KiB
C++
Executable File
1400 lines
54 KiB
C++
Executable File
// Copyright 2023 RealSense, Inc. All Rights Reserved.
|
|
//
|
|
// 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.
|
|
|
|
#include "../include/base_realsense_node.h"
|
|
#include "assert.h"
|
|
#include <algorithm>
|
|
#include <mutex>
|
|
#include <rclcpp/clock.hpp>
|
|
#include <fstream>
|
|
#include <image_publisher.h>
|
|
#include <sensor_msgs/msg/point_cloud2.hpp>
|
|
#include <sensor_msgs/point_cloud2_iterator.hpp>
|
|
|
|
// Header files for disabling intra-process comms for static broadcaster.
|
|
#include <rclcpp/publisher_options.hpp>
|
|
#include <tf2_ros/qos.hpp>
|
|
#include "pointcloud_filter.h"
|
|
#include "align_depth_filter.h"
|
|
|
|
using namespace realsense2_camera;
|
|
|
|
SyncedImuPublisher::SyncedImuPublisher(rclcpp::Publisher<sensor_msgs::msg::Imu>::SharedPtr imu_publisher,
|
|
std::size_t waiting_list_size):
|
|
_publisher(imu_publisher), _pause_mode(false),
|
|
_waiting_list_size(waiting_list_size), _is_enabled(false)
|
|
{}
|
|
|
|
SyncedImuPublisher::~SyncedImuPublisher()
|
|
{
|
|
try
|
|
{
|
|
PublishPendingMessages();
|
|
}
|
|
catch(...){} // Not allowed to throw from Dtor
|
|
}
|
|
|
|
void SyncedImuPublisher::Publish(sensor_msgs::msg::Imu imu_msg)
|
|
{
|
|
std::lock_guard<std::mutex> lock_guard(_mutex);
|
|
if (_pause_mode)
|
|
{
|
|
if (_pending_messages.size() >= _waiting_list_size)
|
|
{
|
|
throw std::runtime_error("SyncedImuPublisher inner list reached maximum size of " + std::to_string(_pending_messages.size()));
|
|
}
|
|
_pending_messages.push(imu_msg);
|
|
}
|
|
else
|
|
{
|
|
_publisher->publish(imu_msg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void SyncedImuPublisher::Pause()
|
|
{
|
|
if (!_is_enabled) return;
|
|
std::lock_guard<std::mutex> lock_guard(_mutex);
|
|
_pause_mode = true;
|
|
}
|
|
|
|
void SyncedImuPublisher::Resume()
|
|
{
|
|
std::lock_guard<std::mutex> lock_guard(_mutex);
|
|
PublishPendingMessages();
|
|
_pause_mode = false;
|
|
}
|
|
|
|
void SyncedImuPublisher::PublishPendingMessages()
|
|
{
|
|
while (!_pending_messages.empty())
|
|
{
|
|
const sensor_msgs::msg::Imu &imu_msg = _pending_messages.front();
|
|
_publisher->publish(imu_msg);
|
|
_pending_messages.pop();
|
|
}
|
|
}
|
|
size_t SyncedImuPublisher::getNumSubscribers()
|
|
{
|
|
if (!_publisher) return 0;
|
|
return _publisher->get_subscription_count();
|
|
}
|
|
|
|
BaseRealSenseNode::BaseRealSenseNode(RosNodeBase& node,
|
|
rs2::device dev,
|
|
std::shared_ptr<Parameters> parameters,
|
|
bool use_intra_process) :
|
|
_is_running(true),
|
|
_node(node),
|
|
_logger(node.get_logger()),
|
|
_parameters(parameters),
|
|
_dev(dev),
|
|
_json_file_path(""),
|
|
_depth_scale_meters(0),
|
|
_clipping_distance(0),
|
|
_linear_accel_cov(0),
|
|
_angular_velocity_cov(0),
|
|
_hold_back_imu_for_frames(false),
|
|
_publish_tf(false),
|
|
_tf_publish_rate(TF_PUBLISH_RATE),
|
|
_diagnostics_period(0),
|
|
_use_intra_process(use_intra_process),
|
|
_is_initialized_time_base(false),
|
|
_camera_time_base(0),
|
|
_sync_frames(SYNC_FRAMES),
|
|
_enable_rgbd(ENABLE_RGBD),
|
|
_is_color_enabled(false),
|
|
_is_depth_enabled(false),
|
|
_is_accel_enabled(false),
|
|
_is_gyro_enabled(false),
|
|
_pointcloud(false),
|
|
_imu_sync_method(imu_sync_method::NONE),
|
|
_is_profile_changed(false),
|
|
_is_align_depth_changed(false),
|
|
_safety_sensor(nullptr)
|
|
#if defined (ACCELERATE_GPU_WITH_GLSL)
|
|
,_app(1280, 720, "RS_GLFW_Window"),
|
|
_accelerate_gpu_with_glsl(false),
|
|
_is_accelerate_gpu_with_glsl_changed(false)
|
|
#endif
|
|
{
|
|
if ( use_intra_process )
|
|
{
|
|
ROS_INFO("Intra-Process communication enabled");
|
|
}
|
|
|
|
initializeFormatsMaps();
|
|
_monitor_options = {RS2_OPTION_ASIC_TEMPERATURE, RS2_OPTION_PROJECTOR_TEMPERATURE};
|
|
}
|
|
|
|
BaseRealSenseNode::~BaseRealSenseNode()
|
|
{
|
|
// Kill dynamic transform thread
|
|
_is_running = false;
|
|
_cv_tf.notify_one();
|
|
if (_tf_t && _tf_t->joinable())
|
|
_tf_t->join();
|
|
|
|
_cv_temp.notify_one();
|
|
_cv_mpc.notify_one();
|
|
if (_monitoring_t && _monitoring_t->joinable())
|
|
{
|
|
_monitoring_t->join();
|
|
}
|
|
if (_monitoring_pc && _monitoring_pc->joinable())
|
|
{
|
|
_monitoring_pc->join();
|
|
}
|
|
clearParameters();
|
|
try
|
|
{
|
|
for(auto&& sensor : _available_ros_sensors)
|
|
{
|
|
sensor->stop();
|
|
}
|
|
}
|
|
catch(...){} // Not allowed to throw from Dtor
|
|
}
|
|
|
|
void BaseRealSenseNode::hardwareResetRequest()
|
|
{
|
|
ROS_ERROR_STREAM("Performing Hardware Reset.");
|
|
_dev.hardware_reset();
|
|
}
|
|
|
|
void BaseRealSenseNode::publishTopics()
|
|
{
|
|
getParameters();
|
|
setup();
|
|
ROS_INFO_STREAM("RealSense Node Is Up!");
|
|
}
|
|
|
|
void BaseRealSenseNode::initializeFormatsMaps()
|
|
{
|
|
// from rs2_format to OpenCV format
|
|
// https://docs.opencv.org/3.4/d1/d1b/group__core__hal__interface.html
|
|
// https://docs.opencv.org/2.4/modules/core/doc/basic_structures.html
|
|
// CV_<bit-depth>{U|S|F}C(<number_of_channels>)
|
|
// where U is unsigned integer type, S is signed integer type, and F is float type.
|
|
// For example, CV_8UC1 means a 8-bit single-channel array,
|
|
// CV_32FC2 means a 2-channel (complex) floating-point array, and so on.
|
|
_rs_format_to_cv_format[RS2_FORMAT_Y8] = CV_8UC1;
|
|
_rs_format_to_cv_format[RS2_FORMAT_Y16] = CV_16UC1;
|
|
_rs_format_to_cv_format[RS2_FORMAT_Z16] = CV_16UC1;
|
|
_rs_format_to_cv_format[RS2_FORMAT_RGB8] = CV_8UC3;
|
|
_rs_format_to_cv_format[RS2_FORMAT_BGR8] = CV_8UC3;
|
|
_rs_format_to_cv_format[RS2_FORMAT_RGBA8] = CV_8UC4;
|
|
_rs_format_to_cv_format[RS2_FORMAT_BGRA8] = CV_8UC4;
|
|
_rs_format_to_cv_format[RS2_FORMAT_YUYV] = CV_8UC2;
|
|
_rs_format_to_cv_format[RS2_FORMAT_UYVY] = CV_8UC2;
|
|
// _rs_format_to_cv_format[RS2_FORMAT_M420] = not supported yet in ROS2
|
|
_rs_format_to_cv_format[RS2_FORMAT_RAW8] = CV_8UC1;
|
|
_rs_format_to_cv_format[RS2_FORMAT_RAW10] = CV_16UC1;
|
|
_rs_format_to_cv_format[RS2_FORMAT_RAW16] = CV_16UC1;
|
|
|
|
// from rs2_format to ROS2 image msg encoding (format)
|
|
// http://docs.ros.org/en/noetic/api/sensor_msgs/html/msg/Image.html
|
|
// http://docs.ros.org/en/jade/api/sensor_msgs/html/image__encodings_8h_source.html
|
|
_rs_format_to_ros_format[RS2_FORMAT_Y8] = sensor_msgs::image_encodings::MONO8;
|
|
_rs_format_to_ros_format[RS2_FORMAT_Y16] = sensor_msgs::image_encodings::MONO16;
|
|
_rs_format_to_ros_format[RS2_FORMAT_Z16] = sensor_msgs::image_encodings::TYPE_16UC1;
|
|
_rs_format_to_ros_format[RS2_FORMAT_RGB8] = sensor_msgs::image_encodings::RGB8;
|
|
_rs_format_to_ros_format[RS2_FORMAT_BGR8] = sensor_msgs::image_encodings::BGR8;
|
|
_rs_format_to_ros_format[RS2_FORMAT_RGBA8] = sensor_msgs::image_encodings::RGBA8;
|
|
_rs_format_to_ros_format[RS2_FORMAT_BGRA8] = sensor_msgs::image_encodings::BGRA8;
|
|
_rs_format_to_ros_format[RS2_FORMAT_YUYV] = sensor_msgs::image_encodings::YUV422_YUY2;
|
|
_rs_format_to_ros_format[RS2_FORMAT_UYVY] = sensor_msgs::image_encodings::YUV422;
|
|
// _rs_format_to_ros_format[RS2_FORMAT_M420] = not supported yet in ROS2
|
|
_rs_format_to_ros_format[RS2_FORMAT_RAW8] = sensor_msgs::image_encodings::TYPE_8UC1;
|
|
_rs_format_to_ros_format[RS2_FORMAT_RAW10] = sensor_msgs::image_encodings::TYPE_16UC1;
|
|
_rs_format_to_ros_format[RS2_FORMAT_RAW16] = sensor_msgs::image_encodings::TYPE_16UC1;
|
|
}
|
|
|
|
void BaseRealSenseNode::setupFilters()
|
|
{
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::decimation_filter>(), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::hdr_merge>(), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::sequence_id_filter>(), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::disparity_transform>(), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::spatial_filter>(), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::temporal_filter>(), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::hole_filling_filter>(), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::disparity_transform>(false), _parameters, _logger));
|
|
_filters.push_back(std::make_shared<NamedFilter>(std::make_shared<rs2::rotation_filter>(std::vector< rs2_stream >{ RS2_STREAM_DEPTH, RS2_STREAM_COLOR, RS2_STREAM_INFRARED }), _parameters, _logger));
|
|
|
|
/*
|
|
update_align_depth_func is being used in the align depth filter for triggiring the thread that monitors profile
|
|
changes (_monitoring_pc) on every disable/enable of the align depth filter. This filter enablement/disablement affects
|
|
several topics creation/destruction, therefore, refreshing the topics is required similarly to what is done when turning on/off a sensor.
|
|
See BaseRealSenseNode::monitoringProfileChanges() as reference.
|
|
*/
|
|
std::function<void(const rclcpp::Parameter&)> update_align_depth_func = [this](const rclcpp::Parameter&){
|
|
{
|
|
std::lock_guard<std::mutex> lock_guard(_profile_changes_mutex);
|
|
_is_align_depth_changed = true;
|
|
}
|
|
_cv_mpc.notify_one();
|
|
};
|
|
|
|
#if defined (ACCELERATE_GPU_WITH_GLSL)
|
|
_colorizer_filter = std::make_shared<NamedFilter>(std::make_shared<rs2::gl::colorizer>(), _parameters, _logger);
|
|
_pc_filter = std::make_shared<PointcloudFilter>(std::make_shared<rs2::gl::pointcloud>(), _node, _parameters, _logger);
|
|
#else
|
|
_colorizer_filter = std::make_shared<NamedFilter>(std::make_shared<rs2::colorizer>(), _parameters, _logger);
|
|
_pc_filter = std::make_shared<PointcloudFilter>(std::make_shared<rs2::pointcloud>(), _node, _parameters, _logger);
|
|
#endif
|
|
|
|
// Apply PointCloud filter before applying Align-depth as it requires original depth image not aligned-depth image.
|
|
_filters.push_back(_pc_filter);
|
|
|
|
_align_depth_filter = std::make_shared<AlignDepthFilter>(std::make_shared<rs2::align>(RS2_STREAM_COLOR), update_align_depth_func, _parameters, _logger);
|
|
_filters.push_back(_align_depth_filter);
|
|
|
|
// Apply Colorizer filter after applying Align-Depth to get colorized aligned depth image.
|
|
_filters.push_back(_colorizer_filter);
|
|
}
|
|
|
|
cv::Mat& BaseRealSenseNode::fix_depth_scale(const cv::Mat& from_image, cv::Mat& to_image)
|
|
{
|
|
static const float meter_to_mm = 0.001f;
|
|
if (fabs(_depth_scale_meters - meter_to_mm) < 1e-6)
|
|
{
|
|
to_image = from_image;
|
|
return to_image;
|
|
}
|
|
|
|
if (to_image.size() != from_image.size())
|
|
{
|
|
to_image.create(from_image.rows, from_image.cols, from_image.type());
|
|
}
|
|
|
|
CV_Assert(CV_MAKETYPE(from_image.depth(),from_image.channels()) == _rs_format_to_cv_format[RS2_FORMAT_Z16]);
|
|
|
|
int nRows = from_image.rows;
|
|
int nCols = from_image.cols;
|
|
|
|
if (from_image.isContinuous())
|
|
{
|
|
nCols *= nRows;
|
|
nRows = 1;
|
|
}
|
|
|
|
int i,j;
|
|
const uint16_t* p_from;
|
|
uint16_t* p_to;
|
|
for( i = 0; i < nRows; ++i)
|
|
{
|
|
p_from = from_image.ptr<uint16_t>(i);
|
|
p_to = to_image.ptr<uint16_t>(i);
|
|
for ( j = 0; j < nCols; ++j)
|
|
{
|
|
p_to[j] = p_from[j] * _depth_scale_meters / meter_to_mm;
|
|
}
|
|
}
|
|
return to_image;
|
|
}
|
|
|
|
void BaseRealSenseNode::clip_depth(rs2::depth_frame depth_frame, float clipping_dist)
|
|
{
|
|
uint16_t* p_depth_frame = reinterpret_cast<uint16_t*>(const_cast<void*>(depth_frame.get_data()));
|
|
uint16_t clipping_value = static_cast<uint16_t>(clipping_dist / _depth_scale_meters);
|
|
|
|
int width = depth_frame.get_width();
|
|
int height = depth_frame.get_height();
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for schedule(dynamic) //Using OpenMP to try to parallelise the loop
|
|
#endif
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
auto depth_pixel_index = y * width;
|
|
for (int x = 0; x < width; x++, ++depth_pixel_index)
|
|
{
|
|
// Check if the depth value is greater than the threashold
|
|
if (p_depth_frame[depth_pixel_index] > clipping_value)
|
|
{
|
|
p_depth_frame[depth_pixel_index] = 0; //Set to invalid (<=0) value.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sensor_msgs::msg::Imu BaseRealSenseNode::CreateUnitedMessage(const CimuData accel_data, const CimuData gyro_data)
|
|
{
|
|
sensor_msgs::msg::Imu imu_msg;
|
|
rclcpp::Time t(gyro_data.m_time_ns); //rclcpp::Time(uint64_t nanoseconds)
|
|
imu_msg.header.stamp = t;
|
|
|
|
imu_msg.angular_velocity.x = gyro_data.m_data.x();
|
|
imu_msg.angular_velocity.y = gyro_data.m_data.y();
|
|
imu_msg.angular_velocity.z = gyro_data.m_data.z();
|
|
|
|
imu_msg.linear_acceleration.x = accel_data.m_data.x();
|
|
imu_msg.linear_acceleration.y = accel_data.m_data.y();
|
|
imu_msg.linear_acceleration.z = accel_data.m_data.z();
|
|
return imu_msg;
|
|
}
|
|
|
|
template <typename T> T lerp(const T &a, const T &b, const double t) {
|
|
return a * (1.0 - t) + b * t;
|
|
}
|
|
|
|
void BaseRealSenseNode::FillImuData_LinearInterpolation(const CimuData imu_data, std::deque<sensor_msgs::msg::Imu>& imu_msgs)
|
|
{
|
|
static std::deque<CimuData> _imu_history;
|
|
_imu_history.push_back(imu_data);
|
|
stream_index_pair type(imu_data.m_type);
|
|
imu_msgs.clear();
|
|
|
|
if ((type != ACCEL) || _imu_history.size() < 3)
|
|
return;
|
|
|
|
std::deque<CimuData> gyros_data;
|
|
CimuData accel0, accel1, crnt_imu;
|
|
|
|
while (_imu_history.size())
|
|
{
|
|
crnt_imu = _imu_history.front();
|
|
_imu_history.pop_front();
|
|
if (!accel0.is_set() && crnt_imu.m_type == ACCEL)
|
|
{
|
|
accel0 = crnt_imu;
|
|
}
|
|
else if (accel0.is_set() && crnt_imu.m_type == ACCEL)
|
|
{
|
|
accel1 = crnt_imu;
|
|
const double dt = accel1.m_time_ns - accel0.m_time_ns;
|
|
|
|
while (gyros_data.size())
|
|
{
|
|
CimuData crnt_gyro = gyros_data.front();
|
|
gyros_data.pop_front();
|
|
const double alpha = (crnt_gyro.m_time_ns - accel0.m_time_ns) / dt;
|
|
CimuData crnt_accel(ACCEL, lerp(accel0.m_data, accel1.m_data, alpha), crnt_gyro.m_time_ns);
|
|
imu_msgs.push_back(CreateUnitedMessage(crnt_accel, crnt_gyro));
|
|
}
|
|
accel0 = accel1;
|
|
}
|
|
else if (accel0.is_set() && crnt_imu.m_time_ns >= accel0.m_time_ns && crnt_imu.m_type == GYRO)
|
|
{
|
|
gyros_data.push_back(crnt_imu);
|
|
}
|
|
}
|
|
_imu_history.push_back(crnt_imu);
|
|
return;
|
|
}
|
|
|
|
void BaseRealSenseNode::FillImuData_Copy(const CimuData imu_data, std::deque<sensor_msgs::msg::Imu>& imu_msgs)
|
|
{
|
|
stream_index_pair type(imu_data.m_type);
|
|
|
|
static CimuData _accel_data(ACCEL, {0,0,0}, -1.0);
|
|
if (ACCEL == type)
|
|
{
|
|
_accel_data = imu_data;
|
|
return;
|
|
}
|
|
if (!_accel_data.is_set())
|
|
return;
|
|
|
|
imu_msgs.push_back(CreateUnitedMessage(_accel_data, imu_data));
|
|
}
|
|
|
|
void BaseRealSenseNode::ImuMessage_AddDefaultValues(sensor_msgs::msg::Imu& imu_msg)
|
|
{
|
|
imu_msg.header.frame_id = IMU_OPTICAL_FRAME_ID;
|
|
imu_msg.orientation.x = 0.0;
|
|
imu_msg.orientation.y = 0.0;
|
|
imu_msg.orientation.z = 0.0;
|
|
imu_msg.orientation.w = 0.0;
|
|
|
|
imu_msg.orientation_covariance = { -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
|
imu_msg.linear_acceleration_covariance = { _linear_accel_cov, 0.0, 0.0, 0.0, _linear_accel_cov, 0.0, 0.0, 0.0, _linear_accel_cov};
|
|
imu_msg.angular_velocity_covariance = { _angular_velocity_cov, 0.0, 0.0, 0.0, _angular_velocity_cov, 0.0, 0.0, 0.0, _angular_velocity_cov};
|
|
}
|
|
|
|
void BaseRealSenseNode::imu_callback_sync(rs2::frame frame, imu_sync_method sync_method)
|
|
{
|
|
static std::mutex m_mutex;
|
|
|
|
m_mutex.lock();
|
|
|
|
auto stream = frame.get_profile().stream_type();
|
|
auto stream_index = (stream == GYRO.first)?GYRO:ACCEL;
|
|
double frame_time = frame.get_timestamp();
|
|
|
|
bool placeholder_false(false);
|
|
if (_is_initialized_time_base.compare_exchange_strong(placeholder_false, true) )
|
|
{
|
|
_is_initialized_time_base = setBaseTime(frame_time, frame.get_frame_timestamp_domain());
|
|
}
|
|
|
|
if (_synced_imu_publisher && (0 != _synced_imu_publisher->getNumSubscribers()))
|
|
{
|
|
auto crnt_reading = *(reinterpret_cast<const float3*>(frame.get_data()));
|
|
Eigen::Vector3d v(crnt_reading.x, crnt_reading.y, crnt_reading.z);
|
|
CimuData imu_data(stream_index, v, frameSystemTimeSec(frame).nanoseconds());
|
|
std::deque<sensor_msgs::msg::Imu> imu_msgs;
|
|
switch (sync_method)
|
|
{
|
|
case imu_sync_method::COPY:
|
|
FillImuData_Copy(imu_data, imu_msgs);
|
|
break;
|
|
case imu_sync_method::LINEAR_INTERPOLATION:
|
|
FillImuData_LinearInterpolation(imu_data, imu_msgs);
|
|
break;
|
|
case imu_sync_method::NONE: //Cannot really be NONE. Just to avoid compilation warning.
|
|
throw std::runtime_error("sync_method in this section can be either COPY or LINEAR_INTERPOLATION");
|
|
break;
|
|
}
|
|
while (imu_msgs.size())
|
|
{
|
|
sensor_msgs::msg::Imu imu_msg = imu_msgs.front();
|
|
ImuMessage_AddDefaultValues(imu_msg);
|
|
_synced_imu_publisher->Publish(imu_msg);
|
|
ROS_DEBUG("Publish united %s stream", rs2_stream_to_string(frame.get_profile().stream_type()));
|
|
imu_msgs.pop_front();
|
|
}
|
|
}
|
|
m_mutex.unlock();
|
|
}
|
|
|
|
void BaseRealSenseNode::imu_callback(rs2::frame frame)
|
|
{
|
|
auto stream = frame.get_profile().stream_type();
|
|
double frame_time = frame.get_timestamp();
|
|
bool placeholder_false(false);
|
|
if (_is_initialized_time_base.compare_exchange_strong(placeholder_false, true) )
|
|
{
|
|
_is_initialized_time_base = setBaseTime(frame_time, frame.get_frame_timestamp_domain());
|
|
}
|
|
|
|
ROS_DEBUG("Frame arrived: stream: %s ; index: %d ; Timestamp Domain: %s",
|
|
ros_stream_to_string(frame.get_profile().stream_type()).c_str(),
|
|
frame.get_profile().stream_index(),
|
|
rs2_timestamp_domain_to_string(frame.get_frame_timestamp_domain()));
|
|
|
|
stream_index_pair stream_index;
|
|
|
|
if(stream == GYRO.first)
|
|
{
|
|
stream_index = GYRO;
|
|
}
|
|
else if(stream == ACCEL.first)
|
|
{
|
|
stream_index = ACCEL;
|
|
}
|
|
else if(stream == MOTION.first)
|
|
{
|
|
stream_index = MOTION;
|
|
}
|
|
else
|
|
{
|
|
ROS_ERROR("Unknown IMU stream type.");
|
|
return;
|
|
}
|
|
|
|
rclcpp::Time t(frameSystemTimeSec(frame));
|
|
|
|
if(_imu_publishers.find(stream_index) == _imu_publishers.end())
|
|
{
|
|
ROS_DEBUG("Received IMU callback while topic does not exist");
|
|
return;
|
|
}
|
|
|
|
if (0 != _imu_publishers[stream_index]->get_subscription_count())
|
|
{
|
|
auto imu_msg = sensor_msgs::msg::Imu();
|
|
ImuMessage_AddDefaultValues(imu_msg);
|
|
imu_msg.header.frame_id = OPTICAL_FRAME_ID(stream_index);
|
|
|
|
if (MOTION == stream_index)
|
|
{
|
|
auto combined_motion_data = frame.as<rs2::motion_frame>().get_combined_motion_data();
|
|
|
|
imu_msg.linear_acceleration.x = combined_motion_data.linear_acceleration.x;
|
|
imu_msg.linear_acceleration.y = combined_motion_data.linear_acceleration.y;
|
|
imu_msg.linear_acceleration.z = combined_motion_data.linear_acceleration.z;
|
|
|
|
imu_msg.angular_velocity.x = combined_motion_data.angular_velocity.x;
|
|
imu_msg.angular_velocity.y = combined_motion_data.angular_velocity.y;
|
|
imu_msg.angular_velocity.z = combined_motion_data.angular_velocity.z;
|
|
|
|
imu_msg.orientation.x = combined_motion_data.orientation.x;
|
|
imu_msg.orientation.y = combined_motion_data.orientation.y;
|
|
imu_msg.orientation.z = combined_motion_data.orientation.z;
|
|
imu_msg.orientation.w = combined_motion_data.orientation.w;
|
|
|
|
}
|
|
else
|
|
{
|
|
auto motion_data = frame.as<rs2::motion_frame>().get_motion_data();
|
|
if (GYRO == stream_index)
|
|
{
|
|
imu_msg.angular_velocity.x = motion_data.x;
|
|
imu_msg.angular_velocity.y = motion_data.y;
|
|
imu_msg.angular_velocity.z = motion_data.z;
|
|
}
|
|
else // ACCEL == stream_index
|
|
{
|
|
imu_msg.linear_acceleration.x = motion_data.x;
|
|
imu_msg.linear_acceleration.y = motion_data.y;
|
|
imu_msg.linear_acceleration.z = motion_data.z;
|
|
}
|
|
}
|
|
|
|
imu_msg.header.stamp = t;
|
|
_imu_publishers[stream_index]->publish(imu_msg);
|
|
ROS_DEBUG("Publish %s stream", ros_stream_to_string(frame.get_profile().stream_type()).c_str());
|
|
}
|
|
publishMetadata(frame, t, OPTICAL_FRAME_ID(stream_index));
|
|
}
|
|
|
|
|
|
void BaseRealSenseNode::frame_callback(rs2::frame frame)
|
|
{
|
|
if (_synced_imu_publisher)
|
|
_synced_imu_publisher->Pause();
|
|
double frame_time = frame.get_timestamp();
|
|
|
|
// We compute a ROS timestamp which is based on an initial ROS time at point of first frame,
|
|
// and the incremental timestamp from the camera.
|
|
// In sync mode the timestamp is based on ROS time
|
|
bool placeholder_false(false);
|
|
if (_is_initialized_time_base.compare_exchange_strong(placeholder_false, true) )
|
|
{
|
|
_is_initialized_time_base = setBaseTime(frame_time, frame.get_frame_timestamp_domain());
|
|
}
|
|
|
|
rclcpp::Time t(frameSystemTimeSec(frame));
|
|
if (frame.is<rs2::frameset>())
|
|
{
|
|
ROS_DEBUG("Frameset arrived.");
|
|
auto frameset = frame.as<rs2::frameset>();
|
|
ROS_DEBUG("List of frameset before applying filters: size: %d", static_cast<int>(frameset.size()));
|
|
for (auto it = frameset.begin(); it != frameset.end(); ++it)
|
|
{
|
|
auto f = (*it);
|
|
auto stream_type = f.get_profile().stream_type();
|
|
auto stream_index = f.get_profile().stream_index();
|
|
auto stream_format = f.get_profile().format();
|
|
auto stream_unique_id = f.get_profile().unique_id();
|
|
|
|
ROS_DEBUG("Frameset contain (%s, %d, %s %d) frame. frame_number: %llu ; frame_TS: %f ; ros_TS(NSec): %lu",
|
|
rs2_stream_to_string(stream_type), stream_index, rs2_format_to_string(stream_format), stream_unique_id, frame.get_frame_number(), frame_time, t.nanoseconds());
|
|
}
|
|
// Clip depth_frame for max range:
|
|
rs2::depth_frame original_depth_frame = frameset.get_depth_frame();
|
|
if (original_depth_frame && _clipping_distance > 0)
|
|
{
|
|
clip_depth(original_depth_frame, _clipping_distance);
|
|
}
|
|
|
|
rs2::video_frame original_color_frame = frameset.get_color_frame();
|
|
rs2::video_frame original_infra2_frame = frameset.get_infrared_frame(2);
|
|
|
|
ROS_DEBUG("num_filters: %d", static_cast<int>(_filters.size()));
|
|
for (auto filter_it : _filters)
|
|
{
|
|
frameset = filter_it->Process(frameset);
|
|
}
|
|
|
|
ROS_DEBUG("List of frameset after applying filters: size: %d", static_cast<int>(frameset.size()));
|
|
bool sent_depth_frame(false);
|
|
for (auto it = frameset.begin(); it != frameset.end(); ++it)
|
|
{
|
|
auto f = (*it);
|
|
auto stream_type = f.get_profile().stream_type();
|
|
auto stream_index = f.get_profile().stream_index();
|
|
auto stream_format = f.get_profile().format();
|
|
stream_index_pair sip{stream_type,stream_index};
|
|
|
|
ROS_DEBUG("Frameset contain (%s, %d, %s) frame. frame_number: %llu ; frame_TS: %f ; ros_TS(NSec): %lu",
|
|
rs2_stream_to_string(stream_type), stream_index, rs2_format_to_string(stream_format), f.get_frame_number(), frame_time, t.nanoseconds());
|
|
if (f.is<rs2::video_frame>())
|
|
ROS_DEBUG_STREAM("frame: " << f.as<rs2::video_frame>().get_width() << " x " << f.as<rs2::video_frame>().get_height());
|
|
|
|
if (f.is<rs2::labeled_points>())
|
|
{
|
|
publishLabeledPointCloud(f.as<rs2::labeled_points>(), t);
|
|
publishMetadata(f, t, OPTICAL_FRAME_ID(sip));
|
|
}
|
|
else if (f.is<rs2::points>())
|
|
{
|
|
publishPointCloud(f.as<rs2::points>(), t, frameset);
|
|
}
|
|
else if(stream_type == RS2_STREAM_OCCUPANCY)
|
|
{
|
|
publishOccupancyFrame(f, t);
|
|
}
|
|
else
|
|
{
|
|
if (stream_type == RS2_STREAM_DEPTH)
|
|
{
|
|
if (sent_depth_frame) continue;
|
|
sent_depth_frame = true;
|
|
if (original_color_frame && _align_depth_filter->is_enabled())
|
|
{
|
|
publishFrame(f, t, COLOR, _depth_aligned_image, _depth_aligned_info_publisher, _depth_aligned_image_publishers, false);
|
|
continue;
|
|
}
|
|
if (original_infra2_frame && _align_depth_filter->is_enabled())
|
|
{
|
|
publishFrame(f, t, INFRA2, _depth_aligned_image, _depth_aligned_info_publisher, _depth_aligned_image_publishers, false);
|
|
continue;
|
|
}
|
|
}
|
|
publishFrame(f, t, sip, _images, _info_publishers, _image_publishers);
|
|
}
|
|
}
|
|
if (original_depth_frame && _align_depth_filter->is_enabled())
|
|
{
|
|
rs2::frame frame_to_send;
|
|
if (_colorizer_filter->is_enabled())
|
|
frame_to_send = _colorizer_filter->Process(original_depth_frame);
|
|
else
|
|
frame_to_send = original_depth_frame;
|
|
publishFrame(frame_to_send, t, DEPTH, _images, _info_publishers, _image_publishers);
|
|
|
|
// Publish RGBD only if rgbd enabled and both depth and color frames exist.
|
|
// On this line we already know original_depth_frame is valid.
|
|
if(_enable_rgbd && original_color_frame)
|
|
{
|
|
auto color_format = original_color_frame.get_profile().format();
|
|
auto depth_format = original_depth_frame.get_profile().format();
|
|
publishRGBD(_images[COLOR], color_format, _depth_aligned_image[COLOR], depth_format, t);
|
|
}
|
|
}
|
|
}
|
|
else if (frame.is<rs2::video_frame>())
|
|
{
|
|
auto stream_type = frame.get_profile().stream_type();
|
|
auto stream_index = frame.get_profile().stream_index();
|
|
ROS_DEBUG("Single video frame arrived (%s, %d). frame_number: %llu ; frame_TS: %f ; ros_TS(NSec): %lu",
|
|
rs2_stream_to_string(stream_type), stream_index, frame.get_frame_number(), frame_time, t.nanoseconds());
|
|
|
|
stream_index_pair sip{stream_type,stream_index};
|
|
if(stream_type == RS2_STREAM_OCCUPANCY)
|
|
{
|
|
publishOccupancyFrame(frame, t);
|
|
}
|
|
else
|
|
{
|
|
if (frame.is<rs2::depth_frame>())
|
|
{
|
|
if (_clipping_distance > 0)
|
|
{
|
|
clip_depth(frame, _clipping_distance);
|
|
}
|
|
}
|
|
publishFrame(frame, t, sip, _images, _info_publishers, _image_publishers);
|
|
}
|
|
}
|
|
else if (frame.is<rs2::labeled_points>())
|
|
{
|
|
auto stream_type = frame.get_profile().stream_type();
|
|
auto stream_index = frame.get_profile().stream_index();
|
|
stream_index_pair sip{stream_type,stream_index};
|
|
ROS_DEBUG("Single labeled point cloud frame arrived (%s, %d). frame_number: %llu ; frame_TS: %f ; ros_TS(NSec): %lu",
|
|
rs2_stream_to_string(stream_type), stream_index, frame.get_frame_number(), frame_time, t.nanoseconds());
|
|
publishLabeledPointCloud(frame.as<rs2::labeled_points>(), t);
|
|
publishMetadata(frame, t, OPTICAL_FRAME_ID(sip));
|
|
}
|
|
if (_synced_imu_publisher)
|
|
_synced_imu_publisher->Resume();
|
|
} // frame_callback
|
|
|
|
void BaseRealSenseNode::multiple_message_callback(rs2::frame frame, imu_sync_method sync_method)
|
|
{
|
|
auto stream = frame.get_profile().stream_type();
|
|
switch (stream)
|
|
{
|
|
case RS2_STREAM_GYRO:
|
|
case RS2_STREAM_ACCEL:
|
|
if (sync_method > imu_sync_method::NONE) imu_callback_sync(frame, sync_method);
|
|
else imu_callback(frame);
|
|
break;
|
|
default:
|
|
frame_callback(frame);
|
|
}
|
|
}
|
|
|
|
bool BaseRealSenseNode::setBaseTime(double frame_time, rs2_timestamp_domain time_domain)
|
|
{
|
|
if (time_domain == RS2_TIMESTAMP_DOMAIN_SYSTEM_TIME)
|
|
{
|
|
ROS_WARN_ONCE("Frame metadata isn't available! (frame_timestamp_domain = RS2_TIMESTAMP_DOMAIN_SYSTEM_TIME)");
|
|
}
|
|
|
|
if (time_domain == RS2_TIMESTAMP_DOMAIN_HARDWARE_CLOCK)
|
|
{
|
|
ROS_WARN("frame's time domain is HARDWARE_CLOCK. Timestamps may reset periodically.");
|
|
_ros_time_base = _node.now();
|
|
_camera_time_base = frame_time;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint64_t BaseRealSenseNode::millisecondsToNanoseconds(double timestamp_ms)
|
|
{
|
|
// modf breaks input into an integral and fractional part
|
|
double int_part_ms, fract_part_ms;
|
|
fract_part_ms = modf(timestamp_ms, &int_part_ms);
|
|
|
|
//convert both parts to ns
|
|
static constexpr uint64_t milli_to_nano = 1000000;
|
|
uint64_t int_part_ns = static_cast<uint64_t>(int_part_ms) * milli_to_nano;
|
|
uint64_t fract_part_ns = static_cast<uint64_t>(std::round(fract_part_ms * milli_to_nano));
|
|
|
|
return int_part_ns + fract_part_ns;
|
|
}
|
|
|
|
rclcpp::Time BaseRealSenseNode::frameSystemTimeSec(rs2::frame frame)
|
|
{
|
|
double timestamp_ms = frame.get_timestamp();
|
|
if (frame.get_frame_timestamp_domain() == RS2_TIMESTAMP_DOMAIN_HARDWARE_CLOCK)
|
|
{
|
|
double elapsed_camera_ns = millisecondsToNanoseconds(timestamp_ms - _camera_time_base);
|
|
|
|
/*
|
|
Fixing deprecated-declarations compilation error for EOL distro (foxy)
|
|
*/
|
|
#if defined(FOXY)
|
|
auto duration = rclcpp::Duration(elapsed_camera_ns);
|
|
#else
|
|
auto duration = rclcpp::Duration::from_nanoseconds(elapsed_camera_ns);
|
|
#endif
|
|
|
|
return rclcpp::Time(_ros_time_base + duration);
|
|
}
|
|
else
|
|
{
|
|
return rclcpp::Time(millisecondsToNanoseconds(timestamp_ms));
|
|
}
|
|
}
|
|
|
|
void BaseRealSenseNode::updateProfilesStreamCalibData(const std::vector<rs2::stream_profile>& profiles)
|
|
{
|
|
std::shared_ptr<rs2::stream_profile> left_profile;
|
|
std::shared_ptr<rs2::stream_profile> right_profile;
|
|
for (auto& profile : profiles)
|
|
{
|
|
if (profile.is<rs2::video_stream_profile>())
|
|
{
|
|
updateStreamCalibData(profile.as<rs2::video_stream_profile>());
|
|
|
|
// stream index: 1=left, 2=right
|
|
if (profile.stream_index() == 1) { left_profile = std::make_shared<rs2::stream_profile>(profile); }
|
|
if (profile.stream_index() == 2) { right_profile = std::make_shared<rs2::stream_profile>(profile); }
|
|
}
|
|
}
|
|
if (left_profile && right_profile) {
|
|
updateExtrinsicsCalibData(left_profile->as<rs2::video_stream_profile>(), right_profile->as<rs2::video_stream_profile>());
|
|
}
|
|
}
|
|
|
|
void BaseRealSenseNode::updateStreamCalibData(const rs2::video_stream_profile& video_profile)
|
|
{
|
|
stream_index_pair stream_index{video_profile.stream_type(), video_profile.stream_index()};
|
|
|
|
rs2_intrinsics intrinsic;
|
|
try
|
|
{
|
|
intrinsic = video_profile.get_intrinsics();
|
|
}
|
|
catch(const std::exception& ex)
|
|
{
|
|
// e.g. infra1/infra2 in Y16i format (calibration mode) doesn't have intrinsics.
|
|
ROS_WARN_STREAM("No intrinsics available for this stream profile. Using zeroed intrinsics as default.");
|
|
intrinsic = { 0, 0, 0, 0, 0, 0, RS2_DISTORTION_NONE ,{ 0,0,0,0,0 } };
|
|
}
|
|
|
|
_camera_info[stream_index].width = intrinsic.width;
|
|
_camera_info[stream_index].height = intrinsic.height;
|
|
_camera_info[stream_index].header.frame_id = OPTICAL_FRAME_ID(stream_index);
|
|
|
|
_camera_info[stream_index].k.at(0) = intrinsic.fx;
|
|
_camera_info[stream_index].k.at(2) = intrinsic.ppx;
|
|
_camera_info[stream_index].k.at(4) = intrinsic.fy;
|
|
_camera_info[stream_index].k.at(5) = intrinsic.ppy;
|
|
_camera_info[stream_index].k.at(8) = 1;
|
|
|
|
_camera_info[stream_index].p.at(0) = _camera_info[stream_index].k.at(0);
|
|
_camera_info[stream_index].p.at(1) = 0;
|
|
_camera_info[stream_index].p.at(2) = _camera_info[stream_index].k.at(2);
|
|
_camera_info[stream_index].p.at(3) = 0;
|
|
_camera_info[stream_index].p.at(4) = 0;
|
|
_camera_info[stream_index].p.at(5) = _camera_info[stream_index].k.at(4);
|
|
_camera_info[stream_index].p.at(6) = _camera_info[stream_index].k.at(5);
|
|
_camera_info[stream_index].p.at(7) = 0;
|
|
_camera_info[stream_index].p.at(8) = 0;
|
|
_camera_info[stream_index].p.at(9) = 0;
|
|
_camera_info[stream_index].p.at(10) = 1;
|
|
_camera_info[stream_index].p.at(11) = 0;
|
|
|
|
// set R (rotation matrix) values to identity matrix
|
|
_camera_info[stream_index].r.at(0) = 1.0;
|
|
_camera_info[stream_index].r.at(1) = 0.0;
|
|
_camera_info[stream_index].r.at(2) = 0.0;
|
|
_camera_info[stream_index].r.at(3) = 0.0;
|
|
_camera_info[stream_index].r.at(4) = 1.0;
|
|
_camera_info[stream_index].r.at(5) = 0.0;
|
|
_camera_info[stream_index].r.at(6) = 0.0;
|
|
_camera_info[stream_index].r.at(7) = 0.0;
|
|
_camera_info[stream_index].r.at(8) = 1.0;
|
|
|
|
int coeff_size(5);
|
|
if (intrinsic.model == RS2_DISTORTION_KANNALA_BRANDT4)
|
|
{
|
|
_camera_info[stream_index].distortion_model = "equidistant";
|
|
coeff_size = 4;
|
|
} else {
|
|
_camera_info[stream_index].distortion_model = "plumb_bob";
|
|
}
|
|
|
|
_camera_info[stream_index].d.resize(coeff_size);
|
|
for (int i = 0; i < coeff_size; i++)
|
|
{
|
|
_camera_info[stream_index].d.at(i) = intrinsic.coeffs[i];
|
|
}
|
|
|
|
if (stream_index == DEPTH && _enable[DEPTH] && _enable[COLOR])
|
|
{
|
|
_camera_info[stream_index].p.at(3) = 0; // Tx
|
|
_camera_info[stream_index].p.at(7) = 0; // Ty
|
|
}
|
|
}
|
|
|
|
void BaseRealSenseNode::updateExtrinsicsCalibData(const rs2::video_stream_profile& left_video_profile, const rs2::video_stream_profile& right_video_profile)
|
|
{
|
|
stream_index_pair left{left_video_profile.stream_type(), left_video_profile.stream_index()};
|
|
stream_index_pair right{right_video_profile.stream_type(), right_video_profile.stream_index()};
|
|
|
|
float fx = _camera_info[right].k.at(0);
|
|
float fy = _camera_info[right].k.at(4);
|
|
const auto& ex = right_video_profile.get_extrinsics_to(left_video_profile);
|
|
_camera_info[right].header.frame_id = OPTICAL_FRAME_ID(left);
|
|
_camera_info[right].p.at(3) = -fx * ex.translation[0] + 0.0; // Tx - avoid -0.0 values.
|
|
_camera_info[right].p.at(7) = -fy * ex.translation[1] + 0.0; // Ty - avoid -0.0 values.
|
|
}
|
|
|
|
void BaseRealSenseNode::SetBaseStream()
|
|
{
|
|
const std::vector<stream_index_pair> base_stream_priority = {DEPTH};
|
|
std::set<stream_index_pair> checked_sips;
|
|
std::map<stream_index_pair, rs2::stream_profile> available_profiles;
|
|
for(auto&& sensor : _available_ros_sensors)
|
|
{
|
|
for (auto& profile : sensor->get_stream_profiles())
|
|
{
|
|
stream_index_pair sip(profile.stream_type(), profile.stream_index());
|
|
if (available_profiles.find(sip) != available_profiles.end())
|
|
continue;
|
|
available_profiles[sip] = profile;
|
|
}
|
|
}
|
|
|
|
std::vector<stream_index_pair>::const_iterator base_stream(base_stream_priority.begin());
|
|
while((base_stream != base_stream_priority.end()) && (available_profiles.find(*base_stream) == available_profiles.end()))
|
|
{
|
|
base_stream++;
|
|
}
|
|
if (base_stream == base_stream_priority.end())
|
|
{
|
|
throw std::runtime_error("No known base_stream found for transformations.");
|
|
}
|
|
ROS_DEBUG_STREAM("SELECTED BASE:" << base_stream->first << ", " << base_stream->second);
|
|
|
|
_base_profile = available_profiles[*base_stream];
|
|
}
|
|
|
|
void BaseRealSenseNode::publishPointCloud(rs2::points pc, const rclcpp::Time& t, const rs2::frameset& frameset)
|
|
{
|
|
std::string frame_id = OPTICAL_FRAME_ID(DEPTH);
|
|
_pc_filter->Publish(pc, t, frameset, frame_id);
|
|
}
|
|
|
|
bool BaseRealSenseNode::shouldPublishCameraInfo(const stream_index_pair& sip)
|
|
{
|
|
const rs2_stream stream = sip.first;
|
|
return (stream != RS2_STREAM_SAFETY && stream != RS2_STREAM_OCCUPANCY && stream != RS2_STREAM_LABELED_POINT_CLOUD);
|
|
}
|
|
|
|
void BaseRealSenseNode::publishOccupancyFrame(rs2::frame f, const rclcpp::Time& t)
|
|
{
|
|
if(!_occupancy_publisher || 0 == _occupancy_publisher->get_subscription_count())
|
|
return;
|
|
|
|
ROS_DEBUG("Publishing Occupancy GridCells Frame");
|
|
|
|
// get frame bytes and frame metadata relevant info
|
|
auto frame_as_uint8_arr = (uint8_t*)f.get_data();
|
|
auto cols = static_cast<int>(f.get_frame_metadata(RS2_FRAME_METADATA_OCCUPANCY_GRID_COLUMNS)); // grid cells width
|
|
auto rows = static_cast<int>(f.get_frame_metadata(RS2_FRAME_METADATA_OCCUPANCY_GRID_ROWS)); // grid cells height
|
|
auto cell_size = static_cast<float>(f.get_frame_metadata(RS2_FRAME_METADATA_OCCUPANCY_CELL_SIZE) / 100.0f); // convert to meters
|
|
|
|
// create GridCells msg and start filling it
|
|
nav_msgs::msg::GridCells msg;
|
|
msg.header.stamp = t;
|
|
msg.header.frame_id = FRAME_ID(OCCUPANCY);
|
|
msg.cell_width = cell_size;
|
|
msg.cell_height = cell_size;
|
|
|
|
for (auto i = 0; i < cols * rows; ++i)
|
|
{
|
|
// AICV algo is packing each 8 cells into one byte. Each byte include 8 bits <--> 8 cells
|
|
// The rightest bit (LSB) inside the packed byte from AICV algo represnts the closest cell we want to work with in the grid.
|
|
// e.g. Original Occupancy Cells: 0 0 1 1 0 0 1 0 ---> AICV packing algo ---> 01001100 (not the opposite order)
|
|
// In this if we check if current cell is occupied.
|
|
// Note that we start working from the most left bit, aka, the farest point of the grid.
|
|
if ((frame_as_uint8_arr[i / 8U] & (1U << i % 8)) != 0)
|
|
{
|
|
// Find x,y,z positions of current index
|
|
// Remember, in ROS CS: (X: Forward, Y: Left, Z: Up)
|
|
geometry_msgs::msg::Point p3d;
|
|
uint32_t row = (i / cols);
|
|
uint32_t col = (i % cols);
|
|
p3d.x = (cell_size * static_cast<float>(rows)) - cell_size * (static_cast<float>(row) + 0.5f);
|
|
p3d.y = (cell_size * static_cast<float>(cols)) / 2 - cell_size * (static_cast<float>(col) + 0.5f);
|
|
p3d.z = 0;
|
|
msg.cells.push_back(p3d);
|
|
}
|
|
}
|
|
_occupancy_publisher->publish(msg);
|
|
}
|
|
|
|
void BaseRealSenseNode::publishLabeledPointCloud(rs2::labeled_points lpc, const rclcpp::Time& t)
|
|
{
|
|
if(!_labeled_pointcloud_publisher || 0 == _labeled_pointcloud_publisher->get_subscription_count())
|
|
return;
|
|
|
|
ROS_DEBUG("Publishing Labeled Point Cloud Frame");
|
|
|
|
// Create the PointCloud message
|
|
sensor_msgs::msg::PointCloud2::UniquePtr msg_pointcloud = std::make_unique<sensor_msgs::msg::PointCloud2>();
|
|
|
|
// Define the fields of the PointCloud message
|
|
sensor_msgs::PointCloud2Modifier modifier(*msg_pointcloud);
|
|
|
|
modifier.setPointCloud2Fields(4, "x", 1, sensor_msgs::msg::PointField::FLOAT32,
|
|
"y", 1, sensor_msgs::msg::PointField::FLOAT32,
|
|
"z", 1, sensor_msgs::msg::PointField::FLOAT32,
|
|
"label", 1, sensor_msgs::msg::PointField::UINT8);
|
|
modifier.resize(lpc.size());
|
|
|
|
// Fill the PointCloud message with data
|
|
sensor_msgs::PointCloud2Iterator<float> iter_x(*msg_pointcloud, "x");
|
|
sensor_msgs::PointCloud2Iterator<float> iter_y(*msg_pointcloud, "y");
|
|
sensor_msgs::PointCloud2Iterator<float> iter_z(*msg_pointcloud, "z");
|
|
sensor_msgs::PointCloud2Iterator<uint8_t> iter_label(*msg_pointcloud, "label");
|
|
const rs2::vertex* vertex = lpc.get_vertices();
|
|
const uint8_t* label = lpc.get_labels();
|
|
|
|
msg_pointcloud->width = lpc.get_width();
|
|
msg_pointcloud->height = lpc.get_height();
|
|
msg_pointcloud->point_step = lpc.get_bits_per_pixel() / 8;
|
|
msg_pointcloud->row_step = msg_pointcloud->width * msg_pointcloud->point_step;
|
|
msg_pointcloud->data.resize(msg_pointcloud->height * msg_pointcloud->row_step);
|
|
|
|
for (size_t point_idx=0; point_idx < lpc.size(); point_idx++, vertex++, label++)
|
|
{
|
|
*iter_x = vertex->x;
|
|
*iter_y = vertex->y;
|
|
*iter_z = vertex->z;
|
|
*iter_label = *label;
|
|
++iter_x; ++iter_y; ++iter_z; ++iter_label;
|
|
}
|
|
|
|
msg_pointcloud->header.stamp = t;
|
|
msg_pointcloud->header.frame_id = FRAME_ID(LABELED_POINT_CLOUD);
|
|
|
|
// Publish the PointCloud message
|
|
_labeled_pointcloud_publisher->publish(std::move(msg_pointcloud));
|
|
}
|
|
|
|
|
|
Extrinsics BaseRealSenseNode::rsExtrinsicsToMsg(const rs2_extrinsics& extrinsics) const
|
|
{
|
|
Extrinsics extrinsicsMsg;
|
|
for (int i = 0; i < 9; ++i)
|
|
{
|
|
extrinsicsMsg.rotation[i] = extrinsics.rotation[i];
|
|
if (i < 3)
|
|
extrinsicsMsg.translation[i] = extrinsics.translation[i];
|
|
}
|
|
return extrinsicsMsg;
|
|
}
|
|
|
|
IMUInfo BaseRealSenseNode::getImuInfo(const rs2::stream_profile& profile)
|
|
{
|
|
IMUInfo info{};
|
|
auto sp = profile.as<rs2::motion_stream_profile>();
|
|
rs2_motion_device_intrinsic imuIntrinsics;
|
|
try
|
|
{
|
|
imuIntrinsics = sp.get_motion_intrinsics();
|
|
}
|
|
catch(const std::runtime_error &ex)
|
|
{
|
|
ROS_DEBUG_STREAM("No Motion Intrinsics available.");
|
|
imuIntrinsics = {{{1,0,0,0},{0,1,0,0},{0,0,1,0}}, {0,0,0}, {0,0,0}};
|
|
}
|
|
|
|
auto index = 0;
|
|
stream_index_pair sip(profile.stream_type(), profile.stream_index());
|
|
info.header.frame_id = OPTICAL_FRAME_ID(sip);
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
for (int j = 0; j < 4; ++j)
|
|
{
|
|
info.data[index] = imuIntrinsics.data[i][j];
|
|
++index;
|
|
}
|
|
info.noise_variances[i] = imuIntrinsics.noise_variances[i];
|
|
info.bias_variances[i] = imuIntrinsics.bias_variances[i];
|
|
}
|
|
return info;
|
|
}
|
|
|
|
bool BaseRealSenseNode::fillROSImageMsgAndReturnStatus(
|
|
const cv::Mat& cv_matrix_image,
|
|
const stream_index_pair& stream,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
const rs2_format& stream_format,
|
|
const rclcpp::Time& t,
|
|
sensor_msgs::msg::Image* img_msg_ptr)
|
|
{
|
|
if (cv_matrix_image.empty())
|
|
{
|
|
ROS_ERROR_STREAM("cv::Mat is empty. Ignoring this frame.");
|
|
return false;
|
|
}
|
|
else if (_rs_format_to_ros_format.find(stream_format) == _rs_format_to_ros_format.end())
|
|
{
|
|
ROS_ERROR_STREAM("Format " << rs2_format_to_string(stream_format) << " is not supported in ROS2 image messages"
|
|
<< "Please try different format of this stream.");
|
|
return false;
|
|
}
|
|
// Convert the CV::Mat into a ROS image message (1 copy is done here)
|
|
cv_bridge::CvImage(std_msgs::msg::Header(), _rs_format_to_ros_format[stream_format], cv_matrix_image).toImageMsg(*img_msg_ptr);
|
|
|
|
// Convert OpenCV Mat to ROS Image
|
|
img_msg_ptr->header.frame_id = OPTICAL_FRAME_ID(stream);
|
|
img_msg_ptr->header.stamp = t;
|
|
img_msg_ptr->height = height;
|
|
img_msg_ptr->width = width;
|
|
img_msg_ptr->is_bigendian = false;
|
|
img_msg_ptr->step = width * cv_matrix_image.elemSize();
|
|
return true;
|
|
}
|
|
|
|
bool BaseRealSenseNode::fillCVMatImageAndReturnStatus(
|
|
rs2::frame& frame,
|
|
std::map<stream_index_pair, cv::Mat>& images,
|
|
unsigned int width,
|
|
unsigned int height,
|
|
const stream_index_pair& stream)
|
|
{
|
|
auto& image = images[stream];
|
|
auto stream_format = frame.get_profile().format();
|
|
|
|
if (_rs_format_to_cv_format.find(stream_format) == _rs_format_to_cv_format.end())
|
|
{
|
|
ROS_ERROR_STREAM("Format " << rs2_format_to_string(stream_format) << " is not supported in realsense2_camera node."
|
|
<< "\nPlease try different format of this stream.");
|
|
return false;
|
|
}
|
|
// we try to reduce image creation as much we can, so we check if the same image structure
|
|
// was already created before, and we fill this image next with the frame data
|
|
// image.create() should be called once per <stream>_<profile>_<format>
|
|
if (image.size() != cv::Size(width, height) || CV_MAKETYPE(image.depth(), image.channels()) != _rs_format_to_cv_format[stream_format])
|
|
{
|
|
image.create(height, width, _rs_format_to_cv_format[stream_format]);
|
|
}
|
|
|
|
image.data = (uint8_t*)frame.get_data();
|
|
|
|
if (frame.is<rs2::depth_frame>())
|
|
{
|
|
image = fix_depth_scale(image, _depth_scaled_image[stream]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void BaseRealSenseNode::publishFrame(
|
|
rs2::frame f,
|
|
const rclcpp::Time& t,
|
|
const stream_index_pair& stream,
|
|
std::map<stream_index_pair, cv::Mat>& images,
|
|
const std::map<stream_index_pair, rclcpp::Publisher<sensor_msgs::msg::CameraInfo>::SharedPtr>& info_publishers,
|
|
const std::map<stream_index_pair, std::shared_ptr<image_publisher>>& image_publishers,
|
|
const bool is_publishMetadata)
|
|
{
|
|
ROS_DEBUG("publishFrame(...)");
|
|
unsigned int width = 0;
|
|
unsigned int height = 0;
|
|
auto stream_format = RS2_FORMAT_ANY;
|
|
if (f.is<rs2::video_frame>())
|
|
{
|
|
auto timage = f.as<rs2::video_frame>();
|
|
if(stream.first == RS2_STREAM_OCCUPANCY)
|
|
{
|
|
if (!f.supports_frame_metadata(RS2_FRAME_METADATA_OCCUPANCY_GRID_ROWS) ||
|
|
!f.supports_frame_metadata(RS2_FRAME_METADATA_OCCUPANCY_GRID_COLUMNS))
|
|
throw std::runtime_error("Occupancy rows / columns could not be read from frame metadata");
|
|
|
|
width = static_cast<int>(f.get_frame_metadata(RS2_FRAME_METADATA_OCCUPANCY_GRID_COLUMNS));
|
|
height = static_cast<int>(f.get_frame_metadata(RS2_FRAME_METADATA_OCCUPANCY_GRID_ROWS));
|
|
}
|
|
else
|
|
{
|
|
width = timage.get_width();
|
|
height = timage.get_height();
|
|
}
|
|
stream_format = timage.get_profile().format();
|
|
}
|
|
else
|
|
{
|
|
ROS_ERROR("f.is<rs2::video_frame>() check failed. Frame was dropped.");
|
|
return;
|
|
}
|
|
|
|
// Publish stream image
|
|
if (image_publishers.find(stream) != image_publishers.end())
|
|
{
|
|
auto &image_publisher = image_publishers.at(stream);
|
|
cv::Mat image_cv_matrix;
|
|
|
|
// if rgbd has subscribers we fetch the CV image here
|
|
if (_rgbd_publisher && 0 != _rgbd_publisher->get_subscription_count())
|
|
{
|
|
if (fillCVMatImageAndReturnStatus(f, images, width, height, stream))
|
|
{
|
|
image_cv_matrix = images[stream];
|
|
}
|
|
}
|
|
|
|
// if depth/color has subscribers, ask first if rgbd already fetched
|
|
// the images from the frame. if not, fetch the relevant color/depth image.
|
|
if (0 != image_publisher->get_subscription_count())
|
|
{
|
|
if (image_cv_matrix.empty() && fillCVMatImageAndReturnStatus(f, images, width, height, stream))
|
|
{
|
|
image_cv_matrix = images[stream];
|
|
}
|
|
|
|
// Prepare image topic to be published
|
|
// We use UniquePtr for allow intra-process publish when subscribers of that type are available
|
|
sensor_msgs::msg::Image::UniquePtr img_msg_ptr(new sensor_msgs::msg::Image());
|
|
if (!img_msg_ptr)
|
|
{
|
|
ROS_ERROR("Sensor image message allocation failed. Frame was dropped.");
|
|
return;
|
|
}
|
|
|
|
if (fillROSImageMsgAndReturnStatus(image_cv_matrix, stream, width, height, stream_format, t, img_msg_ptr.get()))
|
|
{
|
|
|
|
// Transfer the unique pointer ownership to the RMW
|
|
sensor_msgs::msg::Image *msg_address = img_msg_ptr.get();
|
|
image_publisher->publish(std::move(img_msg_ptr));
|
|
|
|
ROS_DEBUG_STREAM(rs2_stream_to_string(f.get_profile().stream_type()) << " stream published, message address: " << std::hex << msg_address);
|
|
}
|
|
else
|
|
{
|
|
ROS_ERROR("Could not fill ROS message. Frame was dropped.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Publish stream camera info
|
|
if(info_publishers.find(stream) != info_publishers.end())
|
|
{
|
|
auto& info_publisher = info_publishers.at(stream);
|
|
|
|
// If rgbd has subscribers, get the camera info of color/detph sensors from _camera_info map.
|
|
// We need this camera info to fill the rgbd msg, regardless if there subscribers to depth/color camera info.
|
|
// We are not publishing this cam_info here, but will be published by rgbd publisher.
|
|
if (_rgbd_publisher && 0 != _rgbd_publisher->get_subscription_count())
|
|
{
|
|
auto& cam_info = _camera_info.at(stream);
|
|
|
|
// Fix the camera info if needed, usually only in the first time
|
|
// when we init this object in the _camera_info map
|
|
if (cam_info.width != width)
|
|
{
|
|
updateStreamCalibData(f.get_profile().as<rs2::video_stream_profile>());
|
|
}
|
|
cam_info.header.stamp = t;
|
|
}
|
|
|
|
// If depth/color camera info has subscribers get camera info from _camera_info map,
|
|
// and publish this msg.
|
|
if(0 != info_publisher->get_subscription_count())
|
|
{
|
|
auto& cam_info = _camera_info.at(stream);
|
|
|
|
// Fix the camera info if needed, usually only in the first time
|
|
// when we init this object in the _camera_info map
|
|
if (cam_info.width != width)
|
|
{
|
|
updateStreamCalibData(f.get_profile().as<rs2::video_stream_profile>());
|
|
}
|
|
cam_info.header.stamp = t;
|
|
info_publisher->publish(cam_info);
|
|
}
|
|
}
|
|
|
|
// Publish stream metadata
|
|
if (is_publishMetadata)
|
|
{
|
|
publishMetadata(f, t, OPTICAL_FRAME_ID(stream));
|
|
}
|
|
}
|
|
|
|
|
|
void BaseRealSenseNode::publishRGBD(
|
|
const cv::Mat& rgb_cv_matrix,
|
|
const rs2_format& color_format,
|
|
const cv::Mat& depth_cv_matrix,
|
|
const rs2_format& depth_format,
|
|
const rclcpp::Time& t)
|
|
{
|
|
if (_rgbd_publisher && 0 != _rgbd_publisher->get_subscription_count())
|
|
{
|
|
ROS_DEBUG_STREAM("Publishing RGBD message");
|
|
unsigned int rgb_width = rgb_cv_matrix.size().width;
|
|
unsigned int rgb_height = rgb_cv_matrix.size().height;
|
|
unsigned int depth_width = depth_cv_matrix.size().width;
|
|
unsigned int depth_height = depth_cv_matrix.size().height;
|
|
|
|
realsense2_camera_msgs::msg::RGBD::UniquePtr msg(new realsense2_camera_msgs::msg::RGBD());
|
|
|
|
msg->rgb_camera_info = _camera_info.at(COLOR);
|
|
msg->depth_camera_info = _camera_info.at(DEPTH);
|
|
|
|
auto depth_stream_index_pair = DEPTH;
|
|
if (_align_depth_filter->is_enabled())
|
|
{
|
|
depth_stream_index_pair = COLOR;
|
|
msg->depth_camera_info = _camera_info.at(COLOR);
|
|
}
|
|
|
|
bool rgb_message_filled = fillROSImageMsgAndReturnStatus(rgb_cv_matrix, COLOR, rgb_width, rgb_height, color_format, t, &msg->rgb);
|
|
if(!rgb_message_filled)
|
|
{
|
|
ROS_ERROR_STREAM("Failed to fill rgb message inside RGBD message");
|
|
return;
|
|
}
|
|
|
|
bool depth_messages_filled = fillROSImageMsgAndReturnStatus(depth_cv_matrix, depth_stream_index_pair, depth_width, depth_height, depth_format, t, &msg->depth);
|
|
if(!depth_messages_filled)
|
|
{
|
|
ROS_ERROR_STREAM("Failed to fill depth message inside RGBD message");
|
|
return;
|
|
}
|
|
|
|
msg->header.frame_id = "camera_rgbd_optical_frame";
|
|
msg->header.stamp = t;
|
|
|
|
|
|
realsense2_camera_msgs::msg::RGBD *msg_address = msg.get();
|
|
_rgbd_publisher->publish(std::move(msg));
|
|
ROS_DEBUG_STREAM("rgbd stream published, message address: " << std::hex << msg_address);
|
|
}
|
|
}
|
|
|
|
void BaseRealSenseNode::publishMetadata(rs2::frame f, const rclcpp::Time& header_time, const std::string& frame_id)
|
|
{
|
|
stream_index_pair stream = {f.get_profile().stream_type(), f.get_profile().stream_index()};
|
|
if (_metadata_publishers.find(stream) != _metadata_publishers.end())
|
|
{
|
|
auto& md_publisher = _metadata_publishers.at(stream);
|
|
if (0 != md_publisher->get_subscription_count())
|
|
{
|
|
realsense2_camera_msgs::msg::Metadata msg;
|
|
msg.header.frame_id = frame_id;
|
|
msg.header.stamp = header_time;
|
|
std::stringstream json_data;
|
|
const char* separator = ",";
|
|
json_data << "{";
|
|
// Add additional fields:
|
|
json_data << "\"" << "frame_number" << "\":" << f.get_frame_number();
|
|
json_data << separator << "\"" << "clock_domain" << "\":" << "\"" << create_graph_resource_name(rs2_timestamp_domain_to_string(f.get_frame_timestamp_domain())) << "\"";
|
|
json_data << separator << "\"" << "frame_timestamp" << "\":" << std::fixed << f.get_timestamp();
|
|
|
|
for (auto i = 0; i < RS2_FRAME_METADATA_COUNT; i++)
|
|
{
|
|
if (f.supports_frame_metadata((rs2_frame_metadata_value)i))
|
|
{
|
|
rs2_frame_metadata_value mparam = (rs2_frame_metadata_value)i;
|
|
std::string name = create_graph_resource_name(rs2_frame_metadata_to_string(mparam));
|
|
if (RS2_FRAME_METADATA_FRAME_TIMESTAMP == i)
|
|
{
|
|
name = "hw_timestamp";
|
|
}
|
|
rs2_metadata_type val = f.get_frame_metadata(mparam);
|
|
json_data << separator << "\"" << name << "\":" << val;
|
|
}
|
|
}
|
|
json_data << "}";
|
|
msg.json_data = json_data.str();
|
|
md_publisher->publish(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BaseRealSenseNode::startDiagnosticsUpdater()
|
|
{
|
|
std::string serial_no = _dev.get_info(RS2_CAMERA_INFO_SERIAL_NUMBER);
|
|
if (_diagnostics_period > 0)
|
|
{
|
|
ROS_INFO_STREAM("Publish diagnostics every " << _diagnostics_period << " seconds.");
|
|
_diagnostics_updater = std::make_shared<diagnostic_updater::Updater>(&_node, _diagnostics_period);
|
|
|
|
_diagnostics_updater->setHardwareID(serial_no);
|
|
|
|
_diagnostics_updater->add("Temperatures", [this](diagnostic_updater::DiagnosticStatusWrapper& status)
|
|
{
|
|
bool got_temperature(false);
|
|
for(auto&& sensor : _available_ros_sensors)
|
|
{
|
|
for (rs2_option option : _monitor_options)
|
|
{
|
|
try
|
|
{
|
|
if (sensor->supports(option))
|
|
{
|
|
status.add(rs2_option_to_string(option), sensor->get_option(option));
|
|
got_temperature = true;
|
|
}
|
|
}
|
|
catch(const std::exception& ex)
|
|
{
|
|
got_temperature = false;
|
|
ROS_WARN_STREAM("An error has occurred during monitoring: " << ex.what());
|
|
}
|
|
}
|
|
if (got_temperature) break;
|
|
}
|
|
status.summary(0, "OK");
|
|
});
|
|
}
|
|
}
|