Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
range_sensor_layer.cpp
1 /*
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2018 David V. Lu!!
5  * Copyright (c) 2020, Bytes Robotics
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * * Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  * * Redistributions in binary form must reproduce the above
15  * copyright notice, this list of conditions and the following
16  * disclaimer in the documentation and/or other materials provided
17  * with the distribution.
18  * * Neither the name of the copyright holder nor the names of its
19  * contributors may be used to endorse or promote products derived
20  * from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 #include <angles/angles.h>
37 #include <algorithm>
38 #include <list>
39 #include <limits>
40 #include <string>
41 #include <vector>
42 
43 #include "pluginlib/class_list_macros.hpp"
44 #include "geometry_msgs/msg/point_stamped.hpp"
45 #include "nav2_costmap_2d/range_sensor_layer.hpp"
46 
48 
49 using nav2_costmap_2d::LETHAL_OBSTACLE;
50 using nav2_costmap_2d::INSCRIBED_INFLATED_OBSTACLE;
51 using nav2_costmap_2d::NO_INFORMATION;
52 
53 using namespace std::literals::chrono_literals;
54 
55 namespace nav2_costmap_2d
56 {
57 
58 RangeSensorLayer::RangeSensorLayer() {}
59 
60 void RangeSensorLayer::onInitialize()
61 {
62  current_ = true;
63  was_reset_ = false;
64  buffered_readings_ = 0;
65  last_reading_time_ = clock_->now();
66  default_value_ = to_cost(0.5);
67 
68  matchSize();
69  resetRange();
70 
71  auto node = node_.lock();
72  if (!node) {
73  throw std::runtime_error{"Failed to lock node"};
74  }
75 
76  declareParameter("enabled", rclcpp::ParameterValue(true));
77  node->get_parameter(name_ + "." + "enabled", enabled_);
78  declareParameter("phi", rclcpp::ParameterValue(1.2));
79  node->get_parameter(name_ + "." + "phi", phi_v_);
80  declareParameter("inflate_cone", rclcpp::ParameterValue(1.0));
81  node->get_parameter(name_ + "." + "inflate_cone", inflate_cone_);
82  declareParameter("no_readings_timeout", rclcpp::ParameterValue(0.0));
83  node->get_parameter(name_ + "." + "no_readings_timeout", no_readings_timeout_);
84  declareParameter("clear_threshold", rclcpp::ParameterValue(0.2));
85  node->get_parameter(name_ + "." + "clear_threshold", clear_threshold_);
86  declareParameter("mark_threshold", rclcpp::ParameterValue(0.8));
87  node->get_parameter(name_ + "." + "mark_threshold", mark_threshold_);
88  declareParameter("clear_on_max_reading", rclcpp::ParameterValue(false));
89  node->get_parameter(name_ + "." + "clear_on_max_reading", clear_on_max_reading_);
90 
91  double temp_tf_tol = 0.0;
92  node->get_parameter("transform_tolerance", temp_tf_tol);
93  transform_tolerance_ = tf2::durationFromSec(temp_tf_tol);
94 
95  std::vector<std::string> topic_names{};
96  declareParameter("topics", rclcpp::ParameterValue(topic_names));
97  node->get_parameter(name_ + "." + "topics", topic_names);
98 
99  InputSensorType input_sensor_type = InputSensorType::ALL;
100  std::string sensor_type_name;
101  declareParameter("input_sensor_type", rclcpp::ParameterValue("ALL"));
102  node->get_parameter(name_ + "." + "input_sensor_type", sensor_type_name);
103 
104  std::transform(
105  sensor_type_name.begin(), sensor_type_name.end(),
106  sensor_type_name.begin(), ::toupper);
107  RCLCPP_INFO(
108  logger_, "%s: %s as input_sensor_type given",
109  name_.c_str(), sensor_type_name.c_str());
110 
111  if (sensor_type_name == "VARIABLE") {
112  input_sensor_type = InputSensorType::VARIABLE;
113  } else if (sensor_type_name == "FIXED") {
114  input_sensor_type = InputSensorType::FIXED;
115  } else if (sensor_type_name == "ALL") {
116  input_sensor_type = InputSensorType::ALL;
117  } else {
118  RCLCPP_ERROR(
119  logger_, "%s: Invalid input sensor type: %s. Defaulting to ALL.",
120  name_.c_str(), sensor_type_name.c_str());
121  }
122 
123  // Validate topic names list: it must be a (normally non-empty) list of strings
124  if (topic_names.empty()) {
125  RCLCPP_FATAL(
126  logger_, "Invalid topic names list: it must"
127  "be a non-empty list of strings");
128  return;
129  }
130 
131  // Traverse the topic names list subscribing to all of them with the same callback method
132  for (auto & topic_name : topic_names) {
133  topic_name = joinWithParentNamespace(topic_name);
134  if (input_sensor_type == InputSensorType::VARIABLE) {
135  processRangeMessageFunc_ = std::bind(
136  &RangeSensorLayer::processVariableRangeMsg, this,
137  std::placeholders::_1);
138  } else if (input_sensor_type == InputSensorType::FIXED) {
139  processRangeMessageFunc_ = std::bind(
140  &RangeSensorLayer::processFixedRangeMsg, this,
141  std::placeholders::_1);
142  } else if (input_sensor_type == InputSensorType::ALL) {
143  processRangeMessageFunc_ = std::bind(
144  &RangeSensorLayer::processRangeMsg, this,
145  std::placeholders::_1);
146  } else {
147  RCLCPP_ERROR(
148  logger_,
149  "%s: Invalid input sensor type: %s. Did you make a new type"
150  "and forgot to choose the subscriber for it?",
151  name_.c_str(), sensor_type_name.c_str());
152  }
153  range_subs_.push_back(
154  node->create_subscription<sensor_msgs::msg::Range>(
155  topic_name,
156  std::bind(
157  &RangeSensorLayer::bufferIncomingRangeMsg, this,
158  std::placeholders::_1),
160 
161  RCLCPP_INFO(
162  logger_, "RangeSensorLayer: subscribed to "
163  "topic %s", range_subs_.back()->get_topic_name());
164  }
165  global_frame_ = layered_costmap_->getGlobalFrameID();
166 }
167 
168 
169 double RangeSensorLayer::gamma(double theta)
170 {
171  if (fabs(theta) > max_angle_) {
172  return 0.0;
173  } else {
174  return 1 - pow(theta / max_angle_, 2);
175  }
176 }
177 
178 double RangeSensorLayer::delta(double phi)
179 {
180  return 1 - (1 + tanh(2 * (phi - phi_v_))) / 2;
181 }
182 
183 void RangeSensorLayer::get_deltas(double angle, double * dx, double * dy)
184 {
185  double ta = tan(angle);
186  if (ta == 0) {
187  *dx = 0;
188  } else {
189  *dx = resolution_ / ta;
190  }
191 
192  *dx = copysign(*dx, cos(angle));
193  *dy = copysign(resolution_, sin(angle));
194 }
195 
196 double RangeSensorLayer::sensor_model(double r, double phi, double theta)
197 {
198  double lbda = delta(phi) * gamma(theta);
199 
200  double delta = resolution_;
201 
202  if (phi >= 0.0 && phi < r - 2 * delta * r) {
203  return (1 - lbda) * (0.5);
204  } else if (phi < r - delta * r) {
205  return lbda * 0.5 * pow((phi - (r - 2 * delta * r)) / (delta * r), 2) +
206  (1 - lbda) * .5;
207  } else if (phi < r + delta * r) {
208  double J = (r - phi) / (delta * r);
209  return lbda * ((1 - (0.5) * pow(J, 2)) - 0.5) + 0.5;
210  } else {
211  return 0.5;
212  }
213 }
214 
215 void RangeSensorLayer::bufferIncomingRangeMsg(
216  const sensor_msgs::msg::Range::SharedPtr range_message)
217 {
218  range_message_mutex_.lock();
219  range_msgs_buffer_.push_back(*range_message);
220  range_message_mutex_.unlock();
221 }
222 
223 void RangeSensorLayer::updateCostmap()
224 {
225  std::list<sensor_msgs::msg::Range> range_msgs_buffer_copy;
226 
227  range_message_mutex_.lock();
228  range_msgs_buffer_copy = std::list<sensor_msgs::msg::Range>(range_msgs_buffer_);
229  range_msgs_buffer_.clear();
230  range_message_mutex_.unlock();
231 
232  for (auto & range_msgs_it : range_msgs_buffer_copy) {
233  processRangeMessageFunc_(range_msgs_it);
234  }
235 }
236 
237 void RangeSensorLayer::processRangeMsg(sensor_msgs::msg::Range & range_message)
238 {
239  if (range_message.min_range == range_message.max_range) {
240  processFixedRangeMsg(range_message);
241  } else {
242  processVariableRangeMsg(range_message);
243  }
244 }
245 
246 void RangeSensorLayer::processFixedRangeMsg(sensor_msgs::msg::Range & range_message)
247 {
248  if (!std::isinf(range_message.range)) {
249  RCLCPP_ERROR(
250  logger_,
251  "Fixed distance ranger (min_range == max_range) in frame %s sent invalid value. "
252  "Only -Inf (== object detected) and Inf (== no object detected) are valid.",
253  range_message.header.frame_id.c_str());
254  return;
255  }
256 
257  bool clear_sensor_cone = false;
258 
259  if (range_message.range > 0) { // +inf
260  if (!clear_on_max_reading_) {
261  return; // no clearing at all
262  }
263  clear_sensor_cone = true;
264  }
265 
266  range_message.range = range_message.min_range;
267 
268  updateCostmap(range_message, clear_sensor_cone);
269 }
270 
271 void RangeSensorLayer::processVariableRangeMsg(sensor_msgs::msg::Range & range_message)
272 {
273  if (range_message.range < range_message.min_range || range_message.range >
274  range_message.max_range)
275  {
276  return;
277  }
278 
279  bool clear_sensor_cone = false;
280 
281  if (range_message.range >= range_message.max_range && clear_on_max_reading_) {
282  clear_sensor_cone = true;
283  }
284 
285  updateCostmap(range_message, clear_sensor_cone);
286 }
287 
288 void RangeSensorLayer::updateCostmap(
289  sensor_msgs::msg::Range & range_message,
290  bool clear_sensor_cone)
291 {
292  max_angle_ = range_message.field_of_view / 2;
293 
294  geometry_msgs::msg::PointStamped in, out;
295  in.header.stamp = range_message.header.stamp;
296  in.header.frame_id = range_message.header.frame_id;
297 
298  if (!tf_->canTransform(
299  in.header.frame_id, global_frame_,
300  tf2_ros::fromMsg(in.header.stamp),
301  tf2_ros::fromRclcpp(transform_tolerance_)))
302  {
303  RCLCPP_INFO(
304  logger_, "Range sensor layer can't transform from %s to %s",
305  global_frame_.c_str(), in.header.frame_id.c_str());
306  return;
307  }
308 
309  tf_->transform(in, out, global_frame_, transform_tolerance_);
310 
311  double ox = out.point.x, oy = out.point.y;
312 
313  in.point.x = range_message.range;
314 
315  tf_->transform(in, out, global_frame_, transform_tolerance_);
316 
317  double tx = out.point.x, ty = out.point.y;
318 
319  // calculate target props
320  double dx = tx - ox, dy = ty - oy, theta = atan2(dy, dx), d = sqrt(dx * dx + dy * dy);
321 
322  // Integer Bounds of Update
323  int bx0, by0, bx1, by1;
324 
325  // Triangle that will be really updated; the other cells within bounds are ignored
326  // This triangle is formed by the origin and left and right sides of sonar cone
327  int Ox, Oy, Ax, Ay, Bx, By;
328 
329  // Bounds includes the origin
330  worldToMapNoBounds(ox, oy, Ox, Oy);
331  bx1 = bx0 = Ox;
332  by1 = by0 = Oy;
333  touch(ox, oy, &min_x_, &min_y_, &max_x_, &max_y_);
334 
335  // Update Map with Target Point
336  unsigned int aa, ab;
337  if (worldToMap(tx, ty, aa, ab)) {
338  setCost(aa, ab, 233);
339  touch(tx, ty, &min_x_, &min_y_, &max_x_, &max_y_);
340  }
341 
342  double mx, my;
343 
344  // Update left side of sonar cone
345  mx = ox + cos(theta - max_angle_) * d * 1.2;
346  my = oy + sin(theta - max_angle_) * d * 1.2;
347  worldToMapNoBounds(mx, my, Ax, Ay);
348  bx0 = std::min(bx0, Ax);
349  bx1 = std::max(bx1, Ax);
350  by0 = std::min(by0, Ay);
351  by1 = std::max(by1, Ay);
352  touch(mx, my, &min_x_, &min_y_, &max_x_, &max_y_);
353 
354  // Update right side of sonar cone
355  mx = ox + cos(theta + max_angle_) * d * 1.2;
356  my = oy + sin(theta + max_angle_) * d * 1.2;
357 
358  worldToMapNoBounds(mx, my, Bx, By);
359  bx0 = std::min(bx0, Bx);
360  bx1 = std::max(bx1, Bx);
361  by0 = std::min(by0, By);
362  by1 = std::max(by1, By);
363  touch(mx, my, &min_x_, &min_y_, &max_x_, &max_y_);
364 
365  // Limit Bounds to Grid
366  bx0 = std::max(0, bx0);
367  by0 = std::max(0, by0);
368  bx1 = std::min(static_cast<int>(size_x_), bx1);
369  by1 = std::min(static_cast<int>(size_y_), by1);
370 
371  for (unsigned int x = bx0; x <= (unsigned int)bx1; x++) {
372  for (unsigned int y = by0; y <= (unsigned int)by1; y++) {
373  bool update_xy_cell = true;
374 
375  // Unless inflate_cone_ is set to 100 %, we update cells only within the
376  // (partially inflated) sensor cone, projected on the costmap as a triangle.
377  // 0 % corresponds to just the triangle, but if your sensor fov is very
378  // narrow, the covered area can become zero due to cell discretization.
379  // See wiki description for more details
380  if (inflate_cone_ < 1.0) {
381  // Determine barycentric coordinates
382  int w0 = orient2d(Ax, Ay, Bx, By, x, y);
383  int w1 = orient2d(Bx, By, Ox, Oy, x, y);
384  int w2 = orient2d(Ox, Oy, Ax, Ay, x, y);
385 
386  // Barycentric coordinates inside area threshold; this is not mathematically
387  // sound at all, but it works!
388  float bcciath = -static_cast<float>(inflate_cone_) * area(Ax, Ay, Bx, By, Ox, Oy);
389  update_xy_cell = w0 >= bcciath && w1 >= bcciath && w2 >= bcciath;
390  }
391 
392  if (update_xy_cell) {
393  double wx, wy;
394  mapToWorld(x, y, wx, wy);
395  update_cell(ox, oy, theta, range_message.range, wx, wy, clear_sensor_cone);
396  }
397  }
398  }
399 
400  buffered_readings_++;
401  last_reading_time_ = clock_->now();
402 }
403 
404 void RangeSensorLayer::update_cell(
405  double ox, double oy, double ot, double r,
406  double nx, double ny, bool clear)
407 {
408  unsigned int x, y;
409  if (worldToMap(nx, ny, x, y)) {
410  double dx = nx - ox, dy = ny - oy;
411  double theta = atan2(dy, dx) - ot;
412  theta = angles::normalize_angle(theta);
413  double phi = sqrt(dx * dx + dy * dy);
414  double sensor = 0.0;
415  if (!clear) {
416  sensor = sensor_model(r, phi, theta);
417  }
418  double prior = to_prob(getCost(x, y));
419  double prob_occ = sensor * prior;
420  double prob_not = (1 - sensor) * (1 - prior);
421  double new_prob = prob_occ / (prob_occ + prob_not);
422 
423  RCLCPP_DEBUG(
424  logger_,
425  "%f %f | %f %f = %f", dx, dy, theta, phi, sensor);
426  RCLCPP_DEBUG(
427  logger_,
428  "%f | %f %f | %f", prior, prob_occ, prob_not, new_prob);
429  unsigned char c = to_cost(new_prob);
430  setCost(x, y, c);
431  }
432 }
433 
434 void RangeSensorLayer::resetRange()
435 {
436  min_x_ = min_y_ = std::numeric_limits<double>::max();
437  max_x_ = max_y_ = -std::numeric_limits<double>::max();
438 }
439 
440 void RangeSensorLayer::updateBounds(
441  double robot_x, double robot_y,
442  double robot_yaw, double * min_x, double * min_y,
443  double * max_x, double * max_y)
444 {
445  robot_yaw = 0 + robot_yaw; // Avoid error if variable not in use
446  if (layered_costmap_->isRolling()) {
447  updateOrigin(robot_x - getSizeInMetersX() / 2, robot_y - getSizeInMetersY() / 2);
448  }
449 
450  updateCostmap();
451 
452  *min_x = std::min(*min_x, min_x_);
453  *min_y = std::min(*min_y, min_y_);
454  *max_x = std::max(*max_x, max_x_);
455  *max_y = std::max(*max_y, max_y_);
456 
457  resetRange();
458 
459  if (!enabled_) {
460  current_ = true;
461  return;
462  }
463 
464  if (buffered_readings_ == 0) {
465  if (no_readings_timeout_ > 0.0 &&
466  (clock_->now() - last_reading_time_).seconds() >
467  no_readings_timeout_)
468  {
469  RCLCPP_WARN(
470  logger_,
471  "No range readings received for %.2f seconds, while expected at least every %.2f seconds.",
472  (clock_->now() - last_reading_time_).seconds(),
473  no_readings_timeout_);
474  current_ = false;
475  }
476  }
477 }
478 
479 void RangeSensorLayer::updateCosts(
480  nav2_costmap_2d::Costmap2D & master_grid,
481  int min_i, int min_j, int max_i, int max_j)
482 {
483  if (!enabled_) {
484  return;
485  }
486 
487  unsigned char * master_array = master_grid.getCharMap();
488  unsigned int span = master_grid.getSizeInCellsX();
489  unsigned char clear = to_cost(clear_threshold_), mark = to_cost(mark_threshold_);
490 
491  for (int j = min_j; j < max_j; j++) {
492  unsigned int it = j * span + min_i;
493  for (int i = min_i; i < max_i; i++) {
494  unsigned char prob = costmap_[it];
495  unsigned char current;
496  if (prob == nav2_costmap_2d::NO_INFORMATION) {
497  it++;
498  continue;
499  } else if (prob > mark) {
500  current = nav2_costmap_2d::LETHAL_OBSTACLE;
501  } else if (prob < clear) {
502  current = nav2_costmap_2d::FREE_SPACE;
503  } else {
504  it++;
505  continue;
506  }
507 
508  unsigned char old_cost = master_array[it];
509 
510  if (old_cost == NO_INFORMATION || old_cost < current) {
511  master_array[it] = current;
512  }
513  it++;
514  }
515  }
516 
517  buffered_readings_ = 0;
518 
519  // if not current due to reset, set current now after clearing
520  if (!current_ && was_reset_) {
521  was_reset_ = false;
522  current_ = true;
523  }
524 }
525 
526 void RangeSensorLayer::reset()
527 {
528  RCLCPP_DEBUG(logger_, "Resetting range sensor layer...");
529  deactivate();
530  resetMaps();
531  was_reset_ = true;
532  activate();
533 }
534 
535 void RangeSensorLayer::deactivate()
536 {
537  range_msgs_buffer_.clear();
538 }
539 
540 void RangeSensorLayer::activate()
541 {
542  range_msgs_buffer_.clear();
543 }
544 
545 } // namespace nav2_costmap_2d
A QoS profile for best-effort sensor data with a history of 10 messages.
A 2D costmap provides a mapping between points in the world and their associated "costs".
Definition: costmap_2d.hpp:69
unsigned char * getCharMap() const
Will return a pointer to the underlying unsigned char array used as the costmap.
Definition: costmap_2d.cpp:259
unsigned int getSizeInCellsX() const
Accessor for the x size of the costmap in cells.
Definition: costmap_2d.cpp:546
Abstract class for layered costmap plugin implementations.
Definition: layer.hpp:59
Takes in IR/Sonar/similar point measurement sensors and populates in costmap.