Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
observation_buffer.cpp
1 /*********************************************************************
2  *
3  * Software License Agreement (BSD License)
4  *
5  * Copyright (c) 2008, 2013, Willow Garage, Inc.
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 Willow Garage, Inc. 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 OWNER 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  * Author: Eitan Marder-Eppstein
36  *********************************************************************/
37 #include "nav2_costmap_2d/observation_buffer.hpp"
38 
39 #include <algorithm>
40 #include <list>
41 #include <string>
42 #include <vector>
43 #include <chrono>
44 
45 #include "tf2/convert.hpp"
46 #include "sensor_msgs/point_cloud2_iterator.hpp"
47 using namespace std::chrono_literals;
48 
49 namespace nav2_costmap_2d
50 {
51 ObservationBuffer::ObservationBuffer(
52  const nav2::LifecycleNode::WeakPtr & parent,
53  std::string topic_name,
54  double observation_keep_time,
55  double expected_update_rate,
56  double min_obstacle_height, double max_obstacle_height, double obstacle_max_range,
57  double obstacle_min_range,
58  double raytrace_max_range, double raytrace_min_range, nav2::TransformBuffer & tf2_buffer,
59  std::string global_frame,
60  std::string sensor_frame,
61  tf2::Duration tf_tolerance)
62 : tf2_buffer_(tf2_buffer),
63  observation_keep_time_(rclcpp::Duration::from_seconds(observation_keep_time)),
64  expected_update_rate_(rclcpp::Duration::from_seconds(expected_update_rate)),
65  global_frame_(global_frame),
66  sensor_frame_(sensor_frame),
67  topic_name_(topic_name),
68  min_obstacle_height_(min_obstacle_height), max_obstacle_height_(max_obstacle_height),
69  obstacle_max_range_(obstacle_max_range), obstacle_min_range_(obstacle_min_range),
70  raytrace_max_range_(raytrace_max_range), raytrace_min_range_(
71  raytrace_min_range), tf_tolerance_(tf_tolerance)
72 {
73  auto node = parent.lock();
74  clock_ = node->get_clock();
75  logger_ = node->get_logger();
76  last_updated_ = node->now();
77 }
78 
79 void ObservationBuffer::bufferCloud(const sensor_msgs::msg::PointCloud2 & cloud)
80 {
81  geometry_msgs::msg::PointStamped global_origin;
82 
83  // check whether the origin frame has been set explicitly
84  // or whether we should get it from the cloud
85  std::string origin_frame = sensor_frame_ == "" ? cloud.header.frame_id : sensor_frame_;
86 
87  try {
88  // create a new observation to add to the list after being populated
89  auto observation = Observation::make_shared();
90 
91  // given these observations come from sensors...
92  // we'll need to store the origin pt of the sensor
93  geometry_msgs::msg::PointStamped local_origin;
94  local_origin.header.stamp = cloud.header.stamp;
95  local_origin.header.frame_id = origin_frame;
96  local_origin.point.x = 0;
97  local_origin.point.y = 0;
98  local_origin.point.z = 0;
99  tf2_buffer_.transform(local_origin, global_origin, global_frame_, tf_tolerance_);
100  tf2::convert(global_origin.point, observation->origin_);
101 
102  // make sure to pass on the raytrace/obstacle range
103  // of the observation buffer to the observations
104  observation->raytrace_max_range_ = raytrace_max_range_;
105  observation->raytrace_min_range_ = raytrace_min_range_;
106  observation->obstacle_max_range_ = obstacle_max_range_;
107  observation->obstacle_min_range_ = obstacle_min_range_;
108 
109  sensor_msgs::msg::PointCloud2 global_frame_cloud;
110 
111  // transform the point cloud
112  tf2_buffer_.transform(cloud, global_frame_cloud, global_frame_, tf_tolerance_);
113  global_frame_cloud.header.stamp = cloud.header.stamp;
114 
115  // now we need to remove observations from the cloud that are below
116  // or above our height thresholds
117  sensor_msgs::msg::PointCloud2 & observation_cloud = observation->cloud_;
118  observation_cloud.height = global_frame_cloud.height;
119  observation_cloud.width = global_frame_cloud.width;
120  observation_cloud.fields = global_frame_cloud.fields;
121  observation_cloud.is_bigendian = global_frame_cloud.is_bigendian;
122  observation_cloud.point_step = global_frame_cloud.point_step;
123  observation_cloud.row_step = global_frame_cloud.row_step;
124  observation_cloud.is_dense = global_frame_cloud.is_dense;
125 
126  unsigned int cloud_size = global_frame_cloud.height * global_frame_cloud.width;
127  sensor_msgs::PointCloud2Modifier modifier(observation_cloud);
128  modifier.resize(cloud_size);
129  unsigned int point_count = 0;
130 
131  // copy over the points that are within our height bounds
132  sensor_msgs::PointCloud2Iterator<float> iter_z(global_frame_cloud, "z");
133  std::vector<unsigned char>::const_iterator iter_global = global_frame_cloud.data.begin(),
134  iter_global_end = global_frame_cloud.data.end();
135  std::vector<unsigned char>::iterator iter_obs = observation_cloud.data.begin();
136  for (; iter_global != iter_global_end; ++iter_z, iter_global +=
137  global_frame_cloud.point_step)
138  {
139  if ((*iter_z) <= max_obstacle_height_ &&
140  (*iter_z) >= min_obstacle_height_)
141  {
142  std::copy(iter_global, iter_global + global_frame_cloud.point_step, iter_obs);
143  iter_obs += global_frame_cloud.point_step;
144  ++point_count;
145  }
146  }
147 
148  // resize the cloud for the number of legal points
149  modifier.resize(point_count);
150  observation_cloud.header.stamp = cloud.header.stamp;
151  observation_cloud.header.frame_id = global_frame_cloud.header.frame_id;
152 
153  observation_list_.push_front(std::move(observation));
154  } catch (tf2::TransformException & ex) {
155  // if an exception occurs, we need to remove the empty observation from the list
156  RCLCPP_ERROR(
157  logger_,
158  "TF Exception that should never happen for sensor frame: %s, cloud frame: %s, %s",
159  sensor_frame_.c_str(),
160  cloud.header.frame_id.c_str(), ex.what());
161  return;
162  }
163 
164  // if the update was successful, we want to update the last updated time
165  last_updated_ = clock_->now();
166 
167  // we'll also remove any stale observations from the list
168  purgeStaleObservations();
169 }
170 
171 // appends the observations from the buffer to the passed vector
172 void ObservationBuffer::getObservations(std::vector<Observation::ConstSharedPtr> & observations)
173 {
174  // first... let's make sure that we don't have any stale observations
175  purgeStaleObservations();
176 
177  // now we'll just copy the observation ptrs for the caller
178  observations.insert(observations.end(), observation_list_.cbegin(), observation_list_.cend());
179 }
180 
181 void ObservationBuffer::purgeStaleObservations()
182 {
183  if (!observation_list_.empty()) {
184  // if we're keeping observations for no time... then we'll only keep one observation
185  if (observation_keep_time_ == rclcpp::Duration(0.0s)) {
186  observation_list_.erase(std::next(observation_list_.begin()), observation_list_.end());
187  } else {
188  // otherwise remove all observations that are older then the keep time
189  observation_list_.remove_if([now = clock_->now(), this](const auto observation){
190  return (now - observation->cloud_.header.stamp) > observation_keep_time_;
191  });
192  }
193  }
194 }
195 
197 {
198  if (expected_update_rate_ == rclcpp::Duration(0.0s)) {
199  return true;
200  }
201 
202  bool current = (clock_->now() - last_updated_) <=
203  expected_update_rate_;
204  if (!current) {
205  RCLCPP_WARN(
206  logger_,
207  "The %s observation buffer has not been updated for %.2f seconds, "
208  "and it should be updated every %.2f seconds.",
209  topic_name_.c_str(),
210  (clock_->now() - last_updated_).seconds(),
211  expected_update_rate_.seconds());
212  }
213  return current;
214 }
215 
217 {
218  last_updated_ = clock_->now();
219 }
220 } // namespace nav2_costmap_2d
bool isCurrent() const
Check if the observation buffer is being update at its expected rate.
void getObservations(std::vector< Observation::ConstSharedPtr > &observations)
Pushes copies of all current observations onto the end of the vector passed in.
void bufferCloud(const sensor_msgs::msg::PointCloud2 &cloud)
Transforms a PointCloud to the global frame and buffers it Note: The burden is on the user to make su...
void resetLastUpdated()
Reset last updated timestamp.