Background: I am currently trying to improve a project of mine: I want to make a specific camera usable with a framework. The camera is accessible via a proprietary SDK (Pylon). The framework is ROS2.
Now for the challenge: As memory bandwidth is a bottle-neck in my use case, I want to avoid copying image data at all costs. The camera SDK provides a mechanism where the user-defined buffer is allocated only once. Buffer is a native ROS2 type called sensor_msgs::msg::Image
(please note: There is no std::unique_ptr
involved here). The camera is writing into it asynchronously. This is working fine.
ROS2 provides a zero-copy mechanism to implicitly share memory between components using a std::unique_ptr<sensor_msgs::msg::Image>
in publish. This is also working fine.
Problem: The mechanism assumes you std::move
the std::unique_ptr
away from the sender. This is totally reasonable for the default use-case. After the data has been transferred, std::unique_ptr
calls the deleter which then de-allocates the transferred object. Unfortunately, that still is the buffer the camera driver wants to write to.
The first thing I tried was wrapping the "static" object into a std::unique_ptr
with a no-op deleter using a lambda:
auto deleter = [](sensor_msgs::msg::Image*){ std::cerr << "NoDeleter is not deleting an Image." << std::endl; };
std::unique_ptr<sensor_msgs::msg::Image, decltype(deleter)> img_msg_ptr(img_msg, deleter);
But the types are incompatible:
no known conversion for argument 1 from ‘unique_ptr<[...],…::<lambda(sensor_msgs::msg::Image_<std::allocator<void> >*)>>’ to ‘unique_ptr<[...],std::default_delete<sensor_msgs::msg::Image_<std::allocator<void> > >
It looks like the deleter must inherit from std::default_delete
. sensor_msgs::msg::Image
is a shorthand for sensor_msgs::msg::Image_<std::allocator<void> >
.
Next I tried inheriting from std::default_delete
:
class NoDeleter : public std::default_delete<sensor_msgs::msg::Image> {
public:
void operator()(sensor_msgs::msg::Image *) const override {
std::cerr << "NoDeleter is not deleting an Image." << std::endl;
}
};
But gcc tells me I am doing it wrong:
error: ‘void NoDeleter::operator()(sensor_msgs::msg::Image*) const’ marked ‘override’, but does not override
I tried specializing std::default_delete
, but it had no effect. I assume, I am using the wrong type.
How can I (manually) determine the type I need to use?
Update: As requested, here is a complete example (it depends on ROS2 "foxy"):
staticbufferpublisher.cpp:
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_components/register_node_macro.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <chrono> // for 1s literal
using namespace std::chrono_literals;
class StaticBufferPublisher : public rclcpp::Node {
private:
rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr imagePublisher;
rclcpp::TimerBase::SharedPtr timer;
sensor_msgs::msg::Image buffer;
public:
StaticBufferPublisher(const rclcpp::NodeOptions & options) : rclcpp::Node("StaticBufferPublisher", options) {
if (!options.use_intra_process_comms()) {
throw std::runtime_error("Must use use_intra_process_comms!");
}
imagePublisher = this->create_publisher<sensor_msgs::msg::Image>("image", 1);
buffer.data.resize(1024); // prepare buffer once
// camera.set_buffer(buffer.data.data()) // tell camera where to put the data
// camera.start_grabbing() // start camera
// this timer simulates a while(camera.block_until_image_ready())
timer = this->create_wall_timer(1s, [this]() -> void {
std::cerr << "Sending data starting at " << static_cast<void*>(buffer.data.data()) << "." << std::endl;
/*
* THE INTERESTING PART IS HERE
*/
imagePublisher->publish(buffer); // this works, but creates a copy
std::unique_ptr<sensor_msgs::msg::Image> img_msg_ptr(&buffer);
imagePublisher->publish(std::move(img_msg_ptr)); // this does not create a copy, but releases the buffer – so it "works", but only once
//auto deleter = [](sensor_msgs::msg::Image*){ std::cerr << "NoDeleter is not deleting an Image." << std::endl; };
//std::unique_ptr<sensor_msgs::msg::Image, decltype(deleter)> img_msg_ptr(img_msg, deleter); // publish() will not accept this unique_ptr
});
}
};
RCLCPP_COMPONENTS_REGISTER_NODE(StaticBufferPublisher)
addressprinter.cpp:
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_components/register_node_macro.hpp>
#include <sensor_msgs/msg/image.hpp>
class AddressPrinter : public rclcpp::Node {
private:
rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr imageSubscription;
public:
AddressPrinter(const rclcpp::NodeOptions & options) : rclcpp::Node("AddressPrinter", options) {
if (!options.use_intra_process_comms()) {
throw std::runtime_error("Must use use_intra_process_comms!");
}
imageSubscription = this->create_subscription<sensor_msgs::msg::Image>("image", 1,
[this](sensor_msgs::msg::Image::UniquePtr msg) {
std::cerr << "Received data starting at " << static_cast<void*>(msg->data.data()) << "." << std::endl;
}
);
}
};
RCLCPP_COMPONENTS_REGISTER_NODE(AddressPrinter)
Launchfile:
import launch
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
def generate_launch_description():
image_processing = ComposableNodeContainer(
name = 'container',
namespace = '',
package = 'rclcpp_components',
executable = 'component_container',
composable_node_descriptions = [
ComposableNode(
name = 'address_printer',
namespace = '',
package = 'pylon_instant_camera',
plugin = 'AddressPrinter',
extra_arguments=[{'use_intra_process_comms': True}]
),
ComposableNode(
name = 'publisher',
namespace = '',
package = 'pylon_instant_camera',
plugin = 'StaticBufferPublisher',
extra_arguments=[{'use_intra_process_comms': True}]
)
],
output = 'screen'
)
return launch.LaunchDescription([image_processing])
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(pylon_instant_camera)
set (CMAKE_CXX_STANDARD 17)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
add_library(AddressPrinter SHARED src/addressprinter.cpp)
ament_target_dependencies(AddressPrinter
rclcpp
rclcpp_components
sensor_msgs
)
rclcpp_components_register_node(AddressPrinter PLUGIN "AddressPrinter" EXECUTABLE "addressprinter")
add_library(StaticBufferPublisher SHARED src/staticbufferpublisher.cpp)
ament_target_dependencies(StaticBufferPublisher
rclcpp
rclcpp_components
sensor_msgs
)
rclcpp_components_register_node(StaticBufferPublisher PLUGIN "StaticBufferPublisher" EXECUTABLE "staticbufferpublisher")
install(TARGETS
StaticBufferPublisher
AddressPrinter
DESTINATION lib
)
install(DIRECTORY launch
DESTINATION share/${PROJECT_NAME}
)
ament_package()