Nav2 Navigation Stack - rolling  main
ROS 2 Navigation Stack
savitzky_golay_smoother.cpp
1 // Copyright (c) 2022, Samsung Research America
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License. Reserved.
14 
15 #include <vector>
16 #include <memory>
17 #include "nav2_smoother/savitzky_golay_smoother.hpp"
18 #include "nav2_core/smoother_exceptions.hpp"
19 
20 namespace nav2_smoother
21 {
22 using namespace nav2_util::geometry_utils; // NOLINT
23 using namespace std::chrono; // NOLINT
24 using nav2::declare_parameter_if_not_declared;
26 
27 void SavitzkyGolaySmoother::configure(
28  const nav2::LifecycleNode::WeakPtr & parent,
29  std::string name, std::shared_ptr<tf2_ros::Buffer>/*tf*/,
30  std::shared_ptr<nav2_costmap_2d::CostmapSubscriber>/*costmap_sub*/,
31  std::shared_ptr<nav2_costmap_2d::FootprintSubscriber>/*footprint_sub*/)
32 {
33  auto node = parent.lock();
34  logger_ = node->get_logger();
35 
36  declare_parameter_if_not_declared(
37  node, name + ".do_refinement", rclcpp::ParameterValue(true));
38  declare_parameter_if_not_declared(
39  node, name + ".refinement_num", rclcpp::ParameterValue(2));
40  declare_parameter_if_not_declared(
41  node, name + ".enforce_path_inversion", rclcpp::ParameterValue(true));
42  declare_parameter_if_not_declared(
43  node, name + ".window_size", rclcpp::ParameterValue(7));
44  declare_parameter_if_not_declared(
45  node, name + ".poly_order", rclcpp::ParameterValue(3));
46  node->get_parameter(name + ".do_refinement", do_refinement_);
47  node->get_parameter(name + ".refinement_num", refinement_num_);
48  node->get_parameter(name + ".enforce_path_inversion", enforce_path_inversion_);
49  node->get_parameter(name + ".window_size", window_size_);
50  node->get_parameter(name + ".poly_order", poly_order_);
51  if (window_size_ % 2 == 0 || window_size_ <= 2) {
53  "Savitzky-Golay Smoother requires an odd window size of 3 or greater");
54  }
55  half_window_size_ = (window_size_ - 1) / 2;
56  calculateCoefficients();
57 }
58 
59 // For more details on calculating Savitzky–Golay filter coefficients,
60 // see: https://www.colmryan.org/posts/savitsky_golay/
62 {
63  // We construct the Vandermonde matrix here
64  Eigen::VectorXd v = Eigen::VectorXd::LinSpaced(window_size_, -half_window_size_,
65  half_window_size_);
66  Eigen::MatrixXd x = Eigen::MatrixXd::Ones(window_size_, poly_order_ + 1);
67  for(int i = 1; i <= poly_order_; i++) {
68  x.col(i) = (x.col(i - 1).array() * v.array()).matrix();
69  }
70  // Compute the pseudoinverse of X, (X^T * X)^-1 * X^T
71  Eigen::MatrixXd coeff_mat = (x.transpose() * x).inverse() * x.transpose();
72 
73  // Extract the smoothing coefficients
74  sg_coeffs_ = coeff_mat.row(0).transpose();
75 }
76 
78  nav_msgs::msg::Path & path,
79  const rclcpp::Duration & max_time)
80 {
81  steady_clock::time_point start = steady_clock::now();
82  double time_remaining = max_time.seconds();
83 
84  bool success = true, reversing_segment;
85  nav_msgs::msg::Path curr_path_segment;
86  curr_path_segment.header = path.header;
87 
88  std::vector<PathSegment> path_segments{
89  PathSegment{0u, static_cast<unsigned int>(path.poses.size() - 1)}};
90  if (enforce_path_inversion_) {
91  path_segments = nav2_util::findDirectionalPathSegments(path);
92  }
93 
94  // Minimum point size to smooth is SG filter size + start + end
95  unsigned int minimum_points = window_size_ + 2;
96  for (unsigned int i = 0; i != path_segments.size(); i++) {
97  if (path_segments[i].end - path_segments[i].start > minimum_points) {
98  // Populate path segment
99  curr_path_segment.poses.clear();
100  std::copy(
101  path.poses.begin() + path_segments[i].start,
102  path.poses.begin() + path_segments[i].end + 1,
103  std::back_inserter(curr_path_segment.poses));
104 
105  // Make sure we're still able to smooth with time remaining
106  steady_clock::time_point now = steady_clock::now();
107  time_remaining = max_time.seconds() - duration_cast<duration<double>>(now - start).count();
108 
109  if (time_remaining <= 0.0) {
110  RCLCPP_WARN(
111  logger_,
112  "Smoothing time exceeded allowed duration of %0.2f.", max_time.seconds());
113  throw nav2_core::SmootherTimedOut("Smoothing time exceed allowed duration");
114  }
115 
116  // Smooth path segment
117  success = success && smoothImpl(curr_path_segment, reversing_segment);
118 
119  // Assemble the path changes to the main path
120  std::copy(
121  curr_path_segment.poses.begin(),
122  curr_path_segment.poses.end(),
123  path.poses.begin() + path_segments[i].start);
124  }
125  }
126 
127  return success;
128 }
129 
131  nav_msgs::msg::Path & path,
132  bool & reversing_segment)
133 {
134  const unsigned int & path_size = path.poses.size();
135 
136  // Convert PoseStamped to Eigen
137  auto toEigenVec = [](const geometry_msgs::msg::PoseStamped & pose) -> Eigen::Vector2d {
138  return {pose.pose.position.x, pose.pose.position.y};
139  };
140 
141  auto applyFilterOverAxes =
142  [&](std::vector<geometry_msgs::msg::PoseStamped> & plan_pts,
143  const std::vector<Eigen::Vector2d> & init_plan_pts) -> void
144  {
145  // First point is fixed
146  for (unsigned int idx = 1; idx != path_size - 1; idx++) {
147  Eigen::Vector2d accum(0.0, 0.0);
148 
149  for(int j = -half_window_size_; j <= half_window_size_; j++) {
150  int path_idx = std::clamp<int>(idx + j, 0, path_size - 1);
151  accum += sg_coeffs_(j + half_window_size_) * init_plan_pts[path_idx];
152  }
153  plan_pts[idx].pose.position.x = accum.x();
154  plan_pts[idx].pose.position.y = accum.y();
155  }
156  };
157 
158  std::vector<Eigen::Vector2d> initial_path_poses(path.poses.size());
159  std::transform(path.poses.begin(), path.poses.end(),
160  initial_path_poses.begin(), toEigenVec);
161  applyFilterOverAxes(path.poses, initial_path_poses);
162 
163  // Let's do additional refinement, it shouldn't take more than a couple milliseconds
164  if (do_refinement_) {
165  for (int i = 0; i < refinement_num_; i++) {
166  std::vector<Eigen::Vector2d> reined_initial_path_poses(path.poses.size());
167  std::transform(path.poses.begin(), path.poses.end(),
168  reined_initial_path_poses.begin(), toEigenVec);
169  applyFilterOverAxes(path.poses, reined_initial_path_poses);
170  }
171  }
172 
173  nav2_util::updateApproximatePathOrientations(path, reversing_segment);
174  return true;
175 }
176 
177 } // namespace nav2_smoother
178 
179 #include "pluginlib/class_list_macros.hpp"
smoother interface that acts as a virtual base class for all smoother plugins
Definition: smoother.hpp:38
A path smoother implementation using Savitzky Golay filters.
void calculateCoefficients()
Method to calculate SavitzkyGolay Coefficients.
bool smoothImpl(nav_msgs::msg::Path &path, bool &reversing_segment)
Smoother method - does the smoothing on a segment.
bool smooth(nav_msgs::msg::Path &path, const rclcpp::Duration &max_time) override
Method to smooth given path.
A segment of a path in start/end indices.