moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
pointcloud_octomap_updater.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2011, Willow Garage, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of Willow Garage nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
35/* Author: Jon Binney, Ioan Sucan */
36
37#include <cmath>
40#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
41// TODO: Remove conditional includes when released to all active distros.
42#if __has_include(<tf2/LinearMath/Vector3.hpp>)
43#include <tf2/LinearMath/Vector3.hpp>
44#else
45#include <tf2/LinearMath/Vector3.h>
46#endif
47#if __has_include(<tf2/LinearMath/Transform.hpp>)
48#include <tf2/LinearMath/Transform.hpp>
49#else
50#include <tf2/LinearMath/Transform.h>
51#endif
52#include <sensor_msgs/point_cloud2_iterator.hpp>
53// For Rolling, Kilted, and newer
54#if RCLCPP_VERSION_GTE(29, 6, 0)
55#include <tf2_ros/create_timer_interface.hpp>
56#include <tf2_ros/create_timer_ros.hpp>
57// For Jazzy and older
58#else
59#include <tf2_ros/create_timer_interface.h>
60#include <tf2_ros/create_timer_ros.h>
61#endif
63#include <rclcpp/version.h>
64
65#include <memory>
66
68{
70 : OccupancyMapUpdater("PointCloudUpdater")
71 , scale_(1.0)
72 , padding_(0.0)
73 , max_range_(std::numeric_limits<double>::infinity())
74 , point_subsample_(1)
75 , max_update_rate_(0)
76 , point_cloud_subscriber_(nullptr)
77 , point_cloud_filter_(nullptr)
78 , logger_(moveit::getLogger("moveit.ros.pointcloud_octomap_updater"))
79{
80}
81
82bool PointCloudOctomapUpdater::setParams(const std::string& name_space)
83{
84 auto check_required = [this, &name_space](const std::string& key, auto& target,
85 std::vector<std::string>& missing_keys) {
86 if (!this->node_->get_parameter(name_space + "." + key, target))
87 {
88 missing_keys.push_back(key);
89 }
90 };
91 // This parameter is optional
92 node_->get_parameter_or(name_space + ".ns", ns_, std::string());
93
94 std::vector<std::string> missing_keys;
95
96 check_required("point_cloud_topic", point_cloud_topic_, missing_keys);
97 check_required("max_range", max_range_, missing_keys);
98 check_required("padding_offset", padding_, missing_keys);
99 check_required("padding_scale", scale_, missing_keys);
100 check_required("point_subsample", point_subsample_, missing_keys);
101 check_required("max_update_rate", max_update_rate_, missing_keys);
102 check_required("filtered_cloud_topic", filtered_cloud_topic_, missing_keys);
103
104 if (missing_keys.empty())
105 {
106 return true;
107 }
108 std::ostringstream oss;
109 for (const auto& name : missing_keys)
110 {
111 oss << ", "
112 << "'" << name << "'";
113 }
114 RCLCPP_ERROR(node_->get_logger(), "Missing parameters under '%s': %s", name_space.c_str(), oss.str().c_str());
115 return false;
116}
117
118bool PointCloudOctomapUpdater::initialize(const rclcpp::Node::SharedPtr& node)
119{
120 node_ = node;
121 tf_buffer_ = std::make_shared<tf2_ros::Buffer>(node_->get_clock());
122 auto create_timer_interface =
123#if RCLCPP_VERSION_GTE(29, 6, 0)
124 std::make_shared<tf2_ros::CreateTimerROS>(*node);
125#else
126 std::make_shared<tf2_ros::CreateTimerROS>(node->get_node_base_interface(), node->get_node_timers_interface());
127#endif
128 tf_buffer_->setCreateTimerInterface(create_timer_interface);
129 tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
130 shape_mask_ = std::make_unique<point_containment_filter::ShapeMask>();
131 shape_mask_->setTransformCallback(
132 [this](ShapeHandle shape, Eigen::Isometry3d& tf) { return getShapeTransform(shape, tf); });
133
134 return true;
135}
136
138{
139 std::string prefix = "";
140 if (!ns_.empty())
141 prefix = ns_ + "/";
142
143 if (!filtered_cloud_topic_.empty())
144 {
145 filtered_cloud_publisher_ =
146 node_->create_publisher<sensor_msgs::msg::PointCloud2>(prefix + filtered_cloud_topic_, rclcpp::SensorDataQoS());
147 }
148
149 if (point_cloud_subscriber_)
150 return;
151
152 rclcpp::SubscriptionOptions options;
153 options.callback_group = node_->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
154 /* subscribe to point cloud topic using tf filter*/
155 auto qos_profile =
156#if RCLCPP_VERSION_GTE(28, 3, 0)
157 rclcpp::SensorDataQoS();
158#else
159 rmw_qos_profile_sensor_data;
160#endif
161 point_cloud_subscriber_ =
162 new message_filters::Subscriber<sensor_msgs::msg::PointCloud2>(node_, point_cloud_topic_, qos_profile, options);
163 if (tf_listener_ && tf_buffer_ && !monitor_->getMapFrame().empty())
164 {
165// For Rolling, L-turtle, and newer
166#if RCLCPP_VERSION_GTE(30, 0, 0)
167 using MessageFilterPointCloud2 = tf2_ros::MessageFilter<sensor_msgs::msg::PointCloud2>;
168
169 MessageFilterPointCloud2::RequiredInterfaces required_interfaces{ node_->get_node_logging_interface(),
170 node_->get_node_clock_interface() };
171
172 point_cloud_filter_ = new tf2_ros::MessageFilter<sensor_msgs::msg::PointCloud2>(
173 *point_cloud_subscriber_, *tf_buffer_, monitor_->getMapFrame(), 5, std::move(required_interfaces));
174#else
175 point_cloud_filter_ = new tf2_ros::MessageFilter<sensor_msgs::msg::PointCloud2>(
176 *point_cloud_subscriber_, *tf_buffer_, monitor_->getMapFrame(), 5, node_);
177#endif
178 point_cloud_filter_->registerCallback(
179 [this](const sensor_msgs::msg::PointCloud2::ConstSharedPtr& cloud) { cloudMsgCallback(cloud); });
180 RCLCPP_INFO(logger_, "Listening to '%s' using message filter with target frame '%s'", point_cloud_topic_.c_str(),
181 point_cloud_filter_->getTargetFramesString().c_str());
182 }
183 else
184 {
185 point_cloud_subscriber_->registerCallback(
186 [this](const sensor_msgs::msg::PointCloud2::ConstSharedPtr& cloud) { cloudMsgCallback(cloud); });
187 RCLCPP_INFO(logger_, "Listening to '%s'", point_cloud_topic_.c_str());
188 }
189}
190
192{
193 delete point_cloud_filter_;
194 delete point_cloud_subscriber_;
195 point_cloud_filter_ = nullptr;
196 point_cloud_subscriber_ = nullptr;
197}
198
199ShapeHandle PointCloudOctomapUpdater::excludeShape(const shapes::ShapeConstPtr& shape)
200{
201 ShapeHandle h = 0;
202 if (shape_mask_)
203 {
204 h = shape_mask_->addShape(shape, scale_, padding_);
205 }
206 else
207 {
208 RCLCPP_ERROR(logger_, "Shape filter not yet initialized!");
209 }
210 return h;
211}
212
214{
215 if (shape_mask_)
216 shape_mask_->removeShape(handle);
217}
218
219bool PointCloudOctomapUpdater::getShapeTransform(ShapeHandle h, Eigen::Isometry3d& transform) const
220{
221 ShapeTransformCache::const_iterator it = transform_cache_.find(h);
222 if (it != transform_cache_.end())
223 {
224 transform = it->second;
225 }
226 return it != transform_cache_.end();
227}
228
229void PointCloudOctomapUpdater::updateMask(const sensor_msgs::msg::PointCloud2& /*cloud*/,
230 const Eigen::Vector3d& /*sensor_origin*/, std::vector<int>& /*mask*/)
231{
232}
233
234void PointCloudOctomapUpdater::cloudMsgCallback(const sensor_msgs::msg::PointCloud2::ConstSharedPtr& cloud_msg)
235{
236 RCLCPP_DEBUG(logger_, "Received a new point cloud message");
237 rclcpp::Time start = rclcpp::Clock(RCL_ROS_TIME).now();
238
239 if (max_update_rate_ > 0)
240 {
241 // ensure we are not updating the octomap representation too often
242 if ((node_->now() - last_update_time_) <= rclcpp::Duration::from_seconds(1.0 / max_update_rate_))
243 return;
244 last_update_time_ = node_->now();
245 }
246
247 if (monitor_->getMapFrame().empty())
248 monitor_->setMapFrame(cloud_msg->header.frame_id);
249
250 /* get transform for cloud into map frame */
251 tf2::Stamped<tf2::Transform> map_h_sensor;
252 if (monitor_->getMapFrame() == cloud_msg->header.frame_id)
253 {
254 map_h_sensor.setIdentity();
255 }
256 else
257 {
258 if (tf_buffer_)
259 {
260 try
261 {
262 tf2::fromMsg(tf_buffer_->lookupTransform(monitor_->getMapFrame(), cloud_msg->header.frame_id,
263 cloud_msg->header.stamp),
264 map_h_sensor);
265 }
266 catch (tf2::TransformException& ex)
267 {
268 RCLCPP_ERROR_STREAM(logger_, "Transform error of sensor data: " << ex.what() << "; quitting callback");
269 return;
270 }
271 }
272 else
273 {
274 return;
275 }
276 }
277
278 /* compute sensor origin in map frame */
279 const tf2::Vector3& sensor_origin_tf = map_h_sensor.getOrigin();
280 octomap::point3d sensor_origin(sensor_origin_tf.getX(), sensor_origin_tf.getY(), sensor_origin_tf.getZ());
281 Eigen::Vector3d sensor_origin_eigen(sensor_origin_tf.getX(), sensor_origin_tf.getY(), sensor_origin_tf.getZ());
282
283 if (!updateTransformCache(cloud_msg->header.frame_id, cloud_msg->header.stamp))
284 return;
285
286 /* mask out points on the robot */
287 shape_mask_->maskContainment(*cloud_msg, sensor_origin_eigen, 0.0, max_range_, mask_);
288 updateMask(*cloud_msg, sensor_origin_eigen, mask_);
289
290 octomap::KeySet free_cells, occupied_cells, model_cells, clip_cells;
291 std::unique_ptr<sensor_msgs::msg::PointCloud2> filtered_cloud;
292
293 // We only use these iterators if we are creating a filtered_cloud for
294 // publishing. We cannot default construct these, so we use unique_ptr's
295 // to defer construction
296 std::unique_ptr<sensor_msgs::PointCloud2Iterator<float>> iter_filtered_x;
297 std::unique_ptr<sensor_msgs::PointCloud2Iterator<float>> iter_filtered_y;
298 std::unique_ptr<sensor_msgs::PointCloud2Iterator<float>> iter_filtered_z;
299
300 if (!filtered_cloud_topic_.empty())
301 {
302 filtered_cloud = std::make_unique<sensor_msgs::msg::PointCloud2>();
303 filtered_cloud->header = cloud_msg->header;
304 sensor_msgs::PointCloud2Modifier pcd_modifier(*filtered_cloud);
305 pcd_modifier.setPointCloud2FieldsByString(1, "xyz");
306 pcd_modifier.resize(cloud_msg->width * cloud_msg->height);
307
308 // we have created a filtered_out, so we can create the iterators now
309 iter_filtered_x = std::make_unique<sensor_msgs::PointCloud2Iterator<float>>(*filtered_cloud, "x");
310 iter_filtered_y = std::make_unique<sensor_msgs::PointCloud2Iterator<float>>(*filtered_cloud, "y");
311 iter_filtered_z = std::make_unique<sensor_msgs::PointCloud2Iterator<float>>(*filtered_cloud, "z");
312 }
313 size_t filtered_cloud_size = 0;
314
315 tree_->lockRead();
316
317 try
318 {
319 /* do ray tracing to find which cells this point cloud indicates should be free, and which it indicates
320 * should be occupied */
321 for (unsigned int row = 0; row < cloud_msg->height; row += point_subsample_)
322 {
323 unsigned int row_c = row * cloud_msg->width;
324 sensor_msgs::PointCloud2ConstIterator<float> pt_iter(*cloud_msg, "x");
325 // set iterator to point at start of the current row
326 pt_iter += row_c;
327
328 for (unsigned int col = 0; col < cloud_msg->width; col += point_subsample_, pt_iter += point_subsample_)
329 {
330 // if (mask_[row_c + col] == point_containment_filter::ShapeMask::CLIP)
331 // continue;
332
333 /* check for NaN */
334 if (!std::isnan(pt_iter[0]) && !std::isnan(pt_iter[1]) && !std::isnan(pt_iter[2]))
335 {
336 /* occupied cell at ray endpoint if ray is shorter than max range and this point
337 isn't on a part of the robot*/
338 if (mask_[row_c + col] == point_containment_filter::ShapeMask::INSIDE)
339 {
340 // transform to map frame
341 tf2::Vector3 point_tf = map_h_sensor * tf2::Vector3(pt_iter[0], pt_iter[1], pt_iter[2]);
342 model_cells.insert(tree_->coordToKey(point_tf.getX(), point_tf.getY(), point_tf.getZ()));
343 }
344 else if (mask_[row_c + col] == point_containment_filter::ShapeMask::CLIP)
345 {
346 tf2::Vector3 clipped_point_tf =
347 map_h_sensor * (tf2::Vector3(pt_iter[0], pt_iter[1], pt_iter[2]).normalize() * max_range_);
348 clip_cells.insert(
349 tree_->coordToKey(clipped_point_tf.getX(), clipped_point_tf.getY(), clipped_point_tf.getZ()));
350 }
351 else
352 {
353 tf2::Vector3 point_tf = map_h_sensor * tf2::Vector3(pt_iter[0], pt_iter[1], pt_iter[2]);
354 occupied_cells.insert(tree_->coordToKey(point_tf.getX(), point_tf.getY(), point_tf.getZ()));
355 // build list of valid points if we want to publish them
356 if (filtered_cloud)
357 {
358 **iter_filtered_x = pt_iter[0];
359 **iter_filtered_y = pt_iter[1];
360 **iter_filtered_z = pt_iter[2];
361 ++filtered_cloud_size;
362 ++*iter_filtered_x;
363 ++*iter_filtered_y;
364 ++*iter_filtered_z;
365 }
366 }
367 }
368 }
369 }
370
371 /* compute the free cells along each ray that ends at an occupied cell */
372 for (const octomap::OcTreeKey& occupied_cell : occupied_cells)
373 {
374 if (tree_->computeRayKeys(sensor_origin, tree_->keyToCoord(occupied_cell), key_ray_))
375 free_cells.insert(key_ray_.begin(), key_ray_.end());
376 }
377
378 /* compute the free cells along each ray that ends at a model cell */
379 for (const octomap::OcTreeKey& model_cell : model_cells)
380 {
381 if (tree_->computeRayKeys(sensor_origin, tree_->keyToCoord(model_cell), key_ray_))
382 free_cells.insert(key_ray_.begin(), key_ray_.end());
383 }
384
385 /* compute the free cells along each ray that ends at a clipped cell */
386 for (const octomap::OcTreeKey& clip_cell : clip_cells)
387 {
388 free_cells.insert(clip_cell);
389 if (tree_->computeRayKeys(sensor_origin, tree_->keyToCoord(clip_cell), key_ray_))
390 free_cells.insert(key_ray_.begin(), key_ray_.end());
391 }
392 }
393 catch (...)
394 {
395 tree_->unlockRead();
396 return;
397 }
398
399 tree_->unlockRead();
400
401 /* cells that overlap with the model are not occupied */
402 for (const octomap::OcTreeKey& model_cell : model_cells)
403 occupied_cells.erase(model_cell);
404
405 /* occupied cells are not free */
406 for (const octomap::OcTreeKey& occupied_cell : occupied_cells)
407 free_cells.erase(occupied_cell);
408
409 tree_->lockWrite();
410
411 try
412 {
413 /* mark free cells only if not seen occupied in this cloud */
414 for (const octomap::OcTreeKey& free_cell : free_cells)
415 tree_->updateNode(free_cell, false);
416
417 /* now mark all occupied cells */
418 for (const octomap::OcTreeKey& occupied_cell : occupied_cells)
419 tree_->updateNode(occupied_cell, true);
420
421 // set the logodds to the minimum for the cells that are part of the model
422 const float lg = tree_->getClampingThresMinLog() - tree_->getClampingThresMaxLog();
423 for (const octomap::OcTreeKey& model_cell : model_cells)
424 tree_->updateNode(model_cell, lg);
425 }
426 catch (...)
427 {
428 RCLCPP_ERROR(logger_, "Internal error while updating octree");
429 }
430 tree_->unlockWrite();
431 RCLCPP_DEBUG(logger_, "Processed point cloud in %lf ms", (node_->now() - start).seconds() * 1000.0);
432 tree_->triggerUpdateCallback();
433
434 if (filtered_cloud)
435 {
436 sensor_msgs::PointCloud2Modifier pcd_modifier(*filtered_cloud);
437 pcd_modifier.resize(filtered_cloud_size);
438 filtered_cloud_publisher_->publish(*filtered_cloud);
439 }
440}
441} // namespace occupancy_map_monitor
const std::string & getMapFrame() const
Gets the map frame (this is set either by the constor or a parameter).
void setMapFrame(const std::string &frame)
Sets the map frame.
bool updateTransformCache(const std::string &target_frame, const rclcpp::Time &target_time)
bool setParams(const std::string &name_space) override
Set updater params using struct that comes from parsing a yaml string. This must be called after setM...
virtual void updateMask(const sensor_msgs::msg::PointCloud2 &cloud, const Eigen::Vector3d &sensor_origin, std::vector< int > &mask)
bool initialize(const rclcpp::Node::SharedPtr &node) override
Do any necessary setup (subscribe to ros topics, etc.). This call assumes setMonitor() and setParams(...
ShapeHandle excludeShape(const shapes::ShapeConstPtr &shape) override
Main namespace for MoveIt.