Nav2 Navigation Stack - lyrical  lyrical
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
25 
26 void SavitzkyGolaySmoother::configure(
27  const nav2::LifecycleNode::WeakPtr & parent,
28  std::string name, nav2::TransformBuffer::SharedPtr/*tf*/,
29  std::shared_ptr<nav2_costmap_2d::CostmapSubscriber>/*costmap_sub*/,
30  std::shared_ptr<nav2_costmap_2d::FootprintSubscriber>/*footprint_sub*/)
31 {
32  auto node = parent.lock();
33  logger_ = node->get_logger();
34 
35  do_refinement_ = node->declare_or_get_parameter(
36  name + ".do_refinement", true);
37  refinement_num_ = node->declare_or_get_parameter(
38  name + ".refinement_num", 2);
39  enforce_path_inversion_ = node->declare_or_get_parameter(
40  name + ".enforce_path_inversion", true);
41  window_size_ = node->declare_or_get_parameter(
42  name + ".window_size", 7);
43  poly_order_ = node->declare_or_get_parameter(
44  name + ".poly_order", 3);
45 
46  if (window_size_ % 2 == 0 || window_size_ <= 2) {
48  "Savitzky-Golay Smoother requires an odd window size of 3 or greater");
49  }
50  half_window_size_ = (window_size_ - 1) / 2;
51  calculateCoefficients();
52 }
53 
54 // For more details on calculating Savitzky–Golay filter coefficients,
55 // see: https://www.colmryan.org/posts/savitsky_golay/
57 {
58  // We construct the Vandermonde matrix here
59  Eigen::VectorXd v = Eigen::VectorXd::LinSpaced(
60  window_size_, -half_window_size_,
61  half_window_size_);
62  Eigen::MatrixXd x = Eigen::MatrixXd::Ones(window_size_, poly_order_ + 1);
63  for (int i = 1; i <= poly_order_; i++) {
64  x.col(i) = (x.col(i - 1).array() * v.array()).matrix();
65  }
66  // Compute the pseudoinverse of X, (X^T * X)^-1 * X^T
67  Eigen::MatrixXd coeff_mat = (x.transpose() * x).inverse() * x.transpose();
68 
69  // Extract the smoothing coefficients
70  sg_coeffs_ = coeff_mat.row(0).transpose();
71 }
72 
74  nav_msgs::msg::Path & path,
75  const rclcpp::Duration & max_time)
76 {
77  steady_clock::time_point start = steady_clock::now();
78  double time_remaining = max_time.seconds();
79 
80  bool success = true, reversing_segment;
81  nav_msgs::msg::Path curr_path_segment;
82  curr_path_segment.header = path.header;
83 
84  std::vector<PathSegment> path_segments{
85  PathSegment{0u, static_cast<unsigned int>(path.poses.size() - 1)}};
86  if (enforce_path_inversion_) {
87  path_segments = nav2_util::findDirectionalPathSegments(path);
88  }
89 
90  // Minimum point size to smooth is SG filter size + start + end
91  unsigned int minimum_points = window_size_ + 2;
92  for (unsigned int i = 0; i != path_segments.size(); i++) {
93  if (path_segments[i].end - path_segments[i].start > minimum_points) {
94  // Populate path segment
95  curr_path_segment.poses.clear();
96  std::copy(
97  path.poses.begin() + path_segments[i].start,
98  path.poses.begin() + path_segments[i].end + 1,
99  std::back_inserter(curr_path_segment.poses));
100 
101  // Make sure we're still able to smooth with time remaining
102  steady_clock::time_point now = steady_clock::now();
103  time_remaining = max_time.seconds() - duration_cast<duration<double>>(now - start).count();
104 
105  if (time_remaining <= 0.0) {
106  RCLCPP_WARN(
107  logger_,
108  "Smoothing time exceeded allowed duration of %0.2f.", max_time.seconds());
109  throw nav2_core::SmootherTimedOut("Smoothing time exceed allowed duration");
110  }
111 
112  // Smooth path segment
113  success = success && smoothImpl(curr_path_segment, reversing_segment);
114 
115  // Assemble the path changes to the main path
116  std::copy(
117  curr_path_segment.poses.begin(),
118  curr_path_segment.poses.end(),
119  path.poses.begin() + path_segments[i].start);
120  }
121  }
122 
123  return success;
124 }
125 
127  nav_msgs::msg::Path & path,
128  bool & reversing_segment)
129 {
130  const unsigned int & path_size = path.poses.size();
131 
132  // Convert PoseStamped to Eigen
133  auto toEigenVec = [](const geometry_msgs::msg::PoseStamped & pose) -> Eigen::Vector2d {
134  return {pose.pose.position.x, pose.pose.position.y};
135  };
136 
137  auto applyFilterOverAxes =
138  [&](std::vector<geometry_msgs::msg::PoseStamped> & plan_pts,
139  const std::vector<Eigen::Vector2d> & init_plan_pts) -> void
140  {
141  // First point is fixed
142  for (unsigned int idx = 1; idx != path_size - 1; idx++) {
143  Eigen::Vector2d accum(0.0, 0.0);
144 
145  for (int j = -half_window_size_; j <= half_window_size_; j++) {
146  int path_idx = std::clamp<int>(idx + j, 0, path_size - 1);
147  accum += sg_coeffs_(j + half_window_size_) * init_plan_pts[path_idx];
148  }
149  plan_pts[idx].pose.position.x = accum.x();
150  plan_pts[idx].pose.position.y = accum.y();
151  }
152  };
153 
154  std::vector<Eigen::Vector2d> initial_path_poses(path.poses.size());
155  std::transform(
156  path.poses.begin(), path.poses.end(),
157  initial_path_poses.begin(), toEigenVec);
158  applyFilterOverAxes(path.poses, initial_path_poses);
159 
160  // Let's do additional refinement, it shouldn't take more than a couple milliseconds
161  if (do_refinement_) {
162  for (int i = 0; i < refinement_num_; i++) {
163  std::vector<Eigen::Vector2d> reined_initial_path_poses(path.poses.size());
164  std::transform(
165  path.poses.begin(), path.poses.end(),
166  reined_initial_path_poses.begin(), toEigenVec);
167  applyFilterOverAxes(path.poses, reined_initial_path_poses);
168  }
169  }
170 
171  nav2_util::updateApproximatePathOrientations(path, reversing_segment);
172  return true;
173 }
174 
175 } // namespace nav2_smoother
176 
177 #include "pluginlib/class_list_macros.hpp"
178 #include "nav2_ros_common/tf2_factories.hpp"
smoother interface that acts as a virtual base class for all smoother plugins
Definition: smoother.hpp:37
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.