-
Notifications
You must be signed in to change notification settings - Fork 56
Advanced ROS Tutorials
Here are advanced examples of how UFOMap can be used with ROS. Most of the things mentioned here are already implemented in the UFOMap mapping server, so by used that you already get all of these benefits.
Again, these are not complete examples. In the examples variables will be accessed in ways it is not possible. This is to reduce the amount of code needed for the examples. When you implemented these things you will probably have classes, so the map will be a class member that you can access from member functions.
Often only a small portion of the map will be updated at a time. To increase performance and reduce the amount of data that has to be published between nodes, it is possible to define which part of the map should be published.
// We will publish colored UFOMaps in this tutorial
#include <ufo/map/occupancy_map_color.h>
// UFOMap ROS msg
#include <ufomap_msgs/UFOMapStamped.h>
// To convert between UFO and ROS
#include <ufomap_msgs/conversions.h>
#include <ufomap_ros/conversions.h>
#include <ros/ros.h>
int main(int argc, char *argv[])
{
ros::init(argc, argv, "ufomap_publisher");
ros::NodeHandle nh;
ros::Publisher map_pub = nh.advertise<ufomap_msgs::UFOMapStamped>("map", 10);
// Create a colored UFOMap
ufo::map::OccupancyMapColor map(0.1);
// Enable min/max change detection. This allows us to get an
// axis-aligned bounding box of the updated region.
map.enableMinMaxChangeDetection(true);
// If the UFOMap should be compressed using LZ4.
// Good if you are sending the UFOMap between computers.
bool compress = false;
// Lowest depth to publish.
// Higher value means less data to transfer, good in
// situation where the data rate is low.
// Many nodes do not require detailed maps as well.
ufo::map::DepthType pub_depth = 0;
ros::Rate rate(10);
while (ros::ok()) {
// TODO: Integrate data into map
// Get axis-aligned bounding box of update part of the map.
ufo::geometry::AABB updated_aabb(map.minChange(), map.maxChange());
// This is the UFOMap message object.
ufomap_msgs::UFOMapStamped::Ptr msg(new ufomap_msgs::UFOMapStamped);
// Convert UFOMap to ROS message. Here we add the axis-aligned bounding box,
// which tells us what part of the UFOMap we want to publish.
if (ufomap_msgs::ufoToMsg(map, msg->map, updated_aabb, compress, pub_depth)) {
// Conversion was successful
msg->header.stamp = ros::Time::now();
msg->header.frame_id = "map";
map_pub.publish(msg);
// Reset min/max change detection so we get correct axis-aligned bounding box
// next time we publish.
map.resetMinMaxChangeDetection();
}
rate.sleep();
}
return 0;
}
Step by step what we did here:
- Enabled
min/max change detection
with the linemap.enableMinMaxChangeDetection(true);
, this allows us to get the axis-aligned bounding box of the updated part of the map. - Retrieved the axis-aligned bounding box of the updated part of the map, with the line
ufo::geometry::AABB updated_aabb(map.minChange(), map.maxChange());
, before we convert UFOMap to ROS message. - Insert the axis-aligned bounding box in the UFO to ROS conversion function
ufomap_msgs::ufoToMsg(map, msg->map, updated_aabb, compress, pub_depth)
. Themsg
will now contain information about this axis-aligned bounding box, insidemsg->map.info.bounding_volume.aabbs
, such that the nodes that subscribe to this topic will know what part of the map they should update with this message. - Reset the
min/max change detection
, with the linemap.resetMinMaxChangeDetection();
, such that we can use it again next time we should publish.
Nothing on the subscriber side has to be changed for this to work, since all information is included in the message.
If you followed the previous tutorial and now only publish the updated part of the map, you will notice that when nodes subscribe to the map at different times they will not receive the full map. In order to solve this, we have to detect when a new node is subscribing to our map and send the whole map to that node.
// We will publish colored UFOMaps in this tutorial
#include <ufo/map/occupancy_map_color.h>
// UFOMap ROS msg
#include <ufomap_msgs/UFOMapStamped.h>
// To convert between UFO and ROS
#include <ufomap_msgs/conversions.h>
#include <ufomap_ros/conversions.h>
#include <ros/ros.h>
// Function that publishes the whole map to new nodes that subscribe to the map
void mapConnectCallback(ros::SingleSubscriberPublisher const &pub)
{
ufomap_msgs::UFOMapStamped::Ptr msg(new ufomap_msgs::UFOMapStamped);
// Convert UFOMap to ROS, with compression and at depth 0. You can of course change these.
if (ufomap_msgs::ufoToMsg(map, msg->map, true, 0)) {
// Conversion was successful
msg->header.stamp = ros::Time::now();
msg->header.frame_id = "map";
// Publish the whole map to the specific node that just subscribed
pub.publish(msg);
}
}
int main(int argc, char *argv[])
{
ros::init(argc, argv, "ufomap_publisher");
ros::NodeHandle nh;
// Advance publisher.
ros::Publisher map_pub = nh.advertise<ufomap_msgs::UFOMapStamped>("map", 10, &mapConnectCallback,
ros::SubscriberStatusCallback(),
ros::VoidConstPtr());
// Create a colored UFOMap
ufo::map::OccupancyMapColor map(0.1);
// Enable min/max change detection. This allows us to get an
// axis-aligned bounding box of the updated region.
map.enableMinMaxChangeDetection(true);
// If the UFOMap should be compressed using LZ4.
// Good if you are sending the UFOMap between computers.
bool compress = false;
// Lowest depth to publish.
// Higher value means less data to transfer, good in
// situation where the data rate is low.
// Many nodes do not require detailed maps as well.
ufo::map::DepthType pub_depth = 0;
ros::Rate rate(10);
while (ros::ok()) {
// TODO: Integrate data into map
// Get axis-aligned bounding box of update part of the map.
ufo::geometry::AABB updated_aabb(map.minChange(), map.maxChange());
// This is the UFOMap message object.
ufomap_msgs::UFOMapStamped::Ptr msg(new ufomap_msgs::UFOMapStamped);
// Convert UFOMap to ROS message. Here we add the axis-aligned bounding box,
// which tells us what part of the UFOMap we want to publish.
if (ufomap_msgs::ufoToMsg(map, msg->map, updated_aabb, compress, pub_depth)) {
// Conversion was successful
msg->header.stamp = ros::Time::now();
msg->header.frame_id = "map";
map_pub.publish(msg);
// Reset min/max change detection so we get correct axis-aligned bounding box
// next time we publish.
map.resetMinMaxChangeDetection();
}
rate.sleep();
}
return 0;
}
Step by step what we did here:
- Used the advance ROS publisher constructor, to setup a callback for when a new nodes subscribes to the topic. The line
ros::Publisher map_pub = nh.advertise<ufomap_msgs::UFOMapStamped>("map", 10, &mapConnectCallback, ros::SubscriberStatusCallback(), ros::VoidConstPtr());
- Implemented the
mapConnectCallback
function that publishes the whole map to a single node that subscribes.
Again, nothing on the subscriber side has to be changed for this to work.
Another approach to solving this problem can be to publish the whole map every X seconds; however, when mapping at 5 mm the map will get huge, so this will eventually get slow.
Mapping at a fine voxel size (2 cm or below) results in a lot of data. In a complete robotics system, different nodes require different map resolutions. It can therefore be beneficial to publish the map at different depths, and only publish at a specific depth if a node subscribes to the topic for that depth.
// We will publish colored UFOMaps in this tutorial
#include <ufo/map/occupancy_map_color.h>
// UFOMap ROS msg
#include <ufomap_msgs/UFOMapStamped.h>
// To convert between UFO and ROS
#include <ufomap_msgs/conversions.h>
#include <ufomap_ros/conversions.h>
#include <ros/ros.h>
#include <vector>
// Function that publishes the whole map to new nodes that subscribe to the map.
// d is the depth of the map we should publish.
void mapConnectCallback(ros::SingleSubscriberPublisher const &pub, int d)
{
ufomap_msgs::UFOMapStamped::Ptr msg(new ufomap_msgs::UFOMapStamped);
// Convert UFOMap to ROS, with compression and at depth d. You can of course change these.
if (ufomap_msgs::ufoToMsg(map, msg->map, true, d)) {
// Conversion was successful
msg->header.stamp = ros::Time::now();
msg->header.frame_id = "map";
// Publish the whole map to the specific node that just subscribed
pub.publish(msg);
}
}
int main(int argc, char *argv[])
{
ros::init(argc, argv, "ufomap_publisher");
ros::NodeHandle nh;
size_t max_depth_to_pub = 4;
// Create a number of publishers.
std::vector<ros::Publisher> map_pubs(max_depth_to_pub);
// Construct publishers.
for (int d = 0; d < map_pubs.size(); ++d) {
std::string final_topic = d == 0 ? "map" : "map_depth_" + std::to_string(d);
// Advance publisher
map_pubs[d] = nh.advertise<ufomap_msgs::UFOMapStamped>(final_topic, 10,
boost::bind(&mapConnectCallback, _1, d),
ros::SubscriberStatusCallback(),
ros::VoidConstPtr());
}
// Create a colored UFOMap
ufo::map::OccupancyMapColor map(0.1);
// Enable min/max change detection. This allows us to get an
// axis-aligned bounding box of the updated region.
map.enableMinMaxChangeDetection(true);
// If the UFOMap should be compressed using LZ4.
// Good if you are sending the UFOMap between computers.
bool compress = false;
ros::Rate rate(10);
while (ros::ok()) {
// TODO: Integrate data into map
// Get axis-aligned bounding box of update part of the map.
ufo::geometry::AABB updated_aabb(map.minChange(), map.maxChange());
for (int d = 0; d < map_pubs.size(); ++d) {
if (!map_pubs[d] || (0 >= map_pubs[d].getNumSubscribers() && !map_pubs[d].isLatched())) {
// This publisher is broken(?) or
// There are no subscribers on this topic and the topic is not latched
continue;
}
// This is the UFOMap message object.
ufomap_msgs::UFOMapStamped::Ptr msg(new ufomap_msgs::UFOMapStamped);
// Convert UFOMap to ROS message. Here we add the axis-aligned bounding box,
// which tells us what part of the UFOMap we want to publish.
if (ufomap_msgs::ufoToMsg(map, msg->map, updated_aabb, compress, d)) {
// Conversion was successful
msg->header.stamp = ros::Time::now();
msg->header.frame_id = "map";
map_pubs[d].publish(msg);
}
}
// Reset min/max change detection so we get correct axis-aligned bounding box
// next time we publish.
map.resetMinMaxChangeDetection();
rate.sleep();
}
return 0;
}
Step by step what we did here:
- Created a vector of X publishers, publisher d publishes the map at depth d.
- Check if any node is subscribing to the topic. If there is no node subscribing to the topic we do not publish on that topic.
- If there is a node subscribing to the topic we publish on that topic at the corresponding depth.
In this tutorial, we will show how you can write your node such that it supports both vanilla and colored UFOMaps. We will utilize std::variant
for this.
#include <ufo/map/occupancy_map.h>
#include <ufo/map/occupancy_map_color.h>
// UFOMap ROS msg
#include <ufomap_msgs/UFOMapStamped.h>
// To convert between UFO and ROS
#include <ufomap_msgs/conversions.h>
#include <ufomap_ros/conversions.h>
#include <ros/ros.h>
#include <variant>
// Create a UFOMap variant
// The first is std::monostate so we do not have to initialize it.
std::variant<std::monostate, ufo::map::OccupancyMap, ufo::map::OccupancyMapColor> map;
// This function checks if the current map is of correct type and has correct resolution and
// number of depth levels.
bool checkMap(std::string const &type, double resolution, ufo::map::DepthType depth_levels)
{
return std::visit([&type, resolution, depth_levels](auto &map_l) -> bool {
if constexpr (!std::is_same_v<std::decay_t<decltype(map_l)>, std::monostate>) {
return map_l.getTreeType() == type &&
map_l.getResolution() == resolution &&
map_l.getTreeDepthLevels() == depth_levels;
}
return false;
}, map);
}
// This function creates the correct type of UFOMap based on the message metadata.
bool createMap(ufomap_msgs::UFOMapMetaData const &info)
{
double occupied_thres = 0.5;
double free_thres = 0.5;
// FIXME: Remove hardcoded
if ("occupancy_map" == info.id) {
map.emplace<ufo::map::OccupancyMap>(info.resolution, info.depth_levels, true,
occupied_thres, free_thres);
return true;
} else if ("occupancy_map_color" == info.id) {
map.emplace<ufo::map::OccupancyMapColor>(info.resolution, info.depth_levels, true,
occupied_thres, free_thres);
return true;
}
return false;
}
void mapCallback(ufomap_msgs::UFOMapStamped::ConstPtr const& msg)
{
// Check if we have correct map already
if (!checkMap(msg->map.info.id, msg->map.info.resolution, msg->map.info.depth_levels)) {
// Our map was not the same as in msg, create a new
if (!createMap(msg->map.info)) {
// Could not create the type of map specified in msg
ROS_WARN("Unknown UFOMap type '%s'", msg->map.info.id.c_str());
return;
}
}
// We can access the map with std::visit.
// Here we simply convert the message into our map.
std::visit([&msg](auto &map_l) {
if constexpr (!std::is_same_v<std::decay_t<decltype(map_l)>, std::monostate>) {
// Convert ROS message to UFOMap
if (ufomap_msgs::msgToUfo(msg->map, map_l)) {
// Conversion was successful
ROS_INFO("UFOMap conversion successful");
} else {
ROS_WARN("UFOMap conversion failed");
}
}
}, map);
}
int main(int argc, char *argv[])
{
ros::init(argc, argv, "ufomap_subscriber");
ros::NodeHandle nh;
ros::Subscriber map_sub = nh.subscribe("map", 10, mapCallback);
ros::spin();
return 0;
}
Here we created two helper functions to check if our current map is of the same type as the message in our callback and then another function to create a UFOMap of the correct type based on the current message in the callback.
It is recommended to read up about std::variant
and std::visit
if this code is confusing.
If you use UFOMap in a scientific publication, please cite the following paper:
@article{duberg2020ufomap,
author={Daniel Duberg and Patric Jensfelt},
journal={IEEE Robotics and Automation Letters},
title={{UFOMap}: An Efficient Probabilistic {3D} Mapping Framework That Embraces the Unknown},
year={2020},
volume={5},
number={4},
pages={6411-6418},
doi={10.1109/LRA.2020.3013861}
}
- Creating a UFOMap
- Integrate Point Cloud into UFOMap
- Collision Checking
- K-Nearest Neighbor Search
- Saving UFOMap to File
- Loading UFOMap from File
- Convert Between UFOMap and OctoMap
- ROS Tutorials
- Introduction
- CmakeLists.txt and package.xml
- UFOMap Publisher
- UFOMap Subscriber
- Integrate sensor_msgs/PointCloud2
- Perform Mapping
- Visualize UFOMap in RViz
- Convert Between UFOMap and OctoMap
- General UFOMap Usage
- Advanced ROS Tutorials