From 4361c8f699e77a7e8319fd20d3dfaf93ea52bf69 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Fri, 25 Aug 2023 21:43:08 +0900 Subject: [PATCH 001/547] feat(behavior_path_planner): visualize ego predicted path (#4743) visualize ego predicted path Signed-off-by: kyoichi-sugahara --- .../marker_utils/utils.hpp | 4 ++ .../path_safety_checker/safety_check.hpp | 3 + .../src/marker_utils/utils.cpp | 55 +++++++++++++++++++ .../path_safety_checker/safety_check.cpp | 13 +++++ 4 files changed, 75 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index b3cb53ec18349..0fc10109240f9 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -131,6 +131,10 @@ MarkerArray createObjectsMarkerArray( MarkerArray createDrivableLanesMarkerArray( const std::vector & drivable_lanes, std::string && ns); +MarkerArray createPredictedPathMarkerArray( + const PredictedPath & ego_predicted_path, const vehicle_info_util::VehicleInfo & vehicle_info, + std::string && ns, const int32_t & id, const float & r, const float & g, const float & b); + } // namespace marker_utils #endif // BEHAVIOR_PATH_PLANNER__MARKER_UTILS__UTILS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 6d9df7677ead5..f9903b24f42cb 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -63,6 +63,9 @@ Polygon2d createExtendedPolygon( const Pose & obj_pose, const Shape & shape, const double lon_length, const double lat_margin, CollisionCheckDebug & debug); +PredictedPath convertToPredictedPath( + const std::vector & path, const double time_resolution); + double calcRssDistance( const double front_object_velocity, const double rear_object_velocity, const RSSparams & rss_params); diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index d8c87b6d1b291..40163f4f93d8a 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -408,4 +408,59 @@ MarkerArray createDrivableLanesMarkerArray( return msg; } +MarkerArray createPredictedPathMarkerArray( + const PredictedPath & predicted_path, const vehicle_info_util::VehicleInfo & vehicle_info, + std::string && ns, const int32_t & id, const float & r, const float & g, const float & b) +{ + if (predicted_path.path.empty()) { + return MarkerArray{}; + } + + const auto current_time = rclcpp::Clock{RCL_ROS_TIME}.now(); + MarkerArray msg; + + const auto & path = predicted_path.path; + + Marker marker = createDefaultMarker( + "map", current_time, ns, id, Marker::LINE_STRIP, createMarkerScale(0.1, 0.1, 0.1), + + createMarkerColor(r, g, b, 0.999)); + marker.lifetime = rclcpp::Duration::from_seconds(1.5); + + MarkerArray marker_array; + for (size_t i = 0; i < path.size(); ++i) { + marker.id = i + id; + marker.points.clear(); + + const auto & predicted_path_pose = path.at(i); + const double half_width = vehicle_info.vehicle_width_m / 2.0; + const double base_to_front = vehicle_info.vehicle_length_m - vehicle_info.rear_overhang_m; + const double base_to_rear = vehicle_info.rear_overhang_m; + + marker.points.push_back( + tier4_autoware_utils::calcOffsetPose(predicted_path_pose, base_to_front, -half_width, 0.0) + .position); + marker.points.push_back( + tier4_autoware_utils::calcOffsetPose(predicted_path_pose, base_to_front, half_width, 0.0) + .position); + marker.points.push_back( + tier4_autoware_utils::calcOffsetPose(predicted_path_pose, -base_to_rear, half_width, 0.0) + .position); + marker.points.push_back( + tier4_autoware_utils::calcOffsetPose(predicted_path_pose, -base_to_rear, -half_width, 0.0) + .position); + marker.points.push_back(marker.points.front()); + + marker_array.markers.push_back(marker); + } + return marker_array; + + marker.points.reserve(path.size()); + for (const auto & point : path) { + marker.points.push_back(point.position); + } + msg.markers.push_back(marker); + return msg; +} + } // namespace marker_utils diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index c6a68e108e7e1..7f3fe46c6b8ec 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -133,6 +133,19 @@ Polygon2d createExtendedPolygon( : tier4_autoware_utils::inverseClockwise(polygon); } +PredictedPath convertToPredictedPath( + const std::vector & path, const double time_resolution) +{ + PredictedPath predicted_path; + predicted_path.time_step = rclcpp::Duration::from_seconds(time_resolution); + predicted_path.path.resize(path.size()); + + for (size_t i = 0; i < path.size(); ++i) { + predicted_path.path.at(i) = path.at(i).pose; + } + return predicted_path; +} + double calcRssDistance( const double front_object_velocity, const double rear_object_velocity, const RSSparams & rss_params) From 692684f17158b8b4105618032de2006729303bf2 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Fri, 25 Aug 2023 22:56:39 +0900 Subject: [PATCH 002/547] chore(obstacle_cruise_planner): add maintainers (#4724) Signed-off-by: Takayuki Murooka --- planning/obstacle_cruise_planner/package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/planning/obstacle_cruise_planner/package.xml b/planning/obstacle_cruise_planner/package.xml index 6cfbf9225dd7e..f482be8ebafe7 100644 --- a/planning/obstacle_cruise_planner/package.xml +++ b/planning/obstacle_cruise_planner/package.xml @@ -6,6 +6,8 @@ Takayuki Murooka Yutaka Shimizu + Kosuke Takeuchi + Satoshi Ota Apache License 2.0 From bbb69c6efb5a7a8561ac4cdf8368154917291619 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Fri, 25 Aug 2023 23:01:21 +0900 Subject: [PATCH 003/547] feat(obstacle_cruise_planner): add a option to suppress feasible stop check (#4723) * feat(obstacle_cruise_planner): add a option to suppress feasible stop check Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka * fix Signed-off-by: Takayuki Murooka * fix Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/obstacle_cruise_planner.param.yaml | 1 + .../include/obstacle_cruise_planner/node.hpp | 1 + .../planner_interface.hpp | 4 +- planning/obstacle_cruise_planner/src/node.cpp | 8 +++- .../src/planner_interface.cpp | 39 +++++++++++-------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml index e2012fab43ba4..08ebea4284bf1 100644 --- a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml +++ b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml @@ -20,6 +20,7 @@ nearest_dist_deviation_threshold: 3.0 # [m] for finding nearest index nearest_yaw_deviation_threshold: 1.57 # [rad] for finding nearest index min_behavior_stop_margin: 3.0 # [m] + suppress_sudden_obstacle_stop: false stop_obstacle_type: unknown: true diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp index 260a4c6400588..ac6684d163aea 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp @@ -100,6 +100,7 @@ class ObstacleCruisePlannerNode : public rclcpp::Node bool enable_debug_info_; bool enable_calculation_time_info_; double min_behavior_stop_margin_; + bool suppress_sudden_obstacle_stop_; std::vector stop_obstacle_types_; std::vector inside_cruise_obstacle_types_; diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp index 9be6ec10e952e..c3fa364da269e 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp @@ -53,11 +53,12 @@ class PlannerInterface void setParam( const bool enable_debug_info, const bool enable_calculation_time_info, - const double min_behavior_stop_margin) + const double min_behavior_stop_margin, const bool suppress_sudden_obstacle_stop) { enable_debug_info_ = enable_debug_info; enable_calculation_time_info_ = enable_calculation_time_info; min_behavior_stop_margin_ = min_behavior_stop_margin; + suppress_sudden_obstacle_stop_ = suppress_sudden_obstacle_stop; } std::vector generateStopTrajectory( @@ -101,6 +102,7 @@ class PlannerInterface bool enable_calculation_time_info_{false}; LongitudinalInfo longitudinal_info_; double min_behavior_stop_margin_; + bool suppress_sudden_obstacle_stop_; // stop watch tier4_autoware_utils::StopWatch< diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index 70c0c687b2514..266e184a06a08 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -397,8 +397,11 @@ ObstacleCruisePlannerNode::ObstacleCruisePlannerNode(const rclcpp::NodeOptions & } min_behavior_stop_margin_ = declare_parameter("common.min_behavior_stop_margin"); + suppress_sudden_obstacle_stop_ = + declare_parameter("common.suppress_sudden_obstacle_stop"); planner_ptr_->setParam( - enable_debug_info_, enable_calculation_time_info_, min_behavior_stop_margin_); + enable_debug_info_, enable_calculation_time_info_, min_behavior_stop_margin_, + suppress_sudden_obstacle_stop_); } { // stop/cruise/slow down obstacle type @@ -436,7 +439,8 @@ rcl_interfaces::msg::SetParametersResult ObstacleCruisePlannerNode::onParam( tier4_autoware_utils::updateParam( parameters, "common.enable_calculation_time_info", enable_calculation_time_info_); planner_ptr_->setParam( - enable_debug_info_, enable_calculation_time_info_, min_behavior_stop_margin_); + enable_debug_info_, enable_calculation_time_info_, min_behavior_stop_margin_, + suppress_sudden_obstacle_stop_); tier4_autoware_utils::updateParam( parameters, "common.enable_slow_down_planning", enable_slow_down_planning_); diff --git a/planning/obstacle_cruise_planner/src/planner_interface.cpp b/planning/obstacle_cruise_planner/src/planner_interface.cpp index 4cf6bf9e23806..03cae6e6f9d88 100644 --- a/planning/obstacle_cruise_planner/src/planner_interface.cpp +++ b/planning/obstacle_cruise_planner/src/planner_interface.cpp @@ -300,26 +300,31 @@ std::vector PlannerInterface::generateStopTrajectory( return longitudinal_info_.safe_distance_margin; }(); - // Calculate feasible stop margin (Check the feasibility) - const double feasible_stop_dist = calcMinimumDistanceToStop( - planner_data.ego_vel, longitudinal_info_.limit_max_accel, - longitudinal_info_.limit_min_accel) + - dist_to_ego; - const double closest_obstacle_stop_dist = - closest_obstacle_dist - margin_from_obstacle - abs_ego_offset; - - bool will_collide_with_obstacle = false; - double feasible_margin_from_obstacle = margin_from_obstacle; - if (closest_obstacle_stop_dist < feasible_stop_dist) { - feasible_margin_from_obstacle = - margin_from_obstacle - (feasible_stop_dist - closest_obstacle_stop_dist); - will_collide_with_obstacle = true; - } + const auto [stop_margin_from_obstacle, will_collide_with_obstacle] = [&]() { + // Check feasibility to stop + if (suppress_sudden_obstacle_stop_) { + const double closest_obstacle_stop_dist = + closest_obstacle_dist - margin_from_obstacle - abs_ego_offset; + + // Calculate feasible stop margin (Check the feasibility) + const double feasible_stop_dist = calcMinimumDistanceToStop( + planner_data.ego_vel, longitudinal_info_.limit_max_accel, + longitudinal_info_.limit_min_accel) + + dist_to_ego; + + if (closest_obstacle_stop_dist < feasible_stop_dist) { + const auto feasible_margin_from_obstacle = + margin_from_obstacle - (feasible_stop_dist - closest_obstacle_stop_dist); + return std::make_pair(feasible_margin_from_obstacle, true); + } + } + return std::make_pair(margin_from_obstacle, false); + }(); // Generate Output Trajectory const double zero_vel_dist = [&]() { const double current_zero_vel_dist = - std::max(0.0, closest_obstacle_dist - abs_ego_offset - feasible_margin_from_obstacle); + std::max(0.0, closest_obstacle_dist - abs_ego_offset - stop_margin_from_obstacle); // Hold previous stop distance if necessary if ( @@ -378,7 +383,7 @@ std::vector PlannerInterface::generateStopTrajectory( StopPlanningDebugInfo::TYPE::STOP_CURRENT_OBSTACLE_VELOCITY, closest_stop_obstacle->velocity); stop_planning_debug_info_.set( - StopPlanningDebugInfo::TYPE::STOP_TARGET_OBSTACLE_DISTANCE, feasible_margin_from_obstacle); + StopPlanningDebugInfo::TYPE::STOP_TARGET_OBSTACLE_DISTANCE, stop_margin_from_obstacle); stop_planning_debug_info_.set(StopPlanningDebugInfo::TYPE::STOP_TARGET_VELOCITY, 0.0); stop_planning_debug_info_.set(StopPlanningDebugInfo::TYPE::STOP_TARGET_ACCELERATION, 0.0); From 4550e7acc799ac29884d4239d465868672983de5 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Fri, 25 Aug 2023 23:03:19 +0900 Subject: [PATCH 004/547] feat(crosswalk): add debug publisher of collision info (#4670) * feat(crosswalk): add debug publisher of collision info Signed-off-by: Takayuki Murooka * add plotter Signed-off-by: Takayuki Murooka * update doc Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../CMakeLists.txt | 5 + .../README.md | 10 + .../docs/time_to_collision_plot.png | Bin 0 -> 152214 bytes .../util.hpp | 3 +- .../package.xml | 1 + .../scripts/time_to_collision_plotter.py | 212 ++++++++++++++++++ .../src/debug.cpp | 6 +- .../src/manager.cpp | 2 +- .../src/scene_crosswalk.cpp | 30 ++- .../src/scene_crosswalk.hpp | 5 +- 10 files changed, 266 insertions(+), 8 deletions(-) create mode 100644 planning/behavior_velocity_crosswalk_module/docs/time_to_collision_plot.png create mode 100755 planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py diff --git a/planning/behavior_velocity_crosswalk_module/CMakeLists.txt b/planning/behavior_velocity_crosswalk_module/CMakeLists.txt index 118931780a92c..3217b3828b8d7 100644 --- a/planning/behavior_velocity_crosswalk_module/CMakeLists.txt +++ b/planning/behavior_velocity_crosswalk_module/CMakeLists.txt @@ -13,3 +13,8 @@ ament_auto_add_library(${PROJECT_NAME} SHARED ) ament_auto_package(INSTALL_TO_SHARE config) + +install(PROGRAMS + scripts/time_to_collision_plotter.py + DESTINATION lib/${PROJECT_NAME} +) diff --git a/planning/behavior_velocity_crosswalk_module/README.md b/planning/behavior_velocity_crosswalk_module/README.md index f85c472c6d856..5f82fe839fda6 100644 --- a/planning/behavior_velocity_crosswalk_module/README.md +++ b/planning/behavior_velocity_crosswalk_module/README.md @@ -218,6 +218,16 @@ When multiple crosswalks are nearby (such as intersection), this module may make ### Known Issues +### Debugging + +By `ros2 run behavior_velocity_crosswalk_module time_to_collision_plotter.py`, you can visualize the following figure of the ego and pedestrian's time to collision. +The label of each plot is `-`. + +
+ ![limitation](docs/time_to_collision_plot.png){width=1000} +
Plot of time to collision
+
+ ### References/External links [1] 佐藤 みなみ, 早坂 祥一, 清水 政行, 村野 隆彦, 横断歩行者に対するドライバのリスク回避行動のモデル化, 自動車技術会論文集, 2013, 44 巻, 3 号, p. 931-936. diff --git a/planning/behavior_velocity_crosswalk_module/docs/time_to_collision_plot.png b/planning/behavior_velocity_crosswalk_module/docs/time_to_collision_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..71ce642f382bc25a1975f54485cb262f0a7e30e9 GIT binary patch literal 152214 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ⅇ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

MYOHZuQ56+xr^SC1C=H~2)jkI7W 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 literal 0 HcmV?d00001 diff --git a/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp b/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp index 578ae66e40006..dfe682d0aa0c9 100644 --- a/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp +++ b/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -73,7 +74,7 @@ struct DebugData std::optional range_near_point{std::nullopt}; std::optional range_far_point{std::nullopt}; - std::vector> collision_points; + std::vector> collision_points; std::vector stop_poses; std::vector slow_poses; diff --git a/planning/behavior_velocity_crosswalk_module/package.xml b/planning/behavior_velocity_crosswalk_module/package.xml index d5ef92a4c097d..75f96c9194aef 100644 --- a/planning/behavior_velocity_crosswalk_module/package.xml +++ b/planning/behavior_velocity_crosswalk_module/package.xml @@ -34,6 +34,7 @@ sensor_msgs tier4_api_msgs tier4_autoware_utils + tier4_debug_msgs vehicle_info_util visualization_msgs diff --git a/planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py b/planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py new file mode 100755 index 0000000000000..5093683e17c22 --- /dev/null +++ b/planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 + +# Copyright 2023 TIER IV, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +from collections import deque +from copy import deepcopy + +from ament_index_python.packages import get_package_share_directory +import matplotlib.pyplot as plt +import rclpy +from rclpy.node import Node +from tier4_debug_msgs.msg import StringStamped +import yaml + + +def expand_margin(x_vec, y_vec, min_x=0.0, max_x=10.0): + expanded_x_vec = deepcopy(x_vec) + expanded_y_vec = deepcopy(y_vec) + + if min_x < expanded_x_vec[0]: + expanded_x_vec = [min_x] + expanded_x_vec + min_y = expanded_y_vec[0] + min_x - expanded_x_vec[0] + expanded_y_vec = [min_y] + expanded_y_vec + + if expanded_x_vec[-1] < max_x: + expanded_x_vec.append(max_x) + max_y = expanded_y_vec[-1] + max_x - expanded_x_vec[-2] + expanded_y_vec.append(max_y) + + return expanded_x_vec, expanded_y_vec + + +class CollisionInfo: + def __init__(self, module_id, obj_id, ttc, ttv, state): + self.module_id = module_id + self.obj_id = obj_id + self.ttc = ttc + self.ttv = ttv + self.state = state + + +class TimeToCollisionPlotter(Node): + def __init__(self, args): + super().__init__("time_to_collision_plotter") + + self.depth = args.depth + + self.sub_calculation_cost = self.create_subscription( + StringStamped, + "/planning/scenario_planning/lane_driving/behavior_planning/behavior_velocity_planner/debug/collision_info", + self.on_collision_info, + 1, + ) + + self.time_to_collision_hist = {} + self.max_hist_size = args.depth + + crosswalk_config = ( + get_package_share_directory("autoware_launch") + + "/config/planning/scenario_planning/lane_driving/behavior_planning/behavior_velocity_planner/crosswalk.param.yaml" + ) + with open(crosswalk_config) as f: + crosswalk_config_obj = yaml.safe_load(f)["/**"]["ros__parameters"] + + ego_pass_first_x = crosswalk_config_obj["crosswalk"]["pass_judge"][ + "ego_pass_first_margin_x" + ] + ego_pass_first_y = crosswalk_config_obj["crosswalk"]["pass_judge"][ + "ego_pass_first_margin_y" + ] + self.ego_pass_first_margin = [ego_pass_first_x, ego_pass_first_y] + + ego_pass_later_x = crosswalk_config_obj["crosswalk"]["pass_judge"][ + "ego_pass_later_margin_x" + ] + ego_pass_later_y = crosswalk_config_obj["crosswalk"]["pass_judge"][ + "ego_pass_later_margin_y" + ] + self.ego_pass_later_margin = [ego_pass_later_x, ego_pass_later_y] + + plt.ion() + _, self.ax = plt.subplots() + plt.show() + plt.connect("key_press_event", self.on_key) + + self.timer = self.create_timer(0.3, self.on_timer) + + def on_key(self, event): + if event.key == "c": + self.time_to_collision_hist = {} + + def on_collision_info(self, msg): + collision_info_data_vec = [] + collision_info_str_vec = msg.data[:-1].split(",") + if len(collision_info_str_vec) < 5: + return + + for i in range(int(len(collision_info_str_vec) / 5)): + collision_info_data_vec.append(CollisionInfo(*collision_info_str_vec[i : i + 5])) + + # memorize data in the history dictionary + for collision_info_data in collision_info_data_vec: + uuid = collision_info_data.module_id + collision_info_data.obj_id + if uuid not in self.time_to_collision_hist: + self.time_to_collision_hist[uuid] = deque([]) + + self.time_to_collision_hist[uuid].append( + [float(collision_info_data.ttv), float(collision_info_data.ttc)] + ) + + def on_timer(self): + # make the size of history not exceed the maximum value + for uuid in self.time_to_collision_hist: + while self.max_hist_size < len(self.time_to_collision_hist[uuid]): + self.time_to_collision_hist[uuid].popleft() + + plt.cla() + + # plot "ego_pass_later" border + ego_pass_later_x = [] + ego_pass_later_y = [] + for i in range(len(self.ego_pass_later_margin[0])): + ego_pass_later_x.append(self.ego_pass_later_margin[0][i]) + ego_pass_later_y.append(self.ego_pass_later_margin[1][i]) + expanded_ego_pass_later_x, expanded_ego_pass_later_y = expand_margin( + ego_pass_later_x, ego_pass_later_y + ) + plt.fill_between( + expanded_ego_pass_later_x, + expanded_ego_pass_later_y, + [expanded_ego_pass_later_y[-1] for i in expanded_ego_pass_later_y], + alpha=0.2, + color="green", + ) + + # plot "ego_pass_first" border + ego_pass_first_x = [] + ego_pass_first_y = [] + for i in range(len(self.ego_pass_first_margin[0])): + ego_pass_first_x.append(self.ego_pass_first_margin[0][i]) + ego_pass_first_y.append(self.ego_pass_first_margin[1][i]) + expanded_ego_pass_first_x, expanded_ego_pass_first_y = expand_margin( + ego_pass_first_x, ego_pass_first_y + ) + plt.fill_between( + expanded_ego_pass_first_y, + [expanded_ego_pass_first_x[0] for i in expanded_ego_pass_first_x], + expanded_ego_pass_first_x, + alpha=0.2, + color="blue", + ) + plt.text( + expanded_ego_pass_later_x[0], + expanded_ego_pass_later_y[-1], + "Ego passes later.", + va="top", + ha="left", + color="green", + ) + plt.text( + expanded_ego_pass_first_y[-1], + expanded_ego_pass_first_x[0], + "Ego passes first.", + va="bottom", + ha="right", + color="blue", + ) + plt.text(0, 0, "Ego yields.", va="bottom", ha="left", color="red") + + # plot history + for uuid in self.time_to_collision_hist: + hist = self.time_to_collision_hist[uuid] + plt.plot( + [val[0] for val in hist], + [val[1] for val in hist], + label=uuid[0:4] + "-" + uuid[4:8], + ) + + plt.legend() + plt.draw() + plt.title("Time to collision") + plt.xlabel("Pedestrian's time to collision") + plt.ylabel("Ego's time to collision") + plt.pause(0.01) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "-d", + "--depth", + type=float, + default=100, + ) + args = parser.parse_args() + + rclpy.init() + node = TimeToCollisionPlotter(args) + rclpy.spin(node) diff --git a/planning/behavior_velocity_crosswalk_module/src/debug.cpp b/planning/behavior_velocity_crosswalk_module/src/debug.cpp index 409898233cbbe..a5bc3ebf72179 100644 --- a/planning/behavior_velocity_crosswalk_module/src/debug.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/debug.cpp @@ -44,8 +44,8 @@ visualization_msgs::msg::MarkerArray createCrosswalkMarkers( { for (size_t i = 0; i < debug_data.collision_points.size(); ++i) { const auto & collision_point = debug_data.collision_points.at(i); - const auto & point = collision_point.first; - const auto & state = collision_point.second; + const auto & point = std::get<1>(collision_point); + const auto & state = std::get<2>(collision_point); auto marker = createDefaultMarker( "map", now, "collision point state", uid + i++, Marker::TEXT_VIEW_FACING, @@ -134,7 +134,7 @@ visualization_msgs::msg::MarkerArray createCrosswalkMarkers( "map", now, "collision point", uid, Marker::POINTS, createMarkerScale(0.25, 0.25, 0.0), createMarkerColor(1.0, 0.0, 1.0, 0.999)); for (const auto & collision_point : debug_data.collision_points) { - marker.points.push_back(collision_point.first.collision_point); + marker.points.push_back(std::get<1>(collision_point).collision_point); } msg.markers.push_back(marker); } diff --git a/planning/behavior_velocity_crosswalk_module/src/manager.cpp b/planning/behavior_velocity_crosswalk_module/src/manager.cpp index 992d1c48eb9f4..96e4e873ce111 100644 --- a/planning/behavior_velocity_crosswalk_module/src/manager.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/manager.cpp @@ -129,7 +129,7 @@ void CrosswalkModuleManager::launchNewModules(const PathWithLaneId & path) const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); registerModule(std::make_shared( - id, lanelet_map_ptr, p, *opt_use_regulatory_element_, logger, clock_)); + node_, id, lanelet_map_ptr, p, *opt_use_regulatory_element_, logger, clock_)); generateUUID(id); updateRTCStatus(getUUID(id), true, std::numeric_limits::lowest(), path.header.stamp); }; diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index 0edfa6167e999..3b69b74c53d80 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace behavior_velocity_planner @@ -164,10 +165,27 @@ std::optional toStdOptional( } return std::nullopt; } + +tier4_debug_msgs::msg::StringStamped createStringStampedMessage( + const rclcpp::Time & now, const int64_t module_id_, + const std::vector> & collision_points) +{ + tier4_debug_msgs::msg::StringStamped msg; + msg.stamp = now; + for (const auto & collision_point : collision_points) { + std::stringstream ss; + ss << module_id_ << "," << std::get<0>(collision_point).substr(0, 4) << "," + << std::get<1>(collision_point).time_to_collision << "," + << std::get<1>(collision_point).time_to_vehicle << "," + << static_cast(std::get<2>(collision_point)) << ","; + msg.data += ss.str(); + } + return msg; +} } // namespace CrosswalkModule::CrosswalkModule( - const int64_t module_id, const lanelet::LaneletMapPtr & lanelet_map_ptr, + rclcpp::Node & node, const int64_t module_id, const lanelet::LaneletMapPtr & lanelet_map_ptr, const PlannerParam & planner_param, const bool use_regulatory_element, const rclcpp::Logger & logger, const rclcpp::Clock::SharedPtr clock) : SceneModuleInterface(module_id, logger, clock), @@ -190,6 +208,9 @@ CrosswalkModule::CrosswalkModule( } crosswalk_ = lanelet_map_ptr->laneletLayer.get(module_id); } + + collision_info_pub_ = + node.create_publisher("~/debug/collision_info", 1); } bool CrosswalkModule::modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) @@ -264,6 +285,10 @@ bool CrosswalkModule::modifyPathVelocity(PathWithLaneId * path, StopReason * sto } recordTime(4); + const auto collision_info_msg = + createStringStampedMessage(clock_->now(), module_id_, debug_data_.collision_points); + collision_info_pub_->publish(collision_info_msg); + return true; } @@ -882,7 +907,8 @@ void CrosswalkModule::updateObjectState( if (collision_point) { const auto collision_state = object_info_manager_.getCollisionState(obj_uuid); - debug_data_.collision_points.push_back(std::make_pair(*collision_point, collision_state)); + debug_data_.collision_points.push_back( + std::make_tuple(obj_uuid, *collision_point, collision_state)); } } object_info_manager_.finalize(); diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp index 961126bf4bca1..edff62371f6b4 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -257,7 +258,7 @@ class CrosswalkModule : public SceneModuleInterface }; CrosswalkModule( - const int64_t module_id, const lanelet::LaneletMapPtr & lanelet_map_ptr, + rclcpp::Node & node, const int64_t module_id, const lanelet::LaneletMapPtr & lanelet_map_ptr, const PlannerParam & planner_param, const bool use_regulatory_element, const rclcpp::Logger & logger, const rclcpp::Clock::SharedPtr clock); @@ -360,6 +361,8 @@ class CrosswalkModule : public SceneModuleInterface const int64_t module_id_; + rclcpp::Publisher::SharedPtr collision_info_pub_; + lanelet::ConstLanelet crosswalk_; lanelet::ConstLineStrings3d stop_lines_; From f42f760620a88b8008ae478d3143accff1576184 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Fri, 25 Aug 2023 23:14:18 +0900 Subject: [PATCH 005/547] feat(behavior_path_planner): update filter objects by velocity (#4742) --- .../path_safety_checker/objects_filtering.hpp | 35 +++++++++++++------ .../path_safety_checker/objects_filtering.cpp | 16 ++++++--- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp index b4575eb4d3b7e..8a3b7094c69d1 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp @@ -55,24 +55,37 @@ PredictedObjects filterObjects( const std::shared_ptr & params); /** - * @brief Filter objects based on their velocity. + * @brief Filters objects based on their velocity. * - * @param objects The predicted objects to filter. - * @param lim_v Velocity limit for filtering. - * @return PredictedObjects The filtered objects. + * Depending on the remove_above_threshold parameter, this function either removes objects with + * velocities above the given threshold or only keeps those objects. It uses the helper function + * filterObjectsByVelocity() to do the actual filtering. + * + * @param objects The objects to be filtered. + * @param velocity_threshold The velocity threshold for the filtering. + * @param remove_above_threshold If true, objects with velocities above the threshold are removed. + * If false, only objects with velocities above the threshold are + * kept. + * @return A new collection of objects that have been filtered according to the rules. */ -PredictedObjects filterObjectsByVelocity(const PredictedObjects & objects, double lim_v); +PredictedObjects filterObjectsByVelocity( + const PredictedObjects & objects, const double velocity_threshold, + const bool remove_above_threshold = true); /** - * @brief Filter objects based on a velocity range. + * @brief Helper function to filter objects based on their velocity. * - * @param objects The predicted objects to filter. - * @param min_v Minimum velocity for filtering. - * @param max_v Maximum velocity for filtering. - * @return PredictedObjects The filtered objects. + * This function iterates over all objects and calculates their velocity norm. If the velocity norm + * is within the velocity_threshold and max_velocity range, the object is added to a new collection. + * This new collection is then returned. + * + * @param objects The objects to be filtered. + * @param velocity_threshold The minimum velocity for an object to be included in the output. + * @param max_velocity The maximum velocity for an object to be included in the output. + * @return A new collection of objects that have been filtered according to the rules. */ PredictedObjects filterObjectsByVelocity( - const PredictedObjects & objects, double min_v, double max_v); + const PredictedObjects & objects, double velocity_threshold, double max_velocity); /** * @brief Filter objects based on their position relative to a current_pose. diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index 33afe0fe6642f..a775e7c16efed 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -37,7 +37,7 @@ PredictedObjects filterObjects( PredictedObjects filtered_objects; - filtered_objects = filterObjectsByVelocity(*objects, ignore_object_velocity_threshold); + filtered_objects = filterObjectsByVelocity(*objects, ignore_object_velocity_threshold, false); filterObjectsByClass(filtered_objects, target_object_types); @@ -51,13 +51,19 @@ PredictedObjects filterObjects( return filtered_objects; } -PredictedObjects filterObjectsByVelocity(const PredictedObjects & objects, double lim_v) +PredictedObjects filterObjectsByVelocity( + const PredictedObjects & objects, const double velocity_threshold, + const bool remove_above_threshold) { - return filterObjectsByVelocity(objects, -lim_v, lim_v); + if (remove_above_threshold) { + return filterObjectsByVelocity(objects, -velocity_threshold, velocity_threshold); + } else { + return filterObjectsByVelocity(objects, velocity_threshold, std::numeric_limits::max()); + } } PredictedObjects filterObjectsByVelocity( - const PredictedObjects & objects, double min_v, double max_v) + const PredictedObjects & objects, double velocity_threshold, double max_velocity) { PredictedObjects filtered; filtered.header = objects.header; @@ -65,7 +71,7 @@ PredictedObjects filterObjectsByVelocity( const auto v_norm = std::hypot( obj.kinematics.initial_twist_with_covariance.twist.linear.x, obj.kinematics.initial_twist_with_covariance.twist.linear.y); - if (min_v < v_norm && v_norm < max_v) { + if (velocity_threshold < v_norm && v_norm < max_velocity) { filtered.objects.push_back(obj); } } From febb7d556ef94e0273e5a3f6d218404abe36c0be Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Fri, 25 Aug 2023 23:44:12 +0900 Subject: [PATCH 006/547] refactor(map_based_prediction): move hard coded declare parameters to yaml file (#4756) * move hard coded declare parameters to yaml file Signed-off-by: yoshiri * style(pre-commit): autofix --------- Signed-off-by: yoshiri Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../config/map_based_prediction.param.yaml | 3 ++ .../src/map_based_prediction_node.cpp | 42 +++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/perception/map_based_prediction/config/map_based_prediction.param.yaml b/perception/map_based_prediction/config/map_based_prediction.param.yaml index cb9a61b600e43..1f6def24912a2 100644 --- a/perception/map_based_prediction/config/map_based_prediction.param.yaml +++ b/perception/map_based_prediction/config/map_based_prediction.param.yaml @@ -11,7 +11,10 @@ sigma_yaw_angle_deg: 5.0 #[angle degree] object_buffer_time_length: 2.0 #[s] history_time_length: 1.0 #[s] + # parameter for shoulder lane prediction + prediction_time_horizon_rate_for_validate_shoulder_lane_length: 0.8 + # parameters for lc prediction lane_change_detection: method: "lat_diff_distance" # choose from "lat_diff_distance" or "time_to_change_lane" time_to_change_lane: diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index be6327cf5c01a..8d633dbd5af64 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -639,33 +639,31 @@ lanelet::Lanelets getLeftOppositeLanelets( MapBasedPredictionNode::MapBasedPredictionNode(const rclcpp::NodeOptions & node_options) : Node("map_based_prediction", node_options), debug_accumulated_time_(0.0) { - enable_delay_compensation_ = declare_parameter("enable_delay_compensation", true); - prediction_time_horizon_ = declare_parameter("prediction_time_horizon", 10.0); - prediction_sampling_time_interval_ = declare_parameter("prediction_sampling_delta_time", 0.5); + enable_delay_compensation_ = declare_parameter("enable_delay_compensation"); + prediction_time_horizon_ = declare_parameter("prediction_time_horizon"); + prediction_sampling_time_interval_ = declare_parameter("prediction_sampling_delta_time"); min_velocity_for_map_based_prediction_ = - declare_parameter("min_velocity_for_map_based_prediction", 1.0); - min_crosswalk_user_velocity_ = declare_parameter("min_crosswalk_user_velocity", 1.0); + declare_parameter("min_velocity_for_map_based_prediction"); + min_crosswalk_user_velocity_ = declare_parameter("min_crosswalk_user_velocity"); dist_threshold_for_searching_lanelet_ = - declare_parameter("dist_threshold_for_searching_lanelet", 3.0); + declare_parameter("dist_threshold_for_searching_lanelet"); delta_yaw_threshold_for_searching_lanelet_ = - declare_parameter("delta_yaw_threshold_for_searching_lanelet", 0.785); - sigma_lateral_offset_ = declare_parameter("sigma_lateral_offset", 0.5); - sigma_yaw_angle_deg_ = declare_parameter("sigma_yaw_angle_deg", 5.0); - object_buffer_time_length_ = declare_parameter("object_buffer_time_length", 2.0); - history_time_length_ = declare_parameter("history_time_length", 1.0); + declare_parameter("delta_yaw_threshold_for_searching_lanelet"); + sigma_lateral_offset_ = declare_parameter("sigma_lateral_offset"); + sigma_yaw_angle_deg_ = declare_parameter("sigma_yaw_angle_deg"); + object_buffer_time_length_ = declare_parameter("object_buffer_time_length"); + history_time_length_ = declare_parameter("history_time_length"); { // lane change detection lane_change_detection_method_ = declare_parameter("lane_change_detection.method"); // lane change detection by time_to_change_lane - dist_threshold_to_bound_ = declare_parameter( - "lane_change_detection.time_to_change_lane.dist_threshold_for_lane_change_detection", - 1.0); // 1m - time_threshold_to_bound_ = declare_parameter( - "lane_change_detection.time_to_change_lane.time_threshold_for_lane_change_detection", - 5.0); // 5 sec - cutoff_freq_of_velocity_lpf_ = declare_parameter( - "lane_change_detection.time_to_change_lane.cutoff_freq_of_velocity_for_lane_change_detection", - 0.1); // 0.1Hz + dist_threshold_to_bound_ = declare_parameter( + "lane_change_detection.time_to_change_lane.dist_threshold_for_lane_change_detection"); // 1m + time_threshold_to_bound_ = declare_parameter( + "lane_change_detection.time_to_change_lane.time_threshold_for_lane_change_detection"); + cutoff_freq_of_velocity_lpf_ = declare_parameter( + "lane_change_detection.time_to_change_lane.cutoff_freq_of_velocity_for_lane_change_" + "detection"); // lane change detection by lat_diff_distance dist_ratio_threshold_to_left_bound_ = declare_parameter( @@ -680,11 +678,11 @@ MapBasedPredictionNode::MapBasedPredictionNode(const rclcpp::NodeOptions & node_ num_continuous_state_transition_ = declare_parameter("lane_change_detection.num_continuous_state_transition"); } - reference_path_resolution_ = declare_parameter("reference_path_resolution", 0.5); + reference_path_resolution_ = declare_parameter("reference_path_resolution"); /* prediction path will disabled when the estimated path length exceeds lanelet length. This * parameter control the estimated path length = vx * th * (rate) */ prediction_time_horizon_rate_for_validate_lane_length_ = - declare_parameter("prediction_time_horizon_rate_for_validate_lane_length", 0.8); + declare_parameter("prediction_time_horizon_rate_for_validate_shoulder_lane_length"); path_generator_ = std::make_shared( prediction_time_horizon_, prediction_sampling_time_interval_, min_crosswalk_user_velocity_); From a1584619bc5974c11f99acfc06e1e9bd158e9067 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Sun, 27 Aug 2023 21:50:20 +0900 Subject: [PATCH 007/547] feat(safety_check): add hysteresis factor in safety check logic (#4735) * feat(safety_check): add hysteresis factor Signed-off-by: satoshi-ota * feat(lane_change): add hysteresis factor Signed-off-by: satoshi-ota * feat(avoidance): add hysteresis factor Signed-off-by: satoshi-ota * feat(avoidance): add time series hysteresis Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../config/avoidance/avoidance.param.yaml | 3 ++- .../scene_module/avoidance/avoidance_module.hpp | 4 ++++ .../utils/avoidance/avoidance_module_data.hpp | 3 ++- .../utils/path_safety_checker/safety_check.hpp | 2 +- .../src/scene_module/avoidance/avoidance_module.cpp | 11 +++++++++-- .../src/scene_module/avoidance/manager.cpp | 5 +++-- .../src/scene_module/lane_change/manager.cpp | 4 ++-- .../src/scene_module/lane_change/normal.cpp | 2 +- .../src/utils/avoidance/utils.cpp | 2 +- .../src/utils/path_safety_checker/safety_check.cpp | 6 +++--- 10 files changed, 28 insertions(+), 14 deletions(-) diff --git a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml index 9ecc1a63b55b0..26f53cc91c4cf 100644 --- a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml +++ b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml @@ -142,7 +142,8 @@ time_resolution: 0.5 # [s] time_horizon: 10.0 # [s] safety_check_backward_distance: 100.0 # [m] - safety_check_hysteresis_factor: 2.0 # [-] + hysteresis_factor_expand_rate: 2.0 # [-] + hysteresis_factor_safe_count: 10 # [-] # rss parameters expected_front_deceleration: -1.0 # [m/ss] expected_rear_deceleration: -1.0 # [m/ss] diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp index 7889e91767155..096ae61a9432e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp @@ -556,6 +556,8 @@ class AvoidanceModule : public SceneModuleInterface bool arrived_path_end_{false}; + bool safe_{true}; + std::shared_ptr parameters_; helper::avoidance::AvoidanceHelper helper_; @@ -576,6 +578,8 @@ class AvoidanceModule : public SceneModuleInterface ObjectDataArray registered_objects_; + mutable size_t safe_count_{0}; + mutable ObjectDataArray ego_stopped_objects_; mutable ObjectDataArray stopped_objects_; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 1f47b804e185b..5484ef834a177 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -194,7 +194,8 @@ struct AvoidanceParameters double safety_check_backward_distance; // transit hysteresis (unsafe to safe) - double safety_check_hysteresis_factor; + size_t hysteresis_factor_safe_count; + double hysteresis_factor_expand_rate; // keep target velocity in yield maneuver double yield_velocity; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index f9903b24f42cb..79947d826ed10 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -101,7 +101,7 @@ bool checkCollision( const ExtendedPredictedObject & target_object, const PredictedPathWithPolygon & target_object_path, const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, - CollisionCheckDebug & debug); + const double hysteresis_factor, CollisionCheckDebug & debug); /** * @brief Check collision between ego path footprints with extra longitudinal stopping margin and diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index ff5706c34bdc7..e0c79a46d731e 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1869,6 +1869,8 @@ bool AvoidanceModule::isSafePath( return true; } + const auto hysteresis_factor = safe_ ? 1.0 : parameters_->hysteresis_factor_expand_rate; + const auto safety_check_target_objects = utils::avoidance::getSafetyCheckTargetObjects( avoid_data_, planner_data_, parameters_, is_right_shift.value()); @@ -1879,13 +1881,16 @@ bool AvoidanceModule::isSafePath( CollisionCheckDebug collision{}; if (!utils::path_safety_checker::checkCollision( shifted_path.path, ego_predicted_path, object, obj_path, p, parameters_->rss_params, - collision)) { + hysteresis_factor, collision)) { + safe_count_ = 0; return false; } } } - return true; + safe_count_++; + + return safe_ || safe_count_ > parameters_->hysteresis_factor_safe_count; } void AvoidanceModule::generateExtendedDrivableArea(BehaviorModuleOutput & output) const @@ -2505,6 +2510,8 @@ void AvoidanceModule::updateData() // update rtc status. updateRTCData(); + + safe_ = avoid_data_.safe; } void AvoidanceModule::processOnEntry() diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp index c13c237673625..2d4a6b9c42b98 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp @@ -145,8 +145,9 @@ AvoidanceModuleManager::AvoidanceModuleManager( p.safety_check_time_resolution = get_parameter(node, ns + "time_resolution"); p.safety_check_backward_distance = get_parameter(node, ns + "safety_check_backward_distance"); - p.safety_check_hysteresis_factor = - get_parameter(node, ns + "safety_check_hysteresis_factor"); + p.hysteresis_factor_expand_rate = + get_parameter(node, ns + "hysteresis_factor_expand_rate"); + p.hysteresis_factor_safe_count = get_parameter(node, ns + "hysteresis_factor_safe_count"); } // safety check rss params diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index f392ef6a26e22..31aab9da87e2a 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -288,8 +288,8 @@ AvoidanceByLaneChangeModuleManager::AvoidanceByLaneChangeModuleManager( // safety check { std::string ns = "avoidance.safety_check."; - p.safety_check_hysteresis_factor = - get_parameter(node, ns + "safety_check_hysteresis_factor"); + p.hysteresis_factor_expand_rate = + get_parameter(node, ns + "hysteresis_factor_expand_rate"); } avoidance_parameters_ = std::make_shared(p); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 3db3e049c3c81..251720f4202d5 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1380,7 +1380,7 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( obj, lane_change_parameters_->use_all_predicted_path); for (const auto & obj_path : obj_predicted_paths) { if (!utils::path_safety_checker::checkCollision( - path, ego_predicted_path, obj, obj_path, common_parameters, rss_params, + path, ego_predicted_path, obj, obj_path, common_parameters, rss_params, 1.0, current_debug_data.second)) { path_safety_status.is_safe = false; updateDebugInfo(current_debug_data, path_safety_status.is_safe); diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 4cbfebef07a39..c1a883d7a9b82 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -771,7 +771,7 @@ void fillAvoidanceNecessity( } // TRUE -> ? (check with hysteresis factor) - object_data.avoid_required = check_necessity(parameters->safety_check_hysteresis_factor); + object_data.avoid_required = check_necessity(parameters->hysteresis_factor_expand_rate); } void fillObjectStoppableJudge( diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 7f3fe46c6b8ec..579862de93865 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -232,7 +232,7 @@ bool checkCollision( const ExtendedPredictedObject & target_object, const PredictedPathWithPolygon & target_object_path, const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, - CollisionCheckDebug & debug) + double hysteresis_factor, CollisionCheckDebug & debug) { debug.lerped_path.reserve(target_object_path.path.size()); @@ -286,8 +286,8 @@ bool checkCollision( const auto min_lon_length = calcMinimumLongitudinalLength(front_object_velocity, rear_object_velocity, rss_parameters); - const auto & lon_offset = std::max(rss_dist, min_lon_length); - const auto & lat_margin = rss_parameters.lateral_distance_max_threshold; + const auto & lon_offset = std::max(rss_dist, min_lon_length) * hysteresis_factor; + const auto & lat_margin = rss_parameters.lateral_distance_max_threshold * hysteresis_factor; const auto & extended_ego_polygon = is_object_front ? createExtendedPolygon(ego_pose, ego_vehicle_info, lon_offset, lat_margin, debug) From 4003ba4a4e72414a4cd13c80099ba6f5a8457910 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 28 Aug 2023 10:30:23 +0900 Subject: [PATCH 008/547] feat(dynamic_avoidance): not avoid back object (#4751) * feat(dynamic_avoidance): not avoid back object Signed-off-by: Takayuki Murooka * fix Signed-off-by: Takayuki Murooka * minor change Signed-off-by: Takayuki Murooka * fix Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../dynamic_avoidance_module.cpp | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index 8e6ab24929a00..1f5cf8c2a9500 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -107,14 +107,15 @@ std::pair projectObstacleVelocityToTrajectory( const size_t obj_idx = motion_utils::findNearestIndex(path_points, obj_pose.position); - const double obj_vel_yaw = std::atan2( - object.kinematics.initial_twist_with_covariance.twist.linear.y, - object.kinematics.initial_twist_with_covariance.twist.linear.x); + const double obj_yaw = tf2::getYaw(obj_pose.orientation); + const double obj_vel_yaw = + obj_yaw + std::atan2( + object.kinematics.initial_twist_with_covariance.twist.linear.y, + object.kinematics.initial_twist_with_covariance.twist.linear.x); const double path_yaw = tf2::getYaw(path_points.at(obj_idx).point.pose.orientation); - return std::make_pair( - obj_vel_norm * std::cos(obj_vel_yaw - path_yaw), - obj_vel_norm * std::sin(obj_vel_yaw - path_yaw)); + const double diff_yaw = tier4_autoware_utils::normalizeRadian(obj_vel_yaw - path_yaw); + return std::make_pair(obj_vel_norm * std::cos(diff_yaw), obj_vel_norm * std::sin(diff_yaw)); } double calcObstacleMaxLength(const autoware_auto_perception_msgs::msg::Shape & shape) @@ -517,7 +518,31 @@ void DynamicAvoidanceModule::updateTargetObjects() ? isLeft(prev_module_path->points, future_obj_pose->position) : is_object_left; - // 2.g. calculate longitudinal and lateral offset to avoid + // 2.g. check if the ego is not ahead of the object. + const double signed_dist_ego_to_obj = [&]() { + const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(prev_module_path->points); + const double lon_offset_ego_to_obj = motion_utils::calcSignedArcLength( + prev_module_path->points, getEgoPose().position, ego_seg_idx, lat_lon_offset.nearest_idx); + if (0 < lon_offset_ego_to_obj) { + return std::max( + 0.0, lon_offset_ego_to_obj - planner_data_->parameters.front_overhang + + lat_lon_offset.min_lon_offset); + } + return std::min( + 0.0, lon_offset_ego_to_obj + planner_data_->parameters.rear_overhang + + lat_lon_offset.max_lon_offset); + }(); + if (signed_dist_ego_to_obj < 0) { + RCLCPP_INFO_EXPRESSION( + getLogger(), parameters_->enable_debug_info, + "[DynamicAvoidance] Ignore obstacle (%s) since distance from ego to object (%f) is less " + "than 0.", + obj_uuid.c_str(), signed_dist_ego_to_obj); + continue; + } + + // 2.h. calculate longitudinal and lateral offset to avoid to generate object polygon by + // "object_path_base" const auto obj_points = tier4_autoware_utils::toPolygon2d(object.pose, object.shape); const auto lon_offset_to_avoid = calcMinMaxLongitudinalOffsetToAvoid( path_points_for_object_polygon, object.pose, obj_points, object.vel, time_to_collision); From 55b53e1f2c942542440e6650a1ca0a4eb9277926 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 28 Aug 2023 11:02:08 +0900 Subject: [PATCH 009/547] feat(imu_corrector): add gyro_bias_validator (#4729) * feat(imu_corrector): add gyro_bias_validator Signed-off-by: kminoda * style(pre-commit): autofix * update Signed-off-by: kminoda * revert launch Signed-off-by: kminoda * updat Signed-off-by: kminoda * style(pre-commit): autofix * add debug publisher Signed-off-by: kminoda * minor fix Signed-off-by: kminoda * style(pre-commit): autofix * style(pre-commit): autofix * add gtest Signed-off-by: kminoda * style(pre-commit): autofix * updat e readme Signed-off-by: kminoda * style(pre-commit): autofix * add diagnostics Signed-off-by: kminoda * style(pre-commit): autofix * update Signed-off-by: kminoda * style(pre-commit): autofix * validator -> estimator Signed-off-by: kminoda * fix build Signed-off-by: kminoda * update default parameter Signed-off-by: kminoda * update comment Signed-off-by: kminoda * update readme Signed-off-by: kminoda * style(pre-commit): autofix * updated Signed-off-by: kminoda * minor update in readme Signed-off-by: kminoda * style(pre-commit): autofix * fix pre-commit Signed-off-by: kminoda * update readme Signed-off-by: kminoda * style(pre-commit): autofix * Fix NG -> WARN Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sensing/imu_corrector/CMakeLists.txt | 30 ++++- sensing/imu_corrector/README.md | 39 +++++-- .../config/gyro_bias_estimator.param.yaml | 6 + .../launch/gyro_bias_estimator.launch.xml | 16 +++ sensing/imu_corrector/package.xml | 2 + .../src/gyro_bias_estimation_module.cpp | 71 ++++++++++++ .../src/gyro_bias_estimation_module.hpp | 44 +++++++ .../imu_corrector/src/gyro_bias_estimator.cpp | 109 ++++++++++++++++++ .../imu_corrector/src/gyro_bias_estimator.hpp | 66 +++++++++++ .../imu_corrector/src/imu_corrector_core.cpp | 4 +- .../imu_corrector_core.hpp | 6 +- .../test/test_gyro_bias_estimation_module.cpp | 65 +++++++++++ 12 files changed, 442 insertions(+), 16 deletions(-) create mode 100644 sensing/imu_corrector/config/gyro_bias_estimator.param.yaml create mode 100644 sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml create mode 100644 sensing/imu_corrector/src/gyro_bias_estimation_module.cpp create mode 100644 sensing/imu_corrector/src/gyro_bias_estimation_module.hpp create mode 100644 sensing/imu_corrector/src/gyro_bias_estimator.cpp create mode 100644 sensing/imu_corrector/src/gyro_bias_estimator.hpp rename sensing/imu_corrector/{include/imu_corrector => src}/imu_corrector_core.hpp (92%) create mode 100644 sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp diff --git a/sensing/imu_corrector/CMakeLists.txt b/sensing/imu_corrector/CMakeLists.txt index 31e1727b1e034..8fda61f6f32ad 100644 --- a/sensing/imu_corrector/CMakeLists.txt +++ b/sensing/imu_corrector/CMakeLists.txt @@ -4,16 +4,44 @@ project(imu_corrector) find_package(autoware_cmake REQUIRED) autoware_package() +ament_auto_add_library(gyro_bias_estimation_module SHARED + src/gyro_bias_estimation_module.cpp +) + ament_auto_add_library(imu_corrector_node SHARED src/imu_corrector_core.cpp - include/imu_corrector/imu_corrector_core.hpp ) +ament_auto_add_library(gyro_bias_estimator_node SHARED + src/gyro_bias_estimator.cpp +) + +target_link_libraries(gyro_bias_estimator_node gyro_bias_estimation_module) + rclcpp_components_register_node(imu_corrector_node PLUGIN "imu_corrector::ImuCorrector" EXECUTABLE imu_corrector ) +rclcpp_components_register_node(gyro_bias_estimator_node + PLUGIN "imu_corrector::GyroBiasEstimator" + EXECUTABLE gyro_bias_estimator +) + +function(add_testcase filepath) + get_filename_component(filename ${filepath} NAME) + string(REGEX REPLACE ".cpp" "" test_name ${filename}) + ament_add_gmock(${test_name} ${filepath}) + target_link_libraries("${test_name}" gyro_bias_estimation_module imu_corrector_node gyro_bias_estimator_node) + ament_target_dependencies(${test_name} ${${PROJECT_NAME}_FOUND_BUILD_DEPENDS}) +endfunction() + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + add_testcase(test/test_gyro_bias_estimation_module.cpp) +endif() + ament_auto_package(INSTALL_TO_SHARE launch config diff --git a/sensing/imu_corrector/README.md b/sensing/imu_corrector/README.md index 8a00c4005ece2..b47ae1500f43b 100644 --- a/sensing/imu_corrector/README.md +++ b/sensing/imu_corrector/README.md @@ -1,6 +1,6 @@ # imu_corrector -## Purpose +## imu_corrector `imu_corrector_node` is a node that correct imu data. @@ -19,8 +19,6 @@ We also assume that $n\sim\mathcal{N}(0, \sigma^2)$. -## Inputs / Outputs - ### Input | Name | Type | Description | @@ -33,9 +31,7 @@ We also assume that $n\sim\mathcal{N}(0, \sigma^2)$. | --------- | ----------------------- | ------------------ | | `~output` | `sensor_msgs::msg::Imu` | corrected imu data | -## Parameters - -### Core Parameters +### Parameters | Name | Type | Description | | ---------------------------- | ------ | ------------------------------------------------ | @@ -47,12 +43,33 @@ We also assume that $n\sim\mathcal{N}(0, \sigma^2)$. | `angular_velocity_stddev_zz` | double | yaw rate standard deviation imu_link [rad/s] | | `acceleration_stddev` | double | acceleration standard deviation imu_link [m/s^2] | -## Assumptions / Known limits +## gyro_bias_estimator + +`gyro_bias_validator` is a node that validates the bias of the gyroscope. It subscribes to the `sensor_msgs::msg::Imu` topic and validate if the bias of the gyroscope is within the specified range. -## (Optional) Error detection and handling +Note that the node calculates bias from the gyroscope data by averaging the data only when the vehicle is stopped. -## (Optional) Performance characterization +### Input + +| Name | Type | Description | +| ----------------- | ------------------------------------------------ | ---------------- | +| `~/input/imu_raw` | `sensor_msgs::msg::Imu` | **raw** imu data | +| `~/input/twist` | `geometry_msgs::msg::TwistWithCovarianceStamped` | vehicle velocity | -## (Optional) References/External links +### Output -## (Optional) Future extensions / Unimplemented parts +| Name | Type | Description | +| -------------------- | ------------------------------------ | ----------------------------- | +| `~/output/gyro_bias` | `geometry_msgs::msg::Vector3Stamped` | bias of the gyroscope [rad/s] | + +### Parameters + +| Name | Type | Description | +| --------------------------- | ------ | ----------------------------------------------------------------------------- | +| `angular_velocity_offset_x` | double | roll rate offset in imu_link [rad/s] | +| `angular_velocity_offset_y` | double | pitch rate offset imu_link [rad/s] | +| `angular_velocity_offset_z` | double | yaw rate offset imu_link [rad/s] | +| `gyro_bias_threshold` | double | threshold of the bias of the gyroscope [rad/s] | +| `velocity_threshold` | double | threshold of the vehicle velocity to determine if the vehicle is stopped[m/s] | +| `timestamp_threshold` | double | threshold of the timestamp diff between IMU and twist [s] | +| `data_num_threshold` | int | number of data used to calculate bias | diff --git a/sensing/imu_corrector/config/gyro_bias_estimator.param.yaml b/sensing/imu_corrector/config/gyro_bias_estimator.param.yaml new file mode 100644 index 0000000000000..d5868e1df416a --- /dev/null +++ b/sensing/imu_corrector/config/gyro_bias_estimator.param.yaml @@ -0,0 +1,6 @@ +/**: + ros__parameters: + gyro_bias_threshold: 0.0015 # [rad/s] + velocity_threshold: 0.0 # [m/s] + timestamp_threshold: 0.1 # [s] + data_num_threshold: 200 # [num] diff --git a/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml b/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml new file mode 100644 index 0000000000000..a25ce5f90ed27 --- /dev/null +++ b/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/sensing/imu_corrector/package.xml b/sensing/imu_corrector/package.xml index 07c6cfd9a570d..67cb5c1596040 100644 --- a/sensing/imu_corrector/package.xml +++ b/sensing/imu_corrector/package.xml @@ -20,6 +20,8 @@ tf2_ros tier4_autoware_utils + ament_cmake_gmock + ament_cmake_gtest ament_lint_auto autoware_lint_common diff --git a/sensing/imu_corrector/src/gyro_bias_estimation_module.cpp b/sensing/imu_corrector/src/gyro_bias_estimation_module.cpp new file mode 100644 index 0000000000000..2deb6088f6542 --- /dev/null +++ b/sensing/imu_corrector/src/gyro_bias_estimation_module.cpp @@ -0,0 +1,71 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gyro_bias_estimation_module.hpp" + +namespace imu_corrector +{ +GyroBiasEstimationModule::GyroBiasEstimationModule( + const double velocity_threshold, const double timestamp_threshold, + const size_t data_num_threshold) +: velocity_threshold_(velocity_threshold), + timestamp_threshold_(timestamp_threshold), + data_num_threshold_(data_num_threshold), + is_stopped_(false) +{ +} + +void GyroBiasEstimationModule::update_gyro( + const double time, const geometry_msgs::msg::Vector3 & gyro) +{ + if (time - last_velocity_time_ > timestamp_threshold_) { + return; + } + if (!is_stopped_) { + return; + } + gyro_buffer_.push_back(gyro); + if (gyro_buffer_.size() > data_num_threshold_) { + gyro_buffer_.pop_front(); + } +} + +void GyroBiasEstimationModule::update_velocity(const double time, const double velocity) +{ + is_stopped_ = velocity <= velocity_threshold_; + last_velocity_time_ = time; +} + +geometry_msgs::msg::Vector3 GyroBiasEstimationModule::get_bias() const +{ + if (gyro_buffer_.size() < data_num_threshold_) { + throw std::runtime_error("Bias estimation is not yet ready because of insufficient data."); + } + + geometry_msgs::msg::Vector3 bias; + bias.x = 0.0; + bias.y = 0.0; + bias.z = 0.0; + for (const auto & gyro : gyro_buffer_) { + bias.x += gyro.x; + bias.y += gyro.y; + bias.z += gyro.z; + } + bias.x /= gyro_buffer_.size(); + bias.y /= gyro_buffer_.size(); + bias.z /= gyro_buffer_.size(); + return bias; +} + +} // namespace imu_corrector diff --git a/sensing/imu_corrector/src/gyro_bias_estimation_module.hpp b/sensing/imu_corrector/src/gyro_bias_estimation_module.hpp new file mode 100644 index 0000000000000..6ebae942fff5d --- /dev/null +++ b/sensing/imu_corrector/src/gyro_bias_estimation_module.hpp @@ -0,0 +1,44 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef GYRO_BIAS_ESTIMATION_MODULE_HPP_ +#define GYRO_BIAS_ESTIMATION_MODULE_HPP_ + +#include + +#include + +namespace imu_corrector +{ +class GyroBiasEstimationModule +{ +public: + GyroBiasEstimationModule( + const double velocity_threshold, const double timestamp_threshold, + const size_t data_num_threshold); + geometry_msgs::msg::Vector3 get_bias() const; + void update_gyro(const double time, const geometry_msgs::msg::Vector3 & gyro); + void update_velocity(const double time, const double velocity); + +private: + const double velocity_threshold_; + const double timestamp_threshold_; + const size_t data_num_threshold_; + bool is_stopped_; + std::deque gyro_buffer_; + + double last_velocity_time_; +}; +} // namespace imu_corrector + +#endif // GYRO_BIAS_ESTIMATION_MODULE_HPP_ diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.cpp b/sensing/imu_corrector/src/gyro_bias_estimator.cpp new file mode 100644 index 0000000000000..42795805f803e --- /dev/null +++ b/sensing/imu_corrector/src/gyro_bias_estimator.cpp @@ -0,0 +1,109 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gyro_bias_estimator.hpp" + +namespace imu_corrector +{ +GyroBiasEstimator::GyroBiasEstimator(const rclcpp::NodeOptions & node_options) +: Node("gyro_bias_validator", node_options), + gyro_bias_threshold_(declare_parameter("gyro_bias_threshold")), + angular_velocity_offset_x_(declare_parameter("angular_velocity_offset_x")), + angular_velocity_offset_y_(declare_parameter("angular_velocity_offset_y")), + angular_velocity_offset_z_(declare_parameter("angular_velocity_offset_z")), + updater_(this), + gyro_bias_(std::nullopt) +{ + updater_.setHardwareID(get_name()); + updater_.add("gyro_bias_validator", this, &GyroBiasEstimator::update_diagnostics); + + const double velocity_threshold = declare_parameter("velocity_threshold"); + const double timestamp_threshold = declare_parameter("timestamp_threshold"); + const size_t data_num_threshold = + static_cast(declare_parameter("data_num_threshold")); + gyro_bias_estimation_module_ = std::make_unique( + velocity_threshold, timestamp_threshold, data_num_threshold); + + imu_sub_ = create_subscription( + "~/input/imu_raw", rclcpp::SensorDataQoS(), + [this](const Imu::ConstSharedPtr msg) { callback_imu(msg); }); + twist_sub_ = create_subscription( + "~/input/twist", rclcpp::SensorDataQoS(), + [this](const TwistWithCovarianceStamped::ConstSharedPtr msg) { callback_twist(msg); }); + + gyro_bias_pub_ = create_publisher("~/output/gyro_bias", rclcpp::SensorDataQoS()); +} + +void GyroBiasEstimator::callback_imu(const Imu::ConstSharedPtr imu_msg_ptr) +{ + // Update gyro data + gyro_bias_estimation_module_->update_gyro( + rclcpp::Time(imu_msg_ptr->header.stamp).seconds(), imu_msg_ptr->angular_velocity); + + // Estimate gyro bias + try { + gyro_bias_ = gyro_bias_estimation_module_->get_bias(); + } catch (const std::runtime_error & e) { + RCLCPP_WARN_STREAM_THROTTLE(this->get_logger(), *(this->get_clock()), 1000, e.what()); + } + + // Publish results for debugging + if (gyro_bias_ != std::nullopt) { + Vector3Stamped gyro_bias_msg; + gyro_bias_msg.header.stamp = this->now(); + gyro_bias_msg.vector = gyro_bias_.value(); + gyro_bias_pub_->publish(gyro_bias_msg); + } + + // Update diagnostics + updater_.force_update(); +} + +void GyroBiasEstimator::callback_twist( + const TwistWithCovarianceStamped::ConstSharedPtr twist_msg_ptr) +{ + gyro_bias_estimation_module_->update_velocity( + rclcpp::Time(twist_msg_ptr->header.stamp).seconds(), twist_msg_ptr->twist.twist.linear.x); +} + +void GyroBiasEstimator::update_diagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat) +{ + if (gyro_bias_ == std::nullopt) { + stat.add("gyro_bias", "Bias estimation is not yet ready because of insufficient data."); + stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "Not initialized"); + } else { + // Validation + const bool is_bias_small_enough = + std::abs(gyro_bias_.value().x - angular_velocity_offset_x_) < gyro_bias_threshold_ && + std::abs(gyro_bias_.value().y - angular_velocity_offset_y_) < gyro_bias_threshold_ && + std::abs(gyro_bias_.value().z - angular_velocity_offset_z_) < gyro_bias_threshold_; + + // Update diagnostics + if (is_bias_small_enough) { + stat.add("gyro_bias", "OK"); + stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "OK"); + } else { + stat.add( + "gyro_bias", + "Gyro bias may be incorrect. Please calibrate IMU and reflect the result in " + "imu_corrector. You may also use the output of gyro_bias_estimator."); + stat.summary(diagnostic_msgs::msg::DiagnosticStatus::WARN, "WARN"); + } + } +} + +} // namespace imu_corrector + +#include +RCLCPP_COMPONENTS_REGISTER_NODE(imu_corrector::GyroBiasEstimator) diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.hpp b/sensing/imu_corrector/src/gyro_bias_estimator.hpp new file mode 100644 index 0000000000000..37ca1d81dc8fa --- /dev/null +++ b/sensing/imu_corrector/src/gyro_bias_estimator.hpp @@ -0,0 +1,66 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef GYRO_BIAS_ESTIMATOR_HPP_ +#define GYRO_BIAS_ESTIMATOR_HPP_ + +#include "gyro_bias_estimation_module.hpp" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace imu_corrector +{ +class GyroBiasEstimator : public rclcpp::Node +{ +private: + using Imu = sensor_msgs::msg::Imu; + using TwistWithCovarianceStamped = geometry_msgs::msg::TwistWithCovarianceStamped; + using Vector3Stamped = geometry_msgs::msg::Vector3Stamped; + using Vector3 = geometry_msgs::msg::Vector3; + +public: + explicit GyroBiasEstimator(const rclcpp::NodeOptions & node_options); + +private: + void update_diagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat); + void callback_imu(const Imu::ConstSharedPtr imu_msg_ptr); + void callback_twist(const TwistWithCovarianceStamped::ConstSharedPtr twist_msg_ptr); + + rclcpp::Subscription::SharedPtr imu_sub_; + rclcpp::Subscription::SharedPtr twist_sub_; + + rclcpp::Publisher::SharedPtr gyro_bias_pub_; + + std::unique_ptr gyro_bias_estimation_module_; + + const double gyro_bias_threshold_; + const double angular_velocity_offset_x_; + const double angular_velocity_offset_y_; + const double angular_velocity_offset_z_; + + diagnostic_updater::Updater updater_; + + std::optional gyro_bias_; +}; +} // namespace imu_corrector + +#endif // GYRO_BIAS_ESTIMATOR_HPP_ diff --git a/sensing/imu_corrector/src/imu_corrector_core.cpp b/sensing/imu_corrector/src/imu_corrector_core.cpp index edc82f6d56549..c31f4968f557b 100644 --- a/sensing/imu_corrector/src/imu_corrector_core.cpp +++ b/sensing/imu_corrector/src/imu_corrector_core.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "imu_corrector/imu_corrector_core.hpp" +#include "imu_corrector_core.hpp" + +#include std::array transformCovariance(const std::array & cov) { diff --git a/sensing/imu_corrector/include/imu_corrector/imu_corrector_core.hpp b/sensing/imu_corrector/src/imu_corrector_core.hpp similarity index 92% rename from sensing/imu_corrector/include/imu_corrector/imu_corrector_core.hpp rename to sensing/imu_corrector/src/imu_corrector_core.hpp index 1c20dc6977f61..c155288f3cb17 100644 --- a/sensing/imu_corrector/include/imu_corrector/imu_corrector_core.hpp +++ b/sensing/imu_corrector/src/imu_corrector_core.hpp @@ -11,8 +11,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#ifndef IMU_CORRECTOR__IMU_CORRECTOR_CORE_HPP_ -#define IMU_CORRECTOR__IMU_CORRECTOR_CORE_HPP_ +#ifndef IMU_CORRECTOR_CORE_HPP_ +#define IMU_CORRECTOR_CORE_HPP_ #include "tier4_autoware_utils/ros/transform_listener.hpp" #include "tier4_autoware_utils/tier4_autoware_utils.hpp" @@ -59,4 +59,4 @@ class ImuCorrector : public rclcpp::Node }; } // namespace imu_corrector -#endif // IMU_CORRECTOR__IMU_CORRECTOR_CORE_HPP_ +#endif // IMU_CORRECTOR_CORE_HPP_ diff --git a/sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp b/sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp new file mode 100644 index 0000000000000..a0da4d0e24e17 --- /dev/null +++ b/sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp @@ -0,0 +1,65 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../src/gyro_bias_estimation_module.hpp" + +#include + +namespace imu_corrector +{ +class GyroBiasEstimationModuleTest : public ::testing::Test +{ +protected: + double velocity_threshold = 1.0; + double timestamp_threshold = 5.0; + size_t data_num_threshold = 10; + GyroBiasEstimationModule module = + GyroBiasEstimationModule(velocity_threshold, timestamp_threshold, data_num_threshold); +}; + +TEST_F(GyroBiasEstimationModuleTest, GetBiasEstimationWhenVehicleStopped) +{ + geometry_msgs::msg::Vector3 gyro; + gyro.x = 0.1; + gyro.y = 0.2; + gyro.z = 0.3; + for (size_t i = 0; i < data_num_threshold + 1; ++i) { + module.update_velocity( + i * 0.1 * timestamp_threshold, 0.0); // velocity = 0.0 < 1.0 = velocity_threshold + module.update_gyro(i * 0.1 * timestamp_threshold, gyro); + } + ASSERT_NEAR(module.get_bias().x, gyro.x, 0.0001); + ASSERT_NEAR(module.get_bias().y, gyro.y, 0.0001); + ASSERT_NEAR(module.get_bias().z, gyro.z, 0.0001); +} + +TEST_F(GyroBiasEstimationModuleTest, GetInsufficientDataException) +{ + ASSERT_THROW(module.get_bias(), std::runtime_error); +} + +TEST_F(GyroBiasEstimationModuleTest, GetInsufficientDataExceptionWhenVehicleMoving) +{ + geometry_msgs::msg::Vector3 gyro; + gyro.x = 0.1; + gyro.y = 0.2; + gyro.z = 0.3; + for (size_t i = 0; i < data_num_threshold + 1; ++i) { + module.update_velocity( + i * 0.1 * timestamp_threshold, 5.0); // velocity = 5.0 > 1.0 = velocity_threshold + module.update_gyro(i * 0.1 * timestamp_threshold, gyro); + } + ASSERT_THROW(module.get_bias(), std::runtime_error); +} +} // namespace imu_corrector From e897b195d7fced39a3c975e04320d24fe3d13aff Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 28 Aug 2023 11:28:55 +0900 Subject: [PATCH 010/547] feat(eluclidean_cluster): add cyclic/processing time ms (#4747) Signed-off-by: Mamoru Sobue --- .../src/euclidean_cluster_node.cpp | 15 +++++++++++++++ .../src/euclidean_cluster_node.hpp | 6 +++++- .../voxel_grid_based_euclidean_cluster_node.cpp | 15 +++++++++++++++ .../voxel_grid_based_euclidean_cluster_node.hpp | 4 ++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/perception/euclidean_cluster/src/euclidean_cluster_node.cpp b/perception/euclidean_cluster/src/euclidean_cluster_node.cpp index 744eb7af36a5b..a890c19e25d06 100644 --- a/perception/euclidean_cluster/src/euclidean_cluster_node.cpp +++ b/perception/euclidean_cluster/src/euclidean_cluster_node.cpp @@ -38,11 +38,18 @@ EuclideanClusterNode::EuclideanClusterNode(const rclcpp::NodeOptions & options) cluster_pub_ = this->create_publisher( "output", rclcpp::QoS{1}); debug_pub_ = this->create_publisher("debug/clusters", 1); + stop_watch_ptr_ = std::make_unique>(); + debug_publisher_ = + std::make_unique(this, "euclidean_cluster"); + stop_watch_ptr_->tic("cyclic_time"); + stop_watch_ptr_->tic("processing_time"); } void EuclideanClusterNode::onPointCloud( const sensor_msgs::msg::PointCloud2::ConstSharedPtr input_msg) { + stop_watch_ptr_->toc("processing_time", true); + // convert ros to pcl pcl::PointCloud::Ptr raw_pointcloud_ptr(new pcl::PointCloud); pcl::fromROSMsg(*input_msg, *raw_pointcloud_ptr); @@ -65,6 +72,14 @@ void EuclideanClusterNode::onPointCloud( convertObjectMsg2SensorMsg(output, debug); debug_pub_->publish(debug); } + if (debug_publisher_) { + const double cyclic_time_ms = stop_watch_ptr_->toc("cyclic_time", true); + const double processing_time_ms = stop_watch_ptr_->toc("processing_time", true); + debug_publisher_->publish( + "debug/cyclic_time_ms", cyclic_time_ms); + debug_publisher_->publish( + "debug/processing_time_ms", processing_time_ms); + } } } // namespace euclidean_cluster diff --git a/perception/euclidean_cluster/src/euclidean_cluster_node.hpp b/perception/euclidean_cluster/src/euclidean_cluster_node.hpp index e68a346a9a7d3..16e179b2512d3 100644 --- a/perception/euclidean_cluster/src/euclidean_cluster_node.hpp +++ b/perception/euclidean_cluster/src/euclidean_cluster_node.hpp @@ -17,13 +17,15 @@ #include "euclidean_cluster/euclidean_cluster.hpp" #include +#include +#include #include #include #include +#include #include - namespace euclidean_cluster { class EuclideanClusterNode : public rclcpp::Node @@ -39,6 +41,8 @@ class EuclideanClusterNode : public rclcpp::Node rclcpp::Publisher::SharedPtr debug_pub_; std::shared_ptr cluster_; + std::unique_ptr> stop_watch_ptr_; + std::unique_ptr debug_publisher_; }; } // namespace euclidean_cluster diff --git a/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp b/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp index 01856f3e6c836..53fb4df8977d8 100644 --- a/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp +++ b/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp @@ -42,11 +42,18 @@ VoxelGridBasedEuclideanClusterNode::VoxelGridBasedEuclideanClusterNode( cluster_pub_ = this->create_publisher( "output", rclcpp::QoS{1}); debug_pub_ = this->create_publisher("debug/clusters", 1); + stop_watch_ptr_ = std::make_unique>(); + debug_publisher_ = std::make_unique( + this, "voxel_grid_based_euclidean_cluster"); + stop_watch_ptr_->tic("cyclic_time"); + stop_watch_ptr_->tic("processing_time"); } void VoxelGridBasedEuclideanClusterNode::onPointCloud( const sensor_msgs::msg::PointCloud2::ConstSharedPtr input_msg) { + stop_watch_ptr_->toc("processing_time", true); + // convert ros to pcl pcl::PointCloud::Ptr raw_pointcloud_ptr(new pcl::PointCloud); pcl::fromROSMsg(*input_msg, *raw_pointcloud_ptr); @@ -69,6 +76,14 @@ void VoxelGridBasedEuclideanClusterNode::onPointCloud( convertObjectMsg2SensorMsg(output, debug); debug_pub_->publish(debug); } + if (debug_publisher_) { + const double processing_time_ms = stop_watch_ptr_->toc("processing_time", true); + const double cyclic_time_ms = stop_watch_ptr_->toc("cyclic_time", true); + debug_publisher_->publish( + "debug/cyclic_time_ms", cyclic_time_ms); + debug_publisher_->publish( + "debug/processing_time_ms", processing_time_ms); + } } } // namespace euclidean_cluster diff --git a/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.hpp b/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.hpp index d05e5ca1356fe..18330b5f12074 100644 --- a/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.hpp +++ b/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.hpp @@ -17,6 +17,8 @@ #include "euclidean_cluster/voxel_grid_based_euclidean_cluster.hpp" #include +#include +#include #include #include @@ -39,6 +41,8 @@ class VoxelGridBasedEuclideanClusterNode : public rclcpp::Node rclcpp::Publisher::SharedPtr debug_pub_; std::shared_ptr cluster_; + std::unique_ptr> stop_watch_ptr_; + std::unique_ptr debug_publisher_; }; } // namespace euclidean_cluster From 572cf8e8b7f90e1ab4e685a0fb8dbde74d139684 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Mon, 28 Aug 2023 11:42:47 +0900 Subject: [PATCH 011/547] refactor(behavior_path_planner): renaming and comment collision check debug (#4749) * refactor(behavior_path_planner): renaming and comment collision check debug Signed-off-by: Zulfaqar Azmi * fix spell check Signed-off-by: Zulfaqar Azmi --------- Signed-off-by: Zulfaqar Azmi --- .../marker_utils/utils.hpp | 34 +++++++++---------- .../src/marker_utils/lane_change/debug.cpp | 15 ++++---- .../scene_module/lane_change/interface.cpp | 5 ++- .../src/scene_module/lane_change/normal.cpp | 22 ++++++------ .../path_safety_checker/safety_check.cpp | 22 ++++++------ 5 files changed, 48 insertions(+), 50 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index 0fc10109240f9..c9f4d6c909900 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -51,24 +51,22 @@ using visualization_msgs::msg::MarkerArray; struct CollisionCheckDebug { - std::string failed_reason; - std::size_t lane_id{0}; - Pose current_pose{}; - Twist current_twist{}; - Twist object_twist{}; - Pose expected_ego_pose{}; - Pose expected_obj_pose{}; - Pose relative_to_ego{}; - double rss_longitudinal{0.0}; - double ego_to_obj_margin{0.0}; - double longitudinal_offset{0.0}; - double lateral_offset{0.0}; - bool is_front{false}; - bool allow_lane_change{false}; - std::vector lerped_path; - std::vector ego_predicted_path{}; - Polygon2d ego_polygon{}; - Polygon2d obj_polygon{}; + std::string unsafe_reason; ///< Reason indicating unsafe situation. + Pose current_pose{}; ///< Ego vehicle's current pose. + Twist current_twist{}; ///< Ego vehicle's current velocity and rotation. + Twist object_twist{}; ///< Detected object's velocity and rotation. + Pose expected_ego_pose{}; ///< Predicted future pose of ego vehicle. + Pose expected_obj_pose{}; ///< Predicted future pose of object. + double rss_longitudinal{0.0}; ///< Longitudinal RSS measure. + double inter_vehicle_distance{0.0}; ///< Distance between ego vehicle and object. + double extended_polygon_lon_offset{0.0}; ///< Longitudinal offset for extended polygon. + double extended_polygon_lat_offset{0.0}; ///< Lateral offset for extended polygon. + bool is_front{false}; ///< True if object is in front of ego vehicle. + bool is_safe{false}; ///< True if situation is deemed safe. + std::vector lerped_path; ///< Interpolated ego vehicle path. + std::vector ego_predicted_path{}; ///< Predicted future path of ego vehicle. + Polygon2d extended_ego_polygon{}; ///< Ego vehicle's extended collision polygon. + Polygon2d extended_obj_polygon{}; ///< Detected object's extended collision polygon. }; using CollisionCheckDebugMap = std::unordered_map; diff --git a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp index fca53930a0ec5..cd0236f909b14 100644 --- a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp @@ -56,12 +56,13 @@ MarkerArray showObjectInfo( std::ostringstream ss; - ss << "Idx: " << ++idx << "\nReason: " << info.failed_reason + ss << "Idx: " << ++idx << "\nReason: " << info.unsafe_reason << "\nRSS dist: " << std::setprecision(4) << info.rss_longitudinal - << "\nEgo to obj: " << info.ego_to_obj_margin << "\nLateral offset: " << info.lateral_offset - << "\nLongitudinal offset: " << info.longitudinal_offset + << "\nEgo to obj: " << info.inter_vehicle_distance + << "\nExtended polygon lateral offset: " << info.extended_polygon_lat_offset + << "\nExtended polygon longitudinal offset: " << info.extended_polygon_lon_offset << "\nPosition: " << (info.is_front ? "front" : "back") - << "\nSafe: " << (info.allow_lane_change ? "Yes" : "No"); + << "\nSafe: " << (info.is_safe ? "Yes" : "No"); obj_marker.text = ss.str(); @@ -195,8 +196,8 @@ MarkerArray showPolygon( int32_t idx = {0}; for (const auto & [uuid, info] : obj_debug_vec) { - const auto & color = info.allow_lane_change ? green_color : red_color; - const auto & ego_polygon = info.ego_polygon.outer(); + const auto & color = info.is_safe ? green_color : red_color; + const auto & ego_polygon = info.extended_ego_polygon.outer(); const auto poly_z = info.current_pose.position.z; // temporally ego_marker.id = ++id; ego_marker.color = createMarkerColor(color[0], color[1], color[2], 0.8); @@ -214,7 +215,7 @@ MarkerArray showPolygon( marker_array.markers.push_back(text_marker); - const auto & obj_polygon = info.obj_polygon.outer(); + const auto & obj_polygon = info.extended_obj_polygon.outer(); obj_marker.id = ++id; obj_marker.color = createMarkerColor(color[0], color[1], color[2], 0.8); obj_marker.points.reserve(obj_polygon.size()); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 270e2064228af..072c1c62ae3d2 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -332,10 +332,9 @@ std::shared_ptr LaneChangeInterface::get_debug_msg_arra for (const auto & [uuid, debug_data] : debug_data) { LaneChangeDebugMsg debug_msg; debug_msg.object_id = uuid; - debug_msg.allow_lane_change = debug_data.allow_lane_change; + debug_msg.allow_lane_change = debug_data.is_safe; debug_msg.is_front = debug_data.is_front; - debug_msg.relative_distance = debug_data.relative_to_ego; - debug_msg.failed_reason = debug_data.failed_reason; + debug_msg.failed_reason = debug_data.unsafe_reason; debug_msg.velocity = std::hypot(debug_data.object_twist.linear.x, debug_data.object_twist.linear.y); debug_msg_array.lane_change_info.push_back(debug_msg); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 251720f4202d5..cf3312901b1d9 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1361,17 +1361,17 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( return std::make_pair(tier4_autoware_utils::toHexString(obj.uuid), debug); }; - const auto updateDebugInfo = - [&debug_data](std::pair & obj, bool is_allowed) { - const auto & key = obj.first; - auto & element = obj.second; - element.allow_lane_change = is_allowed; - if (debug_data.find(key) != debug_data.end()) { - debug_data[key] = element; - } else { - debug_data.insert(obj); - } - }; + const auto updateDebugInfo = [&debug_data]( + std::pair & obj, bool is_safe) { + const auto & key = obj.first; + auto & element = obj.second; + element.is_safe = is_safe; + if (debug_data.find(key) != debug_data.end()) { + debug_data[key] = element; + } else { + debug_data.insert(obj); + } + }; for (const auto & obj : collision_check_objects) { auto current_debug_data = assignDebugData(obj); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 579862de93865..15a23c8f27dff 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -62,8 +62,8 @@ Polygon2d createExtendedPolygon( const double lat_offset = width / 2.0 + lat_margin; { - debug.longitudinal_offset = lon_offset; - debug.lateral_offset = lat_offset; + debug.extended_polygon_lon_offset = lon_offset; + debug.extended_polygon_lat_offset = lat_offset; } const auto p1 = tier4_autoware_utils::calcOffsetPose(base_link_pose, lon_offset, lat_offset, 0.0); @@ -113,8 +113,8 @@ Polygon2d createExtendedPolygon( const double right_lat_offset = min_y - lat_margin; { - debug.longitudinal_offset = lon_offset; - debug.lateral_offset = (left_lat_offset + right_lat_offset) / 2; + debug.extended_polygon_lon_offset = lon_offset; + debug.extended_polygon_lat_offset = (left_lat_offset + right_lat_offset) / 2; } const auto p1 = tier4_autoware_utils::calcOffsetPose(obj_pose, lon_offset, left_lat_offset, 0.0); @@ -261,13 +261,13 @@ bool checkCollision( debug.lerped_path.push_back(ego_pose); debug.expected_ego_pose = ego_pose; debug.expected_obj_pose = obj_pose; - debug.ego_polygon = ego_polygon; - debug.obj_polygon = obj_polygon; + debug.extended_ego_polygon = ego_polygon; + debug.extended_obj_polygon = obj_polygon; } // check overlap if (boost::geometry::overlaps(ego_polygon, obj_polygon)) { - debug.failed_reason = "overlap_polygon"; + debug.unsafe_reason = "overlap_polygon"; return false; } @@ -299,15 +299,15 @@ bool checkCollision( { debug.rss_longitudinal = rss_dist; - debug.ego_to_obj_margin = min_lon_length; - debug.ego_polygon = extended_ego_polygon; - debug.obj_polygon = extended_obj_polygon; + debug.inter_vehicle_distance = min_lon_length; + debug.extended_ego_polygon = extended_ego_polygon; + debug.extended_obj_polygon = extended_obj_polygon; debug.is_front = is_object_front; } // check overlap with extended polygon if (boost::geometry::overlaps(extended_ego_polygon, extended_obj_polygon)) { - debug.failed_reason = "overlap_extended_polygon"; + debug.unsafe_reason = "overlap_extended_polygon"; return false; } } From 1e273271537bac401e982afe927a01cf5ac6744c Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 28 Aug 2023 13:35:05 +0900 Subject: [PATCH 012/547] chore(map_based_prediction): add maintainer (#4627) Signed-off-by: Takayuki Murooka --- perception/map_based_prediction/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/perception/map_based_prediction/package.xml b/perception/map_based_prediction/package.xml index 0765490502ec4..a76310972c738 100644 --- a/perception/map_based_prediction/package.xml +++ b/perception/map_based_prediction/package.xml @@ -8,6 +8,7 @@ Tomoya Kimura Shunsuke Miura Yoshi Ri + Takayuki Murooka Apache License 2.0 ament_cmake From 2875bf6496db2aba4f41ad640d6f9ed27131e84f Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 28 Aug 2023 13:42:19 +0900 Subject: [PATCH 013/547] feat(tier4_autoware_utils): add getOrDeclearParam function (#4763) * feat(tier4_autoware_utils): add getOrDeclearParam function Signed-off-by: Takamasa Horibe * modify utils.hpp Signed-off-by: Takamasa Horibe * remove unused include Signed-off-by: Takamasa Horibe * remove error message since it is shown by ros-default Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../tier4_autoware_utils/ros/parameter.hpp | 35 +++++++++++++++++++ .../tier4_autoware_utils.hpp | 1 + 2 files changed, 36 insertions(+) create mode 100644 common/tier4_autoware_utils/include/tier4_autoware_utils/ros/parameter.hpp diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/parameter.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/parameter.hpp new file mode 100644 index 0000000000000..ac9b124a02cdb --- /dev/null +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/parameter.hpp @@ -0,0 +1,35 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TIER4_AUTOWARE_UTILS__ROS__PARAMETER_HPP_ +#define TIER4_AUTOWARE_UTILS__ROS__PARAMETER_HPP_ + +#include + +#include + +namespace tier4_autoware_utils +{ +template +T getOrDeclareParameter(rclcpp::Node & node, const std::string & name) +{ + if (node.has_parameter(name)) { + return node.get_parameter(name).get_value(); + } + + return node.declare_parameter(name); +} +} // namespace tier4_autoware_utils + +#endif // TIER4_AUTOWARE_UTILS__ROS__PARAMETER_HPP_ diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp index 450549f75fdec..2aa5ae0c7e6ef 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp @@ -31,6 +31,7 @@ #include "tier4_autoware_utils/ros/marker_helper.hpp" #include "tier4_autoware_utils/ros/msg_covariance.hpp" #include "tier4_autoware_utils/ros/msg_operation.hpp" +#include "tier4_autoware_utils/ros/parameter.hpp" #include "tier4_autoware_utils/ros/processing_time_publisher.hpp" #include "tier4_autoware_utils/ros/self_pose_listener.hpp" #include "tier4_autoware_utils/ros/transform_listener.hpp" From 8613b510fb1fbae99ea7d91a30086ea6895b2c2f Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Mon, 28 Aug 2023 14:17:10 +0900 Subject: [PATCH 014/547] feat(map_based_prediciton): consider y component of the velocity vector (#4728) * consider y component of the velocity vector Signed-off-by: yoshiri * uncomment compensateTimeDelay function because it is not used Signed-off-by: yoshiri * style(pre-commit): autofix --------- Signed-off-by: yoshiri Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../src/map_based_prediction_node.cpp | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 8d633dbd5af64..5360562e231ba 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -313,8 +313,11 @@ bool validateIsolatedLaneletLength( const auto & end_point = center_line.back(); // calc approx distance between closest point and end point const double approx_distance = lanelet::geometry::distance2d(obj_point, end_point); - const double min_length = - object.kinematics.twist_with_covariance.twist.linear.x * prediction_time; + // calc min length for prediction + const double abs_speed = std::hypot( + object.kinematics.twist_with_covariance.twist.linear.x, + object.kinematics.twist_with_covariance.twist.linear.y); + const double min_length = abs_speed * prediction_time; return approx_distance > min_length; } @@ -553,8 +556,11 @@ ObjectClassification::_label_type changeLabelForPrediction( // if object is within road lanelet and satisfies yaw constraints const bool within_road_lanelet = withinRoadLanelet(object, lanelet_map_ptr_, true); const float high_speed_threshold = 25.0 / 18.0 * 5.0; // High speed bicycle 25 km/h - const bool high_speed_object = - object.kinematics.twist_with_covariance.twist.linear.x > high_speed_threshold; + // calc abs speed from x and y velocity + const double abs_speed = std::hypot( + object.kinematics.twist_with_covariance.twist.linear.x, + object.kinematics.twist_with_covariance.twist.linear.y); + const bool high_speed_object = abs_speed > high_speed_threshold; // if the object is within lanelet, do the same estimation with vehicle if (within_road_lanelet) { @@ -570,8 +576,10 @@ ObjectClassification::_label_type changeLabelForPrediction( const bool within_road_lanelet = withinRoadLanelet(object, lanelet_map_ptr_, true); const float max_velocity_for_human_mps = 25.0 / 18.0 * 5.0; // Max human being motion speed is 25km/h - const bool high_speed_object = - object.kinematics.twist_with_covariance.twist.linear.x > max_velocity_for_human_mps; + const double abs_speed = std::hypot( + object.kinematics.twist_with_covariance.twist.linear.x, + object.kinematics.twist_with_covariance.twist.linear.y); + const bool high_speed_object = abs_speed > max_velocity_for_human_mps; // fast, human-like object: like segway if (within_road_lanelet && high_speed_object) { return label; // currently do nothing @@ -826,9 +834,10 @@ void MapBasedPredictionNode::objectsCallback(const TrackedObjects::ConstSharedPt } // For too-slow vehicle - if ( - std::fabs(transformed_object.kinematics.twist_with_covariance.twist.linear.x) < - min_velocity_for_map_based_prediction_) { + const double abs_obj_speed = std::hypot( + transformed_object.kinematics.twist_with_covariance.twist.linear.x, + transformed_object.kinematics.twist_with_covariance.twist.linear.y); + if (std::fabs(abs_obj_speed) < min_velocity_for_map_based_prediction_) { PredictedPath predicted_path = path_generator_->generatePathForLowSpeedVehicle(transformed_object); predicted_path.confidence = 1.0; @@ -1046,6 +1055,7 @@ void MapBasedPredictionNode::updateObjectData(TrackedObject & object) const auto future_object_pose = tier4_autoware_utils::calcOffsetPose( object_pose, object_twist.linear.x * 0.1, object_twist.linear.y * 0.1, 0.0); + // assumption: the object vx is much larger than vy if (object.kinematics.twist_with_covariance.twist.linear.x < 0.0) { if ( object.kinematics.orientation_availability == @@ -1064,6 +1074,7 @@ void MapBasedPredictionNode::updateObjectData(TrackedObject & object) } object.kinematics.twist_with_covariance.twist.linear.x *= -1.0; + object.kinematics.twist_with_covariance.twist.linear.y *= -1.0; } return; @@ -1326,7 +1337,9 @@ std::vector MapBasedPredictionNode::getPredictedReferencePath( const TrackedObject & object, const LaneletsData & current_lanelets_data, const double object_detected_time) { - const double obj_vel = std::fabs(object.kinematics.twist_with_covariance.twist.linear.x); + const double obj_vel = std::hypot( + object.kinematics.twist_with_covariance.twist.linear.x, + object.kinematics.twist_with_covariance.twist.linear.y); std::vector all_ref_paths; for (const auto & current_lanelet_data : current_lanelets_data) { @@ -1667,21 +1680,22 @@ geometry_msgs::msg::Pose MapBasedPredictionNode::compensateTimeDelay( /* == Nonlinear model == * - * x_{k+1} = x_k + vx_k * cos(yaw_k) * dt - * y_{k+1} = y_k + vx_k * sin(yaw_k) * dt + * x_{k+1} = x_k + vx_k * cos(yaw_k) * dt - vy_k * sin(yaw_k) * dt + * y_{k+1} = y_k + vx_k * sin(yaw_k) * dt + vy_k * cos(yaw_k) * dt * yaw_{k+1} = yaw_k + (wz_k) * dt * */ const double vx = twist.linear.x; + const double vy = twist.linear.y; const double wz = twist.angular.z; const double prev_yaw = tf2::getYaw(delayed_pose.orientation); const double prev_x = delayed_pose.position.x; const double prev_y = delayed_pose.position.y; const double prev_z = delayed_pose.position.z; - const double curr_x = prev_x + vx * std::cos(prev_yaw) * dt; - const double curr_y = prev_y + vx * std::sin(prev_yaw) * dt; + const double curr_x = prev_x + vx * std::cos(prev_yaw) * dt - vy * std::sin(prev_yaw) * dt; + const double curr_y = prev_y + vx * std::sin(prev_yaw) * dt + vy * std::cos(prev_yaw) * dt; const double curr_z = prev_z; const double curr_yaw = prev_yaw + wz * dt; From 6bc983c5c77b9e087ce49f72aede18679b288df1 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 28 Aug 2023 14:17:23 +0900 Subject: [PATCH 015/547] fix(multi_object_tracker): consider vehicle twist direction (#4637) * feat(multi_object_tracker): consider vehicle twist direction Signed-off-by: Takayuki Murooka * fix bicycle model Signed-off-by: Takayuki Murooka * update doc Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- perception/multi_object_tracker/models.md | 12 ++++++++++++ .../src/tracker/model/bicycle_tracker.cpp | 3 ++- .../src/tracker/model/big_vehicle_tracker.cpp | 3 ++- .../src/tracker/model/normal_vehicle_tracker.cpp | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/perception/multi_object_tracker/models.md b/perception/multi_object_tracker/models.md index 0f7fc50ed535f..8ca12e3191dc5 100644 --- a/perception/multi_object_tracker/models.md +++ b/perception/multi_object_tracker/models.md @@ -65,6 +65,18 @@ $$ \end{array}\right] $$ +#### remarks on the output twist + +Remarks that the velocity $v_{k}$ is the norm of velocity of vehicle, not the longitudinal velocity. +So the output twist in the object coordinate $(x,y)$ is calculated as follows. + +$$ +\begin{aligned} +v_{x} &= v_{k} \cos \left(\beta_{k}\right) \\ +v_{y} &= v_{k} \sin \left(\beta_{k}\right) +\end{aligned} +$$ + ## Anchor point based estimation To separate the estimation of the position and the shape, we use anchor point based position estimation. diff --git a/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp index 2b0fff35729d0..006a87430965f 100644 --- a/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp @@ -427,7 +427,8 @@ bool BicycleTracker::getTrackedObject( pose_with_cov.covariance[utils::MSG_COV_IDX::YAW_YAW] = P(IDX::YAW, IDX::YAW); // twist - twist_with_cov.twist.linear.x = X_t(IDX::VX); + twist_with_cov.twist.linear.x = X_t(IDX::VX) * std::cos(X_t(IDX::SLIP)); + twist_with_cov.twist.linear.y = X_t(IDX::VX) * std::sin(X_t(IDX::SLIP)); twist_with_cov.twist.angular.z = X_t(IDX::VX) / lr_ * std::sin(X_t(IDX::SLIP)); // yaw_rate = vx_k / l_r * sin(slip_k) // twist covariance diff --git a/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp index 73916742f17bb..50f582d6a2501 100644 --- a/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp @@ -478,7 +478,8 @@ bool BigVehicleTracker::getTrackedObject( pose_with_cov.covariance[utils::MSG_COV_IDX::YAW_YAW] = P(IDX::YAW, IDX::YAW); // twist - twist_with_cov.twist.linear.x = X_t(IDX::VX); + twist_with_cov.twist.linear.x = X_t(IDX::VX) * std::cos(X_t(IDX::SLIP)); + twist_with_cov.twist.linear.y = X_t(IDX::VX) * std::sin(X_t(IDX::SLIP)); twist_with_cov.twist.angular.z = X_t(IDX::VX) / lr_ * std::sin(X_t(IDX::SLIP)); // yaw_rate = vx_k / l_r * sin(slip_k) // twist covariance diff --git a/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp index 27c2a7b821eb6..978b5e346efb2 100644 --- a/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp @@ -494,7 +494,8 @@ bool NormalVehicleTracker::getTrackedObject( pose_with_cov.covariance[utils::MSG_COV_IDX::YAW_YAW] = P(IDX::YAW, IDX::YAW); // twist - twist_with_cov.twist.linear.x = X_t(IDX::VX); + twist_with_cov.twist.linear.x = X_t(IDX::VX) * std::cos(X_t(IDX::SLIP)); + twist_with_cov.twist.linear.y = X_t(IDX::VX) * std::sin(X_t(IDX::SLIP)); twist_with_cov.twist.angular.z = X_t(IDX::VX) / lr_ * std::sin(X_t(IDX::SLIP)); // yaw_rate = vx_k / l_r * sin(slip_k) // twist covariance From 6dcd8b051412d11606026f48ec89fb0958e58270 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 28 Aug 2023 14:46:55 +0900 Subject: [PATCH 016/547] chore(blind_spot): use getOrDeclareParam (#4764) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/planning/behavior_velocity_blind_spot_module/src/manager.cpp b/planning/behavior_velocity_blind_spot_module/src/manager.cpp index 09fef7a496b09..d64f1ebc39a51 100644 --- a/planning/behavior_velocity_blind_spot_module/src/manager.cpp +++ b/planning/behavior_velocity_blind_spot_module/src/manager.cpp @@ -16,6 +16,7 @@ #include #include +#include #include @@ -27,22 +28,26 @@ namespace behavior_velocity_planner { +using tier4_autoware_utils::getOrDeclareParameter; + BlindSpotModuleManager::BlindSpotModuleManager(rclcpp::Node & node) : SceneModuleManagerInterfaceWithRTC( node, getModuleName(), - node.declare_parameter(std::string(getModuleName()) + ".enable_rtc")) + getOrDeclareParameter(node, std::string(getModuleName()) + ".enable_rtc")) { const std::string ns(getModuleName()); - planner_param_.use_pass_judge_line = node.declare_parameter(ns + ".use_pass_judge_line"); - planner_param_.stop_line_margin = node.declare_parameter(ns + ".stop_line_margin"); - planner_param_.backward_length = node.declare_parameter(ns + ".backward_length"); + planner_param_.use_pass_judge_line = + getOrDeclareParameter(node, ns + ".use_pass_judge_line"); + planner_param_.stop_line_margin = getOrDeclareParameter(node, ns + ".stop_line_margin"); + planner_param_.backward_length = getOrDeclareParameter(node, ns + ".backward_length"); planner_param_.ignore_width_from_center_line = - node.declare_parameter(ns + ".ignore_width_from_center_line"); + getOrDeclareParameter(node, ns + ".ignore_width_from_center_line"); planner_param_.max_future_movement_time = - node.declare_parameter(ns + ".max_future_movement_time"); - planner_param_.threshold_yaw_diff = node.declare_parameter(ns + ".threshold_yaw_diff"); + getOrDeclareParameter(node, ns + ".max_future_movement_time"); + planner_param_.threshold_yaw_diff = + getOrDeclareParameter(node, ns + ".threshold_yaw_diff"); planner_param_.adjacent_extend_width = - node.declare_parameter(ns + ".adjacent_extend_width"); + getOrDeclareParameter(node, ns + ".adjacent_extend_width"); } void BlindSpotModuleManager::launchNewModules( From bb702a68d97b218004c7e871afa4f0de42dd7f3c Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:54:12 +0900 Subject: [PATCH 017/547] feat(gnss_poser): remove local cartesian wgs84 coordinate (#4726) * feat(gnss_poser): remove local cartesian wgs84 coordinate Signed-off-by: kminoda * update readme Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sensing/gnss_poser/README.md | 16 +++++++------- .../gnss_poser/include/gnss_poser/convert.hpp | 22 ------------------- .../include/gnss_poser/gnss_stat.hpp | 2 +- .../gnss_poser/launch/gnss_poser.launch.xml | 2 +- sensing/gnss_poser/src/gnss_poser_core.cpp | 3 --- 5 files changed, 10 insertions(+), 35 deletions(-) diff --git a/sensing/gnss_poser/README.md b/sensing/gnss_poser/README.md index 6eef2cc243ef5..3487e9515c48a 100644 --- a/sensing/gnss_poser/README.md +++ b/sensing/gnss_poser/README.md @@ -27,14 +27,14 @@ The `gnss_poser` is a node that subscribes gnss sensing messages and calculates ### Core Parameters -| Name | Type | Default Value | Description | -| ---------------------- | ------ | ---------------- | -------------------------------------------------------------------------------------------------------- | -| `base_frame` | string | "base_link" | frame id | -| `gnss_frame` | string | "gnss" | frame id | -| `gnss_base_frame` | string | "gnss_base_link" | frame id | -| `map_frame` | string | "map" | frame id | -| `coordinate_system` | int | "4" | coordinate system enumeration; 1: MGRS, 3: WGS84 Local Coordinate System, 4: UTM Local Coordinate System | -| `gnss_pose_pub_method` | int | 0 | 0: Instant Value 1: Average Value 2: Median Value. If 0 is chosen buffer_epoch parameter loses affect. | +| Name | Type | Default Value | Description | +| ---------------------- | ------ | ---------------- | ------------------------------------------------------------------------------------------------------ | +| `base_frame` | string | "base_link" | frame id | +| `gnss_frame` | string | "gnss" | frame id | +| `gnss_base_frame` | string | "gnss_base_link" | frame id | +| `map_frame` | string | "map" | frame id | +| `coordinate_system` | int | "4" | coordinate system enumeration; 1: MGRS, 4: UTM Local Coordinate System | +| `gnss_pose_pub_method` | int | 0 | 0: Instant Value 1: Average Value 2: Median Value. If 0 is chosen buffer_epoch parameter loses affect. | ## Assumptions / Known limits diff --git a/sensing/gnss_poser/include/gnss_poser/convert.hpp b/sensing/gnss_poser/include/gnss_poser/convert.hpp index 5c1f621626aa2..74c9734833232 100644 --- a/sensing/gnss_poser/include/gnss_poser/convert.hpp +++ b/sensing/gnss_poser/include/gnss_poser/convert.hpp @@ -17,7 +17,6 @@ #include "gnss_poser/gnss_stat.hpp" #include -#include #include #include #include @@ -56,27 +55,6 @@ double EllipsoidHeight2OrthometricHeight( } return OrthometricHeight; } -GNSSStat NavSatFix2LocalCartesianWGS84( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, - sensor_msgs::msg::NavSatFix nav_sat_fix_origin_, const rclcpp::Logger & logger) -{ - GNSSStat local_cartesian; - - try { - GeographicLib::LocalCartesian localCartesian_origin( - nav_sat_fix_origin_.latitude, nav_sat_fix_origin_.longitude, nav_sat_fix_origin_.altitude); - localCartesian_origin.Forward( - nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, nav_sat_fix_msg.altitude, - local_cartesian.x, local_cartesian.y, local_cartesian.z); - - local_cartesian.latitude = nav_sat_fix_msg.latitude; - local_cartesian.longitude = nav_sat_fix_msg.longitude; - local_cartesian.altitude = nav_sat_fix_msg.altitude; - } catch (const GeographicLib::GeographicErr & err) { - RCLCPP_ERROR_STREAM(logger, "Failed to convert NavSatFix to LocalCartesian" << err.what()); - } - return local_cartesian; -} GNSSStat NavSatFix2UTM( const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, const rclcpp::Logger & logger, int height_system) diff --git a/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp b/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp index fa17bf0e6e232..d33bd022c3714 100644 --- a/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp +++ b/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp @@ -18,7 +18,7 @@ namespace gnss_poser { -enum class CoordinateSystem { MGRS = 1, LOCAL_CARTESIAN_WGS84 = 3, LOCAL_CARTESIAN_UTM = 4 }; +enum class CoordinateSystem { MGRS = 1, LOCAL_CARTESIAN_UTM = 4 }; struct GNSSStat { diff --git a/sensing/gnss_poser/launch/gnss_poser.launch.xml b/sensing/gnss_poser/launch/gnss_poser.launch.xml index 3883a492358d6..8a0ffb8cab8c3 100755 --- a/sensing/gnss_poser/launch/gnss_poser.launch.xml +++ b/sensing/gnss_poser/launch/gnss_poser.launch.xml @@ -13,7 +13,7 @@ - + diff --git a/sensing/gnss_poser/src/gnss_poser_core.cpp b/sensing/gnss_poser/src/gnss_poser_core.cpp index 7d244ce188eee..4f0723944913b 100644 --- a/sensing/gnss_poser/src/gnss_poser_core.cpp +++ b/sensing/gnss_poser/src/gnss_poser_core.cpp @@ -196,9 +196,6 @@ GNSSStat GNSSPoser::convert( } else if (coordinate_system == CoordinateSystem::MGRS) { gnss_stat = NavSatFix2MGRS( nav_sat_fix_msg, MGRSPrecision::_100MICRO_METER, this->get_logger(), height_system); - } else if (coordinate_system == CoordinateSystem::LOCAL_CARTESIAN_WGS84) { - gnss_stat = - NavSatFix2LocalCartesianWGS84(nav_sat_fix_msg, nav_sat_fix_origin_, this->get_logger()); } else { RCLCPP_ERROR_STREAM_THROTTLE( this->get_logger(), *this->get_clock(), std::chrono::milliseconds(1000).count(), From e01772541aaa2913c0fde7456eff0f1c01c2b4ad Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 28 Aug 2023 15:27:12 +0900 Subject: [PATCH 018/547] refactor(crosswalk): use getOrDeclareParameter (#4765) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/manager.cpp b/planning/behavior_velocity_crosswalk_module/src/manager.cpp index 96e4e873ce111..30a1a81a1b50a 100644 --- a/planning/behavior_velocity_crosswalk_module/src/manager.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/manager.cpp @@ -15,6 +15,7 @@ #include "manager.hpp" #include +#include #include #include @@ -26,86 +27,93 @@ namespace behavior_velocity_planner { using lanelet::autoware::Crosswalk; +using tier4_autoware_utils::getOrDeclareParameter; CrosswalkModuleManager::CrosswalkModuleManager(rclcpp::Node & node) : SceneModuleManagerInterfaceWithRTC( node, getModuleName(), - node.declare_parameter(std::string(getModuleName()) + ".common.enable_rtc")) + getOrDeclareParameter(node, std::string(getModuleName()) + ".common.enable_rtc")) { const std::string ns(getModuleName()); // for crosswalk parameters auto & cp = crosswalk_planner_param_; - cp.show_processing_time = node.declare_parameter(ns + ".common.show_processing_time"); + cp.show_processing_time = getOrDeclareParameter(node, ns + ".common.show_processing_time"); // param for input data cp.traffic_light_state_timeout = - node.declare_parameter(ns + ".common.traffic_light_state_timeout"); + getOrDeclareParameter(node, ns + ".common.traffic_light_state_timeout"); // param for stop position cp.stop_distance_from_crosswalk = - node.declare_parameter(ns + ".stop_position.stop_distance_from_crosswalk"); + getOrDeclareParameter(node, ns + ".stop_position.stop_distance_from_crosswalk"); cp.stop_distance_from_object = - node.declare_parameter(ns + ".stop_position.stop_distance_from_object"); + getOrDeclareParameter(node, ns + ".stop_position.stop_distance_from_object"); cp.far_object_threshold = - node.declare_parameter(ns + ".stop_position.far_object_threshold"); + getOrDeclareParameter(node, ns + ".stop_position.far_object_threshold"); cp.stop_position_threshold = - node.declare_parameter(ns + ".stop_position.stop_position_threshold"); + getOrDeclareParameter(node, ns + ".stop_position.stop_position_threshold"); // param for ego velocity cp.min_slow_down_velocity = - node.declare_parameter(ns + ".slow_down.min_slow_down_velocity"); - cp.max_slow_down_jerk = node.declare_parameter(ns + ".slow_down.max_slow_down_jerk"); - cp.max_slow_down_accel = node.declare_parameter(ns + ".slow_down.max_slow_down_accel"); - cp.no_relax_velocity = node.declare_parameter(ns + ".slow_down.no_relax_velocity"); + getOrDeclareParameter(node, ns + ".slow_down.min_slow_down_velocity"); + cp.max_slow_down_jerk = getOrDeclareParameter(node, ns + ".slow_down.max_slow_down_jerk"); + cp.max_slow_down_accel = + getOrDeclareParameter(node, ns + ".slow_down.max_slow_down_accel"); + cp.no_relax_velocity = getOrDeclareParameter(node, ns + ".slow_down.no_relax_velocity"); // param for stuck vehicle cp.stuck_vehicle_velocity = - node.declare_parameter(ns + ".stuck_vehicle.stuck_vehicle_velocity"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_velocity"); cp.max_stuck_vehicle_lateral_offset = - node.declare_parameter(ns + ".stuck_vehicle.max_stuck_vehicle_lateral_offset"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.max_stuck_vehicle_lateral_offset"); cp.stuck_vehicle_attention_range = - node.declare_parameter(ns + ".stuck_vehicle.stuck_vehicle_attention_range"); - cp.min_acc_for_stuck_vehicle = node.declare_parameter(ns + ".stuck_vehicle.min_acc"); - cp.max_jerk_for_stuck_vehicle = node.declare_parameter(ns + ".stuck_vehicle.max_jerk"); - cp.min_jerk_for_stuck_vehicle = node.declare_parameter(ns + ".stuck_vehicle.min_jerk"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_attention_range"); + cp.min_acc_for_stuck_vehicle = getOrDeclareParameter(node, ns + ".stuck_vehicle.min_acc"); + cp.max_jerk_for_stuck_vehicle = + getOrDeclareParameter(node, ns + ".stuck_vehicle.max_jerk"); + cp.min_jerk_for_stuck_vehicle = + getOrDeclareParameter(node, ns + ".stuck_vehicle.min_jerk"); // param for pass judge logic cp.ego_pass_first_margin_x = - node.declare_parameter>(ns + ".pass_judge.ego_pass_first_margin_x"); + getOrDeclareParameter>(node, ns + ".pass_judge.ego_pass_first_margin_x"); cp.ego_pass_first_margin_y = - node.declare_parameter>(ns + ".pass_judge.ego_pass_first_margin_y"); + getOrDeclareParameter>(node, ns + ".pass_judge.ego_pass_first_margin_y"); cp.ego_pass_first_additional_margin = - node.declare_parameter(ns + ".pass_judge.ego_pass_first_additional_margin"); + getOrDeclareParameter(node, ns + ".pass_judge.ego_pass_first_additional_margin"); cp.ego_pass_later_margin_x = - node.declare_parameter>(ns + ".pass_judge.ego_pass_later_margin_x"); + getOrDeclareParameter>(node, ns + ".pass_judge.ego_pass_later_margin_x"); cp.ego_pass_later_margin_y = - node.declare_parameter>(ns + ".pass_judge.ego_pass_later_margin_y"); + getOrDeclareParameter>(node, ns + ".pass_judge.ego_pass_later_margin_y"); cp.ego_pass_later_additional_margin = - node.declare_parameter(ns + ".pass_judge.ego_pass_later_additional_margin"); + getOrDeclareParameter(node, ns + ".pass_judge.ego_pass_later_additional_margin"); cp.max_offset_to_crosswalk_for_yield = - node.declare_parameter(ns + ".pass_judge.max_offset_to_crosswalk_for_yield"); + getOrDeclareParameter(node, ns + ".pass_judge.max_offset_to_crosswalk_for_yield"); cp.stop_object_velocity = - node.declare_parameter(ns + ".pass_judge.stop_object_velocity_threshold"); - cp.min_object_velocity = node.declare_parameter(ns + ".pass_judge.min_object_velocity"); + getOrDeclareParameter(node, ns + ".pass_judge.stop_object_velocity_threshold"); + cp.min_object_velocity = + getOrDeclareParameter(node, ns + ".pass_judge.min_object_velocity"); cp.disable_stop_for_yield_cancel = - node.declare_parameter(ns + ".pass_judge.disable_stop_for_yield_cancel"); + getOrDeclareParameter(node, ns + ".pass_judge.disable_stop_for_yield_cancel"); cp.disable_yield_for_new_stopped_object = - node.declare_parameter(ns + ".pass_judge.disable_yield_for_new_stopped_object"); + getOrDeclareParameter(node, ns + ".pass_judge.disable_yield_for_new_stopped_object"); cp.timeout_no_intention_to_walk = - node.declare_parameter(ns + ".pass_judge.timeout_no_intention_to_walk"); + getOrDeclareParameter(node, ns + ".pass_judge.timeout_no_intention_to_walk"); cp.timeout_ego_stop_for_yield = - node.declare_parameter(ns + ".pass_judge.timeout_ego_stop_for_yield"); + getOrDeclareParameter(node, ns + ".pass_judge.timeout_ego_stop_for_yield"); // param for target area & object cp.crosswalk_attention_range = - node.declare_parameter(ns + ".object_filtering.crosswalk_attention_range"); - cp.look_unknown = node.declare_parameter(ns + ".object_filtering.target_object.unknown"); - cp.look_bicycle = node.declare_parameter(ns + ".object_filtering.target_object.bicycle"); + getOrDeclareParameter(node, ns + ".object_filtering.crosswalk_attention_range"); + cp.look_unknown = + getOrDeclareParameter(node, ns + ".object_filtering.target_object.unknown"); + cp.look_bicycle = + getOrDeclareParameter(node, ns + ".object_filtering.target_object.bicycle"); cp.look_motorcycle = - node.declare_parameter(ns + ".object_filtering.target_object.motorcycle"); + getOrDeclareParameter(node, ns + ".object_filtering.target_object.motorcycle"); cp.look_pedestrian = - node.declare_parameter(ns + ".object_filtering.target_object.pedestrian"); + getOrDeclareParameter(node, ns + ".object_filtering.target_object.pedestrian"); } void CrosswalkModuleManager::launchNewModules(const PathWithLaneId & path) From ddb49ec29dc0b45e2854164a71a08d7563f4a4ab Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 28 Aug 2023 16:09:01 +0900 Subject: [PATCH 019/547] refactor(detection_area): use getOrDeclareParameter (#4766) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/planning/behavior_velocity_detection_area_module/src/manager.cpp b/planning/behavior_velocity_detection_area_module/src/manager.cpp index 99888d0789559..16d32b8a49964 100644 --- a/planning/behavior_velocity_detection_area_module/src/manager.cpp +++ b/planning/behavior_velocity_detection_area_module/src/manager.cpp @@ -15,6 +15,7 @@ #include "manager.hpp" #include +#include #include @@ -29,22 +30,24 @@ namespace behavior_velocity_planner { using lanelet::autoware::DetectionArea; +using tier4_autoware_utils::getOrDeclareParameter; DetectionAreaModuleManager::DetectionAreaModuleManager(rclcpp::Node & node) : SceneModuleManagerInterfaceWithRTC( node, getModuleName(), - node.declare_parameter(std::string(getModuleName()) + ".enable_rtc")) + getOrDeclareParameter(node, std::string(getModuleName()) + ".enable_rtc")) { const std::string ns(getModuleName()); - planner_param_.stop_margin = node.declare_parameter(ns + ".stop_margin"); - planner_param_.use_dead_line = node.declare_parameter(ns + ".use_dead_line"); - planner_param_.dead_line_margin = node.declare_parameter(ns + ".dead_line_margin"); - planner_param_.use_pass_judge_line = node.declare_parameter(ns + ".use_pass_judge_line"); - planner_param_.state_clear_time = node.declare_parameter(ns + ".state_clear_time"); + planner_param_.stop_margin = getOrDeclareParameter(node, ns + ".stop_margin"); + planner_param_.use_dead_line = getOrDeclareParameter(node, ns + ".use_dead_line"); + planner_param_.dead_line_margin = getOrDeclareParameter(node, ns + ".dead_line_margin"); + planner_param_.use_pass_judge_line = + getOrDeclareParameter(node, ns + ".use_pass_judge_line"); + planner_param_.state_clear_time = getOrDeclareParameter(node, ns + ".state_clear_time"); planner_param_.hold_stop_margin_distance = - node.declare_parameter(ns + ".hold_stop_margin_distance"); + getOrDeclareParameter(node, ns + ".hold_stop_margin_distance"); planner_param_.distance_to_judge_over_stop_line = - node.declare_parameter(ns + ".distance_to_judge_over_stop_line"); + getOrDeclareParameter(node, ns + ".distance_to_judge_over_stop_line"); } void DetectionAreaModuleManager::launchNewModules( From 61630e6721310b9ae3de08b125184eb62dc851f6 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 28 Aug 2023 16:50:45 +0900 Subject: [PATCH 020/547] refactor(intersection): use getOrDeclareParameter (#4767) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 102 +++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 6a2b7200abb2e..9d6359ebe9b9b 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -29,94 +30,99 @@ namespace behavior_velocity_planner { +using tier4_autoware_utils::getOrDeclareParameter; + IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) : SceneModuleManagerInterfaceWithRTC( node, getModuleName(), - node.declare_parameter(std::string(getModuleName()) + ".enable_rtc.intersection")), + getOrDeclareParameter(node, std::string(getModuleName()) + ".enable_rtc.intersection")), occlusion_rtc_interface_( &node, "intersection_occlusion", - node.declare_parameter( - std::string(getModuleName()) + ".enable_rtc.intersection_to_occlusion")) + getOrDeclareParameter( + node, std::string(getModuleName()) + ".enable_rtc.intersection_to_occlusion")) { const std::string ns(getModuleName()); auto & ip = intersection_param_; const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); ip.common.attention_area_margin = - node.declare_parameter(ns + ".common.attention_area_margin"); + getOrDeclareParameter(node, ns + ".common.attention_area_margin"); ip.common.attention_area_length = - node.declare_parameter(ns + ".common.attention_area_length"); + getOrDeclareParameter(node, ns + ".common.attention_area_length"); ip.common.attention_area_angle_thr = - node.declare_parameter(ns + ".common.attention_area_angle_threshold"); - ip.common.stop_line_margin = node.declare_parameter(ns + ".common.stop_line_margin"); + getOrDeclareParameter(node, ns + ".common.attention_area_angle_threshold"); + ip.common.stop_line_margin = getOrDeclareParameter(node, ns + ".common.stop_line_margin"); ip.common.intersection_velocity = - node.declare_parameter(ns + ".common.intersection_velocity"); + getOrDeclareParameter(node, ns + ".common.intersection_velocity"); ip.common.intersection_max_acc = - node.declare_parameter(ns + ".common.intersection_max_accel"); + getOrDeclareParameter(node, ns + ".common.intersection_max_accel"); ip.common.stop_overshoot_margin = - node.declare_parameter(ns + ".common.stop_overshoot_margin"); + getOrDeclareParameter(node, ns + ".common.stop_overshoot_margin"); ip.common.use_intersection_area = - node.declare_parameter(ns + ".common.use_intersection_area"); + getOrDeclareParameter(node, ns + ".common.use_intersection_area"); ip.common.path_interpolation_ds = - node.declare_parameter(ns + ".common.path_interpolation_ds"); + getOrDeclareParameter(node, ns + ".common.path_interpolation_ds"); ip.common.consider_wrong_direction_vehicle = - node.declare_parameter(ns + ".common.consider_wrong_direction_vehicle"); + getOrDeclareParameter(node, ns + ".common.consider_wrong_direction_vehicle"); ip.stuck_vehicle.use_stuck_stopline = - node.declare_parameter(ns + ".stuck_vehicle.use_stuck_stopline"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.use_stuck_stopline"); ip.stuck_vehicle.stuck_vehicle_detect_dist = - node.declare_parameter(ns + ".stuck_vehicle.stuck_vehicle_detect_dist"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_detect_dist"); ip.stuck_vehicle.stuck_vehicle_ignore_dist = - node.declare_parameter(ns + ".stuck_vehicle.stuck_vehicle_ignore_dist") + + getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_ignore_dist") + vehicle_info.max_longitudinal_offset_m; ip.stuck_vehicle.stuck_vehicle_vel_thr = - node.declare_parameter(ns + ".stuck_vehicle.stuck_vehicle_vel_thr"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_vel_thr"); /* ip.stuck_vehicle.assumed_front_car_decel = - node.declare_parameter(ns + ".stuck_vehicle.assumed_front_car_decel"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.assumed_front_car_decel"); ip.stuck_vehicle.enable_front_car_decel_prediction = - node.declare_parameter(ns + ".stuck_vehicle.enable_front_car_decel_prediction"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.enable_front_car_decel_prediction"); */ ip.stuck_vehicle.timeout_private_area = - node.declare_parameter(ns + ".stuck_vehicle.timeout_private_area"); + getOrDeclareParameter(node, ns + ".stuck_vehicle.timeout_private_area"); ip.collision_detection.min_predicted_path_confidence = - node.declare_parameter(ns + ".collision_detection.min_predicted_path_confidence"); + getOrDeclareParameter(node, ns + ".collision_detection.min_predicted_path_confidence"); ip.collision_detection.minimum_ego_predicted_velocity = - node.declare_parameter(ns + ".collision_detection.minimum_ego_predicted_velocity"); + getOrDeclareParameter(node, ns + ".collision_detection.minimum_ego_predicted_velocity"); ip.collision_detection.state_transit_margin_time = - node.declare_parameter(ns + ".collision_detection.state_transit_margin_time"); - ip.collision_detection.normal.collision_start_margin_time = - node.declare_parameter(ns + ".collision_detection.normal.collision_start_margin_time"); - ip.collision_detection.normal.collision_end_margin_time = - node.declare_parameter(ns + ".collision_detection.normal.collision_end_margin_time"); - ip.collision_detection.relaxed.collision_start_margin_time = - node.declare_parameter(ns + ".collision_detection.relaxed.collision_start_margin_time"); - ip.collision_detection.relaxed.collision_end_margin_time = - node.declare_parameter(ns + ".collision_detection.relaxed.collision_end_margin_time"); + getOrDeclareParameter(node, ns + ".collision_detection.state_transit_margin_time"); + ip.collision_detection.normal.collision_start_margin_time = getOrDeclareParameter( + node, ns + ".collision_detection.normal.collision_start_margin_time"); + ip.collision_detection.normal.collision_end_margin_time = getOrDeclareParameter( + node, ns + ".collision_detection.normal.collision_end_margin_time"); + ip.collision_detection.relaxed.collision_start_margin_time = getOrDeclareParameter( + node, ns + ".collision_detection.relaxed.collision_start_margin_time"); + ip.collision_detection.relaxed.collision_end_margin_time = getOrDeclareParameter( + node, ns + ".collision_detection.relaxed.collision_end_margin_time"); ip.collision_detection.keep_detection_vel_thr = - node.declare_parameter(ns + ".collision_detection.keep_detection_vel_thr"); + getOrDeclareParameter(node, ns + ".collision_detection.keep_detection_vel_thr"); - ip.occlusion.enable = node.declare_parameter(ns + ".occlusion.enable"); + ip.occlusion.enable = getOrDeclareParameter(node, ns + ".occlusion.enable"); ip.occlusion.occlusion_attention_area_length = - node.declare_parameter(ns + ".occlusion.occlusion_attention_area_length"); - ip.occlusion.enable_creeping = node.declare_parameter(ns + ".occlusion.enable_creeping"); + getOrDeclareParameter(node, ns + ".occlusion.occlusion_attention_area_length"); + ip.occlusion.enable_creeping = + getOrDeclareParameter(node, ns + ".occlusion.enable_creeping"); ip.occlusion.occlusion_creep_velocity = - node.declare_parameter(ns + ".occlusion.occlusion_creep_velocity"); - ip.occlusion.peeking_offset = node.declare_parameter(ns + ".occlusion.peeking_offset"); - ip.occlusion.free_space_max = node.declare_parameter(ns + ".occlusion.free_space_max"); - ip.occlusion.occupied_min = node.declare_parameter(ns + ".occlusion.occupied_min"); - ip.occlusion.do_dp = node.declare_parameter(ns + ".occlusion.do_dp"); + getOrDeclareParameter(node, ns + ".occlusion.occlusion_creep_velocity"); + ip.occlusion.peeking_offset = + getOrDeclareParameter(node, ns + ".occlusion.peeking_offset"); + ip.occlusion.free_space_max = getOrDeclareParameter(node, ns + ".occlusion.free_space_max"); + ip.occlusion.occupied_min = getOrDeclareParameter(node, ns + ".occlusion.occupied_min"); + ip.occlusion.do_dp = getOrDeclareParameter(node, ns + ".occlusion.do_dp"); ip.occlusion.before_creep_stop_time = - node.declare_parameter(ns + ".occlusion.before_creep_stop_time"); + getOrDeclareParameter(node, ns + ".occlusion.before_creep_stop_time"); ip.occlusion.min_vehicle_brake_for_rss = - node.declare_parameter(ns + ".occlusion.min_vehicle_brake_for_rss"); + getOrDeclareParameter(node, ns + ".occlusion.min_vehicle_brake_for_rss"); ip.occlusion.max_vehicle_velocity_for_rss = - node.declare_parameter(ns + ".occlusion.max_vehicle_velocity_for_rss"); - ip.occlusion.denoise_kernel = node.declare_parameter(ns + ".occlusion.denoise_kernel"); + getOrDeclareParameter(node, ns + ".occlusion.max_vehicle_velocity_for_rss"); + ip.occlusion.denoise_kernel = + getOrDeclareParameter(node, ns + ".occlusion.denoise_kernel"); ip.occlusion.possible_object_bbox = - node.declare_parameter>(ns + ".occlusion.possible_object_bbox"); + getOrDeclareParameter>(node, ns + ".occlusion.possible_object_bbox"); ip.occlusion.ignore_parked_vehicle_speed_threshold = - node.declare_parameter(ns + ".occlusion.ignore_parked_vehicle_speed_threshold"); + getOrDeclareParameter(node, ns + ".occlusion.ignore_parked_vehicle_speed_threshold"); } void IntersectionModuleManager::launchNewModules( @@ -263,10 +269,10 @@ MergeFromPrivateModuleManager::MergeFromPrivateModuleManager(rclcpp::Node & node { const std::string ns(getModuleName()); auto & mp = merge_from_private_area_param_; - mp.stop_duration_sec = node.declare_parameter(ns + ".stop_duration_sec"); + mp.stop_duration_sec = getOrDeclareParameter(node, ns + ".stop_duration_sec"); mp.attention_area_length = node.get_parameter("intersection.common.attention_area_length").as_double(); - mp.stop_line_margin = node.declare_parameter(ns + ".stop_line_margin"); + mp.stop_line_margin = getOrDeclareParameter(node, ns + ".stop_line_margin"); mp.path_interpolation_ds = node.get_parameter("intersection.common.path_interpolation_ds").as_double(); } From af177f4347be1ff0c9dfcac9d73134293645d73f Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 28 Aug 2023 18:14:17 +0900 Subject: [PATCH 021/547] refactor(no_stopping_area): use getOrDeclareParameter (#4769) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp b/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp index c7ff0f496f551..60d659497e55a 100644 --- a/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp +++ b/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp @@ -15,6 +15,7 @@ #include "manager.hpp" #include +#include #include @@ -29,23 +30,24 @@ namespace behavior_velocity_planner { using lanelet::autoware::NoStoppingArea; +using tier4_autoware_utils::getOrDeclareParameter; NoStoppingAreaModuleManager::NoStoppingAreaModuleManager(rclcpp::Node & node) : SceneModuleManagerInterfaceWithRTC( node, getModuleName(), - node.declare_parameter(std::string(getModuleName()) + ".enable_rtc")) + getOrDeclareParameter(node, std::string(getModuleName()) + ".enable_rtc")) { const std::string ns(getModuleName()); auto & pp = planner_param_; const auto & vi = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); - pp.state_clear_time = node.declare_parameter(ns + ".state_clear_time"); - pp.stuck_vehicle_vel_thr = node.declare_parameter(ns + ".stuck_vehicle_vel_thr"); - pp.stop_margin = node.declare_parameter(ns + ".stop_margin"); - pp.dead_line_margin = node.declare_parameter(ns + ".dead_line_margin"); - pp.stop_line_margin = node.declare_parameter(ns + ".stop_line_margin"); - pp.detection_area_length = node.declare_parameter(ns + ".detection_area_length"); + pp.state_clear_time = getOrDeclareParameter(node, ns + ".state_clear_time"); + pp.stuck_vehicle_vel_thr = getOrDeclareParameter(node, ns + ".stuck_vehicle_vel_thr"); + pp.stop_margin = getOrDeclareParameter(node, ns + ".stop_margin"); + pp.dead_line_margin = getOrDeclareParameter(node, ns + ".dead_line_margin"); + pp.stop_line_margin = getOrDeclareParameter(node, ns + ".stop_line_margin"); + pp.detection_area_length = getOrDeclareParameter(node, ns + ".detection_area_length"); pp.stuck_vehicle_front_margin = - node.declare_parameter(ns + ".stuck_vehicle_front_margin"); + getOrDeclareParameter(node, ns + ".stuck_vehicle_front_margin"); pp.path_expand_width = vi.vehicle_width_m * 0.5; } From 73f3e0e528e1c1a44b4062b66c9c4a3669e4016d Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 28 Aug 2023 18:56:38 +0900 Subject: [PATCH 022/547] refactor(occlusion_spot): use getOrDeclareParameter (#4770) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/planning/behavior_velocity_occlusion_spot_module/src/manager.cpp b/planning/behavior_velocity_occlusion_spot_module/src/manager.cpp index 5b1b3d489041f..f257012c05519 100644 --- a/planning/behavior_velocity_occlusion_spot_module/src/manager.cpp +++ b/planning/behavior_velocity_occlusion_spot_module/src/manager.cpp @@ -17,6 +17,7 @@ #include "scene_occlusion_spot.hpp" #include +#include #include @@ -28,6 +29,7 @@ namespace behavior_velocity_planner { using occlusion_spot_utils::DETECTION_METHOD; using occlusion_spot_utils::PASS_JUDGE; +using tier4_autoware_utils::getOrDeclareParameter; OcclusionSpotModuleManager::OcclusionSpotModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()) @@ -36,7 +38,7 @@ OcclusionSpotModuleManager::OcclusionSpotModuleManager(rclcpp::Node & node) auto & pp = planner_param_; // for detection type { - const std::string method = node.declare_parameter(ns + ".detection_method"); + const std::string method = getOrDeclareParameter(node, ns + ".detection_method"); if (method == "occupancy_grid") { // module id 0 pp.detection_method = DETECTION_METHOD::OCCUPANCY_GRID; module_id_ = DETECTION_METHOD::OCCUPANCY_GRID; @@ -50,7 +52,7 @@ OcclusionSpotModuleManager::OcclusionSpotModuleManager(rclcpp::Node & node) } // for passable judgement { - const std::string pass_judge = node.declare_parameter(ns + ".pass_judge"); + const std::string pass_judge = getOrDeclareParameter(node, ns + ".pass_judge"); if (pass_judge == "current_velocity") { pp.pass_judge = PASS_JUDGE::CURRENT_VELOCITY; } else if (pass_judge == "smooth_velocity") { @@ -60,48 +62,52 @@ OcclusionSpotModuleManager::OcclusionSpotModuleManager(rclcpp::Node & node) "[behavior_velocity]: occlusion spot pass judge method has invalid argument"}; } } - pp.use_object_info = node.declare_parameter(ns + ".use_object_info"); - pp.use_moving_object_ray_cast = node.declare_parameter(ns + ".use_moving_object_ray_cast"); - pp.use_partition_lanelet = node.declare_parameter(ns + ".use_partition_lanelet"); - pp.pedestrian_vel = node.declare_parameter(ns + ".pedestrian_vel"); - pp.pedestrian_radius = node.declare_parameter(ns + ".pedestrian_radius"); + pp.use_object_info = getOrDeclareParameter(node, ns + ".use_object_info"); + pp.use_moving_object_ray_cast = + getOrDeclareParameter(node, ns + ".use_moving_object_ray_cast"); + pp.use_partition_lanelet = getOrDeclareParameter(node, ns + ".use_partition_lanelet"); + pp.pedestrian_vel = getOrDeclareParameter(node, ns + ".pedestrian_vel"); + pp.pedestrian_radius = getOrDeclareParameter(node, ns + ".pedestrian_radius"); // debug - pp.is_show_occlusion = node.declare_parameter(ns + ".debug.is_show_occlusion"); - pp.is_show_cv_window = node.declare_parameter(ns + ".debug.is_show_cv_window"); - pp.is_show_processing_time = node.declare_parameter(ns + ".debug.is_show_processing_time"); + pp.is_show_occlusion = getOrDeclareParameter(node, ns + ".debug.is_show_occlusion"); + pp.is_show_cv_window = getOrDeclareParameter(node, ns + ".debug.is_show_cv_window"); + pp.is_show_processing_time = + getOrDeclareParameter(node, ns + ".debug.is_show_processing_time"); // threshold pp.detection_area_offset = - node.declare_parameter(ns + ".threshold.detection_area_offset"); + getOrDeclareParameter(node, ns + ".threshold.detection_area_offset"); pp.detection_area_length = - node.declare_parameter(ns + ".threshold.detection_area_length"); - pp.stuck_vehicle_vel = node.declare_parameter(ns + ".threshold.stuck_vehicle_vel"); - pp.lateral_distance_thr = node.declare_parameter(ns + ".threshold.lateral_distance"); - pp.dist_thr = node.declare_parameter(ns + ".threshold.search_dist"); - pp.angle_thr = node.declare_parameter(ns + ".threshold.search_angle"); + getOrDeclareParameter(node, ns + ".threshold.detection_area_length"); + pp.stuck_vehicle_vel = getOrDeclareParameter(node, ns + ".threshold.stuck_vehicle_vel"); + pp.lateral_distance_thr = getOrDeclareParameter(node, ns + ".threshold.lateral_distance"); + pp.dist_thr = getOrDeclareParameter(node, ns + ".threshold.search_dist"); + pp.angle_thr = getOrDeclareParameter(node, ns + ".threshold.search_angle"); // ego additional velocity config - pp.v.safety_ratio = node.declare_parameter(ns + ".motion.safety_ratio"); - pp.v.safe_margin = node.declare_parameter(ns + ".motion.safe_margin"); - pp.v.max_slow_down_jerk = node.declare_parameter(ns + ".motion.max_slow_down_jerk"); - pp.v.max_slow_down_accel = node.declare_parameter(ns + ".motion.max_slow_down_accel"); - pp.v.non_effective_jerk = node.declare_parameter(ns + ".motion.non_effective_jerk"); + pp.v.safety_ratio = getOrDeclareParameter(node, ns + ".motion.safety_ratio"); + pp.v.safe_margin = getOrDeclareParameter(node, ns + ".motion.safe_margin"); + pp.v.max_slow_down_jerk = getOrDeclareParameter(node, ns + ".motion.max_slow_down_jerk"); + pp.v.max_slow_down_accel = + getOrDeclareParameter(node, ns + ".motion.max_slow_down_accel"); + pp.v.non_effective_jerk = getOrDeclareParameter(node, ns + ".motion.non_effective_jerk"); pp.v.non_effective_accel = - node.declare_parameter(ns + ".motion.non_effective_acceleration"); - pp.v.min_allowed_velocity = node.declare_parameter(ns + ".motion.min_allowed_velocity"); + getOrDeclareParameter(node, ns + ".motion.non_effective_acceleration"); + pp.v.min_allowed_velocity = + getOrDeclareParameter(node, ns + ".motion.min_allowed_velocity"); // detection_area param pp.detection_area.min_occlusion_spot_size = - node.declare_parameter(ns + ".detection_area.min_occlusion_spot_size"); + getOrDeclareParameter(node, ns + ".detection_area.min_occlusion_spot_size"); pp.detection_area.min_longitudinal_offset = - node.declare_parameter(ns + ".detection_area.min_longitudinal_offset"); + getOrDeclareParameter(node, ns + ".detection_area.min_longitudinal_offset"); pp.detection_area.max_lateral_distance = - node.declare_parameter(ns + ".detection_area.max_lateral_distance"); + getOrDeclareParameter(node, ns + ".detection_area.max_lateral_distance"); pp.detection_area.slice_length = - node.declare_parameter(ns + ".detection_area.slice_length"); + getOrDeclareParameter(node, ns + ".detection_area.slice_length"); // occupancy grid param - pp.grid.free_space_max = node.declare_parameter(ns + ".grid.free_space_max"); - pp.grid.occupied_min = node.declare_parameter(ns + ".grid.occupied_min"); + pp.grid.free_space_max = getOrDeclareParameter(node, ns + ".grid.free_space_max"); + pp.grid.occupied_min = getOrDeclareParameter(node, ns + ".grid.occupied_min"); const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); pp.baselink_to_front = vehicle_info.max_longitudinal_offset_m; From 7f5f946efdfc6d51fc4fd527e13299f62a67b5d2 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 28 Aug 2023 20:26:22 +0900 Subject: [PATCH 023/547] feat!: add vertical datum in map_projector_info (#4708) * resolve conflict Signed-off-by: kminoda * update Signed-off-by: kminoda * UTM -> LocalCartesianUTM Signed-off-by: kminoda * style(pre-commit): autofix * update Signed-off-by: kminoda * update readme Signed-off-by: kminoda * add altitude Signed-off-by: kminoda * style(pre-commit): autofix * update minor parts Signed-off-by: kminoda * add vertical datum for lanelet2 Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../lanelet2_map_loader_node.cpp | 4 ++-- map/map_projection_loader/README.md | 13 ++++++++----- .../src/load_info_from_lanelet2_map.cpp | 9 +++++++-- .../src/map_projection_loader.cpp | 10 ++++++---- .../test/data/projection_info_local.yaml | 2 +- .../data/projection_info_local_cartesian_utm.yaml | 8 +++++--- .../test/data/projection_info_mgrs.yaml | 3 ++- .../test/test_load_info_from_lanelet2_map.cpp | 6 +++--- ..._node_load_local_cartesian_utm_from_yaml.test.py | 3 ++- .../test/test_node_load_local_from_yaml.test.py | 2 +- .../test/test_node_load_mgrs_from_yaml.test.py | 3 ++- system/default_ad_api/src/vehicle.cpp | 4 ++-- 12 files changed, 41 insertions(+), 26 deletions(-) diff --git a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp index c443318a146ac..ed527c38824fc 100644 --- a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp +++ b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp @@ -69,8 +69,8 @@ void Lanelet2MapLoaderNode::on_map_projector_info( const auto center_line_resolution = get_parameter("center_line_resolution").as_double(); // load map from file - const auto map = - load_map(lanelet2_filename, msg->type, msg->map_origin.latitude, msg->map_origin.longitude); + const auto map = load_map( + lanelet2_filename, msg->projector_type, msg->map_origin.latitude, msg->map_origin.longitude); if (!map) { return; } diff --git a/map/map_projection_loader/README.md b/map/map_projection_loader/README.md index e31db7890cf47..13808e6bf0228 100644 --- a/map/map_projection_loader/README.md +++ b/map/map_projection_loader/README.md @@ -34,8 +34,9 @@ If you want to use MGRS, please specify the MGRS grid as well. ```yaml # map_projector_info.yaml -type: "MGRS" -mgrs_grid: "54SUE" +projector_type: MGRS +vertical_datum: WGS84 +mgrs_grid: 54SUE ``` ### Using LocalCartesianUTM @@ -44,10 +45,12 @@ If you want to use local cartesian UTM, please specify the map origin as well. ```yaml # map_projector_info.yaml -type: "LocalCartesianUTM" +projector_type: LocalCartesianUTM +vertical_datum: WGS84 map_origin: - latitude: 35.6092 - longitude: 139.7303 + latitude: 35.6762 # [deg] + longitude: 139.6503 # [deg] + altitude: 0.0 # [m] ``` ## Published Topics diff --git a/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp b/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp index 77958c20a9e75..f213432b66b50 100644 --- a/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp +++ b/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp @@ -46,10 +46,15 @@ tier4_map_msgs::msg::MapProjectorInfo load_info_from_lanelet2_map(const std::str tier4_map_msgs::msg::MapProjectorInfo msg; if (is_local) { - msg.type = "local"; + msg.projector_type = "local"; } else { - msg.type = "MGRS"; + msg.projector_type = "MGRS"; msg.mgrs_grid = projector.getProjectedMGRSGrid(); } + + // We assume that the vertical datum of the map is WGS84 when using lanelet2 map. + // However, do note that this is not always true, and may cause problems in the future. + // Thus, please consider using the map_projector_info.yaml instead of this deprecated function. + msg.vertical_datum = "WGS84"; return msg; } diff --git a/map/map_projection_loader/src/map_projection_loader.cpp b/map/map_projection_loader/src/map_projection_loader.cpp index 2bd8a9cfa243a..ec38f0ff3642e 100644 --- a/map/map_projection_loader/src/map_projection_loader.cpp +++ b/map/map_projection_loader/src/map_projection_loader.cpp @@ -27,13 +27,15 @@ tier4_map_msgs::msg::MapProjectorInfo load_info_from_yaml(const std::string & fi YAML::Node data = YAML::LoadFile(filename); tier4_map_msgs::msg::MapProjectorInfo msg; - msg.type = data["type"].as(); - if (msg.type == "MGRS") { + msg.projector_type = data["projector_type"].as(); + if (msg.projector_type == "MGRS") { + msg.vertical_datum = data["vertical_datum"].as(); msg.mgrs_grid = data["mgrs_grid"].as(); - } else if (msg.type == "LocalCartesianUTM") { + } else if (msg.projector_type == "LocalCartesianUTM") { + msg.vertical_datum = data["vertical_datum"].as(); msg.map_origin.latitude = data["map_origin"]["latitude"].as(); msg.map_origin.longitude = data["map_origin"]["longitude"].as(); - } else if (msg.type == "local") { + } else if (msg.projector_type == "local") { ; // do nothing } else { throw std::runtime_error( diff --git a/map/map_projection_loader/test/data/projection_info_local.yaml b/map/map_projection_loader/test/data/projection_info_local.yaml index cea4aaf31d439..2e5d27c3e8143 100644 --- a/map/map_projection_loader/test/data/projection_info_local.yaml +++ b/map/map_projection_loader/test/data/projection_info_local.yaml @@ -1 +1 @@ -type: local +projector_type: local diff --git a/map/map_projection_loader/test/data/projection_info_local_cartesian_utm.yaml b/map/map_projection_loader/test/data/projection_info_local_cartesian_utm.yaml index c7500d37bb35a..854a6ad7b5a21 100644 --- a/map/map_projection_loader/test/data/projection_info_local_cartesian_utm.yaml +++ b/map/map_projection_loader/test/data/projection_info_local_cartesian_utm.yaml @@ -1,4 +1,6 @@ -type: LocalCartesianUTM +projector_type: LocalCartesianUTM +vertical_datum: WGS84 map_origin: - latitude: 35.6762 - longitude: 139.6503 + latitude: 35.6762 # [deg] + longitude: 139.6503 # [deg] + altitude: 0.0 # [m] diff --git a/map/map_projection_loader/test/data/projection_info_mgrs.yaml b/map/map_projection_loader/test/data/projection_info_mgrs.yaml index d7687521be5fa..5446de4898900 100644 --- a/map/map_projection_loader/test/data/projection_info_mgrs.yaml +++ b/map/map_projection_loader/test/data/projection_info_mgrs.yaml @@ -1,2 +1,3 @@ -type: MGRS +projector_type: MGRS +vertical_datum: WGS84 mgrs_grid: 54SUE diff --git a/map/map_projection_loader/test/test_load_info_from_lanelet2_map.cpp b/map/map_projection_loader/test/test_load_info_from_lanelet2_map.cpp index 206053fd2e8d2..fdfe96434fe0d 100644 --- a/map/map_projection_loader/test/test_load_info_from_lanelet2_map.cpp +++ b/map/map_projection_loader/test/test_load_info_from_lanelet2_map.cpp @@ -95,7 +95,7 @@ TEST(TestLoadFromLanelet2Map, LoadMGRSGrid) const auto projector_info = load_info_from_lanelet2_map(output_path); // Check the result - EXPECT_EQ(projector_info.type, "MGRS"); + EXPECT_EQ(projector_info.projector_type, "MGRS"); EXPECT_EQ(projector_info.mgrs_grid, mgrs_grid); } @@ -109,7 +109,7 @@ TEST(TestLoadFromLanelet2Map, LoadLocalGrid) const auto projector_info = load_info_from_lanelet2_map(output_path); // Check the result - EXPECT_EQ(projector_info.type, "local"); + EXPECT_EQ(projector_info.projector_type, "local"); } TEST(TestLoadFromLanelet2Map, LoadNoLocalGrid) @@ -122,7 +122,7 @@ TEST(TestLoadFromLanelet2Map, LoadNoLocalGrid) const auto projector_info = load_info_from_lanelet2_map(output_path); // Check the result - EXPECT_EQ(projector_info.type, "MGRS"); + EXPECT_EQ(projector_info.projector_type, "MGRS"); } int main(int argc, char ** argv) diff --git a/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py b/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py index 920f5e3be11a1..9c3b072888ba3 100644 --- a/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py +++ b/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py @@ -131,7 +131,8 @@ def test_node_link(self): self.assertIsNotNone( self.received_message, "No message received on map_projector_info topic" ) - self.assertEqual(self.received_message.type, yaml_data["type"]) + self.assertEqual(self.received_message.projector_type, yaml_data["projector_type"]) + self.assertEqual(self.received_message.vertical_datum, yaml_data["vertical_datum"]) self.assertEqual( self.received_message.map_origin.latitude, yaml_data["map_origin"]["latitude"] ) diff --git a/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py b/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py index 8ab0db43e32a7..e215c4a0354cb 100644 --- a/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py +++ b/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py @@ -131,7 +131,7 @@ def test_node_link(self): self.assertIsNotNone( self.received_message, "No message received on map_projector_info topic" ) - self.assertEqual(self.received_message.type, yaml_data["type"]) + self.assertEqual(self.received_message.projector_type, yaml_data["projector_type"]) self.test_node.destroy_subscription(subscription) diff --git a/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py b/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py index f18f9b7c17e75..5e71d43ed9280 100644 --- a/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py +++ b/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py @@ -131,7 +131,8 @@ def test_node_link(self): self.assertIsNotNone( self.received_message, "No message received on map_projector_info topic" ) - self.assertEqual(self.received_message.type, yaml_data["type"]) + self.assertEqual(self.received_message.projector_type, yaml_data["projector_type"]) + self.assertEqual(self.received_message.vertical_datum, yaml_data["vertical_datum"]) self.assertEqual(self.received_message.mgrs_grid, yaml_data["mgrs_grid"]) self.test_node.destroy_subscription(subscription) diff --git a/system/default_ad_api/src/vehicle.cpp b/system/default_ad_api/src/vehicle.cpp index 61d5b2dde6103..43efdb2adfaf8 100644 --- a/system/default_ad_api/src/vehicle.cpp +++ b/system/default_ad_api/src/vehicle.cpp @@ -146,7 +146,7 @@ void VehicleNode::publish_kinematics() vehicle_kinematics.twist.header = kinematic_state_msgs_->header; vehicle_kinematics.twist.header.frame_id = kinematic_state_msgs_->child_frame_id; vehicle_kinematics.twist.twist = kinematic_state_msgs_->twist; - if (map_projector_info_->type == "MGRS") { + if (map_projector_info_->projector_type == "MGRS") { lanelet::GPSPoint projected_gps_point = lanelet::projection::MGRSProjector::reverse( toBasicPoint3dPt(kinematic_state_msgs_->pose.pose.position), map_projector_info_->mgrs_grid); vehicle_kinematics.geographic_pose.header = kinematic_state_msgs_->header; @@ -154,7 +154,7 @@ void VehicleNode::publish_kinematics() vehicle_kinematics.geographic_pose.position.latitude = projected_gps_point.lat; vehicle_kinematics.geographic_pose.position.longitude = projected_gps_point.lon; vehicle_kinematics.geographic_pose.position.altitude = projected_gps_point.ele; - } else if (map_projector_info_->type == "LocalCartesianUTM") { + } else if (map_projector_info_->projector_type == "LocalCartesianUTM") { lanelet::GPSPoint position{ map_projector_info_->map_origin.latitude, map_projector_info_->map_origin.longitude}; lanelet::Origin origin{position}; From d5204ab92ad99c03afd3a0444c9139d42b001668 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 29 Aug 2023 00:34:31 +0900 Subject: [PATCH 024/547] refactor(out_of_lane): use getOrDeclareParameter (#4771) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/planning/behavior_velocity_out_of_lane_module/src/manager.cpp b/planning/behavior_velocity_out_of_lane_module/src/manager.cpp index 22fb29110466a..075738f07d62e 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/manager.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/manager.cpp @@ -17,6 +17,7 @@ #include "scene_out_of_lane.hpp" #include +#include #include @@ -26,42 +27,47 @@ namespace behavior_velocity_planner { +using tier4_autoware_utils::getOrDeclareParameter; + OutOfLaneModuleManager::OutOfLaneModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()), module_id_(0UL) { const std::string ns(getModuleName()); auto & pp = planner_param_; - pp.mode = node.declare_parameter(ns + ".mode"); + pp.mode = getOrDeclareParameter(node, ns + ".mode"); pp.skip_if_already_overlapping = - node.declare_parameter(ns + ".skip_if_already_overlapping"); + getOrDeclareParameter(node, ns + ".skip_if_already_overlapping"); - pp.time_threshold = node.declare_parameter(ns + ".threshold.time_threshold"); - pp.intervals_ego_buffer = node.declare_parameter(ns + ".intervals.ego_time_buffer"); - pp.intervals_obj_buffer = node.declare_parameter(ns + ".intervals.objects_time_buffer"); - pp.ttc_threshold = node.declare_parameter(ns + ".ttc.threshold"); + pp.time_threshold = getOrDeclareParameter(node, ns + ".threshold.time_threshold"); + pp.intervals_ego_buffer = getOrDeclareParameter(node, ns + ".intervals.ego_time_buffer"); + pp.intervals_obj_buffer = + getOrDeclareParameter(node, ns + ".intervals.objects_time_buffer"); + pp.ttc_threshold = getOrDeclareParameter(node, ns + ".ttc.threshold"); - pp.objects_min_vel = node.declare_parameter(ns + ".objects.minimum_velocity"); + pp.objects_min_vel = getOrDeclareParameter(node, ns + ".objects.minimum_velocity"); pp.objects_use_predicted_paths = - node.declare_parameter(ns + ".objects.use_predicted_paths"); + getOrDeclareParameter(node, ns + ".objects.use_predicted_paths"); pp.objects_min_confidence = - node.declare_parameter(ns + ".objects.predicted_path_min_confidence"); + getOrDeclareParameter(node, ns + ".objects.predicted_path_min_confidence"); - pp.overlap_min_dist = node.declare_parameter(ns + ".overlap.minimum_distance"); - pp.overlap_extra_length = node.declare_parameter(ns + ".overlap.extra_length"); + pp.overlap_min_dist = getOrDeclareParameter(node, ns + ".overlap.minimum_distance"); + pp.overlap_extra_length = getOrDeclareParameter(node, ns + ".overlap.extra_length"); - pp.skip_if_over_max_decel = node.declare_parameter(ns + ".action.skip_if_over_max_decel"); - pp.strict = node.declare_parameter(ns + ".action.strict"); - pp.dist_buffer = node.declare_parameter(ns + ".action.distance_buffer"); - pp.slow_velocity = node.declare_parameter(ns + ".action.slowdown.velocity"); + pp.skip_if_over_max_decel = + getOrDeclareParameter(node, ns + ".action.skip_if_over_max_decel"); + pp.strict = getOrDeclareParameter(node, ns + ".action.strict"); + pp.dist_buffer = getOrDeclareParameter(node, ns + ".action.distance_buffer"); + pp.slow_velocity = getOrDeclareParameter(node, ns + ".action.slowdown.velocity"); pp.slow_dist_threshold = - node.declare_parameter(ns + ".action.slowdown.distance_threshold"); - pp.stop_dist_threshold = node.declare_parameter(ns + ".action.stop.distance_threshold"); + getOrDeclareParameter(node, ns + ".action.slowdown.distance_threshold"); + pp.stop_dist_threshold = + getOrDeclareParameter(node, ns + ".action.stop.distance_threshold"); - pp.extra_front_offset = node.declare_parameter(ns + ".ego.extra_front_offset"); - pp.extra_rear_offset = node.declare_parameter(ns + ".ego.extra_rear_offset"); - pp.extra_left_offset = node.declare_parameter(ns + ".ego.extra_left_offset"); - pp.extra_right_offset = node.declare_parameter(ns + ".ego.extra_right_offset"); + pp.extra_front_offset = getOrDeclareParameter(node, ns + ".ego.extra_front_offset"); + pp.extra_rear_offset = getOrDeclareParameter(node, ns + ".ego.extra_rear_offset"); + pp.extra_left_offset = getOrDeclareParameter(node, ns + ".ego.extra_left_offset"); + pp.extra_right_offset = getOrDeclareParameter(node, ns + ".ego.extra_right_offset"); const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); pp.front_offset = vehicle_info.max_longitudinal_offset_m; pp.rear_offset = vehicle_info.min_longitudinal_offset_m; From 820130a3be2cd5d0f4d44cb0b3c4a0507d1f5666 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 29 Aug 2023 01:17:14 +0900 Subject: [PATCH 025/547] refactor(run_out): use getOrDeclareParameter (#4772) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 83 +++++++++---------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/planning/behavior_velocity_run_out_module/src/manager.cpp b/planning/behavior_velocity_run_out_module/src/manager.cpp index 8887f0749a2d5..b262bf00cb57a 100644 --- a/planning/behavior_velocity_run_out_module/src/manager.cpp +++ b/planning/behavior_velocity_run_out_module/src/manager.cpp @@ -14,14 +14,14 @@ #include "manager.hpp" +#include + #include #include namespace behavior_velocity_planner { -namespace -{ -} // namespace +using tier4_autoware_utils::getOrDeclareParameter; RunOutModuleManager::RunOutModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()) @@ -48,82 +48,75 @@ RunOutModuleManager::RunOutModuleManager(rclcpp::Node & node) { auto & p = planner_param_.common; - p.normal_min_jerk = node.has_parameter("normal.min_jerk") - ? node.get_parameter("normal.min_jerk").get_value() - : node.declare_parameter("normal.min_jerk"); - p.normal_min_acc = node.has_parameter("normal.min_acc") - ? node.get_parameter("normal.min_acc").get_value() - : node.declare_parameter("normal.min_acc"); - p.limit_min_jerk = node.has_parameter("limit.min_jerk") - ? node.get_parameter("limit.min_jerk").get_value() - : node.declare_parameter("limit.min_jerk"); - p.limit_min_acc = node.has_parameter("limit.min_acc") - ? node.get_parameter("limit.min_acc").get_value() - : node.declare_parameter("limit.min_acc"); + p.normal_min_jerk = getOrDeclareParameter(node, "normal.min_jerk"); + p.normal_min_acc = getOrDeclareParameter(node, "normal.min_acc"); + p.limit_min_jerk = getOrDeclareParameter(node, "limit.min_jerk"); + p.limit_min_acc = getOrDeclareParameter(node, "limit.min_acc"); } { auto & p = planner_param_.run_out; - p.detection_method = node.declare_parameter(ns + ".detection_method"); - p.use_partition_lanelet = node.declare_parameter(ns + ".use_partition_lanelet"); - p.specify_decel_jerk = node.declare_parameter(ns + ".specify_decel_jerk"); - p.stop_margin = node.declare_parameter(ns + ".stop_margin"); - p.passing_margin = node.declare_parameter(ns + ".passing_margin"); - p.deceleration_jerk = node.declare_parameter(ns + ".deceleration_jerk"); - p.detection_distance = node.declare_parameter(ns + ".detection_distance"); - p.detection_span = node.declare_parameter(ns + ".detection_span"); - p.min_vel_ego_kmph = node.declare_parameter(ns + ".min_vel_ego_kmph"); + p.detection_method = getOrDeclareParameter(node, ns + ".detection_method"); + p.use_partition_lanelet = getOrDeclareParameter(node, ns + ".use_partition_lanelet"); + p.specify_decel_jerk = getOrDeclareParameter(node, ns + ".specify_decel_jerk"); + p.stop_margin = getOrDeclareParameter(node, ns + ".stop_margin"); + p.passing_margin = getOrDeclareParameter(node, ns + ".passing_margin"); + p.deceleration_jerk = getOrDeclareParameter(node, ns + ".deceleration_jerk"); + p.detection_distance = getOrDeclareParameter(node, ns + ".detection_distance"); + p.detection_span = getOrDeclareParameter(node, ns + ".detection_span"); + p.min_vel_ego_kmph = getOrDeclareParameter(node, ns + ".min_vel_ego_kmph"); } { auto & p = planner_param_.detection_area; const std::string ns_da = ns + ".detection_area"; - p.margin_ahead = node.declare_parameter(ns_da + ".margin_ahead"); - p.margin_behind = node.declare_parameter(ns_da + ".margin_behind"); + p.margin_ahead = getOrDeclareParameter(node, ns_da + ".margin_ahead"); + p.margin_behind = getOrDeclareParameter(node, ns_da + ".margin_behind"); } { auto & p = planner_param_.mandatory_area; const std::string ns_da = ns + ".mandatory_area"; - p.decel_jerk = node.declare_parameter(ns_da + ".decel_jerk"); + p.decel_jerk = getOrDeclareParameter(node, ns_da + ".decel_jerk"); } { auto & p = planner_param_.dynamic_obstacle; const std::string ns_do = ns + ".dynamic_obstacle"; - p.use_mandatory_area = node.declare_parameter(ns_do + ".use_mandatory_area"); - p.min_vel_kmph = node.declare_parameter(ns_do + ".min_vel_kmph"); - p.max_vel_kmph = node.declare_parameter(ns_do + ".max_vel_kmph"); - p.diameter = node.declare_parameter(ns_do + ".diameter"); - p.height = node.declare_parameter(ns_do + ".height"); - p.max_prediction_time = node.declare_parameter(ns_do + ".max_prediction_time"); - p.time_step = node.declare_parameter(ns_do + ".time_step"); - p.points_interval = node.declare_parameter(ns_do + ".points_interval"); + p.use_mandatory_area = getOrDeclareParameter(node, ns_do + ".use_mandatory_area"); + p.min_vel_kmph = getOrDeclareParameter(node, ns_do + ".min_vel_kmph"); + p.max_vel_kmph = getOrDeclareParameter(node, ns_do + ".max_vel_kmph"); + p.diameter = getOrDeclareParameter(node, ns_do + ".diameter"); + p.height = getOrDeclareParameter(node, ns_do + ".height"); + p.max_prediction_time = getOrDeclareParameter(node, ns_do + ".max_prediction_time"); + p.time_step = getOrDeclareParameter(node, ns_do + ".time_step"); + p.points_interval = getOrDeclareParameter(node, ns_do + ".points_interval"); } { auto & p = planner_param_.approaching; const std::string ns_a = ns + ".approaching"; - p.enable = node.declare_parameter(ns_a + ".enable"); - p.margin = node.declare_parameter(ns_a + ".margin"); - p.limit_vel_kmph = node.declare_parameter(ns_a + ".limit_vel_kmph"); + p.enable = getOrDeclareParameter(node, ns_a + ".enable"); + p.margin = getOrDeclareParameter(node, ns_a + ".margin"); + p.limit_vel_kmph = getOrDeclareParameter(node, ns_a + ".limit_vel_kmph"); } { auto & p = planner_param_.state_param; const std::string ns_s = ns + ".state"; - p.stop_thresh = node.declare_parameter(ns_s + ".stop_thresh"); - p.stop_time_thresh = node.declare_parameter(ns_s + ".stop_time_thresh"); - p.disable_approach_dist = node.declare_parameter(ns_s + ".disable_approach_dist"); - p.keep_approach_duration = node.declare_parameter(ns_s + ".keep_approach_duration"); + p.stop_thresh = getOrDeclareParameter(node, ns_s + ".stop_thresh"); + p.stop_time_thresh = getOrDeclareParameter(node, ns_s + ".stop_time_thresh"); + p.disable_approach_dist = getOrDeclareParameter(node, ns_s + ".disable_approach_dist"); + p.keep_approach_duration = + getOrDeclareParameter(node, ns_s + ".keep_approach_duration"); } { auto & p = planner_param_.slow_down_limit; const std::string ns_m = ns + ".slow_down_limit"; - p.enable = node.declare_parameter(ns_m + ".enable"); - p.max_jerk = node.declare_parameter(ns_m + ".max_jerk"); - p.max_acc = node.declare_parameter(ns_m + ".max_acc"); + p.enable = getOrDeclareParameter(node, ns_m + ".enable"); + p.max_jerk = getOrDeclareParameter(node, ns_m + ".max_jerk"); + p.max_acc = getOrDeclareParameter(node, ns_m + ".max_acc"); } debug_ptr_ = std::make_shared(node); From 3cdf4f52c823277d6b7ddd73b0d04d5238f77fff Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 29 Aug 2023 02:00:19 +0900 Subject: [PATCH 026/547] refactor(speed_bump): use getOrDeclareParameter (#4773) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/planning/behavior_velocity_speed_bump_module/src/manager.cpp b/planning/behavior_velocity_speed_bump_module/src/manager.cpp index e502963e60b47..dacc99d55b68e 100644 --- a/planning/behavior_velocity_speed_bump_module/src/manager.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/manager.cpp @@ -15,6 +15,7 @@ #include "manager.hpp" #include +#include #include @@ -28,25 +29,26 @@ namespace behavior_velocity_planner { using lanelet::autoware::SpeedBump; +using tier4_autoware_utils::getOrDeclareParameter; SpeedBumpModuleManager::SpeedBumpModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()) { std::string ns(getModuleName()); - planner_param_.slow_start_margin = node.declare_parameter(ns + ".slow_start_margin"); - planner_param_.slow_end_margin = node.declare_parameter(ns + ".slow_end_margin"); - planner_param_.print_debug_info = node.declare_parameter(ns + ".print_debug_info"); + planner_param_.slow_start_margin = getOrDeclareParameter(node, ns + ".slow_start_margin"); + planner_param_.slow_end_margin = getOrDeclareParameter(node, ns + ".slow_end_margin"); + planner_param_.print_debug_info = getOrDeclareParameter(node, ns + ".print_debug_info"); // limits for speed bump height and slow down speed ns += ".speed_calculation"; planner_param_.speed_calculation_min_height = - static_cast(node.declare_parameter(ns + ".min_height")); + static_cast(getOrDeclareParameter(node, ns + ".min_height")); planner_param_.speed_calculation_max_height = - static_cast(node.declare_parameter(ns + ".max_height")); + static_cast(getOrDeclareParameter(node, ns + ".max_height")); planner_param_.speed_calculation_min_speed = - static_cast(node.declare_parameter(ns + ".min_speed")); + static_cast(getOrDeclareParameter(node, ns + ".min_speed")); planner_param_.speed_calculation_max_speed = - static_cast(node.declare_parameter(ns + ".max_speed")); + static_cast(getOrDeclareParameter(node, ns + ".max_speed")); } void SpeedBumpModuleManager::launchNewModules( From 440d1c9e4ea3d27c8d002b981cd1fa5cfd4a22f4 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 29 Aug 2023 02:43:36 +0900 Subject: [PATCH 027/547] refactor(stop_line): use getOrDeclareParameter (#4774) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/planning/behavior_velocity_stop_line_module/src/manager.cpp b/planning/behavior_velocity_stop_line_module/src/manager.cpp index 390550fc9e563..5c9f0fd39c644 100644 --- a/planning/behavior_velocity_stop_line_module/src/manager.cpp +++ b/planning/behavior_velocity_stop_line_module/src/manager.cpp @@ -14,6 +14,8 @@ #include "manager.hpp" +#include "tier4_autoware_utils/ros/parameter.hpp" + #include #include #include @@ -22,20 +24,22 @@ namespace behavior_velocity_planner { using lanelet::TrafficSign; +using tier4_autoware_utils::getOrDeclareParameter; StopLineModuleManager::StopLineModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()) { const std::string ns(getModuleName()); auto & p = planner_param_; - p.stop_margin = node.declare_parameter(ns + ".stop_margin"); - p.hold_stop_margin_distance = node.declare_parameter(ns + ".hold_stop_margin_distance"); - p.stop_duration_sec = node.declare_parameter(ns + ".stop_duration_sec"); + p.stop_margin = getOrDeclareParameter(node, ns + ".stop_margin"); + p.hold_stop_margin_distance = + getOrDeclareParameter(node, ns + ".hold_stop_margin_distance"); + p.stop_duration_sec = getOrDeclareParameter(node, ns + ".stop_duration_sec"); p.use_initialization_stop_line_state = - node.declare_parameter(ns + ".use_initialization_stop_line_state"); + getOrDeclareParameter(node, ns + ".use_initialization_stop_line_state"); // debug p.show_stop_line_collision_check = - node.declare_parameter(ns + ".debug.show_stop_line_collision_check"); + getOrDeclareParameter(node, ns + ".debug.show_stop_line_collision_check"); } std::vector StopLineModuleManager::getStopLinesWithLaneIdOnPath( From 4ed36d226318aba16f4b8f5caf03c3de137d9df2 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 29 Aug 2023 03:26:34 +0900 Subject: [PATCH 028/547] refactor(traffic_light): use getOrDeclareParameter (#4775) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/planning/behavior_velocity_traffic_light_module/src/manager.cpp b/planning/behavior_velocity_traffic_light_module/src/manager.cpp index 5516710ff3d5d..1b0646f4bee00 100644 --- a/planning/behavior_velocity_traffic_light_module/src/manager.cpp +++ b/planning/behavior_velocity_traffic_light_module/src/manager.cpp @@ -14,6 +14,8 @@ #include "manager.hpp" +#include + #include #include @@ -26,17 +28,19 @@ namespace behavior_velocity_planner { using lanelet::TrafficLight; +using tier4_autoware_utils::getOrDeclareParameter; TrafficLightModuleManager::TrafficLightModuleManager(rclcpp::Node & node) : SceneModuleManagerInterfaceWithRTC( node, getModuleName(), - node.declare_parameter(std::string(getModuleName()) + ".enable_rtc")) + getOrDeclareParameter(node, std::string(getModuleName()) + ".enable_rtc")) { const std::string ns(getModuleName()); - planner_param_.stop_margin = node.declare_parameter(ns + ".stop_margin"); - planner_param_.tl_state_timeout = node.declare_parameter(ns + ".tl_state_timeout"); - planner_param_.enable_pass_judge = node.declare_parameter(ns + ".enable_pass_judge"); - planner_param_.yellow_lamp_period = node.declare_parameter(ns + ".yellow_lamp_period"); + planner_param_.stop_margin = getOrDeclareParameter(node, ns + ".stop_margin"); + planner_param_.tl_state_timeout = getOrDeclareParameter(node, ns + ".tl_state_timeout"); + planner_param_.enable_pass_judge = getOrDeclareParameter(node, ns + ".enable_pass_judge"); + planner_param_.yellow_lamp_period = + getOrDeclareParameter(node, ns + ".yellow_lamp_period"); pub_tl_state_ = node.create_publisher( "~/output/traffic_signal", 1); } From 8f4e4737226e7cf928a585a4fb517cf34662c90e Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 29 Aug 2023 04:09:21 +0900 Subject: [PATCH 029/547] refactor(virtual_traffic_light): use getOrDeclareParameter (#4776) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp b/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp index e5a2b0e43def2..3fe6c19a5f485 100644 --- a/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp +++ b/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp @@ -15,6 +15,7 @@ #include "manager.hpp" #include +#include #include #include @@ -25,6 +26,7 @@ namespace behavior_velocity_planner { using lanelet::autoware::VirtualTrafficLight; +using tier4_autoware_utils::getOrDeclareParameter; VirtualTrafficLightModuleManager::VirtualTrafficLightModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()) @@ -33,14 +35,15 @@ VirtualTrafficLightModuleManager::VirtualTrafficLightModuleManager(rclcpp::Node { auto & p = planner_param_; - p.max_delay_sec = node.declare_parameter(ns + ".max_delay_sec"); - p.near_line_distance = node.declare_parameter(ns + ".near_line_distance"); - p.dead_line_margin = node.declare_parameter(ns + ".dead_line_margin"); - p.hold_stop_margin_distance = node.declare_parameter(ns + ".hold_stop_margin_distance"); - p.max_yaw_deviation_rad = - tier4_autoware_utils::deg2rad(node.declare_parameter(ns + ".max_yaw_deviation_deg")); + p.max_delay_sec = getOrDeclareParameter(node, ns + ".max_delay_sec"); + p.near_line_distance = getOrDeclareParameter(node, ns + ".near_line_distance"); + p.dead_line_margin = getOrDeclareParameter(node, ns + ".dead_line_margin"); + p.hold_stop_margin_distance = + getOrDeclareParameter(node, ns + ".hold_stop_margin_distance"); + p.max_yaw_deviation_rad = tier4_autoware_utils::deg2rad( + getOrDeclareParameter(node, ns + ".max_yaw_deviation_deg")); p.check_timeout_after_stop_line = - node.declare_parameter(ns + ".check_timeout_after_stop_line"); + getOrDeclareParameter(node, ns + ".check_timeout_after_stop_line"); } } From e70531fffa5ce35cdbc00287e3eb9044b3389b99 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 29 Aug 2023 04:52:17 +0900 Subject: [PATCH 030/547] refactor(walkway): use getOrDeclareParameter (#4777) Signed-off-by: Takamasa Horibe --- planning/behavior_velocity_walkway_module/src/manager.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/planning/behavior_velocity_walkway_module/src/manager.cpp b/planning/behavior_velocity_walkway_module/src/manager.cpp index d0c1a47408929..d10ff52850752 100644 --- a/planning/behavior_velocity_walkway_module/src/manager.cpp +++ b/planning/behavior_velocity_walkway_module/src/manager.cpp @@ -15,6 +15,7 @@ #include "manager.hpp" #include +#include #include #include @@ -26,6 +27,7 @@ namespace behavior_velocity_planner { using lanelet::autoware::Crosswalk; +using tier4_autoware_utils::getOrDeclareParameter; WalkwayModuleManager::WalkwayModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()) @@ -35,8 +37,8 @@ WalkwayModuleManager::WalkwayModuleManager(rclcpp::Node & node) // for walkway parameters auto & wp = walkway_planner_param_; wp.stop_distance_from_crosswalk = - node.declare_parameter(ns + ".stop_distance_from_crosswalk"); - wp.stop_duration = node.declare_parameter(ns + ".stop_duration"); + getOrDeclareParameter(node, ns + ".stop_distance_from_crosswalk"); + wp.stop_duration = getOrDeclareParameter(node, ns + ".stop_duration"); } void WalkwayModuleManager::launchNewModules(const PathWithLaneId & path) From ae51e2adcd5c15624ef80f2803326de219e4c4aa Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 29 Aug 2023 05:25:18 +0900 Subject: [PATCH 031/547] refactor(behavior_path_planner): use getOrDeclareParameter (#4790) * refactor(avoidance): use getOrDeclareParameter Signed-off-by: satoshi-ota * refactor(lane_change): use getOrDeclareParameter Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../src/scene_module/avoidance/manager.cpp | 214 +++++++++--------- .../src/scene_module/lane_change/manager.cpp | 194 ++++++++-------- 2 files changed, 207 insertions(+), 201 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp index 2d4a6b9c42b98..1257ae2056850 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp @@ -23,68 +23,62 @@ namespace behavior_path_planner { -namespace -{ -template -T get_parameter(rclcpp::Node * node, const std::string & ns) -{ - if (node->has_parameter(ns)) { - return node->get_parameter(ns).get_value(); - } - - return node->declare_parameter(ns); -} -} // namespace - AvoidanceModuleManager::AvoidanceModuleManager( rclcpp::Node * node, const std::string & name, const ModuleConfigParameters & config) : SceneModuleManagerInterface(node, name, config, {"left", "right"}) { using autoware_auto_perception_msgs::msg::ObjectClassification; + using tier4_autoware_utils::getOrDeclareParameter; AvoidanceParameters p{}; // general params { std::string ns = "avoidance."; p.resample_interval_for_planning = - get_parameter(node, ns + "resample_interval_for_planning"); + getOrDeclareParameter(*node, ns + "resample_interval_for_planning"); p.resample_interval_for_output = - get_parameter(node, ns + "resample_interval_for_output"); - p.enable_bound_clipping = get_parameter(node, ns + "enable_bound_clipping"); - p.enable_cancel_maneuver = get_parameter(node, ns + "enable_cancel_maneuver"); + getOrDeclareParameter(*node, ns + "resample_interval_for_output"); + p.enable_bound_clipping = getOrDeclareParameter(*node, ns + "enable_bound_clipping"); + p.enable_cancel_maneuver = getOrDeclareParameter(*node, ns + "enable_cancel_maneuver"); p.enable_force_avoidance_for_stopped_vehicle = - get_parameter(node, ns + "enable_force_avoidance_for_stopped_vehicle"); - p.enable_yield_maneuver = get_parameter(node, ns + "enable_yield_maneuver"); + getOrDeclareParameter(*node, ns + "enable_force_avoidance_for_stopped_vehicle"); + p.enable_yield_maneuver = getOrDeclareParameter(*node, ns + "enable_yield_maneuver"); p.enable_yield_maneuver_during_shifting = - get_parameter(node, ns + "enable_yield_maneuver_during_shifting"); - p.disable_path_update = get_parameter(node, ns + "disable_path_update"); - p.publish_debug_marker = get_parameter(node, ns + "publish_debug_marker"); - p.print_debug_info = get_parameter(node, ns + "print_debug_info"); + getOrDeclareParameter(*node, ns + "enable_yield_maneuver_during_shifting"); + p.disable_path_update = getOrDeclareParameter(*node, ns + "disable_path_update"); + p.publish_debug_marker = getOrDeclareParameter(*node, ns + "publish_debug_marker"); + p.print_debug_info = getOrDeclareParameter(*node, ns + "print_debug_info"); } // drivable area { std::string ns = "avoidance."; - p.use_adjacent_lane = get_parameter(node, ns + "use_adjacent_lane"); - p.use_opposite_lane = get_parameter(node, ns + "use_opposite_lane"); - p.use_intersection_areas = get_parameter(node, ns + "use_intersection_areas"); - p.use_hatched_road_markings = get_parameter(node, ns + "use_hatched_road_markings"); + p.use_adjacent_lane = getOrDeclareParameter(*node, ns + "use_adjacent_lane"); + p.use_opposite_lane = getOrDeclareParameter(*node, ns + "use_opposite_lane"); + p.use_intersection_areas = getOrDeclareParameter(*node, ns + "use_intersection_areas"); + p.use_hatched_road_markings = + getOrDeclareParameter(*node, ns + "use_hatched_road_markings"); } // target object { const auto get_object_param = [&](std::string && ns) { ObjectParameter param{}; - param.is_target = get_parameter(node, ns + "is_target"); - param.execute_num = get_parameter(node, ns + "execute_num"); - param.moving_speed_threshold = get_parameter(node, ns + "moving_speed_threshold"); - param.moving_time_threshold = get_parameter(node, ns + "moving_time_threshold"); - param.max_expand_ratio = get_parameter(node, ns + "max_expand_ratio"); - param.envelope_buffer_margin = get_parameter(node, ns + "envelope_buffer_margin"); - param.avoid_margin_lateral = get_parameter(node, ns + "avoid_margin_lateral"); - param.safety_buffer_lateral = get_parameter(node, ns + "safety_buffer_lateral"); + param.is_target = getOrDeclareParameter(*node, ns + "is_target"); + param.execute_num = getOrDeclareParameter(*node, ns + "execute_num"); + param.moving_speed_threshold = + getOrDeclareParameter(*node, ns + "moving_speed_threshold"); + param.moving_time_threshold = + getOrDeclareParameter(*node, ns + "moving_time_threshold"); + param.max_expand_ratio = getOrDeclareParameter(*node, ns + "max_expand_ratio"); + param.envelope_buffer_margin = + getOrDeclareParameter(*node, ns + "envelope_buffer_margin"); + param.avoid_margin_lateral = + getOrDeclareParameter(*node, ns + "avoid_margin_lateral"); + param.safety_buffer_lateral = + getOrDeclareParameter(*node, ns + "safety_buffer_lateral"); param.safety_buffer_longitudinal = - get_parameter(node, ns + "safety_buffer_longitudinal"); + getOrDeclareParameter(*node, ns + "safety_buffer_longitudinal"); return param; }; @@ -101,119 +95,128 @@ AvoidanceModuleManager::AvoidanceModuleManager( p.object_parameters.emplace(ObjectClassification::UNKNOWN, get_object_param(ns + "unknown.")); p.lower_distance_for_polygon_expansion = - get_parameter(node, ns + "lower_distance_for_polygon_expansion"); + getOrDeclareParameter(*node, ns + "lower_distance_for_polygon_expansion"); p.upper_distance_for_polygon_expansion = - get_parameter(node, ns + "upper_distance_for_polygon_expansion"); + getOrDeclareParameter(*node, ns + "upper_distance_for_polygon_expansion"); } // target filtering { std::string ns = "avoidance.target_filtering."; - p.threshold_time_force_avoidance_for_stopped_vehicle = - get_parameter(node, ns + "threshold_time_force_avoidance_for_stopped_vehicle"); - p.object_ignore_section_traffic_light_in_front_distance = - get_parameter(node, ns + "object_ignore_section_traffic_light_in_front_distance"); - p.object_ignore_section_crosswalk_in_front_distance = - get_parameter(node, ns + "object_ignore_section_crosswalk_in_front_distance"); + p.threshold_time_force_avoidance_for_stopped_vehicle = getOrDeclareParameter( + *node, ns + "threshold_time_force_avoidance_for_stopped_vehicle"); + p.object_ignore_section_traffic_light_in_front_distance = getOrDeclareParameter( + *node, ns + "object_ignore_section_traffic_light_in_front_distance"); + p.object_ignore_section_crosswalk_in_front_distance = getOrDeclareParameter( + *node, ns + "object_ignore_section_crosswalk_in_front_distance"); p.object_ignore_section_crosswalk_behind_distance = - get_parameter(node, ns + "object_ignore_section_crosswalk_behind_distance"); + getOrDeclareParameter(*node, ns + "object_ignore_section_crosswalk_behind_distance"); p.object_check_forward_distance = - get_parameter(node, ns + "object_check_forward_distance"); + getOrDeclareParameter(*node, ns + "object_check_forward_distance"); p.object_check_backward_distance = - get_parameter(node, ns + "object_check_backward_distance"); - p.object_check_goal_distance = get_parameter(node, ns + "object_check_goal_distance"); + getOrDeclareParameter(*node, ns + "object_check_backward_distance"); + p.object_check_goal_distance = + getOrDeclareParameter(*node, ns + "object_check_goal_distance"); p.threshold_distance_object_is_on_center = - get_parameter(node, ns + "threshold_distance_object_is_on_center"); + getOrDeclareParameter(*node, ns + "threshold_distance_object_is_on_center"); p.object_check_shiftable_ratio = - get_parameter(node, ns + "object_check_shiftable_ratio"); + getOrDeclareParameter(*node, ns + "object_check_shiftable_ratio"); p.object_check_min_road_shoulder_width = - get_parameter(node, ns + "object_check_min_road_shoulder_width"); - p.object_last_seen_threshold = get_parameter(node, ns + "object_last_seen_threshold"); + getOrDeclareParameter(*node, ns + "object_check_min_road_shoulder_width"); + p.object_last_seen_threshold = + getOrDeclareParameter(*node, ns + "object_last_seen_threshold"); } // safety check general params { std::string ns = "avoidance.safety_check."; - p.enable_safety_check = get_parameter(node, ns + "enable"); - p.check_current_lane = get_parameter(node, ns + "check_current_lane"); - p.check_shift_side_lane = get_parameter(node, ns + "check_shift_side_lane"); - p.check_other_side_lane = get_parameter(node, ns + "check_other_side_lane"); - p.check_unavoidable_object = get_parameter(node, ns + "check_unavoidable_object"); - p.check_other_object = get_parameter(node, ns + "check_other_object"); - p.check_all_predicted_path = get_parameter(node, ns + "check_all_predicted_path"); - p.safety_check_time_horizon = get_parameter(node, ns + "time_horizon"); - p.safety_check_time_resolution = get_parameter(node, ns + "time_resolution"); + p.enable_safety_check = getOrDeclareParameter(*node, ns + "enable"); + p.check_current_lane = getOrDeclareParameter(*node, ns + "check_current_lane"); + p.check_shift_side_lane = getOrDeclareParameter(*node, ns + "check_shift_side_lane"); + p.check_other_side_lane = getOrDeclareParameter(*node, ns + "check_other_side_lane"); + p.check_unavoidable_object = + getOrDeclareParameter(*node, ns + "check_unavoidable_object"); + p.check_other_object = getOrDeclareParameter(*node, ns + "check_other_object"); + p.check_all_predicted_path = + getOrDeclareParameter(*node, ns + "check_all_predicted_path"); + p.safety_check_time_horizon = getOrDeclareParameter(*node, ns + "time_horizon"); + p.safety_check_time_resolution = getOrDeclareParameter(*node, ns + "time_resolution"); p.safety_check_backward_distance = - get_parameter(node, ns + "safety_check_backward_distance"); + getOrDeclareParameter(*node, ns + "safety_check_backward_distance"); p.hysteresis_factor_expand_rate = - get_parameter(node, ns + "hysteresis_factor_expand_rate"); - p.hysteresis_factor_safe_count = get_parameter(node, ns + "hysteresis_factor_safe_count"); + getOrDeclareParameter(*node, ns + "hysteresis_factor_expand_rate"); + p.hysteresis_factor_safe_count = + getOrDeclareParameter(*node, ns + "hysteresis_factor_safe_count"); } // safety check rss params { std::string ns = "avoidance.safety_check."; p.rss_params.longitudinal_distance_min_threshold = - get_parameter(node, ns + "longitudinal_distance_min_threshold"); + getOrDeclareParameter(*node, ns + "longitudinal_distance_min_threshold"); p.rss_params.longitudinal_velocity_delta_time = - get_parameter(node, ns + "longitudinal_velocity_delta_time"); + getOrDeclareParameter(*node, ns + "longitudinal_velocity_delta_time"); p.rss_params.front_vehicle_deceleration = - get_parameter(node, ns + "expected_front_deceleration"); + getOrDeclareParameter(*node, ns + "expected_front_deceleration"); p.rss_params.rear_vehicle_deceleration = - get_parameter(node, ns + "expected_rear_deceleration"); + getOrDeclareParameter(*node, ns + "expected_rear_deceleration"); p.rss_params.rear_vehicle_reaction_time = - get_parameter(node, ns + "rear_vehicle_reaction_time"); + getOrDeclareParameter(*node, ns + "rear_vehicle_reaction_time"); p.rss_params.rear_vehicle_safety_time_margin = - get_parameter(node, ns + "rear_vehicle_safety_time_margin"); + getOrDeclareParameter(*node, ns + "rear_vehicle_safety_time_margin"); p.rss_params.lateral_distance_max_threshold = - get_parameter(node, ns + "lateral_distance_max_threshold"); + getOrDeclareParameter(*node, ns + "lateral_distance_max_threshold"); } // avoidance maneuver (lateral) { std::string ns = "avoidance.avoidance.lateral."; - p.soft_road_shoulder_margin = get_parameter(node, ns + "soft_road_shoulder_margin"); - p.hard_road_shoulder_margin = get_parameter(node, ns + "hard_road_shoulder_margin"); - p.lateral_execution_threshold = get_parameter(node, ns + "lateral_execution_threshold"); + p.soft_road_shoulder_margin = + getOrDeclareParameter(*node, ns + "soft_road_shoulder_margin"); + p.hard_road_shoulder_margin = + getOrDeclareParameter(*node, ns + "hard_road_shoulder_margin"); + p.lateral_execution_threshold = + getOrDeclareParameter(*node, ns + "lateral_execution_threshold"); p.lateral_small_shift_threshold = - get_parameter(node, ns + "lateral_small_shift_threshold"); + getOrDeclareParameter(*node, ns + "lateral_small_shift_threshold"); p.lateral_avoid_check_threshold = - get_parameter(node, ns + "lateral_avoid_check_threshold"); - p.max_right_shift_length = get_parameter(node, ns + "max_right_shift_length"); - p.max_left_shift_length = get_parameter(node, ns + "max_left_shift_length"); + getOrDeclareParameter(*node, ns + "lateral_avoid_check_threshold"); + p.max_right_shift_length = getOrDeclareParameter(*node, ns + "max_right_shift_length"); + p.max_left_shift_length = getOrDeclareParameter(*node, ns + "max_left_shift_length"); } // avoidance maneuver (longitudinal) { std::string ns = "avoidance.avoidance.longitudinal."; - p.prepare_time = get_parameter(node, ns + "prepare_time"); - p.min_prepare_distance = get_parameter(node, ns + "min_prepare_distance"); - p.remain_buffer_distance = get_parameter(node, ns + "remain_buffer_distance"); - p.min_slow_down_speed = get_parameter(node, ns + "min_slow_down_speed"); - p.buf_slow_down_speed = get_parameter(node, ns + "buf_slow_down_speed"); - p.nominal_avoidance_speed = get_parameter(node, ns + "nominal_avoidance_speed"); + p.prepare_time = getOrDeclareParameter(*node, ns + "prepare_time"); + p.min_prepare_distance = getOrDeclareParameter(*node, ns + "min_prepare_distance"); + p.remain_buffer_distance = getOrDeclareParameter(*node, ns + "remain_buffer_distance"); + p.min_slow_down_speed = getOrDeclareParameter(*node, ns + "min_slow_down_speed"); + p.buf_slow_down_speed = getOrDeclareParameter(*node, ns + "buf_slow_down_speed"); + p.nominal_avoidance_speed = + getOrDeclareParameter(*node, ns + "nominal_avoidance_speed"); } // yield { std::string ns = "avoidance.yield."; - p.yield_velocity = get_parameter(node, ns + "yield_velocity"); + p.yield_velocity = getOrDeclareParameter(*node, ns + "yield_velocity"); } // stop { std::string ns = "avoidance.stop."; - p.stop_max_distance = get_parameter(node, ns + "max_distance"); - p.stop_buffer = get_parameter(node, ns + "stop_buffer"); + p.stop_max_distance = getOrDeclareParameter(*node, ns + "max_distance"); + p.stop_buffer = getOrDeclareParameter(*node, ns + "stop_buffer"); } // policy { std::string ns = "avoidance.policy."; - p.policy_deceleration = get_parameter(node, ns + "deceleration"); - p.policy_lateral_margin = get_parameter(node, ns + "lateral_margin"); + p.policy_deceleration = getOrDeclareParameter(*node, ns + "deceleration"); + p.policy_lateral_margin = getOrDeclareParameter(*node, ns + "lateral_margin"); p.use_shorten_margin_immediately = - get_parameter(node, ns + "use_shorten_margin_immediately"); + getOrDeclareParameter(*node, ns + "use_shorten_margin_immediately"); if (p.policy_deceleration != "best_effort" && p.policy_deceleration != "reliable") { throw std::domain_error("invalid policy. please select 'best_effort' or 'reliable'."); @@ -227,20 +230,23 @@ AvoidanceModuleManager::AvoidanceModuleManager( // constraints (longitudinal) { std::string ns = "avoidance.constraints.longitudinal."; - p.nominal_deceleration = get_parameter(node, ns + "nominal_deceleration"); - p.nominal_jerk = get_parameter(node, ns + "nominal_jerk"); - p.max_deceleration = get_parameter(node, ns + "max_deceleration"); - p.max_jerk = get_parameter(node, ns + "max_jerk"); - p.max_acceleration = get_parameter(node, ns + "max_acceleration"); + p.nominal_deceleration = getOrDeclareParameter(*node, ns + "nominal_deceleration"); + p.nominal_jerk = getOrDeclareParameter(*node, ns + "nominal_jerk"); + p.max_deceleration = getOrDeclareParameter(*node, ns + "max_deceleration"); + p.max_jerk = getOrDeclareParameter(*node, ns + "max_jerk"); + p.max_acceleration = getOrDeclareParameter(*node, ns + "max_acceleration"); } // constraints (lateral) { std::string ns = "avoidance.constraints.lateral."; - p.velocity_map = get_parameter>(node, ns + "velocity"); - p.lateral_max_accel_map = get_parameter>(node, ns + "max_accel_values"); - p.lateral_min_jerk_map = get_parameter>(node, ns + "min_jerk_values"); - p.lateral_max_jerk_map = get_parameter>(node, ns + "max_jerk_values"); + p.velocity_map = getOrDeclareParameter>(*node, ns + "velocity"); + p.lateral_max_accel_map = + getOrDeclareParameter>(*node, ns + "max_accel_values"); + p.lateral_min_jerk_map = + getOrDeclareParameter>(*node, ns + "min_jerk_values"); + p.lateral_max_jerk_map = + getOrDeclareParameter>(*node, ns + "max_jerk_values"); if (p.velocity_map.empty()) { throw std::domain_error("invalid velocity map."); @@ -263,15 +269,15 @@ AvoidanceModuleManager::AvoidanceModuleManager( { std::string ns = "avoidance.shift_line_pipeline."; p.quantize_filter_threshold = - get_parameter(node, ns + "trim.quantize_filter_threshold"); + getOrDeclareParameter(*node, ns + "trim.quantize_filter_threshold"); p.same_grad_filter_1_threshold = - get_parameter(node, ns + "trim.same_grad_filter_1_threshold"); + getOrDeclareParameter(*node, ns + "trim.same_grad_filter_1_threshold"); p.same_grad_filter_2_threshold = - get_parameter(node, ns + "trim.same_grad_filter_2_threshold"); + getOrDeclareParameter(*node, ns + "trim.same_grad_filter_2_threshold"); p.same_grad_filter_3_threshold = - get_parameter(node, ns + "trim.same_grad_filter_3_threshold"); + getOrDeclareParameter(*node, ns + "trim.same_grad_filter_3_threshold"); p.sharp_shift_filter_threshold = - get_parameter(node, ns + "trim.sharp_shift_filter_threshold"); + getOrDeclareParameter(*node, ns + "trim.sharp_shift_filter_threshold"); } parameters_ = std::make_shared(p); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index 31aab9da87e2a..27f44433c6d4e 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -27,122 +27,114 @@ namespace behavior_path_planner using route_handler::Direction; using utils::convertToSnakeCase; -namespace -{ -template -T get_parameter(rclcpp::Node * node, const std::string & ns) -{ - if (node->has_parameter(ns)) { - return node->get_parameter(ns).get_value(); - } - - return node->declare_parameter(ns); -} -} // namespace - LaneChangeModuleManager::LaneChangeModuleManager( rclcpp::Node * node, const std::string & name, const ModuleConfigParameters & config, const Direction direction, const LaneChangeModuleType type) : SceneModuleManagerInterface(node, name, config, {""}), direction_{direction}, type_{type} { + using tier4_autoware_utils::getOrDeclareParameter; + LaneChangeParameters p{}; const auto parameter = [](std::string && name) { return "lane_change." + name; }; // trajectory generation - p.backward_lane_length = get_parameter(node, parameter("backward_lane_length")); + p.backward_lane_length = getOrDeclareParameter(*node, parameter("backward_lane_length")); p.prediction_time_resolution = - get_parameter(node, parameter("prediction_time_resolution")); + getOrDeclareParameter(*node, parameter("prediction_time_resolution")); p.longitudinal_acc_sampling_num = - get_parameter(node, parameter("longitudinal_acceleration_sampling_num")); + getOrDeclareParameter(*node, parameter("longitudinal_acceleration_sampling_num")); p.lateral_acc_sampling_num = - get_parameter(node, parameter("lateral_acceleration_sampling_num")); + getOrDeclareParameter(*node, parameter("lateral_acceleration_sampling_num")); // parked vehicle detection p.object_check_min_road_shoulder_width = - get_parameter(node, parameter("object_check_min_road_shoulder_width")); + getOrDeclareParameter(*node, parameter("object_check_min_road_shoulder_width")); p.object_shiftable_ratio_threshold = - get_parameter(node, parameter("object_shiftable_ratio_threshold")); + getOrDeclareParameter(*node, parameter("object_shiftable_ratio_threshold")); // turn signal p.min_length_for_turn_signal_activation = - get_parameter(node, parameter("min_length_for_turn_signal_activation")); + getOrDeclareParameter(*node, parameter("min_length_for_turn_signal_activation")); p.length_ratio_for_turn_signal_deactivation = - get_parameter(node, parameter("length_ratio_for_turn_signal_deactivation")); + getOrDeclareParameter(*node, parameter("length_ratio_for_turn_signal_deactivation")); // acceleration - p.min_longitudinal_acc = get_parameter(node, parameter("min_longitudinal_acc")); - p.max_longitudinal_acc = get_parameter(node, parameter("max_longitudinal_acc")); + p.min_longitudinal_acc = getOrDeclareParameter(*node, parameter("min_longitudinal_acc")); + p.max_longitudinal_acc = getOrDeclareParameter(*node, parameter("max_longitudinal_acc")); // collision check p.enable_prepare_segment_collision_check = - get_parameter(node, parameter("enable_prepare_segment_collision_check")); - p.prepare_segment_ignore_object_velocity_thresh = - get_parameter(node, parameter("prepare_segment_ignore_object_velocity_thresh")); + getOrDeclareParameter(*node, parameter("enable_prepare_segment_collision_check")); + p.prepare_segment_ignore_object_velocity_thresh = getOrDeclareParameter( + *node, parameter("prepare_segment_ignore_object_velocity_thresh")); p.check_objects_on_current_lanes = - get_parameter(node, parameter("check_objects_on_current_lanes")); + getOrDeclareParameter(*node, parameter("check_objects_on_current_lanes")); p.check_objects_on_other_lanes = - get_parameter(node, parameter("check_objects_on_other_lanes")); - p.use_all_predicted_path = get_parameter(node, parameter("use_all_predicted_path")); - - p.rss_params.longitudinal_distance_min_threshold = - get_parameter(node, parameter("safety_check.longitudinal_distance_min_threshold")); - p.rss_params.longitudinal_velocity_delta_time = - get_parameter(node, parameter("safety_check.longitudinal_velocity_delta_time")); + getOrDeclareParameter(*node, parameter("check_objects_on_other_lanes")); + p.use_all_predicted_path = + getOrDeclareParameter(*node, parameter("use_all_predicted_path")); + + p.rss_params.longitudinal_distance_min_threshold = getOrDeclareParameter( + *node, parameter("safety_check.longitudinal_distance_min_threshold")); + p.rss_params.longitudinal_velocity_delta_time = getOrDeclareParameter( + *node, parameter("safety_check.longitudinal_velocity_delta_time")); p.rss_params.front_vehicle_deceleration = - get_parameter(node, parameter("safety_check.expected_front_deceleration")); + getOrDeclareParameter(*node, parameter("safety_check.expected_front_deceleration")); p.rss_params.rear_vehicle_deceleration = - get_parameter(node, parameter("safety_check.expected_rear_deceleration")); + getOrDeclareParameter(*node, parameter("safety_check.expected_rear_deceleration")); p.rss_params.rear_vehicle_reaction_time = - get_parameter(node, parameter("safety_check.rear_vehicle_reaction_time")); + getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_reaction_time")); p.rss_params.rear_vehicle_safety_time_margin = - get_parameter(node, parameter("safety_check.rear_vehicle_safety_time_margin")); + getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_safety_time_margin")); p.rss_params.lateral_distance_max_threshold = - get_parameter(node, parameter("safety_check.lateral_distance_max_threshold")); - - p.rss_params_for_abort.longitudinal_distance_min_threshold = - get_parameter(node, parameter("safety_check.longitudinal_distance_min_threshold")); - p.rss_params_for_abort.longitudinal_velocity_delta_time = - get_parameter(node, parameter("safety_check.longitudinal_velocity_delta_time")); - p.rss_params_for_abort.front_vehicle_deceleration = - get_parameter(node, parameter("safety_check.expected_front_deceleration_for_abort")); - p.rss_params_for_abort.rear_vehicle_deceleration = - get_parameter(node, parameter("safety_check.expected_rear_deceleration_for_abort")); + getOrDeclareParameter(*node, parameter("safety_check.lateral_distance_max_threshold")); + + p.rss_params_for_abort.longitudinal_distance_min_threshold = getOrDeclareParameter( + *node, parameter("safety_check.longitudinal_distance_min_threshold")); + p.rss_params_for_abort.longitudinal_velocity_delta_time = getOrDeclareParameter( + *node, parameter("safety_check.longitudinal_velocity_delta_time")); + p.rss_params_for_abort.front_vehicle_deceleration = getOrDeclareParameter( + *node, parameter("safety_check.expected_front_deceleration_for_abort")); + p.rss_params_for_abort.rear_vehicle_deceleration = getOrDeclareParameter( + *node, parameter("safety_check.expected_rear_deceleration_for_abort")); p.rss_params_for_abort.rear_vehicle_reaction_time = - get_parameter(node, parameter("safety_check.rear_vehicle_reaction_time")); + getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_reaction_time")); p.rss_params_for_abort.rear_vehicle_safety_time_margin = - get_parameter(node, parameter("safety_check.rear_vehicle_safety_time_margin")); + getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_safety_time_margin")); p.rss_params_for_abort.lateral_distance_max_threshold = - get_parameter(node, parameter("safety_check.lateral_distance_max_threshold")); + getOrDeclareParameter(*node, parameter("safety_check.lateral_distance_max_threshold")); // target object { std::string ns = "lane_change.target_object."; - p.check_car = get_parameter(node, ns + "car"); - p.check_truck = get_parameter(node, ns + "truck"); - p.check_bus = get_parameter(node, ns + "bus"); - p.check_trailer = get_parameter(node, ns + "trailer"); - p.check_unknown = get_parameter(node, ns + "unknown"); - p.check_bicycle = get_parameter(node, ns + "bicycle"); - p.check_motorcycle = get_parameter(node, ns + "motorcycle"); - p.check_pedestrian = get_parameter(node, ns + "pedestrian"); + p.check_car = getOrDeclareParameter(*node, ns + "car"); + p.check_truck = getOrDeclareParameter(*node, ns + "truck"); + p.check_bus = getOrDeclareParameter(*node, ns + "bus"); + p.check_trailer = getOrDeclareParameter(*node, ns + "trailer"); + p.check_unknown = getOrDeclareParameter(*node, ns + "unknown"); + p.check_bicycle = getOrDeclareParameter(*node, ns + "bicycle"); + p.check_motorcycle = getOrDeclareParameter(*node, ns + "motorcycle"); + p.check_pedestrian = getOrDeclareParameter(*node, ns + "pedestrian"); } // lane change cancel p.cancel.enable_on_prepare_phase = - get_parameter(node, parameter("cancel.enable_on_prepare_phase")); + getOrDeclareParameter(*node, parameter("cancel.enable_on_prepare_phase")); p.cancel.enable_on_lane_changing_phase = - get_parameter(node, parameter("cancel.enable_on_lane_changing_phase")); - p.cancel.delta_time = get_parameter(node, parameter("cancel.delta_time")); - p.cancel.duration = get_parameter(node, parameter("cancel.duration")); - p.cancel.max_lateral_jerk = get_parameter(node, parameter("cancel.max_lateral_jerk")); - p.cancel.overhang_tolerance = get_parameter(node, parameter("cancel.overhang_tolerance")); + getOrDeclareParameter(*node, parameter("cancel.enable_on_lane_changing_phase")); + p.cancel.delta_time = getOrDeclareParameter(*node, parameter("cancel.delta_time")); + p.cancel.duration = getOrDeclareParameter(*node, parameter("cancel.duration")); + p.cancel.max_lateral_jerk = + getOrDeclareParameter(*node, parameter("cancel.max_lateral_jerk")); + p.cancel.overhang_tolerance = + getOrDeclareParameter(*node, parameter("cancel.overhang_tolerance")); p.finish_judge_lateral_threshold = - get_parameter(node, parameter("finish_judge_lateral_threshold")); + getOrDeclareParameter(*node, parameter("finish_judge_lateral_threshold")); // debug marker - p.publish_debug_marker = get_parameter(node, parameter("publish_debug_marker")); + p.publish_debug_marker = getOrDeclareParameter(*node, parameter("publish_debug_marker")); // validation of parameters if (p.longitudinal_acc_sampling_num < 1 || p.lateral_acc_sampling_num < 1) { @@ -196,6 +188,7 @@ AvoidanceByLaneChangeModuleManager::AvoidanceByLaneChangeModuleManager( node, name, config, Direction::NONE, LaneChangeModuleType::AVOIDANCE_BY_LANE_CHANGE) { using autoware_auto_perception_msgs::msg::ObjectClassification; + using tier4_autoware_utils::getOrDeclareParameter; rtc_interface_ptr_map_.clear(); const std::vector rtc_types = {"left", "right"}; @@ -211,34 +204,39 @@ AvoidanceByLaneChangeModuleManager::AvoidanceByLaneChangeModuleManager( { std::string ns = "avoidance_by_lane_change."; p.execute_object_longitudinal_margin = - get_parameter(node, ns + "execute_object_longitudinal_margin"); + getOrDeclareParameter(*node, ns + "execute_object_longitudinal_margin"); p.execute_only_when_lane_change_finish_before_object = - get_parameter(node, ns + "execute_only_when_lane_change_finish_before_object"); + getOrDeclareParameter(*node, ns + "execute_only_when_lane_change_finish_before_object"); } // general params { std::string ns = "avoidance."; p.resample_interval_for_planning = - get_parameter(node, ns + "resample_interval_for_planning"); + getOrDeclareParameter(*node, ns + "resample_interval_for_planning"); p.resample_interval_for_output = - get_parameter(node, ns + "resample_interval_for_output"); + getOrDeclareParameter(*node, ns + "resample_interval_for_output"); p.enable_force_avoidance_for_stopped_vehicle = - get_parameter(node, ns + "enable_force_avoidance_for_stopped_vehicle"); + getOrDeclareParameter(*node, ns + "enable_force_avoidance_for_stopped_vehicle"); } // target object { const auto get_object_param = [&](std::string && ns) { ObjectParameter param{}; - param.is_target = get_parameter(node, ns + "is_target"); - param.execute_num = get_parameter(node, ns + "execute_num"); - param.moving_speed_threshold = get_parameter(node, ns + "moving_speed_threshold"); - param.moving_time_threshold = get_parameter(node, ns + "moving_time_threshold"); - param.max_expand_ratio = get_parameter(node, ns + "max_expand_ratio"); - param.envelope_buffer_margin = get_parameter(node, ns + "envelope_buffer_margin"); - param.avoid_margin_lateral = get_parameter(node, ns + "avoid_margin_lateral"); - param.safety_buffer_lateral = get_parameter(node, ns + "safety_buffer_lateral"); + param.is_target = getOrDeclareParameter(*node, ns + "is_target"); + param.execute_num = getOrDeclareParameter(*node, ns + "execute_num"); + param.moving_speed_threshold = + getOrDeclareParameter(*node, ns + "moving_speed_threshold"); + param.moving_time_threshold = + getOrDeclareParameter(*node, ns + "moving_time_threshold"); + param.max_expand_ratio = getOrDeclareParameter(*node, ns + "max_expand_ratio"); + param.envelope_buffer_margin = + getOrDeclareParameter(*node, ns + "envelope_buffer_margin"); + param.avoid_margin_lateral = + getOrDeclareParameter(*node, ns + "avoid_margin_lateral"); + param.safety_buffer_lateral = + getOrDeclareParameter(*node, ns + "safety_buffer_lateral"); return param; }; @@ -255,41 +253,43 @@ AvoidanceByLaneChangeModuleManager::AvoidanceByLaneChangeModuleManager( p.object_parameters.emplace(ObjectClassification::UNKNOWN, get_object_param(ns + "unknown.")); p.lower_distance_for_polygon_expansion = - get_parameter(node, ns + "lower_distance_for_polygon_expansion"); + getOrDeclareParameter(*node, ns + "lower_distance_for_polygon_expansion"); p.upper_distance_for_polygon_expansion = - get_parameter(node, ns + "upper_distance_for_polygon_expansion"); + getOrDeclareParameter(*node, ns + "upper_distance_for_polygon_expansion"); } // target filtering { std::string ns = "avoidance.target_filtering."; - p.threshold_time_force_avoidance_for_stopped_vehicle = - get_parameter(node, ns + "threshold_time_force_avoidance_for_stopped_vehicle"); - p.object_ignore_section_traffic_light_in_front_distance = - get_parameter(node, ns + "object_ignore_section_traffic_light_in_front_distance"); - p.object_ignore_section_crosswalk_in_front_distance = - get_parameter(node, ns + "object_ignore_section_crosswalk_in_front_distance"); + p.threshold_time_force_avoidance_for_stopped_vehicle = getOrDeclareParameter( + *node, ns + "threshold_time_force_avoidance_for_stopped_vehicle"); + p.object_ignore_section_traffic_light_in_front_distance = getOrDeclareParameter( + *node, ns + "object_ignore_section_traffic_light_in_front_distance"); + p.object_ignore_section_crosswalk_in_front_distance = getOrDeclareParameter( + *node, ns + "object_ignore_section_crosswalk_in_front_distance"); p.object_ignore_section_crosswalk_behind_distance = - get_parameter(node, ns + "object_ignore_section_crosswalk_behind_distance"); + getOrDeclareParameter(*node, ns + "object_ignore_section_crosswalk_behind_distance"); p.object_check_forward_distance = - get_parameter(node, ns + "object_check_forward_distance"); + getOrDeclareParameter(*node, ns + "object_check_forward_distance"); p.object_check_backward_distance = - get_parameter(node, ns + "object_check_backward_distance"); - p.object_check_goal_distance = get_parameter(node, ns + "object_check_goal_distance"); + getOrDeclareParameter(*node, ns + "object_check_backward_distance"); + p.object_check_goal_distance = + getOrDeclareParameter(*node, ns + "object_check_goal_distance"); p.threshold_distance_object_is_on_center = - get_parameter(node, ns + "threshold_distance_object_is_on_center"); + getOrDeclareParameter(*node, ns + "threshold_distance_object_is_on_center"); p.object_check_shiftable_ratio = - get_parameter(node, ns + "object_check_shiftable_ratio"); + getOrDeclareParameter(*node, ns + "object_check_shiftable_ratio"); p.object_check_min_road_shoulder_width = - get_parameter(node, ns + "object_check_min_road_shoulder_width"); - p.object_last_seen_threshold = get_parameter(node, ns + "object_last_seen_threshold"); + getOrDeclareParameter(*node, ns + "object_check_min_road_shoulder_width"); + p.object_last_seen_threshold = + getOrDeclareParameter(*node, ns + "object_last_seen_threshold"); } // safety check { std::string ns = "avoidance.safety_check."; p.hysteresis_factor_expand_rate = - get_parameter(node, ns + "hysteresis_factor_expand_rate"); + getOrDeclareParameter(*node, ns + "hysteresis_factor_expand_rate"); } avoidance_parameters_ = std::make_shared(p); From dbeeeb1fba43e70e7cd7e2cada92de53cba9953e Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 29 Aug 2023 08:14:29 +0900 Subject: [PATCH 032/547] feat(avoidance): flexible avoidance safety check param (#4754) Signed-off-by: satoshi-ota --- .../config/avoidance/avoidance.param.yaml | 3 ++- .../utils/avoidance/avoidance_module_data.hpp | 3 ++- .../utils/avoidance/utils.hpp | 2 +- .../scene_module/avoidance/avoidance_module.cpp | 16 ++++++++++++++-- .../src/scene_module/avoidance/manager.cpp | 5 ++++- .../src/utils/avoidance/utils.cpp | 8 +++++--- 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml index 26f53cc91c4cf..589233cf5a441 100644 --- a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml +++ b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml @@ -140,7 +140,8 @@ # collision check parameters check_all_predicted_path: false # [-] time_resolution: 0.5 # [s] - time_horizon: 10.0 # [s] + time_horizon_for_front_object: 10.0 # [s] + time_horizon_for_rear_object: 10.0 # [s] safety_check_backward_distance: 100.0 # [m] hysteresis_factor_expand_rate: 2.0 # [-] hysteresis_factor_safe_count: 10 # [-] diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 5484ef834a177..c0ed2c975a54d 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -187,7 +187,8 @@ struct AvoidanceParameters // parameters for collision check. bool check_all_predicted_path{false}; - double safety_check_time_horizon{0.0}; + double time_horizon_for_front_object{0.0}; + double time_horizon_for_rear_object{0.0}; double safety_check_time_resolution{0.0}; // find adjacent lane vehicles diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index 6b7830147ebf9..e2934a02b63d8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -89,7 +89,7 @@ std::vector generateObstaclePolygonsForDrivableArea( std::vector convertToPredictedPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, - const std::shared_ptr & parameters); + const bool is_object_front, const std::shared_ptr & parameters); double getLongitudinalVelocity(const Pose & p_ref, const Pose & p_target, const double v); diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index e0c79a46d731e..91fe2d0fa6da7 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1845,8 +1845,10 @@ bool AvoidanceModule::isSafePath( return true; // if safety check is disabled, it always return safe. } - const auto ego_predicted_path = - utils::avoidance::convertToPredictedPath(shifted_path.path, planner_data_, parameters_); + const auto ego_predicted_path_for_front_object = + utils::avoidance::convertToPredictedPath(shifted_path.path, planner_data_, true, parameters_); + const auto ego_predicted_path_for_rear_object = + utils::avoidance::convertToPredictedPath(shifted_path.path, planner_data_, false, parameters_); const auto ego_idx = planner_data_->findEgoIndex(shifted_path.path.points); const auto is_right_shift = [&]() -> std::optional { @@ -1875,8 +1877,18 @@ bool AvoidanceModule::isSafePath( avoid_data_, planner_data_, parameters_, is_right_shift.value()); for (const auto & object : safety_check_target_objects) { + const auto obj_polygon = + tier4_autoware_utils::toPolygon2d(object.initial_pose.pose, object.shape); + + const auto is_object_front = utils::path_safety_checker::isTargetObjectFront( + shifted_path.path, getEgoPose(), p.vehicle_info, obj_polygon); + const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( object, parameters_->check_all_predicted_path); + + const auto & ego_predicted_path = + is_object_front ? ego_predicted_path_for_front_object : ego_predicted_path_for_rear_object; + for (const auto & obj_path : obj_predicted_paths) { CollisionCheckDebug collision{}; if (!utils::path_safety_checker::checkCollision( diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp index 1257ae2056850..8900e2b3e827c 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp @@ -139,7 +139,10 @@ AvoidanceModuleManager::AvoidanceModuleManager( p.check_other_object = getOrDeclareParameter(*node, ns + "check_other_object"); p.check_all_predicted_path = getOrDeclareParameter(*node, ns + "check_all_predicted_path"); - p.safety_check_time_horizon = getOrDeclareParameter(*node, ns + "time_horizon"); + p.time_horizon_for_front_object = + getOrDeclareParameter(*node, ns + "time_horizon_for_front_object"); + p.time_horizon_for_rear_object = + getOrDeclareParameter(*node, ns + "time_horizon_for_rear_object"); p.safety_check_time_resolution = getOrDeclareParameter(*node, ns + "time_resolution"); p.safety_check_backward_distance = getOrDeclareParameter(*node, ns + "safety_check_backward_distance"); diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index c1a883d7a9b82..856c3220165e7 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -1436,7 +1436,7 @@ AvoidLineArray combineRawShiftLinesWithUniqueCheck( std::vector convertToPredictedPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, - const std::shared_ptr & parameters) + const bool is_object_front, const std::shared_ptr & parameters) { if (path.points.empty()) { return {}; @@ -1445,7 +1445,8 @@ std::vector convertToPredictedPath( const auto & acceleration = parameters->max_acceleration; const auto & vehicle_pose = planner_data->self_odometry->pose.pose; const auto & initial_velocity = std::abs(planner_data->self_odometry->twist.twist.linear.x); - const auto & time_horizon = parameters->safety_check_time_horizon; + const auto & time_horizon = is_object_front ? parameters->time_horizon_for_front_object + : parameters->time_horizon_for_rear_object; const auto & time_resolution = parameters->safety_check_time_resolution; const size_t ego_seg_idx = planner_data->findEgoSegmentIndex(path.points); @@ -1477,7 +1478,8 @@ ExtendedPredictedObject transform( const auto & obj_velocity_norm = std::hypot( extended_object.initial_twist.twist.linear.x, extended_object.initial_twist.twist.linear.y); - const auto & time_horizon = parameters->safety_check_time_horizon; + const auto & time_horizon = + std::max(parameters->time_horizon_for_front_object, parameters->time_horizon_for_rear_object); const auto & time_resolution = parameters->safety_check_time_resolution; extended_object.predicted_paths.resize(object.kinematics.predicted_paths.size()); From 84107d0813ec5f7872a91dddac42c6d61e9752e9 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 29 Aug 2023 08:53:08 +0900 Subject: [PATCH 033/547] style(map_projection_loader): unify the name of yaml file (#4778) fix(map_projection_loader): unify the name of yaml file Signed-off-by: kminoda --- ...n_info_local.yaml => map_projector_info_local.yaml} | 0 ...aml => map_projector_info_local_cartesian_utm.yaml} | 0 ...ion_info_mgrs.yaml => map_projector_info_mgrs.yaml} | 0 ...est_node_load_local_cartesian_utm_from_yaml.test.py | 10 +++++----- .../test/test_node_load_local_from_yaml.test.py | 10 +++++----- .../test/test_node_load_mgrs_from_yaml.test.py | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) rename map/map_projection_loader/test/data/{projection_info_local.yaml => map_projector_info_local.yaml} (100%) rename map/map_projection_loader/test/data/{projection_info_local_cartesian_utm.yaml => map_projector_info_local_cartesian_utm.yaml} (100%) rename map/map_projection_loader/test/data/{projection_info_mgrs.yaml => map_projector_info_mgrs.yaml} (100%) diff --git a/map/map_projection_loader/test/data/projection_info_local.yaml b/map/map_projection_loader/test/data/map_projector_info_local.yaml similarity index 100% rename from map/map_projection_loader/test/data/projection_info_local.yaml rename to map/map_projection_loader/test/data/map_projector_info_local.yaml diff --git a/map/map_projection_loader/test/data/projection_info_local_cartesian_utm.yaml b/map/map_projection_loader/test/data/map_projector_info_local_cartesian_utm.yaml similarity index 100% rename from map/map_projection_loader/test/data/projection_info_local_cartesian_utm.yaml rename to map/map_projection_loader/test/data/map_projector_info_local_cartesian_utm.yaml diff --git a/map/map_projection_loader/test/data/projection_info_mgrs.yaml b/map/map_projection_loader/test/data/map_projector_info_mgrs.yaml similarity index 100% rename from map/map_projection_loader/test/data/projection_info_mgrs.yaml rename to map/map_projection_loader/test/data/map_projector_info_mgrs.yaml diff --git a/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py b/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py index 9c3b072888ba3..ec13df010c8c3 100644 --- a/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py +++ b/map/map_projection_loader/test/test_node_load_local_cartesian_utm_from_yaml.test.py @@ -33,12 +33,12 @@ logger = get_logger(__name__) -YAML_FILE_PATH = "test/data/projection_info_local_cartesian_utm.yaml" +YAML_FILE_PATH = "test/data/map_projector_info_local_cartesian_utm.yaml" @pytest.mark.launch_test def generate_test_description(): - map_projection_info_path = os.path.join( + map_projector_info_path = os.path.join( get_package_share_directory("map_projection_loader"), YAML_FILE_PATH ) @@ -48,7 +48,7 @@ def generate_test_description(): output="screen", parameters=[ { - "map_projector_info_path": map_projection_info_path, + "map_projector_info_path": map_projector_info_path, "lanelet2_map_path": "", "use_local_projector": False, }, @@ -121,10 +121,10 @@ def test_node_link(self): ) # Load the yaml file directly - map_projection_info_path = os.path.join( + map_projector_info_path = os.path.join( get_package_share_directory("map_projection_loader"), YAML_FILE_PATH ) - with open(map_projection_info_path) as f: + with open(map_projector_info_path) as f: yaml_data = yaml.load(f, Loader=yaml.FullLoader) # Test if message received diff --git a/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py b/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py index e215c4a0354cb..ccec68486b79c 100644 --- a/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py +++ b/map/map_projection_loader/test/test_node_load_local_from_yaml.test.py @@ -33,12 +33,12 @@ logger = get_logger(__name__) -YAML_FILE_PATH = "test/data/projection_info_local.yaml" +YAML_FILE_PATH = "test/data/map_projector_info_local.yaml" @pytest.mark.launch_test def generate_test_description(): - map_projection_info_path = os.path.join( + map_projector_info_path = os.path.join( get_package_share_directory("map_projection_loader"), YAML_FILE_PATH ) @@ -48,7 +48,7 @@ def generate_test_description(): output="screen", parameters=[ { - "map_projector_info_path": map_projection_info_path, + "map_projector_info_path": map_projector_info_path, "lanelet2_map_path": "", "use_local_projector": False, }, @@ -121,10 +121,10 @@ def test_node_link(self): ) # Load the yaml file directly - map_projection_info_path = os.path.join( + map_projector_info_path = os.path.join( get_package_share_directory("map_projection_loader"), YAML_FILE_PATH ) - with open(map_projection_info_path) as f: + with open(map_projector_info_path) as f: yaml_data = yaml.load(f, Loader=yaml.FullLoader) # Test if message received diff --git a/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py b/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py index 5e71d43ed9280..200504d0ea18f 100644 --- a/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py +++ b/map/map_projection_loader/test/test_node_load_mgrs_from_yaml.test.py @@ -33,12 +33,12 @@ logger = get_logger(__name__) -YAML_FILE_PATH = "test/data/projection_info_mgrs.yaml" +YAML_FILE_PATH = "test/data/map_projector_info_mgrs.yaml" @pytest.mark.launch_test def generate_test_description(): - map_projection_info_path = os.path.join( + map_projector_info_path = os.path.join( get_package_share_directory("map_projection_loader"), YAML_FILE_PATH ) @@ -48,7 +48,7 @@ def generate_test_description(): output="screen", parameters=[ { - "map_projector_info_path": map_projection_info_path, + "map_projector_info_path": map_projector_info_path, "lanelet2_map_path": "", "use_local_projector": False, }, @@ -121,10 +121,10 @@ def test_node_link(self): ) # Load the yaml file directly - map_projection_info_path = os.path.join( + map_projector_info_path = os.path.join( get_package_share_directory("map_projection_loader"), YAML_FILE_PATH ) - with open(map_projection_info_path) as f: + with open(map_projector_info_path) as f: yaml_data = yaml.load(f, Loader=yaml.FullLoader) # Test if message received From 0cbfc3da3e10f7d7bbe46d998762f94b61acd707 Mon Sep 17 00:00:00 2001 From: Yuxuan Liu <619684051@qq.com> Date: Tue, 29 Aug 2023 09:01:34 +0800 Subject: [PATCH 034/547] refactor(perception_rviz_plugin): only delete markers with unused MarkersIDs during updates. (#4783) * refactor(perception_rviz_plugin): only delete markers with unused MarkersIDs during updates. Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * fix: include for linting test Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix --------- Signed-off-by: Owen-Liuyuxuan Co-authored-by: Owen-Liuyuxuan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../object_polygon_display_base.hpp | 5 +++++ .../predicted_objects_display.hpp | 2 ++ .../predicted_objects_display.cpp | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/common/autoware_auto_perception_rviz_plugin/include/object_detection/object_polygon_display_base.hpp b/common/autoware_auto_perception_rviz_plugin/include/object_detection/object_polygon_display_base.hpp index b20a835f80e39..10bf11847c2f5 100644 --- a/common/autoware_auto_perception_rviz_plugin/include/object_detection/object_polygon_display_base.hpp +++ b/common/autoware_auto_perception_rviz_plugin/include/object_detection/object_polygon_display_base.hpp @@ -146,6 +146,11 @@ class AUTOWARE_AUTO_PERCEPTION_RVIZ_PLUGIN_PUBLIC ObjectPolygonDisplayBase m_marker_common.addMessage(markers_ptr); } + void deleteMarker(rviz_default_plugins::displays::MarkerID marker_id) + { + m_marker_common.deleteMarker(marker_id); + } + protected: /// \brief Convert given shape msg into a Marker /// \tparam ClassificationContainerT List type with ObjectClassificationMsg diff --git a/common/autoware_auto_perception_rviz_plugin/include/object_detection/predicted_objects_display.hpp b/common/autoware_auto_perception_rviz_plugin/include/object_detection/predicted_objects_display.hpp index ee2c7e0d5c3b9..5493f1dd594ce 100644 --- a/common/autoware_auto_perception_rviz_plugin/include/object_detection/predicted_objects_display.hpp +++ b/common/autoware_auto_perception_rviz_plugin/include/object_detection/predicted_objects_display.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -112,6 +113,7 @@ class AUTOWARE_AUTO_PERCEPTION_RVIZ_PLUGIN_PUBLIC PredictedObjectsDisplay std::mutex mutex; std::condition_variable condition; std::vector markers; + std::set existing_marker_ids; }; } // namespace object_detection diff --git a/common/autoware_auto_perception_rviz_plugin/src/object_detection/predicted_objects_display.cpp b/common/autoware_auto_perception_rviz_plugin/src/object_detection/predicted_objects_display.cpp index 870b8f2c77543..24e67a6f44e95 100644 --- a/common/autoware_auto_perception_rviz_plugin/src/object_detection/predicted_objects_display.cpp +++ b/common/autoware_auto_perception_rviz_plugin/src/object_detection/predicted_objects_display.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace autoware { @@ -130,7 +131,7 @@ std::vector PredictedObjectsDisplay: auto acceleration_text_marker_ptr = acceleration_text_marker.value(); acceleration_text_marker_ptr->header = msg->header; acceleration_text_marker_ptr->id = uuid_to_marker_id(object.object_id); - add_marker(acceleration_text_marker_ptr); + markers.push_back(acceleration_text_marker_ptr); } // Get marker for twist @@ -195,11 +196,19 @@ void PredictedObjectsDisplay::update(float wall_dt, float ros_dt) std::unique_lock lock(mutex); if (!markers.empty()) { - clear_markers(); - + std::set new_marker_ids = std::set(); for (const auto & marker : markers) { + rviz_default_plugins::displays::MarkerID marker_id = + rviz_default_plugins::displays::MarkerID(marker->ns, marker->id); add_marker(marker); + new_marker_ids.insert(marker_id); + } + for (auto itr = existing_marker_ids.begin(); itr != existing_marker_ids.end(); itr++) { + if (new_marker_ids.find(*itr) == new_marker_ids.end()) { + deleteMarker(*itr); + } } + existing_marker_ids = new_marker_ids; markers.clear(); } From 06f4052349c3178249f85407d4f4467bc4570442 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 29 Aug 2023 10:05:42 +0900 Subject: [PATCH 035/547] perf(safety_check): use light weight object position check logic (#4762) perf(safety_check): use light weight object position check Signed-off-by: satoshi-ota --- .../path_safety_checker/safety_check.hpp | 3 +++ .../avoidance/avoidance_module.cpp | 4 ++-- .../path_safety_checker/safety_check.cpp | 24 ++++++++++++++++--- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 79947d826ed10..7b5b1f562f1b8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -52,6 +52,9 @@ using vehicle_info_util::VehicleInfo; namespace bg = boost::geometry; +bool isTargetObjectFront( + const geometry_msgs::msg::Pose & ego_pose, const Polygon2d & obj_polygon, + const vehicle_info_util::VehicleInfo & vehicle_info); bool isTargetObjectFront( const PathWithLaneId & path, const geometry_msgs::msg::Pose & ego_pose, const vehicle_info_util::VehicleInfo & vehicle_info, const Polygon2d & obj_polygon); diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 91fe2d0fa6da7..f68e54266fe85 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1880,8 +1880,8 @@ bool AvoidanceModule::isSafePath( const auto obj_polygon = tier4_autoware_utils::toPolygon2d(object.initial_pose.pose, object.shape); - const auto is_object_front = utils::path_safety_checker::isTargetObjectFront( - shifted_path.path, getEgoPose(), p.vehicle_info, obj_polygon); + const auto is_object_front = + utils::path_safety_checker::isTargetObjectFront(getEgoPose(), obj_polygon, p.vehicle_info); const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( object, parameters_->check_all_predicted_path); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 15a23c8f27dff..2939ca29c0a40 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -30,6 +30,25 @@ void appendPointToPolygon(Polygon2d & polygon, const geometry_msgs::msg::Point & bg::append(polygon.outer(), point); } +bool isTargetObjectFront( + const geometry_msgs::msg::Pose & ego_pose, const Polygon2d & obj_polygon, + const vehicle_info_util::VehicleInfo & vehicle_info) +{ + const double base_to_front = vehicle_info.max_longitudinal_offset_m; + const auto ego_offset_pose = + tier4_autoware_utils::calcOffsetPose(ego_pose, base_to_front, 0.0, 0.0); + + // check all edges in the polygon + for (const auto & obj_edge : obj_polygon.outer()) { + const auto obj_point = tier4_autoware_utils::createPoint(obj_edge.x(), obj_edge.y(), 0.0); + if (tier4_autoware_utils::calcLongitudinalDeviation(ego_offset_pose, obj_point) > 0.0) { + return true; + } + } + + return false; +} + bool isTargetObjectFront( const PathWithLaneId & path, const geometry_msgs::msg::Pose & ego_pose, const vehicle_info_util::VehicleInfo & vehicle_info, const Polygon2d & obj_polygon) @@ -227,7 +246,7 @@ boost::optional getInterpolatedPoseWithVeloci } bool checkCollision( - const PathWithLaneId & planned_path, + [[maybe_unused]] const PathWithLaneId & planned_path, const std::vector & predicted_ego_path, const ExtendedPredictedObject & target_object, const PredictedPathWithPolygon & target_object_path, @@ -272,8 +291,7 @@ bool checkCollision( } // compute which one is at the front of the other - const bool is_object_front = - isTargetObjectFront(planned_path, ego_pose, ego_vehicle_info, obj_polygon); + const bool is_object_front = isTargetObjectFront(ego_pose, obj_polygon, ego_vehicle_info); const auto & [front_object_velocity, rear_object_velocity] = is_object_front ? std::make_pair(object_velocity, ego_velocity) : std::make_pair(ego_velocity, object_velocity); From 7a4a905b29c251fe71d53242428b14132ea992de Mon Sep 17 00:00:00 2001 From: "Takagi, Isamu" <43976882+isamu-takagi@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:14:28 +0900 Subject: [PATCH 036/547] refactor(vehicle_cmd_gate): use stop control command for adapi pause (#4101) use stop control command for adapi pause Signed-off-by: Takagi, Isamu --- control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp index 9030521b6b334..b561b6427b978 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp @@ -419,8 +419,7 @@ void VehicleCmdGate::publishControlCommands(const Commands & commands) // Check pause. Place this check after all other checks as it needs the final output. adapi_pause_->update(filtered_commands.control); if (adapi_pause_->is_paused()) { - filtered_commands.control.longitudinal.speed = 0.0; - filtered_commands.control.longitudinal.acceleration = stop_hold_acceleration_; + filtered_commands.control = createStopControlCmd(); } // Check if command filtering option is enable From eadfa78c21db721f3ffa54620f8bfdffbf360ea9 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:30:34 +0900 Subject: [PATCH 037/547] perf(avoidance): improve processing time (#4792) * perf(avoidance): improve processing time Signed-off-by: satoshi-ota * perf(avoidance): remove heavy debug process Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.cpp | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index f68e54266fe85..60fb0530d879a 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -247,7 +247,9 @@ void AvoidanceModule::fillAvoidanceTargetObjects( // Separate dynamic objects based on whether they are inside or outside of the expanded lanelets. const auto [object_within_target_lane, object_outside_target_lane] = utils::avoidance::separateObjectsByPath( - helper_.getPreviousSplineShiftPath().path, planner_data_, data, parameters_, debug); + utils::resamplePathWithSpline( + helper_.getPreviousSplineShiftPath().path, parameters_->resample_interval_for_output), + planner_data_, data, parameters_, debug); for (const auto & object : object_outside_target_lane.objects) { ObjectData other_object; @@ -1303,33 +1305,6 @@ AvoidLineArray AvoidanceModule::mergeShiftLines( debug.total_backward_grad = shift_line_data.backward_grad; } - // debug print - { - const auto & arc = avoid_data_.arclength_from_ego; - const auto & closest = avoid_data_.ego_closest_path_index; - const auto & sl = shift_line_data.shift_line; - const auto & sg = shift_line_data.shift_line_grad; - const auto & fg = shift_line_data.forward_grad; - const auto & bg = shift_line_data.backward_grad; - using std::setw; - std::stringstream ss; - ss << std::fixed << std::setprecision(3); - ss << "\n[idx, arc, shift (for each shift points, filtered | total), grad (ideal, bwd, fwd)]: " - "closest = " - << closest << ", raw_shift_lines size = " << raw_shift_lines.size() << std::endl; - for (size_t i = 0; i < arc.size(); ++i) { - ss << "i = " << i << " | arc: " << arc.at(i) << " | shift: ("; - for (const auto & p : shift_line_data.shift_line_history) { - ss << setw(5) << p.at(i) << ", "; - } - ss << "| total: " << setw(5) << sl.at(i) << ") | grad: (" << sg.at(i) << ", " << fg.at(i) - << ", " << bg.at(i) << ")" << std::endl; - } - DEBUG_PRINT("%s", ss.str().c_str()); - } - - printShiftLines(merged_shift_lines, "merged_shift_lines"); - return merged_shift_lines; } From c75a8eba9e17d6652a5b15ec7db95fa493efb4e8 Mon Sep 17 00:00:00 2001 From: Tomoya Kimura Date: Tue, 29 Aug 2023 17:32:01 +0900 Subject: [PATCH 038/547] feat: add marker of cmd gate filter (#4741) * feat: add marker of cmd gate filter Signed-off-by: tomoya.kimura * feat: add sphere marker Signed-off-by: tomoya.kimura * misc Signed-off-by: tomoya.kimura --------- Signed-off-by: tomoya.kimura --- control/vehicle_cmd_gate/package.xml | 1 + .../vehicle_cmd_gate/src/marker_helper.hpp | 119 ++++++++++++++++++ .../vehicle_cmd_gate/src/vehicle_cmd_gate.cpp | 52 ++++++++ .../vehicle_cmd_gate/src/vehicle_cmd_gate.hpp | 6 + 4 files changed, 178 insertions(+) create mode 100644 control/vehicle_cmd_gate/src/marker_helper.hpp diff --git a/control/vehicle_cmd_gate/package.xml b/control/vehicle_cmd_gate/package.xml index 36bb662fb2f53..deb988d61738f 100644 --- a/control/vehicle_cmd_gate/package.xml +++ b/control/vehicle_cmd_gate/package.xml @@ -34,6 +34,7 @@ tier4_system_msgs tier4_vehicle_msgs vehicle_info_util + visualization_msgs rosidl_default_runtime diff --git a/control/vehicle_cmd_gate/src/marker_helper.hpp b/control/vehicle_cmd_gate/src/marker_helper.hpp new file mode 100644 index 0000000000000..44c43a3630151 --- /dev/null +++ b/control/vehicle_cmd_gate/src/marker_helper.hpp @@ -0,0 +1,119 @@ +// Copyright 2020 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MARKER_HELPER_HPP_ +#define MARKER_HELPER_HPP_ + +#include + +#include + +#include + +namespace vehicle_cmd_gate +{ +inline geometry_msgs::msg::Point createMarkerPosition(double x, double y, double z) +{ + geometry_msgs::msg::Point point; + + point.x = x; + point.y = y; + point.z = z; + + return point; +} + +inline geometry_msgs::msg::Quaternion createMarkerOrientation( + double x, double y, double z, double w) +{ + geometry_msgs::msg::Quaternion quaternion; + + quaternion.x = x; + quaternion.y = y; + quaternion.z = z; + quaternion.w = w; + + return quaternion; +} + +inline geometry_msgs::msg::Vector3 createMarkerScale(double x, double y, double z) +{ + geometry_msgs::msg::Vector3 scale; + + scale.x = x; + scale.y = y; + scale.z = z; + + return scale; +} + +inline std_msgs::msg::ColorRGBA createMarkerColor(float r, float g, float b, float a) +{ + std_msgs::msg::ColorRGBA color; + + color.r = r; + color.g = g; + color.b = b; + color.a = a; + + return color; +} + +inline visualization_msgs::msg::Marker createMarker( + const std::string & frame_id, const std::string & ns, const int32_t id, const int32_t type, + geometry_msgs::msg::Point point, geometry_msgs::msg::Vector3 scale, + const std_msgs::msg::ColorRGBA & color) +{ + visualization_msgs::msg::Marker marker; + + marker.header.frame_id = frame_id; + marker.header.stamp = rclcpp::Time(0); + marker.ns = ns; + marker.id = id; + marker.type = type; + marker.action = visualization_msgs::msg::Marker::ADD; + marker.lifetime = rclcpp::Duration::from_seconds(0.2); + marker.pose.position = point; + marker.pose.orientation = createMarkerOrientation(0.0, 0.0, 0.0, 1.0); + marker.scale = scale; + marker.color = color; + marker.frame_locked = false; + + return marker; +} + +inline visualization_msgs::msg::Marker createStringMarker( + const std::string & frame_id, const std::string & ns, const int32_t id, const int32_t type, + geometry_msgs::msg::Point point, geometry_msgs::msg::Vector3 scale, + const std_msgs::msg::ColorRGBA & color, const std::string text) +{ + visualization_msgs::msg::Marker marker; + + marker = createMarker(frame_id, ns, id, type, point, scale, color); + marker.text = text; + + return marker; +} + +inline void appendMarkerArray( + const visualization_msgs::msg::MarkerArray & additional_marker_array, + visualization_msgs::msg::MarkerArray * marker_array) +{ + for (const auto & marker : additional_marker_array.markers) { + marker_array->markers.push_back(marker); + } +} +} // namespace vehicle_cmd_gate + +#endif // MARKER_HELPER_HPP_ diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp index b561b6427b978..6d3893cbb9ca9 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp @@ -14,6 +14,8 @@ #include "vehicle_cmd_gate.hpp" +#include "marker_helper.hpp" + #include #include @@ -73,6 +75,8 @@ VehicleCmdGate::VehicleCmdGate(const rclcpp::NodeOptions & node_options) is_filter_activated_pub_ = create_publisher("~/is_filter_activated", durable_qos); + filter_activated_marker_pub_ = + create_publisher("~/is_filter_activated/marker", durable_qos); // Subscriber external_emergency_stop_heartbeat_sub_ = create_subscription( @@ -564,6 +568,7 @@ AckermannControlCommand VehicleCmdGate::filterControlCommand(const AckermannCont is_filter_activated.stamp = now(); is_filter_activated_pub_->publish(is_filter_activated); + filter_activated_marker_pub_->publish(createMarkerArray(is_filter_activated)); return out; } @@ -731,6 +736,53 @@ void VehicleCmdGate::checkExternalEmergencyStop(diagnostic_updater::DiagnosticSt stat.summary(status.level, status.message); } +MarkerArray VehicleCmdGate::createMarkerArray(const IsFilterActivated & filter_activated) +{ + MarkerArray msg; + + if (!filter_activated.is_activated) { + return msg; + } + + /* add string marker */ + bool first_msg = true; + std::string reason = "filter activated on"; + + if (filter_activated.is_activated_on_acceleration) { + reason += " lon_acc"; + first_msg = false; + } + if (filter_activated.is_activated_on_jerk) { + reason += first_msg ? " jerk" : ", jerk"; + first_msg = false; + } + if (filter_activated.is_activated_on_speed) { + reason += first_msg ? " speed" : ", speed"; + first_msg = false; + } + if (filter_activated.is_activated_on_steering) { + reason += first_msg ? " steer" : ", steer"; + first_msg = false; + } + if (filter_activated.is_activated_on_steering_rate) { + reason += first_msg ? " steer_rate" : ", steer_rate"; + first_msg = false; + } + + msg.markers.emplace_back(createStringMarker( + "base_link", "msg", 0, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, + createMarkerPosition(0.0, 0.0, 1.0), createMarkerScale(0.0, 0.0, 1.0), + createMarkerColor(1.0, 0.0, 0.0, 1.0), reason)); + + /* add sphere marker */ + msg.markers.emplace_back(createMarker( + "base_link", "sphere", 0, visualization_msgs::msg::Marker::SPHERE, + createMarkerPosition(0.0, 0.0, 0.0), createMarkerScale(3.0, 3.0, 3.0), + createMarkerColor(1.0, 0.0, 0.0, 0.3))); + + return msg; +} + } // namespace vehicle_cmd_gate #include diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp index d516ecac29ca2..aeace3234d6b9 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp @@ -44,6 +44,7 @@ #include #include #include +#include #include @@ -66,6 +67,7 @@ using tier4_external_api_msgs::srv::SetEmergency; using tier4_system_msgs::msg::MrmBehaviorStatus; using tier4_vehicle_msgs::msg::VehicleEmergencyStamped; using vehicle_cmd_gate::msg::IsFilterActivated; +using visualization_msgs::msg::MarkerArray; using diagnostic_msgs::msg::DiagnosticStatus; using nav_msgs::msg::Odometry; @@ -102,6 +104,7 @@ class VehicleCmdGate : public rclcpp::Node rclcpp::Publisher::SharedPtr engage_pub_; rclcpp::Publisher::SharedPtr operation_mode_pub_; rclcpp::Publisher::SharedPtr is_filter_activated_pub_; + rclcpp::Publisher::SharedPtr filter_activated_marker_pub_; // Subscription rclcpp::Subscription::SharedPtr external_emergency_stop_heartbeat_sub_; @@ -229,6 +232,9 @@ class VehicleCmdGate : public rclcpp::Node // stop checker std::unique_ptr vehicle_stop_checker_; double stop_check_duration_; + + // debug + MarkerArray createMarkerArray(const IsFilterActivated & filter_activated); }; } // namespace vehicle_cmd_gate From 66c6e15c1180fe8cf16caa61b018e5d2ded9e94b Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:39:46 +0900 Subject: [PATCH 039/547] feat: use constant string for map_projector_info (#4789) * feat: use constant string for map_projector_info Signed-off-by: kminoda * style(pre-commit): autofix * update Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../src/lanelet2_map_loader/lanelet2_map_loader_node.cpp | 7 ++++--- .../src/load_info_from_lanelet2_map.cpp | 6 +++--- map/map_projection_loader/src/map_projection_loader.cpp | 6 +++--- system/default_ad_api/src/vehicle.cpp | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp index ed527c38824fc..328808e04d9e0 100644 --- a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp +++ b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp @@ -92,13 +92,14 @@ lanelet::LaneletMapPtr Lanelet2MapLoaderNode::load_map( const double & map_origin_lat, const double & map_origin_lon) { lanelet::ErrorMessages errors{}; - if (lanelet2_map_projector_type == "MGRS") { + if (lanelet2_map_projector_type == tier4_map_msgs::msg::MapProjectorInfo::MGRS) { lanelet::projection::MGRSProjector projector{}; const lanelet::LaneletMapPtr map = lanelet::load(lanelet2_filename, projector, &errors); if (errors.empty()) { return map; } - } else if (lanelet2_map_projector_type == "LocalCartesianUTM") { + } else if ( + lanelet2_map_projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL_CARTESIAN_UTM) { lanelet::GPSPoint position{map_origin_lat, map_origin_lon}; lanelet::Origin origin{position}; lanelet::projection::UtmProjector projector{origin}; @@ -106,7 +107,7 @@ lanelet::LaneletMapPtr Lanelet2MapLoaderNode::load_map( if (errors.empty()) { return map; } - } else if (lanelet2_map_projector_type == "local") { + } else if (lanelet2_map_projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL) { // Use MGRSProjector as parser lanelet::projection::MGRSProjector projector{}; const lanelet::LaneletMapPtr map = lanelet::load(lanelet2_filename, projector, &errors); diff --git a/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp b/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp index f213432b66b50..18306e8d39e25 100644 --- a/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp +++ b/map/map_projection_loader/src/load_info_from_lanelet2_map.cpp @@ -46,15 +46,15 @@ tier4_map_msgs::msg::MapProjectorInfo load_info_from_lanelet2_map(const std::str tier4_map_msgs::msg::MapProjectorInfo msg; if (is_local) { - msg.projector_type = "local"; + msg.projector_type = tier4_map_msgs::msg::MapProjectorInfo::LOCAL; } else { - msg.projector_type = "MGRS"; + msg.projector_type = tier4_map_msgs::msg::MapProjectorInfo::MGRS; msg.mgrs_grid = projector.getProjectedMGRSGrid(); } // We assume that the vertical datum of the map is WGS84 when using lanelet2 map. // However, do note that this is not always true, and may cause problems in the future. // Thus, please consider using the map_projector_info.yaml instead of this deprecated function. - msg.vertical_datum = "WGS84"; + msg.vertical_datum = tier4_map_msgs::msg::MapProjectorInfo::WGS84; return msg; } diff --git a/map/map_projection_loader/src/map_projection_loader.cpp b/map/map_projection_loader/src/map_projection_loader.cpp index ec38f0ff3642e..79083b755fb26 100644 --- a/map/map_projection_loader/src/map_projection_loader.cpp +++ b/map/map_projection_loader/src/map_projection_loader.cpp @@ -28,14 +28,14 @@ tier4_map_msgs::msg::MapProjectorInfo load_info_from_yaml(const std::string & fi tier4_map_msgs::msg::MapProjectorInfo msg; msg.projector_type = data["projector_type"].as(); - if (msg.projector_type == "MGRS") { + if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::MGRS) { msg.vertical_datum = data["vertical_datum"].as(); msg.mgrs_grid = data["mgrs_grid"].as(); - } else if (msg.projector_type == "LocalCartesianUTM") { + } else if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL_CARTESIAN_UTM) { msg.vertical_datum = data["vertical_datum"].as(); msg.map_origin.latitude = data["map_origin"]["latitude"].as(); msg.map_origin.longitude = data["map_origin"]["longitude"].as(); - } else if (msg.projector_type == "local") { + } else if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL) { ; // do nothing } else { throw std::runtime_error( diff --git a/system/default_ad_api/src/vehicle.cpp b/system/default_ad_api/src/vehicle.cpp index 43efdb2adfaf8..fb434adbb0e40 100644 --- a/system/default_ad_api/src/vehicle.cpp +++ b/system/default_ad_api/src/vehicle.cpp @@ -146,7 +146,7 @@ void VehicleNode::publish_kinematics() vehicle_kinematics.twist.header = kinematic_state_msgs_->header; vehicle_kinematics.twist.header.frame_id = kinematic_state_msgs_->child_frame_id; vehicle_kinematics.twist.twist = kinematic_state_msgs_->twist; - if (map_projector_info_->projector_type == "MGRS") { + if (map_projector_info_->projector_type == MapProjectorInfo::MGRS) { lanelet::GPSPoint projected_gps_point = lanelet::projection::MGRSProjector::reverse( toBasicPoint3dPt(kinematic_state_msgs_->pose.pose.position), map_projector_info_->mgrs_grid); vehicle_kinematics.geographic_pose.header = kinematic_state_msgs_->header; @@ -154,7 +154,7 @@ void VehicleNode::publish_kinematics() vehicle_kinematics.geographic_pose.position.latitude = projected_gps_point.lat; vehicle_kinematics.geographic_pose.position.longitude = projected_gps_point.lon; vehicle_kinematics.geographic_pose.position.altitude = projected_gps_point.ele; - } else if (map_projector_info_->projector_type == "LocalCartesianUTM") { + } else if (map_projector_info_->projector_type == MapProjectorInfo::LOCAL_CARTESIAN_UTM) { lanelet::GPSPoint position{ map_projector_info_->map_origin.latitude, map_projector_info_->map_origin.longitude}; lanelet::Origin origin{position}; From b8b2d3e50e53ff2ee41fbc61d49cae420b96940b Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Tue, 29 Aug 2023 21:09:17 +0900 Subject: [PATCH 040/547] fix(tier4_percetion_launch): fix order of Camera-Lidar-Radar fusion pipeline (#4779) * fix(tier4_percetion_launch): fix order of Camera-Lidar-Radar fusion pipeline Signed-off-by: scepter914 * fix clustering update Signed-off-by: scepter914 * fix from Camera-LidAR fusion Signed-off-by: scepter914 * refactor Signed-off-by: scepter914 * refactor Signed-off-by: scepter914 * fix merge Signed-off-by: scepter914 * Update launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> * style(pre-commit): autofix --------- Signed-off-by: scepter914 Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ...ra_lidar_fusion_based_detection.launch.xml | 29 +- ...ar_radar_fusion_based_detection.launch.xml | 355 +++++++++++++++--- 2 files changed, 329 insertions(+), 55 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index 79e04e1d6fc61..c74c0fdcc63b7 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -1,8 +1,25 @@ + + + + - + + + + + + + + + + + + + + @@ -28,16 +45,6 @@ - - - - - - - - - - + + + + - + + + + + + - - + + + - + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + @@ -80,10 +78,279 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 25d99bd19da382cfd4b67f991ca48618e5bd9e0b Mon Sep 17 00:00:00 2001 From: Phoebe Wu Date: Tue, 29 Aug 2023 20:25:44 +0800 Subject: [PATCH 041/547] refactor(radar_scan_to_pointcloud2): rework parameters (#4620) * refactor(radar_scan_to_pointcloud2): rework parameters Signed-off-by: PhoebeWu21 * style(pre-commit): autofix Signed-off-by: PhoebeWu21 * refactor(radar_scan_to_pointcloud2): rework parameters Signed-off-by: PhoebeWu21 * style(pre-commit): autofix Signed-off-by: PhoebeWu21 * refactor(radar_scan_to_pointcloud2): rework parameters Signed-off-by: PhoebeWu21 --------- Signed-off-by: PhoebeWu21 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> --- .../radar_scan_to_pointcloud2/CMakeLists.txt | 1 + .../radar_scan_to_pointcloud2.param.yaml | 4 +++ .../radar_scan_to_pointcloud2.launch.xml | 7 ++-- .../radar_scan_to_pointcloud2.schema.json | 35 +++++++++++++++++++ .../radar_scan_to_pointcloud2_node.cpp | 5 ++- 5 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 sensing/radar_scan_to_pointcloud2/config/radar_scan_to_pointcloud2.param.yaml create mode 100644 sensing/radar_scan_to_pointcloud2/schema/radar_scan_to_pointcloud2.schema.json diff --git a/sensing/radar_scan_to_pointcloud2/CMakeLists.txt b/sensing/radar_scan_to_pointcloud2/CMakeLists.txt index 1fb63df6594bf..6ec1759fc7d4d 100644 --- a/sensing/radar_scan_to_pointcloud2/CMakeLists.txt +++ b/sensing/radar_scan_to_pointcloud2/CMakeLists.txt @@ -27,4 +27,5 @@ endif() ament_auto_package( INSTALL_TO_SHARE launch + config ) diff --git a/sensing/radar_scan_to_pointcloud2/config/radar_scan_to_pointcloud2.param.yaml b/sensing/radar_scan_to_pointcloud2/config/radar_scan_to_pointcloud2.param.yaml new file mode 100644 index 0000000000000..381977af218be --- /dev/null +++ b/sensing/radar_scan_to_pointcloud2/config/radar_scan_to_pointcloud2.param.yaml @@ -0,0 +1,4 @@ +/**: + ros__parameters: + publish_amplitude_pointcloud: true + publish_doppler_pointcloud: false diff --git a/sensing/radar_scan_to_pointcloud2/launch/radar_scan_to_pointcloud2.launch.xml b/sensing/radar_scan_to_pointcloud2/launch/radar_scan_to_pointcloud2.launch.xml index f94b0bfd91ea8..45853c409fced 100644 --- a/sensing/radar_scan_to_pointcloud2/launch/radar_scan_to_pointcloud2.launch.xml +++ b/sensing/radar_scan_to_pointcloud2/launch/radar_scan_to_pointcloud2.launch.xml @@ -2,14 +2,13 @@ - - + + - - + diff --git a/sensing/radar_scan_to_pointcloud2/schema/radar_scan_to_pointcloud2.schema.json b/sensing/radar_scan_to_pointcloud2/schema/radar_scan_to_pointcloud2.schema.json new file mode 100644 index 0000000000000..3d60c3719af8f --- /dev/null +++ b/sensing/radar_scan_to_pointcloud2/schema/radar_scan_to_pointcloud2.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Radar Scan to PointCloud2 Node", + "type": "object", + "definitions": { + "radar_scan_to_pointcloud2": { + "type": "object", + "properties": { + "publish_amplitude_pointcloud": { + "type": "boolean", + "description": "Whether publish radar pointcloud whose intensity is amplitude.", + "default": "true" + }, + "publish_doppler_pointcloud": { + "type": "boolean", + "description": "Whether publish radar pointcloud whose intensity is doppler velocity.", + "default": "false" + } + }, + "required": ["publish_amplitude_pointcloud", "publish_doppler_pointcloud"] + } + }, + "properties": { + "/**": { + "type": "object", + "properties": { + "ros__parameters": { + "$ref": "#/definitions/radar_scan_to_pointcloud2" + } + }, + "required": ["ros__parameters"] + } + }, + "required": ["/**"] +} diff --git a/sensing/radar_scan_to_pointcloud2/src/radar_scan_to_pointcloud2_node/radar_scan_to_pointcloud2_node.cpp b/sensing/radar_scan_to_pointcloud2/src/radar_scan_to_pointcloud2_node/radar_scan_to_pointcloud2_node.cpp index de4ac74db6429..b182147468262 100644 --- a/sensing/radar_scan_to_pointcloud2/src/radar_scan_to_pointcloud2_node/radar_scan_to_pointcloud2_node.cpp +++ b/sensing/radar_scan_to_pointcloud2/src/radar_scan_to_pointcloud2_node/radar_scan_to_pointcloud2_node.cpp @@ -104,9 +104,8 @@ RadarScanToPointcloud2Node::RadarScanToPointcloud2Node(const rclcpp::NodeOptions // Node Parameter node_param_.publish_amplitude_pointcloud = - declare_parameter("publish_amplitude_pointcloud", true); - node_param_.publish_doppler_pointcloud = - declare_parameter("publish_doppler_pointcloud", false); + declare_parameter("publish_amplitude_pointcloud"); + node_param_.publish_doppler_pointcloud = declare_parameter("publish_doppler_pointcloud"); // Subscriber sub_radar_ = create_subscription( From 9c513ecb4d7131180da5b5ece7551686fffb1d58 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Tue, 29 Aug 2023 21:39:29 +0900 Subject: [PATCH 042/547] refactor(behavior_path_planner): type aliasing for collision check debug type (#4796) Signed-off-by: Zulfaqar Azmi --- .../marker_utils/lane_change/debug.hpp | 15 ++---- .../marker_utils/utils.hpp | 32 ++++-------- .../scene_module/lane_change/base_class.hpp | 2 +- .../scene_module/lane_change/normal.hpp | 6 +-- .../utils/avoidance/avoidance_module_data.hpp | 2 +- .../path_safety_checker_parameters.hpp | 27 ++++++++++ .../path_safety_checker/safety_check.hpp | 2 +- .../src/marker_utils/lane_change/debug.cpp | 50 ++----------------- .../src/marker_utils/utils.cpp | 23 +++++++++ .../avoidance/avoidance_module.cpp | 2 +- .../src/scene_module/lane_change/normal.cpp | 31 +++--------- .../test/test_safety_check.cpp | 2 +- 12 files changed, 82 insertions(+), 112 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp index 6bcc9cee877b4..808e76a140761 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp @@ -25,20 +25,13 @@ namespace marker_utils::lane_change_markers { using behavior_path_planner::LaneChangePath; -using marker_utils::CollisionCheckDebug; using visualization_msgs::msg::MarkerArray; -MarkerArray showObjectInfo( - const std::unordered_map & obj_debug_vec, std::string && ns); +MarkerArray showObjectInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); MarkerArray showAllValidLaneChangePath( const std::vector & lanes, std::string && ns); -MarkerArray showLerpedPose( - const std::unordered_map & obj_debug_vec, std::string && ns); -MarkerArray showEgoPredictedPaths( - const std::unordered_map & obj_debug_vec, std::string && ns); -MarkerArray showPolygon( - const std::unordered_map & obj_debug_vec, std::string && ns); -MarkerArray showPolygonPose( - const std::unordered_map & obj_debug_vec, std::string && ns); +MarkerArray showLerpedPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); +MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); +MarkerArray showPolygonPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); MarkerArray createLaneChangingVirtualWallMarker( const geometry_msgs::msg::Pose & lane_changing_pose, const std::string & module_name, const rclcpp::Time & now, const std::string & ns); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index c9f4d6c909900..86964117de34c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -15,6 +15,7 @@ #define BEHAVIOR_PATH_PLANNER__MARKER_UTILS__UTILS_HPP_ #include "behavior_path_planner/data_manager.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include "tier4_autoware_utils/tier4_autoware_utils.hpp" @@ -29,7 +30,6 @@ #include #include -#include #include namespace marker_utils @@ -39,6 +39,9 @@ using autoware_auto_perception_msgs::msg::PredictedPath; using autoware_auto_planning_msgs::msg::PathWithLaneId; using behavior_path_planner::DrivableLanes; using behavior_path_planner::ShiftLineArray; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugMap; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugPair; +using behavior_path_planner::utils::path_safety_checker::ExtendedPredictedObject; using geometry_msgs::msg::Point; using geometry_msgs::msg::Polygon; using geometry_msgs::msg::Pose; @@ -49,27 +52,6 @@ using tier4_autoware_utils::Polygon2d; using visualization_msgs::msg::Marker; using visualization_msgs::msg::MarkerArray; -struct CollisionCheckDebug -{ - std::string unsafe_reason; ///< Reason indicating unsafe situation. - Pose current_pose{}; ///< Ego vehicle's current pose. - Twist current_twist{}; ///< Ego vehicle's current velocity and rotation. - Twist object_twist{}; ///< Detected object's velocity and rotation. - Pose expected_ego_pose{}; ///< Predicted future pose of ego vehicle. - Pose expected_obj_pose{}; ///< Predicted future pose of object. - double rss_longitudinal{0.0}; ///< Longitudinal RSS measure. - double inter_vehicle_distance{0.0}; ///< Distance between ego vehicle and object. - double extended_polygon_lon_offset{0.0}; ///< Longitudinal offset for extended polygon. - double extended_polygon_lat_offset{0.0}; ///< Lateral offset for extended polygon. - bool is_front{false}; ///< True if object is in front of ego vehicle. - bool is_safe{false}; ///< True if situation is deemed safe. - std::vector lerped_path; ///< Interpolated ego vehicle path. - std::vector ego_predicted_path{}; ///< Predicted future path of ego vehicle. - Polygon2d extended_ego_polygon{}; ///< Ego vehicle's extended collision polygon. - Polygon2d extended_obj_polygon{}; ///< Detected object's extended collision polygon. -}; -using CollisionCheckDebugMap = std::unordered_map; - constexpr std::array, 10> colorsList() { constexpr std::array red = {1., 0., 0.}; @@ -85,12 +67,16 @@ constexpr std::array, 10> colorsList() return {red, green, blue, yellow, aqua, magenta, medium_orchid, light_pink, light_yellow, light_steel_blue}; } - inline int64_t bitShift(int64_t original_id) { return original_id << (sizeof(int32_t) * 8 / 2); } +CollisionCheckDebugPair createObjectDebug(const ExtendedPredictedObject & obj); + +void updateCollisionCheckDebugMap( + CollisionCheckDebugMap & debug_map, CollisionCheckDebugPair & object_debug, bool is_safe); + MarkerArray createPoseMarkerArray( const Pose & pose, std::string && ns, const int32_t & id, const float & r, const float & g, const float & b); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp index 6ca06ff21c4a9..f30bf31d4e010 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp @@ -43,11 +43,11 @@ namespace behavior_path_planner { using autoware_auto_planning_msgs::msg::PathWithLaneId; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugMap; using data::lane_change::PathSafetyStatus; using geometry_msgs::msg::Point; using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; -using marker_utils::CollisionCheckDebugMap; using route_handler::Direction; using tier4_autoware_utils::StopWatch; using tier4_planning_msgs::msg::LaneChangeDebugMsg; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index 5d13507f5206c..d1476abb63e27 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -26,6 +26,8 @@ namespace behavior_path_planner { using autoware_auto_planning_msgs::msg::PathWithLaneId; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugMap; using behavior_path_planner::utils::path_safety_checker::ExtendedPredictedObject; using behavior_path_planner::utils::path_safety_checker::PoseWithVelocityAndPolygonStamped; using behavior_path_planner::utils::path_safety_checker::PoseWithVelocityStamped; @@ -33,8 +35,6 @@ using behavior_path_planner::utils::path_safety_checker::PredictedPathWithPolygo using geometry_msgs::msg::Point; using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; -using marker_utils::CollisionCheckDebug; -using marker_utils::CollisionCheckDebugMap; using route_handler::Direction; using tier4_planning_msgs::msg::LaneChangeDebugMsg; using tier4_planning_msgs::msg::LaneChangeDebugMsgArray; @@ -141,7 +141,7 @@ class NormalLaneChange : public LaneChangeBase PathSafetyStatus isLaneChangePathSafe( const LaneChangePath & lane_change_path, const LaneChangeTargetObjects & target_objects, const utils::path_safety_checker::RSSparams & rss_params, - std::unordered_map & debug_data) const; + CollisionCheckDebugMap & debug_data) const; LaneChangeTargetObjectIndices filterObject( const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index c0ed2c975a54d..0278b73b0acb4 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -48,7 +48,7 @@ using geometry_msgs::msg::Point; using geometry_msgs::msg::Pose; using geometry_msgs::msg::TransformStamped; -using marker_utils::CollisionCheckDebug; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; struct ObjectParameter { diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index c4a6a86861e6c..87c93f49901f0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -19,13 +19,18 @@ #include #include +#include +#include +#include +#include #include namespace behavior_path_planner::utils::path_safety_checker { using geometry_msgs::msg::Pose; +using geometry_msgs::msg::Twist; using tier4_autoware_utils::Polygon2d; struct PoseWithVelocity @@ -171,6 +176,28 @@ struct SafetyCheckParams bool publish_debug_marker{false}; ///< Option to publish debug markers. }; +struct CollisionCheckDebug +{ + std::string unsafe_reason; ///< Reason indicating unsafe situation. + Pose current_pose{}; ///< Ego vehicle's current pose. + Twist current_twist{}; ///< Ego vehicle's current velocity and rotation. + Twist object_twist{}; ///< Detected object's velocity and rotation. + Pose expected_ego_pose{}; ///< Predicted future pose of ego vehicle. + Pose expected_obj_pose{}; ///< Predicted future pose of object. + double rss_longitudinal{0.0}; ///< Longitudinal RSS measure. + double inter_vehicle_distance{0.0}; ///< Distance between ego vehicle and object. + double extended_polygon_lon_offset{0.0}; ///< Longitudinal offset for extended polygon. + double extended_polygon_lat_offset{0.0}; ///< Lateral offset for extended polygon. + bool is_front{false}; ///< True if object is in front of ego vehicle. + bool is_safe{false}; ///< True if situation is deemed safe. + std::vector lerped_path; ///< Interpolated ego vehicle path. + Polygon2d extended_ego_polygon{}; ///< Ego vehicle's extended collision polygon. + Polygon2d extended_obj_polygon{}; ///< Detected object's extended collision polygon. +}; +using CollisionCheckDebugPair = std::pair; +using CollisionCheckDebugMap = + std::unordered_map; + } // namespace behavior_path_planner::utils::path_safety_checker #endif // BEHAVIOR_PATH_PLANNER__UTILS__PATH_SAFETY_CHECKER__PATH_SAFETY_CHECKER_PARAMETERS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 7b5b1f562f1b8..a9404038d831a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -43,9 +43,9 @@ namespace behavior_path_planner::utils::path_safety_checker using autoware_auto_perception_msgs::msg::PredictedObject; using autoware_auto_perception_msgs::msg::PredictedPath; using autoware_auto_perception_msgs::msg::Shape; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; -using marker_utils::CollisionCheckDebug; using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; using vehicle_info_util::VehicleInfo; diff --git a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp index cd0236f909b14..4a76c7e7dfb11 100644 --- a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp @@ -36,8 +36,7 @@ using tier4_autoware_utils::createDefaultMarker; using tier4_autoware_utils::createMarkerColor; using tier4_autoware_utils::createMarkerScale; -MarkerArray showObjectInfo( - const std::unordered_map & obj_debug_vec, std::string && ns) +MarkerArray showObjectInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) { Marker obj_marker = createDefaultMarker( "map", rclcpp::Clock{RCL_ROS_TIME}.now(), ns, 0L, Marker::TEXT_VIEW_FACING, @@ -105,8 +104,7 @@ MarkerArray showAllValidLaneChangePath(const std::vector & lanes return marker_array; } -MarkerArray showLerpedPose( - const std::unordered_map & obj_debug_vec, std::string && ns) +MarkerArray showLerpedPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) { MarkerArray marker_array; int32_t id{0}; @@ -128,46 +126,7 @@ MarkerArray showLerpedPose( return marker_array; } -MarkerArray showEgoPredictedPaths( - const std::unordered_map & obj_debug_vec, std::string && ns) -{ - if (obj_debug_vec.empty()) { - return MarkerArray{}; - } - - MarkerArray marker_array; - constexpr auto colors = colorsList(); - constexpr float scale_val = 0.2; - const auto current_time{rclcpp::Clock{RCL_ROS_TIME}.now()}; - marker_array.markers.reserve(obj_debug_vec.size()); - - int32_t id{0}; - for (const auto & [uuid, info] : obj_debug_vec) { - const auto loop_size = std::min(info.ego_predicted_path.size(), colors.size()); - - for (std::size_t idx = 0; idx < loop_size; ++idx) { - const auto & path = info.ego_predicted_path.at(idx).path; - const auto & color = colors.at(idx); - - Marker marker = createDefaultMarker( - "map", current_time, ns, ++id, Marker::LINE_STRIP, - createMarkerScale(scale_val, scale_val, scale_val), - createMarkerColor(color[0], color[1], color[2], 0.9)); - - marker.points.reserve(path.size()); - - for (const auto & point : path) { - marker.points.push_back(point.position); - } - - marker_array.markers.push_back(marker); - } - } - return marker_array; -} - -MarkerArray showPolygon( - const std::unordered_map & obj_debug_vec, std::string && ns) +MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) { if (obj_debug_vec.empty()) { return MarkerArray{}; @@ -235,8 +194,7 @@ MarkerArray showPolygon( return marker_array; } -MarkerArray showPolygonPose( - const std::unordered_map & obj_debug_vec, std::string && ns) +MarkerArray showPolygonPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) { constexpr auto colors = colorsList(); const auto loop_size = std::min(colors.size(), obj_debug_vec.size()); diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index 40163f4f93d8a..f24e772d198c1 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -14,6 +14,7 @@ #include "behavior_path_planner/marker_utils/utils.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" @@ -23,6 +24,7 @@ namespace marker_utils { using behavior_path_planner::ShiftLine; using behavior_path_planner::utils::calcPathArcLengthArray; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; using std_msgs::msg::ColorRGBA; using tier4_autoware_utils::calcOffsetPose; using tier4_autoware_utils::createDefaultMarker; @@ -32,6 +34,27 @@ using tier4_autoware_utils::createMarkerScale; using tier4_autoware_utils::createPoint; using visualization_msgs::msg::Marker; +CollisionCheckDebugPair createObjectDebug(const ExtendedPredictedObject & obj) +{ + CollisionCheckDebug debug; + debug.current_pose = obj.initial_pose.pose; + debug.current_twist = obj.initial_twist.twist; + return {tier4_autoware_utils::toHexString(obj.uuid), debug}; +} + +void updateCollisionCheckDebugMap( + CollisionCheckDebugMap & debug_map, CollisionCheckDebugPair & object_debug, bool is_safe) +{ + auto & [key, element] = object_debug; + element.is_safe = is_safe; + if (debug_map.find(key) != debug_map.end()) { + debug_map[key] = element; + return; + } + + debug_map.insert(object_debug); +} + MarkerArray createPoseMarkerArray( const Pose & pose, std::string && ns, const int32_t & id, const float & r, const float & g, const float & b) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 60fb0530d879a..543f02abe730f 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -45,7 +45,7 @@ namespace behavior_path_planner { -using marker_utils::CollisionCheckDebug; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; using motion_utils::calcLongitudinalOffsetPose; using motion_utils::calcSignedArcLength; using motion_utils::findNearestIndex; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index cf3312901b1d9..bf0175ccd92e4 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -14,6 +14,7 @@ #include "behavior_path_planner/scene_module/lane_change/normal.hpp" +#include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/lane_change/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" @@ -1319,7 +1320,7 @@ bool NormalLaneChange::getAbortPath() PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( const LaneChangePath & lane_change_path, const LaneChangeTargetObjects & target_objects, const utils::path_safety_checker::RSSparams & rss_params, - std::unordered_map & debug_data) const + CollisionCheckDebugMap & debug_data) const { PathSafetyStatus path_safety_status; @@ -1354,28 +1355,8 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( target_objects.other_lane.end()); } - const auto assignDebugData = [](const ExtendedPredictedObject & obj) { - CollisionCheckDebug debug; - debug.current_pose = obj.initial_pose.pose; - debug.current_twist = obj.initial_twist.twist; - return std::make_pair(tier4_autoware_utils::toHexString(obj.uuid), debug); - }; - - const auto updateDebugInfo = [&debug_data]( - std::pair & obj, bool is_safe) { - const auto & key = obj.first; - auto & element = obj.second; - element.is_safe = is_safe; - if (debug_data.find(key) != debug_data.end()) { - debug_data[key] = element; - } else { - debug_data.insert(obj); - } - }; - for (const auto & obj : collision_check_objects) { - auto current_debug_data = assignDebugData(obj); - current_debug_data.second.ego_predicted_path.push_back(debug_predicted_path); + auto current_debug_data = marker_utils::createObjectDebug(obj); const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( obj, lane_change_parameters_->use_all_predicted_path); for (const auto & obj_path : obj_predicted_paths) { @@ -1383,7 +1364,8 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( path, ego_predicted_path, obj, obj_path, common_parameters, rss_params, 1.0, current_debug_data.second)) { path_safety_status.is_safe = false; - updateDebugInfo(current_debug_data, path_safety_status.is_safe); + marker_utils::updateCollisionCheckDebugMap( + debug_data, current_debug_data, path_safety_status.is_safe); const auto & obj_pose = obj.initial_pose.pose; const auto obj_polygon = tier4_autoware_utils::toPolygon2d(obj_pose, obj.shape); path_safety_status.is_object_coming_from_rear |= @@ -1391,7 +1373,8 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( path, current_pose, common_parameters.vehicle_info, obj_polygon); } } - updateDebugInfo(current_debug_data, path_safety_status.is_safe); + marker_utils::updateCollisionCheckDebugMap( + debug_data, current_debug_data, path_safety_status.is_safe); } return path_safety_status; diff --git a/planning/behavior_path_planner/test/test_safety_check.cpp b/planning/behavior_path_planner/test/test_safety_check.cpp index e1b74636f5ed1..9dfcef487cc81 100644 --- a/planning/behavior_path_planner/test/test_safety_check.cpp +++ b/planning/behavior_path_planner/test/test_safety_check.cpp @@ -27,10 +27,10 @@ constexpr double epsilon = 1e-6; using autoware_auto_perception_msgs::msg::Shape; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; using geometry_msgs::msg::Point; using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; -using marker_utils::CollisionCheckDebug; using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; From b56dd826f65e48c7913cd862980775a3b73d06df Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Tue, 29 Aug 2023 21:43:35 +0900 Subject: [PATCH 043/547] fix(radar_fusion_sync_policy): change sync policy for radar_fusion_to_detected_object (#4780) Signed-off-by: scepter914 --- .../radar_object_fusion_to_detected_object_node.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perception/radar_fusion_to_detected_object/src/radar_object_fusion_to_detected_object_node/radar_object_fusion_to_detected_object_node.cpp b/perception/radar_fusion_to_detected_object/src/radar_object_fusion_to_detected_object_node/radar_object_fusion_to_detected_object_node.cpp index 8e909e4fc08c8..a65302aea866d 100644 --- a/perception/radar_fusion_to_detected_object/src/radar_object_fusion_to_detected_object_node/radar_object_fusion_to_detected_object_node.cpp +++ b/perception/radar_fusion_to_detected_object/src/radar_object_fusion_to_detected_object_node/radar_object_fusion_to_detected_object_node.cpp @@ -97,7 +97,7 @@ RadarObjectFusionToDetectedObjectNode::RadarObjectFusionToDetectedObjectNode( using std::placeholders::_1; using std::placeholders::_2; - sync_ptr_ = std::make_shared(SyncPolicy(10), sub_object_, sub_radar_); + sync_ptr_ = std::make_shared(SyncPolicy(20), sub_object_, sub_radar_); sync_ptr_->registerCallback( std::bind(&RadarObjectFusionToDetectedObjectNode::onData, this, _1, _2)); From 52fe93fb5d3e54ef183bf7e055855071a31d1dbc Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 29 Aug 2023 22:47:21 +0900 Subject: [PATCH 044/547] fix(control_validator): fix the sign of distance (#4793) Signed-off-by: Takayuki Murooka --- control/control_validator/src/utils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control/control_validator/src/utils.cpp b/control/control_validator/src/utils.cpp index 88b8431b07af0..2aade12d5f1cf 100644 --- a/control/control_validator/src/utils.cpp +++ b/control/control_validator/src/utils.cpp @@ -152,8 +152,8 @@ double calcMaxLateralDistance( // find nearest segment const size_t nearest_segment_idx = motion_utils::findNearestSegmentIndex(reference_trajectory.points, p0); - double temp_dist = - motion_utils::calcLateralOffset(reference_trajectory.points, p0, nearest_segment_idx); + const double temp_dist = std::abs( + motion_utils::calcLateralOffset(reference_trajectory.points, p0, nearest_segment_idx)); if (temp_dist > max_dist) { max_dist = temp_dist; } From 4c714ed46990377113281bfdc83325772b55559e Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Tue, 29 Aug 2023 23:07:20 +0900 Subject: [PATCH 045/547] refactor(behavior_path_planner): use function with color name (#4797) * refactor(behavior_path_planner): use function with color name Signed-off-by: Muhammad Zulfaqar Azmi * Update planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> --------- Signed-off-by: Muhammad Zulfaqar Azmi Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> --- .../marker_utils/colors.hpp | 94 +++++++++++++++++++ .../marker_utils/utils.hpp | 15 --- .../src/marker_utils/lane_change/debug.cpp | 32 +++---- 3 files changed, 108 insertions(+), 33 deletions(-) create mode 100644 planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp new file mode 100644 index 0000000000000..82c387d04e525 --- /dev/null +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp @@ -0,0 +1,94 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef BEHAVIOR_PATH_PLANNER__MARKER_UTILS__COLORS_HPP_ +#define BEHAVIOR_PATH_PLANNER__MARKER_UTILS__COLORS_HPP_ + +#include "tier4_autoware_utils/tier4_autoware_utils.hpp" + +#include "std_msgs/msg/color_rgba.hpp" + +#include + +namespace marker_utils::colors +{ +using std_msgs::msg::ColorRGBA; + +inline ColorRGBA red(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(1., 0., 0., a); +} + +inline ColorRGBA green(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(0., 1., 0., a); +} + +inline ColorRGBA blue(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(0., 0., 1., a); +} + +inline ColorRGBA yellow(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(1., 1., 0., a); +} + +inline ColorRGBA aqua(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(0., 1., 1., a); +} + +inline ColorRGBA magenta(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(1., 0., 1., a); +} + +inline ColorRGBA medium_orchid(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(0.729, 0.333, 0.827, a); +} + +inline ColorRGBA light_pink(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(1., 0.713, 0.756, a); +} + +inline ColorRGBA light_yellow(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(1., 1., 0.878, a); +} + +inline ColorRGBA light_steel_blue(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(0.690, 0.768, 0.870, a); +} + +inline ColorRGBA white(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(1., 1., 1., a); +} + +inline ColorRGBA grey(float a = 0.99) +{ + return tier4_autoware_utils::createMarkerColor(.5, .5, .5, a); +} + +inline std::vector colors_list(float a = 0.99) +{ + return {red(a), green(a), blue(a), yellow(a), aqua(a), + magenta(a), medium_orchid(a), light_pink(a), light_yellow(a), light_steel_blue(a)}; +} +} // namespace marker_utils::colors + +#endif // BEHAVIOR_PATH_PLANNER__MARKER_UTILS__COLORS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index 86964117de34c..657437615f809 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -52,21 +52,6 @@ using tier4_autoware_utils::Polygon2d; using visualization_msgs::msg::Marker; using visualization_msgs::msg::MarkerArray; -constexpr std::array, 10> colorsList() -{ - constexpr std::array red = {1., 0., 0.}; - constexpr std::array green = {0., 1., 0.}; - constexpr std::array blue = {0., 0., 1.}; - constexpr std::array yellow = {1., 1., 0.}; - constexpr std::array aqua = {0., 1., 1.}; - constexpr std::array magenta = {1., 0., 1.}; - constexpr std::array medium_orchid = {0.729, 0.333, 0.827}; - constexpr std::array light_pink = {1, 0.713, 0.756}; - constexpr std::array light_yellow = {1, 1, 0.878}; - constexpr std::array light_steel_blue = {0.690, 0.768, 0.870}; - return {red, green, blue, yellow, aqua, - magenta, medium_orchid, light_pink, light_yellow, light_steel_blue}; -} inline int64_t bitShift(int64_t original_id) { return original_id << (sizeof(int32_t) * 8 / 2); diff --git a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp index 4a76c7e7dfb11..aab2be6f3054e 100644 --- a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "behavior_path_planner/marker_utils/colors.hpp" #include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" @@ -33,14 +34,13 @@ namespace marker_utils::lane_change_markers { using geometry_msgs::msg::Point; using tier4_autoware_utils::createDefaultMarker; -using tier4_autoware_utils::createMarkerColor; using tier4_autoware_utils::createMarkerScale; MarkerArray showObjectInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) { Marker obj_marker = createDefaultMarker( "map", rclcpp::Clock{RCL_ROS_TIME}.now(), ns, 0L, Marker::TEXT_VIEW_FACING, - createMarkerScale(0.5, 0.5, 0.5), createMarkerColor(0.0, 1.0, 1.0, 0.999)); + createMarkerScale(0.5, 0.5, 0.5), colors::aqua()); MarkerArray marker_array; int32_t id{0}; @@ -80,7 +80,7 @@ MarkerArray showAllValidLaneChangePath(const std::vector & lanes int32_t id{0}; const auto current_time{rclcpp::Clock{RCL_ROS_TIME}.now()}; - constexpr auto colors = colorsList(); + const auto colors = colors::colors_list(); const auto loop_size = std::min(lanes.size(), colors.size()); marker_array.markers.reserve(loop_size); @@ -91,8 +91,7 @@ MarkerArray showAllValidLaneChangePath(const std::vector & lanes const auto & color = colors.at(idx); Marker marker = createDefaultMarker( - "map", current_time, ns, ++id, Marker::LINE_STRIP, createMarkerScale(0.1, 0.1, 0.0), - createMarkerColor(color[0], color[1], color[2], 0.9)); + "map", current_time, ns, ++id, Marker::LINE_STRIP, createMarkerScale(0.1, 0.1, 0.0), color); marker.points.reserve(lanes.at(idx).path.points.size()); for (const auto & point : lanes.at(idx).path.points) { @@ -114,7 +113,7 @@ MarkerArray showLerpedPose(const CollisionCheckDebugMap & obj_debug_vec, std::st for (const auto & [uuid, info] : obj_debug_vec) { Marker marker = createDefaultMarker( "map", current_time, ns, ++id, Marker::POINTS, createMarkerScale(0.3, 0.3, 0.3), - createMarkerColor(1.0, 0.3, 1.0, 0.9)); + colors::magenta()); marker.points.reserve(info.lerped_path.size()); for (const auto & point : info.lerped_path) { @@ -137,17 +136,15 @@ MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::strin const auto now = rclcpp::Clock{RCL_ROS_TIME}.now(); Marker ego_marker = createDefaultMarker( "map", now, ns, id, Marker::LINE_STRIP, createMarkerScale(scale_val, scale_val, scale_val), - createMarkerColor(1.0, 1.0, 1.0, 0.9)); + colors::green()); Marker obj_marker = ego_marker; auto text_marker = createDefaultMarker( "map", now, ns + "_text", id, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, - createMarkerScale(1.5, 1.5, 1.5), createMarkerColor(1.0, 1.0, 1.0, 1.0)); + createMarkerScale(1.5, 1.5, 1.5), colors::white()); MarkerArray marker_array; - constexpr auto red_color = colorsList().at(0); - constexpr auto green_color = colorsList().at(1); const auto reserve_size = obj_debug_vec.size(); marker_array.markers.reserve(reserve_size * 4); @@ -155,11 +152,11 @@ MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::strin int32_t idx = {0}; for (const auto & [uuid, info] : obj_debug_vec) { - const auto & color = info.is_safe ? green_color : red_color; + const auto color = info.is_safe ? colors::green() : colors::red(); const auto & ego_polygon = info.extended_ego_polygon.outer(); const auto poly_z = info.current_pose.position.z; // temporally ego_marker.id = ++id; - ego_marker.color = createMarkerColor(color[0], color[1], color[2], 0.8); + ego_marker.color = color; ego_marker.points.reserve(ego_polygon.size()); for (const auto & p : ego_polygon) { ego_marker.points.push_back(tier4_autoware_utils::createPoint(p.x(), p.y(), poly_z)); @@ -176,7 +173,7 @@ MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::strin const auto & obj_polygon = info.extended_obj_polygon.outer(); obj_marker.id = ++id; - obj_marker.color = createMarkerColor(color[0], color[1], color[2], 0.8); + obj_marker.color = color; obj_marker.points.reserve(obj_polygon.size()); for (const auto & p : obj_polygon) { obj_marker.points.push_back(tier4_autoware_utils::createPoint(p.x(), p.y(), poly_z)); @@ -196,7 +193,7 @@ MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::strin MarkerArray showPolygonPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) { - constexpr auto colors = colorsList(); + const auto colors = colors::colors_list(); const auto loop_size = std::min(colors.size(), obj_debug_vec.size()); MarkerArray marker_array; int32_t id{0}; @@ -207,8 +204,7 @@ MarkerArray showPolygonPose(const CollisionCheckDebugMap & obj_debug_vec, std::s for (const auto & [uuid, info] : obj_debug_vec) { const auto & color = colors.at(idx); Marker marker = createDefaultMarker( - "map", current_time, ns, ++id, Marker::POINTS, createMarkerScale(0.2, 0.2, 0.2), - createMarkerColor(color[0], color[1], color[2], 0.999)); + "map", current_time, ns, ++id, Marker::POINTS, createMarkerScale(0.2, 0.2, 0.2), color); marker.points.reserve(2); marker.points.push_back(info.expected_ego_pose.position); marker.points.push_back(info.expected_obj_pose.position); @@ -232,7 +228,7 @@ MarkerArray createLaneChangingVirtualWallMarker( { auto wall_marker = createDefaultMarker( "map", now, ns + "virtual_wall", id, visualization_msgs::msg::Marker::CUBE, - createMarkerScale(0.1, 5.0, 2.0), createMarkerColor(0.0, 1.0, 0.0, 0.5)); + createMarkerScale(0.1, 5.0, 2.0), colors::green()); wall_marker.pose = lane_changing_pose; wall_marker.pose.position.z += 1.0; marker_array.markers.push_back(wall_marker); @@ -241,7 +237,7 @@ MarkerArray createLaneChangingVirtualWallMarker( { auto text_marker = createDefaultMarker( "map", now, ns + "_text", id, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, - createMarkerScale(0.0, 0.0, 1.0), createMarkerColor(1.0, 1.0, 1.0, 1.0)); + createMarkerScale(0.0, 0.0, 1.0), colors::white()); text_marker.pose = lane_changing_pose; text_marker.pose.position.z += 2.0; text_marker.text = module_name; From de28252bad364015dc2cc89482335c68889ef5d3 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Tue, 29 Aug 2023 16:55:12 +0200 Subject: [PATCH 046/547] build: add nlohmann-json-dev dependency (#4795) Signed-off-by: Esteve Fernandez --- perception/radar_object_tracker/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/perception/radar_object_tracker/package.xml b/perception/radar_object_tracker/package.xml index 46fb0826e30e1..3ff6feb617baa 100644 --- a/perception/radar_object_tracker/package.xml +++ b/perception/radar_object_tracker/package.xml @@ -17,6 +17,7 @@ eigen kalman_filter mussp + nlohmann-json-dev object_recognition_utils rclcpp rclcpp_components From 890d45ad686b5ebd4f0ece4a3a2cf76c2a38434d Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 30 Aug 2023 01:49:14 +0900 Subject: [PATCH 047/547] feat(dynamic_avoidance): minimum object polygon longitudinal margin (#4758) * feat(dynamic_avoidance): minimum object polygon longitudinal margin Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../dynamic_avoidance/dynamic_avoidance.param.yaml | 8 ++++++-- .../dynamic_avoidance/dynamic_avoidance_module.hpp | 1 + .../dynamic_avoidance/dynamic_avoidance_module.cpp | 12 ++++++++++-- .../src/scene_module/dynamic_avoidance/manager.cpp | 5 +++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml b/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml index 701f05eb89ba1..fe30397683494 100644 --- a/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml +++ b/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml @@ -2,7 +2,7 @@ ros__parameters: dynamic_avoidance: common: - enable_debug_info: true + enable_debug_info: false use_hatched_road_markings: true # avoidance is performed for the object type with true @@ -42,9 +42,13 @@ max_object_angle: 0.785 drivable_area_generation: + polygon_generation_method: "object_path_base" # choose "ego_path_base" and "object_path_base" + object_path_base: + min_longitudinal_polygon_margin: 3.0 # [m] + lat_offset_from_obstacle: 0.8 # [m] max_lat_offset_to_avoid: 0.5 # [m] - max_time_for_object_lat_shift: 2.0 # [s] + max_time_for_object_lat_shift: 0.0 # [s] lpf_gain_for_lat_avoid_to_offset: 0.9 # [-] # for same directional object diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp index dd0e06fffb255..3c302d934735c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp @@ -75,6 +75,7 @@ struct DynamicAvoidanceParameters // drivable area generation std::string polygon_generation_method{}; + double min_obj_path_based_lon_polygon_margin{0.0}; double lat_offset_from_obstacle{0.0}; double max_lat_offset_to_avoid{0.0}; double max_time_for_lat_shift{0.0}; diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index 1f5cf8c2a9500..0bfda71a09079 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -949,11 +949,19 @@ DynamicAvoidanceModule::calcObjectPathBasedDynamicObstaclePolygon( // calculate left and right bound std::vector obj_left_bound_points; std::vector obj_right_bound_points; + const double obj_path_length = motion_utils::calcArcLength(obj_path.path); for (size_t i = 0; i < obj_path.path.size(); ++i) { const double lon_offset = [&]() { - if (i == 0) return -object.shape.dimensions.x / 2.0 - parameters_->lat_offset_from_obstacle; + if (i == 0) + return -object.shape.dimensions.x / 2.0 - + std::max( + parameters_->min_obj_path_based_lon_polygon_margin, + parameters_->lat_offset_from_obstacle); if (i == obj_path.path.size() - 1) - return object.shape.dimensions.x / 2.0 + parameters_->lat_offset_from_obstacle; + return object.shape.dimensions.x / 2.0 + + std::max( + parameters_->min_obj_path_based_lon_polygon_margin - obj_path_length, + parameters_->lat_offset_from_obstacle); return 0.0; }(); diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp index 3b2e2caf92e6f..c77d81457c15d 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp @@ -83,6 +83,8 @@ DynamicAvoidanceModuleManager::DynamicAvoidanceModuleManager( std::string ns = "dynamic_avoidance.drivable_area_generation."; p.polygon_generation_method = node->declare_parameter(ns + "polygon_generation_method"); + p.min_obj_path_based_lon_polygon_margin = + node->declare_parameter(ns + "object_path_base.min_longitudinal_polygon_margin"); p.lat_offset_from_obstacle = node->declare_parameter(ns + "lat_offset_from_obstacle"); p.max_lat_offset_to_avoid = node->declare_parameter(ns + "max_lat_offset_to_avoid"); p.max_time_for_lat_shift = @@ -182,6 +184,9 @@ void DynamicAvoidanceModuleManager::updateModuleParams( updateParam( parameters, ns + "polygon_generation_method", p->polygon_generation_method); + updateParam( + parameters, ns + "object_path_base.min_longitudinal_polygon_margin", + p->min_obj_path_based_lon_polygon_margin); updateParam(parameters, ns + "lat_offset_from_obstacle", p->lat_offset_from_obstacle); updateParam(parameters, ns + "max_lat_offset_to_avoid", p->max_lat_offset_to_avoid); updateParam( From e9e40ac45655137ca928d54476adde4155f7ade4 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 30 Aug 2023 08:44:00 +0900 Subject: [PATCH 048/547] feat(map_based_prediction): improve crosswalk prediction (#4800) Signed-off-by: Takayuki Murooka --- .../config/map_based_prediction.param.yaml | 1 + .../map_based_prediction_node.hpp | 1 + .../map_based_prediction/path_generator.hpp | 28 +++- .../src/map_based_prediction_node.cpp | 143 ++++++++++++------ .../src/path_generator.cpp | 13 +- 5 files changed, 130 insertions(+), 56 deletions(-) diff --git a/perception/map_based_prediction/config/map_based_prediction.param.yaml b/perception/map_based_prediction/config/map_based_prediction.param.yaml index 1f6def24912a2..31fae9c811092 100644 --- a/perception/map_based_prediction/config/map_based_prediction.param.yaml +++ b/perception/map_based_prediction/config/map_based_prediction.param.yaml @@ -5,6 +5,7 @@ prediction_sampling_delta_time: 0.5 #[s] min_velocity_for_map_based_prediction: 1.39 #[m/s] min_crosswalk_user_velocity: 1.39 #[m/s] + max_crosswalk_user_delta_yaw_threshold_for_lanelet: 0.785 #[m/s] dist_threshold_for_searching_lanelet: 3.0 #[m] delta_yaw_threshold_for_searching_lanelet: 0.785 #[rad] sigma_lateral_offset: 0.5 #[m] diff --git a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp index f265633bd55dc..7364e3d11b650 100644 --- a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp @@ -147,6 +147,7 @@ class MapBasedPredictionNode : public rclcpp::Node double prediction_sampling_time_interval_; double min_velocity_for_map_based_prediction_; double min_crosswalk_user_velocity_; + double max_crosswalk_user_delta_yaw_threshold_for_lanelet_; double debug_accumulated_time_; double dist_threshold_for_searching_lanelet_; double delta_yaw_threshold_for_searching_lanelet_; diff --git a/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp b/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp index 280224dee59a2..7bb1542557d2c 100644 --- a/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp @@ -49,7 +49,31 @@ struct FrenetPoint float d_acc; }; -using EntryPoint = std::pair; +struct CrosswalkEdgePoints +{ + Eigen::Vector2d front_center_point; + Eigen::Vector2d front_right_point; + Eigen::Vector2d front_left_point; + Eigen::Vector2d back_center_point; + Eigen::Vector2d back_right_point; + Eigen::Vector2d back_left_point; + + void swap() + { + const Eigen::Vector2d tmp_center_point = front_center_point; + const Eigen::Vector2d tmp_right_point = front_right_point; + const Eigen::Vector2d tmp_left_point = front_left_point; + + front_center_point = back_center_point; + front_right_point = back_right_point; + front_left_point = back_left_point; + + back_center_point = tmp_center_point; + back_right_point = tmp_right_point; + back_left_point = tmp_left_point; + } +}; + using FrenetPath = std::vector; using PosePath = std::vector; @@ -70,7 +94,7 @@ class PathGenerator const TrackedObject & object, const PosePath & ref_paths); PredictedPath generatePathForCrosswalkUser( - const TrackedObject & object, const EntryPoint & reachable_crosswalk) const; + const TrackedObject & object, const CrosswalkEdgePoints & reachable_crosswalk) const; PredictedPath generatePathToTargetPoint( const TrackedObject & object, const Eigen::Vector2d & point) const; diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 5360562e231ba..259fbae1cef91 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -331,19 +331,18 @@ lanelet::ConstLanelets getLanelets(const map_based_prediction::LaneletsData & da return lanelets; } -EntryPoint getCrosswalkEntryPoint(const lanelet::ConstLanelet & crosswalk) +CrosswalkEdgePoints getCrosswalkEdgePoints(const lanelet::ConstLanelet & crosswalk) { - const auto & r_p_front = crosswalk.rightBound().front(); - const auto & l_p_front = crosswalk.leftBound().front(); - const Eigen::Vector2d front_entry_point( - (r_p_front.x() + l_p_front.x()) / 2.0, (r_p_front.y() + l_p_front.y()) / 2.0); + const Eigen::Vector2d r_p_front = crosswalk.rightBound().front().basicPoint2d(); + const Eigen::Vector2d l_p_front = crosswalk.leftBound().front().basicPoint2d(); + const Eigen::Vector2d front_center_point = (r_p_front + l_p_front) / 2.0; - const auto & r_p_back = crosswalk.rightBound().back(); - const auto & l_p_back = crosswalk.leftBound().back(); - const Eigen::Vector2d back_entry_point( - (r_p_back.x() + l_p_back.x()) / 2.0, (r_p_back.y() + l_p_back.y()) / 2.0); + const Eigen::Vector2d r_p_back = crosswalk.rightBound().back().basicPoint2d(); + const Eigen::Vector2d l_p_back = crosswalk.leftBound().back().basicPoint2d(); + const Eigen::Vector2d back_center_point = (r_p_back + l_p_back) / 2.0; - return std::make_pair(front_entry_point, back_entry_point); + return CrosswalkEdgePoints{front_center_point, r_p_front, l_p_front, + back_center_point, r_p_back, l_p_back}; } bool withinLanelet( @@ -405,8 +404,8 @@ bool withinRoadLanelet( return false; } -boost::optional isReachableEntryPoint( - const TrackedObject & object, const EntryPoint & entry_point, +boost::optional isReachableCrosswalkEdgePoints( + const TrackedObject & object, const CrosswalkEdgePoints & edge_points, const lanelet::LaneletMapPtr & lanelet_map_ptr, const double time_horizon, const double min_object_vel) { @@ -417,15 +416,15 @@ boost::optional isReachableEntryPoint( const auto & obj_vel = object.kinematics.twist_with_covariance.twist.linear; const auto yaw = tier4_autoware_utils::getRPY(object.kinematics.pose_with_covariance.pose).z; - const auto & p1 = entry_point.first; - const auto & p2 = entry_point.second; + const auto & p1 = edge_points.front_center_point; + const auto & p2 = edge_points.back_center_point; - auto ret = std::make_pair(p1, p2); + CrosswalkEdgePoints ret{p1, {}, {}, p2, {}, {}}; auto distance_pedestrian_to_p1 = std::hypot(p1.x() - obj_pos.x, p1.y() - obj_pos.y); auto distance_pedestrian_to_p2 = std::hypot(p2.x() - obj_pos.x, p2.y() - obj_pos.y); if (distance_pedestrian_to_p2 < distance_pedestrian_to_p1) { - std::swap(ret.first, ret.second); + ret.swap(); std::swap(distance_pedestrian_to_p1, distance_pedestrian_to_p2); } @@ -454,7 +453,8 @@ boost::optional isReachableEntryPoint( } { - const Line object_to_entry_point{{obj_pos.x, obj_pos.y}, {ret.first.x(), ret.first.y()}}; + const Line object_to_entry_point{ + {obj_pos.x, obj_pos.y}, {ret.front_center_point.x(), ret.front_center_point.y()}}; std::vector tmp_intersects; boost::geometry::intersection( object_to_entry_point, lanelet.second.polygon2d().basicPolygon(), tmp_intersects); @@ -464,7 +464,8 @@ boost::optional isReachableEntryPoint( } { - const Line object_to_entry_point{{obj_pos.x, obj_pos.y}, {ret.second.x(), ret.second.y()}}; + const Line object_to_entry_point{ + {obj_pos.x, obj_pos.y}, {ret.back_center_point.x(), ret.back_center_point.y()}}; std::vector tmp_intersects; boost::geometry::intersection( object_to_entry_point, lanelet.second.polygon2d().basicPolygon(), tmp_intersects); @@ -487,12 +488,12 @@ boost::optional isReachableEntryPoint( } if (first_intersect_load && !second_intersect_load) { - std::swap(ret.first, ret.second); + ret.swap(); } const Eigen::Vector2d pedestrian_to_crosswalk( - (ret.first.x() + ret.second.x()) / 2.0 - obj_pos.x, - (ret.first.y() + ret.second.y()) / 2.0 - obj_pos.y); + (ret.front_center_point.x() + ret.back_center_point.x()) / 2.0 - obj_pos.x, + (ret.front_center_point.y() + ret.back_center_point.y()) / 2.0 - obj_pos.y); const Eigen::Vector2d pedestrian_heading_direction( obj_vel.x * std::cos(yaw), obj_vel.x * std::sin(yaw)); const auto reachable = @@ -508,8 +509,10 @@ boost::optional isReachableEntryPoint( } bool hasPotentialToReach( - const TrackedObject & object, const Eigen::Vector2d & point, const double time_horizon, - const double min_object_vel) + const TrackedObject & object, const Eigen::Vector2d & center_point, + const Eigen::Vector2d & right_point, const Eigen::Vector2d & left_point, + const double time_horizon, const double min_object_vel, + const double max_crosswalk_user_delta_yaw_threshold_for_lanelet) { const auto & obj_pos = object.kinematics.pose_with_covariance.pose.position; const auto & obj_vel = object.kinematics.twist_with_covariance.twist.linear; @@ -520,13 +523,47 @@ bool hasPotentialToReach( const auto is_stop_object = estimated_velocity < stop_velocity_th; const auto velocity = std::max(min_object_vel, estimated_velocity); - const Eigen::Vector2d pedestrian_to_crosswalk(point.x() - obj_pos.x, point.y() - obj_pos.y); - const Eigen::Vector2d pedestrian_heading_direction( - obj_vel.x * std::cos(yaw), obj_vel.x * std::sin(yaw)); - const auto heading_for_crosswalk = - pedestrian_to_crosswalk.dot(pedestrian_heading_direction) > 0.0; + const double pedestrian_to_crosswalk_center_direction = + std::atan2(center_point.y() - obj_pos.y, center_point.x() - obj_pos.x); + + const auto + [pedestrian_to_crosswalk_right_rel_direction, pedestrian_to_crosswalk_left_rel_direction] = + [&]() { + const double pedestrian_to_crosswalk_right_direction = + std::atan2(right_point.y() - obj_pos.y, right_point.x() - obj_pos.x); + const double pedestrian_to_crosswalk_left_direction = + std::atan2(left_point.y() - obj_pos.y, left_point.x() - obj_pos.x); + return std::make_pair( + tier4_autoware_utils::normalizeRadian( + pedestrian_to_crosswalk_right_direction - pedestrian_to_crosswalk_center_direction), + tier4_autoware_utils::normalizeRadian( + pedestrian_to_crosswalk_left_direction - pedestrian_to_crosswalk_center_direction)); + }(); + + const double pedestrian_heading_rel_direction = [&]() { + const double pedestrian_heading_direction = + std::atan2(obj_vel.x * std::sin(yaw), obj_vel.x * std::cos(yaw)); + return tier4_autoware_utils::normalizeRadian( + pedestrian_heading_direction - pedestrian_to_crosswalk_center_direction); + }(); - const auto reachable = pedestrian_to_crosswalk.norm() < velocity * time_horizon; + const double pedestrian_to_crosswalk_min_rel_direction = std::min( + pedestrian_to_crosswalk_right_rel_direction, pedestrian_to_crosswalk_left_rel_direction); + const double pedestrian_to_crosswalk_max_rel_direction = std::max( + pedestrian_to_crosswalk_right_rel_direction, pedestrian_to_crosswalk_left_rel_direction); + const double pedestrian_vel_angle_against_crosswalk = [&]() { + if (pedestrian_heading_rel_direction < pedestrian_to_crosswalk_min_rel_direction) { + return pedestrian_to_crosswalk_min_rel_direction - pedestrian_heading_rel_direction; + } + if (pedestrian_to_crosswalk_max_rel_direction < pedestrian_heading_rel_direction) { + return pedestrian_to_crosswalk_max_rel_direction - pedestrian_heading_rel_direction; + } + return 0.0; + }(); + const auto heading_for_crosswalk = std::abs(pedestrian_vel_angle_against_crosswalk) < + max_crosswalk_user_delta_yaw_threshold_for_lanelet; + const auto reachable = std::hypot(center_point.x() - obj_pos.x, center_point.y() - obj_pos.y) < + velocity * time_horizon; if (reachable && (heading_for_crosswalk || is_stop_object)) { return true; @@ -653,6 +690,8 @@ MapBasedPredictionNode::MapBasedPredictionNode(const rclcpp::NodeOptions & node_ min_velocity_for_map_based_prediction_ = declare_parameter("min_velocity_for_map_based_prediction"); min_crosswalk_user_velocity_ = declare_parameter("min_crosswalk_user_velocity"); + max_crosswalk_user_delta_yaw_threshold_for_lanelet_ = + declare_parameter("max_crosswalk_user_delta_yaw_threshold_for_lanelet"); dist_threshold_for_searching_lanelet_ = declare_parameter("dist_threshold_for_searching_lanelet"); delta_yaw_threshold_for_searching_lanelet_ = @@ -952,22 +991,24 @@ PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( } if (crossing_crosswalk) { - const auto entry_point = getCrosswalkEntryPoint(crossing_crosswalk.get()); + const auto edge_points = getCrosswalkEdgePoints(crossing_crosswalk.get()); if (hasPotentialToReach( - object, entry_point.first, std::numeric_limits::max(), - min_crosswalk_user_velocity_)) { + object, edge_points.front_center_point, edge_points.front_right_point, + edge_points.front_left_point, std::numeric_limits::max(), + min_crosswalk_user_velocity_, max_crosswalk_user_delta_yaw_threshold_for_lanelet_)) { PredictedPath predicted_path = - path_generator_->generatePathToTargetPoint(object, entry_point.first); + path_generator_->generatePathToTargetPoint(object, edge_points.front_center_point); predicted_path.confidence = 1.0; predicted_object.kinematics.predicted_paths.push_back(predicted_path); } if (hasPotentialToReach( - object, entry_point.second, std::numeric_limits::max(), - min_crosswalk_user_velocity_)) { + object, edge_points.back_center_point, edge_points.back_right_point, + edge_points.back_left_point, std::numeric_limits::max(), + min_crosswalk_user_velocity_, max_crosswalk_user_delta_yaw_threshold_for_lanelet_)) { PredictedPath predicted_path = - path_generator_->generatePathToTargetPoint(object, entry_point.second); + path_generator_->generatePathToTargetPoint(object, edge_points.back_center_point); predicted_path.confidence = 1.0; predicted_object.kinematics.predicted_paths.push_back(predicted_path); } @@ -979,22 +1020,24 @@ PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( lanelet::utils::query::getClosestLanelet(crosswalks_, obj_pose, &closest_crosswalk); if (found_closest_crosswalk) { - const auto entry_point = getCrosswalkEntryPoint(closest_crosswalk); + const auto edge_points = getCrosswalkEdgePoints(closest_crosswalk); if (hasPotentialToReach( - object, entry_point.first, prediction_time_horizon_ * 2.0, - min_crosswalk_user_velocity_)) { + object, edge_points.front_center_point, edge_points.front_right_point, + edge_points.front_left_point, prediction_time_horizon_ * 2.0, + min_crosswalk_user_velocity_, max_crosswalk_user_delta_yaw_threshold_for_lanelet_)) { PredictedPath predicted_path = - path_generator_->generatePathToTargetPoint(object, entry_point.first); + path_generator_->generatePathToTargetPoint(object, edge_points.front_center_point); predicted_path.confidence = 1.0; predicted_object.kinematics.predicted_paths.push_back(predicted_path); } if (hasPotentialToReach( - object, entry_point.second, prediction_time_horizon_ * 2.0, - min_crosswalk_user_velocity_)) { + object, edge_points.back_center_point, edge_points.back_right_point, + edge_points.back_left_point, prediction_time_horizon_ * 2.0, + min_crosswalk_user_velocity_, max_crosswalk_user_delta_yaw_threshold_for_lanelet_)) { PredictedPath predicted_path = - path_generator_->generatePathToTargetPoint(object, entry_point.second); + path_generator_->generatePathToTargetPoint(object, edge_points.back_center_point); predicted_path.confidence = 1.0; predicted_object.kinematics.predicted_paths.push_back(predicted_path); } @@ -1002,19 +1045,23 @@ PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( } else { for (const auto & crosswalk : crosswalks_) { - const auto entry_point = getCrosswalkEntryPoint(crosswalk); + const auto edge_points = getCrosswalkEdgePoints(crosswalk); const auto reachable_first = hasPotentialToReach( - object, entry_point.first, prediction_time_horizon_, min_crosswalk_user_velocity_); + object, edge_points.front_center_point, edge_points.front_right_point, + edge_points.front_left_point, prediction_time_horizon_, min_crosswalk_user_velocity_, + max_crosswalk_user_delta_yaw_threshold_for_lanelet_); const auto reachable_second = hasPotentialToReach( - object, entry_point.second, prediction_time_horizon_, min_crosswalk_user_velocity_); + object, edge_points.back_center_point, edge_points.back_right_point, + edge_points.back_left_point, prediction_time_horizon_, min_crosswalk_user_velocity_, + max_crosswalk_user_delta_yaw_threshold_for_lanelet_); if (!reachable_first && !reachable_second) { continue; } - const auto reachable_crosswalk = isReachableEntryPoint( - object, entry_point, lanelet_map_ptr_, prediction_time_horizon_, + const auto reachable_crosswalk = isReachableCrosswalkEdgePoints( + object, edge_points, lanelet_map_ptr_, prediction_time_horizon_, min_crosswalk_user_velocity_); if (!reachable_crosswalk) { diff --git a/perception/map_based_prediction/src/path_generator.cpp b/perception/map_based_prediction/src/path_generator.cpp index 746a379a2d93e..b1f6319801d4a 100644 --- a/perception/map_based_prediction/src/path_generator.cpp +++ b/perception/map_based_prediction/src/path_generator.cpp @@ -70,7 +70,7 @@ PredictedPath PathGenerator::generatePathToTargetPoint( } PredictedPath PathGenerator::generatePathForCrosswalkUser( - const TrackedObject & object, const EntryPoint & reachable_crosswalk) const + const TrackedObject & object, const CrosswalkEdgePoints & reachable_crosswalk) const { PredictedPath predicted_path{}; const double ep = 0.001; @@ -79,10 +79,11 @@ PredictedPath PathGenerator::generatePathForCrosswalkUser( const auto & obj_vel = object.kinematics.twist_with_covariance.twist.linear; const Eigen::Vector2d pedestrian_to_entry_point( - reachable_crosswalk.first.x() - obj_pos.x, reachable_crosswalk.first.y() - obj_pos.y); + reachable_crosswalk.front_center_point.x() - obj_pos.x, + reachable_crosswalk.front_center_point.y() - obj_pos.y); const Eigen::Vector2d entry_to_exit_point( - reachable_crosswalk.second.x() - reachable_crosswalk.first.x(), - reachable_crosswalk.second.y() - reachable_crosswalk.first.y()); + reachable_crosswalk.back_center_point.x() - reachable_crosswalk.front_center_point.x(), + reachable_crosswalk.back_center_point.y() - reachable_crosswalk.front_center_point.y()); const auto velocity = std::max(std::hypot(obj_vel.x, obj_vel.y), min_crosswalk_user_velocity_); const auto arrival_time = pedestrian_to_entry_point.norm() / velocity; @@ -98,10 +99,10 @@ PredictedPath PathGenerator::generatePathForCrosswalkUser( predicted_path.path.push_back(world_frame_pose); } else { world_frame_pose.position.x = - reachable_crosswalk.first.x() + + reachable_crosswalk.front_center_point.x() + velocity * entry_to_exit_point.normalized().x() * (dt - arrival_time); world_frame_pose.position.y = - reachable_crosswalk.first.y() + + reachable_crosswalk.front_center_point.y() + velocity * entry_to_exit_point.normalized().y() * (dt - arrival_time); world_frame_pose.position.z = obj_pos.z; world_frame_pose.orientation = object.kinematics.pose_with_covariance.pose.orientation; From ae1a5a76ac71db0ba85dce8db5614e2f47d96a2d Mon Sep 17 00:00:00 2001 From: Mateusz Palczuk <80065197+TauTheLepton@users.noreply.github.com> Date: Wed, 30 Aug 2023 02:16:10 +0200 Subject: [PATCH 049/547] fix(trajectory_follower_node): add MPC parameter (#4118) Add lacking MPC parameter Signed-off-by: Mateusz Palczuk --- control/trajectory_follower_node/param/lateral/mpc.param.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/control/trajectory_follower_node/param/lateral/mpc.param.yaml b/control/trajectory_follower_node/param/lateral/mpc.param.yaml index dce35f2c9f6d4..4222082d40deb 100644 --- a/control/trajectory_follower_node/param/lateral/mpc.param.yaml +++ b/control/trajectory_follower_node/param/lateral/mpc.param.yaml @@ -65,6 +65,7 @@ keep_steer_control_until_converged: true new_traj_duration_time: 1.0 new_traj_end_dist: 0.3 + mpc_converged_threshold_rps: 0.01 # threshold of mpc convergence check [rad/s] # steer offset steering_offset: From 8889c52a4ea27c07f84a65f1464837db6edad6cb Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 30 Aug 2023 02:27:31 +0200 Subject: [PATCH 050/547] build: added missing yaml-cpp dependency (#4801) Signed-off-by: Esteve Fernandez --- map/map_projection_loader/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/map/map_projection_loader/package.xml b/map/map_projection_loader/package.xml index 823cea064515b..03053533d3ead 100644 --- a/map/map_projection_loader/package.xml +++ b/map/map_projection_loader/package.xml @@ -18,6 +18,7 @@ rclcpp rclcpp_components tier4_map_msgs + yaml-cpp ament_cmake_gmock ament_cmake_gtest From 33e2956a6f4734cb9f48814d46dfefd8921ba358 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 30 Aug 2023 03:27:25 +0200 Subject: [PATCH 051/547] build: remove Boost from CMakeLists.txt (#4786) Signed-off-by: Esteve Fernandez --- common/perception_utils/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/perception_utils/CMakeLists.txt b/common/perception_utils/CMakeLists.txt index 302bb63512937..d4bbe928c0be8 100644 --- a/common/perception_utils/CMakeLists.txt +++ b/common/perception_utils/CMakeLists.txt @@ -4,6 +4,4 @@ project(perception_utils) find_package(autoware_cmake REQUIRED) autoware_package() -find_package(Boost REQUIRED) - ament_auto_package() From d6748e62f1b501e704d77dbc44442f32e3dca853 Mon Sep 17 00:00:00 2001 From: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:32:31 +0900 Subject: [PATCH 052/547] chore: add TLR model args to launch files (#4805) Signed-off-by: Shunsuke Miura --- .../launch/perception.launch.xml | 13 +++++++++++++ .../traffic_light.launch.xml | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index 6634c36ff3768..c41177e51116b 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -75,6 +75,19 @@ + + + + + diff --git a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml index 1f86a89ddfc7a..ebe3ff6af1893 100644 --- a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml +++ b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml @@ -3,10 +3,12 @@ - - - - + + + + + + @@ -17,6 +19,12 @@ + + + + + + From 8f9da612d166785f9f01bf7a4618226fc5e290e3 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 30 Aug 2023 12:58:31 +0900 Subject: [PATCH 053/547] feat(crosswalk): stabler collision point (#4781) * feat(crosswalk): stabler collision point Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka * fix Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../src/debug.cpp | 6 +- .../src/scene_crosswalk.cpp | 134 +++++++++--------- 2 files changed, 72 insertions(+), 68 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/debug.cpp b/planning/behavior_velocity_crosswalk_module/src/debug.cpp index a5bc3ebf72179..eae7491872cfd 100644 --- a/planning/behavior_velocity_crosswalk_module/src/debug.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/debug.cpp @@ -97,7 +97,7 @@ visualization_msgs::msg::MarkerArray createCrosswalkMarkers( for (size_t i = 0; i < debug_data.obj_polygons.size(); ++i) { const auto & points = debug_data.obj_polygons.at(i); auto marker = createDefaultMarker( - "map", now, "object polygon", uid + i++, Marker::LINE_STRIP, createMarkerScale(0.1, 0.0, 0.0), + "map", now, "object polygon", uid + i, Marker::LINE_STRIP, createMarkerScale(0.1, 0.0, 0.0), createMarkerColor(1.0, 0.0, 1.0, 0.999)); marker.points = points; // marker.points.push_back(marker.points.front()); @@ -107,8 +107,8 @@ visualization_msgs::msg::MarkerArray createCrosswalkMarkers( for (size_t i = 0; i < debug_data.ego_polygons.size(); ++i) { const auto & points = debug_data.ego_polygons.at(i); auto marker = createDefaultMarker( - "map", now, "vehicle polygon", uid + i++, Marker::LINE_STRIP, - createMarkerScale(0.1, 0.0, 0.0), createMarkerColor(1.0, 1.0, 0.0, 0.999)); + "map", now, "vehicle polygon", uid + i, Marker::LINE_STRIP, createMarkerScale(0.1, 0.0, 0.0), + createMarkerColor(1.0, 1.0, 0.0, 0.999)); marker.points = points; // marker.points.push_back(marker.points.front()); msg.markers.push_back(marker); diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index 3b69b74c53d80..b7b2c9c594647 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -68,39 +68,34 @@ std::vector toGeometryPointVector( return points; } -Polygon2d createOneStepPolygon( - const geometry_msgs::msg::Pose & p_front, const geometry_msgs::msg::Pose & p_back, - const geometry_msgs::msg::Polygon & base_polygon) +void offsetPolygon2d( + const geometry_msgs::msg::Pose & origin_point, const geometry_msgs::msg::Polygon & polygon, + Polygon2d & offset_polygon) { - Polygon2d one_step_polygon{}; - - { - geometry_msgs::msg::Polygon out_polygon{}; - geometry_msgs::msg::TransformStamped geometry_tf{}; - geometry_tf.transform = pose2transform(p_front); - tf2::doTransform(base_polygon, out_polygon, geometry_tf); - - for (const auto & p : out_polygon.points) { - one_step_polygon.outer().push_back(Point2d(p.x, p.y)); - } + for (const auto & polygon_point : polygon.points) { + const auto offset_pos = + tier4_autoware_utils::calcOffsetPose(origin_point, polygon_point.x, polygon_point.y, 0.0) + .position; + offset_polygon.outer().push_back(Point2d(offset_pos.x, offset_pos.y)); } +} - { - geometry_msgs::msg::Polygon out_polygon{}; - geometry_msgs::msg::TransformStamped geometry_tf{}; - geometry_tf.transform = pose2transform(p_back); - tf2::doTransform(base_polygon, out_polygon, geometry_tf); - - for (const auto & p : out_polygon.points) { - one_step_polygon.outer().push_back(Point2d(p.x, p.y)); - } +template +Polygon2d createMultiStepPolygon( + const T & obj_path_points, const geometry_msgs::msg::Polygon & polygon, const size_t start_idx, + const size_t end_idx) +{ + Polygon2d multi_step_polygon{}; + for (size_t i = start_idx; i <= end_idx; ++i) { + offsetPolygon2d( + tier4_autoware_utils::getPose(obj_path_points.at(i)), polygon, multi_step_polygon); } - Polygon2d hull_polygon{}; - bg::convex_hull(one_step_polygon, hull_polygon); - bg::correct(hull_polygon); + Polygon2d hull_multi_step_polygon{}; + bg::convex_hull(multi_step_polygon, hull_multi_step_polygon); + bg::correct(hull_multi_step_polygon); - return hull_polygon; + return hull_multi_step_polygon; } void sortCrosswalksByDistance( @@ -127,24 +122,12 @@ void sortCrosswalksByDistance( std::sort(crosswalks.begin(), crosswalks.end(), compare); } -std::vector calcOverlappingPoints(const Polygon2d & polygon1, const Polygon2d & polygon2) +std::vector calcOverlappingPoints(const Polygon2d & polygon1, const Polygon2d & polygon2) { // NOTE: If one polygon is fully inside the other polygon, the result is empty. - std::vector intersection{}; - bg::intersection(polygon1, polygon2, intersection); - - if (bg::within(polygon1, polygon2)) { - for (const auto & p : polygon1.outer()) { - intersection.push_back(Point2d{p.x(), p.y()}); - } - } - if (bg::within(polygon2, polygon1)) { - for (const auto & p : polygon2.outer()) { - intersection.push_back(Point2d{p.x(), p.y()}); - } - } - - return intersection; + std::vector intersection_polygons{}; + bg::intersection(polygon1, polygon2, intersection_polygons); + return intersection_polygons; } StopFactor createStopFactor( @@ -602,37 +585,56 @@ std::optional CrosswalkModule::getCollisionPoint( double minimum_stop_dist = std::numeric_limits::max(); std::optional nearest_collision_point{std::nullopt}; for (const auto & obj_path : object.kinematics.predicted_paths) { - for (size_t i = 0; i < obj_path.path.size() - 1; ++i) { - const auto & p_obj_front = obj_path.path.at(i); - const auto & p_obj_back = obj_path.path.at(i + 1); - const auto obj_one_step_polygon = createOneStepPolygon(p_obj_front, p_obj_back, obj_polygon); + size_t start_idx{0}; + bool is_start_idx_initialized{false}; + for (size_t i = 0; i < obj_path.path.size(); ++i) { + // For effective computation, the point and polygon intersection is calculated first. + const auto obj_one_step_polygon = createMultiStepPolygon(obj_path.path, obj_polygon, i, i); + const auto one_step_intersection_polygons = + calcOverlappingPoints(obj_one_step_polygon, attention_area); + if (!one_step_intersection_polygons.empty()) { + if (!is_start_idx_initialized) { + start_idx = i; + is_start_idx_initialized = true; + } + if (i != obj_path.path.size() - 1) { + // NOTE: Even if the object path does not fully cross the ego path, the path should be + // considered. + continue; + } + } + + if (!is_start_idx_initialized) { + continue; + } + + // Calculate multi-step object polygon, and reset start_idx + const size_t start_idx_with_margin = std::max(static_cast(start_idx) - 1, 0); + const size_t end_idx_with_margin = std::min(i + 1, obj_path.path.size() - 1); + const auto obj_multi_step_polygon = createMultiStepPolygon( + obj_path.path, obj_polygon, start_idx_with_margin, end_idx_with_margin); + is_start_idx_initialized = false; // Calculate intersection points between object and attention area - const auto tmp_intersection = calcOverlappingPoints(obj_one_step_polygon, attention_area); - if (tmp_intersection.empty()) { + const auto multi_step_intersection_polygons = + calcOverlappingPoints(obj_multi_step_polygon, attention_area); + if (multi_step_intersection_polygons.empty()) { continue; } // Calculate nearest collision point among collisions with a predicted path - double local_minimum_stop_dist = std::numeric_limits::max(); - geometry_msgs::msg::Point local_nearest_collision_point{}; - for (const auto & p : tmp_intersection) { - const auto cp = createPoint(p.x(), p.y(), ego_pos.z); - const auto dist_ego2cp = calcSignedArcLength(ego_path.points, ego_pos, cp); - - if (dist_ego2cp < local_minimum_stop_dist) { - local_minimum_stop_dist = dist_ego2cp; - local_nearest_collision_point = cp; - } - } + Point2d boost_intersection_center_point; + bg::centroid(multi_step_intersection_polygons.front(), boost_intersection_center_point); + const auto intersection_center_point = createPoint( + boost_intersection_center_point.x(), boost_intersection_center_point.y(), ego_pos.z); const auto dist_ego2cp = - calcSignedArcLength(ego_path.points, ego_pos, local_nearest_collision_point); + calcSignedArcLength(ego_path.points, ego_pos, intersection_center_point); constexpr double eps = 1e-3; const auto dist_obj2cp = calcArcLength(obj_path.path) < eps ? 0.0 - : calcSignedArcLength(obj_path.path, size_t(0), local_nearest_collision_point); + : calcSignedArcLength(obj_path.path, size_t(0), intersection_center_point); if ( dist_ego2cp < crosswalk_attention_range.first || @@ -644,8 +646,9 @@ std::optional CrosswalkModule::getCollisionPoint( if (dist_ego2cp < minimum_stop_dist) { minimum_stop_dist = dist_ego2cp; nearest_collision_point = createCollisionPoint( - local_nearest_collision_point, dist_ego2cp, dist_obj2cp, ego_vel, obj_vel); - debug_data_.obj_polygons.push_back(toGeometryPointVector(obj_one_step_polygon, ego_pos.z)); + intersection_center_point, dist_ego2cp, dist_obj2cp, ego_vel, obj_vel); + debug_data_.obj_polygons.push_back( + toGeometryPointVector(obj_multi_step_polygon, ego_pos.z)); } break; @@ -753,7 +756,8 @@ Polygon2d CrosswalkModule::getAttentionArea( break; } - const auto ego_one_step_polygon = createOneStepPolygon(p_ego_front, p_ego_back, ego_polygon); + const auto ego_one_step_polygon = + createMultiStepPolygon(sparse_resample_path.points, ego_polygon, j, j + 1); debug_data_.ego_polygons.push_back(toGeometryPointVector(ego_one_step_polygon, ego_pos.z)); From 56c458533f81b5c3201c887aeb52c06232854360 Mon Sep 17 00:00:00 2001 From: Tao Zhong <55872497+tzhong518@users.noreply.github.com> Date: Wed, 30 Aug 2023 13:47:48 +0900 Subject: [PATCH 054/547] fix(crosswalk_traffic_light_estimator): move crosswalk after fusion (#4734) * fix: move crosswalk after fusion Signed-off-by: tzhong518 * Update launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> * Rename TrafficLight to TrafficSignal Signed-off-by: Shunsuke Miura * change input to be considered as the regulatory-element Signed-off-by: Shunsuke Miura --------- Signed-off-by: tzhong518 Signed-off-by: Shunsuke Miura Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Co-authored-by: Shunsuke Miura --- .../traffic_light.launch.xml | 23 +++++- .../traffic_light_node_container.launch.py | 43 ----------- .../node.hpp | 10 ++- .../package.xml | 1 + .../src/node.cpp | 72 +++++++++++-------- 5 files changed, 72 insertions(+), 77 deletions(-) diff --git a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml index ebe3ff6af1893..a9301f31d87aa 100644 --- a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml +++ b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml @@ -11,6 +11,7 @@ + @@ -70,7 +71,7 @@ - + @@ -120,7 +121,7 @@ - + @@ -132,9 +133,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light_node_container.launch.py b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light_node_container.launch.py index 8b3d15f2cff95..a6bcb40e81252 100644 --- a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light_node_container.launch.py +++ b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light_node_container.launch.py @@ -73,7 +73,6 @@ def add_launch_arg(name: str, default_value=None, description=None): add_launch_arg("classifier_mean", "[123.675, 116.28, 103.53]") add_launch_arg("classifier_std", "[58.395, 57.12, 57.375]") - add_launch_arg("use_crosswalk_traffic_light_estimator", "True") add_launch_arg("use_intra_process", "False") add_launch_arg("use_multithread", "False") @@ -137,46 +136,6 @@ def create_parameter_dict(*args): output="both", ) - estimator_loader = LoadComposableNodes( - composable_node_descriptions=[ - ComposableNode( - package="crosswalk_traffic_light_estimator", - plugin="traffic_light::CrosswalkTrafficLightEstimatorNode", - name="crosswalk_traffic_light_estimator", - namespace="classification", - remappings=[ - ("~/input/vector_map", "/map/vector_map"), - ("~/input/route", "/planning/mission_planning/route"), - ("~/input/classified/traffic_signals", "classified/traffic_signals"), - ("~/output/traffic_signals", "estimated/traffic_signals"), - ], - extra_arguments=[{"use_intra_process_comms": False}], - ), - ], - target_container=container, - condition=IfCondition(LaunchConfiguration("use_crosswalk_traffic_light_estimator")), - ) - - relay_loader = LoadComposableNodes( - composable_node_descriptions=[ - ComposableNode( - package="topic_tools", - plugin="topic_tools::RelayNode", - name="classified_signals_relay", - namespace="classification", - parameters=[ - {"input_topic": "classified/traffic_signals"}, - {"output_topic": "estimated/traffic_signals"}, - ], - extra_arguments=[ - {"use_intra_process_comms": LaunchConfiguration("use_intra_process")} - ], - ) - ], - target_container=container, - condition=UnlessCondition(LaunchConfiguration("use_crosswalk_traffic_light_estimator")), - ) - decompressor_loader = LoadComposableNodes( composable_node_descriptions=[ ComposableNode( @@ -251,7 +210,5 @@ def create_parameter_dict(*args): container, decompressor_loader, fine_detector_loader, - estimator_loader, - relay_loader, ] ) diff --git a/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp b/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp index 0850436851e6a..3b4855f1f38e9 100644 --- a/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp +++ b/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -47,9 +48,9 @@ using autoware_planning_msgs::msg::LaneletRoute; using tier4_autoware_utils::DebugPublisher; using tier4_autoware_utils::StopWatch; using tier4_debug_msgs::msg::Float64Stamped; -using tier4_perception_msgs::msg::TrafficLightElement; -using tier4_perception_msgs::msg::TrafficSignal; -using tier4_perception_msgs::msg::TrafficSignalArray; +using TrafficSignal = autoware_perception_msgs::msg::TrafficSignal; +using TrafficSignalArray = autoware_perception_msgs::msg::TrafficSignalArray; +using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; using TrafficSignalAndTime = std::pair; using TrafficLightIdMap = std::unordered_map; @@ -89,6 +90,9 @@ class CrosswalkTrafficLightEstimatorNode : public rclcpp::Node const lanelet::ConstLineStringsOrPolygons3d & traffic_lights, const TrafficLightIdMap & traffic_light_id_map) const; + boost::optional getHighestConfidenceTrafficSignal( + const lanelet::Id & id, const TrafficLightIdMap & traffic_light_id_map) const; + // Node param bool use_last_detect_color_; double last_detect_color_hold_time_; diff --git a/perception/crosswalk_traffic_light_estimator/package.xml b/perception/crosswalk_traffic_light_estimator/package.xml index 435ecf6e6fa3d..f029972f7124f 100644 --- a/perception/crosswalk_traffic_light_estimator/package.xml +++ b/perception/crosswalk_traffic_light_estimator/package.xml @@ -12,6 +12,7 @@ autoware_auto_mapping_msgs autoware_auto_planning_msgs + autoware_perception_msgs autoware_planning_msgs lanelet2_extension rclcpp diff --git a/perception/crosswalk_traffic_light_estimator/src/node.cpp b/perception/crosswalk_traffic_light_estimator/src/node.cpp index 55d272cb71cfe..dd7a9ea04ae38 100644 --- a/perception/crosswalk_traffic_light_estimator/src/node.cpp +++ b/perception/crosswalk_traffic_light_estimator/src/node.cpp @@ -160,7 +160,7 @@ void CrosswalkTrafficLightEstimatorNode::onTrafficLightArray( TrafficLightIdMap traffic_light_id_map; for (const auto & traffic_signal : msg->signals) { - traffic_light_id_map[traffic_signal.traffic_light_id] = + traffic_light_id_map[traffic_signal.traffic_signal_id] = std::pair(traffic_signal, get_clock()->now()); } @@ -191,11 +191,11 @@ void CrosswalkTrafficLightEstimatorNode::updateLastDetectedSignal( continue; } - if (elements.front().color == TrafficLightElement::UNKNOWN) { + if (elements.front().color == TrafficSignalElement::UNKNOWN) { continue; } - const auto & id = input_traffic_signal.second.first.traffic_light_id; + const auto & id = input_traffic_signal.second.first.traffic_signal_id; if (last_detect_color_.count(id) == 0) { last_detect_color_.insert(std::make_pair(id, input_traffic_signal.second)); @@ -207,7 +207,7 @@ void CrosswalkTrafficLightEstimatorNode::updateLastDetectedSignal( std::vector erase_id_list; for (auto & last_traffic_signal : last_detect_color_) { - const auto & id = last_traffic_signal.second.first.traffic_light_id; + const auto & id = last_traffic_signal.second.first.traffic_signal_id; if (traffic_light_id_map.count(id) == 0) { // hold signal recognition results for [last_detect_color_hold_time_] seconds. @@ -227,19 +227,13 @@ void CrosswalkTrafficLightEstimatorNode::setCrosswalkTrafficSignal( const auto tl_reg_elems = crosswalk.regulatoryElementsAs(); for (const auto & tl_reg_elem : tl_reg_elems) { - const auto crosswalk_traffic_lights = tl_reg_elem->trafficLights(); - - for (const auto & traffic_light : crosswalk_traffic_lights) { - const auto ll_traffic_light = static_cast(traffic_light); - - TrafficSignal output_traffic_signal; - TrafficLightElement output_traffic_light; - output_traffic_light.color = color; - output_traffic_light.confidence = 1.0; - output_traffic_signal.elements.push_back(output_traffic_light); - output_traffic_signal.traffic_light_id = ll_traffic_light.id(); - msg.signals.push_back(output_traffic_signal); - } + TrafficSignal output_traffic_signal; + TrafficSignalElement output_traffic_signal_element; + output_traffic_signal_element.color = color; + output_traffic_signal_element.confidence = 1.0; + output_traffic_signal.elements.push_back(output_traffic_signal_element); + output_traffic_signal.traffic_signal_id = tl_reg_elem->id(); + msg.signals.push_back(output_traffic_signal); } } @@ -256,34 +250,32 @@ lanelet::ConstLanelets CrosswalkTrafficLightEstimatorNode::getNonRedLanelets( } const auto tl_reg_elem = tl_reg_elems.front(); - const auto traffic_lights_for_vehicle = tl_reg_elem->trafficLights(); - const auto current_detected_signal = - getHighestConfidenceTrafficSignal(traffic_lights_for_vehicle, traffic_light_id_map); + getHighestConfidenceTrafficSignal(tl_reg_elem->id(), traffic_light_id_map); if (!current_detected_signal && !use_last_detect_color_) { continue; } const auto current_is_not_red = - current_detected_signal ? current_detected_signal.get() == TrafficLightElement::GREEN || - current_detected_signal.get() == TrafficLightElement::AMBER + current_detected_signal ? current_detected_signal.get() == TrafficSignalElement::GREEN || + current_detected_signal.get() == TrafficSignalElement::AMBER : true; const auto current_is_unknown_or_none = - current_detected_signal ? current_detected_signal.get() == TrafficLightElement::UNKNOWN + current_detected_signal ? current_detected_signal.get() == TrafficSignalElement::UNKNOWN : true; const auto last_detected_signal = - getHighestConfidenceTrafficSignal(traffic_lights_for_vehicle, last_detect_color_); + getHighestConfidenceTrafficSignal(tl_reg_elem->id(), last_detect_color_); if (!last_detected_signal) { continue; } const auto was_not_red = current_is_unknown_or_none && - (last_detected_signal.get() == TrafficLightElement::GREEN || - last_detected_signal.get() == TrafficLightElement::AMBER) && + (last_detected_signal.get() == TrafficSignalElement::GREEN || + last_detected_signal.get() == TrafficSignalElement::AMBER) && use_last_detect_color_; if (!current_is_not_red && !was_not_red) { @@ -324,13 +316,13 @@ uint8_t CrosswalkTrafficLightEstimatorNode::estimateCrosswalkTrafficSignal( } if (has_straight_non_red_lane || has_related_non_red_tl) { - return TrafficLightElement::RED; + return TrafficSignalElement::RED; } const auto has_merge_lane = hasMergeLane(non_red_lanelets, routing_graph_ptr_); return !has_merge_lane && has_left_non_red_lane && has_right_non_red_lane - ? TrafficLightElement::RED - : TrafficLightElement::UNKNOWN; + ? TrafficSignalElement::RED + : TrafficSignalElement::UNKNOWN; } boost::optional CrosswalkTrafficLightEstimatorNode::getHighestConfidenceTrafficSignal( @@ -367,6 +359,28 @@ boost::optional CrosswalkTrafficLightEstimatorNode::getHighestConfidenc return ret; } + +boost::optional CrosswalkTrafficLightEstimatorNode::getHighestConfidenceTrafficSignal( + const lanelet::Id & id, const TrafficLightIdMap & traffic_light_id_map) const +{ + boost::optional ret{boost::none}; + + double highest_confidence = 0.0; + if (traffic_light_id_map.count(id) == 0) { + return ret; + } + + for (const auto & element : traffic_light_id_map.at(id).first.elements) { + if (element.confidence < highest_confidence) { + continue; + } + + highest_confidence = element.confidence; + ret = element.color; + } + + return ret; +} } // namespace traffic_light #include From 58a54633edf01dc2f383acbd4237a2ade54a926a Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 30 Aug 2023 14:50:17 +0900 Subject: [PATCH 055/547] feat(crosswalk): no stop decision with braking distance (#4802) * feat(crosswalk): no stop decision with bracking distance Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/crosswalk.param.yaml | 12 +++++++++--- .../src/manager.cpp | 10 ++++++++-- .../src/scene_crosswalk.cpp | 12 +++++++++++- .../src/scene_crosswalk.hpp | 3 +++ 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml b/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml index 4b0a1ffc4317b..b1e5bcb32bd18 100644 --- a/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml +++ b/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml @@ -42,12 +42,18 @@ ego_pass_later_margin_x: [0.0] # [[s]] time to vehicle margin vector for object pass first situation (the module judges that ego don't have to stop at TTV + MARGIN < TTC condition) ego_pass_later_margin_y: [10.0] # [[s]] time to collision margin vector for object pass first situation (the module judges that ego don't have to stop at TTV + MARGIN < TTC condition) ego_pass_later_additional_margin: 0.5 # [s] additional time margin for object pass first situation to suppress chattering - max_offset_to_crosswalk_for_yield: 0.0 # [m] maximum offset from ego's front to crosswalk for yield. Positive value means in front of the crosswalk. + + no_stop_decision: + max_offset_to_crosswalk_for_yield: 0.0 # [m] maximum offset from ego's front to crosswalk for yield. Positive value means in front of the crosswalk. + min_acc: -1.0 # min acceleration [m/ss] + min_jerk: -1.0 # min jerk [m/sss] + max_jerk: 1.0 # max jerk [m/sss] + stop_object_velocity_threshold: 0.28 # [m/s] velocity threshold for the module to judge whether the objects is stopped (0.28 m/s = 1.0 kmph) min_object_velocity: 1.39 # [m/s] minimum object velocity (compare the estimated velocity by perception module with this parameter and adopt the larger one to calculate TTV. 1.39 m/s = 5.0 kmph) ## param for yielding - disable_stop_for_yield_cancel: false # for the crosswalk where there is a traffic signal - disable_yield_for_new_stopped_object: false # for the crosswalk where there is a traffic signal + disable_stop_for_yield_cancel: true # for the crosswalk where there is a traffic signal + disable_yield_for_new_stopped_object: true # for the crosswalk where there is a traffic signal timeout_no_intention_to_walk: 3.0 # [s] if the pedestrian does not move for X seconds after stopping before the crosswalk, the module judge that ego is able to pass first. timeout_ego_stop_for_yield: 3.0 # [s] the amount of time which ego should be stopping to query whether it yields or not diff --git a/planning/behavior_velocity_crosswalk_module/src/manager.cpp b/planning/behavior_velocity_crosswalk_module/src/manager.cpp index 30a1a81a1b50a..5a9de20d79477 100644 --- a/planning/behavior_velocity_crosswalk_module/src/manager.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/manager.cpp @@ -88,8 +88,14 @@ CrosswalkModuleManager::CrosswalkModuleManager(rclcpp::Node & node) getOrDeclareParameter>(node, ns + ".pass_judge.ego_pass_later_margin_y"); cp.ego_pass_later_additional_margin = getOrDeclareParameter(node, ns + ".pass_judge.ego_pass_later_additional_margin"); - cp.max_offset_to_crosswalk_for_yield = - getOrDeclareParameter(node, ns + ".pass_judge.max_offset_to_crosswalk_for_yield"); + cp.max_offset_to_crosswalk_for_yield = getOrDeclareParameter( + node, ns + ".pass_judge.no_stop_decision.max_offset_to_crosswalk_for_yield"); + cp.min_acc_for_no_stop_decision = + getOrDeclareParameter(node, ns + ".pass_judge.no_stop_decision.min_acc"); + cp.max_jerk_for_no_stop_decision = + getOrDeclareParameter(node, ns + ".pass_judge.no_stop_decision.max_jerk"); + cp.min_jerk_for_no_stop_decision = + getOrDeclareParameter(node, ns + ".pass_judge.no_stop_decision.min_jerk"); cp.stop_object_velocity = getOrDeclareParameter(node, ns + ".pass_judge.stop_object_velocity_threshold"); cp.min_object_velocity = diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index b7b2c9c594647..7adde028f59c0 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -309,6 +309,9 @@ std::optional CrosswalkModule::checkStopForCrosswalkUsers( const std::optional & default_stop_pose) { const auto & ego_pos = planner_data_->current_odometry->pose.position; + const double ego_vel = planner_data_->current_velocity->twist.linear.x; + const double ego_acc = planner_data_->current_acceleration->accel.accel.linear.x; + const auto base_link2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; const auto dist_ego_to_stop = calcSignedArcLength(ego_path.points, ego_pos, p_stop_line->first) + p_stop_line->second; @@ -324,11 +327,18 @@ std::optional CrosswalkModule::checkStopForCrosswalkUsers( dist_ego_to_stop, sparse_resample_path, crosswalk_attention_range, attention_area); // Check if ego moves forward enough to ignore yield. + const auto & p = planner_param_; if (!path_intersects.empty()) { const double base_link2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; const double dist_ego2crosswalk = calcSignedArcLength(ego_path.points, ego_pos, path_intersects.front()); - if (dist_ego2crosswalk - base_link2front < planner_param_.max_offset_to_crosswalk_for_yield) { + const auto braking_distance_opt = motion_utils::calcDecelDistWithJerkAndAccConstraints( + ego_vel, 0.0, ego_acc, p.min_acc_for_no_stop_decision, p.max_jerk_for_no_stop_decision, + p.min_jerk_for_no_stop_decision); + const double braking_distance = braking_distance_opt ? *braking_distance_opt : 0.0; + if ( + dist_ego2crosswalk - base_link2front - braking_distance < + p.max_offset_to_crosswalk_for_yield) { return {}; } } diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp index edff62371f6b4..e0851366d7f71 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp @@ -106,6 +106,9 @@ class CrosswalkModule : public SceneModuleInterface std::vector ego_pass_later_margin_y; double ego_pass_later_additional_margin; double max_offset_to_crosswalk_for_yield; + double min_acc_for_no_stop_decision; + double max_jerk_for_no_stop_decision; + double min_jerk_for_no_stop_decision; double stop_object_velocity; double min_object_velocity; bool disable_stop_for_yield_cancel; From 1afe476a1923669cbf14cfcac978050eacaeb5ec Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 30 Aug 2023 15:38:54 +0900 Subject: [PATCH 056/547] feat(intersection): suppress intersection occlusion chattering (#4788) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 1 + .../src/manager.cpp | 2 + .../src/scene_intersection.cpp | 80 ++++++++++++------- .../src/scene_intersection.hpp | 2 + 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 2b3ca11bbe750..fb822cdc5cf4d 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -48,6 +48,7 @@ denoise_kernel: 1.0 # [m] possible_object_bbox: [1.0, 2.0] # [m x m] ignore_parked_vehicle_speed_threshold: 0.333 # == 1.2km/h + stop_release_margin_time: 1.0 # [s] enable_rtc: # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval intersection: true diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 9d6359ebe9b9b..8a4a3e046022d 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -123,6 +123,8 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter>(node, ns + ".occlusion.possible_object_bbox"); ip.occlusion.ignore_parked_vehicle_speed_threshold = getOrDeclareParameter(node, ns + ".occlusion.ignore_parked_vehicle_speed_threshold"); + ip.occlusion.stop_release_margin_time = + getOrDeclareParameter(node, ns + ".occlusion.stop_release_margin_time"); } void IntersectionModuleManager::launchNewModules( diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 7ae549ce8b274..c5fcaca40c24f 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -82,10 +82,19 @@ IntersectionModule::IntersectionModule( turn_direction_ = assigned_lanelet.attributeOr("turn_direction", "else"); collision_state_machine_.setMarginTime( planner_param_.collision_detection.state_transit_margin_time); - before_creep_state_machine_.setMarginTime(planner_param_.occlusion.before_creep_stop_time); - before_creep_state_machine_.setState(StateMachine::State::STOP); - stuck_private_area_timeout_.setMarginTime(planner_param_.stuck_vehicle.timeout_private_area); - stuck_private_area_timeout_.setState(StateMachine::State::STOP); + { + before_creep_state_machine_.setMarginTime(planner_param_.occlusion.before_creep_stop_time); + before_creep_state_machine_.setState(StateMachine::State::STOP); + } + { + occlusion_stop_state_machine_.setMarginTime(planner_param_.occlusion.stop_release_margin_time); + occlusion_stop_state_machine_.setState(StateMachine::State::GO); + } + { + stuck_private_area_timeout_.setMarginTime(planner_param_.stuck_vehicle.timeout_private_area); + stuck_private_area_timeout_.setState(StateMachine::State::STOP); + } + decision_state_pub_ = node_.create_publisher("~/debug/intersection/decision_state", 1); } @@ -639,6 +648,35 @@ void reactRTCApproval( return; } +static std::string formatDecisionResult(const IntersectionModule::DecisionResult & decision_result) +{ + if (std::holds_alternative(decision_result)) { + return "Indecisive"; + } + if (std::holds_alternative(decision_result)) { + return "Safe"; + } + if (std::holds_alternative(decision_result)) { + return "StuckStop"; + } + if (std::holds_alternative(decision_result)) { + return "NonOccludedCollisionStop"; + } + if (std::holds_alternative(decision_result)) { + return "FirstWaitBeforeOcclusion"; + } + if (std::holds_alternative(decision_result)) { + return "PeekingTowardOcclusion"; + } + if (std::holds_alternative(decision_result)) { + return "OccludedCollisionStop"; + } + if (std::holds_alternative(decision_result)) { + return "TrafficLightArrowSolidOn"; + } + return ""; +} + bool IntersectionModule::modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) { debug_data_ = util::DebugData(); @@ -650,31 +688,8 @@ bool IntersectionModule::modifyPathVelocity(PathWithLaneId * path, StopReason * // calculate the const auto decision_result = modifyPathVelocityDetail(path, stop_reason); - std::string decision_type = "intersection" + std::to_string(module_id_) + " : "; - if (std::get_if(&decision_result)) { - decision_type += "Indecisive"; - } - if (std::get_if(&decision_result)) { - decision_type += "StuckStop"; - } - if (std::get_if(&decision_result)) { - decision_type += "NonOccludedCollisionStop"; - } - if (std::get_if(&decision_result)) { - decision_type += "FirstWaitBeforeOcclusion"; - } - if (std::get_if(&decision_result)) { - decision_type += "PeekingTowardOcclusion"; - } - if (std::get_if(&decision_result)) { - decision_type += "OccludedCollisionStop"; - } - if (std::get_if(&decision_result)) { - decision_type += "Safe"; - } - if (std::get_if(&decision_result)) { - decision_type += "TrafficLightArrowSolidOn"; - } + const std::string decision_type = + "intersection" + std::to_string(module_id_) + " : " + formatDecisionResult(decision_result); std_msgs::msg::String decision_result_msg; decision_result_msg.data = decision_type; decision_state_pub_->publish(decision_result_msg); @@ -898,10 +913,15 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( first_attention_area.value(), interpolated_path_info, occlusion_attention_divisions_.value(), parked_attention_objects, occlusion_dist_thr) : true; + occlusion_stop_state_machine_.setStateWithMarginTime( + is_occlusion_cleared ? StateMachine::State::GO : StateMachine::STOP, + logger_.get_child("occlusion_stop"), *clock_); // check safety const bool ext_occlusion_requested = (is_occlusion_cleared && !occlusion_activated_); - if (!is_occlusion_cleared || ext_occlusion_requested) { + if ( + occlusion_stop_state_machine_.getState() == StateMachine::State::STOP || + ext_occlusion_requested) { const double dist_stopline = motion_utils::calcSignedArcLength( path->points, path->points.at(closest_idx).point.pose.position, path->points.at(default_stop_line_idx).point.pose.position); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 774b68710be7f..6684ae5ae0b45 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -109,6 +109,7 @@ class IntersectionModule : public SceneModuleInterface double denoise_kernel; std::vector possible_object_bbox; double ignore_parked_vehicle_speed_threshold; + double stop_release_margin_time; } occlusion; }; @@ -223,6 +224,7 @@ class IntersectionModule : public SceneModuleInterface // OcclusionState prev_occlusion_state_ = OcclusionState::NONE; StateMachine collision_state_machine_; //! for stable collision checking StateMachine before_creep_state_machine_; //! for two phase stop + StateMachine occlusion_stop_state_machine_; // NOTE: uuid_ is base member // for stuck vehicle detection From 198587c9a254940ba072e110e3fb1ab36644265e Mon Sep 17 00:00:00 2001 From: Phoebe Wu Date: Wed, 30 Aug 2023 16:35:22 +0800 Subject: [PATCH 057/547] refactor(radar_fusion_to_detected_object): rework parameters (#4663) * refactor(radar_fusion_to_detected_object): rework parameters Signed-off-by: PhoebeWu21 * style(pre-commit): autofix * style(pre-commit): autofix * style(pre-commit): autofix --------- Signed-off-by: PhoebeWu21 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> --- .../radar_fusion_to_detected_object/README.md | 10 +- ...adar_fusion_to_detected_object.schema.json | 114 ++++++++++++++++++ ..._object_fusion_to_detected_object_node.cpp | 26 ++-- 3 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 perception/radar_fusion_to_detected_object/schema/radar_fusion_to_detected_object.schema.json diff --git a/perception/radar_fusion_to_detected_object/README.md b/perception/radar_fusion_to_detected_object/README.md index b22938cbf0e5a..24eb4eb0e93c1 100644 --- a/perception/radar_fusion_to_detected_object/README.md +++ b/perception/radar_fusion_to_detected_object/README.md @@ -13,11 +13,11 @@ The document of core algorithm is [here](docs/algorithm.md) ### Parameters for sensor fusion -| Name | Type | Description | Default value | -| :----------------------- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | -| bounding_box_margin | double | The distance to extend the 2D bird's-eye view Bounding Box on each side. This distance is used as a threshold to find radar centroids falling inside the extended box. [m] | 2.0 | -| split_threshold_velocity | double | The object's velocity threshold to decide to split for two objects from radar information (currently not implemented) [m/s] | 5.0 | -| threshold_yaw_diff | double | The yaw orientation threshold. If $ \vert \theta _{ob} - \theta_ {ra} \vert < threshold*yaw_diff $ attached to radar information include estimated velocity, where $ \theta*{ob} $ is yaw angle from 3d detected object, $ \theta\_ {ra} $ is yaw angle from radar object. [rad] | 0.35 | +| Name | Type | Description | Default value | +| :----------------------- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| bounding_box_margin | double | The distance to extend the 2D bird's-eye view Bounding Box on each side. This distance is used as a threshold to find radar centroids falling inside the extended box. [m] | 2.0 | +| split_threshold_velocity | double | The object's velocity threshold to decide to split for two objects from radar information (currently not implemented) [m/s] | 5.0 | +| threshold_yaw_diff | double | The yaw orientation threshold. If ∣*θob* − *θra*∣ < threshold × yaw*diff attached to radar information include estimated velocity, where*θob*is yaw angle from 3d detected object,*θra\_ is yaw angle from radar object. [rad] | 0.35 | ### Weight parameters for velocity estimation diff --git a/perception/radar_fusion_to_detected_object/schema/radar_fusion_to_detected_object.schema.json b/perception/radar_fusion_to_detected_object/schema/radar_fusion_to_detected_object.schema.json new file mode 100644 index 0000000000000..688414df56c1e --- /dev/null +++ b/perception/radar_fusion_to_detected_object/schema/radar_fusion_to_detected_object.schema.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Radar Fusion to Detected Object Node", + "type": "object", + "definitions": { + "radar_fusion_to_detected_object": { + "type": "object", + "properties": { + "node_params": { + "type": "object", + "properties": { + "update_rate_hz": { + "type": "number", + "description": "Update rate for parameters. [hz]", + "default": "10.0", + "minimum": 0.0 + } + }, + "required": ["update_rate_hz"] + }, + "core_params": { + "type": "object", + "properties": { + "bounding_box_margin": { + "type": "number", + "description": "The distance to extend the 2D bird's-eye view Bounding Box on each side. This distance is used as a threshold to find radar centroids falling inside the extended box. [m]", + "default": "2.0", + "minimum": 0.0 + }, + "split_threshold_velocity": { + "type": "number", + "description": "The object's velocity threshold to decide to split for two objects from radar information (currently not implemented) [m/s]", + "default": "5.0", + "minimum": 0.0 + }, + "threshold_yaw_diff": { + "type": "number", + "description": "The yaw orientation threshold. If ∣θob−θra∣("node_params.update_rate_hz", 10.0); + node_param_.update_rate_hz = declare_parameter("node_params.update_rate_hz"); // Core Parameter - core_param_.bounding_box_margin = - declare_parameter("core_params.bounding_box_margin", 0.5); + core_param_.bounding_box_margin = declare_parameter("core_params.bounding_box_margin"); core_param_.split_threshold_velocity = - declare_parameter("core_params.split_threshold_velocity", 0.0); - core_param_.threshold_yaw_diff = - declare_parameter("core_params.threshold_yaw_diff", 0.35); + declare_parameter("core_params.split_threshold_velocity"); + core_param_.threshold_yaw_diff = declare_parameter("core_params.threshold_yaw_diff"); core_param_.velocity_weight_min_distance = - declare_parameter("core_params.velocity_weight_min_distance", 1.0); + declare_parameter("core_params.velocity_weight_min_distance"); core_param_.velocity_weight_average = - declare_parameter("core_params.velocity_weight_average", 0.0); + declare_parameter("core_params.velocity_weight_average"); core_param_.velocity_weight_median = - declare_parameter("core_params.velocity_weight_median", 0.0); + declare_parameter("core_params.velocity_weight_median"); core_param_.velocity_weight_target_value_average = - declare_parameter("core_params.velocity_weight_target_value_average", 0.0); + declare_parameter("core_params.velocity_weight_target_value_average"); core_param_.velocity_weight_target_value_top = - declare_parameter("core_params.velocity_weight_target_value_top", 1.0); + declare_parameter("core_params.velocity_weight_target_value_top"); core_param_.convert_doppler_to_twist = - declare_parameter("core_params.convert_doppler_to_twist", false); + declare_parameter("core_params.convert_doppler_to_twist"); core_param_.threshold_probability = - declare_parameter("core_params.threshold_probability", 0.0); + declare_parameter("core_params.threshold_probability"); core_param_.compensate_probability = - declare_parameter("core_params.compensate_probability", false); + declare_parameter("core_params.compensate_probability"); // Core radar_fusion_to_detected_object_ = std::make_unique(get_logger()); From 14deed5b2d3cfb11d3adffb504d2c799dc25bffc Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 30 Aug 2023 17:43:43 +0900 Subject: [PATCH 058/547] feat(intersection): strict definition of stuck vehicle detection area (#4782) Signed-off-by: Mamoru Sobue --- .../README.md | 3 +- .../config/intersection.param.yaml | 1 - .../docs/stuck-vehicle.drawio.svg | 443 +++++++++++------- .../src/manager.cpp | 4 - .../src/scene_intersection.cpp | 34 +- .../src/scene_intersection.hpp | 6 +- .../src/util.cpp | 130 +++-- .../src/util.hpp | 14 +- .../src/util_type.hpp | 3 + 9 files changed, 376 insertions(+), 262 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/README.md b/planning/behavior_velocity_intersection_module/README.md index 3e62ab75d23cd..5f26ef34c1186 100644 --- a/planning/behavior_velocity_intersection_module/README.md +++ b/planning/behavior_velocity_intersection_module/README.md @@ -62,7 +62,7 @@ Objects that satisfy all of the following conditions are considered as target ob #### Stuck Vehicle Detection -If there is any object in a certain distance (`stuck_vehicle_ignore_dist` and `stuck_vehicle_detect_dist`) before and after the exit of the intersection lane and the object velocity is less than a threshold (=`stuck_vehicle_vel_thr`), the object is regarded as a stuck vehicle. If stuck vehicles exist, this module inserts a stopline a certain distance (=`stop_line_margin`) before the overlapped region with other lanes. The stuck vehicle detection area is generated based on the vehicle path, so the stuck vehicle stopline is not inserted if the upstream module generated avoidance path +If there is any object on the path in inside the intersection and at the exit of the intersection (up to `stuck_vehicle_detect_dist`) lane and its velocity is less than a threshold (`stuck_vehicle.stuck_vehicle_vel_thr`), the object is regarded as a stuck vehicle. If stuck vehicles exist, this module inserts a stopline a certain distance (=`stop_line_margin`) before the overlapped region with other lanes. The stuck vehicle detection area is generated based on the vehicle path, so the stuck vehicle stopline is not inserted if the upstream module generated avoidance path ![stuck_vehicle_detection](./docs/stuck-vehicle.drawio.svg) @@ -107,7 +107,6 @@ To avoid a rapid braking, if deceleration and jerk more than a threshold (`behav | `common.stop_overshoot_margin` | double | [m] margin for the overshoot from stopline | | `common.path_interpolation_ds` | double | [m] path interpolation interval | | `stuck_vehicle.stuck_vehicle_detect_dist` | double | [m] length toward from the exit of intersection for stuck vehicle detection | -| `stuck_vehicle.stuck_vehicle_ignore_dist` | double | [m] length behind the exit of intersection for stuck vehicle detection | | `stuck_vehicle.stuck_vehicle_vel_thr` | double | [m/s] velocity threshold for stuck vehicle detection | | `collision_detection.state_transit_margin_time` | double | [m] time margin to change state | | `collision_detection.min_predicted_path_confidence` | double | [-] minimum confidence value of predicted path to use for collision detection | diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index fb822cdc5cf4d..c30c5d70f8c60 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -15,7 +15,6 @@ stuck_vehicle: use_stuck_stopline: true # stopline generated before the first conflicting area stuck_vehicle_detect_dist: 3.0 # this should be the length between cars when they are stopped. The actual stuck vehicle detection length will be this value + vehicle_length. - stuck_vehicle_ignore_dist: 10.0 # obstacle stop max distance(5.0m) + stuck vehicle size / 2 (0.0m-) stuck_vehicle_vel_thr: 0.833 # 0.833m/s = 3.0km/h # enable_front_car_decel_prediction: false # By default this feature is disabled # assumed_front_car_decel: 1.0 # [m/ss] the expected deceleration of front car when front car as well as ego are turning diff --git a/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg b/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg index d2a8286122e54..8802dc786b617 100644 --- a/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg @@ -5,28 +5,28 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - width="2831px" - height="1073px" - viewBox="-0.5 -0.5 2831 1073" - content="<mxfile host="Electron" modified="2023-06-07T05:26:53.508Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="B7vxgoBz0X2z8hjEAQoV" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + width="2842px" + height="1201px" + viewBox="-0.5 -0.5 2842 1201" + content="<mxfile host="Electron" modified="2023-08-28T08:00:31.600Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="CbgId97oWJwBUVWni98L" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > - - - - - - - - - - - - - - + + + + + + + + + + + + + - + - + - + - - - - - + + + + + - + - - - + + + - - - + + + - - - - + + + + + + + - - - - + + + + - - - + + + - - + + - + - - - - - + + + + + - +

@@ -259,22 +262,22 @@
- stuck vehicle detection area is generated on the path around the exit o... + stuck vehicle detection area is generated on the path around the exit o... - - - - - - - - - - - - + + + + + + + + + + + - + - + - + - - - - - + + + + + - + - +
@@ -366,14 +369,14 @@
- %3CmxGraphModel%3E%3Croot%3E%3... + %3CmxGraphModel%3E%3Croot%3E%3... - - - + + + - - - + + + - - - - + + + + - - - - + + + + - - - + + + - - + + - + - - + + - - + + @@ -510,7 +513,7 @@
@@ -521,18 +524,18 @@
- stuck vehicle detection area is generated from the path, so in this... + stuck vehicle detection area is generated from the path, so in this... - - - - - + + + + + -
+
@@ -542,76 +545,158 @@
- stop_line_margin + stop_line_margin - + - - + +
`
- ` + `
+ + + + +
+
+
+ + + stuck_vehicle_detect_dist + + +
+
+
+
+ stuck_vehicle_detect_dist +
+
+ + + +
+
+
+ + + %3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2035px%3B%26quot%3B%26gt%3B%26lt%3Bb%26gt%3Bw%2F%20traffic%20light%2C%20right%26lt%3B%2Fb%26gt%3B%26lt%3Bbr%26gt%3B%26lt%3Bbr%26gt%3B%26lt%3Bb%26gt%3B(Left-hand%20traffic)%26lt%3B%2Fb%26gt%3B%26lt%3B%2Ffont%26gt%3B%26lt%3Bspan%20style%3D%26quot%3Bfont-size%3A%2030px%3B%26quot%3B%26gt%3B%26lt%3Bb%26gt%3B%26lt%3Bbr%26gt%3B%26lt%3B%2Fb%26gt%3B%26lt%3B%2Fspan%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3Bresizable%3D0%3Bpoints%3D%5B%5D%3Bautosize%3D1%3BstrokeColor%3Dnone%3BfillColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22-17325%22%20y%3D%22-7209%22%20width%3D%22350%22%20height%3D%22140%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E + + straight +
+
+ (Right-hand traffic) +
+ + +
+
+
+
+
+
+
+ %3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%... +
+
+ + + +
+
+
+ + + %3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%26lt%3Bfont%20style%3D%26quot%3Bfont-size%3A%2035px%3B%26quot%3B%26gt%3B%26lt%3Bb%26gt%3Bw%2F%20traffic%20light%2C%20right%26lt%3B%2Fb%26gt%3B%26lt%3Bbr%26gt%3B%26lt%3Bbr%26gt%3B%26lt%3Bb%26gt%3B(Left-hand%20traffic)%26lt%3B%2Fb%26gt%3B%26lt%3B%2Ffont%26gt%3B%26lt%3Bspan%20style%3D%26quot%3Bfont-size%3A%2030px%3B%26quot%3B%26gt%3B%26lt%3Bb%26gt%3B%26lt%3Bbr%26gt%3B%26lt%3B%2Fb%26gt%3B%26lt%3B%2Fspan%26gt%3B%22%20style%3D%22text%3Bhtml%3D1%3Balign%3Dleft%3BverticalAlign%3Dmiddle%3Bresizable%3D0%3Bpoints%3D%5B%5D%3Bautosize%3D1%3BstrokeColor%3Dnone%3BfillColor%3Dnone%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22-17325%22%20y%3D%22-7209%22%20width%3D%22350%22%20height%3D%22140%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E + + left turn +
+
+ (Right-hand traffic) +
+ + +
+
+
+
+
+
+
+ %3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%... +
+
diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 8a4a3e046022d..bf0970f3640d1 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -43,7 +43,6 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) { const std::string ns(getModuleName()); auto & ip = intersection_param_; - const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); ip.common.attention_area_margin = getOrDeclareParameter(node, ns + ".common.attention_area_margin"); ip.common.attention_area_length = @@ -68,9 +67,6 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".stuck_vehicle.use_stuck_stopline"); ip.stuck_vehicle.stuck_vehicle_detect_dist = getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_detect_dist"); - ip.stuck_vehicle.stuck_vehicle_ignore_dist = - getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_ignore_dist") + - vehicle_info.max_longitudinal_offset_m; ip.stuck_vehicle.stuck_vehicle_vel_thr = getOrDeclareParameter(node, ns + ".stuck_vehicle.stuck_vehicle_vel_thr"); /* diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index c5fcaca40c24f..0dfadf2f00d7a 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -771,22 +771,18 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( [closest_idx, stuck_stop_line_idx, default_stop_line_idx, occlusion_peeking_stop_line_idx, pass_judge_line_idx] = intersection_stop_lines; + const auto & conflicting_area = intersection_lanelets_.value().conflicting_area(); const auto path_lanelets_opt = util::generatePathLanelets( - lanelets_on_path, *path, associative_ids_, closest_idx, - planner_data_->vehicle_info_.vehicle_width_m); + lanelets_on_path, interpolated_path_info, associative_ids_, first_conflicting_area.value(), + conflicting_area, closest_idx, planner_data_->vehicle_info_.vehicle_width_m); if (!path_lanelets_opt.has_value()) { RCLCPP_DEBUG(logger_, "failed to generate PathLanelets"); return IntersectionModule::Indecisive{}; } const auto path_lanelets = path_lanelets_opt.value(); - const auto ego_lane_with_next_lane = - path_lanelets.next.has_value() - ? std::vector< - lanelet::ConstLanelet>{path_lanelets.ego_or_entry2exit, path_lanelets.next.value()} - : std::vector{path_lanelets.ego_or_entry2exit}; - const bool stuck_detected = - checkStuckVehicle(planner_data_, ego_lane_with_next_lane, *path, intersection_stop_lines); + const bool stuck_detected = checkStuckVehicle( + planner_data_, path_lanelets, interpolated_path_info, intersection_stop_lines); if (stuck_detected) { const double dist_stopline = motion_utils::calcSignedArcLength( @@ -967,9 +963,8 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } bool IntersectionModule::checkStuckVehicle( - const std::shared_ptr & planner_data, - const lanelet::ConstLanelets & ego_lane_with_next_lane, - const autoware_auto_planning_msgs::msg::PathWithLaneId & input_path, + const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets, + const util::InterpolatedPathInfo & interpolated_path_info, const util::IntersectionStopLines & intersection_stop_lines) { const auto & objects_ptr = planner_data->predicted_objects; @@ -978,20 +973,16 @@ bool IntersectionModule::checkStuckVehicle( const auto stuck_line_idx = intersection_stop_lines.stuck_stop_line; // considering lane change in the intersection, these lanelets are generated from the path - const auto ego_lane = ego_lane_with_next_lane.front(); - debug_data_.ego_lane = ego_lane.polygon3d(); + const auto & path = interpolated_path_info.path; const auto stuck_vehicle_detect_area = util::generateStuckVehicleDetectAreaPolygon( - input_path, ego_lane_with_next_lane, closest_idx, - planner_param_.stuck_vehicle.stuck_vehicle_detect_dist, - planner_param_.stuck_vehicle.stuck_vehicle_ignore_dist, - planner_data->vehicle_info_.vehicle_length_m); + path_lanelets, planner_param_.stuck_vehicle.stuck_vehicle_detect_dist); debug_data_.stuck_vehicle_detect_area = toGeomPoly(stuck_vehicle_detect_area); const double dist_stuck_stopline = motion_utils::calcSignedArcLength( - input_path.points, input_path.points.at(stuck_line_idx).point.pose.position, - input_path.points.at(closest_idx).point.pose.position); + path.points, path.points.at(stuck_line_idx).point.pose.position, + path.points.at(closest_idx).point.pose.position); const bool is_over_stuck_stopline = - util::isOverTargetIndex(input_path, closest_idx, current_pose, stuck_line_idx) && + util::isOverTargetIndex(path, closest_idx, current_pose, stuck_line_idx) && (dist_stuck_stopline > planner_param_.common.stop_overshoot_margin); bool is_stuck = false; @@ -1079,6 +1070,7 @@ bool IntersectionModule::checkCollision( const auto closest_arc_coords = getArcCoordinates( concat_lanelets, tier4_autoware_utils::getPose(path.points.at(closest_idx).point)); const auto & ego_lane = path_lanelets.ego_or_entry2exit; + debug_data_.ego_lane = ego_lane.polygon3d(); const auto ego_poly = ego_lane.polygon2d().basicPolygon(); // check collision between predicted_path and ego_area diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 6684ae5ae0b45..245503683e128 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -65,8 +65,6 @@ class IntersectionModule : public SceneModuleInterface { bool use_stuck_stopline; //! stopline generate before the intersection lanelet when is_stuck. double stuck_vehicle_detect_dist; //! distance from end point to finish stuck vehicle check - double stuck_vehicle_ignore_dist; //! distance from intersection start to start stuck vehicle - //! check double stuck_vehicle_vel_thr; //! Threshold of the speed to be recognized as stopped /* double @@ -248,8 +246,8 @@ class IntersectionModule : public SceneModuleInterface bool checkStuckVehicle( const std::shared_ptr & planner_data, - const lanelet::ConstLanelets & ego_lane_with_next_lane, - const autoware_auto_planning_msgs::msg::PathWithLaneId & input_path, + const util::PathLanelets & path_lanelets, + const util::InterpolatedPathInfo & interpolated_path_info, const util::IntersectionStopLines & intersection_stop_lines); autoware_auto_perception_msgs::msg::PredictedObjects filterTargetObjects( diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index a987ce9af9dfe..cd6d941b43de2 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -324,14 +324,25 @@ std::optional generateIntersectionStopLines( std::optional getFirstPointInsidePolygon( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const std::pair lane_interval, const lanelet::CompoundPolygon3d & polygon) + const std::pair lane_interval, const lanelet::CompoundPolygon3d & polygon, + const bool search_forward) { const auto polygon_2d = lanelet::utils::to2D(polygon); - for (size_t i = lane_interval.first; i <= lane_interval.second; ++i) { - auto p = path.points.at(i).point.pose.position; - const auto is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); - if (is_in_lanelet) { - return std::make_optional(i); + if (search_forward) { + for (size_t i = lane_interval.first; i <= lane_interval.second; ++i) { + const auto & p = path.points.at(i).point.pose.position; + const auto is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); + if (is_in_lanelet) { + return std::make_optional(i); + } + } + } else { + for (size_t i = lane_interval.second; i >= lane_interval.first; --i) { + const auto & p = path.points.at(i).point.pose.position; + const auto is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); + if (is_in_lanelet) { + return std::make_optional(i); + } } } return std::nullopt; @@ -341,21 +352,39 @@ static std::optional> getFirstPointInsidePolygons( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const std::pair lane_interval, - const std::vector & polygons) + const std::vector & polygons, const bool search_forward = true) { - for (size_t i = lane_interval.first; i <= lane_interval.second; ++i) { - bool is_in_lanelet = false; - auto p = path.points.at(i).point.pose.position; - for (const auto & polygon : polygons) { - const auto polygon_2d = lanelet::utils::to2D(polygon); - is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); + if (search_forward) { + for (size_t i = lane_interval.first; i <= lane_interval.second; ++i) { + bool is_in_lanelet = false; + const auto & p = path.points.at(i).point.pose.position; + for (const auto & polygon : polygons) { + const auto polygon_2d = lanelet::utils::to2D(polygon); + is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); + if (is_in_lanelet) { + return std::make_optional>( + i, polygon); + } + } if (is_in_lanelet) { - return std::make_optional>( - i, polygon); + break; } } - if (is_in_lanelet) { - break; + } else { + for (size_t i = lane_interval.second; i >= lane_interval.first; --i) { + bool is_in_lanelet = false; + const auto & p = path.points.at(i).point.pose.position; + for (const auto & polygon : polygons) { + const auto polygon_2d = lanelet::utils::to2D(polygon); + is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); + if (is_in_lanelet) { + return std::make_optional>( + i, polygon); + } + } + if (is_in_lanelet) { + break; + } } } return std::nullopt; @@ -919,35 +948,29 @@ bool checkStuckVehicleInIntersection( } Polygon2d generateStuckVehicleDetectAreaPolygon( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const lanelet::ConstLanelets & ego_lane_with_next_lane, const int closest_idx, - const double stuck_vehicle_detect_dist, const double stuck_vehicle_ignore_dist, - const double vehicle_length_m) + const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist) { using lanelet::utils::getArcCoordinates; using lanelet::utils::getLaneletLength3d; using lanelet::utils::getPolygonFromArcLength; using lanelet::utils::to2D; - const double extra_dist = stuck_vehicle_detect_dist + vehicle_length_m; - const double ignore_dist = stuck_vehicle_ignore_dist + vehicle_length_m; - - const double intersection_exit_length = getLaneletLength3d(ego_lane_with_next_lane.front()); - - const auto closest_arc_coords = getArcCoordinates( - ego_lane_with_next_lane, tier4_autoware_utils::getPose(path.points.at(closest_idx).point)); - - const double start_arc_length = intersection_exit_length - ignore_dist > closest_arc_coords.length - ? intersection_exit_length - ignore_dist - : closest_arc_coords.length; - - const double end_arc_length = getLaneletLength3d(ego_lane_with_next_lane.front()) + extra_dist; + Polygon2d polygon{}; + if (path_lanelets.conflicting_interval_and_remaining.size() == 0) { + return polygon; + } + double target_polygon_length = + getLaneletLength3d(path_lanelets.conflicting_interval_and_remaining); + lanelet::ConstLanelets targets = path_lanelets.conflicting_interval_and_remaining; + if (path_lanelets.next) { + targets.push_back(path_lanelets.next.value()); + const double next_arc_length = + std::min(stuck_vehicle_detect_dist, getLaneletLength3d(path_lanelets.next.value())); + target_polygon_length += next_arc_length; + } const auto target_polygon = - to2D(getPolygonFromArcLength(ego_lane_with_next_lane, start_arc_length, end_arc_length)) - .basicPolygon(); - - Polygon2d polygon{}; + to2D(getPolygonFromArcLength(targets, 0, target_polygon_length)).basicPolygon(); if (target_polygon.empty()) { return polygon; @@ -1130,13 +1153,17 @@ static lanelet::ConstLanelets getPrevLanelets( std::optional generatePathLanelets( const lanelet::ConstLanelets & lanelets_on_path, - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const std::set & associative_ids, const size_t closest_idx, const double width) + const util::InterpolatedPathInfo & interpolated_path_info, const std::set & associative_ids, + const lanelet::CompoundPolygon3d & first_conflicting_area, + const std::vector & conflicting_areas, const size_t closest_idx, + const double width) { - const auto assigned_lane_interval_opt = findLaneIdsInterval(path, associative_ids); + const auto & assigned_lane_interval_opt = interpolated_path_info.lane_id_interval; if (!assigned_lane_interval_opt) { return std::nullopt; } + const auto assigned_lane_interval = assigned_lane_interval_opt.value(); + const auto & path = interpolated_path_info.path; PathLanelets path_lanelets; // prev @@ -1144,7 +1171,7 @@ std::optional generatePathLanelets( path_lanelets.all = path_lanelets.prev; // entry2ego if exist - const auto [assigned_lane_start, assigned_lane_end] = assigned_lane_interval_opt.value(); + const auto [assigned_lane_start, assigned_lane_end] = assigned_lane_interval; if (closest_idx > assigned_lane_start) { path_lanelets.all.push_back( planning_utils::generatePathLanelet(path, assigned_lane_start, closest_idx, width)); @@ -1162,12 +1189,27 @@ std::optional generatePathLanelets( const auto next_lane_interval_opt = findLaneIdsInterval(path, {next_id}); if (next_lane_interval_opt) { const auto [next_start, next_end] = next_lane_interval_opt.value(); - path_lanelets.next = - planning_utils::generatePathLanelet(path, next_start + 1, next_end, width); + path_lanelets.next = planning_utils::generatePathLanelet(path, next_start, next_end, width); path_lanelets.all.push_back(path_lanelets.next.value()); } } + const auto first_inside_conflicting_idx_opt = + getFirstPointInsidePolygon(path, assigned_lane_interval, first_conflicting_area); + const auto last_inside_conflicting_idx_opt = + getFirstPointInsidePolygons(path, assigned_lane_interval, conflicting_areas, false); + if (first_inside_conflicting_idx_opt && last_inside_conflicting_idx_opt) { + const auto first_inside_conflicting_idx = first_inside_conflicting_idx_opt.value(); + const auto last_inside_conflicting_idx = last_inside_conflicting_idx_opt.value().first; + lanelet::ConstLanelet conflicting_interval = planning_utils::generatePathLanelet( + path, first_inside_conflicting_idx, last_inside_conflicting_idx, width); + path_lanelets.conflicting_interval_and_remaining.push_back(std::move(conflicting_interval)); + if (last_inside_conflicting_idx < assigned_lane_end) { + lanelet::ConstLanelet remaining_interval = planning_utils::generatePathLanelet( + path, last_inside_conflicting_idx, assigned_lane_end, width); + path_lanelets.conflicting_interval_and_remaining.push_back(std::move(remaining_interval)); + } + } return path_lanelets; } diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 287a75979927b..1361847b4b980 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -83,7 +83,8 @@ std::optional generateIntersectionStopLines( std::optional getFirstPointInsidePolygon( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const std::pair lane_interval, const lanelet::CompoundPolygon3d & polygon); + const std::pair lane_interval, const lanelet::CompoundPolygon3d & polygon, + const bool search_forward = true); /** * @brief check if ego is over the target_idx. If the index is same, compare the exact pose @@ -132,10 +133,7 @@ bool checkStuckVehicleInIntersection( DebugData * debug_data); Polygon2d generateStuckVehicleDetectAreaPolygon( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const lanelet::ConstLanelets & ego_lane_with_next_lane, const int closest_idx, - const double stuck_vehicle_detect_dist, const double stuck_vehicle_ignore_dist, - const double vehicle_length_m); + const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist); bool checkAngleForTargetLanelets( const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, @@ -158,8 +156,10 @@ double calcDistanceUntilIntersectionLanelet( std::optional generatePathLanelets( const lanelet::ConstLanelets & lanelets_on_path, - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const std::set & associative_ids, const size_t closest_idx, const double width); + const util::InterpolatedPathInfo & interpolated_path_info, const std::set & associative_ids, + const lanelet::CompoundPolygon3d & first_conflicting_area, + const std::vector & conflicting_areas, const size_t closest_idx, + const double width); } // namespace util } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 26187d75ff53c..0af797b35b2cc 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -142,6 +142,9 @@ struct PathLanelets std::optional next = std::nullopt; // this is nullopt is the goal is inside intersection lanelet::ConstLanelets all; + lanelet::ConstLanelets + conflicting_interval_and_remaining; // the left/right-most interval of path conflicting with + // conflicting lanelets plus the next lane part of the path }; } // namespace behavior_velocity_planner::util From 741ab23abfdf0b62f6c8848feb9c8d90ab696753 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 30 Aug 2023 12:03:51 +0200 Subject: [PATCH 059/547] build(behavior_velocity_intersection_module): add libopencv-dev dependency (#4814) Signed-off-by: Esteve Fernandez --- planning/behavior_velocity_intersection_module/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_velocity_intersection_module/package.xml b/planning/behavior_velocity_intersection_module/package.xml index 1d6067d4be322..f22e9d788fc65 100644 --- a/planning/behavior_velocity_intersection_module/package.xml +++ b/planning/behavior_velocity_intersection_module/package.xml @@ -23,6 +23,7 @@ geometry_msgs interpolation lanelet2_extension + libopencv-dev magic_enum motion_utils nav_msgs From 812910abf1c8d21de0337d64cd29835ca8c400d1 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Wed, 30 Aug 2023 19:48:12 +0900 Subject: [PATCH 060/547] feat(geography_utils): add geography_utils package (#4807) * first commit Signed-off-by: kminoda * update Signed-off-by: kminoda * style(pre-commit): autofix * update height.cpp Signed-off-by: kminoda * style(pre-commit): autofix * updat Signed-off-by: kminoda * style(pre-commit): autofix * add test Signed-off-by: kminoda * revert tier4_autoware_utils Signed-off-by: kminoda * style(pre-commit): autofix * fix cspell Signed-off-by: kminoda * remove and revert convert.cpp Signed-off-by: kminoda * remove boost Signed-off-by: kminoda * update comment Signed-off-by: kminoda * remove maybe_unsued Signed-off-by: kminoda * style(pre-commit): autofix * rename from tier4_geography_utils to geography_utils Signed-off-by: kminoda * rename from tier4_geography_utils to geography_utils Signed-off-by: kminoda * style(pre-commit): autofix * add some test Signed-off-by: kminoda * style(pre-commit): autofix * edit test Signed-off-by: kminoda * rename namespace Signed-off-by: kminoda * style(pre-commit): autofix * rename namespace complete Signed-off-by: kminoda * style(pre-commit): autofix * use angle brackets inclusion Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- common/geography_utils/CMakeLists.txt | 35 +++++++ common/geography_utils/README.md | 5 + .../include/geography_utils/height.hpp | 36 ++++++++ common/geography_utils/package.xml | 22 +++++ common/geography_utils/src/height.cpp | 63 +++++++++++++ common/geography_utils/test/test_height.cpp | 92 +++++++++++++++++++ .../gnss_poser/include/gnss_poser/convert.hpp | 47 +++++----- sensing/gnss_poser/package.xml | 1 + system/default_ad_api/package.xml | 1 + system/default_ad_api/src/vehicle.cpp | 10 +- 10 files changed, 284 insertions(+), 28 deletions(-) create mode 100644 common/geography_utils/CMakeLists.txt create mode 100644 common/geography_utils/README.md create mode 100644 common/geography_utils/include/geography_utils/height.hpp create mode 100644 common/geography_utils/package.xml create mode 100644 common/geography_utils/src/height.cpp create mode 100644 common/geography_utils/test/test_height.cpp diff --git a/common/geography_utils/CMakeLists.txt b/common/geography_utils/CMakeLists.txt new file mode 100644 index 0000000000000..5cc2290636157 --- /dev/null +++ b/common/geography_utils/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.14) +project(geography_utils) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +# GeographicLib +find_package(PkgConfig) +find_path(GeographicLib_INCLUDE_DIR GeographicLib/Config.h + PATH_SUFFIXES GeographicLib +) +set(GeographicLib_INCLUDE_DIRS ${GeographicLib_INCLUDE_DIR}) +find_library(GeographicLib_LIBRARIES NAMES Geographic) + +ament_auto_add_library(geography_utils SHARED + src/height.cpp +) + +target_link_libraries(geography_utils + ${GeographicLib_LIBRARIES} +) + +if(BUILD_TESTING) + find_package(ament_cmake_ros REQUIRED) + + file(GLOB_RECURSE test_files test/*.cpp) + + ament_add_ros_isolated_gtest(test_geography_utils ${test_files}) + + target_link_libraries(test_geography_utils + geography_utils + ) +endif() + +ament_auto_package() diff --git a/common/geography_utils/README.md b/common/geography_utils/README.md new file mode 100644 index 0000000000000..fb4c2dc3a8312 --- /dev/null +++ b/common/geography_utils/README.md @@ -0,0 +1,5 @@ +# geography_utils + +## Purpose + +This package contains geography-related functions used by other packages, so please refer to them as needed. diff --git a/common/geography_utils/include/geography_utils/height.hpp b/common/geography_utils/include/geography_utils/height.hpp new file mode 100644 index 0000000000000..1921e79699970 --- /dev/null +++ b/common/geography_utils/include/geography_utils/height.hpp @@ -0,0 +1,36 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GEOGRAPHY_UTILS__HEIGHT_HPP_ +#define GEOGRAPHY_UTILS__HEIGHT_HPP_ + +#include +#include +#include +#include + +namespace geography_utils +{ + +typedef double (*HeightConversionFunction)( + const double height, const double latitude, const double longitude); +double convert_wgs84_to_egm2008(const double height, const double latitude, const double longitude); +double convert_egm2008_to_wgs84(const double height, const double latitude, const double longitude); +double convert_height( + const double height, const double latitude, const double longitude, + const std::string & source_vertical_datum, const std::string & target_vertical_datum); + +} // namespace geography_utils + +#endif // GEOGRAPHY_UTILS__HEIGHT_HPP_ diff --git a/common/geography_utils/package.xml b/common/geography_utils/package.xml new file mode 100644 index 0000000000000..61ac473498632 --- /dev/null +++ b/common/geography_utils/package.xml @@ -0,0 +1,22 @@ + + + + geography_utils + 0.1.0 + The geography_utils package + Koji Minoda + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + + geographiclib + + ament_cmake_ros + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/common/geography_utils/src/height.cpp b/common/geography_utils/src/height.cpp new file mode 100644 index 0000000000000..fe69557f25a76 --- /dev/null +++ b/common/geography_utils/src/height.cpp @@ -0,0 +1,63 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include +#include + +namespace geography_utils +{ + +double convert_wgs84_to_egm2008(const double height, const double latitude, const double longitude) +{ + GeographicLib::Geoid egm2008("egm2008-1"); + // cSpell: ignore ELLIPSOIDTOGEOID + return egm2008.ConvertHeight(latitude, longitude, height, GeographicLib::Geoid::ELLIPSOIDTOGEOID); +} + +double convert_egm2008_to_wgs84(const double height, const double latitude, const double longitude) +{ + GeographicLib::Geoid egm2008("egm2008-1"); + // cSpell: ignore GEOIDTOELLIPSOID + return egm2008.ConvertHeight(latitude, longitude, height, GeographicLib::Geoid::GEOIDTOELLIPSOID); +} + +double convert_height( + const double height, const double latitude, const double longitude, + const std::string & source_vertical_datum, const std::string & target_vertical_datum) +{ + if (source_vertical_datum == target_vertical_datum) { + return height; + } + std::map, HeightConversionFunction> conversion_map; + conversion_map[{"WGS84", "EGM2008"}] = convert_wgs84_to_egm2008; + conversion_map[{"EGM2008", "WGS84"}] = convert_egm2008_to_wgs84; + + auto key = std::make_pair(source_vertical_datum, target_vertical_datum); + if (conversion_map.find(key) != conversion_map.end()) { + return conversion_map[key](height, latitude, longitude); + } else { + std::string error_message = + "Invalid conversion types: " + std::string(source_vertical_datum.c_str()) + " to " + + std::string(target_vertical_datum.c_str()); + + throw std::invalid_argument(error_message); + } +} + +} // namespace geography_utils diff --git a/common/geography_utils/test/test_height.cpp b/common/geography_utils/test/test_height.cpp new file mode 100644 index 0000000000000..3d8a4c9c203fe --- /dev/null +++ b/common/geography_utils/test/test_height.cpp @@ -0,0 +1,92 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include +#include + +// Test case to verify if same source and target datums return original height +TEST(Tier4GeographyUtils, SameSourceTargetDatum) +{ + const double height = 10.0; + const double latitude = 35.0; + const double longitude = 139.0; + const std::string datum = "WGS84"; + + double converted_height = + geography_utils::convert_height(height, latitude, longitude, datum, datum); + + EXPECT_DOUBLE_EQ(height, converted_height); +} + +// Test case to verify valid source and target datums +TEST(Tier4GeographyUtils, ValidSourceTargetDatum) +{ + // Calculated with + // https://www.unavco.org/software/geodetic-utilities/geoid-height-calculator/geoid-height-calculator.html + const double height = 10.0; + const double latitude = 35.0; + const double longitude = 139.0; + const double target_height = -30.18; + + double converted_height = + geography_utils::convert_height(height, latitude, longitude, "WGS84", "EGM2008"); + + EXPECT_NEAR(target_height, converted_height, 0.1); +} + +// Test case to verify invalid source and target datums +TEST(Tier4GeographyUtils, InvalidSourceTargetDatum) +{ + const double height = 10.0; + const double latitude = 35.0; + const double longitude = 139.0; + + EXPECT_THROW( + geography_utils::convert_height(height, latitude, longitude, "INVALID1", "INVALID2"), + std::invalid_argument); +} + +// Test case to verify invalid source datums +TEST(Tier4GeographyUtils, InvalidSourceDatum) +{ + const double height = 10.0; + const double latitude = 35.0; + const double longitude = 139.0; + + EXPECT_THROW( + geography_utils::convert_height(height, latitude, longitude, "INVALID1", "WGS84"), + std::invalid_argument); +} + +// Test case to verify invalid target datums +TEST(Tier4GeographyUtils, InvalidTargetDatum) +{ + const double height = 10.0; + const double latitude = 35.0; + const double longitude = 139.0; + + EXPECT_THROW( + geography_utils::convert_height(height, latitude, longitude, "WGS84", "INVALID2"), + std::invalid_argument); +} + +int main(int argc, char ** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/sensing/gnss_poser/include/gnss_poser/convert.hpp b/sensing/gnss_poser/include/gnss_poser/convert.hpp index 74c9734833232..4dda5d1276c54 100644 --- a/sensing/gnss_poser/include/gnss_poser/convert.hpp +++ b/sensing/gnss_poser/include/gnss_poser/convert.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -38,23 +39,7 @@ enum class MGRSPrecision { _1_MIllI_METER = 8, _100MICRO_METER = 9, }; -// EllipsoidHeight:height above ellipsoid -// OrthometricHeight:height above geoid -double EllipsoidHeight2OrthometricHeight( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, const rclcpp::Logger & logger) -{ - double OrthometricHeight{0.0}; - try { - GeographicLib::Geoid egm2008("egm2008-1"); - OrthometricHeight = egm2008.ConvertHeight( - nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, nav_sat_fix_msg.altitude, - GeographicLib::Geoid::ELLIPSOIDTOGEOID); - } catch (const GeographicLib::GeographicErr & err) { - RCLCPP_ERROR_STREAM( - logger, "Failed to convert Height from Ellipsoid to Orthometric" << err.what()); - } - return OrthometricHeight; -} + GNSSStat NavSatFix2UTM( const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, const rclcpp::Logger & logger, int height_system) @@ -65,11 +50,16 @@ GNSSStat NavSatFix2UTM( GeographicLib::UTMUPS::Forward( nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, utm.zone, utm.east_north_up, utm.x, utm.y); + + std::string target_height_system; if (height_system == 0) { - utm.z = EllipsoidHeight2OrthometricHeight(nav_sat_fix_msg, logger); + target_height_system = "EGM2008"; } else { - utm.z = nav_sat_fix_msg.altitude; + target_height_system = "WGS84"; } + utm.z = geography_utils::convert_height( + nav_sat_fix_msg.altitude, nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, "WGS84", + target_height_system); utm.latitude = nav_sat_fix_msg.latitude; utm.longitude = nav_sat_fix_msg.longitude; utm.altitude = nav_sat_fix_msg.altitude; @@ -78,6 +68,7 @@ GNSSStat NavSatFix2UTM( } return utm; } + GNSSStat NavSatFix2LocalCartesianUTM( const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, sensor_msgs::msg::NavSatFix nav_sat_fix_origin, const rclcpp::Logger & logger, int height_system) @@ -89,11 +80,15 @@ GNSSStat NavSatFix2LocalCartesianUTM( GeographicLib::UTMUPS::Forward( nav_sat_fix_origin.latitude, nav_sat_fix_origin.longitude, utm_origin.zone, utm_origin.east_north_up, utm_origin.x, utm_origin.y); + std::string target_height_system; if (height_system == 0) { - utm_origin.z = EllipsoidHeight2OrthometricHeight(nav_sat_fix_origin, logger); + target_height_system = "EGM2008"; } else { - utm_origin.z = nav_sat_fix_origin.altitude; + target_height_system = "WGS84"; } + utm_origin.z = geography_utils::convert_height( + nav_sat_fix_origin.altitude, nav_sat_fix_origin.latitude, nav_sat_fix_origin.longitude, + "WGS84", target_height_system); // individual coordinates of global coordinate system double global_x = 0.0; @@ -107,17 +102,17 @@ GNSSStat NavSatFix2LocalCartesianUTM( // individual coordinates of local coordinate system utm_local.x = global_x - utm_origin.x; utm_local.y = global_y - utm_origin.y; - if (height_system == 0) { - utm_local.z = EllipsoidHeight2OrthometricHeight(nav_sat_fix_msg, logger) - utm_origin.z; - } else { - utm_local.z = nav_sat_fix_msg.altitude - utm_origin.z; - } + utm_local.z = geography_utils::convert_height( + nav_sat_fix_msg.altitude, nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, + "WGS84", target_height_system) - + utm_origin.z; } catch (const GeographicLib::GeographicErr & err) { RCLCPP_ERROR_STREAM( logger, "Failed to convert from LLH to UTM in local coordinates" << err.what()); } return utm_local; } + GNSSStat UTM2MGRS( const GNSSStat & utm, const MGRSPrecision & precision, const rclcpp::Logger & logger) { diff --git a/sensing/gnss_poser/package.xml b/sensing/gnss_poser/package.xml index 1488f329edecb..77380bf5492a1 100644 --- a/sensing/gnss_poser/package.xml +++ b/sensing/gnss_poser/package.xml @@ -15,6 +15,7 @@ autoware_sensing_msgs geographiclib + geography_utils geometry_msgs rclcpp rclcpp_components diff --git a/system/default_ad_api/package.xml b/system/default_ad_api/package.xml index dfceeabe41122..fd61d3c62e397 100644 --- a/system/default_ad_api/package.xml +++ b/system/default_ad_api/package.xml @@ -23,6 +23,7 @@ autoware_planning_msgs component_interface_specs component_interface_utils + geography_utils lanelet2_extension motion_utils nav_msgs diff --git a/system/default_ad_api/src/vehicle.cpp b/system/default_ad_api/src/vehicle.cpp index fb434adbb0e40..c8f7df315c487 100644 --- a/system/default_ad_api/src/vehicle.cpp +++ b/system/default_ad_api/src/vehicle.cpp @@ -14,6 +14,8 @@ #include "vehicle.hpp" +#include + #include namespace default_ad_api @@ -153,7 +155,9 @@ void VehicleNode::publish_kinematics() vehicle_kinematics.geographic_pose.header.frame_id = "global"; vehicle_kinematics.geographic_pose.position.latitude = projected_gps_point.lat; vehicle_kinematics.geographic_pose.position.longitude = projected_gps_point.lon; - vehicle_kinematics.geographic_pose.position.altitude = projected_gps_point.ele; + vehicle_kinematics.geographic_pose.position.altitude = geography_utils::convert_height( + projected_gps_point.ele, projected_gps_point.lat, projected_gps_point.lon, + map_projector_info_->vertical_datum, "WGS84"); } else if (map_projector_info_->projector_type == MapProjectorInfo::LOCAL_CARTESIAN_UTM) { lanelet::GPSPoint position{ map_projector_info_->map_origin.latitude, map_projector_info_->map_origin.longitude}; @@ -165,7 +169,9 @@ void VehicleNode::publish_kinematics() vehicle_kinematics.geographic_pose.header.frame_id = "global"; vehicle_kinematics.geographic_pose.position.latitude = projected_gps_point.lat; vehicle_kinematics.geographic_pose.position.longitude = projected_gps_point.lon; - vehicle_kinematics.geographic_pose.position.altitude = projected_gps_point.ele; + vehicle_kinematics.geographic_pose.position.altitude = geography_utils::convert_height( + projected_gps_point.ele, projected_gps_point.lat, projected_gps_point.lon, + map_projector_info_->vertical_datum, "WGS84"); } else { vehicle_kinematics.geographic_pose.position.latitude = std::numeric_limits::quiet_NaN(); vehicle_kinematics.geographic_pose.position.longitude = From 5f04a370b2712aa9c42943e4d2866a1577169d6f Mon Sep 17 00:00:00 2001 From: Keisuke Shima <19993104+KeisukeShima@users.noreply.github.com> Date: Wed, 30 Aug 2023 20:59:28 +0900 Subject: [PATCH 061/547] fix(lane_change): fix lane_change document (#4817) Signed-off-by: Keisuke Shima <19993104+KeisukeShima@users.noreply.github.com> --- .../docs/behavior_path_planner_lane_change_design.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md index fddd535931d94..2a04580aef5b5 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md @@ -55,12 +55,12 @@ where `common_param` is vehicle common parameter, which defines vehicle common m The `longitudinal_acceleration_resolution` is determine by the following ```C++ -longitudinal_acceleration_resolution = (maximum_longitudinal_acceleration - minimum_longitudinal_deceleration) / longitudinal_acceleration_sampling_num +longitudinal_acceleration_resolution = (maximum_longitudinal_acceleration - minimum_longitudinal_acceleration) / longitudinal_acceleration_sampling_num ``` Note that when the `current_velocity` is lower than `minimum_lane_changing_velocity`, the vehicle needs to accelerate its velocity to `minimum_lane_changing_velocity`. Therefore, longitudinal acceleration becomes positive value (not decelerate). -The following figure illustrates when `lane_change_sampling_num = 4`. Assuming that `maximum_deceleration = 1.0` then `a0 == 0.0 == no deceleration`, `a1 == 0.25`, `a2 == 0.5`, `a3 == 0.75` and `a4 == 1.0 == maximum_deceleration`. `a0` is the expected lane change trajectories should ego vehicle do not decelerate, and `a1`'s path is the expected lane change trajectories should ego vehicle decelerate at `0.25 m/s^2`. +The following figure illustrates when `longitudinal_acceleration_sampling_num = 4`. Assuming that `maximum_deceleration = 1.0` then `a0 == 0.0 == no deceleration`, `a1 == 0.25`, `a2 == 0.5`, `a3 == 0.75` and `a4 == 1.0 == maximum_deceleration`. `a0` is the expected lane change trajectories should ego vehicle do not decelerate, and `a1`'s path is the expected lane change trajectories should ego vehicle decelerate at `0.25 m/s^2`. ![path_samples](../image/lane_change/lane_change-candidate_path_samples.png) @@ -79,12 +79,12 @@ The maximum and minimum lateral accelerations are defined in the lane change par | 4.0 | 0.3 | 0.4 | | 6.0 | 0.3 | 0.5 | -In this case, when the current velocity of the ego vehicle is 3.0, the minimum and maximum lateral accelerations are 0.2 and 0.35 respectively. These values are obtained by linearly interpolating the second and third rows of the map, which provide the minimum and maximum lateral acceleration values. +In this case, when the current velocity of the ego vehicle is 3.0, the minimum and maximum lateral accelerations are 0.25 and 0.4 respectively. These values are obtained by linearly interpolating the second and third rows of the map, which provide the minimum and maximum lateral acceleration values. Within this range, we sample the lateral acceleration for the ego vehicle. Similar to the method used for sampling longitudinal acceleration, the resolution of lateral acceleration (lateral_acceleration_resolution) is determined by the following: ```C++ -lateral_acceleration_resolution = (maximum_lateral_acceleration - minimum_lateral_deceleration) / lateral_acceleration_sampling_num +lateral_acceleration_resolution = (maximum_lateral_acceleration - minimum_lateral_acceleration) / lateral_acceleration_sampling_num ``` #### Candidate Path's validity check @@ -280,7 +280,6 @@ The following parameters are configurable in `lane_change.param.yaml`. | `object_shiftable_ratio_threshold` | [-] | double | Vehicles around the center line within this distance ratio will be excluded from parking objects | 0.6 | | `min_length_for_turn_signal_activation` | [m] | double | Turn signal will be activated if the ego vehicle approaches to this length from minimum lane change length | 10.0 | | `length_ratio_for_turn_signal_deactivation` | [-] | double | Turn signal will be deactivated if the ego vehicle approaches to this length ratio for lane change finish point | 0.8 | -| `object_shiftable_ratio_threshold` | [-] | double | Vehicles around the center line within this distance ratio will be excluded from parking objects | 0.6 | | `max_longitudinal_acc` | [-] | double | maximum longitudinal acceleration for lane change | 1.0 | | `min_longitudinal_acc` | [-] | double | maximum longitudinal deceleration for lane change | -1.0 | | `lateral_acceleration.velocity` | [m/s] | double | Reference velocity for lateral acceleration calculation (look up table) | [0.0, 4.0, 10.0] | From 058680d2eb17df82d3ca79961c0c5e2b483b842e Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Wed, 30 Aug 2023 23:12:54 +0900 Subject: [PATCH 062/547] fix(goal_planner): set correct reference path (#4809) Signed-off-by: satoshi-ota --- .../scene_module/goal_planner/goal_planner_module.cpp | 10 ++++++++++ .../utils/goal_planner/default_fixed_goal_planner.cpp | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index f37149f706436..3574abe7b1185 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -520,8 +520,13 @@ void GoalPlannerModule::generateGoalCandidates() BehaviorModuleOutput GoalPlannerModule::plan() { + resetPathCandidate(); + resetPathReference(); + generateGoalCandidates(); + path_reference_ = getPreviousModuleOutput().reference_path; + if (goal_planner_utils::isAllowedGoalModification( planner_data_->route_handler, left_side_parking_)) { return planWithGoalModification(); @@ -840,6 +845,11 @@ BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() BehaviorModuleOutput GoalPlannerModule::planWaitingApproval() { + resetPathCandidate(); + resetPathReference(); + + path_reference_ = getPreviousModuleOutput().reference_path; + if (goal_planner_utils::isAllowedGoalModification( planner_data_->route_handler, left_side_parking_)) { return planWaitingApprovalWithGoalModification(); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp b/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp index 5d77027efa5d0..7efdbdf1552d5 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp @@ -35,7 +35,7 @@ BehaviorModuleOutput DefaultFixedGoalPlanner::plan( const PathWithLaneId smoothed_path = modifyPathForSmoothGoalConnection(*(output.path), planner_data); output.path = std::make_shared(smoothed_path); - output.reference_path = std::make_shared(smoothed_path); + output.reference_path = getPreviousModuleOutput().reference_path; return output; } From 54b3958489b8f527f49ed4addcc57431be796e2f Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Thu, 31 Aug 2023 01:04:35 +0900 Subject: [PATCH 063/547] feat(dynamic_avoidance): generate drivable area by ego/object-path-based depending on the case (#4813) * feat(dynamic_avoidance): generate drivable area by ego/object-path-based depending on the case Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka * apply clang-format Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../dynamic_avoidance_module.hpp | 18 +++-- .../dynamic_avoidance_module.cpp | 74 +++++++++++++------ .../dynamic_avoidance/manager.cpp | 5 -- 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp index 3c302d934735c..fccc27051380e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp @@ -74,7 +74,6 @@ struct DynamicAvoidanceParameters double max_oncoming_crossing_object_angle{0.0}; // drivable area generation - std::string polygon_generation_method{}; double min_obj_path_based_lon_polygon_margin{0.0}; double lat_offset_from_obstacle{0.0}; double max_lat_offset_to_avoid{0.0}; @@ -94,6 +93,10 @@ struct DynamicAvoidanceParameters class DynamicAvoidanceModule : public SceneModuleInterface { public: + enum class PolygonGenerationMethod { + EGO_PATH_BASE = 0, + OBJECT_PATH_BASE, + }; struct DynamicAvoidanceObject { DynamicAvoidanceObject( @@ -128,15 +131,18 @@ class DynamicAvoidanceModule : public SceneModuleInterface std::optional lat_offset_to_avoid{std::nullopt}; bool is_collision_left; bool should_be_avoided{false}; + PolygonGenerationMethod polygon_generation_method{PolygonGenerationMethod::OBJECT_PATH_BASE}; void update( const MinMaxValue & arg_lon_offset_to_avoid, const MinMaxValue & arg_lat_offset_to_avoid, - const bool arg_is_collision_left, const bool arg_should_be_avoided) + const bool arg_is_collision_left, const bool arg_should_be_avoided, + const PolygonGenerationMethod & arg_polygon_generation_method) { lon_offset_to_avoid = arg_lon_offset_to_avoid; lat_offset_to_avoid = arg_lat_offset_to_avoid; is_collision_left = arg_is_collision_left; should_be_avoided = arg_should_be_avoided; + polygon_generation_method = arg_polygon_generation_method; } }; @@ -230,11 +236,12 @@ class DynamicAvoidanceModule : public SceneModuleInterface void updateObject( const std::string & uuid, const MinMaxValue & lon_offset_to_avoid, const MinMaxValue & lat_offset_to_avoid, const bool is_collision_left, - const bool should_be_avoided) + const bool should_be_avoided, const PolygonGenerationMethod & polygon_generation_method) { if (object_map_.count(uuid) != 0) { object_map_.at(uuid).update( - lon_offset_to_avoid, lat_offset_to_avoid, is_collision_left, should_be_avoided); + lon_offset_to_avoid, lat_offset_to_avoid, is_collision_left, should_be_avoided, + polygon_generation_method); } } @@ -312,7 +319,8 @@ class DynamicAvoidanceModule : public SceneModuleInterface void updateTargetObjects(); bool willObjectCutIn( const std::vector & ego_path, const PredictedPath & predicted_path, - const double obj_tangent_vel, const LatLonOffset & lat_lon_offset) const; + const double obj_tangent_vel, const LatLonOffset & lat_lon_offset, + PolygonGenerationMethod & polygon_generation_method) const; DecisionWithReason willObjectCutOut( const double obj_tangent_vel, const double obj_normal_vel, const bool is_object_left, const std::optional & prev_object) const; diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index 0bfda71a09079..da98c0478e580 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -63,7 +63,7 @@ void appendObjectMarker(MarkerArray & marker_array, const geometry_msgs::msg::Po } void appendExtractedPolygonMarker( - MarkerArray & marker_array, const tier4_autoware_utils::Polygon2d & obj_poly) + MarkerArray & marker_array, const tier4_autoware_utils::Polygon2d & obj_poly, const double obj_z) { auto marker = tier4_autoware_utils::createDefaultMarker( "map", rclcpp::Clock{RCL_ROS_TIME}.now(), "extracted_polygons", marker_array.markers.size(), @@ -78,6 +78,7 @@ void appendExtractedPolygonMarker( geometry_msgs::msg::Point bound_geom_point; bound_geom_point.x = bound_point.x(); bound_geom_point.y = bound_point.y(); + bound_geom_point.z = obj_z; marker.points.push_back(bound_geom_point); } @@ -151,6 +152,25 @@ double calcDiffAngleAgainstPath( return diff_yaw; } +double calcDiffAngleBetweenPaths( + const std::vector & path_points, const PredictedPath & predicted_path) +{ + const size_t nearest_idx = + motion_utils::findNearestSegmentIndex(path_points, predicted_path.path.front().position); + const double ego_yaw = tf2::getYaw(path_points.at(nearest_idx).point.pose.orientation); + + constexpr size_t max_predicted_path_size = 5; + double signed_max_angle{0.0}; + for (size_t i = 0; i < std::min(max_predicted_path_size, predicted_path.path.size()); ++i) { + const double obj_yaw = tf2::getYaw(predicted_path.path.at(i).orientation); + const double diff_yaw = tier4_autoware_utils::normalizeRadian(obj_yaw - ego_yaw); + if (std::abs(signed_max_angle) < std::abs(diff_yaw)) { + signed_max_angle = diff_yaw; + } + } + return signed_max_angle; +} + double calcDistanceToPath( const std::vector & path_points, const geometry_msgs::msg::Point & target_pos) @@ -281,10 +301,10 @@ BehaviorModuleOutput DynamicAvoidanceModule::plan() std::vector obstacles_for_drivable_area; for (const auto & object : target_objects_) { const auto obstacle_poly = [&]() { - if (parameters_->polygon_generation_method == "ego_path_base") { + if (object.polygon_generation_method == PolygonGenerationMethod::EGO_PATH_BASE) { return calcEgoPathBasedDynamicObstaclePolygon(object); } - if (parameters_->polygon_generation_method == "object_path_base") { + if (object.polygon_generation_method == PolygonGenerationMethod::OBJECT_PATH_BASE) { return calcObjectPathBasedDynamicObstaclePolygon(object); } throw std::logic_error("The polygon_generation_method's string is invalid."); @@ -294,7 +314,7 @@ BehaviorModuleOutput DynamicAvoidanceModule::plan() {object.pose, obstacle_poly.value(), object.is_collision_left}); appendObjectMarker(info_marker_, object.pose); - appendExtractedPolygonMarker(debug_marker_, obstacle_poly.value()); + appendExtractedPolygonMarker(debug_marker_, obstacle_poly.value(), object.pose.position.z); } } @@ -375,6 +395,10 @@ void DynamicAvoidanceModule::updateTargetObjects() predicted_object.kinematics.initial_twist_with_covariance.twist.linear.x, predicted_object.kinematics.initial_twist_with_covariance.twist.linear.y); const auto prev_object = getObstacleFromUuid(prev_objects, obj_uuid); + const auto obj_path = *std::max_element( + predicted_object.kinematics.predicted_paths.begin(), + predicted_object.kinematics.predicted_paths.end(), + [](const PredictedPath & a, const PredictedPath & b) { return a.confidence < b.confidence; }); // 1.a. check label const bool is_label_target_obstacle = @@ -391,7 +415,7 @@ void DynamicAvoidanceModule::updateTargetObjects() } // 1.c. check if object is not crossing ego's path - const double obj_angle = calcDiffAngleAgainstPath(prev_module_path->points, obj_pose); + const double obj_angle = calcDiffAngleBetweenPaths(prev_module_path->points, obj_path); const double max_crossing_object_angle = 0.0 <= obj_tangent_vel ? parameters_->max_overtaking_crossing_object_angle : parameters_->max_oncoming_crossing_object_angle; @@ -448,6 +472,7 @@ void DynamicAvoidanceModule::updateTargetObjects() // 2. Precise filtering of target objects and check if they should be avoided for (const auto & object : target_objects_manager_.getValidObjects()) { + PolygonGenerationMethod polygon_generation_method{PolygonGenerationMethod::EGO_PATH_BASE}; const auto obj_uuid = object.uuid; const auto prev_object = getObstacleFromUuid(prev_objects, obj_uuid); const auto obj_path = *std::max_element( @@ -472,8 +497,8 @@ void DynamicAvoidanceModule::updateTargetObjects() getLateralLongitudinalOffset(prev_module_path->points, object.pose, object.shape); // 2.c. check if object will not cut in - const bool will_object_cut_in = - willObjectCutIn(prev_module_path->points, obj_path, object.vel, lat_lon_offset); + const bool will_object_cut_in = willObjectCutIn( + prev_module_path->points, obj_path, object.vel, lat_lon_offset, polygon_generation_method); if (will_object_cut_in) { RCLCPP_INFO_EXPRESSION( getLogger(), parameters_->enable_debug_info, @@ -551,7 +576,8 @@ void DynamicAvoidanceModule::updateTargetObjects() const bool should_be_avoided = true; target_objects_manager_.updateObject( - obj_uuid, lon_offset_to_avoid, lat_offset_to_avoid, is_collision_left, should_be_avoided); + obj_uuid, lon_offset_to_avoid, lat_offset_to_avoid, is_collision_left, should_be_avoided, + polygon_generation_method); } } @@ -627,16 +653,26 @@ bool DynamicAvoidanceModule::isObjectFarFromPath( bool DynamicAvoidanceModule::willObjectCutIn( const std::vector & ego_path, const PredictedPath & predicted_path, - const double obj_tangent_vel, const LatLonOffset & lat_lon_offset) const + const double obj_tangent_vel, const LatLonOffset & lat_lon_offset, + PolygonGenerationMethod & polygon_generation_method) const { - constexpr double epsilon_path_lat_diff = 0.3; - - // Ignore oncoming object - if (obj_tangent_vel < 0) { + // Check if ego's path and object's path are close. + const bool will_object_cut_in = [&]() { + for (const auto & predicted_path_point : predicted_path.path) { + const double paths_lat_diff = + motion_utils::calcLateralOffset(ego_path, predicted_path_point.position); + if (std::abs(paths_lat_diff) < planner_data_->parameters.vehicle_width / 2.0) { + return true; + } + } + return false; + }(); + if (!will_object_cut_in) { + // The object's path will not cut in return false; } - // Ignore object close to the ego + // Ignore object longitudinally close to the ego const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(ego_path); const double relative_velocity = getEgoSpeed() - obj_tangent_vel; const double lon_offset_ego_to_obj = @@ -647,17 +683,11 @@ bool DynamicAvoidanceModule::willObjectCutIn( lon_offset_ego_to_obj < std::max( parameters_->min_lon_offset_ego_to_cut_in_object, relative_velocity * parameters_->min_time_to_start_cut_in)) { + polygon_generation_method = PolygonGenerationMethod::EGO_PATH_BASE; return false; } - for (const auto & predicted_path_point : predicted_path.path) { - const double paths_lat_diff = - motion_utils::calcLateralOffset(ego_path, predicted_path_point.position); - if (std::abs(paths_lat_diff) < epsilon_path_lat_diff) { - return true; - } - } - return false; + return true; } DynamicAvoidanceModule::DecisionWithReason DynamicAvoidanceModule::willObjectCutOut( diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp index c77d81457c15d..82ebf59ea5bb2 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp @@ -81,8 +81,6 @@ DynamicAvoidanceModuleManager::DynamicAvoidanceModuleManager( { // drivable_area_generation std::string ns = "dynamic_avoidance.drivable_area_generation."; - p.polygon_generation_method = - node->declare_parameter(ns + "polygon_generation_method"); p.min_obj_path_based_lon_polygon_margin = node->declare_parameter(ns + "object_path_base.min_longitudinal_polygon_margin"); p.lat_offset_from_obstacle = node->declare_parameter(ns + "lat_offset_from_obstacle"); @@ -181,9 +179,6 @@ void DynamicAvoidanceModuleManager::updateModuleParams( { // drivable_area_generation const std::string ns = "dynamic_avoidance.drivable_area_generation."; - - updateParam( - parameters, ns + "polygon_generation_method", p->polygon_generation_method); updateParam( parameters, ns + "object_path_base.min_longitudinal_polygon_margin", p->min_obj_path_based_lon_polygon_margin); From 0b2afec2610c46aa363672f11ad0a88f660e9c1b Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Thu, 31 Aug 2023 01:04:51 +0900 Subject: [PATCH 064/547] fix(crosswalk): fix plotter data (#4822) Signed-off-by: Takayuki Murooka --- .../scripts/time_to_collision_plotter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py b/planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py index 5093683e17c22..8a9de1a563249 100755 --- a/planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py +++ b/planning/behavior_velocity_crosswalk_module/scripts/time_to_collision_plotter.py @@ -109,7 +109,9 @@ def on_collision_info(self, msg): return for i in range(int(len(collision_info_str_vec) / 5)): - collision_info_data_vec.append(CollisionInfo(*collision_info_str_vec[i : i + 5])) + collision_info_data_vec.append( + CollisionInfo(*collision_info_str_vec[i * 5 : i * 5 + 5]) + ) # memorize data in the history dictionary for collision_info_data in collision_info_data_vec: From 721d9d34ab80b6a97eb78ab919f85d5bbfb275ed Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 31 Aug 2023 05:09:04 +0900 Subject: [PATCH 065/547] chore(map_base_prediction): add maintainer (#4825) add maintainer Signed-off-by: kyoichi-sugahara --- perception/map_based_prediction/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/perception/map_based_prediction/package.xml b/perception/map_based_prediction/package.xml index a76310972c738..9b7b32e686cfb 100644 --- a/perception/map_based_prediction/package.xml +++ b/perception/map_based_prediction/package.xml @@ -9,6 +9,7 @@ Shunsuke Miura Yoshi Ri Takayuki Murooka + Kyoichi Sugahara Apache License 2.0 ament_cmake From 8f7112537ded9db3969b5a35ceaacb57050ee4b1 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 31 Aug 2023 09:06:15 +0900 Subject: [PATCH 066/547] chore(build): include missing lanelet2 header (#4819) Signed-off-by: Mamoru Sobue --- localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp | 1 + map/util/lanelet2_map_preprocessor/src/fix_lane_change_tags.cpp | 1 + perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp b/localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp index d0279caf88dda..427bf513058d4 100644 --- a/localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp +++ b/localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp @@ -19,6 +19,7 @@ #include #include +#include #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/map/util/lanelet2_map_preprocessor/src/fix_lane_change_tags.cpp b/map/util/lanelet2_map_preprocessor/src/fix_lane_change_tags.cpp index 601e8dfa0117f..8900381060472 100644 --- a/map/util/lanelet2_map_preprocessor/src/fix_lane_change_tags.cpp +++ b/map/util/lanelet2_map_preprocessor/src/fix_lane_change_tags.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp b/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp index 3baefaa43163a..fadcb767f347b 100644 --- a/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp +++ b/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include From 053d31f0c5e9d7dfbf4b4d3257e28dc133d03bbe Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Thu, 31 Aug 2023 09:22:45 +0900 Subject: [PATCH 067/547] refactor(goal_planner): remove unncecessary variable (#4810) rafactor(goal_planner): remove unncecessary variable Signed-off-by: kosuke55 --- .../scene_module/goal_planner/goal_planner_module.hpp | 1 - .../src/scene_module/goal_planner/goal_planner_module.cpp | 4 ---- 2 files changed, 5 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 8d9c8e147f532..964e0ff87594a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -177,7 +177,6 @@ class GoalPlannerModule : public SceneModuleInterface // for parking policy bool left_side_parking_{true}; - mutable bool allow_goal_modification_{false}; // need to be set in isExecutionRequested // pre-generate lane parking paths in a separate thread rclcpp::TimerBase::SharedPtr lane_parking_timer_; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 3574abe7b1185..6c7853ac0e54f 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -273,10 +273,6 @@ bool GoalPlannerModule::isExecutionRequested() const const Pose & current_pose = planner_data_->self_odometry->pose.pose; const Pose & goal_pose = route_handler->getGoalPose(); - // if goal is shoulder lane, allow goal modification - allow_goal_modification_ = - route_handler->isAllowedGoalModification() || checkOriginalGoalIsInShoulder(); - // check if goal_pose is in current_lanes. lanelet::ConstLanelet current_lane{}; const lanelet::ConstLanelets current_lanes = utils::getCurrentLanes(planner_data_); From 2df7540ce5faa2681427abc22b1488ec45d722bc Mon Sep 17 00:00:00 2001 From: "awf-autoware-bot[bot]" <94889083+awf-autoware-bot[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 02:00:50 +0000 Subject: [PATCH 068/547] chore: update CODEOWNERS (#4759) Signed-off-by: GitHub Co-authored-by: kenji-miyake Co-authored-by: kminoda <44218668+kminoda@users.noreply.github.com> --- .github/CODEOWNERS | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index fb6df15ab7679..03f9f22b56015 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,6 +14,7 @@ common/component_interface_tools/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp m common/component_interface_utils/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp makoto.yabuta@tier4.jp yukihiro.saito@tier4.jp common/cuda_utils/** daisuke.nishimatsu@tier4.jp manato.hirabayashi@tier4.jp common/fake_test_node/** opensource@apex.ai satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp +common/geography_utils/** koji.minoda@tier4.jp common/global_parameter_loader/** ryohsuke.mitsudome@tier4.jp common/glog_component/** takamasa.horibe@tier4.jp common/goal_distance_calculator/** taiki.tanaka@tier4.jp @@ -117,7 +118,7 @@ perception/lidar_apollo_segmentation_tvm/** ambroise.vincent@arm.com xinyu.wang@ perception/lidar_apollo_segmentation_tvm_nodes/** ambroise.vincent@arm.com xinyu.wang@tier4.jp perception/lidar_centerpoint/** kenzo.lobos@tier4.jp yusuke.muramatsu@tier4.jp perception/lidar_centerpoint_tvm/** carl.liu@autocore.ai xinyu.wang@tier4.jp -perception/map_based_prediction/** shunsuke.miura@tier4.jp tomoya.kimura@tier4.jp yoshi.ri@tier4.jp yutaka.shimizu@tier4.jp +perception/map_based_prediction/** kyoichi.sugahara@tier4.jp shunsuke.miura@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp yoshi.ri@tier4.jp yutaka.shimizu@tier4.jp perception/multi_object_tracker/** yoshi.ri@tier4.jp yukihiro.saito@tier4.jp perception/object_merger/** yoshi.ri@tier4.jp yukihiro.saito@tier4.jp perception/object_range_splitter/** yukihiro.saito@tier4.jp @@ -166,7 +167,7 @@ planning/freespace_planning_algorithms/** kosuke.takeuchi@tier4.jp takamasa.hori planning/mission_planner/** isamu.takagi@tier4.jp kosuke.takeuchi@tier4.jp ryohsuke.mitsudome@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp planning/motion_velocity_smoother/** fumiya.watanabe@tier4.jp makoto.kurihara@tier4.jp takamasa.horibe@tier4.jp yutaka.shimizu@tier4.jp planning/obstacle_avoidance_planner/** kosuke.takeuchi@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp -planning/obstacle_cruise_planner/** takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp +planning/obstacle_cruise_planner/** kosuke.takeuchi@tier4.jp satoshi.ota@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp planning/obstacle_stop_planner/** berkay@leodrive.ai satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp tomoya.kimura@tier4.jp planning/obstacle_velocity_limiter/** maxime.clement@tier4.jp planning/path_smoother/** maxime.clement@tier4.jp takayuki.murooka@tier4.jp From a8ad0767b9885e9baacf928f5aa211ba1748230e Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:26:29 +0900 Subject: [PATCH 069/547] refactor(behavior_path_planner): scene module interface clang-tidy (#4484) * refactor(behavior_path_planner): scene module interface clang-tidy Signed-off-by: Muhammad Zulfaqar Azmi * style(pre-commit): autofix --------- Signed-off-by: Muhammad Zulfaqar Azmi Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../scene_module/scene_module_interface.hpp | 44 +++++++++---------- .../avoidance/avoidance_module.cpp | 1 + .../scene_module/lane_change/interface.cpp | 1 + 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp index 3405ae446615d..6acf5b945308f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp @@ -50,12 +50,8 @@ namespace behavior_path_planner { using autoware_adapi_v1_msgs::msg::SteeringFactor; using autoware_auto_planning_msgs::msg::PathWithLaneId; -using motion_utils::createDeadLineVirtualWallMarker; -using motion_utils::createSlowDownVirtualWallMarker; -using motion_utils::createStopVirtualWallMarker; using rtc_interface::RTCInterface; using steering_factor_interface::SteeringFactorInterface; -using tier4_autoware_utils::appendMarkerArray; using tier4_autoware_utils::calcOffsetPose; using tier4_autoware_utils::generateUUID; using tier4_planning_msgs::msg::AvoidanceDebugMsgArray; @@ -79,16 +75,16 @@ class SceneModuleInterface public: SceneModuleInterface( const std::string & name, rclcpp::Node & node, - const std::unordered_map> & rtc_interface_ptr_map) + std::unordered_map> rtc_interface_ptr_map) : name_{name}, logger_{node.get_logger().get_child(name)}, clock_{node.get_clock()}, - rtc_interface_ptr_map_(rtc_interface_ptr_map), + rtc_interface_ptr_map_(std::move(rtc_interface_ptr_map)), steering_factor_interface_ptr_( std::make_unique(&node, utils::convertToSnakeCase(name))) { - for (auto itr = rtc_interface_ptr_map_.begin(); itr != rtc_interface_ptr_map_.end(); ++itr) { - uuid_map_.emplace(itr->first, generateUUID()); + for (const auto & [module_name, ptr] : rtc_interface_ptr_map_) { + uuid_map_.emplace(module_name, generateUUID()); } } @@ -176,9 +172,9 @@ class SceneModuleInterface */ void publishRTCStatus() { - for (auto itr = rtc_interface_ptr_map_.begin(); itr != rtc_interface_ptr_map_.end(); ++itr) { - if (itr->second) { - itr->second->publishCooperateStatus(clock_->now()); + for (const auto & [module_name, ptr] : rtc_interface_ptr_map_) { + if (ptr) { + ptr->publishCooperateStatus(clock_->now()); } } } @@ -193,18 +189,18 @@ class SceneModuleInterface void lockRTCCommand() { - for (auto itr = rtc_interface_ptr_map_.begin(); itr != rtc_interface_ptr_map_.end(); ++itr) { - if (itr->second) { - itr->second->lockCommandUpdate(); + for (const auto & [module_name, ptr] : rtc_interface_ptr_map_) { + if (ptr) { + ptr->lockCommandUpdate(); } } } void unlockRTCCommand() { - for (auto itr = rtc_interface_ptr_map_.begin(); itr != rtc_interface_ptr_map_.end(); ++itr) { - if (itr->second) { - itr->second->unlockCommandUpdate(); + for (const auto & [module_name, ptr] : rtc_interface_ptr_map_) { + if (ptr) { + ptr->unlockCommandUpdate(); } } } @@ -398,10 +394,10 @@ class SceneModuleInterface virtual void updateRTCStatus(const double start_distance, const double finish_distance) { - for (auto itr = rtc_interface_ptr_map_.begin(); itr != rtc_interface_ptr_map_.end(); ++itr) { - if (itr->second) { - itr->second->updateCooperateStatus( - uuid_map_.at(itr->first), isExecutionReady(), start_distance, finish_distance, + for (const auto & [module_name, ptr] : rtc_interface_ptr_map_) { + if (ptr) { + ptr->updateCooperateStatus( + uuid_map_.at(module_name), isExecutionReady(), start_distance, finish_distance, clock_->now()); } } @@ -483,9 +479,9 @@ class SceneModuleInterface void removeRTCStatus() { - for (auto itr = rtc_interface_ptr_map_.begin(); itr != rtc_interface_ptr_map_.end(); ++itr) { - if (itr->second) { - itr->second->clearCooperateStatus(); + for (const auto & [module_name, ptr] : rtc_interface_ptr_map_) { + if (ptr) { + ptr->clearCooperateStatus(); } } } diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 543f02abe730f..c47aa66fdac90 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -50,6 +50,7 @@ using motion_utils::calcLongitudinalOffsetPose; using motion_utils::calcSignedArcLength; using motion_utils::findNearestIndex; using motion_utils::findNearestSegmentIndex; +using tier4_autoware_utils::appendMarkerArray; using tier4_autoware_utils::calcDistance2d; using tier4_autoware_utils::calcInterpolatedPose; using tier4_autoware_utils::calcLateralDeviation; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 072c1c62ae3d2..5adfa72b06560 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -31,6 +31,7 @@ namespace behavior_path_planner { +using tier4_autoware_utils::appendMarkerArray; using utils::lane_change::assignToCandidate; LaneChangeInterface::LaneChangeInterface( From 124dc1d0b6ce57af33e184067c79d5a416b51d49 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:39:06 +0900 Subject: [PATCH 070/547] refactor(behavior_path_plannner): commonize collision check markers (#4798) * refactor(behavior_path_plannner): commonize collision check markers Signed-off-by: Muhammad Zulfaqar Azmi * Update planning/behavior_path_planner/src/marker_utils/utils.cpp Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> --------- Signed-off-by: Muhammad Zulfaqar Azmi Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> --- .../marker_utils/lane_change/debug.hpp | 4 - .../marker_utils/utils.hpp | 5 + .../path_safety_checker_parameters.hpp | 11 +- .../src/marker_utils/lane_change/debug.cpp | 149 ---------------- .../src/marker_utils/utils.cpp | 161 +++++++++++++++++- .../scene_module/lane_change/interface.cpp | 15 +- .../path_safety_checker/safety_check.cpp | 7 +- 7 files changed, 183 insertions(+), 169 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp index 808e76a140761..d337bae17ca6c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp @@ -26,12 +26,8 @@ namespace marker_utils::lane_change_markers { using behavior_path_planner::LaneChangePath; using visualization_msgs::msg::MarkerArray; -MarkerArray showObjectInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); MarkerArray showAllValidLaneChangePath( const std::vector & lanes, std::string && ns); -MarkerArray showLerpedPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); -MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); -MarkerArray showPolygonPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); MarkerArray createLaneChangingVirtualWallMarker( const geometry_msgs::msg::Pose & lane_changing_pose, const std::string & module_name, const rclcpp::Time & now, const std::string & ns); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index 657437615f809..ba8ddd9b19c46 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -104,6 +104,11 @@ MarkerArray createPredictedPathMarkerArray( const PredictedPath & ego_predicted_path, const vehicle_info_util::VehicleInfo & vehicle_info, std::string && ns, const int32_t & id, const float & r, const float & g, const float & b); +MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); + +MarkerArray showPredictedPath(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); + +MarkerArray showSafetyCheckInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); } // namespace marker_utils #endif // BEHAVIOR_PATH_PLANNER__MARKER_UTILS__UTILS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index 87c93f49901f0..93eb0457d7cf4 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -179,10 +179,10 @@ struct SafetyCheckParams struct CollisionCheckDebug { std::string unsafe_reason; ///< Reason indicating unsafe situation. - Pose current_pose{}; ///< Ego vehicle's current pose. Twist current_twist{}; ///< Ego vehicle's current velocity and rotation. - Twist object_twist{}; ///< Detected object's velocity and rotation. Pose expected_ego_pose{}; ///< Predicted future pose of ego vehicle. + Pose current_obj_pose{}; ///< Detected object's current pose. + Twist object_twist{}; ///< Detected object's velocity and rotation. Pose expected_obj_pose{}; ///< Predicted future pose of object. double rss_longitudinal{0.0}; ///< Longitudinal RSS measure. double inter_vehicle_distance{0.0}; ///< Distance between ego vehicle and object. @@ -190,9 +190,10 @@ struct CollisionCheckDebug double extended_polygon_lat_offset{0.0}; ///< Lateral offset for extended polygon. bool is_front{false}; ///< True if object is in front of ego vehicle. bool is_safe{false}; ///< True if situation is deemed safe. - std::vector lerped_path; ///< Interpolated ego vehicle path. - Polygon2d extended_ego_polygon{}; ///< Ego vehicle's extended collision polygon. - Polygon2d extended_obj_polygon{}; ///< Detected object's extended collision polygon. + std::vector ego_predicted_path; ///< ego vehicle's predicted path. + std::vector obj_predicted_path; ///< object's predicted path. + Polygon2d extended_ego_polygon{}; ///< Ego vehicle's extended collision polygon. + Polygon2d extended_obj_polygon{}; ///< Detected object's extended collision polygon. }; using CollisionCheckDebugPair = std::pair; using CollisionCheckDebugMap = diff --git a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp index aab2be6f3054e..7493982db78b1 100644 --- a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp @@ -36,40 +36,6 @@ using geometry_msgs::msg::Point; using tier4_autoware_utils::createDefaultMarker; using tier4_autoware_utils::createMarkerScale; -MarkerArray showObjectInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) -{ - Marker obj_marker = createDefaultMarker( - "map", rclcpp::Clock{RCL_ROS_TIME}.now(), ns, 0L, Marker::TEXT_VIEW_FACING, - createMarkerScale(0.5, 0.5, 0.5), colors::aqua()); - - MarkerArray marker_array; - int32_t id{0}; - - marker_array.markers.reserve(obj_debug_vec.size()); - - int idx{0}; - - for (const auto & [uuid, info] : obj_debug_vec) { - obj_marker.id = ++id; - obj_marker.pose = info.current_pose; - - std::ostringstream ss; - - ss << "Idx: " << ++idx << "\nReason: " << info.unsafe_reason - << "\nRSS dist: " << std::setprecision(4) << info.rss_longitudinal - << "\nEgo to obj: " << info.inter_vehicle_distance - << "\nExtended polygon lateral offset: " << info.extended_polygon_lat_offset - << "\nExtended polygon longitudinal offset: " << info.extended_polygon_lon_offset - << "\nPosition: " << (info.is_front ? "front" : "back") - << "\nSafe: " << (info.is_safe ? "Yes" : "No"); - - obj_marker.text = ss.str(); - - marker_array.markers.push_back(obj_marker); - } - return marker_array; -} - MarkerArray showAllValidLaneChangePath(const std::vector & lanes, std::string && ns) { if (lanes.empty()) { @@ -103,121 +69,6 @@ MarkerArray showAllValidLaneChangePath(const std::vector & lanes return marker_array; } -MarkerArray showLerpedPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) -{ - MarkerArray marker_array; - int32_t id{0}; - const auto current_time{rclcpp::Clock{RCL_ROS_TIME}.now()}; - marker_array.markers.reserve(obj_debug_vec.size()); - - for (const auto & [uuid, info] : obj_debug_vec) { - Marker marker = createDefaultMarker( - "map", current_time, ns, ++id, Marker::POINTS, createMarkerScale(0.3, 0.3, 0.3), - colors::magenta()); - marker.points.reserve(info.lerped_path.size()); - - for (const auto & point : info.lerped_path) { - marker.points.push_back(point.position); - } - - marker_array.markers.push_back(marker); - } - return marker_array; -} - -MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) -{ - if (obj_debug_vec.empty()) { - return MarkerArray{}; - } - - constexpr float scale_val{0.2}; - int32_t id{0}; - const auto now = rclcpp::Clock{RCL_ROS_TIME}.now(); - Marker ego_marker = createDefaultMarker( - "map", now, ns, id, Marker::LINE_STRIP, createMarkerScale(scale_val, scale_val, scale_val), - colors::green()); - Marker obj_marker = ego_marker; - - auto text_marker = createDefaultMarker( - "map", now, ns + "_text", id, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, - createMarkerScale(1.5, 1.5, 1.5), colors::white()); - - MarkerArray marker_array; - - const auto reserve_size = obj_debug_vec.size(); - - marker_array.markers.reserve(reserve_size * 4); - - int32_t idx = {0}; - - for (const auto & [uuid, info] : obj_debug_vec) { - const auto color = info.is_safe ? colors::green() : colors::red(); - const auto & ego_polygon = info.extended_ego_polygon.outer(); - const auto poly_z = info.current_pose.position.z; // temporally - ego_marker.id = ++id; - ego_marker.color = color; - ego_marker.points.reserve(ego_polygon.size()); - for (const auto & p : ego_polygon) { - ego_marker.points.push_back(tier4_autoware_utils::createPoint(p.x(), p.y(), poly_z)); - } - marker_array.markers.push_back(ego_marker); - - std::ostringstream ss; - text_marker.id = ego_marker.id; - ss << ++idx; - text_marker.text = ss.str(); - text_marker.pose = info.expected_ego_pose; - - marker_array.markers.push_back(text_marker); - - const auto & obj_polygon = info.extended_obj_polygon.outer(); - obj_marker.id = ++id; - obj_marker.color = color; - obj_marker.points.reserve(obj_polygon.size()); - for (const auto & p : obj_polygon) { - obj_marker.points.push_back(tier4_autoware_utils::createPoint(p.x(), p.y(), poly_z)); - } - marker_array.markers.push_back(obj_marker); - - text_marker.id = obj_marker.id; - text_marker.pose = info.expected_obj_pose; - marker_array.markers.push_back(text_marker); - - ego_marker.points.clear(); - obj_marker.points.clear(); - } - - return marker_array; -} - -MarkerArray showPolygonPose(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) -{ - const auto colors = colors::colors_list(); - const auto loop_size = std::min(colors.size(), obj_debug_vec.size()); - MarkerArray marker_array; - int32_t id{0}; - size_t idx{0}; - const auto current_time{rclcpp::Clock{RCL_ROS_TIME}.now()}; - marker_array.markers.reserve(obj_debug_vec.size()); - - for (const auto & [uuid, info] : obj_debug_vec) { - const auto & color = colors.at(idx); - Marker marker = createDefaultMarker( - "map", current_time, ns, ++id, Marker::POINTS, createMarkerScale(0.2, 0.2, 0.2), color); - marker.points.reserve(2); - marker.points.push_back(info.expected_ego_pose.position); - marker.points.push_back(info.expected_obj_pose.position); - marker_array.markers.push_back(marker); - ++idx; - if (idx >= loop_size) { - break; - } - } - - return marker_array; -} - MarkerArray createLaneChangingVirtualWallMarker( const geometry_msgs::msg::Pose & lane_changing_pose, const std::string & module_name, const rclcpp::Time & now, const std::string & ns) diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index f24e772d198c1..d6a237079448f 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -14,6 +14,7 @@ #include "behavior_path_planner/marker_utils/utils.hpp" +#include "behavior_path_planner/marker_utils/colors.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" @@ -37,7 +38,7 @@ using visualization_msgs::msg::Marker; CollisionCheckDebugPair createObjectDebug(const ExtendedPredictedObject & obj) { CollisionCheckDebug debug; - debug.current_pose = obj.initial_pose.pose; + debug.current_obj_pose = obj.initial_pose.pose; debug.current_twist = obj.initial_twist.twist; return {tier4_autoware_utils::toHexString(obj.uuid), debug}; } @@ -486,4 +487,162 @@ MarkerArray createPredictedPathMarkerArray( return msg; } +MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) +{ + if (obj_debug_vec.empty()) { + return MarkerArray{}; + } + + int32_t id{0}; + const auto now = rclcpp::Clock{RCL_ROS_TIME}.now(); + + constexpr float line_scale_val{0.2}; + const auto line_marker_scale = createMarkerScale(line_scale_val, line_scale_val, line_scale_val); + + auto default_line_marker = [&](const auto & color = colors::green()) { + return createDefaultMarker("map", now, ns, ++id, Marker::LINE_STRIP, line_marker_scale, color); + }; + + constexpr float text_scale_val{1.5}; + const auto text_marker_scale = createMarkerScale(text_scale_val, text_scale_val, text_scale_val); + + auto default_text_marker = [&]() { + return createDefaultMarker( + "map", now, ns + "_text", ++id, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, + text_marker_scale, colors::white()); + }; + + auto default_cube_marker = + [&](const auto & width, const auto & depth, const auto & color = colors::green()) { + return createDefaultMarker( + "map", now, ns + "_cube", ++id, visualization_msgs::msg::Marker::CUBE, + createMarkerScale(width, depth, 1.0), color); + }; + + MarkerArray marker_array; + marker_array.markers.reserve( + obj_debug_vec.size() * 5); // poly ego, text ego, poly obj, text obj, cube obj + + int32_t idx = {0}; + for (const auto & [uuid, info] : obj_debug_vec) { + const auto color = info.is_safe ? colors::green() : colors::red(); + const auto poly_z = info.current_obj_pose.position.z; // temporally + + const auto insert_polygon_marker = [&](const auto & polygon) { + marker_array.markers.emplace_back(); + auto & polygon_marker = marker_array.markers.back(); + polygon_marker = default_line_marker(color); + polygon_marker.points.reserve(polygon.outer().size()); + for (const auto & p : polygon.outer()) { + polygon_marker.points.push_back(createPoint(p.x(), p.y(), poly_z)); + } + }; + + insert_polygon_marker(info.extended_ego_polygon); + insert_polygon_marker(info.extended_obj_polygon); + + const auto str_idx = std::to_string(++idx); + const auto insert_text_marker = [&](const auto & pose) { + marker_array.markers.emplace_back(); + auto & text_marker = marker_array.markers.back(); + text_marker = default_text_marker(); + text_marker.text = str_idx; + text_marker.pose = pose; + }; + + insert_text_marker(info.expected_ego_pose); + insert_text_marker(info.expected_obj_pose); + + const auto insert_cube_marker = [&](const auto & pose) { + marker_array.markers.emplace_back(); + auto & cube_marker = marker_array.markers.back(); + cube_marker = default_cube_marker(1.0, 1.0, color); + cube_marker.pose = pose; + }; + insert_cube_marker(info.current_obj_pose); + } + return marker_array; +} + +MarkerArray showPredictedPath(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) +{ + int32_t id{0}; + const auto current_time{rclcpp::Clock{RCL_ROS_TIME}.now()}; + const auto arrow_marker_scale = createMarkerScale(1.0, 0.3, 0.3); + const auto default_arrow_marker = [&](const auto & color) { + return createDefaultMarker( + "map", current_time, ns, ++id, Marker::ARROW, arrow_marker_scale, color); + }; + + MarkerArray marker_array; + marker_array.markers.reserve(std::accumulate( + obj_debug_vec.cbegin(), obj_debug_vec.cend(), 0UL, + [&](const auto current_sum, const auto & obj_debug) { + const auto & [uuid, info] = obj_debug; + return current_sum + info.ego_predicted_path.size() + info.obj_predicted_path.size() + 2; + })); + + for (const auto & [uuid, info] : obj_debug_vec) { + const auto insert_marker = [&](const auto & path, const auto & color) { + for (const auto & pose : path) { + marker_array.markers.emplace_back(); + auto & marker = marker_array.markers.back(); + marker = default_arrow_marker(color); + marker.pose = pose.pose; + } + }; + + insert_marker(info.ego_predicted_path, colors::aqua()); + insert_marker(info.obj_predicted_path, colors::yellow()); + const auto insert_expected_pose_marker = [&](const auto & pose, const auto & color) { + // instead of checking for distance, inserting a new marker might be more efficient + marker_array.markers.emplace_back(); + auto & marker = marker_array.markers.back(); + marker = default_arrow_marker(color); + marker.pose = pose; + marker.pose.position.z += 0.05; + }; + + insert_expected_pose_marker(info.expected_ego_pose, colors::red()); + insert_expected_pose_marker(info.expected_obj_pose, colors::red()); + } + return marker_array; +} + +MarkerArray showSafetyCheckInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns) +{ + int32_t id{0}; + auto default_text_marker = [&]() { + return createDefaultMarker( + "map", rclcpp::Clock{RCL_ROS_TIME}.now(), ns, ++id, Marker::TEXT_VIEW_FACING, + createMarkerScale(0.5, 0.5, 0.5), colors::aqua()); + }; + + MarkerArray marker_array; + + marker_array.markers.reserve(obj_debug_vec.size()); + + int idx{0}; + + for (const auto & [uuid, info] : obj_debug_vec) { + auto safety_check_info_text = default_text_marker(); + safety_check_info_text.pose = info.current_obj_pose; + + std::ostringstream ss; + + ss << "Idx: " << ++idx << "\nUnsafe reason: " << info.unsafe_reason + << "\nRSS dist: " << std::setprecision(4) << info.rss_longitudinal + << "\nEgo to obj: " << info.inter_vehicle_distance + << "\nExtended polygon: " << (info.is_front ? "ego" : "object") + << "\nExtended polygon lateral offset: " << info.extended_polygon_lat_offset + << "\nExtended polygon longitudinal offset: " << info.extended_polygon_lon_offset + << "\nLast checked position: " << (info.is_front ? "obj in front ego" : "obj at back ego") + << "\nSafe: " << (info.is_safe ? "Yes" : "No"); + + safety_check_info_text.text = ss.str(); + marker_array.markers.push_back(safety_check_info_text); + } + return marker_array; +} + } // namespace marker_utils diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 5adfa72b06560..7668976466e64 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -296,11 +296,10 @@ void LaneChangeInterface::setObjectDebugVisualization() const if (!parameters_->publish_debug_marker) { return; } + using marker_utils::showPolygon; + using marker_utils::showPredictedPath; + using marker_utils::showSafetyCheckInfo; using marker_utils::lane_change_markers::showAllValidLaneChangePath; - using marker_utils::lane_change_markers::showLerpedPose; - using marker_utils::lane_change_markers::showObjectInfo; - using marker_utils::lane_change_markers::showPolygon; - using marker_utils::lane_change_markers::showPolygonPose; const auto debug_data = module_type_->getDebugData(); const auto debug_after_approval = module_type_->getAfterApprovalDebugData(); @@ -313,14 +312,14 @@ void LaneChangeInterface::setObjectDebugVisualization() const add(showAllValidLaneChangePath(debug_valid_path, "lane_change_valid_paths")); if (!debug_data.empty()) { - add(showObjectInfo(debug_data, "object_debug_info")); - add(showLerpedPose(debug_data, "ego_predicted_path")); + add(showSafetyCheckInfo(debug_data, "object_debug_info")); + add(showPredictedPath(debug_data, "ego_predicted_path")); add(showPolygon(debug_data, "ego_and_target_polygon_relation")); } if (!debug_after_approval.empty()) { - add(showObjectInfo(debug_after_approval, "object_debug_info_after_approval")); - add(showLerpedPose(debug_after_approval, "ego_predicted_path_after_approval")); + add(showSafetyCheckInfo(debug_after_approval, "object_debug_info_after_approval")); + add(showPredictedPath(debug_after_approval, "ego_predicted_path_after_approval")); add(showPolygon(debug_after_approval, "ego_and_target_polygon_relation_after_approval")); } } diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 2939ca29c0a40..1838dc6bdc07b 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -253,7 +253,11 @@ bool checkCollision( const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, double hysteresis_factor, CollisionCheckDebug & debug) { - debug.lerped_path.reserve(target_object_path.path.size()); + { + debug.ego_predicted_path = predicted_ego_path; + debug.obj_predicted_path = target_object_path.path; + debug.current_obj_pose = target_object.initial_pose.pose; + } for (const auto & obj_pose_with_poly : target_object_path.path) { const auto & current_time = obj_pose_with_poly.time; @@ -277,7 +281,6 @@ bool checkCollision( const auto & ego_velocity = interpolated_data->velocity; { - debug.lerped_path.push_back(ego_pose); debug.expected_ego_pose = ego_pose; debug.expected_obj_pose = obj_pose; debug.extended_ego_polygon = ego_polygon; From 3dd38defad7fdae18a2be1f24172e2a14c839bcb Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 31 Aug 2023 11:43:54 +0900 Subject: [PATCH 071/547] fix(control_validator): fix error msg (#4794) fix error msg Signed-off-by: kyoichi-sugahara --- control/control_validator/src/control_validator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/control/control_validator/src/control_validator.cpp b/control/control_validator/src/control_validator.cpp index 59b798a786722..9fe5121ee3dbd 100644 --- a/control/control_validator/src/control_validator.cpp +++ b/control/control_validator/src/control_validator.cpp @@ -204,7 +204,9 @@ void ControlValidator::displayStatus() const auto & s = validation_status_; - warn(s.is_valid_max_distance_deviation, "planning trajectory is too far from ego!!"); + warn( + s.is_valid_max_distance_deviation, + "predicted trajectory is too far from planning trajectory!!"); } } // namespace control_validator From 1b1ac5cc56e0a44f761edc4e9cf9423e9400c65b Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:00:06 +0900 Subject: [PATCH 072/547] fix(walkway): fix module launch condition (#4823) Signed-off-by: satoshi-ota --- .../src/manager.cpp | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/planning/behavior_velocity_walkway_module/src/manager.cpp b/planning/behavior_velocity_walkway_module/src/manager.cpp index d10ff52850752..f9c222f662e82 100644 --- a/planning/behavior_velocity_walkway_module/src/manager.cpp +++ b/planning/behavior_velocity_walkway_module/src/manager.cpp @@ -44,11 +44,8 @@ WalkwayModuleManager::WalkwayModuleManager(rclcpp::Node & node) void WalkwayModuleManager::launchNewModules(const PathWithLaneId & path) { const auto rh = planner_data_->route_handler_; - if (!opt_use_regulatory_element_) { - opt_use_regulatory_element_ = checkRegulatoryElementExistence(rh->getLaneletMapPtr()); - } - const auto launch = [this, &path](const auto & lanelet) { + const auto launch = [this, &path](const auto & lanelet, const auto use_regulatory_element) { const auto attribute = lanelet.attributeOr(lanelet::AttributeNamesString::Subtype, std::string("")); if (attribute != lanelet::AttributeValueString::Walkway) { @@ -64,24 +61,21 @@ void WalkwayModuleManager::launchNewModules(const PathWithLaneId & path) const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); registerModule(std::make_shared( - lanelet.id(), lanelet_map_ptr, p, *opt_use_regulatory_element_, logger, clock_)); + lanelet.id(), lanelet_map_ptr, p, use_regulatory_element, logger, clock_)); }; - if (*opt_use_regulatory_element_) { - const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( - path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); + const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( + path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); - for (const auto & crosswalk : crosswalk_leg_elem_map) { - launch(crosswalk.first->crosswalkLanelet()); - } - } else { - const auto crosswalk_lanelets = getCrosswalksOnPath( - planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), - rh->getOverallGraphPtr()); + for (const auto & crosswalk : crosswalk_leg_elem_map) { + launch(crosswalk.first->crosswalkLanelet(), true); + } - for (const auto & crosswalk : crosswalk_lanelets) { - launch(crosswalk); - } + const auto crosswalk_lanelets = getCrosswalksOnPath( + planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), rh->getOverallGraphPtr()); + + for (const auto & crosswalk : crosswalk_lanelets) { + launch(crosswalk, false); } } @@ -92,17 +86,14 @@ WalkwayModuleManager::getModuleExpiredFunction(const PathWithLaneId & path) std::set walkway_id_set; - if (*opt_use_regulatory_element_) { - const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( - path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); + walkway_id_set = getCrosswalkIdSetOnPath( + planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), rh->getOverallGraphPtr()); - for (const auto & crosswalk : crosswalk_leg_elem_map) { - walkway_id_set.insert(crosswalk.first->id()); - } - } else { - walkway_id_set = getCrosswalkIdSetOnPath( - planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), - rh->getOverallGraphPtr()); + const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( + path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); + + for (const auto & crosswalk : crosswalk_leg_elem_map) { + walkway_id_set.insert(crosswalk.first->id()); } return [walkway_id_set](const std::shared_ptr & scene_module) { From b09f3096f6e9af7d808174e80c2b492f6a5306ec Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Thu, 31 Aug 2023 14:45:43 +0900 Subject: [PATCH 073/547] feat(behavior_path_planner): add virtual stop wall for lane change (#4808) Signed-off-by: Fumiya Watanabe --- .../scene_module/lane_change/base_class.hpp | 5 +++++ .../scene_module/lane_change/normal.hpp | 2 ++ .../src/scene_module/lane_change/interface.cpp | 6 ++++++ .../src/scene_module/lane_change/normal.cpp | 8 ++++++++ 4 files changed, 21 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp index f30bf31d4e010..1a7350d82aa4e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp @@ -209,6 +209,10 @@ class LaneChangeBase return direction_; } + boost::optional getStopPose() const { return lane_change_stop_pose_; } + + void resetStopPose() { lane_change_stop_pose_ = boost::none; } + protected: virtual lanelet::ConstLanelets getCurrentLanes() const = 0; @@ -244,6 +248,7 @@ class LaneChangeBase PathWithLaneId prev_module_path_{}; DrivableAreaInfo prev_drivable_area_info_{}; TurnSignalInfo prev_turn_signal_info_{}; + boost::optional lane_change_stop_pose_{boost::none}; PathWithLaneId prev_approved_path_{}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index d1476abb63e27..ef57c31c28b5e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -147,6 +147,8 @@ class NormalLaneChange : public LaneChangeBase const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, const lanelet::ConstLanelets & target_backward_lanes) const; + void setStopPose(const Pose & stop_pose); + rclcpp::Logger logger_ = rclcpp::get_logger("lane_change").get_child(getModuleTypeStr()); }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 7668976466e64..64a7e926cc2df 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -204,6 +204,7 @@ void LaneChangeInterface::updateData() module_type_->setPreviousModulePaths( getPreviousModuleOutput().reference_path, getPreviousModuleOutput().path); module_type_->updateSpecialData(); + module_type_->resetStopPose(); } BehaviorModuleOutput LaneChangeInterface::plan() @@ -222,6 +223,8 @@ BehaviorModuleOutput LaneChangeInterface::plan() *prev_approved_path_ = *getPreviousModuleOutput().path; module_type_->insertStopPoint(module_type_->getLaneChangeStatus().target_lanes, *output.path); + stop_pose_ = module_type_->getStopPose(); + updateSteeringFactorPtr(output); clearWaitingApproval(); @@ -250,6 +253,8 @@ BehaviorModuleOutput LaneChangeInterface::planWaitingApproval() path_reference_ = getPreviousModuleOutput().reference_path; + stop_pose_ = module_type_->getStopPose(); + if (!module_type_->isValidPath()) { removeRTCStatus(); path_candidate_ = std::make_shared(); @@ -287,6 +292,7 @@ void LaneChangeInterface::updateModuleParams(const std::any & parameters) void LaneChangeInterface::setData(const std::shared_ptr & data) { + planner_data_ = data; module_type_->setData(data); } diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index bf0175ccd92e4..28ceb63d88352 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -138,6 +138,7 @@ BehaviorModuleOutput NormalLaneChange::generateOutput() const auto stop_dist = -(current_velocity * current_velocity / (2.0 * planner_data_->parameters.min_acc)); const auto stop_point = utils::insertStopPoint(stop_dist + current_dist, *output.path); + setStopPose(stop_point.point.pose); } const auto current_seg_idx = planner_data_->findEgoSegmentIndex(output.path->points); @@ -198,6 +199,7 @@ void NormalLaneChange::insertStopPoint( const double stopping_distance = distance_to_terminal - lane_change_buffer - stop_point_buffer; if (stopping_distance > 0.0) { const auto stop_point = utils::insertStopPoint(stopping_distance, path); + setStopPose(stop_point.point.pose); } } @@ -1379,4 +1381,10 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( return path_safety_status; } + +void NormalLaneChange::setStopPose(const Pose & stop_pose) +{ + lane_change_stop_pose_ = stop_pose; +} + } // namespace behavior_path_planner From 9fae1316e8fa4b56d5e0ebc50b6ca21f9ad473a9 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 31 Aug 2023 17:45:46 +0900 Subject: [PATCH 074/547] chore(build): remove tier4_autoware_utils.hpp in vehicle/ (#4829) removed tier4_autoware_utils.hpp in vehicle/ Signed-off-by: Mamoru Sobue --- .../autonomous_emergency_braking/node.hpp | 1 + .../controller_node.hpp | 1 + control/trajectory_follower_node/package.xml | 1 + .../src/controller_node.cpp | 1 + .../drivable_area_expansion/footprints.cpp | 3 + vehicle/vehicle_info_util/CMakeLists.txt | 1 + .../vehicle_info_util/vehicle_info.hpp | 68 ++------------ .../vehicle_info_util/src/vehicle_info.cpp | 88 +++++++++++++++++++ 8 files changed, 101 insertions(+), 63 deletions(-) create mode 100644 vehicle/vehicle_info_util/src/vehicle_info.cpp diff --git a/control/autonomous_emergency_braking/include/autonomous_emergency_braking/node.hpp b/control/autonomous_emergency_braking/include/autonomous_emergency_braking/node.hpp index 8859367c25e9b..a8f260795d485 100644 --- a/control/autonomous_emergency_braking/include/autonomous_emergency_braking/node.hpp +++ b/control/autonomous_emergency_braking/include/autonomous_emergency_braking/node.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include diff --git a/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp b/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp index 4868fa7a6f51d..489709d1f5c4c 100644 --- a/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp +++ b/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp @@ -36,6 +36,7 @@ #include "geometry_msgs/msg/pose_stamped.hpp" #include "nav_msgs/msg/odometry.hpp" #include "tf2_msgs/msg/tf_message.hpp" +#include "visualization_msgs/msg/marker_array.hpp" #include #include diff --git a/control/trajectory_follower_node/package.xml b/control/trajectory_follower_node/package.xml index 8673634e24058..34e9fb45424a7 100644 --- a/control/trajectory_follower_node/package.xml +++ b/control/trajectory_follower_node/package.xml @@ -32,6 +32,7 @@ rclcpp_components trajectory_follower_base vehicle_info_util + visualization_msgs ros2launch diff --git a/control/trajectory_follower_node/src/controller_node.cpp b/control/trajectory_follower_node/src/controller_node.cpp index 5ee9f7f4f1e71..675ca9dffe3ae 100644 --- a/control/trajectory_follower_node/src/controller_node.cpp +++ b/control/trajectory_follower_node/src/controller_node.cpp @@ -17,6 +17,7 @@ #include "mpc_lateral_controller/mpc_lateral_controller.hpp" #include "pid_longitudinal_controller/pid_longitudinal_controller.hpp" #include "pure_pursuit/pure_pursuit_lateral_controller.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp index 0c31093a83c0e..45e128288578a 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp @@ -16,6 +16,9 @@ #include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" +#include +#include + #include #include diff --git a/vehicle/vehicle_info_util/CMakeLists.txt b/vehicle/vehicle_info_util/CMakeLists.txt index f490b0680a19a..ca72b0ed2db65 100644 --- a/vehicle/vehicle_info_util/CMakeLists.txt +++ b/vehicle/vehicle_info_util/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(autoware_cmake REQUIRED) autoware_package() ament_auto_add_library(vehicle_info_util SHARED + src/vehicle_info.cpp src/vehicle_info_util.cpp ) diff --git a/vehicle/vehicle_info_util/include/vehicle_info_util/vehicle_info.hpp b/vehicle/vehicle_info_util/include/vehicle_info_util/vehicle_info.hpp index 967e71933a209..cdbbe0427b145 100644 --- a/vehicle/vehicle_info_util/include/vehicle_info_util/vehicle_info.hpp +++ b/vehicle/vehicle_info_util/include/vehicle_info_util/vehicle_info.hpp @@ -15,7 +15,7 @@ #ifndef VEHICLE_INFO_UTIL__VEHICLE_INFO_HPP_ #define VEHICLE_INFO_UTIL__VEHICLE_INFO_HPP_ -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/boost_geometry.hpp" namespace vehicle_info_util { @@ -47,76 +47,18 @@ struct VehicleInfo double min_height_offset_m; double max_height_offset_m; - tier4_autoware_utils::LinearRing2d createFootprint(const double margin = 0.0) const - { - return createFootprint(margin, margin); - } - + tier4_autoware_utils::LinearRing2d createFootprint(const double margin = 0.0) const; tier4_autoware_utils::LinearRing2d createFootprint( - const double lat_margin, const double lon_margin) const - { - using tier4_autoware_utils::LinearRing2d; - using tier4_autoware_utils::Point2d; - - const double x_front = front_overhang_m + wheel_base_m + lon_margin; - const double x_center = wheel_base_m / 2.0; - const double x_rear = -(rear_overhang_m + lon_margin); - const double y_left = wheel_tread_m / 2.0 + left_overhang_m + lat_margin; - const double y_right = -(wheel_tread_m / 2.0 + right_overhang_m + lat_margin); - - LinearRing2d footprint; - footprint.push_back(Point2d{x_front, y_left}); - footprint.push_back(Point2d{x_front, y_right}); - footprint.push_back(Point2d{x_center, y_right}); - footprint.push_back(Point2d{x_rear, y_right}); - footprint.push_back(Point2d{x_rear, y_left}); - footprint.push_back(Point2d{x_center, y_left}); - footprint.push_back(Point2d{x_front, y_left}); - - return footprint; - } + const double lat_margin, const double lon_margin) const; }; /// Create vehicle info from base parameters -inline VehicleInfo createVehicleInfo( +VehicleInfo createVehicleInfo( const double wheel_radius_m, const double wheel_width_m, const double wheel_base_m, const double wheel_tread_m, const double front_overhang_m, const double rear_overhang_m, const double left_overhang_m, const double right_overhang_m, const double vehicle_height_m, - const double max_steer_angle_rad) -{ - // Calculate derived parameters - const double vehicle_length_m_ = front_overhang_m + wheel_base_m + rear_overhang_m; - const double vehicle_width_m_ = wheel_tread_m + left_overhang_m + right_overhang_m; - const double min_longitudinal_offset_m_ = -rear_overhang_m; - const double max_longitudinal_offset_m_ = front_overhang_m + wheel_base_m; - const double min_lateral_offset_m_ = -(wheel_tread_m / 2.0 + right_overhang_m); - const double max_lateral_offset_m_ = wheel_tread_m / 2.0 + left_overhang_m; - const double min_height_offset_m_ = 0.0; - const double max_height_offset_m_ = vehicle_height_m; + const double max_steer_angle_rad); - return VehicleInfo{ - // Base parameters - wheel_radius_m, - wheel_width_m, - wheel_base_m, - wheel_tread_m, - front_overhang_m, - rear_overhang_m, - left_overhang_m, - right_overhang_m, - vehicle_height_m, - max_steer_angle_rad, - // Derived parameters - vehicle_length_m_, - vehicle_width_m_, - min_longitudinal_offset_m_, - max_longitudinal_offset_m_, - min_lateral_offset_m_, - max_lateral_offset_m_, - min_height_offset_m_, - max_height_offset_m_, - }; -} } // namespace vehicle_info_util #endif // VEHICLE_INFO_UTIL__VEHICLE_INFO_HPP_ diff --git a/vehicle/vehicle_info_util/src/vehicle_info.cpp b/vehicle/vehicle_info_util/src/vehicle_info.cpp new file mode 100644 index 0000000000000..1a47252f82d04 --- /dev/null +++ b/vehicle/vehicle_info_util/src/vehicle_info.cpp @@ -0,0 +1,88 @@ +// Copyright 2015-2021 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "vehicle_info_util/vehicle_info.hpp" + +namespace vehicle_info_util +{ +tier4_autoware_utils::LinearRing2d VehicleInfo::createFootprint(const double margin) const +{ + return createFootprint(margin, margin); +} + +tier4_autoware_utils::LinearRing2d VehicleInfo::createFootprint( + const double lat_margin, const double lon_margin) const +{ + using tier4_autoware_utils::LinearRing2d; + using tier4_autoware_utils::Point2d; + + const double x_front = front_overhang_m + wheel_base_m + lon_margin; + const double x_center = wheel_base_m / 2.0; + const double x_rear = -(rear_overhang_m + lon_margin); + const double y_left = wheel_tread_m / 2.0 + left_overhang_m + lat_margin; + const double y_right = -(wheel_tread_m / 2.0 + right_overhang_m + lat_margin); + + LinearRing2d footprint; + footprint.push_back(Point2d{x_front, y_left}); + footprint.push_back(Point2d{x_front, y_right}); + footprint.push_back(Point2d{x_center, y_right}); + footprint.push_back(Point2d{x_rear, y_right}); + footprint.push_back(Point2d{x_rear, y_left}); + footprint.push_back(Point2d{x_center, y_left}); + footprint.push_back(Point2d{x_front, y_left}); + + return footprint; +} + +VehicleInfo createVehicleInfo( + const double wheel_radius_m, const double wheel_width_m, const double wheel_base_m, + const double wheel_tread_m, const double front_overhang_m, const double rear_overhang_m, + const double left_overhang_m, const double right_overhang_m, const double vehicle_height_m, + const double max_steer_angle_rad) +{ + // Calculate derived parameters + const double vehicle_length_m_ = front_overhang_m + wheel_base_m + rear_overhang_m; + const double vehicle_width_m_ = wheel_tread_m + left_overhang_m + right_overhang_m; + const double min_longitudinal_offset_m_ = -rear_overhang_m; + const double max_longitudinal_offset_m_ = front_overhang_m + wheel_base_m; + const double min_lateral_offset_m_ = -(wheel_tread_m / 2.0 + right_overhang_m); + const double max_lateral_offset_m_ = wheel_tread_m / 2.0 + left_overhang_m; + const double min_height_offset_m_ = 0.0; + const double max_height_offset_m_ = vehicle_height_m; + + return VehicleInfo{ + // Base parameters + wheel_radius_m, + wheel_width_m, + wheel_base_m, + wheel_tread_m, + front_overhang_m, + rear_overhang_m, + left_overhang_m, + right_overhang_m, + vehicle_height_m, + max_steer_angle_rad, + // Derived parameters + vehicle_length_m_, + vehicle_width_m_, + min_longitudinal_offset_m_, + max_longitudinal_offset_m_, + min_lateral_offset_m_, + max_lateral_offset_m_, + min_height_offset_m_, + max_height_offset_m_, + }; +} + +} // namespace vehicle_info_util From b16ab344870b9d6fd6cbb471f67b4acaa958e071 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Thu, 31 Aug 2023 18:09:47 +0900 Subject: [PATCH 075/547] fix(behavior_path_planner): proper drivable area expansion limit with lines (#4710) Signed-off-by: Maxime CLEMENT --- .../drivable_area_expansion/expansion.cpp | 13 ++++-- .../test/test_drivable_area_expansion.cpp | 41 +++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp index 035cc579d2a82..5304985b1cbf2 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp @@ -25,10 +25,15 @@ double calculateDistanceLimit( const multi_linestring_t & limit_lines) { auto dist_limit = std::numeric_limits::max(); - multi_point_t intersections; - boost::geometry::intersection(expansion_polygon, limit_lines, intersections); - for (const auto & p : intersections) - dist_limit = std::min(dist_limit, boost::geometry::distance(p, base_ls)); + for (const auto & line : limit_lines) { + multi_point_t intersections; + boost::geometry::intersection(expansion_polygon, limit_lines, intersections); + for (const auto & p : intersections) + dist_limit = std::min(dist_limit, boost::geometry::distance(p, base_ls)); + for (const auto & p : line) + if (boost::geometry::within(p, expansion_polygon)) + dist_limit = std::min(dist_limit, boost::geometry::distance(p, base_ls)); + } return dist_limit; } diff --git a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp index 605b3b96ee9b9..fe33d48f74fe5 100644 --- a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp @@ -304,7 +304,7 @@ TEST(DrivableAreaExpansion, calculateDistanceLimit) const linestring_t base_ls = {{0.0, 0.0}, {10.0, 0.0}}; const multi_linestring_t uncrossable_lines = {}; const polygon_t expansion_polygon = { - {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {10.0, -4.0}}, {}}; + {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; const auto limit_distance = calculateDistanceLimit(base_ls, expansion_polygon, uncrossable_lines); EXPECT_NEAR(limit_distance, std::numeric_limits::max(), 1e-9); @@ -313,7 +313,7 @@ TEST(DrivableAreaExpansion, calculateDistanceLimit) const linestring_t base_ls = {{0.0, 0.0}, {10.0, 0.0}}; const linestring_t uncrossable_line = {{0.0, 2.0}, {10.0, 2.0}}; const polygon_t expansion_polygon = { - {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {10.0, -4.0}}, {}}; + {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; const auto limit_distance = calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_line}); EXPECT_NEAR(limit_distance, 2.0, 1e-9); @@ -323,9 +323,44 @@ TEST(DrivableAreaExpansion, calculateDistanceLimit) const multi_linestring_t uncrossable_lines = { {{0.0, 2.0}, {10.0, 2.0}}, {{0.0, 1.5}, {10.0, 1.0}}}; const polygon_t expansion_polygon = { - {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {10.0, -4.0}}, {}}; + {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; const auto limit_distance = calculateDistanceLimit(base_ls, expansion_polygon, uncrossable_lines); EXPECT_NEAR(limit_distance, 1.0, 1e-9); } } + +TEST(DrivableAreaExpansion, calculateDistanceLimitEdgeCases) +{ + using drivable_area_expansion::calculateDistanceLimit; + using drivable_area_expansion::linestring_t; + using drivable_area_expansion::polygon_t; + + const linestring_t base_ls = {{0.0, 0.0}, {10.0, 0.0}}; + const polygon_t expansion_polygon = { + {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; + { // intersection points further than the line point inside the expansion polygon + const linestring_t uncrossable_lines = {{4.0, 5.0}, {6.0, 3.0}}; + const auto limit_distance = + calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); + EXPECT_NEAR(limit_distance, 3.0, 1e-9); + } + { // intersection points further than the line point inside the expansion polygon + const linestring_t uncrossable_lines = {{4.0, 5.0}, {5.0, 2.0}, {6.0, 4.5}}; + const auto limit_distance = + calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); + EXPECT_NEAR(limit_distance, 2.0, 1e-9); + } + { // line completely inside the expansion polygon + const linestring_t uncrossable_lines = {{4.0, 2.0}, {6.0, 3.0}}; + const auto limit_distance = + calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); + EXPECT_NEAR(limit_distance, 2.0, 1e-9); + } + { // line completely inside the expansion polygon + const linestring_t uncrossable_lines = {{4.0, 3.5}, {6.0, 3.0}}; + const auto limit_distance = + calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); + EXPECT_NEAR(limit_distance, 3.0, 1e-9); + } +} From 8e6d440c42d212342c9842e38093f3aff59955f6 Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Thu, 31 Aug 2023 18:57:24 +0900 Subject: [PATCH 076/547] docs(behavior_path_planner): fix object_check_min_road_shoulder_width explanation (#4834) Signed-off-by: Fumiya Watanabe --- .../docs/behavior_path_planner_avoidance_design.md | 6 +++--- .../docs/behavior_path_planner_lane_change_design.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_avoidance_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_avoidance_design.md index 693d610951bbb..da96567ed542a 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_avoidance_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_avoidance_design.md @@ -192,7 +192,7 @@ The avoidance target should be limited to stationary objects (you should not avo Not only the length from the centerline, but also the length from the road shoulder is calculated and used for the filtering process. It calculates the ratio of _the actual length between the the object's center and the center line_ `shift_length` and _the maximum length the object can shift_ `shiftable_length`. $$ -l_D = l_a - \frac{width}{2} \\ +l_D = l_a - \frac{width}{2}, \\ ratio = \frac{l_d}{l_D} $$ @@ -201,7 +201,7 @@ $$ - $l_a$ : distance between centerline and most left boundary. - $width$ : object width -The closer the object is to the shoulder, the larger the value of $ratio$ (theoretical max value is 1.0), and it compares the value and `object_check_shiftable_ratio` to determine whether the object is a parked-car. +The closer the object is to the shoulder, the larger the value of $ratio$ (theoretical max value is 1.0), and it compares the value and `object_check_shiftable_ratio` to determine whether the object is a parked-car. If the road has no road shoulders, it uses `object_check_min_road_shoulder_width` as a road shoulder width virtually. ![fig2](../image/avoidance/parked-car-detection.svg) @@ -692,7 +692,7 @@ namespace: `avoidance.target_filtering.` | object_check_backward_distance | [m] | double | Backward distance to search the avoidance target. | 2.0 | | object_check_goal_distance | [m] | double | Backward distance to search the avoidance target. | 20.0 | | object_check_shiftable_ratio | [m] | double | Vehicles around the center line within this distance will be excluded from avoidance target. | 0.6 | -| object_check_min_road_shoulder_width | [m] | double | Vehicles around the center line within this distance will be excluded from avoidance target. | 0.5 | +| object_check_min_road_shoulder_width | [m] | double | Width considered as a road shoulder if the lane does not have a road shoulder target. | 0.5 | | object_last_seen_threshold | [s] | double | For the compensation of the detection lost. The object is registered once it is observed as an avoidance target. When the detection loses, the timer will start and the object will be un-registered when the time exceeds this limit. | 2.0 | ### Safety check parameters diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md index 2a04580aef5b5..2fd1ff1cfd376 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md @@ -163,7 +163,7 @@ First, we divide the target objects into obstacles in the target lane, obstacles ![object lanes](../image/lane_change/lane_objects.drawio.svg) -Furthermore, to change lanes behind a vehicle waiting at a traffic light, we skip the safety check for the stopping vehicles near the traffic light. +Furthermore, to change lanes behind a vehicle waiting at a traffic light, we skip the safety check for the stopping vehicles near the traffic light. The explanation for parked car detection is written in [documentation for avoidance module](../docs/behavior_path_planner_avoidance_design.md). ##### Collision check in prepare phase @@ -276,7 +276,7 @@ The following parameters are configurable in `lane_change.param.yaml`. | `prediction_time_resolution` | [s] | double | Time resolution for object's path interpolation and collision check. | 0.5 | | `longitudinal_acceleration_sampling_num` | [-] | int | Number of possible lane-changing trajectories that are being influenced by longitudinal acceleration | 5 | | `lateral_acceleration_sampling_num` | [-] | int | Number of possible lane-changing trajectories that are being influenced by lateral acceleration | 3 | -| `object_check_min_road_shoulder_width` | [m] | double | Vehicles around the center line within this distance will be excluded from parking objects | 0.5 | +| `object_check_min_road_shoulder_width` | [m] | double | Width considered as a road shoulder if the lane does not have a road shoulder | 0.5 | | `object_shiftable_ratio_threshold` | [-] | double | Vehicles around the center line within this distance ratio will be excluded from parking objects | 0.6 | | `min_length_for_turn_signal_activation` | [m] | double | Turn signal will be activated if the ego vehicle approaches to this length from minimum lane change length | 10.0 | | `length_ratio_for_turn_signal_deactivation` | [-] | double | Turn signal will be deactivated if the ego vehicle approaches to this length ratio for lane change finish point | 0.8 | From 63033beebc9d45b6185b8ec789f41a7b379ac04a Mon Sep 17 00:00:00 2001 From: Ryohsuke Mitsudome <43976834+mitsudome-r@users.noreply.github.com> Date: Thu, 31 Aug 2023 21:32:39 +0900 Subject: [PATCH 077/547] feat: add feature to convert json schema to markdown table (#4656) * feat: add feature to convert json schema to markdown table Signed-off-by: Ryohsuke Mitsudome * fix: fix typo Signed-off-by: Ryohsuke Mitsudome * style(pre-commit): autofix * fix: rename main.py and add some comments for the file Signed-off-by: Ryohsuke Mitsudome * style(pre-commit): autofix * fix: use simplified symbols for minimum and maximum Signed-off-by: Ryohsuke Mitsudome * fix: Capitalize the first letter in columns' headers Signed-off-by: Ryohsuke Mitsudome --------- Signed-off-by: Ryohsuke Mitsudome Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- mkdocs.yaml | 3 +- mkdocs_macros.py | 70 +++++++++++++++++++ .../README.md | 4 ++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 mkdocs_macros.py diff --git a/mkdocs.yaml b/mkdocs.yaml index a72529fbe959a..2ca8b3c86ad43 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -55,7 +55,8 @@ plugins: regex: - ^(?!(.*/)?assets/).*\.(?!(.*\.)?md|(.*\.)?svg|(.*\.)?png|(.*\.)?gif|(.*\.)?jpg).*$ - ^(.*/)?[^.]*$ - - macros + - macros: + module_name: mkdocs_macros - mkdocs-video - same-dir - search diff --git a/mkdocs_macros.py b/mkdocs_macros.py new file mode 100644 index 0000000000000..56292a815d80d --- /dev/null +++ b/mkdocs_macros.py @@ -0,0 +1,70 @@ +import json + +from tabulate import tabulate + +# This file is for defining macros for mkdocs-macros plugin +# Check https://mkdocs-macros-plugin.readthedocs.io/en/latest/macros/ for the details + + +def format_param_type(param_type): + if param_type == "number": + return "float" + else: + return param_type + + +def format_param_range(param): + list_of_range = [] + if "enum" in param.keys(): + list_of_range.append(param["enum"]) + if "minimum" in param.keys(): + list_of_range.append("≥" + str(param["minimum"])) + if "exclusiveMinimum" in param.keys(): + list_of_range.append(">" + str(param["exclusiveMinimum"])) + if "maximum" in param.keys(): + list_of_range.append("≤" + str(param["maximum"])) + if "exclusiveMaximum" in param.keys(): + list_of_range.append("<" + str(param["exclusiveMaximum"])) + if "exclusive" in param.keys(): + list_of_range.append("≠" + str(param["exclusive"])) + + if len(list_of_range) == 0: + return "N/A" + else: + range_in_text = "" + for item in list_of_range: + if range_in_text != "": + range_in_text += "
" + range_in_text += str(item) + return range_in_text + + +def extract_parameter_info(parameters, namespace=""): + params = [] + for k, v in parameters.items(): + if v["type"] != "object": + param = {} + param["Name"] = namespace + k + param["Type"] = format_param_type(v["type"]) + param["Description"] = v["description"] + param["Default"] = v["default"] + param["Range"] = format_param_range(v) + params.append(param) + else: # if the object is namespace, then dive deeper in to json value + params.extend(extract_parameter_info(v["properties"], k + ".")) + return params + + +def format_json(json_data): + parameters = list(json_data["definitions"].values())[0]["properties"] + # cspell: ignore tablefmt + markdown_table = tabulate(extract_parameter_info(parameters), headers="keys", tablefmt="github") + return markdown_table + + +def define_env(env): + @env.macro + def json_to_markdown(json_schema_file_path): + with open(json_schema_file_path) as f: + data = json.load(f) + return format_json(data) diff --git a/perception/lidar_apollo_segmentation_tvm_nodes/README.md b/perception/lidar_apollo_segmentation_tvm_nodes/README.md index 626048b2416a0..488eea4f92168 100644 --- a/perception/lidar_apollo_segmentation_tvm_nodes/README.md +++ b/perception/lidar_apollo_segmentation_tvm_nodes/README.md @@ -49,6 +49,10 @@ The input are non-ground points as a PointCloud2 message from the sensor_msgs pa The output is a [DetectedObjectsWithFeature](https://github.com/tier4/tier4_autoware_msgs/blob/tier4/universe/tier4_perception_msgs/msg/object_recognition/DetectedObjectsWithFeature.msg). +#### Parameters + +{{ json_to_markdown("perception/lidar_apollo_segmentation_tvm_nodes/schema/lidar_apollo_segmentation_tvm_nodes.schema.json") }} + ### Error detection and handling Abort and warn when the input frame can't be converted to `base_link`. From b2e4582f748924f008895992cef3c414ea320a00 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 31 Aug 2023 23:24:30 +0900 Subject: [PATCH 078/547] chore(build): remove motion_utils.hpp in common/ (#4827) removed motion_utils.hpp in common/ Signed-off-by: Mamoru Sobue --- .../path_distance_calculator/src/path_distance_calculator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/path_distance_calculator/src/path_distance_calculator.cpp b/common/path_distance_calculator/src/path_distance_calculator.cpp index e4c9c8e027f7b..85578d3c18dbc 100644 --- a/common/path_distance_calculator/src/path_distance_calculator.cpp +++ b/common/path_distance_calculator/src/path_distance_calculator.cpp @@ -14,7 +14,7 @@ #include "path_distance_calculator.hpp" -#include +#include #include #include From 58db04eef84e2e1dfbf7df7f951206a2cfc844cd Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Fri, 1 Sep 2023 07:56:07 +0900 Subject: [PATCH 079/547] feat(interface): add new option `last_keep` (#4811) * feat(interface): add new option last_keep Signed-off-by: satoshi-ota * feat(planner_manager): keep last module Signed-off-by: satoshi-ota * update param config Signed-off-by: kosuke55 --------- Signed-off-by: satoshi-ota Signed-off-by: kosuke55 Co-authored-by: kosuke55 --- .../config/scene_module_manager.param.yaml | 10 +++ .../behavior_path_planner/parameters.hpp | 1 + .../scene_module_manager_interface.hpp | 5 ++ .../src/behavior_path_planner_node.cpp | 1 + .../src/planner_manager.cpp | 64 ++++++++++++++----- 5 files changed, 64 insertions(+), 17 deletions(-) diff --git a/planning/behavior_path_planner/config/scene_module_manager.param.yaml b/planning/behavior_path_planner/config/scene_module_manager.param.yaml index e89c5539a0965..9895d9b5e473f 100644 --- a/planning/behavior_path_planner/config/scene_module_manager.param.yaml +++ b/planning/behavior_path_planner/config/scene_module_manager.param.yaml @@ -8,6 +8,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: true + keep_last: false priority: 6 max_module_size: 1 @@ -16,6 +17,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: true + keep_last: false priority: 6 max_module_size: 1 @@ -24,6 +26,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: true + keep_last: false priority: 5 max_module_size: 1 @@ -32,6 +35,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: true + keep_last: false priority: 5 max_module_size: 1 @@ -40,6 +44,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false + keep_last: false priority: 0 max_module_size: 1 @@ -48,6 +53,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false + keep_last: false priority: 2 max_module_size: 1 @@ -56,6 +62,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false + keep_last: true priority: 1 max_module_size: 1 @@ -64,6 +71,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: false + keep_last: false priority: 4 max_module_size: 1 @@ -72,6 +80,7 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false + keep_last: false priority: 3 max_module_size: 1 @@ -80,5 +89,6 @@ enable_rtc: true enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: true + keep_last: false priority: 7 max_module_size: 1 diff --git a/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp index d6b99dca70618..e3a3784c5e928 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp @@ -28,6 +28,7 @@ struct ModuleConfigParameters bool enable_rtc{false}; bool enable_simultaneous_execution_as_approved_module{false}; bool enable_simultaneous_execution_as_candidate_module{false}; + bool keep_last{false}; uint8_t priority{0}; uint8_t max_module_size{0}; }; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp index cb47c33b6901c..87b21bf7c2924 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp @@ -55,6 +55,7 @@ class SceneModuleManagerInterface enable_simultaneous_execution_as_candidate_module_( config.enable_simultaneous_execution_as_candidate_module), enable_rtc_(config.enable_rtc), + keep_last_(config.keep_last), max_module_num_(config.max_module_size), priority_(config.priority) { @@ -224,6 +225,8 @@ class SceneModuleManagerInterface return enable_simultaneous_execution_as_candidate_module_; } + bool isKeepLast() const { return keep_last_; } + void setData(const std::shared_ptr & planner_data) { planner_data_ = planner_data; } void reset() @@ -290,6 +293,8 @@ class SceneModuleManagerInterface private: bool enable_rtc_; + bool keep_last_; + size_t max_module_num_; size_t priority_; diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index 3023357bd0a96..f86b2ff2c9c61 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -284,6 +284,7 @@ BehaviorPathPlannerParameters BehaviorPathPlannerNode::getCommonParam() declare_parameter(ns + "enable_simultaneous_execution_as_approved_module"); config.enable_simultaneous_execution_as_candidate_module = declare_parameter(ns + "enable_simultaneous_execution_as_candidate_module"); + config.keep_last = declare_parameter(ns + "keep_last"); config.priority = declare_parameter(ns + "priority"); config.max_module_size = declare_parameter(ns + "max_module_size"); return config; diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index c09df4151ca35..920b44235cba9 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -456,11 +456,28 @@ BehaviorModuleOutput PlannerManager::runApprovedModules(const std::shared_ptrisKeepLast() && getManager(b)->isKeepLast(); + }); + } + // lock approved modules besides last one std::for_each(approved_module_ptrs_.begin(), approved_module_ptrs_.end(), [&](const auto & m) { m->lockOutputPath(); }); - approved_module_ptrs_.back()->unlockOutputPath(); + + // unlock only last approved module except keep last module. + { + const auto not_keep_last_modules = std::find_if( + approved_module_ptrs_.rbegin(), approved_module_ptrs_.rend(), + [this](const auto & m) { return !getManager(m)->isKeepLast(); }); + + if (not_keep_last_modules != approved_module_ptrs_.rend()) { + (*not_keep_last_modules)->unlockOutputPath(); + } + } /** * execute all approved modules. @@ -478,21 +495,31 @@ BehaviorModuleOutput PlannerManager::runApprovedModules(const std::shared_ptrisWaitingApproval(); }; + const auto not_keep_last_module = std::find_if( + approved_module_ptrs_.rbegin(), approved_module_ptrs_.rend(), + [this](const auto & m) { return !getManager(m)->isKeepLast(); }); - const auto itr = std::find_if( - approved_module_ptrs_.rbegin(), approved_module_ptrs_.rend(), waiting_approval_modules); + // convert reverse iterator -> iterator + const auto begin_itr = not_keep_last_module != approved_module_ptrs_.rend() + ? std::next(not_keep_last_module).base() + : approved_module_ptrs_.begin(); - if (itr != approved_module_ptrs_.rend()) { - const auto is_last_module = std::distance(approved_module_ptrs_.rbegin(), itr) == 0; - if (is_last_module) { - clearCandidateModules(); - candidate_module_ptrs_.push_back(*itr); + const auto waiting_approval_modules_itr = std::find_if( + begin_itr, approved_module_ptrs_.end(), + [](const auto & m) { return m->isWaitingApproval(); }); - debug_info_.emplace_back(*itr, Action::MOVE, "Back To Waiting Approval"); + if (waiting_approval_modules_itr != approved_module_ptrs_.end()) { + clearCandidateModules(); + candidate_module_ptrs_.push_back(*waiting_approval_modules_itr); - approved_module_ptrs_.pop_back(); - } + debug_info_.emplace_back( + *waiting_approval_modules_itr, Action::MOVE, "Back To Waiting Approval"); + + std::for_each( + waiting_approval_modules_itr, approved_module_ptrs_.end(), + [&results](const auto & m) { results.erase(m->name()); }); + + approved_module_ptrs_.erase(waiting_approval_modules_itr); std::for_each( manager_ptrs_.begin(), manager_ptrs_.end(), [](const auto & m) { m->updateObserver(); }); @@ -529,12 +556,15 @@ BehaviorModuleOutput PlannerManager::runApprovedModules(const std::shared_ptrname(); - const auto approved_modules_output = [&output_module_name, &results]() { - if (results.count(output_module_name) == 0) { - return results.at("root"); + const auto approved_modules_output = [&results, this]() { + const auto itr = std::find_if( + approved_module_ptrs_.rbegin(), approved_module_ptrs_.rend(), + [&results](const auto & m) { return results.count(m->name()) != 0; }); + + if (itr != approved_module_ptrs_.rend()) { + return results.at((*itr)->name()); } - return results.at(output_module_name); + return results.at("root"); }(); /** From 834dd0777b897698cd94de32688c03be3ebb6984 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Fri, 1 Sep 2023 10:43:42 +0900 Subject: [PATCH 080/547] feat(behavior_path_planner): more stable dynamic drivable area expansion (#4677) Signed-off-by: Maxime CLEMENT --- .../config/drivable_area_expansion.param.yaml | 4 +- .../behavior_path_planner/data_manager.hpp | 2 + .../drivable_area_expansion.hpp | 13 ++-- .../drivable_area_expansion/footprints.hpp | 2 +- .../drivable_area_expansion/parameters.hpp | 6 +- .../utils/drivable_area_expansion/types.hpp | 1 + .../src/behavior_path_planner_node.cpp | 3 + .../drivable_area_expansion.cpp | 60 +++++++++++++++++-- .../drivable_area_expansion/expansion.cpp | 2 +- .../drivable_area_expansion/footprints.cpp | 6 +- .../behavior_path_planner/src/utils/utils.cpp | 4 +- .../test/test_drivable_area_expansion.cpp | 10 +++- 12 files changed, 92 insertions(+), 21 deletions(-) diff --git a/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml b/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml index 1609cdbc60b7a..160ebdc180020 100644 --- a/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml +++ b/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml @@ -21,12 +21,14 @@ rear: 0.5 # [m] extra length to add to the rear of the dynamic object footprint left: 0.5 # [m] extra length to add to the left of the dynamic object footprint right: 0.5 # [m] extra length to add to the rear of the dynamic object footprint + path_preprocessing: + max_arc_length: 50.0 # [m] maximum arc length along the path where the ego footprint is projected (0.0 means no limit) + resample_interval: 2.0 # [m] fixed interval between resampled path points (0.0 means path points are directly used) expansion: method: polygon # method used to expand the drivable area. Either 'lanelet' or 'polygon'. # 'lanelet': add lanelets overlapped by the ego footprints # 'polygon': add polygons built around sections of the ego footprint that go out of the drivable area max_distance: 0.0 # [m] maximum distance by which the drivable area can be expended (0.0 means no limit) - max_path_arc_length: 50.0 # [m] maximum arc length along the path where the ego footprint is projected (0.0 means no limit) extra_arc_length: 0.5 # [m] extra expansion arc length around an ego footprint avoid_linestring: types: # linestring types in the lanelet maps that will not be crossed when expanding the drivable area diff --git a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp index 5b252cef92714..9217e139d218c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -148,6 +149,7 @@ struct PlannerData BehaviorPathPlannerParameters parameters{}; drivable_area_expansion::DrivableAreaExpansionParameters drivable_area_expansion_parameters{}; + mutable std::optional drivable_area_expansion_prev_crop_pose; mutable TurnSignalDecider turn_signal_decider; TurnIndicatorsCommand getTurnSignal( diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp index 53ef91473fe24..8293e0a1d10a9 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp @@ -15,6 +15,7 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__DRIVABLE_AREA_EXPANSION_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__DRIVABLE_AREA_EXPANSION_HPP_ +#include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" @@ -22,17 +23,17 @@ #include +#include + namespace drivable_area_expansion { /// @brief Expand the drivable area based on the projected ego footprint along the path -/// @param[in] path path whose drivable area will be expanded -/// @param[in] params expansion parameters -/// @param[in] dynamic_objects dynamic objects -/// @param[in] route_handler route handler +/// @param[inout] path path whose drivable area will be expanded +/// @param[inout] planner_data planning data (params, dynamic objects, route handler, ...) /// @param[in] path_lanes lanelets of the path void expandDrivableArea( - PathWithLaneId & path, const DrivableAreaExpansionParameters & params, - const PredictedObjects & dynamic_objects, const route_handler::RouteHandler & route_handler, + PathWithLaneId & path, + const std::shared_ptr planner_data, const lanelet::ConstLanelets & path_lanes); /// @brief Create a polygon combining the drivable area of a path and some expansion polygons diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp index e5e1c76f2521f..8fc0157710dc8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp @@ -64,6 +64,6 @@ multi_polygon_t createObjectFootprints( /// @param[in] params expansion parameters defining how to create the footprint /// @return footprint polygons of the path multi_polygon_t createPathFootprints( - const PathWithLaneId & path, const DrivableAreaExpansionParameters & params); + const std::vector & path, const DrivableAreaExpansionParameters & params); } // namespace drivable_area_expansion #endif // BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__FOOTPRINTS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp index a4c11c68d13ec..81eab9560b736 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp @@ -48,8 +48,10 @@ struct DrivableAreaExpansionParameters "dynamic_expansion.dynamic_objects.extra_footprint_offset.right"; static constexpr auto EXPANSION_METHOD_PARAM = "dynamic_expansion.expansion.method"; static constexpr auto MAX_EXP_DIST_PARAM = "dynamic_expansion.expansion.max_distance"; + static constexpr auto RESAMPLE_INTERVAL_PARAM = + "dynamic_expansion.path_preprocessing.resample_interval"; static constexpr auto MAX_PATH_ARC_LENGTH_PARAM = - "dynamic_expansion.expansion.max_path_arc_length"; + "dynamic_expansion.path_preprocessing.max_arc_length"; static constexpr auto EXTRA_ARC_LENGTH_PARAM = "dynamic_expansion.expansion.extra_arc_length"; static constexpr auto AVOID_DYN_OBJECTS_PARAM = "dynamic_expansion.dynamic_objects.avoid"; static constexpr auto AVOID_LINESTRING_TYPES_PARAM = "dynamic_expansion.avoid_linestring.types"; @@ -78,6 +80,7 @@ struct DrivableAreaExpansionParameters double dynamic_objects_extra_front_offset{}; double max_expansion_distance{}; double max_path_arc_length{}; + double resample_interval{}; double extra_arc_length{}; bool avoid_dynamic_objects{}; std::vector avoid_linestring_types{}; @@ -98,6 +101,7 @@ struct DrivableAreaExpansionParameters enabled = node.declare_parameter(ENABLED_PARAM); max_expansion_distance = node.declare_parameter(MAX_EXP_DIST_PARAM); max_path_arc_length = node.declare_parameter(MAX_PATH_ARC_LENGTH_PARAM); + resample_interval = node.declare_parameter(RESAMPLE_INTERVAL_PARAM); ego_extra_front_offset = node.declare_parameter(EGO_EXTRA_OFFSET_FRONT); ego_extra_rear_offset = node.declare_parameter(EGO_EXTRA_OFFSET_REAR); ego_extra_left_offset = node.declare_parameter(EGO_EXTRA_OFFSET_LEFT); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp index daabfa97598fd..e300a347e47a8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp @@ -26,6 +26,7 @@ namespace drivable_area_expansion { using autoware_auto_perception_msgs::msg::PredictedObjects; using autoware_auto_planning_msgs::msg::PathPoint; +using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using autoware_auto_planning_msgs::msg::PathWithLaneId; using geometry_msgs::msg::Point; diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index f86b2ff2c9c61..a9b137e54e90f 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -1044,6 +1044,9 @@ SetParametersResult BehaviorPathPlannerNode::onSetParam( updateParam( parameters, DrivableAreaExpansionParameters::MAX_PATH_ARC_LENGTH_PARAM, planner_data_->drivable_area_expansion_parameters.max_path_arc_length); + updateParam( + parameters, DrivableAreaExpansionParameters::RESAMPLE_INTERVAL_PARAM, + planner_data_->drivable_area_expansion_parameters.resample_interval); updateParam( parameters, DrivableAreaExpansionParameters::EXTRA_ARC_LENGTH_PARAM, planner_data_->drivable_area_expansion_parameters.extra_arc_length); diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index 2b62d5e7c3b53..f494a377e0f95 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -19,20 +19,71 @@ #include "behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" -#include "interpolation/linear_interpolation.hpp" #include +#include +#include #include namespace drivable_area_expansion { +std::vector crop_and_resample( + const std::vector & points, + const std::shared_ptr planner_data, + const double resample_interval) +{ + auto lon_offset = 0.0; + auto crop_pose = *planner_data->drivable_area_expansion_prev_crop_pose; + // reuse or update the previous crop point + if (planner_data->drivable_area_expansion_prev_crop_pose) { + const auto lon_offset = motion_utils::calcSignedArcLength( + points, points.front().point.pose.position, crop_pose.position); + if (lon_offset < 0.0) { + planner_data->drivable_area_expansion_prev_crop_pose.reset(); + } else { + const auto is_behind_ego = + motion_utils::calcSignedArcLength( + points, crop_pose.position, planner_data->self_odometry->pose.pose.position) > 0.0; + const auto is_too_far = motion_utils::calcLateralOffset(points, crop_pose.position) > 0.1; + if (!is_behind_ego || is_too_far) + planner_data->drivable_area_expansion_prev_crop_pose.reset(); + } + } + if (!planner_data->drivable_area_expansion_prev_crop_pose) { + crop_pose = planner_data->drivable_area_expansion_prev_crop_pose.value_or( + motion_utils::calcInterpolatedPose(points, resample_interval - lon_offset)); + } + // crop + const auto crop_seg_idx = motion_utils::findNearestSegmentIndex(points, crop_pose.position); + const auto cropped_points = motion_utils::cropPoints( + points, crop_pose.position, crop_seg_idx + 1, + planner_data->drivable_area_expansion_parameters.max_path_arc_length, 0.0); + planner_data->drivable_area_expansion_prev_crop_pose = crop_pose; + // resample + PathWithLaneId cropped_path; + if (tier4_autoware_utils::calcDistance2d(crop_pose, cropped_points.front()) > 1e-3) { + PathPointWithLaneId crop_path_point; + crop_path_point.point.pose = crop_pose; + cropped_path.points.push_back(crop_path_point); + } + cropped_path.points.insert( + cropped_path.points.end(), cropped_points.begin(), cropped_points.end()); + const auto resampled_path = + motion_utils::resamplePath(cropped_path, resample_interval, true, true, false); + + return resampled_path.points; +} + void expandDrivableArea( - PathWithLaneId & path, const DrivableAreaExpansionParameters & params, - const PredictedObjects & dynamic_objects, const route_handler::RouteHandler & route_handler, + PathWithLaneId & path, + const std::shared_ptr planner_data, const lanelet::ConstLanelets & path_lanes) { + const auto & params = planner_data->drivable_area_expansion_parameters; + const auto & dynamic_objects = *planner_data->dynamic_object; + const auto & route_handler = *planner_data->route_handler; const auto uncrossable_lines = extractUncrossableLines(*route_handler.getLaneletMapPtr(), params.avoid_linestring_types); multi_linestring_t uncrossable_lines_in_range; @@ -40,7 +91,8 @@ void expandDrivableArea( for (const auto & line : uncrossable_lines) if (boost::geometry::distance(line, point_t{p.x, p.y}) < params.max_path_arc_length) uncrossable_lines_in_range.push_back(line); - const auto path_footprints = createPathFootprints(path, params); + const auto points = crop_and_resample(path.points, planner_data, params.resample_interval); + const auto path_footprints = createPathFootprints(points, params); const auto predicted_paths = createObjectFootprints(dynamic_objects, params); const auto expansion_polygons = params.expansion_method == "lanelet" diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp index 5304985b1cbf2..828fdc2f17a51 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp @@ -85,7 +85,7 @@ std::array calculate_arc_length_range_and_distance( } for (const auto & p : footprint.outer()) { const auto projection = point_to_linestring_projection(p, path_ls); - if (projection.arc_length <= 0.0 || projection.arc_length >= path_length) continue; + if (projection.arc_length <= 0.0 || projection.arc_length >= path_length - 1e-3) continue; if (is_left == (projection.distance > 0) && std::abs(projection.distance) > expansion_dist) { expansion_dist = std::abs(projection.distance); from_arc_length = std::min(from_arc_length, projection.arc_length); diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp index 45e128288578a..4a45dce6a0bcc 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp @@ -66,7 +66,7 @@ multi_polygon_t createObjectFootprints( } multi_polygon_t createPathFootprints( - const PathWithLaneId & path, const DrivableAreaExpansionParameters & params) + const std::vector & points, const DrivableAreaExpansionParameters & params) { const auto left = params.ego_left_offset + params.ego_extra_left_offset; const auto right = params.ego_right_offset - params.ego_extra_right_offset; @@ -78,9 +78,9 @@ multi_polygon_t createPathFootprints( point_t{front, left}}; multi_polygon_t footprints; // skip the last footprint as its orientation is usually wrong - footprints.reserve(path.points.size() - 1); + footprints.reserve(points.size() - 1); double arc_length = 0.0; - for (auto it = path.points.begin(); std::next(it) != path.points.end(); ++it) { + for (auto it = points.begin(); std::next(it) != points.end(); ++it) { footprints.push_back(createFootprint(it->point.pose, base_footprint)); if (params.max_path_arc_length > 0.0) { arc_length += tier4_autoware_utils::calcDistance2d(it->point.pose, std::next(it)->point.pose); diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index eb2859ff700af..dbfc86e5b8ba5 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1532,9 +1532,7 @@ void generateDrivableArea( } const auto & expansion_params = planner_data->drivable_area_expansion_parameters; if (expansion_params.enabled) { - drivable_area_expansion::expandDrivableArea( - path, expansion_params, *planner_data->dynamic_object, *planner_data->route_handler, - transformed_lanes); + drivable_area_expansion::expandDrivableArea(path, planner_data, transformed_lanes); } // make bound longitudinally monotonic diff --git a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp index fe33d48f74fe5..6c02609b474c4 100644 --- a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/expansion.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp" @@ -257,6 +258,7 @@ TEST(DrivableAreaExpansionProjection, expandDrivableArea) params.compensate_extra_dist = false; params.max_expansion_distance = 0.0; // means no limit params.max_path_arc_length = 0.0; // means no limit + params.resample_interval = 1.0; params.extra_arc_length = 1.0; params.expansion_method = "polygon"; // 2m x 4m ego footprint @@ -265,9 +267,15 @@ TEST(DrivableAreaExpansionProjection, expandDrivableArea) params.ego_left_offset = 2.0; params.ego_right_offset = -2.0; } + behavior_path_planner::PlannerData planner_data; + planner_data.drivable_area_expansion_parameters = params; + planner_data.reference_path = std::make_shared(path); + planner_data.dynamic_object = + std::make_shared(dynamic_objects); + planner_data.route_handler = std::make_shared(route_handler); // we expect the drivable area to be expanded by 1m on each side drivable_area_expansion::expandDrivableArea( - path, params, dynamic_objects, route_handler, path_lanes); + path, std::make_shared(planner_data), path_lanes); // unchanged path points ASSERT_EQ(path.points.size(), 3ul); for (auto i = 0.0; i < path.points.size(); ++i) { From 39e5999e1eb1a05aa12189f0f42ea41e2bc1d322 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Fri, 1 Sep 2023 10:44:30 +0900 Subject: [PATCH 081/547] fix(out_of_lane): handle undetected overlap edge cases (#4761) Signed-off-by: Maxime CLEMENT --- .../src/lanelets_selection.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp b/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp index a0e2b86caa623..9924319b141f7 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp @@ -54,16 +54,21 @@ lanelet::ConstLanelets calculate_other_lanelets( const auto lanelets_within_range = lanelet::geometry::findWithin2d( route_handler.getLaneletMapPtr()->laneletLayer, ego_point, std::max(params.slow_dist_threshold, params.stop_dist_threshold)); + const auto is_overlapped_by_path_lanelets = [&](const auto & l) { + for (const auto & path_ll : path_lanelets) { + if ( + boost::geometry::overlaps( + path_ll.polygon2d().basicPolygon(), l.polygon2d().basicPolygon()) || + boost::geometry::within(path_ll.polygon2d().basicPolygon(), l.polygon2d().basicPolygon()) || + boost::geometry::within(l.polygon2d().basicPolygon(), path_ll.polygon2d().basicPolygon())) { + return true; + } + } + return false; + }; for (const auto & ll : lanelets_within_range) { const auto is_path_lanelet = contains_lanelet(path_lanelets, ll.second.id()); const auto is_ignored_lanelet = contains_lanelet(ignored_lanelets, ll.second.id()); - const auto is_overlapped_by_path_lanelets = [&](const auto & l) { - for (const auto & path_ll : path_lanelets) - if (boost::geometry::overlaps( - path_ll.polygon2d().basicPolygon(), l.polygon2d().basicPolygon())) - return true; - return false; - }; if (!is_path_lanelet && !is_ignored_lanelet && !is_overlapped_by_path_lanelets(ll.second)) other_lanelets.push_back(ll.second); } From ade1c41c0ff9dbc4d45b8aa1bcb674b0d014774f Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Fri, 1 Sep 2023 12:50:10 +0900 Subject: [PATCH 082/547] chore(build): remove tier4_autoware_utils.hpp in common/ (#4828) removed tier4_autoware_utils.hpp in common/ Signed-off-by: Mamoru Sobue --- .../include/motion_utils/marker/marker_helper.hpp | 3 ++- .../motion_utils/marker/virtual_wall_marker_creator.hpp | 2 -- common/motion_utils/src/marker/marker_helper.cpp | 4 ++++ .../motion_utils/src/marker/virtual_wall_marker_creator.cpp | 2 ++ .../path_distance_calculator/src/path_distance_calculator.cpp | 1 - common/tier4_vehicle_rviz_plugin/src/tools/console_meter.hpp | 3 ++- control/mpc_lateral_controller/src/mpc.cpp | 1 + 7 files changed, 11 insertions(+), 5 deletions(-) diff --git a/common/motion_utils/include/motion_utils/marker/marker_helper.hpp b/common/motion_utils/include/motion_utils/marker/marker_helper.hpp index 6433f1b9ae2f9..b6d10bbf40de5 100644 --- a/common/motion_utils/include/motion_utils/marker/marker_helper.hpp +++ b/common/motion_utils/include/motion_utils/marker/marker_helper.hpp @@ -16,7 +16,8 @@ #define MOTION_UTILS__MARKER__MARKER_HELPER_HPP_ #include "motion_utils/resample/resample_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" + +#include #include #include diff --git a/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp b/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp index 331b3dd5275fd..afead3a3614d4 100644 --- a/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp +++ b/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp @@ -18,8 +18,6 @@ #include "motion_utils/marker/marker_helper.hpp" #include -#include -#include #include #include diff --git a/common/motion_utils/src/marker/marker_helper.cpp b/common/motion_utils/src/marker/marker_helper.cpp index 25d8616949b02..c869271222387 100644 --- a/common/motion_utils/src/marker/marker_helper.cpp +++ b/common/motion_utils/src/marker/marker_helper.cpp @@ -14,6 +14,10 @@ #include "motion_utils/marker/marker_helper.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" + +#include + #include using tier4_autoware_utils::appendMarkerArray; diff --git a/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp b/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp index 63c456ab24fb0..114c4f87907c0 100644 --- a/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp +++ b/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp @@ -14,6 +14,8 @@ #include "motion_utils/marker/virtual_wall_marker_creator.hpp" +#include + namespace motion_utils { diff --git a/common/path_distance_calculator/src/path_distance_calculator.cpp b/common/path_distance_calculator/src/path_distance_calculator.cpp index 85578d3c18dbc..aa20dd1ffc7ce 100644 --- a/common/path_distance_calculator/src/path_distance_calculator.cpp +++ b/common/path_distance_calculator/src/path_distance_calculator.cpp @@ -15,7 +15,6 @@ #include "path_distance_calculator.hpp" #include -#include #include #include diff --git a/common/tier4_vehicle_rviz_plugin/src/tools/console_meter.hpp b/common/tier4_vehicle_rviz_plugin/src/tools/console_meter.hpp index e71003ea7f61c..cba0fa16b50fe 100644 --- a/common/tier4_vehicle_rviz_plugin/src/tools/console_meter.hpp +++ b/common/tier4_vehicle_rviz_plugin/src/tools/console_meter.hpp @@ -25,7 +25,8 @@ #include #include #include -#include +#include +#include #include #endif diff --git a/control/mpc_lateral_controller/src/mpc.cpp b/control/mpc_lateral_controller/src/mpc.cpp index 643f8a6f91023..62f96a89676ec 100644 --- a/control/mpc_lateral_controller/src/mpc.cpp +++ b/control/mpc_lateral_controller/src/mpc.cpp @@ -16,6 +16,7 @@ #include "motion_utils/motion_utils.hpp" #include "mpc_lateral_controller/mpc_utils.hpp" +#include "tier4_autoware_utils/math/unit_conversion.hpp" #include #include From fbb59d9147075c9318b4443bae22c3a3eb388331 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Fri, 1 Sep 2023 15:37:55 +0900 Subject: [PATCH 083/547] feat(out_of_lane): filter predicted paths and add min assumed velocity (#4784) Signed-off-by: Maxime CLEMENT --- .../config/out_of_lane.param.yaml | 1 + .../src/decisions.cpp | 19 +++--- .../src/decisions.hpp | 5 +- .../src/filter_predicted_objects.hpp | 68 +++++++++++++++++++ .../src/manager.cpp | 1 + .../src/scene_out_of_lane.cpp | 3 +- .../src/types.hpp | 7 +- 7 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp diff --git a/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml b/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml index dd4c1c610261c..510dc86ef651d 100644 --- a/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml +++ b/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml @@ -34,6 +34,7 @@ distance_threshold: 15.0 # [m] insert a stop when closer than this distance from an overlap ego: + min_assumed_velocity: 2.0 # [m/s] minimum velocity used to calculate the enter and exit times of ego extra_front_offset: 0.0 # [m] extra front distance extra_rear_offset: 0.0 # [m] extra rear distance extra_right_offset: 0.0 # [m] extra right distance diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp index 163c8632e40c7..46db7a53b5c81 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp @@ -30,11 +30,11 @@ double distance_along_path(const EgoData & ego_data, const size_t target_idx) ego_data.path->points, ego_data.pose.position, ego_data.first_path_idx + target_idx); } -double time_along_path(const EgoData & ego_data, const size_t target_idx) +double time_along_path(const EgoData & ego_data, const size_t target_idx, const double min_velocity) { const auto dist = distance_along_path(ego_data, target_idx); const auto v = std::max( - ego_data.velocity, + std::max(ego_data.velocity, min_velocity), ego_data.path->points[ego_data.first_path_idx + target_idx].point.longitudinal_velocity_mps * 0.5); return dist / v; @@ -55,8 +55,7 @@ bool object_is_incoming( std::optional> object_time_to_range( const autoware_auto_perception_msgs::msg::PredictedObject & object, const OverlapRange & range, - const std::shared_ptr route_handler, const double min_confidence, - const rclcpp::Logger & logger) + const std::shared_ptr route_handler, const rclcpp::Logger & logger) { // skip the dynamic object if it is not in a lane preceding the overlapped lane // lane changes are intentionally not considered @@ -65,13 +64,12 @@ std::optional> object_time_to_range( object.kinematics.initial_pose_with_covariance.pose.position.y); if (!object_is_incoming(object_point, route_handler, range.lane)) return {}; - const auto max_deviation = object.shape.dimensions.y * 2.0; + const auto max_deviation = object.shape.dimensions.y + range.inside_distance; auto worst_enter_time = std::optional(); auto worst_exit_time = std::optional(); for (const auto & predicted_path : object.kinematics.predicted_paths) { - if (predicted_path.confidence < min_confidence) continue; const auto time_step = rclcpp::Duration(predicted_path.time_step).seconds(); const auto enter_point = geometry_msgs::msg::Point().set__x(range.entering_point.x()).set__y(range.entering_point.y()); @@ -281,8 +279,10 @@ bool should_not_enter( const rclcpp::Logger & logger) { RangeTimes range_times{}; - range_times.ego.enter_time = time_along_path(inputs.ego_data, range.entering_path_idx); - range_times.ego.exit_time = time_along_path(inputs.ego_data, range.exiting_path_idx); + range_times.ego.enter_time = + time_along_path(inputs.ego_data, range.entering_path_idx, params.ego_min_velocity); + range_times.ego.exit_time = + time_along_path(inputs.ego_data, range.exiting_path_idx, params.ego_min_velocity); RCLCPP_DEBUG( logger, "\t[%lu -> %lu] %ld | ego enters at %2.2f, exits at %2.2f\n", range.entering_path_idx, range.exiting_path_idx, range.lane.id(), range_times.ego.enter_time, range_times.ego.exit_time); @@ -298,8 +298,7 @@ bool should_not_enter( // skip objects that are already on the interval const auto enter_exit_time = params.objects_use_predicted_paths - ? object_time_to_range( - object, range, inputs.route_handler, params.objects_min_confidence, logger) + ? object_time_to_range(object, range, inputs.route_handler, logger) : object_time_to_range(object, range, inputs, logger); if (!enter_exit_time) { RCLCPP_DEBUG(logger, " SKIP (no enter/exit times found)\n"); diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp index bf6bf81e506cf..1d1fe8bea94a6 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp @@ -48,6 +48,7 @@ double distance_along_path(const EgoData & ego_data, const size_t target_idx); /// @brief estimate the time when ego will reach some target path index /// @param [in] ego_data data related to the ego vehicle /// @param [in] target_idx target ego path index +/// @param [in] min_velocity minimum ego velocity used to estimate the time /// @return time taken by ego to reach the target [s] double time_along_path(const EgoData & ego_data, const size_t target_idx); /// @brief use an object's predicted paths to estimate the times it will reach the enter and exit @@ -57,14 +58,12 @@ double time_along_path(const EgoData & ego_data, const size_t target_idx); /// @param [in] object dynamic object /// @param [in] range overlapping range /// @param [in] route_handler route handler used to estimate the path of the dynamic object -/// @param [in] min_confidence minimum confidence to consider a predicted path /// @param [in] logger ros logger /// @return an optional pair (time at enter [s], time at exit [s]). If the dynamic object drives in /// the opposite direction, time at enter > time at exit std::optional> object_time_to_range( const autoware_auto_perception_msgs::msg::PredictedObject & object, const OverlapRange & range, - const std::shared_ptr route_handler, const double min_confidence, - const rclcpp::Logger & logger); + const std::shared_ptr route_handler, const rclcpp::Logger & logger); /// @brief use the lanelet map to estimate the times when an object will reach the enter and exit /// points of an overlapping range /// @param [in] object dynamic object diff --git a/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp new file mode 100644 index 0000000000000..48869e5e3926d --- /dev/null +++ b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp @@ -0,0 +1,68 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FILTER_PREDICTED_OBJECTS_HPP_ +#define FILTER_PREDICTED_OBJECTS_HPP_ + +#include "types.hpp" + +#include + +namespace behavior_velocity_planner::out_of_lane +{ +/// @brief filter predicted objects and their predicted paths +/// @param [in] objects predicted objects to filter +/// @param [in] ego_data ego data +/// @param [in] params parameters +/// @return filtered predicted objects +autoware_auto_perception_msgs::msg::PredictedObjects filter_predicted_objects( + const autoware_auto_perception_msgs::msg::PredictedObjects & objects, const EgoData & ego_data, + const PlannerParam & params) +{ + autoware_auto_perception_msgs::msg::PredictedObjects filtered_objects; + filtered_objects.header = objects.header; + for (const auto & object : objects.objects) { + const auto is_pedestrian = + std::find_if(object.classification.begin(), object.classification.end(), [](const auto & c) { + return c.label == autoware_auto_perception_msgs::msg::ObjectClassification::PEDESTRIAN; + }) != object.classification.end(); + if (is_pedestrian) continue; + + auto filtered_object = object; + const auto is_invalid_predicted_path = [&](const auto & predicted_path) { + const auto is_low_confidence = predicted_path.confidence < params.objects_min_confidence; + const auto lat_offset_to_current_ego = + std::abs(motion_utils::calcLateralOffset(predicted_path.path, ego_data.pose.position)); + const auto is_crossing_ego = + lat_offset_to_current_ego <= + object.shape.dimensions.y / 2.0 + std::max( + params.left_offset + params.extra_left_offset, + params.right_offset + params.extra_right_offset); + return is_low_confidence || is_crossing_ego; + }; + if (params.objects_use_predicted_paths) { + auto & predicted_paths = filtered_object.kinematics.predicted_paths; + const auto new_end = + std::remove_if(predicted_paths.begin(), predicted_paths.end(), is_invalid_predicted_path); + predicted_paths.erase(new_end, predicted_paths.end()); + } + if (!params.objects_use_predicted_paths || !filtered_object.kinematics.predicted_paths.empty()) + filtered_objects.objects.push_back(filtered_object); + } + return filtered_objects; +} + +} // namespace behavior_velocity_planner::out_of_lane + +#endif // FILTER_PREDICTED_OBJECTS_HPP_ diff --git a/planning/behavior_velocity_out_of_lane_module/src/manager.cpp b/planning/behavior_velocity_out_of_lane_module/src/manager.cpp index 075738f07d62e..5765363024aed 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/manager.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/manager.cpp @@ -64,6 +64,7 @@ OutOfLaneModuleManager::OutOfLaneModuleManager(rclcpp::Node & node) pp.stop_dist_threshold = getOrDeclareParameter(node, ns + ".action.stop.distance_threshold"); + pp.ego_min_velocity = getOrDeclareParameter(node, ns + ".ego.min_assumed_velocity"); pp.extra_front_offset = getOrDeclareParameter(node, ns + ".ego.extra_front_offset"); pp.extra_rear_offset = getOrDeclareParameter(node, ns + ".ego.extra_rear_offset"); pp.extra_left_offset = getOrDeclareParameter(node, ns + ".ego.extra_left_offset"); diff --git a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp index 5922a5b9b4488..430cec2d3d4ab 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp @@ -16,6 +16,7 @@ #include "debug.hpp" #include "decisions.hpp" +#include "filter_predicted_objects.hpp" #include "footprint.hpp" #include "lanelets_selection.hpp" #include "overlapping_range.hpp" @@ -103,7 +104,7 @@ bool OutOfLaneModule::modifyPathVelocity( DecisionInputs inputs; inputs.ranges = ranges; inputs.ego_data = ego_data; - inputs.objects = *planner_data_->predicted_objects; + inputs.objects = filter_predicted_objects(*planner_data_->predicted_objects, ego_data, params_); inputs.route_handler = planner_data_->route_handler_; inputs.lanelets = other_lanelets; auto decisions = calculate_decisions(inputs, params_, logger_); diff --git a/planning/behavior_velocity_out_of_lane_module/src/types.hpp b/planning/behavior_velocity_out_of_lane_module/src/types.hpp index 85266d779f34e..b81fa09884819 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/types.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/types.hpp @@ -42,10 +42,11 @@ struct PlannerParam double intervals_ego_buffer; // [s](mode="intervals") buffer to extend the ego time range double intervals_obj_buffer; // [s](mode="intervals") buffer to extend the objects time range double ttc_threshold; // [s](mode="ttc") threshold on time to collision between ego and an object + double ego_min_velocity; // [m/s] minimum velocity of ego used to calculate its ttc or time range - bool objects_use_predicted_paths; // # whether to use the objects' predicted paths - double objects_min_vel; // # [m/s] objects lower than this velocity will be ignored - double objects_min_confidence; // # minimum confidence to consider a predicted path + bool objects_use_predicted_paths; // whether to use the objects' predicted paths + double objects_min_vel; // [m/s] objects lower than this velocity will be ignored + double objects_min_confidence; // minimum confidence to consider a predicted path double overlap_extra_length; // [m] extra length to add around an overlap range double overlap_min_dist; // [m] min distance inside another lane to consider an overlap From 6b118c2f57d681eb8ba326e265cf61f13f15b75e Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Fri, 1 Sep 2023 15:40:27 +0900 Subject: [PATCH 084/547] fix(lane_change): fix state transition in lane change for multiple module cooperation (#4836) Signed-off-by: Fumiya Watanabe --- .../behavior_path_planner/planner_manager.hpp | 10 ++-------- .../src/planner_manager.cpp | 2 +- .../scene_module/lane_change/interface.cpp | 19 +++---------------- 3 files changed, 6 insertions(+), 25 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index 29857590d4eda..b77ac16a9db61 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -364,16 +364,10 @@ class PlannerManager * @param planner data. * @return root lanelet. */ - lanelet::ConstLanelet updateRootLanelet( - const std::shared_ptr & data, bool success_lane_change = false) const + lanelet::ConstLanelet updateRootLanelet(const std::shared_ptr & data) const { lanelet::ConstLanelet ret{}; - if (success_lane_change) { - data->route_handler->getClosestPreferredLaneletWithinRoute( - data->self_odometry->pose.pose, &ret); - } else { - data->route_handler->getClosestLaneletWithinRoute(data->self_odometry->pose.pose, &ret); - } + data->route_handler->getClosestLaneletWithinRoute(data->self_odometry->pose.pose, &ret); RCLCPP_DEBUG(logger_, "update start lanelet. id:%ld", ret.id()); return ret; } diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index 920b44235cba9..e6e2bf9ac5346 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -580,7 +580,7 @@ BehaviorModuleOutput PlannerManager::runApprovedModules(const std::shared_ptrname().find("lane_change") != std::string::npos; }); if (success_lane_change) { - root_lanelet_ = updateRootLanelet(data, true); + root_lanelet_ = updateRootLanelet(data); } std::for_each(success_module_itr, approved_module_ptrs_.end(), [&](auto & m) { diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 64a7e926cc2df..74af83c2db2a1 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -88,14 +88,11 @@ ModuleStatus LaneChangeInterface::updateState() } if (!module_type_->isValidPath()) { - return ModuleStatus::RUNNING; + return ModuleStatus::SUCCESS; } if (module_type_->isAbortState()) { - if (module_type_->hasFinishedAbort()) { - resetLaneChangeModule(); - } - return ModuleStatus::RUNNING; + return module_type_->hasFinishedAbort() ? ModuleStatus::SUCCESS : ModuleStatus::RUNNING; } if (module_type_->hasFinishedLaneChange()) { @@ -154,10 +151,7 @@ ModuleStatus LaneChangeInterface::updateState() getLogger().get_child(module_type_->getModuleTypeStr()), *clock_, 5000, "Lane change path is unsafe. Cancel lane change."); module_type_->toCancelState(); - if (!isWaitingApproval()) { - resetLaneChangeModule(); - } - return ModuleStatus::RUNNING; + return isWaitingApproval() ? ModuleStatus::RUNNING : ModuleStatus::SUCCESS; } if (!module_type_->isAbortEnabled()) { @@ -192,13 +186,6 @@ ModuleStatus LaneChangeInterface::updateState() return ModuleStatus::RUNNING; } -void LaneChangeInterface::resetLaneChangeModule() -{ - processOnExit(); - removeRTCStatus(); - processOnEntry(); -} - void LaneChangeInterface::updateData() { module_type_->setPreviousModulePaths( From acfe549fbc557edb8bbedc7589746c3c14d071c5 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Fri, 1 Sep 2023 16:48:06 +0900 Subject: [PATCH 085/547] fix(behavior_path_planner): fix left/right split of the drivable area (#4738) Signed-off-by: Maxime CLEMENT --- .../drivable_area_expansion.cpp | 19 ++++++++----------- .../test/test_drivable_area_expansion.cpp | 4 +++- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index f494a377e0f95..0fcb0acfcb000 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -165,8 +165,8 @@ void updateDrivableAreaBounds(PathWithLaneId & path, const polygon_t & expanded_ { const auto original_left_bound = path.left_bound; const auto original_right_bound = path.right_bound; - const auto is_left_of_segment = [](const point_t & a, const point_t & b, const point_t & p) { - return (b.x() - a.x()) * (p.y() - a.y()) - (b.y() - a.y()) * (p.x() - a.x()) > 0; + const auto is_left_of_path = [&](const point_t & p) { + return motion_utils::calcLateralOffset(path.points, convert_point(p)) > 0.0; }; // prepare delimiting lines: start and end of the original expanded drivable area const auto start_segment = @@ -230,12 +230,10 @@ void updateDrivableAreaBounds(PathWithLaneId & path, const polygon_t & expanded_ *it, *std::next(it), start_segment.first, start_segment.second); if (inter_start) { const auto dist = boost::geometry::distance(*inter_start, path_start_segment); - const auto is_left_of_path_start = is_left_of_segment( - convert_point(path.points[0].point.pose.position), - convert_point(path.points[1].point.pose.position), *inter_start); - if (is_left_of_path_start && dist < start_left.distance) + const auto is_left = is_left_of_path(*inter_start); + if (is_left && dist < start_left.distance) start_left.update(*inter_start, it, dist); - else if (!is_left_of_path_start && dist < start_right.distance) + else if (!is_left && dist < start_right.distance) start_right.update(*inter_start, it, dist); } const auto inter_end = @@ -244,11 +242,10 @@ void updateDrivableAreaBounds(PathWithLaneId & path, const polygon_t & expanded_ : segment_to_line_intersection(*it, *std::next(it), end_segment.first, end_segment.second); if (inter_end) { const auto dist = boost::geometry::distance(*inter_end, path_end_segment); - const auto is_left_of_path_end = is_left_of_segment( - convert_point(path.points.back().point.pose.position), end_segment_center, *inter_end); - if (is_left_of_path_end && dist < end_left.distance) + const auto is_left = is_left_of_path(*inter_end); + if (is_left && dist < end_left.distance) end_left.update(*inter_end, it, dist); - else if (!is_left_of_path_end && dist < end_right.distance) + else if (!is_left && dist < end_right.distance) end_right.update(*inter_end, it, dist); } } diff --git a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp index 6c02609b474c4..7a5bb68decdfa 100644 --- a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp @@ -284,13 +284,15 @@ TEST(DrivableAreaExpansionProjection, expandDrivableArea) } // expanded left bound - ASSERT_EQ(path.left_bound.size(), 3ul); + ASSERT_EQ(path.left_bound.size(), 4ul); EXPECT_NEAR(path.left_bound[0].x, 0.0, eps); EXPECT_NEAR(path.left_bound[0].y, 1.0, eps); EXPECT_NEAR(path.left_bound[1].x, 0.0, eps); EXPECT_NEAR(path.left_bound[1].y, 2.0, eps); EXPECT_NEAR(path.left_bound[2].x, 2.0, eps); EXPECT_NEAR(path.left_bound[2].y, 2.0, eps); + EXPECT_NEAR(path.left_bound[3].x, 2.0, eps); + EXPECT_NEAR(path.left_bound[3].y, 1.0, eps); // expanded right bound ASSERT_EQ(path.right_bound.size(), 3ul); EXPECT_NEAR(path.right_bound[0].x, 0.0, eps); From 472a678cba601fe9c2ad2a634ac7280fbf4c275d Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Fri, 1 Sep 2023 19:29:52 +0900 Subject: [PATCH 086/547] chore(build): remove tier4_autoware_utils.hpp system/ (#4840) --- system/default_ad_api/src/planning.cpp | 2 ++ system/default_ad_api/src/planning.hpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/system/default_ad_api/src/planning.cpp b/system/default_ad_api/src/planning.cpp index 46c8ea4125c9a..5df76e9b5f3cc 100644 --- a/system/default_ad_api/src/planning.cpp +++ b/system/default_ad_api/src/planning.cpp @@ -14,6 +14,8 @@ #include "planning.hpp" +#include + #include #include #include diff --git a/system/default_ad_api/src/planning.hpp b/system/default_ad_api/src/planning.hpp index 03b45fd3b5d41..b533390dcc6fb 100644 --- a/system/default_ad_api/src/planning.hpp +++ b/system/default_ad_api/src/planning.hpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include From af34f67ab8cfd62a1fbaa250cae35150ca073b3f Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Fri, 1 Sep 2023 22:20:31 +0900 Subject: [PATCH 087/547] fix(map_projection_loader): fix altitude output (#4850) --- map/map_projection_loader/src/map_projection_loader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/map/map_projection_loader/src/map_projection_loader.cpp b/map/map_projection_loader/src/map_projection_loader.cpp index 79083b755fb26..74c340ca53557 100644 --- a/map/map_projection_loader/src/map_projection_loader.cpp +++ b/map/map_projection_loader/src/map_projection_loader.cpp @@ -35,6 +35,7 @@ tier4_map_msgs::msg::MapProjectorInfo load_info_from_yaml(const std::string & fi msg.vertical_datum = data["vertical_datum"].as(); msg.map_origin.latitude = data["map_origin"]["latitude"].as(); msg.map_origin.longitude = data["map_origin"]["longitude"].as(); + msg.map_origin.altitude = data["map_origin"]["altitude"].as(); } else if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL) { ; // do nothing } else { From 9eb5b14c916cad4f0072d1042989cca7e2be4c8d Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Fri, 1 Sep 2023 22:37:05 +0900 Subject: [PATCH 088/547] chore(build): remove tier4_autoware_utils.hpp localization/ map/ (#4842) --- .../gyro_odometer/include/gyro_odometer/gyro_odometer_core.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/gyro_odometer/include/gyro_odometer/gyro_odometer_core.hpp b/localization/gyro_odometer/include/gyro_odometer/gyro_odometer_core.hpp index b0c7a5c4471c6..97d4a02472f2d 100644 --- a/localization/gyro_odometer/include/gyro_odometer/gyro_odometer_core.hpp +++ b/localization/gyro_odometer/include/gyro_odometer/gyro_odometer_core.hpp @@ -15,8 +15,8 @@ #ifndef GYRO_ODOMETER__GYRO_ODOMETER_CORE_HPP_ #define GYRO_ODOMETER__GYRO_ODOMETER_CORE_HPP_ +#include "tier4_autoware_utils/ros/msg_covariance.hpp" #include "tier4_autoware_utils/ros/transform_listener.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include From 162817550714cd0ab16b81947dd664e512b08f08 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Fri, 1 Sep 2023 22:50:40 +0900 Subject: [PATCH 089/547] feat(gnss_poser): subscribe map projector info (#4791) * feat(gnss_poser): Subscribe map_projector_info Signed-off-by: kminoda * style(pre-commit): autofix * update readme Signed-off-by: kminoda * style(pre-commit): autofix * small fix Signed-off-by: kminoda * update commetn Signed-off-by: kminoda * style(pre-commit): autofix * add local cartesian Signed-off-by: kminoda * update launch file Signed-off-by: kminoda * style(pre-commit): autofix * fix Signed-off-by: kminoda * create new function for conversion of height Signed-off-by: kminoda * minor update Signed-off-by: kminoda * style(pre-commit): autofix * update Signed-off-by: kminoda * rename Signed-off-by: kminoda * remove unnecessary include Signed-off-by: kminoda * fix map origin Signed-off-by: kminoda * style(pre-commit): autofix * remove config file Signed-off-by: kminoda * rfix readme Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sensing/gnss_poser/CMakeLists.txt | 1 - sensing/gnss_poser/README.md | 2 +- .../config/set_local_origin.param.yaml | 11 ----- .../gnss_poser/include/gnss_poser/convert.hpp | 34 +++++-------- .../include/gnss_poser/gnss_poser_core.hpp | 16 ++++--- .../include/gnss_poser/gnss_stat.hpp | 2 - .../gnss_poser/launch/gnss_poser.launch.xml | 6 --- sensing/gnss_poser/package.xml | 3 ++ sensing/gnss_poser/src/gnss_poser_core.cpp | 48 ++++++++++++------- 9 files changed, 57 insertions(+), 66 deletions(-) delete mode 100644 sensing/gnss_poser/config/set_local_origin.param.yaml diff --git a/sensing/gnss_poser/CMakeLists.txt b/sensing/gnss_poser/CMakeLists.txt index 993cf2d9da9e1..bd9886d376cad 100644 --- a/sensing/gnss_poser/CMakeLists.txt +++ b/sensing/gnss_poser/CMakeLists.txt @@ -37,5 +37,4 @@ rclcpp_components_register_node(gnss_poser_node ament_auto_package(INSTALL_TO_SHARE launch - config ) diff --git a/sensing/gnss_poser/README.md b/sensing/gnss_poser/README.md index 3487e9515c48a..b271f75eeeb6c 100644 --- a/sensing/gnss_poser/README.md +++ b/sensing/gnss_poser/README.md @@ -12,6 +12,7 @@ The `gnss_poser` is a node that subscribes gnss sensing messages and calculates | Name | Type | Description | | ------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `/map/map_projector_info` | `tier4_map_msgs::msg::MapProjectorInfo` | map projection info | | `~/input/fix` | `sensor_msgs::msg::NavSatFix` | gnss status message | | `~/input/autoware_orientation` | `autoware_sensing_msgs::msg::GnssInsOrientationStamped` | orientation [click here for more details](https://github.com/autowarefoundation/autoware_msgs/tree/main/autoware_sensing_msgs) | @@ -33,7 +34,6 @@ The `gnss_poser` is a node that subscribes gnss sensing messages and calculates | `gnss_frame` | string | "gnss" | frame id | | `gnss_base_frame` | string | "gnss_base_link" | frame id | | `map_frame` | string | "map" | frame id | -| `coordinate_system` | int | "4" | coordinate system enumeration; 1: MGRS, 4: UTM Local Coordinate System | | `gnss_pose_pub_method` | int | 0 | 0: Instant Value 1: Average Value 2: Median Value. If 0 is chosen buffer_epoch parameter loses affect. | ## Assumptions / Known limits diff --git a/sensing/gnss_poser/config/set_local_origin.param.yaml b/sensing/gnss_poser/config/set_local_origin.param.yaml deleted file mode 100644 index e931e5adceb68..0000000000000 --- a/sensing/gnss_poser/config/set_local_origin.param.yaml +++ /dev/null @@ -1,11 +0,0 @@ -/**: - ros__parameters: - - # Latitude of local origin - latitude: 40.81187906 - - # Longitude of local origin - longitude: 29.35810110 - - # Altitude of local origin - altitude: 47.62 diff --git a/sensing/gnss_poser/include/gnss_poser/convert.hpp b/sensing/gnss_poser/include/gnss_poser/convert.hpp index 4dda5d1276c54..5cd0767f28130 100644 --- a/sensing/gnss_poser/include/gnss_poser/convert.hpp +++ b/sensing/gnss_poser/include/gnss_poser/convert.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -42,7 +43,7 @@ enum class MGRSPrecision { GNSSStat NavSatFix2UTM( const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, const rclcpp::Logger & logger, - int height_system) + const std::string & target_vertical_datum) { GNSSStat utm; @@ -50,16 +51,9 @@ GNSSStat NavSatFix2UTM( GeographicLib::UTMUPS::Forward( nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, utm.zone, utm.east_north_up, utm.x, utm.y); - - std::string target_height_system; - if (height_system == 0) { - target_height_system = "EGM2008"; - } else { - target_height_system = "WGS84"; - } utm.z = geography_utils::convert_height( nav_sat_fix_msg.altitude, nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, "WGS84", - target_height_system); + target_vertical_datum); utm.latitude = nav_sat_fix_msg.latitude; utm.longitude = nav_sat_fix_msg.longitude; utm.altitude = nav_sat_fix_msg.altitude; @@ -71,24 +65,20 @@ GNSSStat NavSatFix2UTM( GNSSStat NavSatFix2LocalCartesianUTM( const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, - sensor_msgs::msg::NavSatFix nav_sat_fix_origin, const rclcpp::Logger & logger, int height_system) + const geographic_msgs::msg::GeoPoint geo_point_origin, const rclcpp::Logger & logger, + const std::string & target_vertical_datum) { GNSSStat utm_local; try { // origin of the local coordinate system in global frame GNSSStat utm_origin; GeographicLib::UTMUPS::Forward( - nav_sat_fix_origin.latitude, nav_sat_fix_origin.longitude, utm_origin.zone, + geo_point_origin.latitude, geo_point_origin.longitude, utm_origin.zone, utm_origin.east_north_up, utm_origin.x, utm_origin.y); - std::string target_height_system; - if (height_system == 0) { - target_height_system = "EGM2008"; - } else { - target_height_system = "WGS84"; - } + utm_origin.z = geography_utils::convert_height( - nav_sat_fix_origin.altitude, nav_sat_fix_origin.latitude, nav_sat_fix_origin.longitude, - "WGS84", target_height_system); + geo_point_origin.altitude, geo_point_origin.latitude, geo_point_origin.longitude, "WGS84", + target_vertical_datum); // individual coordinates of global coordinate system double global_x = 0.0; @@ -104,7 +94,7 @@ GNSSStat NavSatFix2LocalCartesianUTM( utm_local.y = global_y - utm_origin.y; utm_local.z = geography_utils::convert_height( nav_sat_fix_msg.altitude, nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, - "WGS84", target_height_system) - + "WGS84", target_vertical_datum) - utm_origin.z; } catch (const GeographicLib::GeographicErr & err) { RCLCPP_ERROR_STREAM( @@ -143,9 +133,9 @@ GNSSStat UTM2MGRS( GNSSStat NavSatFix2MGRS( const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, const MGRSPrecision & precision, - const rclcpp::Logger & logger, int height_system) + const rclcpp::Logger & logger, const std::string & vertical_datum) { - const auto utm = NavSatFix2UTM(nav_sat_fix_msg, logger, height_system); + const auto utm = NavSatFix2UTM(nav_sat_fix_msg, logger, vertical_datum); const auto mgrs = UTM2MGRS(utm, precision, logger); return mgrs; } diff --git a/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp b/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp index 4baaec2dfe80e..05acfa967eb96 100644 --- a/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp +++ b/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp @@ -17,6 +17,8 @@ #include "gnss_poser/convert.hpp" #include "gnss_poser/gnss_stat.hpp" +#include +#include #include #include @@ -48,6 +50,9 @@ class GNSSPoser : public rclcpp::Node explicit GNSSPoser(const rclcpp::NodeOptions & node_options); private: + using MapProjectorInfo = map_interface::MapProjectorInfo; + + void callbackMapProjectorInfo(const MapProjectorInfo::Message::ConstSharedPtr msg); void callbackNavSatFix(const sensor_msgs::msg::NavSatFix::ConstSharedPtr nav_sat_fix_msg_ptr); void callbackGnssInsOrientationStamped( const autoware_sensing_msgs::msg::GnssInsOrientationStamped::ConstSharedPtr msg); @@ -55,8 +60,8 @@ class GNSSPoser : public rclcpp::Node bool isFixed(const sensor_msgs::msg::NavSatStatus & nav_sat_status_msg); bool canGetCovariance(const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg); GNSSStat convert( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, CoordinateSystem coordinate_system, - int height_system); + const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, + const MapProjectorInfo::Message & projector_info); geometry_msgs::msg::Point getPosition(const GNSSStat & gnss_stat); geometry_msgs::msg::Point getMedianPosition( const boost::circular_buffer & position_buffer); @@ -81,6 +86,7 @@ class GNSSPoser : public rclcpp::Node tf2_ros::TransformListener tf2_listener_; tf2_ros::TransformBroadcaster tf2_broadcaster_; + component_interface_utils::Subscription::SharedPtr sub_map_projector_info_; rclcpp::Subscription::SharedPtr nav_sat_fix_sub_; rclcpp::Subscription::SharedPtr autoware_orientation_sub_; @@ -89,20 +95,18 @@ class GNSSPoser : public rclcpp::Node rclcpp::Publisher::SharedPtr pose_cov_pub_; rclcpp::Publisher::SharedPtr fixed_pub_; - CoordinateSystem coordinate_system_; + MapProjectorInfo::Message projector_info_; std::string base_frame_; std::string gnss_frame_; std::string gnss_base_frame_; std::string map_frame_; - - sensor_msgs::msg::NavSatFix nav_sat_fix_origin_; + bool received_map_projector_info_ = false; bool use_gnss_ins_orientation_; boost::circular_buffer position_buffer_; autoware_sensing_msgs::msg::GnssInsOrientationStamped::SharedPtr msg_gnss_ins_orientation_stamped_; - int height_system_; int gnss_pose_pub_method; }; } // namespace gnss_poser diff --git a/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp b/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp index d33bd022c3714..acd21a83d96e4 100644 --- a/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp +++ b/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp @@ -18,8 +18,6 @@ namespace gnss_poser { -enum class CoordinateSystem { MGRS = 1, LOCAL_CARTESIAN_UTM = 4 }; - struct GNSSStat { GNSSStat() diff --git a/sensing/gnss_poser/launch/gnss_poser.launch.xml b/sensing/gnss_poser/launch/gnss_poser.launch.xml index 8a0ffb8cab8c3..60e952c6845f4 100755 --- a/sensing/gnss_poser/launch/gnss_poser.launch.xml +++ b/sensing/gnss_poser/launch/gnss_poser.launch.xml @@ -2,7 +2,6 @@ - @@ -13,10 +12,8 @@ - - @@ -31,11 +28,8 @@ - - - diff --git a/sensing/gnss_poser/package.xml b/sensing/gnss_poser/package.xml index 77380bf5492a1..7d80bc903a550 100644 --- a/sensing/gnss_poser/package.xml +++ b/sensing/gnss_poser/package.xml @@ -14,6 +14,9 @@ libboost-dev autoware_sensing_msgs + component_interface_specs + component_interface_utils + geographic_msgs geographiclib geography_utils geometry_msgs diff --git a/sensing/gnss_poser/src/gnss_poser_core.cpp b/sensing/gnss_poser/src/gnss_poser_core.cpp index 4f0723944913b..9cd5a3ad34098 100644 --- a/sensing/gnss_poser/src/gnss_poser_core.cpp +++ b/sensing/gnss_poser/src/gnss_poser_core.cpp @@ -34,16 +34,13 @@ GNSSPoser::GNSSPoser(const rclcpp::NodeOptions & node_options) use_gnss_ins_orientation_(declare_parameter("use_gnss_ins_orientation", true)), msg_gnss_ins_orientation_stamped_( std::make_shared()), - height_system_(declare_parameter("height_system", 1)), gnss_pose_pub_method(declare_parameter("gnss_pose_pub_method", 0)) { - int coordinate_system = - declare_parameter("coordinate_system", static_cast(CoordinateSystem::MGRS)); - coordinate_system_ = static_cast(coordinate_system); - - nav_sat_fix_origin_.latitude = declare_parameter("latitude", 0.0); - nav_sat_fix_origin_.longitude = declare_parameter("longitude", 0.0); - nav_sat_fix_origin_.altitude = declare_parameter("altitude", 0.0); + // Subscribe to map_projector_info topic + const auto adaptor = component_interface_utils::NodeAdaptor(this); + adaptor.init_sub( + sub_map_projector_info_, + [this](const MapProjectorInfo::Message::ConstSharedPtr msg) { callbackMapProjectorInfo(msg); }); int buff_epoch = declare_parameter("buff_epoch", 1); position_buffer_.set_capacity(buff_epoch); @@ -61,9 +58,24 @@ GNSSPoser::GNSSPoser(const rclcpp::NodeOptions & node_options) fixed_pub_ = create_publisher("gnss_fixed", rclcpp::QoS{1}); } +void GNSSPoser::callbackMapProjectorInfo(const MapProjectorInfo::Message::ConstSharedPtr msg) +{ + projector_info_ = *msg; + received_map_projector_info_ = true; +} + void GNSSPoser::callbackNavSatFix( const sensor_msgs::msg::NavSatFix::ConstSharedPtr nav_sat_fix_msg_ptr) { + // Return immediately if map_projector_info has not been received yet. + if (!received_map_projector_info_) { + RCLCPP_WARN_THROTTLE( + this->get_logger(), *this->get_clock(), std::chrono::milliseconds(1000).count(), + "map_projector_info has not been received yet. Check if the map_projection_loader is " + "successfully launched."); + return; + } + // check fixed topic const bool is_fixed = isFixed(nav_sat_fix_msg_ptr->status); @@ -80,8 +92,8 @@ void GNSSPoser::callbackNavSatFix( return; } - // get position in coordinate_system - const auto gnss_stat = convert(*nav_sat_fix_msg_ptr, coordinate_system_, height_system_); + // get position + const auto gnss_stat = convert(*nav_sat_fix_msg_ptr, projector_info_); const auto position = getPosition(gnss_stat); geometry_msgs::msg::Pose gnss_antenna_pose{}; @@ -186,20 +198,22 @@ bool GNSSPoser::canGetCovariance(const sensor_msgs::msg::NavSatFix & nav_sat_fix } GNSSStat GNSSPoser::convert( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, CoordinateSystem coordinate_system, - int height_system) + const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, + const MapProjectorInfo::Message & map_projector_info) { GNSSStat gnss_stat; - if (coordinate_system == CoordinateSystem::LOCAL_CARTESIAN_UTM) { + if (map_projector_info.projector_type == MapProjectorInfo::Message::LOCAL_CARTESIAN_UTM) { gnss_stat = NavSatFix2LocalCartesianUTM( - nav_sat_fix_msg, nav_sat_fix_origin_, this->get_logger(), height_system); - } else if (coordinate_system == CoordinateSystem::MGRS) { + nav_sat_fix_msg, map_projector_info.map_origin, this->get_logger(), + map_projector_info.vertical_datum); + } else if (map_projector_info.projector_type == MapProjectorInfo::Message::MGRS) { gnss_stat = NavSatFix2MGRS( - nav_sat_fix_msg, MGRSPrecision::_100MICRO_METER, this->get_logger(), height_system); + nav_sat_fix_msg, MGRSPrecision::_100MICRO_METER, this->get_logger(), + map_projector_info.vertical_datum); } else { RCLCPP_ERROR_STREAM_THROTTLE( this->get_logger(), *this->get_clock(), std::chrono::milliseconds(1000).count(), - "Unknown Coordinate System"); + "Unknown Projector type"); } return gnss_stat; } From 887722ccb7aa728229a585a8258311e83abf67b3 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Sat, 2 Sep 2023 17:41:52 +0900 Subject: [PATCH 090/547] chore(build): remove tier4_autoware_utils.hpp evaluator/ simulator/ (#4839) Signed-off-by: Mamoru Sobue --- .../kinematic_evaluator/src/metrics/kinematic_metrics.cpp | 2 -- .../src/metrics/localization_metrics.cpp | 2 +- .../planning_evaluator/src/metrics/deviation_metrics.cpp | 3 ++- evaluator/planning_evaluator/src/metrics/metrics_utils.cpp | 3 +-- .../planning_evaluator/src/metrics/obstacle_metrics.cpp | 2 +- .../planning_evaluator/src/metrics/stability_metrics.cpp | 2 +- .../planning_evaluator/src/metrics/trajectory_metrics.cpp | 3 +-- evaluator/planning_evaluator/src/metrics_calculator.cpp | 5 ++--- simulator/dummy_perception_publisher/src/node.cpp | 2 +- .../simple_planning_simulator_core.cpp | 4 +++- 10 files changed, 13 insertions(+), 15 deletions(-) diff --git a/evaluator/kinematic_evaluator/src/metrics/kinematic_metrics.cpp b/evaluator/kinematic_evaluator/src/metrics/kinematic_metrics.cpp index 2a22692dc5ac9..2bb0dad86bf71 100644 --- a/evaluator/kinematic_evaluator/src/metrics/kinematic_metrics.cpp +++ b/evaluator/kinematic_evaluator/src/metrics/kinematic_metrics.cpp @@ -14,8 +14,6 @@ #include "kinematic_evaluator/metrics/kinematic_metrics.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - namespace kinematic_diagnostics { namespace metrics diff --git a/evaluator/localization_evaluator/src/metrics/localization_metrics.cpp b/evaluator/localization_evaluator/src/metrics/localization_metrics.cpp index 240a7dd91e9b2..fab3377913bd4 100644 --- a/evaluator/localization_evaluator/src/metrics/localization_metrics.cpp +++ b/evaluator/localization_evaluator/src/metrics/localization_metrics.cpp @@ -14,7 +14,7 @@ #include "localization_evaluator/metrics/localization_metrics.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include namespace localization_diagnostics { diff --git a/evaluator/planning_evaluator/src/metrics/deviation_metrics.cpp b/evaluator/planning_evaluator/src/metrics/deviation_metrics.cpp index 5146fcd3ec0f2..e1ad9c1a2eeec 100644 --- a/evaluator/planning_evaluator/src/metrics/deviation_metrics.cpp +++ b/evaluator/planning_evaluator/src/metrics/deviation_metrics.cpp @@ -15,7 +15,8 @@ #include "planning_evaluator/metrics/deviation_metrics.hpp" #include "motion_utils/trajectory/trajectory.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/geometry/pose_deviation.hpp" namespace planning_diagnostics { diff --git a/evaluator/planning_evaluator/src/metrics/metrics_utils.cpp b/evaluator/planning_evaluator/src/metrics/metrics_utils.cpp index e2fd04a1d7a6e..6134a100f31a4 100644 --- a/evaluator/planning_evaluator/src/metrics/metrics_utils.cpp +++ b/evaluator/planning_evaluator/src/metrics/metrics_utils.cpp @@ -13,8 +13,7 @@ // limitations under the License. #include "planning_evaluator/metrics/trajectory_metrics.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - +#include "tier4_autoware_utils/geometry/geometry.hpp" namespace planning_diagnostics { namespace metrics diff --git a/evaluator/planning_evaluator/src/metrics/obstacle_metrics.cpp b/evaluator/planning_evaluator/src/metrics/obstacle_metrics.cpp index 4d97757c3c33e..82d6dcc51097e 100644 --- a/evaluator/planning_evaluator/src/metrics/obstacle_metrics.cpp +++ b/evaluator/planning_evaluator/src/metrics/obstacle_metrics.cpp @@ -14,7 +14,7 @@ #include "planning_evaluator/metrics/obstacle_metrics.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include diff --git a/evaluator/planning_evaluator/src/metrics/stability_metrics.cpp b/evaluator/planning_evaluator/src/metrics/stability_metrics.cpp index abd3ca5584951..7a3418da881c6 100644 --- a/evaluator/planning_evaluator/src/metrics/stability_metrics.cpp +++ b/evaluator/planning_evaluator/src/metrics/stability_metrics.cpp @@ -15,7 +15,7 @@ #include "planning_evaluator/metrics/stability_metrics.hpp" #include "motion_utils/trajectory/trajectory.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include diff --git a/evaluator/planning_evaluator/src/metrics/trajectory_metrics.cpp b/evaluator/planning_evaluator/src/metrics/trajectory_metrics.cpp index cba03e8b2d90e..d0d8d61c4c231 100644 --- a/evaluator/planning_evaluator/src/metrics/trajectory_metrics.cpp +++ b/evaluator/planning_evaluator/src/metrics/trajectory_metrics.cpp @@ -15,8 +15,7 @@ #include "planning_evaluator/metrics/trajectory_metrics.hpp" #include "planning_evaluator/metrics/metrics_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - +#include "tier4_autoware_utils/geometry/geometry.hpp" namespace planning_diagnostics { namespace metrics diff --git a/evaluator/planning_evaluator/src/metrics_calculator.cpp b/evaluator/planning_evaluator/src/metrics_calculator.cpp index da04d6fcd2bfa..38113513dbad2 100644 --- a/evaluator/planning_evaluator/src/metrics_calculator.cpp +++ b/evaluator/planning_evaluator/src/metrics_calculator.cpp @@ -14,13 +14,12 @@ #include "planning_evaluator/metrics_calculator.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "planning_evaluator/metrics/deviation_metrics.hpp" #include "planning_evaluator/metrics/obstacle_metrics.hpp" #include "planning_evaluator/metrics/stability_metrics.hpp" #include "planning_evaluator/metrics/trajectory_metrics.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - +#include "tier4_autoware_utils/geometry/geometry.hpp" namespace planning_diagnostics { std::optional> MetricsCalculator::calculate( diff --git a/simulator/dummy_perception_publisher/src/node.cpp b/simulator/dummy_perception_publisher/src/node.cpp index 6036e760fed47..f4cb6063a9bf6 100644 --- a/simulator/dummy_perception_publisher/src/node.cpp +++ b/simulator/dummy_perception_publisher/src/node.cpp @@ -14,7 +14,7 @@ #include "dummy_perception_publisher/node.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include #include diff --git a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp index cfc58cdaeed63..5c8a7eabdab86 100644 --- a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp +++ b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp @@ -17,7 +17,9 @@ #include "autoware_auto_tf2/tf2_autoware_auto_msgs.hpp" #include "rclcpp_components/register_node_macro.hpp" #include "simple_planning_simulator/vehicle_model/sim_model.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/ros/msg_covariance.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include From b50e1d7008e961fae08bc97851cd44de6ae6225a Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Sat, 2 Sep 2023 21:10:21 +0900 Subject: [PATCH 091/547] refactor(no-drivable-lane): use getOrDeclareParameter (#4768) Signed-off-by: Takamasa Horibe --- .../src/manager.cpp | 7 +++++-- .../test/src/test_node_interface.cpp | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp index 418f05baeaf20..2c57ff9458833 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp @@ -14,17 +14,20 @@ #include "manager.hpp" +#include + #include namespace behavior_velocity_planner { +using tier4_autoware_utils::getOrDeclareParameter; NoDrivableLaneModuleManager::NoDrivableLaneModuleManager(rclcpp::Node & node) : SceneModuleManagerInterface(node, getModuleName()) { const std::string ns(getModuleName()); - planner_param_.stop_margin = node.declare_parameter(ns + ".stop_margin", 1.5); - planner_param_.print_debug_info = node.declare_parameter(ns + ".print_debug_info", false); + planner_param_.stop_margin = getOrDeclareParameter(node, ns + ".stop_margin"); + planner_param_.print_debug_info = getOrDeclareParameter(node, ns + ".print_debug_info"); } void NoDrivableLaneModuleManager::launchNewModules( diff --git a/planning/behavior_velocity_planner/test/src/test_node_interface.cpp b/planning/behavior_velocity_planner/test/src/test_node_interface.cpp index eb405f06c8c4b..6756a82ca20d4 100644 --- a/planning/behavior_velocity_planner/test/src/test_node_interface.cpp +++ b/planning/behavior_velocity_planner/test/src/test_node_interface.cpp @@ -79,7 +79,8 @@ std::shared_ptr generateNode() get_behavior_velocity_module_config("stop_line"), get_behavior_velocity_module_config("traffic_light"), get_behavior_velocity_module_config("virtual_traffic_light"), - get_behavior_velocity_module_config("out_of_lane")}); + get_behavior_velocity_module_config("out_of_lane"), + get_behavior_velocity_module_config("no_drivable_lane")}); // TODO(Takagi, Isamu): set launch_modules // TODO(Kyoichi Sugahara) set to true launch_virtual_traffic_light From 038655b1517c364dcfdc61d2b55830f7d7f65716 Mon Sep 17 00:00:00 2001 From: Tomoya Kimura Date: Sun, 3 Sep 2023 12:53:19 +0900 Subject: [PATCH 092/547] chore(planning_debug_tools): enable to set max velocity by arg in trajectory_visualizer.py (#4748) Signed-off-by: tomoya.kimura --- .../scripts/trajectory_visualizer.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/planning/planning_debug_tools/scripts/trajectory_visualizer.py b/planning/planning_debug_tools/scripts/trajectory_visualizer.py index 3ce69aa265090..f942d9a00c098 100755 --- a/planning/planning_debug_tools/scripts/trajectory_visualizer.py +++ b/planning/planning_debug_tools/scripts/trajectory_visualizer.py @@ -40,6 +40,13 @@ help="Options VEL(default): show velocity only, VEL_ACC_JERK: show vel & acc & jerk", ) +parser.add_argument( + "-v", + "--max-velocity", + type=int, + help="maximum plotting velocity in Matplotlib", +) + args = parser.parse_args() PLOT_MIN_ARCLENGTH = -5 @@ -61,6 +68,11 @@ PLOT_TYPE = "VEL" print("plot type = " + PLOT_TYPE) +if args.max_velocity is None: + MAX_VELOCITY = 20 +else: + MAX_VELOCITY = args.max_velocity + class TrajectoryVisualizer(Node): def __init__(self): @@ -274,6 +286,7 @@ def setPlotTrajectoryVelocity(self): self.ax1.set_title("trajectory's velocity") self.ax1.legend() self.ax1.set_xlim([PLOT_MIN_ARCLENGTH, PLOT_MAX_ARCLENGTH]) + self.ax1.set_ylim([0, MAX_VELOCITY]) self.ax1.set_ylabel("vel [m/s]") return ( @@ -398,9 +411,6 @@ def plotTrajectoryVelocity(self, data): if len(y) != 0: self.min_vel = np.min(y) - # change y-range - self.ax1.set_ylim([self.min_vel - 1.0, self.max_vel + 1.0]) - return ( self.im1, self.im2, @@ -577,6 +587,7 @@ def setPlotTrajectory(self): self.ax1.set_title("trajectory's velocity") self.ax1.legend() self.ax1.set_xlim([PLOT_MIN_ARCLENGTH, PLOT_MAX_ARCLENGTH]) + self.ax1.set_ylim([0, MAX_VELOCITY]) self.ax1.set_ylabel("vel [m/s]") self.ax2 = plt.subplot(3, 1, 2) From c03fb2f9c6b7756129c636ea5a158521ea449fa0 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Sun, 3 Sep 2023 16:39:03 +0900 Subject: [PATCH 093/547] feat(rviz_plugin): add target object type display (#4855) * add common/tier4_target_object_type_rviz_plugin Signed-off-by: Takamasa Horibe * fix format Signed-off-by: Takamasa Horibe * update color Signed-off-by: Takamasa Horibe * update Signed-off-by: Takamasa Horibe * add readme Signed-off-by: Takamasa Horibe * Update common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.cpp Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> * Update common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.hpp Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> * remove unused depend Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> --- .../CMakeLists.txt | 25 ++ .../README.md | 9 + .../image/window.png | Bin 0 -> 132631 bytes .../package.xml | 27 ++ .../plugins/plugin_description.xml | 9 + .../src/target_object_type_panel.cpp | 248 ++++++++++++++++++ .../src/target_object_type_panel.hpp | 60 +++++ 7 files changed, 378 insertions(+) create mode 100644 common/tier4_target_object_type_rviz_plugin/CMakeLists.txt create mode 100644 common/tier4_target_object_type_rviz_plugin/README.md create mode 100644 common/tier4_target_object_type_rviz_plugin/image/window.png create mode 100644 common/tier4_target_object_type_rviz_plugin/package.xml create mode 100644 common/tier4_target_object_type_rviz_plugin/plugins/plugin_description.xml create mode 100644 common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.cpp create mode 100644 common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.hpp diff --git a/common/tier4_target_object_type_rviz_plugin/CMakeLists.txt b/common/tier4_target_object_type_rviz_plugin/CMakeLists.txt new file mode 100644 index 0000000000000..ed458cf924e33 --- /dev/null +++ b/common/tier4_target_object_type_rviz_plugin/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.14) +project(tier4_target_object_type_rviz_plugin) + +find_package(autoware_cmake REQUIRED) +autoware_package() +find_package(OpenCV REQUIRED) +find_package(Qt5 REQUIRED Core Widgets) +set(QT_LIBRARIES Qt5::Widgets) +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +ament_auto_add_library(${PROJECT_NAME} SHARED + src/target_object_type_panel.hpp + src/target_object_type_panel.cpp +) +target_link_libraries(${PROJECT_NAME} + ${QT_LIBRARIES} + ${OpenCV_LIBRARIES} +) +pluginlib_export_plugin_description_file(rviz_common plugins/plugin_description.xml) + +ament_auto_package( + INSTALL_TO_SHARE + plugins +) diff --git a/common/tier4_target_object_type_rviz_plugin/README.md b/common/tier4_target_object_type_rviz_plugin/README.md new file mode 100644 index 0000000000000..1296bd3442ab3 --- /dev/null +++ b/common/tier4_target_object_type_rviz_plugin/README.md @@ -0,0 +1,9 @@ +# tier4_target_object_type_rviz_plugin + +This plugin allows you to check which types of the dynamic object is being used by each planner. + +![window](./image/window.png) + +## Limitations + +Currently, which parameters of which module to check are hardcoded. In the future, this will be parameterized using YAML. diff --git a/common/tier4_target_object_type_rviz_plugin/image/window.png b/common/tier4_target_object_type_rviz_plugin/image/window.png new file mode 100644 index 0000000000000000000000000000000000000000..33aa9a1e5a26ec2893e1b9337a15807b4d7f9c60 GIT binary patch literal 132631 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�n4)U9GxTQ+(zu`tSX1>3G|!1zWA+P%RG;4f^; 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
A z`*wzodXMxh3D>&?O}#wEzCWHFbF^Z8mVU#8Cz3)4Yrk3532Y{_KnfxMfvS+%a*j27 z2wIJ^?(GYw0;e zG1l5-^9%9dBCl4HGY?%V(|3}OCK(8h zq#~@U0hZ}$h5Xw<#Nr$ebMj4qf0$DxgZarq=b7RX0-a=Dys2Ypa0RNPw?QHV15gxL z{24>2{Yl4+p}}ZJ{A#w|iN+B^&Q_Fhe226=hRoC@z&E@F@MbKZm^YFdwL4to%#Bbw zolNe%8qs3fjs9(=H5ex3n3i+2TWog^I(zD6ce||io{>qZmzbQgWQ};er;-9Osj9US zHgX*{#b3IvHtZ+GGZAdt0rSuOJZwgP%UbK)qK*~f9lN(j{<$d|DF@?uPqCn!H0+?{ z(n@N9J;EI?s(C!w$GmE%E{Ml#=g=wgRbpqtK(@=W$L1TfljX58d_@-fwxC#XS*@St zVH6DO%|XVMyQUe2@)(LK)y_fz_HV`)v@*}omgEv;cBG)+X+R3q2Zb!j&8y2lZH(sV zOBDV1}o*xQ^~0yq-gfs;vW%Jj;Q5x->{j$!X?-1U?yJGeQoAiY0ac(o6;NNsCG{N)oTNIu}-x@-Ogf? z)0uw+Hn_0?rw?vCSlN@JE*q+iHqA&LF|)ppNo}ByQi1fPxPY_-8@V0QZHzL+BPsB8 zJ}ZN81(Xz>N9``wTB=OOXJhyq*DbTXwmf{Fq_}eaj66e%H3o#!TUYqGzD?ufkHu#! zjFLT%-?&S(XRl_QY?95nC>2nqEuS`eZ(0;fx=BgZvhvaPurtdeXVhV<2-Ia2KPSeM z1CTSS>dVGIS!=pI=diAfHKkF>kGo{{EE=?l(d6T+Qe49W2L;rO^Zs=stclUzsee2? zAFjA1>+X2$+@8pPbJ|tQa^murP!RhVF6{qZEP|9qr@OHE}WF_yX=ptc9z$ zIm~@5TEJ?d)k-UcK7mwRC7&AM^@oTd+%Lfr`~L5CGt1~!Tm9Ij{K&F3WuuL) ziv-QgD9%)^?(5t%pWK^E6|37(Bma zs{CW}^q9jB{Uk0vaV+1n0(Oo~DTbnN8hl0&!Gk{H&O3?lWNs}@ z8!tr|oMdZrQ+t>zSzkILnHcg>qQ|?sDO{Bx6X%~r5UfxL-TC{Nz`eKcj}J_KIDN=a z9XxV1PR%`e`U{{kb90Q+UjP%I1Jqt2F1gt-B5dfS)^ej?j3t7d?Fv$8G;5f^TjyK*dCV=V4EU&@{0u<88@6 zlo2ZvIGG?gmUZ$Gb5+W>OT0^pJ6xw( zzi<2fvYOYhk%#HEhh!VRv6>ERbQXD04NNE?=}{yN%UXE5_g00>Mt6?|;^mEjR=(5) z=afku@v_HE1lVwV8D!s7gPE)Z%iP$QdFNS)mRJe(TT|ygfhNA3hANCRE5B+I@JPne zj&5*Ea4hd9rXz7a5Q$)I_w&C)pGDDV6KP9ez8!vpJ*I2^B_T|1{(coB@uafvYE16K zfq9TH*95*nz#n`l+R^xJ9jN!K9Y(H$W-jwIS;KA$Z zuz!yGY3r4Ytmx~DwkFm&%afbmU{xt;VAm(H_>9ub5gr5d z+qj&mMC$$ZY29PLX!y5L{ut%Xf!2eTj5id_QKBG_#Keq|_e-J0-H&7i1t{k9=P~#r z9$JlK^~%-Ot|%1pl;6k`hK#jNgeuDN7lO7%7y7WvKG%A8qq*M}HvV}!wmE#RXs{k$ z<}CxZammmyIu>N@f4?8b^kh{}-p%b`+lNHj3w2M}#P??8!To37Ib?Sy4qX|Hqak%1{o@20E;3Lc81Z%3H z*g(nL-&(FDV$@ZKJTJ0-uDVnvc#7tD?qd?^&fY1$MJmI7Y;%KTY$e0nkQW&tnFJ#Q-jQBmXHodgG%KxI?c!ZXrIetl1dxUvtgO zACfK!J0&j04s@Gsi+Aawv|5+teU$)ykIUGEI}=3i)2yI}OEH$zIZP!zqrsH$X`kwtB)jWL@VV;UHsvhSPxQ+nFR!s$ zV0Ar!+r-`-Jgw-`jA0-wI;m#u%SGX&E0>*R*tFDj%t8HTnES6Dq+o0-8(hD#eDAUZ zR<=(Jar?uKH`OJ@=NSb1IK5ntlXt$J1G!&EJn@W`KYhgpo~Sg->MXQBg{8iOMuJbl z$#JL5`Y4X&2H6R$ia{yxOB|qXM`@CI<8HlsCWwH5h%0L@!X`X<1}`e+jRe7HbvN^Z zQ=K95jtnpwe-Fe4??Bp2ggb{N)Et!`UcugM!eNlBAQ%P^_2FtkQ&1^%1+Hjy4AT53 z&FHDmj7Fj{=1f4ar3n;1Mx8{+$RPLJXK8gKo^?$My4mr~kQ3YOopC^`OOtx>3#OZu`xUTaY{)o6S(cCjfp+rBS? zhfkhZ;{c(2$=p1y+@=h>%6@o~<(|uqAFsnjoRW-lHK_9%kXSQ&5TA z8*yb8Bz$TcYh(4Tu(n5NqlberA>9s^>Tgzvg1n;uzKMeg33}%yT%DVm9ms(F6Vx^N zC!LQR)Wzr)kYtf9(QSy`u4*58!qyifZXQeLB_2>hmelu6FiA3l+E^P}#^CoRd^epW zMQSA8S1G)?h!Yfkkb=k8N?d_ekyXHiMCGo)k3tXS2Rg+q=HmQ3(M zqP%OKK%MM)@y;x9wHrNjK*e-)Zxpnq+L<5l!Tc$R?#BUlSI5fuMPmnf97Ea1jUi_D zhN!W;tx?KvA95X6bzxbpeNO^RXGhO-kQB2SH3{o45SkCFl$8U8YIo|(Es(8w>a00W zVA_On(fY<4VP~8!J(h^UaDkY9{EYs=r1HA&tGs?8+n`Uje}!zvewVz!_FxXg-l%h&j|9HucorIN=FK0nSXSA@{Prs-lrWIhCeE`Zpm2`-nc{Ya+ILfNe z#NFPPh0~P+G_(R*>yx(?vFhxdNcxraDs@MVV5Wf)t8YESF!^14AM!FxG$+{!#9xy7 zz+J}3vCOkbf86cAdNLtRP_4yyOX*3qN341_dwYgx2U(tQ=nE99yqOllve-?J5uONA z1xUEE_U(U{F_=9Xd=38T>Ui7pkzxGGk9tScySix_#~@292 zN|z)y8QGV>LvsG0N2t4aU(X=1@NbtOO zv-_6&>Fk{H4?ZsC2n{MX66&csa0m{-H~_o{L!QH)t}$P1HNbs8j7(--URvb)n0l+dqsTJ_ za47zjKbS+;B)KjbM88dlGL4`8C#kjgv%9uU>GwmaH*ai*JgR3WYSZ7sJ%fN0p zM~{^K^!l+;YpOp*lDjJSE|GpbhgWv3q$ z%Ffc~T~$P7E?tmyOyhMIS|(Sb)pQsmZ7&+F&bxxYSLRV`C6YZxFVSjUpW?whU6xOH zXnwqKJ>Ri2+H_&Q$u2azk$u>W#uu%VLUo?tgXQP4M@yD?Z1b06%ju{Y9aoeF&ykzPa-*{P(?{7<0o11p)%Qbmt z)Urs4OE_vsi^tg+C9LJ&Gr+u!>FO*l=)KbhylJpgssV=JDu=YnpWG%UxN_oySoR;N z(NuoD&u{CA4K>Za0AcpSKDPWw4)I01bqd`(4rk*{=~i<&e2LwC{z4p;WO$h3!YeR8 ze=%!|fhJ?jdzE>GP&GP6(c<_`)0@u-5ge~+Xn+ouU9|b81!85tPVeyB!IJB`5Do-C zvrVpvE{tC{#Ou7EbVroC3s6mDw0Uw>svz1H-vvNL)kQ8HkVi^JJ}BHEiHqL2Q@iH| z7QRFa0d#qe(v7`)`qVfwjkwnLnQM53YSQFW3`QLyjebYF4>i(p%Mvyn4v#uimj1BC zPDuYa5FEHML-9|aG-=@jL=KnxFDw^-ckdxpUv+sbB5aV`C`V$^8?G;+^79Kt&J~Gp z3OAVwmI-qC*Olc2oUuqGA-waW;fTVjzFUu>r@caVB*&d~A+SZ;SL1@iz^zTkxAVn7 zgBtXcl||B=FI1~4j>5&A%_z_N7G-po(5Tod+2|9j(h|Af%}F6#JA7}B8S565m}F_Y z&}?go=ywqh2EmUWllk3FdrL=U>|kz)<17vZ zkL9;qyydW6r(1JYslLad(rMYnB{WfSS|7PsugAq9zMT$_B-0g{X{kDu*GbC?8*{8Q ziBG5@Z`r3Z{P9B~Ex#p%{!@QWamPt-MeOJ|$;Xtcv*6mS$3dMYcc19V;L!C_qxHqF zu<@$zeHFR13T^6inu6+|#2wxjmlo_Qv|Y2eixxx|ia3QtuveH)7fEVrV^NyoP#4Pc zpVlMl81mhZ>lRk{W!40vdNGSZcZ8>B_RPu!nc*pff8=J~A|wClo^5QN=LuV!`e??; zbo_8@eRs!IW4AV%r>gM0%DUMJ^!MBBK$9*0kRqSc7}-{gjivo z8K;!m%kBntPv?3=c#eytv<0Nd!{N*3t%nf$wL|hWjjl?c;-(26sJn#tRf@8*p8WPQ z4+w`AAvf%6iB}h7&RZnkj&R3#j+x8^D{?w^X}h<8?hBHsg!bOo*5SPaUzkrvL{BdM zpv7%)hTBdDdV5Qi#KS3Fmvaq4RLD zfaLp3{KSBdOekjV@V4q;uFC0n5xpL@SeVVC>OG4|rq8z~&obQ1Mw_3q=xlv1?Fc)< zxIs>CP*)n`&?c|_YBX$?UrkQ`6i&}bjlT>zv3n%0(~C?a!N>MXXfuqqaD7;@P9-%T zN-F+eCd?SQ?b|>fl={497hWnOXZ?)1auTtjw}h2uRskEOvFh zC|1ldKVEL0w{~0Z+Q3LySTvTPKE~-@l=t>&SJ>&uA`tYO46E#UC`=Md#i5E( zu5{5Mcv$PnsLY`xmJxTl9JWxdn2SjIr@`y??CI6@r_xkG zDUn$bUogJtOZtOY`JdpA|EphNn}|!DLkn-WK$;RMbwFX(%zAqo*z^Y z9135jH)P>2AY-ZL{Il$T1PvSzr-W~lEft7?sP&pPHtGM|3qbBX<@Cd+7p&R;RxH@~ zOxzk^tc6wNA$0PJv9DbmkNV&01rF1Ibcbmjacx!Dd(vVlQMG^H23tUCBUz2M|M&0T zE9K&ESfVH?DV1swt`j~fnQ;z3c7-D<)gfH3=PxjdL*EM==MVvM89t(M_sVR5IEG-p z{H;A-Zl&|nUr&*mPmLSgOM;$|i5L)l9i}-sxN!{`yBJ^&eNI)*>XoxQ{gTeO9jJ4e_8g z0N5|UM=E_N%C!iZNCpfk&=YK#S$}Eq)uj_MnStDV?ws@U^M4vkVUlt50l0h1r#1N0 z>-K4(p`oo>QSXhhT-@B8imsT0oZY`TqZ z|8$NSjSwI9ob~j_fBMGpK?4p3i9{*d*x!ES-f}ScL0^I{?w_W*x4?1>38Fs}Wc+R4 znSMr?@qYBTth9grbA=+BrhoxC87%KZgvip;`J?OqG-j{mU1Wr%TmU6%@_j%_aWyEZ z+~(^)HU9$ALAtD+cADCFvaFp|e-EQFup4yMkzC+_DLXpE-8rtQjUde0ne(G|ry4N+ z88D6{XGhNTKTh+;p-S1J^#$<$`IlFOkK-%sW2ny)`Y0VAz7MB`^)2pY!;|IVN z8#1&sVI&)$1*LJ>K6ZbZqhKW9@3WlK=41d=Gd8yB^c((tEx_VCTk)6ht!8b|rJexv z+QKdI&kQ1UL`!&6L!6HrxI~$9fC@)}mxdb2aPC*DP{n`@^Wt<*1eb5mLE$&{nQc{G zfW(iEXZX}UlV-Y0=Y4k>sXPITQ?2E|V(4Bovz*2fc|iegZDc6JZ*onn@6{w?TFr!d z33J;5WEsZwR}!!zhXDyzQ*~6GF7Y3vQp!H@Q;vLiiMM}?FNfc+FpxPnDzfsX_p5On ziePs~eyq9sNMQz)Q)C!#oxfkf{%01)nL<(amHU(Jr*G3>>{Ni>*eets%-)-duxE># zx3f43%`)Q3D}7(n@}kCgiQZf4eNOBiG)x;|w}boBcmtq#V`oZq_P!Dcz3f621aw2( z6!-;@C^o{bHEv~rwo8u1y)@&3kFVL@ zI{dhU9TFT75okW=fQdk@sRZqjj4G+D-gGE+%x6el`SYIBzBJ0o&UMPsI4%20hmFe~ z8=hL1JF=kX|4>j0jUA~3 z)Ec3zH7e2w3a*oFz;buG;PTyfsg=a$Vs5dsDPt^t0^q#R-oLUABYkq@INS^U;G-;E zYobCsWIJ1YO;X^AMPSPtzw^15uYGz0B2FNTHL-C0dbmVG-M07AL=% zkxVKko1Z9&`ZlL^`ah>a#WDj_^)eS1A=?+5C@At4+2Y;yi>E1))H=%0hbwrsZwr&# z`EASQW{~JXT%hgPtTt5?K{FHG^WR!;=g7TELPphgPd9ydF+3rUg%A4w$*oaQMT@E z)6;!3EiK4seBOUyXD~H^j|9FCA^loLhy&qnm*o4hIy3y#>!W*^J=R8@FAL*=%21}Y zEdu*8nsR(>I0>ALp^qvrXMjFuAhbLN&eE87`<^2^SIoxr<_k`u$5?HmYP3Pi!11$_ z&^>RKJ8UrR!t|v&rfalmW;R?!=Bf+0eGs8(sx|HJ1*69SiVnJtWsLJF2>ll0iXtjfLk|s;Noy>PnO@_AunF<^bc7IP`r+6goA6b!l_lxQwK>u+z}C|>lQ5E-9rpmMjNm*zV%=-AduUBx*b zd4(JS>~}=SeI3dDZBeaVh*U?;|EUgr7@`+)Luib85jM zZ{_>j;ZL)IdIR}i@k8_c&EzdQ+CmDR9kWwA{gPKkxN0q9;-=zFfrfq1>#cwM&CRD+ z3VMVY&-#`G!>eukq0UF1C}U?YSl~a8NZAJ%xy{=BU{aBgjxt@r>L9LF z!QPpt*EEvJfs5kwTCD*w7X|L9m^10wE{XE7Q>_QYf3DZjFV!-eus-T?rf%yHnb`Ow zzp4sI#HCtw3(#Zg91d`r+a-`rYLJA`84QtiK2cy{;!Kwz$GA=S!ImBl?4Cf4^1al$ z1Nj^ox9v=`zYkZ&Wtk2)l$`k59WX|SK593IhY_vxCEnqPGluN#Q8={3oosYH^%C$d ziHtWzz+zPz*EQVT#RXSy1n>3+<3hlA7P~zPu7`qSkQBzdO(gB=z&k{u?MV&}HCS_v z&vy&`0)OGf4}(YU&MPH|GkC#Dm)8^g{LtXzEeSuv5{H=M zn=DDBBS#_4Dr&LNYD0ynpu6$aFEN@;rrd!7fLhC|iYHeS)<&>|DzroCyv8QI8{+cL zUej1}2eN#{sxTQXy`}#VyOD6Q)+3jQgS)jw2NxY;rP zT&j^_xg;d)78F+M-3W~z7B^4)Gx2fIfXI};D&TemIc;utVAGe%$%gkL~UBV;s5mFe&Efm@h;J`afjNdE$f+H)io0=^Ai?pfC z(05i0I90vxpfm$sh&;Cg%t;h6<$BqYqFKnmFnV$XFN<7qK$z#egUNWfZz0lDvL~8en2zb`H!k*c^F;fk0admUr(-M9Xwt6NJ06zfEEFin=C7c{REFn zc}Z$I@=e2x!t&USx&EbabW5f37}4Iw<@HOfRP6YZM!>m;GEIM&#Mj+ZN^`O^`Sz4DLhc}&piDj-DuJp5ak^eL^<~P7hW=j- zaSnSqq1@M=LUc0uuLVL)8G&h@x0u_WTM=ZdK{a#niGR(*l~2-h>bs5Wr77B6Z2MST z=e>`wXMWtlCT>@HM=eW+dD8)2K?HrusIcPIq{|j&_Tw1Zg6lrs9OabUwf_3@tlxT= zU1pw(ZZA3t#x+lk*;HC-NFlS2UugggDM3R`j2??Dg_!ctDObUbfEg1mgHM=wccAxU zs@r24w(m7(7~>_YnN}La0U@qqNOruvZ~UfVf@sd+mcOYUR0OZ3n#FjZ73Q_cWZstA zeW7!^F4W!?t15t!rm(DlTz5mwmwn5~)aetE((y}Ddw(Q-e`^t()%K@XW5JCfYVRG8 z7CoK+n3QchEBDM&&GRT0X$-;~q-KnKf62{UivduRm@nNtB@lNyM6(IJtv;An5vV_O z54G^C|F%-hi!-Kes)~WaVYgYKH^@EBBQkr`f1qNtOCP5HNHX4jNx7tWl@N@pUnB=D zT4G%PCY^wXTX3X;*Ul{`0>0tYprhIKB$mP zLH|hv92S2c1jZLZ3uvKM7ssWnjAoev#OuqQ6ZEzreD8g#+GSia(IAJn%*|qCV9+D{@|3;Vjs01p)L>w#xWU+aJ1Oz zN!yK5rrhfJkY`Tt5~*Mh2Z@ZRW$0KOGjAdc!$yPJT#>eWtBX4;3+s5`Riv@P!`PB( z_70UUMm~4E`R~IwCIyyLbt=_=`Vv*;t`#R%P*In~U{u7}pwsg{9Wz&d0FwL}N0LVO8V!6@Rlgfkl`VM%?JZdd7$^4p(2x zD_R?GrZ@lckl3|b?RI;HdAJ^H2^%SsxJ>d#8=UHs#M{`4>tbsYnZ_(I;U}5({h2JmVg+l7Bv_*} z+cE60&e325;JU|%?(ZgY+&IN)^b`2On?5bgTq?{j&6-UD&u!79>ws-qYq}{tu<`p1 zpF_(@X7jQ0he%q@BAQT3J&2M@ zcO2*(D#zdN{;3<+Wy9XGaa@;eB1_?w5wm0xKy}Zvo>(@8J9P$1l7@H7#t7RS1LHGN z1-v(vEMilHyT)bI@OA`16};J$*(zsSt<`+>O9ni}3(UIj9z=%TQAvH5W5t>`bIHjx zDP;S;VOcaqoBfH)^*)QOv6=ACLp-ibyp$P4d#G}QXGlgq?xG|*v~ehArD#>qX(e>U zW^gCu(lF4}(=&64+zD8ZZjjxI$-0A!cpk;uLh6b$cxI1_HX`iKmJG_t)=YA>W0J+& z=J^y(PZ~QQ%`RrYu+q{fzqq1dT6J_Sa`e;F|02;5HPMUj2=ePIqZ)VgwdHBJ#ZAOT zUBRII@D@D=D}JZeq!D*IBU{Uoe!54+nO)MqEs!)aP+_%wx2K*thB~BQKe2=zTrRy> zRA*#HvE;`;1jN$jFhp@4C2wyJG^7|FxxCM0<1QLzZcl97)8L}hVWVcPa!J;%nH{mR zhikrp-REl-SCO~$C9#H7!AtXfccDACgR{u>jZB8d(!mDx$<<()1Zl9QE85e;t511v zZsCazUEwk+^9_x(v9Ru1*_4c9-QIc14QMYm)p{Y`Q|+7{0Lh}%+3~{FLc_uXO(o8O z86nS5+0jvgrqdS}`0{edAg{ExDO$XY^dA%SnoXHs+vEC#a(?$E_z>(G$SUVW>cOs8 zu~!MlPxAcp^JxvuNB7PKjsy*~G>fD;;eOCYmJTJK>J~01dJOcnnZOLelwg!xx*w6G zIjv6L^8HKmOR+u=+?_huYMHX7{7p&1i!0kkJ0VYRiL&PS+G$+wod6ntzr0wvSnRkC zKt)WTma|o06~QU4tfU4n9$-DNrI65>;aIKyYE#e>oNhP9@QaMr}r|^2n z(3|<<5~SLTae3)q1K272$ByM7y8UlA)7>`iPyENNCa|--x1Lo&HX(>{s!0yn1)yR$ z$%%mR?7RZAGzblv)+Ji@mh@l*n*7vUm7&3L|G~t6qW_)2K#$(s0Ub5$66LLcrbxf&b7bcm`t7Z~i_F%hxVh&X zIBP!}TwoWZ-6_x=Is4_-FlWhC$#&cy{hkzkr;dA-+ae|Bus$r@;RW zlO(ZXs)k;x{MP#HOf>|-vDG&15RV66&&e zed92CsnRKk<|oA&Kd#jhW-lGT=)o(Arn_|s{`gcKllc3dRcJxZQ#Mv*ot&hiJfXbNC7Jvq{BQALSR`;E)PT3d6 z)K?c*1(mBMez!F0U{fVy6ESWI5%n5pUjDPhU+>A5V~`TZ#RskSj6W*2^Jbqo+U^bW;hx$~RdC?SF*(6` zzV6<4JN_}v#U85{3+%87j=O=Ww0CGv{)i!$t@3$$D0Z`4GeTXv!O{cAWCTh|$#E*J zO%^nGKmFsEf^3?Q$ar{t{G| zf1GjUZPYg%SIeBFczJ$a#Fn#5kZ+^NH;VXyS{7Ix4=d`60YbUo@;75c0F}PPyOE>% z_Qjs;2x$o{_;3lAOXu`6t03K#1&NCU&|>Y^l&bR&sM0`RVL43>EqVOJbHzZY`=o_D zy!%=d`BAKw*OPKobMS@6s`cg}1ZAL>_H*U=hj%jn{2)d0$GtAi)ehg6TL?T3t?}3^ zp~?7I;F1?PyS@=tuya||FqN6k(4vWS0?{wp{w@vN!s7&Eys>(xh)=LlkwwM#0R$XeSJp3SDx{25wtS|Q4O3ITS%!--8k0&D=u>;#NpF#R344g& z{C480p(wWJLt#IXK6lAa zc20;GuRjy0AdltsprTWAMZhpAG3HA|1g$AmntFeH@j0Qz8Nxm3jWcdhjNi`Ks?F4q zOg>pPV_fro`>&3YaPa_c`&blk2N7e=$@i+M0EQ%Sr{-?#JZ}ZK5Ic@(?{j)HBgC&A zgf_Kd$7EjId|w6|ex#+>z9^j$S`m^)Wm}#Tm6SwNXbern$=&{KS&4!-A4z9P-Q+?U z)ikHoc$7@@#TK!<;i|kXZL2FI$oaTwD0}t=;Nj77>5yaPJ3fqPo^!1SXENlS)`&k_ zAxW(u=eVc|(U|hD(zSj&%z8^qEDmP8P6A2B`BuSCmE;)5{3$-o3GhyO!R?72$0`$# zwxnnr)h!f|rxh}1TSaEy1x1mLfD{F`Ngg=^x5y@@95Y3(>fA}`39`Q!P_kW;mX2%= zhPT%zg!w*JnUhhz=NBmT&XH$UWS1_*(!ofY*J=XD`$w;V>EN*n^C$gt&R;dh8z8$A z{O?CG++^6^TjGExvB=pehC0{Bn<>uJy#O^0a6IE-TF8mf7Ya+rc!#`54kY5^L86vIJ2pZe8+9)9lAQ8 zl(CF1RQ$}+soP634F%rI;ReGiS`Pi6rF#QI%A4F{A9XrM+`Ko-ZQg7U@`wWJ!EFnw zLmNW+iY5-p{;Z+-U%z|}eM*5NK&xjv1RI_X)5IPvkFvndFNZzYXI@LA=)xYdxtdMO zK5o^S^yxG^cO;3-Nb2jq2wPG3s4c9VjlXm((DqR0IAF#9$AxJ%Jm+qawRG4=_shaa zf*$2+iA?_f>m-e^G^tw}6?HWRf4cf`Nx`+gnw{(O>+w(=VqM5^>5FS9j)NVB)4};$ z-uB>Ec;FF4Zr{Av(v25a{WInBZILa#Rdt*2F;7-enxC=ztBP>i1tr|K&0)Wim7dTQ zShbS$oEvowclKW2QW$0Klfzk5I+|&A6GEsCl^j6dl;f+6*mI1R>Z;-!DbZ|}$<>5X zOYc;M=d6u3N1nkeRgb}!hFm^QaNMEV^N$-kx}2P6Lp>h!RjyrqG3sLjj1I>W3i?jb zP7G_YQ-0AlLVV&GUYN3}^k&kUBB`5}d0(ieDPuU=?mC#0ZvDrGDo@1P;FVh5y^*Vpb(}B8Jqrv*ttW_q@ z?AZ1J7b_F8YE5MdypD`kTOR{>4VfK3_Q5;l`NV3-Ym=3#(Zvy_iJy8mWy-IV~h=83$0DQ>DNnoWFOq=>aU*f>o1%y?Z4`QrNO8+z$awd&J#oD1wG zp{6zPkU%=2n-+T6)0E;99X3x#dz4d{wk7UR{G|2k(tI@lLoS|S47GBUhW%Xu4ce!s zl+V2#UFq}8AkMhw$zHpPd+=kT8k+9`{bI5hAr=~)Bd*Bpvkz8=5>lX3t z&WiLD&itcz-#+nnK~zjHqFoRxuNn>Cn_K+eaC-FXy3 zvZl~4eQ`RKRi9NG-u59k-FP43O##kt28VwUY>G`r{4w4n3mfvm^9Nu2P zb3L-oudfady_x|D{qcH`r0ct=I^3C{LtJNKO+Ojq87Vw2sS)4!2W$@hAnOGkY zky)aB$Y=~1?EJ$+DO{;LCM)!@vRfl9cB;e z!2&zK2)|jN?O>LHv7j;540ugj^pFkw~p?X1fEZ>~Sn=cCb7Q>F~4qsIdn!(>8OWQMBBm`ouTTo<>y z1TY~M8Gh7sGU58BqEG~%0!Y?Vz#KpWh+39OFH8rV^ zY12Pf!fLK&+%YWWMgyqS#FXiCymJ7IPvb(R{&FLLOSH{hKqdGGxfU4+W1b)YP&u0t zFom!Hr=M2nY@)24&(ugQ)531Rs|gkI@Kiv(tAO8v0eSGvquyo2Z?XD`j1F}0V*vn- zbKYI#AG(f&UIF~w#oMV7Gy$`3CYroE>}2yBm@+?8+y+{E7HU{$d~MMuTfT)}>;hwj z=t#8oxV`(cEe(|{1COwl!>04E;2e$nRlm<^FrXc?l~n?>*&`-=*HueXO8dlMROW zRrz|v+B1T|^OXw}93Tc#?>!BE8}i_;$7y;V@jn*M^xbUGKn{GgdCh2b+c$VWygWS- zhL5sY#L-kWiO7nyp?#{QUgsrui)&|G2D6id3d?MlPW=`c(H8$kv@}VZ=XwuYcX%UZ ziUbzIvQke~oKad)mrxkiu8vWX!M6_obD3ge2>@V>bF^y$1!YjOslsff2J-&^zrbXu zAd=VrFV5@=fHtL=8KuMi1En(M|BEvGujxMdoF4&+kpFCL)Z6h{y+2_rPUUeby1_L> z=t4ojarPG@=|@V;Cs$3RSV%PEao8|jSxza>a7_Ak&_u2u;bcNJ_mg0$mR=moEJB== z1na$_2+5m#xFCIV=Jv+v@rC;*mKV|yrhTuV_WnC{Sdf?4W0`9_<84mYtDTP>c25$s zq@)*8s37$Jei9mewh)FdFWCWrU`Ikj_z?fFd0pD=#~P;$7U*rI1T65|TVu0Npqv%n zwZb8S8deghwJ>;1qTCsWZATT>h)ze?4fXnVYtttq&WucC=8IpWs*EzI0+jG+y{^oI z+L5`kBGlYgei9S=ZNjePEl5m3LBUmvg$|ZXN=o`pRWuYr$s%_Y=Ge;Ya1_wL><6=fl21B#SNW#gbIhoLTv_;Y|YZxEl?t zB>nXFT81AEzW6LiMCJe73uqf!fgv5x#&4)&Zf{9R9b@#=RQjBse@S{)AMD4PCFq0< z)P?`|GqiL#U>1Xda^v?M@gT75zh48#6NSYb_kwTR`hdh(qA`z277HKnAq89~n4eIC(nOC@7d3Rv1=nJd++eNm{_76FF+maK{t8>4%F2A+eg^>$i^KZ|q7}lweh0MkFfW_r!!7O< zKwE4 z`I+_Y3SH21hFCuD8CC!B?5)E0ugwRJ{Hk#!xTDV-EudNt$GukzR^W>*DL z6>Dc<6JrRp0a_=N+I+IetpRIHu>xC6!#fT?%x<@E!-stu>u;d9Uh4ARd6{vbH1z+m z_11B5?d;lcTMCro4#isBZE$aKcXxN!L5ml6*Fu5f?yiGRad!rHC@$}G@AEu+pYwh5 z$NZQ@vXVQwleLmu_jR>!G1e|DNj)ZSwro$;b}l(LH$peQGc=$@to3Y7aCH{6B>}T_ z#(j95W5SMWo9V-DlxcLxg*w`&<;L9oVdnRL$3u8T20G0)u=$7^|{WO`mfS#DWd zA1~-Xi(_hT-7dVxC&c@1O)2IjPJ|v4>`HbB`*Cd40k`Pp6rylwo+<;ezXf=sjSm7D zGr7jQ%eun}n`%Mbm!L7$1^47$D20AezBJH78&P_vny`7ui6#Wa#rdL{5#^Mippr^< z_6nnfq3}7{Q%zubaDbQeb~HvH4}wXVV&tHYOO<`PIryx2@5#Dt!?e-Dh3r0Cd+}Y2 zV-ye04KsMr=!8U}+6QggRU6<|C2Dtq4sP3hzh6P4B)D}JdMO3 zI)D%MK;Xct{n;;wN`>I0+{9>td3Dq_IDzZM1%!&wbrW)rNKb4k;jFFnmB3qR*3*82 zkGj+jI27U-n%QM?*zPBmBT6%{u;94VaK<0IVXHoP{-9QYG=4ee;nb@$(&h!?Mmaqy zxZEQbhSCs&w1+J_?9oe4OAFiib4Nu*L<9nwbNn}PsJB^vV9-Q99%b2)_?!m638ZqX zzAS(_8noSY*QPxeA0zNPpLKP75A`RcG@aKVS4Z8H=2ey+hV4PKT9X>?_Z1O`de(WP zxO3lQ!Eut5lK0NF0F0dtpE!aoAJBL0ucK^KFBf?M+LMOCx4AhE(x{GwM2meEkT#|O8liIfX8O|6l8TfI^5XpyzIWDn3B z6VM!%>`RT}#ng&Bi0KI+Njb;I4Dp4B3$8#-riob|j%7&qsJY~o3~Spi5}1T&vnPee z!@>)_pgO~`H((T>O5V^j-q_DAsJ&8-Rp(5xt}Pnb9c(7x>>99KRIy}Bc+(v6GRu}YNp3Dr^3s5Te>Xo*eq`Rpr$w&hfln9 z%NcXF*nv-Z{Hi+IlOxfC*;%u&6@>3`LN3V9Fl=XP6D$6fsxroF%#}KsdV}lknf@4F1;Dwz72-5opPr5w2d626j$qcGP*cT zt_abdE@x`u3#xBEvNPzc((Yg`6x@p7P>@7Oe~L4+TEoAF;@wK!Sdwm_4y>FR{+_8J zfowrg4*X0kcl=@fS0hhx*?w2;Cn-^p8)ils#=A6fVoGuuzibZG>s z9leTlgv>C?9iQ$-*?Se+9T@z+cy*r3svk>_D9SZ zFIyz$9efl;m##&#LttEpqk(hFGQ3e1mZ_hfYO4>qo)JwLAw^C-Omgda8l_&Pnk)7CAg1i|&5U$| z+NbMWtJ)Sxk1|ddp+giWI%Zi2c(ZZ<>og=u30E;W!#S)%E8qPdEj&qCPgz>m60MTG zb*C^QLVGZFkGnf;j)@hWF_zQ&HlIR;gH2JH@Z%mkOD28uHdlK}#V+|=6!`fgdvLI*;ZDTL3VR4HV+yyiEcC8LQYZ8Y5&%+IM@pCIQ)*Uq@0hW z+b#tj#2U)<-;Y6~EB(>Z5OPrkyM^2AU>d1NB_+G0WCa74yb=GQMMVGQow_r&i_0{6 zHG@sO3^EjOR1rE{Y&$f)XG6 znF7VU25A96@7_Y7j)c0Bnzp1V7x-#s#3hDO(zIrqu>S49!t|p*ZUjHJZGeBTsSB}=l;^Ut-3Pq;5}3S?*>-=T$vnrePCkvJ=O|84W$=yS!VDA z@7Ry6{5nb*$70+{JLwMgCT_EeXor*i5f@6Sro)K>8?T=Mq6Me#n%WZ=ts;cJClvX* z_&+`1C=$e=mrru($1W32sf%$vBrx(QQ!L*!o7%K?Mh?Wgo9T(k7Wki5Lgl-oBaT*D zQ(H6=tMqqe5#uwA4>p<2-E|58+5^2g`U4H}NeU!>yqAmDtX9L_m*oJy&IgRj6WhY; z?`WTLSaH$O?{e+tG4$lz0yZL`;sVoa7q{bEI(SlXzf%-f+^F0IE<`&Te7NfsNuI3_ zea_1elbid?UA!3%x+de)wQi2kMbR$6U-GCd2FZ_ z?=*dDg#F|7%RoD_{{lrmMtdaj9m>md&eN+A<|qn4ZUAV(bDc{qs;CAzq zc{5J}=zQQCXT|P>y?3B^W5bl*Nw32tRy;qHC?B5hVR@v@yDp-`5|>wLD82Gg-}^YO zfUW7&LPN(3xgvoAFf($e-T~SIcZ~awmm-aQ)m-kYY4uCef4NZOs7q8nx_cE$DHlGJ zZA|EWPFe*-;yawV#n;@1(lVg>uYAozOT zV!$9^%6p?rbFPjA=DfVg(bWz+9i8HPE^1w1uMJ>&e((cvrrW+zOMd3`9Ypm#!&u9@ zXY;#G>68?&&XM>Y6HzHRp7YdtI(4_lc^9K^Z!UYs)=s6r5qv5*j`=V?!HcY>$c=7K z?A#*MvFT|D>r|{L#?;XrZSNOD1P^vHxltdXZh2?u$0hPC~C zXYjlH3{tFv-eK=-ebK33 z2-n^ZF?VviMkm0dm+iS^bqr@BB?P7jI`6mq%&>SHJW_LO;Lc~NrJu-N=kO!nQq(aU z_%zPdIDB_bt8Z7|G-Q!ZxX15yKEPx@>v0Rne+?1WMxYSe^E`0BW%Bch&UT=q>wJWL z?#`p$8Krh}Mnpb;HPGeNb`TbEb@F8WJa&3Q+PP!c@H{YQ5fjbgaU|OD5V*Fh?Y~xV zHm}Q9$99Eydg4GO?`ZZSe_PwhEq9RJ3ASII5K<-=&weFmiSK*p!}IzC&)nTNr}Ke? z_jW>vANBE|bKM#OG?P2shzgWH)0fxz^bq=j8l%>zpj@4^CnUH#PBq9W+Kp!leeRB! zdrGw6atS6CQM}pM|4a!7zc1Hsd1+KT0BAm(oWrURVQW>>ojctOn3{!m1{)E^vJ zdA>Okc>2>k-9MofxYPM;{=6pwn?d}{)7eZO;`bu~v$Cx(=cROH^zpiL+cx^-AX!NsfoAtcW1)WtJ+5GuV`i{Q;MlGl`Nt&WzSvdG-SpmUri<@LTDg#FSRT{d6-P z)sKf?gwmiJ3*H||O8qyv#*c6pA~Jcrzdcda1~ojx`MpSMYiObOWmpy!)Vu}Hy8}-^ zQ0e6>fJ&%2XLO?v`iDk=0Uy-5s>0cbIKA=k9qNS6C(cYBhs=bCVd~#9tn-4yGV20b zYGr1<_dG@+(gM4is2aB`)&wouuiL75k{c?2aPNTZ7d42PLaf(Aa3&fGliREf(t%FK zQHjXJ>o@wx1x4;(pJiZ^j6;Ff;_&$NzTbFBmdBaaYJ-NW;GHbrC8E6_{k6;UHR6zm zt3tS2l6lf6`3ZasQGx_MK8X|Wthz=e0^?;kl39HY^v{>m5;8lddm487JI}sOOUkhF z^9&0O%R4`asg;)-TUPsTeKi(8OJ#&LcPlPiSYSelHjX{?%IdYx-n?~TKkT@OxjTT$ zSBB_Zl$I3J0+Z8DJI1HL*#^IjlT_4_l$=UZYN^0+ZLKhV_Za*>WI9AL2L0pCn>z7h zqP{&o{7AY)rXQOk<(UxBIG7wR_8d`k&y^k5IB!Gs7YFl{1XmxKe|Pg?nuDiIxwz!~ zAAi9z67xd$^y$+{9vFNrz37P8eUG-47C#6_K8~+Boy{22moEUMMr#o@WU+>l~8!Aa8mNJ!BMbZO)h?je{khP1@x1vPx(j_mTQEG)E3`x=d5Q4yqE z3H5C1rd&^zZVnx_F({-?hVP@eJahdF zO<07?wwP1ioyS|N&nqW5%L|&F&N?N_NU${k?waB^a<3y*LW(UzC4TiE&L~cm_qgin zn7d_a6Ju`q2#&5fBBi)Hta2af;b6_VIufP?AQpo9ygPqYn%ntjxI66F2g=_yRrqA{ zM*H6(4m4YS(AcrrsTgSfvoO7=z7}d;6>_9vnp&9|nK6k>9?LOub*4D~8ul4U-{|Ym z^*g_t9u`JaJJUK~1A>7DF{x_A&x(?~n&mf~GV>XR##8!^6^#+utB#r2jt9vc${n={ z`IW~58Jc6^6cL)s8j`=>_O{O|bahp=y$$&E<&)stjj%H!@M6kGhub&(QWpkEzCe-! zbGt$Yz`-X+8fQSuF6ok+5NMw8R-irndWNWOM*J@Aj`-y2NOJH9j-*x}mlBVpWB9x` zC5?L8@oFZA*_9(w)?ddc5wRC=C3nrr$}=9hbrzu};s8^Cr9NF#_xN_@A;*-HNTyOl z%-NW|9-UzM5ch}fs@=9w%>$cyOHtbU`pd3%YYna4e*4!H#FH?(E{Ppo?kMh)FY9V0 z)}=oqTph2bX)4SX&ycc<+=nT#OU+Gxzs;iZO4TshYW7UZ?zYSv;MG(-_>|bxGn4%( zB$G9HxjIQTV&t0#vV+g4ikcEJ2Xo{?`lPV#mHxqoQbRWAL>GKP(LWu85|50lDMzZrxxilv@acLyGoV=;<}MMBmz0*qqwv&QA8IQ*R5C4 zKO~CYV|l~`_I!!f?gvX7G2bWD*R5R|e(^ROkE>gJITQ(l%90K;Ip&X2#-b#X?LtF={#4dfZFT zbF)N)rl54zz|lc8Wqj2B->z(G@Q#c`yVDlwCsVt4j97|wMld(^uzP|NLuVSexRuva z06T^1nn23VU?!Gs7gFh2g2J{MybDgBjIq+GQLqVAksT%#O*Pg0154D6k|o~@Hv|CG35S@cUnd=hra=@|8x zvBH__`-y}C~KQ_Ssvf0oUF!ae#vOfsJBM&Qy z_7Zkvt5=_}pn8GQ*)rAt?bjKZ2p9FJh1 zkbL~mw3oSnn{Q-=Va9wsX?S?jUBC_xR~iy#3RRj!>O4ernw}xAWT23l3raDYz8{p| z8gGxSC-{Bx9eX_`APeT|rXW?S(<{?FvDAn+O8J3hhrTzSw|LLWT*1J*!6P`e-Iex< z+bxmmWhv(}?}>)zwk*lL zy4xb;0Z458qQIC{(&C_pifNIrJ?@~lcuta6L^8)S7Wf>qk`g11)Kt^MK#!ed^}#nj zI1kLS^dns^&L++%C)$l&TupM(Hsi2UJ*={25(pilbQ=24L@2s@n{B)@4&IsP1q2Iq zGds%wrr14Ruz#-X$?`kV(z!`2ro4HlG>=nrc(bDXt(MbT@+5ES!Wf~lAk~;er?aIe z9WDhK$mF{Wt#%K7%8)~)K~sD|mcfM_9Y`LgaW7GU3cD5AFzm<``nQy-oe7N9TQ`zj zy%^;VuD*~0fi~hTtsVS$R9 zSXXIpkE2Ea2|sJs!tQ;O4R)=oH4TLQnZOx0WRxEu^dm1w6&2_5zOi-RyFGETfW7)eP*`OAZ@A#_!U0lG<1)Hx|=KfmX4r#1l~ot#3J>3Xl=H*MM*6FrL6 zx4gfz3S-SB{c7Ouv-We2%dbpPRyimrU%dpRp&lJ?9A%@!2|G3jC`dUj=R+SU0ExEh zpa$93dQaCtlv5_E54DPOTjQ&*jCNzM-gcu@ljHSLiWqiQ>Ue7^@|m8;V! zWIIqw6Bde4AkO7{!8-I?`kj}p%%qGQ(gd(2!)C}wr7xh5Pg7~``n*ZNwaZm^P(C(| zYiI*tc~P&LnAjJ#9$;uF_CdoJT=}cbjfg zQX9cp?KpGeu@i=SciNg`{KDCQHU^&EY(c5mT5qA;NY|1IkJ-3#bG~qif0%6D@OjTo1=m9i#_K-tC({NJphV1(47*d~2 z`eub?*4vVzpc*Zi_o+ftrsA-}IIZ3qsv_z~vJ-mGalML61;dMzcWr$*zkJ8z<(9La zcG>G9r~*~Df9J>1zc)lIcY2b4StdQiWWPB+COVz6AM3rrdX_Bm^JxICA$t*Q4JO%5 z0|>j=R+`>jpsl*Z#~JgxbS#6@q$#oWSGtmU^QM#c8m@vzy43YVo+>Mi4~L$PfIHNJ z-28({@K%<(IDHwCtlrzSMQ%6FCnd6w$_n$5LS|J*(+&sx+Y+Pcm&FeaL`&dRDwNAD zWf+}~%5+odK#xg;5EOmL_Q`8Tl-r-r!&(itNQ#POqqy{qLT;@64!c+Yw{Ph!AJ3Ht zrrwZY6`E-VsF=qW3uMHl#-uCxL^FBAb%$W!JW0Xd5H5l`PTRVbK~_z-#k5-KPaac} z_ozA>w-ujT%t)twf!OYqm&zD3T>4E&0TII*k4bJP+;urmes?_bM8%Mvl_WOKyR*WC zt9IYud?w_H1ctol6m=)C7E`|=)P}U6^byPFNv9JZW0K03ecu7pnF(KY*oY5p&~iL) zbT{{;d#{=uKXg~uGhn|OQn=hy0Q$L$&EiGyISi!IP-OJyNKt(?FFv!?k zLlP>Fw~H}}aqK%oXVY0X&6u9PCV*LeAmqE_a)!tSsnBrti52k0qXY7Bj;%RR8lkyb zuLYcGM6MxDf7CCB!otcktWx+Y86GLa!ZCC5=jWZlk;mdiHGa$|e6E$gW zvY{zpef#~1#T^bW%9ZniI~BofU2VyHAIIw#jO0oiQ^*2z6AtteCHUSiy4-d~B{=>{ z|0vIWxSuB7he!=bhipq^Q~pR?2_*nm;Pv6mNYB3=;NiX-M)Bp*=&jMWDynZrLgjE{ zmV0PGRv{D|*sma;$~ZAQY=L|94NRi5;yQaom&=S%_@F-~sDod?<$X+iqd$$UIJX_b zLQl|I7LWX5!XLN7pDDmLK9iHVy%|HOgCr9pn_H94{?e3Ok2+V?G#(+ZjE=+Bd%aFm zYP9vz;XhbGJWiOF`v9V9I&(*n_c8na-TOc+ZnN+lbpsejP5r@B#>)`#gHI_c&sqrZ`#kMnwUq!>xk~F&Y1z z!n79Hr_)^VLI)KWjO={x^+Y(*PIjrvVAGzT94()ujKN&D8}(;3?qd9bT8*WiuCdD2 zDCF5Z+G#WRbgCXSFg_M5LlDH_9S0)T?4JyO=Q|Z6s3RjP8ry9weX3z=s5e=lj}ev5KX*Bu+#jt(5;l)6WUWD%V#Lk}fbU_RWbmXdkm1kLe7z?`%U&MU zfxqT^<5uT@=kQr=_!oxSl-&*D9uC&Lbi)T0+G}!SX_eP} z{oNg$z~itf!kF804IDJ%ncc&80|Evx6x@C5Y3+P3%RttKU_-1yxkzSH_L`0E@;Y4L zH#jXzOYYCY>YP;WZCx9)`sUVSq>GKqAdemjpEFlC0bl<)e#YQ58YPv<=QR2AyWi|8 zqWpMpB6J5DEshir-fZKa5Z_@d0*mmVb6!`QMU{TQTBl5IOS&kXcJYM{uVX`avAz#y zPWm|O^RnXj$^EA%``hy+SS4d&BplkG<dS0YdV9M)rBG;B zd|#w!Z?8V&`&c=BDUy-VS0a=s89Y$ys!EG1eycIvy3&(m=1v2#(6@HtWi246P|@n#dHyP+8M#qu8J>VQwQoa~!X!Nokhl78~> z`}pvnx-*b)A{(e!^Fw~&Y$-pw9c)N25sEU<9P`lFuw-18bw0zB?ovqdTl@}&RWgpE z^482nY2%yT9?U3(dQcVFT@_)&7PmSBBp#%^=ivF%E z)9g=%KQWf|No*4AZL-}$4Q5gHiG(V{YJ4KarfaS>2B6wj6!0AMgIKjryo2=9f^g-p zSkk59kp1_g)??^*#+jaJ)8_S1vfI9Z_s|mNJxJ*^T1_deQ`9gyk?8dk!=v=KSxOTR z=1?8aWS9aIXwE?I(kPQ5WLgV72gmx`wZ-i^2pU|?Z_IuMcr3YfTtvM}swCXpThP0w zDw5FnoCi^SqX&lXj2O8x@@fk@!s^XOV6A}nEAtmUoUM7TXw@YCz0R33RShA=Xci{? zFcj;q^FHXO&05dOchWDStsciFxV?Xl3TZ}oIjZiIm*y29Ag^PYdCby0&!=8KPe1?f zkPOWgCE04rFS$V!O9ErMWGNC%t6fCLNw=%CGR z(BfG8e0&c_nBCz+(w z3MNC)m8rQOJ%CPqLe0eB1Dc&4IGq8Le3(99X?Ak2@eKd#;U^U>c_@NtSy%5f-*&T` zA}EkkpTS$_z^@1> zb!d2&26aBWf1yREJF(V0*o|#lcU!U2$Tn6v?r#Vl$1Yz?|DZ>m))v9l=h><<^b5Ry z@`kyT_YA|OS}X|u^-E7P5DIA3c^vq2ryqNho*$xkEv&54PnDopu?aCTF)rUnuH)ll zkM`A!3qq}P6_I~F+q)LUOBp3ydlNY|Hw8p|z80H4H|(5m=oap!Q7_hoOD!k!en3}9 z|M^^=7_%$r+&pQ$VIAqZ3Vw&3qO7{V`-CIP`qWKUVMz#vT*<6Z3~}1W+uhdp=;;126GG7V zX`L3A{lAdX^add6&YHKq%sbF77WN;xf#Q2vK>@>$4f&(?OrkSq1T!x#9V4;LiNU`h zB~Xy_j>fIin0LxYXL|2uyAJ7CZWCy*1mkBBky||E#LZ2k^z?K)69!UNF#QPWhp2IP z{Qqf$M)naU7*Ub!gmtLB(EXg<%auVb5tD4qwmfe z3c(PmdU`ErT5ozQyqG2RufBNO#aKyWzMouxmDDFN4MPO_lrgsYqejF;KqFa{G671L z`QLuH2umJ;*xftN6S-IXSa%h;f+;F0;&wflmZg>y*-1#J`X{C2E*D8h<+S95C`2G z%A|k!Dd`(1w0{YT@WssmyDR66WD{m>?^%ndJs$MI`^Ii@ktayP!NCneoMhzX6QE6L zl=d%6oLUh>d*8l!oi{-Zfj~5pU8k}~+;me^NJT&C-*%xwJ;TV`o3L_D+S;0~uo%+$ zuRh$~VMPjzi$jMJrtTdc=9iSbUuv))c3yZ@ak-JaHf+L*7xLNIn9N`3RWGxhl;gh& zB|TNq(t-}VtLy94>0?m=C2h)#vG@_lyU^ZOi!c8wLS(1!_mjMtnVAFOZXrKdT+53t zWh8FQ%--dHq0KuLzAJo8lJdDQ%k4TU!yM!Lmk-7@bKpLO#kALkr~kXnSk-r|^sh5& z+1vl>y?5MB#pvTFj|d>m?mW|x`Cm$p6SL^gKbKiNaMqFhXbF>-9*$v}^QuzOqN-_7ERj=}Wz`Y&SJ+7OibV15VyT3i^Yxg|5fI|K+QvMZh)J$z%(__o!Dr$j^~NHhCBN@m};rf_?hhWudqLjSJ0Te$6d`dx26YTUdp zuJ#8r^A@~lm_m47LNemPxUBt+D>`}BwDAPQ0bm(_rj8>Re66uL(e--NnCc>#li zVMN}ip`n9icSzdA^TV_KzLEaIjgv>Y-^Och9M8s4WE0Tk!Az4SMgl6_PztuUGeoatb{8jmj z2nz(Jzb1m_262hkMNm~ki`dWd0Xm+MaTQyHe#e$>zu$)WHAL+cN{z-T>v-M)S7n$W z(%Kx=z+)pTKE_~&uc3|nz0V|ig%20l6($*)C(lRDP0a{O`Svwi&6huTo@r2DCXKz1 zPiXnLs#>zv2Th-knlA}~zdxo3G$wK^^@pn|?Tn!fnLr2T^1P3YMn|AC)xiGgK2=(I zmzO6M{5XZ9wg9Ne@9B_lb|=GU5&|nGm$M}WzN){(tqW`WYohV~XqEf-jB{OaEV~&Q zhv%8_@|ZMt`$7*hKRYB%M?r8)YtfY%Fjo9X3%|9 zmb8s$Tuia->K+ibZPVM+eoffKXy=*katg~R_dsJ?R@Bn+cFEO|StOFxfH>y(Jnjg-ZW)&_h$>Hb>l|QP-FRy{& z9C1+GB|3UD#Cwx74ysW+F%Vse0k5gCQZAQ@VuG;f`(O!=5+DDda11746cu$w< zNcyN|lE!pbZN)OA;w5D?`8~j?v8P~ogKnInxLik;G;v+GDbDbG9LKyxhEr-Rf9H)jr(VX^bt5ZjRM{;U=rFgzwYvSu29AJ*tpl zkO*Qxxv_ZWPDTOeUtZY*39PjOezZnA9_+F3w8G=#4-RLvc%<*=zo;x+U+*Rhn`k=Au#F5N+$_lA4sCKIR#Cv1R_O6v$C>|tB}fG zm#ddN$%{PuU7zssRPgeiJ>CR+CIP6s(2kKQxxmAW`$RF|_x~x11f8MpgnfQit=8V<2v~ zom1|bL$vQ@gu`>&3UyWhI{iDw2RA;Dv(NDr12q*Gx%xZfCa^S(KR^Tmo)(suXOX^Zcy_5y;Oeh^$f;jX-~NGm}l&T+euE!*_CK#KQ!_cj1Z*Xp9V4 zH>}{yF?pfGf1A9$F_FMlAH$4+Y@~|)tIVR`TC@HPZBC!}bFVx|0=3f6w*%3-EN;LW zCer%#n}hP#QfQ)o@uhzQGm9$v{{EcviiY%W^7g*Ub^_NgGEMkWEcUY~)xi!ac01bg zM-aizTO4szh=Ss!C^3!qH^EzQ?ByfD5%&e(OPzIQabk3D4mgu)cyN@dk4;y@#xR-h z`K>)_$LHR({I*Qx?izbl5Y*yD|ffM?-kL(+>Bo?Zhc)HGYd=ia-#NR2uc`~RY^X%=P6u4Ui z6#ob?bH*HG{78lMvDbEakTa8#XZo|eJF4!Ju}q&*YIt}d0bVveYcF@=F0d#ENm@J& z@2H@$HT0z0k@Os^uq2NVp9(hhC=%Eo010 ze(`RQSHW)0hg9AH;jfU~^Myj7)8P4bEMRLVXL;WLO|F*1Hn@QCC2LC-3(JeL8Irc0 z+g*B$rD=*-nxPk*K5Y_MHei)Kd%u6W!|KrAwI?psa5TeG zh*uOEdWgPiaQwTRPXe_9QB8dy-)pG=s{cd+{~Z&2dn3kC`R@Q>=;dY6@WKCmX%hgM z`X=lB<5jpJ-n?_$R2cE2-2kp{zzvISj@Zd^HSh=_) zXp`=LJdpog;(tPs&$B71sM1nXyY^jH-~U`jB(a1KU$LY55CzTfPJ)){UqQ=%>g1JP z$O2V}VEfR2db2h~L`_2TbkXsp(VLjCRKUriCdmz@0k{Llc+ z+b3TcWvOAr^N$Kx!QI`T2SA`|4f>l_-hbyfPs4uqwratKTeJ=D6~Hpp4{ z?Hhlud44FZ`S%jqz`($(n;U6|OJj)wCFsi4h9hJ2`|Wgr5{*(Z7Guhxulqms+MG+D zJA6_A9iD4s=JUoVDd{@Jw2%kgHra3xd{_9ULQC=bE2`ua8`P=riHMT8v2u81r~Y^a zMwo({npd}Hau_n)b1bi zzs0OazY5QzuK7jQ2P1YeBtI|)RKXH;%l#&wwA}lT)j6^UPbXi|HlK!i{+E8sGRgl> zb528B_#3<~tr%M}d2n>UU4LwpBnydpUwwJ!19blSJIqQvgV@a4g;~?_6td29I)` z7o&SFc}kci#ckAYb=XSFdEUPD(&HD|viN#dBg@APqUxsn$4}4WBwoRG$rula?H$G# z@#r+c5NGBLhGz_!>;lI*2_Xe1S{H|O=bFE72K@lk$WFI5sKn))cu_BGxtZ|lu2fi^d0RyU@$)lk*Jo{*E#*Ny9l zOv@bdG|v}UqX=FYzh@+?3nGo_hjZdWJWGC1eZ_0ffrP0i6&1D!IkGF zH+|f1V}s0O{Kq<$&VXcQ$@Vo{$5XE_R&7x`jce;^$I7GQ8baffGRp!_(RVCl$7~!n zTM(E)q_Gi`-{JV%YD{L`D(~$5Q(CteI_=^U0+HCV*4O>*Ff1_v96f{UXzgT{nRgUH zgtQ@r>$K4T{7lgOf`H0xV&!K{iC@0AuzCv%qjxNv8?o@hGrt(%LdIl2R>lwCNiS68 zCEJ7seD^IOdfe_4$2;T!t*W~#bL~ox(gT3z4cJ&-9Q`wwEL1)wLAE!wMgNSsO~8rq ztdOLzXXIt&BaxtQvp@eaO&2*)h5>6~?gzCp4#XCytf$Fw&NG9)j}3n5C2?62FF-)@ zYV`7P3AO0VwgFv_FJrT{uv?}-O#W&DcrUBxv&8^C+L3XdV>U)WF6ETjDDzY+(+8(R zE>p9%21>eHZ``SKsoQq3O4SHg(O>~)%m7}P&R!v9fgvJN%X_A_{tn#LP2e%7#*+iP zZ)t$2B<8W@kMo7jO?lth9t*bMG`ZMspw#ffYOa-e7g$xrbCoia?v$5+Q=j5@m8$Ki zh<%O~b`QiaXF4$U98}dB$Sx~gCn)i0`rV_jbQ;Z$WQW#09!S42*I>^+(}X)&^Q$W+ z5_FzO%U3~C;Ac5S7#b^Sr+9W&?A>FRus@AangGM5jnRk3#7*@$l^q66<=a!nhTofxlybA$i zO42?ux1>=*g2|80D299X;B-Dj7;(q>Vg_OqR#4q5Cfj^?K5^pk_FM;INQf755%}O zschQDcuPsN7}2mkj2}vtlh8>(yL!QfixBREW&FguiFuc`KiBs0IN;q%VRe-in7d^# zvQkOYC1PdLe#koWR236&Rv?cMy;cWgeZ-GJ59|9zr95?MTy0dpShYmYd7_=5LjMIy zfuAlBFO`-W9$>~*MX8*cYET#!h04PAOFXCKyA>Qq3~p$CYI>3(?bL^sUdKb{VHqlk zJ|mHQ_j>DE!^Vy&J05g0oUH5=lSL6l6_rw$*0J#3CVAZN|4ckhOaeP_@YpI$wHz;u zod9z>?ZU|)r1?l}k7O?w$*`wAre$k&U!8~)Y=X>2bGgn8VC4ny66U)o(%oJ#{m$EU zb=*gP7@e4P)Ul_;GK_0LsRlah19zWu5(v4^xjG-H)(li79vtL7=!km&a~HQc45qaQ zUHetuQg<_0M;rK~-jCqhLm(#kWGpe1%uyKrwmNo&haGeg{xbhWK(LP zbn8l=P!i(WSbBZX>C=vrld6Ie1L6Hb%)BelXCsLHg}Qd8j`!Bue8(=Hc*QkKCUe!p zrN%wP{<8UA%BtsvY%4dw>}Gw)m6eXq2f46a*Cq4Dp!VQxxS1k%uT(T9mZ+$r67__2 zruRPg>at&evTd)-PhZwyoT~G%^0JblqQM)v_Y8p5v7{&a8uMN4qt0KS^{1u8_Pq~C z7W))A^^J9h3E3<2weT?PsSHWk)`-nAub+@EE$B;wa*(R^FUS5c|=lfr~|&m_*< z`Y!Y>2?mNp1TRy5|F5aD4vM4M+IRxNEog8exVvj`+XROMTWoQ64Gz0VaCZytZoygH zU9xC!cl-9eU){RzRM-A9wL3Fiz1?&AJm>eoAk71dFJgXO)g8L$nfEx4u#G)EdT36m zF8?`^oweN`DYfxP*Q9F49zjZdHLg`wv=G*l?cWuAiw!RbEKe%xTF8qKO3<{V)m`_7 zHFePc7)oaco5mG#j6Z_KVb|Q0QyH)~xM5eB_j1u9RA9feYqWj6|V<&Nm#` zREvGJ4;7sJlys)LB8rZLKK-;*kElS7q=s$iJXbi3uM zj6p7ryrVZ~^?~1U15c}UAX&*md)1fI@!*$1V-O8w2(|Q;yi6L7!{I3hc7}DKk3Be^ zD{boTK&QSe(PNurMd3WQZ3G7DkbPJd>#5XHvA%FWk8Y@``#Ab{(dY4yF4O`|xI6BP zoyqniC;FqS^|#dC8BU^kjk9}<)&)P$cVf49m_vS`4<2J=xbeO8n-nM>&#f%NT*3fu z+drrYYATHZqd23V#!b5ll_uS`*|Emk2RlY8&sw8Lja)OyXn5pJs#1)X-Ry5aGua@j z;LW{V)VR>O`75)t#eb@!0bd-mOv;-5SG}U_H;hp9j2Q^Dk|!Qnyti}E34W8?!Jg{f&(Z!s6zE?Rb6oq7}P0_HK~TxjK-_4r+rRkh?El)K6GlU$+N%+&f}?L9lw^>Kqu_wRfeJ#k7j1aM^j$OdU* z+4te@65Vr=6J6hMe~ffd@8m$)afbq{AcsC>z$|BCrG9sJ>gO6;>p=EtT4G;>J1(8C zok7e~`I~VgX@s;lvo?>X?*BQP&Q7SGTfm0*q?1XZ1@D5F`gV1Ih$le%!qBRIx$dY?JX?Sjn_ zMP>lRs=u=GaQ48}A4Vj4uYUBV_M$jLE9l{pgO%IH%*+=%M=5eI;4C9uY+0r=R=UZE zwqBG_Lmroc%*@1u9K?-A{XwL6FUhyJA#c^0zHdgP22Ke5JY1}n?kx#4;*10-GUT+8 zr%&Qz>Yfy~(?zA^Cb;Dbv$8?w>w1X zdyzQtR_0BV$AG0!cf8IPjyKyF8A}Qsoh~Gna`TGO!MVA6M9vVTp0B?nI2YG@_xv7& zd>w^l1oOZxSg(cma6Q>5%?5A=hAkqcg6pC=o4*t!FHw(wSML$5|~ekJ2ak}q8&v_{oWn^u~Q#vM%1 zRZe=t$n8$T*wbClWTO*+Gzxx-pOVt<|GLeepQe63RKT;cB{bDRzbwrbq}Ms4t|tVd zw;{XQC2v%L)uOq0;IFJe)@3^n{r#tH*#pI<}d72 z)KOCs#9~hMhuOohkWp9W6r?y*2(V^hJ$8z5$%lCdhm`loGU0cQcSFKdW6n4>ARIWa z*P-Hes2CNc%2M-?2x~T-nsZ|0IqM}%3$8-?$!XuRdON}&GiFTQMl3x%T4V$yfvLy4 z&r39jy^w}hM6u^Kqu%QF%CI>(KA>)@sl?9JmkqYX7|B-~-MoR0jD9L3qn~?E*$Qa6 z5oQ4aPBkIHHS0^=`?F5T27mfJty&zRSVHnwE*uL!VSH{!+n1Y8r`k5tyUNn?fa}S{ z6FT#H<5^l_E1~|iIo|sXpX%o<=&fnp8E<2{CQ89d{kTS#N4Ak>HVCbXv*Ma)(oRlQi|Jq|HCp{ z2qDPP@r)EcMZ8b(<>M`S*XrcSy(iCS8|q&HkChQ_5quH!g9%n6ES_{1Ta&bpzJVd= z3{E7;Q2MhuNpf@ko|#8Rg;~_e$zgKeJHjY38h$QBlV;`E>eoxrZBkk{?-A1|&3euk z354+2XNPjMGEL{hu`85GZhiD0V^UDGh!Gzn;D*CLwm^6NT#!5YQz! zy+2**3jo+Xz&laEkJT%C1Yc8;O8LY?gyc>BFz#MQv9lBP;CcoWd>}ME{kR)VZnA#% zA-$kr>sDDcMnVv9Qdne{3}pFzQU)C;X37t(w7)yG)%U!6|1Rz|0)TC7;Vok1gZq)E z7{mBUjYUY(`^MSMrW2+L((4FvN2^_qSnjJdgW^QV*L_rO@f!fc^GcqLjvL7=rZnduZK?utwafIBaZPURHW zk}PQVJv7DlQjUuSZZM7=l^x|exe{lj5-!u0m#~H#98VWoBOD3k0yRygyl<*WMLJ00 zSg-tmMtE6cA?Mszw^)b`q)Whg0vZ+0SV&{N` z+qdn?yUotUaj$13eI?$O=EQ*k>?Hp2MX~qFX~lIZ+5}|SUa4Dukghv#Y2W2tjp)>X z>mdrzdfDXtA<3tC5Wm|!|3LTM%oYe=PBxi1Lu$AlOZ?d z4ZCX_me3^keV8W^0ZD!Z#r7%F`1#YWfzi$*o}*Kn2@bc@b~)&7G75msCp@;F;4%Vr z19ELf=%7Qd7KvXG$kKKUMS5S|P!$fE_E|dov2Uqb)v(*Hn9PCGR)xZf%2Jp9Ky+;_ zib_Ed>MF>+#+dqTa+N9a`mV_v%yH@i?5p#hlXHH7gg7i_8Y-y^h8XXzMF-eiJH~9& zaK7k&Kq6%LsI|UTKOZ%9_^a?fCv;Jm4`cV1#`WpoX3=+py;YcoIV($g?8t>c>7u`q zAp9@2@v5FP9Ql#6bb6mb9D?NsWN{f2Rv>uCDgw3NR206c^+^|5`n|TDrX9a06^+fo z&n~^9(Y0UH(O0M&c{w2+)ppJ-^o3$$d*14H<(wSdIc~D>6Zvvu^gLU$_CtAjS>;%_ zm`gZUtHEyHeB+s2>%1CSX7Eczv)6hHAJy+sQbwxK{gL0_g_8(1#!oK_A$59{Cgveo z;?4muE|g}@{eHfppp@+t?ItGAfN+wmk4w(Uwetfh^pjP|_?Pwi1vqdoIJjur-A_pC z!FkRMZt{M(c{}^sb4iFqdExAudrA-{T^ntf|Ioa|{kddtSlkKYi9<+6RIYC+FHyLd~)@=!7DSe#N3x%lSQ*D6oW!9mvistI&)3IE8m1}jWT zyUojD-a<{}JyE~dxntaZPKBzpz&ZnxH%cEZ2x)l7yT?q7uC)mpE ziPUoz!OVk)FG=qz#pF)sSSj$2QL~f912UcH%=aiwnH76%u^pc(TxFM2%_GINU0|+4 z_7MXqlizTnP3E_1B5NA$`Z&gG-SA$kloCBfB@!k1Ep4Bf5 z!BcwLdZgFj>CwZNN+E&invlhWOfrm^6S*t>4bM%IYqwDCr-3wW_GwvvwXQm4V43Y! zUj|C^l%SmK=$piATI>^HX}d1Fv!kM{KRF&A?k9tfafWXT-Le-DgPbm)7uwZ6$%GOR zDlZuxMYCmrN#Ee>D36E�BF`T>-ZQ`4i8Y+8zSI-)+^u_n_t3&N3`cC75#aRrV^_ zF;Sg{%d;5dq7ISD9+ZM^og;f)%nC$oMn~Y3Ei&<{%vr^2d)S@+5}Uz>g0%oF?L?mO zv$OZl<0$NnV$@9^7F}!i1U;{@X&8&~MJ^r!r4Yc2coo)zulQ3T&Hdf1+LP<2jG_>8 z8w%R`K@J~8dfxO7YjCMmN=CVn3J4eM%Fc62E7oKmRazZ7SQj`_a*{ej6KzyQURvX8VEp(Xox5q+ z+O<_(#I~vj;DyB{F{>T8U-;Oi8BsB$2kToYG6zPjWRlMZlEwu75*eJaRY#MHm06A%z#?r_Sm+?RK-rv#B&&%^~^gUm1_Vs)XNrDHX>5fB= zPwv)&INGtI|I5BO;P?!^jwisUpyR)qBNKkuS7CL<=1k<217PD#)_!ewz_WcJ z$v@9ZTYRmo2I(ZaPHzW)F@Mrp?f-i6rpI`%6@i{Mrt3AbP@2Vw&BprktwV#3x-*g- zom_trF}Xa9B9engU~s`4r<{V3q||Egtl41|q9NJ`9*r4|O_+V$k}a<@uq(2t2?NWa z?2^e=CB?_2#YGy(vtR00YL(NEeKSkEv)~sUG}J=zss8vawUTRXcCevSsK0|sySW*V z8HLG33eE9eC6!_QphMOJs8O(U!G$Rl===Xa4%cf*`hfTIpqsIiHV7GH@ht=GH=)K zru4~D5e#D^lySourO+QpAar4*S*LA&*61xglE)=iBc7We?)o~%=g%Mdo}CVb1pPr* zw%L~I*Vt%1w`?n;Y)_(wyIDzAWBA4*|c6xTG0*7GjWAyHpc;}*j0 zejlT5=Wp%eST2g1XMF>fgH3s#h%iI5tg`|R@YftnQnTM!7MB|IsYPpcw(1pSk!J-c z;&50^+u@byKue}W1*-hSgr03tgi0xmHxoy_O(jXzPz0jr{*`Q1IUy0h<7WtrPb<#0 z_13UeG>E4Z9_yMn_!CT#w1+lZ>nqLc+Y0@?s7G#hxLaefrfmaPaqY%9%97^Q!Ao7_ zj7VL}TB6|d1;Itbj27Q?NQxn?JT+nZwv|qmJ@zo8Pa~0`^0ZVnC{9mRI?+=LTp*;% zYSGkA`6$Y?2X(Eu$`890_s~)f;g0K(#Jq~aovt0k)~WhFyF7L=#!rAmpdmf$qOO`h zHH`t`!%h<=LgGsJn9*Q-HCuJu*rz)gu+*MZC%meN z*KJglZ@*X;T1#tP?^*WrIao-$vhBVq{pEQ^BUoAQaQqqV?w$sq=Z3GZC8uxu3Nqz3 zpzC<>q*kbgOhTF(OAGg2q*neIBZW3s<9_^6la;>Lsn%n~@4Hq1hO8cLJywY55_i}D zj$3SPJ8oK}2Xd``0^&GdtLCfHmQ*Hhl#F^iX<+l}W%!m`7?o%e*b!o&Bkjr9^=X4fH-}Zs1Gjg2sA;lf?NuJ%8khvMFzt~#XNWf)Cq*X z5+V^WjfsmEIO)D|9Hy@nke-Zwi9xi>+EVr4<-!;~6{~{GqlXMf@{#vubtny7Imjre z{mk(v33O+g7o-h0r!hf+BW*M<1H8Qx6(cUOCRfu80sf}<>Sq8?uTYY(1$(U^w36)g zCSN6Aoga6|rBr?Zl3AynpEDYyjC(RcBu{f2Nmo&HuLbJ+p7x{gF=Vr_%V^MyvyUnB~ z`<5`ps{&?6v8sP3Yi@(uL0AOSkICbd>tLUi=85>@RwXrKCar#ik(VVR-cg(wpyGsO z9oLdjuc#1(l+7U)>PqJ5)3s9aMKAw?u5X{+2x3s2_64>^VF|8&YcXo8nmg4sW5l@U_lT!`z+aVvg4PrQ-ZPCdv%7Rm z@T!|=pq+zz)gGeKqVTN&8sIrsc_RYogwhpVwfz!DChYC+-Ls9!auX9| zcJ6mwv7|dT+R!qg05#GUV4v+~e-zGFJ%Oze-TuOICg_w)8801ntN*NHt&9ZtxRlP^ zn$)DXK6%{cj-2U2cp)y5Uyya$k5hjCnt#=1epD#wSV%$$ABlj?HVH9$AQ;91I_t3- ztUe%a)XxsE?Afl#y&;loW8n4vqJXhuO=CGQM94v+s+t6 zOXrAUIcve6SFbAdtTNyF*O2aCL{GY!Q-#`aT#}G9sUcqSbwK@zs>T=#YlO*)M#UKn zhE@U;)6E6PU?RRX`w+C{jZ1?SXwJn`1iwNuSEGq1UkI=-S2{fE(xJURn!}BV72f(L+`1#uEsa(A8Q^xbd=B6x zq~Va)tI1?Q4U$Q=#}pA?%mWjF8HXRKQ4>aWXP_i{uOrmOxBQ<1B{4bR26{^4&1&TQGb7sOlm$qyYeY^c+j+R6 z?NuoI<|K6m9l{TCEIDoT(_7>PQ%r`OW4BeS{_1;^c#Nd)?e^7TrSoSfPv}eM%>NFM zXSV1%Xaw?S@R$EQw1jQc>$d@gSk<|ELI!p{>4;B<>5=&vi@$4+#b~uQS2afK6^LLG ziYn>&{;*C$U`WQMOjf#dnOL0!gpLQtP#WiMvP2nVP#+KFR8ePTkA?`iu`Y&wBWauK z?rX3_AAW4YOmOxF=O2Goi4=M$)TwTMGzsnrQa@HW;9j*A#`^%=CuXHeA)(aeWlF3= zES(LIRtC78^?c=mvJB*8K|YIROoV;NL>xHt&rkGOCpMigS9igzx|_fm;#Ub&;Rxe z-0p>|rLXe!+B?0qvkY2;6zyG%ngR7#E`|`z6S+;;jP8c;F?s78j!v!GIMwB%L;xp! z({=rv(3HloS*M=A=*j-xj-Opg+4OcF{qc2K)&?;vLoW2a14?amQ#8sJYBDdz4GNEL zS@n-v6ZcrybfJ-up_F7{odXG{)@fs&bzfOH~gbNQxm?>oAh|>e8d{>RCq2i`po** z=C}~_Sz=t(PnwE=T&Fp{LU(-Q-E&l9qsSMdUupi$A9V3;(nTj^ZtT&Jegyc#M$9Jj zaMV_tB0qT`%)n} z@IE+q-&`U6Qk$y?o058CI{QX=H<|)?b4Klv)>zfV9EuPMy=v_*G1FAhu1$_4#Cq&x zdlng2D$6S`Oh#ywXB3nU)$B1YWnw^Ug$`C`Kj##vQ~GA`WpeDbYYudS-?Ore$}y+) zm72w+Bq#$CDcXTlTJdoXOW4GQxz50avjCJE> z=#elVZ})AgK>RHa-lx@GM4m0JQIKiy)hYwjy%Gy;y11Y79D*va%s=g%Y*QH&riE zuJBS!NMNZoUXf|nx>md0_+nFf2j>~1se@=?q(YeiDbz}~Z(7t>q&4DaW;k9BNkX)k zb7oGu@<{o+??&|Nezr}I&akFRJNP1NzRv{XlN{u4A5EuQk{B^pk6av(=jBk_c46Q-)xBg#@k;=o zE5*jdvl@9g>4M}ov+p4TFH4rRO9ws;kK=(x2#rMk31d;J;2t^37hfz4Ug4qp1PpER z%rTK)O`zIC4lIK?(Y1xO-_;a!#NiBoE11y_bj#?!7$SgkjrfJ#y-CrEw#U3Q&Ni?# zE(RMjb9d=l`5i6k-bF8-C5+{DaDNv5K5%>ngLvAnF3|pRFVT5U4q>Z{KC)YxJ@)2{ z0`IwwE)XNK(D8fz`FPZ$f>%Oeu!gx)zWh5R;o{`i)4$ZX&re?C1o~71<)rkDetw%c zbrYPLB7y+MD`BzKE3w-H8(cgvl18oJc&4(7-rH7Qz0S?K13S2S?sKw8sQ9er^CcVp zKEKy`>NgBS2H%`yQMZSLr&w* zSbk~j_B*VmeY=_Y-hI{qLqZomUZf4nyDxLWag7lIwd;k)gCzQC^gCxf8bO@t=6qI} zEBs@Y*OT72NU4oCTyJYPFtt=(>emeq`lV-f|I(*MB{qjtdJ9Saot^h^nFshjW`Kyl z{95o97+Lvas0iSpZ`gZd#|LRznHzUAs3z@@HrT5EfUys$yyzEu5)h)pH{_Ub5z?uh zuVU`;%%klc)#>EuPvfz)?F>oLpQwk|*)3|~08X49Zwq{^U!E}@g^^3Rl)B2cy@6ds zmbK!;6*-P7DBl5fr_-R9V8n}|K`0ttmG7UdaV>F8y5+_$EpZ1Rw;v426Hj3sj zMMhiY%miMdh)9yhcV%W<#L(>q|59XdUNX}6mmmA1Xn0U=TE-IFE>~l6H{I`Zx+R(- z%99)A-ZU{EO#@2Ev~66==9UpqERnZMJ3l{fu>E9rDWk-Cptx&Q|J}b*=@&Yb54K!z zc$!j_rLmfv7S>BMWYSz&S{1|O{whLH>7HuG>K(uamlK|Zml!WlER@~1M&XRC3q*_-n7R0o)XwdweS1OLfX+ArJ)|YMthJA>_ly(XJ2m*GmB>!# zGXuDf+vs?_-r9|-!CemS1UfS1;yz2vvS?G-k|#=A-#?Mg74SF@2-SODiVM`v6MWTt znGrfFAx+&jYel#0A{D*qoT)uaLyO(pOLr(S>oTe}uKgw|{^MUM$49SoG`b%u6V{^L zI4@L>+t!3u{7K!*-zQdr%*xF2I@3!+Lx>^N@_D)DM3_Z5Ze|}ogd*vsjDm|^K5dgJ zGt;<|H}2Z0I&;g?dSEo3Zyy$vQIUc;Gigb(jA~$BkB!J|*J+SuFxc`cPV5A?!DQSR zX8b8a8ZTc73$iLHqyM2eT5yjE(&~aOi*-p8B&q6f58+%hnw=yvPA)Ka8NRn> z66DA*;s~54jCT%D=LB$<7{W_m1fSwtjtAf886ibKyMAy%%!^Q_kxOQ5nGNPrSCU@f zdAe*eGQ8SS>)2rO&NC573!zH}7jf{A@fy4)41T@BODKEPBYf34Y6$)PW->m&?B!*- zHEF$Rj5BI;-Ki7KSG?xWbTA%>qEJoc?o!T^DX{q#gNlx;%!VF7fyYEtRlIOYRRw|? z{8)HTgj$@7ngeEh%aeaYlnRmGn7MZE-%R1!t&-cMj^e5gjw2E39Jl1&&A9Oy6BY7w$Rz7X1v5=9%A-q@-t8T_Ug@$*W*$uItwk zKlU?ZinT=Vb%kv&bfaO!Q<0gvzjr%8uG+3Oek6%%=jdPCt#D|c%cmgIwv}smB<|@c zt|SulzV19zTobl6CXiW}q%rQtWK#0E&^X_e72y4zHyPDcS7n{VE+$v~txe^P@3=hR zbNs5$G*JjGgyOB4 zVnl=|fiU98#{@dS>3y1)hv%H3;W{hhi|l>x4r^{QzXUSfptFTB41~UNRn-oJ-7=7i zN#FbS5M$f^{S#8!YVYtlX=?vQ8AE6=7Gur@uiez+)&j?GRG$5J)mb;Qe$(99?5N$S zg&S>DR{vh=Yv7+kj55AS=((-+p4&-`vbC7<((dy*pJB^uV4-;RmWvH28N1@-Plbp- zlW{BXy5eBB0l>aWK;l;ZS0tcpU|$i+POLG|Et7ewq2F&Nz#-c~))kx9s@Oza=WZZ=)xw);GYgph~l$DFPoF|`g=u(RC#2QXOo z64Zmg>rlY{yon3V+`emHJs?e%h&k7eaculuMGv4XT&OQf<8U`Y9vH-o;+QPgv*n9J zaa$KrwGFbg4cgDhFuK!mOkjRsGB}tjR;-HhAo@@J>^~nd&-HuX-9^mh>*OwmydJM` zKzCD)Ro-M=-wa{rbZuTay~b>mTffy4D9FkkCh>85YrW6MvIHK+JI`*MvK$IdsmCeG zREO@|tWD><(8`;Kb#Dj0w>?Rx-H&v-ziiTMWPCYmPr6pQJQM=wuQzbamD_Zg^l;lv z&10e*ez;bm`k+;=I%*Ne^xr#L6y+5`-1a}H3nO|Ses|J2DnzBXG4&bJkU+~P44v=1 zQu96CSHX^a*VrFULlMU2bR6Be?dEqE0V$AF%DVp`=2XxX1T+ ze=~2sO)Fsk$OFg}?sDr-Z{7cp3`q6%KxNy634MF>b}?~;;Ek;N(K}gB3i+n6>(lw`ijY!c}Y6yVnvXS}qx zc7hN*DF5J%Y$lD|4vX5}xc`t64>7$oKvrI{ty$4F1TxTa*yxH;qk1ENX07QcHhw|7 zucTwyzr1a_b?wX;or6!1yJH!~zP_2&)nsG=eGV&|zZ$T6*x4}yJ=+20n#Shls6VLX z?;l>$tugp2J7w}DN@tCno$Ka5{Fk&ch+LNPYjXa}5GikB675NOQJF(0*Q;ptE;#YF z*-zV6{4a9Q-2*qbH8Y``fqNMQbBsWG5#l z6HXH5;co86e$G;BJ^9!&=1lj@`2+j^XJ5dX8iATbUv=mE99rJp6UBs%JCKtPd;ovk zzY@#<$m-A^m@M-;kZ*cWrYMELpCE7YeU$NNj_}tIprH;C{6q`uqIJW+Z@!#4I|zs> zo;RuO!SbQ82^I!$Svyo(%rA%-tA;0@Q$4)7%#I-<$<0;8&{biL)82 zVfJ39OHhbWun88H{O^cr&Y&>+9P6qi7*{xH&d$XpS^teDYV&z*NYR8$G4{z zUb!a3Tl7n@`o4?y_spIDXKwuSfOEs5S8-~(8J*o7h!2Ci7b9b1jyoeHmi4J%X2@lj zMJl{=yw{7gnPbP-D^LGZPyhcz$C~zGqv&0e{T;%n9Un9bbS4~44-U)yHD4VrJVidB zvlyU&H2xnZ$$$EhlJ()Sb+=j2Eur7YxIp{g7NdiCBOQ+?B>R&eRlbw_5A*})JG?3c zk}?vp{fG3BRx-W!R(f*CE)ss3P4{j0)>ZxDX5s;kBUi80EW-bEh6nL8it+ + + + tier4_target_object_type_rviz_plugin + 0.0.1 + The tier4_target_object_type_rviz_plugin package + Takamasa Horibe + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + + libqt5-core + libqt5-gui + libqt5-widgets + qtbase5-dev + rclcpp + rviz_common + rviz_rendering + ament_lint_auto + autoware_lint_common + + + ament_cmake + + + diff --git a/common/tier4_target_object_type_rviz_plugin/plugins/plugin_description.xml b/common/tier4_target_object_type_rviz_plugin/plugins/plugin_description.xml new file mode 100644 index 0000000000000..eb3350e4333bd --- /dev/null +++ b/common/tier4_target_object_type_rviz_plugin/plugins/plugin_description.xml @@ -0,0 +1,9 @@ + + + + TargetObjectTypePanel + + + diff --git a/common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.cpp b/common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.cpp new file mode 100644 index 0000000000000..cf5960cac281f --- /dev/null +++ b/common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.cpp @@ -0,0 +1,248 @@ +// Copyright 2023 TIER IV, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "target_object_type_panel.hpp" + +#include +#include +#include +#include +#include + +TargetObjectTypePanel::TargetObjectTypePanel(QWidget * parent) : rviz_common::Panel(parent) +{ + node_ = std::make_shared("matrix_display_node"); + + setParameters(); + + matrix_widget_ = new QTableWidget(modules_.size(), targets_.size(), this); + for (size_t i = 0; i < modules_.size(); i++) { + matrix_widget_->setVerticalHeaderItem( + i, new QTableWidgetItem(QString::fromStdString(modules_[i]))); + } + for (size_t j = 0; j < targets_.size(); j++) { + matrix_widget_->setHorizontalHeaderItem( + j, new QTableWidgetItem(QString::fromStdString(targets_[j]))); + } + + updateMatrix(); + + reload_button_ = new QPushButton("Reload", this); + connect( + reload_button_, &QPushButton::clicked, this, &TargetObjectTypePanel::onReloadButtonClicked); + + QVBoxLayout * layout = new QVBoxLayout; + layout->addWidget(matrix_widget_); + layout->addWidget(reload_button_); + setLayout(layout); +} + +void TargetObjectTypePanel::onReloadButtonClicked() +{ + RCLCPP_INFO(node_->get_logger(), "Reload button clicked. Update parameter data."); + updateMatrix(); +} + +void TargetObjectTypePanel::setParameters() +{ + // Parameter will be investigated for these modules: + modules_ = { + "avoidance", + "avoidance_by_lane_change", + "lane_change", + "obstacle_cruise (inside)", + "obstacle_cruise (outside)", + "obstacle_stop", + "obstacle_slowdown"}; + + // Parameter will be investigated for targets in each module + targets_ = {"car", "truck", "bus", "trailer", "unknown", "bicycle", "motorcycle", "pedestrian"}; + + // TODO(Horibe): If the param naming strategy is aligned, this should be done automatically based + // on the modules_ and targets_. + + // default + ParamNameEnableObject default_param_name; + default_param_name.name.emplace("car", "car"); + default_param_name.name.emplace("truck", "truck"); + default_param_name.name.emplace("bus", "bus"); + default_param_name.name.emplace("trailer", "trailer"); + default_param_name.name.emplace("unknown", "unknown"); + default_param_name.name.emplace("bicycle", "bicycle"); + default_param_name.name.emplace("motorcycle", "motorcycle"); + default_param_name.name.emplace("pedestrian", "pedestrian"); + + // avoidance + { + const auto module = "avoidance"; + ParamNameEnableObject param_name; + param_name.node = + "/planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner"; + param_name.ns = "avoidance.target_object"; + param_name.name.emplace("car", "car.is_target"); + param_name.name.emplace("truck", "truck.is_target"); + param_name.name.emplace("bus", "bus.is_target"); + param_name.name.emplace("trailer", "trailer.is_target"); + param_name.name.emplace("unknown", "unknown.is_target"); + param_name.name.emplace("bicycle", "bicycle.is_target"); + param_name.name.emplace("motorcycle", "motorcycle.is_target"); + param_name.name.emplace("pedestrian", "pedestrian.is_target"); + param_names_.emplace(module, param_name); + } + + // avoidance_by_lane_change + { + const auto module = "avoidance_by_lane_change"; + ParamNameEnableObject param_name; + param_name.node = + "/planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner"; + param_name.ns = "avoidance_by_lane_change.target_object"; + param_name.name.emplace("car", "car.is_target"); + param_name.name.emplace("truck", "truck.is_target"); + param_name.name.emplace("bus", "bus.is_target"); + param_name.name.emplace("trailer", "trailer.is_target"); + param_name.name.emplace("unknown", "unknown.is_target"); + param_name.name.emplace("bicycle", "bicycle.is_target"); + param_name.name.emplace("motorcycle", "motorcycle.is_target"); + param_name.name.emplace("pedestrian", "pedestrian.is_target"); + param_names_.emplace(module, param_name); + } + + // lane_change + { + const auto module = "lane_change"; + ParamNameEnableObject param_name; + param_name.node = + "/planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner"; + param_name.ns = "lane_change.target_object"; + param_name.name = default_param_name.name; + param_names_.emplace(module, param_name); + } + + // obstacle cruise (inside) + { + const auto module = "obstacle_cruise (inside)"; + ParamNameEnableObject param_name; + param_name.node = + "/planning/scenario_planning/lane_driving/motion_planning/obstacle_cruise_planner"; + param_name.ns = "common.cruise_obstacle_type.inside"; + param_name.name = default_param_name.name; + param_names_.emplace(module, param_name); + } + + // obstacle cruise (outside) + { + const auto module = "obstacle_cruise (outside)"; + ParamNameEnableObject param_name; + param_name.node = + "/planning/scenario_planning/lane_driving/motion_planning/obstacle_cruise_planner"; + param_name.ns = "common.cruise_obstacle_type.outside"; + param_name.name = default_param_name.name; + param_names_.emplace(module, param_name); + } + + // obstacle stop + { + const auto module = "obstacle_stop"; + ParamNameEnableObject param_name; + param_name.node = + "/planning/scenario_planning/lane_driving/motion_planning/obstacle_cruise_planner"; + param_name.ns = "common.stop_obstacle_type"; + param_name.name = default_param_name.name; + param_names_.emplace(module, param_name); + } + + // obstacle slowdown + { + const auto module = "obstacle_slowdown"; + ParamNameEnableObject param_name; + param_name.node = + "/planning/scenario_planning/lane_driving/motion_planning/obstacle_cruise_planner"; + param_name.ns = "common.slow_down_obstacle_type"; + param_name.name = default_param_name.name; + param_names_.emplace(module, param_name); + } +} + +void TargetObjectTypePanel::updateMatrix() +{ + // blue base + // const QColor color_in_use("#6eb6cc"); + // const QColor color_no_use("#1d3e48"); + // const QColor color_undefined("#9e9e9e"); + + // green base + const QColor color_in_use("#afff70"); + const QColor color_no_use("#44642b"); + const QColor color_undefined("#9e9e9e"); + + const auto set_undefined = [&](const auto i, const auto j) { + QTableWidgetItem * item = new QTableWidgetItem("N/A"); + item->setForeground(QBrush(Qt::black)); // set the text color to black + item->setBackground(color_undefined); + matrix_widget_->setItem(i, j, item); + }; + + for (size_t i = 0; i < modules_.size(); i++) { + const auto & module = modules_[i]; + + // Check if module exists in param_names_ + if (param_names_.find(module) == param_names_.end()) { + RCLCPP_WARN_STREAM(node_->get_logger(), module << " is not in the param names"); + continue; + } + + const auto & module_params = param_names_.at(module); + auto parameters_client = + std::make_shared(node_, module_params.node); + if (!parameters_client->wait_for_service(std::chrono::microseconds(500))) { + RCLCPP_WARN_STREAM( + node_->get_logger(), "Failed to find parameter service for node: " << module_params.node); + for (size_t j = 0; j < targets_.size(); j++) { + set_undefined(i, j); + } + continue; + } + + for (size_t j = 0; j < targets_.size(); j++) { + const auto & target = targets_[j]; + + // Check if target exists in module's name map + if (module_params.name.find(target) == module_params.name.end()) { + RCLCPP_WARN_STREAM( + node_->get_logger(), target << " parameter is not set in the " << module); + continue; + } + + std::string param_name = module_params.ns + "." + module_params.name.at(target); + auto parameter_result = parameters_client->get_parameters({param_name}); + + if (!parameter_result.empty()) { + bool value = parameter_result[0].as_bool(); + QTableWidgetItem * item = new QTableWidgetItem(value ? "O" : "X"); + item->setForeground(QBrush(value ? Qt::black : Qt::black)); // set the text color to black + item->setBackground(QBrush(value ? color_in_use : color_no_use)); + matrix_widget_->setItem(i, j, item); + } else { + RCLCPP_WARN_STREAM( + node_->get_logger(), + "Failed to get parameter " << module_params.node << " " << param_name); + + set_undefined(i, j); + } + } + } +} + +PLUGINLIB_EXPORT_CLASS(TargetObjectTypePanel, rviz_common::Panel) diff --git a/common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.hpp b/common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.hpp new file mode 100644 index 0000000000000..1f3934369bfba --- /dev/null +++ b/common/tier4_target_object_type_rviz_plugin/src/target_object_type_panel.hpp @@ -0,0 +1,60 @@ +// Copyright 2023 TIER IV, Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TARGET_OBJECT_TYPE_PANEL_HPP_ +#define TARGET_OBJECT_TYPE_PANEL_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +class TargetObjectTypePanel : public rviz_common::Panel +{ + Q_OBJECT + +public: + explicit TargetObjectTypePanel(QWidget * parent = 0); + +protected: + QTableWidget * matrix_widget_; + std::shared_ptr node_; + std::vector modules_; + std::vector targets_; + + struct ParamNameEnableObject + { + std::string node; + std::string ns; + std::unordered_map name; + }; + std::unordered_map param_names_; + +private slots: + void onReloadButtonClicked(); + +private: + QPushButton * reload_button_; + + void updateMatrix(); + void setParameters(); +}; + +#endif // TARGET_OBJECT_TYPE_PANEL_HPP_ From 3d456319fc99b961232a801c6bab926f25a48f6c Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Sun, 3 Sep 2023 19:25:36 +0900 Subject: [PATCH 094/547] refactor(behavior_path_planner): consolidate common function which is used by start and goal planner (#4804) * refactor code * consolidate function * add utility * util to utils * define parameters * fix typo * fix arg name * commonize calcFeasibleDecelDistance * define modifyVelocityByDirection * add description --------- Signed-off-by: kyoichi-sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- planning/behavior_path_planner/CMakeLists.txt | 1 + .../goal_planner/goal_planner_module.hpp | 8 +- .../geometric_parallel_parking.hpp | 5 + .../goal_planner/goal_planner_parameters.hpp | 13 ++ .../goal_planner/pull_over_planner_base.hpp | 3 + .../path_safety_checker_parameters.hpp | 2 +- .../common_module_data.hpp | 45 ++++++ .../utils/start_goal_planner_common/utils.hpp | 94 +++++++++++ .../start_planner_parameters.hpp | 13 ++ .../goal_planner/goal_planner_module.cpp | 95 +++++------ .../geometric_parallel_parking.cpp | 15 ++ .../goal_planner/freespace_pull_over.cpp | 15 +- .../goal_planner/geometric_pull_over.cpp | 1 + .../utils/goal_planner/shift_pull_over.cpp | 1 + .../path_safety_checker/objects_filtering.cpp | 6 +- .../src/utils/path_utils.cpp | 1 + .../utils/start_goal_planner_common/utils.cpp | 148 ++++++++++++++++++ .../start_planner/geometric_pull_out.cpp | 10 +- 18 files changed, 405 insertions(+), 71 deletions(-) create mode 100644 planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp create mode 100644 planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp create mode 100644 planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp diff --git a/planning/behavior_path_planner/CMakeLists.txt b/planning/behavior_path_planner/CMakeLists.txt index 0352096d02b2b..f62e418371401 100644 --- a/planning/behavior_path_planner/CMakeLists.txt +++ b/planning/behavior_path_planner/CMakeLists.txt @@ -32,6 +32,7 @@ ament_auto_add_library(behavior_path_planner_node SHARED src/utils/path_utils.cpp src/utils/path_safety_checker/safety_check.cpp src/utils/path_safety_checker/objects_filtering.cpp + src/utils/start_goal_planner_common/utils.cpp src/utils/avoidance/utils.cpp src/utils/lane_change/utils.cpp src/utils/side_shift/util.cpp diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 964e0ff87594a..1ddf288e06ae3 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -65,7 +65,7 @@ enum class PathType { FREESPACE, }; -struct PUllOverStatus +struct PullOverStatus { std::shared_ptr pull_over_path{}; std::shared_ptr lane_parking_pull_over_path{}; @@ -76,7 +76,8 @@ struct PUllOverStatus lanelet::ConstLanelets pull_over_lanes{}; std::vector lanes{}; // current + pull_over bool has_decided_path{false}; - bool is_safe{false}; + bool is_safe_static_objects{false}; // current path is safe against static objects + bool is_safe_dynamic_objects{false}; // current path is safe against dynamic objects bool prev_is_safe{false}; bool has_decided_velocity{false}; bool has_requested_approval{false}; @@ -134,7 +135,7 @@ class GoalPlannerModule : public SceneModuleInterface bool canTransitIdleToRunningState() override { return false; } - PUllOverStatus status_; + PullOverStatus status_; std::shared_ptr parameters_; @@ -205,7 +206,6 @@ class GoalPlannerModule : public SceneModuleInterface const Pose & search_start_offset_pose, PathWithLaneId & path) const; PathWithLaneId generateStopPath(); PathWithLaneId generateFeasibleStopPath(); - boost::optional calcFeasibleDecelDistance(const double target_velocity) const; void keepStoppedWithCurrentPath(PathWithLaneId & path); double calcSignedArcLengthFromEgo(const PathWithLaneId & path, const Pose & pose) const; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp index cb769a1f2884c..9cae5e18f1352 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp @@ -94,6 +94,10 @@ class GeometricParallelParking std::vector getArcPaths() const { return arc_paths_; } std::vector getPaths() const { return paths_; } + std::vector> getPairsTerminalVelocityAndAccel() const + { + return pairs_terminal_velocity_and_accel_; + } PathWithLaneId getPathByIdx(size_t const idx) const; PathWithLaneId getCurrentPath() const; PathWithLaneId getFullPath() const; @@ -112,6 +116,7 @@ class GeometricParallelParking std::vector arc_paths_; std::vector paths_; + std::vector> pairs_terminal_velocity_and_accel_; size_t current_path_idx_ = 0; void clearPaths(); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index f87944641f59b..6b8a62d2caec9 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -17,6 +17,7 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__GOAL_PLANNER__GOAL_PLANNER_PARAMETERS_HPP_ #include "behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include #include @@ -99,6 +100,18 @@ struct GoalPlannerParameters AstarParam astar_parameters; RRTStarParam rrt_star_parameters; + // stop condition + double maximum_deceleration_for_stop; + double maximum_jerk_for_stop; + + // hysteresis parameter + double hysteresis_factor_expand_rate; + + // path safety checker + utils::path_safety_checker::EgoPredictedPathParams ego_predicted_path_params; + utils::path_safety_checker::ObjectsFilteringParams objects_filtering_params; + utils::path_safety_checker::SafetyCheckParams safety_check_params; + // debug bool print_debug_info; }; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp index 4b800917d4aec..20788e53309ec 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp @@ -26,6 +26,7 @@ #include #include +#include #include using autoware_auto_planning_msgs::msg::PathWithLaneId; @@ -46,6 +47,8 @@ struct PullOverPath { PullOverPlannerType type{PullOverPlannerType::NONE}; std::vector partial_paths{}; + // accelerate with constant acceleration to the target velocity + std::vector> pairs_terminal_velocity_and_accel{}; Pose start_pose{}; Pose end_pose{}; std::vector debug_poses{}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index 93eb0457d7cf4..475b38f5bb824 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -170,7 +170,7 @@ struct ObjectsFilteringParams struct SafetyCheckParams { bool enable_safety_check; ///< Enable safety checks. - double backward_lane_length; ///< Length of the backward lane for path generation. + double backward_path_length; ///< Length of the backward lane for path generation. double forward_path_length; ///< Length of the forward path lane for path generation. RSSparams rss_params; ///< Parameters related to the RSS model. bool publish_debug_marker{false}; ///< Option to publish debug markers. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp new file mode 100644 index 0000000000000..4ba2cb08b6341 --- /dev/null +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp @@ -0,0 +1,45 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__COMMON_MODULE_DATA_HPP_ +#define BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__COMMON_MODULE_DATA_HPP_ + +#include "behavior_path_planner/marker_utils/utils.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" + +#include + +#include + +#include + +namespace behavior_path_planner +{ +using autoware_auto_perception_msgs::msg::PredictedObjects; +using behavior_path_planner::utils::path_safety_checker::PoseWithVelocityStamped; +using behavior_path_planner::utils::path_safety_checker::TargetObjectsOnLane; +/* + * Common data for start/goal_planner module + */ +struct StartGoalPlannerData +{ + // filtered objects + PredictedObjects filtered_objects; + TargetObjectsOnLane target_objects_on_lane; + std::vector ego_predicted_path; +}; + +} // namespace behavior_path_planner + +#endif // BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__COMMON_MODULE_DATA_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp new file mode 100644 index 0000000000000..60bbb3c78b401 --- /dev/null +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp @@ -0,0 +1,94 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__UTILS_HPP_ +#define BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__UTILS_HPP_ + +#include "behavior_path_planner/data_manager.hpp" +#include "behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" +#include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" +#include "behavior_path_planner/utils/start_planner/start_planner_parameters.hpp" + +#include + +#include +#include +#include + +namespace behavior_path_planner::utils::start_goal_planner_common +{ + +using behavior_path_planner::StartPlannerParameters; +using behavior_path_planner::utils::path_safety_checker::EgoPredictedPathParams; +using behavior_path_planner::utils::path_safety_checker::ObjectsFilteringParams; +using behavior_path_planner::utils::path_safety_checker::SafetyCheckParams; + +boost::optional calcFeasibleDecelDistance( + std::shared_ptr planner_data, const double acc_lim, const double jerk_lim, + const double target_velocity); + +/** + * @brief Update path velocities based on driving direction. + * + * This function updates the longitudinal velocity of each point in the provided paths, + * based on whether the vehicle is driving forward or backward. It also sets the terminal + * velocity and acceleration for each path. + * + * @param paths A vector of paths with lane IDs to be updated. + * @param terminal_vel_acc_pairs A vector of pairs, where each pair contains the terminal + * velocity and acceleration for a corresponding path. + * @param target_velocity The target velocity for ego vehicle predicted path. + * @param acceleration The acceleration for ego vehicle predicted path. + */ +void modifyVelocityByDirection( + std::vector & paths, + std::vector> & terminal_vel_acc_pairs, const double target_velocity, + const double acceleration); + +void updateEgoPredictedPathParams( + std::shared_ptr & ego_predicted_path_params, + const std::shared_ptr & start_planner_params); + +void updateEgoPredictedPathParams( + std::shared_ptr & ego_predicted_path_params, + const std::shared_ptr & start_planner_params); + +void updateSafetyCheckParams( + std::shared_ptr & safety_check_params, + const std::shared_ptr & start_planner_params); + +void updateSafetyCheckParams( + std::shared_ptr & safety_check_params, + const std::shared_ptr & start_planner_params); + +void updateObjectsFilteringParams( + std::shared_ptr & objects_filtering_params, + const std::shared_ptr & start_planner_params); + +void updateObjectsFilteringParams( + std::shared_ptr & objects_filtering_params, + const std::shared_ptr & start_planner_params); + +void updatePathProperty( + std::shared_ptr & ego_predicted_path_params, + const std::pair & pairs_terminal_velocity_and_accel); + +std::pair getPairsTerminalVelocityAndAccel( + const std::vector> & pairs_terminal_velocity_and_accel, + const size_t current_path_idx); + +} // namespace behavior_path_planner::utils::start_goal_planner_common + +#endif // BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__UTILS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp index 1e26bef0c85be..771fbd93f7196 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp @@ -17,6 +17,7 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__START_PLANNER__START_PLANNER_PARAMETERS_HPP_ #include "behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include #include @@ -76,6 +77,18 @@ struct StartPlannerParameters PlannerCommonParam freespace_planner_common_parameters; AstarParam astar_parameters; RRTStarParam rrt_star_parameters; + + // stop condition + double maximum_deceleration_for_stop; + double maximum_jerk_for_stop; + + // hysteresis parameter + double hysteresis_factor_expand_rate; + + // path safety checker + utils::path_safety_checker::EgoPredictedPathParams ego_predicted_path_params; + utils::path_safety_checker::ObjectsFilteringParams objects_filtering_params; + utils::path_safety_checker::SafetyCheckParams safety_check_params; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 6c7853ac0e54f..1d491a7fb7b87 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -18,6 +18,7 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/utils.hpp" #include @@ -34,6 +35,7 @@ #include #include +using behavior_path_planner::utils::start_goal_planner_common::calcFeasibleDecelDistance; using motion_utils::calcDecelDistWithJerkAndAccConstraints; using motion_utils::calcLongitudinalOffsetPose; using motion_utils::calcSignedArcLength; @@ -117,7 +119,7 @@ GoalPlannerModule::GoalPlannerModule( void GoalPlannerModule::resetStatus() { - PUllOverStatus initial_status{}; + PullOverStatus initial_status{}; status_ = initial_status; pull_over_path_candidates_.clear(); closest_start_pose_.reset(); @@ -342,7 +344,8 @@ bool GoalPlannerModule::isExecutionReady() const double GoalPlannerModule::calcModuleRequestLength() const { - const auto min_stop_distance = calcFeasibleDecelDistance(0.0); + const auto min_stop_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); if (!min_stop_distance) { return parameters_->minimum_request_length; } @@ -442,7 +445,7 @@ bool GoalPlannerModule::planFreespacePath() mutex_.lock(); status_.pull_over_path = std::make_shared(*freespace_path); status_.current_path_idx = 0; - status_.is_safe = true; + status_.is_safe_static_objects = true; modified_goal_pose_ = goal_candidate; last_path_update_time_ = std::make_unique(clock_->now()); debug_data_.freespace_planner.is_planning = false; @@ -479,7 +482,7 @@ void GoalPlannerModule::returnToLaneParking() } mutex_.lock(); - status_.is_safe = true; + status_.is_safe_static_objects = true; status_.has_decided_path = false; status_.pull_over_path = status_.lane_parking_pull_over_path; status_.current_path_idx = 0; @@ -541,7 +544,7 @@ void GoalPlannerModule::selectSafePullOverPath() const auto pull_over_path_candidates = pull_over_path_candidates_; const auto goal_candidates = goal_candidates_; mutex_.unlock(); - status_.is_safe = false; + status_.is_safe_static_objects = false; for (const auto & pull_over_path : pull_over_path_candidates) { // check if goal is safe const auto goal_candidate_it = std::find_if( @@ -560,7 +563,7 @@ void GoalPlannerModule::selectSafePullOverPath() continue; } - status_.is_safe = true; + status_.is_safe_static_objects = true; mutex_.lock(); status_.pull_over_path = std::make_shared(pull_over_path); status_.current_path_idx = 0; @@ -572,7 +575,7 @@ void GoalPlannerModule::selectSafePullOverPath() } // decelerate before the search area start - if (status_.is_safe) { + if (status_.is_safe_static_objects) { const auto search_start_offset_pose = calcLongitudinalOffsetPose( status_.pull_over_path->getFullPath().points, refined_goal_pose_.position, -parameters_->backward_goal_search_length - planner_data_->parameters.base_link2front - @@ -583,7 +586,9 @@ void GoalPlannerModule::selectSafePullOverPath() decelerateBeforeSearchStart(*search_start_offset_pose, first_path); } else { // if already passed the search start offset pose, set pull_over_velocity to first_path. - const auto min_decel_distance = calcFeasibleDecelDistance(parameters_->pull_over_velocity); + const auto min_decel_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, + parameters_->pull_over_velocity); for (auto & p : first_path.points) { const double distance_from_ego = calcSignedArcLengthFromEgo(first_path, p.point.pose); if (min_decel_distance && distance_from_ego < *min_decel_distance) { @@ -616,7 +621,7 @@ void GoalPlannerModule::setLanes() void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) { - if (status_.is_safe) { + if (status_.is_safe_static_objects) { // clear stop pose when the path is safe and activated if (isActivated()) { resetWallPoses(); @@ -645,7 +650,7 @@ void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) // for the next loop setOutput(). // this is used to determine whether to generate a new stop path or keep the current stop path. - status_.prev_is_safe = status_.is_safe; + status_.prev_is_safe = status_.is_safe_static_objects; } void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) @@ -694,7 +699,7 @@ void GoalPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) const void GoalPlannerModule::setModifiedGoal(BehaviorModuleOutput & output) const { const auto & route_handler = planner_data_->route_handler; - if (status_.is_safe) { + if (status_.is_safe_static_objects) { PoseWithUuidStamped modified_goal{}; modified_goal.uuid = route_handler->getRouteUuid(); modified_goal.pose = modified_goal_pose_->goal_pose; @@ -742,7 +747,7 @@ bool GoalPlannerModule::hasDecidedPath() const } // if path is not safe, not decided - if (!status_.is_safe) { + if (!status_.is_safe_static_objects) { return false; } @@ -864,7 +869,7 @@ BehaviorModuleOutput GoalPlannerModule::planWaitingApprovalWithGoalModification( out.modified_goal = plan().modified_goal; // update status_ out.path = std::make_shared(generateStopPath()); out.reference_path = getPreviousModuleOutput().reference_path; - path_candidate_ = status_.is_safe + path_candidate_ = status_.is_safe_static_objects ? std::make_shared(status_.pull_over_path->getFullPath()) : out.path; path_reference_ = getPreviousModuleOutput().reference_path; @@ -959,17 +964,19 @@ PathWithLaneId GoalPlannerModule::generateStopPath() reference_path.points, refined_goal_pose_.position, -parameters_->backward_goal_search_length - common_parameters.base_link2front - approximate_pull_over_distance_); - if (!status_.is_safe && !closest_start_pose_ && !search_start_offset_pose) { + if (!status_.is_safe_static_objects && !closest_start_pose_ && !search_start_offset_pose) { return generateFeasibleStopPath(); } - const Pose stop_pose = status_.is_safe ? status_.pull_over_path->start_pose - : (closest_start_pose_ ? closest_start_pose_.value() - : *search_start_offset_pose); + const Pose stop_pose = + status_.is_safe_static_objects + ? status_.pull_over_path->start_pose + : (closest_start_pose_ ? closest_start_pose_.value() : *search_start_offset_pose); // if stop pose is closer than min_stop_distance, stop as soon as possible const double ego_to_stop_distance = calcSignedArcLengthFromEgo(reference_path, stop_pose); - const auto min_stop_distance = calcFeasibleDecelDistance(0.0); + const auto min_stop_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); const double eps_vel = 0.01; const bool is_stopped = std::abs(current_vel) < eps_vel; const double buffer = is_stopped ? stop_distance_buffer_ : 0.0; @@ -986,7 +993,9 @@ PathWithLaneId GoalPlannerModule::generateStopPath() decelerateBeforeSearchStart(*search_start_offset_pose, reference_path); } else { // if already passed the search start offset pose, set pull_over_velocity to reference_path. - const auto min_decel_distance = calcFeasibleDecelDistance(pull_over_velocity); + const auto min_decel_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, + pull_over_velocity); for (auto & p : reference_path.points) { const double distance_from_ego = calcSignedArcLengthFromEgo(reference_path, p.point.pose); if (min_decel_distance && distance_from_ego < *min_decel_distance) { @@ -1014,7 +1023,8 @@ PathWithLaneId GoalPlannerModule::generateFeasibleStopPath() auto stop_path = route_handler->getCenterLinePath(status_.current_lanes, s_start, s_end, true); // calc minimum stop distance under maximum deceleration - const auto min_stop_distance = calcFeasibleDecelDistance(0.0); + const auto min_stop_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); if (!min_stop_distance) { return stop_path; } @@ -1099,7 +1109,7 @@ bool GoalPlannerModule::isStuck() } // not found safe path - if (!status_.is_safe) { + if (!status_.is_safe_static_objects) { return true; } @@ -1221,7 +1231,8 @@ bool GoalPlannerModule::hasEnoughDistance(const PullOverPath & pull_over_path) c return false; } - const auto current_to_stop_distance = calcFeasibleDecelDistance(0.0); + const auto current_to_stop_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); if (!current_to_stop_distance) { return false; } @@ -1257,30 +1268,6 @@ void GoalPlannerModule::keepStoppedWithCurrentPath(PathWithLaneId & path) } } -boost::optional GoalPlannerModule::calcFeasibleDecelDistance( - const double target_velocity) const -{ - const auto v_now = planner_data_->self_odometry->twist.twist.linear.x; - const auto a_now = planner_data_->self_acceleration->accel.accel.linear.x; - const auto a_lim = parameters_->maximum_deceleration; // positive value - const auto j_lim = parameters_->maximum_jerk; - - if (v_now < target_velocity) { - return 0.0; - } - - auto min_stop_distance = calcDecelDistWithJerkAndAccConstraints( - v_now, target_velocity, a_now, -a_lim, j_lim, -1.0 * j_lim); - - if (!min_stop_distance) { - return {}; - } - - min_stop_distance = std::max(*min_stop_distance, 0.0); - - return min_stop_distance; -} - double GoalPlannerModule::calcSignedArcLengthFromEgo( const PathWithLaneId & path, const Pose & pose) const { @@ -1305,7 +1292,8 @@ void GoalPlannerModule::decelerateForTurnSignal(const Pose & stop_pose, PathWith const float decel_vel = std::min(point.point.longitudinal_velocity_mps, static_cast(distance_to_stop / time)); const double distance_from_ego = calcSignedArcLengthFromEgo(path, point.point.pose); - const auto min_decel_distance = calcFeasibleDecelDistance(decel_vel); + const auto min_decel_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, decel_vel); // when current velocity already lower than decel_vel, min_decel_distance will be 0.0, // and do not need to decelerate. @@ -1323,7 +1311,8 @@ void GoalPlannerModule::decelerateForTurnSignal(const Pose & stop_pose, PathWith } const double stop_point_length = calcSignedArcLength(path.points, 0, stop_pose.position); - const auto min_stop_distance = calcFeasibleDecelDistance(0.0); + const auto min_stop_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); if (min_stop_distance && *min_stop_distance < stop_point_length) { const auto stop_point = utils::insertStopPoint(stop_point_length, path); @@ -1337,7 +1326,9 @@ void GoalPlannerModule::decelerateBeforeSearchStart( const Pose & current_pose = planner_data_->self_odometry->pose.pose; // slow down before the search area. - const auto min_decel_distance = calcFeasibleDecelDistance(pull_over_velocity); + const auto min_decel_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, + pull_over_velocity); if (min_decel_distance) { const double distance_to_search_start = calcSignedArcLengthFromEgo(path, search_start_offset_pose); @@ -1479,7 +1470,7 @@ void GoalPlannerModule::setDebugData() } // Visualize path and related pose - if (status_.is_safe) { + if (status_.is_safe_static_objects) { add(createPoseMarkerArray( status_.pull_over_path->start_pose, "pull_over_start_pose", 0, 0.3, 0.3, 0.9)); add(createPoseMarkerArray( @@ -1499,8 +1490,8 @@ void GoalPlannerModule::setDebugData() // Visualize planner type text { visualization_msgs::msg::MarkerArray planner_type_marker_array{}; - const auto color = status_.is_safe ? createMarkerColor(1.0, 1.0, 1.0, 0.99) - : createMarkerColor(1.0, 0.0, 0.0, 0.99); + const auto color = status_.is_safe_static_objects ? createMarkerColor(1.0, 1.0, 1.0, 0.99) + : createMarkerColor(1.0, 0.0, 0.0, 0.99); auto marker = createDefaultMarker( header.frame_id, header.stamp, "planner_type", 0, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, createMarkerScale(0.0, 0.0, 1.0), color); diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index a5ccfc9517771..27dbfb532c1fc 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -163,6 +163,7 @@ void GeometricParallelParking::clearPaths() current_path_idx_ = 0; arc_paths_.clear(); paths_.clear(); + pairs_terminal_velocity_and_accel_.clear(); } bool GeometricParallelParking::planPullOver( @@ -182,6 +183,7 @@ bool GeometricParallelParking::planPullOver( if (is_forward) { // When turning forward to the right, the front left goes out, // so reduce the steer angle at that time for seach no lane departure path. + // TODO(Sugahara): define in the config constexpr double start_pose_offset = 0.0; constexpr double min_steer_rad = 0.05; constexpr double steer_interval = 0.1; @@ -481,6 +483,19 @@ std::vector GeometricParallelParking::planOneTrial( paths_.push_back(path_turn_left); paths_.push_back(path_turn_right); + // set terminal velocity and acceleration(temporary implementation) + if (is_forward) { + pairs_terminal_velocity_and_accel_.push_back( + std::make_pair(parameters_.forward_parking_velocity, 0.0)); + pairs_terminal_velocity_and_accel_.push_back( + std::make_pair(parameters_.forward_parking_velocity, 0.0)); + } else { + pairs_terminal_velocity_and_accel_.push_back( + std::make_pair(parameters_.backward_parking_velocity, 0.0)); + pairs_terminal_velocity_and_accel_.push_back( + std::make_pair(parameters_.backward_parking_velocity, 0.0)); + } + // set pull_over start and end pose // todo: make start and end pose for pull_out start_pose_ = start_pose; diff --git a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp index f4aabb13b8916..9656094f5a478 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include #include @@ -55,8 +56,7 @@ boost::optional FreespacePullOver::plan(const Pose & goal_pose) const Pose end_pose = use_back_ ? goal_pose : tier4_autoware_utils::calcOffsetPose(goal_pose, -straight_distance, 0.0, 0.0); - const bool found_path = planner_->makePlan(current_pose, end_pose); - if (!found_path) { + if (!planner_->makePlan(current_pose, end_pose)) { return {}; } @@ -113,18 +113,21 @@ boost::optional FreespacePullOver::plan(const Pose & goal_pose) addPose(goal_pose); } - utils::correctDividedPathVelocity(partial_paths); + std::vector> pairs_terminal_velocity_and_accel{}; + pairs_terminal_velocity_and_accel.resize(partial_paths.size()); + utils::start_goal_planner_common::modifyVelocityByDirection( + partial_paths, pairs_terminal_velocity_and_accel, velocity_, 0); + // Check if driving forward for each path, return empty if not for (auto & path : partial_paths) { - const auto is_driving_forward = motion_utils::isDrivingForward(path.points); - if (!is_driving_forward) { - // path points is less than 2 + if (!motion_utils::isDrivingForward(path.points)) { return {}; } } PullOverPath pull_over_path{}; pull_over_path.partial_paths = partial_paths; + pull_over_path.pairs_terminal_velocity_and_accel = pairs_terminal_velocity_and_accel; pull_over_path.start_pose = current_pose; pull_over_path.end_pose = goal_pose; pull_over_path.type = getPlannerType(); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp index 6a5ccc827db29..7760b9b57d8ab 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp @@ -75,6 +75,7 @@ boost::optional GeometricPullOver::plan(const Pose & goal_pose) PullOverPath pull_over_path{}; pull_over_path.type = getPlannerType(); pull_over_path.partial_paths = planner_.getPaths(); + pull_over_path.pairs_terminal_velocity_and_accel = planner_.getPairsTerminalVelocityAndAccel(); pull_over_path.start_pose = planner_.getStartPose(); pull_over_path.end_pose = planner_.getArcEndPose(); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp index 2f5c1d9b05b7f..c5ac14d4d9727 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp @@ -204,6 +204,7 @@ boost::optional ShiftPullOver::generatePullOverPath( PullOverPath pull_over_path{}; pull_over_path.type = getPlannerType(); pull_over_path.partial_paths.push_back(shifted_path.path); + pull_over_path.pairs_terminal_velocity_and_accel.push_back(std::make_pair(pull_over_velocity, 0)); pull_over_path.start_pose = path_shifter.getShiftLines().front().start; pull_over_path.end_pose = path_shifter.getShiftLines().front().end; pull_over_path.debug_poses.push_back(shift_end_pose_road_lane); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index a775e7c16efed..23016b01cb9ab 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -343,13 +343,13 @@ TargetObjectsOnLane createTargetObjectsOnLane( }; // TODO(Sugahara): Consider shoulder and other lane objects - if (object_lane_configuration.check_current_lane) { + if (object_lane_configuration.check_current_lane && !current_lanes.empty()) { append_objects_on_lane(target_objects_on_lane.on_current_lane, current_lanes); } - if (object_lane_configuration.check_left_lane) { + if (object_lane_configuration.check_left_lane && !all_left_lanelets.empty()) { append_objects_on_lane(target_objects_on_lane.on_left_lane, all_left_lanelets); } - if (object_lane_configuration.check_right_lane) { + if (object_lane_configuration.check_right_lane && !all_right_lanelets.empty()) { append_objects_on_lane(target_objects_on_lane.on_right_lane, all_right_lanelets); } diff --git a/planning/behavior_path_planner/src/utils/path_utils.cpp b/planning/behavior_path_planner/src/utils/path_utils.cpp index ec14a064bf51b..c4bfb62276d42 100644 --- a/planning/behavior_path_planner/src/utils/path_utils.cpp +++ b/planning/behavior_path_planner/src/utils/path_utils.cpp @@ -418,6 +418,7 @@ void correctDividedPathVelocity(std::vector & divided_paths) { for (auto & path : divided_paths) { const auto is_driving_forward = motion_utils::isDrivingForward(path.points); + // If the number of points in the path is less than 2, don't correct the velocity if (!is_driving_forward) { continue; } diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp new file mode 100644 index 0000000000000..f29711c82b3ce --- /dev/null +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -0,0 +1,148 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" + +namespace behavior_path_planner::utils::start_goal_planner_common +{ + +using motion_utils::calcDecelDistWithJerkAndAccConstraints; + +boost::optional calcFeasibleDecelDistance( + std::shared_ptr planner_data, const double acc_lim, const double jerk_lim, + const double target_velocity) +{ + const auto v_now = planner_data->self_odometry->twist.twist.linear.x; + const auto a_now = planner_data->self_acceleration->accel.accel.linear.x; + + if (v_now < target_velocity) { + return 0.0; + } + + auto min_stop_distance = calcDecelDistWithJerkAndAccConstraints( + v_now, target_velocity, a_now, -acc_lim, jerk_lim, -1.0 * jerk_lim); + + if (!min_stop_distance) { + return {}; + } + + min_stop_distance = std::max(*min_stop_distance, 0.0); + + return min_stop_distance; +} + +void modifyVelocityByDirection( + std::vector & paths, + std::vector> & terminal_vel_acc_pairs, const double target_velocity, + const double acceleration) +{ + assert(paths.size() == terminal_vel_acc_pairs.size()); + + auto path_itr = std::begin(paths); + auto pair_itr = std::begin(terminal_vel_acc_pairs); + + for (; path_itr != std::end(paths); ++path_itr, ++pair_itr) { + const auto is_driving_forward = motion_utils::isDrivingForward(path_itr->points); + + // If the number of points in the path is less than 2, don't insert stop velocity and + // set pairs_terminal_velocity_and_accel to 0 + if (!is_driving_forward) { + *pair_itr = std::make_pair(0.0, 0.0); + continue; + } + + if (*is_driving_forward) { + for (auto & point : path_itr->points) { + // TODO(Sugahara): velocity calculation can be improved by considering the acceleration + point.point.longitudinal_velocity_mps = std::abs(point.point.longitudinal_velocity_mps); + } + // TODO(Sugahara): Consider the calculation of the target velocity and acceleration for ego's + // predicted path when ego will stop at the end of the path + *pair_itr = std::make_pair(target_velocity, acceleration); + } else { + for (auto & point : path_itr->points) { + point.point.longitudinal_velocity_mps = -std::abs(point.point.longitudinal_velocity_mps); + } + *pair_itr = std::make_pair(-target_velocity, -acceleration); + } + path_itr->points.back().point.longitudinal_velocity_mps = 0.0; + } +} + +void updateEgoPredictedPathParams( + std::shared_ptr & ego_predicted_path_params, + const std::shared_ptr & start_planner_params) +{ + ego_predicted_path_params = + std::make_shared(start_planner_params->ego_predicted_path_params); +} +void updateEgoPredictedPathParams( + std::shared_ptr & ego_predicted_path_params, + const std::shared_ptr & goal_planner_params) +{ + ego_predicted_path_params = + std::make_shared(goal_planner_params->ego_predicted_path_params); +} + +void updateSafetyCheckParams( + std::shared_ptr & safety_check_params, + const std::shared_ptr & start_planner_params) +{ + safety_check_params = + std::make_shared(start_planner_params->safety_check_params); +} + +void updateSafetyCheckParams( + std::shared_ptr & safety_check_params, + const std::shared_ptr & goal_planner_params) +{ + safety_check_params = + std::make_shared(goal_planner_params->safety_check_params); +} + +void updateObjectsFilteringParams( + std::shared_ptr & objects_filtering_params, + const std::shared_ptr & start_planner_params) +{ + objects_filtering_params = + std::make_shared(start_planner_params->objects_filtering_params); +} + +void updateObjectsFilteringParams( + std::shared_ptr & objects_filtering_params, + const std::shared_ptr & goal_planner_params) +{ + objects_filtering_params = + std::make_shared(goal_planner_params->objects_filtering_params); +} + +void updatePathProperty( + std::shared_ptr & ego_predicted_path_params, + const std::pair & pairs_terminal_velocity_and_accel) +{ + ego_predicted_path_params->target_velocity = pairs_terminal_velocity_and_accel.first; + ego_predicted_path_params->acceleration = pairs_terminal_velocity_and_accel.second; +} + +std::pair getPairsTerminalVelocityAndAccel( + const std::vector> & pairs_terminal_velocity_and_accel, + const size_t current_path_idx) +{ + if (pairs_terminal_velocity_and_accel.size() <= current_path_idx) { + return std::make_pair(0.0, 0.0); + } + return pairs_terminal_velocity_and_accel.at(current_path_idx); +} + +} // namespace behavior_path_planner::utils::start_goal_planner_common diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index 45d36d51d69b4..29abdb08c5c55 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -69,11 +69,10 @@ boost::optional GeometricPullOut::plan(Pose start_pose, Pose goal_p // collision check with stop objects in pull out lanes const auto arc_path = planner_.getArcPath(); - const auto [pull_out_lane_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets( - *(planner_data_->dynamic_object), pull_out_lanes); - const auto pull_out_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( - pull_out_lane_objects, parameters_.th_moving_object_velocity); + const auto & stop_objects = utils::path_safety_checker::filterObjectsByVelocity( + *(planner_data_->dynamic_object), parameters_.th_moving_object_velocity); + const auto [pull_out_lane_stop_objects, others] = + utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_out_lanes); if (utils::checkCollisionBetweenPathFootprintsAndObjects( vehicle_footprint_, arc_path, pull_out_lane_stop_objects, @@ -123,6 +122,7 @@ boost::optional GeometricPullOut::plan(Pose start_pose, Pose goal_p output.pairs_terminal_velocity_and_accel.push_back( std::make_pair(velocity, velocity * velocity / 2 * arc_length_on_path)); } + output.start_pose = planner_.getArcPaths().at(0).points.front().point.pose; output.end_pose = planner_.getArcPaths().at(1).points.back().point.pose; From 3a370d4e648b1baeeb83299ea2bef581cb405d9b Mon Sep 17 00:00:00 2001 From: keisuke <31088744+keiota@users.noreply.github.com> Date: Sun, 3 Sep 2023 23:23:15 +0900 Subject: [PATCH 095/547] fix(control_validator): resolve the bug causing inappropriate diagnostic messages. (#4846) * fix(control_validator): fix reverseTrajectoryPoints to get correct trajectory array Signed-off-by: keiota * fix(control_validator): fix line breaks Signed-off-by: keiota * style(pre-commit): autofix * Update control/control_validator/src/utils.cpp --------- Signed-off-by: keiota Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kyoichi Sugahara --- control/control_validator/src/utils.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/control/control_validator/src/utils.cpp b/control/control_validator/src/utils.cpp index 2aade12d5f1cf..1b11d1e55c376 100644 --- a/control/control_validator/src/utils.cpp +++ b/control/control_validator/src/utils.cpp @@ -44,7 +44,8 @@ void insertPointInPredictedTrajectory( TrajectoryPoints reverseTrajectoryPoints(const TrajectoryPoints & trajectory_points) { - TrajectoryPoints reversed_trajectory_points(trajectory_points.size()); + TrajectoryPoints reversed_trajectory_points; + reversed_trajectory_points.reserve(trajectory_points.size()); std::reverse_copy( trajectory_points.begin(), trajectory_points.end(), std::back_inserter(reversed_trajectory_points)); @@ -106,7 +107,7 @@ Trajectory alignTrajectoryWithReferenceTrajectory( // predicted_trajectory:     p1-----p2-----p3----//------pN // trajectory: t1--------//------tN // ↓ - // predicted_trajectory:      tNew--p3----//------pN + // predicted_trajectory:      pNew--p3----//------pN // trajectory: t1--------//------tN auto predicted_trajectory_point_removed = removeFrontTrajectoryPoint( trajectory_points, modified_trajectory_points, predicted_trajectory_points); From 1e0db7ba3ffac3a395061c060a61bf9e917d7a2e Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 4 Sep 2023 09:37:19 +0900 Subject: [PATCH 096/547] chore(build): remove motion_utils.hpp and tier4_autoware_utils.hpp control/ (#4844) --- control/autonomous_emergency_braking/src/node.cpp | 4 +++- .../lane_departure_checker/lane_departure_checker.hpp | 1 - control/mpc_lateral_controller/src/mpc.cpp | 3 ++- .../mpc_lateral_controller/src/mpc_lateral_controller.cpp | 2 +- control/mpc_lateral_controller/src/mpc_utils.cpp | 2 +- control/operation_mode_transition_manager/src/state.cpp | 5 +++-- .../src/pid_longitudinal_controller.cpp | 5 +++-- .../src/simple_trajectory_follower.cpp | 4 ++-- control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp | 2 +- 9 files changed, 16 insertions(+), 12 deletions(-) diff --git a/control/autonomous_emergency_braking/src/node.cpp b/control/autonomous_emergency_braking/src/node.cpp index 7e10ca9864f11..6eff270e78326 100644 --- a/control/autonomous_emergency_braking/src/node.cpp +++ b/control/autonomous_emergency_braking/src/node.cpp @@ -15,7 +15,9 @@ #include "autonomous_emergency_braking/node.hpp" #include -#include +#include +#include +#include #include #include diff --git a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp index 9822fd820dc3c..573a38593fc59 100644 --- a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp +++ b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include diff --git a/control/mpc_lateral_controller/src/mpc.cpp b/control/mpc_lateral_controller/src/mpc.cpp index 62f96a89676ec..82ebadafa17bf 100644 --- a/control/mpc_lateral_controller/src/mpc.cpp +++ b/control/mpc_lateral_controller/src/mpc.cpp @@ -14,7 +14,8 @@ #include "mpc_lateral_controller/mpc.hpp" -#include "motion_utils/motion_utils.hpp" +#include "interpolation/linear_interpolation.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "mpc_lateral_controller/mpc_utils.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" diff --git a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp index af680b5050037..0f815a482f80d 100644 --- a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp +++ b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp @@ -14,7 +14,7 @@ #include "mpc_lateral_controller/mpc_lateral_controller.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "mpc_lateral_controller/qp_solver/qp_solver_osqp.hpp" #include "mpc_lateral_controller/qp_solver/qp_solver_unconstraint_fast.hpp" #include "mpc_lateral_controller/vehicle_model/vehicle_model_bicycle_dynamics.hpp" diff --git a/control/mpc_lateral_controller/src/mpc_utils.cpp b/control/mpc_lateral_controller/src/mpc_utils.cpp index 891a48299c81d..6d6e980675086 100644 --- a/control/mpc_lateral_controller/src/mpc_utils.cpp +++ b/control/mpc_lateral_controller/src/mpc_utils.cpp @@ -16,7 +16,7 @@ #include "interpolation/linear_interpolation.hpp" #include "interpolation/spline_interpolation.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/math/normalization.hpp" diff --git a/control/operation_mode_transition_manager/src/state.cpp b/control/operation_mode_transition_manager/src/state.cpp index f8434bbdb081f..a17e491ebe98d 100644 --- a/control/operation_mode_transition_manager/src/state.cpp +++ b/control/operation_mode_transition_manager/src/state.cpp @@ -16,8 +16,9 @@ #include "util.hpp" -#include -#include +#include +#include +#include #include #include diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index 337a63bc7dc76..40385f2853983 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -14,8 +14,9 @@ #include "pid_longitudinal_controller/pid_longitudinal_controller.hpp" -#include "motion_utils/motion_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/math/normalization.hpp" #include #include diff --git a/control/trajectory_follower_node/src/simple_trajectory_follower.cpp b/control/trajectory_follower_node/src/simple_trajectory_follower.cpp index f073574cc229f..5e9488bf2ca93 100644 --- a/control/trajectory_follower_node/src/simple_trajectory_follower.cpp +++ b/control/trajectory_follower_node/src/simple_trajectory_follower.cpp @@ -14,8 +14,8 @@ #include "trajectory_follower_node/simple_trajectory_follower.hpp" -#include -#include +#include +#include #include diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp index aeace3234d6b9..78d91e266c516 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp @@ -20,7 +20,7 @@ #include "vehicle_cmd_filter.hpp" #include -#include +#include #include #include #include From 757d9869bb922ca78c95bd87cf2820211f9d9038 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Mon, 4 Sep 2023 10:08:11 +0900 Subject: [PATCH 097/547] fix(avoidance): fix lateral distance calculation (#4820) * fix(avoidance): fix calculation logic for road shoulder distance Signed-off-by: satoshi-ota * fix(avoidance): fix overhang distance calculation Signed-off-by: satoshi-ota * fix(avoidance): don't set avoidable for invalid shift Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../behavior_path_planner/utils/avoidance/utils.hpp | 2 +- .../src/scene_module/avoidance/avoidance_module.cpp | 5 ++++- .../scene_module/lane_change/avoidance_by_lane_change.cpp | 2 +- .../behavior_path_planner/src/utils/avoidance/utils.cpp | 7 ++++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index e2934a02b63d8..0dc07f0950716 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -70,7 +70,7 @@ void fillLongitudinalAndLengthByClosestEnvelopeFootprint( const PathWithLaneId & path, const Point & ego_pos, ObjectData & obj); double calcEnvelopeOverhangDistance( - const ObjectData & object_data, const Pose & base_pose, Point & overhang_pose); + const ObjectData & object_data, const PathWithLaneId & path, Point & overhang_pose); void setEndData( AvoidLine & al, const double length, const geometry_msgs::msg::Pose & end, const size_t end_idx, diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index c47aa66fdac90..1c1b327e717eb 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -332,7 +332,7 @@ ObjectData AvoidanceModule::createObjectData( // Find the footprint point closest to the path, set to object_data.overhang_distance. object_data.overhang_dist = utils::avoidance::calcEnvelopeOverhangDistance( - object_data, object_closest_pose, object_data.overhang_pose.position); + object_data, data.reference_path, object_data.overhang_pose.position); // Check whether the the ego should avoid the object. const auto & vehicle_width = planner_data_->parameters.vehicle_width; @@ -983,6 +983,9 @@ AvoidLineArray AvoidanceModule::calcRawShiftLinesFromObjects( if (is_valid_shift_line(al_avoid) && is_valid_shift_line(al_return)) { avoid_lines.push_back(al_avoid); avoid_lines.push_back(al_return); + } else { + o.reason = "InvalidShiftLine"; + continue; } o.is_avoidable = true; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp index 841fc9092e279..a8ff0c0bbcf02 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp @@ -216,7 +216,7 @@ ObjectData AvoidanceByLaneChange::createObjectData( // Find the footprint point closest to the path, set to object_data.overhang_distance. object_data.overhang_dist = utils::avoidance::calcEnvelopeOverhangDistance( - object_data, object_closest_pose, object_data.overhang_pose.position); + object_data, data.reference_path, object_data.overhang_pose.position); // Check whether the the ego should avoid the object. const auto & vehicle_width = planner_data_->parameters.vehicle_width; diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 856c3220165e7..945344259fdb2 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -406,13 +406,14 @@ void fillLongitudinalAndLengthByClosestEnvelopeFootprint( } double calcEnvelopeOverhangDistance( - const ObjectData & object_data, const Pose & base_pose, Point & overhang_pose) + const ObjectData & object_data, const PathWithLaneId & path, Point & overhang_pose) { double largest_overhang = isOnRight(object_data) ? -100.0 : 100.0; // large number for (const auto & p : object_data.envelope_poly.outer()) { const auto point = tier4_autoware_utils::createPoint(p.x(), p.y(), 0.0); - const auto lateral = tier4_autoware_utils::calcLateralDeviation(base_pose, point); + const auto idx = findFirstNearestIndex(path.points, point); + const auto lateral = calcLateralDeviation(getPose(path.points.at(idx)), point); const auto & overhang_pose_on_right = [&overhang_pose, &largest_overhang, &point, &lateral]() { if (lateral > largest_overhang) { @@ -1014,7 +1015,7 @@ void filterTargetObjects( const auto lines = rh->getFurthestLinestring(target_lanelet, get_right, get_left, get_opposite, true); const auto & line = isOnRight(o) ? lines.back() : lines.front(); - const auto d = distance2d(to2D(overhang_basic_pose), to2D(line.basicLineString())); + const auto d = boost::geometry::distance(o.envelope_poly, to2D(line.basicLineString())); if (d < o.to_road_shoulder_distance) { o.to_road_shoulder_distance = d; target_line = line; From 245a9a405e9f5aecec514d8dfb0687b314a605f9 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Mon, 4 Sep 2023 11:46:55 +0900 Subject: [PATCH 098/547] fix(utils): improve sharp bound cleaning (#4838) Signed-off-by: satoshi-ota --- .../behavior_path_planner/src/utils/utils.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index dbfc86e5b8ba5..0e96cf6793783 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1900,18 +1900,18 @@ void makeBoundLongitudinallyMonotonic( std::vector ret; - ret.push_back(bound.front()); - - for (size_t i = 0; i < bound.size() - 2; i++) { - try { - motion_utils::validateNonSharpAngle(bound.at(i), bound.at(i + 1), bound.at(i + 2)); - ret.push_back(bound.at(i + 1)); - } catch (const std::exception & e) { - continue; + for (size_t i = 0; i < bound.size(); i++) { + const auto & p_new = bound.at(i); + const auto duplicated_points_itr = std::find_if( + ret.begin(), ret.end(), + [&p_new](const auto & p) { return calcDistance2d(p, p_new) < 0.1; }); + + if (duplicated_points_itr != ret.end() && std::next(duplicated_points_itr, 2) == ret.end()) { + ret.erase(duplicated_points_itr, ret.end()); } - } - ret.push_back(bound.back()); + ret.push_back(p_new); + } return ret; }; From 3ac89a1d8053e9c7788273c61dbfe4df7158c29f Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Mon, 4 Sep 2023 12:17:18 +0900 Subject: [PATCH 099/547] refactor(behavior_path_planner): add vehicle_info_ as member variable in goal_planner (#4859) add vehicle_info_ Signed-off-by: kyoichi-sugahara --- .../scene_module/goal_planner/goal_planner_module.hpp | 2 ++ .../src/scene_module/goal_planner/goal_planner_module.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 1ddf288e06ae3..d7f824ba8f419 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -139,6 +139,8 @@ class GoalPlannerModule : public SceneModuleInterface std::shared_ptr parameters_; + vehicle_info_util::VehicleInfo vehicle_info_; + // planner std::vector> pull_over_planners_; std::unique_ptr freespace_planner_; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 1d491a7fb7b87..c7da1a75f3df2 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -53,10 +53,12 @@ GoalPlannerModule::GoalPlannerModule( const std::string & name, rclcpp::Node & node, const std::shared_ptr & parameters, const std::unordered_map > & rtc_interface_ptr_map) -: SceneModuleInterface{name, node, rtc_interface_ptr_map}, parameters_{parameters} +: SceneModuleInterface{name, node, rtc_interface_ptr_map}, + parameters_{parameters}, + vehicle_info_{vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo()} { LaneDepartureChecker lane_departure_checker{}; - lane_departure_checker.setVehicleInfo(vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo()); + lane_departure_checker.setVehicleInfo(vehicle_info_); occupancy_grid_map_ = std::make_shared(); From 9f00bae319146aa93e5247667c50f4c7a8ab1cb0 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 4 Sep 2023 12:50:33 +0900 Subject: [PATCH 100/547] feat(behavior_path_planner): add always executable module option (#4785) * feat(behavior_path_planner): add always executable module option Signed-off-by: kosuke55 * fix conditions Signed-off-by: kosuke55 * rename itr Signed-off-by: kosuke55 * Update planning/behavior_path_planner/src/planner_manager.cpp Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> * Update planning/behavior_path_planner/src/planner_manager.cpp Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> * Update planning/behavior_path_planner/src/planner_manager.cpp Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> * fix candidate break Signed-off-by: kosuke55 * Revert "fix candidate break" This reverts commit 0071bbdd8aa4d510a67f6d9bc32e23716f7aa416. * remove continue Signed-off-by: kosuke55 * check manager_ptr->isAlwaysExecutableModule first Signed-off-by: kosuke55 * common similar process Signed-off-by: kosuke55 * fix getRequestModules Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 Co-authored-by: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> --- .../scene_module/goal_planner/manager.hpp | 2 + .../scene_module_manager_interface.hpp | 19 +++ .../src/planner_manager.cpp | 132 ++++++++++++++---- .../src/scene_module/goal_planner/manager.cpp | 20 +++ 4 files changed, 142 insertions(+), 31 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp index c410999b2aaa9..47ad47aecb9b3 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp @@ -40,6 +40,8 @@ class GoalPlannerModuleManager : public SceneModuleManagerInterface void updateModuleParams(const std::vector & parameters) override; + bool isAlwaysExecutableModule() const override; + bool isSimultaneousExecutableAsApprovedModule() const override; bool isSimultaneousExecutableAsCandidateModule() const override; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp index 87b21bf7c2924..38101416a80b3 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_manager_interface.hpp @@ -215,13 +215,32 @@ class SceneModuleManagerInterface bool canLaunchNewModule() const { return observers_.size() < max_module_num_; } + /** + * Determine if the module is always executable, regardless of the state of other modules. + * + * When this returns true: + * - The module can be executed even if other modules are not marked as 'simultaneously + * executable'. + * - Conversely, the presence of this module will not prevent other modules + * from executing, even if they are not marked as 'simultaneously executable'. + */ + virtual bool isAlwaysExecutableModule() const { return false; } + virtual bool isSimultaneousExecutableAsApprovedModule() const { + if (isAlwaysExecutableModule()) { + return true; + } + return enable_simultaneous_execution_as_approved_module_; } virtual bool isSimultaneousExecutableAsCandidateModule() const { + if (isAlwaysExecutableModule()) { + return true; + } + return enable_simultaneous_execution_as_candidate_module_; } diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index e6e2bf9ac5346..af7347a2db4d0 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -194,22 +194,6 @@ std::vector PlannerManager::getRequestModules( std::vector request_modules{}; - /** - * check whether it is possible to push back more modules to approved modules. - */ - { - const auto find_block_module = [this](const auto & m) { - return !getManager(m)->isSimultaneousExecutableAsApprovedModule(); - }; - - const auto itr = - std::find_if(approved_module_ptrs_.begin(), approved_module_ptrs_.end(), find_block_module); - - if (itr != approved_module_ptrs_.end()) { - return {}; - } - } - const auto toc = [this](const auto & name) { processing_time_.at(name) += stop_watch_.toc(name, true); }; @@ -218,14 +202,62 @@ std::vector PlannerManager::getRequestModules( stop_watch_.tic(manager_ptr->name()); /** - * don't launch candidate module if approved modules already exist. + * determine the execution capability of modules based on existing approved modules. */ - if (!approved_module_ptrs_.empty()) { - if (!manager_ptr->isSimultaneousExecutableAsApprovedModule()) { - toc(manager_ptr->name()); + // Condition 1: always executable module can be added regardless of the existence of other + // modules, so skip checking the existence of other modules. + // in other cases, need to check the existence of other modules and which module can be added. + const bool has_non_always_executable_module = std::any_of( + approved_module_ptrs_.begin(), approved_module_ptrs_.end(), + [this](const auto & m) { return !getManager(m)->isAlwaysExecutableModule(); }); + if (!manager_ptr->isAlwaysExecutableModule() && has_non_always_executable_module) { + // pairs of find_block_module and is_executable + std::vector, std::function>> + conditions; + + // Condition 2: do not add modules that are neither always nor simultaneous executable + // if there exists at least one approved module that is simultaneous but not always + // executable. (only modules that are either always executable or simultaneous executable can + // be added) + conditions.push_back( + {[&](const SceneModulePtr & m) { + return !getManager(m)->isAlwaysExecutableModule() && + getManager(m)->isSimultaneousExecutableAsApprovedModule(); + }, + [&]() { return manager_ptr->isSimultaneousExecutableAsApprovedModule(); }}); + + // Condition 3: do not add modules that are not always executable if there exists + // at least one approved module that is neither always nor simultaneous executable. + // (only modules that are always executable can be added) + conditions.push_back( + {[&](const SceneModulePtr & m) { + return !getManager(m)->isAlwaysExecutableModule() && + !getManager(m)->isSimultaneousExecutableAsApprovedModule(); + }, + [&]() { return false; }}); + + bool skip_module = false; + for (const auto & condition : conditions) { + const auto & find_block_module = condition.first; + const auto & is_executable = condition.second; + + const auto itr = std::find_if( + approved_module_ptrs_.begin(), approved_module_ptrs_.end(), find_block_module); + + if (itr != approved_module_ptrs_.end() && !is_executable()) { + toc(manager_ptr->name()); + skip_module = true; + continue; + } + } + if (skip_module) { continue; } } + // else{ + // Condition 4: if none of the above conditions are met, any module can be added. + // (when the approved modules are either empty or consist only of always executable modules.) + // } /** * launch new candidate module. @@ -347,20 +379,58 @@ std::pair PlannerManager::runRequestModule * remove non-executable modules. */ for (const auto & module_ptr : sorted_request_modules) { - if (!getManager(module_ptr)->isSimultaneousExecutableAsCandidateModule()) { - if (executable_modules.empty()) { - executable_modules.push_back(module_ptr); - break; - } + // Condition 1: always executable module can be added regardless of the existence of other + // modules. + if (getManager(module_ptr)->isAlwaysExecutableModule()) { + executable_modules.push_back(module_ptr); + continue; } - const auto itr = - std::find_if(executable_modules.begin(), executable_modules.end(), [this](const auto & m) { - return !getManager(m)->isSimultaneousExecutableAsCandidateModule(); - }); - - if (itr == executable_modules.end()) { + // Condition 4: If the executable modules are either empty or consist only of always executable + // modules, any module can be added. + const bool has_non_always_executable_module = std::any_of( + executable_modules.begin(), executable_modules.end(), + [this](const auto & m) { return !getManager(m)->isAlwaysExecutableModule(); }); + if (!has_non_always_executable_module) { executable_modules.push_back(module_ptr); + continue; + } + + // pairs of find_block_module and is_executable + std::vector, std::function>> + conditions; + + // Condition 3: Only modules that are always executable can be added + // if there exists at least one executable module that is neither always nor simultaneous + // executable. + conditions.push_back( + {[this](const SceneModulePtr & m) { + return !getManager(m)->isAlwaysExecutableModule() && + !getManager(m)->isSimultaneousExecutableAsCandidateModule(); + }, + [&]() { return false; }}); + + // Condition 2: Only modules that are either always executable or simultaneous executable can be + // added if there exists at least one executable module that is simultaneous but not always + // executable. + conditions.push_back( + {[this](const SceneModulePtr & m) { + return !getManager(m)->isAlwaysExecutableModule() && + getManager(m)->isSimultaneousExecutableAsCandidateModule(); + }, + [&]() { return getManager(module_ptr)->isSimultaneousExecutableAsCandidateModule(); }}); + + for (const auto & condition : conditions) { + const auto & find_block_module = condition.first; + const auto & is_executable = condition.second; + + const auto itr = + std::find_if(executable_modules.begin(), executable_modules.end(), find_block_module); + + if (itr != executable_modules.end() && is_executable()) { + executable_modules.push_back(module_ptr); + break; + } } } diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index 7df1749a0fe82..bcee6aeaf0c36 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -259,8 +259,24 @@ void GoalPlannerModuleManager::updateModuleParams( }); } +bool GoalPlannerModuleManager::isAlwaysExecutableModule() const +{ + // enable AlwaysExecutable whenever goal modification is not allowed + // because only minor path refinements are made for fixed goals + if (!goal_planner_utils::isAllowedGoalModification( + planner_data_->route_handler, left_side_parking_)) { + return true; + } + + return false; +} + bool GoalPlannerModuleManager::isSimultaneousExecutableAsApprovedModule() const { + if (isAlwaysExecutableModule()) { + return true; + } + // enable SimultaneousExecutable whenever goal modification is not allowed // because only minor path refinements are made for fixed goals if (!goal_planner_utils::isAllowedGoalModification( @@ -273,6 +289,10 @@ bool GoalPlannerModuleManager::isSimultaneousExecutableAsApprovedModule() const bool GoalPlannerModuleManager::isSimultaneousExecutableAsCandidateModule() const { + if (isAlwaysExecutableModule()) { + return true; + } + // enable SimultaneousExecutable whenever goal modification is not allowed // because only minor path refinements are made for fixed goals if (!goal_planner_utils::isAllowedGoalModification( From b255111b7b11df532a11817134c33ca36c92b498 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:46:06 +0900 Subject: [PATCH 101/547] feat(geography_utils): add lanelet2_projector (#4852) * feat(geography_utils): add lanelet2_projector Signed-off-by: kminoda * style(pre-commit): autofix * update package.xml Signed-off-by: kminoda * style(pre-commit): autofix * edit static_centerline_optimizer Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- common/geography_utils/CMakeLists.txt | 1 + .../geography_utils/lanelet2_projector.hpp | 32 ++++++++++++++ common/geography_utils/package.xml | 3 ++ .../src/lanelet2_projector.cpp | 43 +++++++++++++++++++ .../map_loader/lanelet2_map_loader_node.hpp | 4 +- map/map_loader/package.xml | 1 + .../lanelet2_map_loader_node.cpp | 29 ++++--------- .../static_centerline_optimizer/package.xml | 1 + .../src/static_centerline_optimizer_node.cpp | 6 ++- 9 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 common/geography_utils/include/geography_utils/lanelet2_projector.hpp create mode 100644 common/geography_utils/src/lanelet2_projector.cpp diff --git a/common/geography_utils/CMakeLists.txt b/common/geography_utils/CMakeLists.txt index 5cc2290636157..feb49c42306fe 100644 --- a/common/geography_utils/CMakeLists.txt +++ b/common/geography_utils/CMakeLists.txt @@ -14,6 +14,7 @@ find_library(GeographicLib_LIBRARIES NAMES Geographic) ament_auto_add_library(geography_utils SHARED src/height.cpp + src/lanelet2_projector.cpp ) target_link_libraries(geography_utils diff --git a/common/geography_utils/include/geography_utils/lanelet2_projector.hpp b/common/geography_utils/include/geography_utils/lanelet2_projector.hpp new file mode 100644 index 0000000000000..739abe7019023 --- /dev/null +++ b/common/geography_utils/include/geography_utils/lanelet2_projector.hpp @@ -0,0 +1,32 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GEOGRAPHY_UTILS__LANELET2_PROJECTOR_HPP_ +#define GEOGRAPHY_UTILS__LANELET2_PROJECTOR_HPP_ + +#include + +#include + +#include + +namespace geography_utils +{ +using MapProjectorInfo = tier4_map_msgs::msg::MapProjectorInfo; + +std::unique_ptr get_lanelet2_projector(const MapProjectorInfo & projector_info); + +} // namespace geography_utils + +#endif // GEOGRAPHY_UTILS__LANELET2_PROJECTOR_HPP_ diff --git a/common/geography_utils/package.xml b/common/geography_utils/package.xml index 61ac473498632..b9b31b0086646 100644 --- a/common/geography_utils/package.xml +++ b/common/geography_utils/package.xml @@ -11,6 +11,9 @@ autoware_cmake geographiclib + lanelet2_extension + lanelet2_io + tier4_map_msgs ament_cmake_ros ament_lint_auto diff --git a/common/geography_utils/src/lanelet2_projector.cpp b/common/geography_utils/src/lanelet2_projector.cpp new file mode 100644 index 0000000000000..d487a5273ce79 --- /dev/null +++ b/common/geography_utils/src/lanelet2_projector.cpp @@ -0,0 +1,43 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +namespace geography_utils +{ + +std::unique_ptr get_lanelet2_projector(const MapProjectorInfo & projector_info) +{ + if (projector_info.projector_type == MapProjectorInfo::LOCAL_CARTESIAN_UTM) { + lanelet::GPSPoint position{ + projector_info.map_origin.latitude, projector_info.map_origin.longitude, + projector_info.map_origin.altitude}; + lanelet::Origin origin{position}; + lanelet::projection::UtmProjector projector{origin}; + return std::make_unique(projector); + } else if (projector_info.projector_type == MapProjectorInfo::MGRS) { + lanelet::projection::MGRSProjector projector{}; + return std::make_unique(projector); + } else { + const std::string error_msg = "Invalid map projector type: " + projector_info.projector_type + + ". Currently supported types: MGRS, and LocalCartesianUTM"; + throw std::invalid_argument(error_msg); + } +} + +} // namespace geography_utils diff --git a/map/map_loader/include/map_loader/lanelet2_map_loader_node.hpp b/map/map_loader/include/map_loader/lanelet2_map_loader_node.hpp index d2e9f66ad047a..0adc7612e9f61 100644 --- a/map/map_loader/include/map_loader/lanelet2_map_loader_node.hpp +++ b/map/map_loader/include/map_loader/lanelet2_map_loader_node.hpp @@ -36,8 +36,8 @@ class Lanelet2MapLoaderNode : public rclcpp::Node explicit Lanelet2MapLoaderNode(const rclcpp::NodeOptions & options); static lanelet::LaneletMapPtr load_map( - const std::string & lanelet2_filename, const std::string & lanelet2_map_projector_type, - const double & map_origin_lat = 0.0, const double & map_origin_lon = 0.0); + const std::string & lanelet2_filename, + const tier4_map_msgs::msg::MapProjectorInfo & projector_info); static HADMapBin create_map_bin_msg( const lanelet::LaneletMapPtr map, const std::string & lanelet2_filename, const rclcpp::Time & now); diff --git a/map/map_loader/package.xml b/map/map_loader/package.xml index fbc2572d0bc6d..e663d19d516ac 100644 --- a/map/map_loader/package.xml +++ b/map/map_loader/package.xml @@ -19,6 +19,7 @@ component_interface_specs component_interface_utils fmt + geography_utils geometry_msgs lanelet2_extension libpcl-all-dev diff --git a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp index 328808e04d9e0..259c168edcc5c 100644 --- a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp +++ b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_loader_node.cpp @@ -34,6 +34,7 @@ #include "map_loader/lanelet2_map_loader_node.hpp" #include +#include #include #include #include @@ -69,8 +70,7 @@ void Lanelet2MapLoaderNode::on_map_projector_info( const auto center_line_resolution = get_parameter("center_line_resolution").as_double(); // load map from file - const auto map = load_map( - lanelet2_filename, msg->projector_type, msg->map_origin.latitude, msg->map_origin.longitude); + const auto map = load_map(lanelet2_filename, *msg); if (!map) { return; } @@ -88,26 +88,18 @@ void Lanelet2MapLoaderNode::on_map_projector_info( } lanelet::LaneletMapPtr Lanelet2MapLoaderNode::load_map( - const std::string & lanelet2_filename, const std::string & lanelet2_map_projector_type, - const double & map_origin_lat, const double & map_origin_lon) + const std::string & lanelet2_filename, + const tier4_map_msgs::msg::MapProjectorInfo & projector_info) { lanelet::ErrorMessages errors{}; - if (lanelet2_map_projector_type == tier4_map_msgs::msg::MapProjectorInfo::MGRS) { - lanelet::projection::MGRSProjector projector{}; - const lanelet::LaneletMapPtr map = lanelet::load(lanelet2_filename, projector, &errors); - if (errors.empty()) { - return map; - } - } else if ( - lanelet2_map_projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL_CARTESIAN_UTM) { - lanelet::GPSPoint position{map_origin_lat, map_origin_lon}; - lanelet::Origin origin{position}; - lanelet::projection::UtmProjector projector{origin}; - const lanelet::LaneletMapPtr map = lanelet::load(lanelet2_filename, projector, &errors); + if (projector_info.projector_type != tier4_map_msgs::msg::MapProjectorInfo::LOCAL) { + std::unique_ptr projector = + geography_utils::get_lanelet2_projector(projector_info); + const lanelet::LaneletMapPtr map = lanelet::load(lanelet2_filename, *projector, &errors); if (errors.empty()) { return map; } - } else if (lanelet2_map_projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL) { + } else { // Use MGRSProjector as parser lanelet::projection::MGRSProjector projector{}; const lanelet::LaneletMapPtr map = lanelet::load(lanelet2_filename, projector, &errors); @@ -132,9 +124,6 @@ lanelet::LaneletMapPtr Lanelet2MapLoaderNode::load_map( } return map; - } else { - RCLCPP_ERROR(rclcpp::get_logger("map_loader"), "lanelet2_map_projector_type is not supported"); - return nullptr; } for (const auto & error : errors) { diff --git a/planning/static_centerline_optimizer/package.xml b/planning/static_centerline_optimizer/package.xml index c30a9f2807df3..89976be5bdd47 100644 --- a/planning/static_centerline_optimizer/package.xml +++ b/planning/static_centerline_optimizer/package.xml @@ -32,6 +32,7 @@ rclcpp_components route_handler tier4_autoware_utils + tier4_map_msgs vehicle_info_util python3-flask-cors diff --git a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp index 9c30c57533335..b2e136fd3916b 100644 --- a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp +++ b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp @@ -27,6 +27,8 @@ #include #include +#include + #include #include #include @@ -242,7 +244,9 @@ void StaticCenterlineOptimizerNode::load_map(const std::string & lanelet2_input_ map_bin_ptr_ = [&]() -> HADMapBin::ConstSharedPtr { // load map lanelet::LaneletMapPtr map_ptr; - map_ptr = Lanelet2MapLoaderNode::load_map(lanelet2_input_file_path, "MGRS"); + tier4_map_msgs::msg::MapProjectorInfo map_projector_info; + map_projector_info.projector_type = tier4_map_msgs::msg::MapProjectorInfo::MGRS; + map_ptr = Lanelet2MapLoaderNode::load_map(lanelet2_input_file_path, map_projector_info); if (!map_ptr) { return nullptr; } From be431075054908beaa0ce94a1fdc175865d5e8ba Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:54:05 +0900 Subject: [PATCH 102/547] chore(imu_corrector): remove library compilation (#4849) * chore(imu_corrector): remove composable node build Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sensing/imu_corrector/CMakeLists.txt | 20 +++++--------- .../imu_corrector/src/gyro_bias_estimator.cpp | 7 ++--- .../imu_corrector/src/gyro_bias_estimator.hpp | 2 +- .../src/gyro_bias_estimator_node.cpp | 26 +++++++++++++++++++ .../imu_corrector/src/imu_corrector_core.cpp | 8 ++---- .../imu_corrector/src/imu_corrector_core.hpp | 2 +- .../imu_corrector/src/imu_corrector_node.cpp | 26 +++++++++++++++++++ 7 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 sensing/imu_corrector/src/gyro_bias_estimator_node.cpp create mode 100644 sensing/imu_corrector/src/imu_corrector_node.cpp diff --git a/sensing/imu_corrector/CMakeLists.txt b/sensing/imu_corrector/CMakeLists.txt index 8fda61f6f32ad..ded596a8a9898 100644 --- a/sensing/imu_corrector/CMakeLists.txt +++ b/sensing/imu_corrector/CMakeLists.txt @@ -8,31 +8,23 @@ ament_auto_add_library(gyro_bias_estimation_module SHARED src/gyro_bias_estimation_module.cpp ) -ament_auto_add_library(imu_corrector_node SHARED +ament_auto_add_executable(imu_corrector src/imu_corrector_core.cpp + src/imu_corrector_node.cpp ) -ament_auto_add_library(gyro_bias_estimator_node SHARED +ament_auto_add_executable(gyro_bias_estimator src/gyro_bias_estimator.cpp + src/gyro_bias_estimator_node.cpp ) -target_link_libraries(gyro_bias_estimator_node gyro_bias_estimation_module) - -rclcpp_components_register_node(imu_corrector_node - PLUGIN "imu_corrector::ImuCorrector" - EXECUTABLE imu_corrector -) - -rclcpp_components_register_node(gyro_bias_estimator_node - PLUGIN "imu_corrector::GyroBiasEstimator" - EXECUTABLE gyro_bias_estimator -) +target_link_libraries(gyro_bias_estimator gyro_bias_estimation_module) function(add_testcase filepath) get_filename_component(filename ${filepath} NAME) string(REGEX REPLACE ".cpp" "" test_name ${filename}) ament_add_gmock(${test_name} ${filepath}) - target_link_libraries("${test_name}" gyro_bias_estimation_module imu_corrector_node gyro_bias_estimator_node) + target_link_libraries("${test_name}" gyro_bias_estimation_module) ament_target_dependencies(${test_name} ${${PROJECT_NAME}_FOUND_BUILD_DEPENDS}) endfunction() diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.cpp b/sensing/imu_corrector/src/gyro_bias_estimator.cpp index 42795805f803e..581eccb0097d4 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimator.cpp +++ b/sensing/imu_corrector/src/gyro_bias_estimator.cpp @@ -16,8 +16,8 @@ namespace imu_corrector { -GyroBiasEstimator::GyroBiasEstimator(const rclcpp::NodeOptions & node_options) -: Node("gyro_bias_validator", node_options), +GyroBiasEstimator::GyroBiasEstimator() +: Node("gyro_bias_validator"), gyro_bias_threshold_(declare_parameter("gyro_bias_threshold")), angular_velocity_offset_x_(declare_parameter("angular_velocity_offset_x")), angular_velocity_offset_y_(declare_parameter("angular_velocity_offset_y")), @@ -104,6 +104,3 @@ void GyroBiasEstimator::update_diagnostics(diagnostic_updater::DiagnosticStatusW } } // namespace imu_corrector - -#include -RCLCPP_COMPONENTS_REGISTER_NODE(imu_corrector::GyroBiasEstimator) diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.hpp b/sensing/imu_corrector/src/gyro_bias_estimator.hpp index 37ca1d81dc8fa..e74a0390af3aa 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimator.hpp +++ b/sensing/imu_corrector/src/gyro_bias_estimator.hpp @@ -38,7 +38,7 @@ class GyroBiasEstimator : public rclcpp::Node using Vector3 = geometry_msgs::msg::Vector3; public: - explicit GyroBiasEstimator(const rclcpp::NodeOptions & node_options); + GyroBiasEstimator(); private: void update_diagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat); diff --git a/sensing/imu_corrector/src/gyro_bias_estimator_node.cpp b/sensing/imu_corrector/src/gyro_bias_estimator_node.cpp new file mode 100644 index 0000000000000..a491957bbb57f --- /dev/null +++ b/sensing/imu_corrector/src/gyro_bias_estimator_node.cpp @@ -0,0 +1,26 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gyro_bias_estimator.hpp" + +#include + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + auto node = std::make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; +} diff --git a/sensing/imu_corrector/src/imu_corrector_core.cpp b/sensing/imu_corrector/src/imu_corrector_core.cpp index c31f4968f557b..56e105aeb0ef0 100644 --- a/sensing/imu_corrector/src/imu_corrector_core.cpp +++ b/sensing/imu_corrector/src/imu_corrector_core.cpp @@ -46,9 +46,8 @@ geometry_msgs::msg::Vector3 transformVector3( namespace imu_corrector { -ImuCorrector::ImuCorrector(const rclcpp::NodeOptions & node_options) -: Node("imu_corrector", node_options), - output_frame_(declare_parameter("base_link", "base_link")) +ImuCorrector::ImuCorrector() +: Node("imu_corrector"), output_frame_(declare_parameter("base_link", "base_link")) { transform_listener_ = std::make_shared(this); @@ -117,6 +116,3 @@ void ImuCorrector::callbackImu(const sensor_msgs::msg::Imu::ConstSharedPtr imu_m } } // namespace imu_corrector - -#include -RCLCPP_COMPONENTS_REGISTER_NODE(imu_corrector::ImuCorrector) diff --git a/sensing/imu_corrector/src/imu_corrector_core.hpp b/sensing/imu_corrector/src/imu_corrector_core.hpp index c155288f3cb17..3717329d81f71 100644 --- a/sensing/imu_corrector/src/imu_corrector_core.hpp +++ b/sensing/imu_corrector/src/imu_corrector_core.hpp @@ -34,7 +34,7 @@ class ImuCorrector : public rclcpp::Node using COV_IDX = tier4_autoware_utils::xyz_covariance_index::XYZ_COV_IDX; public: - explicit ImuCorrector(const rclcpp::NodeOptions & node_options); + ImuCorrector(); private: void callbackImu(const sensor_msgs::msg::Imu::ConstSharedPtr imu_msg_ptr); diff --git a/sensing/imu_corrector/src/imu_corrector_node.cpp b/sensing/imu_corrector/src/imu_corrector_node.cpp new file mode 100644 index 0000000000000..c1df5bee28439 --- /dev/null +++ b/sensing/imu_corrector/src/imu_corrector_node.cpp @@ -0,0 +1,26 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "imu_corrector_core.hpp" + +#include + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + auto node = std::make_shared(); + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; +} From 1d55094415b1a5e70fa11d06684b08fbf94f42b4 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 4 Sep 2023 13:58:34 +0900 Subject: [PATCH 103/547] chore(build): remove tier4_autoware_utils.hpp sensing/ (#4841) --- sensing/imu_corrector/src/imu_corrector_core.cpp | 2 ++ sensing/imu_corrector/src/imu_corrector_core.hpp | 2 +- .../radar_static_pointcloud_filter_node.hpp | 2 +- .../radar_static_pointcloud_filter_node.cpp | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sensing/imu_corrector/src/imu_corrector_core.cpp b/sensing/imu_corrector/src/imu_corrector_core.cpp index 56e105aeb0ef0..e8a7040ce301f 100644 --- a/sensing/imu_corrector/src/imu_corrector_core.cpp +++ b/sensing/imu_corrector/src/imu_corrector_core.cpp @@ -14,6 +14,8 @@ #include "imu_corrector_core.hpp" +#include + #include std::array transformCovariance(const std::array & cov) diff --git a/sensing/imu_corrector/src/imu_corrector_core.hpp b/sensing/imu_corrector/src/imu_corrector_core.hpp index 3717329d81f71..7709c611ab714 100644 --- a/sensing/imu_corrector/src/imu_corrector_core.hpp +++ b/sensing/imu_corrector/src/imu_corrector_core.hpp @@ -14,8 +14,8 @@ #ifndef IMU_CORRECTOR_CORE_HPP_ #define IMU_CORRECTOR_CORE_HPP_ +#include "tier4_autoware_utils/ros/msg_covariance.hpp" #include "tier4_autoware_utils/ros/transform_listener.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include diff --git a/sensing/radar_static_pointcloud_filter/include/radar_static_pointcloud_filter/radar_static_pointcloud_filter_node.hpp b/sensing/radar_static_pointcloud_filter/include/radar_static_pointcloud_filter/radar_static_pointcloud_filter_node.hpp index e97e8fa5ca500..f1ab9344d58f3 100644 --- a/sensing/radar_static_pointcloud_filter/include/radar_static_pointcloud_filter/radar_static_pointcloud_filter_node.hpp +++ b/sensing/radar_static_pointcloud_filter/include/radar_static_pointcloud_filter/radar_static_pointcloud_filter_node.hpp @@ -15,7 +15,7 @@ #ifndef RADAR_STATIC_POINTCLOUD_FILTER__RADAR_STATIC_POINTCLOUD_FILTER_NODE_HPP_ #define RADAR_STATIC_POINTCLOUD_FILTER__RADAR_STATIC_POINTCLOUD_FILTER_NODE_HPP_ -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/transform_listener.hpp" #include diff --git a/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp b/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp index 812a96e7cdcd5..c3550b0338675 100644 --- a/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp +++ b/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include From 9c2fc0dcdf7777fe2496ce4155a412485ed608e9 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:09:35 +0900 Subject: [PATCH 104/547] feat(avoidance): check incomming vehicle (#4864) feat(avoidance): check incomming behicle Signed-off-by: satoshi-ota --- .../marker_utils/avoidance/debug.hpp | 2 - .../utils/avoidance/avoidance_module_data.hpp | 7 +- .../src/marker_utils/avoidance/debug.cpp | 6 -- .../avoidance/avoidance_module.cpp | 74 ++++++++++++------- 4 files changed, 51 insertions(+), 38 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp index ac24591a0a283..d308f55799ce0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp @@ -59,8 +59,6 @@ MarkerArray createTargetObjectsMarkerArray(const ObjectDataArray & objects, cons MarkerArray createOtherObjectsMarkerArray(const ObjectDataArray & objects, const std::string & ns); -MarkerArray createUnsafeObjectsMarkerArray(const ObjectDataArray & objects, std::string && ns); - MarkerArray makeOverhangToRoadShoulderMarkerArray( const behavior_path_planner::ObjectDataArray & objects, std::string && ns); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 0278b73b0acb4..3264ec1e9ddfd 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -556,9 +556,6 @@ struct DebugData // shift path std::vector proposed_spline_shift; - // future pose - PathWithLaneId path_with_planned_velocity; - // avoidance require objects ObjectDataArray unavoidable_objects; @@ -568,6 +565,10 @@ struct DebugData // tmp for plot PathWithLaneId center_line; + // collision check debug map + utils::path_safety_checker::CollisionCheckDebugMap collision_check; + + // debug msg array AvoidanceDebugMsgArray avoidance_debug_msg_array; }; diff --git a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp index 94073ad467326..bea1447b0d95a 100644 --- a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp @@ -416,12 +416,6 @@ MarkerArray createOtherObjectsMarkerArray(const ObjectDataArray & objects, const return msg; } -MarkerArray createUnsafeObjectsMarkerArray(const ObjectDataArray & objects, std::string && ns) -{ - return createObjectsCubeMarkerArray( - objects, ns + "_cube", createMarkerScale(3.2, 1.7, 2.0), createMarkerColor(0.0, 0.0, 1.0, 0.8)); -} - MarkerArray makeOverhangToRoadShoulderMarkerArray( const ObjectDataArray & objects, std::string && ns) { diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 1c1b327e717eb..78c063421f29a 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1856,25 +1856,35 @@ bool AvoidanceModule::isSafePath( avoid_data_, planner_data_, parameters_, is_right_shift.value()); for (const auto & object : safety_check_target_objects) { + auto current_debug_data = marker_utils::createObjectDebug(object); + const auto obj_polygon = tier4_autoware_utils::toPolygon2d(object.initial_pose.pose, object.shape); const auto is_object_front = utils::path_safety_checker::isTargetObjectFront(getEgoPose(), obj_polygon, p.vehicle_info); + const auto is_object_incoming = + std::abs(calcYawDeviation(getEgoPose(), object.initial_pose.pose)) > M_PI_2; + const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( object, parameters_->check_all_predicted_path); - const auto & ego_predicted_path = - is_object_front ? ego_predicted_path_for_front_object : ego_predicted_path_for_rear_object; + const auto & ego_predicted_path = is_object_front && !is_object_incoming + ? ego_predicted_path_for_front_object + : ego_predicted_path_for_rear_object; for (const auto & obj_path : obj_predicted_paths) { - CollisionCheckDebug collision{}; if (!utils::path_safety_checker::checkCollision( shifted_path.path, ego_predicted_path, object, obj_path, p, parameters_->rss_params, - hysteresis_factor, collision)) { + hysteresis_factor, current_debug_data.second)) { + marker_utils::updateCollisionCheckDebugMap( + debug.collision_check, current_debug_data, false); + safe_count_ = 0; return false; + } else { + marker_utils::updateCollisionCheckDebugMap(debug.collision_check, current_debug_data, true); } } } @@ -2686,12 +2696,14 @@ void AvoidanceModule::updateDebugMarker( using marker_utils::createShiftGradMarkerArray; using marker_utils::createShiftLengthMarkerArray; using marker_utils::createShiftLineMarkerArray; + using marker_utils::showPolygon; + using marker_utils::showPredictedPath; + using marker_utils::showSafetyCheckInfo; using marker_utils::avoidance_marker::createAvoidLineMarkerArray; using marker_utils::avoidance_marker::createEgoStatusMarkerArray; using marker_utils::avoidance_marker::createOtherObjectsMarkerArray; using marker_utils::avoidance_marker::createOverhangFurthestLineStringMarkerArray; using marker_utils::avoidance_marker::createPredictedVehiclePositions; - using marker_utils::avoidance_marker::createUnsafeObjectsMarkerArray; using marker_utils::avoidance_marker::makeOverhangToRoadShoulderMarkerArray; using tier4_autoware_utils::appendMarkerArray; @@ -2715,9 +2727,11 @@ void AvoidanceModule::updateDebugMarker( add(createShiftLineMarkerArray(sl_arr, shifter.getBaseOffset(), ns, r, g, b, w)); }; + const auto addObjects = [&](const ObjectDataArray & objects, const auto & ns) { + add(createOtherObjectsMarkerArray(objects, ns)); + }; + add(createEgoStatusMarkerArray(data, getEgoPose(), "ego_status")); - add(createPredictedVehiclePositions( - debug.path_with_planned_velocity, "predicted_vehicle_positions")); const auto & path = data.reference_path; add(createPathMarkerArray(debug.center_line, "centerline", 0, 0.0, 0.5, 0.9)); @@ -2729,31 +2743,37 @@ void AvoidanceModule::updateDebugMarker( add(createLaneletsAreaMarkerArray(*debug.current_lanelets, "current_lanelet", 0.0, 1.0, 0.0)); add(createPolygonMarkerArray(debug.detection_area, "detection_area", 0L, 0.16, 1.0, 0.69, 0.1)); - add(createOtherObjectsMarkerArray( - data.other_objects, AvoidanceDebugFactor::OBJECT_IS_BEHIND_THRESHOLD)); - add(createOtherObjectsMarkerArray( - data.other_objects, AvoidanceDebugFactor::OBJECT_IS_IN_FRONT_THRESHOLD)); - add(createOtherObjectsMarkerArray( - data.other_objects, AvoidanceDebugFactor::OBJECT_BEHIND_PATH_GOAL)); - add(createOtherObjectsMarkerArray( - data.other_objects, AvoidanceDebugFactor::TOO_NEAR_TO_CENTERLINE)); - add(createOtherObjectsMarkerArray(data.other_objects, AvoidanceDebugFactor::OBJECT_IS_NOT_TYPE)); - add(createOtherObjectsMarkerArray(data.other_objects, AvoidanceDebugFactor::NOT_PARKING_OBJECT)); - add(createOtherObjectsMarkerArray(data.other_objects, std::string("MovingObject"))); - add(createOtherObjectsMarkerArray(data.other_objects, std::string("CrosswalkUser"))); - add(createOtherObjectsMarkerArray(data.other_objects, std::string("OutOfTargetArea"))); - add(createOtherObjectsMarkerArray(data.other_objects, std::string("NotNeedAvoidance"))); - add(createOtherObjectsMarkerArray(data.other_objects, std::string("LessThanExecutionThreshold"))); - add(makeOverhangToRoadShoulderMarkerArray(data.target_objects, "overhang")); add(createOverhangFurthestLineStringMarkerArray(debug.bounds, "bounds", 1.0, 0.0, 1.0)); - add(createUnsafeObjectsMarkerArray(debug.unsafe_objects, "unsafe_objects")); + // ignore objects + { + addObjects(data.other_objects, AvoidanceDebugFactor::OBJECT_IS_BEHIND_THRESHOLD); + addObjects(data.other_objects, AvoidanceDebugFactor::OBJECT_IS_IN_FRONT_THRESHOLD); + addObjects(data.other_objects, AvoidanceDebugFactor::OBJECT_IS_NOT_TYPE); + addObjects(data.other_objects, AvoidanceDebugFactor::OBJECT_BEHIND_PATH_GOAL); + addObjects(data.other_objects, AvoidanceDebugFactor::TOO_NEAR_TO_CENTERLINE); + addObjects(data.other_objects, AvoidanceDebugFactor::NOT_PARKING_OBJECT); + addObjects(data.other_objects, std::string("MovingObject")); + addObjects(data.other_objects, std::string("CrosswalkUser")); + addObjects(data.other_objects, std::string("OutOfTargetArea")); + addObjects(data.other_objects, std::string("NotNeedAvoidance")); + addObjects(data.other_objects, std::string("LessThanExecutionThreshold")); + } // parent object info - addAvoidLine(debug.registered_raw_shift, "p_registered_shift", 0.8, 0.8, 0.0); - addAvoidLine(debug.current_raw_shift, "p_current_raw_shift", 0.5, 0.2, 0.2); - addAvoidLine(debug.extra_return_shift, "p_extra_return_shift", 0.0, 0.5, 0.8); + { + addAvoidLine(debug.registered_raw_shift, "p_registered_shift", 0.8, 0.8, 0.0); + addAvoidLine(debug.current_raw_shift, "p_current_raw_shift", 0.5, 0.2, 0.2); + addAvoidLine(debug.extra_return_shift, "p_extra_return_shift", 0.0, 0.5, 0.8); + } + + // safety check + { + add(showSafetyCheckInfo(debug.collision_check, "object_debug_info")); + add(showPredictedPath(debug.collision_check, "ego_predicted_path")); + add(showPolygon(debug.collision_check, "ego_and_target_polygon_relation")); + } // shift length { From 2f84a9223357d80e9005493204121f767691c1c7 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:32:21 +0900 Subject: [PATCH 105/547] fix(utils): fix invalid drivable area z-position (#4866) Signed-off-by: satoshi-ota --- planning/behavior_path_planner/src/utils/utils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 0e96cf6793783..c7fde9dada4ec 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1867,6 +1867,7 @@ void makeBoundLongitudinallyMonotonic( if (intersect_point) { Pose pose; pose.position = *intersect_point; + pose.position.z = bound_with_pose.at(i).position.z; const auto yaw = calcAzimuthAngle(*intersect_point, bound_with_pose.at(i + 1).position); pose.orientation = createQuaternionFromRPY(0.0, 0.0, yaw); monotonic_bound.push_back(pose); From e820499f6f8758de52f9e9e4c9a4fd8a3a6c8217 Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Mon, 4 Sep 2023 16:08:05 +0900 Subject: [PATCH 106/547] feat(map_based_prediciton): enable map_based_prediction.launch.xml to remap input topic (#4824) * enable map_based_prediction.launch to remap input topic Signed-off-by: Yoshi, Ri * cancel unnecessary changes in tracker cpp Signed-off-by: Yoshi, Ri --------- Signed-off-by: Yoshi, Ri --- perception/map_based_prediction/README.md | 9 +++++---- .../launch/map_based_prediction.launch.xml | 4 +++- .../src/map_based_prediction_node.cpp | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/perception/map_based_prediction/README.md b/perception/map_based_prediction/README.md index 256a1d0ae899f..d487bb163c5e8 100644 --- a/perception/map_based_prediction/README.md +++ b/perception/map_based_prediction/README.md @@ -143,10 +143,11 @@ If the target object is inside the road or crosswalk, this module outputs one or ### Output -| Name | Type | Description | -| ------------------------ | ------------------------------------------------------ | ------------------------------------- | -| `~/objects` | `autoware_auto_perception_msgs::msg::PredictedObjects` | tracking objects with predicted path. | -| `~/objects_path_markers` | `visualization_msgs::msg::MarkerArray` | marker for visualization. | +| Name | Type | Description | +| ------------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------- | +| `~/input/objects` | `autoware_auto_perception_msgs::msg::TrackedObjects` | tracking objects. Default is set to `/perception/object_recognition/tracking/objects` | +| `~/output/objects` | `autoware_auto_perception_msgs::msg::PredictedObjects` | tracking objects with predicted path. | +| `~/objects_path_markers` | `visualization_msgs::msg::MarkerArray` | marker for visualization. | ## Parameters diff --git a/perception/map_based_prediction/launch/map_based_prediction.launch.xml b/perception/map_based_prediction/launch/map_based_prediction.launch.xml index f7ae53cd1d588..619c0c9507e0b 100644 --- a/perception/map_based_prediction/launch/map_based_prediction.launch.xml +++ b/perception/map_based_prediction/launch/map_based_prediction.launch.xml @@ -4,10 +4,12 @@ + - + + diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 259fbae1cef91..3b4e010d0a855 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -735,13 +735,13 @@ MapBasedPredictionNode::MapBasedPredictionNode(const rclcpp::NodeOptions & node_ prediction_time_horizon_, prediction_sampling_time_interval_, min_crosswalk_user_velocity_); sub_objects_ = this->create_subscription( - "/perception/object_recognition/tracking/objects", 1, + "~/input/objects", 1, std::bind(&MapBasedPredictionNode::objectsCallback, this, std::placeholders::_1)); sub_map_ = this->create_subscription( "/vector_map", rclcpp::QoS{1}.transient_local(), std::bind(&MapBasedPredictionNode::mapCallback, this, std::placeholders::_1)); - pub_objects_ = this->create_publisher("objects", rclcpp::QoS{1}); + pub_objects_ = this->create_publisher("~/output/objects", rclcpp::QoS{1}); pub_debug_markers_ = this->create_publisher("maneuver", rclcpp::QoS{1}); pub_calculation_time_ = create_publisher("~/debug/calculation_time", 1); From 75fc60f18d1e1c72e2953537237f023fb6a14785 Mon Sep 17 00:00:00 2001 From: Mateusz Palczuk <80065197+TauTheLepton@users.noreply.github.com> Date: Mon, 4 Sep 2023 09:20:31 +0200 Subject: [PATCH 107/547] fix(lat_lon_controller): change reference to the node (#3696) Signed-off-by: Mateusz Palczuk --- .../mpc_lateral_controller.hpp | 21 +- .../src/mpc_lateral_controller.cpp | 108 ++++---- .../pid_longitudinal_controller.hpp | 7 +- .../src/pid_longitudinal_controller.cpp | 245 +++++++++--------- .../pure_pursuit_lateral_controller.hpp | 4 +- .../pure_pursuit_lateral_controller.cpp | 67 ++--- 6 files changed, 228 insertions(+), 224 deletions(-) diff --git a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp index ee33062854ab9..b108e7602939c 100644 --- a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp +++ b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp @@ -52,11 +52,13 @@ using tier4_debug_msgs::msg::Float32Stamped; class MpcLateralController : public trajectory_follower::LateralControllerBase { public: + /// \param node Reference to the node used only for the component and parameter initialization. explicit MpcLateralController(rclcpp::Node & node); virtual ~MpcLateralController(); private: - rclcpp::Node * node_; + rclcpp::Clock::SharedPtr clock_; + rclcpp::Logger logger_; rclcpp::Publisher::SharedPtr m_pub_predicted_traj; rclcpp::Publisher::SharedPtr m_pub_debug_values; @@ -144,23 +146,27 @@ class MpcLateralController : public trajectory_follower::LateralControllerBase * @param wheelbase Vehicle's wheelbase. * @param steer_lim Steering command limit. * @param steer_tau Steering time constant. + * @param node Reference to the node. * @return Pointer to the created vehicle model. */ std::shared_ptr createVehicleModel( - const double wheelbase, const double steer_lim, const double steer_tau); + const double wheelbase, const double steer_lim, const double steer_tau, rclcpp::Node & node); /** * @brief Create the quadratic problem solver interface. + * @param node Reference to the node. * @return Pointer to the created QP solver interface. */ - std::shared_ptr createQPSolverInterface(); + std::shared_ptr createQPSolverInterface(rclcpp::Node & node); /** * @brief Create the steering offset estimator for offset compensation. * @param wheelbase Vehicle's wheelbase. + * @param node Reference to the node. * @return Pointer to the created steering offset estimator. */ - std::shared_ptr createSteerOffsetEstimator(const double wheelbase); + std::shared_ptr createSteerOffsetEstimator( + const double wheelbase, rclcpp::Node & node); /** * @brief Check if all necessary data is received and ready to run the control. @@ -250,8 +256,9 @@ class MpcLateralController : public trajectory_follower::LateralControllerBase /** * @brief Declare MPC parameters as ROS parameters to allow tuning on the fly. + * @param node Reference to the node. */ - void declareMPCparameters(); + void declareMPCparameters(rclcpp::Node & node); /** * @brief Callback function called when parameters are changed outside of the node. @@ -264,13 +271,13 @@ class MpcLateralController : public trajectory_follower::LateralControllerBase template inline void info_throttle(Args &&... args) { - RCLCPP_INFO_THROTTLE(node_->get_logger(), *node_->get_clock(), 5000, args...); + RCLCPP_INFO_THROTTLE(logger_, *clock_, 5000, args...); } template inline void warn_throttle(Args &&... args) { - RCLCPP_WARN_THROTTLE(node_->get_logger(), *node_->get_clock(), 5000, args...); + RCLCPP_WARN_THROTTLE(logger_, *clock_, 5000, args...); } }; } // namespace autoware::motion::control::mpc_lateral_controller diff --git a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp index 0f815a482f80d..751873cacbf12 100644 --- a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp +++ b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp @@ -35,13 +35,14 @@ namespace autoware::motion::control::mpc_lateral_controller { -MpcLateralController::MpcLateralController(rclcpp::Node & node) : node_{&node} +MpcLateralController::MpcLateralController(rclcpp::Node & node) +: clock_(node.get_clock()), logger_(node.get_logger()) { - const auto dp_int = [&](const std::string & s) { return node_->declare_parameter(s); }; - const auto dp_bool = [&](const std::string & s) { return node_->declare_parameter(s); }; - const auto dp_double = [&](const std::string & s) { return node_->declare_parameter(s); }; + const auto dp_int = [&](const std::string & s) { return node.declare_parameter(s); }; + const auto dp_bool = [&](const std::string & s) { return node.declare_parameter(s); }; + const auto dp_double = [&](const std::string & s) { return node.declare_parameter(s); }; - m_mpc.m_ctrl_period = node_->get_parameter("ctrl_period").as_double(); + m_mpc.m_ctrl_period = node.get_parameter("ctrl_period").as_double(); auto & p_filt = m_trajectory_filtering_param; p_filt.enable_path_smoothing = dp_bool("enable_path_smoothing"); @@ -66,16 +67,16 @@ MpcLateralController::MpcLateralController(rclcpp::Node & node) : node_{&node} m_mpc_converged_threshold_rps = dp_double("mpc_converged_threshold_rps"); // [rad/s] /* mpc parameters */ - const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(*node_).getVehicleInfo(); + const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); const double wheelbase = vehicle_info.wheel_base_m; constexpr double deg2rad = static_cast(M_PI) / 180.0; m_mpc.m_steer_lim = vehicle_info.max_steer_angle_rad; // steer rate limit depending on curvature const auto steer_rate_lim_dps_list_by_curvature = - node_->declare_parameter>("steer_rate_lim_dps_list_by_curvature"); + node.declare_parameter>("steer_rate_lim_dps_list_by_curvature"); const auto curvature_list_for_steer_rate_lim = - node_->declare_parameter>("curvature_list_for_steer_rate_lim"); + node.declare_parameter>("curvature_list_for_steer_rate_lim"); for (size_t i = 0; i < steer_rate_lim_dps_list_by_curvature.size(); ++i) { m_mpc.m_steer_rate_lim_map_by_curvature.emplace_back( curvature_list_for_steer_rate_lim.at(i), @@ -84,9 +85,9 @@ MpcLateralController::MpcLateralController(rclcpp::Node & node) : node_{&node} // steer rate limit depending on velocity const auto steer_rate_lim_dps_list_by_velocity = - node_->declare_parameter>("steer_rate_lim_dps_list_by_velocity"); + node.declare_parameter>("steer_rate_lim_dps_list_by_velocity"); const auto velocity_list_for_steer_rate_lim = - node_->declare_parameter>("velocity_list_for_steer_rate_lim"); + node.declare_parameter>("velocity_list_for_steer_rate_lim"); for (size_t i = 0; i < steer_rate_lim_dps_list_by_velocity.size(); ++i) { m_mpc.m_steer_rate_lim_map_by_velocity.emplace_back( velocity_list_for_steer_rate_lim.at(i), steer_rate_lim_dps_list_by_velocity.at(i) * deg2rad); @@ -94,12 +95,12 @@ MpcLateralController::MpcLateralController(rclcpp::Node & node) : node_{&node} /* vehicle model setup */ auto vehicle_model_ptr = - createVehicleModel(wheelbase, m_mpc.m_steer_lim, m_mpc.m_param.steer_tau); + createVehicleModel(wheelbase, m_mpc.m_steer_lim, m_mpc.m_param.steer_tau, node); m_mpc.setVehicleModel(vehicle_model_ptr); /* QP solver setup */ m_mpc.setVehicleModel(vehicle_model_ptr); - auto qpsolver_ptr = createQPSolverInterface(); + auto qpsolver_ptr = createQPSolverInterface(node); m_mpc.setQPSolver(qpsolver_ptr); /* delay compensation */ @@ -113,7 +114,7 @@ MpcLateralController::MpcLateralController(rclcpp::Node & node) : node_{&node} /* steering offset compensation */ enable_auto_steering_offset_removal_ = dp_bool("steering_offset.enable_auto_steering_offset_removal"); - steering_offset_ = createSteerOffsetEstimator(wheelbase); + steering_offset_ = createSteerOffsetEstimator(wheelbase, node); /* initialize low-pass filter */ { @@ -124,30 +125,29 @@ MpcLateralController::MpcLateralController(rclcpp::Node & node) : node_{&node} // ego nearest index search const auto check_and_get_param = [&](const auto & param) { - return node_->has_parameter(param) ? node_->get_parameter(param).as_double() : dp_double(param); + return node.has_parameter(param) ? node.get_parameter(param).as_double() : dp_double(param); }; m_ego_nearest_dist_threshold = check_and_get_param("ego_nearest_dist_threshold"); m_ego_nearest_yaw_threshold = check_and_get_param("ego_nearest_yaw_threshold"); m_mpc.ego_nearest_dist_threshold = m_ego_nearest_dist_threshold; m_mpc.ego_nearest_yaw_threshold = m_ego_nearest_yaw_threshold; - m_pub_predicted_traj = node_->create_publisher("~/output/predicted_trajectory", 1); + m_pub_predicted_traj = node.create_publisher("~/output/predicted_trajectory", 1); m_pub_debug_values = - node_->create_publisher("~/output/lateral_diagnostic", 1); - m_pub_steer_offset = - node_->create_publisher("~/output/estimated_steer_offset", 1); + node.create_publisher("~/output/lateral_diagnostic", 1); + m_pub_steer_offset = node.create_publisher("~/output/estimated_steer_offset", 1); - declareMPCparameters(); + declareMPCparameters(node); /* get parameter updates */ using std::placeholders::_1; - m_set_param_res = node_->add_on_set_parameters_callback( - std::bind(&MpcLateralController::paramCallback, this, _1)); + m_set_param_res = + node.add_on_set_parameters_callback(std::bind(&MpcLateralController::paramCallback, this, _1)); m_mpc.initializeSteeringPredictor(); - m_mpc.setLogger(node_->get_logger()); - m_mpc.setClock(node_->get_clock()); + m_mpc.setLogger(logger_); + m_mpc.setClock(clock_); } MpcLateralController::~MpcLateralController() @@ -155,12 +155,11 @@ MpcLateralController::~MpcLateralController() } std::shared_ptr MpcLateralController::createVehicleModel( - const double wheelbase, const double steer_lim, const double steer_tau) + const double wheelbase, const double steer_lim, const double steer_tau, rclcpp::Node & node) { std::shared_ptr vehicle_model_ptr; - const std::string vehicle_model_type = - node_->declare_parameter("vehicle_model_type"); + const std::string vehicle_model_type = node.declare_parameter("vehicle_model_type"); if (vehicle_model_type == "kinematics") { vehicle_model_ptr = std::make_shared(wheelbase, steer_lim, steer_tau); @@ -173,12 +172,12 @@ std::shared_ptr MpcLateralController::createVehicleModel( } if (vehicle_model_type == "dynamics") { - const double mass_fl = node_->declare_parameter("vehicle.mass_fl"); - const double mass_fr = node_->declare_parameter("vehicle.mass_fr"); - const double mass_rl = node_->declare_parameter("vehicle.mass_rl"); - const double mass_rr = node_->declare_parameter("vehicle.mass_rr"); - const double cf = node_->declare_parameter("vehicle.cf"); - const double cr = node_->declare_parameter("vehicle.cr"); + const double mass_fl = node.declare_parameter("vehicle.mass_fl"); + const double mass_fr = node.declare_parameter("vehicle.mass_fr"); + const double mass_rl = node.declare_parameter("vehicle.mass_rl"); + const double mass_rr = node.declare_parameter("vehicle.mass_rr"); + const double cf = node.declare_parameter("vehicle.cf"); + const double cr = node.declare_parameter("vehicle.cr"); // vehicle_model_ptr is only assigned in ctor, so parameter value have to be passed at init time vehicle_model_ptr = @@ -186,15 +185,16 @@ std::shared_ptr MpcLateralController::createVehicleModel( return vehicle_model_ptr; } - RCLCPP_ERROR(node_->get_logger(), "vehicle_model_type is undefined"); + RCLCPP_ERROR(logger_, "vehicle_model_type is undefined"); return vehicle_model_ptr; } -std::shared_ptr MpcLateralController::createQPSolverInterface() +std::shared_ptr MpcLateralController::createQPSolverInterface( + rclcpp::Node & node) { std::shared_ptr qpsolver_ptr; - const std::string qp_solver_type = node_->declare_parameter("qp_solver_type"); + const std::string qp_solver_type = node.declare_parameter("qp_solver_type"); if (qp_solver_type == "unconstraint_fast") { qpsolver_ptr = std::make_shared(); @@ -202,22 +202,22 @@ std::shared_ptr MpcLateralController::createQPSolverInterface } if (qp_solver_type == "osqp") { - qpsolver_ptr = std::make_shared(node_->get_logger()); + qpsolver_ptr = std::make_shared(logger_); return qpsolver_ptr; } - RCLCPP_ERROR(node_->get_logger(), "qp_solver_type is undefined"); + RCLCPP_ERROR(logger_, "qp_solver_type is undefined"); return qpsolver_ptr; } std::shared_ptr MpcLateralController::createSteerOffsetEstimator( - const double wheelbase) + const double wheelbase, rclcpp::Node & node) { const std::string ns = "steering_offset."; - const auto vel_thres = node_->declare_parameter(ns + "update_vel_threshold"); - const auto steer_thres = node_->declare_parameter(ns + "update_steer_threshold"); - const auto limit = node_->declare_parameter(ns + "steering_offset_limit"); - const auto num = node_->declare_parameter(ns + "average_num"); + const auto vel_thres = node.declare_parameter(ns + "update_vel_threshold"); + const auto steer_thres = node.declare_parameter(ns + "update_steer_threshold"); + const auto limit = node.declare_parameter(ns + "steering_offset_limit"); + const auto num = node.declare_parameter(ns + "average_num"); steering_offset_ = std::make_shared(wheelbase, num, vel_thres, steer_thres, limit); return steering_offset_; @@ -306,7 +306,7 @@ bool MpcLateralController::isSteerConverged(const AckermannLateralCommand & cmd) // wait for a while to propagate the trajectory shape to the output command when the trajectory // shape is changed. if (!m_has_received_first_trajectory || isTrajectoryShapeChanged()) { - RCLCPP_DEBUG(node_->get_logger(), "trajectory shaped is changed"); + RCLCPP_DEBUG(logger_, "trajectory shaped is changed"); return false; } @@ -344,12 +344,12 @@ void MpcLateralController::setTrajectory(const Trajectory & msg) m_current_trajectory = msg; if (msg.points.size() < 3) { - RCLCPP_DEBUG(node_->get_logger(), "received path size is < 3, not enough."); + RCLCPP_DEBUG(logger_, "received path size is < 3, not enough."); return; } if (!isValidTrajectory(msg)) { - RCLCPP_ERROR(node_->get_logger(), "Trajectory is invalid!! stop computing."); + RCLCPP_ERROR(logger_, "Trajectory is invalid!! stop computing."); return; } @@ -424,32 +424,32 @@ AckermannLateralCommand MpcLateralController::createCtrlCmdMsg( const AckermannLateralCommand & ctrl_cmd) { auto out = ctrl_cmd; - out.stamp = node_->now(); + out.stamp = clock_->now(); m_steer_cmd_prev = out.steering_tire_angle; return out; } void MpcLateralController::publishPredictedTraj(Trajectory & predicted_traj) const { - predicted_traj.header.stamp = node_->now(); + predicted_traj.header.stamp = clock_->now(); predicted_traj.header.frame_id = m_current_trajectory.header.frame_id; m_pub_predicted_traj->publish(predicted_traj); } void MpcLateralController::publishDebugValues(Float32MultiArrayStamped & debug_values) const { - debug_values.stamp = node_->now(); + debug_values.stamp = clock_->now(); m_pub_debug_values->publish(debug_values); Float32Stamped offset; - offset.stamp = node_->now(); + offset.stamp = clock_->now(); offset.data = steering_offset_->getOffset(); m_pub_steer_offset->publish(offset); } void MpcLateralController::setSteeringToHistory(const AckermannLateralCommand & steering) { - const auto time = node_->now(); + const auto time = clock_->now(); if (m_mpc_steering_history.empty()) { m_mpc_steering_history.emplace_back(steering, time); m_is_mpc_history_filled = false; @@ -501,12 +501,12 @@ bool MpcLateralController::isMpcConverged() return (max_steering_value - min_steering_value) < m_mpc_converged_threshold_rps; } -void MpcLateralController::declareMPCparameters() +void MpcLateralController::declareMPCparameters(rclcpp::Node & node) { - m_mpc.m_param.prediction_horizon = node_->declare_parameter("mpc_prediction_horizon"); - m_mpc.m_param.prediction_dt = node_->declare_parameter("mpc_prediction_dt"); + m_mpc.m_param.prediction_horizon = node.declare_parameter("mpc_prediction_horizon"); + m_mpc.m_param.prediction_dt = node.declare_parameter("mpc_prediction_dt"); - const auto dp = [&](const auto & param) { return node_->declare_parameter(param); }; + const auto dp = [&](const auto & param) { return node.declare_parameter(param); }; auto & nw = m_mpc.m_param.nominal_weight; nw.lat_error = dp("mpc_weight_lat_error"); diff --git a/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp b/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp index c2dbc67f011dc..41d98a4d15541 100644 --- a/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp +++ b/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp @@ -56,6 +56,7 @@ namespace trajectory_follower = ::autoware::motion::control::trajectory_follower class PidLongitudinalController : public trajectory_follower::LongitudinalControllerBase { public: + /// \param node Reference to the node used only for the component and parameter initialization. explicit PidLongitudinalController(rclcpp::Node & node); private: @@ -77,7 +78,9 @@ class PidLongitudinalController : public trajectory_follower::LongitudinalContro double slope_angle{0.0}; double dt{0.0}; }; - rclcpp::Node * node_; + rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_; + rclcpp::Clock::SharedPtr clock_; + rclcpp::Logger logger_; // ros variables rclcpp::Publisher::SharedPtr m_pub_slope; rclcpp::Publisher::SharedPtr m_pub_debug; @@ -202,7 +205,7 @@ class PidLongitudinalController : public trajectory_follower::LongitudinalContro // debug values DebugValues m_debug_values; - std::shared_ptr m_last_running_time{std::make_shared(node_->now())}; + std::shared_ptr m_last_running_time{std::make_shared(clock_->now())}; // Diagnostic diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index 40385f2853983..6d3489811b195 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -28,110 +28,110 @@ namespace autoware::motion::control::pid_longitudinal_controller { PidLongitudinalController::PidLongitudinalController(rclcpp::Node & node) -: node_{&node}, diagnostic_updater_(&node) +: node_parameters_(node.get_node_parameters_interface()), + clock_(node.get_clock()), + logger_(node.get_logger()), + diagnostic_updater_(&node) { using std::placeholders::_1; // parameters timer - m_longitudinal_ctrl_period = node_->get_parameter("ctrl_period").as_double(); + m_longitudinal_ctrl_period = node.get_parameter("ctrl_period").as_double(); - m_wheel_base = vehicle_info_util::VehicleInfoUtil(*node_).getVehicleInfo().wheel_base_m; + m_wheel_base = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo().wheel_base_m; // parameters for delay compensation - m_delay_compensation_time = node_->declare_parameter("delay_compensation_time"); // [s] + m_delay_compensation_time = node.declare_parameter("delay_compensation_time"); // [s] // parameters to enable functions - m_enable_smooth_stop = node_->declare_parameter("enable_smooth_stop"); - m_enable_overshoot_emergency = node_->declare_parameter("enable_overshoot_emergency"); + m_enable_smooth_stop = node.declare_parameter("enable_smooth_stop"); + m_enable_overshoot_emergency = node.declare_parameter("enable_overshoot_emergency"); m_enable_large_tracking_error_emergency = - node_->declare_parameter("enable_large_tracking_error_emergency"); - m_enable_slope_compensation = node_->declare_parameter("enable_slope_compensation"); + node.declare_parameter("enable_large_tracking_error_emergency"); + m_enable_slope_compensation = node.declare_parameter("enable_slope_compensation"); m_enable_keep_stopped_until_steer_convergence = - node_->declare_parameter("enable_keep_stopped_until_steer_convergence"); + node.declare_parameter("enable_keep_stopped_until_steer_convergence"); // parameters for state transition { auto & p = m_state_transition_params; // drive - p.drive_state_stop_dist = node_->declare_parameter("drive_state_stop_dist"); // [m] + p.drive_state_stop_dist = node.declare_parameter("drive_state_stop_dist"); // [m] p.drive_state_offset_stop_dist = - node_->declare_parameter("drive_state_offset_stop_dist"); // [m] + node.declare_parameter("drive_state_offset_stop_dist"); // [m] // stopping - p.stopping_state_stop_dist = - node_->declare_parameter("stopping_state_stop_dist"); // [m] + p.stopping_state_stop_dist = node.declare_parameter("stopping_state_stop_dist"); // [m] p.stopped_state_entry_duration_time = - node_->declare_parameter("stopped_state_entry_duration_time"); // [s] + node.declare_parameter("stopped_state_entry_duration_time"); // [s] // stop - p.stopped_state_entry_vel = - node_->declare_parameter("stopped_state_entry_vel"); // [m/s] + p.stopped_state_entry_vel = node.declare_parameter("stopped_state_entry_vel"); // [m/s] p.stopped_state_entry_acc = - node_->declare_parameter("stopped_state_entry_acc"); // [m/s²] + node.declare_parameter("stopped_state_entry_acc"); // [m/s²] // emergency p.emergency_state_overshoot_stop_dist = - node_->declare_parameter("emergency_state_overshoot_stop_dist"); // [m] + node.declare_parameter("emergency_state_overshoot_stop_dist"); // [m] p.emergency_state_traj_trans_dev = - node_->declare_parameter("emergency_state_traj_trans_dev"); // [m] + node.declare_parameter("emergency_state_traj_trans_dev"); // [m] p.emergency_state_traj_rot_dev = - node_->declare_parameter("emergency_state_traj_rot_dev"); // [m] + node.declare_parameter("emergency_state_traj_rot_dev"); // [m] } // parameters for drive state { // initialize PID gain - const double kp{node_->declare_parameter("kp")}; - const double ki{node_->declare_parameter("ki")}; - const double kd{node_->declare_parameter("kd")}; + const double kp{node.declare_parameter("kp")}; + const double ki{node.declare_parameter("ki")}; + const double kd{node.declare_parameter("kd")}; m_pid_vel.setGains(kp, ki, kd); // initialize PID limits - const double max_pid{node_->declare_parameter("max_out")}; // [m/s^2] - const double min_pid{node_->declare_parameter("min_out")}; // [m/s^2] - const double max_p{node_->declare_parameter("max_p_effort")}; // [m/s^2] - const double min_p{node_->declare_parameter("min_p_effort")}; // [m/s^2] - const double max_i{node_->declare_parameter("max_i_effort")}; // [m/s^2] - const double min_i{node_->declare_parameter("min_i_effort")}; // [m/s^2] - const double max_d{node_->declare_parameter("max_d_effort")}; // [m/s^2] - const double min_d{node_->declare_parameter("min_d_effort")}; // [m/s^2] + const double max_pid{node.declare_parameter("max_out")}; // [m/s^2] + const double min_pid{node.declare_parameter("min_out")}; // [m/s^2] + const double max_p{node.declare_parameter("max_p_effort")}; // [m/s^2] + const double min_p{node.declare_parameter("min_p_effort")}; // [m/s^2] + const double max_i{node.declare_parameter("max_i_effort")}; // [m/s^2] + const double min_i{node.declare_parameter("min_i_effort")}; // [m/s^2] + const double max_d{node.declare_parameter("max_d_effort")}; // [m/s^2] + const double min_d{node.declare_parameter("min_d_effort")}; // [m/s^2] m_pid_vel.setLimits(max_pid, min_pid, max_p, min_p, max_i, min_i, max_d, min_d); // set lowpass filter for vel error and pitch - const double lpf_vel_error_gain{node_->declare_parameter("lpf_vel_error_gain")}; + const double lpf_vel_error_gain{node.declare_parameter("lpf_vel_error_gain")}; m_lpf_vel_error = std::make_shared(0.0, lpf_vel_error_gain); m_current_vel_threshold_pid_integrate = - node_->declare_parameter("current_vel_threshold_pid_integration"); // [m/s] + node.declare_parameter("current_vel_threshold_pid_integration"); // [m/s] m_enable_brake_keeping_before_stop = - node_->declare_parameter("enable_brake_keeping_before_stop"); // [-] - m_brake_keeping_acc = node_->declare_parameter("brake_keeping_acc"); // [m/s^2] + node.declare_parameter("enable_brake_keeping_before_stop"); // [-] + m_brake_keeping_acc = node.declare_parameter("brake_keeping_acc"); // [m/s^2] } // parameters for smooth stop state { const double max_strong_acc{ - node_->declare_parameter("smooth_stop_max_strong_acc")}; // [m/s^2] + node.declare_parameter("smooth_stop_max_strong_acc")}; // [m/s^2] const double min_strong_acc{ - node_->declare_parameter("smooth_stop_min_strong_acc")}; // [m/s^2] - const double weak_acc{node_->declare_parameter("smooth_stop_weak_acc")}; // [m/s^2] + node.declare_parameter("smooth_stop_min_strong_acc")}; // [m/s^2] + const double weak_acc{node.declare_parameter("smooth_stop_weak_acc")}; // [m/s^2] const double weak_stop_acc{ - node_->declare_parameter("smooth_stop_weak_stop_acc")}; // [m/s^2] + node.declare_parameter("smooth_stop_weak_stop_acc")}; // [m/s^2] const double strong_stop_acc{ - node_->declare_parameter("smooth_stop_strong_stop_acc")}; // [m/s^2] + node.declare_parameter("smooth_stop_strong_stop_acc")}; // [m/s^2] - const double max_fast_vel{ - node_->declare_parameter("smooth_stop_max_fast_vel")}; // [m/s] + const double max_fast_vel{node.declare_parameter("smooth_stop_max_fast_vel")}; // [m/s] const double min_running_vel{ - node_->declare_parameter("smooth_stop_min_running_vel")}; // [m/s] + node.declare_parameter("smooth_stop_min_running_vel")}; // [m/s] const double min_running_acc{ - node_->declare_parameter("smooth_stop_min_running_acc")}; // [m/s^2] + node.declare_parameter("smooth_stop_min_running_acc")}; // [m/s^2] const double weak_stop_time{ - node_->declare_parameter("smooth_stop_weak_stop_time")}; // [s] + node.declare_parameter("smooth_stop_weak_stop_time")}; // [s] const double weak_stop_dist{ - node_->declare_parameter("smooth_stop_weak_stop_dist")}; // [m] + node.declare_parameter("smooth_stop_weak_stop_dist")}; // [m] const double strong_stop_dist{ - node_->declare_parameter("smooth_stop_strong_stop_dist")}; // [m] + node.declare_parameter("smooth_stop_strong_stop_dist")}; // [m] m_smooth_stop.setParams( max_strong_acc, min_strong_acc, weak_acc, weak_stop_acc, strong_stop_acc, max_fast_vel, @@ -141,52 +141,52 @@ PidLongitudinalController::PidLongitudinalController(rclcpp::Node & node) // parameters for stop state { auto & p = m_stopped_state_params; - p.vel = node_->declare_parameter("stopped_vel"); // [m/s] - p.acc = node_->declare_parameter("stopped_acc"); // [m/s^2] - p.jerk = node_->declare_parameter("stopped_jerk"); // [m/s^3] + p.vel = node.declare_parameter("stopped_vel"); // [m/s] + p.acc = node.declare_parameter("stopped_acc"); // [m/s^2] + p.jerk = node.declare_parameter("stopped_jerk"); // [m/s^3] } // parameters for emergency state { auto & p = m_emergency_state_params; - p.vel = node_->declare_parameter("emergency_vel"); // [m/s] - p.acc = node_->declare_parameter("emergency_acc"); // [m/s^2] - p.jerk = node_->declare_parameter("emergency_jerk"); // [m/s^3] + p.vel = node.declare_parameter("emergency_vel"); // [m/s] + p.acc = node.declare_parameter("emergency_acc"); // [m/s^2] + p.jerk = node.declare_parameter("emergency_jerk"); // [m/s^3] } // parameters for acceleration limit - m_max_acc = node_->declare_parameter("max_acc"); // [m/s^2] - m_min_acc = node_->declare_parameter("min_acc"); // [m/s^2] + m_max_acc = node.declare_parameter("max_acc"); // [m/s^2] + m_min_acc = node.declare_parameter("min_acc"); // [m/s^2] // parameters for jerk limit - m_max_jerk = node_->declare_parameter("max_jerk"); // [m/s^3] - m_min_jerk = node_->declare_parameter("min_jerk"); // [m/s^3] + m_max_jerk = node.declare_parameter("max_jerk"); // [m/s^3] + m_min_jerk = node.declare_parameter("min_jerk"); // [m/s^3] // parameters for slope compensation - m_use_traj_for_pitch = node_->declare_parameter("use_trajectory_for_pitch_calculation"); - const double lpf_pitch_gain{node_->declare_parameter("lpf_pitch_gain")}; + m_use_traj_for_pitch = node.declare_parameter("use_trajectory_for_pitch_calculation"); + const double lpf_pitch_gain{node.declare_parameter("lpf_pitch_gain")}; m_lpf_pitch = std::make_shared(0.0, lpf_pitch_gain); - m_max_pitch_rad = node_->declare_parameter("max_pitch_rad"); // [rad] - m_min_pitch_rad = node_->declare_parameter("min_pitch_rad"); // [rad] + m_max_pitch_rad = node.declare_parameter("max_pitch_rad"); // [rad] + m_min_pitch_rad = node.declare_parameter("min_pitch_rad"); // [rad] // ego nearest index search m_ego_nearest_dist_threshold = - node_->has_parameter("ego_nearest_dist_threshold") - ? node_->get_parameter("ego_nearest_dist_threshold").as_double() - : node_->declare_parameter("ego_nearest_dist_threshold"); // [m] + node.has_parameter("ego_nearest_dist_threshold") + ? node.get_parameter("ego_nearest_dist_threshold").as_double() + : node.declare_parameter("ego_nearest_dist_threshold"); // [m] m_ego_nearest_yaw_threshold = - node_->has_parameter("ego_nearest_yaw_threshold") - ? node_->get_parameter("ego_nearest_yaw_threshold").as_double() - : node_->declare_parameter("ego_nearest_yaw_threshold"); // [rad] + node.has_parameter("ego_nearest_yaw_threshold") + ? node.get_parameter("ego_nearest_yaw_threshold").as_double() + : node.declare_parameter("ego_nearest_yaw_threshold"); // [rad] // subscriber, publisher - m_pub_slope = node_->create_publisher( + m_pub_slope = node.create_publisher( "~/output/slope_angle", rclcpp::QoS{1}); - m_pub_debug = node_->create_publisher( + m_pub_debug = node.create_publisher( "~/output/longitudinal_diagnostic", rclcpp::QoS{1}); // set parameter callback - m_set_param_res = node_->add_on_set_parameters_callback( + m_set_param_res = node.add_on_set_parameters_callback( std::bind(&PidLongitudinalController::paramCallback, this, _1)); // diagnostic @@ -213,14 +213,12 @@ void PidLongitudinalController::setTrajectory( const autoware_auto_planning_msgs::msg::Trajectory & msg) { if (!longitudinal_utils::isValidTrajectory(msg)) { - RCLCPP_ERROR_THROTTLE( - node_->get_logger(), *node_->get_clock(), 3000, "received invalid trajectory. ignore."); + RCLCPP_ERROR_THROTTLE(logger_, *clock_, 3000, "received invalid trajectory. ignore."); return; } if (msg.points.size() < 2) { - RCLCPP_WARN_THROTTLE( - node_->get_logger(), *node_->get_clock(), 3000, "Unexpected trajectory size < 2. Ignored."); + RCLCPP_WARN_THROTTLE(logger_, *clock_, 3000, "Unexpected trajectory size < 2. Ignored."); return; } @@ -259,22 +257,22 @@ rcl_interfaces::msg::SetParametersResult PidLongitudinalController::paramCallbac // drive state { - double kp{node_->get_parameter("kp").as_double()}; - double ki{node_->get_parameter("ki").as_double()}; - double kd{node_->get_parameter("kd").as_double()}; + double kp{node_parameters_->get_parameter("kp").as_double()}; + double ki{node_parameters_->get_parameter("ki").as_double()}; + double kd{node_parameters_->get_parameter("kd").as_double()}; update_param("kp", kp); update_param("ki", ki); update_param("kd", kd); m_pid_vel.setGains(kp, ki, kd); - double max_pid{node_->get_parameter("max_out").as_double()}; - double min_pid{node_->get_parameter("min_out").as_double()}; - double max_p{node_->get_parameter("max_p_effort").as_double()}; - double min_p{node_->get_parameter("min_p_effort").as_double()}; - double max_i{node_->get_parameter("max_i_effort").as_double()}; - double min_i{node_->get_parameter("min_i_effort").as_double()}; - double max_d{node_->get_parameter("max_d_effort").as_double()}; - double min_d{node_->get_parameter("min_d_effort").as_double()}; + double max_pid{node_parameters_->get_parameter("max_out").as_double()}; + double min_pid{node_parameters_->get_parameter("min_out").as_double()}; + double max_p{node_parameters_->get_parameter("max_p_effort").as_double()}; + double min_p{node_parameters_->get_parameter("min_p_effort").as_double()}; + double max_i{node_parameters_->get_parameter("max_i_effort").as_double()}; + double min_i{node_parameters_->get_parameter("min_i_effort").as_double()}; + double max_d{node_parameters_->get_parameter("max_d_effort").as_double()}; + double min_d{node_parameters_->get_parameter("min_d_effort").as_double()}; update_param("max_out", max_pid); update_param("min_out", min_pid); update_param("max_p_effort", max_p); @@ -290,17 +288,25 @@ rcl_interfaces::msg::SetParametersResult PidLongitudinalController::paramCallbac // stopping state { - double max_strong_acc{node_->get_parameter("smooth_stop_max_strong_acc").as_double()}; - double min_strong_acc{node_->get_parameter("smooth_stop_min_strong_acc").as_double()}; - double weak_acc{node_->get_parameter("smooth_stop_weak_acc").as_double()}; - double weak_stop_acc{node_->get_parameter("smooth_stop_weak_stop_acc").as_double()}; - double strong_stop_acc{node_->get_parameter("smooth_stop_strong_stop_acc").as_double()}; - double max_fast_vel{node_->get_parameter("smooth_stop_max_fast_vel").as_double()}; - double min_running_vel{node_->get_parameter("smooth_stop_min_running_vel").as_double()}; - double min_running_acc{node_->get_parameter("smooth_stop_min_running_acc").as_double()}; - double weak_stop_time{node_->get_parameter("smooth_stop_weak_stop_time").as_double()}; - double weak_stop_dist{node_->get_parameter("smooth_stop_weak_stop_dist").as_double()}; - double strong_stop_dist{node_->get_parameter("smooth_stop_strong_stop_dist").as_double()}; + double max_strong_acc{ + node_parameters_->get_parameter("smooth_stop_max_strong_acc").as_double()}; + double min_strong_acc{ + node_parameters_->get_parameter("smooth_stop_min_strong_acc").as_double()}; + double weak_acc{node_parameters_->get_parameter("smooth_stop_weak_acc").as_double()}; + double weak_stop_acc{node_parameters_->get_parameter("smooth_stop_weak_stop_acc").as_double()}; + double strong_stop_acc{ + node_parameters_->get_parameter("smooth_stop_strong_stop_acc").as_double()}; + double max_fast_vel{node_parameters_->get_parameter("smooth_stop_max_fast_vel").as_double()}; + double min_running_vel{ + node_parameters_->get_parameter("smooth_stop_min_running_vel").as_double()}; + double min_running_acc{ + node_parameters_->get_parameter("smooth_stop_min_running_acc").as_double()}; + double weak_stop_time{ + node_parameters_->get_parameter("smooth_stop_weak_stop_time").as_double()}; + double weak_stop_dist{ + node_parameters_->get_parameter("smooth_stop_weak_stop_dist").as_double()}; + double strong_stop_dist{ + node_parameters_->get_parameter("smooth_stop_strong_stop_dist").as_double()}; update_param("smooth_stop_max_strong_acc", max_strong_acc); update_param("smooth_stop_min_strong_acc", min_strong_acc); update_param("smooth_stop_weak_acc", weak_acc); @@ -471,8 +477,7 @@ PidLongitudinalController::Motion PidLongitudinalController::calcEmergencyCtrlCm longitudinal_utils::applyDiffLimitFilter(p.acc, m_prev_raw_ctrl_cmd.acc, dt, p.jerk); RCLCPP_ERROR_THROTTLE( - node_->get_logger(), *node_->get_clock(), 3000, "[Emergency stop] vel: %3.3f, acc: %3.3f", vel, - acc); + logger_, *clock_, 3000, "[Emergency stop] vel: %3.3f, acc: %3.3f", vel, acc); return Motion{vel, acc}; } @@ -497,11 +502,11 @@ void PidLongitudinalController::updateControlState(const ControlData & control_d if ( std::fabs(current_vel) > p.stopped_state_entry_vel || std::fabs(current_acc) > p.stopped_state_entry_acc) { - m_last_running_time = std::make_shared(node_->now()); + m_last_running_time = std::make_shared(clock_->now()); } const bool stopped_condition = m_last_running_time - ? (node_->now() - *m_last_running_time).seconds() > p.stopped_state_entry_duration_time + ? (clock_->now() - *m_last_running_time).seconds() > p.stopped_state_entry_duration_time : false; static constexpr double vel_epsilon = @@ -516,15 +521,14 @@ void PidLongitudinalController::updateControlState(const ControlData & control_d const auto changeState = [this](const auto s) { if (s != m_control_state) { RCLCPP_DEBUG_STREAM( - node_->get_logger(), - "controller state changed: " << toStr(m_control_state) << " -> " << toStr(s)); + logger_, "controller state changed: " << toStr(m_control_state) << " -> " << toStr(s)); } m_control_state = s; return; }; const auto info_throttle = [this](const auto & s) { - RCLCPP_INFO_SKIPFIRST_THROTTLE(node_->get_logger(), *node_->get_clock(), 5000, "%s", s); + RCLCPP_INFO_SKIPFIRST_THROTTLE(logger_, *clock_, 5000, "%s", s); }; // if current operation mode is not autonomous mode, then change state to stopped @@ -611,7 +615,7 @@ void PidLongitudinalController::updateControlState(const ControlData & control_d return; } - RCLCPP_FATAL(node_->get_logger(), "invalid state found."); + RCLCPP_FATAL(logger_, "invalid state found."); return; } @@ -642,7 +646,7 @@ PidLongitudinalController::Motion PidLongitudinalController::calcCtrlCmd( raw_ctrl_cmd.vel = target_motion.vel; raw_ctrl_cmd.acc = applyVelocityFeedback(target_motion, control_data.dt, pred_vel_in_target); RCLCPP_DEBUG( - node_->get_logger(), + logger_, "[feedback control] vel: %3.3f, acc: %3.3f, dt: %3.3f, v_curr: %3.3f, v_ref: %3.3f " "feedback_ctrl_cmd.ac: %3.3f", raw_ctrl_cmd.vel, raw_ctrl_cmd.acc, control_data.dt, current_vel, target_motion.vel, @@ -653,8 +657,8 @@ PidLongitudinalController::Motion PidLongitudinalController::calcCtrlCmd( raw_ctrl_cmd.vel = m_stopped_state_params.vel; RCLCPP_DEBUG( - node_->get_logger(), "[smooth stop]: Smooth stopping. vel: %3.3f, acc: %3.3f", - raw_ctrl_cmd.vel, raw_ctrl_cmd.acc); + logger_, "[smooth stop]: Smooth stopping. vel: %3.3f, acc: %3.3f", raw_ctrl_cmd.vel, + raw_ctrl_cmd.acc); } else if (m_control_state == ControlState::STOPPED) { // This acceleration is without slope compensation const auto & p = m_stopped_state_params; @@ -662,8 +666,7 @@ PidLongitudinalController::Motion PidLongitudinalController::calcCtrlCmd( raw_ctrl_cmd.acc = longitudinal_utils::applyDiffLimitFilter( p.acc, m_prev_raw_ctrl_cmd.acc, control_data.dt, p.jerk); - RCLCPP_DEBUG( - node_->get_logger(), "[Stopped]. vel: %3.3f, acc: %3.3f", raw_ctrl_cmd.vel, raw_ctrl_cmd.acc); + RCLCPP_DEBUG(logger_, "[Stopped]. vel: %3.3f, acc: %3.3f", raw_ctrl_cmd.vel, raw_ctrl_cmd.acc); } else if (m_control_state == ControlState::EMERGENCY) { raw_ctrl_cmd = calcEmergencyCtrlCmd(control_data.dt); } @@ -687,12 +690,12 @@ autoware_auto_control_msgs::msg::LongitudinalCommand PidLongitudinalController:: { // publish control command autoware_auto_control_msgs::msg::LongitudinalCommand cmd{}; - cmd.stamp = node_->now(); + cmd.stamp = clock_->now(); cmd.speed = static_cast(ctrl_cmd.vel); cmd.acceleration = static_cast(ctrl_cmd.acc); // store current velocity history - m_vel_hist.push_back({node_->now(), current_vel}); + m_vel_hist.push_back({clock_->now(), current_vel}); while (m_vel_hist.size() > static_cast(0.5 / m_longitudinal_ctrl_period)) { m_vel_hist.erase(m_vel_hist.begin()); } @@ -715,7 +718,7 @@ void PidLongitudinalController::publishDebugData( // publish debug values tier4_debug_msgs::msg::Float32MultiArrayStamped debug_msg{}; - debug_msg.stamp = node_->now(); + debug_msg.stamp = clock_->now(); for (const auto & v : m_debug_values.getValues()) { debug_msg.data.push_back(static_cast(v)); } @@ -723,7 +726,7 @@ void PidLongitudinalController::publishDebugData( // slope angle tier4_debug_msgs::msg::Float32MultiArrayStamped slope_msg{}; - slope_msg.stamp = node_->now(); + slope_msg.stamp = clock_->now(); slope_msg.data.push_back( static_cast(control_data.slope_angle)); m_pub_slope->publish(slope_msg); @@ -734,10 +737,10 @@ double PidLongitudinalController::getDt() double dt; if (!m_prev_control_time) { dt = m_longitudinal_ctrl_period; - m_prev_control_time = std::make_shared(node_->now()); + m_prev_control_time = std::make_shared(clock_->now()); } else { - dt = (node_->now() - *m_prev_control_time).seconds(); - *m_prev_control_time = node_->now(); + dt = (clock_->now() - *m_prev_control_time).seconds(); + *m_prev_control_time = clock_->now(); } const double max_dt = m_longitudinal_ctrl_period * 2.0; const double min_dt = m_longitudinal_ctrl_period * 0.5; @@ -786,7 +789,7 @@ void PidLongitudinalController::storeAccelCmd(const double accel) if (m_control_state == ControlState::DRIVE) { // convert format autoware_auto_control_msgs::msg::LongitudinalCommand cmd; - cmd.stamp = node_->now(); + cmd.stamp = clock_->now(); cmd.acceleration = static_cast(accel); // store published ctrl cmd @@ -800,7 +803,7 @@ void PidLongitudinalController::storeAccelCmd(const double accel) if (m_ctrl_cmd_vec.size() <= 2) { return; } - if ((node_->now() - m_ctrl_cmd_vec.at(1).stamp).seconds() > m_delay_compensation_time) { + if ((clock_->now() - m_ctrl_cmd_vec.at(1).stamp).seconds() > m_delay_compensation_time) { m_ctrl_cmd_vec.erase(m_ctrl_cmd_vec.begin()); } } @@ -887,9 +890,9 @@ double PidLongitudinalController::predictedVelocityInTargetPoint( double pred_vel = current_vel_abs; const auto past_delay_time = - node_->now() - rclcpp::Duration::from_seconds(delay_compensation_time); + clock_->now() - rclcpp::Duration::from_seconds(delay_compensation_time); for (std::size_t i = 0; i < m_ctrl_cmd_vec.size(); ++i) { - if ((node_->now() - m_ctrl_cmd_vec.at(i).stamp).seconds() < m_delay_compensation_time) { + if ((clock_->now() - m_ctrl_cmd_vec.at(i).stamp).seconds() < m_delay_compensation_time) { if (i == 0) { // size of m_ctrl_cmd_vec is less than m_delay_compensation_time pred_vel = current_vel_abs + @@ -908,7 +911,7 @@ double PidLongitudinalController::predictedVelocityInTargetPoint( const double last_acc = m_ctrl_cmd_vec.at(m_ctrl_cmd_vec.size() - 1).acceleration; const double time_to_current = - (node_->now() - m_ctrl_cmd_vec.at(m_ctrl_cmd_vec.size() - 1).stamp).seconds(); + (clock_->now() - m_ctrl_cmd_vec.at(m_ctrl_cmd_vec.size() - 1).stamp).seconds(); pred_vel += last_acc * time_to_current; // avoid to change sign of current_vel and pred_vel diff --git a/control/pure_pursuit/include/pure_pursuit/pure_pursuit_lateral_controller.hpp b/control/pure_pursuit/include/pure_pursuit/pure_pursuit_lateral_controller.hpp index e9e57cf08bcc4..daaebaacde8de 100644 --- a/control/pure_pursuit/include/pure_pursuit/pure_pursuit_lateral_controller.hpp +++ b/control/pure_pursuit/include/pure_pursuit/pure_pursuit_lateral_controller.hpp @@ -100,10 +100,12 @@ struct DebugData class PurePursuitLateralController : public LateralControllerBase { public: + /// \param node Reference to the node used only for the component and parameter initialization. explicit PurePursuitLateralController(rclcpp::Node & node); private: - rclcpp::Node::SharedPtr node_; + rclcpp::Clock::SharedPtr clock_; + rclcpp::Logger logger_; std::vector output_tp_array_; autoware_auto_planning_msgs::msg::Trajectory::SharedPtr trajectory_resampled_; autoware_auto_planning_msgs::msg::Trajectory trajectory_; diff --git a/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp b/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp index cb812202d8651..91b110b35b401 100644 --- a/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp +++ b/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp @@ -57,48 +57,43 @@ enum TYPE { namespace pure_pursuit { PurePursuitLateralController::PurePursuitLateralController(rclcpp::Node & node) -: node_{&node}, tf_buffer_(node_->get_clock()), tf_listener_(tf_buffer_) +: clock_(node.get_clock()), logger_(node.get_logger()), tf_buffer_(clock_), tf_listener_(tf_buffer_) { pure_pursuit_ = std::make_unique(); // Vehicle Parameters - const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(*node_).getVehicleInfo(); + const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); param_.wheel_base = vehicle_info.wheel_base_m; param_.max_steering_angle = vehicle_info.max_steer_angle_rad; // Algorithm Parameters - param_.ld_velocity_ratio = node_->declare_parameter("ld_velocity_ratio"); - param_.ld_lateral_error_ratio = node_->declare_parameter("ld_lateral_error_ratio"); - param_.ld_curvature_ratio = node_->declare_parameter("ld_curvature_ratio"); + param_.ld_velocity_ratio = node.declare_parameter("ld_velocity_ratio"); + param_.ld_lateral_error_ratio = node.declare_parameter("ld_lateral_error_ratio"); + param_.ld_curvature_ratio = node.declare_parameter("ld_curvature_ratio"); param_.long_ld_lateral_error_threshold = - node_->declare_parameter("long_ld_lateral_error_threshold"); - param_.min_lookahead_distance = node_->declare_parameter("min_lookahead_distance"); - param_.max_lookahead_distance = node_->declare_parameter("max_lookahead_distance"); + node.declare_parameter("long_ld_lateral_error_threshold"); + param_.min_lookahead_distance = node.declare_parameter("min_lookahead_distance"); + param_.max_lookahead_distance = node.declare_parameter("max_lookahead_distance"); param_.reverse_min_lookahead_distance = - node_->declare_parameter("reverse_min_lookahead_distance"); - param_.converged_steer_rad_ = node_->declare_parameter("converged_steer_rad"); - param_.prediction_ds = node_->declare_parameter("prediction_ds"); - param_.prediction_distance_length = - node_->declare_parameter("prediction_distance_length"); - param_.resampling_ds = node_->declare_parameter("resampling_ds"); + node.declare_parameter("reverse_min_lookahead_distance"); + param_.converged_steer_rad_ = node.declare_parameter("converged_steer_rad"); + param_.prediction_ds = node.declare_parameter("prediction_ds"); + param_.prediction_distance_length = node.declare_parameter("prediction_distance_length"); + param_.resampling_ds = node.declare_parameter("resampling_ds"); param_.curvature_calculation_distance = - node_->declare_parameter("curvature_calculation_distance"); - param_.enable_path_smoothing = node_->declare_parameter("enable_path_smoothing"); - param_.path_filter_moving_ave_num = - node_->declare_parameter("path_filter_moving_ave_num"); + node.declare_parameter("curvature_calculation_distance"); + param_.enable_path_smoothing = node.declare_parameter("enable_path_smoothing"); + param_.path_filter_moving_ave_num = node.declare_parameter("path_filter_moving_ave_num"); // Debug Publishers pub_debug_marker_ = - node_->create_publisher("~/debug/markers", 0); - pub_debug_values_ = node_->create_publisher( + node.create_publisher("~/debug/markers", 0); + pub_debug_values_ = node.create_publisher( "~/debug/ld_outputs", rclcpp::QoS{1}); // Publish predicted trajectory - pub_predicted_trajectory_ = node_->create_publisher( + pub_predicted_trajectory_ = node.create_publisher( "~/output/predicted_trajectory", 1); - - // Wait for first current pose - tf_utils::waitForTransform(tf_buffer_, "map", "base_link"); } double PurePursuitLateralController::calcLookaheadDistance( @@ -128,7 +123,7 @@ double PurePursuitLateralController::calcLookaheadDistance( debug_msg.data.at(TYPE::VELOCITY) = static_cast(velocity); debug_msg.data.at(TYPE::CURVATURE) = static_cast(curvature); debug_msg.data.at(TYPE::LATERAL_ERROR) = static_cast(lateral_error); - debug_msg.stamp = node_->now(); + debug_msg.stamp = clock_->now(); pub_debug_values_->publish(debug_msg); }; @@ -220,7 +215,7 @@ void PurePursuitLateralController::averageFilterTrajectory( autoware_auto_planning_msgs::msg::Trajectory & u) { if (static_cast(u.points.size()) <= 2 * param_.path_filter_moving_ave_num) { - RCLCPP_ERROR(node_->get_logger(), "Cannot smooth path! Trajectory size is too low!"); + RCLCPP_ERROR(logger_, "Cannot smooth path! Trajectory size is too low!"); return; } @@ -303,9 +298,7 @@ boost::optional PurePursuitLateralController::generatePredictedTraje tmp_msg = generateCtrlCmdMsg(pp_output->curvature); predicted_trajectory.points.at(i).longitudinal_velocity_mps = pp_output->velocity; } else { - RCLCPP_WARN_THROTTLE( - node_->get_logger(), *node_->get_clock(), 5000, - "failed to solve pure_pursuit for prediction"); + RCLCPP_WARN_THROTTLE(logger_, *clock_, 5000, "failed to solve pure_pursuit for prediction"); tmp_msg = generateCtrlCmdMsg(0.0); } TrajectoryPoint p2; @@ -320,9 +313,7 @@ boost::optional PurePursuitLateralController::generatePredictedTraje tmp_msg = generateCtrlCmdMsg(pp_output->curvature); predicted_trajectory.points.at(i).longitudinal_velocity_mps = pp_output->velocity; } else { - RCLCPP_WARN_THROTTLE( - node_->get_logger(), *node_->get_clock(), 5000, - "failed to solve pure_pursuit for prediction"); + RCLCPP_WARN_THROTTLE(logger_, *clock_, 5000, "failed to solve pure_pursuit for prediction"); tmp_msg = generateCtrlCmdMsg(0.0); } predicted_trajectory.points.push_back( @@ -363,7 +354,7 @@ LateralOutput PurePursuitLateralController::run(const InputData & input_data) // calculate predicted trajectory with iterative calculation const auto predicted_trajectory = generatePredictedTrajectory(); if (!predicted_trajectory) { - RCLCPP_ERROR(node_->get_logger(), "Failed to generate predicted trajectory."); + RCLCPP_ERROR(logger_, "Failed to generate predicted trajectory."); } else { pub_predicted_trajectory_->publish(*predicted_trajectory); } @@ -389,8 +380,7 @@ AckermannLateralCommand PurePursuitLateralController::generateOutputControlCmd() publishDebugMarker(); } else { RCLCPP_WARN_THROTTLE( - node_->get_logger(), *node_->get_clock(), 5000, - "failed to solve pure_pursuit for control command calculation"); + logger_, *clock_, 5000, "failed to solve pure_pursuit for control command calculation"); if (prev_cmd_) { output_cmd = *prev_cmd_; } else { @@ -406,7 +396,7 @@ AckermannLateralCommand PurePursuitLateralController::generateCtrlCmdMsg( const double tmp_steering = planning_utils::convertCurvatureToSteeringAngle(param_.wheel_base, target_curvature); AckermannLateralCommand cmd; - cmd.stamp = node_->get_clock()->now(); + cmd.stamp = clock_->now(); cmd.steering_tire_angle = static_cast( std::min(std::max(tmp_steering, -param_.max_steering_angle), param_.max_steering_angle)); @@ -428,8 +418,7 @@ boost::optional PurePursuitLateralController::calcTargetCurvature( { // Ignore invalid trajectory if (trajectory_resampled_->points.size() < 3) { - RCLCPP_WARN_THROTTLE( - node_->get_logger(), *node_->get_clock(), 5000, "received path size is < 3, ignored"); + RCLCPP_WARN_THROTTLE(logger_, *clock_, 5000, "received path size is < 3, ignored"); return {}; } @@ -438,7 +427,7 @@ boost::optional PurePursuitLateralController::calcTargetCurvature( const auto closest_idx_result = motion_utils::findNearestIndex(output_tp_array_, pose, 3.0, M_PI_4); if (!closest_idx_result) { - RCLCPP_ERROR(node_->get_logger(), "cannot find closest waypoint"); + RCLCPP_ERROR(logger_, "cannot find closest waypoint"); return {}; } From 9ac0ea4f7967587d0d5cac21e43274443302165d Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:35:51 +0900 Subject: [PATCH 108/547] fix(imu_corrector): fix build failure (#4867) * fix(imu_corrector): fix build failure Signed-off-by: kminoda * style(pre-commit): autofix * support galactic Signed-off-by: kminoda * fix Signed-off-by: kminoda * add package.xml Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sensing/imu_corrector/package.xml | 1 + sensing/imu_corrector/src/imu_corrector_core.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/sensing/imu_corrector/package.xml b/sensing/imu_corrector/package.xml index 67cb5c1596040..7466d158da2df 100644 --- a/sensing/imu_corrector/package.xml +++ b/sensing/imu_corrector/package.xml @@ -17,6 +17,7 @@ rclcpp_components sensor_msgs tf2 + tf2_geometry_msgs tf2_ros tier4_autoware_utils diff --git a/sensing/imu_corrector/src/imu_corrector_core.cpp b/sensing/imu_corrector/src/imu_corrector_core.cpp index e8a7040ce301f..a26c64925729c 100644 --- a/sensing/imu_corrector/src/imu_corrector_core.cpp +++ b/sensing/imu_corrector/src/imu_corrector_core.cpp @@ -14,6 +14,11 @@ #include "imu_corrector_core.hpp" +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif #include #include From 532c050d132ae7e438a005b9ad2c053326f921b2 Mon Sep 17 00:00:00 2001 From: Tomohito ANDO Date: Mon, 4 Sep 2023 17:57:17 +0900 Subject: [PATCH 109/547] chore(traffic_light_arbiter): use THROTTLE to prevent obscuring other logs (#4870) chore(traffic_light_arbiter): update warning log to use THROTTLE Signed-off-by: Tomohito Ando --- .../traffic_light_arbiter/src/traffic_light_arbiter.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp b/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp index fadcb767f347b..3838e93113066 100644 --- a/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp +++ b/perception/traffic_light_arbiter/src/traffic_light_arbiter.cpp @@ -112,7 +112,8 @@ void TrafficLightArbiter::arbitrateAndPublish(const builtin_interfaces::msg::Tim std::unordered_map> regulatory_element_signals_map; if (map_regulatory_elements_set_ == nullptr) { - RCLCPP_WARN(get_logger(), "Received traffic signal messages before a map"); + RCLCPP_WARN_THROTTLE( + get_logger(), *get_clock(), 5000, "Received traffic signal messages before a map"); return; } @@ -127,8 +128,9 @@ void TrafficLightArbiter::arbitrateAndPublish(const builtin_interfaces::msg::Tim auto add_signal_function = [&](const auto & signal, bool priority) { const auto id = signal.traffic_signal_id; if (!map_regulatory_elements_set_->count(id)) { - RCLCPP_WARN( - get_logger(), "Received a traffic signal not present in the current map (%lu)", id); + RCLCPP_WARN_THROTTLE( + get_logger(), *get_clock(), 5000, + "Received a traffic signal not present in the current map (%lu)", id); return; } From 58a0007aa6d9d4c0b3bb6769e0cc80fd699f0bb7 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:41:23 +0900 Subject: [PATCH 110/547] fix(planner_manager): fix module swapping logic (#4845) Signed-off-by: satoshi-ota --- .../src/planner_manager.cpp | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index af7347a2db4d0..7708fd8ac116e 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -526,11 +526,32 @@ BehaviorModuleOutput PlannerManager::runApprovedModules(const std::shared_ptrisKeepLast() && getManager(b)->isKeepLast(); - }); + const auto keep_last_module_cond = [this](const auto & m) { + return getManager(m)->isKeepLast(); + }; + move_to_end(approved_module_ptrs_, keep_last_module_cond); } // lock approved modules besides last one @@ -641,24 +662,28 @@ BehaviorModuleOutput PlannerManager::runApprovedModules(const std::shared_ptrgetCurrentStatus() != ModuleStatus::SUCCESS; }); + const auto success_module_cond = [](const auto & m) { + return m->getCurrentStatus() == ModuleStatus::SUCCESS; + }; + move_to_end(approved_module_ptrs_, success_module_cond); + + const auto itr = + std::find_if(approved_module_ptrs_.begin(), approved_module_ptrs_.end(), success_module_cond); const auto success_lane_change = std::any_of( - success_module_itr, approved_module_ptrs_.end(), + itr, approved_module_ptrs_.end(), [](const auto & m) { return m->name().find("lane_change") != std::string::npos; }); if (success_lane_change) { root_lanelet_ = updateRootLanelet(data); } - std::for_each(success_module_itr, approved_module_ptrs_.end(), [&](auto & m) { + std::for_each(itr, approved_module_ptrs_.end(), [&](auto & m) { debug_info_.emplace_back(m, Action::DELETE, "From Approved"); deleteExpiredModules(m); }); - approved_module_ptrs_.erase(success_module_itr, approved_module_ptrs_.end()); + approved_module_ptrs_.erase(itr, approved_module_ptrs_.end()); std::for_each( manager_ptrs_.begin(), manager_ptrs_.end(), [](const auto & m) { m->updateObserver(); }); From 51af06846828fa6c5e52c2760d1b81ec86273347 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 4 Sep 2023 20:06:06 +0900 Subject: [PATCH 111/547] refactor(goal_planner): refactor isAllowedGoalModification (#4856) * ractor(goal_planner): refactor isAllowedGoalModification Signed-off-by: kosuke55 * fix build Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../scene_module/goal_planner/manager.hpp | 2 -- .../utils/goal_planner/util.hpp | 6 ++--- .../goal_planner/goal_planner_module.cpp | 24 +++++++------------ .../src/scene_module/goal_planner/manager.cpp | 11 +++------ .../src/utils/goal_planner/util.cpp | 19 ++++++--------- 5 files changed, 21 insertions(+), 41 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp index 47ad47aecb9b3..4a7eecfc68caf 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/manager.hpp @@ -48,8 +48,6 @@ class GoalPlannerModuleManager : public SceneModuleManagerInterface private: std::shared_ptr parameters_; - - bool left_side_parking_; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp index 3dde96e813aad..29a4a8efa719f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp @@ -51,10 +51,8 @@ PredictedObjects filterObjectsByLateralDistance( const Pose & ego_pose, const double vehicle_width, const PredictedObjects & objects, const double distance_thresh, const bool filter_inside); -bool isAllowedGoalModification( - const std::shared_ptr & route_handler, const bool left_side_parking); -bool checkOriginalGoalIsInShoulder( - const std::shared_ptr & route_handler, const bool left_side_parking); +bool isAllowedGoalModification(const std::shared_ptr & route_handler); +bool checkOriginalGoalIsInShoulder(const std::shared_ptr & route_handler); // debug MarkerArray createPullOverAreaMarkerArray( diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index c7da1a75f3df2..9e7b101b8c4ab 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -309,10 +309,9 @@ bool GoalPlannerModule::isExecutionRequested() const // if goal arc coordinates can be calculated, check if goal is in request_length const double self_to_goal_arc_length = utils::getSignedDistance(current_pose, goal_pose, current_lanes); - const double request_length = - goal_planner_utils::isAllowedGoalModification(route_handler, left_side_parking_) - ? calcModuleRequestLength() - : parameters_->minimum_request_length; + const double request_length = goal_planner_utils::isAllowedGoalModification(route_handler) + ? calcModuleRequestLength() + : parameters_->minimum_request_length; if (self_to_goal_arc_length < 0.0 || self_to_goal_arc_length > request_length) { // if current position is far from goal or behind goal, do not execute goal_planner return false; @@ -321,7 +320,7 @@ bool GoalPlannerModule::isExecutionRequested() const // if goal modification is not allowed // 1) goal_pose is in current_lanes, plan path to the original fixed goal // 2) goal_pose is NOT in current_lanes, do not execute goal_planner - if (!goal_planner_utils::isAllowedGoalModification(route_handler, left_side_parking_)) { + if (!goal_planner_utils::isAllowedGoalModification(route_handler)) { return goal_is_in_current_lanes; } @@ -409,8 +408,7 @@ ModuleStatus GoalPlannerModule::updateState() { // finish module only when the goal is fixed if ( - !goal_planner_utils::isAllowedGoalModification( - planner_data_->route_handler, left_side_parking_) && + !goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler) && hasFinishedGoalPlanner()) { return ModuleStatus::SUCCESS; } @@ -508,7 +506,7 @@ void GoalPlannerModule::generateGoalCandidates() // calculate goal candidates const Pose goal_pose = route_handler->getGoalPose(); refined_goal_pose_ = calcRefinedGoal(goal_pose); - if (goal_planner_utils::isAllowedGoalModification(route_handler, left_side_parking_)) { + if (goal_planner_utils::isAllowedGoalModification(route_handler)) { goal_searcher_->setPlannerData(planner_data_); goal_candidates_ = goal_searcher_->search(refined_goal_pose_); } else { @@ -528,8 +526,7 @@ BehaviorModuleOutput GoalPlannerModule::plan() path_reference_ = getPreviousModuleOutput().reference_path; - if (goal_planner_utils::isAllowedGoalModification( - planner_data_->route_handler, left_side_parking_)) { + if (goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { return planWithGoalModification(); } else { fixed_goal_planner_->setPreviousModuleOutput(getPreviousModuleOutput()); @@ -853,8 +850,7 @@ BehaviorModuleOutput GoalPlannerModule::planWaitingApproval() path_reference_ = getPreviousModuleOutput().reference_path; - if (goal_planner_utils::isAllowedGoalModification( - planner_data_->route_handler, left_side_parking_)) { + if (goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { return planWaitingApprovalWithGoalModification(); } else { fixed_goal_planner_->setPreviousModuleOutput(getPreviousModuleOutput()); @@ -1457,9 +1453,7 @@ void GoalPlannerModule::setDebugData() } tier4_autoware_utils::appendMarkerArray(added, &debug_marker_); }; - - if (goal_planner_utils::isAllowedGoalModification( - planner_data_->route_handler, left_side_parking_)) { + if (goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { // Visualize pull over areas const auto color = status_.has_decided_path ? createMarkerColor(1.0, 1.0, 0.0, 0.999) // yellow : createMarkerColor(0.0, 1.0, 0.0, 0.999); // green diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index bcee6aeaf0c36..209b12c42537c 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -241,8 +241,6 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( } parameters_ = std::make_shared(p); - - left_side_parking_ = parameters_->parking_policy == ParkingPolicy::LEFT_SIDE; } void GoalPlannerModuleManager::updateModuleParams( @@ -263,8 +261,7 @@ bool GoalPlannerModuleManager::isAlwaysExecutableModule() const { // enable AlwaysExecutable whenever goal modification is not allowed // because only minor path refinements are made for fixed goals - if (!goal_planner_utils::isAllowedGoalModification( - planner_data_->route_handler, left_side_parking_)) { + if (!goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { return true; } @@ -279,8 +276,7 @@ bool GoalPlannerModuleManager::isSimultaneousExecutableAsApprovedModule() const // enable SimultaneousExecutable whenever goal modification is not allowed // because only minor path refinements are made for fixed goals - if (!goal_planner_utils::isAllowedGoalModification( - planner_data_->route_handler, left_side_parking_)) { + if (!goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { return true; } @@ -295,8 +291,7 @@ bool GoalPlannerModuleManager::isSimultaneousExecutableAsCandidateModule() const // enable SimultaneousExecutable whenever goal modification is not allowed // because only minor path refinements are made for fixed goals - if (!goal_planner_utils::isAllowedGoalModification( - planner_data_->route_handler, left_side_parking_)) { + if (!goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { return true; } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index 19b6bd27ea07c..01fc4b182e211 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -181,25 +181,20 @@ MarkerArray createGoalCandidatesMarkerArray( return marker_array; } -bool isAllowedGoalModification( - const std::shared_ptr & route_handler, const bool left_side_parking) +bool isAllowedGoalModification(const std::shared_ptr & route_handler) { - return route_handler->isAllowedGoalModification() || - checkOriginalGoalIsInShoulder(route_handler, left_side_parking); + return route_handler->isAllowedGoalModification() || checkOriginalGoalIsInShoulder(route_handler); } -bool checkOriginalGoalIsInShoulder( - const std::shared_ptr & route_handler, const bool left_side_parking) +bool checkOriginalGoalIsInShoulder(const std::shared_ptr & route_handler) { const Pose & goal_pose = route_handler->getGoalPose(); + const auto shoulder_lanes = route_handler->getShoulderLanelets(); - const lanelet::ConstLanelets pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(route_handler), left_side_parking); - lanelet::ConstLanelet target_lane{}; - lanelet::utils::query::getClosestLanelet(pull_over_lanes, goal_pose, &target_lane); + lanelet::ConstLanelet closest_shoulder_lane{}; + lanelet::utils::query::getClosestLanelet(shoulder_lanes, goal_pose, &closest_shoulder_lane); - return route_handler->isShoulderLanelet(target_lane) && - lanelet::utils::isInLanelet(goal_pose, target_lane, 0.1); + return lanelet::utils::isInLanelet(goal_pose, closest_shoulder_lane, 0.1); } } // namespace goal_planner_utils From fa8da4c5f161c55ea9fbd2e248cbcdbab3d80f97 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Mon, 4 Sep 2023 20:27:38 +0900 Subject: [PATCH 112/547] fix(crosswalk): set large minus value in rtc distance if the ego already passed crosswalk (#4869) Signed-off-by: satoshi-ota --- .../src/scene_crosswalk.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index 7adde028f59c0..feb6e42587196 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -231,9 +231,12 @@ bool CrosswalkModule::modifyPathVelocity(PathWithLaneId * path, StopReason * sto // Calculate stop point with margin const auto p_stop_line = getStopPointWithMargin(*path, path_intersects); - // TODO(murooka) add a guard of p_stop_line - const auto default_stop_pose = toStdOptional( - calcLongitudinalOffsetPose(path->points, p_stop_line->first, p_stop_line->second)); + + std::optional default_stop_pose = std::nullopt; + if (p_stop_line.has_value()) { + default_stop_pose = toStdOptional( + calcLongitudinalOffsetPose(path->points, p_stop_line->first, p_stop_line->second)); + } // Resample path sparsely for less computation cost constexpr double resample_interval = 4.0; @@ -1068,6 +1071,8 @@ void CrosswalkModule::setDistanceToStop( const auto & ego_pos = planner_data_->current_odometry->pose.position; const double dist_ego2stop = calcSignedArcLength(ego_path.points, ego_pos, *stop_pos); setDistance(dist_ego2stop); + } else { + setDistance(std::numeric_limits::lowest()); } } From d339c0960fd58f454a21a70e244a54a5e8a54a12 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 4 Sep 2023 20:52:09 +0900 Subject: [PATCH 113/547] feat(geography_utils): add projection in geography_utils (#4833) * feat(gnss_poser): Subscribe map_projector_info Signed-off-by: kminoda * style(pre-commit): autofix * update readme Signed-off-by: kminoda * style(pre-commit): autofix * small fix Signed-off-by: kminoda * update commetn Signed-off-by: kminoda * style(pre-commit): autofix * add local cartesian Signed-off-by: kminoda * update launch file Signed-off-by: kminoda * style(pre-commit): autofix * fix Signed-off-by: kminoda * create new function for conversion of height Signed-off-by: kminoda * minor update Signed-off-by: kminoda * style(pre-commit): autofix * update Signed-off-by: kminoda * rename Signed-off-by: kminoda * remove unnecessary include Signed-off-by: kminoda * add projection in geography_utils Signed-off-by: kminoda * update projection.cpp Signed-off-by: kminoda * use projection in default_ad_api Signed-off-by: kminoda * style(pre-commit): autofix * Feat/gnss poser/subscribe map projector info (#24) * fix map origin Signed-off-by: kminoda * style(pre-commit): autofix * remove config file Signed-off-by: kminoda * rfix readme Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * use projection.cpp in gnss_poser Signed-off-by: kminoda * style(pre-commit): autofix * remove unnecessary files Signed-off-by: kminoda * remove unnecessary parts Signed-off-by: kminoda * remove lanelet2 dependency from default_ad_api Signed-off-by: kminoda * remove geography_utils.hpp Signed-off-by: kminoda * add get_lanelet2_projector and use that in whole system Signed-off-by: kminoda * style(pre-commit): autofix * remove unnecessary parts Signed-off-by: kminoda * revert using Point Signed-off-by: kminoda * style(pre-commit): autofix * remove unnecessary files Signed-off-by: kminoda * update test Signed-off-by: kminoda * update comment Signed-off-by: kminoda * style(pre-commit): autofix * style(pre-commit): autofix * use constant string instead Signed-off-by: kminoda * use reference Signed-off-by: kminoda * fix test name Signed-off-by: kminoda * fix bug Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Yamato Ando --- common/geography_utils/CMakeLists.txt | 1 + .../include/geography_utils/height.hpp | 3 - .../include/geography_utils/projection.hpp | 33 ++++ common/geography_utils/package.xml | 2 + common/geography_utils/src/projection.cpp | 95 +++++++++++ .../test/test_geography_utils.cpp | 44 ++--- common/geography_utils/test/test_height.cpp | 16 +- .../geography_utils/test/test_projection.cpp | 161 ++++++++++++++++++ sensing/gnss_poser/CMakeLists.txt | 2 - .../gnss_poser/include/gnss_poser/convert.hpp | 145 ---------------- .../include/gnss_poser/gnss_poser_core.hpp | 7 - sensing/gnss_poser/src/gnss_poser_core.cpp | 43 ++--- system/default_ad_api/package.xml | 2 +- system/default_ad_api/src/vehicle.cpp | 40 ++--- system/default_ad_api/src/vehicle.hpp | 6 - 15 files changed, 330 insertions(+), 270 deletions(-) create mode 100644 common/geography_utils/include/geography_utils/projection.hpp create mode 100644 common/geography_utils/src/projection.cpp rename sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp => common/geography_utils/test/test_geography_utils.cpp (51%) create mode 100644 common/geography_utils/test/test_projection.cpp delete mode 100644 sensing/gnss_poser/include/gnss_poser/convert.hpp diff --git a/common/geography_utils/CMakeLists.txt b/common/geography_utils/CMakeLists.txt index feb49c42306fe..b705e819c059b 100644 --- a/common/geography_utils/CMakeLists.txt +++ b/common/geography_utils/CMakeLists.txt @@ -14,6 +14,7 @@ find_library(GeographicLib_LIBRARIES NAMES Geographic) ament_auto_add_library(geography_utils SHARED src/height.cpp + src/projection.cpp src/lanelet2_projector.cpp ) diff --git a/common/geography_utils/include/geography_utils/height.hpp b/common/geography_utils/include/geography_utils/height.hpp index 1921e79699970..8bcda41f84cda 100644 --- a/common/geography_utils/include/geography_utils/height.hpp +++ b/common/geography_utils/include/geography_utils/height.hpp @@ -15,10 +15,7 @@ #ifndef GEOGRAPHY_UTILS__HEIGHT_HPP_ #define GEOGRAPHY_UTILS__HEIGHT_HPP_ -#include -#include #include -#include namespace geography_utils { diff --git a/common/geography_utils/include/geography_utils/projection.hpp b/common/geography_utils/include/geography_utils/projection.hpp new file mode 100644 index 0000000000000..be616d854a466 --- /dev/null +++ b/common/geography_utils/include/geography_utils/projection.hpp @@ -0,0 +1,33 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GEOGRAPHY_UTILS__PROJECTION_HPP_ +#define GEOGRAPHY_UTILS__PROJECTION_HPP_ + +#include +#include +#include + +namespace geography_utils +{ +using MapProjectorInfo = tier4_map_msgs::msg::MapProjectorInfo; +using GeoPoint = geographic_msgs::msg::GeoPoint; +using LocalPoint = geometry_msgs::msg::Point; + +LocalPoint project_forward(const GeoPoint & geo_point, const MapProjectorInfo & projector_info); +GeoPoint project_reverse(const LocalPoint & local_point, const MapProjectorInfo & projector_info); + +} // namespace geography_utils + +#endif // GEOGRAPHY_UTILS__PROJECTION_HPP_ diff --git a/common/geography_utils/package.xml b/common/geography_utils/package.xml index b9b31b0086646..414364e36ad29 100644 --- a/common/geography_utils/package.xml +++ b/common/geography_utils/package.xml @@ -10,7 +10,9 @@ ament_cmake_auto autoware_cmake + geographic_msgs geographiclib + geometry_msgs lanelet2_extension lanelet2_io tier4_map_msgs diff --git a/common/geography_utils/src/projection.cpp b/common/geography_utils/src/projection.cpp new file mode 100644 index 0000000000000..ac9ce19052702 --- /dev/null +++ b/common/geography_utils/src/projection.cpp @@ -0,0 +1,95 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +namespace geography_utils +{ + +Eigen::Vector3d to_basic_point_3d_pt(const LocalPoint src) +{ + Eigen::Vector3d dst; + dst.x() = src.x; + dst.y() = src.y; + dst.z() = src.z; + return dst; +} + +LocalPoint project_forward(const GeoPoint & geo_point, const MapProjectorInfo & projector_info) +{ + std::unique_ptr projector = get_lanelet2_projector(projector_info); + lanelet::GPSPoint position{geo_point.latitude, geo_point.longitude, geo_point.altitude}; + + lanelet::BasicPoint3d projected_local_point; + if (projector_info.projector_type == MapProjectorInfo::MGRS) { + const int mgrs_precision = 9; // set precision as 100 micro meter + const auto mgrs_projector = dynamic_cast(projector.get()); + + // project x and y using projector + // note that the altitude is ignored in MGRS projection conventionally + projected_local_point = mgrs_projector->forward(position, mgrs_precision); + } else { + // project x and y using projector + // note that the original projector such as UTM projector does not compensate for the altitude + // offset + projected_local_point = projector->forward(position); + + // correct z based on the map origin + // note that the converted altitude in local point is in the same vertical datum as the geo + // point + projected_local_point.z() = geo_point.altitude - projector_info.map_origin.altitude; + } + + LocalPoint local_point; + local_point.x = projected_local_point.x(); + local_point.y = projected_local_point.y(); + local_point.z = projected_local_point.z(); + + return local_point; +} + +GeoPoint project_reverse(const LocalPoint & local_point, const MapProjectorInfo & projector_info) +{ + std::unique_ptr projector = get_lanelet2_projector(projector_info); + + lanelet::GPSPoint projected_gps_point; + if (projector_info.projector_type == MapProjectorInfo::MGRS) { + const auto mgrs_projector = dynamic_cast(projector.get()); + // project latitude and longitude using projector + // note that the z is ignored in MGRS projection conventionally + projected_gps_point = + mgrs_projector->reverse(to_basic_point_3d_pt(local_point), projector_info.mgrs_grid); + } else { + // project latitude and longitude using projector + // note that the original projector such as UTM projector does not compensate for the altitude + // offset + projected_gps_point = projector->reverse(to_basic_point_3d_pt(local_point)); + + // correct altitude based on the map origin + // note that the converted altitude in local point is in the same vertical datum as the geo + // point + projected_gps_point.ele = local_point.z + projector_info.map_origin.altitude; + } + + GeoPoint geo_point; + geo_point.latitude = projected_gps_point.lat; + geo_point.longitude = projected_gps_point.lon; + geo_point.altitude = projected_gps_point.ele; + return geo_point; +} + +} // namespace geography_utils diff --git a/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp b/common/geography_utils/test/test_geography_utils.cpp similarity index 51% rename from sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp rename to common/geography_utils/test/test_geography_utils.cpp index acd21a83d96e4..480e17b8f49a4 100644 --- a/sensing/gnss_poser/include/gnss_poser/gnss_stat.hpp +++ b/common/geography_utils/test/test_geography_utils.cpp @@ -1,4 +1,4 @@ -// Copyright 2020 Tier IV, Inc. +// Copyright 2023 TIER IV, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,38 +11,16 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#ifndef GNSS_POSER__GNSS_STAT_HPP_ -#define GNSS_POSER__GNSS_STAT_HPP_ -#include +#include "geography_utils/height.hpp" +#include "geography_utils/lanelet2_projector.hpp" +#include "geography_utils/projection.hpp" -namespace gnss_poser -{ -struct GNSSStat -{ - GNSSStat() - : east_north_up(true), - zone(0), - mgrs_zone(""), - x(0), - y(0), - z(0), - latitude(0), - longitude(0), - altitude(0) - { - } +#include - bool east_north_up; - int zone; - std::string mgrs_zone; - double x; - double y; - double z; - double latitude; - double longitude; - double altitude; -}; -} // namespace gnss_poser - -#endif // GNSS_POSER__GNSS_STAT_HPP_ +int main(int argc, char * argv[]) +{ + testing::InitGoogleTest(&argc, argv); + bool result = RUN_ALL_TESTS(); + return result; +} diff --git a/common/geography_utils/test/test_height.cpp b/common/geography_utils/test/test_height.cpp index 3d8a4c9c203fe..15297089ee131 100644 --- a/common/geography_utils/test/test_height.cpp +++ b/common/geography_utils/test/test_height.cpp @@ -20,7 +20,7 @@ #include // Test case to verify if same source and target datums return original height -TEST(Tier4GeographyUtils, SameSourceTargetDatum) +TEST(GeographyUtils, SameSourceTargetDatum) { const double height = 10.0; const double latitude = 35.0; @@ -34,7 +34,7 @@ TEST(Tier4GeographyUtils, SameSourceTargetDatum) } // Test case to verify valid source and target datums -TEST(Tier4GeographyUtils, ValidSourceTargetDatum) +TEST(GeographyUtils, ValidSourceTargetDatum) { // Calculated with // https://www.unavco.org/software/geodetic-utilities/geoid-height-calculator/geoid-height-calculator.html @@ -50,7 +50,7 @@ TEST(Tier4GeographyUtils, ValidSourceTargetDatum) } // Test case to verify invalid source and target datums -TEST(Tier4GeographyUtils, InvalidSourceTargetDatum) +TEST(GeographyUtils, InvalidSourceTargetDatum) { const double height = 10.0; const double latitude = 35.0; @@ -62,7 +62,7 @@ TEST(Tier4GeographyUtils, InvalidSourceTargetDatum) } // Test case to verify invalid source datums -TEST(Tier4GeographyUtils, InvalidSourceDatum) +TEST(GeographyUtils, InvalidSourceDatum) { const double height = 10.0; const double latitude = 35.0; @@ -74,7 +74,7 @@ TEST(Tier4GeographyUtils, InvalidSourceDatum) } // Test case to verify invalid target datums -TEST(Tier4GeographyUtils, InvalidTargetDatum) +TEST(GeographyUtils, InvalidTargetDatum) { const double height = 10.0; const double latitude = 35.0; @@ -84,9 +84,3 @@ TEST(Tier4GeographyUtils, InvalidTargetDatum) geography_utils::convert_height(height, latitude, longitude, "WGS84", "INVALID2"), std::invalid_argument); } - -int main(int argc, char ** argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/common/geography_utils/test/test_projection.cpp b/common/geography_utils/test/test_projection.cpp new file mode 100644 index 0000000000000..74ffb616cde6f --- /dev/null +++ b/common/geography_utils/test/test_projection.cpp @@ -0,0 +1,161 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include +#include + +TEST(GeographyUtilsProjection, ProjectForwardToMGRS) +{ + // source point + geographic_msgs::msg::GeoPoint geo_point; + geo_point.latitude = 35.62426; + geo_point.longitude = 139.74252; + geo_point.altitude = 10.0; + + // target point + geometry_msgs::msg::Point local_point; + local_point.x = 86128.0; + local_point.y = 43002.0; + local_point.z = 10.0; + + // projector info + tier4_map_msgs::msg::MapProjectorInfo projector_info; + projector_info.projector_type = tier4_map_msgs::msg::MapProjectorInfo::MGRS; + projector_info.mgrs_grid = "54SUE"; + projector_info.vertical_datum = tier4_map_msgs::msg::MapProjectorInfo::WGS84; + + // conversion + const geometry_msgs::msg::Point converted_point = + geography_utils::project_forward(geo_point, projector_info); + + EXPECT_NEAR(converted_point.x, local_point.x, 1.0); + EXPECT_NEAR(converted_point.y, local_point.y, 1.0); + EXPECT_NEAR(converted_point.z, local_point.z, 1.0); +} + +TEST(GeographyUtilsProjection, ProjectReverseFromMGRS) +{ + // source point + geometry_msgs::msg::Point local_point; + local_point.x = 86128.0; + local_point.y = 43002.0; + local_point.z = 10.0; + + // target point + geographic_msgs::msg::GeoPoint geo_point; + geo_point.latitude = 35.62426; + geo_point.longitude = 139.74252; + geo_point.altitude = 10.0; + + // projector info + tier4_map_msgs::msg::MapProjectorInfo projector_info; + projector_info.projector_type = tier4_map_msgs::msg::MapProjectorInfo::MGRS; + projector_info.mgrs_grid = "54SUE"; + projector_info.vertical_datum = tier4_map_msgs::msg::MapProjectorInfo::WGS84; + + // conversion + const geographic_msgs::msg::GeoPoint converted_point = + geography_utils::project_reverse(local_point, projector_info); + + EXPECT_NEAR(converted_point.latitude, geo_point.latitude, 0.0001); + EXPECT_NEAR(converted_point.longitude, geo_point.longitude, 0.0001); + EXPECT_NEAR(converted_point.altitude, geo_point.altitude, 0.0001); +} + +TEST(GeographyUtilsProjection, ProjectForwardAndReverseMGRS) +{ + // source point + geographic_msgs::msg::GeoPoint geo_point; + geo_point.latitude = 35.62426; + geo_point.longitude = 139.74252; + geo_point.altitude = 10.0; + + // projector info + tier4_map_msgs::msg::MapProjectorInfo projector_info; + projector_info.projector_type = tier4_map_msgs::msg::MapProjectorInfo::MGRS; + projector_info.mgrs_grid = "54SUE"; + projector_info.vertical_datum = tier4_map_msgs::msg::MapProjectorInfo::WGS84; + + // conversion + const geometry_msgs::msg::Point converted_local_point = + geography_utils::project_forward(geo_point, projector_info); + const geographic_msgs::msg::GeoPoint converted_geo_point = + geography_utils::project_reverse(converted_local_point, projector_info); + + EXPECT_NEAR(converted_geo_point.latitude, geo_point.latitude, 0.0001); + EXPECT_NEAR(converted_geo_point.longitude, geo_point.longitude, 0.0001); + EXPECT_NEAR(converted_geo_point.altitude, geo_point.altitude, 0.0001); +} + +TEST(GeographyUtilsProjection, ProjectForwardToLocalCartesianUTMOrigin) +{ + // source point + geographic_msgs::msg::GeoPoint geo_point; + geo_point.latitude = 35.62406; + geo_point.longitude = 139.74252; + geo_point.altitude = 10.0; + + // target point + geometry_msgs::msg::Point local_point; + local_point.x = 0.0; + local_point.y = -22.18; + local_point.z = 20.0; + + // projector info + tier4_map_msgs::msg::MapProjectorInfo projector_info; + projector_info.projector_type = tier4_map_msgs::msg::MapProjectorInfo::LOCAL_CARTESIAN_UTM; + projector_info.vertical_datum = tier4_map_msgs::msg::MapProjectorInfo::WGS84; + projector_info.map_origin.latitude = 35.62426; + projector_info.map_origin.longitude = 139.74252; + projector_info.map_origin.altitude = -10.0; + + // conversion + const geometry_msgs::msg::Point converted_point = + geography_utils::project_forward(geo_point, projector_info); + + EXPECT_NEAR(converted_point.x, local_point.x, 1.0); + EXPECT_NEAR(converted_point.y, local_point.y, 1.0); + EXPECT_NEAR(converted_point.z, local_point.z, 1.0); +} + +TEST(GeographyUtilsProjection, ProjectForwardAndReverseLocalCartesianUTMOrigin) +{ + // source point + geographic_msgs::msg::GeoPoint geo_point; + geo_point.latitude = 35.62426; + geo_point.longitude = 139.74252; + geo_point.altitude = 10.0; + + // projector info + tier4_map_msgs::msg::MapProjectorInfo projector_info; + projector_info.projector_type = tier4_map_msgs::msg::MapProjectorInfo::LOCAL_CARTESIAN_UTM; + projector_info.vertical_datum = tier4_map_msgs::msg::MapProjectorInfo::WGS84; + projector_info.map_origin.latitude = 35.0; + projector_info.map_origin.longitude = 139.0; + projector_info.map_origin.altitude = 0.0; + + // conversion + const geometry_msgs::msg::Point converted_local_point = + geography_utils::project_forward(geo_point, projector_info); + const geographic_msgs::msg::GeoPoint converted_geo_point = + geography_utils::project_reverse(converted_local_point, projector_info); + + EXPECT_NEAR(converted_geo_point.latitude, geo_point.latitude, 0.0001); + EXPECT_NEAR(converted_geo_point.longitude, geo_point.longitude, 0.0001); + EXPECT_NEAR(converted_geo_point.altitude, geo_point.altitude, 0.0001); +} diff --git a/sensing/gnss_poser/CMakeLists.txt b/sensing/gnss_poser/CMakeLists.txt index bd9886d376cad..167f2c51337ce 100644 --- a/sensing/gnss_poser/CMakeLists.txt +++ b/sensing/gnss_poser/CMakeLists.txt @@ -16,9 +16,7 @@ find_library(GeographicLib_LIBRARIES ) set(GNSS_POSER_HEADERS - include/gnss_poser/convert.hpp include/gnss_poser/gnss_poser_core.hpp - include/gnss_poser/gnss_stat.hpp ) ament_auto_add_library(gnss_poser_node SHARED diff --git a/sensing/gnss_poser/include/gnss_poser/convert.hpp b/sensing/gnss_poser/include/gnss_poser/convert.hpp deleted file mode 100644 index 5cd0767f28130..0000000000000 --- a/sensing/gnss_poser/include/gnss_poser/convert.hpp +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2020 Tier IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#ifndef GNSS_POSER__CONVERT_HPP_ -#define GNSS_POSER__CONVERT_HPP_ - -#include "gnss_poser/gnss_stat.hpp" - -#include -#include -#include -#include -#include - -#include -#include - -#include - -namespace gnss_poser -{ -enum class MGRSPrecision { - _10_KILO_METER = 1, - _1_KILO_METER = 2, - _100_METER = 3, - _10_METER = 4, - _1_METER = 5, - _100_MIllI_METER = 6, - _10_MIllI_METER = 7, - _1_MIllI_METER = 8, - _100MICRO_METER = 9, -}; - -GNSSStat NavSatFix2UTM( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, const rclcpp::Logger & logger, - const std::string & target_vertical_datum) -{ - GNSSStat utm; - - try { - GeographicLib::UTMUPS::Forward( - nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, utm.zone, utm.east_north_up, utm.x, - utm.y); - utm.z = geography_utils::convert_height( - nav_sat_fix_msg.altitude, nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, "WGS84", - target_vertical_datum); - utm.latitude = nav_sat_fix_msg.latitude; - utm.longitude = nav_sat_fix_msg.longitude; - utm.altitude = nav_sat_fix_msg.altitude; - } catch (const GeographicLib::GeographicErr & err) { - RCLCPP_ERROR_STREAM(logger, "Failed to convert from LLH to UTM" << err.what()); - } - return utm; -} - -GNSSStat NavSatFix2LocalCartesianUTM( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, - const geographic_msgs::msg::GeoPoint geo_point_origin, const rclcpp::Logger & logger, - const std::string & target_vertical_datum) -{ - GNSSStat utm_local; - try { - // origin of the local coordinate system in global frame - GNSSStat utm_origin; - GeographicLib::UTMUPS::Forward( - geo_point_origin.latitude, geo_point_origin.longitude, utm_origin.zone, - utm_origin.east_north_up, utm_origin.x, utm_origin.y); - - utm_origin.z = geography_utils::convert_height( - geo_point_origin.altitude, geo_point_origin.latitude, geo_point_origin.longitude, "WGS84", - target_vertical_datum); - - // individual coordinates of global coordinate system - double global_x = 0.0; - double global_y = 0.0; - GeographicLib::UTMUPS::Forward( - nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, utm_origin.zone, - utm_origin.east_north_up, global_x, global_y); - utm_local.latitude = nav_sat_fix_msg.latitude; - utm_local.longitude = nav_sat_fix_msg.longitude; - utm_local.altitude = nav_sat_fix_msg.altitude; - // individual coordinates of local coordinate system - utm_local.x = global_x - utm_origin.x; - utm_local.y = global_y - utm_origin.y; - utm_local.z = geography_utils::convert_height( - nav_sat_fix_msg.altitude, nav_sat_fix_msg.latitude, nav_sat_fix_msg.longitude, - "WGS84", target_vertical_datum) - - utm_origin.z; - } catch (const GeographicLib::GeographicErr & err) { - RCLCPP_ERROR_STREAM( - logger, "Failed to convert from LLH to UTM in local coordinates" << err.what()); - } - return utm_local; -} - -GNSSStat UTM2MGRS( - const GNSSStat & utm, const MGRSPrecision & precision, const rclcpp::Logger & logger) -{ - constexpr int GZD_ID_size = 5; // size of header like "53SPU" - - GNSSStat mgrs = utm; - try { - std::string mgrs_code; - GeographicLib::MGRS::Forward( - utm.zone, utm.east_north_up, utm.x, utm.y, utm.latitude, static_cast(precision), - mgrs_code); - mgrs.mgrs_zone = std::string(mgrs_code.substr(0, GZD_ID_size)); - mgrs.x = std::stod(mgrs_code.substr(GZD_ID_size, static_cast(precision))) * - std::pow( - 10, static_cast(MGRSPrecision::_1_METER) - - static_cast(precision)); // set unit as [m] - mgrs.y = std::stod(mgrs_code.substr( - GZD_ID_size + static_cast(precision), static_cast(precision))) * - std::pow( - 10, static_cast(MGRSPrecision::_1_METER) - - static_cast(precision)); // set unit as [m] - mgrs.z = utm.z; // TODO(ryo.watanabe) - } catch (const GeographicLib::GeographicErr & err) { - RCLCPP_ERROR_STREAM(logger, "Failed to convert from UTM to MGRS" << err.what()); - } - return mgrs; -} - -GNSSStat NavSatFix2MGRS( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, const MGRSPrecision & precision, - const rclcpp::Logger & logger, const std::string & vertical_datum) -{ - const auto utm = NavSatFix2UTM(nav_sat_fix_msg, logger, vertical_datum); - const auto mgrs = UTM2MGRS(utm, precision, logger); - return mgrs; -} - -} // namespace gnss_poser - -#endif // GNSS_POSER__CONVERT_HPP_ diff --git a/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp b/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp index 05acfa967eb96..2118d33bc4b30 100644 --- a/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp +++ b/sensing/gnss_poser/include/gnss_poser/gnss_poser_core.hpp @@ -14,9 +14,6 @@ #ifndef GNSS_POSER__GNSS_POSER_CORE_HPP_ #define GNSS_POSER__GNSS_POSER_CORE_HPP_ -#include "gnss_poser/convert.hpp" -#include "gnss_poser/gnss_stat.hpp" - #include #include #include @@ -59,10 +56,6 @@ class GNSSPoser : public rclcpp::Node bool isFixed(const sensor_msgs::msg::NavSatStatus & nav_sat_status_msg); bool canGetCovariance(const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg); - GNSSStat convert( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, - const MapProjectorInfo::Message & projector_info); - geometry_msgs::msg::Point getPosition(const GNSSStat & gnss_stat); geometry_msgs::msg::Point getMedianPosition( const boost::circular_buffer & position_buffer); geometry_msgs::msg::Point getAveragePosition( diff --git a/sensing/gnss_poser/src/gnss_poser_core.cpp b/sensing/gnss_poser/src/gnss_poser_core.cpp index 9cd5a3ad34098..1ae8bce2ed7f2 100644 --- a/sensing/gnss_poser/src/gnss_poser_core.cpp +++ b/sensing/gnss_poser/src/gnss_poser_core.cpp @@ -14,6 +14,9 @@ #include "gnss_poser/gnss_poser_core.hpp" +#include +#include + #include #include @@ -93,8 +96,14 @@ void GNSSPoser::callbackNavSatFix( } // get position - const auto gnss_stat = convert(*nav_sat_fix_msg_ptr, projector_info_); - const auto position = getPosition(gnss_stat); + geographic_msgs::msg::GeoPoint gps_point; + gps_point.latitude = nav_sat_fix_msg_ptr->latitude; + gps_point.longitude = nav_sat_fix_msg_ptr->longitude; + gps_point.altitude = nav_sat_fix_msg_ptr->altitude; + geometry_msgs::msg::Point position = geography_utils::project_forward(gps_point, projector_info_); + position.z = geography_utils::convert_height( + position.z, gps_point.latitude, gps_point.longitude, MapProjectorInfo::Message::WGS84, + projector_info_.vertical_datum); geometry_msgs::msg::Pose gnss_antenna_pose{}; @@ -197,36 +206,6 @@ bool GNSSPoser::canGetCovariance(const sensor_msgs::msg::NavSatFix & nav_sat_fix sensor_msgs::msg::NavSatFix::COVARIANCE_TYPE_UNKNOWN; } -GNSSStat GNSSPoser::convert( - const sensor_msgs::msg::NavSatFix & nav_sat_fix_msg, - const MapProjectorInfo::Message & map_projector_info) -{ - GNSSStat gnss_stat; - if (map_projector_info.projector_type == MapProjectorInfo::Message::LOCAL_CARTESIAN_UTM) { - gnss_stat = NavSatFix2LocalCartesianUTM( - nav_sat_fix_msg, map_projector_info.map_origin, this->get_logger(), - map_projector_info.vertical_datum); - } else if (map_projector_info.projector_type == MapProjectorInfo::Message::MGRS) { - gnss_stat = NavSatFix2MGRS( - nav_sat_fix_msg, MGRSPrecision::_100MICRO_METER, this->get_logger(), - map_projector_info.vertical_datum); - } else { - RCLCPP_ERROR_STREAM_THROTTLE( - this->get_logger(), *this->get_clock(), std::chrono::milliseconds(1000).count(), - "Unknown Projector type"); - } - return gnss_stat; -} - -geometry_msgs::msg::Point GNSSPoser::getPosition(const GNSSStat & gnss_stat) -{ - geometry_msgs::msg::Point point; - point.x = gnss_stat.x; - point.y = gnss_stat.y; - point.z = gnss_stat.z; - return point; -} - geometry_msgs::msg::Point GNSSPoser::getMedianPosition( const boost::circular_buffer & position_buffer) { diff --git a/system/default_ad_api/package.xml b/system/default_ad_api/package.xml index fd61d3c62e397..6a9a22f39cdc4 100644 --- a/system/default_ad_api/package.xml +++ b/system/default_ad_api/package.xml @@ -23,8 +23,8 @@ autoware_planning_msgs component_interface_specs component_interface_utils + geographic_msgs geography_utils - lanelet2_extension motion_utils nav_msgs rclcpp diff --git a/system/default_ad_api/src/vehicle.cpp b/system/default_ad_api/src/vehicle.cpp index c8f7df315c487..e25933e6ca2ad 100644 --- a/system/default_ad_api/src/vehicle.cpp +++ b/system/default_ad_api/src/vehicle.cpp @@ -15,6 +15,9 @@ #include "vehicle.hpp" #include +#include + +#include #include @@ -91,15 +94,6 @@ void VehicleNode::kinematic_state( kinematic_state_msgs_ = msg_ptr; } -Eigen::Vector3d VehicleNode::toBasicPoint3dPt(const geometry_msgs::msg::Point src) -{ - Eigen::Vector3d dst; - dst.x() = src.x; - dst.y() = src.y; - dst.z() = src.z; - return dst; -} - void VehicleNode::acceleration_status( const localization_interface::Acceleration::Message::ConstSharedPtr msg_ptr) { @@ -148,30 +142,16 @@ void VehicleNode::publish_kinematics() vehicle_kinematics.twist.header = kinematic_state_msgs_->header; vehicle_kinematics.twist.header.frame_id = kinematic_state_msgs_->child_frame_id; vehicle_kinematics.twist.twist = kinematic_state_msgs_->twist; - if (map_projector_info_->projector_type == MapProjectorInfo::MGRS) { - lanelet::GPSPoint projected_gps_point = lanelet::projection::MGRSProjector::reverse( - toBasicPoint3dPt(kinematic_state_msgs_->pose.pose.position), map_projector_info_->mgrs_grid); - vehicle_kinematics.geographic_pose.header = kinematic_state_msgs_->header; - vehicle_kinematics.geographic_pose.header.frame_id = "global"; - vehicle_kinematics.geographic_pose.position.latitude = projected_gps_point.lat; - vehicle_kinematics.geographic_pose.position.longitude = projected_gps_point.lon; - vehicle_kinematics.geographic_pose.position.altitude = geography_utils::convert_height( - projected_gps_point.ele, projected_gps_point.lat, projected_gps_point.lon, - map_projector_info_->vertical_datum, "WGS84"); - } else if (map_projector_info_->projector_type == MapProjectorInfo::LOCAL_CARTESIAN_UTM) { - lanelet::GPSPoint position{ - map_projector_info_->map_origin.latitude, map_projector_info_->map_origin.longitude}; - lanelet::Origin origin{position}; - lanelet::projection::UtmProjector projector{origin}; - lanelet::GPSPoint projected_gps_point = - projector.reverse(toBasicPoint3dPt(kinematic_state_msgs_->pose.pose.position)); + if (map_projector_info_->projector_type != MapProjectorInfo::LOCAL) { + const geographic_msgs::msg::GeoPoint projected_gps_point = geography_utils::project_reverse( + kinematic_state_msgs_->pose.pose.position, *map_projector_info_); vehicle_kinematics.geographic_pose.header = kinematic_state_msgs_->header; vehicle_kinematics.geographic_pose.header.frame_id = "global"; - vehicle_kinematics.geographic_pose.position.latitude = projected_gps_point.lat; - vehicle_kinematics.geographic_pose.position.longitude = projected_gps_point.lon; + vehicle_kinematics.geographic_pose.position.latitude = projected_gps_point.latitude; + vehicle_kinematics.geographic_pose.position.longitude = projected_gps_point.longitude; vehicle_kinematics.geographic_pose.position.altitude = geography_utils::convert_height( - projected_gps_point.ele, projected_gps_point.lat, projected_gps_point.lon, - map_projector_info_->vertical_datum, "WGS84"); + projected_gps_point.altitude, projected_gps_point.latitude, projected_gps_point.longitude, + map_projector_info_->vertical_datum, MapProjectorInfo::WGS84); } else { vehicle_kinematics.geographic_pose.position.latitude = std::numeric_limits::quiet_NaN(); vehicle_kinematics.geographic_pose.position.longitude = diff --git a/system/default_ad_api/src/vehicle.hpp b/system/default_ad_api/src/vehicle.hpp index 90f8b686206c5..56012152a132b 100644 --- a/system/default_ad_api/src/vehicle.hpp +++ b/system/default_ad_api/src/vehicle.hpp @@ -15,8 +15,6 @@ #ifndef VEHICLE_HPP_ #define VEHICLE_HPP_ -#include "lanelet2_extension/projection/mgrs_projector.hpp" - #include #include #include @@ -27,9 +25,6 @@ #include #include -#include -#include - #include // This file should be included after messages. @@ -83,7 +78,6 @@ class VehicleNode : public rclcpp::Node void publish_kinematics(); void publish_status(); void on_timer(); - Eigen::Vector3d toBasicPoint3dPt(const geometry_msgs::msg::Point src); }; } // namespace default_ad_api From 26086bfb92d4765180f946ee5d05391086fa1b9e Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Mon, 4 Sep 2023 21:32:23 +0900 Subject: [PATCH 114/547] fix(lane_change): skip parked object filtering (#4868) Signed-off-by: Fumiya Watanabe --- .../src/scene_module/lane_change/normal.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 28ceb63d88352..8deee76656dca 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -644,7 +644,6 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( const lanelet::ConstLanelets & target_backward_lanes) const { const auto current_pose = getEgoPose(); - const auto current_vel = getEgoVelocity(); const auto & route_handler = *getRouteHandler(); const auto & common_parameters = planner_data_->parameters; const auto & objects = *planner_data_->dynamic_object; @@ -667,12 +666,6 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( const auto target_backward_polygon = utils::lane_change::createPolygon( target_backward_lanes, 0.0, std::numeric_limits::max()); - // minimum distance to lane changing start point - const double t_prepare = common_parameters.lane_change_prepare_duration; - const double a_min = lane_change_parameters_->min_longitudinal_acc; - const double min_dist_to_lane_changing_start = std::max( - current_vel * t_prepare, current_vel * t_prepare + 0.5 * a_min * t_prepare * t_prepare); - LaneChangeTargetObjectIndices filtered_obj_indices; for (size_t i = 0; i < objects.objects.size(); ++i) { const auto & object = objects.objects.at(i); @@ -707,12 +700,8 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( // check if the object intersects with target lanes if (target_polygon && boost::geometry::intersects(target_polygon.value(), obj_polygon)) { - // ignore static parked object that are in front of the ego vehicle in target lanes - bool is_parked_object = - utils::lane_change::isParkedObject(target_path, route_handler, extended_object, 0.0, 0.0); - if (is_parked_object && min_dist_ego_to_obj > min_dist_to_lane_changing_start) { - continue; - } + // TODO(watanabe): ignore static parked object that are in front of the ego vehicle in target + // lanes filtered_obj_indices.target_lane.push_back(i); continue; From 80885667f6a4ff9586126209ad5e788697211e9c Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 4 Sep 2023 22:43:34 +0900 Subject: [PATCH 115/547] fix(behavior_path_planner): fix checkOriginalGoalIsInShoulder (#4878) Signed-off-by: kosuke55 --- .../behavior_path_planner/src/utils/goal_planner/util.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index 01fc4b182e211..8b48d1a867126 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -192,9 +192,11 @@ bool checkOriginalGoalIsInShoulder(const std::shared_ptr & route_h const auto shoulder_lanes = route_handler->getShoulderLanelets(); lanelet::ConstLanelet closest_shoulder_lane{}; - lanelet::utils::query::getClosestLanelet(shoulder_lanes, goal_pose, &closest_shoulder_lane); + if (lanelet::utils::query::getClosestLanelet(shoulder_lanes, goal_pose, &closest_shoulder_lane)) { + return lanelet::utils::isInLanelet(goal_pose, closest_shoulder_lane, 0.1); + } - return lanelet::utils::isInLanelet(goal_pose, closest_shoulder_lane, 0.1); + return false; } } // namespace goal_planner_utils From e68e8acac76e72901a5e4132145aa4385ce6d466 Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Tue, 5 Sep 2023 00:07:51 +0900 Subject: [PATCH 116/547] fix(radar_fusion_to_detected_object): fix parameter handling (#4837) * fix README Signed-off-by: scepter914 * fix bug Signed-off-by: scepter914 * style(pre-commit): autofix --------- Signed-off-by: scepter914 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- perception/radar_fusion_to_detected_object/README.md | 2 +- .../src/radar_fusion_to_detected_object.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/perception/radar_fusion_to_detected_object/README.md b/perception/radar_fusion_to_detected_object/README.md index 24eb4eb0e93c1..defc716a744d9 100644 --- a/perception/radar_fusion_to_detected_object/README.md +++ b/perception/radar_fusion_to_detected_object/README.md @@ -17,7 +17,7 @@ The document of core algorithm is [here](docs/algorithm.md) | :----------------------- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | | bounding_box_margin | double | The distance to extend the 2D bird's-eye view Bounding Box on each side. This distance is used as a threshold to find radar centroids falling inside the extended box. [m] | 2.0 | | split_threshold_velocity | double | The object's velocity threshold to decide to split for two objects from radar information (currently not implemented) [m/s] | 5.0 | -| threshold_yaw_diff | double | The yaw orientation threshold. If ∣*θob* − *θra*∣ < threshold × yaw*diff attached to radar information include estimated velocity, where*θob*is yaw angle from 3d detected object,*θra\_ is yaw angle from radar object. [rad] | 0.35 | +| threshold_yaw_diff | double | The yaw orientation threshold. If ∣ θ_ob − θ_ra ∣ < threshold × yaw_diff attached to radar information include estimated velocity, where*θob*is yaw angle from 3d detected object,\*θ_ra is yaw angle from radar object. [rad] | 0.35 | ### Weight parameters for velocity estimation diff --git a/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp b/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp index 4f0933e993de8..79e6db0a69a71 100644 --- a/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp +++ b/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp @@ -40,7 +40,7 @@ void RadarFusionToDetectedObject::setParam(const Param & param) // Radar fusion param param_.bounding_box_margin = param.bounding_box_margin; param_.split_threshold_velocity = param.split_threshold_velocity; - param_.threshold_yaw_diff = param.split_threshold_velocity; + param_.threshold_yaw_diff = param.threshold_yaw_diff; // Normalize weight param double sum_weight = param.velocity_weight_median + param.velocity_weight_min_distance + From feb34ed57a8919f7d2a17875b0699a307ade62a7 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 5 Sep 2023 01:18:55 +0900 Subject: [PATCH 117/547] docs(behavior_path_planner): fix table (#4875) fix table Signed-off-by: kyoichi-sugahara --- .../docs/behavior_path_planner_goal_planner_design.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index 99dadc959f5a5..0a78787c559d7 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -150,11 +150,11 @@ Generate footprints from ego-vehicle path points and determine obstacle collisio #### Parameters for object recognition based collision check -| Name | Unit | Type | Description | Default value | -| :----------------------------------------------------------- | :--- | :----- | :--------------------------------------------------------- | :------------ | ---------------------------------------------------------------------------------------------------------- | -| use_object_recognition | [-] | bool | flag whether to use object recognition for collision check | true | -| object_recognition_collision_check_margin | [m] | double | margin to calculate ego-vehicle cells from footprint. | 0.6 | -| object_recognition_collision_check_max_extra_stopping_margin | [m] | double | | 1.0 |  maximum value when adding longitudinal distance margin for collision check considering stopping distance | +| Name | Unit | Type | Description | Default value | +| :----------------------------------------------------------- | :--- | :----- | :------------------------------------------------------------------------------------------------------- | :------------ | +| use_object_recognition | [-] | bool | flag whether to use object recognition for collision check | true | +| object_recognition_collision_check_margin | [m] | double | margin to calculate ego-vehicle cells from footprint. | 0.6 | +| object_recognition_collision_check_max_extra_stopping_margin | [m] | double | maximum value when adding longitudinal distance margin for collision check considering stopping distance | 1.0 | ## **Goal Search** From bc48f560d7dc71ad76aebb41552cb42ced201de2 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 5 Sep 2023 09:00:41 +0900 Subject: [PATCH 118/547] fix(avoidance): use find nearest index to calculate object's overhang & longitudinal (#4874) Signed-off-by: satoshi-ota --- .../behavior_path_planner/src/utils/avoidance/utils.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 945344259fdb2..b60abbddf6632 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -396,7 +396,8 @@ void fillLongitudinalAndLengthByClosestEnvelopeFootprint( double max_distance = std::numeric_limits::lowest(); for (const auto & p : obj.envelope_poly.outer()) { const auto point = tier4_autoware_utils::createPoint(p.x(), p.y(), 0.0); - const double arc_length = calcSignedArcLengthToFirstNearestPoint(path.points, ego_pos, point); + // TODO(someone): search around first position where the ego should avoid the object. + const double arc_length = calcSignedArcLength(path.points, ego_pos, point); min_distance = std::min(min_distance, arc_length); max_distance = std::max(max_distance, arc_length); } @@ -412,7 +413,8 @@ double calcEnvelopeOverhangDistance( for (const auto & p : object_data.envelope_poly.outer()) { const auto point = tier4_autoware_utils::createPoint(p.x(), p.y(), 0.0); - const auto idx = findFirstNearestIndex(path.points, point); + // TODO(someone): search around first position where the ego should avoid the object. + const auto idx = findNearestIndex(path.points, point); const auto lateral = calcLateralDeviation(getPose(path.points.at(idx)), point); const auto & overhang_pose_on_right = [&overhang_pose, &largest_overhang, &point, &lateral]() { From 6ae26549a31b06f8ee96952af0c962925eae2aed Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 5 Sep 2023 10:06:27 +0900 Subject: [PATCH 119/547] fix(start_planner): start pose candidates (#4880) Signed-off-by: kosuke55 --- .../src/scene_module/start_planner/start_planner_module.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 2dc2401571d9a..abdd41f4935c5 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -416,6 +416,9 @@ void StartPlannerModule::planWithPriority( } const auto is_safe_with_pose_planner = [&](const size_t i, const auto & planner) { + // Set back_finished flag based on the current index + status_.back_finished = i == 0; + // Get the pull_out_start_pose for the current index const auto & pull_out_start_pose = start_pose_candidates.at(i); @@ -495,8 +498,6 @@ void StartPlannerModule::planWithPriority( for (const auto & p : order_priority) { if (is_safe_with_pose_planner(p.first, p.second)) { const std::lock_guard lock(mutex_); - // Set back_finished flag based on the current index - status_.back_finished = p.first == 0; return; } } From 624023cec8a03448f867218ddb221a97540851c6 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:34:11 +0900 Subject: [PATCH 120/547] perf(crosswalk): decrease calcSignedArcLength() process (#4879) Signed-off-by: satoshi-ota --- .../src/scene_crosswalk.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index feb6e42587196..6d2a3110cbdb7 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -37,6 +37,7 @@ using motion_utils::calcLateralOffset; using motion_utils::calcLongitudinalOffsetPoint; using motion_utils::calcLongitudinalOffsetPose; using motion_utils::calcSignedArcLength; +using motion_utils::calcSignedArcLengthPartialSum; using motion_utils::findNearestSegmentIndex; using motion_utils::insertTargetPoint; using motion_utils::resamplePath; @@ -751,15 +752,15 @@ Polygon2d CrosswalkModule::getAttentionArea( { const auto & ego_pos = planner_data_->current_odometry->pose.position; const auto ego_polygon = createVehiclePolygon(planner_data_->vehicle_info_); + const auto backward_path_length = + calcSignedArcLength(sparse_resample_path.points, size_t(0), ego_pos); + const auto length_sum = calcSignedArcLengthPartialSum( + sparse_resample_path.points, size_t(0), sparse_resample_path.points.size()); Polygon2d attention_area; for (size_t j = 0; j < sparse_resample_path.points.size() - 1; ++j) { - const auto & p_ego_front = sparse_resample_path.points.at(j).point.pose; - const auto & p_ego_back = sparse_resample_path.points.at(j + 1).point.pose; - const auto front_length = - calcSignedArcLength(sparse_resample_path.points, ego_pos, p_ego_front.position); - const auto back_length = - calcSignedArcLength(sparse_resample_path.points, ego_pos, p_ego_back.position); + const auto front_length = length_sum.at(j) - backward_path_length; + const auto back_length = length_sum.at(j + 1) - backward_path_length; if (back_length < crosswalk_attention_range.first) { continue; From ed511b135c83f42aefeed09224a32db5c19ad08f Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Tue, 5 Sep 2023 10:49:56 +0900 Subject: [PATCH 121/547] chore(build): remove tier4_autoware_utils.hpp perception/ (#4843) removed tier4_autoware_utils.hpp in perception/ Signed-off-by: Mamoru Sobue Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> --- .../include/crosswalk_traffic_light_estimator/node.hpp | 3 ++- .../include/obstacle_pointcloud_based_validator/debugger.hpp | 1 - .../detected_object_validation/src/object_lanelet_filter.cpp | 2 +- .../detected_object_validation/src/object_position_filter.cpp | 2 -- .../src/obstacle_pointcloud_based_validator.cpp | 2 +- .../src/occupancy_grid_based_validator.cpp | 2 +- .../include/detection_by_tracker/debugger.hpp | 1 - .../detection_by_tracker/detection_by_tracker_core.hpp | 1 - .../detection_by_tracker/src/detection_by_tracker_core.cpp | 3 +++ .../front_vehicle_velocity_estimator.hpp | 2 +- .../front_vehicle_velocity_estimator.cpp | 2 ++ perception/heatmap_visualizer/src/utils.cpp | 3 ++- .../lib/postprocess/non_maximum_suppression.cpp | 3 +-- .../include/multi_object_tracker/utils/utils.hpp | 4 +++- .../src/tracker/model/bicycle_tracker.cpp | 4 +++- .../src/tracker/model/big_vehicle_tracker.cpp | 4 +++- .../src/tracker/model/multiple_vehicle_tracker.cpp | 2 -- .../src/tracker/model/normal_vehicle_tracker.cpp | 4 +++- .../src/tracker/model/pass_through_tracker.cpp | 1 - .../src/tracker/model/pedestrian_and_bicycle_tracker.cpp | 2 -- .../src/tracker/model/pedestrian_tracker.cpp | 4 +++- .../src/tracker/model/unknown_tracker.cpp | 4 +++- .../include/object_association_merger/utils/utils.hpp | 2 -- .../object_merger/src/object_association_merger/node.cpp | 2 +- .../object_velocity_splitter_node.cpp | 2 +- .../src/occupancy_grid_map_outlier_filter_nodelet.cpp | 4 +++- .../probabilistic_occupancy_grid_map/include/utils/utils.hpp | 1 - .../laserscan_based_occupancy_grid_map_node.cpp | 1 - .../occupancy_grid_map_base.cpp | 1 - .../occupancy_grid_map_fixed.cpp | 2 +- .../occupancy_grid_map_projective.cpp | 2 +- .../pointcloud_based_occupancy_grid_map_node.cpp | 3 ++- .../probabilistic_occupancy_grid_map/src/utils/utils.cpp | 2 ++ .../include/radar_fusion_to_detected_object.hpp | 3 +-- .../src/radar_fusion_to_detected_object.cpp | 3 +++ .../radar_object_clustering/radar_object_clustering_node.hpp | 1 - .../radar_object_clustering_node.cpp | 2 ++ .../radar_tracks_msgs_converter_node.hpp | 2 +- .../radar_tracks_msgs_converter_node.cpp | 4 +++- .../include/shape_estimation/corrector/utils.hpp | 2 -- .../include/shape_estimation/model/model_interface.hpp | 2 -- perception/shape_estimation/lib/corrector/utils.cpp | 3 ++- perception/shape_estimation/src/node.cpp | 2 +- .../simple_object_merger/simple_object_merger_node.hpp | 2 +- .../simple_object_merger_node/simple_object_merger_node.cpp | 2 ++ perception/traffic_light_map_based_detector/src/node.cpp | 3 ++- perception/traffic_light_occlusion_predictor/src/nodelet.cpp | 1 - 47 files changed, 61 insertions(+), 49 deletions(-) diff --git a/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp b/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp index 3b4855f1f38e9..161a4d19b96ef 100644 --- a/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp +++ b/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp @@ -19,7 +19,8 @@ #include #include #include -#include +#include +#include #include #include diff --git a/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/debugger.hpp b/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/debugger.hpp index 97c4387122301..9ef1f75427b65 100644 --- a/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/debugger.hpp +++ b/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/debugger.hpp @@ -16,7 +16,6 @@ #define OBSTACLE_POINTCLOUD_BASED_VALIDATOR__DEBUGGER_HPP_ #include -#include #include #include diff --git a/perception/detected_object_validation/src/object_lanelet_filter.cpp b/perception/detected_object_validation/src/object_lanelet_filter.cpp index d22a05fc7162d..2e393f27c37a7 100644 --- a/perception/detected_object_validation/src/object_lanelet_filter.cpp +++ b/perception/detected_object_validation/src/object_lanelet_filter.cpp @@ -15,7 +15,7 @@ #include "detected_object_filter/object_lanelet_filter.hpp" #include -#include +#include #include #include diff --git a/perception/detected_object_validation/src/object_position_filter.cpp b/perception/detected_object_validation/src/object_position_filter.cpp index 6980f5fd3c17f..0f59e60d57d55 100644 --- a/perception/detected_object_validation/src/object_position_filter.cpp +++ b/perception/detected_object_validation/src/object_position_filter.cpp @@ -14,8 +14,6 @@ #include "detected_object_filter/object_position_filter.hpp" -#include - namespace object_position_filter { ObjectPositionFilterNode::ObjectPositionFilterNode(const rclcpp::NodeOptions & node_options) diff --git a/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp b/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp index bad20d8da577f..00e53e9de9a9c 100644 --- a/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp +++ b/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp @@ -15,7 +15,7 @@ #include "obstacle_pointcloud_based_validator/obstacle_pointcloud_based_validator.hpp" #include -#include +#include #include diff --git a/perception/detected_object_validation/src/occupancy_grid_based_validator.cpp b/perception/detected_object_validation/src/occupancy_grid_based_validator.cpp index 50e3f3b540929..948f040d7ebde 100644 --- a/perception/detected_object_validation/src/occupancy_grid_based_validator.cpp +++ b/perception/detected_object_validation/src/occupancy_grid_based_validator.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include diff --git a/perception/detection_by_tracker/include/detection_by_tracker/debugger.hpp b/perception/detection_by_tracker/include/detection_by_tracker/debugger.hpp index 09ae73a7fa03c..fb7642356e8d9 100644 --- a/perception/detection_by_tracker/include/detection_by_tracker/debugger.hpp +++ b/perception/detection_by_tracker/include/detection_by_tracker/debugger.hpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include diff --git a/perception/detection_by_tracker/include/detection_by_tracker/detection_by_tracker_core.hpp b/perception/detection_by_tracker/include/detection_by_tracker/detection_by_tracker_core.hpp index 577e0ff12c367..0eacfa527750b 100644 --- a/perception/detection_by_tracker/include/detection_by_tracker/detection_by_tracker_core.hpp +++ b/perception/detection_by_tracker/include/detection_by_tracker/detection_by_tracker_core.hpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include diff --git a/perception/detection_by_tracker/src/detection_by_tracker_core.cpp b/perception/detection_by_tracker/src/detection_by_tracker_core.cpp index de71f94151486..7b436e1edd64c 100644 --- a/perception/detection_by_tracker/src/detection_by_tracker_core.cpp +++ b/perception/detection_by_tracker/src/detection_by_tracker_core.cpp @@ -16,6 +16,9 @@ #include "object_recognition_utils/object_recognition_utils.hpp" +#include +#include + #include #include #include diff --git a/perception/front_vehicle_velocity_estimator/include/front_vehicle_velocity_estimator/front_vehicle_velocity_estimator.hpp b/perception/front_vehicle_velocity_estimator/include/front_vehicle_velocity_estimator/front_vehicle_velocity_estimator.hpp index 5141bcad04990..01f738fc6beaa 100644 --- a/perception/front_vehicle_velocity_estimator/include/front_vehicle_velocity_estimator/front_vehicle_velocity_estimator.hpp +++ b/perception/front_vehicle_velocity_estimator/include/front_vehicle_velocity_estimator/front_vehicle_velocity_estimator.hpp @@ -19,7 +19,7 @@ #include "pcl/point_types.h" #include "pcl_conversions/pcl_conversions.h" #include "rclcpp/logger.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/boost_geometry.hpp" #include "autoware_auto_perception_msgs/msg/detected_objects.hpp" #include "nav_msgs/msg/odometry.hpp" diff --git a/perception/front_vehicle_velocity_estimator/src/front_vehicle_velocity_estimator_node/front_vehicle_velocity_estimator.cpp b/perception/front_vehicle_velocity_estimator/src/front_vehicle_velocity_estimator_node/front_vehicle_velocity_estimator.cpp index f1b8be19c3223..6f59823615c04 100644 --- a/perception/front_vehicle_velocity_estimator/src/front_vehicle_velocity_estimator_node/front_vehicle_velocity_estimator.cpp +++ b/perception/front_vehicle_velocity_estimator/src/front_vehicle_velocity_estimator_node/front_vehicle_velocity_estimator.cpp @@ -14,6 +14,8 @@ #include "front_vehicle_velocity_estimator/front_vehicle_velocity_estimator.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" + #include #include diff --git a/perception/heatmap_visualizer/src/utils.cpp b/perception/heatmap_visualizer/src/utils.cpp index 2c1be762b2cc9..c4b1c3d6a22c6 100644 --- a/perception/heatmap_visualizer/src/utils.cpp +++ b/perception/heatmap_visualizer/src/utils.cpp @@ -15,7 +15,8 @@ #include "heatmap_visualizer/utils.hpp" #include -#include +#include +#include #include diff --git a/perception/lidar_centerpoint/lib/postprocess/non_maximum_suppression.cpp b/perception/lidar_centerpoint/lib/postprocess/non_maximum_suppression.cpp index 90129b837eb1b..d4d7b1379776a 100644 --- a/perception/lidar_centerpoint/lib/postprocess/non_maximum_suppression.cpp +++ b/perception/lidar_centerpoint/lib/postprocess/non_maximum_suppression.cpp @@ -16,8 +16,7 @@ #include "object_recognition_utils/geometry.hpp" #include "object_recognition_utils/object_recognition_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - +#include "tier4_autoware_utils/geometry/geometry.hpp" namespace centerpoint { diff --git a/perception/multi_object_tracker/include/multi_object_tracker/utils/utils.hpp b/perception/multi_object_tracker/include/multi_object_tracker/utils/utils.hpp index f115d8c07f72f..d2b6ee5de475e 100644 --- a/perception/multi_object_tracker/include/multi_object_tracker/utils/utils.hpp +++ b/perception/multi_object_tracker/include/multi_object_tracker/utils/utils.hpp @@ -21,14 +21,16 @@ #include #include -#include #include #include #include #include +#include #include +#include + #include #include #include diff --git a/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp index 006a87430965f..6571e70b8c123 100644 --- a/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/bicycle_tracker.cpp @@ -20,7 +20,9 @@ #include "multi_object_tracker/utils/utils.hpp" -#include +#include +#include +#include #include #include diff --git a/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp index 50f582d6a2501..97d6d48c35d1b 100644 --- a/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/big_vehicle_tracker.cpp @@ -34,7 +34,9 @@ #include #include -#include +#include +#include +#include using Label = autoware_auto_perception_msgs::msg::ObjectClassification; diff --git a/perception/multi_object_tracker/src/tracker/model/multiple_vehicle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/multiple_vehicle_tracker.cpp index d9137bc67ef55..51adca7e69b56 100644 --- a/perception/multi_object_tracker/src/tracker/model/multiple_vehicle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/multiple_vehicle_tracker.cpp @@ -18,8 +18,6 @@ #include "multi_object_tracker/tracker/model/multiple_vehicle_tracker.hpp" -#include - using Label = autoware_auto_perception_msgs::msg::ObjectClassification; MultipleVehicleTracker::MultipleVehicleTracker( diff --git a/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp index 978b5e346efb2..aa3f7b1c30d01 100644 --- a/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/normal_vehicle_tracker.cpp @@ -34,7 +34,9 @@ #include #include -#include +#include +#include +#include using Label = autoware_auto_perception_msgs::msg::ObjectClassification; diff --git a/perception/multi_object_tracker/src/tracker/model/pass_through_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/pass_through_tracker.cpp index 00822a2776ec8..2084ac28e70f0 100644 --- a/perception/multi_object_tracker/src/tracker/model/pass_through_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/pass_through_tracker.cpp @@ -34,7 +34,6 @@ #include #include -#include PassThroughTracker::PassThroughTracker( const rclcpp::Time & time, const autoware_auto_perception_msgs::msg::DetectedObject & object, diff --git a/perception/multi_object_tracker/src/tracker/model/pedestrian_and_bicycle_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/pedestrian_and_bicycle_tracker.cpp index 87036c9b7665a..eed9d05359b77 100644 --- a/perception/multi_object_tracker/src/tracker/model/pedestrian_and_bicycle_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/pedestrian_and_bicycle_tracker.cpp @@ -18,8 +18,6 @@ #include "multi_object_tracker/tracker/model/pedestrian_and_bicycle_tracker.hpp" -#include - using Label = autoware_auto_perception_msgs::msg::ObjectClassification; PedestrianAndBicycleTracker::PedestrianAndBicycleTracker( diff --git a/perception/multi_object_tracker/src/tracker/model/pedestrian_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/pedestrian_tracker.cpp index d2f144a25f103..b168717042db3 100644 --- a/perception/multi_object_tracker/src/tracker/model/pedestrian_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/pedestrian_tracker.cpp @@ -21,7 +21,9 @@ #include "multi_object_tracker/utils/utils.hpp" #include "object_recognition_utils/object_recognition_utils.hpp" -#include +#include +#include +#include #include #include diff --git a/perception/multi_object_tracker/src/tracker/model/unknown_tracker.cpp b/perception/multi_object_tracker/src/tracker/model/unknown_tracker.cpp index 474191f8926ec..897b858a6aabe 100644 --- a/perception/multi_object_tracker/src/tracker/model/unknown_tracker.cpp +++ b/perception/multi_object_tracker/src/tracker/model/unknown_tracker.cpp @@ -30,7 +30,9 @@ #include #include -#include +#include +#include +#include UnknownTracker::UnknownTracker( const rclcpp::Time & time, const autoware_auto_perception_msgs::msg::DetectedObject & object, diff --git a/perception/object_merger/include/object_association_merger/utils/utils.hpp b/perception/object_merger/include/object_association_merger/utils/utils.hpp index dce5a48c5d2ce..bb4466bd4944d 100644 --- a/perception/object_merger/include/object_association_merger/utils/utils.hpp +++ b/perception/object_merger/include/object_association_merger/utils/utils.hpp @@ -19,8 +19,6 @@ #ifndef OBJECT_ASSOCIATION_MERGER__UTILS__UTILS_HPP_ #define OBJECT_ASSOCIATION_MERGER__UTILS__UTILS_HPP_ -#include - #include #include #include diff --git a/perception/object_merger/src/object_association_merger/node.cpp b/perception/object_merger/src/object_association_merger/node.cpp index 4f600ce8a4948..0ad2a76f74754 100644 --- a/perception/object_merger/src/object_association_merger/node.cpp +++ b/perception/object_merger/src/object_association_merger/node.cpp @@ -16,7 +16,7 @@ #include "object_association_merger/utils/utils.hpp" #include "object_recognition_utils/object_recognition_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include diff --git a/perception/object_velocity_splitter/src/object_velocity_splitter_node/object_velocity_splitter_node.cpp b/perception/object_velocity_splitter/src/object_velocity_splitter_node/object_velocity_splitter_node.cpp index 775f612b8caeb..a5d54b63b3311 100644 --- a/perception/object_velocity_splitter/src/object_velocity_splitter_node/object_velocity_splitter_node.cpp +++ b/perception/object_velocity_splitter/src/object_velocity_splitter_node/object_velocity_splitter_node.cpp @@ -14,7 +14,7 @@ #include "object_velocity_splitter/object_velocity_splitter_node.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include #include diff --git a/perception/occupancy_grid_map_outlier_filter/src/occupancy_grid_map_outlier_filter_nodelet.cpp b/perception/occupancy_grid_map_outlier_filter/src/occupancy_grid_map_outlier_filter_nodelet.cpp index 72136c20fddb8..691c13a6d4701 100644 --- a/perception/occupancy_grid_map_outlier_filter/src/occupancy_grid_map_outlier_filter_nodelet.cpp +++ b/perception/occupancy_grid_map_outlier_filter/src/occupancy_grid_map_outlier_filter_nodelet.cpp @@ -15,7 +15,9 @@ #include "occupancy_grid_map_outlier_filter/occupancy_grid_map_outlier_filter_nodelet.hpp" #include -#include +#include +#include +#include #include diff --git a/perception/probabilistic_occupancy_grid_map/include/utils/utils.hpp b/perception/probabilistic_occupancy_grid_map/include/utils/utils.hpp index 4af7ef950272d..047b747c2861f 100644 --- a/perception/probabilistic_occupancy_grid_map/include/utils/utils.hpp +++ b/perception/probabilistic_occupancy_grid_map/include/utils/utils.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include diff --git a/perception/probabilistic_occupancy_grid_map/src/laserscan_based_occupancy_grid_map/laserscan_based_occupancy_grid_map_node.cpp b/perception/probabilistic_occupancy_grid_map/src/laserscan_based_occupancy_grid_map/laserscan_based_occupancy_grid_map_node.cpp index e6f8ca17757c2..b4505eedddd21 100644 --- a/perception/probabilistic_occupancy_grid_map/src/laserscan_based_occupancy_grid_map/laserscan_based_occupancy_grid_map_node.cpp +++ b/perception/probabilistic_occupancy_grid_map/src/laserscan_based_occupancy_grid_map/laserscan_based_occupancy_grid_map_node.cpp @@ -18,7 +18,6 @@ #include "utils/utils.hpp" #include -#include #include diff --git a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_base.cpp b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_base.cpp index 4e71c4bda3f21..8652cfa34d96c 100644 --- a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_base.cpp +++ b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_base.cpp @@ -56,7 +56,6 @@ #include #include -#include #include diff --git a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_fixed.cpp b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_fixed.cpp index b07076de8342a..56aeea30e0773 100644 --- a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_fixed.cpp +++ b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_fixed.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include diff --git a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_projective.cpp b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_projective.cpp index 9556b0a93cc93..20a5770e37fdb 100644 --- a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_projective.cpp +++ b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/occupancy_grid_map_projective.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include diff --git a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/pointcloud_based_occupancy_grid_map_node.cpp b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/pointcloud_based_occupancy_grid_map_node.cpp index e8b53b91d81f7..f6369602b8890 100644 --- a/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/pointcloud_based_occupancy_grid_map_node.cpp +++ b/perception/probabilistic_occupancy_grid_map/src/pointcloud_based_occupancy_grid_map/pointcloud_based_occupancy_grid_map_node.cpp @@ -20,7 +20,8 @@ #include "utils/utils.hpp" #include -#include +#include +#include #include diff --git a/perception/probabilistic_occupancy_grid_map/src/utils/utils.cpp b/perception/probabilistic_occupancy_grid_map/src/utils/utils.cpp index f2cd6203b3685..0060754cd875c 100644 --- a/perception/probabilistic_occupancy_grid_map/src/utils/utils.cpp +++ b/perception/probabilistic_occupancy_grid_map/src/utils/utils.cpp @@ -14,6 +14,8 @@ #include "utils/utils.hpp" +#include + #include namespace utils diff --git a/perception/radar_fusion_to_detected_object/include/radar_fusion_to_detected_object.hpp b/perception/radar_fusion_to_detected_object/include/radar_fusion_to_detected_object.hpp index e51024ba4e205..b5f2005a84baf 100644 --- a/perception/radar_fusion_to_detected_object/include/radar_fusion_to_detected_object.hpp +++ b/perception/radar_fusion_to_detected_object/include/radar_fusion_to_detected_object.hpp @@ -16,8 +16,7 @@ #define RADAR_FUSION_TO_DETECTED_OBJECT_HPP_ #include "rclcpp/logger.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - +#include "tier4_autoware_utils/geometry/boost_geometry.hpp" #define EIGEN_MPL2_ONLY #include #include diff --git a/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp b/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp index 79e6db0a69a71..839a606c0e307 100644 --- a/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp +++ b/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp @@ -15,6 +15,9 @@ #include "radar_fusion_to_detected_object.hpp" +#include +#include + #include #include diff --git a/perception/radar_object_clustering/include/radar_object_clustering/radar_object_clustering_node.hpp b/perception/radar_object_clustering/include/radar_object_clustering/radar_object_clustering_node.hpp index 0e82c5388d297..c762996f8e0a7 100644 --- a/perception/radar_object_clustering/include/radar_object_clustering/radar_object_clustering_node.hpp +++ b/perception/radar_object_clustering/include/radar_object_clustering/radar_object_clustering_node.hpp @@ -16,7 +16,6 @@ #define RADAR_OBJECT_CLUSTERING__RADAR_OBJECT_CLUSTERING_NODE_HPP_ #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include "autoware_auto_perception_msgs/msg/detected_objects.hpp" diff --git a/perception/radar_object_clustering/src/radar_object_clustering_node/radar_object_clustering_node.cpp b/perception/radar_object_clustering/src/radar_object_clustering_node/radar_object_clustering_node.cpp index 7f9879fc357a5..74e85bde21385 100644 --- a/perception/radar_object_clustering/src/radar_object_clustering_node/radar_object_clustering_node.cpp +++ b/perception/radar_object_clustering/src/radar_object_clustering_node/radar_object_clustering_node.cpp @@ -15,6 +15,8 @@ #include "radar_object_clustering/radar_object_clustering_node.hpp" #include "object_recognition_utils/object_recognition_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/math/unit_conversion.hpp" #include diff --git a/perception/radar_tracks_msgs_converter/include/radar_tracks_msgs_converter/radar_tracks_msgs_converter_node.hpp b/perception/radar_tracks_msgs_converter/include/radar_tracks_msgs_converter/radar_tracks_msgs_converter_node.hpp index 62ebb2ec0146b..351410161d8b2 100644 --- a/perception/radar_tracks_msgs_converter/include/radar_tracks_msgs_converter/radar_tracks_msgs_converter_node.hpp +++ b/perception/radar_tracks_msgs_converter/include/radar_tracks_msgs_converter/radar_tracks_msgs_converter_node.hpp @@ -16,7 +16,7 @@ #define RADAR_TRACKS_MSGS_CONVERTER__RADAR_TRACKS_MSGS_CONVERTER_NODE_HPP_ #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/transform_listener.hpp" #include "autoware_auto_perception_msgs/msg/detected_objects.hpp" #include "autoware_auto_perception_msgs/msg/object_classification.hpp" diff --git a/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp b/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp index ab5e4eb5abe90..75556fbb0c372 100644 --- a/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp +++ b/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp @@ -14,7 +14,9 @@ #include "radar_tracks_msgs_converter/radar_tracks_msgs_converter_node.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/math/unit_conversion.hpp" +#include "tier4_autoware_utils/ros/msg_covariance.hpp" #include diff --git a/perception/shape_estimation/include/shape_estimation/corrector/utils.hpp b/perception/shape_estimation/include/shape_estimation/corrector/utils.hpp index b932c67133850..60ccb0b4ef02c 100644 --- a/perception/shape_estimation/include/shape_estimation/corrector/utils.hpp +++ b/perception/shape_estimation/include/shape_estimation/corrector/utils.hpp @@ -17,8 +17,6 @@ #include "shape_estimation/shape_estimator.hpp" -#include - #include #include diff --git a/perception/shape_estimation/include/shape_estimation/model/model_interface.hpp b/perception/shape_estimation/include/shape_estimation/model/model_interface.hpp index 7f9baa9a0f5e0..ccd04bae40698 100644 --- a/perception/shape_estimation/include/shape_estimation/model/model_interface.hpp +++ b/perception/shape_estimation/include/shape_estimation/model/model_interface.hpp @@ -15,8 +15,6 @@ #ifndef SHAPE_ESTIMATION__MODEL__MODEL_INTERFACE_HPP_ #define SHAPE_ESTIMATION__MODEL__MODEL_INTERFACE_HPP_ -#include - #include #include diff --git a/perception/shape_estimation/lib/corrector/utils.cpp b/perception/shape_estimation/lib/corrector/utils.cpp index e75dcc25f521a..e16f8bed8d36b 100644 --- a/perception/shape_estimation/lib/corrector/utils.cpp +++ b/perception/shape_estimation/lib/corrector/utils.cpp @@ -14,10 +14,11 @@ #include "shape_estimation/corrector/utils.hpp" +#include + #include #include #include - #ifdef ROS_DISTRO_GALACTIC #include #include diff --git a/perception/shape_estimation/src/node.cpp b/perception/shape_estimation/src/node.cpp index 9be64112bc32d..987cf8106c99e 100644 --- a/perception/shape_estimation/src/node.cpp +++ b/perception/shape_estimation/src/node.cpp @@ -15,7 +15,7 @@ #include "shape_estimation/shape_estimator.hpp" #include -#include +#include #include diff --git a/perception/simple_object_merger/include/simple_object_merger/simple_object_merger_node.hpp b/perception/simple_object_merger/include/simple_object_merger/simple_object_merger_node.hpp index 6e2daf9266c9c..0d041439e6092 100644 --- a/perception/simple_object_merger/include/simple_object_merger/simple_object_merger_node.hpp +++ b/perception/simple_object_merger/include/simple_object_merger/simple_object_merger_node.hpp @@ -16,7 +16,7 @@ #define SIMPLE_OBJECT_MERGER__SIMPLE_OBJECT_MERGER_NODE_HPP_ #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/transform_listener.hpp" #include "autoware_auto_perception_msgs/msg/detected_objects.hpp" diff --git a/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp b/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp index 506a40e672fb6..73a96da454833 100644 --- a/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp +++ b/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp @@ -14,6 +14,8 @@ #include "simple_object_merger/simple_object_merger_node.hpp" +#include + #include #include #include diff --git a/perception/traffic_light_map_based_detector/src/node.cpp b/perception/traffic_light_map_based_detector/src/node.cpp index fcf13e3900793..a46be77667bcb 100644 --- a/perception/traffic_light_map_based_detector/src/node.cpp +++ b/perception/traffic_light_map_based_detector/src/node.cpp @@ -17,7 +17,8 @@ #include #include #include -#include +#include +#include #include #include diff --git a/perception/traffic_light_occlusion_predictor/src/nodelet.cpp b/perception/traffic_light_occlusion_predictor/src/nodelet.cpp index 366820a725018..c460f6a623bc4 100644 --- a/perception/traffic_light_occlusion_predictor/src/nodelet.cpp +++ b/perception/traffic_light_occlusion_predictor/src/nodelet.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include From 12fcbc036e2c503b9698605be4146debc0968bae Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:53:10 +0900 Subject: [PATCH 122/547] ci: ignore tier4_pcl_extensions and remove sensing ignorance (#4865) * ci: ignore tier4_pcl_extensions and remove sensing ignorance Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kotaro Yoshimoto --- .cspell-partial.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.cspell-partial.json b/.cspell-partial.json index 45b85e779c39d..f45ae8961c56b 100644 --- a/.cspell-partial.json +++ b/.cspell-partial.json @@ -1,5 +1,9 @@ { - "ignorePaths": ["**/perception/**", "**/sensing/**", "perception/bytetrack/lib/**"], + "ignorePaths": [ + "**/perception/**", + "sensing/tier4_pcl_extensions/include/**", + "perception/bytetrack/lib/**" + ], "ignoreRegExpList": [], "words": [] } From 5094fa26085bc90a3ad943c6680a093b8cddb61d Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Tue, 5 Sep 2023 11:05:19 +0900 Subject: [PATCH 123/547] docs(out_of_lane): fix parameters description (#4246) Signed-off-by: Maxime CLEMENT --- planning/behavior_velocity_out_of_lane_module/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/planning/behavior_velocity_out_of_lane_module/README.md b/planning/behavior_velocity_out_of_lane_module/README.md index da46055460a99..885c0dc2859ab 100644 --- a/planning/behavior_velocity_out_of_lane_module/README.md +++ b/planning/behavior_velocity_out_of_lane_module/README.md @@ -140,9 +140,9 @@ Moreover, parameter `action.distance_buffer` adds an extra distance between the | Parameter /objects | Type | Description | | ------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `minimum_velocity` | double | [m/s] consider objects with an estimated time to collision bellow this value while on the overlap | -| `use_predicted_paths` | bool | [-] if true, use the predicted paths to estimate future positions; if false, assume the object moves at constant velocity along _all_ lanelets it currently is located in | +| `minimum_velocity` | double | [m/s] ignore objects with a velocity lower than this value | | `predicted_path_min_confidence` | double | [-] minimum confidence required for a predicted path to be considered | +| `use_predicted_paths` | bool | [-] if true, use the predicted paths to estimate future positions; if false, assume the object moves at constant velocity along _all_ lanelets it currently is located in | | Parameter /overlap | Type | Description | | ------------------ | ------ | ---------------------------------------------------------------------------------------------------- | @@ -155,7 +155,7 @@ Moreover, parameter `action.distance_buffer` adds an extra distance between the | `strict` | bool | [-] if true, when a decision is taken to avoid entering a lane, the stop point will make sure no lane at all is entered by ego; if false, ego stops just before entering a lane but may then be overlapping another lane | | `distance_buffer` | double | [m] buffer distance to try to keep between the ego footprint and lane | | `slowdown.distance_threshold` | double | [m] insert a slow down when closer than this distance from an overlap | -| `slowdown.velocity` | double | [m] slow down velocity | +| `slowdown.velocity` | double | [m/s] slow down velocity | | `stop.distance_threshold` | double | [m] insert a stop when closer than this distance from an overlap | | Parameter /ego | Type | Description | From 87b3b4e6fbaae1325eba24bb6d63ecab6d67801d Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 5 Sep 2023 14:54:28 +0900 Subject: [PATCH 124/547] fix(goal_planner): fix goal search for narrow shoulder lane (#4816) Signed-off-by: kosuke55 --- .../behavior_path_planner/utils/utils.hpp | 4 +- .../goal_planner/goal_planner_module.cpp | 15 +- .../src/utils/goal_planner/goal_searcher.cpp | 21 ++- .../behavior_path_planner/src/utils/utils.cpp | 162 ++++++++++++------ 4 files changed, 139 insertions(+), 63 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp index be227f4f7e93e..0caa79ba5d062 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp @@ -323,8 +323,8 @@ PathPointWithLaneId insertStopPoint(const double length, PathWithLaneId & path); double getSignedDistanceFromBoundary( const lanelet::ConstLanelets & shoulder_lanelets, const Pose & pose, const bool left_side); std::optional getSignedDistanceFromBoundary( - const lanelet::ConstLanelets & shoulder_lanelets, const LinearRing2d & footprint, - const Pose & vehicle_pose, const bool left_side); + const lanelet::ConstLanelets & lanelets, const double vehicle_width, const double base_link2front, + const double base_link2rear, const Pose & vehicle_pose, const bool left_side); // misc diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 9e7b101b8c4ab..4249e8826bda9 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -359,6 +359,10 @@ double GoalPlannerModule::calcModuleRequestLength() const Pose GoalPlannerModule::calcRefinedGoal(const Pose & goal_pose) const { + const double vehicle_width = planner_data_->parameters.vehicle_width; + const double base_link2front = planner_data_->parameters.base_link2front; + const double base_link2rear = planner_data_->parameters.base_link2rear; + const lanelet::ConstLanelets pull_over_lanes = goal_planner_utils::getPullOverLanes(*(planner_data_->route_handler), left_side_parking_); @@ -390,15 +394,18 @@ Pose GoalPlannerModule::calcRefinedGoal(const Pose & goal_pose) const center_pose.orientation = tf2::toMsg(tf_quat); } - const auto distance_from_left_bound = utils::getSignedDistanceFromBoundary( - pull_over_lanes, vehicle_footprint_, center_pose, left_side_parking_); - if (!distance_from_left_bound) { + const auto distance_from_bound = utils::getSignedDistanceFromBoundary( + pull_over_lanes, vehicle_width, base_link2front, base_link2rear, center_pose, + left_side_parking_); + if (!distance_from_bound) { RCLCPP_ERROR(getLogger(), "fail to calculate refined goal"); return goal_pose; } + const double sign = left_side_parking_ ? -1.0 : 1.0; const double offset_from_center_line = - distance_from_left_bound.value() + parameters_->margin_from_boundary; + sign * (distance_from_bound.value() + parameters_->margin_from_boundary); + const auto refined_goal_pose = calcOffsetPose(center_pose, 0, -offset_from_center_line, 0); return refined_goal_pose; diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index 1e57dcc319dc5..e12ee576a5d4b 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -53,6 +53,9 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) const double lateral_offset_interval = parameters_.lateral_offset_interval; const double max_lateral_offset = parameters_.max_lateral_offset; const double ignore_distance_from_lane_start = parameters_.ignore_distance_from_lane_start; + const double vehicle_width = planner_data_->parameters.vehicle_width; + const double base_link2front = planner_data_->parameters.base_link2front; + const double base_link2rear = planner_data_->parameters.base_link2rear; const auto pull_over_lanes = goal_planner_utils::getPullOverLanes(*route_handler, left_side_parking_); @@ -76,7 +79,10 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) std::vector original_search_poses{}; // for search area visualizing size_t goal_id = 0; for (const auto & p : center_line_path.points) { - const Pose & center_pose = p.point.pose; + // todo(kosuke55): fix orientation for inverseTransformPoint temporarily + Pose center_pose = p.point.pose; + center_pose.orientation = + tier4_autoware_utils::createQuaternionFromYaw(tf2::getYaw(center_pose.orientation)); // ignore goal_pose near lane start const double distance_from_lane_start = @@ -85,13 +91,14 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) continue; } - const auto distance_from_left_bound = utils::getSignedDistanceFromBoundary( - pull_over_lanes, vehicle_footprint_, center_pose, left_side_parking_); - if (!distance_from_left_bound) continue; + const auto distance_from_bound = utils::getSignedDistanceFromBoundary( + pull_over_lanes, vehicle_width, base_link2front, base_link2rear, center_pose, + left_side_parking_); + if (!distance_from_bound) continue; - const double sign = left_side_parking_ ? 1.0 : -1.0; + const double sign = left_side_parking_ ? -1.0 : 1.0; const double offset_from_center_line = - sign * (std::abs(distance_from_left_bound.value()) - margin_from_boundary); + -distance_from_bound.value() + sign * margin_from_boundary; const Pose original_search_pose = calcOffsetPose(center_pose, 0, offset_from_center_line, 0); const double longitudinal_distance_from_original_goal = std::abs(motion_utils::calcSignedArcLength( @@ -102,7 +109,7 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) double lateral_offset = 0.0; for (double dy = 0; dy <= max_lateral_offset; dy += lateral_offset_interval) { lateral_offset = dy; - search_pose = calcOffsetPose(original_search_pose, 0, -sign * dy, 0); + search_pose = calcOffsetPose(original_search_pose, 0, sign * dy, 0); const auto transformed_vehicle_footprint = transformVector(vehicle_footprint_, tier4_autoware_utils::pose2transform(search_pose)); diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index c7fde9dada4ec..da23d818cf59a 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -2405,64 +2405,126 @@ double getSignedDistanceFromBoundary( } std::optional getSignedDistanceFromBoundary( - const lanelet::ConstLanelets & lanelets, const LinearRing2d & footprint, - const Pose & vehicle_pose, const bool left_side) -{ - double min_distance = std::numeric_limits::max(); - - const auto transformed_footprint = - transformVector(footprint, tier4_autoware_utils::pose2transform(vehicle_pose)); - bool found_neighbor_bound = false; - for (const auto & vehicle_corner_point : transformed_footprint) { - // convert point of footprint to pose + const lanelet::ConstLanelets & lanelets, const double vehicle_width, const double base_link2front, + const double base_link2rear, const Pose & vehicle_pose, const bool left_side) +{ + // Depending on which side is selected, calculate the transformed coordinates of the front and + // rear vehicle corners + Point rear_corner_point, front_corner_point; + if (left_side) { + Point front_left, rear_left; + rear_left.x = -base_link2rear; + rear_left.y = vehicle_width / 2; + front_left.x = base_link2front; + front_left.y = vehicle_width / 2; + rear_corner_point = tier4_autoware_utils::transformPoint(rear_left, vehicle_pose); + front_corner_point = tier4_autoware_utils::transformPoint(front_left, vehicle_pose); + } else { + Point front_right, rear_right; + rear_right.x = -base_link2rear; + rear_right.y = -vehicle_width / 2; + front_right.x = base_link2front; + front_right.y = -vehicle_width / 2; + rear_corner_point = tier4_autoware_utils::transformPoint(rear_right, vehicle_pose); + front_corner_point = tier4_autoware_utils::transformPoint(front_right, vehicle_pose); + } + + const auto combined_lane = lanelet::utils::combineLaneletsShape(lanelets); + const auto & bound_line_2d = left_side ? lanelet::utils::to2D(combined_lane.leftBound3d()) + : lanelet::utils::to2D(combined_lane.rightBound3d()); + + // Initialize the lateral distance to the maximum (for left side) or the minimum (for right side) + // possible value. + double lateral_distance = + left_side ? -std::numeric_limits::max() : std::numeric_limits::max(); + + // Find the closest bound segment that contains the corner point in the X-direction + // and calculate the lateral distance from that segment. + const auto calcLateralDistanceFromBound = [&]( + const Point & vehicle_corner_point, + boost::optional & lateral_distance, + boost::optional & segment_idx) { Pose vehicle_corner_pose{}; - vehicle_corner_pose.position = tier4_autoware_utils::toMsg(vehicle_corner_point.to_3d()); + vehicle_corner_pose.position = vehicle_corner_point; vehicle_corner_pose.orientation = vehicle_pose.orientation; - // calculate distance to the bound directly next to footprint points - lanelet::ConstLanelet closest_lanelet{}; - if (lanelet::utils::query::getClosestLanelet(lanelets, vehicle_corner_pose, &closest_lanelet)) { - const auto & bound_line_2d = left_side ? lanelet::utils::to2D(closest_lanelet.leftBound3d()) - : lanelet::utils::to2D(closest_lanelet.rightBound3d()); - - for (size_t i = 1; i < bound_line_2d.size(); ++i) { - const Point p_front = lanelet::utils::conversion::toGeomMsgPt(bound_line_2d[i - 1]); - const Point p_back = lanelet::utils::conversion::toGeomMsgPt(bound_line_2d[i]); - - const Point inverse_p_front = - tier4_autoware_utils::inverseTransformPoint(p_front, vehicle_corner_pose); - const Point inverse_p_back = - tier4_autoware_utils::inverseTransformPoint(p_back, vehicle_corner_pose); - - const double dy_front = inverse_p_front.y; - const double dy_back = inverse_p_back.y; - const double dx_front = inverse_p_front.x; - const double dx_back = inverse_p_back.x; - // is in segment - if (dx_front < 0 && dx_back > 0) { - const double lateral_distance_from_pose_to_segment = - (dy_front * dx_back + dy_back * -dx_front) / (dx_back - dx_front); - - if (std::abs(lateral_distance_from_pose_to_segment) < std::abs(min_distance)) { - min_distance = lateral_distance_from_pose_to_segment; - } - - found_neighbor_bound = true; - break; - } + // Euclidean distance to find the closest segment containing the corner point. + double min_distance = std::numeric_limits::max(); + + for (size_t i = 0; i < bound_line_2d.size() - 1; i++) { + const Point p1 = lanelet::utils::conversion::toGeomMsgPt(bound_line_2d[i]); + const Point p2 = lanelet::utils::conversion::toGeomMsgPt(bound_line_2d[i + 1]); + + const Point inverse_p1 = tier4_autoware_utils::inverseTransformPoint(p1, vehicle_corner_pose); + const Point inverse_p2 = tier4_autoware_utils::inverseTransformPoint(p2, vehicle_corner_pose); + const double dx_p1 = inverse_p1.x; + const double dx_p2 = inverse_p2.x; + const double dy_p1 = inverse_p1.y; + const double dy_p2 = inverse_p2.y; + + // Calculate the Euclidean distances between vehicle's corner and the current and next points. + const double distance1 = tier4_autoware_utils::calcDistance2d(p1, vehicle_corner_point); + const double distance2 = tier4_autoware_utils::calcDistance2d(p2, vehicle_corner_point); + + // If one of the bound points is behind and the other is in front of the vehicle corner point + // and any of these points is closer than the current minimum distance, + // then update minimum distance, lateral distance and the segment index. + if (dx_p1 < 0 && dx_p2 > 0 && (distance1 < min_distance || distance2 < min_distance)) { + min_distance = std::min(distance1, distance2); + // Update lateral distance using the formula derived from similar triangles in the lateral + // cross-section view. + lateral_distance = -1.0 * (dy_p1 * dx_p2 + dy_p2 * -dx_p1) / (dx_p2 - dx_p1); + segment_idx = i; } } - } + }; - if (!found_neighbor_bound) { - RCLCPP_ERROR_STREAM( - rclcpp::get_logger("behavior_path_planner").get_child("utils"), - "neighbor shoulder bound to footprint is not found."); + // Calculate the lateral distance for both the rear and front corners of the vehicle. + boost::optional rear_segment_idx{}; + boost::optional rear_lateral_distance{}; + calcLateralDistanceFromBound(rear_corner_point, rear_lateral_distance, rear_segment_idx); + boost::optional front_segment_idx{}; + boost::optional front_lateral_distance{}; + calcLateralDistanceFromBound(front_corner_point, front_lateral_distance, front_segment_idx); + + // If no closest bound segment was found for both corners, return an empty optional. + if (!rear_lateral_distance && !front_lateral_distance) { return {}; } - - // min_distance is the distance from corner_pose to bound, so reverse this value - return -min_distance; + // If only one of them found the closest bound, return the found lateral distance. + if (!rear_lateral_distance) { + return *front_lateral_distance; + } else if (!front_lateral_distance) { + return *rear_lateral_distance; + } + // If both corners found their closest bound, return the maximum (for left side) or the minimum + // (for right side) lateral distance. + lateral_distance = left_side ? std::max(*rear_lateral_distance, *front_lateral_distance) + : std::min(*rear_lateral_distance, *front_lateral_distance); + + // Iterate through all segments between the segments closest to the rear and front corners. + // Update the lateral distance in case any of these inner segments are closer to the vehicle. + for (size_t i = *rear_segment_idx + 1; i < *front_segment_idx; i++) { + Pose bound_pose; + bound_pose.position = lanelet::utils::conversion::toGeomMsgPt(bound_line_2d[i]); + bound_pose.orientation = vehicle_pose.orientation; + + const Point inverse_rear_point = + tier4_autoware_utils::inverseTransformPoint(rear_corner_point, bound_pose); + const Point inverse_front_point = + tier4_autoware_utils::inverseTransformPoint(front_corner_point, bound_pose); + const double dx_rear = inverse_rear_point.x; + const double dx_front = inverse_front_point.x; + const double dy_rear = inverse_rear_point.y; + const double dy_front = inverse_front_point.y; + + const double current_lateral_distance = + (dy_rear * dx_front + dy_front * -dx_rear) / (dx_front - dx_rear); + lateral_distance = left_side ? std::max(lateral_distance, current_lateral_distance) + : std::min(lateral_distance, current_lateral_distance); + } + + return lateral_distance; } double getArcLengthToTargetLanelet( From 46a263cddaa0bfdfc7f2014db2914c47e6ee0cf4 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 5 Sep 2023 15:36:19 +0900 Subject: [PATCH 125/547] fix(route_handler): fix getter for pose (#4884) Signed-off-by: kosuke55 --- planning/route_handler/src/route_handler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planning/route_handler/src/route_handler.cpp b/planning/route_handler/src/route_handler.cpp index b8cedaa947455..48884cdfa2b46 100644 --- a/planning/route_handler/src/route_handler.cpp +++ b/planning/route_handler/src/route_handler.cpp @@ -426,7 +426,7 @@ Pose RouteHandler::getStartPose() const { if (!route_ptr_) { RCLCPP_WARN(logger_, "[Route Handler] getStartPose: Route has not been set yet"); - Pose(); + return Pose(); } return route_ptr_->start_pose; } @@ -440,7 +440,7 @@ Pose RouteHandler::getGoalPose() const { if (!route_ptr_) { RCLCPP_WARN(logger_, "[Route Handler] getGoalPose: Route has not been set yet"); - Pose(); + return Pose(); } return route_ptr_->goal_pose; } From 30ec8f7c2f2cd6d8f33a9b39adaec6bd5551b48c Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 5 Sep 2023 17:17:14 +0900 Subject: [PATCH 126/547] fix(behavior_path_planner): save original route goal pose (#4876) * fix(behavior_path_planner): save original route goal pose Signed-off-by: kosuke55 * fix return Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../goal_planner/goal_planner_module.cpp | 6 +++--- .../src/utils/goal_planner/util.cpp | 2 +- .../include/route_handler/route_handler.hpp | 2 ++ planning/route_handler/src/route_handler.cpp | 14 ++++++++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 4249e8826bda9..7b86cb5c16440 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -275,7 +275,7 @@ bool GoalPlannerModule::isExecutionRequested() const const auto & route_handler = planner_data_->route_handler; const Pose & current_pose = planner_data_->self_odometry->pose.pose; - const Pose & goal_pose = route_handler->getGoalPose(); + const Pose & goal_pose = route_handler->getOriginalGoalPose(); // check if goal_pose is in current_lanes. lanelet::ConstLanelet current_lane{}; @@ -511,7 +511,7 @@ void GoalPlannerModule::generateGoalCandidates() } // calculate goal candidates - const Pose goal_pose = route_handler->getGoalPose(); + const Pose goal_pose = route_handler->getOriginalGoalPose(); refined_goal_pose_ = calcRefinedGoal(goal_pose); if (goal_planner_utils::isAllowedGoalModification(route_handler)) { goal_searcher_->setPlannerData(planner_data_); @@ -1548,7 +1548,7 @@ void GoalPlannerModule::printParkingPositionError() const bool GoalPlannerModule::checkOriginalGoalIsInShoulder() const { const auto & route_handler = planner_data_->route_handler; - const Pose & goal_pose = route_handler->getGoalPose(); + const Pose & goal_pose = route_handler->getOriginalGoalPose(); const lanelet::ConstLanelets pull_over_lanes = goal_planner_utils::getPullOverLanes(*(route_handler), left_side_parking_); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index 8b48d1a867126..0d58a54431be0 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -57,7 +57,7 @@ PathWithLaneId combineReferencePath(const PathWithLaneId & path1, const PathWith lanelet::ConstLanelets getPullOverLanes(const RouteHandler & route_handler, const bool left_side) { - const Pose goal_pose = route_handler.getGoalPose(); + const Pose goal_pose = route_handler.getOriginalGoalPose(); lanelet::ConstLanelet target_shoulder_lane{}; if (route_handler::RouteHandler::getPullOverTarget( diff --git a/planning/route_handler/include/route_handler/route_handler.hpp b/planning/route_handler/include/route_handler/route_handler.hpp index 1818c460e407b..a797edaa34556 100644 --- a/planning/route_handler/include/route_handler/route_handler.hpp +++ b/planning/route_handler/include/route_handler/route_handler.hpp @@ -98,6 +98,7 @@ class RouteHandler Pose getGoalPose() const; Pose getStartPose() const; Pose getOriginalStartPose() const; + Pose getOriginalGoalPose() const; lanelet::Id getGoalLaneId() const; bool getGoalLanelet(lanelet::ConstLanelet * goal_lanelet) const; std::vector getLanesBeforePose( @@ -378,6 +379,7 @@ class RouteHandler // save original(not modified) route start pose for start planer execution Pose original_start_pose_; + Pose original_goal_pose_; // non-const methods void setLaneletsFromRouteMsg(); diff --git a/planning/route_handler/src/route_handler.cpp b/planning/route_handler/src/route_handler.cpp index 48884cdfa2b46..91f86ed0e454f 100644 --- a/planning/route_handler/src/route_handler.cpp +++ b/planning/route_handler/src/route_handler.cpp @@ -176,6 +176,7 @@ void RouteHandler::setRoute(const LaneletRoute & route_msg) // if get not modified route but new route, reset original start pose if (!route_ptr_ || route_ptr_->uuid != route_msg.uuid) { original_start_pose_ = route_msg.start_pose; + original_goal_pose_ = route_msg.goal_pose; } route_ptr_ = std::make_shared(route_msg); is_handler_ready_ = false; @@ -433,6 +434,10 @@ Pose RouteHandler::getStartPose() const Pose RouteHandler::getOriginalStartPose() const { + if (!route_ptr_) { + RCLCPP_WARN(logger_, "[Route Handler] getOriginalStartPose: Route has not been set yet"); + return Pose(); + } return original_start_pose_; } @@ -445,6 +450,15 @@ Pose RouteHandler::getGoalPose() const return route_ptr_->goal_pose; } +Pose RouteHandler::getOriginalGoalPose() const +{ + if (!route_ptr_) { + RCLCPP_WARN(logger_, "[Route Handler] getOriginalGoalPose: Route has not been set yet"); + return Pose(); + } + return original_goal_pose_; +} + lanelet::Id RouteHandler::getGoalLaneId() const { if (!route_ptr_ || route_ptr_->segments.empty()) { From 6806b134cec3411f437f3a5b79ae59d88e652605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Yavuz=20K=C3=B6seo=C4=9Flu?= Date: Tue, 5 Sep 2023 12:24:00 +0300 Subject: [PATCH 127/547] fix(ndt_scan_matcher): skipping_publish_num does not switches error state (#4853) fix skip_publishing_num status Co-authored-by: kminoda <44218668+kminoda@users.noreply.github.com> --- localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 667fd59196df3..154fc8526f512 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -270,7 +270,8 @@ void NDTScanMatcher::timer_diagnostic() } if ( state_ptr_->count("skipping_publish_num") && - std::stoi((*state_ptr_)["skipping_publish_num"]) > 1) { + std::stoi((*state_ptr_)["skipping_publish_num"]) > 1 && + std::stoi((*state_ptr_)["skipping_publish_num"]) < 5) { diag_status_msg.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; diag_status_msg.message += "skipping_publish_num > 1. "; } From d0876804b75a8240b94ad1c97ed16a16ee5e3370 Mon Sep 17 00:00:00 2001 From: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Date: Tue, 5 Sep 2023 19:52:58 +0900 Subject: [PATCH 128/547] chore: update maintainer of TLR packages (#4882) Signed-off-by: Shunsuke Miura --- common/perception_utils/package.xml | 1 - perception/crosswalk_traffic_light_estimator/package.xml | 2 ++ perception/traffic_light_fine_detector/package.xml | 2 +- perception/traffic_light_multi_camera_fusion/package.xml | 2 +- perception/traffic_light_occlusion_predictor/package.xml | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/common/perception_utils/package.xml b/common/perception_utils/package.xml index 6cae7f9ce8c4f..5304d481737e5 100644 --- a/common/perception_utils/package.xml +++ b/common/perception_utils/package.xml @@ -4,7 +4,6 @@ perception_utils 0.1.0 The perception_utils package - Mingyu Li Satoshi Tanaka Yusuke Muramatsu Shunsuke Miura diff --git a/perception/crosswalk_traffic_light_estimator/package.xml b/perception/crosswalk_traffic_light_estimator/package.xml index f029972f7124f..bec5b666683c4 100644 --- a/perception/crosswalk_traffic_light_estimator/package.xml +++ b/perception/crosswalk_traffic_light_estimator/package.xml @@ -5,6 +5,8 @@ The crosswalk_traffic_light_estimator package Satoshi Ota + Shunsuke Miura + Tao Zhong Apache License 2.0 ament_cmake_auto diff --git a/perception/traffic_light_fine_detector/package.xml b/perception/traffic_light_fine_detector/package.xml index c54912e256c02..8ff9f2d133b52 100644 --- a/perception/traffic_light_fine_detector/package.xml +++ b/perception/traffic_light_fine_detector/package.xml @@ -4,7 +4,7 @@ traffic_light_fine_detector 0.1.0 The traffic_light_fine_detector package - Mingyu Li + Tao Zhong Shunsuke Miura Apache License 2.0 diff --git a/perception/traffic_light_multi_camera_fusion/package.xml b/perception/traffic_light_multi_camera_fusion/package.xml index eb0858152e69e..b08ccfe668085 100644 --- a/perception/traffic_light_multi_camera_fusion/package.xml +++ b/perception/traffic_light_multi_camera_fusion/package.xml @@ -4,7 +4,7 @@ traffic_light_multi_camera_fusion 0.1.0 The traffic_light_multi_camera_fusion package - Mingyu Li + Tao Zhong Shunsuke Miura Apache License 2.0 diff --git a/perception/traffic_light_occlusion_predictor/package.xml b/perception/traffic_light_occlusion_predictor/package.xml index 0ae390c90b9a9..49a67d68057d9 100644 --- a/perception/traffic_light_occlusion_predictor/package.xml +++ b/perception/traffic_light_occlusion_predictor/package.xml @@ -4,7 +4,7 @@ traffic_light_occlusion_predictor 0.1.0 The traffic_light_occlusion_predictor package - Mingyu Li + Tao Zhong Shunsuke Miura Apache License 2.0 From a0ce39a9b3ec4bd199861e197e859e0029af13e3 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 5 Sep 2023 20:08:50 +0900 Subject: [PATCH 129/547] feat(behavior_path_planner): add safety check against dynamic objects for start/goal planner (#4873) * add safety check for start/goal planner Signed-off-by: kyoichi-sugahara * fix typo Signed-off-by: kyoichi-sugahara * update comments Signed-off-by: kyoichi-sugahara * add const Signed-off-by: kosuke55 * fix empty check Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara Signed-off-by: kosuke55 Co-authored-by: kosuke55 --- .../goal_planner/goal_planner.param.yaml | 60 +++++++ .../start_planner/start_planner.param.yaml | 60 +++++++ .../goal_planner/goal_planner_module.hpp | 29 ++++ .../start_planner/start_planner_module.hpp | 24 +++ .../utils/start_goal_planner_common/utils.hpp | 6 +- .../utils/start_planner/util.hpp | 4 + .../goal_planner/goal_planner_module.cpp | 98 +++++++++++ .../src/scene_module/goal_planner/manager.cpp | 152 +++++++++++++++--- .../scene_module/start_planner/manager.cpp | 116 +++++++++++++ .../start_planner/start_planner_module.cpp | 94 +++++++++++ .../utils/start_goal_planner_common/utils.cpp | 9 +- 11 files changed, 630 insertions(+), 22 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 39d05a7fb761b..433ac428fe73e 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -104,6 +104,66 @@ neighbor_radius: 8.0 margin: 1.0 + stop_condition: + maximum_deceleration_for_stop: 1.0 + maximum_jerk_for_stop: 1.0 + path_safety_check: + # EgoPredictedPath + ego_predicted_path: + acceleration: 1.0 + time_horizon: 10.0 + time_resolution: 0.5 + min_slow_speed: 0.0 + delay_until_departure: 1.0 + target_velocity: 1.0 + # For target object filtering + target_filtering: + safety_check_time_horizon: 5.0 + safety_check_time_resolution: 1.0 + # detection range + object_check_forward_distance: 10.0 + object_check_backward_distance: 100.0 + ignore_object_velocity_threshold: 0.0 + # ObjectTypesToCheck + object_types_to_check: + check_car: true + check_truck: true + check_bus: true + check_trailer: true + check_bicycle: true + check_motorcycle: true + check_pedestrian: true + check_unknown: false + # ObjectLaneConfiguration + object_lane_configuration: + check_current_lane: true + check_right_side_lane: true + check_left_side_lane: true + check_shoulder_lane: true + check_other_lane: false + include_opposite_lane: false + invert_opposite_lane: false + check_all_predicted_path: true + use_all_predicted_path: true + use_predicted_path_outside_lanelet: false + + # For safety check + safety_check_params: + # safety check configuration + enable_safety_check: true + # collision check parameters + check_all_predicted_path: true + publish_debug_marker: false + rss_params: + rear_vehicle_reaction_time: 1.0 + rear_vehicle_safety_time_margin: 1.0 + lateral_distance_max_threshold: 1.0 + longitudinal_distance_min_threshold: 1.0 + longitudinal_velocity_delta_time: 1.0 + # temporary + backward_path_length: 30.0 + forward_path_length: 100.0 + # debug debug: print_debug_info: false diff --git a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml index bea78664c65a3..b62262423fa72 100644 --- a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml +++ b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml @@ -72,3 +72,63 @@ max_planning_time: 150.0 neighbor_radius: 8.0 margin: 1.0 + + stop_condition: + maximum_deceleration_for_stop: 1.0 + maximum_jerk_for_stop: 1.0 + path_safety_check: + # EgoPredictedPath + ego_predicted_path: + acceleration: 1.0 + time_horizon: 10.0 + time_resolution: 0.5 + min_slow_speed: 0.0 + delay_until_departure: 1.0 + target_velocity: 1.0 + # For target object filtering + target_filtering: + safety_check_time_horizon: 5.0 + safety_check_time_resolution: 1.0 + # detection range + object_check_forward_distance: 10.0 + object_check_backward_distance: 100.0 + ignore_object_velocity_threshold: 0.0 + # ObjectTypesToCheck + object_types_to_check: + check_car: true + check_truck: true + check_bus: true + check_trailer: true + check_bicycle: true + check_motorcycle: true + check_pedestrian: true + check_unknown: false + # ObjectLaneConfiguration + object_lane_configuration: + check_current_lane: true + check_right_side_lane: true + check_left_side_lane: true + check_shoulder_lane: true + check_other_lane: false + include_opposite_lane: false + invert_opposite_lane: false + check_all_predicted_path: true + use_all_predicted_path: true + use_predicted_path_outside_lanelet: false + + # For safety check + safety_check_params: + # safety check configuration + enable_safety_check: true + # collision check parameters + check_all_predicted_path: true + publish_debug_marker: false + rss_params: + rear_vehicle_reaction_time: 1.0 + rear_vehicle_safety_time_margin: 1.0 + lateral_distance_max_threshold: 1.0 + longitudinal_distance_min_threshold: 1.0 + longitudinal_velocity_delta_time: 1.0 + # temporary + backward_path_length: 30.0 + forward_path_length: 100.0 diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index d7f824ba8f419..ab6f77b1f53d6 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -24,6 +24,9 @@ #include "behavior_path_planner/utils/goal_planner/goal_searcher.hpp" #include "behavior_path_planner/utils/goal_planner/shift_pull_over.hpp" #include "behavior_path_planner/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp" +#include "behavior_path_planner/utils/utils.hpp" #include #include @@ -57,6 +60,12 @@ using freespace_planning_algorithms::PlannerCommonParam; using freespace_planning_algorithms::RRTStar; using freespace_planning_algorithms::RRTStarParam; +using behavior_path_planner::utils::path_safety_checker::EgoPredictedPathParams; +using behavior_path_planner::utils::path_safety_checker::ObjectsFilteringParams; +using behavior_path_planner::utils::path_safety_checker::PoseWithVelocityStamped; +using behavior_path_planner::utils::path_safety_checker::SafetyCheckParams; +using behavior_path_planner::utils::path_safety_checker::TargetObjectsOnLane; + enum class PathType { NONE = 0, SHIFT, @@ -107,6 +116,13 @@ class GoalPlannerModule : public SceneModuleInterface void updateModuleParams(const std::any & parameters) override { parameters_ = std::any_cast>(parameters); + if (parameters_->safety_check_params.enable_safety_check) { + ego_predicted_path_params_ = + std::make_shared(parameters_->ego_predicted_path_params); + objects_filtering_params_ = + std::make_shared(parameters_->objects_filtering_params); + safety_check_params_ = std::make_shared(parameters_->safety_check_params); + } } // TODO(someone): remove this, and use base class function @@ -137,8 +153,14 @@ class GoalPlannerModule : public SceneModuleInterface PullOverStatus status_; + mutable StartGoalPlannerData goal_planner_data_; + std::shared_ptr parameters_; + mutable std::shared_ptr ego_predicted_path_params_; + mutable std::shared_ptr objects_filtering_params_; + mutable std::shared_ptr safety_check_params_; + vehicle_info_util::VehicleInfo vehicle_info_; // planner @@ -276,6 +298,13 @@ class GoalPlannerModule : public SceneModuleInterface // rtc std::pair calcDistanceToPathChange() const; + // safety check + SafetyCheckParams createSafetyCheckParams() const; + void updateSafetyCheckTargetObjectsData( + const PredictedObjects & filtered_objects, const TargetObjectsOnLane & target_objects_on_lane, + const std::vector & ego_predicted_path) const; + bool isSafePath() const; + // debug void setDebugData(); void printParkingPositionError() const; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index 2e8c9e57f8842..b716221f5e82b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -17,12 +17,15 @@ #include "behavior_path_planner/scene_module/scene_module_interface.hpp" #include "behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp" #include "behavior_path_planner/utils/start_planner/freespace_pull_out.hpp" #include "behavior_path_planner/utils/start_planner/geometric_pull_out.hpp" #include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" #include "behavior_path_planner/utils/start_planner/shift_pull_out.hpp" #include "behavior_path_planner/utils/start_planner/start_planner_parameters.hpp" +#include "behavior_path_planner/utils/utils.hpp" #include #include @@ -43,6 +46,11 @@ namespace behavior_path_planner { +using behavior_path_planner::utils::path_safety_checker::EgoPredictedPathParams; +using behavior_path_planner::utils::path_safety_checker::ObjectsFilteringParams; +using behavior_path_planner::utils::path_safety_checker::PoseWithVelocityStamped; +using behavior_path_planner::utils::path_safety_checker::SafetyCheckParams; +using behavior_path_planner::utils::path_safety_checker::TargetObjectsOnLane; using geometry_msgs::msg::PoseArray; using lane_departure_checker::LaneDepartureChecker; @@ -90,6 +98,13 @@ class StartPlannerModule : public SceneModuleInterface void setParameters(const std::shared_ptr & parameters) { parameters_ = parameters; + if (parameters->safety_check_params.enable_safety_check) { + ego_predicted_path_params_ = + std::make_shared(parameters_->ego_predicted_path_params); + objects_filtering_params_ = + std::make_shared(parameters_->objects_filtering_params); + safety_check_params_ = std::make_shared(parameters_->safety_check_params); + } } void resetStatus(); @@ -110,10 +125,14 @@ class StartPlannerModule : public SceneModuleInterface bool canTransitIdleToRunningState() override { return false; } std::shared_ptr parameters_; + mutable std::shared_ptr ego_predicted_path_params_; + mutable std::shared_ptr objects_filtering_params_; + mutable std::shared_ptr safety_check_params_; vehicle_info_util::VehicleInfo vehicle_info_; std::vector> start_planners_; PullOutStatus status_; + mutable StartGoalPlannerData start_planner_data_; std::deque odometry_buffer_; @@ -155,6 +174,10 @@ class StartPlannerModule : public SceneModuleInterface bool isStopped(); bool isStuck(); bool hasFinishedCurrentPath(); + void updateSafetyCheckTargetObjectsData( + const PredictedObjects & filtered_objects, const TargetObjectsOnLane & target_objects_on_lane, + const std::vector & ego_predicted_path) const; + bool isSafePath() const; void setDrivableAreaInfo(BehaviorModuleOutput & output) const; // check if the goal is located behind the ego in the same route segment. @@ -163,6 +186,7 @@ class StartPlannerModule : public SceneModuleInterface // generate BehaviorPathOutput with stopping path and update status BehaviorModuleOutput generateStopOutput(); + SafetyCheckParams createSafetyCheckParams() const; // freespace planner void onFreespacePlannerTimer(); bool planFreespacePath(); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp index 60bbb3c78b401..bea671c266899 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp @@ -63,7 +63,7 @@ void updateEgoPredictedPathParams( void updateEgoPredictedPathParams( std::shared_ptr & ego_predicted_path_params, - const std::shared_ptr & start_planner_params); + const std::shared_ptr & goal_planner_params); void updateSafetyCheckParams( std::shared_ptr & safety_check_params, @@ -71,7 +71,7 @@ void updateSafetyCheckParams( void updateSafetyCheckParams( std::shared_ptr & safety_check_params, - const std::shared_ptr & start_planner_params); + const std::shared_ptr & goal_planner_params); void updateObjectsFilteringParams( std::shared_ptr & objects_filtering_params, @@ -79,7 +79,7 @@ void updateObjectsFilteringParams( void updateObjectsFilteringParams( std::shared_ptr & objects_filtering_params, - const std::shared_ptr & start_planner_params); + const std::shared_ptr & goal_planner_params); void updatePathProperty( std::shared_ptr & ego_predicted_path_params, diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp index 63e374e074a5a..d85e6181764d5 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp @@ -16,6 +16,9 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__START_PLANNER__UTIL_HPP_ #include "behavior_path_planner/data_manager.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" +#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" +#include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" #include @@ -36,6 +39,7 @@ namespace behavior_path_planner::start_planner_utils using autoware_auto_perception_msgs::msg::PredictedObjects; using autoware_auto_perception_msgs::msg::PredictedPath; using autoware_auto_planning_msgs::msg::PathWithLaneId; +using behavior_path_planner::utils::path_safety_checker::EgoPredictedPathParams; using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; using route_handler::RouteHandler; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 7b86cb5c16440..b9fd3ef781d1a 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/create_vehicle_footprint.hpp" #include "behavior_path_planner/utils/goal_planner/util.hpp" +#include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" @@ -340,6 +341,22 @@ bool GoalPlannerModule::isExecutionRequested() const bool GoalPlannerModule::isExecutionReady() const { + // TODO(Sugahara): should check safe or not but in the current flow, it is not possible. + if (status_.pull_over_path == nullptr) { + return true; + } + + if (status_.is_safe_static_objects && parameters_->safety_check_params.enable_safety_check) { + utils::start_goal_planner_common::updateEgoPredictedPathParams( + ego_predicted_path_params_, parameters_); + utils::start_goal_planner_common::updateSafetyCheckParams(safety_check_params_, parameters_); + utils::start_goal_planner_common::updateObjectsFilteringParams( + objects_filtering_params_, parameters_); + if (!isSafePath()) { + RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against dynamic objects"); + return false; + } + } return true; } @@ -1441,12 +1458,82 @@ bool GoalPlannerModule::isCrossingPossible(const PullOverPath & pull_over_path) return isCrossingPossible(start_pose, end_pose, lanes); } +void GoalPlannerModule::updateSafetyCheckTargetObjectsData( + const PredictedObjects & filtered_objects, const TargetObjectsOnLane & target_objects_on_lane, + const std::vector & ego_predicted_path) const +{ + goal_planner_data_.filtered_objects = filtered_objects; + goal_planner_data_.target_objects_on_lane = target_objects_on_lane; + goal_planner_data_.ego_predicted_path = ego_predicted_path; +} + +bool GoalPlannerModule::isSafePath() const +{ + const auto & pull_over_path = getCurrentPath(); + const auto & current_pose = planner_data_->self_odometry->pose.pose; + const auto & current_velocity = std::hypot( + planner_data_->self_odometry->twist.twist.linear.x, + planner_data_->self_odometry->twist.twist.linear.y); + const auto & dynamic_object = planner_data_->dynamic_object; + const auto & route_handler = planner_data_->route_handler; + const double backward_path_length = planner_data_->parameters.backward_path_length; + const auto current_lanes = utils::getExtendedCurrentLanes( + planner_data_, backward_path_length, std::numeric_limits::max(), + /*forward_only_in_route*/ true); + const auto & pull_over_lanes = + goal_planner_utils::getPullOverLanes(*(route_handler), left_side_parking_); + const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(pull_over_path.points); + const auto & common_param = planner_data_->parameters; + const std::pair terminal_velocity_and_accel = + utils::start_goal_planner_common::getPairsTerminalVelocityAndAccel( + status_.pull_over_path->pairs_terminal_velocity_and_accel, status_.current_path_idx); + RCLCPP_DEBUG( + getLogger(), "pairs_terminal_velocity_and_accel: %f, %f", terminal_velocity_and_accel.first, + terminal_velocity_and_accel.second); + RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.current_path_idx); + utils::start_goal_planner_common::updatePathProperty( + ego_predicted_path_params_, terminal_velocity_and_accel); + const auto & ego_predicted_path = + behavior_path_planner::utils::path_safety_checker::createPredictedPath( + ego_predicted_path_params_, pull_over_path.points, current_pose, current_velocity, + ego_seg_idx); + + const auto & filtered_objects = utils::path_safety_checker::filterObjects( + dynamic_object, route_handler, pull_over_lanes, current_pose.position, + objects_filtering_params_); + + const auto & target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( + pull_over_lanes, route_handler, filtered_objects, objects_filtering_params_); + + const double hysteresis_factor = + status_.is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; + + updateSafetyCheckTargetObjectsData(filtered_objects, target_objects_on_lane, ego_predicted_path); + + for (const auto & object : target_objects_on_lane.on_current_lane) { + const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( + object, objects_filtering_params_->check_all_predicted_path); + for (const auto & obj_path : obj_predicted_paths) { + CollisionCheckDebug collision{}; + if (!utils::path_safety_checker::checkCollision( + pull_over_path, ego_predicted_path, object, obj_path, common_param, + safety_check_params_->rss_params, hysteresis_factor, collision)) { + return false; + } + } + } + + return true; +} + void GoalPlannerModule::setDebugData() { debug_marker_.markers.clear(); + using marker_utils::createObjectsMarkerArray; using marker_utils::createPathMarkerArray; using marker_utils::createPoseMarkerArray; + using marker_utils::createPredictedPathMarkerArray; using motion_utils::createStopVirtualWallMarker; using tier4_autoware_utils::createDefaultMarker; using tier4_autoware_utils::createMarkerColor; @@ -1488,6 +1575,17 @@ void GoalPlannerModule::setDebugData() add( createPathMarkerArray(partial_path, "partial_path_" + std::to_string(i), 0, 0.9, 0.5, 0.9)); } + if (goal_planner_data_.ego_predicted_path.size() > 0) { + const auto & ego_predicted_path = utils::path_safety_checker::convertToPredictedPath( + goal_planner_data_.ego_predicted_path, ego_predicted_path_params_->time_resolution); + add(createPredictedPathMarkerArray( + ego_predicted_path, vehicle_info_, "ego_predicted_path", 0, 0.0, 0.5, 0.9)); + } + + if (goal_planner_data_.filtered_objects.objects.size() > 0) { + add(createObjectsMarkerArray( + goal_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); + } } // Visualize planner type text diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index 209b12c42537c..d51046d3456c7 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -32,18 +32,18 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( { GoalPlannerParameters p; + const std::string base_ns = "goal_planner."; // general params { - std::string ns = "goal_planner."; - p.minimum_request_length = node->declare_parameter(ns + "minimum_request_length"); - p.th_stopped_velocity = node->declare_parameter(ns + "th_stopped_velocity"); - p.th_arrived_distance = node->declare_parameter(ns + "th_arrived_distance"); - p.th_stopped_time = node->declare_parameter(ns + "th_stopped_time"); + p.minimum_request_length = node->declare_parameter(base_ns + "minimum_request_length"); + p.th_stopped_velocity = node->declare_parameter(base_ns + "th_stopped_velocity"); + p.th_arrived_distance = node->declare_parameter(base_ns + "th_arrived_distance"); + p.th_stopped_time = node->declare_parameter(base_ns + "th_stopped_time"); } // goal search { - std::string ns = "goal_planner.goal_search."; + const std::string ns = base_ns + "goal_search."; p.search_priority = node->declare_parameter(ns + "search_priority"); p.forward_goal_search_length = node->declare_parameter(ns + "forward_goal_search_length"); @@ -72,7 +72,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // occupancy grid map { - std::string ns = "goal_planner.occupancy_grid."; + const std::string ns = base_ns + "occupancy_grid."; p.use_occupancy_grid = node->declare_parameter(ns + "use_occupancy_grid"); p.use_occupancy_grid_for_longitudinal_margin = node->declare_parameter(ns + "use_occupancy_grid_for_longitudinal_margin"); @@ -84,7 +84,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // object recognition { - std::string ns = "goal_planner.object_recognition."; + const std::string ns = base_ns + "object_recognition."; p.use_object_recognition = node->declare_parameter(ns + "use_object_recognition"); p.object_recognition_collision_check_margin = node->declare_parameter(ns + "object_recognition_collision_check_margin"); @@ -95,7 +95,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // pull over general params { - std::string ns = "goal_planner.pull_over."; + const std::string ns = base_ns + "pull_over."; p.pull_over_velocity = node->declare_parameter(ns + "pull_over_velocity"); p.pull_over_minimum_velocity = node->declare_parameter(ns + "pull_over_minimum_velocity"); @@ -106,7 +106,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // shift parking { - std::string ns = "goal_planner.pull_over.shift_parking."; + const std::string ns = base_ns + "pull_over.shift_parking."; p.enable_shift_parking = node->declare_parameter(ns + "enable_shift_parking"); p.shift_sampling_num = node->declare_parameter(ns + "shift_sampling_num"); p.maximum_lateral_jerk = node->declare_parameter(ns + "maximum_lateral_jerk"); @@ -118,7 +118,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // forward parallel parking forward { - std::string ns = "goal_planner.pull_over.parallel_parking.forward."; + const std::string ns = base_ns + "pull_over.parallel_parking.forward."; p.enable_arc_forward_parking = node->declare_parameter(ns + "enable_arc_forward_parking"); p.parallel_parking_parameters.after_forward_parking_straight_distance = node->declare_parameter(ns + "after_forward_parking_straight_distance"); @@ -134,7 +134,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // forward parallel parking backward { - std::string ns = "goal_planner.pull_over.parallel_parking.backward."; + const std::string ns = base_ns + "pull_over.parallel_parking.backward."; p.enable_arc_backward_parking = node->declare_parameter(ns + "enable_arc_backward_parking"); p.parallel_parking_parameters.after_backward_parking_straight_distance = @@ -151,7 +151,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // freespace parking general params { - std::string ns = "goal_planner.pull_over.freespace_parking."; + const std::string ns = base_ns + "pull_over.freespace_parking."; p.enable_freespace_parking = node->declare_parameter(ns + "enable_freespace_parking"); p.freespace_parking_algorithm = node->declare_parameter(ns + "freespace_parking_algorithm"); @@ -174,7 +174,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // freespace parking search config { - std::string ns = "goal_planner.pull_over.freespace_parking.search_configs."; + const std::string ns = base_ns + "pull_over.freespace_parking.search_configs."; p.freespace_parking_common_parameters.theta_size = node->declare_parameter(ns + "theta_size"); p.freespace_parking_common_parameters.angle_goal_range = @@ -191,14 +191,14 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // freespace parking costmap configs { - std::string ns = "goal_planner.pull_over.freespace_parking.costmap_configs."; + const std::string ns = base_ns + "pull_over.freespace_parking.costmap_configs."; p.freespace_parking_common_parameters.obstacle_threshold = node->declare_parameter(ns + "obstacle_threshold"); } // freespace parking astar { - std::string ns = "goal_planner.pull_over.freespace_parking.astar."; + const std::string ns = base_ns + "pull_over.freespace_parking.astar."; p.astar_parameters.only_behind_solutions = node->declare_parameter(ns + "only_behind_solutions"); p.astar_parameters.use_back = node->declare_parameter(ns + "use_back"); @@ -208,7 +208,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // freespace parking rrtstar { - std::string ns = "goal_planner.pull_over.freespace_parking.rrtstar."; + const std::string ns = base_ns + "pull_over.freespace_parking.rrtstar."; p.rrt_star_parameters.enable_update = node->declare_parameter(ns + "enable_update"); p.rrt_star_parameters.use_informed_sampling = node->declare_parameter(ns + "use_informed_sampling"); @@ -218,9 +218,125 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( p.rrt_star_parameters.margin = node->declare_parameter(ns + "margin"); } + // stop condition + { + p.maximum_deceleration_for_stop = + node->declare_parameter(base_ns + "stop_condition.maximum_deceleration_for_stop"); + p.maximum_jerk_for_stop = + node->declare_parameter(base_ns + "stop_condition.maximum_jerk_for_stop"); + } + + std::string path_safety_check_ns = "goal_planner.path_safety_check."; + + // EgoPredictedPath + std::string ego_path_ns = path_safety_check_ns + "ego_predicted_path."; + { + p.ego_predicted_path_params.acceleration = + node->declare_parameter(ego_path_ns + "acceleration"); + p.ego_predicted_path_params.time_horizon = + node->declare_parameter(ego_path_ns + "time_horizon"); + p.ego_predicted_path_params.time_resolution = + node->declare_parameter(ego_path_ns + "time_resolution"); + p.ego_predicted_path_params.min_slow_speed = + node->declare_parameter(ego_path_ns + "min_slow_speed"); + p.ego_predicted_path_params.delay_until_departure = + node->declare_parameter(ego_path_ns + "delay_until_departure"); + p.ego_predicted_path_params.target_velocity = + node->declare_parameter(ego_path_ns + "target_velocity"); + } + + // ObjectFilteringParams + std::string obj_filter_ns = path_safety_check_ns + "target_filtering."; + { + p.objects_filtering_params.safety_check_time_horizon = + node->declare_parameter(obj_filter_ns + "safety_check_time_horizon"); + p.objects_filtering_params.safety_check_time_resolution = + node->declare_parameter(obj_filter_ns + "safety_check_time_resolution"); + p.objects_filtering_params.object_check_forward_distance = + node->declare_parameter(obj_filter_ns + "object_check_forward_distance"); + p.objects_filtering_params.object_check_backward_distance = + node->declare_parameter(obj_filter_ns + "object_check_backward_distance"); + p.objects_filtering_params.ignore_object_velocity_threshold = + node->declare_parameter(obj_filter_ns + "ignore_object_velocity_threshold"); + p.objects_filtering_params.include_opposite_lane = + node->declare_parameter(obj_filter_ns + "include_opposite_lane"); + p.objects_filtering_params.invert_opposite_lane = + node->declare_parameter(obj_filter_ns + "invert_opposite_lane"); + p.objects_filtering_params.check_all_predicted_path = + node->declare_parameter(obj_filter_ns + "check_all_predicted_path"); + p.objects_filtering_params.use_all_predicted_path = + node->declare_parameter(obj_filter_ns + "use_all_predicted_path"); + p.objects_filtering_params.use_predicted_path_outside_lanelet = + node->declare_parameter(obj_filter_ns + "use_predicted_path_outside_lanelet"); + } + + // ObjectTypesToCheck + std::string obj_types_ns = obj_filter_ns + "object_types_to_check."; + { + p.objects_filtering_params.object_types_to_check.check_car = + node->declare_parameter(obj_types_ns + "check_car"); + p.objects_filtering_params.object_types_to_check.check_truck = + node->declare_parameter(obj_types_ns + "check_truck"); + p.objects_filtering_params.object_types_to_check.check_bus = + node->declare_parameter(obj_types_ns + "check_bus"); + p.objects_filtering_params.object_types_to_check.check_trailer = + node->declare_parameter(obj_types_ns + "check_trailer"); + p.objects_filtering_params.object_types_to_check.check_unknown = + node->declare_parameter(obj_types_ns + "check_unknown"); + p.objects_filtering_params.object_types_to_check.check_bicycle = + node->declare_parameter(obj_types_ns + "check_bicycle"); + p.objects_filtering_params.object_types_to_check.check_motorcycle = + node->declare_parameter(obj_types_ns + "check_motorcycle"); + p.objects_filtering_params.object_types_to_check.check_pedestrian = + node->declare_parameter(obj_types_ns + "check_pedestrian"); + } + + // ObjectLaneConfiguration + std::string obj_lane_ns = obj_filter_ns + "object_lane_configuration."; + { + p.objects_filtering_params.object_lane_configuration.check_current_lane = + node->declare_parameter(obj_lane_ns + "check_current_lane"); + p.objects_filtering_params.object_lane_configuration.check_right_lane = + node->declare_parameter(obj_lane_ns + "check_right_side_lane"); + p.objects_filtering_params.object_lane_configuration.check_left_lane = + node->declare_parameter(obj_lane_ns + "check_left_side_lane"); + p.objects_filtering_params.object_lane_configuration.check_shoulder_lane = + node->declare_parameter(obj_lane_ns + "check_shoulder_lane"); + p.objects_filtering_params.object_lane_configuration.check_other_lane = + node->declare_parameter(obj_lane_ns + "check_other_lane"); + } + + // SafetyCheckParams + std::string safety_check_ns = path_safety_check_ns + "safety_check_params."; + { + p.safety_check_params.enable_safety_check = + node->declare_parameter(safety_check_ns + "enable_safety_check"); + p.safety_check_params.backward_path_length = + node->declare_parameter(safety_check_ns + "backward_path_length"); + p.safety_check_params.forward_path_length = + node->declare_parameter(safety_check_ns + "forward_path_length"); + p.safety_check_params.publish_debug_marker = + node->declare_parameter(safety_check_ns + "publish_debug_marker"); + } + + // RSSparams + std::string rss_ns = safety_check_ns + "rss_params."; + { + p.safety_check_params.rss_params.rear_vehicle_reaction_time = + node->declare_parameter(rss_ns + "rear_vehicle_reaction_time"); + p.safety_check_params.rss_params.rear_vehicle_safety_time_margin = + node->declare_parameter(rss_ns + "rear_vehicle_safety_time_margin"); + p.safety_check_params.rss_params.lateral_distance_max_threshold = + node->declare_parameter(rss_ns + "lateral_distance_max_threshold"); + p.safety_check_params.rss_params.longitudinal_distance_min_threshold = + node->declare_parameter(rss_ns + "longitudinal_distance_min_threshold"); + p.safety_check_params.rss_params.longitudinal_velocity_delta_time = + node->declare_parameter(rss_ns + "longitudinal_velocity_delta_time"); + } + // debug { - std::string ns = "goal_planner.debug."; + const std::string ns = base_ns + "debug."; p.print_debug_info = node->declare_parameter(ns + "print_debug_info"); } diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp index 9d2da0d13f56b..5f4b57d9c6367 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp @@ -148,6 +148,122 @@ StartPlannerModuleManager::StartPlannerModuleManager( p.rrt_star_parameters.margin = node->declare_parameter(ns + "margin"); } + // stop condition + { + p.maximum_deceleration_for_stop = + node->declare_parameter(ns + "stop_condition.maximum_deceleration_for_stop"); + p.maximum_jerk_for_stop = + node->declare_parameter(ns + "stop_condition.maximum_jerk_for_stop"); + } + + std::string base_ns = "start_planner.path_safety_check."; + + // EgoPredictedPath + std::string ego_path_ns = base_ns + "ego_predicted_path."; + { + p.ego_predicted_path_params.acceleration = + node->declare_parameter(ego_path_ns + "acceleration"); + p.ego_predicted_path_params.time_horizon = + node->declare_parameter(ego_path_ns + "time_horizon"); + p.ego_predicted_path_params.time_resolution = + node->declare_parameter(ego_path_ns + "time_resolution"); + p.ego_predicted_path_params.min_slow_speed = + node->declare_parameter(ego_path_ns + "min_slow_speed"); + p.ego_predicted_path_params.delay_until_departure = + node->declare_parameter(ego_path_ns + "delay_until_departure"); + p.ego_predicted_path_params.target_velocity = + node->declare_parameter(ego_path_ns + "target_velocity"); + } + + // ObjectFilteringParams + std::string obj_filter_ns = base_ns + "target_filtering."; + { + p.objects_filtering_params.safety_check_time_horizon = + node->declare_parameter(obj_filter_ns + "safety_check_time_horizon"); + p.objects_filtering_params.safety_check_time_resolution = + node->declare_parameter(obj_filter_ns + "safety_check_time_resolution"); + p.objects_filtering_params.object_check_forward_distance = + node->declare_parameter(obj_filter_ns + "object_check_forward_distance"); + p.objects_filtering_params.object_check_backward_distance = + node->declare_parameter(obj_filter_ns + "object_check_backward_distance"); + p.objects_filtering_params.ignore_object_velocity_threshold = + node->declare_parameter(obj_filter_ns + "ignore_object_velocity_threshold"); + p.objects_filtering_params.include_opposite_lane = + node->declare_parameter(obj_filter_ns + "include_opposite_lane"); + p.objects_filtering_params.invert_opposite_lane = + node->declare_parameter(obj_filter_ns + "invert_opposite_lane"); + p.objects_filtering_params.check_all_predicted_path = + node->declare_parameter(obj_filter_ns + "check_all_predicted_path"); + p.objects_filtering_params.use_all_predicted_path = + node->declare_parameter(obj_filter_ns + "use_all_predicted_path"); + p.objects_filtering_params.use_predicted_path_outside_lanelet = + node->declare_parameter(obj_filter_ns + "use_predicted_path_outside_lanelet"); + } + + // ObjectTypesToCheck + std::string obj_types_ns = obj_filter_ns + "object_types_to_check."; + { + p.objects_filtering_params.object_types_to_check.check_car = + node->declare_parameter(obj_types_ns + "check_car"); + p.objects_filtering_params.object_types_to_check.check_truck = + node->declare_parameter(obj_types_ns + "check_truck"); + p.objects_filtering_params.object_types_to_check.check_bus = + node->declare_parameter(obj_types_ns + "check_bus"); + p.objects_filtering_params.object_types_to_check.check_trailer = + node->declare_parameter(obj_types_ns + "check_trailer"); + p.objects_filtering_params.object_types_to_check.check_unknown = + node->declare_parameter(obj_types_ns + "check_unknown"); + p.objects_filtering_params.object_types_to_check.check_bicycle = + node->declare_parameter(obj_types_ns + "check_bicycle"); + p.objects_filtering_params.object_types_to_check.check_motorcycle = + node->declare_parameter(obj_types_ns + "check_motorcycle"); + p.objects_filtering_params.object_types_to_check.check_pedestrian = + node->declare_parameter(obj_types_ns + "check_pedestrian"); + } + + // ObjectLaneConfiguration + std::string obj_lane_ns = obj_filter_ns + "object_lane_configuration."; + { + p.objects_filtering_params.object_lane_configuration.check_current_lane = + node->declare_parameter(obj_lane_ns + "check_current_lane"); + p.objects_filtering_params.object_lane_configuration.check_right_lane = + node->declare_parameter(obj_lane_ns + "check_right_side_lane"); + p.objects_filtering_params.object_lane_configuration.check_left_lane = + node->declare_parameter(obj_lane_ns + "check_left_side_lane"); + p.objects_filtering_params.object_lane_configuration.check_shoulder_lane = + node->declare_parameter(obj_lane_ns + "check_shoulder_lane"); + p.objects_filtering_params.object_lane_configuration.check_other_lane = + node->declare_parameter(obj_lane_ns + "check_other_lane"); + } + + // SafetyCheckParams + std::string safety_check_ns = base_ns + "safety_check_params."; + { + p.safety_check_params.enable_safety_check = + node->declare_parameter(safety_check_ns + "enable_safety_check"); + p.safety_check_params.backward_path_length = + node->declare_parameter(safety_check_ns + "backward_path_length"); + p.safety_check_params.forward_path_length = + node->declare_parameter(safety_check_ns + "forward_path_length"); + p.safety_check_params.publish_debug_marker = + node->declare_parameter(safety_check_ns + "publish_debug_marker"); + } + + // RSSparams + std::string rss_ns = safety_check_ns + "rss_params."; + { + p.safety_check_params.rss_params.rear_vehicle_reaction_time = + node->declare_parameter(rss_ns + "rear_vehicle_reaction_time"); + p.safety_check_params.rss_params.rear_vehicle_safety_time_margin = + node->declare_parameter(rss_ns + "rear_vehicle_safety_time_margin"); + p.safety_check_params.rss_params.lateral_distance_max_threshold = + node->declare_parameter(rss_ns + "lateral_distance_max_threshold"); + p.safety_check_params.rss_params.longitudinal_distance_min_threshold = + node->declare_parameter(rss_ns + "longitudinal_distance_min_threshold"); + p.safety_check_params.rss_params.longitudinal_velocity_delta_time = + node->declare_parameter(rss_ns + "longitudinal_velocity_delta_time"); + } + // validation of parameters if (p.lateral_acceleration_sampling_num < 1) { RCLCPP_FATAL_STREAM( diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index abdd41f4935c5..949b6ae633b64 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -17,7 +17,9 @@ #include "behavior_path_planner/utils/create_vehicle_footprint.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include #include @@ -169,6 +171,21 @@ bool StartPlannerModule::isExecutionRequested() const bool StartPlannerModule::isExecutionReady() const { + if (status_.pull_out_path.partial_paths.empty()) { + return true; + } + + if (status_.is_safe_static_objects && parameters_->safety_check_params.enable_safety_check) { + utils::start_goal_planner_common::updateEgoPredictedPathParams( + ego_predicted_path_params_, parameters_); + utils::start_goal_planner_common::updateSafetyCheckParams(safety_check_params_, parameters_); + utils::start_goal_planner_common::updateObjectsFilteringParams( + objects_filtering_params_, parameters_); + if (!isSafePath()) { + RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against dynamic objects"); + return false; + } + } return true; } @@ -923,6 +940,70 @@ TurnSignalInfo StartPlannerModule::calcTurnSignalInfo() const return turn_signal; } +void StartPlannerModule::updateSafetyCheckTargetObjectsData( + const PredictedObjects & filtered_objects, const TargetObjectsOnLane & target_objects_on_lane, + const std::vector & ego_predicted_path) const +{ + start_planner_data_.filtered_objects = filtered_objects; + start_planner_data_.target_objects_on_lane = target_objects_on_lane; + start_planner_data_.ego_predicted_path = ego_predicted_path; +} + +bool StartPlannerModule::isSafePath() const +{ + // TODO(Sugahara): should safety check for backward path + + const auto & pull_out_path = getCurrentPath(); + const auto & current_pose = planner_data_->self_odometry->pose.pose; + const auto & current_velocity = std::hypot( + planner_data_->self_odometry->twist.twist.linear.x, + planner_data_->self_odometry->twist.twist.linear.y); + const auto & dynamic_object = planner_data_->dynamic_object; + const auto & route_handler = planner_data_->route_handler; + const double backward_path_length = + planner_data_->parameters.backward_path_length + parameters_->max_back_distance; + const auto current_lanes = utils::getExtendedCurrentLanes( + planner_data_, backward_path_length, std::numeric_limits::max(), + /*forward_only_in_route*/ true); + const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(pull_out_path.points); + const auto & common_param = planner_data_->parameters; + const std::pair terminal_velocity_and_accel = + utils::start_goal_planner_common::getPairsTerminalVelocityAndAccel( + status_.pull_out_path.pairs_terminal_velocity_and_accel, status_.current_path_idx); + utils::start_goal_planner_common::updatePathProperty( + ego_predicted_path_params_, terminal_velocity_and_accel); + const auto & ego_predicted_path = + behavior_path_planner::utils::path_safety_checker::createPredictedPath( + ego_predicted_path_params_, pull_out_path.points, current_pose, current_velocity, + ego_seg_idx); + + const auto & filtered_objects = utils::path_safety_checker::filterObjects( + dynamic_object, route_handler, current_lanes, current_pose.position, objects_filtering_params_); + + const auto & target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( + current_lanes, route_handler, filtered_objects, objects_filtering_params_); + + const double hysteresis_factor = + status_.is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; + + updateSafetyCheckTargetObjectsData(filtered_objects, target_objects_on_lane, ego_predicted_path); + + for (const auto & object : target_objects_on_lane.on_current_lane) { + const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( + object, objects_filtering_params_->check_all_predicted_path); + for (const auto & obj_path : obj_predicted_paths) { + CollisionCheckDebug collision{}; + if (!utils::path_safety_checker::checkCollision( + pull_out_path, ego_predicted_path, object, obj_path, common_param, + safety_check_params_->rss_params, hysteresis_factor, collision)) { + return false; + } + } + } + + return true; +} + bool StartPlannerModule::IsGoalBehindOfEgoInSameRouteSegment() const { const auto & rh = planner_data_->route_handler; @@ -1041,8 +1122,10 @@ void StartPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) cons void StartPlannerModule::setDebugData() const { + using marker_utils::createObjectsMarkerArray; using marker_utils::createPathMarkerArray; using marker_utils::createPoseMarkerArray; + using marker_utils::createPredictedPathMarkerArray; using tier4_autoware_utils::createDefaultMarker; using tier4_autoware_utils::createMarkerColor; using tier4_autoware_utils::createMarkerScale; @@ -1060,6 +1143,17 @@ void StartPlannerModule::setDebugData() const add(createPoseMarkerArray(status_.pull_out_path.start_pose, "start_pose", 0, 0.3, 0.9, 0.3)); add(createPoseMarkerArray(status_.pull_out_path.end_pose, "end_pose", 0, 0.9, 0.9, 0.3)); add(createPathMarkerArray(getFullPath(), "full_path", 0, 0.0, 0.5, 0.9)); + if (start_planner_data_.ego_predicted_path.size() > 0) { + const auto & ego_predicted_path = utils::path_safety_checker::convertToPredictedPath( + start_planner_data_.ego_predicted_path, ego_predicted_path_params_->time_resolution); + add(createPredictedPathMarkerArray( + ego_predicted_path, vehicle_info_, "ego_predicted_path", 0, 0.0, 0.5, 0.9)); + } + + if (start_planner_data_.filtered_objects.objects.size() > 0) { + add(createObjectsMarkerArray( + start_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); + } // Visualize planner type text const auto header = planner_data_->route_handler->getRouteHeader(); diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index f29711c82b3ce..cff6e9f81f02e 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -131,8 +131,15 @@ void updatePathProperty( std::shared_ptr & ego_predicted_path_params, const std::pair & pairs_terminal_velocity_and_accel) { + // If acceleration is close to 0, the ego predicted path will be too short, so a minimum value is + // necessary to ensure a reasonable path length. + // TODO(Sugahara): define them as parameter + const double min_accel_for_ego_predicted_path = 1.0; + const double acceleration = + std::max(pairs_terminal_velocity_and_accel.second, min_accel_for_ego_predicted_path); + ego_predicted_path_params->target_velocity = pairs_terminal_velocity_and_accel.first; - ego_predicted_path_params->acceleration = pairs_terminal_velocity_and_accel.second; + ego_predicted_path_params->acceleration = acceleration; } std::pair getPairsTerminalVelocityAndAccel( From cc6efbff570c622dbeb0d646eb7f097ceb3cb4d6 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 5 Sep 2023 21:24:17 +0900 Subject: [PATCH 130/547] feat(goal_planner): set ignore_distance_from_lane_start 0.0 (#4889) Signed-off-by: kosuke55 --- .../config/goal_planner/goal_planner.param.yaml | 2 +- .../docs/behavior_path_planner_goal_planner_design.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 433ac428fe73e..9f5e6e6949aef 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -17,7 +17,7 @@ longitudinal_margin: 3.0 max_lateral_offset: 0.5 lateral_offset_interval: 0.25 - ignore_distance_from_lane_start: 15.0 + ignore_distance_from_lane_start: 0.0 margin_from_boundary: 0.5 # occupancy grid map diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index 0a78787c559d7..65447d0c1d618 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -174,7 +174,7 @@ searched for in certain range of the shoulder lane. | longitudinal_margin | [m] | double | margin between ego-vehicle at the goal position and obstacles | 3.0 | | max_lateral_offset | [m] | double | maximum offset of goal search in the lateral direction | 0.5 | | lateral_offset_interval | [m] | double | distance interval of goal search in the lateral direction | 0.25 | -| ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 15.0 | +| ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 0.0 | ## **Path Generation** From 340b10e9bb7e718906309f177cb57102f34527ad Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 5 Sep 2023 21:54:00 +0900 Subject: [PATCH 131/547] fix(operation_transition_manager): trajectgory deviation calculation (#4860) Signed-off-by: Takamasa Horibe --- .../src/state.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/control/operation_mode_transition_manager/src/state.cpp b/control/operation_mode_transition_manager/src/state.cpp index a17e491ebe98d..5062c596dce00 100644 --- a/control/operation_mode_transition_manager/src/state.cpp +++ b/control/operation_mode_transition_manager/src/state.cpp @@ -122,14 +122,24 @@ bool AutonomousMode::isModeChangeCompleted() const auto closest_point = trajectory_.points.at(*closest_idx); // check for lateral deviation - const auto dist_deviation = calcDistance2d(closest_point.pose, kinematics_.pose.pose); + const auto dist_deviation = + motion_utils::calcLateralOffset(trajectory_.points, kinematics_.pose.pose.position); + if (std::isnan(dist_deviation)) { + RCLCPP_INFO(logger_, "Not stable yet: lateral offset calculation failed."); + return unstable(); + } if (dist_deviation > stable_check_param_.dist_threshold) { RCLCPP_INFO(logger_, "Not stable yet: distance deviation is too large: %f", dist_deviation); return unstable(); } // check for yaw deviation - const auto yaw_deviation = calcYawDeviation(closest_point.pose, kinematics_.pose.pose); + const auto yaw_deviation = + motion_utils::calcYawDeviation(trajectory_.points, kinematics_.pose.pose); + if (std::isnan(yaw_deviation)) { + RCLCPP_INFO(logger_, "Not stable yet: lateral offset calculation failed."); + return unstable(); + } if (yaw_deviation > stable_check_param_.yaw_threshold) { RCLCPP_INFO(logger_, "Not stable yet: yaw deviation is too large: %f", yaw_deviation); return unstable(); From c03ca7865e2af67694431a23ca4e17ae1a7b53c4 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 6 Sep 2023 02:16:59 +0900 Subject: [PATCH 132/547] refactor(start/goal_planner): minor refactor like const and reference (#4897) Signed-off-by: kosuke55 --- .../utils/start_planner/freespace_pull_out.hpp | 2 +- .../utils/start_planner/geometric_pull_out.hpp | 2 +- .../start_planner/pull_out_planner_base.hpp | 2 +- .../utils/start_planner/shift_pull_out.hpp | 2 +- .../goal_planner/goal_planner_module.cpp | 16 ++++++++-------- .../start_planner/start_planner_module.cpp | 14 +++++++------- .../geometric_parallel_parking.cpp | 1 - .../utils/start_planner/freespace_pull_out.cpp | 4 ++-- .../utils/start_planner/geometric_pull_out.cpp | 2 +- .../src/utils/start_planner/shift_pull_out.cpp | 2 +- 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp index abdf17c9c6cfe..2b2de183b2dac 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp @@ -41,7 +41,7 @@ class FreespacePullOut : public PullOutPlannerBase PlannerType getPlannerType() override { return PlannerType::FREESPACE; } - boost::optional plan(Pose start_pose, Pose end_pose) override; + boost::optional plan(const Pose & start_pose, const Pose & end_pose) override; protected: std::unique_ptr planner_; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/geometric_pull_out.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/geometric_pull_out.hpp index 7ace770dc7ff1..a5ff52e74038f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/geometric_pull_out.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/geometric_pull_out.hpp @@ -29,7 +29,7 @@ class GeometricPullOut : public PullOutPlannerBase explicit GeometricPullOut(rclcpp::Node & node, const StartPlannerParameters & parameters); PlannerType getPlannerType() override { return PlannerType::GEOMETRIC; }; - boost::optional plan(Pose start_pose, Pose goal_pose) override; + boost::optional plan(const Pose & start_pose, const Pose & goal_pose) override; GeometricParallelParking planner_; ParallelParkingParameters parallel_parking_parameters_; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp index 6e6e5111e5dd9..cb662efd767bf 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp @@ -60,7 +60,7 @@ class PullOutPlannerBase } virtual PlannerType getPlannerType() = 0; - virtual boost::optional plan(Pose start_pose, Pose goal_pose) = 0; + virtual boost::optional plan(const Pose & start_pose, const Pose & goal_pose) = 0; protected: std::shared_ptr planner_data_; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp index 2042593064c51..0842d0a17bd97 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp @@ -37,7 +37,7 @@ class ShiftPullOut : public PullOutPlannerBase std::shared_ptr & lane_departure_checker); PlannerType getPlannerType() override { return PlannerType::SHIFT; }; - boost::optional plan(Pose start_pose, Pose goal_pose) override; + boost::optional plan(const Pose & start_pose, const Pose & goal_pose) override; std::vector calcPullOutPaths( const RouteHandler & route_handler, const lanelet::ConstLanelets & road_lanes, diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index b9fd3ef781d1a..8403c9904c624 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -276,7 +276,7 @@ bool GoalPlannerModule::isExecutionRequested() const const auto & route_handler = planner_data_->route_handler; const Pose & current_pose = planner_data_->self_odometry->pose.pose; - const Pose & goal_pose = route_handler->getOriginalGoalPose(); + const Pose goal_pose = route_handler->getOriginalGoalPose(); // check if goal_pose is in current_lanes. lanelet::ConstLanelet current_lane{}; @@ -1145,7 +1145,7 @@ bool GoalPlannerModule::isStuck() bool GoalPlannerModule::hasFinishedCurrentPath() { - const auto & current_path_end = getCurrentPath().points.back(); + const auto current_path_end = getCurrentPath().points.back(); const auto & self_pose = planner_data_->self_odometry->pose.pose; const bool is_near_target = tier4_autoware_utils::calcDistance2d(current_path_end, self_pose) < parameters_->th_arrived_distance; @@ -1469,9 +1469,9 @@ void GoalPlannerModule::updateSafetyCheckTargetObjectsData( bool GoalPlannerModule::isSafePath() const { - const auto & pull_over_path = getCurrentPath(); + const auto pull_over_path = getCurrentPath(); const auto & current_pose = planner_data_->self_odometry->pose.pose; - const auto & current_velocity = std::hypot( + const double current_velocity = std::hypot( planner_data_->self_odometry->twist.twist.linear.x, planner_data_->self_odometry->twist.twist.linear.y); const auto & dynamic_object = planner_data_->dynamic_object; @@ -1480,7 +1480,7 @@ bool GoalPlannerModule::isSafePath() const const auto current_lanes = utils::getExtendedCurrentLanes( planner_data_, backward_path_length, std::numeric_limits::max(), /*forward_only_in_route*/ true); - const auto & pull_over_lanes = + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes(*(route_handler), left_side_parking_); const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(pull_over_path.points); const auto & common_param = planner_data_->parameters; @@ -1493,16 +1493,16 @@ bool GoalPlannerModule::isSafePath() const RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.current_path_idx); utils::start_goal_planner_common::updatePathProperty( ego_predicted_path_params_, terminal_velocity_and_accel); - const auto & ego_predicted_path = + const auto ego_predicted_path = behavior_path_planner::utils::path_safety_checker::createPredictedPath( ego_predicted_path_params_, pull_over_path.points, current_pose, current_velocity, ego_seg_idx); - const auto & filtered_objects = utils::path_safety_checker::filterObjects( + const auto filtered_objects = utils::path_safety_checker::filterObjects( dynamic_object, route_handler, pull_over_lanes, current_pose.position, objects_filtering_params_); - const auto & target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( + const auto target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( pull_over_lanes, route_handler, filtered_objects, objects_filtering_params_); const double hysteresis_factor = diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 949b6ae633b64..e652dde998b39 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -110,7 +110,7 @@ bool StartPlannerModule::isExecutionRequested() const { // TODO(Sugahara): if required lateral shift distance is small, don't engage this module. // Execute when current pose is near route start pose - const Pose & start_pose = planner_data_->route_handler->getOriginalStartPose(); + const Pose start_pose = planner_data_->route_handler->getOriginalStartPose(); const Pose & current_pose = planner_data_->self_odometry->pose.pose; if ( tier4_autoware_utils::calcDistance2d(start_pose.position, current_pose.position) > @@ -119,7 +119,7 @@ bool StartPlannerModule::isExecutionRequested() const } // Check if ego arrives at goal - const Pose & goal_pose = planner_data_->route_handler->getGoalPose(); + const Pose goal_pose = planner_data_->route_handler->getGoalPose(); if ( tier4_autoware_utils::calcDistance2d(goal_pose.position, current_pose.position) < parameters_->th_arrived_distance) { @@ -953,9 +953,9 @@ bool StartPlannerModule::isSafePath() const { // TODO(Sugahara): should safety check for backward path - const auto & pull_out_path = getCurrentPath(); + const auto pull_out_path = getCurrentPath(); const auto & current_pose = planner_data_->self_odometry->pose.pose; - const auto & current_velocity = std::hypot( + const double current_velocity = std::hypot( planner_data_->self_odometry->twist.twist.linear.x, planner_data_->self_odometry->twist.twist.linear.y); const auto & dynamic_object = planner_data_->dynamic_object; @@ -972,15 +972,15 @@ bool StartPlannerModule::isSafePath() const status_.pull_out_path.pairs_terminal_velocity_and_accel, status_.current_path_idx); utils::start_goal_planner_common::updatePathProperty( ego_predicted_path_params_, terminal_velocity_and_accel); - const auto & ego_predicted_path = + const auto ego_predicted_path = behavior_path_planner::utils::path_safety_checker::createPredictedPath( ego_predicted_path_params_, pull_out_path.points, current_pose, current_velocity, ego_seg_idx); - const auto & filtered_objects = utils::path_safety_checker::filterObjects( + const auto filtered_objects = utils::path_safety_checker::filterObjects( dynamic_object, route_handler, current_lanes, current_pose.position, objects_filtering_params_); - const auto & target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( + const auto target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( current_lanes, route_handler, filtered_objects, objects_filtering_params_); const double hysteresis_factor = diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index 27dbfb532c1fc..99d109dd0bece 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -447,7 +447,6 @@ std::vector GeometricParallelParking::planOneTrial( if (std::abs(end_pose_offset) > 0) { PathPointWithLaneId straight_point{}; straight_point.point.pose = goal_pose; - // setLaneIds(straight_point); path_turn_right.points.push_back(straight_point); } diff --git a/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp index ba34901d9df6a..36fb6381ac967 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp @@ -44,7 +44,7 @@ FreespacePullOut::FreespacePullOut( } } -boost::optional FreespacePullOut::plan(const Pose start_pose, const Pose end_pose) +boost::optional FreespacePullOut::plan(const Pose & start_pose, const Pose & end_pose) { const auto & route_handler = planner_data_->route_handler; const double backward_path_length = planner_data_->parameters.backward_path_length; @@ -86,7 +86,7 @@ boost::optional FreespacePullOut::plan(const Pose start_pose, const } // push back generate road lane path between end pose and goal pose to last path - const auto & goal_pose = route_handler->getGoalPose(); + const Pose goal_pose = route_handler->getGoalPose(); constexpr double offset_from_end_pose = 1.0; const auto arc_position_end = lanelet::utils::getArcCoordinates(road_lanes, end_pose); const double s_start = std::max(arc_position_end.length + offset_from_end_pose, 0.0); diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index 29abdb08c5c55..f4f65474d4a30 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -36,7 +36,7 @@ GeometricPullOut::GeometricPullOut(rclcpp::Node & node, const StartPlannerParame planner_.setParameters(parallel_parking_parameters_); } -boost::optional GeometricPullOut::plan(Pose start_pose, Pose goal_pose) +boost::optional GeometricPullOut::plan(const Pose & start_pose, const Pose & goal_pose) { PullOutPath output; diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index 7352821e140be..f76aca793d571 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -40,7 +40,7 @@ ShiftPullOut::ShiftPullOut( { } -boost::optional ShiftPullOut::plan(Pose start_pose, Pose goal_pose) +boost::optional ShiftPullOut::plan(const Pose & start_pose, const Pose & goal_pose) { const auto & route_handler = planner_data_->route_handler; const auto & common_parameters = planner_data_->parameters; From b1f15486aed3cf2ee6a44ed2f5c806efcec8f88e Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 6 Sep 2023 02:18:12 +0900 Subject: [PATCH 133/547] refactor(start/goal_planner): use combineLanelets (#4894) Signed-off-by: kosuke55 --- .../start_planner/start_planner_module.cpp | 12 +----------- .../src/utils/goal_planner/geometric_pull_over.cpp | 9 ++++----- .../src/utils/start_planner/geometric_pull_out.cpp | 10 ---------- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index e652dde998b39..356f30f6d5842 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -150,17 +150,7 @@ bool StartPlannerModule::isExecutionRequested() const /*forward_only_in_route*/ true); const auto pull_out_lanes = start_planner_utils::getPullOutLanes(planner_data_, backward_path_length); - auto lanes = current_lanes; - for (const auto & pull_out_lane : pull_out_lanes) { - auto it = std::find_if( - lanes.begin(), lanes.end(), [&pull_out_lane](const lanelet::ConstLanelet & lane) { - return lane.id() == pull_out_lane.id(); - }); - - if (it == lanes.end()) { - lanes.push_back(pull_out_lane); - } - } + const auto lanes = utils::combineLanelets(current_lanes, pull_out_lanes); if (LaneDepartureChecker::isOutOfLane(lanes, vehicle_footprint)) { return false; diff --git a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp index 7760b9b57d8ab..5594f7af1c428 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp @@ -47,13 +47,12 @@ boost::optional GeometricPullOver::plan(const Pose & goal_pose) const auto road_lanes = utils::getExtendedCurrentLanes( planner_data_, parameters_.backward_goal_search_length, parameters_.forward_goal_search_length, /*forward_only_in_route*/ false); - const auto shoulder_lanes = + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes(*route_handler, left_side_parking_); - if (road_lanes.empty() || shoulder_lanes.empty()) { + if (road_lanes.empty() || pull_over_lanes.empty()) { return {}; } - auto lanes = road_lanes; - lanes.insert(lanes.end(), shoulder_lanes.begin(), shoulder_lanes.end()); + const auto lanes = utils::combineLanelets(road_lanes, pull_over_lanes); const auto & p = parallel_parking_parameters_; const double max_steer_angle = @@ -62,7 +61,7 @@ boost::optional GeometricPullOver::plan(const Pose & goal_pose) planner_.setPlannerData(planner_data_); const bool found_valid_path = - planner_.planPullOver(goal_pose, road_lanes, shoulder_lanes, is_forward_); + planner_.planPullOver(goal_pose, road_lanes, pull_over_lanes, is_forward_); if (!found_valid_path) { return {}; } diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index f4f65474d4a30..628f1cfd7e421 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -47,16 +47,6 @@ boost::optional GeometricPullOut::plan(const Pose & start_pose, con planner_data_, backward_path_length, std::numeric_limits::max(), /*forward_only_in_route*/ true); const auto pull_out_lanes = getPullOutLanes(planner_data_, backward_path_length); - auto lanes = road_lanes; - for (const auto & pull_out_lane : pull_out_lanes) { - auto it = std::find_if( - lanes.begin(), lanes.end(), [&pull_out_lane](const lanelet::ConstLanelet & lane) { - return lane.id() == pull_out_lane.id(); - }); - if (it == lanes.end()) { - lanes.push_back(pull_out_lane); - } - } planner_.setTurningRadius( planner_data_->parameters, parallel_parking_parameters_.pull_out_max_steer_angle); From c442e38f735fe1a3bd17886873875984d415f64b Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 6 Sep 2023 02:21:37 +0900 Subject: [PATCH 134/547] feat(goal_planner): do not use minimum_request_length for fixed goal planner (#4831) Signed-off-by: kosuke55 --- .../goal_planner/goal_planner.param.yaml | 2 +- ...havior_path_planner_goal_planner_design.md | 36 ++++++++++--------- .../goal_planner/goal_planner_parameters.hpp | 2 +- .../goal_planner/goal_planner_module.cpp | 13 +++++-- .../src/scene_module/goal_planner/manager.cpp | 3 +- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 9f5e6e6949aef..e77f7726015af 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -2,7 +2,6 @@ ros__parameters: goal_planner: # general params - minimum_request_length: 100.0 th_arrived_distance: 1.0 th_stopped_velocity: 0.01 th_stopped_time: 2.0 # It must be greater than the state_machine's. @@ -36,6 +35,7 @@ # pull over pull_over: + minimum_request_length: 100.0 pull_over_velocity: 3.0 pull_over_minimum_velocity: 1.38 decide_path_distance: 10.0 diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index 65447d0c1d618..4cc8910acf82b 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -86,8 +86,8 @@ Either one is activated when all conditions are met. ### fixed_goal_planner -- The distance between the goal and ego-vehicle is shorter than `minimum_request_length`. - Route is set with `allow_goal_modification=false` by default. +- ego-vehicle is in the same lane as the goal. @@ -95,7 +95,7 @@ Either one is activated when all conditions are met. #### pull over on road lane -- The distance between the goal and ego-vehicle is shorter than `minimum_request_length`. +- The distance between the goal and ego-vehicle is shorter than `pull_over_minimum_request_length`. - Route is set with `allow_goal_modification=true` . - We can set this option with [SetRoute](https://github.com/autowarefoundation/autoware_adapi_msgs/blob/main/autoware_adapi_v1_msgs/routing/srv/SetRoute.srv#L2) api service. - We support `2D Rough Goal Pose` with the key bind `r` in RViz, but in the future there will be a panel of tools to manipulate various Route API from RViz. @@ -105,7 +105,7 @@ Either one is activated when all conditions are met. #### pull over on shoulder lane -- The distance between the goal and ego-vehicle is shorter than `minimum_request_length`. +- The distance between the goal and ego-vehicle is shorter than `pull_over_minimum_request_length`. - Goal is set in the `road_shoulder`. @@ -118,17 +118,11 @@ Either one is activated when all conditions are met. ## General parameters for goal_planner -| Name | Unit | Type | Description | Default value | -| :------------------------- | :----- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------- | :------------ | -| minimum_request_length | [m] | double | when the ego-vehicle approaches the goal by this distance or a safe distance to stop, the module is activated. | 100.0 | -| th_arrived_distance | [m] | double | distance threshold for arrival of path termination | 1.0 | -| th_stopped_velocity | [m/s] | double | velocity threshold for arrival of path termination | 0.01 | -| th_stopped_time | [s] | double | time threshold for arrival of path termination | 2.0 | -| pull_over_velocity | [m/s] | double | decelerate to this speed by the goal search area | 3.0 | -| pull_over_minimum_velocity | [m/s] | double | speed of pull_over after stopping once. this prevents excessive acceleration. | 1.38 | -| margin_from_boundary | [m] | double | distance margin from edge of the shoulder lane | 0.5 | -| decide_path_distance | [m] | double | decide path if it approaches this distance relative to the parking position. after that, no path planning and goal search are performed | 10.0 | -| maximum_deceleration | [m/s2] | double | maximum deceleration. it prevents sudden deceleration when a parking path cannot be found suddenly | 1.0 | +| Name | Unit | Type | Description | Default value | +| :------------------ | :---- | :----- | :------------------------------------------------- | :------------ | +| th_arrived_distance | [m] | double | distance threshold for arrival of path termination | 1.0 | +| th_stopped_velocity | [m/s] | double | velocity threshold for arrival of path termination | 0.01 | +| th_stopped_time | [s] | double | time threshold for arrival of path termination | 2.0 | ## **collision check** @@ -175,11 +169,21 @@ searched for in certain range of the shoulder lane. | max_lateral_offset | [m] | double | maximum offset of goal search in the lateral direction | 0.5 | | lateral_offset_interval | [m] | double | distance interval of goal search in the lateral direction | 0.25 | | ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 0.0 | +| ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 0.0 | +| margin_from_boundary | [m] | double | distance margin from edge of the shoulder lane | 0.5 | -## **Path Generation** +## **Pull Over** There are three path generation methods. -The path is generated with a certain margin (default: `0.5 m`) from left boundary of shoulder lane. +The path is generated with a certain margin (default: `0.5 m`) from the boundary of shoulder lane. + +| Name | Unit | Type | Description | Default value | +| :------------------------------- | :----- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| pull_over_minimum_request_length | [m] | double | when the ego-vehicle approaches the goal by this distance or a safe distance to stop, pull over is activated. | 100.0 | +| pull_over_velocity | [m/s] | double | decelerate to this speed by the goal search area | 3.0 | +| pull_over_minimum_velocity | [m/s] | double | speed of pull_over after stopping once. this prevents excessive acceleration. | 1.38 | +| decide_path_distance | [m] | double | decide path if it approaches this distance relative to the parking position. after that, no path planning and goal search are performed | 10.0 | +| maximum_deceleration | [m/s2] | double | maximum deceleration. it prevents sudden deceleration when a parking path cannot be found suddenly | 1.0 | ### **shift parking** diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index 6b8a62d2caec9..c458d017f518c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -41,7 +41,6 @@ enum class ParkingPolicy { struct GoalPlannerParameters { // general params - double minimum_request_length; double th_arrived_distance; double th_stopped_velocity; double th_stopped_time; @@ -72,6 +71,7 @@ struct GoalPlannerParameters double object_recognition_collision_check_max_extra_stopping_margin; // pull over general params + double pull_over_minimum_request_length; double pull_over_velocity; double pull_over_minimum_velocity; double decide_path_distance; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 8403c9904c624..6f22ee9b1412b 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -307,12 +307,19 @@ bool GoalPlannerModule::isExecutionRequested() const return false; } + // if goal modification is not allowed + // 1) goal_pose is in current_lanes, plan path to the original fixed goal + // 2) goal_pose is NOT in current_lanes, do not execute goal_planner + if (!goal_planner_utils::isAllowedGoalModification(route_handler)) { + return goal_is_in_current_lanes; + } + // if goal arc coordinates can be calculated, check if goal is in request_length const double self_to_goal_arc_length = utils::getSignedDistance(current_pose, goal_pose, current_lanes); const double request_length = goal_planner_utils::isAllowedGoalModification(route_handler) ? calcModuleRequestLength() - : parameters_->minimum_request_length; + : parameters_->pull_over_minimum_request_length; if (self_to_goal_arc_length < 0.0 || self_to_goal_arc_length > request_length) { // if current position is far from goal or behind goal, do not execute goal_planner return false; @@ -365,13 +372,13 @@ double GoalPlannerModule::calcModuleRequestLength() const const auto min_stop_distance = calcFeasibleDecelDistance( planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); if (!min_stop_distance) { - return parameters_->minimum_request_length; + return parameters_->pull_over_minimum_request_length; } const double minimum_request_length = *min_stop_distance + parameters_->backward_goal_search_length + approximate_pull_over_distance_; - return std::max(minimum_request_length, parameters_->minimum_request_length); + return std::max(minimum_request_length, parameters_->pull_over_minimum_request_length); } Pose GoalPlannerModule::calcRefinedGoal(const Pose & goal_pose) const diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index d51046d3456c7..7e2975a73aa44 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -35,7 +35,6 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( const std::string base_ns = "goal_planner."; // general params { - p.minimum_request_length = node->declare_parameter(base_ns + "minimum_request_length"); p.th_stopped_velocity = node->declare_parameter(base_ns + "th_stopped_velocity"); p.th_arrived_distance = node->declare_parameter(base_ns + "th_arrived_distance"); p.th_stopped_time = node->declare_parameter(base_ns + "th_stopped_time"); @@ -96,6 +95,8 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // pull over general params { const std::string ns = base_ns + "pull_over."; + p.pull_over_minimum_request_length = + node->declare_parameter(ns + "minimum_request_length"); p.pull_over_velocity = node->declare_parameter(ns + "pull_over_velocity"); p.pull_over_minimum_velocity = node->declare_parameter(ns + "pull_over_minimum_velocity"); From dec3d0671c97a98020fd07f85b4ab39985a6d6d8 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 6 Sep 2023 02:23:00 +0900 Subject: [PATCH 135/547] fix(start_planner): fix start pose candidats when the ego is close to the lane end (#4893) Signed-off-by: kosuke55 --- .../start_planner/start_planner_module.hpp | 10 ++- .../start_planner/start_planner_module.cpp | 79 +++++++++++-------- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index b716221f5e82b..ba942da69732d 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -63,7 +63,8 @@ struct PullOutStatus lanelet::ConstLanelets pull_out_lanes{}; bool is_safe_static_objects{false}; // current path is safe against static objects bool is_safe_dynamic_objects{false}; // current path is safe against dynamic objects - bool back_finished{false}; + bool back_finished{false}; // if backward driving is not required, this is also set to true + // todo: rename to clear variable name. Pose pull_out_start_pose{}; PullOutStatus() {} @@ -150,7 +151,8 @@ class StartPlannerModule : public SceneModuleInterface std::mutex mutex_; PathWithLaneId getFullPath() const; - std::vector searchPullOutStartPoses(); + PathWithLaneId calcStartPoseCandidatesBackwardPath() const; + std::vector searchPullOutStartPoses(const PathWithLaneId & start_pose_candidates) const; std::shared_ptr lane_departure_checker_; @@ -160,8 +162,8 @@ class StartPlannerModule : public SceneModuleInterface void incrementPathIndex(); PathWithLaneId getCurrentPath() const; void planWithPriority( - const std::vector & start_pose_candidates, const Pose & goal_pose, - const std::string search_priority); + const std::vector & start_pose_candidates, const Pose & refined_start_pose, + const Pose & goal_pose, const std::string search_priority); PathWithLaneId generateStopPath() const; lanelet::ConstLanelets getPathRoadLanes(const PathWithLaneId & path) const; std::vector generateDrivableLanes(const PathWithLaneId & path) const; diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 356f30f6d5842..ee173796d125c 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -414,8 +414,8 @@ PathWithLaneId StartPlannerModule::getCurrentPath() const } void StartPlannerModule::planWithPriority( - const std::vector & start_pose_candidates, const Pose & goal_pose, - const std::string search_priority) + const std::vector & start_pose_candidates, const Pose & refined_start_pose, + const Pose & goal_pose, const std::string search_priority) { // check if start pose candidates are valid if (start_pose_candidates.empty()) { @@ -423,12 +423,13 @@ void StartPlannerModule::planWithPriority( } const auto is_safe_with_pose_planner = [&](const size_t i, const auto & planner) { - // Set back_finished flag based on the current index - status_.back_finished = i == 0; - // Get the pull_out_start_pose for the current index const auto & pull_out_start_pose = start_pose_candidates.at(i); + // Set back_finished to true if the current start pose is same to refined_start_pose + status_.back_finished = + tier4_autoware_utils::calcDistance2d(pull_out_start_pose, refined_start_pose) < 0.01; + planner->setPlannerData(planner_data_); const auto pull_out_path = planner->plan(pull_out_start_pose, goal_pose); // not found safe path @@ -504,7 +505,6 @@ void StartPlannerModule::planWithPriority( for (const auto & p : order_priority) { if (is_safe_with_pose_planner(p.first, p.second)) { - const std::lock_guard lock(mutex_); return; } } @@ -622,9 +622,24 @@ void StartPlannerModule::updatePullOutStatus() const auto & current_pose = planner_data_->self_odometry->pose.pose; const auto & goal_pose = planner_data_->route_handler->getGoalPose(); + // refine start pose with pull out lanes. + // 1) backward driving is not allowed: use refined pose just as start pose. + // 2) backward driving is allowed: use refined pose to check if backward driving is needed. + const PathWithLaneId start_pose_candidates_path = calcStartPoseCandidatesBackwardPath(); + const auto refined_start_pose = calcLongitudinalOffsetPose( + start_pose_candidates_path.points, planner_data_->self_odometry->pose.pose.position, 0.0); + if (!refined_start_pose) return; + // search pull out start candidates backward - std::vector start_pose_candidates = searchPullOutStartPoses(); - planWithPriority(start_pose_candidates, goal_pose, parameters_->search_priority); + const std::vector start_pose_candidates = std::invoke([&]() -> std::vector { + if (parameters_->enable_back) { + return searchPullOutStartPoses(start_pose_candidates_path); + } + return {*refined_start_pose}; + }); + + planWithPriority( + start_pose_candidates, *refined_start_pose, goal_pose, parameters_->search_priority); checkBackFinished(); if (!status_.back_finished) { @@ -634,20 +649,34 @@ void StartPlannerModule::updatePullOutStatus() } } -std::vector StartPlannerModule::searchPullOutStartPoses() +PathWithLaneId StartPlannerModule::calcStartPoseCandidatesBackwardPath() const { - std::vector pull_out_start_pose{}; - const Pose & current_pose = planner_data_->self_odometry->pose.pose; // get backward shoulder path const auto arc_position_pose = lanelet::utils::getArcCoordinates(status_.pull_out_lanes, current_pose); const double check_distance = parameters_->max_back_distance + 30.0; // buffer - auto backward_shoulder_path = planner_data_->route_handler->getCenterLinePath( + auto path = planner_data_->route_handler->getCenterLinePath( status_.pull_out_lanes, arc_position_pose.length - check_distance, arc_position_pose.length + check_distance); + // lateral shift to current_pose + const double distance_from_center_line = arc_position_pose.distance; + for (auto & p : path.points) { + p.point.pose = calcOffsetPose(p.point.pose, 0, distance_from_center_line, 0); + } + + return path; +} + +std::vector StartPlannerModule::searchPullOutStartPoses( + const PathWithLaneId & start_pose_candidates) const +{ + const Pose & current_pose = planner_data_->self_odometry->pose.pose; + + std::vector pull_out_start_pose{}; + // filter pull out lanes stop objects const auto [pull_out_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( @@ -655,28 +684,12 @@ std::vector StartPlannerModule::searchPullOutStartPoses() const auto pull_out_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_out_lane_objects, parameters_->th_moving_object_velocity); - // lateral shift to current_pose - const double distance_from_center_line = arc_position_pose.distance; - for (auto & p : backward_shoulder_path.points) { - p.point.pose = calcOffsetPose(p.point.pose, 0, distance_from_center_line, 0); - } - - // if backward driving is disable, just refine current pose to the lanes - if (!parameters_->enable_back) { - const auto refined_pose = - calcLongitudinalOffsetPose(backward_shoulder_path.points, current_pose.position, 0); - if (refined_pose) { - pull_out_start_pose.push_back(*refined_pose); - } - return pull_out_start_pose; - } - // check collision between footprint and object at the backed pose const auto local_vehicle_footprint = createVehicleFootprint(vehicle_info_); for (double back_distance = 0.0; back_distance <= parameters_->max_back_distance; back_distance += parameters_->backward_search_resolution) { const auto backed_pose = calcLongitudinalOffsetPose( - backward_shoulder_path.points, current_pose.position, -back_distance); + start_pose_candidates.points, current_pose.position, -back_distance); if (!backed_pose) { continue; } @@ -684,10 +697,10 @@ std::vector StartPlannerModule::searchPullOutStartPoses() // check the back pose is near the lane end const double length_to_backed_pose = lanelet::utils::getArcCoordinates(status_.pull_out_lanes, *backed_pose).length; - double length_to_lane_end = 0.0; - for (const auto & lane : status_.pull_out_lanes) { - length_to_lane_end += lanelet::utils::getLaneletLength2d(lane); - } + + const double length_to_lane_end = std::accumulate( + std::begin(status_.pull_out_lanes), std::end(status_.pull_out_lanes), 0.0, + [](double acc, const auto & lane) { return acc + lanelet::utils::getLaneletLength2d(lane); }); const double distance_from_lane_end = length_to_lane_end - length_to_backed_pose; if (distance_from_lane_end < parameters_->ignore_distance_from_lane_end) { RCLCPP_WARN_THROTTLE( From 23851b62dc9de76a0a2f61860bd2ace56948a07e Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 6 Sep 2023 02:57:51 +0900 Subject: [PATCH 136/547] feat(start_planner): run start planner even if ego is out of lane (#4895) Signed-off-by: kosuke55 --- .../start_planner/start_planner_module.cpp | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index ee173796d125c..90078a50e6ac9 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -136,26 +136,6 @@ bool StartPlannerModule::isExecutionRequested() const return false; } - // Create vehicle footprint - const auto local_vehicle_footprint = createVehicleFootprint(vehicle_info_); - const auto vehicle_footprint = transformVector( - local_vehicle_footprint, - tier4_autoware_utils::pose2transform(planner_data_->self_odometry->pose.pose)); - - // Check if ego is not out of lanes - const double backward_path_length = - planner_data_->parameters.backward_path_length + parameters_->max_back_distance; - const auto current_lanes = utils::getExtendedCurrentLanes( - planner_data_, backward_path_length, std::numeric_limits::max(), - /*forward_only_in_route*/ true); - const auto pull_out_lanes = - start_planner_utils::getPullOutLanes(planner_data_, backward_path_length); - const auto lanes = utils::combineLanelets(current_lanes, pull_out_lanes); - - if (LaneDepartureChecker::isOutOfLane(lanes, vehicle_footprint)) { - return false; - } - return true; } From 3cb06ff937c08ae44dd26519697c86744b66764a Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 6 Sep 2023 12:36:20 +0900 Subject: [PATCH 137/547] feat(intersection): do not generate stuck vehicle detect area before attention area if attention area exists (#4872) --- .../src/scene_intersection.cpp | 16 ++++++++++++---- .../src/util.cpp | 12 +++++++++--- .../src/util.hpp | 4 +++- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 0dfadf2f00d7a..0c6726b45dcbc 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -335,9 +335,16 @@ void reactRTCApprovalByDecisionResult( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), "StuckStop, approval = (default: %d, occlusion: %d)", rtc_default_approved, rtc_occlusion_approved); + const auto closest_idx = decision_result.stop_lines.closest_idx; if (!rtc_default_approved) { // use default_rtc uuid for stuck vehicle detection - const auto stop_line_idx = decision_result.stop_line_idx; + auto stop_line_idx = decision_result.stop_line_idx; + if ( + !decision_result.is_detection_area_empty && + motion_utils::calcSignedArcLength(path->points, stop_line_idx, closest_idx) > + planner_param.common.stop_overshoot_margin) { + stop_line_idx = decision_result.stop_lines.default_stop_line; + } planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->collision_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -347,7 +354,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_factor_points = planning_utils::toRosPoints(debug_data->conflicting_targets); planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -363,7 +370,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(occlusion_stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(closest_idx).point.pose, path->points.at(occlusion_stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -774,7 +781,8 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const auto & conflicting_area = intersection_lanelets_.value().conflicting_area(); const auto path_lanelets_opt = util::generatePathLanelets( lanelets_on_path, interpolated_path_info, associative_ids_, first_conflicting_area.value(), - conflicting_area, closest_idx, planner_data_->vehicle_info_.vehicle_width_m); + conflicting_area, first_attention_area, intersection_lanelets_.value().attention_area(), + closest_idx, planner_data_->vehicle_info_.vehicle_width_m); if (!path_lanelets_opt.has_value()) { RCLCPP_DEBUG(logger_, "failed to generate PathLanelets"); return IntersectionModule::Indecisive{}; diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index cd6d941b43de2..e430f925ab2d6 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -1155,7 +1155,9 @@ std::optional generatePathLanelets( const lanelet::ConstLanelets & lanelets_on_path, const util::InterpolatedPathInfo & interpolated_path_info, const std::set & associative_ids, const lanelet::CompoundPolygon3d & first_conflicting_area, - const std::vector & conflicting_areas, const size_t closest_idx, + const std::vector & conflicting_areas, + const std::optional & first_attention_area, + const std::vector & attention_areas, const size_t closest_idx, const double width) { const auto & assigned_lane_interval_opt = interpolated_path_info.lane_id_interval; @@ -1195,9 +1197,13 @@ std::optional generatePathLanelets( } const auto first_inside_conflicting_idx_opt = - getFirstPointInsidePolygon(path, assigned_lane_interval, first_conflicting_area); + first_attention_area.has_value() + ? getFirstPointInsidePolygon(path, assigned_lane_interval, first_attention_area.value()) + : getFirstPointInsidePolygon(path, assigned_lane_interval, first_conflicting_area); const auto last_inside_conflicting_idx_opt = - getFirstPointInsidePolygons(path, assigned_lane_interval, conflicting_areas, false); + first_attention_area.has_value() + ? getFirstPointInsidePolygons(path, assigned_lane_interval, attention_areas, false) + : getFirstPointInsidePolygons(path, assigned_lane_interval, conflicting_areas, false); if (first_inside_conflicting_idx_opt && last_inside_conflicting_idx_opt) { const auto first_inside_conflicting_idx = first_inside_conflicting_idx_opt.value(); const auto last_inside_conflicting_idx = last_inside_conflicting_idx_opt.value().first; diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 1361847b4b980..4ae4e72cb4ac2 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -158,7 +158,9 @@ std::optional generatePathLanelets( const lanelet::ConstLanelets & lanelets_on_path, const util::InterpolatedPathInfo & interpolated_path_info, const std::set & associative_ids, const lanelet::CompoundPolygon3d & first_conflicting_area, - const std::vector & conflicting_areas, const size_t closest_idx, + const std::vector & conflicting_areas, + const std::optional & first_attention_area, + const std::vector & attention_areas, const size_t closest_idx, const double width); } // namespace util From c8f826dc15df68ff45e50aed500915b8380dddf1 Mon Sep 17 00:00:00 2001 From: Phoebe Wu Date: Wed, 6 Sep 2023 13:39:52 +0800 Subject: [PATCH 138/547] refactor(radar_static_pointcloud_filter): rework parameters (#4871) * refactor(radar_static_pointcloud_filter): rework parameters Signed-off-by: PhoebeWu21 * style(pre-commit): autofix --------- Signed-off-by: PhoebeWu21 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../CMakeLists.txt | 1 + .../radar_static_pointcloud_filter.param.yaml | 17 ++++++++++ .../radar_static_pointcloud_filter.launch.xml | 5 +-- ...radar_static_pointcloud_filter.schema.json | 31 +++++++++++++++++++ .../radar_static_pointcloud_filter_node.cpp | 4 +-- 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 sensing/radar_static_pointcloud_filter/config/radar_static_pointcloud_filter.param.yaml create mode 100644 sensing/radar_static_pointcloud_filter/schema/radar_static_pointcloud_filter.schema.json diff --git a/sensing/radar_static_pointcloud_filter/CMakeLists.txt b/sensing/radar_static_pointcloud_filter/CMakeLists.txt index 1f08f7842cfdf..ecd33d3166b19 100644 --- a/sensing/radar_static_pointcloud_filter/CMakeLists.txt +++ b/sensing/radar_static_pointcloud_filter/CMakeLists.txt @@ -27,4 +27,5 @@ endif() ament_auto_package( INSTALL_TO_SHARE launch + config ) diff --git a/sensing/radar_static_pointcloud_filter/config/radar_static_pointcloud_filter.param.yaml b/sensing/radar_static_pointcloud_filter/config/radar_static_pointcloud_filter.param.yaml new file mode 100644 index 0000000000000..a429f7a065ffe --- /dev/null +++ b/sensing/radar_static_pointcloud_filter/config/radar_static_pointcloud_filter.param.yaml @@ -0,0 +1,17 @@ +# Copyright 2023 Foxconn, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +/**: + ros__parameters: + doppler_velocity_sd: 4.0 diff --git a/sensing/radar_static_pointcloud_filter/launch/radar_static_pointcloud_filter.launch.xml b/sensing/radar_static_pointcloud_filter/launch/radar_static_pointcloud_filter.launch.xml index 08fdeee74f15a..b9491227837c5 100644 --- a/sensing/radar_static_pointcloud_filter/launch/radar_static_pointcloud_filter.launch.xml +++ b/sensing/radar_static_pointcloud_filter/launch/radar_static_pointcloud_filter.launch.xml @@ -3,13 +3,14 @@ - + + - + diff --git a/sensing/radar_static_pointcloud_filter/schema/radar_static_pointcloud_filter.schema.json b/sensing/radar_static_pointcloud_filter/schema/radar_static_pointcloud_filter.schema.json new file mode 100644 index 0000000000000..65340d889f62e --- /dev/null +++ b/sensing/radar_static_pointcloud_filter/schema/radar_static_pointcloud_filter.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Radar Static Pointcloud Filter Node", + "type": "object", + "definitions": { + "radar_static_pointcloud_filter": { + "type": "object", + "properties": { + "doppler_velocity_sd": { + "type": "number", + "default": "4.0", + "minimum": 0.0, + "description": "Standard deviation for radar doppler velocity. [m/s]" + } + }, + "required": ["doppler_velocity_sd"] + } + }, + "properties": { + "/**": { + "type": "object", + "properties": { + "ros__parameters": { + "$ref": "#/definitions/radar_static_pointcloud_filter" + } + }, + "required": ["ros__parameters"] + } + }, + "required": ["/**"] +} diff --git a/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp b/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp index c3550b0338675..772dcef753c11 100644 --- a/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp +++ b/sensing/radar_static_pointcloud_filter/src/radar_static_pointcloud_filter_node/radar_static_pointcloud_filter_node.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 TIER IV, Inc. +// Copyright 2022-2023 Foxconn, TIER IV, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -93,7 +93,7 @@ RadarStaticPointcloudFilterNode::RadarStaticPointcloudFilterNode( std::bind(&RadarStaticPointcloudFilterNode::onSetParam, this, _1)); // Node Parameter - node_param_.doppler_velocity_sd = declare_parameter("doppler_velocity_sd", 2.0); + node_param_.doppler_velocity_sd = declare_parameter("doppler_velocity_sd"); // Subscriber transform_listener_ = std::make_shared(this); From 318cddb2bfb9fd43f4f32b5dc034f4dc91b52900 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 6 Sep 2023 18:01:41 +0900 Subject: [PATCH 139/547] fix(behavior_path_planner): fix functions related to pull over lanes (#4857) * fix(behavior_path_planner): fix functions related to pull over lanes Signed-off-by: kosuke55 * add buffer Signed-off-by: kosuke55 * add comments Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../utils/goal_planner/util.hpp | 4 +- .../goal_planner/goal_planner_module.cpp | 25 +++++---- .../goal_planner/freespace_pull_over.cpp | 5 +- .../goal_planner/geometric_pull_over.cpp | 5 +- .../src/utils/goal_planner/goal_searcher.cpp | 10 ++-- .../utils/goal_planner/shift_pull_over.cpp | 8 +-- .../src/utils/goal_planner/util.cpp | 27 +++++----- .../include/route_handler/route_handler.hpp | 12 +++-- planning/route_handler/src/route_handler.cpp | 54 +++++++++++++++---- 9 files changed, 100 insertions(+), 50 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp index 29a4a8efa719f..0adda77a2ad72 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp @@ -46,7 +46,9 @@ using visualization_msgs::msg::MarkerArray; // TODO(sugahara) move to util PathWithLaneId combineReferencePath(const PathWithLaneId & path1, const PathWithLaneId & path2); -lanelet::ConstLanelets getPullOverLanes(const RouteHandler & route_handler, const bool left_side); +lanelet::ConstLanelets getPullOverLanes( + const RouteHandler & route_handler, const bool left_side, const double backward_distance, + const double forward_distance); PredictedObjects filterObjectsByLateralDistance( const Pose & ego_pose, const double vehicle_width, const PredictedObjects & objects, const double distance_thresh, const bool filter_inside); diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 6f22ee9b1412b..37d44154789a6 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -335,8 +335,9 @@ bool GoalPlannerModule::isExecutionRequested() const // if (A) or (B) is met execute pull over // (A) target lane is `road` and same to the current lanes // (B) target lane is `road_shoulder` and neighboring to the current lanes - const lanelet::ConstLanelets pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(route_handler), left_side_parking_); + const lanelet::ConstLanelets pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(route_handler), left_side_parking_, parameters_->backward_goal_search_length, + parameters_->forward_goal_search_length); lanelet::ConstLanelet target_lane{}; lanelet::utils::query::getClosestLanelet(pull_over_lanes, goal_pose, &target_lane); if (!isCrossingPossible(current_lane, target_lane)) { @@ -387,8 +388,9 @@ Pose GoalPlannerModule::calcRefinedGoal(const Pose & goal_pose) const const double base_link2front = planner_data_->parameters.base_link2front; const double base_link2rear = planner_data_->parameters.base_link2rear; - const lanelet::ConstLanelets pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(planner_data_->route_handler), left_side_parking_); + const lanelet::ConstLanelets pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, + parameters_->forward_goal_search_length); lanelet::Lanelet closest_pull_over_lanelet{}; lanelet::utils::query::getClosestLanelet(pull_over_lanes, goal_pose, &closest_pull_over_lanelet); @@ -643,8 +645,9 @@ void GoalPlannerModule::setLanes() planner_data_, parameters_->backward_goal_search_length, parameters_->forward_goal_search_length, /*forward_only_in_route*/ false); - status_.pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(planner_data_->route_handler), left_side_parking_); + status_.pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, + parameters_->forward_goal_search_length); status_.lanes = utils::generateDrivableLanesWithShoulderLanes(status_.current_lanes, status_.pull_over_lanes); } @@ -1487,8 +1490,9 @@ bool GoalPlannerModule::isSafePath() const const auto current_lanes = utils::getExtendedCurrentLanes( planner_data_, backward_path_length, std::numeric_limits::max(), /*forward_only_in_route*/ true); - const auto pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(route_handler), left_side_parking_); + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *route_handler, left_side_parking_, parameters_->backward_goal_search_length, + parameters_->forward_goal_search_length); const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(pull_over_path.points); const auto & common_param = planner_data_->parameters; const std::pair terminal_velocity_and_accel = @@ -1655,8 +1659,9 @@ bool GoalPlannerModule::checkOriginalGoalIsInShoulder() const const auto & route_handler = planner_data_->route_handler; const Pose & goal_pose = route_handler->getOriginalGoalPose(); - const lanelet::ConstLanelets pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(route_handler), left_side_parking_); + const lanelet::ConstLanelets pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(route_handler), left_side_parking_, parameters_->backward_goal_search_length, + parameters_->forward_goal_search_length); lanelet::ConstLanelet target_lane{}; lanelet::utils::query::getClosestLanelet(pull_over_lanes, goal_pose, &target_lane); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp index 9656094f5a478..9322350877ad1 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp @@ -63,8 +63,9 @@ boost::optional FreespacePullOver::plan(const Pose & goal_pose) const auto road_lanes = utils::getExtendedCurrentLanes( planner_data_, parameters_.backward_goal_search_length, parameters_.forward_goal_search_length, /*forward_only_in_route*/ false); - const auto pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(planner_data_->route_handler), left_side_parking_); + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, + parameters_.forward_goal_search_length); if (road_lanes.empty() || pull_over_lanes.empty()) { return {}; } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp index 5594f7af1c428..ed0106d891bb4 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp @@ -47,8 +47,9 @@ boost::optional GeometricPullOver::plan(const Pose & goal_pose) const auto road_lanes = utils::getExtendedCurrentLanes( planner_data_, parameters_.backward_goal_search_length, parameters_.forward_goal_search_length, /*forward_only_in_route*/ false); - const auto pull_over_lanes = - goal_planner_utils::getPullOverLanes(*route_handler, left_side_parking_); + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *route_handler, left_side_parking_, parameters_.backward_goal_search_length, + parameters_.forward_goal_search_length); if (road_lanes.empty() || pull_over_lanes.empty()) { return {}; } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index e12ee576a5d4b..19dec4d3c97d1 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -57,8 +57,9 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) const double base_link2front = planner_data_->parameters.base_link2front; const double base_link2rear = planner_data_->parameters.base_link2rear; - const auto pull_over_lanes = - goal_planner_utils::getPullOverLanes(*route_handler, left_side_parking_); + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *route_handler, left_side_parking_, parameters_.backward_goal_search_length, + parameters_.forward_goal_search_length); auto lanes = utils::getExtendedCurrentLanes( planner_data_, backward_length, forward_length, /*forward_only_in_route*/ false); @@ -157,8 +158,9 @@ void GoalSearcher::update(GoalCandidates & goal_candidates) const } // check margin with pull over lane objects - const auto pull_over_lanes = - goal_planner_utils::getPullOverLanes(*(planner_data_->route_handler), left_side_parking_); + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, + parameters_.forward_goal_search_length); const auto [shoulder_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( *(planner_data_->dynamic_object), pull_over_lanes); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp index c5ac14d4d9727..aeb28d74e5f95 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp @@ -49,16 +49,16 @@ boost::optional ShiftPullOver::plan(const Pose & goal_pose) const auto road_lanes = utils::getExtendedCurrentLanes( planner_data_, backward_search_length, forward_search_length, /*forward_only_in_route*/ false); - const auto shoulder_lanes = - goal_planner_utils::getPullOverLanes(*route_handler, left_side_parking_); - if (road_lanes.empty() || shoulder_lanes.empty()) { + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *route_handler, left_side_parking_, backward_search_length, forward_search_length); + if (road_lanes.empty() || pull_over_lanes.empty()) { return {}; } // find safe one from paths with different jerk for (double lateral_jerk = min_jerk; lateral_jerk <= max_jerk; lateral_jerk += jerk_resolution) { const auto pull_over_path = - generatePullOverPath(road_lanes, shoulder_lanes, goal_pose, lateral_jerk); + generatePullOverPath(road_lanes, pull_over_lanes, goal_pose, lateral_jerk); if (!pull_over_path) continue; return *pull_over_path; } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index 0d58a54431be0..aa3380c2b99df 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -55,36 +55,37 @@ PathWithLaneId combineReferencePath(const PathWithLaneId & path1, const PathWith return path; } -lanelet::ConstLanelets getPullOverLanes(const RouteHandler & route_handler, const bool left_side) +lanelet::ConstLanelets getPullOverLanes( + const RouteHandler & route_handler, const bool left_side, const double backward_distance, + const double forward_distance) { const Pose goal_pose = route_handler.getOriginalGoalPose(); + // Buffer to get enough lanes in front of the goal, need much longer than the pull over distance. + // In the case of loop lanes, it may not be possible to extend the lane forward. + // todo(kosuek55): automatically calculates this distance. + const double backward_distance_with_buffer = backward_distance + 100; + lanelet::ConstLanelet target_shoulder_lane{}; if (route_handler::RouteHandler::getPullOverTarget( route_handler.getShoulderLanelets(), goal_pose, &target_shoulder_lane)) { // pull over on shoulder lane - return route_handler.getShoulderLaneletSequence(target_shoulder_lane, goal_pose); + return route_handler.getShoulderLaneletSequence( + target_shoulder_lane, goal_pose, backward_distance_with_buffer, forward_distance); } - // pull over on road lane lanelet::ConstLanelet closest_lane{}; route_handler.getClosestLaneletWithinRoute(goal_pose, &closest_lane); - lanelet::ConstLanelet outermost_lane{}; if (left_side) { - outermost_lane = route_handler.getMostLeftLanelet(closest_lane); + outermost_lane = route_handler.getMostLeftLanelet(closest_lane, false, true); } else { - outermost_lane = route_handler.getMostRightLanelet(closest_lane); - } - - lanelet::ConstLanelet outermost_shoulder_lane; - if (route_handler.getLeftShoulderLanelet(outermost_lane, &outermost_shoulder_lane)) { - return route_handler.getShoulderLaneletSequence(outermost_shoulder_lane, goal_pose); + outermost_lane = route_handler.getMostRightLanelet(closest_lane, false, true); } - const bool dist = std::numeric_limits::max(); constexpr bool only_route_lanes = false; - return route_handler.getLaneletSequence(outermost_lane, dist, dist, only_route_lanes); + return route_handler.getLaneletSequence( + outermost_lane, backward_distance_with_buffer, forward_distance, only_route_lanes); } PredictedObjects filterObjectsByLateralDistance( diff --git a/planning/route_handler/include/route_handler/route_handler.hpp b/planning/route_handler/include/route_handler/route_handler.hpp index a797edaa34556..847c8db3b16a9 100644 --- a/planning/route_handler/include/route_handler/route_handler.hpp +++ b/planning/route_handler/include/route_handler/route_handler.hpp @@ -122,7 +122,8 @@ class RouteHandler * @return vector of lanelet having same direction if true */ boost::optional getRightLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false) const; + const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false, + const bool get_shoulder_lane = false) const; /** * @brief Check if same-direction lane is available at the left side of the lanelet @@ -132,7 +133,8 @@ class RouteHandler * @return vector of lanelet having same direction if true */ boost::optional getLeftLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false) const; + const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false, + const bool get_shoulder_lane = false) const; lanelet::ConstLanelets getNextLanelets(const lanelet::ConstLanelet & lanelet) const; lanelet::ConstLanelets getPreviousLanelets(const lanelet::ConstLanelet & lanelet) const; @@ -195,7 +197,8 @@ class RouteHandler * @return vector of lanelet having same direction if true */ lanelet::ConstLanelet getMostRightLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false) const; + const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false, + const bool get_shoulder_lane = false) const; /** * @brief Check if same-direction lane is available at the left side of the lanelet @@ -205,7 +208,8 @@ class RouteHandler * @return vector of lanelet having same direction if true */ lanelet::ConstLanelet getMostLeftLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false) const; + const lanelet::ConstLanelet & lanelet, const bool enable_same_root = false, + const bool get_shoulder_lane = false) const; /** * @brief Searches the furthest linestring to the right side of the lanelet diff --git a/planning/route_handler/src/route_handler.cpp b/planning/route_handler/src/route_handler.cpp index 91f86ed0e454f..6a95434c81c23 100644 --- a/planning/route_handler/src/route_handler.cpp +++ b/planning/route_handler/src/route_handler.cpp @@ -528,7 +528,14 @@ lanelet::ConstLanelets RouteHandler::getLaneletSequenceAfter( while (rclcpp::ok() && length < min_length) { lanelet::ConstLanelet next_lanelet; if (!getNextLaneletWithinRoute(current_lanelet, &next_lanelet)) { - break; + if (only_route_lanes) { + break; + } + const auto next_lanes = getNextLanelets(current_lanelet); + if (next_lanes.empty()) { + break; + } + next_lanelet = next_lanes.front(); } // loop check if (lanelet.id() == next_lanelet.id()) { @@ -556,7 +563,14 @@ lanelet::ConstLanelets RouteHandler::getLaneletSequenceUpTo( while (rclcpp::ok() && length < min_length) { lanelet::ConstLanelets candidate_lanelets; if (!getPreviousLaneletsWithinRoute(current_lanelet, &candidate_lanelets)) { - break; + if (only_route_lanes) { + break; + } + const auto prev_lanes = getPreviousLanelets(current_lanelet); + if (prev_lanes.empty()) { + break; + } + candidate_lanelets = prev_lanes; } // loop check if (std::any_of( @@ -954,7 +968,8 @@ bool RouteHandler::isBijectiveConnection( } boost::optional RouteHandler::getRightLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root) const + const lanelet::ConstLanelet & lanelet, const bool enable_same_root, + const bool get_shoulder_lane) const { // right road lanelet of shoulder lanelet if (isShoulderLanelet(lanelet)) { @@ -966,6 +981,14 @@ boost::optional RouteHandler::getRightLanelet( return boost::none; } + // right shoulder lanelet + if (get_shoulder_lane) { + lanelet::ConstLanelet right_shoulder_lanelet; + if (getRightShoulderLanelet(lanelet, &right_shoulder_lanelet)) { + return right_shoulder_lanelet; + } + } + // routable lane const auto & right_lane = routing_graph_ptr_->right(lanelet); if (right_lane) { @@ -1026,7 +1049,8 @@ bool RouteHandler::getLeftLaneletWithinRoute( } boost::optional RouteHandler::getLeftLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root) const + const lanelet::ConstLanelet & lanelet, const bool enable_same_root, + const bool get_shoulder_lane) const { // left road lanelet of shoulder lanelet if (isShoulderLanelet(lanelet)) { @@ -1038,6 +1062,14 @@ boost::optional RouteHandler::getLeftLanelet( return boost::none; } + // left shoulder lanelet + if (get_shoulder_lane) { + lanelet::ConstLanelet left_shoulder_lanelet; + if (getLeftShoulderLanelet(lanelet, &left_shoulder_lanelet)) { + return left_shoulder_lanelet; + } + } + // routable lane const auto & left_lane = routing_graph_ptr_->left(lanelet); if (left_lane) { @@ -1213,26 +1245,28 @@ lanelet::Lanelets RouteHandler::getLeftOppositeLanelets(const lanelet::ConstLane } lanelet::ConstLanelet RouteHandler::getMostRightLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root) const + const lanelet::ConstLanelet & lanelet, const bool enable_same_root, + const bool get_shoulder_lane) const { // recursively compute the width of the lanes - const auto & same = getRightLanelet(lanelet, enable_same_root); + const auto & same = getRightLanelet(lanelet, enable_same_root, get_shoulder_lane); if (same) { - return getMostRightLanelet(same.get(), enable_same_root); + return getMostRightLanelet(same.get(), enable_same_root, get_shoulder_lane); } return lanelet; } lanelet::ConstLanelet RouteHandler::getMostLeftLanelet( - const lanelet::ConstLanelet & lanelet, const bool enable_same_root) const + const lanelet::ConstLanelet & lanelet, const bool enable_same_root, + const bool get_shoulder_lane) const { // recursively compute the width of the lanes - const auto & same = getLeftLanelet(lanelet, enable_same_root); + const auto & same = getLeftLanelet(lanelet, enable_same_root, get_shoulder_lane); if (same) { - return getMostLeftLanelet(same.get(), enable_same_root); + return getMostLeftLanelet(same.get(), enable_same_root, get_shoulder_lane); } return lanelet; From e9aacc39a97af425e716fb187458838922bf95c6 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Wed, 6 Sep 2023 19:04:08 +0900 Subject: [PATCH 140/547] fix(avoidance): don't output unfeasible path (#4899) Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.cpp | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 78c063421f29a..c2b372d2403e5 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -881,17 +881,35 @@ AvoidLineArray AvoidanceModule::calcRawShiftLinesFromObjects( } // output avoidance path under lateral jerk constraints. - const auto feasible_shift_length = PathShifter::calcLateralDistFromJerk( + const auto feasible_relative_shift_length = PathShifter::calcLateralDistFromJerk( remaining_distance, helper_.getLateralMaxJerkLimit(), helper_.getAvoidanceEgoSpeed()); - RCLCPP_WARN_THROTTLE( - getLogger(), *clock_, 1000, - "original shift length is not feasible. generate avoidance path under the constraints. " - "[original: (%.2f) actual: (%.2f)]", - std::abs(avoiding_shift), feasible_shift_length); + if (std::abs(feasible_relative_shift_length) < parameters_->lateral_execution_threshold) { + object.reason = "LessThanExecutionThreshold"; + return boost::none; + } + + const auto feasible_shift_length = + desire_shift_length > 0.0 ? feasible_relative_shift_length + current_ego_shift + : -1.0 * feasible_relative_shift_length + current_ego_shift; + + const auto feasible = + std::abs(feasible_shift_length - object.overhang_dist) < + 0.5 * planner_data_->parameters.vehicle_width + object_parameter.safety_buffer_lateral; + if (feasible) { + RCLCPP_WARN_THROTTLE( + getLogger(), *clock_, 1000, "feasible shift length is not enough to avoid. "); + object.reason = AvoidanceDebugFactor::INSUFFICIENT_LATERAL_MARGIN; + return boost::none; + } + + { + RCLCPP_WARN_THROTTLE( + getLogger(), *clock_, 1000, "use feasible shift length. [original: (%.2f) actual: (%.2f)]", + std::abs(avoiding_shift), feasible_relative_shift_length); + } - return desire_shift_length > 0.0 ? feasible_shift_length + current_ego_shift - : -1.0 * feasible_shift_length + current_ego_shift; + return feasible_shift_length; }; const auto is_forward_object = [](const auto & object) { return object.longitudinal > 0.0; }; From b1e6fcfc23cf47da319a7986f035f56b8875796b Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Wed, 6 Sep 2023 21:11:12 +0900 Subject: [PATCH 141/547] fix(planning_debug_tools): disable traffic light msg in perception replay (#4887) Signed-off-by: Maxime CLEMENT --- .../perception_replayer/perception_replayer.py | 16 ++++++++-------- .../perception_replayer/perception_reproducer.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py index b45357c7bd8b2..1299062638471 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py @@ -73,7 +73,7 @@ def on_timer(self): # extract message by the timestamp msgs = copy.deepcopy(self.find_topics_by_timestamp(self.bag_timestamp)) objects_msg = msgs[0] - traffic_signals_msg = msgs[1] + # traffic_signals_msg = msgs[1] # objects if objects_msg: @@ -92,13 +92,13 @@ def on_timer(self): self.objects_pub.publish(objects_msg) # traffic signals - if traffic_signals_msg: - traffic_signals_msg.header.stamp = timestamp - self.traffic_signals_pub.publish(traffic_signals_msg) - self.prev_traffic_signals_msg = traffic_signals_msg - elif self.prev_traffic_signals_msg: - self.prev_traffic_signals_msg.header.stamp = timestamp - self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) + # if traffic_signals_msg: + # traffic_signals_msg.header.stamp = timestamp + # self.traffic_signals_pub.publish(traffic_signals_msg) + # self.prev_traffic_signals_msg = traffic_signals_msg + # elif self.prev_traffic_signals_msg: + # self.prev_traffic_signals_msg.header.stamp = timestamp + # self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) def onPushed(self, event): if self.widget.button.isChecked(): diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py index 4228d506d5bec..8895f7d921626 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py @@ -56,7 +56,7 @@ def on_timer(self): # extract message by the nearest ego odom timestamp msgs = copy.deepcopy(self.find_topics_by_timestamp(pose_timestamp)) objects_msg = msgs[0] - traffic_signals_msg = msgs[1] + # traffic_signals_msg = msgs[1] # objects if objects_msg: @@ -66,13 +66,13 @@ def on_timer(self): self.objects_pub.publish(objects_msg) # traffic signals - if traffic_signals_msg: - traffic_signals_msg.header.stamp = timestamp - self.traffic_signals_pub.publish(traffic_signals_msg) - self.prev_traffic_signals_msg = traffic_signals_msg - elif self.prev_traffic_signals_msg: - self.prev_traffic_signals_msg.header.stamp = timestamp - self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) + # if traffic_signals_msg: + # traffic_signals_msg.header.stamp = timestamp + # self.traffic_signals_pub.publish(traffic_signals_msg) + # self.prev_traffic_signals_msg = traffic_signals_msg + # elif self.prev_traffic_signals_msg: + # self.prev_traffic_signals_msg.header.stamp = timestamp + # self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) def find_nearest_ego_odom_by_observation(self, ego_pose): if self.ego_pose_idx: From 0ab6a3be3835f97b87c524896aca9d9ab75cb92d Mon Sep 17 00:00:00 2001 From: Tomohito ANDO Date: Thu, 7 Sep 2023 00:14:47 +0900 Subject: [PATCH 142/547] feat(behavior_velocity): support new traffic signal interface (#4133) * feat(behavior_velocity): support new traffic signal interface Signed-off-by: Tomohito Ando * style(pre-commit): autofix * add missing dependency Signed-off-by: Tomohito Ando * style(pre-commit): autofix * remove the external signal input source in behavior_planning_launch.py Signed-off-by: Tomohito Ando * replace TrafficLightElement with TrafficSignalElement Signed-off-by: Tomohito Ando * style(pre-commit): autofix * use the regulatory element id instead of traffic light id Signed-off-by: Tomohito Ando * change the input of traffic signal to traffic light arbiter Signed-off-by: Tomohito Ando * style(pre-commit): autofix * do not return until the all regulatory elements are checked Signed-off-by: Tomohito Ando * change input topic of the traffic signals Signed-off-by: Tomohito Ando * fix the traffic signal type in perception reproducer Signed-off-by: Tomohito Ando * add debug log when the signal data is outdated Signed-off-by: Tomohito Ando --------- Signed-off-by: Tomohito Ando Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../tier4_traffic_light_rviz_plugin/README.md | 6 +- .../package.xml | 2 +- .../src/traffic_light_publish_panel.cpp | 96 +++++----- .../src/traffic_light_publish_panel.hpp | 9 +- .../behavior_planning.launch.py | 4 - .../package.xml | 1 + .../src/scene_crosswalk.cpp | 35 ++-- .../src/scene_crosswalk.hpp | 2 +- .../package.xml | 1 + .../src/util.cpp | 18 +- .../src/util.hpp | 3 +- planning/behavior_velocity_planner/README.md | 18 +- .../behavior_velocity_planner/src/node.cpp | 10 +- .../behavior_velocity_planner/src/node.hpp | 4 +- .../planner_data.hpp | 12 +- .../utilization/util.hpp | 7 + .../package.xml | 2 +- .../package.xml | 2 +- .../src/manager.cpp | 6 +- .../src/manager.hpp | 3 +- .../src/scene.cpp | 176 +++++------------- .../src/scene.hpp | 40 ++-- .../perception_replayer.py | 16 +- .../perception_replayer_common.py | 2 +- .../perception_reproducer.py | 16 +- .../planning_interface_test_manager.hpp | 4 +- planning/planning_test_utils/package.xml | 2 +- 27 files changed, 188 insertions(+), 309 deletions(-) diff --git a/common/tier4_traffic_light_rviz_plugin/README.md b/common/tier4_traffic_light_rviz_plugin/README.md index 953412a917432..91fb5bc124cb7 100644 --- a/common/tier4_traffic_light_rviz_plugin/README.md +++ b/common/tier4_traffic_light_rviz_plugin/README.md @@ -8,9 +8,9 @@ This plugin panel publishes dummy traffic light signals. ### Output -| Name | Type | Description | -| ------------------------------------------------------- | -------------------------------------------------------- | ----------------------------- | -| `/perception/traffic_light_recognition/traffic_signals` | `autoware_auto_perception_msgs::msg::TrafficSignalArray` | Publish traffic light signals | +| Name | Type | Description | +| ------------------------------------------------------- | --------------------------------------------------- | ----------------------------- | +| `/perception/traffic_light_recognition/traffic_signals` | `autoware_perception_msgs::msg::TrafficSignalArray` | Publish traffic light signals | ## HowToUse diff --git a/common/tier4_traffic_light_rviz_plugin/package.xml b/common/tier4_traffic_light_rviz_plugin/package.xml index 21d8bae8f6cff..2b118b429f1af 100644 --- a/common/tier4_traffic_light_rviz_plugin/package.xml +++ b/common/tier4_traffic_light_rviz_plugin/package.xml @@ -11,7 +11,7 @@ autoware_cmake autoware_auto_mapping_msgs - autoware_auto_perception_msgs + autoware_perception_msgs lanelet2_extension libqt5-core libqt5-gui diff --git a/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.cpp b/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.cpp index e4fb0095b8d0a..35c5a88a2f8a6 100644 --- a/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.cpp +++ b/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.cpp @@ -34,6 +34,7 @@ #include #include +#undef signals namespace rviz_plugins { TrafficLightPublishPanel::TrafficLightPublishPanel(QWidget * parent) : rviz_common::Panel(parent) @@ -138,55 +139,55 @@ void TrafficLightPublishPanel::onSetTrafficLightState() const auto shape = light_shape_combo_->currentText(); const auto status = light_status_combo_->currentText(); - TrafficLight traffic_light; + TrafficSignalElement traffic_light; traffic_light.confidence = traffic_light_confidence_input_->value(); if (color == "RED") { - traffic_light.color = TrafficLight::RED; + traffic_light.color = TrafficSignalElement::RED; } else if (color == "AMBER") { - traffic_light.color = TrafficLight::AMBER; + traffic_light.color = TrafficSignalElement::AMBER; } else if (color == "GREEN") { - traffic_light.color = TrafficLight::GREEN; + traffic_light.color = TrafficSignalElement::GREEN; } else if (color == "WHITE") { - traffic_light.color = TrafficLight::WHITE; + traffic_light.color = TrafficSignalElement::WHITE; } else if (color == "UNKNOWN") { - traffic_light.color = TrafficLight::UNKNOWN; + traffic_light.color = TrafficSignalElement::UNKNOWN; } if (shape == "CIRCLE") { - traffic_light.shape = TrafficLight::CIRCLE; + traffic_light.shape = TrafficSignalElement::CIRCLE; } else if (shape == "LEFT_ARROW") { - traffic_light.shape = TrafficLight::LEFT_ARROW; + traffic_light.shape = TrafficSignalElement::LEFT_ARROW; } else if (shape == "RIGHT_ARROW") { - traffic_light.shape = TrafficLight::RIGHT_ARROW; + traffic_light.shape = TrafficSignalElement::RIGHT_ARROW; } else if (shape == "UP_ARROW") { - traffic_light.shape = TrafficLight::UP_ARROW; + traffic_light.shape = TrafficSignalElement::UP_ARROW; } else if (shape == "DOWN_ARROW") { - traffic_light.shape = TrafficLight::DOWN_ARROW; + traffic_light.shape = TrafficSignalElement::DOWN_ARROW; } else if (shape == "DOWN_LEFT_ARROW") { - traffic_light.shape = TrafficLight::DOWN_LEFT_ARROW; + traffic_light.shape = TrafficSignalElement::DOWN_LEFT_ARROW; } else if (shape == "DOWN_RIGHT_ARROW") { - traffic_light.shape = TrafficLight::DOWN_RIGHT_ARROW; + traffic_light.shape = TrafficSignalElement::DOWN_RIGHT_ARROW; } else if (shape == "UNKNOWN") { - traffic_light.shape = TrafficLight::UNKNOWN; + traffic_light.shape = TrafficSignalElement::UNKNOWN; } if (status == "SOLID_OFF") { - traffic_light.status = TrafficLight::SOLID_OFF; + traffic_light.status = TrafficSignalElement::SOLID_OFF; } else if (status == "SOLID_ON") { - traffic_light.status = TrafficLight::SOLID_ON; + traffic_light.status = TrafficSignalElement::SOLID_ON; } else if (status == "FLASHING") { - traffic_light.status = TrafficLight::FLASHING; + traffic_light.status = TrafficSignalElement::FLASHING; } else if (status == "UNKNOWN") { - traffic_light.status = TrafficLight::UNKNOWN; + traffic_light.status = TrafficSignalElement::UNKNOWN; } TrafficSignal traffic_signal; - traffic_signal.lights.push_back(traffic_light); - traffic_signal.map_primitive_id = traffic_light_id; + traffic_signal.elements.push_back(traffic_light); + traffic_signal.traffic_signal_id = traffic_light_id; for (auto & signal : extra_traffic_signals_.signals) { - if (signal.map_primitive_id == traffic_light_id) { + if (signal.traffic_signal_id == traffic_light_id) { signal = traffic_signal; return; } @@ -247,7 +248,7 @@ void TrafficLightPublishPanel::createWallTimer() void TrafficLightPublishPanel::onTimer() { if (enable_publish_) { - extra_traffic_signals_.header.stamp = rclcpp::Clock().now(); + extra_traffic_signals_.stamp = rclcpp::Clock().now(); pub_traffic_signals_->publish(extra_traffic_signals_); } @@ -260,35 +261,35 @@ void TrafficLightPublishPanel::onTimer() for (size_t i = 0; i < extra_traffic_signals_.signals.size(); ++i) { const auto & signal = extra_traffic_signals_.signals.at(i); - if (signal.lights.empty()) { + if (signal.elements.empty()) { continue; } - auto id_label = new QLabel(QString::number(signal.map_primitive_id)); + auto id_label = new QLabel(QString::number(signal.traffic_signal_id)); id_label->setAlignment(Qt::AlignCenter); auto color_label = new QLabel(); color_label->setAlignment(Qt::AlignCenter); - const auto & light = signal.lights.front(); + const auto & light = signal.elements.front(); switch (light.color) { - case TrafficLight::RED: + case TrafficSignalElement::RED: color_label->setText("RED"); color_label->setStyleSheet("background-color: #FF0000;"); break; - case TrafficLight::AMBER: + case TrafficSignalElement::AMBER: color_label->setText("AMBER"); color_label->setStyleSheet("background-color: #FFBF00;"); break; - case TrafficLight::GREEN: + case TrafficSignalElement::GREEN: color_label->setText("GREEN"); color_label->setStyleSheet("background-color: #7CFC00;"); break; - case TrafficLight::WHITE: + case TrafficSignalElement::WHITE: color_label->setText("WHITE"); color_label->setStyleSheet("background-color: #FFFFFF;"); break; - case TrafficLight::UNKNOWN: + case TrafficSignalElement::UNKNOWN: color_label->setText("UNKNOWN"); color_label->setStyleSheet("background-color: #808080;"); break; @@ -300,31 +301,28 @@ void TrafficLightPublishPanel::onTimer() shape_label->setAlignment(Qt::AlignCenter); switch (light.shape) { - case TrafficLight::CIRCLE: + case TrafficSignalElement::CIRCLE: shape_label->setText("CIRCLE"); break; - case TrafficLight::LEFT_ARROW: + case TrafficSignalElement::LEFT_ARROW: shape_label->setText("LEFT_ARROW"); break; - case TrafficLight::RIGHT_ARROW: + case TrafficSignalElement::RIGHT_ARROW: shape_label->setText("RIGHT_ARROW"); break; - case TrafficLight::UP_ARROW: + case TrafficSignalElement::UP_ARROW: shape_label->setText("UP_ARROW"); break; - case TrafficLight::DOWN_ARROW: + case TrafficSignalElement::DOWN_ARROW: shape_label->setText("DOWN_ARROW"); break; - case TrafficLight::DOWN_LEFT_ARROW: + case TrafficSignalElement::DOWN_LEFT_ARROW: shape_label->setText("DOWN_LEFT_ARROW"); break; - case TrafficLight::DOWN_RIGHT_ARROW: + case TrafficSignalElement::DOWN_RIGHT_ARROW: shape_label->setText("DOWN_RIGHT_ARROW"); break; - case TrafficLight::FLASHING: - shape_label->setText("FLASHING"); - break; - case TrafficLight::UNKNOWN: + case TrafficSignalElement::UNKNOWN: shape_label->setText("UNKNOWN"); break; default: @@ -335,16 +333,16 @@ void TrafficLightPublishPanel::onTimer() status_label->setAlignment(Qt::AlignCenter); switch (light.status) { - case TrafficLight::SOLID_OFF: + case TrafficSignalElement::SOLID_OFF: status_label->setText("SOLID_OFF"); break; - case TrafficLight::SOLID_ON: + case TrafficSignalElement::SOLID_ON: status_label->setText("SOLID_ON"); break; - case TrafficLight::FLASHING: + case TrafficSignalElement::FLASHING: status_label->setText("FLASHING"); break; - case TrafficLight::UNKNOWN: + case TrafficSignalElement::UNKNOWN: status_label->setText("UNKNOWN"); break; default: @@ -375,11 +373,9 @@ void TrafficLightPublishPanel::onVectorMap(const HADMapBin::ConstSharedPtr msg) std::string info = "Fetching traffic lights :"; std::string delim = " "; for (auto && tl_reg_elem_ptr : tl_reg_elems) { - for (auto && traffic_light : tl_reg_elem_ptr->trafficLights()) { - auto id = static_cast(traffic_light.id()); - info += (std::exchange(delim, ", ") + std::to_string(id)); - traffic_light_ids_.insert(id); - } + auto id = static_cast(tl_reg_elem_ptr->id()); + info += (std::exchange(delim, ", ") + std::to_string(id)); + traffic_light_ids_.insert(id); } RCLCPP_INFO_STREAM(raw_node_->get_logger(), info); received_vector_map_ = true; diff --git a/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.hpp b/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.hpp index 75e6405417084..e54b6a301873b 100644 --- a/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.hpp +++ b/common/tier4_traffic_light_rviz_plugin/src/traffic_light_publish_panel.hpp @@ -27,7 +27,7 @@ #include #include -#include +#include #endif #include @@ -36,10 +36,9 @@ namespace rviz_plugins { using autoware_auto_mapping_msgs::msg::HADMapBin; -using autoware_auto_perception_msgs::msg::TrafficLight; -using autoware_auto_perception_msgs::msg::TrafficSignal; -using autoware_auto_perception_msgs::msg::TrafficSignalArray; - +using autoware_perception_msgs::msg::TrafficSignal; +using autoware_perception_msgs::msg::TrafficSignalArray; +using autoware_perception_msgs::msg::TrafficSignalElement; class TrafficLightPublishPanel : public rviz_common::Panel { Q_OBJECT diff --git a/launch/tier4_planning_launch/launch/scenario_planning/lane_driving/behavior_planning/behavior_planning.launch.py b/launch/tier4_planning_launch/launch/scenario_planning/lane_driving/behavior_planning/behavior_planning.launch.py index 59e8038e49188..62d4c5b7188ee 100644 --- a/launch/tier4_planning_launch/launch/scenario_planning/lane_driving/behavior_planning/behavior_planning.launch.py +++ b/launch/tier4_planning_launch/launch/scenario_planning/lane_driving/behavior_planning/behavior_planning.launch.py @@ -177,10 +177,6 @@ def launch_setup(context, *args, **kwargs): "~/input/traffic_signals", "/perception/traffic_light_recognition/traffic_signals", ), - ( - "~/input/external_traffic_signals", - "/external/traffic_light_recognition/traffic_signals", - ), ( "~/input/external_velocity_limit_mps", "/planning/scenario_planning/max_velocity_default", diff --git a/planning/behavior_velocity_crosswalk_module/package.xml b/planning/behavior_velocity_crosswalk_module/package.xml index 75f96c9194aef..66f326ed799af 100644 --- a/planning/behavior_velocity_crosswalk_module/package.xml +++ b/planning/behavior_velocity_crosswalk_module/package.xml @@ -21,6 +21,7 @@ autoware_auto_perception_msgs autoware_auto_planning_msgs autoware_auto_tf2 + autoware_perception_msgs behavior_velocity_planner_common eigen geometry_msgs diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index 6d2a3110cbdb7..7a972891fd053 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -938,28 +938,25 @@ bool CrosswalkModule::isRedSignalForPedestrians() const crosswalk_.regulatoryElementsAs(); for (const auto & traffic_lights_reg_elem : traffic_lights_reg_elems) { - lanelet::ConstLineStringsOrPolygons3d traffic_lights = traffic_lights_reg_elem->trafficLights(); - for (const auto & traffic_light : traffic_lights) { - const auto ll_traffic_light = static_cast(traffic_light); - const auto traffic_signal_stamped = planner_data_->getTrafficSignal(ll_traffic_light.id()); - if (!traffic_signal_stamped) { - continue; - } + const auto traffic_signal_stamped = + planner_data_->getTrafficSignal(traffic_lights_reg_elem->id()); + if (!traffic_signal_stamped) { + continue; + } - if ( - planner_param_.traffic_light_state_timeout < - (clock_->now() - traffic_signal_stamped->header.stamp).seconds()) { - continue; - } + if ( + planner_param_.traffic_light_state_timeout < + (clock_->now() - traffic_signal_stamped->stamp).seconds()) { + continue; + } - const auto & lights = traffic_signal_stamped->signal.lights; - if (lights.empty()) { - continue; - } + const auto & lights = traffic_signal_stamped->signal.elements; + if (lights.empty()) { + continue; + } - if (lights.front().color == TrafficLight::RED) { - return true; - } + if (lights.front().color == TrafficSignalElement::RED) { + return true; } } diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp index e0851366d7f71..65f3772cd4d33 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp @@ -50,8 +50,8 @@ namespace behavior_velocity_planner using autoware_auto_perception_msgs::msg::ObjectClassification; using autoware_auto_perception_msgs::msg::PredictedObject; using autoware_auto_perception_msgs::msg::PredictedObjects; -using autoware_auto_perception_msgs::msg::TrafficLight; using autoware_auto_planning_msgs::msg::PathWithLaneId; +using autoware_perception_msgs::msg::TrafficSignalElement; using lanelet::autoware::Crosswalk; using tier4_api_msgs::msg::CrosswalkStatus; using tier4_autoware_utils::Polygon2d; diff --git a/planning/behavior_velocity_intersection_module/package.xml b/planning/behavior_velocity_intersection_module/package.xml index f22e9d788fc65..879b26209a05e 100644 --- a/planning/behavior_velocity_intersection_module/package.xml +++ b/planning/behavior_velocity_intersection_module/package.xml @@ -19,6 +19,7 @@ autoware_auto_perception_msgs autoware_auto_planning_msgs + autoware_perception_msgs behavior_velocity_planner_common geometry_msgs interpolation diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index e430f925ab2d6..d78b7252d0878 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -704,9 +704,10 @@ bool hasAssociatedTrafficLight(lanelet::ConstLanelet lane) } bool isTrafficLightArrowActivated( - lanelet::ConstLanelet lane, - const std::map & tl_infos) + lanelet::ConstLanelet lane, const std::map & tl_infos) { + using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; + const auto & turn_direction = lane.attributeOr("turn_direction", "else"); std::optional tl_id = std::nullopt; for (auto && tl_reg_elem : lane.regulatoryElementsAs()) { @@ -723,16 +724,13 @@ bool isTrafficLightArrowActivated( return false; } const auto & tl_info = tl_info_it->second; - for (auto && tl_light : tl_info.signal.lights) { - if (tl_light.color != autoware_auto_perception_msgs::msg::TrafficLight::GREEN) continue; - if (tl_light.status != autoware_auto_perception_msgs::msg::TrafficLight::SOLID_ON) continue; - if ( - turn_direction == std::string("left") && - tl_light.shape == autoware_auto_perception_msgs::msg::TrafficLight::LEFT_ARROW) + for (auto && tl_light : tl_info.signal.elements) { + if (tl_light.color != TrafficSignalElement::GREEN) continue; + if (tl_light.status != TrafficSignalElement::SOLID_ON) continue; + if (turn_direction == std::string("left") && tl_light.shape == TrafficSignalElement::LEFT_ARROW) return true; if ( - turn_direction == std::string("right") && - tl_light.shape == autoware_auto_perception_msgs::msg::TrafficLight::RIGHT_ARROW) + turn_direction == std::string("right") && tl_light.shape == TrafficSignalElement::RIGHT_ARROW) return true; } return false; diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 4ae4e72cb4ac2..e766da6651417 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -112,8 +112,7 @@ std::optional getIntersectionArea( bool hasAssociatedTrafficLight(lanelet::ConstLanelet lane); bool isTrafficLightArrowActivated( - lanelet::ConstLanelet lane, - const std::map & tl_infos); + lanelet::ConstLanelet lane, const std::map & tl_infos); std::vector generateDetectionLaneDivisions( lanelet::ConstLanelets detection_lanelets, diff --git a/planning/behavior_velocity_planner/README.md b/planning/behavior_velocity_planner/README.md index 25546c95074c3..519e68764b450 100644 --- a/planning/behavior_velocity_planner/README.md +++ b/planning/behavior_velocity_planner/README.md @@ -29,15 +29,15 @@ So for example, in order to stop at a stop line with the vehicles' front on the ## Input topics -| Name | Type | Description | -| ----------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | -| `~input/path_with_lane_id` | autoware_auto_planning_msgs::msg::PathWithLaneId | path with lane_id | -| `~input/vector_map` | autoware_auto_mapping_msgs::msg::HADMapBin | vector map | -| `~input/vehicle_odometry` | nav_msgs::msg::Odometry | vehicle velocity | -| `~input/dynamic_objects` | autoware_auto_perception_msgs::msg::PredictedObjects | dynamic objects | -| `~input/no_ground_pointcloud` | sensor_msgs::msg::PointCloud2 | obstacle pointcloud | -| `~/input/compare_map_filtered_pointcloud` | sensor_msgs::msg::PointCloud2 | obstacle pointcloud filtered by compare map. Note that this is used only when the detection method of run out module is Points. | -| `~input/traffic_signals` | autoware_auto_perception_msgs::msg::TrafficSignalArray | traffic light states | +| Name | Type | Description | +| ----------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `~input/path_with_lane_id` | autoware_auto_planning_msgs::msg::PathWithLaneId | path with lane_id | +| `~input/vector_map` | autoware_auto_mapping_msgs::msg::HADMapBin | vector map | +| `~input/vehicle_odometry` | nav_msgs::msg::Odometry | vehicle velocity | +| `~input/dynamic_objects` | autoware_auto_perception_msgs::msg::PredictedObjects | dynamic objects | +| `~input/no_ground_pointcloud` | sensor_msgs::msg::PointCloud2 | obstacle pointcloud | +| `~/input/compare_map_filtered_pointcloud` | sensor_msgs::msg::PointCloud2 | obstacle pointcloud filtered by compare map. Note that this is used only when the detection method of run out module is Points. | +| `~input/traffic_signals` | autoware_perception_msgs::msg::TrafficSignalArray | traffic light states | ## Output topics diff --git a/planning/behavior_velocity_planner/src/node.cpp b/planning/behavior_velocity_planner/src/node.cpp index 12503f743b0f2..0aed3015fbf40 100644 --- a/planning/behavior_velocity_planner/src/node.cpp +++ b/planning/behavior_velocity_planner/src/node.cpp @@ -99,7 +99,7 @@ BehaviorVelocityPlannerNode::BehaviorVelocityPlannerNode(const rclcpp::NodeOptio std::bind(&BehaviorVelocityPlannerNode::onLaneletMap, this, _1), createSubscriptionOptions(this)); sub_traffic_signals_ = - this->create_subscription( + this->create_subscription( "~/input/traffic_signals", 1, std::bind(&BehaviorVelocityPlannerNode::onTrafficSignals, this, _1), createSubscriptionOptions(this)); @@ -287,15 +287,15 @@ void BehaviorVelocityPlannerNode::onLaneletMap( } void BehaviorVelocityPlannerNode::onTrafficSignals( - const autoware_auto_perception_msgs::msg::TrafficSignalArray::ConstSharedPtr msg) + const autoware_perception_msgs::msg::TrafficSignalArray::ConstSharedPtr msg) { std::lock_guard lock(mutex_); for (const auto & signal : msg->signals) { - autoware_auto_perception_msgs::msg::TrafficSignalStamped traffic_signal; - traffic_signal.header = msg->header; + TrafficSignalStamped traffic_signal; + traffic_signal.stamp = msg->stamp; traffic_signal.signal = signal; - planner_data_.traffic_light_id_map[signal.map_primitive_id] = traffic_signal; + planner_data_.traffic_light_id_map[signal.traffic_signal_id] = traffic_signal; } } diff --git a/planning/behavior_velocity_planner/src/node.hpp b/planning/behavior_velocity_planner/src/node.hpp index 91172fdd77da8..98cdaeb3e0cdb 100644 --- a/planning/behavior_velocity_planner/src/node.hpp +++ b/planning/behavior_velocity_planner/src/node.hpp @@ -61,7 +61,7 @@ class BehaviorVelocityPlannerNode : public rclcpp::Node rclcpp::Subscription::SharedPtr sub_vehicle_odometry_; rclcpp::Subscription::SharedPtr sub_acceleration_; rclcpp::Subscription::SharedPtr sub_lanelet_map_; - rclcpp::Subscription::SharedPtr + rclcpp::Subscription::SharedPtr sub_traffic_signals_; rclcpp::Subscription::SharedPtr sub_virtual_traffic_light_states_; @@ -77,7 +77,7 @@ class BehaviorVelocityPlannerNode : public rclcpp::Node void onAcceleration(const geometry_msgs::msg::AccelWithCovarianceStamped::ConstSharedPtr msg); void onLaneletMap(const autoware_auto_mapping_msgs::msg::HADMapBin::ConstSharedPtr msg); void onTrafficSignals( - const autoware_auto_perception_msgs::msg::TrafficSignalArray::ConstSharedPtr msg); + const autoware_perception_msgs::msg::TrafficSignalArray::ConstSharedPtr msg); void onVirtualTrafficLightStates( const tier4_v2x_msgs::msg::VirtualTrafficLightStateArray::ConstSharedPtr msg); void onOccupancyGrid(const nav_msgs::msg::OccupancyGrid::ConstSharedPtr msg); diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp index 44d3184a5f6b7..2668d83b0f510 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp @@ -17,14 +17,14 @@ #include "route_handler/route_handler.hpp" +#include #include #include #include #include #include -#include -#include +#include #include #include #include @@ -83,7 +83,7 @@ struct PlannerData double ego_nearest_yaw_threshold; // other internal data - std::map traffic_light_id_map; + std::map traffic_light_id_map; boost::optional external_velocity_limit; tier4_v2x_msgs::msg::VirtualTrafficLightStateArray::ConstSharedPtr virtual_traffic_light_states; @@ -132,14 +132,12 @@ struct PlannerData return true; } - std::shared_ptr getTrafficSignal( - const int id) const + std::shared_ptr getTrafficSignal(const int id) const { if (traffic_light_id_map.count(id) == 0) { return {}; } - return std::make_shared( - traffic_light_id_map.at(id)); + return std::make_shared(traffic_light_id_map.at(id)); } }; } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp index 14b009ceb0748..14c80b59acf5e 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,12 @@ struct PointWithSearchRangeIndex SearchRangeIndex index; }; +struct TrafficSignalStamped +{ + builtin_interfaces::msg::Time stamp; + autoware_perception_msgs::msg::TrafficSignal signal; +}; + using geometry_msgs::msg::Pose; using BasicPolygons2d = std::vector; using Polygons2d = std::vector; diff --git a/planning/behavior_velocity_planner_common/package.xml b/planning/behavior_velocity_planner_common/package.xml index 6a7a6f698bfbc..aedbab65068fb 100644 --- a/planning/behavior_velocity_planner_common/package.xml +++ b/planning/behavior_velocity_planner_common/package.xml @@ -19,9 +19,9 @@ autoware_adapi_v1_msgs autoware_auto_mapping_msgs - autoware_auto_perception_msgs autoware_auto_planning_msgs autoware_auto_tf2 + autoware_perception_msgs diagnostic_msgs eigen geometry_msgs diff --git a/planning/behavior_velocity_traffic_light_module/package.xml b/planning/behavior_velocity_traffic_light_module/package.xml index a8733c5dcf5be..c658f0890b986 100644 --- a/planning/behavior_velocity_traffic_light_module/package.xml +++ b/planning/behavior_velocity_traffic_light_module/package.xml @@ -18,8 +18,8 @@ eigen3_cmake_module autoware_adapi_v1_msgs - autoware_auto_perception_msgs autoware_auto_planning_msgs + autoware_perception_msgs behavior_velocity_planner_common eigen geometry_msgs diff --git a/planning/behavior_velocity_traffic_light_module/src/manager.cpp b/planning/behavior_velocity_traffic_light_module/src/manager.cpp index 1b0646f4bee00..f035544b81592 100644 --- a/planning/behavior_velocity_traffic_light_module/src/manager.cpp +++ b/planning/behavior_velocity_traffic_light_module/src/manager.cpp @@ -41,7 +41,7 @@ TrafficLightModuleManager::TrafficLightModuleManager(rclcpp::Node & node) planner_param_.enable_pass_judge = getOrDeclareParameter(node, ns + ".enable_pass_judge"); planner_param_.yellow_lamp_period = getOrDeclareParameter(node, ns + ".yellow_lamp_period"); - pub_tl_state_ = node.create_publisher( + pub_tl_state_ = node.create_publisher( "~/output/traffic_signal", 1); } @@ -51,9 +51,7 @@ void TrafficLightModuleManager::modifyPathVelocity( visualization_msgs::msg::MarkerArray debug_marker_array; visualization_msgs::msg::MarkerArray virtual_wall_marker_array; - autoware_auto_perception_msgs::msg::LookingTrafficSignal tl_state; - tl_state.header.stamp = path->header.stamp; - tl_state.is_module_running = false; + autoware_perception_msgs::msg::TrafficSignal tl_state; autoware_adapi_v1_msgs::msg::VelocityFactorArray velocity_factor_array; velocity_factor_array.header.frame_id = "map"; diff --git a/planning/behavior_velocity_traffic_light_module/src/manager.hpp b/planning/behavior_velocity_traffic_light_module/src/manager.hpp index 450cba09e1e08..c36c6af1128ce 100644 --- a/planning/behavior_velocity_traffic_light_module/src/manager.hpp +++ b/planning/behavior_velocity_traffic_light_module/src/manager.hpp @@ -55,8 +55,7 @@ class TrafficLightModuleManager : public SceneModuleManagerInterfaceWithRTC const lanelet::TrafficLightConstPtr registered_element) const; // Debug - rclcpp::Publisher::SharedPtr - pub_tl_state_; + rclcpp::Publisher::SharedPtr pub_tl_state_; boost::optional first_ref_stop_path_point_index_; }; diff --git a/planning/behavior_velocity_traffic_light_module/src/scene.cpp b/planning/behavior_velocity_traffic_light_module/src/scene.cpp index 679a2f0cb9320..e0633926e35df 100644 --- a/planning/behavior_velocity_traffic_light_module/src/scene.cpp +++ b/planning/behavior_velocity_traffic_light_module/src/scene.cpp @@ -159,32 +159,6 @@ bool calcStopPointAndInsertIndex( } return false; } - -geometry_msgs::msg::Point getTrafficLightPosition( - const lanelet::ConstLineStringOrPolygon3d & traffic_light) -{ - if (!traffic_light.lineString()) { - throw std::invalid_argument{"Traffic light is not LineString"}; - } - geometry_msgs::msg::Point tl_center; - auto traffic_light_ls = traffic_light.lineString().value(); - for (const auto & tl_point : traffic_light_ls) { - tl_center.x += tl_point.x() / traffic_light_ls.size(); - tl_center.y += tl_point.y() / traffic_light_ls.size(); - tl_center.z += tl_point.z() / traffic_light_ls.size(); - } - return tl_center; -} -autoware_auto_perception_msgs::msg::LookingTrafficSignal initializeTrafficSignal( - const rclcpp::Time stamp) -{ - autoware_auto_perception_msgs::msg::LookingTrafficSignal state; - state.header.stamp = stamp; - state.is_module_running = true; - state.perception.has_state = false; - state.result.has_state = false; - return state; -} } // namespace TrafficLightModule::TrafficLightModule( @@ -197,7 +171,6 @@ TrafficLightModule::TrafficLightModule( traffic_light_reg_elem_(traffic_light_reg_elem), lane_(lane), state_(State::APPROACH), - input_(Input::PERCEPTION), is_prev_state_stop_(false) { velocity_factor_.init(VelocityFactor::TRAFFIC_SIGNAL); @@ -206,7 +179,6 @@ TrafficLightModule::TrafficLightModule( bool TrafficLightModule::modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) { - looking_tl_state_ = initializeTrafficSignal(path->header.stamp); debug_data_ = DebugData(); debug_data_.base_link2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; first_stop_path_point_index_ = static_cast(path->points.size()) - 1; @@ -217,9 +189,8 @@ bool TrafficLightModule::modifyPathVelocity(PathWithLaneId * path, StopReason * const auto & self_pose = planner_data_->current_odometry; - // Get lanelet2 traffic lights and stop lines. + // Get lanelet2 stop lines. lanelet::ConstLineString3d lanelet_stop_lines = *(traffic_light_reg_elem_.stopLine()); - lanelet::ConstLineStringsOrPolygons3d traffic_lights = traffic_light_reg_elem_.trafficLights(); // Calculate stop pose and insert index Eigen::Vector2d stop_line_point{}; @@ -255,7 +226,7 @@ bool TrafficLightModule::modifyPathVelocity(PathWithLaneId * path, StopReason * first_ref_stop_path_point_index_ = stop_line_point_idx; // Check if stop is coming. - setSafe(!isStopSignal(traffic_lights)); + setSafe(!isStopSignal()); if (isActivated()) { is_prev_state_stop_ = false; return true; @@ -283,33 +254,27 @@ bool TrafficLightModule::modifyPathVelocity(PathWithLaneId * path, StopReason * return false; } -bool TrafficLightModule::isStopSignal(const lanelet::ConstLineStringsOrPolygons3d & traffic_lights) +bool TrafficLightModule::isStopSignal() { - if (!updateTrafficSignal(traffic_lights)) { + if (!updateTrafficSignal()) { return false; } - return looking_tl_state_.result.judge == - autoware_auto_perception_msgs::msg::TrafficSignalWithJudge::STOP; + return isTrafficSignalStop(looking_tl_state_); } -bool TrafficLightModule::updateTrafficSignal( - const lanelet::ConstLineStringsOrPolygons3d & traffic_lights) +bool TrafficLightModule::updateTrafficSignal() { - autoware_auto_perception_msgs::msg::TrafficSignalStamped tl_state_perception; - bool found_perception = getHighestConfidenceTrafficSignal(traffic_lights, tl_state_perception); + TrafficSignal signal; + bool found_signal = findValidTrafficSignal(signal); - if (!found_perception) { + if (!found_signal) { // Don't stop when UNKNOWN or TIMEOUT as discussed at #508 - input_ = Input::NONE; return false; } - if (found_perception) { - looking_tl_state_.perception = generateTlStateWithJudgeFromTlState(tl_state_perception.signal); - looking_tl_state_.result = looking_tl_state_.perception; - input_ = Input::PERCEPTION; - } + // Found signal associated with the lanelet + looking_tl_state_ = signal; return true; } @@ -337,7 +302,7 @@ bool TrafficLightModule::isPassthrough(const double & signed_arc_length) const const auto & enable_pass_judge = planner_param_.enable_pass_judge; - if (enable_pass_judge && !stoppable && !is_prev_state_stop_ && input_ == Input::PERCEPTION) { + if (enable_pass_judge && !stoppable && !is_prev_state_stop_) { // Cannot stop under acceleration and jerk limits. // However, ego vehicle can't enter the intersection while the light is yellow. // It is called dilemma zone. Make a stop decision to be safe. @@ -359,10 +324,9 @@ bool TrafficLightModule::isPassthrough(const double & signed_arc_length) const } bool TrafficLightModule::isTrafficSignalStop( - const autoware_auto_perception_msgs::msg::TrafficSignal & tl_state) const + const autoware_perception_msgs::msg::TrafficSignal & tl_state) const { - if (hasTrafficLightCircleColor( - tl_state, autoware_auto_perception_msgs::msg::TrafficLight::GREEN)) { + if (hasTrafficLightCircleColor(tl_state, TrafficSignalElement::GREEN)) { return false; } @@ -373,86 +337,46 @@ bool TrafficLightModule::isTrafficSignalStop( } if ( turn_direction == "right" && - hasTrafficLightShape(tl_state, autoware_auto_perception_msgs::msg::TrafficLight::RIGHT_ARROW)) { + hasTrafficLightShape(tl_state, TrafficSignalElement::RIGHT_ARROW)) { return false; } if ( - turn_direction == "left" && - hasTrafficLightShape(tl_state, autoware_auto_perception_msgs::msg::TrafficLight::LEFT_ARROW)) { + turn_direction == "left" && hasTrafficLightShape(tl_state, TrafficSignalElement::LEFT_ARROW)) { return false; } if ( turn_direction == "straight" && - hasTrafficLightShape(tl_state, autoware_auto_perception_msgs::msg::TrafficLight::UP_ARROW)) { + hasTrafficLightShape(tl_state, TrafficSignalElement::UP_ARROW)) { return false; } return true; } -bool TrafficLightModule::getHighestConfidenceTrafficSignal( - const lanelet::ConstLineStringsOrPolygons3d & traffic_lights, - autoware_auto_perception_msgs::msg::TrafficSignalStamped & highest_confidence_tl_state) +bool TrafficLightModule::findValidTrafficSignal(TrafficSignal & valid_traffic_signal) { - // search traffic light state - bool found = false; - double highest_confidence = 0.0; - std::string reason; - for (const auto & traffic_light : traffic_lights) { - // traffic light must be linestrings - if (!traffic_light.isLineString()) { - reason = "NotLineString"; - continue; - } - - const int id = static_cast(traffic_light).id(); - RCLCPP_DEBUG(logger_, "traffic light id: %d (on route)", id); - const auto tl_state_stamped = planner_data_->getTrafficSignal(id); - if (!tl_state_stamped) { - reason = "TrafficSignalNotFound"; - continue; - } - - const auto header = tl_state_stamped->header; - const auto tl_state = tl_state_stamped->signal; - if (!((clock_->now() - header.stamp).seconds() < planner_param_.tl_state_timeout)) { - reason = "TimeOut"; - continue; - } - - if ( - tl_state.lights.empty() || - tl_state.lights.front().color == autoware_auto_perception_msgs::msg::TrafficLight::UNKNOWN) { - reason = "TrafficLightUnknown"; - continue; - } - - if (highest_confidence < tl_state.lights.front().confidence) { - highest_confidence = tl_state.lights.front().confidence; - highest_confidence_tl_state = *tl_state_stamped; - try { - auto tl_position = getTrafficLightPosition(traffic_light); - debug_data_.traffic_light_points.push_back(tl_position); - debug_data_.highest_confidence_traffic_light_point = std::make_optional(tl_position); - } catch (const std::invalid_argument & ex) { - RCLCPP_WARN_STREAM(logger_, ex.what()); - continue; - } - found = true; - } + // get traffic signal associated with the regulatory element id + const auto traffic_signal_stamped = planner_data_->getTrafficSignal(traffic_light_reg_elem_.id()); + if (!traffic_signal_stamped) { + RCLCPP_WARN_THROTTLE( + logger_, *clock_, 5000 /* ms */, + "the traffic signal data associated with regulatory element id is not received"); + return false; } - if (!found) { - std::string not_found_traffic_ids{""}; - for (size_t i = 0; i < traffic_lights.size(); ++i) { - const int id = static_cast(traffic_lights.at(i)).id(); - not_found_traffic_ids += (i != 0 ? "," : "") + std::to_string(id); - } + // check if the traffic signal data is outdated + const auto is_traffic_signal_timeout = + (clock_->now() - traffic_signal_stamped->stamp).seconds() > planner_param_.tl_state_timeout; + if (is_traffic_signal_timeout) { RCLCPP_WARN_THROTTLE( - logger_, *clock_, 5000 /* ms */, "cannot find traffic light lamp state (%s) due to %s.", - not_found_traffic_ids.c_str(), reason.c_str()); + logger_, *clock_, 5000 /* ms */, "the received traffic signal data is outdated"); + RCLCPP_WARN_STREAM_THROTTLE( + logger_, *clock_, 5000 /* ms */, + "time diff: " << (clock_->now() - traffic_signal_stamped->stamp).seconds()); return false; } + + valid_traffic_signal = traffic_signal_stamped->signal; return true; } @@ -496,40 +420,24 @@ autoware_auto_planning_msgs::msg::PathWithLaneId TrafficLightModule::insertStopP } bool TrafficLightModule::hasTrafficLightCircleColor( - const autoware_auto_perception_msgs::msg::TrafficSignal & tl_state, - const uint8_t & lamp_color) const + const autoware_perception_msgs::msg::TrafficSignal & tl_state, const uint8_t & lamp_color) const { - using autoware_auto_perception_msgs::msg::TrafficLight; - const auto it_lamp = - std::find_if(tl_state.lights.begin(), tl_state.lights.end(), [&lamp_color](const auto & x) { - return x.shape == TrafficLight::CIRCLE && x.color == lamp_color; + std::find_if(tl_state.elements.begin(), tl_state.elements.end(), [&lamp_color](const auto & x) { + return x.shape == TrafficSignalElement::CIRCLE && x.color == lamp_color; }); - return it_lamp != tl_state.lights.end(); + return it_lamp != tl_state.elements.end(); } bool TrafficLightModule::hasTrafficLightShape( - const autoware_auto_perception_msgs::msg::TrafficSignal & tl_state, - const uint8_t & lamp_shape) const + const autoware_perception_msgs::msg::TrafficSignal & tl_state, const uint8_t & lamp_shape) const { const auto it_lamp = std::find_if( - tl_state.lights.begin(), tl_state.lights.end(), + tl_state.elements.begin(), tl_state.elements.end(), [&lamp_shape](const auto & x) { return x.shape == lamp_shape; }); - return it_lamp != tl_state.lights.end(); + return it_lamp != tl_state.elements.end(); } -autoware_auto_perception_msgs::msg::TrafficSignalWithJudge -TrafficLightModule::generateTlStateWithJudgeFromTlState( - const autoware_auto_perception_msgs::msg::TrafficSignal tl_state) const -{ - autoware_auto_perception_msgs::msg::TrafficSignalWithJudge tl_state_with_judge; - tl_state_with_judge.signal = tl_state; - tl_state_with_judge.has_state = true; - tl_state_with_judge.judge = isTrafficSignalStop(tl_state) - ? autoware_auto_perception_msgs::msg::TrafficSignalWithJudge::STOP - : autoware_auto_perception_msgs::msg::TrafficSignalWithJudge::GO; - return tl_state_with_judge; -} } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_traffic_light_module/src/scene.hpp b/planning/behavior_velocity_traffic_light_module/src/scene.hpp index 99ece6cbca09d..a24db2c440e91 100644 --- a/planning/behavior_velocity_traffic_light_module/src/scene.hpp +++ b/planning/behavior_velocity_traffic_light_module/src/scene.hpp @@ -29,8 +29,6 @@ #include #include -#include - #include #include @@ -39,15 +37,15 @@ namespace behavior_velocity_planner class TrafficLightModule : public SceneModuleInterface { public: + using TrafficSignal = autoware_perception_msgs::msg::TrafficSignal; + using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; enum class State { APPROACH, GO_OUT }; - enum class Input { PERCEPTION, NONE }; // EXTERNAL: FOA, V2X, etc. struct DebugData { double base_link2front; std::vector, - autoware_auto_perception_msgs::msg::TrafficSignal>> + std::shared_ptr, autoware_perception_msgs::msg::TrafficSignal>> tl_state; std::vector stop_poses; geometry_msgs::msg::Pose first_stop_pose; @@ -77,10 +75,7 @@ class TrafficLightModule : public SceneModuleInterface visualization_msgs::msg::MarkerArray createDebugMarkerArray() override; motion_utils::VirtualWalls createVirtualWalls() override; - inline autoware_auto_perception_msgs::msg::LookingTrafficSignal getTrafficSignal() const - { - return looking_tl_state_; - } + inline TrafficSignal getTrafficSignal() const { return looking_tl_state_; } inline State getTrafficLightModuleState() const { return state_; } @@ -90,10 +85,9 @@ class TrafficLightModule : public SceneModuleInterface } private: - bool isStopSignal(const lanelet::ConstLineStringsOrPolygons3d & traffic_lights); + bool isStopSignal(); - bool isTrafficSignalStop( - const autoware_auto_perception_msgs::msg::TrafficSignal & tl_state) const; + bool isTrafficSignalStop(const TrafficSignal & tl_state) const; autoware_auto_planning_msgs::msg::PathWithLaneId insertStopPose( const autoware_auto_planning_msgs::msg::PathWithLaneId & input, @@ -102,22 +96,13 @@ class TrafficLightModule : public SceneModuleInterface bool isPassthrough(const double & signed_arc_length) const; - bool hasTrafficLightCircleColor( - const autoware_auto_perception_msgs::msg::TrafficSignal & tl_state, - const uint8_t & lamp_color) const; + bool hasTrafficLightCircleColor(const TrafficSignal & tl_state, const uint8_t & lamp_color) const; - bool hasTrafficLightShape( - const autoware_auto_perception_msgs::msg::TrafficSignal & tl_state, - const uint8_t & lamp_shape) const; + bool hasTrafficLightShape(const TrafficSignal & tl_state, const uint8_t & lamp_shape) const; - bool getHighestConfidenceTrafficSignal( - const lanelet::ConstLineStringsOrPolygons3d & traffic_lights, - autoware_auto_perception_msgs::msg::TrafficSignalStamped & highest_confidence_tl_state); + bool findValidTrafficSignal(TrafficSignal & valid_traffic_signal); - bool updateTrafficSignal(const lanelet::ConstLineStringsOrPolygons3d & traffic_lights); - - autoware_auto_perception_msgs::msg::TrafficSignalWithJudge generateTlStateWithJudgeFromTlState( - const autoware_auto_perception_msgs::msg::TrafficSignal tl_state) const; + bool updateTrafficSignal(); // Lane id const int64_t lane_id_; @@ -129,9 +114,6 @@ class TrafficLightModule : public SceneModuleInterface // State State state_; - // Input - Input input_; - // Parameter PlannerParam planner_param_; @@ -144,7 +126,7 @@ class TrafficLightModule : public SceneModuleInterface boost::optional first_ref_stop_path_point_index_; // Traffic Light State - autoware_auto_perception_msgs::msg::LookingTrafficSignal looking_tl_state_; + TrafficSignal looking_tl_state_; }; } // namespace behavior_velocity_planner diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py index 1299062638471..9d8fa4a32a93d 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py @@ -73,7 +73,7 @@ def on_timer(self): # extract message by the timestamp msgs = copy.deepcopy(self.find_topics_by_timestamp(self.bag_timestamp)) objects_msg = msgs[0] - # traffic_signals_msg = msgs[1] + traffic_signals_msg = msgs[1] # objects if objects_msg: @@ -92,13 +92,13 @@ def on_timer(self): self.objects_pub.publish(objects_msg) # traffic signals - # if traffic_signals_msg: - # traffic_signals_msg.header.stamp = timestamp - # self.traffic_signals_pub.publish(traffic_signals_msg) - # self.prev_traffic_signals_msg = traffic_signals_msg - # elif self.prev_traffic_signals_msg: - # self.prev_traffic_signals_msg.header.stamp = timestamp - # self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) + if traffic_signals_msg: + traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(traffic_signals_msg) + self.prev_traffic_signals_msg = traffic_signals_msg + elif self.prev_traffic_signals_msg: + self.prev_traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) def onPushed(self, event): if self.widget.button.isChecked(): diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index a86a9ae9b2bb0..7774a8c82daf6 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -22,7 +22,7 @@ from autoware_auto_perception_msgs.msg import DetectedObjects from autoware_auto_perception_msgs.msg import PredictedObjects from autoware_auto_perception_msgs.msg import TrackedObjects -from autoware_auto_perception_msgs.msg import TrafficSignalArray +from autoware_perception_msgs.msg import TrafficSignalArray from nav_msgs.msg import Odometry import psutil from rclpy.node import Node diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py index 8895f7d921626..5344e622be38e 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py @@ -56,7 +56,7 @@ def on_timer(self): # extract message by the nearest ego odom timestamp msgs = copy.deepcopy(self.find_topics_by_timestamp(pose_timestamp)) objects_msg = msgs[0] - # traffic_signals_msg = msgs[1] + traffic_signals_msg = msgs[1] # objects if objects_msg: @@ -66,13 +66,13 @@ def on_timer(self): self.objects_pub.publish(objects_msg) # traffic signals - # if traffic_signals_msg: - # traffic_signals_msg.header.stamp = timestamp - # self.traffic_signals_pub.publish(traffic_signals_msg) - # self.prev_traffic_signals_msg = traffic_signals_msg - # elif self.prev_traffic_signals_msg: - # self.prev_traffic_signals_msg.header.stamp = timestamp - # self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) + if traffic_signals_msg: + traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(traffic_signals_msg) + self.prev_traffic_signals_msg = traffic_signals_msg + elif self.prev_traffic_signals_msg: + self.prev_traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) def find_nearest_ego_odom_by_observation(self, ego_pose): if self.ego_pose_idx: diff --git a/planning/planning_test_utils/include/planning_interface_test_manager/planning_interface_test_manager.hpp b/planning/planning_test_utils/include/planning_interface_test_manager/planning_interface_test_manager.hpp index ddc304c99ea6a..c5a30be126661 100644 --- a/planning/planning_test_utils/include/planning_interface_test_manager/planning_interface_test_manager.hpp +++ b/planning/planning_test_utils/include/planning_interface_test_manager/planning_interface_test_manager.hpp @@ -38,11 +38,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -77,10 +77,10 @@ namespace planning_test_utils using autoware_adapi_v1_msgs::msg::OperationModeState; using autoware_auto_mapping_msgs::msg::HADMapBin; using autoware_auto_perception_msgs::msg::PredictedObjects; -using autoware_auto_perception_msgs::msg::TrafficSignalArray; using autoware_auto_planning_msgs::msg::Path; using autoware_auto_planning_msgs::msg::PathWithLaneId; using autoware_auto_planning_msgs::msg::Trajectory; +using autoware_perception_msgs::msg::TrafficSignalArray; using autoware_planning_msgs::msg::LaneletRoute; using geometry_msgs::msg::AccelWithCovarianceStamped; using geometry_msgs::msg::Point; diff --git a/planning/planning_test_utils/package.xml b/planning/planning_test_utils/package.xml index 72662f7ee7b76..23bfb2f44cb96 100644 --- a/planning/planning_test_utils/package.xml +++ b/planning/planning_test_utils/package.xml @@ -15,9 +15,9 @@ autoware_auto_control_msgs autoware_auto_mapping_msgs - autoware_auto_perception_msgs autoware_auto_planning_msgs autoware_auto_vehicle_msgs + autoware_perception_msgs autoware_planning_msgs component_interface_specs component_interface_utils From 114f72a4c0b66f35f340612ef560dbe59e0c288c Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 7 Sep 2023 13:01:34 +0900 Subject: [PATCH 143/547] feat(operation_transition_mannager): add param enable_engage_on_driving (#4891) Signed-off-by: Takamasa Horibe --- .../README.md | 30 ++++++++++++++----- ...eration_mode_transition_manager.param.yaml | 7 ++++- .../src/state.cpp | 10 +++++++ .../src/state.hpp | 1 + 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/control/operation_mode_transition_manager/README.md b/control/operation_mode_transition_manager/README.md index 3bbc3a006c73a..73249095c6d5c 100644 --- a/control/operation_mode_transition_manager/README.md +++ b/control/operation_mode_transition_manager/README.md @@ -63,13 +63,14 @@ For the backward compatibility (to be removed): ## Parameters -| Name | Type | Description | Default value | -| :--------------------------------- | :------- | :------------------------------------------------------------------------------------------------ | :------------ | -| `transition_timeout` | `double` | If the state transition is not completed within this time, it is considered a transition failure. | 10.0 | -| `frequency_hz` | `double` | running hz | 10.0 | -| `check_engage_condition` | `double` | If false, autonomous transition is always available | 0.1 | -| `nearest_dist_deviation_threshold` | `double` | distance threshold used to find nearest trajectory point | 3.0 | -| `nearest_yaw_deviation_threshold` | `double` | angle threshold used to find nearest trajectory point | 1.57 | +| Name | Type | Description | Default value | +| :--------------------------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| `transition_timeout` | `double` | If the state transition is not completed within this time, it is considered a transition failure. | 10.0 | +| `frequency_hz` | `double` | running hz | 10.0 | +| `enable_engage_on_driving` | `bool` | Set true if you want to engage the autonomous driving mode while the vehicle is driving. If set to false, it will deny Engage in any situation where the vehicle speed is not zero. Note that if you use this feature without adjusting the parameters, it may cause issues like sudden deceleration. Before using, please ensure the engage condition and the vehicle_cmd_gate transition filter are appropriately adjusted. | 0.1 | +| `check_engage_condition` | `bool` | If false, autonomous transition is always available | 0.1 | +| `nearest_dist_deviation_threshold` | `double` | distance threshold used to find nearest trajectory point | 3.0 | +| `nearest_yaw_deviation_threshold` | `double` | angle threshold used to find nearest trajectory point | 1.57 | For `engage_acceptable_limits` related parameters: @@ -94,6 +95,21 @@ For `stable_check` related parameters: | `speed_upper_threshold` | `double` | the velocity deviation between control command and ego vehicle must be within this threshold to complete `Autonomous` transition. | 2.0 | | `speed_lower_threshold` | `double` | the velocity deviation between control command and ego vehicle must be within this threshold to complete `Autonomous` transition. | 2.0 | +## Engage check behavior on each parameter setting + +This matrix describes the scenarios in which the vehicle can be engaged based on the combinations of parameter settings: + +| `enable_engage_on_driving` | `check_engage_condition` | `allow_autonomous_in_stopped` | Scenarios where engage is permitted | +| :------------------------: | :----------------------: | :---------------------------: | :---------------------------------------------------------------- | +| x | x | x | Only when the vehicle is stationary. | +| x | x | o | Only when the vehicle is stationary. | +| x | o | x | When the vehicle is stationary and all engage conditions are met. | +| x | o | o | Only when the vehicle is stationary. | +| o | x | x | At any time (Caution: Not recommended). | +| o | x | o | At any time (Caution: Not recommended). | +| o | o | x | When all engage conditions are met, regardless of vehicle status. | +| o | o | o | When all engage conditions are met or the vehicle is stationary. | + ## Future extensions / Unimplemented parts - Need to remove backward compatibility interfaces. diff --git a/control/operation_mode_transition_manager/config/operation_mode_transition_manager.param.yaml b/control/operation_mode_transition_manager/config/operation_mode_transition_manager.param.yaml index a86443f5cabdb..67ce4b485e8c3 100644 --- a/control/operation_mode_transition_manager/config/operation_mode_transition_manager.param.yaml +++ b/control/operation_mode_transition_manager/config/operation_mode_transition_manager.param.yaml @@ -2,11 +2,16 @@ ros__parameters: transition_timeout: 10.0 frequency_hz: 10.0 + + # set true if you want to engage the autonomous driving mode while the vehicle is driving. If set to false, it will deny Engage in any situation where the vehicle speed is not zero. Note that if you use this feature without adjusting the parameters, it may cause issues like sudden deceleration. Before using, please ensure the engage condition and the vehicle_cmd_gate transition filter are appropriately adjusted. + enable_engage_on_driving: false + check_engage_condition: false # set false if you do not want to care about the engage condition. + nearest_dist_deviation_threshold: 3.0 # [m] for finding nearest index nearest_yaw_deviation_threshold: 1.57 # [rad] for finding nearest index engage_acceptable_limits: - allow_autonomous_in_stopped: true # no check if the velocity is zero, always allowed. + allow_autonomous_in_stopped: true # if true, all engage check is skipped. dist_threshold: 1.5 yaw_threshold: 0.524 speed_upper_threshold: 10.0 diff --git a/control/operation_mode_transition_manager/src/state.cpp b/control/operation_mode_transition_manager/src/state.cpp index 5062c596dce00..55b672aa48964 100644 --- a/control/operation_mode_transition_manager/src/state.cpp +++ b/control/operation_mode_transition_manager/src/state.cpp @@ -46,6 +46,7 @@ AutonomousMode::AutonomousMode(rclcpp::Node * node) "trajectory", 1, [this](const Trajectory::SharedPtr msg) { trajectory_ = *msg; }); check_engage_condition_ = node->declare_parameter("check_engage_condition"); + enable_engage_on_driving_ = node->declare_parameter("enable_engage_on_driving"); nearest_dist_deviation_threshold_ = node->declare_parameter("nearest_dist_deviation_threshold"); nearest_yaw_deviation_threshold_ = @@ -218,6 +219,15 @@ bool AutonomousMode::isModeChangeAvailable() const auto target_control_speed = control_cmd_.longitudinal.speed; const auto & param = engage_acceptable_param_; + if (!enable_engage_on_driving_ && std::fabs(current_speed) > 1.0e-2) { + RCLCPP_INFO( + logger_, + "Engage unavailable: enable_engage_on_driving is false, and the vehicle is not " + "stationary."); + debug_info_ = DebugInfo{}; // all false + return false; + } + if (trajectory_.points.size() < 2) { RCLCPP_WARN_SKIPFIRST_THROTTLE( logger_, *clock_, 5000, "Engage unavailable: trajectory size must be > 2"); diff --git a/control/operation_mode_transition_manager/src/state.hpp b/control/operation_mode_transition_manager/src/state.hpp index 9d384857bbe3d..de0dd8a543b15 100644 --- a/control/operation_mode_transition_manager/src/state.hpp +++ b/control/operation_mode_transition_manager/src/state.hpp @@ -73,6 +73,7 @@ class AutonomousMode : public ModeChangeBase rclcpp::Clock::SharedPtr clock_; bool check_engage_condition_ = true; // if false, the vehicle is engaged without any checks. + bool enable_engage_on_driving_ = false; // if false, engage is not permited on driving double nearest_dist_deviation_threshold_; // [m] for finding nearest index double nearest_yaw_deviation_threshold_; // [rad] for finding nearest index EngageAcceptableParam engage_acceptable_param_; From e73231d9939b9215f1fe4cb7bc4ab038c24c5e42 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Thu, 7 Sep 2023 15:18:20 +0900 Subject: [PATCH 144/547] fix(start_planner): fix drivable area without shoulder lanes (#4909) fix(start_planner): fix pull out lanes without shoulder lanes Signed-off-by: kosuke55 --- .../start_planner/start_planner_module.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 90078a50e6ac9..e461995cba1e7 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -553,10 +553,15 @@ std::vector StartPlannerModule::generateDrivableLanes( const PathWithLaneId & path) const { const auto path_road_lanes = getPathRoadLanes(path); - if (!path_road_lanes.empty()) { - return utils::generateDrivableLanesWithShoulderLanes( - getPathRoadLanes(path), status_.pull_out_lanes); + lanelet::ConstLanelets shoulder_lanes; + const auto & rh = planner_data_->route_handler; + std::copy_if( + status_.pull_out_lanes.begin(), status_.pull_out_lanes.end(), + std::back_inserter(shoulder_lanes), + [&rh](const auto & pull_out_lane) { return rh->isShoulderLanelet(pull_out_lane); }); + + return utils::generateDrivableLanesWithShoulderLanes(getPathRoadLanes(path), shoulder_lanes); } // if path_road_lanes is empty, use only pull_out_lanes as drivable lanes From 4a7da1dd36cb3d032687cf6f12bb4d157234146e Mon Sep 17 00:00:00 2001 From: Tao Zhong <55872497+tzhong518@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:29:23 +0900 Subject: [PATCH 145/547] fix(image_projection_based_fusion): multi class of one point (#4705) * fix: class occlusion Signed-off-by: tzhong518 * add comment for the setting of class value * style(pre-commit): autofix * add comment for the decode of class value --------- Signed-off-by: tzhong518 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../pointpainting_fusion/voxel_generator.hpp | 1 + .../src/pointpainting_fusion/node.cpp | 5 +++-- .../src/pointpainting_fusion/voxel_generator.cpp | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/perception/image_projection_based_fusion/include/image_projection_based_fusion/pointpainting_fusion/voxel_generator.hpp b/perception/image_projection_based_fusion/include/image_projection_based_fusion/pointpainting_fusion/voxel_generator.hpp index 3e223dd3101df..5e7a5087efb84 100755 --- a/perception/image_projection_based_fusion/include/image_projection_based_fusion/pointpainting_fusion/voxel_generator.hpp +++ b/perception/image_projection_based_fusion/include/image_projection_based_fusion/pointpainting_fusion/voxel_generator.hpp @@ -17,6 +17,7 @@ #include +#include #include namespace image_projection_based_fusion diff --git a/perception/image_projection_based_fusion/src/pointpainting_fusion/node.cpp b/perception/image_projection_based_fusion/src/pointpainting_fusion/node.cpp index 5253ac192c786..9f8ef7b94a123 100644 --- a/perception/image_projection_based_fusion/src/pointpainting_fusion/node.cpp +++ b/perception/image_projection_based_fusion/src/pointpainting_fusion/node.cpp @@ -133,7 +133,7 @@ PointPaintingFusionNode::PointPaintingFusionNode(const rclcpp::NodeOptions & opt auto it = find(paint_class_names.begin(), paint_class_names.end(), cls); if (it != paint_class_names.end()) { int index = it - paint_class_names.begin(); - class_index_[cls] = index + 1; + class_index_[cls] = pow(2, index); // regard each class as a bit in binary } else { isClassTable_.erase(cls); } @@ -345,7 +345,8 @@ dc | dc dc dc dc ||zc| data = &painted_pointcloud_msg.data[0]; auto p_class = reinterpret_cast(&output[stride + class_offset]); for (const auto & cls : isClassTable_) { - *p_class = cls.second(label2d) ? class_index_[cls.first] : *p_class; + // add up the class values if the point belongs to multiple classes + *p_class = cls.second(label2d) ? (class_index_[cls.first] + *p_class) : *p_class; } } #if 0 diff --git a/perception/image_projection_based_fusion/src/pointpainting_fusion/voxel_generator.cpp b/perception/image_projection_based_fusion/src/pointpainting_fusion/voxel_generator.cpp index bd49d9e446f1f..f462b9b0ba17a 100755 --- a/perception/image_projection_based_fusion/src/pointpainting_fusion/voxel_generator.cpp +++ b/perception/image_projection_based_fusion/src/pointpainting_fusion/voxel_generator.cpp @@ -59,8 +59,10 @@ std::size_t VoxelGenerator::pointsToVoxels( point[1] = point_current.y(); point[2] = point_current.z(); point[3] = time_lag; + // decode the class value back to one-hot binary and assign it to point for (std::size_t i = 1; i <= config_.class_size_; i++) { - point[3 + i] = (*class_iter == i) ? 1 : 0; + auto decode = std::bitset<8>(*class_iter).to_string(); + point[3 + i] = decode[-i] == 1 ? 1 : 0; } out_of_range = false; From 2e6bc3f3da436433e8658d8943ed8e4b2f2d9f0c Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Thu, 7 Sep 2023 17:22:02 +0900 Subject: [PATCH 146/547] chore(ekf_localizer): organize test (#4913) Signed-off-by: kminoda --- localization/ekf_localizer/CMakeLists.txt | 9 +- .../test/src/test_ekf_localizer.cpp | 305 ------------------ .../ekf_localizer/test/test_covariance.cpp | 26 +- .../test/test_ekf_localizer.test | 5 - 4 files changed, 14 insertions(+), 331 deletions(-) delete mode 100644 localization/ekf_localizer/test/src/test_ekf_localizer.cpp delete mode 100644 localization/ekf_localizer/test/test_ekf_localizer.test diff --git a/localization/ekf_localizer/CMakeLists.txt b/localization/ekf_localizer/CMakeLists.txt index 6e7e194f7cf72..d5cd85aa2ed85 100644 --- a/localization/ekf_localizer/CMakeLists.txt +++ b/localization/ekf_localizer/CMakeLists.txt @@ -51,14 +51,7 @@ if(BUILD_TESTING) ) find_package(ament_cmake_gtest REQUIRED) - set(TEST_FILES - test/test_aged_object_queue.cpp - test/test_mahalanobis.cpp - test/test_measurement.cpp - test/test_numeric.cpp - test/test_state_transition.cpp - test/test_string.cpp - test/test_warning_message.cpp) + file(GLOB_RECURSE TEST_FILES test/*.cpp) foreach(filepath ${TEST_FILES}) add_testcase(${filepath}) diff --git a/localization/ekf_localizer/test/src/test_ekf_localizer.cpp b/localization/ekf_localizer/test/src/test_ekf_localizer.cpp deleted file mode 100644 index f249dffef6034..0000000000000 --- a/localization/ekf_localizer/test/src/test_ekf_localizer.cpp +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2018-2019 Autoware Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "ekf_localizer/ekf_localizer.hpp" - -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include - -using std::placeholders::_1; - -class EKFLocalizerTestSuite : public ::testing::Test -{ -protected: - void SetUp() { rclcpp::init(0, nullptr); } - void TearDown() { (void)rclcpp::shutdown(); } -}; // sanity_check - -class TestEKFLocalizerNode : public EKFLocalizer -{ -public: - TestEKFLocalizerNode(const std::string & node_name, const rclcpp::NodeOptions & node_options) - : EKFLocalizer(node_name, node_options) - { - sub_twist = this->create_subscription( - "/ekf_twist", 1, std::bind(&TestEKFLocalizerNode::testCallbackTwist, this, _1)); - sub_pose = this->create_subscription( - "/ekf_pose", 1, std::bind(&TestEKFLocalizerNode::testCallbackPose, this, _1)); - - using std::chrono_literals::operator""ms; - test_timer_ = rclcpp::create_timer( - this, get_clock(), 100ms, std::bind(&TestEKFLocalizerNode::testTimerCallback, this)); - } - ~TestEKFLocalizerNode() {} - - std::string frame_id_a_ = "world"; - std::string frame_id_b_ = "base_link"; - - rclcpp::Subscription::SharedPtr sub_twist; - rclcpp::Subscription::SharedPtr sub_pose; - - rclcpp::TimerBase::SharedPtr test_timer_; - - geometry_msgs::msg::PoseStamped::SharedPtr test_current_pose_ptr_; - geometry_msgs::msg::TwistStamped::SharedPtr test_current_twist_ptr_; - - void testTimerCallback() - { - /* !!! this should be defined before sendTransform() !!! */ - static std::shared_ptr br = - std::make_shared(shared_from_this()); - geometry_msgs::msg::TransformStamped sent; - - rclcpp::Time current_time = this->now(); - - sent.header.stamp = current_time; - sent.header.frame_id = frame_id_a_; - sent.child_frame_id = frame_id_b_; - sent.transform.translation.x = -7.11; - sent.transform.translation.y = 0.0; - sent.transform.translation.z = 0.0; - tf2::Quaternion q; - q.setRPY(0, 0, 0.5); - sent.transform.rotation.x = q.x(); - sent.transform.rotation.y = q.y(); - sent.transform.rotation.z = q.z(); - sent.transform.rotation.w = q.w(); - - br->sendTransform(sent); - } - - void testCallbackPose(geometry_msgs::msg::PoseStamped::SharedPtr pose) - { - test_current_pose_ptr_ = std::make_shared(*pose); - } - - void testCallbackTwist(geometry_msgs::msg::TwistStamped::SharedPtr twist) - { - test_current_twist_ptr_ = std::make_shared(*twist); - } - - void resetCurrentPoseAndTwist() - { - test_current_pose_ptr_ = nullptr; - test_current_twist_ptr_ = nullptr; - } -}; - -TEST_F(EKFLocalizerTestSuite, measurementUpdatePose) -{ - rclcpp::NodeOptions node_options; - auto ekf = std::make_shared("EKFLocalizerTestSuite", node_options); - - auto pub_pose = ekf->create_publisher("/in_pose", 1); - - geometry_msgs::msg::PoseStamped in_pose; - in_pose.header.frame_id = "world"; - in_pose.pose.position.x = 1.0; - in_pose.pose.position.y = 2.0; - in_pose.pose.position.z = 3.0; - in_pose.pose.orientation.x = 0.0; - in_pose.pose.orientation.y = 0.0; - in_pose.pose.orientation.z = 0.0; - in_pose.pose.orientation.w = 1.0; - - /* test for valid value */ - const double pos_x = 12.3; - in_pose.pose.position.x = pos_x; // for valid value - - for (int i = 0; i < 20; ++i) { - in_pose.header.stamp = ekf->now(); - pub_pose->publish(in_pose); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - - ASSERT_NE(ekf->test_current_pose_ptr_, nullptr); - ASSERT_NE(ekf->test_current_twist_ptr_, nullptr); - - double ekf_x = ekf->test_current_pose_ptr_->pose.position.x; - bool is_succeeded = !(std::isnan(ekf_x) || std::isinf(ekf_x)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; - - ASSERT_TRUE(std::fabs(ekf_x - pos_x) < 0.1) - << "ekf pos x: " << ekf_x << " should be close to " << pos_x; - - /* test for invalid value */ - in_pose.pose.position.x = NAN; // check for invalid values - for (int i = 0; i < 10; ++i) { - in_pose.header.stamp = ekf->now(); - pub_pose->publish(in_pose); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - is_succeeded = !(std::isnan(ekf_x) || std::isinf(ekf_x)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; - - ekf->resetCurrentPoseAndTwist(); -} - -TEST_F(EKFLocalizerTestSuite, measurementUpdateTwist) -{ - rclcpp::NodeOptions node_options; - auto ekf = std::make_shared("EKFLocalizerTestSuite", node_options); - - auto pub_twist = ekf->create_publisher("/in_twist", 1); - geometry_msgs::msg::TwistStamped in_twist; - in_twist.header.frame_id = "base_link"; - - /* test for valid value */ - const double vx = 12.3; - in_twist.twist.linear.x = vx; // for valid value - for (int i = 0; i < 20; ++i) { - in_twist.header.stamp = ekf->now(); - pub_twist->publish(in_twist); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - - ASSERT_FALSE(ekf->test_current_pose_ptr_ == nullptr); - ASSERT_FALSE(ekf->test_current_twist_ptr_ == nullptr); - - double ekf_vx = ekf->test_current_twist_ptr_->twist.linear.x; - bool is_succeeded = !(std::isnan(ekf_vx) || std::isinf(ekf_vx)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; - ASSERT_TRUE(std::fabs(ekf_vx - vx) < 0.1) - << "ekf vel x: " << ekf_vx << ", should be close to " << vx; - - /* test for invalid value */ - in_twist.twist.linear.x = NAN; // check for invalid values - for (int i = 0; i < 10; ++i) { - in_twist.header.stamp = ekf->now(); - pub_twist->publish(in_twist); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - - ekf_vx = ekf->test_current_twist_ptr_->twist.linear.x; - is_succeeded = !(std::isnan(ekf_vx) || std::isinf(ekf_vx)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; - - ekf->resetCurrentPoseAndTwist(); -} - -TEST_F(EKFLocalizerTestSuite, measurementUpdatePoseWithCovariance) -{ - rclcpp::NodeOptions node_options; - node_options.append_parameter_override("use_pose_with_covariance", true); - rclcpp::sleep_for(std::chrono::milliseconds(200)); - auto ekf = std::make_shared("EKFLocalizerTestSuite", node_options); - - auto pub_pose = ekf->create_publisher( - "/in_pose_with_covariance", 1); - geometry_msgs::msg::PoseWithCovarianceStamped in_pose; - in_pose.header.frame_id = "world"; - in_pose.pose.pose.position.x = 1.0; - in_pose.pose.pose.position.y = 2.0; - in_pose.pose.pose.position.z = 3.0; - in_pose.pose.pose.orientation.x = 0.0; - in_pose.pose.pose.orientation.y = 0.0; - in_pose.pose.pose.orientation.z = 0.0; - in_pose.pose.pose.orientation.w = 1.0; - for (int i = 0; i < 36; ++i) { - in_pose.pose.covariance[i] = 0.1; - } - - /* test for valid value */ - const double pos_x = 99.3; - in_pose.pose.pose.position.x = pos_x; // for valid value - - for (int i = 0; i < 20; ++i) { - in_pose.header.stamp = ekf->now(); - pub_pose->publish(in_pose); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - - ASSERT_FALSE(ekf->test_current_pose_ptr_ == nullptr); - ASSERT_FALSE(ekf->test_current_twist_ptr_ == nullptr); - - double ekf_x = ekf->test_current_pose_ptr_->pose.position.x; - bool is_succeeded = !(std::isnan(ekf_x) || std::isinf(ekf_x)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; - ASSERT_TRUE(std::fabs(ekf_x - pos_x) < 0.1) - << "ekf pos x: " << ekf_x << " should be close to " << pos_x; - - /* test for invalid value */ - in_pose.pose.pose.position.x = NAN; // check for invalid values - for (int i = 0; i < 10; ++i) { - in_pose.header.stamp = ekf->now(); - pub_pose->publish(in_pose); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - is_succeeded = !(std::isnan(ekf_x) || std::isinf(ekf_x)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; - - ekf->resetCurrentPoseAndTwist(); -} - -TEST_F(EKFLocalizerTestSuite, measurementUpdateTwistWithCovariance) -{ - rclcpp::NodeOptions node_options; - auto ekf = std::make_shared("EKFLocalizerTestSuite", node_options); - - auto pub_twist = ekf->create_publisher( - "/in_twist_with_covariance", 1); - geometry_msgs::msg::TwistWithCovarianceStamped in_twist; - in_twist.header.frame_id = "base_link"; - - /* test for valid value */ - const double vx = 12.3; - in_twist.twist.twist.linear.x = vx; // for valid value - for (int i = 0; i < 36; ++i) { - in_twist.twist.covariance[i] = 0.1; - } - for (int i = 0; i < 10; ++i) { - in_twist.header.stamp = ekf->now(); - pub_twist->publish(in_twist); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - - ASSERT_FALSE(ekf->test_current_pose_ptr_ == nullptr); - ASSERT_FALSE(ekf->test_current_twist_ptr_ == nullptr); - - double ekf_vx = ekf->test_current_twist_ptr_->twist.linear.x; - bool is_succeeded = !(std::isnan(ekf_vx) || std::isinf(ekf_vx)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; - ASSERT_TRUE((ekf_vx - vx) < 0.1) << "vel x should be close to " << vx; - - /* test for invalid value */ - in_twist.twist.twist.linear.x = NAN; // check for invalid values - for (int i = 0; i < 10; ++i) { - in_twist.header.stamp = ekf->now(); - pub_twist->publish(in_twist); - rclcpp::spin_some(ekf); - rclcpp::sleep_for(std::chrono::milliseconds(100)); - } - - ekf_vx = ekf->test_current_twist_ptr_->twist.linear.x; - is_succeeded = !(std::isnan(ekf_vx) || std::isinf(ekf_vx)); - ASSERT_EQ(true, is_succeeded) << "ekf result includes invalid value."; -} diff --git a/localization/ekf_localizer/test/test_covariance.cpp b/localization/ekf_localizer/test/test_covariance.cpp index fdc732ea6d34c..23458fb18a838 100644 --- a/localization/ekf_localizer/test/test_covariance.cpp +++ b/localization/ekf_localizer/test/test_covariance.cpp @@ -31,15 +31,15 @@ TEST(EKFCovarianceToPoseMessageCovariance, SmokeTest) P(2, 2) = 9.; std::array covariance = ekfCovarianceToPoseMessageCovariance(P); - EXPECT_EQ(covariance(0), 1.); - EXPECT_EQ(covariance(1), 2.); - EXPECT_EQ(covariance(5), 3.); - EXPECT_EQ(covariance(6), 4.); - EXPECT_EQ(covariance(7), 5.); - EXPECT_EQ(covariance(11), 6.); - EXPECT_EQ(covariance(30), 7.); - EXPECT_EQ(covariance(31), 8.); - EXPECT_EQ(covariance(35), 9.); + EXPECT_EQ(covariance[0], 1.); + EXPECT_EQ(covariance[1], 2.); + EXPECT_EQ(covariance[5], 3.); + EXPECT_EQ(covariance[6], 4.); + EXPECT_EQ(covariance[7], 5.); + EXPECT_EQ(covariance[11], 6.); + EXPECT_EQ(covariance[30], 7.); + EXPECT_EQ(covariance[31], 8.); + EXPECT_EQ(covariance[35], 9.); } // ensure other elements are zero @@ -62,10 +62,10 @@ TEST(EKFCovarianceToTwistMessageCovariance, SmokeTest) P(5, 5) = 4.; std::array covariance = ekfCovarianceToTwistMessageCovariance(P); - EXPECT_EQ(covariance(0), 1.); - EXPECT_EQ(covariance(5), 2.); - EXPECT_EQ(covariance(30), 3.); - EXPECT_EQ(covariance(35), 4.); + EXPECT_EQ(covariance[0], 1.); + EXPECT_EQ(covariance[5], 2.); + EXPECT_EQ(covariance[30], 3.); + EXPECT_EQ(covariance[35], 4.); } // ensure other elements are zero diff --git a/localization/ekf_localizer/test/test_ekf_localizer.test b/localization/ekf_localizer/test/test_ekf_localizer.test deleted file mode 100644 index 4eecfa6e094b7..0000000000000 --- a/localization/ekf_localizer/test/test_ekf_localizer.test +++ /dev/null @@ -1,5 +0,0 @@ - - - - - From 7524463142695fbf19845bf87bec37c608056f98 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 7 Sep 2023 20:09:49 +0900 Subject: [PATCH 147/547] docs(map_based_prediction): minor refactor for explanation of parameter (#4898) * fix description Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- perception/map_based_prediction/README.md | 32 ++++++++++----------- perception/map_based_prediction/package.xml | 1 + 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/perception/map_based_prediction/README.md b/perception/map_based_prediction/README.md index d487bb163c5e8..7631b47772862 100644 --- a/perception/map_based_prediction/README.md +++ b/perception/map_based_prediction/README.md @@ -151,23 +151,21 @@ If the target object is inside the road or crosswalk, this module outputs one or ## Parameters -| Parameter | Type | Description | -| ------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------ | -| `enable_delay_compensation` | bool | flag to enable the time delay compensation for the position of the object | -| `prediction_time_horizon` | double | predict time duration for predicted path [s] | -| `prediction_sampling_delta_time` | double | sampling time for points in predicted path [s] | -| `min_velocity_for_map_based_prediction` | double | apply map-based prediction to the objects with higher velocity than this value | -| `min_crosswalk_user_velocity` | double | minimum velocity use in path prediction for crosswalk users | -| `dist_threshold_for_searching_lanelet` | double | The threshold of the angle used when searching for the lane to which the object belongs [rad] | -| `delta_yaw_threshold_for_searching_lanelet` | double | The threshold of the distance used when searching for the lane to which the object belongs [m] | -| `sigma_lateral_offset` | double | Standard deviation for lateral position of objects [m] | -| `sigma_yaw_angle` | double | Standard deviation yaw angle of objects [rad] | -| `object_buffer_time_length` | double | Time span of object history to store the information [s] | -| `history_time_length` | double | Time span of object information used for prediction [s] | -| `dist_ratio_threshold_to_left_bound` | double | Conditions for using lane change detection of objects. Distance to the left bound of lanelet. | -| `dist_ratio_threshold_to_right_bound` | double | Conditions for using lane change detection of objects. Distance to the right bound of lanelet. | -| `diff_dist_threshold_to_left_bound` | double | Conditions for using lane change detection of objects. Differential value of horizontal position of objects. | -| `diff_dist_threshold_to_right_bound` | double | Conditions for using lane change detection of objects. Differential value of horizontal position of objects. | +| Parameter | Unit | Type | Description | +| ---------------------------------------------------------------- | ----- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| `enable_delay_compensation` | [-] | bool | flag to enable the time delay compensation for the position of the object | +| `prediction_time_horizon` | [s] | double | predict time duration for predicted path | +| `prediction_sampling_delta_time` | [s] | double | sampling time for points in predicted path | +| `min_velocity_for_map_based_prediction` | [m/s] | double | apply map-based prediction to the objects with higher velocity than this value | +| `min_crosswalk_user_velocity` | [m/s] | double | minimum velocity used when crosswalk user's velocity is calculated | +| `max_crosswalk_user_delta_yaw_threshold_for_lanelet` | [rad] | double | maximum yaw difference between crosswalk user and lanelet to use in path prediction for crosswalk users | +| `dist_threshold_for_searching_lanelet` | [m] | double | The threshold of the angle used when searching for the lane to which the object belongs | +| `delta_yaw_threshold_for_searching_lanelet` | [rad] | double | The threshold of the angle used when searching for the lane to which the object belongs | +| `sigma_lateral_offset` | [m] | double | Standard deviation for lateral position of objects | +| `sigma_yaw_angle_deg` | [deg] | double | Standard deviation yaw angle of objects | +| `object_buffer_time_length` | [s] | double | Time span of object history to store the information | +| `history_time_length` | [s] | double | Time span of object information used for prediction | +| `prediction_time_horizon_rate_for_validate_shoulder_lane_length` | [-] | double | prediction path will disabled when the estimated path length exceeds lanelet length. This parameter control the estimated path length | ## Assumptions / Known limits diff --git a/perception/map_based_prediction/package.xml b/perception/map_based_prediction/package.xml index 9b7b32e686cfb..d7ff85adc9193 100644 --- a/perception/map_based_prediction/package.xml +++ b/perception/map_based_prediction/package.xml @@ -10,6 +10,7 @@ Yoshi Ri Takayuki Murooka Kyoichi Sugahara + Kotaro Uetake Apache License 2.0 ament_cmake From 6bf9650c917aeec354eaa6a99eba50c26defed14 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 7 Sep 2023 21:23:31 +0900 Subject: [PATCH 148/547] refactor(trajectory_visualizer): remove duplicated implementation (#4858) --- .../scripts/trajectory_visualizer.py | 269 +++++------------- 1 file changed, 67 insertions(+), 202 deletions(-) diff --git a/planning/planning_debug_tools/scripts/trajectory_visualizer.py b/planning/planning_debug_tools/scripts/trajectory_visualizer.py index f942d9a00c098..73e41b052cd90 100755 --- a/planning/planning_debug_tools/scripts/trajectory_visualizer.py +++ b/planning/planning_debug_tools/scripts/trajectory_visualizer.py @@ -17,8 +17,11 @@ import argparse from autoware_auto_planning_msgs.msg import Path +from autoware_auto_planning_msgs.msg import PathPoint +from autoware_auto_planning_msgs.msg import PathPointWithLaneId from autoware_auto_planning_msgs.msg import PathWithLaneId from autoware_auto_planning_msgs.msg import Trajectory +from autoware_auto_planning_msgs.msg import TrajectoryPoint from autoware_auto_vehicle_msgs.msg import VelocityReport from geometry_msgs.msg import Pose from matplotlib import animation @@ -201,7 +204,7 @@ def CallbackVelocityLimit(self, cmd): self.velocity_limit = cmd.max_velocity def CallbackMotionVelOptTraj(self, cmd1, cmd2, cmd3, cmd4, cmd5): - print("CallbackMotionVelOptTraj called") + self.get_logger().info("CallbackMotionVelOptTraj called") self.CallBackTrajExVelLim(cmd1) self.CallBackTrajLatAccFiltered(cmd2) self.CallBackTrajRaw(cmd3) @@ -233,7 +236,7 @@ def CallBackTrajFinal(self, cmd): self.update_traj_final = True def CallBackLaneDrivingTraj(self, cmd5, cmd6, cmd7, cmd8, cmd9): - print("CallBackLaneDrivingTraj called") + self.get_logger().info("CallBackLaneDrivingTraj called") self.CallBackTrajFinal(cmd5) self.CallBackLBehaviorPathPlannerPath(cmd6) self.CallBackBehaviorVelocityPlannerPath(cmd7) @@ -306,9 +309,8 @@ def setPlotTrajectoryVelocity(self): ) def plotTrajectoryVelocity(self, data): - # self.updatePose(PATH_ORIGIN_FRAME, SELF_POSE_FRAME) if self.self_pose_received is False: - print("plot start but self pose is not received") + self.get_logger().info("plot start but self pose is not received") return ( self.im1, self.im2, @@ -324,82 +326,9 @@ def plotTrajectoryVelocity(self, data): self.im11, self.im12, ) - print("plot start") - - # copy - behavior_path_planner_path = self.behavior_path_planner_path - behavior_velocity_planner_path = self.behavior_velocity_planner_path - obstacle_avoid_traj = self.obstacle_avoid_traj - obstacle_stop_traj = self.obstacle_stop_traj - trajectory_raw = self.trajectory_raw - trajectory_external_velocity_limited = self.trajectory_external_velocity_limited - trajectory_lateral_acc_filtered = self.trajectory_lateral_acc_filtered - trajectory_steer_rate_filtered = self.trajectory_steer_rate_filtered - trajectory_time_resampled = self.trajectory_time_resampled - trajectory_final = self.trajectory_final - - if self.update_behavior_path_planner_path: - x = self.CalcArcLengthPathWLid(behavior_path_planner_path) - y = self.ToVelListPathWLid(behavior_path_planner_path) - self.im1.set_data(x, y) - self.update_behavior_path_planner_path = False - if len(y) != 0: - self.max_vel = max(10.0, np.max(y)) - - if self.update_behavior_velocity_planner_path: - x = self.CalcArcLengthPath(behavior_velocity_planner_path) - y = self.ToVelListPath(behavior_velocity_planner_path) - self.im2.set_data(x, y) - self.update_behavior_velocity_planner_path = False - - if self.update_traj_ob_avoid: - x = self.CalcArcLength(obstacle_avoid_traj) - y = self.ToVelList(obstacle_avoid_traj) - self.im3.set_data(x, y) - self.update_traj_ob_avoid = False - - if self.update_traj_ob_stop: - x = self.CalcArcLength(obstacle_stop_traj) - y = self.ToVelList(obstacle_stop_traj) - self.im4.set_data(x, y) - self.update_traj_ob_stop = False - - if self.update_traj_raw: - x = self.CalcArcLength(trajectory_raw) - y = self.ToVelList(trajectory_raw) - self.im5.set_data(x, y) - self.update_traj_raw = False - - if self.update_ex_vel_lim: - x = self.CalcArcLength(trajectory_external_velocity_limited) - y = self.ToVelList(trajectory_external_velocity_limited) - self.im6.set_data(x, y) - self.update_ex_vel_lim = False - - if self.update_lat_acc_fil: - x = self.CalcArcLength(trajectory_lateral_acc_filtered) - y = self.ToVelList(trajectory_lateral_acc_filtered) - self.im7.set_data(x, y) - self.update_lat_acc_fil = False - - if self.update_steer_rate_fil: - x = self.CalcArcLength(trajectory_steer_rate_filtered) - y = self.ToVelList(trajectory_steer_rate_filtered) - self.im71.set_data(x, y) - self.update_steer_rate_fil = False - - if self.update_traj_resample: - x = self.CalcArcLength(trajectory_time_resampled) - y = self.ToVelList(trajectory_time_resampled) - self.im8.set_data(x, y) - self.update_traj_resample = False + self.get_logger().info("plot start") if self.update_traj_final: - x = self.CalcArcLength(trajectory_final) - y = self.ToVelList(trajectory_final) - self.im9.set_data(x, y) - self.update_traj_final = False - self.im10.set_data(0, self.localization_vx) self.im11.set_data(0, self.vehicle_vx) @@ -408,8 +337,33 @@ def plotTrajectoryVelocity(self, data): y = [self.velocity_limit, self.velocity_limit] self.im12.set_data(x, y) - if len(y) != 0: - self.min_vel = np.min(y) + if len(y) != 0: + self.min_vel = np.min(y) + + trajectory_data = [ + (self.behavior_path_planner_path, self.update_behavior_path_planner_path, self.im1), + ( + self.behavior_velocity_planner_path, + self.update_behavior_velocity_planner_path, + self.im2, + ), + (self.obstacle_avoid_traj, self.update_traj_ob_avoid, self.im3), + (self.obstacle_stop_traj, self.update_traj_ob_stop, self.im4), + (self.trajectory_raw, self.update_traj_raw, self.im5), + (self.trajectory_external_velocity_limited, self.update_ex_vel_lim, self.im6), + (self.trajectory_lateral_acc_filtered, self.update_lat_acc_fil, self.im7), + (self.trajectory_steer_rate_filtered, self.update_steer_rate_fil, self.im71), + (self.trajectory_time_resampled, self.update_traj_resample, self.im8), + (self.trajectory_final, self.update_traj_final, self.im9), + ] + + # update all trajectory plots + for traj, update_flag, image in trajectory_data: + if update_flag: + x = self.CalcArcLength(traj) + y = self.ToVelList(traj) + image.set_data(x, y) + update_flag = False return ( self.im1, @@ -427,107 +381,43 @@ def plotTrajectoryVelocity(self, data): self.im12, ) - def CalcArcLength(self, traj): - if len(traj.points) == 0: - return - - s_arr = [] - ds = 0.0 - s_sum = 0.0 - - closest_id = self.calcClosestTrajectory(traj) - for i in range(1, closest_id): - p0 = traj.points[i - 1] - p1 = traj.points[i] - dx = p1.pose.position.x - p0.pose.position.x - dy = p1.pose.position.y - p0.pose.position.y - ds = np.sqrt(dx**2 + dy**2) - s_sum -= ds - - s_arr.append(s_sum) - for i in range(1, len(traj.points)): - p0 = traj.points[i - 1] - p1 = traj.points[i] - dx = p1.pose.position.x - p0.pose.position.x - dy = p1.pose.position.y - p0.pose.position.y - ds = np.sqrt(dx**2 + dy**2) - s_sum += ds - s_arr.append(s_sum) - return s_arr - - def CalcArcLengthPathWLid(self, traj): - if len(traj.points) == 0: - return + def getPoint(self, path_point): + if isinstance(path_point, (TrajectoryPoint, PathPoint)): + return path_point + elif isinstance(path_point, PathPointWithLaneId): + return path_point.point + else: + raise TypeError("invalid path_point argument type") - s_arr = [] - ds = 0.0 - s_sum = 0.0 + def CalcDistance(self, point0, point1): + p0 = self.getPoint(point0).pose.position + p1 = self.getPoint(point1).pose.position + dx = p1.x - p0.x + dy = p1.y - p0.y + return np.sqrt(dx**2 + dy**2) - closest_id = self.calcClosestPathWLid(traj) - for i in range(1, closest_id): - p0 = traj.points[i - 1].point - p1 = traj.points[i].point - dx = p1.pose.position.x - p0.pose.position.x - dy = p1.pose.position.y - p0.pose.position.y - ds = np.sqrt(dx**2 + dy**2) - s_sum -= ds - - s_arr.append(s_sum) - for i in range(1, len(traj.points)): - p0 = traj.points[i - 1].point - p1 = traj.points[i].point - dx = p1.pose.position.x - p0.pose.position.x - dy = p1.pose.position.y - p0.pose.position.y - ds = np.sqrt(dx**2 + dy**2) - s_sum += ds - s_arr.append(s_sum) - return s_arr - - def CalcArcLengthPath(self, traj): + def CalcArcLength(self, traj): if len(traj.points) == 0: return s_arr = [] - ds = 0.0 s_sum = 0.0 - closest_id = self.calcClosestPath(traj) + closest_id = self.calcClosestIndex(traj) for i in range(1, closest_id): - p0 = traj.points[i - 1] - p1 = traj.points[i] - dx = p1.pose.position.x - p0.pose.position.x - dy = p1.pose.position.y - p0.pose.position.y - ds = np.sqrt(dx**2 + dy**2) - s_sum -= ds - + s_sum -= self.CalcDistance( + self.getPoint(traj.points[i - 1]), self.getPoint(traj.points[i]) + ) s_arr.append(s_sum) for i in range(1, len(traj.points)): - p0 = traj.points[i - 1] - p1 = traj.points[i] - dx = p1.pose.position.x - p0.pose.position.x - dy = p1.pose.position.y - p0.pose.position.y - ds = np.sqrt(dx**2 + dy**2) - s_sum += ds + s_sum += self.CalcDistance( + self.getPoint(traj.points[i - 1]), self.getPoint(traj.points[i]) + ) s_arr.append(s_sum) return s_arr def ToVelList(self, traj): - v_list = [] - for p in traj.points: - v_list.append(p.longitudinal_velocity_mps) - return v_list - - def ToVelListPathWLid(self, traj): - v_list = [] - for p in traj.points: - v_list.append(p.point.longitudinal_velocity_mps) - return v_list - - def ToVelListPath(self, traj): - v_list = [] - for p in traj.points: - v_list.append(p.longitudinal_velocity_mps) - return v_list + return [self.getPoint(p).longitudinal_velocity_mps for p in traj.points] def CalcAcceleration(self, traj): a_arr = [] @@ -606,8 +496,7 @@ def setPlotTrajectory(self): return self.im0, self.im1, self.im2, self.im3, self.im4 def plotTrajectory(self, data): - print("plot called") - # self.updatePose(PATH_ORIGIN_FRAME, SELF_POSE_FRAME) + self.get_logger().info("plot called") # copy trajectory_raw = self.trajectory_raw @@ -663,40 +552,16 @@ def plotTrajectory(self, data): return self.im0, self.im1, self.im2, self.im3, self.im4 - def calcClosestPath(self, path): - closest = -1 - min_dist_squared = 1.0e10 - for i in range(0, len(path.points)): - dist_sq = self.calcSquaredDist2d(self.self_pose, path.points[i].pose) - if dist_sq < min_dist_squared: - min_dist_squared = dist_sq - closest = i - return closest - - def calcClosestPathWLid(self, path): - closest = -1 - min_dist_squared = 1.0e10 - for i in range(0, len(path.points)): - dist_sq = self.calcSquaredDist2d(self.self_pose, path.points[i].point.pose) - if dist_sq < min_dist_squared: - min_dist_squared = dist_sq - closest = i - return closest - - def calcClosestTrajectory(self, path): - closest = -1 - min_dist_squared = 1.0e10 - for i in range(0, len(path.points)): - dist_sq = self.calcSquaredDist2d(self.self_pose, path.points[i].pose) - if dist_sq < min_dist_squared: - min_dist_squared = dist_sq - closest = i - return closest - - def calcSquaredDist2d(self, p1, p2): - dx = p1.position.x - p2.position.x - dy = p1.position.y - p2.position.y - return dx * dx + dy * dy + def calcClosestIndex(self, path): + points = np.array( + [ + [self.getPoint(p).pose.position.x, self.getPoint(p).pose.position.y] + for p in path.points + ] + ) + self_pose = np.array([self.self_pose.position.x, self.self_pose.position.y]) + dists_squared = np.sum((points - self_pose) ** 2, axis=1) + return np.argmin(dists_squared) def closeFigure(self): plt.close(self.fig) From c377d10c9978729faa048dd6a0587e52371a2bd7 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 7 Sep 2023 22:38:25 +0900 Subject: [PATCH 149/547] feat(lane_departure_checker): add border types to check (#4861) * update lane_departure_checker package Signed-off-by: kyoichi-sugahara * style(pre-commit): autofix * update doc Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- control/lane_departure_checker/README.md | 33 ++++++++++++----- .../config/lane_departure_checker.param.yaml | 8 ++++- .../lane_departure_checker.hpp | 8 +++-- .../lane_departure_checker_node.hpp | 7 +++- .../lane_departure_checker.cpp | 35 +++++++++++-------- .../lane_departure_checker_node.cpp | 27 ++++++++++---- 6 files changed, 83 insertions(+), 35 deletions(-) diff --git a/control/lane_departure_checker/README.md b/control/lane_departure_checker/README.md index 5148d6a998f50..67b770aefb3d2 100644 --- a/control/lane_departure_checker/README.md +++ b/control/lane_departure_checker/README.md @@ -63,15 +63,30 @@ This package includes the following features: ### Node Parameters -| Name | Type | Description | Default value | -| :---------------------------- | :----- | :------------------------------------------------------------ | :------------ | -| update_rate | double | Frequency for publishing [Hz] | 10.0 | -| visualize_lanelet | bool | Flag for visualizing lanelet | False | -| include_right_lanes | bool | Flag for including right lanelet in borders | False | -| include_left_lanes | bool | Flag for including left lanelet in borders | False | -| include_opposite_lanes | bool | Flag for including opposite lanelet in borders | False | -| include_conflicting_lanes | bool | Flag for including conflicting lanelet in borders | False | -| road_border_departure_checker | bool | Flag for checking if the vehicle will departs the road border | False | +#### General Parameters + +| Name | Type | Description | Default value | +| :------------------------- | :----- | :---------------------------------------------------------------------------------------------------------- | :------------ | +| will_out_of_lane_checker | bool | Enable checker whether ego vehicle footprint will depart from lane | True | +| out_of_lane_checker | bool | Enable checker whether ego vehicle footprint is out of lane | True | +| boundary_departure_checker | bool | Enable checker whether ego vehicle footprint wil depart from boundary specified by boundary_types_to_detect | False | +| update_rate | double | Frequency for publishing [Hz] | 10.0 | +| visualize_lanelet | bool | Flag for visualizing lanelet | False | + +#### Parameters For Lane Departure + +| Name | Type | Description | Default value | +| :------------------------ | :--- | :------------------------------------------------ | :------------ | +| include_right_lanes | bool | Flag for including right lanelet in borders | False | +| include_left_lanes | bool | Flag for including left lanelet in borders | False | +| include_opposite_lanes | bool | Flag for including opposite lanelet in borders | False | +| include_conflicting_lanes | bool | Flag for including conflicting lanelet in borders | False | + +#### Parameters For Road Border Departure + +| Name | Type | Description | Default value | +| :----------------------- | :------------------------- | :---------------------------------------------------------- | :------------ | +| boundary_types_to_detect | std::vector\ | line_string types to detect with boundary_departure_checker | [road_border] | ### Core Parameters diff --git a/control/lane_departure_checker/config/lane_departure_checker.param.yaml b/control/lane_departure_checker/config/lane_departure_checker.param.yaml index 008832b1cab21..f0a8df21c425b 100644 --- a/control/lane_departure_checker/config/lane_departure_checker.param.yaml +++ b/control/lane_departure_checker/config/lane_departure_checker.param.yaml @@ -1,5 +1,10 @@ /**: ros__parameters: + # Enable feature + will_out_of_lane_checker: true + out_of_lane_checker: true + boundary_departure_checker: false + # Node update_rate: 10.0 visualize_lanelet: false @@ -7,7 +12,8 @@ include_left_lanes: false include_opposite_lanes: false include_conflicting_lanes: false - road_border_departure_checker: false + boundary_types_to_detect: [road_border] + # Core footprint_margin_scale: 1.0 diff --git a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp index 573a38593fc59..c77503106e485 100644 --- a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp +++ b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker.hpp @@ -72,6 +72,7 @@ struct Input lanelet::ConstLanelets shoulder_lanelets{}; Trajectory::ConstSharedPtr reference_trajectory{}; Trajectory::ConstSharedPtr predicted_trajectory{}; + std::vector boundary_types_to_detect{}; }; struct Output @@ -79,7 +80,7 @@ struct Output std::map processing_time_map{}; bool will_leave_lane{}; bool is_out_of_lane{}; - bool will_cross_road_border{}; + bool will_cross_boundary{}; PoseDeviation trajectory_deviation{}; lanelet::ConstLanelets candidate_lanelets{}; TrajectoryPoints resampled_trajectory{}; @@ -136,9 +137,10 @@ class LaneDepartureChecker const lanelet::ConstLanelets & candidate_lanelets, const std::vector & vehicle_footprints); - static bool willCrossRoadBorder( + static bool willCrossBoundary( const lanelet::ConstLanelets & candidate_lanelets, - const std::vector & vehicle_footprints); + const std::vector & vehicle_footprints, + const std::vector & boundary_types_to_detects); static bool isCrossingRoadBorder( const lanelet::BasicLineString2d & road_border, const std::vector & footprints); diff --git a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp index f7f7bcfda7d51..c146957bbd314 100644 --- a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp +++ b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp @@ -37,6 +37,7 @@ #include #include +#include #include namespace lane_departure_checker @@ -45,13 +46,17 @@ using autoware_auto_mapping_msgs::msg::HADMapBin; struct NodeParam { + bool will_out_of_lane_checker; + bool out_of_lane_checker; + bool boundary_departure_checker; + double update_rate; bool visualize_lanelet; bool include_right_lanes; bool include_left_lanes; bool include_opposite_lanes; bool include_conflicting_lanes; - bool road_border_departure_checker; + std::vector boundary_types_to_detect; }; class LaneDepartureCheckerNode : public rclcpp::Node diff --git a/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker.cpp b/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker.cpp index f1d8d75452df1..9d413b2bf0a70 100644 --- a/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker.cpp +++ b/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker.cpp @@ -67,19 +67,19 @@ bool isInAnyLane(const lanelet::ConstLanelets & candidate_lanelets, const Point2 return false; } -bool isCrossingWithRoadBorder( - const lanelet::BasicLineString2d & road_border, const std::vector & footprints) +bool isCrossingWithBoundary( + const lanelet::BasicLineString2d & boundary, const std::vector & footprints) { for (auto & footprint : footprints) { for (size_t i = 0; i < footprint.size() - 1; ++i) { auto footprint1 = footprint.at(i).to_3d(); auto footprint2 = footprint.at(i + 1).to_3d(); - for (size_t i = 0; i < road_border.size() - 1; ++i) { - auto road_border1 = road_border.at(i); - auto road_border2 = road_border.at(i + 1); + for (size_t i = 0; i < boundary.size() - 1; ++i) { + auto boundary1 = boundary.at(i); + auto boundary2 = boundary.at(i + 1); if (tier4_autoware_utils::intersect( tier4_autoware_utils::toMsg(footprint1), tier4_autoware_utils::toMsg(footprint2), - fromVector2dToMsg(road_border1), fromVector2dToMsg(road_border2))) { + fromVector2dToMsg(boundary1), fromVector2dToMsg(boundary2))) { return true; } } @@ -172,9 +172,9 @@ Output LaneDepartureChecker::update(const Input & input) output.is_out_of_lane = isOutOfLane(output.candidate_lanelets, output.vehicle_footprints.front()); output.processing_time_map["isOutOfLane"] = stop_watch.toc(true); - output.will_cross_road_border = - willCrossRoadBorder(output.candidate_lanelets, output.vehicle_footprints); - output.processing_time_map["willCrossRoadBorder"] = stop_watch.toc(true); + output.will_cross_boundary = willCrossBoundary( + output.candidate_lanelets, output.vehicle_footprints, input.boundary_types_to_detect); + output.processing_time_map["willCrossBoundary"] = stop_watch.toc(true); return output; } @@ -334,15 +334,18 @@ bool LaneDepartureChecker::isOutOfLane( return false; } -bool LaneDepartureChecker::willCrossRoadBorder( +bool LaneDepartureChecker::willCrossBoundary( const lanelet::ConstLanelets & candidate_lanelets, - const std::vector & vehicle_footprints) + const std::vector & vehicle_footprints, + const std::vector & boundary_types_to_detect) { for (const auto & candidate_lanelet : candidate_lanelets) { const std::string r_type = candidate_lanelet.rightBound().attributeOr(lanelet::AttributeName::Type, "none"); - if (r_type == "road_border") { - if (isCrossingWithRoadBorder( + if ( + std::find(boundary_types_to_detect.begin(), boundary_types_to_detect.end(), r_type) != + boundary_types_to_detect.end()) { + if (isCrossingWithBoundary( candidate_lanelet.rightBound2d().basicLineString(), vehicle_footprints)) { // std::cerr << "The crossed road_border's line string id: " // << candidate_lanelet.rightBound().id() << std::endl; @@ -351,8 +354,10 @@ bool LaneDepartureChecker::willCrossRoadBorder( } const std::string l_type = candidate_lanelet.leftBound().attributeOr(lanelet::AttributeName::Type, "none"); - if (l_type == "road_border") { - if (isCrossingWithRoadBorder( + if ( + std::find(boundary_types_to_detect.begin(), boundary_types_to_detect.end(), l_type) != + boundary_types_to_detect.end()) { + if (isCrossingWithBoundary( candidate_lanelet.leftBound2d().basicLineString(), vehicle_footprints)) { // std::cerr << "The crossed road_border's line string id: " // << candidate_lanelet.leftBound().id() << std::endl; diff --git a/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp b/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp index 20d535a82bfa1..f788e64316171 100644 --- a/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp +++ b/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp @@ -125,6 +125,11 @@ LaneDepartureCheckerNode::LaneDepartureCheckerNode(const rclcpp::NodeOptions & o { using std::placeholders::_1; + // Enable feature + node_param_.will_out_of_lane_checker = declare_parameter("will_out_of_lane_checker"); + node_param_.out_of_lane_checker = declare_parameter("out_of_lane_checker"); + node_param_.boundary_departure_checker = declare_parameter("boundary_departure_checker"); + // Node Parameter node_param_.update_rate = declare_parameter("update_rate"); node_param_.visualize_lanelet = declare_parameter("visualize_lanelet"); @@ -132,8 +137,10 @@ LaneDepartureCheckerNode::LaneDepartureCheckerNode(const rclcpp::NodeOptions & o node_param_.include_left_lanes = declare_parameter("include_left_lanes"); node_param_.include_opposite_lanes = declare_parameter("include_opposite_lanes"); node_param_.include_conflicting_lanes = declare_parameter("include_conflicting_lanes"); - node_param_.road_border_departure_checker = - declare_parameter("road_border_departure_checker"); + + // Boundary_departure_checker + node_param_.boundary_types_to_detect = + declare_parameter>("boundary_types_to_detect"); // Vehicle Info const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(*this).getVehicleInfo(); @@ -338,6 +345,7 @@ void LaneDepartureCheckerNode::onTimer() input_.shoulder_lanelets = shoulder_lanelets_; input_.reference_trajectory = reference_trajectory_; input_.predicted_trajectory = predicted_trajectory_; + input_.boundary_types_to_detect = node_param_.boundary_types_to_detect; processing_time_map["Node: setInputData"] = stop_watch.toc(true); output_ = lane_departure_checker_->update(input_); @@ -377,12 +385,19 @@ rcl_interfaces::msg::SetParametersResult LaneDepartureCheckerNode::onParameter( result.reason = "success"; try { + // Enable feature + update_param(parameters, "will_out_of_lane_checker", node_param_.will_out_of_lane_checker); + update_param(parameters, "out_of_lane_checker", node_param_.out_of_lane_checker); + update_param(parameters, "boundary_departure_checker", node_param_.boundary_departure_checker); + // Node update_param(parameters, "visualize_lanelet", node_param_.visualize_lanelet); update_param(parameters, "include_right_lanes", node_param_.include_right_lanes); update_param(parameters, "include_left_lanes", node_param_.include_left_lanes); update_param(parameters, "include_opposite_lanes", node_param_.include_opposite_lanes); update_param(parameters, "include_conflicting_lanes", node_param_.include_conflicting_lanes); + update_param(parameters, "boundary_departure_checker", node_param_.boundary_departure_checker); + update_param(parameters, "boundary_types_to_detect", node_param_.boundary_types_to_detect); // Core update_param(parameters, "footprint_margin_scale", param_.footprint_margin_scale); @@ -409,19 +424,19 @@ void LaneDepartureCheckerNode::checkLaneDeparture( int8_t level = DiagStatus::OK; std::string msg = "OK"; - if (output_.will_leave_lane) { + if (output_.will_leave_lane && node_param_.will_out_of_lane_checker) { level = DiagStatus::WARN; msg = "vehicle will leave lane"; } - if (output_.is_out_of_lane) { + if (output_.is_out_of_lane && node_param_.out_of_lane_checker) { level = DiagStatus::ERROR; msg = "vehicle is out of lane"; } - if (output_.will_cross_road_border && node_param_.road_border_departure_checker) { + if (output_.will_cross_boundary && node_param_.boundary_departure_checker) { level = DiagStatus::ERROR; - msg = "vehicle will cross road border"; + msg = "vehicle will cross boundary"; } stat.summary(level, msg); From a0b022751e90ba2baec0f8905cf061de44260d71 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Fri, 8 Sep 2023 02:45:46 +0900 Subject: [PATCH 150/547] refactor(behavior_path_planner): visualize debug information of safety check against dynamic obstacles (#4892) * visualize debug info for safety check Signed-off-by: kyoichi-sugahara * Update planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp Co-authored-by: Kosuke Takeuchi * fix build error Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara Co-authored-by: Kosuke Takeuchi --- .../common_module_data.hpp | 3 + .../utils/start_goal_planner_common/utils.hpp | 9 +++ .../avoidance/avoidance_module.cpp | 3 +- .../goal_planner/goal_planner_module.cpp | 41 +++++++--- .../start_planner/start_planner_module.cpp | 75 ++++++++++++++----- .../utils/start_goal_planner_common/utils.cpp | 15 ++++ 6 files changed, 115 insertions(+), 31 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp index 4ba2cb08b6341..55b8bdf777692 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp @@ -27,6 +27,7 @@ namespace behavior_path_planner { using autoware_auto_perception_msgs::msg::PredictedObjects; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugMap; using behavior_path_planner::utils::path_safety_checker::PoseWithVelocityStamped; using behavior_path_planner::utils::path_safety_checker::TargetObjectsOnLane; /* @@ -38,6 +39,8 @@ struct StartGoalPlannerData PredictedObjects filtered_objects; TargetObjectsOnLane target_objects_on_lane; std::vector ego_predicted_path; + // collision check debug map + CollisionCheckDebugMap collision_check; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp index bea671c266899..63b8676e2188b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp @@ -18,6 +18,7 @@ #include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp" #include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" #include "behavior_path_planner/utils/start_planner/start_planner_parameters.hpp" @@ -31,6 +32,7 @@ namespace behavior_path_planner::utils::start_goal_planner_common { using behavior_path_planner::StartPlannerParameters; +using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugMap; using behavior_path_planner::utils::path_safety_checker::EgoPredictedPathParams; using behavior_path_planner::utils::path_safety_checker::ObjectsFilteringParams; using behavior_path_planner::utils::path_safety_checker::SafetyCheckParams; @@ -85,6 +87,13 @@ void updatePathProperty( std::shared_ptr & ego_predicted_path_params, const std::pair & pairs_terminal_velocity_and_accel); +void initializeCollisionCheckDebugMap(CollisionCheckDebugMap & collision_check_debug_map); + +void updateSafetyCheckTargetObjectsData( + StartGoalPlannerData & data, const PredictedObjects & filtered_objects, + const TargetObjectsOnLane & target_objects_on_lane, + const std::vector & ego_predicted_path); + std::pair getPairsTerminalVelocityAndAccel( const std::vector> & pairs_terminal_velocity_and_accel, const size_t current_path_idx); diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index c2b372d2403e5..3b005c1b2242d 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1901,10 +1901,9 @@ bool AvoidanceModule::isSafePath( safe_count_ = 0; return false; - } else { - marker_utils::updateCollisionCheckDebugMap(debug.collision_check, current_debug_data, true); } } + marker_utils::updateCollisionCheckDebugMap(debug.collision_check, current_debug_data, true); } safe_count_++; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 37d44154789a6..cfdc968656353 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -1494,13 +1494,12 @@ bool GoalPlannerModule::isSafePath() const *route_handler, left_side_parking_, parameters_->backward_goal_search_length, parameters_->forward_goal_search_length); const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(pull_over_path.points); - const auto & common_param = planner_data_->parameters; const std::pair terminal_velocity_and_accel = utils::start_goal_planner_common::getPairsTerminalVelocityAndAccel( status_.pull_over_path->pairs_terminal_velocity_and_accel, status_.current_path_idx); RCLCPP_DEBUG( - getLogger(), "pairs_terminal_velocity_and_accel: %f, %f", terminal_velocity_and_accel.first, - terminal_velocity_and_accel.second); + getLogger(), "pairs_terminal_velocity_and_accel for goal_planner: %f, %f", + terminal_velocity_and_accel.first, terminal_velocity_and_accel.second); RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.current_path_idx); utils::start_goal_planner_common::updatePathProperty( ego_predicted_path_params_, terminal_velocity_and_accel); @@ -1509,32 +1508,52 @@ bool GoalPlannerModule::isSafePath() const ego_predicted_path_params_, pull_over_path.points, current_pose, current_velocity, ego_seg_idx); - const auto filtered_objects = utils::path_safety_checker::filterObjects( + // filtering objects with velocity, position and class + const auto & filtered_objects = utils::path_safety_checker::filterObjects( dynamic_object, route_handler, pull_over_lanes, current_pose.position, objects_filtering_params_); - const auto target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( + // filtering objects based on the current position's lane + const auto & target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( pull_over_lanes, route_handler, filtered_objects, objects_filtering_params_); const double hysteresis_factor = status_.is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; - updateSafetyCheckTargetObjectsData(filtered_objects, target_objects_on_lane, ego_predicted_path); + utils::start_goal_planner_common::updateSafetyCheckTargetObjectsData( + goal_planner_data_, filtered_objects, target_objects_on_lane, ego_predicted_path); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + goal_planner_data_.collision_check); + bool is_safe_dynamic_objects = true; + // Check for collisions with each predicted path of the object for (const auto & object : target_objects_on_lane.on_current_lane) { + auto current_debug_data = marker_utils::createObjectDebug(object); + + bool is_safe_dynamic_object = true; + const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( object, objects_filtering_params_->check_all_predicted_path); + + // If a collision is detected, mark the object as unsafe and break the loop for (const auto & obj_path : obj_predicted_paths) { - CollisionCheckDebug collision{}; if (!utils::path_safety_checker::checkCollision( - pull_over_path, ego_predicted_path, object, obj_path, common_param, - safety_check_params_->rss_params, hysteresis_factor, collision)) { - return false; + pull_over_path, ego_predicted_path, object, obj_path, planner_data_->parameters, + safety_check_params_->rss_params, hysteresis_factor, current_debug_data.second)) { + marker_utils::updateCollisionCheckDebugMap( + goal_planner_data_.collision_check, current_debug_data, false); + is_safe_dynamic_objects = false; + is_safe_dynamic_object = false; + break; } } + if (is_safe_dynamic_object) { + marker_utils::updateCollisionCheckDebugMap( + goal_planner_data_.collision_check, current_debug_data, is_safe_dynamic_object); + } } - return true; + return is_safe_dynamic_objects; } void GoalPlannerModule::setDebugData() diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index e461995cba1e7..e8acc77113959 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -928,15 +928,6 @@ TurnSignalInfo StartPlannerModule::calcTurnSignalInfo() const return turn_signal; } -void StartPlannerModule::updateSafetyCheckTargetObjectsData( - const PredictedObjects & filtered_objects, const TargetObjectsOnLane & target_objects_on_lane, - const std::vector & ego_predicted_path) const -{ - start_planner_data_.filtered_objects = filtered_objects; - start_planner_data_.target_objects_on_lane = target_objects_on_lane; - start_planner_data_.ego_predicted_path = ego_predicted_path; -} - bool StartPlannerModule::isSafePath() const { // TODO(Sugahara): should safety check for backward path @@ -950,14 +941,20 @@ bool StartPlannerModule::isSafePath() const const auto & route_handler = planner_data_->route_handler; const double backward_path_length = planner_data_->parameters.backward_path_length + parameters_->max_back_distance; + const auto current_lanes = utils::getExtendedCurrentLanes( planner_data_, backward_path_length, std::numeric_limits::max(), /*forward_only_in_route*/ true); + + // for ego predicted path const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(pull_out_path.points); - const auto & common_param = planner_data_->parameters; const std::pair terminal_velocity_and_accel = utils::start_goal_planner_common::getPairsTerminalVelocityAndAccel( status_.pull_out_path.pairs_terminal_velocity_and_accel, status_.current_path_idx); + RCLCPP_DEBUG( + getLogger(), "pairs_terminal_velocity_and_accel for start_planner: %f, %f", + terminal_velocity_and_accel.first, terminal_velocity_and_accel.second); + RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.current_path_idx); utils::start_goal_planner_common::updatePathProperty( ego_predicted_path_params_, terminal_velocity_and_accel); const auto ego_predicted_path = @@ -965,31 +962,51 @@ bool StartPlannerModule::isSafePath() const ego_predicted_path_params_, pull_out_path.points, current_pose, current_velocity, ego_seg_idx); - const auto filtered_objects = utils::path_safety_checker::filterObjects( + // filtering objects with velocity, position and class + const auto & filtered_objects = utils::path_safety_checker::filterObjects( dynamic_object, route_handler, current_lanes, current_pose.position, objects_filtering_params_); - const auto target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( + // filtering objects based on the current position's lane + const auto & target_objects_on_lane = utils::path_safety_checker::createTargetObjectsOnLane( current_lanes, route_handler, filtered_objects, objects_filtering_params_); const double hysteresis_factor = status_.is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; - updateSafetyCheckTargetObjectsData(filtered_objects, target_objects_on_lane, ego_predicted_path); + utils::start_goal_planner_common::updateSafetyCheckTargetObjectsData( + start_planner_data_, filtered_objects, target_objects_on_lane, ego_predicted_path); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + start_planner_data_.collision_check); + bool is_safe_dynamic_objects = true; + // Check for collisions with each predicted path of the object for (const auto & object : target_objects_on_lane.on_current_lane) { + auto current_debug_data = marker_utils::createObjectDebug(object); + + bool is_safe_dynamic_object = true; + const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( object, objects_filtering_params_->check_all_predicted_path); + + // If a collision is detected, mark the object as unsafe and break the loop for (const auto & obj_path : obj_predicted_paths) { - CollisionCheckDebug collision{}; if (!utils::path_safety_checker::checkCollision( - pull_out_path, ego_predicted_path, object, obj_path, common_param, - safety_check_params_->rss_params, hysteresis_factor, collision)) { - return false; + pull_out_path, ego_predicted_path, object, obj_path, planner_data_->parameters, + safety_check_params_->rss_params, hysteresis_factor, current_debug_data.second)) { + marker_utils::updateCollisionCheckDebugMap( + start_planner_data_.collision_check, current_debug_data, false); + is_safe_dynamic_objects = false; + is_safe_dynamic_object = false; + break; } } + if (is_safe_dynamic_object) { + marker_utils::updateCollisionCheckDebugMap( + start_planner_data_.collision_check, current_debug_data, is_safe_dynamic_object); + } } - return true; + return is_safe_dynamic_objects; } bool StartPlannerModule::IsGoalBehindOfEgoInSameRouteSegment() const @@ -1114,6 +1131,9 @@ void StartPlannerModule::setDebugData() const using marker_utils::createPathMarkerArray; using marker_utils::createPoseMarkerArray; using marker_utils::createPredictedPathMarkerArray; + using marker_utils::showPolygon; + using marker_utils::showPredictedPath; + using marker_utils::showSafetyCheckInfo; using tier4_autoware_utils::createDefaultMarker; using tier4_autoware_utils::createMarkerColor; using tier4_autoware_utils::createMarkerScale; @@ -1143,6 +1163,25 @@ void StartPlannerModule::setDebugData() const start_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); } + // safety check + { + add(showSafetyCheckInfo(start_planner_data_.collision_check, "object_debug_info")); + add(showPredictedPath(start_planner_data_.collision_check, "ego_predicted_path")); + add(showPolygon(start_planner_data_.collision_check, "ego_and_target_polygon_relation")); + } + + if (start_planner_data_.ego_predicted_path.size() > 0) { + const auto & ego_predicted_path = utils::path_safety_checker::convertToPredictedPath( + start_planner_data_.ego_predicted_path, ego_predicted_path_params_->time_resolution); + add(createPredictedPathMarkerArray( + ego_predicted_path, vehicle_info_, "ego_predicted_path", 0, 0.0, 0.5, 0.9)); + } + + if (start_planner_data_.filtered_objects.objects.size() > 0) { + add(createObjectsMarkerArray( + start_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); + } + // Visualize planner type text const auto header = planner_data_->route_handler->getRouteHeader(); { diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index cff6e9f81f02e..830860de484b6 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -142,6 +142,21 @@ void updatePathProperty( ego_predicted_path_params->acceleration = acceleration; } +void initializeCollisionCheckDebugMap(CollisionCheckDebugMap & collision_check_debug_map) +{ + collision_check_debug_map.clear(); +} + +void updateSafetyCheckTargetObjectsData( + StartGoalPlannerData & data, const PredictedObjects & filtered_objects, + const TargetObjectsOnLane & target_objects_on_lane, + const std::vector & ego_predicted_path) +{ + data.filtered_objects = filtered_objects; + data.target_objects_on_lane = target_objects_on_lane; + data.ego_predicted_path = ego_predicted_path; +} + std::pair getPairsTerminalVelocityAndAccel( const std::vector> & pairs_terminal_velocity_and_accel, const size_t current_path_idx) From 576acf13116d5d98d78dad8ef47b7658ccf3a26f Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Fri, 8 Sep 2023 09:08:07 +0900 Subject: [PATCH 151/547] chore(build): fix traffic_light_map_based_detector (#4921) Signed-off-by: Mamoru Sobue --- perception/traffic_light_map_based_detector/package.xml | 1 + perception/traffic_light_map_based_detector/src/node.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/perception/traffic_light_map_based_detector/package.xml b/perception/traffic_light_map_based_detector/package.xml index 1021757e99a07..c2d53869fa3d5 100644 --- a/perception/traffic_light_map_based_detector/package.xml +++ b/perception/traffic_light_map_based_detector/package.xml @@ -22,6 +22,7 @@ sensor_msgs tf2 tf2_eigen + tf2_geometry_msgs tf2_ros tier4_autoware_utils tier4_perception_msgs diff --git a/perception/traffic_light_map_based_detector/src/node.cpp b/perception/traffic_light_map_based_detector/src/node.cpp index a46be77667bcb..f0a5d7b7b1fde 100644 --- a/perception/traffic_light_map_based_detector/src/node.cpp +++ b/perception/traffic_light_map_based_detector/src/node.cpp @@ -31,6 +31,12 @@ #include #include +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif + namespace { cv::Point2d calcRawImagePointFromPoint3D( From 28f5b18e71c0458e1d27067bfa2f608c2b682012 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Fri, 8 Sep 2023 10:40:40 +0900 Subject: [PATCH 152/547] docs(planning_debug_tools): rename readme to README (#4222) Signed-off-by: Takamasa Horibe --- planning/planning_debug_tools/{Readme.md => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename planning/planning_debug_tools/{Readme.md => README.md} (100%) diff --git a/planning/planning_debug_tools/Readme.md b/planning/planning_debug_tools/README.md similarity index 100% rename from planning/planning_debug_tools/Readme.md rename to planning/planning_debug_tools/README.md From cd3956e437e8d85c18ed6bde2c826fd6036095af Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Fri, 8 Sep 2023 11:00:38 +0900 Subject: [PATCH 153/547] docs(operation_transition_manager): update readme (#4888) * docs(operation_transition_manager): update readme Signed-off-by: Takamasa Horibe * precommit Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../README.md | 22 +- .../transition_detailed_structure.drawio.svg | 610 ++++++++++++++++++ .../transition_rough_structure.drawio.svg | 321 +++++++++ 3 files changed, 952 insertions(+), 1 deletion(-) create mode 100644 control/operation_mode_transition_manager/image/transition_detailed_structure.drawio.svg create mode 100644 control/operation_mode_transition_manager/image/transition_rough_structure.drawio.svg diff --git a/control/operation_mode_transition_manager/README.md b/control/operation_mode_transition_manager/README.md index 73249095c6d5c..3cb07a9bd7821 100644 --- a/control/operation_mode_transition_manager/README.md +++ b/control/operation_mode_transition_manager/README.md @@ -25,7 +25,27 @@ There is also an `In Transition` state that occurs during each mode transitions. ## Design - +A rough design of the relationship between `operation_mode_transition_manager`` and the other nodes is shown below. + +![transition_rough_structure](image/transition_rough_structure.drawio.svg) + +A more detailed structure is below. + +![transition_detailed_structure](image/transition_detailed_structure.drawio.svg) + +Here we see that `operation_mode_transition_manager` has multiple state transitions as follows + +- **AUTOWARE ENABLED <---> DISABLED** + - **ENABLED**: the vehicle is controlled by Autoware. + - **DISABLED**: the vehicle is out of Autoware control, expecting the e.g. manual driving. +- **AUTOWARE ENABLED <---> AUTO/LOCAL/REMOTE/NONE** + - **AUTO**: the vehicle is controlled by Autoware, with the autonomous control command calculated by the planning/control component. + - **LOCAL**: the vehicle is controlled by Autoware, with the locally connected operator, e.g. joystick controller. + - **REMOTE**: the vehicle is controlled by Autoware, with the remotely connected operator. + - **NONE**: the vehicle is not controlled by any operator. +- **IN TRANSITION <---> COMPLETED** + - **IN TRANSITION**: the mode listed above is in the transition process, expecting the former operator to have a responsibility to confirm the transition is completed. + - **COMPLETED**: the mode transition is completed. ## Inputs / Outputs / API diff --git a/control/operation_mode_transition_manager/image/transition_detailed_structure.drawio.svg b/control/operation_mode_transition_manager/image/transition_detailed_structure.drawio.svg new file mode 100644 index 0000000000000..3492dca6dd86b --- /dev/null +++ b/control/operation_mode_transition_manager/image/transition_detailed_structure.drawio.svg @@ -0,0 +1,610 @@ + + + + + + + + + + + + +
+
+
+ Control Command +
+ (Auto) +
+
+
+
+ Control Command... +
+
+ + + + +
+
+
+ Trajectory Follower +
+
+
+
+ Trajectory Follower +
+
+ + + + + + + +
+
+
+ Control Command +
+
+
+
+ Control Command +
+
+ + + + +
+
+
+ Vehicle Cmd Gate +
+
+
+
+ Vehicle Cmd Gate +
+
+ + + + +
+
+
+ Vehicle Interface +
+
+
+
+ Vehicle Interface +
+
+ + + + + +
+
+
+ Current Mode +
+
+
+
+ Current Mode +
+
+ + + + + +
+
+
+ Auto mode +
+ Availability +
+
+
+
+ Auto mode... +
+
+ + + + + +
+
+
+ Mode Change +
+ Request +
+
+
+
+ Mode Change... +
+
+ + + + +
+
+
+ Operation Mode +
+ Transition Manager +
+
+
+
+ Operation Mode... +
+
+ + + + + +
+
+
+ Mode Change +
+ Request +
+
+
+
+ Mode Change... +
+
+ + + + +
+
+
+ API +
+
+
+
+ API +
+
+ + + + +
+
+
+ Trajectory (From Planning) +
+
+
+
+ Trajectory (From Planning) +
+
+ + + + + +
+
+
+ Mode Change +
+ Response +
+
+
+
+ Mode Change... +
+
+ + + + + + + + +
+
+
+ Ego pose, twist, acceleration (From Localization) +
+
+
+
+ Ego pose, twist, accelera... +
+
+ + + + + +
+
+
+ External Control Command +
+
+
+
+ External C... +
+
+ + + + +
+
+
+ AUTOWARE ENABLED +
+
+
+
+ AUTOWARE ENABLED +
+
+ + + + +
+
+
+ REMOTE +
+
+
+
+ REMOTE +
+
+ + + + + + + +
+
+
+ NONE +
+
+
+
+ NONE +
+
+ + + + + + + + + + +
+
+
+ AUTO +
+
+
+
+ AUTO +
+
+ + + + + + + + + + +
+
+
+ LOCAL +
+
+
+
+ LOCAL +
+
+ + + + + + + +
+
+
+ + AUTOWARE +
+ DISABLED +
+
+
+
+
+ AUTOWARE... +
+
+ + + + + + + +
+
+
+ TRANSITION +
+
+
+
+ TRANSITION +
+
+ + + + +
+
+
+ IN TRANSITION +
+
+
+
+ IN TRANSITION +
+
+ + + + +
+
+
+ COMPLETED +
+
+
+
+ COMPLETED +
+
+ + + + + + + + + +
+
+
+ + Vehicle Control mode +
+ (From Vehicle) +
+
+
+
+
+ Vehicle Control mode... +
+
+ + + + +
+
+
+ + Control Component + +
+
+
+
+ Control Component +
+
+ + + + +
+
+
+ External Command +
+ Selector +
+
+
+
+ External Command... +
+
+ + + + + +
+
+
+ Control Command +
+ (Local) +
+
+
+
+ Control Co... +
+
+ + + + + +
+
+
+ Control Command +
+ (Remote) +
+
+
+
+ Control Command... +
+
+
+ + + + Text is not SVG - cannot display + + +
diff --git a/control/operation_mode_transition_manager/image/transition_rough_structure.drawio.svg b/control/operation_mode_transition_manager/image/transition_rough_structure.drawio.svg new file mode 100644 index 0000000000000..073a2315355be --- /dev/null +++ b/control/operation_mode_transition_manager/image/transition_rough_structure.drawio.svg @@ -0,0 +1,321 @@ + + + + + + + + + + + +
+
+
+ Control Command +
+
+
+
+ Control Command +
+
+ + + + +
+
+
+ Trajectory Follower +
+
+
+
+ Trajectory Follower +
+
+ + + + + +
+
+
+ Control Command +
+
+
+
+ Control Command +
+
+ + + + +
+
+
+ Vehicle Cmd Gate +
+
+
+
+ Vehicle Cmd Gate +
+
+ + + + +
+
+
+ Vehicle Interface +
+
+
+
+ Vehicle Interface +
+
+ + + + + +
+
+
+ Current Mode +
+
+
+
+ Current Mode +
+
+ + + + + +
+
+
+ Auto mode +
+ Availability +
+
+
+
+ Auto mode... +
+
+ + + + + +
+
+
+ Mode Change +
+ Request +
+
+
+
+ Mode Change... +
+
+ + + + +
+
+
+ Operation Mode +
+ Transition Manager +
+
+
+
+ Operation Mode... +
+
+ + + + + +
+
+
+ Mode Change +
+ Request +
+
+
+
+ Mode Change... +
+
+ + + + +
+
+
+ API +
+
+
+
+ API +
+
+ + + + + + +
+
+
+ Change the control mode (e.g. Auto/Manual) for the request, follow the control command in the Auto mode +
+
+
+
+ Change the control mode (e.g. Auto/Ma... +
+
+ + + + +
+
+
+ Manage the operation mode transition (e.g. Manual/Auto). +
+ It also rejects the request based on the driving conditrion, checks the transition is complited. +
+
+
+
+ Manage the operation mode transition... +
+
+ + + + + + +
+
+
+ Manage the operation mode transition (e.g. Manual/Auto). +
+ It also rejects the request, checks the transition is complited. +
+
+
+
+ Manage the operation mode transition... +
+
+ + + + +
+
+
+ Apply filter based on the current operation mode (e.g. Manual/Auto, in the process of the transition) +
+
+
+
+ Apply filter based on t... +
+
+ + + + +
+ + + + Text is not SVG - cannot display + + +
From b498c5352390e3637fb723140bf7b541d5bb04a8 Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Fri, 8 Sep 2023 11:47:18 +0900 Subject: [PATCH 154/547] feat(ndt_scan_matcher): add diag lidar_topic_delay_time_sec (#4815) * feat(ndt_scan_matcher): add diag lidar_topic_delay_time_sec Signed-off-by: yamato-ando * fix param Signed-off-by: yamato-ando * fix stoi to stod Signed-off-by: yamato-ando * style(pre-commit): autofix * update readme Signed-off-by: yamato-ando --------- Signed-off-by: yamato-ando Co-authored-by: yamato-ando Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- localization/ndt_scan_matcher/README.md | 1 + .../config/ndt_scan_matcher.param.yaml | 3 ++ .../ndt_scan_matcher_core.hpp | 1 + .../src/ndt_scan_matcher_core.cpp | 30 ++++++++++++++++++- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/localization/ndt_scan_matcher/README.md b/localization/ndt_scan_matcher/README.md index b30cbaca1e87a..fcbe4804ca7d2 100644 --- a/localization/ndt_scan_matcher/README.md +++ b/localization/ndt_scan_matcher/README.md @@ -64,6 +64,7 @@ One optional function is regularization. Please see the regularization chapter i | `max_iterations` | int | The number of iterations required to calculate alignment | | `converged_param_type` | int | The type of indicators for scan matching score (0: TP, 1: NVTL) | | `converged_param_transform_probability` | double | Threshold for deciding whether to trust the estimation result | +| `lidar_topic_timeout_sec` | double | Tolerance of timestamp difference between current time and sensor pointcloud | | `num_threads` | int | Number of threads used for parallel computing | (TP: Transform Probability, NVTL: Nearest Voxel Transform Probability) diff --git a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml index a5d8142b6616e..b5affd72b2158 100644 --- a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml +++ b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml @@ -37,6 +37,9 @@ # The number of particles to estimate initial pose initial_estimate_particles_num: 100 + # Tolerance of timestamp difference between current time and sensor pointcloud. [sec] + lidar_topic_timeout_sec: 1.0 + # Tolerance of timestamp difference between initial_pose and sensor pointcloud. [sec] initial_pose_timeout_sec: 1.0 diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp index 8657e354c728a..ed3b99019f7d9 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp @@ -179,6 +179,7 @@ class NDTScanMatcher : public rclcpp::Node double converged_param_nearest_voxel_transformation_likelihood_; int initial_estimate_particles_num_; + double lidar_topic_timeout_sec_; double initial_pose_timeout_sec_; double initial_pose_distance_tolerance_m_; float inversion_vector_threshold_; diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 154fc8526f512..714f8f65bad37 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -91,6 +91,7 @@ NDTScanMatcher::NDTScanMatcher() converged_param_transform_probability_(4.5), converged_param_nearest_voxel_transformation_likelihood_(2.3), initial_estimate_particles_num_(100), + lidar_topic_timeout_sec_(1.0), initial_pose_timeout_sec_(1.0), initial_pose_distance_tolerance_m_(10.0), inversion_vector_threshold_(-0.9), @@ -141,6 +142,9 @@ NDTScanMatcher::NDTScanMatcher() "converged_param_nearest_voxel_transformation_likelihood", converged_param_nearest_voxel_transformation_likelihood_); + lidar_topic_timeout_sec_ = + this->declare_parameter("lidar_topic_timeout_sec", lidar_topic_timeout_sec_); + initial_pose_timeout_sec_ = this->declare_parameter("initial_pose_timeout_sec", initial_pose_timeout_sec_); @@ -268,6 +272,12 @@ void NDTScanMatcher::timer_diagnostic() diag_status_msg.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; diag_status_msg.message += "Initializing State. "; } + if ( + state_ptr_->count("lidar_topic_delay_time_sec") && + std::stod((*state_ptr_)["lidar_topic_delay_time_sec"]) > lidar_topic_timeout_sec_) { + diag_status_msg.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + diag_status_msg.message += "lidar_topic_delay_time_sec exceed limit. "; + } if ( state_ptr_->count("skipping_publish_num") && std::stoi((*state_ptr_)["skipping_publish_num"]) > 1 && @@ -347,11 +357,29 @@ void NDTScanMatcher::callback_sensor_points( return; } + const rclcpp::Time sensor_ros_time = sensor_points_msg_in_sensor_frame->header.stamp; + const double lidar_topic_delay_time_sec = (this->now() - sensor_ros_time).seconds(); + (*state_ptr_)["lidar_topic_delay_time_sec"] = std::to_string(lidar_topic_delay_time_sec); + + if (lidar_topic_delay_time_sec > lidar_topic_timeout_sec_) { + RCLCPP_WARN( + this->get_logger(), + "The LiDAR topic is experiencing latency. The delay time is %lf[sec] (the tolerance is " + "%lf[sec])", + lidar_topic_delay_time_sec, lidar_topic_timeout_sec_); + + // If the delay time of the LiDAR topic exceeds the delay compensation time of ekf_localizer, + // even if further processing continues, the estimated result will be rejected by ekf_localizer. + // Therefore, it would be acceptable to exit the function here. + // However, for now, we will continue the processing as it is. + + // return; + } + // mutex ndt_ptr_ std::lock_guard lock(ndt_ptr_mtx_); const auto exe_start_time = std::chrono::system_clock::now(); - const rclcpp::Time sensor_ros_time = sensor_points_msg_in_sensor_frame->header.stamp; // preprocess input pointcloud pcl::shared_ptr> sensor_points_in_sensor_frame( From 61fef17c37d25525ce8ce845c21e5ee7a57c2c98 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Fri, 8 Sep 2023 20:08:51 +0900 Subject: [PATCH 155/547] feat(planning_debug_tools): support old auto TrafficSignalArray (#4910) * feat(planning_debug_tools): support old auto TrafficSignalArray Signed-off-by: kosuke55 * use is_auto flag Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../perception_replayer/perception_replayer.py | 17 +++++++++++++---- .../perception_replayer_common.py | 17 ++++++++++++++--- .../perception_reproducer.py | 17 +++++++++++++---- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py index 9d8fa4a32a93d..a9abc5be60761 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py @@ -92,13 +92,22 @@ def on_timer(self): self.objects_pub.publish(objects_msg) # traffic signals + # temporary support old auto msgs if traffic_signals_msg: - traffic_signals_msg.stamp = timestamp - self.traffic_signals_pub.publish(traffic_signals_msg) + if self.is_auto_traffic_signals: + traffic_signals_msg.header.stamp = timestamp + self.auto_traffic_signals_pub.publish(traffic_signals_msg) + else: + traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(traffic_signals_msg) self.prev_traffic_signals_msg = traffic_signals_msg elif self.prev_traffic_signals_msg: - self.prev_traffic_signals_msg.stamp = timestamp - self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) + if self.is_auto_traffic_signals: + self.prev_traffic_signals_msg.header.stamp = timestamp + self.auto_traffic_signals_pub.publish(self.prev_traffic_signals_msg) + else: + self.prev_traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) def onPushed(self, event): if self.widget.button.isChecked(): diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index 7774a8c82daf6..33cf8ca1f16ee 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -22,6 +22,7 @@ from autoware_auto_perception_msgs.msg import DetectedObjects from autoware_auto_perception_msgs.msg import PredictedObjects from autoware_auto_perception_msgs.msg import TrackedObjects +from autoware_auto_perception_msgs.msg import TrafficSignalArray as AutoTrafficSignalArray from autoware_perception_msgs.msg import TrafficSignalArray from nav_msgs.msg import Odometry import psutil @@ -65,9 +66,6 @@ def __init__(self, args, name): self.pointcloud_pub = self.create_publisher( PointCloud2, "/perception/obstacle_segmentation/pointcloud", 1 ) - self.traffic_signals_pub = self.create_publisher( - TrafficSignalArray, "/perception/traffic_light_recognition/traffic_signals", 1 - ) # load rosbag print("Stared loading rosbag") @@ -78,6 +76,16 @@ def __init__(self, args, name): self.load_rosbag(args.bag) print("Ended loading rosbag") + # temporary support old auto msgs + if self.is_auto_traffic_signals: + self.auto_traffic_signals_pub = self.create_publisher( + AutoTrafficSignalArray, "/perception/traffic_light_recognition/traffic_signals", 1 + ) + else: + self.traffic_signals_pub = self.create_publisher( + TrafficSignalArray, "/perception/traffic_light_recognition/traffic_signals", 1 + ) + # wait for ready to publish/subscribe time.sleep(1.0) @@ -113,6 +121,9 @@ def load_rosbag(self, rosbag2_path: str): self.rosbag_ego_odom_data.append((stamp, msg)) if topic == traffic_signals_topic: self.rosbag_traffic_signals_data.append((stamp, msg)) + self.is_auto_traffic_signals = ( + "autoware_auto_perception_msgs" in type(msg).__module__ + ) def kill_online_perception_node(self): # kill node if required diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py index 5344e622be38e..1b75c86de2709 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py @@ -66,13 +66,22 @@ def on_timer(self): self.objects_pub.publish(objects_msg) # traffic signals + # temporary support old auto msgs if traffic_signals_msg: - traffic_signals_msg.stamp = timestamp - self.traffic_signals_pub.publish(traffic_signals_msg) + if self.is_auto_traffic_signals: + traffic_signals_msg.header.stamp = timestamp + self.auto_traffic_signals_pub.publish(traffic_signals_msg) + else: + traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(traffic_signals_msg) self.prev_traffic_signals_msg = traffic_signals_msg elif self.prev_traffic_signals_msg: - self.prev_traffic_signals_msg.stamp = timestamp - self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) + if self.is_auto_traffic_signals: + self.prev_traffic_signals_msg.header.stamp = timestamp + self.auto_traffic_signals_pub.publish(self.prev_traffic_signals_msg) + else: + self.prev_traffic_signals_msg.stamp = timestamp + self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) def find_nearest_ego_odom_by_observation(self, ego_pose): if self.ego_pose_idx: From 49f3bd6cd28ce1d797af15d27d2b7085374a237f Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Fri, 8 Sep 2023 21:37:24 +0900 Subject: [PATCH 156/547] feat(planning_debug_tools): add recorded ego pose publisher to perception replayer (#4906) Signed-off-by: kosuke55 --- .../perception_replayer.py | 53 +++++++++++++++++++ .../perception_replayer_common.py | 2 + .../time_manager_widget.py | 4 +- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py index a9abc5be60761..f1015be07fd7c 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py @@ -20,6 +20,7 @@ import sys from PyQt5.QtWidgets import QApplication +from geometry_msgs.msg import PoseWithCovarianceStamped from perception_replayer_common import PerceptionReplayerCommon import rclpy from time_manager_widget import TimeManagerWidget @@ -43,6 +44,7 @@ def __init__(self, args): self.widget.button.clicked.connect(self.onPushed) for button in self.widget.rate_button: button.clicked.connect(functools.partial(self.onSetRate, button)) + self.widget.pub_recorded_ego_pose_button.clicked.connect(self.publish_recorded_ego_pose) # start timer callback self.delta_time = 0.1 @@ -118,6 +120,57 @@ def onPushed(self, event): def onSetRate(self, button): self.rate = float(button.text()) + def publish_recorded_ego_pose(self): + ego_odom = self.find_ego_odom_by_timestamp(self.bag_timestamp) + if not ego_odom: + return + + recorded_ego_pose = PoseWithCovarianceStamped() + recorded_ego_pose.header.stamp = self.get_clock().now().to_msg() + recorded_ego_pose.header.frame_id = "map" + recorded_ego_pose.pose.pose = ego_odom.pose.pose + recorded_ego_pose.pose.covariance = [ + 0.25, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.25, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.06853892326654787, + ] + + self.recorded_ego_pub.publish(recorded_ego_pose) + print("Published recorded ego pose as /initialpose") + if __name__ == "__main__": parser = argparse.ArgumentParser() diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index 33cf8ca1f16ee..f59270a9740af 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -24,6 +24,7 @@ from autoware_auto_perception_msgs.msg import TrackedObjects from autoware_auto_perception_msgs.msg import TrafficSignalArray as AutoTrafficSignalArray from autoware_perception_msgs.msg import TrafficSignalArray +from geometry_msgs.msg import PoseWithCovarianceStamped from nav_msgs.msg import Odometry import psutil from rclpy.node import Node @@ -66,6 +67,7 @@ def __init__(self, args, name): self.pointcloud_pub = self.create_publisher( PointCloud2, "/perception/obstacle_segmentation/pointcloud", 1 ) + self.recorded_ego_pub = self.create_publisher(PoseWithCovarianceStamped, "/initialpose", 1) # load rosbag print("Stared loading rosbag") diff --git a/planning/planning_debug_tools/scripts/perception_replayer/time_manager_widget.py b/planning/planning_debug_tools/scripts/perception_replayer/time_manager_widget.py index 382e725b114cd..a1d87a8b06266 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/time_manager_widget.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/time_manager_widget.py @@ -89,13 +89,15 @@ def setupUI(self): self.button.setCheckable(True) self.button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.grid_layout.addWidget(self.button, 1, 0, 1, -1) + self.pub_recorded_ego_pose_button = QPushButton("publish recorded ego pose") + self.grid_layout.addWidget(self.pub_recorded_ego_pose_button, 2, 0, 1, -1) # slider self.slider = QJumpSlider(QtCore.Qt.Horizontal, self.max_value) self.slider.setMinimum(0) self.slider.setMaximum(self.max_value) self.slider.setValue(0) - self.grid_layout.addWidget(self.slider, 2, 0, 1, -1) + self.grid_layout.addWidget(self.slider, 3, 0, 1, -1) self.setCentralWidget(self.central_widget) From 85f8db381fee9cd4b5168fa6f15ca9c0bdfd463a Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Sat, 9 Sep 2023 14:13:55 +0900 Subject: [PATCH 157/547] fix(tier4_planning_rviz_plugin): update vehicle info parameters in panel received from global parameter (#4907) Signed-off-by: Takayuki Murooka --- .../tier4_planning_rviz_plugin/include/path/display_base.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/tier4_planning_rviz_plugin/include/path/display_base.hpp b/common/tier4_planning_rviz_plugin/include/path/display_base.hpp index 55c7a9c53d72d..ccedcceefaaf9 100644 --- a/common/tier4_planning_rviz_plugin/include/path/display_base.hpp +++ b/common/tier4_planning_rviz_plugin/include/path/display_base.hpp @@ -506,6 +506,10 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay vehicle_footprint_info_ = std::make_shared( vehicle_info_->vehicle_length_m, vehicle_info_->vehicle_width_m, vehicle_info_->rear_overhang_m); + + property_vehicle_length_.setValue(vehicle_info_->vehicle_length_m); + property_vehicle_width_.setValue(vehicle_info_->vehicle_width_m); + property_rear_overhang_.setValue(vehicle_info_->rear_overhang_m); } else { const float length{property_vehicle_length_.getFloat()}; const float width{property_vehicle_width_.getFloat()}; From 38b9e1c9d9e9593d379e3dd9882b1a363cfadbb4 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Sat, 9 Sep 2023 14:14:14 +0900 Subject: [PATCH 158/547] feat(surround_obstacle_checker): make parameters more tunable (#4925) * feat(surround_obstacle_checker): make parameters more tunable Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka * update param Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../surround_obstacle_checker.param.yaml | 59 +++++- .../debug_marker.hpp | 24 ++- .../surround_obstacle_checker/node.hpp | 25 ++- .../src/debug_marker.cpp | 86 +++++---- .../surround_obstacle_checker/src/node.cpp | 180 ++++++++++++++---- 5 files changed, 282 insertions(+), 92 deletions(-) diff --git a/planning/surround_obstacle_checker/config/surround_obstacle_checker.param.yaml b/planning/surround_obstacle_checker/config/surround_obstacle_checker.param.yaml index 10b5c2814e2a8..5ec10572ffe83 100644 --- a/planning/surround_obstacle_checker/config/surround_obstacle_checker.param.yaml +++ b/planning/surround_obstacle_checker/config/surround_obstacle_checker.param.yaml @@ -1,12 +1,61 @@ /**: ros__parameters: - # obstacle check - use_pointcloud: true # use pointcloud as obstacle check - use_dynamic_object: true # use dynamic object as obstacle check - surround_check_distance: 0.5 # if objects exist in this distance, transit to "exist-surrounding-obstacle" status [m] - surround_check_recover_distance: 0.8 # if no object exists in this distance, transit to "non-surrounding-obstacle" status [m] + # surround_check_*_distance: if objects exist in this distance, transit to "exist-surrounding-obstacle" status [m] + # surround_check_hysteresis_distance: if no object exists in this hysteresis distance added to the above distance, transit to "non-surrounding-obstacle" status [m] + pointcloud: + enable_check: false + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.5 + surround_check_back_distance: 0.5 + unknown: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.5 + surround_check_back_distance: 0.5 + car: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.0 + surround_check_back_distance: 0.5 + truck: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.0 + surround_check_back_distance: 0.5 + bus: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.0 + surround_check_back_distance: 0.5 + trailer: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.0 + surround_check_back_distance: 0.5 + motorcycle: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.0 + surround_check_back_distance: 0.5 + bicycle: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.5 + surround_check_back_distance: 0.5 + pedestrian: + enable_check: true + surround_check_front_distance: 0.5 + surround_check_side_distance: 0.5 + surround_check_back_distance: 0.5 + + surround_check_hysteresis_distance: 0.3 + state_clear_time: 2.0 + # ego stop state + stop_state_ego_speed: 0.1 #[m/s] + # debug publish_debug_footprints: true # publish vehicle footprint & footprints with surround_check_distance and surround_check_recover_distance offsets + debug_footprint_label: "car" diff --git a/planning/surround_obstacle_checker/include/surround_obstacle_checker/debug_marker.hpp b/planning/surround_obstacle_checker/include/surround_obstacle_checker/debug_marker.hpp index bd0fe48bbda28..590f026942801 100644 --- a/planning/surround_obstacle_checker/include/surround_obstacle_checker/debug_marker.hpp +++ b/planning/surround_obstacle_checker/include/surround_obstacle_checker/debug_marker.hpp @@ -16,6 +16,7 @@ #define SURROUND_OBSTACLE_CHECKER__DEBUG_MARKER_HPP_ #include +#include #include #include @@ -38,6 +39,7 @@ using geometry_msgs::msg::PolygonStamped; using tier4_planning_msgs::msg::StopFactor; using tier4_planning_msgs::msg::StopReason; using tier4_planning_msgs::msg::StopReasonArray; +using vehicle_info_util::VehicleInfo; using visualization_msgs::msg::Marker; using visualization_msgs::msg::MarkerArray; @@ -52,15 +54,19 @@ class SurroundObstacleCheckerDebugNode { public: explicit SurroundObstacleCheckerDebugNode( - const Polygon2d & ego_polygon, const double base_link2front, - const double & surround_check_distance, const double & surround_check_recover_distance, - const geometry_msgs::msg::Pose & self_pose, const rclcpp::Clock::SharedPtr clock, - rclcpp::Node & node); + const vehicle_info_util::VehicleInfo & vehicle_info, const double base_link2front, + const std::string & object_label, const double & surround_check_front_distance, + const double & surround_check_side_distance, const double & surround_check_back_distance, + const double & surround_check_hysteresis_distance, const geometry_msgs::msg::Pose & self_pose, + const rclcpp::Clock::SharedPtr clock, rclcpp::Node & node); bool pushPose(const geometry_msgs::msg::Pose & pose, const PoseType & type); bool pushObstaclePoint(const geometry_msgs::msg::Point & obstacle_point, const PointType & type); void publish(); void publishFootprints(); + void updateFootprintMargin( + const std::string & object_label, const double front_distance, const double side_distance, + const double back_distance); private: rclcpp::Publisher::SharedPtr debug_virtual_wall_pub_; @@ -72,10 +78,13 @@ class SurroundObstacleCheckerDebugNode rclcpp::Publisher::SharedPtr vehicle_footprint_offset_pub_; rclcpp::Publisher::SharedPtr vehicle_footprint_recover_offset_pub_; - Polygon2d ego_polygon_; + vehicle_info_util::VehicleInfo vehicle_info_; double base_link2front_; - double surround_check_distance_; - double surround_check_recover_distance_; + std::string object_label_; + double surround_check_front_distance_; + double surround_check_side_distance_; + double surround_check_back_distance_; + double surround_check_hysteresis_distance_; geometry_msgs::msg::Pose self_pose_; MarkerArray makeVirtualWallMarker(); @@ -83,7 +92,6 @@ class SurroundObstacleCheckerDebugNode StopReasonArray makeStopReasonArray(); VelocityFactorArray makeVelocityFactorArray(); - Polygon2d createSelfPolygonWithOffset(const Polygon2d & base_polygon, const double & offset); PolygonStamped boostPolygonToPolygonStamped(const Polygon2d & boost_polygon, const double & z); std::shared_ptr stop_obstacle_point_ptr_; diff --git a/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp b/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp index de3e4bf120b13..a7c177f202173 100644 --- a/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp +++ b/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp @@ -38,6 +38,7 @@ #include #include +#include #include #include @@ -62,18 +63,27 @@ class SurroundObstacleCheckerNode : public rclcpp::Node struct NodeParam { - bool use_pointcloud; - bool use_dynamic_object; bool is_surround_obstacle; - // For preventing chattering, - // surround_check_recover_distance_ must be bigger than surround_check_distance_ - double surround_check_recover_distance; - double surround_check_distance; + std::unordered_map enable_check_map; + std::unordered_map surround_check_front_distance_map; + std::unordered_map surround_check_side_distance_map; + std::unordered_map surround_check_back_distance_map; + bool pointcloud_enable_check; + double pointcloud_surround_check_front_distance; + double pointcloud_surround_check_side_distance; + double pointcloud_surround_check_back_distance; + double surround_check_hysteresis_distance; double state_clear_time; bool publish_debug_footprints; + std::string debug_footprint_label; }; private: + rcl_interfaces::msg::SetParametersResult onParam( + const std::vector & parameters); + + std::array getCheckDistances(const std::string & str_label) const; + void onTimer(); void onPointCloud(const sensor_msgs::msg::PointCloud2::ConstSharedPtr msg); @@ -107,6 +117,9 @@ class SurroundObstacleCheckerNode : public rclcpp::Node rclcpp::Publisher::SharedPtr pub_clear_velocity_limit_; rclcpp::Publisher::SharedPtr pub_velocity_limit_; + // parameter callback result + OnSetParametersCallbackHandle::SharedPtr set_param_res_; + // stop checker std::unique_ptr vehicle_stop_checker_; diff --git a/planning/surround_obstacle_checker/src/debug_marker.cpp b/planning/surround_obstacle_checker/src/debug_marker.cpp index c30f778584fd7..79c2300be3941 100644 --- a/planning/surround_obstacle_checker/src/debug_marker.cpp +++ b/planning/surround_obstacle_checker/src/debug_marker.cpp @@ -26,6 +26,29 @@ namespace surround_obstacle_checker { +namespace +{ +Polygon2d createSelfPolygon( + const VehicleInfo & vehicle_info, const double front_margin = 0.0, const double side_margin = 0.0, + const double rear_margin = 0.0) +{ + const double & front_m = vehicle_info.max_longitudinal_offset_m + front_margin; + const double & width_left_m = vehicle_info.max_lateral_offset_m + side_margin; + const double & width_right_m = vehicle_info.min_lateral_offset_m - side_margin; + const double & rear_m = vehicle_info.min_longitudinal_offset_m - rear_margin; + + Polygon2d ego_polygon; + + ego_polygon.outer().push_back(Point2d(front_m, width_left_m)); + ego_polygon.outer().push_back(Point2d(front_m, width_right_m)); + ego_polygon.outer().push_back(Point2d(rear_m, width_right_m)); + ego_polygon.outer().push_back(Point2d(rear_m, width_left_m)); + + bg::correct(ego_polygon); + + return ego_polygon; +} +} // namespace using motion_utils::createStopVirtualWallMarker; using tier4_autoware_utils::appendMarkerArray; @@ -36,14 +59,18 @@ using tier4_autoware_utils::createMarkerScale; using tier4_autoware_utils::createPoint; SurroundObstacleCheckerDebugNode::SurroundObstacleCheckerDebugNode( - const Polygon2d & ego_polygon, const double base_link2front, - const double & surround_check_distance, const double & surround_check_recover_distance, - const geometry_msgs::msg::Pose & self_pose, const rclcpp::Clock::SharedPtr clock, - rclcpp::Node & node) -: ego_polygon_(ego_polygon), + const vehicle_info_util::VehicleInfo & vehicle_info, const double base_link2front, + const std::string & object_label, const double & surround_check_front_distance, + const double & surround_check_side_distance, const double & surround_check_back_distance, + const double & surround_check_hysteresis_distance, const geometry_msgs::msg::Pose & self_pose, + const rclcpp::Clock::SharedPtr clock, rclcpp::Node & node) +: vehicle_info_(vehicle_info), base_link2front_(base_link2front), - surround_check_distance_(surround_check_distance), - surround_check_recover_distance_(surround_check_recover_distance), + object_label_(object_label), + surround_check_front_distance_(surround_check_front_distance), + surround_check_side_distance_(surround_check_side_distance), + surround_check_back_distance_(surround_check_back_distance), + surround_check_hysteresis_distance_(surround_check_hysteresis_distance), self_pose_(self_pose), clock_(clock) { @@ -86,20 +113,25 @@ bool SurroundObstacleCheckerDebugNode::pushObstaclePoint( void SurroundObstacleCheckerDebugNode::publishFootprints() { + const auto ego_polygon = createSelfPolygon(vehicle_info_); + /* publish vehicle footprint polygon */ - const auto footprint = boostPolygonToPolygonStamped(ego_polygon_, self_pose_.position.z); + const auto footprint = boostPolygonToPolygonStamped(ego_polygon, self_pose_.position.z); vehicle_footprint_pub_->publish(footprint); /* publish vehicle footprint polygon with offset */ - const auto polygon_with_offset = - createSelfPolygonWithOffset(ego_polygon_, surround_check_distance_); + const auto polygon_with_offset = createSelfPolygon( + vehicle_info_, surround_check_front_distance_, surround_check_side_distance_, + surround_check_back_distance_); const auto footprint_with_offset = boostPolygonToPolygonStamped(polygon_with_offset, self_pose_.position.z); vehicle_footprint_offset_pub_->publish(footprint_with_offset); /* publish vehicle footprint polygon with recover offset */ - const auto polygon_with_recover_offset = - createSelfPolygonWithOffset(ego_polygon_, surround_check_recover_distance_); + const auto polygon_with_recover_offset = createSelfPolygon( + vehicle_info_, surround_check_front_distance_ + surround_check_hysteresis_distance_, + surround_check_side_distance_ + surround_check_hysteresis_distance_, + surround_check_back_distance_ + surround_check_hysteresis_distance_); const auto footprint_with_recover_offset = boostPolygonToPolygonStamped(polygon_with_recover_offset, self_pose_.position.z); vehicle_footprint_recover_offset_pub_->publish(footprint_with_recover_offset); @@ -206,26 +238,6 @@ VelocityFactorArray SurroundObstacleCheckerDebugNode::makeVelocityFactorArray() return velocity_factor_array; } -Polygon2d SurroundObstacleCheckerDebugNode::createSelfPolygonWithOffset( - const Polygon2d & base_polygon, const double & offset) -{ - typedef double coordinate_type; - const double buffer_distance = offset; - const int points_per_circle = 36; - boost::geometry::strategy::buffer::distance_symmetric distance_strategy( - buffer_distance); - boost::geometry::strategy::buffer::join_round join_strategy(points_per_circle); - boost::geometry::strategy::buffer::end_round end_strategy(points_per_circle); - boost::geometry::strategy::buffer::point_circle circle_strategy(points_per_circle); - boost::geometry::strategy::buffer::side_straight side_strategy; - boost::geometry::model::multi_polygon result; - // Create the buffer of a multi polygon - boost::geometry::buffer( - base_polygon, result, distance_strategy, side_strategy, join_strategy, end_strategy, - circle_strategy); - return result.front(); -} - PolygonStamped SurroundObstacleCheckerDebugNode::boostPolygonToPolygonStamped( const Polygon2d & boost_polygon, const double & z) { @@ -244,4 +256,14 @@ PolygonStamped SurroundObstacleCheckerDebugNode::boostPolygonToPolygonStamped( return polygon_stamped; } +void SurroundObstacleCheckerDebugNode::updateFootprintMargin( + const std::string & object_label, const double front_distance, const double side_distance, + const double back_distance) +{ + object_label_ = object_label; + surround_check_front_distance_ = front_distance; + surround_check_side_distance_ = side_distance; + surround_check_back_distance_ = back_distance; +} + } // namespace surround_obstacle_checker diff --git a/planning/surround_obstacle_checker/src/node.cpp b/planning/surround_obstacle_checker/src/node.cpp index 17a47be8c0596..4ea3e81d65411 100644 --- a/planning/surround_obstacle_checker/src/node.cpp +++ b/planning/surround_obstacle_checker/src/node.cpp @@ -47,6 +47,7 @@ namespace surround_obstacle_checker namespace bg = boost::geometry; using Point2d = bg::model::d2::point_xy; using Polygon2d = bg::model::polygon; +using autoware_auto_perception_msgs::msg::ObjectClassification; using tier4_autoware_utils::createPoint; using tier4_autoware_utils::pose2transform; @@ -120,12 +121,14 @@ Polygon2d createObjPolygon( return createObjPolygon(pose, polygon); } -Polygon2d createSelfPolygon(const VehicleInfo & vehicle_info) +Polygon2d createSelfPolygon( + const VehicleInfo & vehicle_info, const double front_margin, const double side_margin, + const double rear_margin) { - const double & front_m = vehicle_info.max_longitudinal_offset_m; - const double & width_left_m = vehicle_info.max_lateral_offset_m; - const double & width_right_m = vehicle_info.min_lateral_offset_m; - const double & rear_m = vehicle_info.min_longitudinal_offset_m; + const double & front_m = vehicle_info.max_longitudinal_offset_m + front_margin; + const double & width_left_m = vehicle_info.max_lateral_offset_m + side_margin; + const double & width_right_m = vehicle_info.min_lateral_offset_m - side_margin; + const double & rear_m = vehicle_info.min_longitudinal_offset_m - rear_margin; Polygon2d ego_polygon; @@ -146,13 +149,42 @@ SurroundObstacleCheckerNode::SurroundObstacleCheckerNode(const rclcpp::NodeOptio // Parameters { auto & p = node_param_; - p.use_pointcloud = this->declare_parameter("use_pointcloud"); - p.use_dynamic_object = this->declare_parameter("use_dynamic_object"); - p.surround_check_distance = this->declare_parameter("surround_check_distance"); - p.surround_check_recover_distance = - this->declare_parameter("surround_check_recover_distance"); + + // for object label + std::unordered_map label_map{ + {"unknown", ObjectClassification::UNKNOWN}, {"car", ObjectClassification::CAR}, + {"truck", ObjectClassification::TRUCK}, {"bus", ObjectClassification::BUS}, + {"trailer", ObjectClassification::TRAILER}, {"motorcycle", ObjectClassification::MOTORCYCLE}, + {"bicycle", ObjectClassification::BICYCLE}, {"pedestrian", ObjectClassification::PEDESTRIAN}}; + for (const auto & label_pair : label_map) { + p.enable_check_map.emplace( + label_pair.second, this->declare_parameter(label_pair.first + ".enable_check")); + p.surround_check_front_distance_map.emplace( + label_pair.second, + this->declare_parameter(label_pair.first + ".surround_check_front_distance")); + p.surround_check_side_distance_map.emplace( + label_pair.second, + this->declare_parameter(label_pair.first + ".surround_check_side_distance")); + p.surround_check_back_distance_map.emplace( + label_pair.second, + this->declare_parameter(label_pair.first + ".surround_check_back_distance")); + } + + // for pointcloud + p.pointcloud_enable_check = this->declare_parameter("pointcloud.enable_check"); + p.pointcloud_surround_check_front_distance = + this->declare_parameter("pointcloud.surround_check_front_distance"); + p.pointcloud_surround_check_side_distance = + this->declare_parameter("pointcloud.surround_check_side_distance"); + p.pointcloud_surround_check_back_distance = + this->declare_parameter("pointcloud.surround_check_back_distance"); + + p.surround_check_hysteresis_distance = + this->declare_parameter("surround_check_hysteresis_distance"); + p.state_clear_time = this->declare_parameter("state_clear_time"); p.publish_debug_footprints = this->declare_parameter("publish_debug_footprints"); + p.debug_footprint_label = this->declare_parameter("debug_footprint_label"); } vehicle_info_ = vehicle_info_util::VehicleInfoUtil(*this).getVehicleInfo(); @@ -176,6 +208,10 @@ SurroundObstacleCheckerNode::SurroundObstacleCheckerNode(const rclcpp::NodeOptio "~/input/odometry", 1, std::bind(&SurroundObstacleCheckerNode::onOdometry, this, std::placeholders::_1)); + // Parameter callback + set_param_res_ = this->add_on_set_parameters_callback( + std::bind(&SurroundObstacleCheckerNode::onParam, this, std::placeholders::_1)); + using std::chrono_literals::operator""ms; timer_ = rclcpp::create_timer( this, get_clock(), 100ms, std::bind(&SurroundObstacleCheckerNode::onTimer, this)); @@ -184,15 +220,55 @@ SurroundObstacleCheckerNode::SurroundObstacleCheckerNode(const rclcpp::NodeOptio vehicle_stop_checker_ = std::make_unique(this); // Debug - auto const self_polygon = createSelfPolygon(vehicle_info_); odometry_ptr_ = std::make_shared(); + const auto check_distances = getCheckDistances(node_param_.debug_footprint_label); debug_ptr_ = std::make_shared( - self_polygon, vehicle_info_.max_longitudinal_offset_m, node_param_.surround_check_distance, - node_param_.surround_check_recover_distance, odometry_ptr_->pose.pose, this->get_clock(), + vehicle_info_, vehicle_info_.max_longitudinal_offset_m, node_param_.debug_footprint_label, + check_distances.at(0), check_distances.at(1), check_distances.at(2), + node_param_.surround_check_hysteresis_distance, odometry_ptr_->pose.pose, this->get_clock(), *this); } +std::array SurroundObstacleCheckerNode::getCheckDistances( + const std::string & str_label) const +{ + if (str_label == "pointcloud") { + return { + node_param_.pointcloud_surround_check_front_distance, + node_param_.pointcloud_surround_check_side_distance, + node_param_.pointcloud_surround_check_back_distance}; + } + + std::unordered_map label_map{ + {"unknown", ObjectClassification::UNKNOWN}, {"car", ObjectClassification::CAR}, + {"truck", ObjectClassification::TRUCK}, {"bus", ObjectClassification::BUS}, + {"trailer", ObjectClassification::TRAILER}, {"motorcycle", ObjectClassification::MOTORCYCLE}, + {"bicycle", ObjectClassification::BICYCLE}, {"pedestrian", ObjectClassification::PEDESTRIAN}}; + + const int int_label = label_map.at(str_label); + return { + node_param_.surround_check_front_distance_map.at(int_label), + node_param_.surround_check_side_distance_map.at(int_label), + node_param_.surround_check_back_distance_map.at(int_label)}; +} + +rcl_interfaces::msg::SetParametersResult SurroundObstacleCheckerNode::onParam( + const std::vector & parameters) +{ + tier4_autoware_utils::updateParam( + parameters, "debug_footprint_label", node_param_.debug_footprint_label); + const auto check_distances = getCheckDistances(node_param_.debug_footprint_label); + debug_ptr_->updateFootprintMargin( + node_param_.debug_footprint_label, check_distances.at(0), check_distances.at(1), + check_distances.at(2)); + + rcl_interfaces::msg::SetParametersResult result; + result.successful = true; + result.reason = "success"; + return result; +} + void SurroundObstacleCheckerNode::onTimer() { if (!odometry_ptr_) { @@ -205,13 +281,22 @@ void SurroundObstacleCheckerNode::onTimer() debug_ptr_->publishFootprints(); } - if (node_param_.use_pointcloud && !pointcloud_ptr_) { + if (node_param_.pointcloud_enable_check && !pointcloud_ptr_) { RCLCPP_INFO_THROTTLE( this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for pointcloud info..."); return; } - if (node_param_.use_dynamic_object && !object_ptr_) { + const bool use_dynamic_object = + node_param_.enable_check_map.at(ObjectClassification::UNKNOWN) || + node_param_.enable_check_map.at(ObjectClassification::CAR) || + node_param_.enable_check_map.at(ObjectClassification::TRUCK) || + node_param_.enable_check_map.at(ObjectClassification::BUS) || + node_param_.enable_check_map.at(ObjectClassification::TRAILER) || + node_param_.enable_check_map.at(ObjectClassification::MOTORCYCLE) || + node_param_.enable_check_map.at(ObjectClassification::BICYCLE) || + node_param_.enable_check_map.at(ObjectClassification::PEDESTRIAN); + if (use_dynamic_object && !object_ptr_) { RCLCPP_INFO_THROTTLE( this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for dynamic object info..."); return; @@ -220,11 +305,11 @@ void SurroundObstacleCheckerNode::onTimer() const auto nearest_obstacle = getNearestObstacle(); const auto is_vehicle_stopped = vehicle_stop_checker_->isVehicleStopped(); + constexpr double epsilon = 1e-3; switch (state_) { case State::PASS: { const auto is_obstacle_found = - !nearest_obstacle ? false - : nearest_obstacle.get().first < node_param_.surround_check_distance; + !nearest_obstacle ? false : nearest_obstacle.get().first < epsilon; if (!isStopRequired(is_obstacle_found, is_vehicle_stopped)) { break; @@ -250,7 +335,7 @@ void SurroundObstacleCheckerNode::onTimer() const auto is_obstacle_found = !nearest_obstacle ? false - : nearest_obstacle.get().first < node_param_.surround_check_recover_distance; + : nearest_obstacle.get().first < node_param_.surround_check_hysteresis_distance; if (isStopRequired(is_obstacle_found, is_vehicle_stopped)) { break; @@ -304,17 +389,8 @@ void SurroundObstacleCheckerNode::onOdometry(const nav_msgs::msg::Odometry::Cons boost::optional SurroundObstacleCheckerNode::getNearestObstacle() const { - boost::optional nearest_pointcloud{boost::none}; - boost::optional nearest_object{boost::none}; - - if (node_param_.use_pointcloud) { - nearest_pointcloud = getNearestObstacleByPointCloud(); - } - - if (node_param_.use_dynamic_object) { - nearest_object = getNearestObstacleByDynamicObject(); - } - + const auto nearest_pointcloud = getNearestObstacleByPointCloud(); + const auto nearest_object = getNearestObstacleByDynamicObject(); if (!nearest_pointcloud && !nearest_object) { return {}; } @@ -333,15 +409,13 @@ boost::optional SurroundObstacleCheckerNode::getNearestObstacle() cons boost::optional SurroundObstacleCheckerNode::getNearestObstacleByPointCloud() const { - if (pointcloud_ptr_->data.empty()) { + if (!node_param_.pointcloud_enable_check || pointcloud_ptr_->data.empty()) { return boost::none; } + const auto transform_stamped = getTransform("base_link", pointcloud_ptr_->header.frame_id, pointcloud_ptr_->header.stamp, 0.5); - geometry_msgs::msg::Point nearest_point; - auto minimum_distance = std::numeric_limits::max(); - if (!transform_stamped) { return {}; } @@ -352,8 +426,14 @@ boost::optional SurroundObstacleCheckerNode::getNearestObstacleByPoint tier4_autoware_utils::transformPointCloud( transformed_pointcloud, transformed_pointcloud, isometry); - const auto ego_polygon = createSelfPolygon(vehicle_info_); + const double front_margin = node_param_.pointcloud_surround_check_front_distance; + const double side_margin = node_param_.pointcloud_surround_check_side_distance; + const double back_margin = node_param_.pointcloud_surround_check_back_distance; + const auto ego_polygon = createSelfPolygon(vehicle_info_, front_margin, side_margin, back_margin); + geometry_msgs::msg::Point nearest_point; + double minimum_distance = std::numeric_limits::max(); + bool was_minimum_distance_updated = false; for (const auto & p : transformed_pointcloud) { Point2d boost_point(p.x, p.y); @@ -362,10 +442,14 @@ boost::optional SurroundObstacleCheckerNode::getNearestObstacleByPoint if (distance_to_object < minimum_distance) { nearest_point = createPoint(p.x, p.y, p.z); minimum_distance = distance_to_object; + was_minimum_distance_updated = true; } } - return std::make_pair(minimum_distance, nearest_point); + if (was_minimum_distance_updated) { + return std::make_pair(minimum_distance, nearest_point); + } + return boost::none; } boost::optional SurroundObstacleCheckerNode::getNearestObstacleByDynamicObject() const @@ -373,9 +457,6 @@ boost::optional SurroundObstacleCheckerNode::getNearestObstacleByDynam const auto transform_stamped = getTransform(object_ptr_->header.frame_id, "base_link", object_ptr_->header.stamp, 0.5); - geometry_msgs::msg::Point nearest_point; - auto minimum_distance = std::numeric_limits::max(); - if (!transform_stamped) { return {}; } @@ -383,10 +464,23 @@ boost::optional SurroundObstacleCheckerNode::getNearestObstacleByDynam tf2::Transform tf_src2target; tf2::fromMsg(transform_stamped.get().transform, tf_src2target); - const auto ego_polygon = createSelfPolygon(vehicle_info_); - + // TODO(murooka) check computation cost + geometry_msgs::msg::Point nearest_point; + double minimum_distance = std::numeric_limits::max(); + bool was_minimum_distance_updated = false; for (const auto & object : object_ptr_->objects) { const auto & object_pose = object.kinematics.initial_pose_with_covariance.pose; + const int label = object.classification.front().label; + + if (!node_param_.enable_check_map.at(label)) { + continue; + } + + const double front_margin = node_param_.surround_check_front_distance_map.at(label); + const double side_margin = node_param_.surround_check_side_distance_map.at(label); + const double back_margin = node_param_.surround_check_back_distance_map.at(label); + const auto ego_polygon = + createSelfPolygon(vehicle_info_, front_margin, side_margin, back_margin); tf2::Transform tf_src2object; tf2::fromMsg(object_pose, tf_src2object); @@ -404,10 +498,14 @@ boost::optional SurroundObstacleCheckerNode::getNearestObstacleByDynam if (distance_to_object < minimum_distance) { nearest_point = object_pose.position; minimum_distance = distance_to_object; + was_minimum_distance_updated = true; } } - return std::make_pair(minimum_distance, nearest_point); + if (was_minimum_distance_updated) { + return std::make_pair(minimum_distance, nearest_point); + } + return boost::none; } boost::optional SurroundObstacleCheckerNode::getTransform( From 94838a90f71ab8d7db82e8d0bbd2db69eb02ee83 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Sun, 10 Sep 2023 01:22:43 +0900 Subject: [PATCH 159/547] chore(build): remove tier4_autoware_utils.hpp/motion_utils.hpp from freespace/route_handler (#4934) remove tier4_autoware_utils.hpp/motion_utils.hpp from freespace/route_handler Signed-off-by: Mamoru Sobue --- .../include/behavior_path_planner/data_manager.hpp | 1 + .../drivable_area_expansion/drivable_area_expansion.cpp | 3 +++ planning/behavior_path_planner/test/test_turn_signal.cpp | 4 ++++ planning/behavior_velocity_out_of_lane_module/src/debug.cpp | 2 ++ .../behavior_velocity_out_of_lane_module/src/decisions.cpp | 4 ++++ planning/freespace_planner/package.xml | 1 + .../src/freespace_planner/freespace_planner_node.cpp | 3 ++- .../src/abstract_algorithm.cpp | 3 ++- planning/freespace_planning_algorithms/src/astar_search.cpp | 3 ++- .../src/lanelet2_plugins/default_planner.cpp | 6 +++++- .../src/lanelet2_plugins/utility_functions.hpp | 2 -- .../mission_planner/src/mission_planner/arrival_checker.cpp | 4 +++- .../route_handler/include/route_handler/route_handler.hpp | 2 -- planning/route_handler/package.xml | 1 - planning/route_handler/src/route_handler.cpp | 1 + .../include/static_centerline_optimizer/type_alias.hpp | 1 + 16 files changed, 31 insertions(+), 10 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp index 9217e139d218c..a144aa12704a8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp @@ -19,6 +19,7 @@ #include "behavior_path_planner/turn_signal_decider.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index 0fcb0acfcb000..52be0ac5a81ef 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -22,7 +22,10 @@ #include #include +#include +#include #include +#include #include diff --git a/planning/behavior_path_planner/test/test_turn_signal.cpp b/planning/behavior_path_planner/test/test_turn_signal.cpp index 8c6f8de7ee6a0..a48c8a6b0d976 100644 --- a/planning/behavior_path_planner/test/test_turn_signal.cpp +++ b/planning/behavior_path_planner/test/test_turn_signal.cpp @@ -13,6 +13,10 @@ // limitations under the License. #include "behavior_path_planner/turn_signal_decider.hpp" +#include +#include +#include + #include "autoware_auto_planning_msgs/msg/path_point.hpp" #include diff --git a/planning/behavior_velocity_out_of_lane_module/src/debug.cpp b/planning/behavior_velocity_out_of_lane_module/src/debug.cpp index 475e341d76528..0574a5226dc4a 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/debug.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/debug.cpp @@ -14,6 +14,8 @@ #include "debug.hpp" +#include + #include namespace behavior_velocity_planner::out_of_lane::debug diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp index 46db7a53b5c81..2ebfa4ecbe431 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp @@ -15,6 +15,10 @@ #include "decisions.hpp" #include +#include +#include +#include +#include #include #include diff --git a/planning/freespace_planner/package.xml b/planning/freespace_planner/package.xml index a748b7f80c9ad..f92c0a449970d 100644 --- a/planning/freespace_planner/package.xml +++ b/planning/freespace_planner/package.xml @@ -19,6 +19,7 @@ autoware_auto_planning_msgs freespace_planning_algorithms geometry_msgs + motion_utils nav_msgs planning_test_utils rclcpp diff --git a/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp b/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp index e0d7f86e45245..025cc32d4ed24 100644 --- a/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp +++ b/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp @@ -32,7 +32,8 @@ #include "freespace_planning_algorithms/abstract_algorithm.hpp" -#include +#include +#include #include #include diff --git a/planning/freespace_planning_algorithms/src/abstract_algorithm.cpp b/planning/freespace_planning_algorithms/src/abstract_algorithm.cpp index 159c0df82fcc3..2c7340072eb53 100644 --- a/planning/freespace_planning_algorithms/src/abstract_algorithm.cpp +++ b/planning/freespace_planning_algorithms/src/abstract_algorithm.cpp @@ -14,7 +14,8 @@ #include "freespace_planning_algorithms/abstract_algorithm.hpp" -#include +#include +#include #include diff --git a/planning/freespace_planning_algorithms/src/astar_search.cpp b/planning/freespace_planning_algorithms/src/astar_search.cpp index b2b2321adacd0..1ab5e9d5df487 100644 --- a/planning/freespace_planning_algorithms/src/astar_search.cpp +++ b/planning/freespace_planning_algorithms/src/astar_search.cpp @@ -14,7 +14,8 @@ #include "freespace_planning_algorithms/astar_search.hpp" -#include +#include +#include #include diff --git a/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp b/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp index d8289824e517c..979800aea0754 100644 --- a/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp +++ b/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp @@ -20,7 +20,11 @@ #include #include #include -#include +#include +#include +#include +#include +#include #include #include diff --git a/planning/mission_planner/src/lanelet2_plugins/utility_functions.hpp b/planning/mission_planner/src/lanelet2_plugins/utility_functions.hpp index 0bfb0553e75ef..3ea6237f38501 100644 --- a/planning/mission_planner/src/lanelet2_plugins/utility_functions.hpp +++ b/planning/mission_planner/src/lanelet2_plugins/utility_functions.hpp @@ -15,8 +15,6 @@ #ifndef LANELET2_PLUGINS__UTILITY_FUNCTIONS_HPP_ #define LANELET2_PLUGINS__UTILITY_FUNCTIONS_HPP_ -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - #include #include #include diff --git a/planning/mission_planner/src/mission_planner/arrival_checker.cpp b/planning/mission_planner/src/mission_planner/arrival_checker.cpp index ac7a37acd4d5b..1e9f02ff0338d 100644 --- a/planning/mission_planner/src/mission_planner/arrival_checker.cpp +++ b/planning/mission_planner/src/mission_planner/arrival_checker.cpp @@ -14,7 +14,9 @@ #include "arrival_checker.hpp" -#include +#include +#include +#include #include diff --git a/planning/route_handler/include/route_handler/route_handler.hpp b/planning/route_handler/include/route_handler/route_handler.hpp index 847c8db3b16a9..6bd9756098558 100644 --- a/planning/route_handler/include/route_handler/route_handler.hpp +++ b/planning/route_handler/include/route_handler/route_handler.hpp @@ -16,9 +16,7 @@ #define ROUTE_HANDLER__ROUTE_HANDLER_HPP_ #include -#include #include -#include #include #include diff --git a/planning/route_handler/package.xml b/planning/route_handler/package.xml index ef2051d1b244a..6304e21932676 100644 --- a/planning/route_handler/package.xml +++ b/planning/route_handler/package.xml @@ -24,7 +24,6 @@ autoware_planning_msgs geometry_msgs lanelet2_extension - motion_utils rclcpp rclcpp_components tf2_ros diff --git a/planning/route_handler/src/route_handler.cpp b/planning/route_handler/src/route_handler.cpp index 6a95434c81c23..3d9dd8fe7a64a 100644 --- a/planning/route_handler/src/route_handler.cpp +++ b/planning/route_handler/src/route_handler.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include diff --git a/planning/static_centerline_optimizer/include/static_centerline_optimizer/type_alias.hpp b/planning/static_centerline_optimizer/include/static_centerline_optimizer/type_alias.hpp index 5798c19d0d563..1a700d36deaaf 100644 --- a/planning/static_centerline_optimizer/include/static_centerline_optimizer/type_alias.hpp +++ b/planning/static_centerline_optimizer/include/static_centerline_optimizer/type_alias.hpp @@ -15,6 +15,7 @@ #define STATIC_CENTERLINE_OPTIMIZER__TYPE_ALIAS_HPP_ #include "route_handler/route_handler.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "autoware_auto_mapping_msgs/msg/had_map_bin.hpp" #include "autoware_auto_perception_msgs/msg/predicted_objects.hpp" From 1a30fc3d4b01c16824b9f0147ad9d3851aec5cb3 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Sun, 10 Sep 2023 02:03:35 +0900 Subject: [PATCH 160/547] chore(build): remove tier4_autoware_utils.hpp/motion_utils.hpp from map_based_prediction (#4937) --- .../map_based_prediction/map_based_prediction_node.hpp | 2 -- .../map_based_prediction/src/map_based_prediction_node.cpp | 5 ++++- perception/map_based_prediction/src/path_generator.cpp | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp index 7364e3d11b650..f694892541f4a 100644 --- a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp @@ -20,11 +20,9 @@ #include #include #include -#include #include #include #include -#include #include #include diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 3b4e010d0a855..c1cb9d9dd3910 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -16,7 +16,10 @@ #include #include -#include +#include +#include +#include +#include #include diff --git a/perception/map_based_prediction/src/path_generator.cpp b/perception/map_based_prediction/src/path_generator.cpp index b1f6319801d4a..547132410badf 100644 --- a/perception/map_based_prediction/src/path_generator.cpp +++ b/perception/map_based_prediction/src/path_generator.cpp @@ -15,8 +15,8 @@ #include "map_based_prediction/path_generator.hpp" #include -#include -#include +#include +#include #include From 477ce95a1975773e426458f986b9a8a8c43a89f7 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 10 Sep 2023 15:37:50 +0900 Subject: [PATCH 161/547] fix(start_planner): prevent approval when stop path (#4932) Signed-off-by: kosuke55 --- .../scene_module/start_planner/start_planner_module.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index e8acc77113959..7be05561aa34a 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -141,6 +141,10 @@ bool StartPlannerModule::isExecutionRequested() const bool StartPlannerModule::isExecutionReady() const { + if (!status_.is_safe_static_objects) { + return false; + } + if (status_.pull_out_path.partial_paths.empty()) { return true; } @@ -185,6 +189,7 @@ BehaviorModuleOutput StartPlannerModule::plan() getLogger(), *clock_, 5000, "Start plan for a backward goal is not supported now"); const auto output = generateStopOutput(); setDebugData(); // use status updated in generateStopOutput() + updateRTCStatus(0, 0); return output; } @@ -202,6 +207,7 @@ BehaviorModuleOutput StartPlannerModule::plan() getLogger(), *clock_, 5000, "Not found safe pull out path, publish stop path"); const auto output = generateStopOutput(); setDebugData(); // use status updated in generateStopOutput() + updateRTCStatus(0, 0); return output; } @@ -298,6 +304,7 @@ BehaviorModuleOutput StartPlannerModule::planWaitingApproval() clearWaitingApproval(); const auto output = generateStopOutput(); setDebugData(); // use status updated in generateStopOutput() + updateRTCStatus(0, 0); return output; } @@ -308,6 +315,7 @@ BehaviorModuleOutput StartPlannerModule::planWaitingApproval() clearWaitingApproval(); const auto output = generateStopOutput(); setDebugData(); // use status updated in generateStopOutput() + updateRTCStatus(0, 0); return output; } From a25cbe07468136f255cb0bdf676c86702698ebf5 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 11 Sep 2023 01:31:17 +0900 Subject: [PATCH 162/547] chore(build): remove tier4_autoware_utils.hpp/motion_utils.hpp from behavior_path_planner (#4936) Signed-off-by: Mamoru Sobue --- .../include/behavior_path_planner/data_manager.hpp | 2 +- .../include/behavior_path_planner/marker_utils/utils.hpp | 3 +-- .../include/behavior_path_planner/planner_manager.hpp | 1 + .../scene_module/scene_module_interface.hpp | 5 +++++ .../utils/avoidance/avoidance_module_data.hpp | 3 ++- .../include/behavior_path_planner/utils/utils.hpp | 4 ++-- .../src/behavior_path_planner_node.cpp | 2 +- .../src/marker_utils/avoidance/debug.cpp | 1 + planning/behavior_path_planner/src/marker_utils/utils.cpp | 3 ++- planning/behavior_path_planner/src/planner_manager.cpp | 1 + .../src/scene_module/avoidance/avoidance_module.cpp | 5 ++++- .../src/scene_module/avoidance/manager.cpp | 3 +++ .../dynamic_avoidance/dynamic_avoidance_module.cpp | 2 +- .../src/scene_module/dynamic_avoidance/manager.cpp | 2 ++ .../src/scene_module/goal_planner/goal_planner_module.cpp | 3 +-- .../src/scene_module/goal_planner/manager.cpp | 2 +- .../src/scene_module/lane_change/manager.cpp | 3 +++ .../src/scene_module/lane_change/normal.cpp | 1 + .../src/scene_module/side_shift/manager.cpp | 2 ++ .../src/scene_module/start_planner/manager.cpp | 2 ++ .../src/scene_module/start_planner/start_planner_module.cpp | 1 - planning/behavior_path_planner/src/turn_signal_decider.cpp | 6 +++++- .../behavior_path_planner/src/utils/avoidance/utils.cpp | 6 +++++- .../geometric_parallel_parking.cpp | 3 ++- .../src/utils/goal_planner/freespace_pull_over.cpp | 1 + .../src/utils/goal_planner/geometric_pull_over.cpp | 1 + .../src/utils/goal_planner/goal_searcher.cpp | 2 ++ .../src/utils/goal_planner/shift_pull_over.cpp | 1 + .../behavior_path_planner/src/utils/lane_change/utils.cpp | 3 +++ .../occupancy_grid_based_collision_detector.cpp | 2 +- .../src/utils/path_safety_checker/objects_filtering.cpp | 4 ++++ .../src/utils/path_safety_checker/safety_check.cpp | 2 ++ .../src/utils/path_shifter/path_shifter.cpp | 2 +- planning/behavior_path_planner/src/utils/path_utils.cpp | 4 +++- .../src/utils/start_goal_planner_common/utils.cpp | 2 ++ .../src/utils/start_planner/geometric_pull_out.cpp | 1 + .../src/utils/start_planner/shift_pull_out.cpp | 1 + .../behavior_path_planner/src/utils/start_planner/util.cpp | 1 + planning/behavior_path_planner/src/utils/utils.cpp | 3 +++ .../behavior_path_planner/test/test_lane_change_utils.cpp | 4 +++- planning/behavior_path_planner/test/test_safety_check.cpp | 3 ++- planning/behavior_path_planner/test/test_turn_signal.cpp | 2 ++ planning/behavior_path_planner/test/test_utils.cpp | 1 + 43 files changed, 84 insertions(+), 22 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp index a144aa12704a8..78172c6fc0750 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp @@ -18,8 +18,8 @@ #include "behavior_path_planner/parameters.hpp" #include "behavior_path_planner/turn_signal_decider.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" +#include "motion_utils/trajectory/trajectory.hpp" -#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index ba8ddd9b19c46..c53735486efb0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -17,9 +17,8 @@ #include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index b77ac16a9db61..4bdfc807aa7c4 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -19,6 +19,7 @@ #include "behavior_path_planner/scene_module/scene_module_manager_interface.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/lane_following/module_data.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp index 6acf5b945308f..077f7a3a763d5 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp @@ -22,10 +22,15 @@ #include #include #include +#include +#include +#include #include #include #include +#include #include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 3264ec1e9ddfd..215a8bfd97cc0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -20,10 +20,11 @@ #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include -#include +#include #include #include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp index 0caa79ba5d062..d5e1d22fdcc88 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp @@ -22,11 +22,11 @@ #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" #include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "object_recognition_utils/predicted_path_utils.hpp" #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index a9b137e54e90f..41df95378c52f 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -19,7 +19,7 @@ #include "behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp" #include "behavior_path_planner/utils/path_utils.hpp" -#include +#include #include #include diff --git a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp index bea1447b0d95a..b2c326fda7a69 100644 --- a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index d6a237079448f..b51fc585ce935 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -19,8 +19,9 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include #include - +#include namespace marker_utils { using behavior_path_planner::ShiftLine; diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index 7708fd8ac116e..ee9c445e9fd2f 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" #include #include diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 3b005c1b2242d..e5374efd0a5b5 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -23,7 +23,10 @@ #include #include -#include +#include +#include +#include +#include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp index 8900e2b3e827c..912f867fd5701 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp @@ -14,6 +14,9 @@ #include "behavior_path_planner/scene_module/avoidance/manager.hpp" +#include "tier4_autoware_utils/ros/parameter.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" + #include #include diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index da98c0478e580..b5ecffbcc9791 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp index 82ebf59ea5bb2..616194ea66ac7 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp @@ -14,6 +14,8 @@ #include "behavior_path_planner/scene_module/dynamic_avoidance/manager.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" + #include #include diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index cfdc968656353..d55b3c173cc83 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -21,13 +21,12 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "tier4_autoware_utils/math/unit_conversion.hpp" #include #include #include -#include #include -#include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index 7e2975a73aa44..ba147a28173c3 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -15,8 +15,8 @@ #include "behavior_path_planner/scene_module/goal_planner/manager.hpp" #include "behavior_path_planner/utils/goal_planner/util.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" -#include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index 27f44433c6d4e..f4a75daa6c764 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -14,6 +14,9 @@ #include "behavior_path_planner/scene_module/lane_change/manager.hpp" +#include "tier4_autoware_utils/ros/parameter.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" + #include #include diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 8deee76656dca..a81f6227e4064 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/side_shift/manager.cpp b/planning/behavior_path_planner/src/scene_module/side_shift/manager.cpp index 5a039a98f2e52..288d9d44aee54 100644 --- a/planning/behavior_path_planner/src/scene_module/side_shift/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/side_shift/manager.cpp @@ -14,6 +14,8 @@ #include "behavior_path_planner/scene_module/side_shift/manager.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" + #include #include diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp index 5f4b57d9c6367..09166cd72a83e 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp @@ -14,6 +14,8 @@ #include "behavior_path_planner/scene_module/start_planner/manager.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" + #include #include diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 7be05561aa34a..afea96a8aa883 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include diff --git a/planning/behavior_path_planner/src/turn_signal_decider.cpp b/planning/behavior_path_planner/src/turn_signal_decider.cpp index fa661aef06681..65e3e8343f49c 100644 --- a/planning/behavior_path_planner/src/turn_signal_decider.cpp +++ b/planning/behavior_path_planner/src/turn_signal_decider.cpp @@ -18,9 +18,13 @@ #include #include +#include #include +#include #include -#include +#include +#include +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index b60abbddf6632..4afe5381daa64 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -17,10 +17,14 @@ #include "behavior_path_planner/utils/avoidance/avoidance_module_data.hpp" #include "behavior_path_planner/utils/avoidance/utils.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include #include -#include +#include +#include +#include #include diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index 99d109dd0bece..5b054de43ef74 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -17,12 +17,13 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/math/unit_conversion.hpp" #include #include #include -#include #ifdef ROS_DISTRO_GALACTIC #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp index 9322350877ad1..5bb4e924b6d89 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp @@ -17,6 +17,7 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" +#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp index ed0106d891bb4..f350262cf4b80 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index 19dec4d3c97d1..2dd81eba7e6c7 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -18,7 +18,9 @@ #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "lanelet2_extension/utility/utilities.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp index aeb28d74e5f95..b7ece14035265 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index f3a1260eecadc..ffd5116a5d2dd 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -25,8 +25,11 @@ #include #include #include +#include +#include #include #include +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.cpp b/planning/behavior_path_planner/src/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.cpp index d244d08abf99b..0f87c68e38289 100644 --- a/planning/behavior_path_planner/src/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.cpp +++ b/planning/behavior_path_planner/src/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.cpp @@ -15,7 +15,7 @@ #include "behavior_path_planner/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.hpp" #include -#include +#include #include diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index 23016b01cb9ab..7205b273ff281 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -16,6 +16,10 @@ #include "behavior_path_planner/utils/utils.hpp" +#include +#include +#include + namespace behavior_path_planner::utils::path_safety_checker { diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 1838dc6bdc07b..dddcc672e7107 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -16,8 +16,10 @@ #include "behavior_path_planner/marker_utils/utils.hpp" #include "interpolation/linear_interpolation.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include "motion_utils/trajectory/trajectory.hpp" #include "object_recognition_utils/predicted_path_utils.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" namespace behavior_path_planner::utils::path_safety_checker { diff --git a/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp b/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp index 9ca083af35c92..44ba6bd364bc4 100644 --- a/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp +++ b/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/path_utils.cpp b/planning/behavior_path_planner/src/utils/path_utils.cpp index c4bfb62276d42..112c7e49c7c86 100644 --- a/planning/behavior_path_planner/src/utils/path_utils.cpp +++ b/planning/behavior_path_planner/src/utils/path_utils.cpp @@ -15,11 +15,13 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include #include -#include +#include +#include #include diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index 830860de484b6..d07a2f54a5e8a 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -14,6 +14,8 @@ #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" +#include + namespace behavior_path_planner::utils::start_goal_planner_common { diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index 628f1cfd7e421..0d75391f9d4a6 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -19,6 +19,7 @@ #include "behavior_path_planner/utils/utils.hpp" #include +#include using lanelet::utils::getArcCoordinates; using motion_utils::findNearestIndex; diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index f76aca793d571..f86f9d3d90a07 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -18,6 +18,7 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include diff --git a/planning/behavior_path_planner/src/utils/start_planner/util.cpp b/planning/behavior_path_planner/src/utils/start_planner/util.cpp index 9ee0491ca017e..f5b4c9b979ee3 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/util.cpp @@ -20,6 +20,7 @@ #include "behavior_path_planner/utils/utils.hpp" #include +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index da23d818cf59a..617d4a224fdd5 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include #include "autoware_auto_perception_msgs/msg/predicted_object.hpp" #include "autoware_auto_perception_msgs/msg/predicted_path.hpp" diff --git a/planning/behavior_path_planner/test/test_lane_change_utils.cpp b/planning/behavior_path_planner/test/test_lane_change_utils.cpp index 3a809509641f4..ad64a7ae3feb5 100644 --- a/planning/behavior_path_planner/test/test_lane_change_utils.cpp +++ b/planning/behavior_path_planner/test/test_lane_change_utils.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. #include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" + +#include +#include #include #include diff --git a/planning/behavior_path_planner/test/test_safety_check.cpp b/planning/behavior_path_planner/test/test_safety_check.cpp index 9dfcef487cc81..39e831bd5565b 100644 --- a/planning/behavior_path_planner/test/test_safety_check.cpp +++ b/planning/behavior_path_planner/test/test_safety_check.cpp @@ -15,7 +15,8 @@ #include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" + +#include #include diff --git a/planning/behavior_path_planner/test/test_turn_signal.cpp b/planning/behavior_path_planner/test/test_turn_signal.cpp index a48c8a6b0d976..4917443064e78 100644 --- a/planning/behavior_path_planner/test/test_turn_signal.cpp +++ b/planning/behavior_path_planner/test/test_turn_signal.cpp @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. #include "behavior_path_planner/turn_signal_decider.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include #include #include "autoware_auto_planning_msgs/msg/path_point.hpp" +#include #include #include diff --git a/planning/behavior_path_planner/test/test_utils.cpp b/planning/behavior_path_planner/test/test_utils.cpp index b9e2727ebaa13..1452e45980793 100644 --- a/planning/behavior_path_planner/test/test_utils.cpp +++ b/planning/behavior_path_planner/test/test_utils.cpp @@ -16,6 +16,7 @@ #include "lanelet2_core/Attribute.h" #include "lanelet2_core/geometry/Point.h" #include "lanelet2_core/primitives/Lanelet.h" +#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include From 96d20f895d1cf2fa31aecbd790405ba472fe61b3 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 11 Sep 2023 13:04:07 +0900 Subject: [PATCH 163/547] chore(build): remove tier4_autoware_utils.hpp/motion_utils.hpp from obstacle_planning (#4939) --- .../include/obstacle_avoidance_planner/common_structs.hpp | 3 ++- .../include/obstacle_avoidance_planner/mpt_optimizer.hpp | 2 +- .../include/obstacle_avoidance_planner/node.hpp | 3 +-- planning/obstacle_avoidance_planner/src/debug_marker.cpp | 1 - planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp | 5 +++-- planning/obstacle_avoidance_planner/src/node.cpp | 1 + .../obstacle_avoidance_planner/src/replan_checker.cpp | 5 +++-- .../src/utils/geometry_utils.cpp | 8 +++++++- .../src/utils/trajectory_utils.cpp | 4 +++- .../include/obstacle_cruise_planner/common_structs.hpp | 8 ++++++-- .../optimization_based_planner.hpp | 1 - .../include/obstacle_cruise_planner/planner_interface.hpp | 5 +++-- .../include/obstacle_cruise_planner/polygon_utils.hpp | 2 +- .../include/obstacle_cruise_planner/utils.hpp | 3 +-- planning/obstacle_cruise_planner/src/node.cpp | 1 + .../optimization_based_planner.cpp | 4 +++- .../src/pid_based_planner/pid_based_planner.cpp | 5 ++++- .../obstacle_cruise_planner/src/planner_interface.cpp | 2 ++ planning/obstacle_cruise_planner/src/utils.cpp | 1 + .../obstacle_stop_planner/adaptive_cruise_control.hpp | 1 - .../include/obstacle_stop_planner/node.hpp | 3 +-- .../include/obstacle_stop_planner/planner_utils.hpp | 3 +-- .../obstacle_stop_planner/src/adaptive_cruise_control.cpp | 2 ++ planning/obstacle_stop_planner/src/debug_marker.cpp | 5 +++-- planning/obstacle_stop_planner/src/planner_utils.cpp | 3 +++ 25 files changed, 53 insertions(+), 28 deletions(-) diff --git a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp index 1b8a40ef8ef02..d0fc995da0ef4 100644 --- a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp +++ b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp @@ -17,7 +17,8 @@ #include "obstacle_avoidance_planner/type_alias.hpp" #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" #include #include diff --git a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/mpt_optimizer.hpp b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/mpt_optimizer.hpp index ea2e18b099f1d..b87cfc7a1e743 100644 --- a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/mpt_optimizer.hpp +++ b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/mpt_optimizer.hpp @@ -22,7 +22,7 @@ #include "obstacle_avoidance_planner/state_equation_generator.hpp" #include "obstacle_avoidance_planner/type_alias.hpp" #include "osqp_interface/osqp_interface.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include diff --git a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp index f716f497b90da..14925a8facf88 100644 --- a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp +++ b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp @@ -15,13 +15,12 @@ #ifndef OBSTACLE_AVOIDANCE_PLANNER__NODE_HPP_ #define OBSTACLE_AVOIDANCE_PLANNER__NODE_HPP_ -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_avoidance_planner/common_structs.hpp" #include "obstacle_avoidance_planner/mpt_optimizer.hpp" #include "obstacle_avoidance_planner/replan_checker.hpp" #include "obstacle_avoidance_planner/type_alias.hpp" #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include diff --git a/planning/obstacle_avoidance_planner/src/debug_marker.cpp b/planning/obstacle_avoidance_planner/src/debug_marker.cpp index 79f3dfd2fe2e4..8bf46c697104a 100644 --- a/planning/obstacle_avoidance_planner/src/debug_marker.cpp +++ b/planning/obstacle_avoidance_planner/src/debug_marker.cpp @@ -14,7 +14,6 @@ #include "obstacle_avoidance_planner/debug_marker.hpp" -#include "motion_utils/motion_utils.hpp" #include "obstacle_avoidance_planner/mpt_optimizer.hpp" #include "visualization_msgs/msg/marker_array.hpp" diff --git a/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp b/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp index 0a55cc8d91f8e..359485fadcfff 100644 --- a/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp +++ b/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp @@ -15,11 +15,12 @@ #include "obstacle_avoidance_planner/mpt_optimizer.hpp" #include "interpolation/spline_interpolation_points_2d.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_avoidance_planner/utils/geometry_utils.hpp" #include "obstacle_avoidance_planner/utils/trajectory_utils.hpp" #include "tf2/utils.h" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/math/normalization.hpp" #include #include diff --git a/planning/obstacle_avoidance_planner/src/node.cpp b/planning/obstacle_avoidance_planner/src/node.cpp index 6c4730e9d86f9..b5dd0e91a541e 100644 --- a/planning/obstacle_avoidance_planner/src/node.cpp +++ b/planning/obstacle_avoidance_planner/src/node.cpp @@ -15,6 +15,7 @@ #include "obstacle_avoidance_planner/node.hpp" #include "interpolation/spline_interpolation_points_2d.hpp" +#include "motion_utils/marker/marker_helper.hpp" #include "obstacle_avoidance_planner/debug_marker.hpp" #include "obstacle_avoidance_planner/utils/geometry_utils.hpp" #include "obstacle_avoidance_planner/utils/trajectory_utils.hpp" diff --git a/planning/obstacle_avoidance_planner/src/replan_checker.cpp b/planning/obstacle_avoidance_planner/src/replan_checker.cpp index 8d281ced9a5ac..6d7c6e702471a 100644 --- a/planning/obstacle_avoidance_planner/src/replan_checker.cpp +++ b/planning/obstacle_avoidance_planner/src/replan_checker.cpp @@ -14,9 +14,10 @@ #include "obstacle_avoidance_planner/replan_checker.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_avoidance_planner/utils/trajectory_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" #include diff --git a/planning/obstacle_avoidance_planner/src/utils/geometry_utils.cpp b/planning/obstacle_avoidance_planner/src/utils/geometry_utils.cpp index 67b500571fa06..3f35de9147e6a 100644 --- a/planning/obstacle_avoidance_planner/src/utils/geometry_utils.cpp +++ b/planning/obstacle_avoidance_planner/src/utils/geometry_utils.cpp @@ -14,15 +14,21 @@ #include "obstacle_avoidance_planner/utils/geometry_utils.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_avoidance_planner/mpt_optimizer.hpp" #include "tf2/utils.h" +#include + #include "autoware_auto_planning_msgs/msg/path_point.hpp" #include "autoware_auto_planning_msgs/msg/trajectory_point.hpp" #include "geometry_msgs/msg/point32.hpp" #include "geometry_msgs/msg/pose.hpp" +#include +#include +#include + #include #include #include diff --git a/planning/obstacle_avoidance_planner/src/utils/trajectory_utils.cpp b/planning/obstacle_avoidance_planner/src/utils/trajectory_utils.cpp index 8390cf379586b..e86782c9cb0cf 100644 --- a/planning/obstacle_avoidance_planner/src/utils/trajectory_utils.cpp +++ b/planning/obstacle_avoidance_planner/src/utils/trajectory_utils.cpp @@ -14,7 +14,9 @@ #include "obstacle_avoidance_planner/utils/trajectory_utils.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/resample/resample.hpp" +#include "motion_utils/trajectory/tmp_conversion.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_avoidance_planner/mpt_optimizer.hpp" #include "obstacle_avoidance_planner/utils/geometry_utils.hpp" #include "tf2/utils.h" diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp index 12ebadf770996..9b7d318879e5f 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp @@ -15,9 +15,13 @@ #ifndef OBSTACLE_CRUISE_PLANNER__COMMON_STRUCTS_HPP_ #define OBSTACLE_CRUISE_PLANNER__COMMON_STRUCTS_HPP_ -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/interpolation.hpp" +#include "motion_utils/trajectory/tmp_conversion.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_cruise_planner/type_alias.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" +#include "tier4_autoware_utils/ros/uuid_helper.hpp" #include diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/optimization_based_planner/optimization_based_planner.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/optimization_based_planner/optimization_based_planner.hpp index 55a97ccc2785d..44626206ec038 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/optimization_based_planner/optimization_based_planner.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/optimization_based_planner/optimization_based_planner.hpp @@ -19,7 +19,6 @@ #include "obstacle_cruise_planner/optimization_based_planner/velocity_optimizer.hpp" #include "obstacle_cruise_planner/planner_interface.hpp" #include "obstacle_cruise_planner/type_alias.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp index c3fa364da269e..8d67a89e369e9 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp @@ -15,12 +15,13 @@ #ifndef OBSTACLE_CRUISE_PLANNER__PLANNER_INTERFACE_HPP_ #define OBSTACLE_CRUISE_PLANNER__PLANNER_INTERFACE_HPP_ -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_cruise_planner/common_structs.hpp" #include "obstacle_cruise_planner/stop_planning_debug_info.hpp" #include "obstacle_cruise_planner/type_alias.hpp" #include "obstacle_cruise_planner/utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" #include #include diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp index fdbf5b413d978..5178597bc4588 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp @@ -17,7 +17,7 @@ #include "obstacle_cruise_planner/common_structs.hpp" #include "obstacle_cruise_planner/type_alias.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/boost_geometry.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/utils.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/utils.hpp index ed6d0f52691e3..c635b400ed104 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/utils.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/utils.hpp @@ -16,9 +16,8 @@ #define OBSTACLE_CRUISE_PLANNER__UTILS_HPP_ #include "common_structs.hpp" -#include "motion_utils/motion_utils.hpp" #include "obstacle_cruise_planner/type_alias.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index 266e184a06a08..fc454e1ed6426 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -20,6 +20,7 @@ #include "obstacle_cruise_planner/polygon_utils.hpp" #include "obstacle_cruise_planner/utils.hpp" #include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" #include "tier4_autoware_utils/ros/update_param.hpp" #include diff --git a/planning/obstacle_cruise_planner/src/optimization_based_planner/optimization_based_planner.cpp b/planning/obstacle_cruise_planner/src/optimization_based_planner/optimization_based_planner.cpp index f420993c1adac..08f893a0a9a50 100644 --- a/planning/obstacle_cruise_planner/src/optimization_based_planner/optimization_based_planner.cpp +++ b/planning/obstacle_cruise_planner/src/optimization_based_planner/optimization_based_planner.cpp @@ -17,11 +17,13 @@ #include "interpolation/linear_interpolation.hpp" #include "interpolation/spline_interpolation.hpp" #include "interpolation/zero_order_hold.hpp" +#include "motion_utils/marker/marker_helper.hpp" #include "motion_utils/resample/resample.hpp" #include "motion_utils/trajectory/interpolation.hpp" #include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_cruise_planner/utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" #ifdef ROS_DISTRO_GALACTIC #include diff --git a/planning/obstacle_cruise_planner/src/pid_based_planner/pid_based_planner.cpp b/planning/obstacle_cruise_planner/src/pid_based_planner/pid_based_planner.cpp index ffd2f97b1eb89..005a85d9b872d 100644 --- a/planning/obstacle_cruise_planner/src/pid_based_planner/pid_based_planner.cpp +++ b/planning/obstacle_cruise_planner/src/pid_based_planner/pid_based_planner.cpp @@ -15,8 +15,11 @@ #include "obstacle_cruise_planner/pid_based_planner/pid_based_planner.hpp" #include "interpolation/spline_interpolation.hpp" +#include "motion_utils/marker/marker_helper.hpp" #include "obstacle_cruise_planner/utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" #include "tier4_planning_msgs/msg/velocity_limit.hpp" diff --git a/planning/obstacle_cruise_planner/src/planner_interface.cpp b/planning/obstacle_cruise_planner/src/planner_interface.cpp index 03cae6e6f9d88..f4f729f1fe8b0 100644 --- a/planning/obstacle_cruise_planner/src/planner_interface.cpp +++ b/planning/obstacle_cruise_planner/src/planner_interface.cpp @@ -15,7 +15,9 @@ #include "obstacle_cruise_planner/planner_interface.hpp" #include "motion_utils/distance/distance.hpp" +#include "motion_utils/marker/marker_helper.hpp" #include "signal_processing/lowpass_filter_1d.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" namespace { diff --git a/planning/obstacle_cruise_planner/src/utils.cpp b/planning/obstacle_cruise_planner/src/utils.cpp index 091adfab586b4..42bac55e63dd8 100644 --- a/planning/obstacle_cruise_planner/src/utils.cpp +++ b/planning/obstacle_cruise_planner/src/utils.cpp @@ -15,6 +15,7 @@ #include "obstacle_cruise_planner/utils.hpp" #include "object_recognition_utils/predicted_path_utils.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" namespace obstacle_cruise_utils { diff --git a/planning/obstacle_stop_planner/include/obstacle_stop_planner/adaptive_cruise_control.hpp b/planning/obstacle_stop_planner/include/obstacle_stop_planner/adaptive_cruise_control.hpp index 40a3f36bff903..9d95c5a4796d4 100644 --- a/planning/obstacle_stop_planner/include/obstacle_stop_planner/adaptive_cruise_control.hpp +++ b/planning/obstacle_stop_planner/include/obstacle_stop_planner/adaptive_cruise_control.hpp @@ -16,7 +16,6 @@ #define OBSTACLE_STOP_PLANNER__ADAPTIVE_CRUISE_CONTROL_HPP_ #include -#include #include #include diff --git a/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp b/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp index c836b380e5cf2..4cb71c710a225 100644 --- a/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp +++ b/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp @@ -23,8 +23,7 @@ #include #include #include -#include -#include +#include #include #include diff --git a/planning/obstacle_stop_planner/include/obstacle_stop_planner/planner_utils.hpp b/planning/obstacle_stop_planner/include/obstacle_stop_planner/planner_utils.hpp index 8023e86583885..7e7e659325abf 100644 --- a/planning/obstacle_stop_planner/include/obstacle_stop_planner/planner_utils.hpp +++ b/planning/obstacle_stop_planner/include/obstacle_stop_planner/planner_utils.hpp @@ -18,7 +18,7 @@ #include "obstacle_stop_planner/planner_data.hpp" -#include +#include #include #include @@ -46,7 +46,6 @@ using diagnostic_msgs::msg::KeyValue; using geometry_msgs::msg::Point; using geometry_msgs::msg::Pose; using geometry_msgs::msg::PoseArray; -using geometry_msgs::msg::TransformStamped; using std_msgs::msg::Header; using autoware_auto_planning_msgs::msg::TrajectoryPoint; diff --git a/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp b/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp index fb1e93185f738..8064aa4764a6d 100644 --- a/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp +++ b/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp @@ -28,6 +28,8 @@ #include #endif +#include + #include #include #include diff --git a/planning/obstacle_stop_planner/src/debug_marker.cpp b/planning/obstacle_stop_planner/src/debug_marker.cpp index 15b0a721ed170..9277d373d498d 100644 --- a/planning/obstacle_stop_planner/src/debug_marker.cpp +++ b/planning/obstacle_stop_planner/src/debug_marker.cpp @@ -14,8 +14,9 @@ #include "obstacle_stop_planner/debug_marker.hpp" -#include -#include +#include +#include +#include #ifdef ROS_DISTRO_GALACTIC #include diff --git a/planning/obstacle_stop_planner/src/planner_utils.cpp b/planning/obstacle_stop_planner/src/planner_utils.cpp index 45cced4599602..21e45cac79fdf 100644 --- a/planning/obstacle_stop_planner/src/planner_utils.cpp +++ b/planning/obstacle_stop_planner/src/planner_utils.cpp @@ -17,10 +17,13 @@ #include #include #include +#include #include #include +#include +#include #include From 80b6281829820fc0cd9ea04ac8974047779c8657 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Mon, 11 Sep 2023 17:27:03 +0900 Subject: [PATCH 164/547] fix(perception_replayer): initialize class variables to prevent crashes (#4944) Signed-off-by: Maxime CLEMENT --- .../scripts/perception_replayer/perception_replayer.py | 1 + .../scripts/perception_replayer/perception_replayer_common.py | 1 + 2 files changed, 2 insertions(+) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py index f1015be07fd7c..79a97f0033b18 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py @@ -35,6 +35,7 @@ def __init__(self, args): self.bag_timestamp = self.rosbag_objects_data[0][0] self.is_pause = False self.rate = 1.0 + self.prev_traffic_signals_msg = None # initialize widget self.widget = TimeManagerWidget( diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index f59270a9740af..6b758bfba24ee 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -44,6 +44,7 @@ def __init__(self, args, name): self.rosbag_objects_data = [] self.rosbag_ego_odom_data = [] self.rosbag_traffic_signals_data = [] + self.is_auto_traffic_signals = False # subscriber self.sub_odom = self.create_subscription( From 5e9f992c680052dc52288b4e0f8b4ab30bab26bb Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Mon, 11 Sep 2023 17:38:29 +0900 Subject: [PATCH 165/547] fix(ar_tag_based_localizer): added small changes (#4885) * Fixed qos Signed-off-by: Shintaro Sakoda * Fixed camera_frame_ Signed-off-by: Shintaro Sakoda * Fixed for awsim Signed-off-by: Shintaro Sakoda * Removed camera_frame Signed-off-by: Shintaro SAKODA * Fixed parameters Signed-off-by: Shintaro SAKODA * Fixed variable name Signed-off-by: Shintaro SAKODA * Updated README.md and added sample result Signed-off-by: Shintaro SAKODA * Updated README.md Signed-off-by: Shintaro SAKODA * Fixed distance_threshold to 13m Signed-off-by: Shintaro SAKODA --------- Signed-off-by: Shintaro Sakoda Signed-off-by: Shintaro SAKODA --- .../ar_tag_based_localizer.launch.xml | 4 +- localization/ar_tag_based_localizer/README.md | 21 +++++++++- .../config/ar_tag_based_localizer.param.yaml | 22 +++++------ .../doc_image/sample_result_in_awsim.png | Bin 0 -> 1542171 bytes .../ar_tag_based_localizer_core.hpp | 8 ++-- .../src/ar_tag_based_localizer_core.cpp | 37 +++++++++++------- 6 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 localization/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png diff --git a/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml b/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml index 4de9aec81bb38..3b32d080ad51c 100644 --- a/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml +++ b/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml @@ -4,8 +4,8 @@ - - + + diff --git a/localization/ar_tag_based_localizer/README.md b/localization/ar_tag_based_localizer/README.md index e471378773cbb..c7b970ebbcdcf 100644 --- a/localization/ar_tag_based_localizer/README.md +++ b/localization/ar_tag_based_localizer/README.md @@ -55,7 +55,26 @@ ros2 launch autoware_launch ... \ ... ``` -[Sample rosbag and map](https://drive.google.com/file/d/1wiCQjyjRnYbb0dg8G6mRecdSGh8tv3zR/view) +### Rosbag + +#### [Sample rosbag and map (AWSIM data)](https://drive.google.com/file/d/1uMVwQQFcfs8JOqfoA1FqfH_fLPwQ71jK/view) + +This data is simulated data created by [AWSIM](https://tier4.github.io/AWSIM/). +Essentially, AR tag-based self-localization is not intended for such public road driving, but for driving in a smaller area, so the max driving speed is set at 15 km/h. + +It is a known problem that the timing of when each AR tag begins to be detected can cause significant changes in estimation. + +![sample_result_in_awsim](./doc_image/sample_result_in_awsim.png) + +#### [Sample rosbag and map (Real world data)](https://drive.google.com/file/d/1wiCQjyjRnYbb0dg8G6mRecdSGh8tv3zR/view) + +Please remap the topic names and play it. + +```bash +ros2 bag play /path/to/ar_tag_based_localizer_sample_bag/ -r 0.5 -s sqlite3 \ + --remap /sensing/camera/front/image:=/sensing/camera/traffic_light/image_raw \ + /sensing/camera/front/image/info:=/sensing/camera/traffic_light/camera_info +``` This dataset contains issues such as missing IMU data, and overall the accuracy is low. Even when running AR tag-based self-localization, significant difference from the true trajectory can be observed. diff --git a/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml b/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml index 385e86498c58c..d0eef44229e0d 100644 --- a/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml +++ b/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml @@ -4,21 +4,19 @@ marker_size: 0.6 # target_tag_ids - target_tag_ids: ['0','4','5'] + target_tag_ids: ['0','1','2','3','4','5','6'] - # covariance - covariance: [0.1, 0.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.1, 0.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.1, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.01, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.01, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.01] + # base_covariance + # This value is dynamically scaled according to the distance at which AR tags are detected. + base_covariance: [0.2, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.2, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.02, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.02, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.02] # Detect AR-Tags within this range and publish the pose of ego vehicle - distance_threshold: 6.0 # [m] - - # Camera frame id - camera_frame: "camera" + distance_threshold: 13.0 # [m] # Detector parameters # See https://github.com/pal-robotics/aruco_ros/blob/7787a6794d30c248bc546d1582e65dd47bc40c12/aruco/include/aruco/markerdetector.h#L106-L126 diff --git a/localization/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png b/localization/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png new file mode 100644 index 0000000000000000000000000000000000000000..875005214bf5e7b05251bfe0c8d3aa859ba832fa GIT binary patch literal 1542171 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%0D;ABeWYJ`O7h{S-}gUSY@bRI{| zdACXo%Y-}}(au5;aU?1tLmC3XC0sMjl|h4LU}?m;?t3hZ*&&^EU3862 z|K-2@7X;ze*IwnX{k6Zw+u#27R z4ncMU#xf$}7^QUvX-q8xCT>KnfuM=ybOFzV+7G~aT(geiX#7#2B1AAd;~2 zl&qe%*df-=Gmx60P9SdNvJ)%Rvcg0HNx! zAS4&5UCqG5aAFauXCJYreKNQyKPXrv;Fw{jb`or4>Koih|u1eLs71+R&rj`1!rl_3)u zqCP+xvl`zdXw6iYfxs|LNe-IfQBv}D(7bnyn+Y|6Muvz)95qor3D1xXhO8r6(a17m zSD{`wLQQBUOjM|)B1ym#snR8cnp!+bJPyC5Fh92@wp6~jNFH_Upk&KjYtDfZZH=fSr4dm)(MPJF_~`}~&wB(2I8~C4Nx{ml5UtLd z7Z^xQ#gvGnb^|Lf6$Z8^n#ds9Il>VuFPOZQbX#gnwy1q#>;r2uw5*Y|M#OQ+Eg?i! zbd8aY@!F7FMdO#4Mk~4GN%+(-NEX0xF$iPla2gmz&q|Dl!g}!3k_g^pwkp<;gqi_U zshB#UlA588QLTt9ftaIZ%QBd%#C>qSLjO`qoIQIM=Nv05D{YX2bB^hBN=nJf0cGQO zaoUNO4zQQ^j>u$)5{RtX!U8bTT!Hcn?Wg z)`TOnLBo_OO9Nq?U@S-QnplZ;j`P&5(m246kvOHQm5_!ABVw?zbt3%bGf(sH{>AU| zg)e-8bmSx-_>rIBm;T59ftya9WF@`IP}lH&374RX6BJ>C0nrUm2{?5GB#R?TbV}nr zQ3j}bD&1h98x)B(ik&&(T*9SD$1Hy zYO(4bJ}CptaNJ15F~w8oGtx$k-H?e?s1qCwemZ4D%}}R!RT4v{Zp4Ng;U#gTK1)SS z<3@N9TpW`S#n?;q(=@F-2(+}VIOK{UtI)U-tmT~tF_k0uVfIc>&@{o$$g2AIhIj!ZApA|0Eh&D_z?NJ>OT%zJNl$wbg zN3>!ot)VEjAKCdu>*smqiLddS|MUOC(Yt<#PyXsZ;KBF(5Ub4w%i2((s5lx)z!XP9 zbO^dc!m{`VI>zaQC=Cfk#giD&u!0Z?n8P4teH5L7M1(|0cHXjzkYsPVMxp;Nz4Q{^ zdrq7<(duu77mUYajvP7CUR#2Q7InE88)9nk&SwXUmHSg269yABCcGX+8Bn`OV+Uh0 zkU*F!PCbn{Y7uIum{lU-^YI}`1Gs>5HE4}Xpc<&f;RTkP2~HcPVp7^r8$g&!g?fk6 zF-et1Dzi5^fQa|dq(Eea#DH=#u}g_nS!1BbdspY^HD z&bEU%>n>fo#9Fpbjz*)oHrLnJSy@?W*H_^Y-TTWw%ffYdA4rL8i%LLEVoI%o_z_M5 zDN3f}i7M6%HQ`in;t>M13Qbipj%x%tXKPhXF;LG+TqmKX?>kQ=f{W{T#(1p|U7`^s z5pgI^g(N~OIyInTw#_Jkq(UV?V=|sb#L9{i{ z;Aos_97&8JIvSP+k=RU$Iz;^loM%}s@sIz(-{f0={t`d>OaFwAee!RxtTiJUTQw~4 z#H^EwfM)}jHb~-V7$LF@>hUxjy2}`}2r(r-|M}1J@WT&t@xnz^mA`!cFZuOP{W?_@vS26ef#&T4I-7hlneeGk zeTol0@d!j&`TC)r`wNFYfK zQsdR*(jY4e6)I+c6}562E*o#eHAEZ}Swh9((*&ovWRyr2d~E(i5>6bRS*ewXSPgrG zC`Od4R1s3J^EW!qA<5FGWvFNtW>bEDJKM$P#Ucq49@JbhNkB1%=e5b11zV5z<#5(@ zm0c$TL_+&_6wPObCF;ha)|`~e$l!^1mx!^>|8Ul{wv%^a*npoA&1c_qi*-EB}22=;xNmR2HW4xTsx-K4n{BbT` zyvX_U=lQ?~KETa4-`pB5+k>%DNXd+BrCf(dHtb^#TAcz8C7@a00}pB?RvqTJGn!y) zC5x-nGDI0rB@><-w*gWU)EuQo+w9GqT1lcPi6{*op~3{EgbN8z!Pire9-I?xNri>OEp}X5Dt* z?dALVzmqw*F-JAA0u4l06D5*xIQ5K@5HwM{m=*k(>5any!xRyq5l%e<6FWSWIrPZTkwV$0^0Ss6bE1cV__0l8$3nDL-WLas`05$cj@ehefLYSFw38yXz7 z*zO7HaH+x!PO|_7MNz3yQB*yGGlK$&=$cHEGlv&Dv&n>WtgqtC;ho1|{(Tft2kP^@ zD;AE^;FubJN@CHOR0z@<$!*8#To8>G#fV(eT51cdNCOKJ5(P2&!pxbb#(K3`S8Scs z&Q7m&>X2=COoGglww^C9w6cVz#dVaO1vz5gtBt?pcTmyUP;Ie}X8&@V>hkqd3rtG+ ze1II-Lf$u`%O*FGNUmn;rhw-4Xzi8NU+9~fx{2rjuN5Kl=pq#~5&+L2f!9dmrqm85 zG5|awTA!mRLQN%;EDH)V>B1XLD8@-0kR&j1Q=)4K$rDnA(?RZ=$$PHNI0&R9ZRs~e zaWn*T!l-%vktlOuvt{5<$efBy2^}qgC+UmI7f~gVK^Le?%X+^dHNZS z9y`jr-t{gflL^D&5PN0{w6u>cuDmg9}qtHd0vlcgb5yvBBo+fN28&ca6zp8t5J)FC`EI&5oyOUWuMUIV@nq9DYgEc&s{V=QGV| z%1YZ87M*p)X&Yl?ZEcO;`JLb4)mLBTmwxH*va+(w{rBI`x4->unx^58{^*bR#3w${ zX15(xpp;5U8q(3l}c5XC78n)jC*CpFYj%$|{l+l9D6`2uYTbBq0fg8c<10 zoMrR)3T*>T-NI}A3u;l5{*Kn7^ECR zjmRXgk8VV^B9ic~q0$<+eu;^UczO8@YawuCy1@w@Gf0XkG|uRI&IxN}3&qxsx6=WM zBnxi9A&NPE)tOVk4T+9yT%FR0&0v*+8U6rV=3g2SnE*m$fH{_mzKgvVb>0o;u$FRQ zW?mS!1~V_B$?LlYJV95C7fM4T%0x~68zj3oI?4Uddu^M*JP~Qj8m_Oe^Si(MyS(_~ zi~Qm*{vtvPO`N?Pb*nHG#<(r!JIQmQY!~L4LQ)MaMgt7Y9m(GR4k}wk37md3mDnh z*x>QUALrlyU;mzWzx&-h`skyaIdcZ*9OurRza7f@)$k8sWWXV`GDq z6057L7Q}u2JRt;}b2$UcGO}*D`DP2sN^0?-;<8*+4ogugHNC8Mk;a)U>eWIplth!y zS`6Bl9F`5!V`t+*cAx|W(U@%;<`_!iiFux!g3tPZh*gq`2#t7i@S})qAZiXaA(UCN zG4qjui-cxE+L&-|c!F03M{(M4qS;_oYXas(Yb0g4ND0a0q9TcHVwV7r!C*j2iM6%0?3h=UmX>pXv2gmPlMDt9aWyD7mO&h;UBj48lY-gn zTIyx=N1b5W0Vp1_9Yiz|QiIc&1960qeAf3&k_#$0BCpf>bmB$pZzTV=|BA^KJt-|@W>;NaMMjUapA%R>bmB!#~$Oo?|m;vj~*qZ#L=Ti z0l0Y4Y{w@~oZ#HKb8T{mh_JG<%5++@y0XIQn@$6XCK>@6Vx1L z4T+e8Qbm=4R;Dwl>CCk=+fSvo_M%G!geo~2fqVdr$<0iSCs1J)XAfB~MnST!+43xz z*qr8I6ST6v$b=+9qQWUustl6iQ+7)_A>vw{6mqt^X_r}(Ppr5NSoj?{C}hRZlq|){ za)|m&Xd_B(#7qKCeaoN~M-+(8n7Ke}h^*a|#@XLKD}UNidW!O0Ool~S(qbUQjE>CA zj9jVnOn@A%W)6xw?!1FL?z{shuHyuq83i>fHYy2KM?{!78w(XvhQSO7E;*tE8Zm=R z&}^I#0rd!;=o%7D7LsSi5y@u(L@|M<$xKAWvdYRxrYwLVW|Vc(5XG_7W;dEQxn)tQBMd(Ndu^}@i{y5+H9mN1hF+QnDZ;CITMRE zo#WwYw4q6j8MInwohi`4Ih?D2V%V_=-soym9Cd)#B_x}PBZ=g)x~b4GXaxpQ&Fe#% z8Hvhy6=mG>yym`|xM+@CvdLwWk@pHiRasqK<;53YWNB%M(P+eAFlf)`X|F>}In84y z+3+KhOm$UAd5<`?f@UVz`Mj4kJ8_c<$2uTNAW8%TSBw`3N$ea56Rra`FJ|zQR>`oE zPmPO~#hc4Gv@?(+|0KJsRT|5RYsD0c2t$1~F2vljqeil^)b^Fd6DZ`YUg-#PUMrlA zP;qm{r?V&KU8T4-^O9Ovtr>0oG14W5vV!A^~r2_Dgq4Z>IGTq~0G0YTMggOIHW%UCwY$o9`J zd$+hGy8Mgmj1h}o#j=Q?&gFC(qATm&vf<3|l|vp?W{=2xw3yg!pOu|aoXD>8PjbY( zWCMD{cXo&EZi_l6a|NUr?TP`_jP+)qESk7)X`=;=WPv{OnIhgNq|=JMM9x#q{N9X5 zU@lHGuu675=B(z{h-`+RT0Kc}yIV|$=3&}S#VBsa?UWPiZ$lzcJ5Phsl=Ya$z4T^F zLxNw0NUwna|_=!(YRe|6C{om)l`|jh^sZ%`u_~X3# z>Z>e`mIzhg```aQciwp?XU?4AnP;8>g*)GMC(l3sJYW3c7x~bKK7^`rYOefl)_-+w>r>+5{`+u!DvTW(=E9P-t#ew7#_ciwp? z02><{Y;0`sE5Gt9JoNteXZgJt9R)KWdozxkWL$v^$4|CIahzn?$&zyE-g5?}esS2%U* z6z_S@d)V06;JN3X2%6ncijaz*4L)2txfoQfA1Ihz{BrH#Nk}g zJ7O!}njzBD`Cgax?dfU=I358`MD>4M-7!uH);(z!zOnvusQIuEkVo_6d)Qw>5;% zwAkA^*M!f{BJZ$bFEI(Fo#tH;qmA*KXW!)IS6=2P|GS@LGM(`I|K|7kz{4NFImdUt z{T=@NFaDgmLsWVMA2Sy3CNmc#5Z7naMHj z`fBZUIp`_(M|4OcFwN_|QpGElokgP3Iy+l34OhuQab`OR8uL0xrmH%Won3N-9LT20 zq6D%WsupsKR$2Ba8!S+QziS30JR#LM;(p1> zK43J=WgFXUJad;QHijF|xmA1}XV0G9w#7Qn~pm^s^Ko_CO zr7^o@F2o7dplVAtsb!~BiZLyl+h#q=&LY)f05k$w2u12DNm`Wna=jT7uhe!E5rWT=fyLORRuU|xn9C-4 zW}-zAK>?8*bCt^#WDQw!L~MBv#J3Jo!DZ*FYulq8ui`kMWmF0Bf#GEg6115@+3=Vn zCTxPU;=xQ2%G{JGgIij+uy&G%~lXU{PwvqjkHOo2qq(UbdAm~%V+xSV}4 zGZ$;lOcb$L!*(ZWDPypULwYtbmklDqyWjn8?z`{4xzaf$qpou$#S|i1#ySTHv{9}_ zNS6FFP|mueb;QkHWfn!P*>G!$n;q6(4AdpczKpfDeX%8NV;xI3(9D=;G|uMCW#efU z%7B?O#r`VgE>*e%U6kAoJ=0v;wKZhf+*$qmysJ|glgnIvbySpL_pOA0f`Ec_BPAWu zAt2o?4I(97Lx-f&-KCUD%Fsin)C?ibF!YGSFf<}{Uw^Ug{qBFW)~tEwt>--F?6dbi zY?a`z(ZgYa5EF_pnk=qend!}r*-rjrZWi*i*9?0Uempd>{U-Liw8s!t zUWp8X#HMbi5jA#D)NEU3?Cs)x?s+aVMx90f-6>Un=X)GlMo(48<+B~EN$N>#v=zxd*O zMK_)bOMhFulo>bG$n20_^b)$CS6i58-kRJ0aF4(7eW*1#bn_uE7I%xk>*`oY_IVR} zYiF=RBPU=d%j8dc$z5RgRS`yaOsnhqcwG9=dK3{J4gkzYe>>hJfUYh)jHfl+7dRN2pJ~xUvi<*+NNrTNkXv(sc z?d>OS@H6$D7=_kXvx%qXE_p1k!_7={z0XTZj<9&Kh>NmPM-5qWPNImlglSBN!8IdG z?(NbpUxT}+07m-wzwO7p>&$q5eqP*Xmk9A^SPYntVjy>B5OQ~gK&GiM*xK2ZO-7hn zTf>5KJb~6EL(nf~Xpz*2irim4VB<=WIe$&d#8y|$nsvK^k$48)A?a3BbimbawDsHl z!tK_sFfQEX?Be5i(|a0Mx602;|2QjKwheRuCMquBDwmdM37dd#?&GR!C6?PVeI!2- zBzZFR`b+y;MYwC2&jzufB{4g1V_%|ozD3p2uZ8^3G*HDm4x2I!Yjt;T$0V0P4>u`W zM5Sh{NCMw?PuoljTin!b?|b&|oO>h$)CYr@6O6Up!&2P34%De9fNd$6@iL8ZqX-ki zQF1xq*tU~x9&o;d^n@M>%bf^!o-exq8e3M24&Sen-#UEv^dSe@#nPidS{wH7>6N9( zs>hE}b{}#O#Tvv8JVf@k*SY3_d(EZZ-!Gm>a-R3$>thatmmjTqRW|6PXGz)l)|7S5hhz7UOnL*c97$MCTi6EM3piV{UWCSVh_E zvSLKWBQ2f%#iXzH!Pm$9cS0k{^mYJdnZcoRyq%%ry*c~B?g{d8%VwgTJduiPwFNDGI)YzFs; zz}i8la)Q)CwzL)}Z4S@X9&GBSWp7sS5JF|SBoVHd!Oo*M58CEq#|ml%*##kLpYZaA zX)O83yKohrwtwLscc7VHe&=vfQ*^{xwreJ8Z@-GL-1zOAHm z<4@sq&bR>uci#R*B`UKrg#EFVy=aFCQV|6JTfcbtt+d?VGP$wub|V4n@A3c4w{{N! zsi3Ku*=Ca1TCaqPkrB|&{0Hd>Fr);INUim$$R4vwpD$A8YFE)ofoh682(?M8sq}ms zT(_P}mUa1;Cw`DXY2NbM34ycx#N97>V7f~g;K$7M%+^hljS*6aDjwF%-y$swj>o!b z_!iYJnH=Sf;ZlunO}=^N)#esE`j}L&SC?rP;A&DEC#$&%@&4+rZaMZ1?C!GrO83mP z^2JgtkAW)a@TufVGJ<(u#xnEF(v`EaJG=cL8JZNM57@fWSOsh--Cn~=>_S87AKL~7 z=B;@26vY7{!OR~#@`$M2^Y*i{+b&UqFx%vh{F81O@WBm9SI^VKhZGtSZkMH-zPk3i zmVDc7PnU!o{9Mdg$)vUfR3TCkiWS@p<2PHwHBKc=2+CDB%9jNuEc8Z>%bOEF){y zr>HOM1hkMD##6taV=8#Fvh$j1^c=On_7ftD{DiZ`SoelHjfs-#AP;}Ew1F&I1`?ea z$qQCm3Fd_)>36%i*gC*0&76Td!3lUmTLzEssi%ltd^!;=D$<+(T~lbVSMsIBwG)h; z%Qdt@2#R9_^RAUDhIhO;zDTYeu`kY^BwyJqjNUBeinm_kwtq2s4tZcRbLrah5STDv zDG4@Wgn*~^IPAs9TQ@X)3nr7GH&)T|z79W1Xdiqs`uNKJ;N#VhElVV*c*GqNsQq(z zc6XGn9{n);Alb~RqJmdAd8%S50ru{~R4RFSMs3QK;>ohNQ-xcRNpnjOY~bKAdZL>b z(WrsEIM2R-Qk&jyvKw@Iz%wy#W0f-`n43k#OKmr#xBN{`__oxv0du~({A7w4v)?^j z~e`E=%Ao9mvNz1x{7XhbLex*4WfE zC=8Px0BS>EuJ-P3fyb^4CXfUsd&T3A2o*zI?}{C*`JifBj~&C$9eMcqeg2+0-vd-* z)c+bbaDT+)zcBOrYtSZ5Gd>K@^UQt4-Og3Z7 zF*dkNk^bRo3%ugtbK$7J*S{Q=YIkmSBYa}I-_4_5>AD?4H!KB^^3K+dE_G=8@!*bw zkvt2nZaa!y@iI1e^VGS%qZ@3($JBVXUCd48b8wuDwgaOc%eBv4`H9kA^$D6I{nBZP zR*h_uSjbH9Rp?0p;$PkC2!ssBlF;lvo2j}oe1?FCs*Uo502pv{(NnNBlES-)Cux@*T(bP#x|G{Kt4SyhbpCZ) zuo0Hw>tE4yA|lrPRlV!xTY z_hTg7iQ8Z?A*-rgp`?6bsqH!x*n<)Fgr1Mh^T)N9K$i_#1wxMEB*ShY(Ft zz;2%7zeElLy!(Hk{@nurlN8i_R_oyf?6285Ie~xos;G(ZR)bKrKqI9%{5CL|UTV0= ze{FPcw(4I>@lo(-J&L5UqeFT@Y_{Yf96Im`?B#|m<&7J2CAJ?n{biUva~`x@4Oi&3 zm_{#0#P!3>Wh~7bi=|dJRGWHsHm%9iI%!Gg^$(%tUnnj^tKnai#4BpRPu%qj^(_PK z$^6n-BzVW%z|FGYE8gGn=3bwv^|LE3;>8dF8SJ9}co6^WehcNoi%qNovqszxAp=b_3(!hAT!MLko{rAbYF<{YjVQ!eaPb>I8fBV<>UG*BF|A(moa0{@w zFq_4}2hh${aTilUG}K74V?jE{VtYf$*jYtbC&UG_zvxcEHC03Yc7X-%77FbbGY==+ zZ{iltzRj81F#WK8ePGp;p?=UxrlYB@-xuhCglM#of=)jv;6r@pBe~{wgP~q|3r5uj z7@p?%l`FHQ#M%IdDm1UvC zz^VImf85mMZ~CJj=vGLViR9~DYm?j#i?)2O9w}O$))8Ao@bFs?9s-G(^Lys|>NQ!_ zSZsu|ak=#_S;JN=Ab*eyX0`wQ1D3d8%*|n(781VU@Ywq*ln*@7RNJ7c0ckRB0Fz#T z<@l<=2Lj@T^LECv$jk)#-COm;+pf>3pYM>U2sb74+cElkaZ8K^^R~-Zxghv8zQQtK z2Y0#0vN9?tHE=$%#u_LF?-^NdsJ*3dP4o{!p?7_HpdGbP9>AqgV5jaaM-$r?CAN{e zK;eaPq44mcG6z2|Z*yE;94Uy{d-U-LzP!mN#6e^`a_m|P*7&A%I9Vh%=8MPDRTwv3 za&4kSaNxP^9|yq(hPOZLH#i!cwRrGeWX0nOm@6hoT3&$ba2g6}XG5XdTaC0jAKdo# zZu*FpPfnT{mpfjAd|ScB6lV=lB}4j&X#5WNa>LL3P(;p&Drj{D3hQz7w@6pe1doYI zoqHwtnLxALOR~VjEuZanmJ<3P@QLZ0bpc1@J_xDpqH=t(lC=1yXZP0F`}}ub0CGn; zCyd}t>>{5Gzjnr(RjAeY0i8dx#f9?sOiAs&hUI<>%r2e~5{%)bzN@8u%P69>q&ajk zpjekP<7h0wJ$Y6I@j%Y^VTW@DcStS3dsFK13GP=n`5WzNM4LZ!u?qONMXE?9~#Cg(c)Tqhx#WyPcbT zKOjKvKtn>**HWUJ#A_D{0fd3pRsmAhhdyzBc% zaXI0LV9d(-SfG9!uU3enEy{Bh^#s2tO=->Glwfh6?){}Ay2uBOv6sm@`<@k5A7Xrf zs_SBqTU?wxZEj0$5$euH75ae7ga*ePv!CSdSkF%2<4HNTM0@j2(KgpnG}A##n#luJ;(`{_?a$;b2whl88qLRE6jwU$Ps}90?;e>`N~5PhR{3R$`#5}i zCuKd<{>0ApBA|8K{IU!A)Pr~8eve-yZ+wm3yxOdYkC2Mx0(uz;u;?56E_8+Z3JZu3 z``7fAdRfh7M;&#_2(UFL>V#~P0jNRj0Q2mgFP|z{CFSM~)U?$yTER3L=*cIJ7!!Z0 z#dS_U^wcxNrlA$(NMbQ7vBlr?^#tEL zlGzEa5^B1+OZs`Kf4lQ{8DT=Vjxuk!*p`&LZM;a@x`P&fhH!vfd>&5M=4- z7@o(^rpz|XPK)r-8*`oIlxJmoS?Ww~pjWIH6 zCN{Olp{&P)qay8GvheIk&I^ABaT50n~aXnnhP!`H+YO&(<2FOy8CSH=&*1p5DF0dZ5C zbT3y+g>gqglVVMy(fb(mSiGxI%eEQyl*5gTjkyKB??!KmY(Po9y)t6XjJu#1JY*-9 z^>9B-CC-?R>A01uD_+p2>ef#qAuG(UYymQzH>5lNBA54h%n9a&R1)a%S56H4!yf(L z_NN`h_x6%ERs-YW;$$4dV#nG8&Fw&+&00&Yf`7@ooAQjc>3o<-vJ-pRh-%}NYZWR4 zjT`ZfttU`}EaTwrvEXXwkZv>kS{vjIn2dNcx62qWq|ENpA7r&`PtW79#Zw0vI*v>D zd&iQ*om^12amTdf#Yw+Q3X}X$-jJK*Ez>)ewvZh85KxhD%A=Xt-~c2c8H@vhptI8erX?{ zlv>6(Akfx)3+=si)$iTcNhS(AEW`97B|5HO8X=!!Wul1F{UAB_Qi_(PYglg@T$*sF z==YcKA^tt@+0Esy%%;KP)fyHL$U{B2i~VJe-ZO|%fOw)zDa^@e0VM%?g8B-QvCL!g zqn9nWKYYqHeUMo+b}mAEvFoMl8aNcI!?nV&u#!R24GXju4dgtvBZ}nR7#VE4P>nj2jU( zND5GlVAZXpu8b!tZ5b|nZ<#Y&bPsj4SN%m}MY1m~@m6#rBw%tLhWK9hqrdhlG^G_6 zwxEOqzW;6i^xH};B;B@ITwAIZqA=1@<}Xvn@CsK;a=eN2BsC|l$f$K`v?og##4X#L zMZeA@E@HWao`?T>HPU|*^~RGI^S#ZaH6Jk)OiY#-Ug6coK)*)qIVG4^uheiSs_D+# zLtl=AkIXR7GDbVhfi4eONVrPq$Bz66+Ru3VPJ%7GTvc>FSsTa@rwFj_uaJ)2j|@KE zR#Pr}{QYCO;qM9S+nBZ-MR4oDCxd8aDHQR!)z{3lzQ2zZ7RiS?xQX>(sJ~<-b;NZ; z!Tzi*9Irm;jj_nC;xtL$oSRh%8-wK!b)!+Onb|I{# z19a1MfObKk25jYS5XZ~t?HMg)7KvOUh@*vla=^>LYvqXz{zYDv&^Ue)+Xh^>@n|>r zuy#A$H%XJTjdwI^An3vB=HJUd?-gyUYCN5LTDj}*7Ei0ATp(inDq#cm{ZA`qPsB5Q zR7k$MV0=-0&`8uY*W8x+#R+RD%8ZX zn{39n>b4^@H62Ez-zO|fR}QYO;V<4WCy2Q8H2JNpNAu?A1$!)Mfcp1Gc8}nfyYN5b zl}~gC!}{D-x_HR^bZ>oLcl!r=P3i05JEH_J?%h%76$CGq!^v4N?Wp@@-OZW{Yz;#N zDhYk9-kD%1b=S~d?_D?XzBo9QXrfmA#>#D9Dc@>;j%BpnVrhQi3BKB3p0)Z_87ht| zD$lpO<}l1DxE+N5t#RuHf=1s=&BKSc8xYoN7TOHnO5X!;p>GhN(l~I1F6UN%0H%ea zL;n(+f7N-C0twqFUAcDGS-dqn&fRAc!<_!)t%H*!NpZ?Z@B5I1-eH~P9!KMhsD0QL z$}Q?X_zp}2)x7DavCfPVwNJ2Qbv&1+GR9vZM4-QmS!fh{OSl#1*ui6_ouWIImOM|A z9g}`jzurN%NaF`(}4VF2sr3l9@O z2q;(13B@P!{NO^l|9u+&d#kpVZS;DQCMP(vL^cLDI{%wJH4nHsQNZ`7xA2MKGgbI~ zNrgK>xhIDp$VCuM4o-Rk>6w`t}m=4R7L_7WD)9%^l$a^shvMH z{ zbBt;$pDEEa&q-NBPkM>Bu9q<2bDx{A#vc@J?;%CzExGnhxiL-VD<%mZ`{!1{ftVo_ zq%Hlb3R!DH2^kfIa9VG_`(G}Az3px3Tj{g5Zm-(izv}fFQB91IgdbjJuvpQp1sZ0t z&%e@IcZk!FM83?5wx4-gRpgT`m*b7khp&e~U_s9zm&u?M-11L>wp6RGm;S2$}C0OTMCWip-jOD*LBT z<W$(4zl( z%Es2UfXv!h=Ybb7hr;Zuki$Q8AC^TP0auF87?R2#x~Jj!B!6*AWYXd)Zq)EUS9%xA zGb7J_10A`cuVfr;7w0R~k|Untuat`oFdZ?HxnvU%EXSVkk0M^U)TzG*$Ll)>#k!9u zftm&jsh?~-F_s8|)fkZ8H=-jAQ6m4K_L0k>s*p>2jgU*3yf2W1lS}E@3s6shzP^+R zRO*R(`~$uFwU=WH-4ExU`@^rSlIw|?wWi3wD%{Uy)hD+&1W>9F zX=7Y@{eH6xeM9b(^&I0J@qRtEg|5uUouaUN$fbqa6Ld?sO(Y?|9RC^$X+Q5U^~rab z`?4G?)l{G8cF9vALdl8g@~Bti+9-7CA)z;4VNs{>-P5_Sz6i85GhL>9oL|`|u^`*C zV)bB|GEZ*ZVcci!9*4q=w44vd2+lwCk9;@CB(+LEy!20q^Izv z7U@4Lt)n%&KA0x*9?Tl2UNR>2%}hTA``M6GoKJBskIQQMcm{tz7>~G`2KMyGT-t$pn2K3ZEv0;UKe$vJEUHbgUk+Ejn3IWk>e8! z-N(rdvPk(gb8%BMeM48!f(RqOZAP3&k@Y^H1&B)$ev!dCMVoCWM9)?HP=cZdRvY{J zS_H{ya>V?ruOxdWyd_5?*i>Q1Ifzhtq;Z6xFJOk;gt&@0iKzCX@tTd+e0oj#ROj=C zIU*$fZtL!vPVF`tJNC#73z5hmF9iCKmQD6@3z)p5W)> zEEv4eH8Q?Yf8u9JmM2lU3>H1f)k4wWi_>Wtw7%aHWY2ew3JAu0`*s_Cz9#acaa$S| zECdT$_-5{6;(sO!2V)E0`%VNR#U=23%$c6fHBJ6lR54NaHAQ56gR9C9IdagCo@+(o zI38}ghvUAv<%6W0I3kox;`gvV05&5wm4d=unC7)_9al2u4u4151AiOZ;uR3-V#8>s zg)KudG8{S%#nP?a90&|+@>ZuuG@hH-mvQXP*B`Q zMX{B|r9>55DzfZkvG=)2p^Su?VPg1s37INDJk2t$I?T4+$6R@l zDC8x0>zR1+1USVsR|3abojWy9-C@DYGx2S@re~;!=-n#zymKu#@3BO7 zC71WS`-0cB`c@E@!oy5pu;*99HS0PQ{R7#RtKKynFFNEFSFe|)g~Lw9HnUTvLTb75 zK?F>Qj_a}hlcA%uv;MW#9mn$fpVhf=u^0RfEht||sgNr*nel$+l*INzOE-u#=f#gF|1!T)W&Ty|S?lV+ODvRI!Tz%QNNp!{`ET#}B8Yggg(yz(_t{_O zhGKZexpWJ9$8R1g^UpF#s7O0aANua8qe^esI)1sSI^D?6cP}#aFLMKBleN-hajthJ zAMBvO$nRUvqnXs6NW-L#Yrvt_)pJ0re8?-~8P_gY#y!w|S+HTt1%Xd~kwJ=$xX9CF zAEH{Cv~PippdQ zXZ6kP1JIK<^!e~O;PJ@Bg@VGY8?=8c9bl(jE$8D&;)UEZ5k6&QZ{|uy*}N?ptbady z?|ZZ&4%bBlh5jF>3>*LAZHSIg_xT+1vMc=RN_sKi!&dpk96eus$;J)liWFHKLVLTa za4Vtm*+>~OJy_0^7&R05vz#hzBbWTs6N|kFc7!Dhu28s?I3OCDdb#bJ?t80CiqIm4sb@Vm z`eyi5zv90>oZ{#+{K)K=C-`U>UoUe(*V=tpRrp&+e8n9Mk=e8n+S!cJkX}T2-w|5d ztfEf3Q|@p&JFIanEf#pOZ#7j`D&zZf1s@O(uXstkE3l2_w6`w7P$%URW&4oR2B#`{ zi&SYkjY*mXLrKVAo-qIp>ascw?Xu#CXEm&J*!y_WR`{rVx%9}3%4VdJPPJH*U6o61 z_L?RH!N2CR;Dvxi{Zw~(=Iwy}!}6`@ykpciV|8=Y3}B=ub??mYsnJEJpLxlwi9c{I zo{xKTiWT^~|4JX`QrJC+%fYKiC)_iPYG0U-(x;&9b_7UZN3-hkrI*qk$)@=-7uC7qk2i3 zICEcjR46I)^PeTEct_TX^>9*Lm4ivMTIGCu0+lCPtO_s{dx<9E0h(E-c;uBtXXcZ= zjxRil_JI^HU?&etb@BE^|F|KeigJ#89}eqQ&*drWc`-Vgg5ILNEd6)i{ZxuTs47y; zzl7cUNpwh~Sv=|nBCdU*uNwVhg&Ys--)OPtrA?Ns5pNXV-xuC_@#93sVu4jdANZQ> zm~S%bn#wSH+EsMg%%FZDA($nehReZiz z-Li?wWt&&J)Uk_#K*@4{q5;(uBQ?UDc|YDX`%@D8X+LWL!;DA;+?~>gLoJ;(*#hEV z-r%(I);15WYHQmi1G)syI+?vRt9@ZhMG@+CO^r(qyBA#CSa22)zWidOpEH<1g?w^bZi(uus08z)OCW}|lVLRZuu z>D(^K?M(ZxA?vr* zO#dE(>$JzDDJzg1ZOEN2GT;@WiI+FirvEuT05m{ofnd`nYr<<9d>q;9r{|n+9p|Mx zEFXlW>|}xUK7vR$#DwkQ{|zdJUuQ^OMqX2z`5PCnQZ06SKRs~Y2xQQ}1kzS7%@Sq4 zIXW8eyuJ&GY!F*tc9ex($=)JLjqsE+?7tBlF1-}EUPsMkMY-%mI990NeMdE~n@ik# z=STM;Cn`#wlzikkw5Lt3)}>ccyJoyMYz`XjBNAi@z#+$N42wd8&&xGro&UCXX;S@D) z+ohcE@F(nL{xcibW&OTq`RUK7lXGyMaM3jh*$4?`(Z3f0?ejMEZC9iyAXKV5xAmxS zBl`00Pw{3Nvh$B`OSX>_*Xo&-sY6Yq7&ke`kd5T`InNyGT7X%;GjtJbY6{?`w<0u8 zlZ!enB@Q)feI}fI^O+=~6%>RtaGndY>jb_NkvJumo>>ZAd?)hp^Ot*_YM?g0KE0fV zyrj^LZA}BZ5o|Usz?EWeA|kfmkM6TLv~Om=m~$YZQ3IG%ODH$y&Kq1}hX2ivZ0W^( z*f7JmWZu|;Bw<2L@T9^MlxH)tF?C=K;f+^V+9n z2z8Cz6_?PKpr8TfhI}A*kqo)EpdiRW=EA3JuNw9x!CP+i)%&hQ0}ZcRd2O`P%OfnC z&61{_eBW-x$Hbj4s-@qpI{i#&?vN&_Wu5B3IX>xFoo6D5KZAC=kMN8)ezpWLF$Or6|!BjXpo123JLWbx|O@kML;ywd)J`SP@)i zNKSmF&&$HXb&ID znLg$S`GbSm5C(+PcQ>tfALcTu0P<#uJ*rRUHYfaA4zK`$gb%h0R|OvZ*1By)T=Tla z+qGwP0vEt?!}Ygg^$p&e1gtF~!UAAnR zV8kn)$)RfY6Wsk|y$>e@;Rt9muPzt`igxAOmCO42`?0$j0lzTS{7OxB#SS=q(7
J1h^347|+pV2K{?E6sXi82U^n8I}k_mTIPFyk(9qfpXx{X**wf4 zKy*1i+)$)R!pqo;9(}9d<|KJSA9TY|HvQ$E)1*U&0~n~(3S~p{|KU)8lce{>fhBMW zVPOnhY*Bp?4&c$gJ}g2)LSKj_a10F%4S-*3Y+?d*)%W-J>AVIDCH>+7N5tV_Wh|{I zrggx-+@dQeizNKdz(2OapcfsqP-0+s7#Q5~NmvfHwkDg9Fzzc!AMGcv`s?fpIM~`@ ztQh%ATX8A?*mM%1*=Iaeor#{?vEN|+|cUnH?j4$ub9cvAftrx!qWgiEnJv8u1X8}ZXUUvPLGh7A`@wJvB6 zrCYG4QED&aBO0B_G_D>BknTIVxn6eEMPNT6yxn@nKItG}t{Ocmub*8hq51S&k}gE9 z*U4eT`{QRWTi$1CZ|G=$h_w$0bJ0~_208R~$I!JG{49`%n99i43M{W*&BN_-H90sd zzRvZt>W(Daf|n-x-adGPecu9np|d0dTJ5qgvfL3#pL8JmA@8{4Yyx_fWvMf}2domS zwzV;vxw^}NUTI?Dvzw4kh%T<%{+@jq15Kl0_5+G(3ron1rq9pf2MHkKn>%2XeqIBvLTntX7-! zk5ceH)y_iJdLW;VkI#Kh6iMt)m&?wYxp%MhPI(JNd7O9$US2T}P9#T}5Z>)eH%-;g zy%VHKN{#?~zf^%Y_DiD;?K64{9qX*-_#nB9|wOyhIXMF5M1 z3J28&_F2I`;xYL7ZX?a`lNa5rt0>_`RhJdw!dbMS^K?c7vEU$Wf?74>uXr2QJzxRR z(on~S^);F8$Hg*Di5Dv}VMhu?Ffr+3z0fP0c1``7x#t_X#o!hpN>lMj9k2W|p7sZ0 zvS{Bwqa>X<+}79ruD457q?mx{fG5Jm<>k(=L;`!|E=%r$BhOloY3~lhpQ(zbN@B}B zONdPrv)aIc$xdc?+}$I=Ahu!Mf9URhTaR4tBd)0&rJiKa_)^$uCXxcHuj-FW&!5&W zF_8sg*6U*77i8D7h)dSnbN(A44{@*LB+7cy@a?ih4$Jd%4=>~fg-+LNrUR(cBtp3$AN^@sNXzjBy&EnEPizO5R zj%oB}l8)>U)TCSabX}4~Ezc-Y{7D4oLXDL4HOZ|%r17dszk=~;jO~xrRXPX+0xsH* z6d;PfSN4c+S;Shjmkt-}PLXkw}?QD zc+mkpC^MI3w5U8O!0FG^u4=0*sInrPC^QXh@Q`Mp)UNHi!Fgx<5(wKXXo|j{VLhno0^r$Gl;HzMwSQ+iQl4q_h1W}SY7Ttr~{MNR?!39i2 z62C5drdp3J<;t=p+Nu|GXaB<#WIVzul9NsZX%&3`aJkOR?ZN?|c+JgZ z1nd2mgdtl11$eGLBulvrx}%Y^xPsb*VV`GAXSO>OR5Y_?Q|7dtj~Ts7!lUG~Tt6<$ zS%ilsi^-fgPykxK1Lvvab4p)niy%#-a(EiJ7}=(A}mp$w_sD4>|ic&tzw!Nai&|;s^ee8 ze!o08n6ykWoyON!j~%eZs$ZY_Xa*WvLQOWV&b_|XCR5T&>z|D@svz&O^1w3?`h7+R zCBlos{$=Xq@IKK`)ii<;SN_P1I$-Bc$I~)y!xI3pIW4AVos0R7{|rA0)+XHNar%Df zAWjFh`S^V1MWwS@xp)hg&&bvh5B*zWIkfOd$M|u+T|SUnoPO0}bQX)JySvS)G$jUS z;MS6ewt#BTX%O|caRC~omMqC_t&HI~8i&fP7fXjPVn{C@H z)8f`sG(v&9BAc(t9z(d=Q3{wdbO^f9^6Xn#SviD<_e88CQY?b)C~*UfQ-Aug4f`ER zzehQ=)eU9@F5i=8)Cp*tkw`}f*B@n5uHznec+ALaq!5wVdw1HWiYxnoxV8cBCwVQr zHup!Gs@W2Qrjgb@;=#Mmko?jLa498L0ddKpjQkeIfVZJZt(}*XoqI_s-#b>f$zY*T z1!4!D=d~P%*m18SNCQJVDk_M5{3taimV7iieSM!Wv8Q6wo_y_5S_m|6jpxT__S5%3x2FEJ%PDXd`BpZCaMT z3+_=7OAbV)ghv?J=Mu$nGQTWXwN3pxKqHD!`IZj^_pTP#h?x!|*C|+KZ6lT#fVCv2 zc6D`qR@MP8ZJ(o8q)#S<0Vd`7`MG%TInMz>NVHFbWd6w+(JQTl_zOVduqp>w(v~}`x2`C; zOz!Tor&c|X3O*qEYGrq~9sw`nbJ1{-Zu2QQaGGLk;Sr!qQrp5^@M#Y`VLwx*U+{Sl839DNGxqz0|QA6BQej@^B!t-x<$YT3IQ;8k4YC zum`Gwp&=!Zzdv9AwGG&Li6;;I{pweTXoCV-d0s(*ot;mVa+!Q>HsoIcHMh!URQ}&3J&pXKyxRYA0V=q1_$(D@@PQk7JfpFXMk1HjmRZyc=5=ak^Gc9& zIo9&3ACVm*G>;g?VsCWU=vxI+-1{P}3;2;DGBsxo zMY9xZ&b6fdyvwc_6p-w?8aD3nj4!+!X(yW8KRW65B;6#{>~NQDp>RqbChPC5GA1OP zvcFnrnRcHVn>E<=93N{Ll9lsT-J>kws#0pwg-SjicYae_jR|CDZ30t)R|4EnCGeE?6)A z$cgK3it{CN3nnLQ31~cBUyr|z^n^>Z?e1^I1vBjPi)GKNiSRwl(IiL|pb}A56?*pd z<4g8HB|~XVW5uuR?1urEtFS_y+n$o$wc3@=qaw^M940$3+M!GpjFAzdteA8xSSz%zQc2Ik*QEH7O2X&>pS&v_ue(rAYmoQ z-N}M~oEkviGgocm+-&v_4*)V%2I+v8Wz*}^Dvb8w;Uyr57LYA3V8D>pk-qg(Ra+_@ z6$|*{=HTxK9aq7e6?)F`P<;it?eF6{p6DI(n!2LxgD%Lu(<>YR0??>m7>9>7)6}6N zB9*~&SR9s*TB!4}-q9Wxj}P2D;&v!bl57se(!rHz>#hNDQQVTKte*gySp3d?rv#tM7pG7=u}1-LO>j8>4tm!zI%V% zKR!wh_?~^%UTf{O89+XZ#>5{~+Yiu1G?4l21bj_Lza^{6=oBnA3EQ?KR+Bgm) z7yI6gL~Nb-&aBK!YJDR0`twR1LpxPX4G3F7&z%N7Z`XtiH8&T-wk_s377$}6D0XvJ zM12FCO#}y4vPX1{{egv(|NU1xllw^fQQ2IkS>ay$!UZ#JYx*+wS^I@Eoxf_INWDeN zDwpz1;bZON_P8>%7^~x*7PUw!EmoNZ)#qx;Y-LfYX|kRCH71Wu#!Y-Dd4ikV7MrW` zgq!rA%}Y7qQTKDP{4^jNo>tWU=MDr zdds1RiT(adVk(0X;p+5AR?C>ALM^1G-*1ZS#oy5Yv%&+Ro^fon?(;wsqy}cvsi=Gk z`MNJu4rw=0FUry6A+q4Nc6HzLlcbq@#54r~CftoUA@W#1Yc5e9rvf;SH7}Z0&aXatX`bf?okaS(JqUBR0qbsjee-K?I-HzMR?(4q6#G{_ zbA0-P*YqG8;z{>p=BLG9XSc_>*GEp*wb!$+gF?KzQ7~-P_bLC6N!HkmWDli3^4%D+ z=ArdDHrW4Ag^sA`o;n6AWTrginCa_hvOwE;*x~3p($dlrT`>OV z?rtOW8_=D~&6PU$yr7I<`;vrp4r+zZ^#NQyB@Ef(lxjgiNF_S#_2)G=a0C7eU--K! ztNMD%!(bkP030P%QU!AB?1}|u)0}f&P-q1s5&Fp0_qnNIVqt`Iu_Nel84nb(zPEHW z-U63{o7mZU20Rh21ZCzbcr&>Uw(JArTHgS28PU!RJD@X3aI5_pc0On(L zatYbsD{Z1mT>~hwoH(NQblQ7YVt@xgw(N7|D+__X>FCRc1oe(3(EpKdo53_o5ZlCX z7vg)}L3d&xYy|QzS`J(ut)KK-KeDrbCXihb`KZoN_Um|a?%y5K;k93n-2E^Mw{7jJj^r^Nqg^pWV)dNgz4FECk7gDov~1@wef!k{cI!$3x_C6+;RVcHlo zwRB?8<@0S+F&Flvr?AA>Oiq}^netG~>iqdUtwR%|{z<=03P%1R-Sp;|g7f7i3k2|# zzmwl2{=G(YPqw+CTK&#APki%bOGcWu5!!}2mckTAU)5C(`rbUR@92>Jd1Y@e`l=#E zwo|!8)yk_jHYw$%^W`Ph%dn*JDg9D5b?DAMeR$Z9)(3(K3=?YjZLzPOgzA-!k4fq! z^Tg}?Y9PC9d@;|?#VPXLzGoS;4TgNxlf0mxux{KaYZK2x-QvD{ZROfh$StU5`s&sv zRFXC7g!U&}k11<-fdhz(l`IhB?xqSQ#>_|cGE9;{X=A(8gzvn}BrGU&EsPaa@kb1; z2+}S01$yc_Uy{FPgB>B7q^2auWNE`$?imTWcZy z={m%|kK=)k2*1Ud6$M|qoq5I;48M2h1y!w(W<=pB?`iuE_n3pHLCZbAjWpE80cXXs z=(VA*U@gq;^NwsIsviE??A>_h>@};j=v7Xq)6vmW;e$~#ePx(#mnWX^u9bbaj&&e( zCtW>kVf{z*0xI?ua5hn6rO1ESEQ#Vf~SA&E_dU8mHK|y4d z%wY0*ntH+lU+#%7%vpcI%7_RZBouUMwMju5NM{p@WA zs@bAXt^aEw`adtxWv~3bE+TZu+nnLx$``rhkLzXp?2({% z1wAk*Z(v}&?EDMLhc8zNBz)`IfgTrN{9tGSYQv+=GDJ3)Ur_-Rg|ZpzBnF(Dz(UIg zokj-_k90sO^yyfs>&DBPL0{J+mn>XdTrguYhU-68cJe{+t@(4mZhL?#>ZUcs{_TS* zr3sA!ovlfhNRSH5ZTr+=0naT@&?iOX*}^Wl*ZyKZB)MY=^JUs-@i$|xo-@#Ut^w2| zm>>-ui3srUh@kMG<}6dm!{^TuDFa_X{1$+dmzTmKNMtFS^plBR>mHzgSQYmh3!0kPMAKkfvfl7eIf~LG zl%Q$IveEqw0JyHnvM>PneoEKJ3Vq;(0hn(jjEBKXYVnx zuo6L%5jFj9gIk=^@3W3#ja(_j=sczlO#>l8x|kfN0ktQ~>)T!zt#CY7tBfLSLd=C;-fU0oue*IOs9F5I9^egi%~%SuI6v1#`N9J4;l6c{G^VgcE(ERA z=)WqMJV+kPJipBB8qX{YDwqx?jN@7yx!l=t=U({iYMQNVM_ljoHr80bFXvbO{xx@_ zG8aqf=kPn%$NBpdZvzZ{He=00Ys^OoTK1NH-u6d|UQH7I9hh^H!KSu>t}$2$Qt{?1 zfOrJ)aw8O%?`-;O7&g3%Bx7tInn0y+Z+gzbUJ}GAQ??s)!D4&RbnHh+iO#L|=`5k!*> zPjAc=UOf{%ofF6S0hwhjrQCYq-fzZ*+)Pk??V0tUmI8XA-17l6;kC8(b3J=MBm0eu zh~Brg32AC-f};##Gf{eIvHXIa9UXYT&VI!|5de=ikS;Jody|bTxXUX^8=IQKxg)sn zW7C{&0=vJr8DaVl{jxc}rwd~O3kwh)KNc2*)t(PSq#J&)zjw3AA@A6vjfB|MF#l^X zQ?l<6l>=gZ?wG|dG{4JqvQrM>Z|49=bY zh}+&?Nn-1~+}w3QKu+4B2AxrpyJ$?lU6E<;0e1+6%du{rU7dFk{%ZOOgfAIwJnmnd zAO~7sP!iNw`4nIU#LiEeVNt31zUdAsu ziJdb)x0-5pJiUCz^p@YN0h(oh^7j@l78aIIj)WH7)5JDUyiIJ|3Cx=tu@MG~lL0oD zoWG+dHU1~tQ(!*UduL{hz4j9k`h7xvs^1ADw0}IkK!qyqK$QKkJ@^-Bs%%xjKJjPV ztJM$1IotkTln_1=Qta_z$FqZPi+8Sf{2i*5z$PWBjMbO_^<}7-g3a4tB1p-M<>}G( zXxgn_jBghKA2(D?0FUJ_Wv)qJ)@ZJYW$}X9r(Sv|(Oak5#Feh2nvJukFLQT=)f+b> zD-aJfH-f}(Z%|E!&&C3+*hKQi-VT-Hf8-Ym*uPZ%dy3^ViJ0@kd%NprM99)LzcM!c zx1;ShkFiLX&(D*EL*M-iWt1Nb5lUk$oR1c{_b%6yH({Ob-Xh& zO+Ze)|1#Sn@OR1n>a1z%(jJi(8xMNR-tw9RhN$a7{Fn8m{f+B38GX`&gqH5mz&kgt z@BKAKzpMfewEiW#_lJt#sqaxH76B|$=!2^im%@cezCK50o(lw>i>h?pAfh`l9V(Oo zEpalcoOv2ay|8ajVhl!p0Fm2eYHlm4$6{AYcm`}>G zwOB%k-~$EPdq`>~RiO|z(>gcsr^w)bSoe^q?g0n>?za9Da1}o6|9nppruhu(T_=cl z0Fu42W^+V}(uPI8VZ!?K_DLlIB&WP+Uvebo0)}BgRr{}qyE-Mp*G*9!N|6@#7fnXF zGl3#H%g3?M(%KrO7Q-M9iI?puHtQ=73;W`!M}#hu&j{8S1r)NxB=m@GPpT`Z0$$Df_gql=EyH2>AMsZUi9Q9!Rw z0hWpN8o$-=H|MdmvB};>)Y69)eY15r{57%$Q6&QqBw1~C(|ua^aZ?|#1lifyfkqE} zao~G^LZQyi&JslEKKmJ%ntitzLx&_ zzb40J9rItXl~%1GV+2vqA?Wh*A{}pxM%gQ!zRQsp{QKg@es;N?xA*r0Q5|yYVJrzY{5jz@>UJ`2zd!Db ztLbvGgF2S8*dBn#dURYn1OY$yP%u4)DwIB$Z_K&WJkYV=z}tV|L`_-18(I_~K#;GH zZm42mMFtgqzPn)LnF|{Dk`Fvl&C2cjSkvhn?L5ybtKuZ_q^o&-ONgK3>MgeVq`GGv zCbV?EoWc@SIKR~D8U^7~+J?HtKl1tyq=-nwvvkn1b<)Enqk=1z|Kfvvs{CA>L#^2Q zjpPtm5(74$!jpNG!k^S2R+uq?e#SB}FW8*8B=K6A{ zA4XoTas|4iM8l6##5Q-nGO<(=1MASQlQUo^a(E2JS2Kv@Lq z(K`&l29m#>q8l5M&sExN@_l0Q@W8+hc&fnpqKucDYpjg7>O}%F0yqIj7$UWXAZsij z;|kcC^z`(?OteeI@%leI3pXPj_yq)T`jdK{C4#0=J3ktL9c6a=B*ujeLF5XJ1gjZ< zhh^9E$#Abl3v%$EXNr9b0{G_7z;Xs;J>ad75eY^sNPwFGGyzN-*tGvd(-z02QG-NX z-uUvxHor*FcFb&_oNsA$4`GR)lfK@Iy>3gM$tkRuvH#r2H3h~%A@P%B@+l zXk-ec^IKs}l>IYNO$(HT7snMgFLL6T*RQo40Oee~_kH#H-Ih zf-_4K9&3d^JAO!rhqZbs2m2c~@B)YDNkeA~+)Dfo<5%m{uX#@D+mS-krtD%0`h6YW?#&E8_?m^%;cSiR^&2xe%{M63vHnS`lx zoNScN7VGBc_U6gHzP^^P9X_xHP9WA;%z0JstIuzj4c2dRmZgD*HN(Q*(UA&gM;DPB zuCvk3)f|zel#ERT=LXR6FI$2cXlfICFme-MhP7ty1-6rzi6ty_V?4m};{q*7qf?su z5U_sGdM^w-iQ&=%QhZN>n+cj}uyNlC0CR*LDbNz{$Hx8>vFrKFK(wET*B;qkN4@l0 zyLgn3xsGP(wSAK=co~og45Bc9R3LN^oC(lm-bjM`>)+D{Caz7nAY4!aoPMH}v#M{l z|2$LocqymO{w)T$2}~56Of*|9Q&OjZyAYK3QGP)lVq{4<->qy495f3yhlZjUIMigg89ne9z>VyIX(5{ zuC-Sf2k0EQqX1}ZYis+EumU37halj<3i=$HC+F~Dc{=!ui!X!J<`{lmZwfeY@z7c} z-Ri=~buwH)PQl(%N92Cw@#TYsBe(MxiMKZom^x_#IR;wauey)x*C^F!nu$_2basR~ zKW*0&(H9M(n(~mJ4Flz&rQ>y{p_QFqtiYGm_{9z{!l@(RlQjK=L;HvQ*O?q7g8g}I zQPXcwcwXC@`V=It%f*fCSH;Iio_8gV@~FY4b;&(eM$h)_USd6qQrql!*H)ehmVH>F zP%iM9gRfceInrAT42iqCN_{{)rXD^|-Qn#QOLfKK4R^akmTmaIY@H z)7WErA!_(;`4^%FZaK5I%`ud2l0-!3_rZ9~&!(O;gN#fqxw$X~dC)v!?C9(2GU2~B z?RvViU*c|9qg>_RF*zSGjT3u(o{`=DH4nviVKbNlw#9z550ywjt-@fDYEKj*c43%q6&I2 zo@~fli5Q8s9Ow{h_MH@C$VSr(v17OHFmkGF=N{~^`!@J$ECcxSHWFyMfBMDcL>bVq z;9hOc2RuRh`+2nZFG*l>>5|xw(?*{9V&96NE{iVL*Ki))etMbTFy+4|`Zce5JOZDl zB$xq;OC!x#QImIC^?z;qchJ44GVo8zQ9=3O68L2WMatv{pEloQFX>h zO3ghDjh68hw>^8DH5CzBG+R-Wn&$t0%}`V+KCeWc{sT%e0!; zABtkQlt{04aKqnE@;xKN&3W~pSRPY`a)|!TBCt8T>Sdz@ z38b)LJ8BFCbR%TMh+R)$`;1F9F(w#B9X5(M*dv)T$821=u+c#G5cNS$k2B%9n}dUr z)hCMv9k%osPG<$yDuJyLu{w@&tMV$ucJxhoZoj8}eq`#{hAdlcSgYDq4;Ma+)6GC@ zpI2tur=a3!0$hC?uGxT6rlB2ROUEs@;64_070SH`_W^A3pA+Bq>B>_jsSix2dFyOK zGI>KTGll?~l;;I4)@-0Zl!YTT)uUCew_}`ibT|P!jSmZv2b2nQgMom$c zsYL^NgdJ&WVq${j^CW^6w2P`wnsu?!0gW{?pBEb&tEi%4*8z3}ohVycmY0v{Z}cIsTb%9&b3v@>o4x8j-v~L6xjoi1vao_gVm4+ADZTp>Lr)^|ZqFeQrJ>41lWaX?f?U}u!1MzEBDX}wO z`a{8_O*5);az?cvRS*PMoQN|jObN1I?-#=^Cmo8d&%8{?C!B>QIMSH6_otsewOdo=J1`H%32CX3 zNy%NynHt9pQ(>tOM2AEX=`ucs8&eWsN4?O&LN+{>RKSalr&UzMNr>Y%53|rFio_|i zguIfXi{=h{MMV09SZZj*{+UTI)WQ6aHEe^WL`g9@$%!PImQ+N6mO-}fr72n+bS6}a zC`li^lxQGALYpa6jxL{_n3xHZCx&~#TJM$A9nO7B5?0yLtnbT)tRx-Eso0?qGrkyk z8giTf*1^-Cmcz9c#&6`><+27KkSO8FVDYc37RuZOe!n!^TY4SGzdnCRaFLIWx z>~FsxlAd5!!29r@1G6?mGg6%D+gD07;v|TUEp1GB56Q_s(ob`-$-;$CI;R-baX|fT z>#jNpM`e=kRSdI$=Src$@Rr)6nxStFUHf);V-9Z=Lrm;Reb)ot5ZlLi9|jna25-3P zph{SgZ)7wWvn|O#ZG-Nd@1SV-%&|p;j!WMFs;T1+z>Hz7fNid1-P?hqP6j_k9a~DN znoQrDS2p}m0#-6yFbtFb3>>b6^`BR_1)~z*G5}e|pZkqGKElL$4UiPX=#n3Tw>_%9 zKWbm%%vQuicpL;6A>UNrl%J+15c}9{^M`7ps;_q8QXw?hEMz2uKfnG;WBOy;w2 z+jQlMlIVVLSZaWtyVlMLW+XNnU7&haZ9WC?opJm!Q&!ch0pcUKBl~a7?t6xynM*Ca zCc`_z`<@UQ_24(NrC^6gj$nebt4@S;VVs4TId9q#XuB5<6y_~SF+Qp)@IDKjq#p&Vvt zF_Ux{k#`psiT#)<7nDx+#!kOYc-AFYF=CbhjYU$lnl}M0R;|=qd#9^*!499Ig&kZ*{{r^2@p zXw0H%C5DLQ9`y6UIfvEmo;3{)pZbvq(beJOlWaWUp`)XE!7ml(=uekC{)eknf`};A z+%hf6r$=F)E?Bj^z(iYa?H5)C7p(j<2j)Nm-L`J;tMh7~{Z8o?)%nH?-@*vIc`4pA~CGA6=#ApJgj+6yA5 zQByJ_{6a_I(vi(wq&l+Sk^@R!*VOj&x+gc1=x`E_WymK3vrTPB5l(IhpS_T zA`@POBs*id3C((z_jo!3?ODoMXYuP6#8Ly-u{pa~soZ@+@oZbsL}g;3Ex(xSA4;QD z`1B|K0yz`qOrBrCT0(qvhAf*L@U-KGqk$H*KWRbAMf3ry4G_pHc1a)o(1dBeZnFTC zKrt{Qi13bGY#c3A&v4-@l#zfwTKsR*i#1~|V=f3y*?Ij%t5ky%N;A#&GCdrzeZqGDUP6ci637p1=?YQ>m||FjROTKQ z>4x1EEnu!toh>~DzgItlCB|i3v`jI(3l65)$J}MQ zfF&Q`neQI#bN(vuIzahktW;bfDpmM{ocXr=GQC==N6D<6!uK-oSKkAS%D5c!KYp_5 z7-H z82$I~YquR)d4m!R3DNfC1vlogmz|HA9*sm5NmXlnnJF^}hRRomUyksNR`RMgB=HQZ z{?1>}E?xXVK%2xvIdREFX4cX`Yw9<#1=o?9XU#W06ip%N=W2Nr;73mQPOrHTCnf37 z`&DlCI<>LiX*A{6uk7iu5o;7CrK?EIDT-n9-3~H$q}yxi4Po{FVyW0Lf~X9z?>l*O z(*}QT0R$$=`by{j2V_w_G+zs^D#Yt&z(*^us9?<;6JfVG%_)Q7rga)70*2ryi!uS5Kxoe17;)IUxFax(u{>6d;T|Kh0NG!_Q-N= zmf20u#)UUk-w!WD6V`#=la-S*YhF^{YTtrXR8qSCmMz^KX^$K_`V#ij)veW0^Kt7s z3vIWCVWzY!lLfiJ94^1!>Mh@t$VdR%qpH1a;J^ld0Si>cEUE8Ejft ziPIa{U7M&^G57R`ea49}82$tl882_>I>Yub>IkU%u5D0BBAT%2A3>^Qsw`x(@Rt5- zf7fx6!3N2WrFnCfB1Fj~aaoR@fs%%2Oy0I<$FD``g?@o@ORNJ@S0-!1a%98RH;4`S zM$#2(*F>obnQ?4#apMF&e89*BeEbG6&6Za-=`@5Ny(C~~=Yit0YK?wvcetjZvD0m& zR1)sw{AT;k>YlQa3bRmPJkv$qSXaQ6b<09D%&r#h%++%w%0m&^OGBGJRVZGV^gcnl zd!?UGv5&=E5DJ*O;m3y}m#MaPEl4HXvWV&Cd{xM*Ab~1$SBH)dpOlu-HBmmJdO3?A zk(G*r%2x5-ag)Ot3eN0Z>JbVj^h{jE`N2k=p0lI(s0(GL!<1pMbrl9={rxMmR2oi# z46gWO89AxoUBigCo=IG${+=f0FWa7&?ubXXD>@@H_lzxk9!g^u+{RUKbH96cYzLlEmp{T3m)z-kRq zAi-cvm6AUMOODPptdD{IHHQ}4U|m4+M?D>zg#FS;>XT-xc&4x-H4M7x`P_B9d51Jn zYoq_|9}-!=4vZf>X^Mns&wa4|PsMN5-ZA)`>>F|FjX4$GMIBMhDUtXr@RW`e+=}(a zwRw-qJSkf~LkT(=1P<-k3lg65(wdUI>FYKA+^=SPeWcLzyFJ>WguDJHrBTK`#W|YH zvHR8-nRNa4VDoI`v7n;DJk?`(nwEQwi*V?X&8+0Hpjg5FsF%J)I&HJ_TM%bYSe`Vq zq%bF#0VnQ1N($6X;0-h8qA8q89Wl3V(pA)8)?U#@c%}`S63c05yO5dvAP3Lqs&gd{ zi^ZT&S3u0LMrlRPaMFZja@{~Z2N3$eyA_9MV@w^MR=FZUNAYPJ47q~uEqp{cZLVFV z0l_pO&l?NN1{$?THl8}8q8nA8<4xUBs}=i4h9SM{G=8ZEhXtL zyu5RZe*5ym`J82H$zV({)dd{s!iE?{&9R6uh;=;AlIsOjU0odzerp$E84h6km@*_{ z9Jq(;iDwk1z((v6Rd^%2CSv~2qZp{5dz2LC)aW74H&+T1`)3?iH)6YiWJrYT#FLzGEdmHvNQ0K>Rh?;8(jBpa>M8;=hKDbVZe9^$?Q0dDFuK3&UPI zksbrB+{JrMs*D)M%NwzR<2v?j$x`^KH*<7y2N`$tyT)RL{uQa}_9aAEY_H*IeB#N= z-jaz{-w(#uVu~kAc)M2zd9!3I{@w)@yqeTbG3~WjBYwqh6I`APe`-9cM)++>eh&7` zO0^hlztOO#fA*|N^6{a{m!8vk+lPCtSB4pO2IYj%jiZ*@5R(}jdlN;E%|MH%-H5e! z&%YF?P4;OBn8<#xC8eLpil7t)rZ8)oWGNy%dU@wq`n@mPf>!f>`ff{)c5>Ow7;}f4 z{n}?s8E> zOG*D~zKY?F$GYiWdsSNx%kc|B<<0Zd|GZhY68vK1EbA#DvMG;>>)$5rd`45pSUj+Y zb>iZ$i}$FPdVi39B+<{+$M`%uHKMh5hA$yCCf!~fE- z_z}?$%N&q`2_?Ma_Z)syeiVk|osTB)B#>^TXzJnge*H=YxFraFittqBd4_#QEllW} z#}J=Vr!vB$salKm7GNcPPP<$sK$i+B%@4W&`Q98L6Y#GkDujmzrhCQQ47%2cl7`=| zr(V#fr02$zOs{JNWBy~xDlaeRBL);L!02EkG%L&4&5d-dx$#CHFi(Y3W=Q`2)wk4s zwCJE@2X?Cvptodewjd29(k<1c{I zB!)%~T%cG84*<##xPO32>GtO8-vZvQeRY^-c0qxqr)P#P45U4xGlZ@Z3FI>3(g!G# zwN+y4lr`hM4-V`mWU!qH>`vTn2fD@aG59=QA))ZJ5g~J3c@Gy`*BHs5V3VAIh$3w^ z9@Q$M3$YD_aNv;OPyL0r(5zIJVosEyz_=zjRPXSq?KkDW3;bo#i zlLD8T7{TRDDWNQ}B!no8*3`X#wx`*iU4(Y;unn!BSao6l3m8* z%2J&xLJZ;pvwR_RT073(T99upL9(vWu59Ucq_*$r2^cm^V=@NuoP?_Fm zg?AL<=e*)$#Sh>ldWsZgDfmMxmzn4P<*?2p{Qm}F_WQqQYA3*TEEi2LgcOQw` z)WHe=YA1-wzamK|g46P3EP5ae&O@faU?mJsw4))J%Bdklz4TmGidZ)KQEBwLpe{wD zh&Z+<`8yM*`m1E)=il_F+l z4%x>_8)F1HLH(;-syXq*UI5)0)>h#C$w*JqEw{1lQ*a1kN}bsS_gD@D@MF3hR>)>1 zmBf@CcjLs#Q&Ix+kg4+BSW5(5p#;r#strMD!OgdxiGii!Md%ZtiM$<^y9SxCfMi zAK_xaSk%$0>w7iWB-4!$o9>Nkz%vdsQtbXD%Le&2{rR?#m}7O@UND3RsB4g2X<7#a zTY%Hn1eNVYl3%%$HarZaDFOxKU7>D;g$^JNmXyl|*fYoI8BN0l699wV)9mn`rf6>S zEj#%L>hSn7KoMDv+*NgU11cMS!1HJrcd8la_D7swU23vDB7gk2%E8ffD%ww5xFyU1 zS#sS&K>7{G(#ndPBlh|$*&EapuZYOl{CtKO;n9U?hJ`95Ur!af`dztxH&iUch7P|M z9PD_@P$@B$XSk{BXy+ldi5&&gA1-r)UvVs-4~QBA!-BsT9}xcCRJnQJ zwd^92JAr9g*Jq*SFY?^);7#jgJ;oM{^34+m%0M}E`QHV_-`fWcT^XDnFt`YspB&>H zwn^1f7MnZzNhaLHhXfnHEvc)M>HJpw7_Jd)ZLVvBm!$ftW$E|4p5toa?Pj62t`2Rg zTiagyw)gT;0sNhoL3kPy4*~Th8plh`y|!?#+nK_@9}2-DDZ?xFa>d*@)`MK;))U_S zb+8gS!zN*sW`xL0{(;#z3EzJyWbd82iTT0Ab0&dKV&(tv%86*M`z!Ib`tor)3|@;q zSV1?4ARr)u$HlRA6%)ol!MxW+wN*sM+Lc}$MN`J0jxpzw-ftGtnE~5jc~`LHn7c55 zIah|*wmDfa7VL?BnonFl{7^P^l@nw&fYN z)W@W$@dyclN7)isTV7knJ0QX4Cc5A^-)oq&x!)5qe_>;jjVM6mJe*at2>_Jo2np-H+gbVuT z=EJFEo%VnW6z~(8MS!9Pu0YV%=m*`LfIXcyebYyx|8dRP9F}~u1>Matx5h42AzyJq zv`r3xahqt@TU%@mybbPt2KhfKcI3YuxEMSJb3EiRppC}3(dVo{m_RU>9JP>so*nY! z;Mz0=WH@T=9or*dOioQr)y^0Mjd}EX7HmQZ{U@BbpM~Z)fV{jMuvhK<_cs(U1X3;B z+#byFIlFNJtUFf!PlJ78w+()DW9-nA_=mU`qLN8THDE^JA{+w*`j|kb_Zmmcd9kIZ z?fF8?{dpBr^*LYHu2g8&lERZUBQi!eX87anpPNOSP`2tkW=@r?S0bs2E2F{2Hjm>= zF|DJeC6G21?}2<%Wqg0GSJYG4{IJ#fgTvx`ZrWvO#W{b)Pa)MRk*lY|1s~6-2rcxw z5Rg*W*))>#?WUCi#cVqj4Fo(g!2@# zy+6eDeXS+v#Nj%q`yki8@sfQ9sZyu?a}FTOmK(5Wj<@jQ@duXdlk7PEVn@3;8jS}g zJF5c9E-Sk&GrKP+XejQvU}x&{PdMEs-eo?BOqgc2=??FqLCZ$}tuRnIVVi53GCxYZ z%kuYiVe$aYgCr^1bpOD z9*|UHFU-o4z?Lk2`~5&ihAO9AUaSz#g%5g%hK7cmV6{e+?!6AEZd za+n59qXj8{L{gSd60nouYCOY{)MO{m&dD(~HGNNm{n;VcuC_Dd&GXJQmIOinbFNN9 zLRLnw>~?NGH@F%11xIQxjnL_6U_A&Vu;z20Hv)IKnHjw*9(fE9gaJfmdGrQ7u&48^ zpj2D2{x2Zl0R=8qEK#K~>p9*>1af=#uK?Rl8owwbVOzEV3=99F>%q8^8JGd}Et$Od zr!FyNqCxcu(${W`rKU&$)E^&9G6r z`k#l9(HMcxJpxTx3j6(muz=q)ykNXj=p#H14NdA#$);L?uitf1nn5SI-^ zKgV|RqPq3=47bJi)aZ{-R>ap)F~0j!`!z>$#0LuZt3Iv7RK8mulwMljZ7PUeidY)$ z#(Oz*;EcQiVRrS;+0qaD_ojYYy*2nhFFEDIi;E%^2zcM^e^+?$tzlb(z;Nh|;SH1FWr|e_4oK5p^S$r2 z;*NKBdS%pq!3phJ6E-}p);c`4(pQm{+v~XET)yi%bA#u@qvP>cZu6{;4iTO2#|SsL z6sgq*j3MaQvUE$yNmJ|U`&S%dry+!YFO9BW6r6o{%(HIJ9R|-o^H2BsvL0}KOZ_GB z=IZ&ztQjw~d)LkXd}?{{Wd&6cRo~YTGk?UGQNNeiUH15IDfWKmzYN{i87*|xK~mg7 zabDM3daNE==bd+_fq`p@MiZ~@_tE@1e%W&5weLsRvf_iLvjlfDI>hZlJ*qXXwx842 z+eoFpVi5@X&7|3q}8ec0nt4D?t4_L0#{y&M&{9oC3cZ%fcs3z%IYil9|f z0IeYP<#Z%Kp%vmcn@Ab50tf{+Yr(m9W#244D_{I$%y%y86Zf<{O}FkkVs-p1n)Jup zpC9|Y`xCdG1&+Nok8m^ws91;#+fi4AphzII$*|>=S6y|q6LMsZ>4sF`)aLc@^uaXof)zl=_xm9xI1Bl-kI^Mf$G+RzZp+F1P~sp`=+N}O66B79f-R<8aEsC#@Pm1%CIq@+2eqm~T-(JRg|pnh|4q?#7X zbu0}vJ5ZirswseisJYLQqOPMpB(b<@@;JsghvroZw-toX_5}fzXw?1{uzP{fK+xDI zlu5BOy5Sw=8R-n-Nz=_4H|Ui7B5w-+v)G50Zkw*#soe0bZ|@AJ@1ArH4P$8WYlsXs zy$kmCsc)F193Xq6ak8OrAJr6KAHE{ULJWI*WKDgBTS5U^s8>l=2MD z%*#OPcdDtk+x%xKKuLWih+&&rYgbeiJNLDehFHp!p*8dg^V-l<{6Rn)Vj!(k?%TnJ z;m)nr%8!?uzgD~4icc!!{x<-Mv4#@E>2cL*UXf_|GNkufV+Z$E*_vQ?+4s3-%TNXuhscW zB)jz*qcz)1apKV*f5%u6$Ij4C%q$~WzefYEw}O1}dTD;^Jol%tqLf~~DL8AWU--!( zezpGb6YQue*OB%whCtU&dftk?%Mr(&!XJ62pu?-y?!cUuyF~F{qtaL2{dR`^zWSx2 z9f{i5$Ddg}eJaJchf@9viB5XHpVQ!*q456efEg4(BLOE%v;j-6paNO*h_|Q86g<{6Aq9vPsZwD}yrwhX@ zN|(-e#Omh)W-r*f*_R;P=lL6_ljw&Z2!YHn&u zry3Z4DwE=;37T`RmoH(_BWhKznuL2n58?u?`;T;J@8GZ*V4(xZaB^C75RdmtwG{ST zY3^4D7S%Lr9%PV$nFnjWN)0y9cz`)QAgR)lyvuPA0lwvbAx%KBv4!^<2d@hXXF3K1 z7(BxbCj!ND@5_-g<-`y{)B5lE^2u|&_Y$dHfJ^Ky*NDonm`OnL>B&a`*1Slq3cRd; z0Zm|KCRE5Z?Atd`cXOIKUU0-ZyW{&wP49Y){9b&T;SYQ@lGweyp;$V)y0iQZXu#YL z74i&w9&|x)$}w(2iTUhVP3whds`}hNL!2U0|06BK-e^*0cP1mzg$iH>YOtExf{HhF zVu!3Vcz(`zWL6Cy!XYkXKzpfORbKd-p6GHMVh>fLh-Y&KDq; zId@rmz%S+^aWeASZ^^H5E2DEbHImMo%9bMWxVAqwA?NHf8nKqt6aRNyn*PIgpf3Z{xS^YlKqY zPG&ZA>(gMy3D%7?!bG*GzQbuNCVp2TiRRz@HOtD^{pn|T;Cs5{{9r|01YUbHx&QY- zWo7iCfc}(}u_=W!*he)ugIbpRHm=SkC8}+sYz5pvw%Pqjs3#yW$uJpl?E6iSeWUK9i+Pl z;&T59O_kt}qwhfe1V;e$K$u*B1kjlBU22@QPuJ znxliG1KQ`HyFd``z)DW~O|xPSSWLmhj0JGzj5fQr0e30_S6mJo4jganW2U~oIRI0D zj_fiXw3yXxk7n6ZOH zJ_|0`Pym`3r8s336;ky3;Kc(zeO)jo%pDpD_UzDw{k*<70!G)ELAnAIh;lT6fJNg9 zMCdGUW?sR*gUp=uD={!SwBYFK=$WvQZV^TVbXnlSYj$X8ZXQ}(%xfluX|PF1`BvL? z2{)TSQpPqwI|9BV6h6aWBZUnLfTj}x<^v#`gM3sF$CKRuT3fh#Ae+}}*qjGsN4Kc5 zlLOXqnr+ezH5rfnn*a!A;Pn73r)lyD22%y%c3#&1aP{8tRPgWrc%q_Y3z5Ca%oZX< zMuhA=%if!;?2)~*laO_6%9dFYk}b!`JofxudcVJq&*$^I|LX3(o#Twx^}4R-y6BUK zDxCM(W%sGtaM5w7gnCj>Hq8e6tNbp|7+KW|?;r)8W~?6S-#$h!Yu?wsqLK6hLJ?tX z^x-&ZiP<~>`QCzH^x*c(2X~=kofV8O+fMr#IBHHZvN^hEeA>7Wl(s7FztOmmJz&a{ zQkcrjDvL~2-HncS_hQM->i4i^p}f0*d`i)+squF7JtN6`ta~h~-VbxsPG44b9PYsy zDb72%C7toA^QIKGP1`bmBhT=tOy5*)6e~AhH#ugex=7}w3p>WPP@d|$XHXv>O#*sX z73)bOAD_&%tmCU$zCJjU{2p5wxBW@UT*8`eQ@k*&>!fGAuXGwYl%+SXe17P|-p{$? zGvh~>WJdQ|^QPy3nNoI3Hk-j2dYvpTA(FShF8*}bO|Mt^yv z@A1a3aWtPYzrmqg+-*+ZO~>Esn)06I6z?yxtIzaBp=t2HNbx)-pP-PxSkWf)p`pgV1HLfob5VyHD_G5$-%5}-bF8;2Yh5C( z_1(or@@p$#8|xnUs)ty}!yfI`I7sduC$bGV-?xo;=Zt}%hr zd)H30ag%ai&kOx~>f;8&@uREp*8+>~v5Xwg6PWu&m6ZF?oN%^AUmT~m4kfp^vx)EI z*%@9uFOx7d!o=c{ylLWG9bmeDi=UQZ?f>Xu2ZHdz%q}=e#P{g52mIBmifTT{>U(2) zhZD_Mit6TgayFJbyy60GOvPJp7qeZ0q@QZ==T1e?YJB*Qu-qplb7~@BpHpMC3QmT8 z`NXHRL&c|gY;FD7K=h;m>smVyx1=F39ViY0Exoik=-`VEho%&2QdU+M7dGS_eXW>KNYDY{DV*`k?BhMpG>OUIzfE0TH4KWt zVhdqV0NYh){^*b`(9IdO1=k;(9tu(SSf~#rqu=S(!Skfcafz&VEnUBosdR&-fKl?{ zP`d*K4wbM19^*PC_VtG0^#(1ytwHlDC{2uvjLbwg0XM0fV2Au%m>73Wk!IZ%eVp}2~FYKsa;xF3vw5jWZ?PXHRdv3W*C530z$xGz**Z% z$xa8NKI^F8!Vxtf5bwbAW3}d9=dlt~1+lu83+wYCl&B3!LrqO5-1aNZ!tid&7pNMm zhc7~N1DRdi+}v@o4RpEIuC8}x4!E%cQNR}n3HA2(V_&Avww!F_X)^P03w?nFfsnsN zA3yM^va)TiU3Oes%jBhP$c>~V%j!w?TDs_aLSJ**Wd&peC{ zfxC*2S|&+&=#Y7pHd(=68)7vhZ-K7ln#Rnp^QhjBx?Z^^x|yZ#q8o*g-E#2`iARUc zfYg!P636Tngt+15egm_J;<9#16 zpT%H+cL;w*WNs+xX$k3KC@mokJD%K)fmym!yzXAZyJ6~#A#w=AxnS-yW_k#me3I{zx-I9b4-47bac_bfnE12|D8ak)Agj`X!S9Y)Z+W1v!G*RDOt}3 z8UDIH@$r#Db`PeOZpwV?#s1{sj-tSJF>X^JmHVGY_W_-m!8N~KFQJ5r-ka!;A|E~G zQXMU5X!th}{8t3;?L@;BWi#r}+VOWVTV*j5=^+xH2;AZH_Ol5Q653Vl_TkQ+ONENC zLbF?r@shwzd93`<>mv_fhSyB0k=iS&{0&ejOvRqfEp&u3#cR3D-}k*(HwPRjU|L>x zeZwqYxxTu0eZo3-SIF;N)QB{$uO4iRuZXpBp2022%llBOWYo9>Q7{(QYg(OlU&}UV zVLZs_Nxg2Diyj2hDYuRlsg@e1F@mPU=VTv0ga!;;FpGn*@yI(1mJl2tHw>=#{c(g! zar5YEOEU8wT>dX#%Inx}==|;m{!9apwGsv9{j}er_On-@g{Fx%DMf%#9=Tp462>}= z7sAMcOCcx$C`yNVy)I3-BuK>&>LaYp)R@j3Q$ICscUOP}5Qq&U(h$a7Koo%b6uuZb z@cF03%$CW^NsNwO=K}JjeC%n@ckkDUqlx$qS_|V9c~CbLKfAYdA9pnj){xgKe;iw@ zD|=d#hW8Pu$%zRqc<%s^PIzio+W+doT?j}CDK0MV;O7YkVcIXsq=R{T#vXFZh2XGa>6XWdOn&BtA` zgEh+cXx+{wlbp#Zi@DyO1rhT{pPt7$yQ*=9$`2eDmWI(&QeX&z*PUdx-ZR{+`Imm} znZT<6!SIyy%!QhAulqM}D4)DfNx5b5v$#nlbuk4C?ND!t?T*&dt;EKSFGTps_Z~`? zj4qvK9#py;#=*|c0K$XJ_Gn{lYQ_mnsgsw{^j_vkshuV6Um3dVkh~ZDJ8rE(#U^Ih zY!$7Mbb-t~Xt&5ZH7k@uQeK*c_lCFo)XvCneLyZOQRI`Ldj0An&XUvm)ht+!(HvT1 zFpXoc&Wg3!IBT4Q60Z04L+e?M*0TzKTq2Ioxw_j=JJBV$Znni1n0 zuSwT`d>bgU+DfBxf4F%#g1d81V5Z;6)|&?3@MiDNV*AM$ugUqU_{AU!lr%k?bC9k6jej;s8b(1 zg`m7Bsr~sbdbQ7&-FdV&E^1Po*qq0Y4M}MnxTwQL6$r25S8zR_|GcK(|J1-;sP)vq zH&fhq^NstLHT||Pk)^nGkAj4RWmdS7|0y;9+XvWjyq&2EF``x=blN?6%S8?G+gjo# zRe#lAKMT#IH-JVUd|WS54S8t${a8|ZZ@Zt$Q1<7LK<@fWy$_j%R?x&^wm+Nv`C*@} zAR|F8M<1VT;ZF3r>y6k3_wt0tFH=9%EuDx#l%%f9sQO`+GGqE21e5)`?qp&DT%DGd z9;kIesaAiwlLz+nz?1wHdU1XRg{eSyZn2q3 zmi}cl`Ux&xNRGwHss7qMyR1q`+v1SF91*}bVJZKm904*{Yi%RuX=AGj!qV`nV^%uWMLBJ zq3E>ESa^7NK~)zcvMK(hnMuj%tY*xq)#g>eGc$G= zK0u@Z6rnhnDqV{dOGa}6*}*`A?u!c>GEl(O0FqI#LMuf-FUUnv5gQfmcXt|cIxgfS zRh*!3)tjczCNA#|8A6aoK>Gys8h{rf=`^kv6X~m-IqXiOYafzQ6@GD3x)kb#cT>4$ z2QGALOdS+Ef&BtT!2Hd+uRza5JYrl}mb?0?R1;xyqJr=8DdHn9Hku^(?viY_yQn+} zBO-2*k{-T}iLaN?pQ7YqXgaMQLycY7^JdqtGe+y7?)|Q<4o|*c`P0U6Wp8Mq%xhyQ zE9Q*uVjbBM(P?}qp1$usC0~DR-X59%M@P$<+o%1wXLxpJtutT!d{N0BqqrBOZ({Dv zogSGbC69qwWb#ufHZLKuHu56XtJOOiN!66}qGf~W(9d@z=J08)GT0#Nw!1**j8W{7>MfuZT_!kN6=k?Jn zs(oTjDc}4o9&JEV`Zc4Sm<#7yRYUGL5w}KCELEt*Pk&zY$hNqXm!Hj1E#9^}7kB^W zCi-Uj7~T7nbEb2>JNYQC_LShn_7LZ>=x2UfrTf`SXH@6RF9d|yr+j#`-Dk45tH;hM zPH^t?@CrV0r5$RV*?WXoEbz|JTX>Vz9BLzSh@XOxvYhqnzuby!ea1ma|dDu(13(#ZesyC@uD;q_h-GG zN3i5RrV@RZQX1x?ghf=r|M>6XXgdYSc#@#WZkVDh^pIz}-bP(9$kT80R>@3%eXv+Q zZeE9YVexnHbZczDYwwF2bV55$mZsdOH{wva^n1G5G4vJzA$hQ z@}8YvnFMWl1T5`=NFFE{(B<%_H%MgknvO$aBMw7`Y70OU0o1dwYh0Mg4j@QyOMqI) z))vPkGGASaV+q$K7g@&_kR(DzgjnLLEv>+#8N#y9$&NY__+Nr}OMfeOGe z@)Oib2zNd8a+&8LPRX;8qWh727IyOSn_ZsG(E_{nRUEJxi6%(n6-x<^^w#?`5$Iv^z8St^>pe=(&bHtp#goRr zfkQFO%9q&pqFBxO=WLGD8z?96#PF^~$3w;>w@RjA38E9`4rl`NWV74W4UTvo)wWMG zZY(ap4s_PGIenBpQncCb62FeH0U!ffDX{N>Z{Of_o&*{WNz5;ci}bO0Iw1ErC*@!1ugq zX-{09zp7`_@5Jpe!tMMf_)}&{e2?7R+tYoo(%lL54mKHk_@?!+QF*J;?ifFa5LJoP z@3cGpnyjIls61lhblQ>AI2lGd0pcG?qh*y%Ra zf7`(O;mrA4ENrHU8>H7|tODHp4($#z-F!mDFFwn9ASeCOR{}aCvYqa=o+`kAL>@9MzJ<=)#HLa*;>7@~PLyTWhCnLvexXkNtA=xl;BQ%6Jt)-oQGI&8)Aw`BInt=XUv}};ufpq zSRiMeuCY6m`biH%!8;J+0@M%CdG(I1hX>in1JF&0>_q1>)20sqyAdTLMR+cW2}iuT zQuZManXKO!N;H@2t`^=gIM6S!RF2?oKx?3Yq7ilU(mKqx|hLjXjhKlR2SFs{&0^$9@?#9KB2 z+D4)y9?+}ghI)&|P|#;2IQR9Ok@FILB0k8 z=>@l$c2xF>sAn@ytq(GF-|2RM_@!ZF4J`|&h8};GVl4?}^kFNhv8fwnmTEWK_W^{B z9_fodb$RVQf%Jh@n|m)-iZ_e@M3Xb+rmLQBpwAg*8`dg zg{g>spM2z+l$L6d`@E#QT&p;By5et>B*zXl=DLmLKoW#SEAVo~ACo*An-MMgFQEzHO%fUtcA71&(ASX^h6M8w z6k?1xhJ;Kfcvu*DT@#O0@w)M4D2xdRZl(7)bD_1P4ok#O(Zvpm(+^W?o1@y58z~=O zp_Q4(>%1y;NJ{&;o1u*!ozwxl@><9{PS(c~Xt?>*1UT0T0z(A}fZ)L6<_V7tK?@1n zYn|{yQ8$*Iw4j_e_BSSzr@!5PhZaly*#}K7o=Cbh$ly1&@#ub@Rx#^{Rq*Y~bOqU`1vMdX3+>hyG!lw`s8uSMY;F zk;Os+;>HB@PhzRRD6^W9(kMV*8N(|sexaSM6o7$6Kz|tadUp6rpn{@uT%Y+6>v@cM zJo5>i`X|s9Q;54}u#2rB=sI9(uq{&gE_E*t5W<%=??;VQnG+qyE6r|QI_Z^^ zcu29UYYH1S;JDgouqM)GDy?NyLM8aVQq7p`Ju`NbKk+_Tp-VNMA}d*pk(GTM3p;(B zNC-nL)a4j?StDd`C=N7>Mu{}E@S`>>56*lI;!~p+xOjQnVgD4wz=0=Uev%#Hnc(H% zOFo-=|9#!Hf;|E}cdO8KV9BUa(k~#jHCr}4~%qZ3P82S82?CI0K!}C;0j{;RA z03QLwNmAwnTSQHOidH-r338cp^~>W2%)ut{*+maZ)MfT{bi5>{^S%M}Hx3jJDdDkF z6+&QzGgzU+pNzgKTLW4(r#ti4c?h}fEX;_)uOQ4Z6J{Y$nhJXEGRG6fCwyhdjaCxS;SE+8W@djfR%;@@EGaY$xHcdgh72%~ zU#ZA_HkosBH{AC2B)b(0R6#Ry)~GsLJcK6iQ54Oa=5}4QeA$;JJk{yZXsbGw7y({5 zuQ<9U56xeF)(gUh#DyU6L0l){`^*74BV(74`_?SO zeNSOzKdlT_a_9*GqYl1Yd(YTEmKY#i-SNM`i zlIj@`Q3x8T6{)uUP3(LH6HI%oH{Jav64pn|3K%dGXkZQGj%^ z6u%GHATb`{v3(Vd0Xxr!(UdJ!56^JXyX53~kZlktCM{1E@`Z0qS4xb^V(bsf^`j)@srafX-y z(vU9@p799Ja1uco7-ICPx+y&zv3z#a#5uF`}?$*g# z5xvC#$qhZy(t(Fs^A&}G0mfv<`paG)Fg;i)3OYqRNpLFkAf2>bihE{?ds*n;!l?%u z6fB^}HD3(vH3p|4FAes|lU{#=bB{Jr-r@8RYjrP8<016W0KN{G2Tn{(q@42H1_w1t z6nfPVBakz=FQr=SrhGH4^k>`uo1Kbq*rd3LBl6rv+loKST>uB0v1)kH5p&?K^bv z%iLuq`2C#d5HxIAHJMhBPXK`9H;wnJuYSloJy_W99NbiW_roc#@m{HM$tD)Zz4Q}n z)Mh$KbH>^p%IJr$c5xYD+XfT*hZ)eQtT-Qklom`DzOJ8TcBTB9jCv5jA63(n@n)+3vM5E-_ zO_eyY@Q>peYXbcIT>#?Lcq+zgd`(iF(ClLUgpIHnv3#)WC6XD9C{PPZUqzSRfdmIA z`~YKtN%~Sq*B+aiN@kPJ$IE*Oe*-YY6$z$gh?oGJq*yH+_RGATYIIxhpoB&Uegslb zgzD|z!s6VYr8>~N(aA<*V{xUwtdTF(fRC)K<@hBmD6E(}mx7iQ9vqM?#mJjRu}a+< z+31Y zk$=kY8X(>2ip7bS;Z6Vq1xr3+yQZ9_YiMk&8{<_ln4Xoj1j8_}cF|PggGu*wwnUZL z1_AXt@)l!66Zin;JQC}}mLbqAi^2L}P%#&3ml8ab$OUaFNHm6jFSkPg^n;nKnq|3zq}R3D@@WPvVa+M zpnzwDeGo9O+W$S|hGmSvBW)M%U?kRDu_%i_t{ zus0JWubrZzoZ{L@0)p~SdIJ^&VIyD_QQb?l&jo`K<+my~%*M=n-Ng$-iES+oi+MZ7 znQ2+LF`i(k2GDe9(Q7w7W$+^&T9}ln7ZLiMjfYHCDERICrClIG&|vP2>P%?*0_De~ z(Sju;`ZLvrf;U6yK{TH69mc2#DgWnAN2c@{J(*f3n`-)CNR1xOl@hNh-Zen^Y} zOA}}mz|06A8$9l1#6*)v7UVlpy-h-s?4Z|!Eli5_4|EhikgGRyj#63)4E#DyvrPs8 zdA;uS`GBRD4+niA6Ax-6;LfD*Exy^P%khTVb`4@&Hc$SeK!?(-pxk{~I+6`sh2CU| zJQm9y3$OeK9b-eC{$vm9EsWW|Nvf0(Dvtd4nGA=o;wveFuu+aBeGqU};D1ANLm{dF z*iaNoBs4enp2+;Bs(b)7+zjFH>U~casCc=#zW^3mty+0GMRnat?&~YUYQG|N2};sn zBdjOdOky6&fN|i!XMbbrRtC*$P{Go`860_y>3FC--tljJ=q~|u{)2CpNejzW=2d)4 zOX~*xJg7ebm5ME-ZSE+GH>jz=yve65CeaJm5agXlKf=lxUKdA~631YBWG8>(9OWml z*E{&=kYgPqOME~QF&6nT;>Pt9P7XH_fx+5p5ckOhVRBu&{+9XXo40>dDqmJ*xEwN( zSxjR&boHoa@v(K0cU`cmZbp79Y(d10c#Acilb>TJD`Qry=)N({rZ`_e zYML9q?({_SrlP2=*mSVri0jbgP{i4}ut(beuG*JPhn+;^oi{`td^d+sgRDd?WZ^~W zx4iR&;zNvJ5U>^6t{w>8~y6``jV|eL%uHJm<(hMrMON84$QVq4%_{w3lhV(6)kPQO+NHhG$|! zDCkAN(*d*ul-e;DK5njGIPxwp*oi9crNfsAh($HlxB+v>Z3I6*j0Km@6lL+V2XAnx z3RR0O3!cIDt>d-wZiJ2EJaF&%2^FRDT(wRQP;7t`B<56_19h#@$XBf$nP@DDsc8Q3 zW7%v2xah;6B_R!gFHKi>cTJ52X^3W0!3ksXfC@*l32i4AnR_5j+>g<9NW5^ieYbgH z`kf4WWcX)vqWragd&+}@<8{5(Ybb<&KrQclY*-SO@D@y1a{T^|#gSBp7)I~ZouOe) zgEgb+Y2!h466arVePcJhks`4DLGZ?qtGy}?R0s+&?-Y~nXkaM&#>YvaeSsV!Km#z_ zL-+RhplWxK+nPfAv>oQ4lqoC?K;yy2l=i@wZ7q1woP`zPFTh*knV}r1%(Znr$f&0* zvr$vWV1!k{4o}DTZpi0Kg@^nWg%(@h%9B3O*+AVv*fg^{h1?ZrsJpm&^mg_9dH#g? zj)l|tRA1P)qebuX5)=T1L#XN~)^}LS-4HYIG{&JuoUd%2`_5H@KH>OcH!-hgy=3Z{ zh;|BJfgfv4zN{Zth1C$tSTSG$<3d2X|PQ?jG#gX&1H;)f=ZAUr8^MV1W=$Hh)n5Yyr2H-z5l13(Pv{e zmLhK8pLDSqRQ0SftX{rh>5|$o4Sv6bx6Z;~OvHHf1a`V8D1G|$=`*bOaF`Fsup&*7 zGRyi^Zl)C(6T>SeHaN{z4s_Mo->JsrwX()&a=WHDSt_4|nX{=G2vy(0g$ePY*Z!7H z>z>YGi*n->1YNxv%~~Z{OvPp&bAcEwkfkXy@}(=<;7(XRNXf|PsAN~nC_b%`%y}Z5 zNt5XKZ(Gs2!=CyJUcQH&kWlKZ@q2rrTL_wA$lK1D*>u_)ec`mTCS_8S^v{t;?;-3}lvsw*Qe*=99q(0iew0VNre zrutez<$XTI=WGWF>-k@4o))N*76N#kk3hG1a96k+6akqQMb^?UUc9*c)m7y|Gcy37 zzXTWkf+R5BWotXXorXZc0M0QA5m)RnmkRdyux+f%MWpsyS70;;xDBUjZ|8Decs0hc zzc~XzmZrA0(csO;!_Y29HgG@})Lwbtr^%L+@sWkOIT$h$M~NRAfPw&!exVe2Rnqn%B(M`}*b?s@<<8L%yVQ@%b=x zzwdi^@wat@(#Qst#PRy$z2IXHRP&y(i6oBRv1+u>7gUG&Ud~J8z|WC#Mj5QHDb}?H zJ1s=2N*NO$vex9Ai+-FZ3o{PN{cO(ol4xPc7DkaXfGve5C)YT(bQnemlEW=T?R2o2 zKCK_~BbkbDTbpM2JSr}shmXYmVBhGRn4}HK&msyk`h5JAg}u+n`sE##AI|Ru#LBPb zOK>ee5NdW#J3V}mYK)~5*|K*{lou^O?d?Ld^sh3qO2+t~}=N zkH0Q15-}-Jj9uG>_#x+phWGEom#?%?;e}L$+w>*fhsGN8FksiaG}NW622e@ygCY5o70c1y5`zk%d- z*9-E?;jV-+XU`KR`0pzZnr&lV3rNU=pK|QTqtNP-mQL;il?^vPzYH|}qc2wi4ncVj zr>28Y`IFs@82E5{U@Y!5=GLh35GzY>L)e&`BP=^#eU*dpy8kn|A!fXOIfQM1#1Ok3 zLLXNp?EpV`{v)X)HAMAgfVx&3pi!AxB-hb8CBuL;E1LR(7r+%6D>;8d42BR$YXL!@ zY)n=`fLHy-kP5+^F~W}yUo5ZF%?xeQ@BE8f(U+}gf{ebV~ts{9>8UrldubPt|iO6euD5j9huI2f{YWK^YEe?0KPz~xY zq9ko{mu}V!&wGzC7W|NZddX?&h)XGsbfB8X{))X|HFG&9F&_02izIMb_1*U|D8WhW zv?mSGHgaR>=D8TPJ1g;XS$e;n97)^pUTC9Whh0il0gomuL?iwxU5n&T@)B?4w(Q~0 z8Bxi?SY)MMvJn|$*Ru<+pER;Z%w(@4@krB<-`gGt%}$q3_a#&L2J{};C?rLss958$ z1idCbrV469r{}p+KDn4Be)yo|Y4?Ov`+W>4vy9hh7OI;eH^}4#@E>CaVQczqaGxKF zUvS8Hq|N`PuXR;(>1r+Le> zL5}~;PMxeeT~p(xk*0PIQ=F#cpC7lWKI7FUGY$^oy;kLtB)yqy`NNl2gY_m&sOe*( zlR#RtP&`QG>xm1~lGjkCTpGHV6(9akT8V5RHxAHX{_{>k{&m7PvR5kG#tq&7Ci|_B zpae`hed!X7ddB~PQ=%B7doIv*vvhlWyPz*gY<-0HtbB}&ipno>5Ty|32$S${w;7** zpzry+iy?8{;4)020tu{k`xHfDv2smUZnm|VCq3p>7R6KvJZ69)LU#wCqkEPzQSh-# zfwhYCqI^LcL~RUYJ@iV=%rsg1DP&v!=gvQ3xTxroe0JaZa^on1)BE)sO3&qw2zAz* zE;uUmZ*HHo&l6)2{<5|C6W`w?A%@o8k-uba2aXSvjM_qF0;(xz!n)GMmJNRF zGD8rALlo)q^%OG)JRk|Y*^RK`zl+_3mg!1P7GnL;c6Lvc$2NoubNRqCB?OBWA$+ByCz^Ez5Spb5kz!wQ-hdDy zPcOdm>=;Atj@Wk*$_aIXUI@h{w$}r9f|0b#g9oVkp7aP6yJn5L6bG&kr)ry13bQPg zGI7=6i=M?}t@U;=hz0+G8>sJ=kFVg1nOihZibg!Gv)2T}Pba;7%hpzdN%QD8OCEAm zPW;t6Y9SfXPtWP8<;TL@*o}jj-zHzeeBieFdS2y=_vTl8FGOVAA(IKJ5ElB6PcMi7I*iRw%WuZGH zCCKmGEGKP50X%vUY zBon@Sd_Bu!nEmYg%fT|(s1i6S03Zy6^gieO{P6 zmv$_BXaHt_xWDJ z>a(xOL>npp2h%1AxK)9akDaaYXQg0qw5=X4>iXz;Cb_?7F2R^d^Cuq)RH??um>)!F zv~m3?nY_mo3<*!358=4h@BeyL<6{K7E5g~zE~gxDhqY`W4Q4^8En=~4oscs5@ot>z z%Yt9!u;pZ@SlQ<$4Mwq=E~gWPY&--7$dhcT52moLzF;K=lnneUK<|G2Q_0v3_b6c9 z5*Z#)By7KSK2U&WQsOgAUtsnEg=VoDEw&V>xa5jPFdj(=XPSdF9U^YnN4}@pLWC_e z$ouOk*fo0hA8Gk(p5Wb-EreGgh}mcrMnEV}D2)#c*ze&0o|1?%_%%ux3PUhfI}S0j zkO0X6ByO=?Fv30Ye(?F9nF&M`vKt}DUnw7c)$Y0vw zUF_@H(f}WVeie{Vs2MPGR%j0+KrEZ9%ZiKD!LKU8`iI~cEyU~@^BrjM;mrqs4!ls% zK0UDC0=e4xML_E&~b?;|@A*+&Zel$_C0jib3^s~vnw29<~=$(96$>|R=Ibn%-# zOOBU;A(IG1oO;Ki<=nCEGZTZTb<~_L#?Vpt5%MdhS53q|=h*fjpE%p)kYmkUR~E6> z&~Tf`=ucU;2c8Vev){90(z}n$ zT~2OkQMROb?n?Fyl89_RwZX1fy7B+C04Ne#^U*a``MXhF()VQ7U$CUh&YWoD?XtS< zs!lhOt-GFh_}yxMRx&-hxysvDaN4?XWSS}3zAd`n-d(r!>lNHn82bKB-_X3Jq=_qr zietAQS5+h0&SuV8XU?|setRHoniir(dMEiI+>5nJF?4Pf?U6!?^xLS;=iYl+;(y3z zj%c3Rgl!%PhJ4xm*>`jVj#&{_5zJtE>fji;C#SyXuOAcKRyVzhE|r4W_jq=Wlrm`x zk1?eq{+-6!3X1Gz+#fRaH0loi<8hfMhPOPgyZaN$Dcm|e_xCzKjHx?dJNT|#Kw}nP zsc2ib!pPC+El`xKw4FHQ7mYi0hE-N~wqDVsg>Py6zLXamEGZ_LQwhHXD&dXkQv=)-~@li~%Vy~%pmk$tgZ zfC{7%!*LBtxL5hpzpaB}^KhA|9%bXkmGu_ynx+vh+g-~x6&&I)R+MsF?Eg&D|0R@p zWNb>o0uhwKy4a%CQybCwb(M8t!9*ca`7#7!`_`~cLf@Y{IPeoy7Pzp}V8t2kB_nh5 z>nseH@fHU&e&Y6p%lpQ6G}9x#z0(*3dz6u)P$u3sYhv@?L_84OZ2HwD#Kcc*Z zX#u%erw5KAWbEFh-lRqwcv353>W-j1Rl@o1y>ei76E0Z=(1OErf)*V1#U~F`R5rAA zbaZ?Y$FLP<90QOG4&3&(Ln*r>#f=dzIplYMonm`UL52G3Do_QMs=WwMm1Mvmnn}S4Z}(P(L3B3eve3k0^o*UDZ;m8{22Lm! zwuy12TH(aY$`;Dn0g$f(O`)0euv$6LSDZr_i8?w=v%X=3srYpXP!TZrQ zL967i9fCZVsc9MXr0x%_XoC#%YL$j8Z9?=fJtrc=x+jbwSK z*tG`jC=c#Bwb8HX#DQNVN`w;kN?K+@%46>A-8K?cuG^NMi;}5pp5{uU6Z!DYyC8c? zj!I8he-QDUBc#xZ0OO0+#CuL<^GrlSq&U0S)@SsOrDYCdlN+?j19}0(g`^)=syiY^ z*!sNpzIh!TFU%N1I*#Mmz0Lgj#hxeRQYqAL>TLpOw64)y4?$b^jK9%(YIY8rd4{6w zd<4V~HgKO&vTS**8+-ZHr8qcT)4tUGTg z@i@`hnDz7CcY?MtN@r$A0}nRB61VKk2I~4uiS6qm;#cS8d=J~Zg=(5Y)(Y#E(n{)9 zKa>d7)b*U7-57T|vgK&CCMh(d4U@q5$eBGGz`w9-`-VCtuOG4g*|ZY%-` z1xg{MCN9q(JE~_m;;9#IcO(I1eK42T(|1qA)xQ0<08q-8F^)-L(4;YCxd#mS?t`!F z9=4R_jsoUuGcC{2lTJxueUVHYSPTcUB?$e|2)dM%f6HU%0)&M>{t+IeXTT8z6*`>L z8tr?b#9s0>vklzaNHAw6RoUoRm}J5>1)#cNrh#J*=J#)F`*8nV?pTm7$W^y{Dc#CS z4XJZ*5I{8PV6NQ)8_XYJJ`&5eFoV@P*;SP%Y8t+Pj2-JXPL*jCYb8m;+Hz0NaPYC& zJb@T`#;$*?9_35~)_{U=;3bV4i1e^9I^*?DOoW8-rf(K=)(SOY+we-N6|^Xjrwj-q$(Z8h zqVSiw9Larig}SRz!vAub!3k(ur3DBIpwOaji$Z11OWMEH?v^8s3Kg4%BPNSQs1sdT zlI1Vgrh_OA>G9inL{Svg&zsLnMln{P$!dg87*J~a23n%V>35SW!zem&wtRH)SP_3{UavZXl2o%VRXTJY7G?~bA~>B@Fn|E=mUa4YNA64 zOw4xrQ0gu80HrsXu%Y0*i=1XbHMS8e*p#bD>*Nfr~DAV^ao;0xZ(rP zmCsYm1>ULHd~*IP=!aZzU_HV;?>PgikZVxDpb^_FA28;Sy?I6s(KC=%a6C5-SVCFnZsxw>wSx0j#h%`Cq1+o7sI zJNE1T92;~smIfM+t)lFcyW*ZZq<#mRzJEQ!h3|W{E*qu)pIHElpou$&eJLRgXZWY* zSAFH?bMr299#wn=hl;)*`9rXA?h!QV;0XBxeoermpbZ7A3**+#Fl(I?ByJBgWW&#fUAWq7GP{~afz}mk3f2) zZRzRhF<@S8$i#V!A0vJ)xL{%eJaXX~v4Vt34nz_nmXM4c zJKqSe=nGb)(coPkgbmS?fKPq%JG`gv9;vNo(-&bs5L&sI{W%MHSSdV`1hl3>_`TzI zd2SxIAukN}sYC}k&sNJF+Fg>XTI_>nj>OMz`|X~z_Ss!*TVT==qQ{r)Z_HVUA1QP3 zURM_N#?7ur>R{lCwf)p{<83t&aho}FcuX{`C-T|vh_iJ_$`3^(V&_GU;<=q8xA-)` zh$5YaI`W+wEPRSNWjAO#V54~8q+7qqxY4lUS!hv+uS$!T!0Iu5l5=3+&Ey;W%#_)5 zhtwz#7lh$QD+jH~(H2`X=d&>$yK{C9$jO;OO%H)WJl$G1rq)BwbD{meqQwb;Ebj<* zSr%{!<$QAO6MY5WStV&{>Md2g*);1u#W~*>zcAqXl=2DNQ7ipbSwp13;UVWIli-!eh0qhH}Bcj`QT=~;<>_((biM3UgV1&>n_mB}h)6ka zLOsNYi;u4}@r8cz`rQ~R@o1!nc9`SeaqcXy4I$AXkym~nd=g84FDZQdcJ(0Q&yA>k z63hTpUVNPZ{HlK&%ZHJ+X`YQB`GSuG0o2i!t84761uc&}v67 zK(CM4jyHrFuX#lRRqR(*wSIYCTFc1FY(p7`C9AP|@y*2hMR=fX$Gn=YROEDL&wGs9`omttG#eO`hGmE&V|xx>S8S#7K1L*S_)AX}@qthY zvi5>%)@z7zwlDjFV&-_QbrIcfjjm~LYV#+R!o2@1Ph5|ooaN6oQB+>*T!jVh$TC@m zpliZjBH!I9el(g8t|wZIi4_hRj@*Me(&<%oCs2kjP$>6i{z zrC{{2apP%r!kJ0ZMOfhKxEaWrYHGG$yG!sIGe^gcC$5IY;vP25YfLH3SQ;65E*-f! zH=56F?FAzwqNf8zc(RhyN3~*KynL7L*O65eI}!f>)FRo~k#{z%C#MtM>=R<*cNb#P zj3aEwVZv@hiNduPBF~fV+Bg6yvqJW>pS5lxUR2f{c~|0Kx5I}CYjDDyRvY2MsaAQM zNjnrk9eP6kFTKcjH;>9;u?Igt;qUMIcp#Ht@&yTkzZ$EwaAqMaw(SUyBxQ@TdU_v% zCvw0ZQT?*Nk{v|N;G2#27jgYNoS$16_i>c}!57O(e%WXW8aT=hLIMJpD>fkmSI1ks zP`Zem&^BBRP#uR2-J+6+k#I~N#pgOFe;n(_Lmat4H3WL7YUyItp<>hv(+*3j^g=m~ z&^VB|;Gn4}eIaB|$| zaG0G%gIqr0OBoaJaj>$%BFp103j=6rAQHzat@_t});~AdO4yF?@}{y+SYL)E3iXT~U4V$`Syg8&pE=ywVWi!}9g7_gmmN3Fi9`6MBK)a2cWVMht{ z2TAvPB&cCL(Y{Hquj%?x)HEnGTEPW?Td}$1Gah}`<`utf$Qwi*-}g3?rLtvskxkh< z>h!f}jYcBGyJ^_|_Tn|!$FNOklfWa}p6ge+rAVFhM%q8d2!rT5i(_GaO-1;Z*gnEx zYlQpk7g^F7p1!>kSGwD`=KQVvXLLJX)iMukyjvGE^tU3{6(b{Xd1dWxW3MMFg(rjF z|NaeERb4U9LaNmVCUj3GWmJ@%caq)19hX9jS{0ThvYrR86-u~&(!&clO(&9k8Gx>b=)@k?5g>rsTt;&~2PhN+bezbgW?Mx~8#^%{w zgXV*w-l2Y#lngqCs}#o=j-UN8tuU?oAPSg{mT~B6HrF!#bstQ0TDovKkw>R!c6}7$ z2YuP}C!fcTj%!=nrDnW?aGOqW#ExHQ8|CUgqUDZDY4=UI5~5hZGr%h@rqDQDBuGic zmVm~HrqbG8oXA&sEnXL)@J79@=iTUEZcUrv6&I|5D@MYk$VAhximgolONaYU(WPLG zX#J&d{)ENF_eXlC{)Vyo)k33X*8eyV0ONt{C_~Uel5Gx>DmpZw7hd0YoDWueTtRFh zPiPOU(d8QGbyrYI13qix!eVB7-9;2VyMAFcQgC-I&@=>D%ngQ^1J#d(KSp&ctNC1G~g9j?< zkRgsi*u3JK1ASUez_&_v(HWB=VlViA;1?PBr>lZQ8mBk7pcVk&$64f_DoZkRco^8S z{jE|G!leVaAQP`+BDh5s@49vNi_*8kB&N|5TWcXl$jkRJCc!+O=f2HmSkpS?_?z* zdlRz9A;~6WkAq`oeJ{N~@6Ye|{r>T~-FkK7bPmtQ3T0ZA;dyj%Yh#fB27ex~Jc9b7(W z4!&KkTHhNF$k%^YS~=QN*}?k@n|LmI!g(%rZLB6f_Id1^+-qFGT}suM>~k#)iWGZ~ z{N7R6H#FM=r$er~rf065YB*FPE+2+Ud@j z)<1v7c~#k2%51B`@gsxobE>$k9ZumDtU$|d2iETPAk#IFPmE8QWM5_zR~O$ zHS@$Eu>>!@#pd9ok_4sfYo#FEoG?j)oDv=97Tx0${gbQS7!B{l{O-FE(-)$0`^RF}u|oEM-!EXzD~TO4p394!->f)2TjEnxtsJio zudnXa`*ix*~ z=Qw#Lu2^;wR8VrDfOAn3d36*DKB z&-Y;}gH@SI9^(5i_j5&Ju){An${->Hvl-~U-F60K_D13&xNrDx|kx6W{gDpMk!#^RXvQ}2a9mn!V6Mx;-6cy zoMgtx#hF9_LhWae5IB53HUP=sPURGQ^Y|=2Hy!IA46Gp{`%ISiJOp#3s-J$hx3UZa z#tac1BGo3)Iky*P{1+J}T0kfQV>#d@wDQb(0OvF6mC8W_*xL!CzE0YJbDj5ZULo!# zgHIt>GK0!@M^1n4i~4iTde+P1u&k@x%71#3B2$OD#aUIb@Ue-fLIfJN16~A>4+uGu z%-@FY=WN^=I+e%TaZe8Pz8>_>fhb#<1U&NKI)=1p@Y&I~q<*DKHeR9s>h4&p=<4+p zip+KK&)+YVZM*K=jQ<@L+UsCWh`2B&|5wGXqQu7k)1z;J;>U)CBMvwfCUYL%U%rS; z*R%Z;;O>bm9l2MZOyM%sJVT5k64JG)H>J^dw@%Ngvi17yPm(a1{TvybzJI%aR$k)MUu9=_9Ole+wa_YCJ_q~?A!_)N^ z;=Olmx|rUinAlqu^aT$M9z^4~dna>!<)Wt%NzUZt@IF3>UOUX&H&CjU0mVC!%)&m)dpH8#|F+ya9*ZjGpJV_ht)?%JG!3W;O1Y{x&=2PnE(PoRzoL zf9#A=AoMQYbeB}&DfJ3RPoCXtIO}MManY6eV_I#2;t*(nz?zjJ>KS&Q6EX#K!@DN{d2-~_5#O+B_QI@iW>M@^d`_C^wh*m}T*XGh!d|U- zPl*f7{&DO59>s{QDVGWVdyI}abP~QK5@AXIuiC~bw0ETnLl^R~R94s|G;1n%%7gBv zWs!?j-@I~2BpLgT4fBxnS%Z+q4zj|`LrGw0(yxF7@1xL!u;}s~CymzG27htly zX#1^pYWHpfI1yhkd$m25TG1zi5mh1^xv{);i1JB`@chFt;d6EZzVPn#nP6=Q%k6(@ zX5eL2e8(sgh~Gb}T?<0PKhMHg(->oF7mzkW3nqL*EOKAb9FS zv`oxBZ z3skndwBW(p^fIv5Ejh<+K`TSn5+_#4Vw@jtVa~goUT1E2oUYz&W7U!G0^M zRJ#hQTB&jrjrJLTRkRo*ymOTI`p7~{fgY_wLq7M6WLVGRjx(FyLxziovo>!Tif%q= z>a&|X9-{CR#duaxhEsp z7mXXY2s(suzJEZ)Svws6E@nB{6aQ;gCKZyG=eBYBeR{?V-4M>5-_ncr5?AuxC_P?1 z+toKpB&FEBD`Im9KHYQz<;mJ;_bd9RzxJzF_ZT$MtW8{)qQUjebz7fg)Y(hXaxg@$l4t2i6F!al_gIIQshtke^>3R z7s-<1bMWrT>zXCm)1AEiLOR_+ULUUfem$FnM!&N;n=Rq9L>|4VG-lCrTHMwLm(EUC zLtBb%H?>4d4Y;W_LGh@?PLuOa)o!(*^4yJ9N3=EkkV=-ZUJuQ2L%NoVy$mwjneCwg zzl(gLs2JruSaV4LaykvcJ?SEF!G&J^K?3>iJZ^J#?(+$I_9&EE@H{ewA=*<6bwfi3 z*lofW!ZM7$fVo4_FsP`X^?=tM@tlXVXVvI2A&sxw0%!4of^Sm5Ym;$~l;>GPRy@4-de2Z($vARxlZ#%B z4#N}sU+Ue_qwc1+u5i;Lv1proSvwzgUssfUxGja(`R57)M0rUpopP>v4pV}>N zP*tdG%6-(h?(&MmQ;p-g_}b#WAT60ohX-eYFIu{!b3(paQ({5yd0h5JaSyv^e_dIG zywAZWgNpm}rKbFEo=(MU?YjC!T>4WVyW%R8+ep!x734RkN7!0K>rcD+i0{xOCW!SN zOVH8wOJ3ybOyzWuH+NLD-jpkZQQmY^@b5RU3~*fu?oEq1j=W^J^aG79DXmp1TAgtt zN-~XLtW?T zQ!$xItkkF{FXBI`#cAqmWj^i5nBiSoE3Nj5b}mvPzlD&#R^T1bI%E=uA2G9My>~6} zOM0zB!E zpz&3P*cR5IPlahLG-2kG-fy4O2It3TJk6~sYfF57EqD0$`Xv9`)@Oac*t6(qs9qBi zW!MeaEmc&GHk3*=63?zON%G4W+9&SLGvQz5m?7SE8L8d7KH5dU>!36fPB1ds@T4U_ zTIlTd=W4!eq)J98vxKK15%TKAe&Uk5=NP;ge~=$x#>?}=yfINt@YnHw_&E3oTV0Rn z)@-Kj>IsokV>j=&`5Z<<;Vo_JSJOOKI``2Ex$4H#2W)K@X`Zw5Ok zwRB=}><_C)yseMsghRGyr3B58*tK%5)YsIRBa$u`qG<5 znPdwX15un_%a4yY(5HnZx88kBaE!()8ugBpZ@p>qxcl+(Df&$8Y%TqS*Ji4&bN+EW zx567xL@Kkzk&AGTMnB2kIu@iT{Ack1(RESxL9W-r1*pQ$mrjn4a$$7_9t9&Kpil#S zOO@9Ve*+q>AA8C`01w|@A*=E{|=~5 z!Fa{oUt@$J$@m8_khXVp%u=&^Z*b$B&>$!%`1GdV1<(uFbUP;1*Po9~V4e$YLn9~h zqSR-z($Om4LJD|U`T6T}TM3?bQDA!0yK1YP7n4U4Vr6S>Z1Te#jO?9n&{3QlGChS9 zkN9r+;)<_d0m*~(Ow}6>EYY}t3WA?*M;{n#fxHx)?bP#uLkXNfKnw!|1A%-G4m`2) z5Gi(nBoQJe&tGGg)p`%`kbi3mAUD8=pP8WxF#J@#%*V8He2|=qF( z!WFqlcSDYb#I)$)=U{Z;;6mnL2{sd5!D>wjR$R4aZfd(I5#zJwSKNMLy<#!y4W;YO zW1=kWh|O@1qlZR&Iwr|Jntkiug6nRai<6^bw2kaN2_2~Wg{n|;i)pYdSJtQ_yq5|arfB74J{5Qk;KDxKjG?!Q^f_S@fh&L!hy~P{1 z25Eg&gk_i=uwF;J4GVuA6m-L3c1q%Yd9+q*nyCs$p$h94TL0HZ5Yio(H7Jzkx*``; zw!G^a6F1Cy=gvhT+Bdm35Bw7(&7}j%S$yK|A)*K+n;(SErb^Ra;47IoNn_)roqvmU zX-e&ism!fbhj=Y9TiSkPi-j*=WcQ-m-^5%c9e*VSRZBr6qIFEFP(A%MYbYn5C>AsE zijp|$t?b1pEDn0aX<4tMMe#oR@7jJ>bhP&3|#=|5Zh9(X0xt3ihzhGJUkrG zHE4%{WSppGwo`aKcE>=}Ly^OwhlBSqk{0Jk|d&xXx-44;Aw>gnB{L?3DJq^oJ01iO!=QQ`F78q`T(TP$+N?7Jr5R_T)seo4& z>4$D*W8-d~8XjtYWPF_A9X;uaQ%&mKj76dYtQzZqRp%SZ>_^&!2#^IhPJYl~%X}%E zXcYj&MG`3m=I8`2jHgr4ebn4qVkyTR&H?nkkgza45*tpkjEszV63_z{o8MlUsBsDf zjPeR+-8Z=14q$~RlR5D8XliOodXUdV2Jn?nKSdzF_EuOwUl>TgvsIKOGZ=4};ofpn znxB4^e!WyEWuKvIv3+GOL4f)#pX&tT&BS!e*iJL|Yp#5&n00zw0YUZE6w_>hqM(%f zWGKw07tOF!?8hH`iqL-H9u1CGJ4}@vw)hW{+Il7g*VNu`T!>x5pyeo9NB5~Eaq=w2 zf{%Y(emzl{LPL!xZFeuSu6wgwXRILCE7SU>icYkrjloO0@5nP^v* zdNOL7WBYFx-kDpv-7X{c|DE~p!os8r9p6W!gg<=`f;5ODYLh4m*Pr}=T-G{W?o;dc zI9zTVG922RcNIUpkTWVn}zYB8X4{!P7n3F4~ z{2;?keej~+U+liw>!5FHbUx~y134Jn^MI^rvr@ z`9Q}Nh1Y%C{`NR4c86~e(qCHAKbSyrkpE{+ftjx%VMtIUQt?m!NBm2qLND+#D3M9> zOm($wJpcC|obkZ!A*qpR>?=d^h8Bq}n_nh0HzwAPmDraW^oND?b)tPxi&%2`+!l&U zHH9TwDJREk_fzhCOuwW%d^1cM4i&MIXhz67$T{<>8L?PVii7unDsuVq)zkULWAp*p z*=*y2i}#|-`H4~{v0op9XI!0qC%dL6QLsy;yTTgGS(lcAz%nRL4I7Jy2xxC>8|$Ax z&WIlbp7~Br>N>kP-;Oo@vH1J9FZ9YmrSRYrA}Fb;-#^LK($-eha%e#3d)9Tn@XKwr zY;doC!W?ZXND25IWXz&4phV+K?iGAgprTxnoTwl@>I!bCux^OIt2^6Drmx1B&T;** z)7%T5rV49drB&-(YvHofCDRr7q@E8)4Zs2RZg$AqsvTSa7iR@#Vt+!20ClfVEBQ7t zJ>8BG{d*!f2R#4`%;tR8o;7KWy`rY3{!IT8I5um7UEzEtwjZH*U_A zd29hR^*z#EP>pBL91_ykKIl-hDwv*`FElH%8Q3z)K~ZZoJ~=H5tvg zytFfd(7}r2-sP))7f=NKd^F<~G;+@_=SDe25Gy}WNbc+-ACw__-w{&41rEi7)B6Ws zJlV1HhOe_sl@Uc_vhTVlIZsY9h>wsq(Da&6(Q?UK5^{Ju5$Wm(x{iK34Ob zyp5&kaFBP9P}LnDo6yk8A*664AVE*+(;h>A|Lh4NEtk)Omdj>T^fu2-zhi{S;}vS`5ff-R^4x(pcLnR;&_(Dt+_2tle|u&WDWz= z`11WFwqv+=folDf^|#=)Ldc)gyc^E|jS>gRmS@C2N7LCDgzz6SpDnV(g6Y3^Ag5t? zwYZ@n972w71%h!v?89fQB>hgQM#Y~xxCZ&4sm#*k*d7!=8H6+Q(V`eKj24*T*)jC- zf$P%^cpHbO`;DlkhYBmQ^P2fXv)l9CK+%LGmuwYWvo(XIOG!BpbKomDcqdjK3);N_ zr&Q3j!KS<4Nq4?bk(n5dy95#rPx#!|va+l(S13+8cLkVAW<{%K?2sSuia~FpbrRcc zBArP@s2!BEMl!b*M2jSkXb0};oZw2D|3e5{_+DWpMZ3PSVU)ytZgg4!!!>lFAR(#A znR9Cp4utB4@Y^kSYI~xf_oqZCr4un6G32w@my%)c8f3e4VE8~RmN{8&5WNqQIygtr zNB50Das+sjRJm@^i-?H8c|NbxQ2AgA&CWhIbq1@nt=()@XsQeK8~bv_cJoHko(<-4 z{)EswKm~YFqAg(*0tb7QjlJ=}&~|pNkju6Ly^e~L+>O{d!9Naeuw&@WWS-2!dpg!A z-(4|d_o-t>>!aD!-_5eaC&RnOwLhxu865EES24dF2r?gtSRczdt$m@IJ-S*=`{24I z!Q=NmWXEQ~Nk{8uQ{hk3A|^KYZX~Ha?qg#dT;_wf_paSo6jCixh}Zux&W=CSr({Ad8-(Oa< z6EInRBk}w+T-ul?(U|GL0&#f4rIgjx^H7LID=xdlYF^VLhTol_vj!>oAc%}udqC|O znX2sfh4(~bx^e|tvMP6G8~#k~WQI5EO=Qg zGw-7kC9&yjg6z*64dQW`0A4fU?hD;-UJ~B??x;zq>eoJzk#$Bgbo1=YLek*a{fn_k z4yBSOTL@B$%X)o~S%>@n)D}OcfHM6JBd3#OmZJX8+;xuIn(0A={R8wD{Yh;Z0BzYk zV5Kkqrf_bOkdEvTuU+^+`cZOa^cE+&dOZ5}f9u=tla@94qtV%WAMmW+A4l!U;!@Kv z6URFWt8>fo80Z~F%4Y)|Ipo2uYk>z1J`&59Sgd)>Lc>fEAfkyWqe@oejNNtH?1*js^{4Ed?x z$z-+$d~ZNi!IFWj@jwb%&Vyf-4XxRa;}2xcmT0!#&>Z_dj=x)6TKcF!-PqO^Y+=q@ ztN3mr1QuBOfKLzXpkv>fRE}p4m`C=$xQP$x>?WUwK9^YWDMrIPq-;Y;UD}x4g;wUaC1NdlG;8B#RVt1_qu9r(C2tE9MGCK<)N0YC1q_CZ=U+%W zIeM6O2Q%zF<^fl*Q+C-%EZacs`f3@*yD`lP6SCXxHgR}#Z<>I5&lQuND!xCc*O*N2 zqn^std2op8{mi8#ayt28^{*N()pe<{i%~|HlNp}A6odbsG3n{A)RI23V8V~SvXTnO ztwhUZe`!Vu2_gj%KH41hm?-`mW4My6!P&-Lib%I*qt7{^v2I(pzuShe=G5`g5z;fW z6B${i-!@%i+)bpp>hG-(+gtXUQ2wH;&X*V#`Wyk-?@9L*Pm~nyIk%(R_cH(POcGz0 z9OYTE%s&uidX^Vn)b~<4Rxp;5I(@MiPw7!|qD~gmGG*|=wOQ_eC)AUJw(&diVeVkC zwfmoVyh9wh9?wH0P88X79J=Npak2n0?oEP`{!Qtql$u_wM1K9xu2cZe?B|qvB8*i0q$Mt%qV)N4RzL( zXFQ_#FP@Bo@dB$xm*>nC{onKP`j?(2y1xI)t;k!Un)19C${-*q=MF$V(6$DIl5s+% ztaigNH=x_yjMGp*X0I2hv-ds0h(Zwr_gtuE|MC3;+Xc2~lcAgq9Z+ZlMMeL>D7Ch} zZok)~1+nem&QD22m9Z)eYbZzogwq0;3bkeLM{+2bsKBMDns*Ap@rXO$Ex*B33H&t=5AD&P!cP>u7hVZT zrMagCF1o=tNqKgjW>H*vYxH_xgbw*Ah+bVoy;zkWYU9AZm(=sHc4O!ifae2aCRl^R zH2~epPypy*z}1crv2%3s*DrOr|2_^{sItWxCHDt+Pn}YObRI67k6a(cmw2foghWNN z#@9hPTCW!m)?Z-n4rwxA9Be%fIf?3d_stgB@{my{DhnHvk1cq_PtD|twX_uyFW(+l zJv~=+wn>`Zjc=*2-sR2z8{J+ru46Yw=^&u;y9|{Q;FAAA*t)M)n?h`*n0n{=LS;3X zDLNS#_RPdO!`XU87gL_P$u*nDJ$Of!?VD4NN!+JJ`5w)9ukQ-R+UDM`q-npGQh}x>2zxOF;TztqP5-)mW4g1FVe0oPgW^a>DOb7L#rFj#C<$D4p-L1 zo(P3|eyB6n|JTd@X1VCMW!?eKCvC2xraXz?-{M!}Gm{W1kOh+eetS2AIwKb!l78A( zW`zTu=6(L({ufQLq3)Pk>Fchxz4)fB9!~=^>{=O%DNPr*KFggc^UT(g>O2;MVkV`K z$ZM(vW>6RH)rYMvjd@5#%5OhJzAmHy8k8z437|L-fRXW(7KsgfW*B_nOuXQsOML=D z#gWm`nPH!k;lus2)3ZMs5G#8C0m3L?kJ#%*%+E7H)~JhZJa}}Q22tl$@bf-UsfDKGi0ya|yT;z}{2(ewz5&y{JBaGIBRfdSm1|;Zp zu_r4q!=rqSU`|vZj}TJdk#vAs{@l5>uAV$#?!QXKJ_}?>AXkEl1Kw?%$@%OHrg&bdu;FJT`9IPtm(@h*kL>ZHN*>;V<>M6N*JDbxh7_e!$hdNA4!2SGpjg8gA6+vLB z!Sg?9g8@G?%rQn|$cJ}m)d~82nF0p*fD%aPPr2|-_Qz~05Az%UvN!BxbLNQ(Td3Yb zyjGb5sc7LUuvG5Fy_ib(r`&&kA?)K)#!tKbTONkwY>%G{GB+{-AkZF-*jv-#&j0Lh z{8B8KfPMJ_8eM&Vt@s6nds;5iUM}RZ_SEQ_NFIIQKEL<|3sq7MOx#84$Yxp5+ybrVceZ79($YytNndY25d$ z%Yv-QBNPRVrIPvYPe0H!AN2YyBiTSu@sJTQqmcBRZ?ZCBCf~I7F#_Rt`yp$dh=s$l zgVoGo)6wbw7?z+p>3j0-^~bVn$Yu{B28U{XuxZP*6Et0Y7=>BtJ3_wSM`^&S(>@nLV+BxoQ~;(U1X8KZ=R0MTpV0*eWGC+32yi`Ji~u5>uS z-m*X)kWJ{dU;+X>W7z2i_YceRn2#LkC`eijCiI)J`@SfC<_11)d+X!oNdYFUpT7Xo z0Ir7ilmMo}aFn`E1%C)rXQ-fNJohwxlFQ5VD&Wf(4tQ|D!xp`N2=k3LGD;AjD!K=CW~r^g`w z4WN&Pj!;We^8vDHX2vjORDaT*l?n_YiimR%~BUljCR^c<|pT6)gjk<$-{WSvP zACK9HUG|+1o5>o#trj&Fb_lw00*Ex*8VW_o9$@^pw+9(^V3$e8 zsi(GIqQ(aAB?uVO zezyTvQ0MS%tchhuF!ur+}-)G@#SJ?nX04S|oMFDN9`MD*Y3==tlF`=u`$*g-Vq28YBjSbc=&0 zTUax^x>hTT+0`@Bnxp+4hbcPI_FkMp6rf7V;zIzmnWzltnF@pNdj4x)f50eg}0in6p zTkMtC2@B(jQWhECcD>pR{V)1U7_8i~&}~U0?vNT#+hqzLy~lm>j&WZihlcX7?1=bM zl43D^2RV@>U6P;$HLikOT@Y82*w-BOr~y8qyCI00gy9Ro+Kgbv_wT1o88ue%_&3=h zXkH>e(pGc3uKN;kMN$knU_RW9{yrWzqA2bg9YGCF7 z_t9BV?N5(Lx=Ddh@k6I>VCl^f7|TbG9%oIKR>7eI=!}= zmbD`Cqub}g9?`Au#5(ovvG6d&itMJMJp&ihJi>wZ0+2WOf3Bg?P^w&K-ja<>O#Im$ z);F`aSDvrB!@&U{{f#a7m(N>BU=H}kP%U1#762;*=H$-V%k3e#&;a06K`J>wFFv;I36EI@&SPY87q7@Ll4>bFgKZq`f| zs_g4FcqW2t9^g`mW@$6@RLgcNaEV5&ui9=ko{papn4J(GV|-2>bogkQXhWJc*;)J* z#+8{Kg+pt0F2`#p61kEo!@~wFB1ERByvs2ZE?b7u5{IgLMeY2)h?knB@RpL++Z^YfW z*q%S?q^rRnTDbZd$R#4yWxiI*rLs%vt1g4B<@7PTVXNo0A;~b+cNVs$~M4o zbGOJR^?5-X412+at07;3$qP3>Jmvv5T(`nw>f;Q_0jhy{YRUfvO&a0KS zt*#k_p+G|hp|k+Qk2*p`0zCT?NGNv@C^<9Aj@r(oISXRfC{s$o%WrqiE5VOMTU3e*QhS(`6hN7 zB*U`zI^yn$>~Fl+{a53xFDh2rVVk>ON`Awgbmeiy!@eK5xzWcRSIGVSWCc2M73FiT z(Y=$oW<$tL-yDiCzl76`m~JP~kw*TAlM_pAeW&vyXrGpY>pytrfgJ<>MS~hPFUzY2 z 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
Zc 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 z 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

07PN_ouDU;k$bj%$g1T8)9Wlf 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

^TlrS1Fk>Qc6fIfNO(Gt4zc-PFyD5FM#GND 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 z

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
    45Q9 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�{JDZR3` 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 *yye%Lh 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 WJOWI@HOUzP4uCvoXHjj2PWl( target_tag_ids_; - std::vector covariance_; + std::vector base_covariance_; double distance_threshold_squared_{}; - std::string camera_frame_; // tf std::unique_ptr tf_buffer_; @@ -87,7 +87,7 @@ class ArTagBasedLocalizer : public rclcpp::Node std::unique_ptr it_; // Subscribers - image_transport::Subscriber image_sub_; + rclcpp::Subscription::SharedPtr image_sub_; rclcpp::Subscription::SharedPtr cam_info_sub_; // Publishers diff --git a/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp b/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp index ca3bfe2e4a6b0..c97e1171949e7 100644 --- a/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp +++ b/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp @@ -69,9 +69,8 @@ bool ArTagBasedLocalizer::setup() */ marker_size_ = static_cast(this->declare_parameter("marker_size")); target_tag_ids_ = this->declare_parameter>("target_tag_ids"); - covariance_ = this->declare_parameter>("covariance"); + base_covariance_ = this->declare_parameter>("base_covariance"); distance_threshold_squared_ = std::pow(this->declare_parameter("distance_threshold"), 2); - camera_frame_ = this->declare_parameter("camera_frame"); std::string detection_mode = this->declare_parameter("detection_mode"); float min_marker_size = static_cast(this->declare_parameter("min_marker_size")); if (detection_mode == "DM_NORMAL") { @@ -109,20 +108,22 @@ bool ArTagBasedLocalizer::setup() /* Subscribers */ - image_sub_ = it_->subscribe("~/input/image", 1, &ArTagBasedLocalizer::image_callback, this); + rclcpp::QoS qos_sub(rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_default)); + qos_sub.best_effort(); + image_sub_ = this->create_subscription( + "~/input/image", qos_sub, + std::bind(&ArTagBasedLocalizer::image_callback, this, std::placeholders::_1)); cam_info_sub_ = this->create_subscription( - "~/input/camera_info", 1, + "~/input/camera_info", qos_sub, std::bind(&ArTagBasedLocalizer::cam_info_callback, this, std::placeholders::_1)); /* Publishers */ - auto qos = rclcpp::QoS(rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_default)); - qos.reliable(); - qos.transient_local(); + rclcpp::QoS qos_pub(rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_default)); image_pub_ = it_->advertise("~/debug/result", 1); pose_pub_ = this->create_publisher( - "~/output/pose_with_covariance", qos); + "~/output/pose_with_covariance", qos_pub); RCLCPP_INFO(this->get_logger(), "Setup of ar_tag_based_localizer node is successful!"); return true; @@ -164,7 +165,7 @@ void ArTagBasedLocalizer::image_callback(const sensor_msgs::msg::Image::ConstSha geometry_msgs::msg::TransformStamped tf_cam_to_marker_stamped; tf2::toMsg(tf_cam_to_marker, tf_cam_to_marker_stamped.transform); tf_cam_to_marker_stamped.header.stamp = curr_stamp; - tf_cam_to_marker_stamped.header.frame_id = camera_frame_; + tf_cam_to_marker_stamped.header.frame_id = msg->header.frame_id; tf_cam_to_marker_stamped.child_frame_id = "detected_marker_" + std::to_string(marker.id); tf_broadcaster_->sendTransform(tf_cam_to_marker_stamped); @@ -172,7 +173,7 @@ void ArTagBasedLocalizer::image_callback(const sensor_msgs::msg::Image::ConstSha tf2::toMsg(tf_cam_to_marker, pose_cam_to_marker.pose); pose_cam_to_marker.header.stamp = curr_stamp; pose_cam_to_marker.header.frame_id = std::to_string(marker.id); - publish_pose_as_base_link(pose_cam_to_marker); + publish_pose_as_base_link(pose_cam_to_marker, msg->header.frame_id); // drawing the detected markers marker.draw(in_image, cv::Scalar(0, 0, 255), 2); @@ -206,7 +207,8 @@ void ArTagBasedLocalizer::cam_info_callback(const sensor_msgs::msg::CameraInfo & cam_info_received_ = true; } -void ArTagBasedLocalizer::publish_pose_as_base_link(const geometry_msgs::msg::PoseStamped & msg) +void ArTagBasedLocalizer::publish_pose_as_base_link( + const geometry_msgs::msg::PoseStamped & msg, const std::string & camera_frame_id) { // Check if frame_id is in target_tag_ids if ( @@ -241,7 +243,7 @@ void ArTagBasedLocalizer::publish_pose_as_base_link(const geometry_msgs::msg::Po geometry_msgs::msg::TransformStamped camera_to_base_link_tf; try { camera_to_base_link_tf = - tf_buffer_->lookupTransform(camera_frame_, "base_link", tf2::TimePointZero); + tf_buffer_->lookupTransform(camera_frame_id, "base_link", tf2::TimePointZero); } catch (tf2::TransformException & ex) { RCLCPP_INFO(this->get_logger(), "Could not transform base_link to camera: %s", ex.what()); return; @@ -265,8 +267,15 @@ void ArTagBasedLocalizer::publish_pose_as_base_link(const geometry_msgs::msg::Po pose_with_covariance_stamped.header.stamp = msg.header.stamp; pose_with_covariance_stamped.header.frame_id = "map"; pose_with_covariance_stamped.pose.pose = tf2::toMsg(map_to_base_link); - std::copy( - covariance_.begin(), covariance_.end(), pose_with_covariance_stamped.pose.covariance.begin()); + + // ~5[m]: base_covariance + // 5~[m]: scaling base_covariance by std::pow(distance/5, 3) + const double distance = std::sqrt(distance_squared); + const double scale = distance / 5; + const double coeff = std::max(1.0, std::pow(scale, 3)); + for (int i = 0; i < 36; i++) { + pose_with_covariance_stamped.pose.covariance[i] = coeff * base_covariance_[i]; + } pose_pub_->publish(pose_with_covariance_stamped); } From 8c514cfb84aed9b643c3db4203990cb9790a0231 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 11 Sep 2023 18:53:15 +0900 Subject: [PATCH 166/547] fix(vehicle_cmd_gate): use real velocity for the lateral filter (#4918) * fix(vehicle_cmd_gate): use real velocity for the lateral filter Signed-off-by: Takamasa Horibe * fix test Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../src/vehicle_cmd_filter.cpp | 22 +++-- .../src/vehicle_cmd_filter.hpp | 1 + .../test_filter_in_vehicle_cmd_gate_node.cpp | 36 +++++-- .../test/src/test_vehicle_cmd_filter.cpp | 95 ++++++++++++------- 4 files changed, 106 insertions(+), 48 deletions(-) diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp index 8dec06c455868..6a33855fb4d0a 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp @@ -104,25 +104,28 @@ void VehicleCmdFilter::VehicleCmdFilter::limitLongitudinalWithJerk( std::clamp(static_cast(input.longitudinal.jerk), -lon_jerk_lim, lon_jerk_lim); } +// Use ego vehicle speed (not speed command) for the lateral acceleration calculation, otherwise the +// filtered steering angle oscillates if the input velocity oscillates. void VehicleCmdFilter::limitLateralWithLatAcc( [[maybe_unused]] const double dt, AckermannControlCommand & input) const { const auto lat_acc_lim = getLatAccLim(); - double latacc = calcLatAcc(input); + double latacc = calcLatAcc(input, current_speed_); if (std::fabs(latacc) > lat_acc_lim) { - double v_sq = - std::max(static_cast(input.longitudinal.speed * input.longitudinal.speed), 0.001); + double v_sq = std::max(static_cast(current_speed_ * current_speed_), 0.001); double steer_lim = std::atan(lat_acc_lim * param_.wheel_base / v_sq); input.lateral.steering_tire_angle = latacc > 0.0 ? steer_lim : -steer_lim; } } +// Use ego vehicle speed (not speed command) for the lateral acceleration calculation, otherwise the +// filtered steering angle oscillates if the input velocity oscillates. void VehicleCmdFilter::limitLateralWithLatJerk( const double dt, AckermannControlCommand & input) const { - double curr_latacc = calcLatAcc(input); - double prev_latacc = calcLatAcc(prev_cmd_); + double curr_latacc = calcLatAcc(input, current_speed_); + double prev_latacc = calcLatAcc(prev_cmd_, current_speed_); const auto lat_jerk_lim = getLatJerkLim(); @@ -130,9 +133,9 @@ void VehicleCmdFilter::limitLateralWithLatJerk( const double latacc_min = prev_latacc - lat_jerk_lim * dt; if (curr_latacc > latacc_max) { - input.lateral.steering_tire_angle = calcSteerFromLatacc(input.longitudinal.speed, latacc_max); + input.lateral.steering_tire_angle = calcSteerFromLatacc(current_speed_, latacc_max); } else if (curr_latacc < latacc_min) { - input.lateral.steering_tire_angle = calcSteerFromLatacc(input.longitudinal.speed, latacc_min); + input.lateral.steering_tire_angle = calcSteerFromLatacc(current_speed_, latacc_min); } } @@ -205,6 +208,11 @@ double VehicleCmdFilter::calcLatAcc(const AckermannControlCommand & cmd) const return v * v * std::tan(cmd.lateral.steering_tire_angle) / param_.wheel_base; } +double VehicleCmdFilter::calcLatAcc(const AckermannControlCommand & cmd, const double v) const +{ + return v * v * std::tan(cmd.lateral.steering_tire_angle) / param_.wheel_base; +} + double VehicleCmdFilter::limitDiff( const double curr, const double prev, const double diff_lim) const { diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp index eb85fcbeb8606..a79b8a38bae8d 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp @@ -81,6 +81,7 @@ class VehicleCmdFilter bool setParameterWithValidation(const VehicleCmdFilterParam & p); double calcLatAcc(const AckermannControlCommand & cmd) const; + double calcLatAcc(const AckermannControlCommand & cmd, const double v) const; double calcSteerFromLatacc(const double v, const double latacc) const; double limitDiff(const double curr, const double prev, const double diff_lim) const; diff --git a/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp b/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp index 6384fbfb22f60..1156656385117 100644 --- a/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp +++ b/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp @@ -150,7 +150,6 @@ class PubSubNode : public rclcpp::Node if (!cmd_history_.empty()) { // ego moves as commanded. msg.twist.twist.linear.x = cmd_history_.back()->longitudinal.speed; // ego moves as commanded. - } else { } pub_odom_->publish(msg); } @@ -238,16 +237,19 @@ class PubSubNode : public rclcpp::Node const auto lon_jerk = (lon_acc - cmd_prev->longitudinal.acceleration) / dt; const auto lat_acc = lon_vel * lon_vel * std::tan(cmd_curr->lateral.steering_tire_angle) / wheelbase; - const auto prev_lon_vel = cmd_prev->longitudinal.speed; + + // TODO(Horibe): prev_lat_acc should use the previous velocity, but use the current velocity + // since the current filtering logic uses the current velocity. const auto prev_lat_acc = - prev_lon_vel * prev_lon_vel * std::tan(cmd_prev->lateral.steering_tire_angle) / wheelbase; + lon_vel * lon_vel * std::tan(cmd_prev->lateral.steering_tire_angle) / wheelbase; const auto lat_jerk = (lat_acc - prev_lat_acc) / dt; /* debug print */ // const auto steer = cmd_curr->lateral.steering_tire_angle; // PRINT_VALUES( - // dt, lon_vel, lon_acc, lon_jerk, lat_acc, lat_jerk, steer, max_lon_acc_lim, - // max_lon_jerk_lim, max_lat_acc_lim, max_lat_jerk_lim); + // dt, i_curr, i_prev, steer, lon_vel, prev_lon_vel, lon_acc, lon_jerk, lat_acc, prev_lat_acc, + // prev_lat_acc2, lat_jerk, max_lon_acc_lim, max_lon_jerk_lim, max_lat_acc_lim, + // max_lat_jerk_lim); // Output command must be smaller than maximum limit. // TODO(Horibe): check for each velocity range. @@ -368,6 +370,16 @@ TEST_P(TestFixture, CheckFilterForSinCmd) [[maybe_unused]] auto b = std::system("ros2 node info /test_vehicle_cmd_gate_filter_pubsub"); [[maybe_unused]] auto c = std::system("ros2 node info /vehicle_cmd_gate"); + // std::cerr << "speed signal: " << cmd_generator_.p_.velocity.max << " * sin(2pi * " + // << cmd_generator_.p_.velocity.freq << " * dt + " << cmd_generator_.p_.velocity.bias + // << ")" << std::endl; + // std::cerr << "accel signal: " << cmd_generator_.p_.acceleration.max << " * sin(2pi * " + // << cmd_generator_.p_.acceleration.freq << " * dt + " + // << cmd_generator_.p_.acceleration.bias << ")" << std::endl; + // std::cerr << "steer signal: " << cmd_generator_.p_.steering.max << " * sin(2pi * " + // << cmd_generator_.p_.steering.freq << " * dt + " << cmd_generator_.p_.steering.bias + // << ")" << std::endl; + for (size_t i = 0; i < 100; ++i) { const bool reset_clock = (i == 0); const auto cmd = cmd_generator_.calcSinWaveCommand(reset_clock); @@ -384,17 +396,23 @@ TEST_P(TestFixture, CheckFilterForSinCmd) }; // High frequency, large value -CmdParams p1 = {/*steer*/ {10, 1, 0}, /*velocity*/ {10, 1.2, 0}, /*acc*/ {5, 1.5, 2}}; +CmdParams p1 = {/*steer*/ {0.5, 1, 0}, /*velocity*/ {10, 0.0, 0}, /*acc*/ {5, 1.5, 2}}; INSTANTIATE_TEST_SUITE_P(TestParam1, TestFixture, ::testing::Values(p1)); // High frequency, normal value -CmdParams p2 = {/*steer*/ {1.5, 2, 1}, /*velocity*/ {5, 1.0, 0}, /*acc*/ {2.0, 3.0, 2}}; +CmdParams p2 = {/*steer*/ {0.5, 2, 1}, /*velocity*/ {5, 1.0, 0}, /*acc*/ {2.0, 3.0, 2}}; INSTANTIATE_TEST_SUITE_P(TestParam2, TestFixture, ::testing::Values(p2)); // High frequency, small value -CmdParams p3 = {/*steer*/ {1.5, 3, 2}, /*velocity*/ {2, 3, 0}, /*acc*/ {0.5, 3, 2}}; +CmdParams p3 = {/*steer*/ {0.5, 3, 2}, /*velocity*/ {2, 3, 0}, /*acc*/ {0.5, 3, 2}}; INSTANTIATE_TEST_SUITE_P(TestParam3, TestFixture, ::testing::Values(p3)); // Low frequency -CmdParams p4 = {/*steer*/ {10, 0.1, 0.5}, /*velocity*/ {10, 0.2, 0}, /*acc*/ {5, 0.1, 2}}; +CmdParams p4 = {/*steer*/ {0.5, 0.1, 0.5}, /*velocity*/ {10, 0.2, 0}, /*acc*/ {5, 0.1, 2}}; INSTANTIATE_TEST_SUITE_P(TestParam4, TestFixture, ::testing::Values(p4)); + +// Large steer, large velocity -> this test fails. +// Lateral acceleration and lateral jerk affect both steer and velocity, and if both steer and +// velocity changes significantly, the current logic cannot adequately handle the situation. +// CmdParams p5 = {/*steer*/ {10.0, 1.0, 0.5}, /*velocity*/ {10, 0.2, 0}, /*acc*/ {5, 0.1, 2}}; +// INSTANTIATE_TEST_SUITE_P(TestParam5, TestFixture, ::testing::Values(p5)); diff --git a/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp b/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp index 7ce8580120652..1791493aeb17b 100644 --- a/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp +++ b/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp @@ -56,12 +56,20 @@ AckermannControlCommand genCmd(double s, double sr, double v, double a) return cmd; } +// calc from ego velocity +double calcLatAcc(const AckermannControlCommand & cmd, const double wheelbase, const double ego_v) +{ + return ego_v * ego_v * std::tan(cmd.lateral.steering_tire_angle) / wheelbase; +} + +// calc from command velocity double calcLatAcc(const AckermannControlCommand & cmd, const double wheelbase) { double v = cmd.longitudinal.speed; return v * v * std::tan(cmd.lateral.steering_tire_angle) / wheelbase; } +// calc from command velocity double calcLatJerk( const AckermannControlCommand & cmd, const AckermannControlCommand & prev_cmd, const double wheelbase, const double dt) @@ -75,14 +83,28 @@ double calcLatJerk( return (curr - prev) / dt; } +// calc from ego velocity +double calcLatJerk( + const AckermannControlCommand & cmd, const AckermannControlCommand & prev_cmd, + const double wheelbase, const double dt, const double ego_v) +{ + const auto prev = ego_v * ego_v * std::tan(prev_cmd.lateral.steering_tire_angle) / wheelbase; + + const auto curr = ego_v * ego_v * std::tan(cmd.lateral.steering_tire_angle) / wheelbase; + + return (curr - prev) / dt; +} + void test_1d_limit( - double V_LIM, double A_LIM, double J_LIM, double LAT_A_LIM, double LAT_J_LIM, double STEER_DIFF, - const AckermannControlCommand & prev_cmd, const AckermannControlCommand & raw_cmd) + double ego_v, double V_LIM, double A_LIM, double J_LIM, double LAT_A_LIM, double LAT_J_LIM, + double STEER_DIFF, const AckermannControlCommand & prev_cmd, + const AckermannControlCommand & raw_cmd) { const double WHEELBASE = 3.0; const double DT = 0.1; // [s] vehicle_cmd_gate::VehicleCmdFilter filter; + filter.setCurrentSpeed(ego_v); setFilterParams( filter, V_LIM, {0.0}, {A_LIM}, {J_LIM}, {LAT_A_LIM}, {LAT_J_LIM}, {STEER_DIFF}, WHEELBASE); filter.setPrevCmd(prev_cmd); @@ -153,8 +175,8 @@ void test_1d_limit( { auto filtered_cmd = raw_cmd; filter.limitLateralWithLatAcc(DT, filtered_cmd); - const double filtered_latacc = calcLatAcc(filtered_cmd, WHEELBASE); - const double raw_latacc = calcLatAcc(raw_cmd, WHEELBASE); + const double filtered_latacc = calcLatAcc(filtered_cmd, WHEELBASE, ego_v); + const double raw_latacc = calcLatAcc(raw_cmd, WHEELBASE, ego_v); // check if the filtered value does not exceed the limit. ASSERT_LT_NEAR(std::abs(filtered_latacc), LAT_A_LIM); @@ -169,9 +191,9 @@ void test_1d_limit( { auto filtered_cmd = raw_cmd; filter.limitLateralWithLatJerk(DT, filtered_cmd); - const double prev_lat_acc = calcLatAcc(prev_cmd, WHEELBASE); - const double filtered_lat_acc = calcLatAcc(filtered_cmd, WHEELBASE); - const double raw_lat_acc = calcLatAcc(raw_cmd, WHEELBASE); + const double prev_lat_acc = calcLatAcc(prev_cmd, WHEELBASE, ego_v); + const double filtered_lat_acc = calcLatAcc(filtered_cmd, WHEELBASE, ego_v); + const double raw_lat_acc = calcLatAcc(raw_cmd, WHEELBASE, ego_v); const double filtered_lateral_jerk = (filtered_lat_acc - prev_lat_acc) / DT; // check if the filtered value does not exceed the limit. @@ -211,6 +233,7 @@ TEST(VehicleCmdFilter, VehicleCmdFilter) const std::vector lat_a_arr = {0.01, 1.0, 100.0}; const std::vector lat_j_arr = {0.01, 1.0, 100.0}; const std::vector steer_diff_arr = {0.01, 1.0, 100.0}; + const std::vector ego_v_arr = {0.0, 0.1, 1.0, 3.0, 15.0}; const std::vector prev_cmd_arr = { genCmd(0.0, 0.0, 0.0, 0.0), genCmd(1.0, 1.0, 1.0, 1.0)}; @@ -226,7 +249,9 @@ TEST(VehicleCmdFilter, VehicleCmdFilter) for (const auto & prev_cmd : prev_cmd_arr) { for (const auto & raw_cmd : raw_cmd_arr) { for (const auto & steer_diff : steer_diff_arr) { - test_1d_limit(v, a, j, la, lj, steer_diff, prev_cmd, raw_cmd); + for (const auto & ego_v : ego_v_arr) { + test_1d_limit(ego_v, v, a, j, la, lj, steer_diff, prev_cmd, raw_cmd); + } } } } @@ -371,66 +396,72 @@ TEST(VehicleCmdFilter, VehicleCmdFilterInterpolate) // lateral acc lim // p.reference_speed_points = std::vector{2.0, 4.0, 10.0}; // p.lat_acc_lim = std::vector{0.1, 0.2, 0.3}; - const auto _calcLatAcc = [&](const auto & cmd) { return calcLatAcc(cmd, WHEELBASE); }; + const auto _calcLatAcc = [&](const auto & cmd, const double ego_v) { + return calcLatAcc(cmd, WHEELBASE, ego_v); + }; { + // since the lateral acceleration is 0 when the velocity is 0, the target value is 0 only in + // this case set_speed_and_reset_prev(0.0); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd)), 0.1, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd), 0.0), 0.0, ep); set_speed_and_reset_prev(2.0); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd)), 0.1, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd), 2.0), 0.1, ep); set_speed_and_reset_prev(3.0); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd)), 0.15, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd), 3.0), 0.15, ep); set_speed_and_reset_prev(5.0); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd)), 0.2 + 0.1 / 6.0, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd), 5.0), 0.2 + 0.1 / 6.0, ep); set_speed_and_reset_prev(8.0); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd)), 0.2 + 0.4 / 6.0, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd), 8.0), 0.2 + 0.4 / 6.0, ep); set_speed_and_reset_prev(10.0); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd)), 0.3, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd), 10.0), 0.3, ep); set_speed_and_reset_prev(15.0); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd)), 0.3, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatAcc(orig_cmd), 15.0), 0.3, ep); } // lateral jerk lim // p.reference_speed_points = std::vector{2.0, 4.0, 10.0}; // p.lat_jerk_lim = std::vector{0.9, 0.7, 0.1}; - const auto _calcLatJerk = [&](const auto & cmd) { - return calcLatJerk(cmd, AckermannControlCommand{}, WHEELBASE, DT); + const auto _calcLatJerk = [&](const auto & cmd, const double ego_v) { + return calcLatJerk(cmd, AckermannControlCommand{}, WHEELBASE, DT, ego_v); }; { + // since the lateral acceleration and jerk is 0 when the velocity is 0, the target value is 0 + // only in this case set_speed_and_reset_prev(0.0); - EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd)), 0.9, ep); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd)), DT * 0.9, ep); + EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd), 0.0), 0.0, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd), 0.0), DT * 0.0, ep); set_speed_and_reset_prev(2.0); - EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd)), 0.9, ep); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd)), DT * 0.9, ep); + EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd), 2.0), 0.9, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd), 2.0), DT * 0.9, ep); set_speed_and_reset_prev(3.0); - EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd)), 0.8, ep); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd)), DT * 0.8, ep); + EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd), 3.0), 0.8, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd), 3.0), DT * 0.8, ep); set_speed_and_reset_prev(5.0); const auto expect_v5 = 0.7 - 0.6 * (1.0 / 6.0); - EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd)), expect_v5, ep); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd)), DT * expect_v5, ep); + EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd), 5.0), expect_v5, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd), 5.0), DT * expect_v5, ep); set_speed_and_reset_prev(8.0); const auto expect_v8 = 0.7 - 0.6 * (4.0 / 6.0); - EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd)), expect_v8, ep); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd)), DT * expect_v8, ep); + EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd), 8.0), expect_v8, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd), 8.0), DT * expect_v8, ep); set_speed_and_reset_prev(10.0); - EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd)), 0.1, ep); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd)), DT * 0.1, ep); + EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd), 10.0), 0.1, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd), 10.0), DT * 0.1, ep); set_speed_and_reset_prev(15.0); - EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd)), 0.1, ep); - EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd)), DT * 0.1, ep); + EXPECT_NEAR(_calcLatJerk(_limitLateralWithLatJerk(orig_cmd), 15.0), 0.1, ep); + EXPECT_NEAR(_calcLatAcc(_limitLateralWithLatJerk(orig_cmd), 15.0), DT * 0.1, ep); } // steering diff lim From 9468848674f2bad7a24973289fd00ab825ba545f Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 11 Sep 2023 18:57:27 +0900 Subject: [PATCH 167/547] fix(static_centerline_optimzier): fix build (#4947) --- .../src/static_centerline_optimizer_node.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp index b2e136fd3916b..4bad84d9dc1f3 100644 --- a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp +++ b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp @@ -26,9 +26,12 @@ #include #include +#include #include +#include + #include #include #include From a962a21f37a0ddb5e186e0e6be8be7fcb363dbc5 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 11 Sep 2023 19:18:27 +0900 Subject: [PATCH 168/547] feat(simple_planning_simulator): consider ego pitch angle for simulation (#4941) * feat(simple_planning_simulator): consider ego pitch angle for simulation Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka * fix spell Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../simple_planning_simulator_core.hpp | 20 +++- .../simple_planning_simulator.launch.py | 1 + .../simple_planning_simulator/package.xml | 4 + .../simple_planning_simulator_core.cpp | 99 +++++++++++++++++-- 4 files changed, 114 insertions(+), 10 deletions(-) diff --git a/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp b/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp index 742f87411b7d8..8eca49d9ffadf 100644 --- a/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp +++ b/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp @@ -22,6 +22,7 @@ #include "autoware_auto_control_msgs/msg/ackermann_control_command.hpp" #include "autoware_auto_geometry_msgs/msg/complex32.hpp" +#include "autoware_auto_mapping_msgs/msg/had_map_bin.hpp" #include "autoware_auto_planning_msgs/msg/trajectory.hpp" #include "autoware_auto_vehicle_msgs/msg/control_mode_command.hpp" #include "autoware_auto_vehicle_msgs/msg/control_mode_report.hpp" @@ -47,6 +48,7 @@ #include "sensor_msgs/msg/imu.hpp" #include "tier4_external_api_msgs/srv/initialize_pose.hpp" +#include #include #include @@ -62,6 +64,7 @@ namespace simple_planning_simulator using autoware_auto_control_msgs::msg::AckermannControlCommand; using autoware_auto_geometry_msgs::msg::Complex32; +using autoware_auto_mapping_msgs::msg::HADMapBin; using autoware_auto_planning_msgs::msg::Trajectory; using autoware_auto_vehicle_msgs::msg::ControlModeReport; using autoware_auto_vehicle_msgs::msg::Engage; @@ -143,6 +146,7 @@ class PLANNING_SIMULATOR_PUBLIC SimplePlanningSimulator : public rclcpp::Node rclcpp::Subscription::SharedPtr sub_vehicle_cmd_; rclcpp::Subscription::SharedPtr sub_ackermann_cmd_; rclcpp::Subscription::SharedPtr sub_manual_ackermann_cmd_; + rclcpp::Subscription::SharedPtr sub_map_; rclcpp::Subscription::SharedPtr sub_init_pose_; rclcpp::Subscription::SharedPtr sub_init_twist_; rclcpp::Subscription::SharedPtr sub_trajectory_; @@ -160,6 +164,8 @@ class PLANNING_SIMULATOR_PUBLIC SimplePlanningSimulator : public rclcpp::Node rcl_interfaces::msg::SetParametersResult on_parameter( const std::vector & parameters); + lanelet::ConstLanelets road_lanelets_; + /* tf */ tf2_ros::Buffer tf_buffer_; tf2_ros::TransformListener tf_listener_; @@ -179,6 +185,7 @@ class PLANNING_SIMULATOR_PUBLIC SimplePlanningSimulator : public rclcpp::Node Trajectory::ConstSharedPtr current_trajectory_ptr_; bool simulate_motion_; //!< stop vehicle motion simulation if false ControlModeReport current_control_mode_; + bool enable_road_slope_simulation_; /* frame_id */ std::string simulated_frame_id_; //!< @brief simulated vehicle frame id @@ -214,7 +221,7 @@ class PLANNING_SIMULATOR_PUBLIC SimplePlanningSimulator : public rclcpp::Node /** * @brief set input steering, velocity, and acceleration of the vehicle model */ - void set_input(const AckermannControlCommand & cmd); + void set_input(const AckermannControlCommand & cmd, const double acc_by_slope); /** * @brief set current_vehicle_state_ with received message @@ -226,6 +233,11 @@ class PLANNING_SIMULATOR_PUBLIC SimplePlanningSimulator : public rclcpp::Node */ void on_hazard_lights_cmd(const HazardLightsCommand::ConstSharedPtr msg); + /** + * @brief subscribe lanelet map + */ + void on_map(const HADMapBin::ConstSharedPtr msg); + /** * @brief set initial pose for simulation with received message */ @@ -276,6 +288,12 @@ class PLANNING_SIMULATOR_PUBLIC SimplePlanningSimulator : public rclcpp::Node */ TransformStamped get_transform_msg(const std::string parent_frame, const std::string child_frame); + /** + * @brief calculate ego pitch angle from trajectory + * @return ego pitch angle + */ + double calculate_ego_pitch() const; + /** * @brief timer callback for simulation with loop_rate */ diff --git a/simulator/simple_planning_simulator/launch/simple_planning_simulator.launch.py b/simulator/simple_planning_simulator/launch/simple_planning_simulator.launch.py index 7837c61ec6593..2f9c2cfe333f4 100644 --- a/simulator/simple_planning_simulator/launch/simple_planning_simulator.launch.py +++ b/simulator/simple_planning_simulator/launch/simple_planning_simulator.launch.py @@ -53,6 +53,7 @@ def launch_setup(context, *args, **kwargs): }, ], remappings=[ + ("input/vector_map", "/map/vector_map"), ("input/initialpose", "/initialpose3d"), ("input/ackermann_control_command", "/control/command/control_cmd"), ("input/manual_ackermann_control_command", "/vehicle/command/manual_control_cmd"), diff --git a/simulator/simple_planning_simulator/package.xml b/simulator/simple_planning_simulator/package.xml index c83433d35bb1b..5652be138b18f 100644 --- a/simulator/simple_planning_simulator/package.xml +++ b/simulator/simple_planning_simulator/package.xml @@ -12,10 +12,14 @@ autoware_cmake autoware_auto_control_msgs + autoware_auto_mapping_msgs autoware_auto_planning_msgs autoware_auto_tf2 autoware_auto_vehicle_msgs geometry_msgs + lanelet2_core + lanelet2_extension + motion_utils nav_msgs rclcpp rclcpp_components diff --git a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp index 5c8a7eabdab86..fe28d063eedd3 100644 --- a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp +++ b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp @@ -15,6 +15,7 @@ #include "simple_planning_simulator/simple_planning_simulator_core.hpp" #include "autoware_auto_tf2/tf2_autoware_auto_msgs.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "rclcpp_components/register_node_macro.hpp" #include "simple_planning_simulator/vehicle_model/sim_model.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" @@ -22,6 +23,11 @@ #include "tier4_autoware_utils/ros/update_param.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" +#include +#include + +#include +#include #include #include @@ -47,13 +53,14 @@ autoware_auto_vehicle_msgs::msg::VelocityReport to_velocity_report( return velocity; } -nav_msgs::msg::Odometry to_odometry(const std::shared_ptr vehicle_model_ptr) +nav_msgs::msg::Odometry to_odometry( + const std::shared_ptr vehicle_model_ptr, const double ego_pitch_angle) { nav_msgs::msg::Odometry odometry; odometry.pose.pose.position.x = vehicle_model_ptr->getX(); odometry.pose.pose.position.y = vehicle_model_ptr->getY(); - odometry.pose.pose.orientation = - tier4_autoware_utils::createQuaternionFromYaw(vehicle_model_ptr->getYaw()); + odometry.pose.pose.orientation = tier4_autoware_utils::createQuaternionFromRPY( + 0.0, ego_pitch_angle, vehicle_model_ptr->getYaw()); odometry.twist.twist.linear.x = vehicle_model_ptr->getVx(); odometry.twist.twist.angular.z = vehicle_model_ptr->getWz(); @@ -68,6 +75,19 @@ autoware_auto_vehicle_msgs::msg::SteeringReport to_steering_report( return steer; } +std::vector convert_centerline_to_points( + const lanelet::Lanelet & lanelet) +{ + std::vector centerline_points; + for (const auto & point : lanelet.centerline()) { + geometry_msgs::msg::Point center_point; + center_point.x = point.basicPoint().x(); + center_point.y = point.basicPoint().y(); + center_point.z = point.basicPoint().z(); + centerline_points.push_back(center_point); + } + return centerline_points; +} } // namespace namespace simulation @@ -82,11 +102,15 @@ SimplePlanningSimulator::SimplePlanningSimulator(const rclcpp::NodeOptions & opt origin_frame_id_ = declare_parameter("origin_frame_id", "odom"); add_measurement_noise_ = declare_parameter("add_measurement_noise", false); simulate_motion_ = declare_parameter("initial_engage_state"); + enable_road_slope_simulation_ = declare_parameter("enable_road_slope_simulation", false); using rclcpp::QoS; using std::placeholders::_1; using std::placeholders::_2; + sub_map_ = create_subscription( + "input/vector_map", rclcpp::QoS(10).transient_local(), + std::bind(&SimplePlanningSimulator::on_map, this, _1)); sub_init_pose_ = create_subscription( "input/initialpose", QoS{1}, std::bind(&SimplePlanningSimulator::on_initialpose, this, _1)); sub_init_twist_ = create_subscription( @@ -253,6 +277,44 @@ rcl_interfaces::msg::SetParametersResult SimplePlanningSimulator::on_parameter( return result; } +double SimplePlanningSimulator::calculate_ego_pitch() const +{ + const double ego_x = vehicle_model_ptr_->getX(); + const double ego_y = vehicle_model_ptr_->getY(); + const double ego_yaw = vehicle_model_ptr_->getYaw(); + + geometry_msgs::msg::Pose ego_pose; + ego_pose.position.x = ego_x; + ego_pose.position.y = ego_y; + ego_pose.orientation = tier4_autoware_utils::createQuaternionFromYaw(ego_yaw); + + // calculate prev/next point of lanelet centerline nearest to ego pose. + lanelet::Lanelet ego_lanelet; + if (!lanelet::utils::query::getClosestLaneletWithConstrains( + road_lanelets_, ego_pose, &ego_lanelet, 2.0, std::numeric_limits::max())) { + return 0.0; + } + const auto centerline_points = convert_centerline_to_points(ego_lanelet); + const size_t ego_seg_idx = + motion_utils::findNearestSegmentIndex(centerline_points, ego_pose.position); + + const auto & prev_point = centerline_points.at(ego_seg_idx); + const auto & next_point = centerline_points.at(ego_seg_idx + 1); + + // calculate ego yaw angle on lanelet coordinates + const double lanelet_yaw = std::atan2(next_point.y - prev_point.y, next_point.x - prev_point.x); + const double ego_yaw_against_lanelet = ego_yaw - lanelet_yaw; + + // calculate ego pitch angle considering ego yaw. + const double diff_z = next_point.z - prev_point.z; + const double diff_xy = std::hypot(next_point.x - prev_point.x, next_point.y - prev_point.y) / + std::cos(ego_yaw_against_lanelet); + const bool reverse_sign = std::cos(ego_yaw_against_lanelet) < 0.0; + const double ego_pitch_angle = + reverse_sign ? std::atan2(-diff_z, -diff_xy) : std::atan2(diff_z, diff_xy); + return ego_pitch_angle; +} + void SimplePlanningSimulator::on_timer() { if (!is_initialized_) { @@ -260,16 +322,21 @@ void SimplePlanningSimulator::on_timer() return; } + // calculate longitudinal acceleration by slope + const double ego_pitch_angle = calculate_ego_pitch(); + const double acc_by_slope = + enable_road_slope_simulation_ ? -9.81 * std::sin(ego_pitch_angle) : 0.0; + // update vehicle dynamics { const double dt = delta_time_.get_dt(get_clock()->now()); if (current_control_mode_.mode == ControlModeReport::AUTONOMOUS) { vehicle_model_ptr_->setGear(current_gear_cmd_.command); - set_input(current_ackermann_cmd_); + set_input(current_ackermann_cmd_, acc_by_slope); } else { vehicle_model_ptr_->setGear(current_manual_gear_cmd_.command); - set_input(current_manual_ackermann_cmd_); + set_input(current_manual_ackermann_cmd_, acc_by_slope); } if (simulate_motion_) { @@ -278,7 +345,7 @@ void SimplePlanningSimulator::on_timer() } // set current state - current_odometry_ = to_odometry(vehicle_model_ptr_); + current_odometry_ = to_odometry(vehicle_model_ptr_, ego_pitch_angle); current_odometry_.pose.pose.position.z = get_z_pose_from_trajectory( current_odometry_.pose.pose.position.x, current_odometry_.pose.pose.position.y); @@ -310,6 +377,19 @@ void SimplePlanningSimulator::on_timer() publish_tf(current_odometry_); } +void SimplePlanningSimulator::on_map(const HADMapBin::ConstSharedPtr msg) +{ + auto lanelet_map_ptr = std::make_shared(); + + lanelet::routing::RoutingGraphPtr routing_graph_ptr; + lanelet::traffic_rules::TrafficRulesPtr traffic_rules_ptr; + lanelet::utils::conversion::fromBinMsg( + *msg, lanelet_map_ptr, &traffic_rules_ptr, &routing_graph_ptr); + + lanelet::ConstLanelets all_lanelets = lanelet::utils::query::laneletLayer(lanelet_map_ptr); + road_lanelets_ = lanelet::utils::query::roadLanelets(all_lanelets); +} + void SimplePlanningSimulator::on_initialpose(const PoseWithCovarianceStamped::ConstSharedPtr msg) { // save initial pose @@ -346,7 +426,8 @@ void SimplePlanningSimulator::on_set_pose( response->status = tier4_api_utils::response_success(); } -void SimplePlanningSimulator::set_input(const AckermannControlCommand & cmd) +void SimplePlanningSimulator::set_input( + const AckermannControlCommand & cmd, const double acc_by_slope) { const auto steer = cmd.lateral.steering_tire_angle; const auto vel = cmd.longitudinal.speed; @@ -358,11 +439,11 @@ void SimplePlanningSimulator::set_input(const AckermannControlCommand & cmd) // TODO(Watanabe): The definition of the sign of acceleration in REVERSE mode is different // between .auto and proposal.iv, and will be discussed later. - float acc = accel; + float acc = accel + acc_by_slope; if (gear == GearCommand::NONE) { acc = 0.0; } else if (gear == GearCommand::REVERSE || gear == GearCommand::REVERSE_2) { - acc = -accel; + acc = -accel - acc_by_slope; } if ( From cdc568db1e4d3180f2c7c9c8c1cca78e7a8374dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Yavuz=20K=C3=B6seo=C4=9Flu?= Date: Mon, 11 Sep 2023 14:24:02 +0300 Subject: [PATCH 169/547] feat(ndt_scan_matcher): Adding state switch mecahism for nearest_voxel_transformation_likelihood: For deciding does NDT have problems while matching scans (#4854) * exe time added to diagnostics and switch state by voxel likelihood * style(pre-commit): autofix * type casting corrected * ndt score condition parameterized * remove exe_time from diagnostic * style(pre-commit): autofix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../ndt_scan_matcher/src/ndt_scan_matcher_core.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 714f8f65bad37..103df19c2b5db 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -291,6 +291,13 @@ void NDTScanMatcher::timer_diagnostic() diag_status_msg.level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; diag_status_msg.message += "skipping_publish_num exceed limit. "; } + if ( + state_ptr_->count("nearest_voxel_transformation_likelihood") && + std::stod((*state_ptr_)["nearest_voxel_transformation_likelihood"]) < + converged_param_nearest_voxel_transformation_likelihood_) { + diag_status_msg.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + diag_status_msg.message += "NDT score is unreliably low. "; + } // Ignore local optimal solution if ( state_ptr_->count("is_local_optimal_solution_oscillation") && From 0638a0115186a45d2a638af0d054bca410b08645 Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Tue, 12 Sep 2023 03:03:02 +0900 Subject: [PATCH 170/547] feat(radar_object_tracker): add distance and lanelet map based noise filtering for radar tracking (#4935) * refactor: put node parameter to config yaml file Signed-off-by: yoshiri * use lane information and distance information to filter outliers Signed-off-by: yoshiri * enable to tune tracker lifetime Signed-off-by: yoshiri * update readme documents Signed-off-by: yoshiri * brushup model information and fix typo Signed-off-by: yoshiri --------- Signed-off-by: yoshiri --- perception/radar_object_tracker/README.md | 44 ++-- .../config/radar_object_tracker.param.yaml | 27 ++ .../image/noise_filtering.drawio.svg | 165 +++++++++++++ .../radar_object_tracker_node.hpp | 45 +++- .../launch/radar_object_tracker.launch.xml | 10 +- perception/radar_object_tracker/models.md | 30 +++ perception/radar_object_tracker/package.xml | 1 + .../radar_object_tracker_node.cpp | 230 +++++++++++++++++- 8 files changed, 518 insertions(+), 34 deletions(-) create mode 100644 perception/radar_object_tracker/config/radar_object_tracker.param.yaml create mode 100644 perception/radar_object_tracker/image/noise_filtering.drawio.svg diff --git a/perception/radar_object_tracker/README.md b/perception/radar_object_tracker/README.md index 9f11d50ce2b3f..739a4a745ff6c 100644 --- a/perception/radar_object_tracker/README.md +++ b/perception/radar_object_tracker/README.md @@ -26,9 +26,10 @@ See more details in the [models.md](models.md). ### Input -| Name | Type | Description | -| --------- | ----------------------------------------------------- | ---------------- | -| `~/input` | `autoware_auto_perception_msgs::msg::DetectedObjects` | Detected objects | +| Name | Type | Description | +| ------------- | ----------------------------------------------------- | ---------------- | +| `~/input` | `autoware_auto_perception_msgs::msg::DetectedObjects` | Detected objects | +| `/vector/map` | `autoware_auto_msgs::msg::HADMapBin` | Map data | ### Output @@ -40,20 +41,29 @@ See more details in the [models.md](models.md). ### Node Parameters -| Name | Type | Default Value | Description | -| --------------------------- | ------ | ----------------------------- | ----------------------------------------------------------- | -| `publish_rate` | double | 30.0 | The rate at which to publish the output messages | -| `world_frame_id` | string | "world" | The frame ID of the world coordinate system | -| `enable_delay_compensation` | bool | false | Whether to enable delay compensation | -| `tracking_config_directory` | string | "" | The directory containing the tracking configuration files | -| `enable_logging` | bool | false | Whether to enable logging | -| `logging_file_path` | string | "~/.ros/association_log.json" | The path to the file where logs should be written | -| `can_assign_matrix` | array | | An array of integers used in the data association algorithm | -| `max_dist_matrix` | array | | An array of doubles used in the data association algorithm | -| `max_area_matrix` | array | | An array of doubles used in the data association algorithm | -| `min_area_matrix` | array | | An array of doubles used in the data association algorithm | -| `max_rad_matrix` | array | | An array of doubles used in the data association algorithm | -| `min_iou_matrix` | array | | An array of doubles used in the data association algorithm | +| Name | Type | Default Value | Description | +| ------------------------------------ | ------ | --------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `publish_rate` | double | 10.0 | The rate at which to publish the output messages | +| `world_frame_id` | string | "map" | The frame ID of the world coordinate system | +| `enable_delay_compensation` | bool | false | Whether to enable delay compensation. If set to `true`, output topic is published by timer with `publish_rate`. | +| `tracking_config_directory` | string | "" | The directory containing the tracking configuration files | +| `enable_logging` | bool | false | Whether to enable logging | +| `logging_file_path` | string | "/tmp/association_log.json" | The path to the file where logs should be written | +| `tracker_lifetime` | double | 1.0 | The lifetime of the tracker in seconds | +| `use_distance_based_noise_filtering` | bool | true | Whether to use distance based filtering | +| `minimum_range_threshold` | double | 70.0 | Minimum distance threshold for filtering in meters | +| `use_map_based_noise_filtering` | bool | true | Whether to use map based filtering | +| `max_distance_from_lane` | double | 5.0 | Maximum distance from lane for filtering in meters | +| `max_angle_diff_from_lane` | double | 0.785398 | Maximum angle difference from lane for filtering in radians | +| `max_lateral_velocity` | double | 5.0 | Maximum lateral velocity for filtering in m/s | +| `can_assign_matrix` | array | | An array of integers used in the data association algorithm | +| `max_dist_matrix` | array | | An array of doubles used in the data association algorithm | +| `max_area_matrix` | array | | An array of doubles used in the data association algorithm | +| `min_area_matrix` | array | | An array of doubles used in the data association algorithm | +| `max_rad_matrix` | array | | An array of doubles used in the data association algorithm | +| `min_iou_matrix` | array | | An array of doubles used in the data association algorithm | + +See more details in the [models.md](models.md). ## Assumptions / Known limits diff --git a/perception/radar_object_tracker/config/radar_object_tracker.param.yaml b/perception/radar_object_tracker/config/radar_object_tracker.param.yaml new file mode 100644 index 0000000000000..f80adffb41090 --- /dev/null +++ b/perception/radar_object_tracker/config/radar_object_tracker.param.yaml @@ -0,0 +1,27 @@ +# general parameters for radar_object_tracker node +/**: + ros__parameters: + # basic settings + world_frame_id: "map" + tracker_lifetime: 1.0 # [sec] + # if empty, use default config declared in this package + tracking_config_directory: "" + + # delay compensate parameters + publish_rate: 10.0 + enable_delay_compensation: false + + # logging + enable_logging: false + logging_file_path: "/tmp/association_log.json" + + # filtering + ## 1. distance based filtering: remove closer objects than this threshold + use_distance_based_noise_filtering: true + minimum_range_threshold: 70.0 # [m] + + ## 2. lanelet map based filtering + use_map_based_noise_filtering: true + max_distance_from_lane: 5.0 # [m] + max_angle_diff_from_lane: 0.785398 # [rad] (45 deg) + max_lateral_velocity: 5.0 # [m/s] diff --git a/perception/radar_object_tracker/image/noise_filtering.drawio.svg b/perception/radar_object_tracker/image/noise_filtering.drawio.svg new file mode 100644 index 0000000000000..cd044cdfa1379 --- /dev/null +++ b/perception/radar_object_tracker/image/noise_filtering.drawio.svg @@ -0,0 +1,165 @@ + + + + + + + + + + + +
    +
    +
    self vehicle
    +
    +
    +
    + self vehicle +
    +
    + + + + + +
    +
    +
    removed
    +
    +
    +
    + removed +
    +
    + + + + + +
    +
    +
    distance threshold
    +
    +
    +
    + distance threshold +
    +
    + + + + + +
    +
    +
    + filtered object +
    +
    +
    +
    + filtered object +
    +
    + + + + + + + + + +
    +
    +
    removed
    +
    +
    +
    + removed +
    +
    + + + + + + + +
    +
    +
    + lane distance +
    + threshold +
    +
    +
    +
    + lane distance... +
    +
    + + + + + + + + + +
    +
    +
    + removed +
    + with +
    + angle threshold +
    +
    +
    +
    + removed... +
    +
    + + + + + + + +
    +
    +
    + removed by +
    + high +
    + lateral velocity +
    +
    +
    +
    + removed by... +
    +
    +
    + + + + Text is not SVG - cannot display + + +
    diff --git a/perception/radar_object_tracker/include/radar_object_tracker/radar_object_tracker_node/radar_object_tracker_node.hpp b/perception/radar_object_tracker/include/radar_object_tracker/radar_object_tracker_node/radar_object_tracker_node.hpp index 898a7855aabae..415ff24b34cc3 100644 --- a/perception/radar_object_tracker/include/radar_object_tracker/radar_object_tracker_node/radar_object_tracker_node.hpp +++ b/perception/radar_object_tracker/include/radar_object_tracker/radar_object_tracker_node/radar_object_tracker_node.hpp @@ -33,6 +33,16 @@ #include #endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include #include @@ -41,8 +51,11 @@ #include #include +using autoware_auto_mapping_msgs::msg::HADMapBin; using autoware_auto_perception_msgs::msg::DetectedObject; using autoware_auto_perception_msgs::msg::DetectedObjects; +using autoware_auto_perception_msgs::msg::TrackedObject; +using autoware_auto_perception_msgs::msg::TrackedObjects; class RadarObjectTrackerNode : public rclcpp::Node { @@ -50,20 +63,23 @@ class RadarObjectTrackerNode : public rclcpp::Node explicit RadarObjectTrackerNode(const rclcpp::NodeOptions & node_options); private: + // pub-sub rclcpp::Publisher::SharedPtr tracked_objects_pub_; rclcpp::Subscription::SharedPtr detected_object_sub_; - rclcpp::TimerBase::SharedPtr publish_timer_; // publish timer + rclcpp::TimerBase::SharedPtr publish_timer_; // publish timer + rclcpp::Subscription::SharedPtr sub_map_; // map subscriber tf2_ros::Buffer tf_buffer_; tf2_ros::TransformListener tf_listener_; - + float tracker_lifetime_; std::map tracker_map_; void onMeasurement( const autoware_auto_perception_msgs::msg::DetectedObjects::ConstSharedPtr input_objects_msg); void onTimer(); + void onMap(const HADMapBin::ConstSharedPtr map_msg); std::string world_frame_id_; // tracking frame std::string tracker_config_directory_; @@ -77,11 +93,36 @@ class RadarObjectTrackerNode : public rclcpp::Node std::string path; } logging_; + // noise reduction + bool use_distance_based_noise_filtering_; + bool use_map_based_noise_filtering_; + + // distance based noise reduction + double minimum_range_threshold_; + std::string sensor_frame_ = "base_link"; + + // map based noise reduction + bool map_is_loaded_ = false; + double max_distance_from_lane_; + double max_lateral_velocity_; + double max_angle_diff_from_lane_; + // Lanelet Map Pointers + std::shared_ptr lanelet_map_ptr_; + std::shared_ptr routing_graph_ptr_; + std::shared_ptr traffic_rules_ptr_; + // Crosswalk Entry Points + // lanelet::ConstLanelets crosswalks_; + void checkTrackerLifeCycle( std::list> & list_tracker, const rclcpp::Time & time, const geometry_msgs::msg::Transform & self_transform); void sanitizeTracker( std::list> & list_tracker, const rclcpp::Time & time); + void mapBasedNoiseFilter( + std::list> & list_tracker, const rclcpp::Time & time); + void distanceBasedNoiseFilter( + std::list> & list_tracker, const rclcpp::Time & time, + const geometry_msgs::msg::Transform & self_transform); std::shared_ptr createNewTracker( const autoware_auto_perception_msgs::msg::DetectedObject & object, const rclcpp::Time & time, const geometry_msgs::msg::Transform & self_transform) const; diff --git a/perception/radar_object_tracker/launch/radar_object_tracker.launch.xml b/perception/radar_object_tracker/launch/radar_object_tracker.launch.xml index 7ef3e58a64161..e2d30c1b69d19 100644 --- a/perception/radar_object_tracker/launch/radar_object_tracker.launch.xml +++ b/perception/radar_object_tracker/launch/radar_object_tracker.launch.xml @@ -7,16 +7,18 @@ + + + + + + - - - - diff --git a/perception/radar_object_tracker/models.md b/perception/radar_object_tracker/models.md index a4c53db156f0f..f4e4c6a02edaf 100644 --- a/perception/radar_object_tracker/models.md +++ b/perception/radar_object_tracker/models.md @@ -74,3 +74,33 @@ v_{k+1} &= v_k \\ \omega_{k+1} &= \omega_k \end{align} $$ + +## Noise filtering + +Radar sensors often have noisy measurement. So we use the following filter to reduce the false positive objects. + +The figure below shows the current noise filtering process. + +![noise_filter](image/noise_filtering.drawio.svg) + +### minimum range filter + +In most cases, Radar sensors are used with other sensors such as LiDAR and Camera, and Radar sensors are used to detect objects far away. So we can filter out objects that are too close to the sensor. + +`use_distance_based_noise_filtering` parameter is used to enable/disable this filter, and `minimum_range_threshold` parameter is used to set the threshold. + +### lanelet based filter + +With lanelet map information, We can filter out false positive objects that are not likely important obstacles. + +We filter out objects that satisfy the following conditions: + +- too large lateral distance from lane +- velocity direction is too different from lane direction +- too large lateral velocity + +Each condition can be set by the following parameters: + +- `max_distance_from_lane` +- `max_angle_diff_from_lane` +- `max_lateral_velocity` diff --git a/perception/radar_object_tracker/package.xml b/perception/radar_object_tracker/package.xml index 3ff6feb617baa..ad71e613c1a18 100644 --- a/perception/radar_object_tracker/package.xml +++ b/perception/radar_object_tracker/package.xml @@ -16,6 +16,7 @@ autoware_auto_perception_msgs eigen kalman_filter + lanelet2_extension mussp nlohmann-json-dev object_recognition_utils diff --git a/perception/radar_object_tracker/src/radar_object_tracker_node/radar_object_tracker_node.cpp b/perception/radar_object_tracker/src/radar_object_tracker_node/radar_object_tracker_node.cpp index f32084493f34e..6b02836e1f933 100644 --- a/perception/radar_object_tracker/src/radar_object_tracker_node/radar_object_tracker_node.cpp +++ b/perception/radar_object_tracker/src/radar_object_tracker_node/radar_object_tracker_node.cpp @@ -60,6 +60,131 @@ boost::optional getTransformAnonymous( } } +// check if lanelet is close enough to the target lanelet +bool isDuplicated( + const std::pair & target_lanelet, + const lanelet::ConstLanelets & lanelets) +{ + const double CLOSE_LANELET_THRESHOLD = 0.1; + for (const auto & lanelet : lanelets) { + const auto target_lanelet_end_p = target_lanelet.second.centerline2d().back(); + const auto lanelet_end_p = lanelet.centerline2d().back(); + const double dist = std::hypot( + target_lanelet_end_p.x() - lanelet_end_p.x(), target_lanelet_end_p.y() - lanelet_end_p.y()); + if (dist < CLOSE_LANELET_THRESHOLD) { + return true; + } + } + + return false; +} + +// check if the lanelet is valid for object tracking +bool checkCloseLaneletCondition( + const std::pair & lanelet, const TrackedObject & object, + const double max_distance_from_lane, const double max_angle_diff_from_lane) +{ + // Step1. If we only have one point in the centerline, we will ignore the lanelet + if (lanelet.second.centerline().size() <= 1) { + return false; + } + + // Step2. Check if the obstacle is inside or close enough to the lanelet + lanelet::BasicPoint2d search_point( + object.kinematics.pose_with_covariance.pose.position.x, + object.kinematics.pose_with_covariance.pose.position.y); + if (!lanelet::geometry::inside(lanelet.second, search_point)) { + const auto distance = lanelet.first; + if (distance > max_distance_from_lane) { + return false; + } + } + + // Step3. Calculate the angle difference between the lane angle and obstacle angle + const double object_yaw = tf2::getYaw(object.kinematics.pose_with_covariance.pose.orientation); + const double lane_yaw = lanelet::utils::getLaneletAngle( + lanelet.second, object.kinematics.pose_with_covariance.pose.position); + const double delta_yaw = object_yaw - lane_yaw; + const double normalized_delta_yaw = tier4_autoware_utils::normalizeRadian(delta_yaw); + const double abs_norm_delta = std::fabs(normalized_delta_yaw); + + // Step4. Check if the closest lanelet is valid, and add all + // of the lanelets that are below max_dist and max_delta_yaw + const double object_vel = object.kinematics.twist_with_covariance.twist.linear.x; + const bool is_yaw_reversed = M_PI - max_angle_diff_from_lane < abs_norm_delta && object_vel < 0.0; + if (is_yaw_reversed || abs_norm_delta < max_angle_diff_from_lane) { + return true; + } + + return false; +} + +lanelet::ConstLanelets getClosestValidLanelets( + const TrackedObject & object, const lanelet::LaneletMapPtr & lanelet_map_ptr, + const double max_distance_from_lane, const double max_angle_diff_from_lane) +{ + // obstacle point + lanelet::BasicPoint2d search_point( + object.kinematics.pose_with_covariance.pose.position.x, + object.kinematics.pose_with_covariance.pose.position.y); + + // nearest lanelet + std::vector> surrounding_lanelets = + lanelet::geometry::findNearest(lanelet_map_ptr->laneletLayer, search_point, 10); + + // No Closest Lanelets + if (surrounding_lanelets.empty()) { + return {}; + } + + lanelet::ConstLanelets closest_lanelets; + for (const auto & lanelet : surrounding_lanelets) { + // Check if the close lanelets meet the necessary condition for start lanelets and + // Check if similar lanelet is inside the closest lanelet + if ( + !checkCloseLaneletCondition( + lanelet, object, max_distance_from_lane, max_angle_diff_from_lane) || + isDuplicated(lanelet, closest_lanelets)) { + continue; + } + + closest_lanelets.push_back(lanelet.second); + } + + return closest_lanelets; +} + +bool hasValidVelocityDirectionToLanelet( + const TrackedObject & object, const lanelet::ConstLanelets & lanelets, + const double max_lateral_velocity) +{ + // get object velocity direction + const double object_yaw = tf2::getYaw(object.kinematics.pose_with_covariance.pose.orientation); + const double object_vel_x = object.kinematics.twist_with_covariance.twist.linear.x; + const double object_vel_y = object.kinematics.twist_with_covariance.twist.linear.y; + const double object_vel_yaw = std::atan2(object_vel_y, object_vel_x); // local frame + const double object_vel_yaw_global = + tier4_autoware_utils::normalizeRadian(object_yaw + object_vel_yaw); + const double object_vel = std::hypot(object_vel_x, object_vel_y); + + for (const auto & lanelet : lanelets) { + // get lanelet angle + const double lane_yaw = lanelet::utils::getLaneletAngle( + lanelet, object.kinematics.pose_with_covariance.pose.position); + const double delta_yaw = object_vel_yaw_global - lane_yaw; + const double normalized_delta_yaw = tier4_autoware_utils::normalizeRadian(delta_yaw); + + // get lateral velocity + const double lane_vel = object_vel * std::sin(normalized_delta_yaw); + // std::cout << "lane_vel: " << lane_vel << " , delta yaw:" << normalized_delta_yaw << " , + // object_vel " << object_vel <("output", rclcpp::QoS{1}); + sub_map_ = this->create_subscription( + "/vector_map", rclcpp::QoS{1}.transient_local(), + std::bind(&RadarObjectTrackerNode::onMap, this, std::placeholders::_1)); // Parameters - double publish_rate = declare_parameter("publish_rate", 30.0); - world_frame_id_ = declare_parameter("world_frame_id", "world"); - bool enable_delay_compensation{declare_parameter("enable_delay_compensation", false)}; - tracker_config_directory_ = declare_parameter("tracking_config_directory", ""); - logging_.enable = declare_parameter("enable_logging", false); - logging_.path = - declare_parameter("logging_file_path", "~/.ros/association_log.json"); + tracker_lifetime_ = declare_parameter("tracker_lifetime"); + double publish_rate = declare_parameter("publish_rate"); + world_frame_id_ = declare_parameter("world_frame_id"); + bool enable_delay_compensation{declare_parameter("enable_delay_compensation")}; + tracker_config_directory_ = declare_parameter("tracking_config_directory"); + logging_.enable = declare_parameter("enable_logging"); + logging_.path = declare_parameter("logging_file_path"); + + // noise filter + use_distance_based_noise_filtering_ = + declare_parameter("use_distance_based_noise_filtering"); + use_map_based_noise_filtering_ = declare_parameter("use_map_based_noise_filtering"); + minimum_range_threshold_ = declare_parameter("minimum_range_threshold"); + max_distance_from_lane_ = declare_parameter("max_distance_from_lane"); + max_angle_diff_from_lane_ = declare_parameter("max_angle_diff_from_lane"); + max_lateral_velocity_ = declare_parameter("max_lateral_velocity"); // Load tracking config file if (tracker_config_directory_.empty()) { @@ -132,6 +269,17 @@ RadarObjectTrackerNode::RadarObjectTrackerNode(const rclcpp::NodeOptions & node_ std::make_pair(Label::MOTORCYCLE, this->declare_parameter("motorcycle_tracker"))); } +// load map information to node parameter +void RadarObjectTrackerNode::onMap(const HADMapBin::ConstSharedPtr msg) +{ + RCLCPP_INFO(get_logger(), "[Radar Object Tracker]: Start loading lanelet"); + lanelet_map_ptr_ = std::make_shared(); + lanelet::utils::conversion::fromBinMsg( + *msg, lanelet_map_ptr_, &traffic_rules_ptr_, &routing_graph_ptr_); + RCLCPP_INFO(get_logger(), "[Radar Object Tracker]: Map is loaded"); + map_is_loaded_ = true; +} + void RadarObjectTrackerNode::onMeasurement( const autoware_auto_perception_msgs::msg::DetectedObjects::ConstSharedPtr input_objects_msg) { @@ -176,6 +324,14 @@ void RadarObjectTrackerNode::onMeasurement( /* life cycle check */ checkTrackerLifeCycle(list_tracker_, measurement_time, *self_transform); + /* map based noise filter */ + if (map_is_loaded_ && use_map_based_noise_filtering_) { + mapBasedNoiseFilter(list_tracker_, measurement_time); + } + /* distance based noise filter */ + if (use_distance_based_noise_filtering_) { + distanceBasedNoiseFilter(list_tracker_, measurement_time, *self_transform); + } /* sanitize trackers */ sanitizeTracker(list_tracker_, measurement_time); @@ -227,6 +383,16 @@ void RadarObjectTrackerNode::onTimer() /* life cycle check */ checkTrackerLifeCycle(list_tracker_, current_time, *self_transform); + + /* map based noise filter */ + if (map_is_loaded_ && use_map_based_noise_filtering_) { + mapBasedNoiseFilter(list_tracker_, current_time); + } + /* distance based noise filter */ + if (use_distance_based_noise_filtering_) { + distanceBasedNoiseFilter(list_tracker_, current_time, *self_transform); + } + /* sanitize trackers */ sanitizeTracker(list_tracker_, current_time); @@ -238,12 +404,9 @@ void RadarObjectTrackerNode::checkTrackerLifeCycle( std::list> & list_tracker, const rclcpp::Time & time, [[maybe_unused]] const geometry_msgs::msg::Transform & self_transform) { - /* params */ - constexpr float max_elapsed_time = 1.0; - /* delete tracker */ for (auto itr = list_tracker.begin(); itr != list_tracker.end(); ++itr) { - const bool is_old = max_elapsed_time < (*itr)->getElapsedTimeFromLastUpdate(time); + const bool is_old = tracker_lifetime_ < (*itr)->getElapsedTimeFromLastUpdate(time); if (is_old) { auto erase_itr = itr; --itr; @@ -252,6 +415,51 @@ void RadarObjectTrackerNode::checkTrackerLifeCycle( } } +// remove objects by lanelet information +void RadarObjectTrackerNode::mapBasedNoiseFilter( + std::list> & list_tracker, const rclcpp::Time & time) +{ + for (auto itr = list_tracker.begin(); itr != list_tracker.end(); ++itr) { + autoware_auto_perception_msgs::msg::TrackedObject object; + (*itr)->getTrackedObject(time, object); + const auto closest_lanelets = getClosestValidLanelets( + object, lanelet_map_ptr_, max_distance_from_lane_, max_angle_diff_from_lane_); + + // 1. If the object is not close to any lanelet, delete the tracker + const bool no_closest_lanelet = closest_lanelets.empty(); + // 2. If the object velocity direction is not close to the lanelet direction, delete the tracker + const bool is_velocity_direction_close_to_lanelet = + hasValidVelocityDirectionToLanelet(object, closest_lanelets, max_lateral_velocity_); + if (no_closest_lanelet || !is_velocity_direction_close_to_lanelet) { + // std::cout << "object removed due to map based noise filter" << " no close lanelet: " << + // no_closest_lanelet << " velocity direction flag:" << is_velocity_direction_close_to_lanelet + // << std::endl; + itr = list_tracker.erase(itr); + --itr; + } + } +} + +// remove objects by distance +void RadarObjectTrackerNode::distanceBasedNoiseFilter( + std::list> & list_tracker, const rclcpp::Time & time, + const geometry_msgs::msg::Transform & self_transform) +{ + // remove objects that are too close + for (auto itr = list_tracker.begin(); itr != list_tracker.end(); ++itr) { + autoware_auto_perception_msgs::msg::TrackedObject object; + (*itr)->getTrackedObject(time, object); + const double distance = std::hypot( + object.kinematics.pose_with_covariance.pose.position.x - self_transform.translation.x, + object.kinematics.pose_with_covariance.pose.position.y - self_transform.translation.y); + if (distance < minimum_range_threshold_) { + // std::cout << "object removed due to small distance. distance: " << distance << std::endl; + itr = list_tracker.erase(itr); + --itr; + } + } +} + void RadarObjectTrackerNode::sanitizeTracker( std::list> & list_tracker, const rclcpp::Time & time) { From f08e64d77ae2d6c5f052ba90f76729a9f19f03a3 Mon Sep 17 00:00:00 2001 From: Yuntianyi Chen Date: Mon, 11 Sep 2023 13:33:06 -0700 Subject: [PATCH 171/547] refactor(crosswalk_traffic_light_estimator): rework parameters (#4699) * refactor the configuration files of the node crosswalk_traffic_light_estimator according to the new ROS node config guideline. update the parameter information in the README.md Signed-off-by: yuntianyi-chen * style(pre-commit): autofix * fix the xml pre-check issue Signed-off-by: yuntianyi-chen * delete the xml declaration to fix the xml pre-check issue Signed-off-by: yuntianyi-chen * Modify the CMakeLists.txt file to enalbe /config directory sharing when building the package. Signed-off-by: yuntianyi-chen * Update the bound for schema file. Signed-off-by: yuntianyi-chen * add crosswalk_traffic_light_estimator_param_file to traffic_light.launch.xml Signed-off-by: Shunsuke Miura --------- Signed-off-by: yuntianyi-chen Signed-off-by: Shunsuke Miura Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Co-authored-by: Shunsuke Miura --- .../traffic_light.launch.xml | 2 ++ .../CMakeLists.txt | 6 ++-- .../README.md | 7 ++-- ...osswalk_traffic_light_estimator.param.yaml | 18 ++++++++++ ...osswalk_traffic_light_estimator.launch.xml | 16 ++++++++- ...sswalk_traffic_light_estimator.schema.json | 36 +++++++++++++++++++ .../src/node.cpp | 6 ++-- 7 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 perception/crosswalk_traffic_light_estimator/config/crosswalk_traffic_light_estimator.param.yaml create mode 100644 perception/crosswalk_traffic_light_estimator/schema/crosswalk_traffic_light_estimator.schema.json diff --git a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml index a9301f31d87aa..58e9e231e4aa0 100644 --- a/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml +++ b/launch/tier4_perception_launch/launch/traffic_light_recognition/traffic_light.launch.xml @@ -15,6 +15,7 @@ + @@ -143,6 +144,7 @@ + diff --git a/perception/crosswalk_traffic_light_estimator/CMakeLists.txt b/perception/crosswalk_traffic_light_estimator/CMakeLists.txt index 11a6b232caf90..2947070eca01e 100644 --- a/perception/crosswalk_traffic_light_estimator/CMakeLists.txt +++ b/perception/crosswalk_traffic_light_estimator/CMakeLists.txt @@ -28,6 +28,8 @@ endif() ## Install ## ############# -ament_auto_package(INSTALL_TO_SHARE - launch +ament_auto_package( + INSTALL_TO_SHARE + launch + config ) diff --git a/perception/crosswalk_traffic_light_estimator/README.md b/perception/crosswalk_traffic_light_estimator/README.md index e23b454905081..73c7151997c54 100644 --- a/perception/crosswalk_traffic_light_estimator/README.md +++ b/perception/crosswalk_traffic_light_estimator/README.md @@ -22,9 +22,10 @@ ## Parameters -| Name | Type | Description | Default value | -| :---------------------- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | -| `use_last_detect_color` | `bool` | If this parameter is `true`, this module estimates pedestrian's traffic signal as RED not only when vehicle's traffic signal is detected as GREEN/AMBER but also when detection results change GREEN/AMBER to UNKNOWN. (If detection results change RED or AMBER to UNKNOWN, this module estimates pedestrian's traffic signal as UNKNOWN.) If this parameter is `false`, this module use only latest detection results for estimation. (Only when the detection result is GREEN/AMBER, this module estimates pedestrian's traffic signal as RED.) | `true` | +| Name | Type | Description | Default value | +| :---------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| `use_last_detect_color` | `bool` | If this parameter is `true`, this module estimates pedestrian's traffic signal as RED not only when vehicle's traffic signal is detected as GREEN/AMBER but also when detection results change GREEN/AMBER to UNKNOWN. (If detection results change RED or AMBER to UNKNOWN, this module estimates pedestrian's traffic signal as UNKNOWN.) If this parameter is `false`, this module use only latest detection results for estimation. (Only when the detection result is GREEN/AMBER, this module estimates pedestrian's traffic signal as RED.) | `true` | +| `last_detect_color_hold_time` | `double` | The time threshold to hold for last detect color. | `2.0` | ## Inner-workings / Algorithms diff --git a/perception/crosswalk_traffic_light_estimator/config/crosswalk_traffic_light_estimator.param.yaml b/perception/crosswalk_traffic_light_estimator/config/crosswalk_traffic_light_estimator.param.yaml new file mode 100644 index 0000000000000..33168c87de7eb --- /dev/null +++ b/perception/crosswalk_traffic_light_estimator/config/crosswalk_traffic_light_estimator.param.yaml @@ -0,0 +1,18 @@ +# Copyright 2023 UCI SORA Lab +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +/**: + ros__parameters: + use_last_detect_color: true + last_detect_color_hold_time: 2.0 diff --git a/perception/crosswalk_traffic_light_estimator/launch/crosswalk_traffic_light_estimator.launch.xml b/perception/crosswalk_traffic_light_estimator/launch/crosswalk_traffic_light_estimator.launch.xml index 91bec60c90aee..21d718c3439cd 100644 --- a/perception/crosswalk_traffic_light_estimator/launch/crosswalk_traffic_light_estimator.launch.xml +++ b/perception/crosswalk_traffic_light_estimator/launch/crosswalk_traffic_light_estimator.launch.xml @@ -1,9 +1,23 @@ - + + + + diff --git a/perception/crosswalk_traffic_light_estimator/schema/crosswalk_traffic_light_estimator.schema.json b/perception/crosswalk_traffic_light_estimator/schema/crosswalk_traffic_light_estimator.schema.json new file mode 100644 index 0000000000000..ecc273263236a --- /dev/null +++ b/perception/crosswalk_traffic_light_estimator/schema/crosswalk_traffic_light_estimator.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Crosswalk Traffic Light Estimator Node", + "type": "object", + "definitions": { + "crosswalk_traffic_light_estimator": { + "type": "object", + "properties": { + "use_last_detect_color": { + "type": "boolean", + "default": true, + "description": "If this parameter is true, this module estimates pedestrian's traffic signal as RED not only when vehicle's traffic signal is detected as GREEN/AMBER but also when detection results change GREEN/AMBER to UNKNOWN. (If detection results change RED or AMBER to UNKNOWN, this module estimates pedestrian's traffic signal as UNKNOWN.) If this parameter is false, this module use only latest detection results for estimation. (Only when the detection result is GREEN/AMBER, this module estimates pedestrian's traffic signal as RED.)." + }, + "last_detect_color_hold_time": { + "type": "number", + "default": 2.0, + "exclusiveMinimum": 0.0, + "description": "The time threshold to hold for last detect color." + } + }, + "required": ["use_last_detect_color", "last_detect_color_hold_time"] + } + }, + "properties": { + "/**": { + "type": "object", + "properties": { + "ros__parameters": { + "$ref": "#/definitions/crosswalk_traffic_light_estimator" + } + }, + "required": ["ros__parameters"] + } + }, + "required": ["/**"] +} diff --git a/perception/crosswalk_traffic_light_estimator/src/node.cpp b/perception/crosswalk_traffic_light_estimator/src/node.cpp index dd7a9ea04ae38..62c8ac1cb7551 100644 --- a/perception/crosswalk_traffic_light_estimator/src/node.cpp +++ b/perception/crosswalk_traffic_light_estimator/src/node.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 TIER IV, Inc. +// Copyright 2022-2023 UCI SORA Lab, TIER IV, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -81,8 +81,8 @@ CrosswalkTrafficLightEstimatorNode::CrosswalkTrafficLightEstimatorNode( { using std::placeholders::_1; - use_last_detect_color_ = this->declare_parameter("use_last_detect_color", true); - last_detect_color_hold_time_ = this->declare_parameter("last_detect_color_hold_time", 2.0); + use_last_detect_color_ = declare_parameter("use_last_detect_color"); + last_detect_color_hold_time_ = declare_parameter("last_detect_color_hold_time"); sub_map_ = create_subscription( "~/input/vector_map", rclcpp::QoS{1}.transient_local(), From ccdf5399f81358d3438950e6a9a138cc2838d1ac Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Tue, 12 Sep 2023 09:33:57 +0900 Subject: [PATCH 172/547] chore(build): remove tier4_autoware_utils.hpp/motion_utils.hpp from other planning packages (#4940) remove tier4_autoware_utils.hpp/motion_utils.hpp from other planning packages Signed-off-by: Mamoru Sobue --- .../path_smoother/include/path_smoother/common_structs.hpp | 3 ++- .../include/path_smoother/elastic_band_smoother.hpp | 3 +-- planning/path_smoother/src/elastic_band.cpp | 2 +- planning/path_smoother/src/utils/trajectory_utils.cpp | 3 ++- planning/planning_validator/src/debug_marker.cpp | 4 ++-- planning/planning_validator/src/planning_validator.cpp | 2 +- planning/planning_validator/src/utils.cpp | 2 +- .../path_sampler/include/path_sampler/common_structs.hpp | 5 ++++- .../path_sampler/include/path_sampler/node.hpp | 2 -- planning/sampling_based_planner/path_sampler/src/node.cpp | 3 +++ .../path_sampler/src/prepare_inputs.cpp | 1 + .../path_sampler/src/utils/trajectory_utils.cpp | 3 ++- .../src/static_centerline_optimizer_node.cpp | 4 +++- .../src/successive_trajectory_optimizer_node.cpp | 6 ++++-- planning/static_centerline_optimizer/src/utils.cpp | 4 ++-- .../include/surround_obstacle_checker/node.hpp | 6 +++--- planning/surround_obstacle_checker/src/debug_marker.cpp | 5 +++-- planning/surround_obstacle_checker/src/node.cpp | 3 +++ 18 files changed, 38 insertions(+), 23 deletions(-) diff --git a/planning/path_smoother/include/path_smoother/common_structs.hpp b/planning/path_smoother/include/path_smoother/common_structs.hpp index 94f4d62c4a6fd..a14c42ed056af 100644 --- a/planning/path_smoother/include/path_smoother/common_structs.hpp +++ b/planning/path_smoother/include/path_smoother/common_structs.hpp @@ -17,7 +17,8 @@ #include "path_smoother/type_alias.hpp" #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" #include #include diff --git a/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp b/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp index 1085ca3815728..8bd73292a56de 100644 --- a/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp +++ b/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp @@ -15,13 +15,12 @@ #ifndef PATH_SMOOTHER__ELASTIC_BAND_SMOOTHER_HPP_ #define PATH_SMOOTHER__ELASTIC_BAND_SMOOTHER_HPP_ -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "path_smoother/common_structs.hpp" #include "path_smoother/elastic_band.hpp" #include "path_smoother/replan_checker.hpp" #include "path_smoother/type_alias.hpp" #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include #include diff --git a/planning/path_smoother/src/elastic_band.cpp b/planning/path_smoother/src/elastic_band.cpp index d77ad542e2e7e..fb3aff7b18419 100644 --- a/planning/path_smoother/src/elastic_band.cpp +++ b/planning/path_smoother/src/elastic_band.cpp @@ -14,7 +14,7 @@ #include "path_smoother/elastic_band.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "path_smoother/type_alias.hpp" #include "path_smoother/utils/geometry_utils.hpp" #include "path_smoother/utils/trajectory_utils.hpp" diff --git a/planning/path_smoother/src/utils/trajectory_utils.cpp b/planning/path_smoother/src/utils/trajectory_utils.cpp index 56432d16d1a7f..d9d02a15628dd 100644 --- a/planning/path_smoother/src/utils/trajectory_utils.cpp +++ b/planning/path_smoother/src/utils/trajectory_utils.cpp @@ -14,7 +14,8 @@ #include "path_smoother/utils/trajectory_utils.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/resample/resample.hpp" +#include "motion_utils/trajectory/tmp_conversion.hpp" #include "path_smoother/utils/geometry_utils.hpp" #include "autoware_auto_planning_msgs/msg/path_point.hpp" diff --git a/planning/planning_validator/src/debug_marker.cpp b/planning/planning_validator/src/debug_marker.cpp index 89614f5c74f5c..1a0793957402a 100644 --- a/planning/planning_validator/src/debug_marker.cpp +++ b/planning/planning_validator/src/debug_marker.cpp @@ -14,8 +14,8 @@ #include "planning_validator/debug_marker.hpp" -#include -#include +#include +#include #include #include diff --git a/planning/planning_validator/src/planning_validator.cpp b/planning/planning_validator/src/planning_validator.cpp index 7bb37fdcdd467..db4510c2e5d0d 100644 --- a/planning/planning_validator/src/planning_validator.cpp +++ b/planning/planning_validator/src/planning_validator.cpp @@ -17,7 +17,7 @@ #include "planning_validator/utils.hpp" #include -#include +#include #include #include diff --git a/planning/planning_validator/src/utils.cpp b/planning/planning_validator/src/utils.cpp index e4c882cfaedb9..5a90e950bb616 100644 --- a/planning/planning_validator/src/utils.cpp +++ b/planning/planning_validator/src/utils.cpp @@ -15,7 +15,7 @@ #include "planning_validator/utils.hpp" #include -#include +#include #include #include diff --git a/planning/sampling_based_planner/path_sampler/include/path_sampler/common_structs.hpp b/planning/sampling_based_planner/path_sampler/include/path_sampler/common_structs.hpp index 30d9a77f31fad..7cf88cb75e13a 100644 --- a/planning/sampling_based_planner/path_sampler/include/path_sampler/common_structs.hpp +++ b/planning/sampling_based_planner/path_sampler/include/path_sampler/common_structs.hpp @@ -18,7 +18,10 @@ #include "path_sampler/type_alias.hpp" #include "rclcpp/rclcpp.hpp" #include "sampler_common/structures.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" + +#include #include #include diff --git a/planning/sampling_based_planner/path_sampler/include/path_sampler/node.hpp b/planning/sampling_based_planner/path_sampler/include/path_sampler/node.hpp index b1c6ec61db845..a9002c8cf8a9f 100644 --- a/planning/sampling_based_planner/path_sampler/include/path_sampler/node.hpp +++ b/planning/sampling_based_planner/path_sampler/include/path_sampler/node.hpp @@ -15,13 +15,11 @@ #ifndef PATH_SAMPLER__NODE_HPP_ #define PATH_SAMPLER__NODE_HPP_ -#include "motion_utils/motion_utils.hpp" #include "path_sampler/common_structs.hpp" #include "path_sampler/parameters.hpp" #include "path_sampler/type_alias.hpp" #include "rclcpp/rclcpp.hpp" #include "sampler_common/transform/spline_transform.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include diff --git a/planning/sampling_based_planner/path_sampler/src/node.cpp b/planning/sampling_based_planner/path_sampler/src/node.cpp index 588f29b06c40d..aa037755bbedd 100644 --- a/planning/sampling_based_planner/path_sampler/src/node.cpp +++ b/planning/sampling_based_planner/path_sampler/src/node.cpp @@ -15,6 +15,7 @@ #include "path_sampler/node.hpp" #include "interpolation/spline_interpolation_points_2d.hpp" +#include "motion_utils/marker/marker_helper.hpp" #include "path_sampler/path_generation.hpp" #include "path_sampler/prepare_inputs.hpp" #include "path_sampler/utils/geometry_utils.hpp" @@ -23,6 +24,8 @@ #include "sampler_common/constraints/hard_constraint.hpp" #include "sampler_common/constraints/soft_constraint.hpp" +#include + #include #include diff --git a/planning/sampling_based_planner/path_sampler/src/prepare_inputs.cpp b/planning/sampling_based_planner/path_sampler/src/prepare_inputs.cpp index c17f3479932f3..fd52764950ca9 100644 --- a/planning/sampling_based_planner/path_sampler/src/prepare_inputs.cpp +++ b/planning/sampling_based_planner/path_sampler/src/prepare_inputs.cpp @@ -18,6 +18,7 @@ #include "path_sampler/utils/geometry_utils.hpp" #include "sampler_common/structures.hpp" #include "sampler_common/transform/spline_transform.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include diff --git a/planning/sampling_based_planner/path_sampler/src/utils/trajectory_utils.cpp b/planning/sampling_based_planner/path_sampler/src/utils/trajectory_utils.cpp index ec41f3032e150..db55e5f8557e4 100644 --- a/planning/sampling_based_planner/path_sampler/src/utils/trajectory_utils.cpp +++ b/planning/sampling_based_planner/path_sampler/src/utils/trajectory_utils.cpp @@ -14,7 +14,8 @@ #include "path_sampler/utils/trajectory_utils.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/resample/resample.hpp" +#include "motion_utils/trajectory/tmp_conversion.hpp" #include "path_sampler/utils/geometry_utils.hpp" #include "tf2/utils.h" diff --git a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp index 4bad84d9dc1f3..8282aff8eae73 100644 --- a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp +++ b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp @@ -18,11 +18,13 @@ #include "lanelet2_extension/utility/query.hpp" #include "lanelet2_extension/utility/utilities.hpp" #include "map_loader/lanelet2_map_loader_node.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/tmp_conversion.hpp" #include "static_centerline_optimizer/msg/points_with_lane_id.hpp" #include "static_centerline_optimizer/successive_trajectory_optimizer_node.hpp" #include "static_centerline_optimizer/type_alias.hpp" #include "static_centerline_optimizer/utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" #include #include diff --git a/planning/static_centerline_optimizer/src/successive_trajectory_optimizer_node.cpp b/planning/static_centerline_optimizer/src/successive_trajectory_optimizer_node.cpp index 325c257e228f9..ab147ab1f4f74 100644 --- a/planning/static_centerline_optimizer/src/successive_trajectory_optimizer_node.cpp +++ b/planning/static_centerline_optimizer/src/successive_trajectory_optimizer_node.cpp @@ -14,9 +14,11 @@ #include "static_centerline_optimizer/successive_trajectory_optimizer_node.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/resample/resample.hpp" +#include "motion_utils/trajectory/tmp_conversion.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "obstacle_avoidance_planner/utils/trajectory_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include diff --git a/planning/static_centerline_optimizer/src/utils.cpp b/planning/static_centerline_optimizer/src/utils.cpp index 691ae7960968c..7871eb9e95163 100644 --- a/planning/static_centerline_optimizer/src/utils.cpp +++ b/planning/static_centerline_optimizer/src/utils.cpp @@ -16,8 +16,8 @@ #include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" - +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" namespace static_centerline_optimizer { namespace diff --git a/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp b/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp index a7c177f202173..f31bb7ddee331 100644 --- a/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp +++ b/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp @@ -17,10 +17,8 @@ #include "surround_obstacle_checker/debug_marker.hpp" -#include +#include #include -#include -#include #include #include @@ -32,6 +30,8 @@ #include #include +#include + #include #include #include diff --git a/planning/surround_obstacle_checker/src/debug_marker.cpp b/planning/surround_obstacle_checker/src/debug_marker.cpp index 79c2300be3941..d3d73a89e0a94 100644 --- a/planning/surround_obstacle_checker/src/debug_marker.cpp +++ b/planning/surround_obstacle_checker/src/debug_marker.cpp @@ -14,8 +14,9 @@ #include "surround_obstacle_checker/debug_marker.hpp" -#include -#include +#include +#include +#include #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/planning/surround_obstacle_checker/src/node.cpp b/planning/surround_obstacle_checker/src/node.cpp index 4ea3e81d65411..ab542bdfa73ed 100644 --- a/planning/surround_obstacle_checker/src/node.cpp +++ b/planning/surround_obstacle_checker/src/node.cpp @@ -15,6 +15,9 @@ #include "surround_obstacle_checker/node.hpp" #include +#include +#include +#include #include #include From 65b414bfe5d2ea8022bf29d0f506175ce8aac9e4 Mon Sep 17 00:00:00 2001 From: atsushi yano <55824710+atsushi421@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:59:14 +0900 Subject: [PATCH 173/547] fix(voxel_grid_downsample_filter): support no intensity case (#4922) fix: support for no intensity field Signed-off-by: atsushi421 --- .../faster_voxel_grid_downsample_filter.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sensing/pointcloud_preprocessor/src/downsample_filter/faster_voxel_grid_downsample_filter.cpp b/sensing/pointcloud_preprocessor/src/downsample_filter/faster_voxel_grid_downsample_filter.cpp index 7c302eec20431..5829277335433 100644 --- a/sensing/pointcloud_preprocessor/src/downsample_filter/faster_voxel_grid_downsample_filter.cpp +++ b/sensing/pointcloud_preprocessor/src/downsample_filter/faster_voxel_grid_downsample_filter.cpp @@ -36,7 +36,12 @@ void FasterVoxelGridDownsampleFilter::set_field_offsets(const PointCloud2ConstPt x_offset_ = input->fields[pcl::getFieldIndex(*input, "x")].offset; y_offset_ = input->fields[pcl::getFieldIndex(*input, "y")].offset; z_offset_ = input->fields[pcl::getFieldIndex(*input, "z")].offset; - intensity_offset_ = input->fields[pcl::getFieldIndex(*input, "intensity")].offset; + int intensity_index = pcl::getFieldIndex(*input, "intensity"); + if (intensity_index != -1) { + intensity_offset_ = input->fields[intensity_index].offset; + } else { + intensity_offset_ = z_offset_ + sizeof(float); + } offset_initialized_ = true; } From 42493b1532b198646329192a3c50f7c5db8d6d35 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Tue, 12 Sep 2023 10:07:29 +0900 Subject: [PATCH 174/547] chore(build): remove tier4_autoware_utils.hpp/motion_utils.hpp from behavior_velocities (#4938) remove tier4_autoware_utils.hpp/motion_utils.hpp from behavior_velocities Signed-off-by: Mamoru Sobue --- planning/behavior_velocity_blind_spot_module/src/debug.cpp | 3 ++- planning/behavior_velocity_crosswalk_module/src/debug.cpp | 5 +++-- .../src/scene_crosswalk.cpp | 5 ++++- .../src/scene_crosswalk.hpp | 3 ++- planning/behavior_velocity_crosswalk_module/src/util.cpp | 4 +++- .../behavior_velocity_detection_area_module/src/debug.cpp | 3 ++- planning/behavior_velocity_intersection_module/src/debug.cpp | 3 ++- .../src/scene_intersection.cpp | 1 + .../src/scene_intersection.hpp | 4 ++-- .../src/scene_merge_from_private_road.hpp | 1 - planning/behavior_velocity_intersection_module/src/util.cpp | 1 + .../behavior_velocity_no_drivable_lane_module/src/debug.cpp | 5 +++-- .../behavior_velocity_no_drivable_lane_module/src/scene.cpp | 4 ++-- .../behavior_velocity_no_drivable_lane_module/src/util.cpp | 3 ++- .../behavior_velocity_no_stopping_area_module/src/debug.cpp | 3 ++- .../src/scene_no_stopping_area.cpp | 3 ++- .../behavior_velocity_occlusion_spot_module/src/debug.cpp | 2 +- .../src/occlusion_spot_utils.cpp | 2 ++ .../test/src/test_occlusion_spot_utils.cpp | 1 - .../behavior_velocity_out_of_lane_module/src/footprint.cpp | 1 - .../src/scene_out_of_lane.cpp | 2 ++ .../src/scene_out_of_lane.hpp | 1 + planning/behavior_velocity_planner/src/node.cpp | 3 +++ planning/behavior_velocity_planner/src/node.hpp | 1 + .../scene_module_interface.hpp | 4 +++- .../behavior_velocity_planner_common/utilization/util.hpp | 4 ++-- .../src/utilization/debug.cpp | 2 +- .../src/utilization/util.cpp | 1 + .../test/src/test_arc_lane_util.cpp | 1 + planning/behavior_velocity_run_out_module/src/debug.cpp | 3 ++- planning/behavior_velocity_run_out_module/src/debug.hpp | 3 +++ .../src/dynamic_obstacle.cpp | 5 +++++ .../src/dynamic_obstacle.hpp | 1 - planning/behavior_velocity_run_out_module/src/path_utils.hpp | 2 -- planning/behavior_velocity_run_out_module/src/scene.cpp | 1 + planning/behavior_velocity_run_out_module/src/utils.cpp | 3 +++ planning/behavior_velocity_run_out_module/src/utils.hpp | 3 ++- planning/behavior_velocity_speed_bump_module/src/debug.cpp | 5 +++-- planning/behavior_velocity_speed_bump_module/src/scene.cpp | 4 ++-- planning/behavior_velocity_speed_bump_module/src/util.cpp | 5 +++-- planning/behavior_velocity_stop_line_module/src/debug.cpp | 3 ++- .../behavior_velocity_traffic_light_module/src/debug.cpp | 3 ++- .../src/debug.cpp | 5 +++-- 43 files changed, 82 insertions(+), 40 deletions(-) diff --git a/planning/behavior_velocity_blind_spot_module/src/debug.cpp b/planning/behavior_velocity_blind_spot_module/src/debug.cpp index dbd4764585d81..1652d9d56a8a0 100644 --- a/planning/behavior_velocity_blind_spot_module/src/debug.cpp +++ b/planning/behavior_velocity_blind_spot_module/src/debug.cpp @@ -16,7 +16,8 @@ #include #include -#include +#include +#include #include diff --git a/planning/behavior_velocity_crosswalk_module/src/debug.cpp b/planning/behavior_velocity_crosswalk_module/src/debug.cpp index eae7491872cfd..d6a2a3cb185a1 100644 --- a/planning/behavior_velocity_crosswalk_module/src/debug.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/debug.cpp @@ -15,8 +15,9 @@ #include "scene_crosswalk.hpp" #include -#include -#include +#include +#include +#include #include diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index 7a972891fd053..ab84caa782d1c 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -18,8 +18,11 @@ #include #include #include +#include #include -#include +#include +#include +#include #include #include diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp index 65f3772cd4d33..11e2d600c687b 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp @@ -22,7 +22,8 @@ #include #include #include -#include +#include +#include #include #include diff --git a/planning/behavior_velocity_crosswalk_module/src/util.cpp b/planning/behavior_velocity_crosswalk_module/src/util.cpp index 72c091cd8bc85..6fabc4c201687 100644 --- a/planning/behavior_velocity_crosswalk_module/src/util.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/util.cpp @@ -15,7 +15,9 @@ #include "behavior_velocity_crosswalk_module/util.hpp" #include -#include +#include +#include +#include #include diff --git a/planning/behavior_velocity_detection_area_module/src/debug.cpp b/planning/behavior_velocity_detection_area_module/src/debug.cpp index 60479d5adfd88..b3d3d5c781ff8 100644 --- a/planning/behavior_velocity_detection_area_module/src/debug.cpp +++ b/planning/behavior_velocity_detection_area_module/src/debug.cpp @@ -16,7 +16,8 @@ #include #include -#include +#include +#include #ifdef ROS_DISTRO_GALACTIC #include diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index ae8eee39d6556..dfaa10ec9d86f 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -17,7 +17,8 @@ #include #include -#include +#include +#include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 0c6726b45dcbc..9680117d726dd 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 245503683e128..8da150cc3bc90 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -20,9 +20,9 @@ #include #include #include -#include +#include +#include #include -#include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp index aa334f11be84d..e00b2a8b886d1 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index d78b7252d0878..940513f8fe7c3 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/debug.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/debug.cpp index a4f693dfaa883..0c6bb747a854b 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/debug.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/debug.cpp @@ -15,8 +15,9 @@ #include "scene.hpp" #include -#include -#include +#include +#include +#include #include diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp index f08154cda336c..8109b99166332 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp @@ -14,8 +14,8 @@ #include "scene.hpp" -#include "motion_utils/motion_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "util.hpp" #include diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp index 9b50a73a725db..889d0dbb064c5 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp @@ -14,7 +14,8 @@ #include "util.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include diff --git a/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp b/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp index 859ac4b4026f8..e8bf83868d122 100644 --- a/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp +++ b/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp @@ -16,7 +16,8 @@ #include #include -#include +#include +#include #ifdef ROS_DISTRO_GALACTIC #include diff --git a/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp b/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp index 77fbb18be2b5b..3e24689f2e774 100644 --- a/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp +++ b/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp @@ -18,7 +18,8 @@ #include #include #include -#include +#include +#include #include diff --git a/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp b/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp index a8e4aee2d2815..527f6f02140ea 100644 --- a/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp +++ b/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp b/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp index 21a8cce93a1be..029b516e93219 100644 --- a/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp +++ b/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp @@ -20,7 +20,9 @@ #include #include #include +#include #include +#include #include #include diff --git a/planning/behavior_velocity_occlusion_spot_module/test/src/test_occlusion_spot_utils.cpp b/planning/behavior_velocity_occlusion_spot_module/test/src/test_occlusion_spot_utils.cpp index ef2e776112766..85ae85f1895d9 100644 --- a/planning/behavior_velocity_occlusion_spot_module/test/src/test_occlusion_spot_utils.cpp +++ b/planning/behavior_velocity_occlusion_spot_module/test/src/test_occlusion_spot_utils.cpp @@ -14,7 +14,6 @@ #include "gtest/gtest.h" #include "occlusion_spot_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" #include "utils.hpp" #include diff --git a/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp b/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp index 929fbd944af9f..9ad7437901309 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp @@ -14,7 +14,6 @@ #include "footprint.hpp" -#include #include #include diff --git a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp index 430cec2d3d4ab..280a832f6d684 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include diff --git a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp index 79c66148926fc..1a64c11a3c921 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp @@ -18,6 +18,7 @@ #include "types.hpp" #include +#include #include #include diff --git a/planning/behavior_velocity_planner/src/node.cpp b/planning/behavior_velocity_planner/src/node.cpp index 0aed3015fbf40..c4f4edd26416c 100644 --- a/planning/behavior_velocity_planner/src/node.cpp +++ b/planning/behavior_velocity_planner/src/node.cpp @@ -16,7 +16,10 @@ #include #include +#include +#include #include +#include #include #include diff --git a/planning/behavior_velocity_planner/src/node.hpp b/planning/behavior_velocity_planner/src/node.hpp index 98cdaeb3e0cdb..bf7c53f974ef6 100644 --- a/planning/behavior_velocity_planner/src/node.hpp +++ b/planning/behavior_velocity_planner/src/node.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp index c33ce84ff09de..4735bb34c7b00 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp @@ -21,7 +21,9 @@ #include #include #include -#include +#include +#include +#include #include #include diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp index 14c80b59acf5e..6513a3c9a43a9 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp @@ -17,8 +17,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/planning/behavior_velocity_planner_common/src/utilization/debug.cpp b/planning/behavior_velocity_planner_common/src/utilization/debug.cpp index dc1eaeacb2f71..845b93576bec8 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/debug.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/debug.cpp @@ -13,7 +13,7 @@ // limitations under the License. #include -#include +#include namespace behavior_velocity_planner { diff --git a/planning/behavior_velocity_planner_common/src/utilization/util.cpp b/planning/behavior_velocity_planner_common/src/utilization/util.cpp index d98e9002b2057..3f8180956cf10 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/util.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/util.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include diff --git a/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp b/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp index c4fdb83e00609..60a0ff878b478 100644 --- a/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp +++ b/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp @@ -16,6 +16,7 @@ #include #include +#include #include diff --git a/planning/behavior_velocity_run_out_module/src/debug.cpp b/planning/behavior_velocity_run_out_module/src/debug.cpp index 7fa17b0cea52a..90f69e0faae5e 100644 --- a/planning/behavior_velocity_run_out_module/src/debug.cpp +++ b/planning/behavior_velocity_run_out_module/src/debug.cpp @@ -16,7 +16,8 @@ #include "scene.hpp" -#include +#include +#include using tier4_autoware_utils::appendMarkerArray; using tier4_autoware_utils::calcOffsetPose; diff --git a/planning/behavior_velocity_run_out_module/src/debug.hpp b/planning/behavior_velocity_run_out_module/src/debug.hpp index 273abb10c780a..0dcd3099b616a 100644 --- a/planning/behavior_velocity_run_out_module/src/debug.hpp +++ b/planning/behavior_velocity_run_out_module/src/debug.hpp @@ -18,6 +18,9 @@ #include +#include +#include + #include #include #include diff --git a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp index e2f9880435d5e..728e0fbad92a5 100644 --- a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp +++ b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp @@ -14,6 +14,11 @@ #include "dynamic_obstacle.hpp" +#include +#include +#include +#include + #include #include diff --git a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp index f9b2b12298d2d..7eece8fac29af 100644 --- a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp +++ b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp @@ -21,7 +21,6 @@ #include #include #include -#include #include diff --git a/planning/behavior_velocity_run_out_module/src/path_utils.hpp b/planning/behavior_velocity_run_out_module/src/path_utils.hpp index b01ee70f8bdfa..11ed2b7282ab3 100644 --- a/planning/behavior_velocity_run_out_module/src/path_utils.hpp +++ b/planning/behavior_velocity_run_out_module/src/path_utils.hpp @@ -15,8 +15,6 @@ #ifndef PATH_UTILS_HPP_ #define PATH_UTILS_HPP_ -#include - #include #include #include diff --git a/planning/behavior_velocity_run_out_module/src/scene.cpp b/planning/behavior_velocity_run_out_module/src/scene.cpp index 3b2ed6a78a96c..b062bf9e3175e 100644 --- a/planning/behavior_velocity_run_out_module/src/scene.cpp +++ b/planning/behavior_velocity_run_out_module/src/scene.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include diff --git a/planning/behavior_velocity_run_out_module/src/utils.cpp b/planning/behavior_velocity_run_out_module/src/utils.cpp index 3def2ade46731..71ab07922b27d 100644 --- a/planning/behavior_velocity_run_out_module/src/utils.cpp +++ b/planning/behavior_velocity_run_out_module/src/utils.cpp @@ -14,6 +14,9 @@ #include "utils.hpp" +#include +#include + #include #include diff --git a/planning/behavior_velocity_run_out_module/src/utils.hpp b/planning/behavior_velocity_run_out_module/src/utils.hpp index 1f725291a38b7..74bc2489d7c32 100644 --- a/planning/behavior_velocity_run_out_module/src/utils.hpp +++ b/planning/behavior_velocity_run_out_module/src/utils.hpp @@ -17,9 +17,10 @@ #include #include -#include #include +#include + #include #include diff --git a/planning/behavior_velocity_speed_bump_module/src/debug.cpp b/planning/behavior_velocity_speed_bump_module/src/debug.cpp index 62e8cd31ec062..3f9a60d1d27e4 100644 --- a/planning/behavior_velocity_speed_bump_module/src/debug.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/debug.cpp @@ -15,8 +15,9 @@ #include "scene.hpp" #include -#include -#include +#include +#include +#include #include diff --git a/planning/behavior_velocity_speed_bump_module/src/scene.cpp b/planning/behavior_velocity_speed_bump_module/src/scene.cpp index fda92f037cf7a..dc944cc0c292b 100644 --- a/planning/behavior_velocity_speed_bump_module/src/scene.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/scene.cpp @@ -14,8 +14,8 @@ #include "scene.hpp" -#include "motion_utils/motion_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "util.hpp" #include diff --git a/planning/behavior_velocity_speed_bump_module/src/util.cpp b/planning/behavior_velocity_speed_bump_module/src/util.cpp index 89845a4b5e271..c815dab29af75 100644 --- a/planning/behavior_velocity_speed_bump_module/src/util.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/util.cpp @@ -14,10 +14,11 @@ #include "util.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include -#include +#include #include #include diff --git a/planning/behavior_velocity_stop_line_module/src/debug.cpp b/planning/behavior_velocity_stop_line_module/src/debug.cpp index 09949751f4e93..8d7de00635d4f 100644 --- a/planning/behavior_velocity_stop_line_module/src/debug.cpp +++ b/planning/behavior_velocity_stop_line_module/src/debug.cpp @@ -15,7 +15,8 @@ #include "scene.hpp" #include -#include +#include +#include #ifdef ROS_DISTRO_GALACTIC #include diff --git a/planning/behavior_velocity_traffic_light_module/src/debug.cpp b/planning/behavior_velocity_traffic_light_module/src/debug.cpp index 9e475441a3f3c..6afd5381ce315 100644 --- a/planning/behavior_velocity_traffic_light_module/src/debug.cpp +++ b/planning/behavior_velocity_traffic_light_module/src/debug.cpp @@ -15,7 +15,8 @@ #include "scene.hpp" #include -#include +#include +#include #ifdef ROS_DISTRO_GALACTIC #include diff --git a/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp b/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp index 1324525ebc6e1..e5cb4dcbc42e7 100644 --- a/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp +++ b/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp @@ -14,8 +14,9 @@ #include "scene.hpp" -#include -#include +#include +#include +#include using motion_utils::createStopVirtualWallMarker; using tier4_autoware_utils::appendMarkerArray; From ca3731b36aaffc55b42d9b482be6d9e535fcf092 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 12 Sep 2023 11:51:52 +0900 Subject: [PATCH 175/547] fix(start_planner): crop backward path to start from narrow space (#4929) * fix(start_planner): crop backward path to start from narrow space Signed-off-by: kosuke55 * check out of lanes Signed-off-by: kosuke55 * refactor Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../utils/start_planner/shift_pull_out.cpp | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index f86f9d3d90a07..6d8884647e5dc 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -97,17 +97,48 @@ boost::optional ShiftPullOut::plan(const Pose & start_pose, const P shift_path.points.begin() + collision_check_end_idx + 1); } - // check lane departure + // extract shoulder lanes from pull out lanes + lanelet::ConstLanelets shoulder_lanes; + std::copy_if( + pull_out_lanes.begin(), pull_out_lanes.end(), std::back_inserter(shoulder_lanes), + [&route_handler](const auto & pull_out_lane) { + return route_handler->isShoulderLanelet(pull_out_lane); + }); const auto drivable_lanes = - utils::generateDrivableLanesWithShoulderLanes(road_lanes, pull_out_lanes); + utils::generateDrivableLanesWithShoulderLanes(road_lanes, shoulder_lanes); const auto & dp = planner_data_->drivable_area_expansion_parameters; - const auto expanded_lanes = utils::expandLanelets( + const auto expanded_lanes = utils::transformToLanelets(utils::expandLanelets( drivable_lanes, dp.drivable_area_left_bound_offset, dp.drivable_area_right_bound_offset, - dp.drivable_area_types_to_skip); + dp.drivable_area_types_to_skip)); + + // crop backward path + // removes points which are out of lanes up to the start pose. + // this ensures that the backward_path stays within the drivable area when starting from a + // narrow place. + const size_t start_segment_idx = motion_utils::findFirstNearestIndexWithSoftConstraints( + shift_path.points, start_pose, common_parameters.ego_nearest_dist_threshold, + common_parameters.ego_nearest_yaw_threshold); + PathWithLaneId cropped_path{}; + for (size_t i = 0; i < shift_path.points.size(); ++i) { + const Pose pose = shift_path.points.at(i).point.pose; + const auto transformed_vehicle_footprint = + transformVector(vehicle_footprint_, tier4_autoware_utils::pose2transform(pose)); + const bool is_out_of_lane = + LaneDepartureChecker::isOutOfLane(expanded_lanes, transformed_vehicle_footprint); + if (i <= start_segment_idx) { + if (!is_out_of_lane) { + cropped_path.points.push_back(shift_path.points.at(i)); + } + } else { + cropped_path.points.push_back(shift_path.points.at(i)); + } + } + shift_path.points = cropped_path.points; + + // check lane departure if ( parameters_.check_shift_path_lane_departure && - lane_departure_checker_->checkPathWillLeaveLane( - utils::transformToLanelets(expanded_lanes), path_start_to_end)) { + lane_departure_checker_->checkPathWillLeaveLane(expanded_lanes, path_start_to_end)) { continue; } From a0a7ab5fb1c4a25c00b875d61819b6eac45677fb Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 12 Sep 2023 11:52:13 +0900 Subject: [PATCH 176/547] refactor(start_planner): refactor drivable lanes (#4951) Signed-off-by: kosuke55 --- .../src/scene_module/start_planner/start_planner_module.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index afea96a8aa883..d12b19dcbf7ff 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -568,7 +568,7 @@ std::vector StartPlannerModule::generateDrivableLanes( std::back_inserter(shoulder_lanes), [&rh](const auto & pull_out_lane) { return rh->isShoulderLanelet(pull_out_lane); }); - return utils::generateDrivableLanesWithShoulderLanes(getPathRoadLanes(path), shoulder_lanes); + return utils::generateDrivableLanesWithShoulderLanes(path_road_lanes, shoulder_lanes); } // if path_road_lanes is empty, use only pull_out_lanes as drivable lanes From 82006db7ad10e1d78003d6ddd1cb4265b3cc1cfd Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 12 Sep 2023 11:52:35 +0900 Subject: [PATCH 177/547] fix(start_planner): fix path update (#4931) Signed-off-by: kosuke55 --- .../src/scene_module/start_planner/start_planner_module.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index d12b19dcbf7ff..9cc75a384192b 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -599,7 +599,7 @@ void StartPlannerModule::updatePullOutStatus() // skip updating if enough time has not passed for preventing chattering between back and // start_planner - if (!has_received_new_route && !last_pull_out_start_update_time_ && !status_.back_finished) { + if (!has_received_new_route) { if (!last_pull_out_start_update_time_) { last_pull_out_start_update_time_ = std::make_unique(clock_->now()); } From e38612dddec399ca11ec7629e34b6c5ee422787c Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Tue, 12 Sep 2023 14:23:16 +0900 Subject: [PATCH 178/547] fix(static_centerline_optimizer): remove double include (#4956) fixed double include Signed-off-by: Shintaro SAKODA --- .../src/static_centerline_optimizer_node.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp index 8282aff8eae73..8c3b84854a6d0 100644 --- a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp +++ b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp @@ -24,7 +24,6 @@ #include "static_centerline_optimizer/type_alias.hpp" #include "static_centerline_optimizer/utils.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" -#include "tier4_autoware_utils/ros/marker_helper.hpp" #include #include From 8f34f2d1bf88b43bf5c6c5906e4840830e60643b Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:53:12 +0900 Subject: [PATCH 179/547] fix(avoidance): clear rtc status in process on entry (#4926) Signed-off-by: satoshi-ota --- .../src/scene_module/avoidance/avoidance_module.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index e5374efd0a5b5..51871b9f6c4f0 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -2538,6 +2538,7 @@ void AvoidanceModule::updateData() void AvoidanceModule::processOnEntry() { initVariables(); + removeRTCStatus(); } void AvoidanceModule::processOnExit() From 57b1ef1e17034d0e62b3decfc65f3ec5632cfdbf Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 12 Sep 2023 16:03:35 +0900 Subject: [PATCH 180/547] feat(behavior_path_planner): add processOnEntry in start_planner (#4953) * add process on entry in start_planner Signed-off-by: kyoichi-sugahara * delete description Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../goal_planner/goal_planner_module.hpp | 13 +++++---- .../start_planner/start_planner_module.hpp | 4 ++- .../goal_planner/goal_planner_module.cpp | 29 +++++++++++++------ .../start_planner/start_planner_module.cpp | 26 ++++++++++++----- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index ab6f77b1f53d6..6ba90f54e48fe 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -81,12 +81,12 @@ struct PullOverStatus size_t current_path_idx{0}; bool require_increment_{true}; // if false, keep current path idx. std::shared_ptr prev_stop_path{nullptr}; - lanelet::ConstLanelets current_lanes{}; - lanelet::ConstLanelets pull_over_lanes{}; - std::vector lanes{}; // current + pull_over - bool has_decided_path{false}; - bool is_safe_static_objects{false}; // current path is safe against static objects - bool is_safe_dynamic_objects{false}; // current path is safe against dynamic objects + lanelet::ConstLanelets current_lanes{}; // TODO(someone): explain + lanelet::ConstLanelets pull_over_lanes{}; // TODO(someone): explain + std::vector lanes{}; // current + pull_over + bool has_decided_path{false}; // if true, the path is decided and safe against static objects + bool is_safe_static_objects{false}; // current path is safe against *static* objects + bool is_safe_dynamic_objects{false}; // current path is safe against *dynamic* objects bool prev_is_safe{false}; bool has_decided_velocity{false}; bool has_requested_approval{false}; @@ -299,6 +299,7 @@ class GoalPlannerModule : public SceneModuleInterface std::pair calcDistanceToPathChange() const; // safety check + void initializeSafetyCheckParameters(); SafetyCheckParams createSafetyCheckParams() const; void updateSafetyCheckTargetObjectsData( const PredictedObjects & filtered_objects, const TargetObjectsOnLane & target_objects_on_lane, diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index ba942da69732d..e9a94126d9f0e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -93,7 +93,7 @@ class StartPlannerModule : public SceneModuleInterface BehaviorModuleOutput plan() override; BehaviorModuleOutput planWaitingApproval() override; CandidateOutput planCandidate() const override; - + void processOnEntry() override; void processOnExit() override; void setParameters(const std::shared_ptr & parameters) @@ -125,6 +125,8 @@ class StartPlannerModule : public SceneModuleInterface bool canTransitIdleToRunningState() override { return false; } + void initializeSafetyCheckParameters(); + std::shared_ptr parameters_; mutable std::shared_ptr ego_predicted_path_params_; mutable std::shared_ptr objects_filtering_params_; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index d55b3c173cc83..54a052c91f180 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -252,12 +252,27 @@ void GoalPlannerModule::initializeOccupancyGridMap() occupancy_grid_map_->setParam(occupancy_grid_map_param); } +void GoalPlannerModule::initializeSafetyCheckParameters() +{ + utils::start_goal_planner_common::updateEgoPredictedPathParams( + ego_predicted_path_params_, parameters_); + utils::start_goal_planner_common::updateSafetyCheckParams(safety_check_params_, parameters_); + utils::start_goal_planner_common::updateObjectsFilteringParams( + objects_filtering_params_, parameters_); +} + void GoalPlannerModule::processOnEntry() { // Initialize occupancy grid map if (parameters_->use_occupancy_grid) { initializeOccupancyGridMap(); } + // Initialize safety checker + if (parameters_->safety_check_params.enable_safety_check) { + initializeSafetyCheckParameters(); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + goal_planner_data_.collision_check); + } } void GoalPlannerModule::processOnExit() @@ -354,11 +369,6 @@ bool GoalPlannerModule::isExecutionReady() const } if (status_.is_safe_static_objects && parameters_->safety_check_params.enable_safety_check) { - utils::start_goal_planner_common::updateEgoPredictedPathParams( - ego_predicted_path_params_, parameters_); - utils::start_goal_planner_common::updateSafetyCheckParams(safety_check_params_, parameters_); - utils::start_goal_planner_common::updateObjectsFilteringParams( - objects_filtering_params_, parameters_); if (!isSafePath()) { RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against dynamic objects"); return false; @@ -690,7 +700,6 @@ void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) if (status_.prev_is_safe || status_.prev_stop_path == nullptr) { // safe -> not_safe or no prev_stop_path: generate new stop_path output.path = std::make_shared(generateStopPath()); - output.reference_path = getPreviousModuleOutput().reference_path; status_.prev_stop_path = output.path; // set stop path as pull over path mutex_.lock(); @@ -705,10 +714,10 @@ void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) } else { // not_safe -> not_safe: use previous stop path output.path = status_.prev_stop_path; - output.reference_path = getPreviousModuleOutput().reference_path; RCLCPP_WARN_THROTTLE( getLogger(), *clock_, 5000, "Not found safe pull_over path, use previous stop path"); } + output.reference_path = getPreviousModuleOutput().reference_path; } void GoalPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) const @@ -1097,6 +1106,10 @@ bool GoalPlannerModule::incrementPathIndex() PathWithLaneId GoalPlannerModule::getCurrentPath() const { + if (status_.pull_over_path == nullptr) { + return PathWithLaneId{}; + } + if (status_.pull_over_path->partial_paths.size() <= status_.current_path_idx) { return PathWithLaneId{}; } @@ -1521,8 +1534,6 @@ bool GoalPlannerModule::isSafePath() const utils::start_goal_planner_common::updateSafetyCheckTargetObjectsData( goal_planner_data_, filtered_objects, target_objects_on_lane, ego_predicted_path); - utils::start_goal_planner_common::initializeCollisionCheckDebugMap( - goal_planner_data_.collision_check); bool is_safe_dynamic_objects = true; // Check for collisions with each predicted path of the object diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 9cc75a384192b..29e16fd570720 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -98,6 +98,16 @@ BehaviorModuleOutput StartPlannerModule::run() return plan(); } +void StartPlannerModule::processOnEntry() +{ + // Initialize safety checker + if (parameters_->safety_check_params.enable_safety_check) { + initializeSafetyCheckParameters(); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + start_planner_data_.collision_check); + } +} + void StartPlannerModule::processOnExit() { resetPathCandidate(); @@ -149,11 +159,6 @@ bool StartPlannerModule::isExecutionReady() const } if (status_.is_safe_static_objects && parameters_->safety_check_params.enable_safety_check) { - utils::start_goal_planner_common::updateEgoPredictedPathParams( - ego_predicted_path_params_, parameters_); - utils::start_goal_planner_common::updateSafetyCheckParams(safety_check_params_, parameters_); - utils::start_goal_planner_common::updateObjectsFilteringParams( - objects_filtering_params_, parameters_); if (!isSafePath()) { RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against dynamic objects"); return false; @@ -272,6 +277,15 @@ CandidateOutput StartPlannerModule::planCandidate() const return CandidateOutput{}; } +void StartPlannerModule::initializeSafetyCheckParameters() +{ + utils::start_goal_planner_common::updateEgoPredictedPathParams( + ego_predicted_path_params_, parameters_); + utils::start_goal_planner_common::updateSafetyCheckParams(safety_check_params_, parameters_); + utils::start_goal_planner_common::updateObjectsFilteringParams( + objects_filtering_params_, parameters_); +} + PathWithLaneId StartPlannerModule::getFullPath() const { // combine partial pull out path @@ -982,8 +996,6 @@ bool StartPlannerModule::isSafePath() const utils::start_goal_planner_common::updateSafetyCheckTargetObjectsData( start_planner_data_, filtered_objects, target_objects_on_lane, ego_predicted_path); - utils::start_goal_planner_common::initializeCollisionCheckDebugMap( - start_planner_data_.collision_check); bool is_safe_dynamic_objects = true; // Check for collisions with each predicted path of the object From 64ed461c2f57404372937679e478499209a3be73 Mon Sep 17 00:00:00 2001 From: Kotaro Uetake <60615504+ktro2828@users.noreply.github.com> Date: Tue, 12 Sep 2023 17:18:39 +0900 Subject: [PATCH 181/547] fix: update parameter value (#4955) Signed-off-by: ktro2828 --- .../roi_cluster_fusion/node.hpp | 8 ++++---- .../launch/roi_cluster_fusion.launch.xml | 3 ++- .../src/roi_cluster_fusion/node.cpp | 20 +++++++++---------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp b/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp index 38f9e71eea02d..70a7866e79b87 100644 --- a/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp +++ b/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp @@ -43,13 +43,13 @@ class RoiClusterFusionNode bool use_iou_{false}; bool use_cluster_semantic_type_{false}; bool only_allow_inside_cluster_{false}; - float roi_scale_factor_{1.1f}; - float iou_threshold_{0.0f}; - float unknown_iou_threshold_{0.0f}; + double roi_scale_factor_{1.1}; + double iou_threshold_{0.0}; + double unknown_iou_threshold_{0.0}; const float min_roi_existence_prob_ = 0.1; // keep small value to lessen affect on merger object stage bool remove_unknown_; - float trust_distance_; + double trust_distance_; bool filter_by_distance(const DetectedObjectWithFeature & obj); bool out_of_scope(const DetectedObjectWithFeature & obj); diff --git a/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml b/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml index 37fbd309d3ee5..2d99c25bb68f7 100644 --- a/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml +++ b/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml @@ -48,9 +48,10 @@ + - + diff --git a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp index 9180b18adeed8..77f6a5c506d92 100644 --- a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp +++ b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp @@ -36,16 +36,16 @@ namespace image_projection_based_fusion RoiClusterFusionNode::RoiClusterFusionNode(const rclcpp::NodeOptions & options) : FusionNode("roi_cluster_fusion", options) { - use_iou_x_ = declare_parameter("use_iou_x", true); - use_iou_y_ = declare_parameter("use_iou_y", false); - use_iou_ = declare_parameter("use_iou", false); - use_cluster_semantic_type_ = declare_parameter("use_cluster_semantic_type", false); - only_allow_inside_cluster_ = declare_parameter("only_allow_inside_cluster_", true); - roi_scale_factor_ = declare_parameter("roi_scale_factor", 1.1); - iou_threshold_ = declare_parameter("iou_threshold", 0.1); - unknown_iou_threshold_ = declare_parameter("unknown_iou_threshold", 0.1); - remove_unknown_ = declare_parameter("remove_unknown", false); - trust_distance_ = declare_parameter("trust_distance", 100.0); + use_iou_x_ = declare_parameter("use_iou_x"); + use_iou_y_ = declare_parameter("use_iou_y"); + use_iou_ = declare_parameter("use_iou"); + use_cluster_semantic_type_ = declare_parameter("use_cluster_semantic_type"); + only_allow_inside_cluster_ = declare_parameter("only_allow_inside_cluster"); + roi_scale_factor_ = declare_parameter("roi_scale_factor"); + iou_threshold_ = declare_parameter("iou_threshold"); + unknown_iou_threshold_ = declare_parameter("unknown_iou_threshold"); + remove_unknown_ = declare_parameter("remove_unknown"); + trust_distance_ = declare_parameter("trust_distance"); } void RoiClusterFusionNode::preprocess(DetectedObjectsWithFeature & output_cluster_msg) From 8798d5c3ec666588361d061a7add75946f5c55cc Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 12 Sep 2023 17:38:16 +0900 Subject: [PATCH 182/547] fix(start/goal_planner): fix inverse order path points (#4950) fix(start_planner): fix inverse order path points Signed-off-by: kosuke55 --- .../utils/start_goal_planner_common/utils.hpp | 15 +++++++++++++++ .../src/utils/goal_planner/shift_pull_over.cpp | 4 ++++ .../src/utils/start_goal_planner_common/utils.cpp | 15 +++++++++++++++ .../src/utils/start_planner/shift_pull_out.cpp | 4 ++++ 4 files changed, 38 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp index 63b8676e2188b..217b3748fa250 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp @@ -98,6 +98,21 @@ std::pair getPairsTerminalVelocityAndAccel( const std::vector> & pairs_terminal_velocity_and_accel, const size_t current_path_idx); +/** + * @brief removeInverseOrderPathPoints function + * + * This function is designed to handle a situation that can arise when shifting paths on a curve, + * where the positions of the path points may become inverted (i.e., a point further along the path + * comes to be located before an earlier point). It corrects for this by using the function + * tier4_autoware_utils::isDrivingForward(p1, p2) to check if each pair of adjacent points is in + * the correct order (with the second point being 'forward' of the first). Any points which fail + * this test are omitted from the returned PathWithLaneId. + * + * @param path A path with points possibly in incorrect order. + * @return Returns a new PathWithLaneId that has all points in the correct order. + */ +PathWithLaneId removeInverseOrderPathPoints(const PathWithLaneId & path); + } // namespace behavior_path_planner::utils::start_goal_planner_common #endif // BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__UTILS_HPP_ diff --git a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp index b7ece14035265..d6a3746187b11 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "motion_utils/trajectory/path_with_lane_id.hpp" #include @@ -171,6 +172,9 @@ boost::optional ShiftPullOver::generatePullOverPath( shifted_path.path.points.push_back(p); } + shifted_path.path = + utils::start_goal_planner_common::removeInverseOrderPathPoints(shifted_path.path); + // set the same z as the goal for (auto & p : shifted_path.path.points) { p.point.pose.position.z = goal_pose.position.z; diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index d07a2f54a5e8a..bd695055b2079 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -169,4 +169,19 @@ std::pair getPairsTerminalVelocityAndAccel( return pairs_terminal_velocity_and_accel.at(current_path_idx); } +PathWithLaneId removeInverseOrderPathPoints(const PathWithLaneId & path) +{ + PathWithLaneId fixed_path; + fixed_path.header = path.header; + fixed_path.points.push_back(path.points.at(0)); + for (size_t i = 1; i < path.points.size(); i++) { + const auto p1 = path.points.at(i - 1).point.pose; + const auto p2 = path.points.at(i).point.pose; + if (tier4_autoware_utils::isDrivingForward(p1, p2)) { + fixed_path.points.push_back(path.points.at(i)); + } + } + return fixed_path; +} + } // namespace behavior_path_planner::utils::start_goal_planner_common diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index 6d8884647e5dc..6225ce598afe3 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "behavior_path_planner/utils/utils.hpp" #include "motion_utils/trajectory/path_with_lane_id.hpp" @@ -298,6 +299,9 @@ std::vector ShiftPullOut::calcPullOutPaths( continue; } + shifted_path.path = + utils::start_goal_planner_common::removeInverseOrderPathPoints(shifted_path.path); + // set velocity const size_t pull_out_end_idx = findNearestIndex(shifted_path.path.points, shift_end_pose_ptr->position); From 3e28ab51de1dfd3a2a8fe91bb7f9a5d771326b19 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 12 Sep 2023 18:02:35 +0900 Subject: [PATCH 183/547] chore(control_validator): use throttle for the error log (#4960) Signed-off-by: Takamasa Horibe --- control/control_validator/src/control_validator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/control/control_validator/src/control_validator.cpp b/control/control_validator/src/control_validator.cpp index 9fe5121ee3dbd..6c3996bfd274b 100644 --- a/control/control_validator/src/control_validator.cpp +++ b/control/control_validator/src/control_validator.cpp @@ -163,7 +163,9 @@ void ControlValidator::publishDebugInfo() void ControlValidator::validate(const Trajectory & predicted_trajectory) { if (predicted_trajectory.points.size() < 2) { - RCLCPP_ERROR(get_logger(), "predicted_trajectory size is less than 2. Cannot validate."); + RCLCPP_ERROR_THROTTLE( + get_logger(), *get_clock(), 1000, + "predicted_trajectory size is less than 2. Cannot validate."); return; } From bcf52e029ba23386d0cc3f409428de04a91521c0 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Tue, 12 Sep 2023 18:40:54 +0900 Subject: [PATCH 184/547] fix(intersection): stuck stop line generation when ego is over fist conflicting area (#4903) Signed-off-by: Mamoru Sobue --- .../src/scene_intersection.cpp | 350 +++++++++--------- .../src/scene_intersection.hpp | 69 ++-- .../src/util.cpp | 108 ++++-- .../src/util_type.hpp | 26 +- 4 files changed, 301 insertions(+), 252 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 9680117d726dd..5f9d838b7c220 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -125,7 +125,7 @@ template void prepareRTCByDecisionResult( const T & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, double * default_distance, bool * occlusion_safety, - double * occlusion_distance, bool * occlusion_first_stop_required) + double * occlusion_distance) { static_assert("Unsupported type passed to prepareRTCByDecisionResult"); return; @@ -136,8 +136,7 @@ void prepareRTCByDecisionResult( [[maybe_unused]] const IntersectionModule::Indecisive & result, [[maybe_unused]] const autoware_auto_planning_msgs::msg::PathWithLaneId & path, [[maybe_unused]] bool * default_safety, [[maybe_unused]] double * default_distance, - [[maybe_unused]] bool * occlusion_safety, [[maybe_unused]] double * occlusion_distance, - [[maybe_unused]] bool * occlusion_first_stop_required) + [[maybe_unused]] bool * occlusion_safety, [[maybe_unused]] double * occlusion_distance) { return; } @@ -146,17 +145,16 @@ template <> void prepareRTCByDecisionResult( const IntersectionModule::StuckStop & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance, - [[maybe_unused]] bool * occlusion_first_stop_required) + double * default_distance, bool * occlusion_safety, double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "StuckStop"); - const auto closest_idx = result.stop_lines.closest_idx; - const auto stop_line_idx = result.stop_line_idx; + const auto closest_idx = result.closest_idx; + const auto stop_line_idx = result.stuck_stop_line_idx; *default_safety = false; *default_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, stop_line_idx); *occlusion_safety = true; - if (!result.is_detection_area_empty) { - const auto occlusion_stop_line_idx = result.stop_lines.occlusion_peeking_stop_line; + if (result.occlusion_stop_line_idx) { + const auto occlusion_stop_line_idx = result.occlusion_stop_line_idx.value(); *occlusion_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, occlusion_stop_line_idx); } @@ -167,15 +165,15 @@ template <> void prepareRTCByDecisionResult( const IntersectionModule::NonOccludedCollisionStop & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance, - [[maybe_unused]] bool * occlusion_first_stop_required) + double * default_distance, bool * occlusion_safety, double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "NonOccludedCollisionStop"); - const auto closest_idx = result.stop_lines.closest_idx; - const auto stop_line_idx = result.stop_line_idx; + const auto closest_idx = result.closest_idx; + const auto collision_stop_line_idx = result.collision_stop_line_idx; *default_safety = false; - *default_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, stop_line_idx); - const auto occlusion_stop_line = result.stop_lines.occlusion_peeking_stop_line; + *default_distance = + motion_utils::calcSignedArcLength(path.points, closest_idx, collision_stop_line_idx); + const auto occlusion_stop_line = result.occlusion_stop_line_idx; *occlusion_safety = true; *occlusion_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, occlusion_stop_line); @@ -186,11 +184,10 @@ template <> void prepareRTCByDecisionResult( const IntersectionModule::FirstWaitBeforeOcclusion & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance, - bool * occlusion_first_stop_required) + double * default_distance, bool * occlusion_safety, double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "FirstWaitBeforeOcclusion"); - const auto closest_idx = result.stop_lines.closest_idx; + const auto closest_idx = result.closest_idx; const auto first_stop_line_idx = result.first_stop_line_idx; const auto occlusion_stop_line_idx = result.occlusion_stop_line_idx; *default_safety = false; @@ -199,7 +196,6 @@ void prepareRTCByDecisionResult( *occlusion_safety = result.is_actually_occlusion_cleared; *occlusion_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, occlusion_stop_line_idx); - *occlusion_first_stop_required = true; return; } @@ -207,18 +203,18 @@ template <> void prepareRTCByDecisionResult( const IntersectionModule::PeekingTowardOcclusion & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance, - [[maybe_unused]] bool * occlusion_first_stop_required) + double * default_distance, bool * occlusion_safety, double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "PeekingTowardOcclusion"); - const auto closest_idx = result.stop_lines.closest_idx; - const auto stop_line_idx = result.stop_line_idx; - *occlusion_safety = result.is_actually_occlusion_cleared; - *occlusion_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, stop_line_idx); - const auto default_stop_line_idx = result.stop_lines.default_stop_line; + const auto closest_idx = result.closest_idx; + const auto collision_stop_line_idx = result.collision_stop_line_idx; + const auto occlusion_stop_line_idx = result.occlusion_stop_line_idx; *default_safety = true; *default_distance = - motion_utils::calcSignedArcLength(path.points, closest_idx, default_stop_line_idx); + motion_utils::calcSignedArcLength(path.points, closest_idx, collision_stop_line_idx); + *occlusion_safety = result.is_actually_occlusion_cleared; + *occlusion_distance = + motion_utils::calcSignedArcLength(path.points, closest_idx, occlusion_stop_line_idx); return; } @@ -226,15 +222,15 @@ template <> void prepareRTCByDecisionResult( const IntersectionModule::OccludedCollisionStop & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance, - [[maybe_unused]] bool * occlusion_first_stop_required) + double * default_distance, bool * occlusion_safety, double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "OccludedCollisionStop"); - const auto closest_idx = result.stop_lines.closest_idx; - const auto stop_line_idx = result.stop_line_idx; + const auto closest_idx = result.closest_idx; + const auto collision_stop_line_idx = result.collision_stop_line_idx; const auto occlusion_stop_line_idx = result.occlusion_stop_line_idx; *default_safety = false; - *default_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, stop_line_idx); + *default_distance = + motion_utils::calcSignedArcLength(path.points, closest_idx, collision_stop_line_idx); *occlusion_safety = result.is_actually_occlusion_cleared; *occlusion_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, occlusion_stop_line_idx); @@ -245,16 +241,15 @@ template <> void prepareRTCByDecisionResult( const IntersectionModule::Safe & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance, - [[maybe_unused]] bool * occlusion_first_stop_required) + double * default_distance, bool * occlusion_safety, double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "Safe"); - const auto closest_idx = result.stop_lines.closest_idx; - const auto default_stop_line_idx = result.stop_lines.default_stop_line; - const auto occlusion_stop_line_idx = result.stop_lines.occlusion_peeking_stop_line; + const auto closest_idx = result.closest_idx; + const auto collision_stop_line_idx = result.collision_stop_line_idx; + const auto occlusion_stop_line_idx = result.occlusion_stop_line_idx; *default_safety = true; *default_distance = - motion_utils::calcSignedArcLength(path.points, closest_idx, default_stop_line_idx); + motion_utils::calcSignedArcLength(path.points, closest_idx, collision_stop_line_idx); *occlusion_safety = true; *occlusion_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, occlusion_stop_line_idx); @@ -265,16 +260,15 @@ template <> void prepareRTCByDecisionResult( const IntersectionModule::TrafficLightArrowSolidOn & result, const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, - double * default_distance, bool * occlusion_safety, double * occlusion_distance, - [[maybe_unused]] bool * occlusion_first_stop_required) + double * default_distance, bool * occlusion_safety, double * occlusion_distance) { RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "TrafficLightArrowSolidOn"); - const auto closest_idx = result.stop_lines.closest_idx; - const auto default_stop_line_idx = result.stop_lines.default_stop_line; - const auto occlusion_stop_line_idx = result.stop_lines.occlusion_peeking_stop_line; + const auto closest_idx = result.closest_idx; + const auto collision_stop_line_idx = result.collision_stop_line_idx; + const auto occlusion_stop_line_idx = result.occlusion_stop_line_idx; *default_safety = !result.collision_detected; *default_distance = - motion_utils::calcSignedArcLength(path.points, closest_idx, default_stop_line_idx); + motion_utils::calcSignedArcLength(path.points, closest_idx, collision_stop_line_idx); *occlusion_safety = true; *occlusion_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, occlusion_stop_line_idx); @@ -291,11 +285,13 @@ void IntersectionModule::prepareRTCStatus( VisitorSwitch{[&](const auto & decision) { prepareRTCByDecisionResult( decision, path, &default_safety, &default_distance, &occlusion_safety_, - &occlusion_stop_distance_, &occlusion_first_stop_required_); + &occlusion_stop_distance_); }}, decision_result); setSafe(default_safety); setDistance(default_distance); + occlusion_first_stop_required_ = + std::holds_alternative(decision_result); } template @@ -336,16 +332,10 @@ void reactRTCApprovalByDecisionResult( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), "StuckStop, approval = (default: %d, occlusion: %d)", rtc_default_approved, rtc_occlusion_approved); - const auto closest_idx = decision_result.stop_lines.closest_idx; + const auto closest_idx = decision_result.closest_idx; if (!rtc_default_approved) { // use default_rtc uuid for stuck vehicle detection - auto stop_line_idx = decision_result.stop_line_idx; - if ( - !decision_result.is_detection_area_empty && - motion_utils::calcSignedArcLength(path->points, stop_line_idx, closest_idx) > - planner_param.common.stop_overshoot_margin) { - stop_line_idx = decision_result.stop_lines.default_stop_line; - } + const auto stop_line_idx = decision_result.stuck_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->collision_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -360,9 +350,9 @@ void reactRTCApprovalByDecisionResult( } } if ( - !rtc_occlusion_approved && !decision_result.is_detection_area_empty && + !rtc_occlusion_approved && decision_result.occlusion_stop_line_idx && planner_param.occlusion.enable) { - const auto occlusion_stop_line_idx = decision_result.stop_lines.occlusion_peeking_stop_line; + const auto occlusion_stop_line_idx = decision_result.occlusion_stop_line_idx.value(); planning_utils::setVelocityFromIndex(occlusion_stop_line_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = planning_utils::getAheadPose(occlusion_stop_line_idx, baselink2front, *path); @@ -391,7 +381,7 @@ void reactRTCApprovalByDecisionResult( "NonOccludedCollisionStop, approval = (default: %d, occlusion: %d)", rtc_default_approved, rtc_occlusion_approved); if (!rtc_default_approved) { - const auto stop_line_idx = decision_result.stop_line_idx; + const auto stop_line_idx = decision_result.collision_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->collision_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -400,12 +390,12 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } if (!rtc_occlusion_approved && planner_param.occlusion.enable) { - const auto stop_line_idx = decision_result.stop_lines.occlusion_peeking_stop_line; + const auto stop_line_idx = decision_result.occlusion_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -414,7 +404,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -443,14 +433,14 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } if (!rtc_occlusion_approved && planner_param.occlusion.enable) { if (planner_param.occlusion.enable_creeping) { const size_t occlusion_peeking_stop_line = decision_result.occlusion_stop_line_idx; - const size_t closest_idx = decision_result.stop_lines.closest_idx; + const size_t closest_idx = decision_result.closest_idx; for (size_t i = closest_idx; i < occlusion_peeking_stop_line; i++) { planning_utils::setVelocityFromIndex( i, planner_param.occlusion.occlusion_creep_velocity, path); @@ -465,7 +455,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -487,10 +477,9 @@ void reactRTCApprovalByDecisionResult( // NOTE: creep_velocity should be inserted first at closest_idx if !rtc_default_approved if (!rtc_occlusion_approved && planner_param.occlusion.enable) { - const size_t occlusion_peeking_stop_line = - decision_result.stop_lines.occlusion_peeking_stop_line; + const size_t occlusion_peeking_stop_line = decision_result.occlusion_stop_line_idx; if (planner_param.occlusion.enable_creeping) { - const size_t closest_idx = decision_result.stop_lines.closest_idx; + const size_t closest_idx = decision_result.closest_idx; for (size_t i = closest_idx; i < occlusion_peeking_stop_line; i++) { planning_utils::setVelocityFromIndex( i, planner_param.occlusion.occlusion_creep_velocity, path); @@ -504,12 +493,12 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(occlusion_peeking_stop_line).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(occlusion_peeking_stop_line).point.pose, VelocityFactor::INTERSECTION); } } - if (!rtc_default_approved && planner_param.occlusion.enable) { - const auto stop_line_idx = decision_result.stop_lines.default_stop_line; + if (!rtc_default_approved) { + const auto stop_line_idx = decision_result.collision_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->collision_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -518,7 +507,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -538,7 +527,7 @@ void reactRTCApprovalByDecisionResult( "OccludedCollisionStop, approval = (default: %d, occlusion: %d)", rtc_default_approved, rtc_occlusion_approved); if (!rtc_default_approved) { - const auto stop_line_idx = decision_result.stop_line_idx; + const auto stop_line_idx = decision_result.collision_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->collision_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -547,7 +536,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -561,7 +550,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -580,7 +569,7 @@ void reactRTCApprovalByDecisionResult( rclcpp::get_logger("reactRTCApprovalByDecisionResult"), "Safe, approval = (default: %d, occlusion: %d)", rtc_default_approved, rtc_occlusion_approved); if (!rtc_default_approved) { - const auto stop_line_idx = decision_result.stop_lines.default_stop_line; + const auto stop_line_idx = decision_result.collision_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->collision_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -589,12 +578,12 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } if (!rtc_occlusion_approved && planner_param.occlusion.enable) { - const auto stop_line_idx = decision_result.stop_lines.occlusion_peeking_stop_line; + const auto stop_line_idx = decision_result.occlusion_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -603,7 +592,7 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -623,7 +612,7 @@ void reactRTCApprovalByDecisionResult( "TrafficLightArrowSolidOn, approval = (default: %d, occlusion: %d)", rtc_default_approved, rtc_occlusion_approved); if (!rtc_default_approved) { - const auto stop_line_idx = decision_result.stop_lines.default_stop_line; + const auto stop_line_idx = decision_result.collision_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->collision_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -632,7 +621,21 @@ void reactRTCApprovalByDecisionResult( stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; planning_utils::appendStopReason(stop_factor, stop_reason); velocity_factor->set( - path->points, path->points.at(decision_result.stop_lines.closest_idx).point.pose, + path->points, path->points.at(decision_result.closest_idx).point.pose, + path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); + } + } + if (!rtc_occlusion_approved && planner_param.occlusion.enable) { + const auto stop_line_idx = decision_result.occlusion_stop_line_idx; + planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); + debug_data->occlusion_stop_wall_pose = + planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); + { + tier4_planning_msgs::msg::StopFactor stop_factor; + stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; + planning_utils::appendStopReason(stop_factor, stop_reason); + velocity_factor->set( + path->points, path->points.at(decision_result.closest_idx).point.pose, path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); } } @@ -695,6 +698,7 @@ bool IntersectionModule::modifyPathVelocity(PathWithLaneId * path, StopReason * // calculate the const auto decision_result = modifyPathVelocityDetail(path, stop_reason); + prev_decision_result_ = decision_result; const std::string decision_type = "intersection" + std::to_string(module_id_) + " : " + formatDecisionResult(decision_result); @@ -731,13 +735,11 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( lane_id_, associative_ids_, *path, planner_param_.common.path_interpolation_ds, logger_); if (!interpolated_path_info_opt) { RCLCPP_DEBUG(logger_, "splineInterpolate failed"); - is_peeking_ = false; return IntersectionModule::Indecisive{}; } const auto & interpolated_path_info = interpolated_path_info_opt.value(); if (!interpolated_path_info.lane_id_interval) { RCLCPP_WARN(logger_, "Path has no interval on intersection lane %ld", lane_id_); - is_peeking_ = false; return IntersectionModule::Indecisive{}; } @@ -756,33 +758,33 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( intersection_lanelets_.value().update(tl_arrow_solid_on, interpolated_path_info); const auto & conflicting_lanelets = intersection_lanelets_.value().conflicting(); - const auto & first_conflicting_area = intersection_lanelets_.value().first_conflicting_area(); - if (conflicting_lanelets.empty() || !first_conflicting_area) { - is_peeking_ = false; + const auto & first_conflicting_area_opt = intersection_lanelets_.value().first_conflicting_area(); + if (conflicting_lanelets.empty() || !first_conflicting_area_opt) { + RCLCPP_DEBUG(logger_, "conflicting area is empty"); return IntersectionModule::Indecisive{}; } + const auto first_conflicting_area = first_conflicting_area_opt.value(); - const auto & first_attention_area = intersection_lanelets_.value().first_attention_area(); + const auto & first_attention_area_opt = intersection_lanelets_.value().first_attention_area(); const auto & dummy_first_attention_area = - first_attention_area ? first_attention_area.value() : first_conflicting_area.value(); + first_attention_area_opt ? first_attention_area_opt.value() : first_conflicting_area; const auto intersection_stop_lines_opt = util::generateIntersectionStopLines( - first_conflicting_area.value(), dummy_first_attention_area, planner_data_, - interpolated_path_info, planner_param_.stuck_vehicle.use_stuck_stopline, - planner_param_.common.stop_line_margin, planner_param_.occlusion.peeking_offset, path); + first_conflicting_area, dummy_first_attention_area, planner_data_, interpolated_path_info, + planner_param_.stuck_vehicle.use_stuck_stopline, planner_param_.common.stop_line_margin, + planner_param_.occlusion.peeking_offset, path); if (!intersection_stop_lines_opt) { RCLCPP_DEBUG(logger_, "failed to generate intersection_stop_lines"); - is_peeking_ = false; return IntersectionModule::Indecisive{}; } const auto & intersection_stop_lines = intersection_stop_lines_opt.value(); const auto - [closest_idx, stuck_stop_line_idx, default_stop_line_idx, occlusion_peeking_stop_line_idx, - pass_judge_line_idx] = intersection_stop_lines; + [closest_idx, stuck_stop_line_idx_opt, default_stop_line_idx_opt, + occlusion_peeking_stop_line_idx_opt, pass_judge_line_idx] = intersection_stop_lines; const auto & conflicting_area = intersection_lanelets_.value().conflicting_area(); const auto path_lanelets_opt = util::generatePathLanelets( - lanelets_on_path, interpolated_path_info, associative_ids_, first_conflicting_area.value(), - conflicting_area, first_attention_area, intersection_lanelets_.value().attention_area(), + lanelets_on_path, interpolated_path_info, associative_ids_, first_conflicting_area, + conflicting_area, first_attention_area_opt, intersection_lanelets_.value().attention_area(), closest_idx, planner_data_->vehicle_info_.vehicle_width_m); if (!path_lanelets_opt.has_value()) { RCLCPP_DEBUG(logger_, "failed to generate PathLanelets"); @@ -790,10 +792,10 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } const auto path_lanelets = path_lanelets_opt.value(); - const bool stuck_detected = checkStuckVehicle( - planner_data_, path_lanelets, interpolated_path_info, intersection_stop_lines); + const bool stuck_detected = checkStuckVehicle(planner_data_, path_lanelets); - if (stuck_detected) { + if (stuck_detected && stuck_stop_line_idx_opt) { + auto stuck_stop_line_idx = stuck_stop_line_idx_opt.value(); const double dist_stopline = motion_utils::calcSignedArcLength( path->points, path->points.at(closest_idx).point.pose.position, path->points.at(stuck_stop_line_idx).point.pose.position); @@ -807,24 +809,30 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const bool timeout = (is_private_area_ && stuck_private_area_timeout_.getState() == StateMachine::State::GO); if (!timeout) { - is_peeking_ = false; + if ( + default_stop_line_idx_opt && + motion_utils::calcSignedArcLength(path->points, stuck_stop_line_idx, closest_idx) > + planner_param_.common.stop_overshoot_margin) { + stuck_stop_line_idx = default_stop_line_idx_opt.value(); + } return IntersectionModule::StuckStop{ - stuck_stop_line_idx, !first_attention_area.has_value(), intersection_stop_lines}; + closest_idx, stuck_stop_line_idx, occlusion_peeking_stop_line_idx_opt}; } } - if (!first_attention_area) { + if (!first_attention_area_opt) { RCLCPP_DEBUG(logger_, "attention area is empty"); - is_peeking_ = false; return IntersectionModule::Indecisive{}; } + const auto first_attention_area = first_attention_area_opt.value(); - if (default_stop_line_idx == 0) { - RCLCPP_DEBUG(logger_, "stop line index is 0"); - is_peeking_ = false; + if (!default_stop_line_idx_opt) { + RCLCPP_DEBUG(logger_, "default stop line is null"); return IntersectionModule::Indecisive{}; } + const auto default_stop_line_idx = default_stop_line_idx_opt.value(); + // TODO(Mamoru Sobue): this part needs more formal handling const double baselink2front = planner_data_->vehicle_info_.max_longitudinal_offset_m; debug_data_.pass_judge_wall_pose = planning_utils::getAheadPose(pass_judge_line_idx, baselink2front, *path); @@ -837,24 +845,30 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( planner_data_->current_velocity->twist.linear.y); const bool keep_detection = (vel_norm < planner_param_.collision_detection.keep_detection_vel_thr); + const bool was_safe = std::holds_alternative(prev_decision_result_); // if ego is over the pass judge line and not stopped - if (is_peeking_) { - // do nothing - RCLCPP_DEBUG(logger_, "peeking now"); - } else if (is_over_default_stop_line && !is_over_pass_judge_line && keep_detection) { + if (is_over_default_stop_line && !is_over_pass_judge_line && keep_detection) { RCLCPP_DEBUG( logger_, "is_over_default_stop_line && !is_over_pass_judge_line && keep_detection"); // do nothing } else if ( - (is_over_default_stop_line && is_over_pass_judge_line && is_go_out_) || is_permanent_go_) { + (was_safe && is_over_default_stop_line && is_over_pass_judge_line && is_go_out_) || + is_permanent_go_) { // is_go_out_: previous RTC approval // activated_: current RTC approval is_permanent_go_ = true; RCLCPP_DEBUG(logger_, "over the pass judge line. no plan needed."); - is_peeking_ = false; return IntersectionModule::Indecisive{}; } + if (!occlusion_peeking_stop_line_idx_opt) { + RCLCPP_DEBUG(logger_, "occlusion stop line is null"); + return IntersectionModule::Indecisive{}; + } + const auto collision_stop_line_idx = + is_over_default_stop_line ? closest_idx : default_stop_line_idx; + const auto occlusion_stop_line_idx = occlusion_peeking_stop_line_idx_opt.value(); + const auto & attention_lanelets = intersection_lanelets_.value().attention(); const auto & adjacent_lanelets = intersection_lanelets_.value().adjacent(); const auto & occlusion_attention_lanelets = intersection_lanelets_.value().occlusion_attention(); @@ -888,8 +902,8 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( collision_state_machine_.getState() == StateMachine::State::STOP; if (tl_arrow_solid_on) { - is_peeking_ = false; - return TrafficLightArrowSolidOn{has_collision, intersection_stop_lines}; + return TrafficLightArrowSolidOn{ + has_collision_with_margin, closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; } // check occlusion on detection lane @@ -898,6 +912,7 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( occlusion_attention_lanelets, routing_graph_ptr, planner_data_->occupancy_grid->info.resolution / std::sqrt(2.0)); } + const auto & occlusion_attention_divisions = occlusion_attention_divisions_.value(); const double occlusion_dist_thr = std::fabs( std::pow(planner_param_.occlusion.max_vehicle_velocity_for_rss, 2) / @@ -915,15 +930,17 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( (enable_occlusion_detection_ && !occlusion_attention_lanelets.empty() && !tl_arrow_solid_on) ? isOcclusionCleared( *planner_data_->occupancy_grid, occlusion_attention_area, adjacent_lanelets, - first_attention_area.value(), interpolated_path_info, - occlusion_attention_divisions_.value(), parked_attention_objects, occlusion_dist_thr) + first_attention_area, interpolated_path_info, occlusion_attention_divisions, + parked_attention_objects, occlusion_dist_thr) : true; occlusion_stop_state_machine_.setStateWithMarginTime( is_occlusion_cleared ? StateMachine::State::GO : StateMachine::STOP, logger_.get_child("occlusion_stop"), *clock_); + const bool is_occlusion_cleared_with_margin = + (occlusion_stop_state_machine_.getState() == StateMachine::State::GO); // check safety - const bool ext_occlusion_requested = (is_occlusion_cleared && !occlusion_activated_); + const bool ext_occlusion_requested = (is_occlusion_cleared_with_margin && !occlusion_activated_); if ( occlusion_stop_state_machine_.getState() == StateMachine::State::STOP || ext_occlusion_requested) { @@ -939,68 +956,45 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( before_creep_state_machine_.setState(StateMachine::State::GO); } if (before_creep_state_machine_.getState() == StateMachine::State::GO) { - if (has_collision) { - is_peeking_ = true; + if (has_collision_with_margin) { return IntersectionModule::OccludedCollisionStop{ - default_stop_line_idx, occlusion_peeking_stop_line_idx, is_occlusion_cleared, - intersection_stop_lines}; + is_occlusion_cleared_with_margin, closest_idx, collision_stop_line_idx, + occlusion_stop_line_idx}; } else { - is_peeking_ = true; return IntersectionModule::PeekingTowardOcclusion{ - occlusion_peeking_stop_line_idx, is_occlusion_cleared, intersection_stop_lines}; + is_occlusion_cleared_with_margin, closest_idx, collision_stop_line_idx, + occlusion_stop_line_idx}; } } else { if (is_stopped && approached_stop_line) { // start waiting at the first stop line before_creep_state_machine_.setState(StateMachine::State::GO); } - is_peeking_ = true; return IntersectionModule::FirstWaitBeforeOcclusion{ - default_stop_line_idx, occlusion_peeking_stop_line_idx, is_occlusion_cleared, - intersection_stop_lines}; + is_occlusion_cleared_with_margin, closest_idx, default_stop_line_idx, + occlusion_stop_line_idx}; } } else if (has_collision_with_margin) { - const bool is_over_default_stopLine = - util::isOverTargetIndex(*path, closest_idx, current_pose, default_stop_line_idx); - const auto stop_line_idx = is_over_default_stopLine ? closest_idx : default_stop_line_idx; - is_peeking_ = false; - return IntersectionModule::NonOccludedCollisionStop{stop_line_idx, intersection_stop_lines}; + return IntersectionModule::NonOccludedCollisionStop{ + closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; } - is_peeking_ = false; - return IntersectionModule::Safe{intersection_stop_lines}; + return IntersectionModule::Safe{closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; } bool IntersectionModule::checkStuckVehicle( - const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets, - const util::InterpolatedPathInfo & interpolated_path_info, - const util::IntersectionStopLines & intersection_stop_lines) + const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets) { const auto & objects_ptr = planner_data->predicted_objects; - const geometry_msgs::msg::Pose & current_pose = planner_data->current_odometry->pose; - const auto closest_idx = intersection_stop_lines.closest_idx; - const auto stuck_line_idx = intersection_stop_lines.stuck_stop_line; // considering lane change in the intersection, these lanelets are generated from the path - const auto & path = interpolated_path_info.path; const auto stuck_vehicle_detect_area = util::generateStuckVehicleDetectAreaPolygon( path_lanelets, planner_param_.stuck_vehicle.stuck_vehicle_detect_dist); debug_data_.stuck_vehicle_detect_area = toGeomPoly(stuck_vehicle_detect_area); - const double dist_stuck_stopline = motion_utils::calcSignedArcLength( - path.points, path.points.at(stuck_line_idx).point.pose.position, - path.points.at(closest_idx).point.pose.position); - const bool is_over_stuck_stopline = - util::isOverTargetIndex(path, closest_idx, current_pose, stuck_line_idx) && - (dist_stuck_stopline > planner_param_.common.stop_overshoot_margin); - - bool is_stuck = false; - if (!is_over_stuck_stopline) { - is_stuck = util::checkStuckVehicleInIntersection( - objects_ptr, stuck_vehicle_detect_area, planner_param_.stuck_vehicle.stuck_vehicle_vel_thr, - &debug_data_); - } - return is_stuck; + return util::checkStuckVehicleInIntersection( + objects_ptr, stuck_vehicle_detect_area, planner_param_.stuck_vehicle.stuck_vehicle_vel_thr, + &debug_data_); } autoware_auto_perception_msgs::msg::PredictedObjects IntersectionModule::filterTargetObjects( @@ -1531,54 +1525,54 @@ bool IntersectionModule::isOcclusionCleared( } /* -bool IntersectionModule::checkFrontVehicleDeceleration( + bool IntersectionModule::checkFrontVehicleDeceleration( lanelet::ConstLanelets & ego_lane_with_next_lane, lanelet::ConstLanelet & closest_lanelet, const Polygon2d & stuck_vehicle_detect_area, const autoware_auto_perception_msgs::msg::PredictedObject & object, const double assumed_front_car_decel) -{ + { const auto & object_pose = object.kinematics.initial_pose_with_covariance.pose; // consider vehicle in ego-lane && in front of ego const auto lon_vel = object.kinematics.initial_twist_with_covariance.twist.linear.x; const double object_decel = - planner_param_.stuck_vehicle.assumed_front_car_decel; // NOTE: this is positive + planner_param_.stuck_vehicle.assumed_front_car_decel; // NOTE: this is positive const double stopping_distance = lon_vel * lon_vel / (2 * object_decel); std::vector center_points; for (auto && p : ego_lane_with_next_lane[0].centerline()) - center_points.push_back(std::move(lanelet::utils::conversion::toGeomMsgPt(p))); + center_points.push_back(std::move(lanelet::utils::conversion::toGeomMsgPt(p))); for (auto && p : ego_lane_with_next_lane[1].centerline()) - center_points.push_back(std::move(lanelet::utils::conversion::toGeomMsgPt(p))); + center_points.push_back(std::move(lanelet::utils::conversion::toGeomMsgPt(p))); const double lat_offset = - std::fabs(motion_utils::calcLateralOffset(center_points, object_pose.position)); + std::fabs(motion_utils::calcLateralOffset(center_points, object_pose.position)); // get the nearest centerpoint to object std::vector dist_obj_center_points; for (const auto & p : center_points) - dist_obj_center_points.push_back(tier4_autoware_utils::calcDistance2d(object_pose.position, -p)); const int obj_closest_centerpoint_idx = std::distance( dist_obj_center_points.begin(), - std::min_element(dist_obj_center_points.begin(), dist_obj_center_points.end())); + dist_obj_center_points.push_back(tier4_autoware_utils::calcDistance2d(object_pose.position, + p)); const int obj_closest_centerpoint_idx = std::distance( dist_obj_center_points.begin(), + std::min_element(dist_obj_center_points.begin(), dist_obj_center_points.end())); // find two center_points whose distances from `closest_centerpoint` cross stopping_distance double acc_dist_prev = 0.0, acc_dist = 0.0; auto p1 = center_points[obj_closest_centerpoint_idx]; auto p2 = center_points[obj_closest_centerpoint_idx]; for (unsigned i = obj_closest_centerpoint_idx; i < center_points.size() - 1; ++i) { - p1 = center_points[i]; - p2 = center_points[i + 1]; - acc_dist_prev = acc_dist; - const auto arc_position_p1 = - lanelet::utils::getArcCoordinates(ego_lane_with_next_lane, toPose(p1)); - const auto arc_position_p2 = - lanelet::utils::getArcCoordinates(ego_lane_with_next_lane, toPose(p2)); - const double delta = arc_position_p2.length - arc_position_p1.length; - acc_dist += delta; - if (acc_dist > stopping_distance) { - break; - } + p1 = center_points[i]; + p2 = center_points[i + 1]; + acc_dist_prev = acc_dist; + const auto arc_position_p1 = + lanelet::utils::getArcCoordinates(ego_lane_with_next_lane, toPose(p1)); + const auto arc_position_p2 = + lanelet::utils::getArcCoordinates(ego_lane_with_next_lane, toPose(p2)); + const double delta = arc_position_p2.length - arc_position_p1.length; + acc_dist += delta; + if (acc_dist > stopping_distance) { + break; + } } // if stopping_distance >= center_points, stopping_point is center_points[end] const double ratio = (acc_dist <= stopping_distance) - ? 0.0 - : (acc_dist - stopping_distance) / (stopping_distance - acc_dist_prev); + ? 0.0 + : (acc_dist - stopping_distance) / (stopping_distance - acc_dist_prev); // linear interpolation geometry_msgs::msg::Point stopping_point; stopping_point.x = (p1.x * ratio + p2.x) / (1 + ratio); @@ -1592,18 +1586,18 @@ p)); const int obj_closest_centerpoint_idx = std::distance( dist_obj_center_poin autoware_auto_perception_msgs::msg::PredictedObject predicted_object = object; predicted_object.kinematics.initial_pose_with_covariance.pose.position = stopping_point; predicted_object.kinematics.initial_pose_with_covariance.pose.orientation = - tier4_autoware_utils::createQuaternionFromRPY(0, 0, lane_yaw); + tier4_autoware_utils::createQuaternionFromRPY(0, 0, lane_yaw); auto predicted_obj_footprint = tier4_autoware_utils::toPolygon2d(predicted_object); const bool is_in_stuck_area = !bg::disjoint(predicted_obj_footprint, stuck_vehicle_detect_area); debug_data_.predicted_obj_pose.position = stopping_point; debug_data_.predicted_obj_pose.orientation = - tier4_autoware_utils::createQuaternionFromRPY(0, 0, lane_yaw); + tier4_autoware_utils::createQuaternionFromRPY(0, 0, lane_yaw); if (is_in_stuck_area) { - return true; + return true; } return false; -} + } */ } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 8da150cc3bc90..8063300b9b787 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -111,62 +111,55 @@ class IntersectionModule : public SceneModuleInterface } occlusion; }; - /* - enum OcclusionState { - NONE, - BEFORE_FIRST_STOP_LINE, - WAIT_FIRST_STOP_LINE, - CREEP_SECOND_STOP_LINE, - COLLISION_DETECTED, - }; - */ - using Indecisive = std::monostate; struct StuckStop { - size_t stop_line_idx; - // NOTE: this is optional because stuck vehicle detection is possible - // even if the detection area is empty. - // Still this may be required for RTC's default stop line - bool is_detection_area_empty; - util::IntersectionStopLines stop_lines; + size_t closest_idx{0}; + size_t stuck_stop_line_idx{0}; + std::optional occlusion_stop_line_idx{std::nullopt}; }; struct NonOccludedCollisionStop { - size_t stop_line_idx; - util::IntersectionStopLines stop_lines; + size_t closest_idx{0}; + size_t collision_stop_line_idx{0}; + size_t occlusion_stop_line_idx{0}; }; struct FirstWaitBeforeOcclusion { - size_t first_stop_line_idx; - size_t occlusion_stop_line_idx; - bool is_actually_occlusion_cleared; - util::IntersectionStopLines stop_lines; + bool is_actually_occlusion_cleared{false}; + size_t closest_idx{0}; + size_t first_stop_line_idx{0}; + size_t occlusion_stop_line_idx{0}; }; struct PeekingTowardOcclusion { - size_t stop_line_idx; // NOTE: if intersection_occlusion is disapproved externally through RTC, // it indicates "is_forcefully_occluded" - bool is_actually_occlusion_cleared; - util::IntersectionStopLines stop_lines; + bool is_actually_occlusion_cleared{false}; + size_t closest_idx{0}; + size_t collision_stop_line_idx{0}; + size_t occlusion_stop_line_idx{0}; }; struct OccludedCollisionStop { - size_t stop_line_idx; - size_t occlusion_stop_line_idx; - bool is_actually_occlusion_cleared; - util::IntersectionStopLines stop_lines; + bool is_actually_occlusion_cleared{false}; + size_t closest_idx{0}; + size_t collision_stop_line_idx{0}; + size_t occlusion_stop_line_idx{0}; }; struct Safe { // NOTE: if RTC is disapproved status, default stop lines are still needed. - util::IntersectionStopLines stop_lines; + size_t closest_idx{0}; + size_t collision_stop_line_idx{0}; + size_t occlusion_stop_line_idx{0}; }; struct TrafficLightArrowSolidOn { - bool collision_detected; - util::IntersectionStopLines stop_lines; + bool collision_detected{false}; + size_t closest_idx{0}; + size_t collision_stop_line_idx{0}; + size_t occlusion_stop_line_idx{0}; }; using DecisionResult = std::variant< Indecisive, // internal process error, or over the pass judge line @@ -207,14 +200,15 @@ class IntersectionModule : public SceneModuleInterface const int64_t lane_id_; const std::set associative_ids_; std::string turn_direction_; + bool is_go_out_ = false; bool is_permanent_go_ = false; - bool is_peeking_ = false; + DecisionResult prev_decision_result_; + // Parameter PlannerParam planner_param_; + std::optional intersection_lanelets_; - // for an intersection lane, its associative lanes are those that share same parent lanelet and - // have same turn_direction // for occlusion detection const bool enable_occlusion_detection_; @@ -235,7 +229,6 @@ class IntersectionModule : public SceneModuleInterface double occlusion_stop_distance_; bool occlusion_activated_ = true; // for first stop in two-phase stop - const UUID occlusion_first_stop_uuid_; bool occlusion_first_stop_required_ = false; void initializeRTCStatus(); @@ -246,9 +239,7 @@ class IntersectionModule : public SceneModuleInterface bool checkStuckVehicle( const std::shared_ptr & planner_data, - const util::PathLanelets & path_lanelets, - const util::InterpolatedPathInfo & interpolated_path_info, - const util::IntersectionStopLines & intersection_stop_lines); + const util::PathLanelets & path_lanelets); autoware_auto_perception_msgs::msg::PredictedObjects filterTargetObjects( const lanelet::ConstLanelets & attention_area_lanelets, diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 940513f8fe7c3..ce2d7252ac93d 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -228,15 +228,20 @@ std::optional generateIntersectionStopLines( const auto first_inside_detection_ip = first_inside_detection_idx_ip_opt.value(); // (1) default stop line position on interpolated path - int stop_idx_ip_int = 0; + bool default_stop_line_valid = true; + int stop_idx_ip_int = -1; if (const auto map_stop_idx_ip = getStopLineIndexFromMap(interpolated_path_info, planner_data, 10.0); map_stop_idx_ip) { stop_idx_ip_int = static_cast(map_stop_idx_ip.value()) - base2front_idx_dist; - } else { + } + if (stop_idx_ip_int < 0) { stop_idx_ip_int = static_cast(first_inside_detection_ip) - stop_line_margin_idx_dist - base2front_idx_dist; } + if (stop_idx_ip_int < 0) { + default_stop_line_valid = false; + } const auto default_stop_line_ip = stop_idx_ip_int >= 0 ? static_cast(stop_idx_ip_int) : 0; // (2) ego front stop line position on interpolated path @@ -252,13 +257,25 @@ std::optional generateIntersectionStopLines( const auto local_footprint = planner_data->vehicle_info_.createFootprint(0.0, 0.0); const auto area_2d = lanelet::utils::to2D(first_detection_area).basicPolygon(); int occlusion_peeking_line_ip_int = static_cast(default_stop_line_ip); - for (size_t i = default_stop_line_ip; i <= std::get<1>(lane_interval_ip); ++i) { - const auto & base_pose = path_ip.points.at(i).point.pose; - const auto path_footprint = tier4_autoware_utils::transformVector( - local_footprint, tier4_autoware_utils::pose2transform(base_pose)); - if (bg::intersects(path_footprint, area_2d)) { - occlusion_peeking_line_ip_int = i; - break; + bool occlusion_peeking_line_valid = true; + { + // NOTE: if footprints[0] is already inside the detection area, invalid + const auto & base_pose0 = path_ip.points.at(default_stop_line_ip).point.pose; + const auto path_footprint0 = tier4_autoware_utils::transformVector( + local_footprint, tier4_autoware_utils::pose2transform(base_pose0)); + if (bg::intersects(path_footprint0, area_2d)) { + occlusion_peeking_line_valid = false; + } + } + if (occlusion_peeking_line_valid) { + for (size_t i = default_stop_line_ip + 1; i <= std::get<1>(lane_interval_ip); ++i) { + const auto & base_pose = path_ip.points.at(i).point.pose; + const auto path_footprint = tier4_autoware_utils::transformVector( + local_footprint, tier4_autoware_utils::pose2transform(base_pose)); + if (bg::intersects(path_footprint, area_2d)) { + occlusion_peeking_line_ip_int = i; + break; + } } } occlusion_peeking_line_ip_int += std::ceil(peeking_offset / ds); @@ -280,26 +297,43 @@ std::optional generateIntersectionStopLines( // (5) stuck vehicle stop line int stuck_stop_line_ip_int = 0; + bool stuck_stop_line_valid = true; if (use_stuck_stopline) { + // NOTE: when ego vehicle is approaching detection area and already passed + // first_conflicting_area, this could be null. const auto stuck_stop_line_idx_ip_opt = getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_conflicting_area); if (!stuck_stop_line_idx_ip_opt) { - return std::nullopt; + stuck_stop_line_valid = false; + stuck_stop_line_ip_int = 0; + } else { + stuck_stop_line_ip_int = stuck_stop_line_idx_ip_opt.value(); } - stuck_stop_line_ip_int = stuck_stop_line_idx_ip_opt.value(); } else { stuck_stop_line_ip_int = std::get<0>(lane_interval_ip); } - const auto stuck_stop_line_ip = static_cast( - std::max(0, stuck_stop_line_ip_int - stop_line_margin_idx_dist - base2front_idx_dist)); + stuck_stop_line_ip_int -= (stop_line_margin_idx_dist + base2front_idx_dist); + if (stuck_stop_line_ip_int < 0) { + stuck_stop_line_valid = false; + } + const auto stuck_stop_line_ip = static_cast(std::max(0, stuck_stop_line_ip_int)); - IntersectionStopLines intersection_stop_lines; + struct IntersectionStopLinesTemp + { + size_t closest_idx{0}; + size_t stuck_stop_line{0}; + size_t default_stop_line{0}; + size_t occlusion_peeking_stop_line{0}; + size_t pass_judge_line{0}; + }; + + IntersectionStopLinesTemp intersection_stop_lines_temp; std::list> stop_lines = { - {&closest_idx_ip, &intersection_stop_lines.closest_idx}, - {&stuck_stop_line_ip, &intersection_stop_lines.stuck_stop_line}, - {&default_stop_line_ip, &intersection_stop_lines.default_stop_line}, - {&occlusion_peeking_line_ip, &intersection_stop_lines.occlusion_peeking_stop_line}, - {&pass_judge_line_ip, &intersection_stop_lines.pass_judge_line}, + {&closest_idx_ip, &intersection_stop_lines_temp.closest_idx}, + {&stuck_stop_line_ip, &intersection_stop_lines_temp.stuck_stop_line}, + {&default_stop_line_ip, &intersection_stop_lines_temp.default_stop_line}, + {&occlusion_peeking_line_ip, &intersection_stop_lines_temp.occlusion_peeking_stop_line}, + {&pass_judge_line_ip, &intersection_stop_lines_temp.pass_judge_line}, }; stop_lines.sort( [](const auto & it1, const auto & it2) { return *(std::get<0>(it1)) < *(std::get<0>(it2)); }); @@ -312,14 +346,31 @@ std::optional generateIntersectionStopLines( *stop_idx = insert_idx.value(); } if ( - intersection_stop_lines.occlusion_peeking_stop_line < - intersection_stop_lines.default_stop_line) { - intersection_stop_lines.occlusion_peeking_stop_line = intersection_stop_lines.default_stop_line; + intersection_stop_lines_temp.occlusion_peeking_stop_line < + intersection_stop_lines_temp.default_stop_line) { + intersection_stop_lines_temp.occlusion_peeking_stop_line = + intersection_stop_lines_temp.default_stop_line; } if ( - intersection_stop_lines.occlusion_peeking_stop_line > intersection_stop_lines.pass_judge_line) { - intersection_stop_lines.pass_judge_line = intersection_stop_lines.occlusion_peeking_stop_line; + intersection_stop_lines_temp.occlusion_peeking_stop_line > + intersection_stop_lines_temp.pass_judge_line) { + intersection_stop_lines_temp.pass_judge_line = + intersection_stop_lines_temp.occlusion_peeking_stop_line; + } + + IntersectionStopLines intersection_stop_lines; + intersection_stop_lines.closest_idx = intersection_stop_lines_temp.closest_idx; + if (stuck_stop_line_valid) { + intersection_stop_lines.stuck_stop_line = intersection_stop_lines_temp.stuck_stop_line; } + if (default_stop_line_valid) { + intersection_stop_lines.default_stop_line = intersection_stop_lines_temp.default_stop_line; + } + if (occlusion_peeking_line_valid) { + intersection_stop_lines.occlusion_peeking_stop_line = + intersection_stop_lines_temp.occlusion_peeking_stop_line; + } + intersection_stop_lines.pass_judge_line = intersection_stop_lines_temp.pass_judge_line; return intersection_stop_lines; } @@ -328,8 +379,13 @@ std::optional getFirstPointInsidePolygon( const std::pair lane_interval, const lanelet::CompoundPolygon3d & polygon, const bool search_forward) { + // NOTE: if first point is already inside the polygon, returns nullopt const auto polygon_2d = lanelet::utils::to2D(polygon); if (search_forward) { + const auto & p0 = path.points.at(lane_interval.first).point.pose.position; + if (bg::within(to_bg2d(p0), polygon_2d)) { + return std::nullopt; + } for (size_t i = lane_interval.first; i <= lane_interval.second; ++i) { const auto & p = path.points.at(i).point.pose.position; const auto is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); @@ -338,6 +394,10 @@ std::optional getFirstPointInsidePolygon( } } } else { + const auto & p0 = path.points.at(lane_interval.second).point.pose.position; + if (bg::within(to_bg2d(p0), polygon_2d)) { + return std::nullopt; + } for (size_t i = lane_interval.second; i >= lane_interval.first; --i) { const auto & p = path.points.at(i).point.pose.position; const auto is_in_lanelet = bg::within(to_bg2d(p), polygon_2d); diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 0af797b35b2cc..64be330a99232 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -55,10 +55,10 @@ struct DebugData struct InterpolatedPathInfo { autoware_auto_planning_msgs::msg::PathWithLaneId path; - double ds; - int lane_id; - std::set associative_lane_ids; - std::optional> lane_id_interval; + double ds{0.0}; + int lane_id{0}; + std::set associative_lane_ids{}; + std::optional> lane_id_interval{std::nullopt}; }; struct IntersectionLanelets @@ -117,19 +117,23 @@ struct IntersectionLanelets struct DiscretizedLane { - int lane_id; + int lane_id{0}; // discrete fine lines from left to right - std::vector divisions; + std::vector divisions{}; }; struct IntersectionStopLines { // NOTE: for baselink - size_t closest_idx; - size_t stuck_stop_line; - size_t default_stop_line; - size_t occlusion_peeking_stop_line; - size_t pass_judge_line; + size_t closest_idx{0}; + // NOTE: null if path does not conflict with first_conflicting_area + std::optional stuck_stop_line{std::nullopt}; + // NOTE: null if path is over map stop_line OR its value is calculated negative area + std::optional default_stop_line{std::nullopt}; + // NOTE: null if footprints do not change from outside to inside of detection area + std::optional occlusion_peeking_stop_line{std::nullopt}; + // if the value is calculated negative, its value is 0 + size_t pass_judge_line{0}; }; struct PathLanelets From 7fd0706091f23276b95cbadb92761d1baa14f857 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 12 Sep 2023 19:31:56 +0900 Subject: [PATCH 185/547] feat(behavior_path_planner): safety check against dynamic objects after approval in manual mode (#4927) * add feature of stop after approval for goal_planner Signed-off-by: kyoichi-sugahara * insert stop point after approval Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../goal_planner/goal_planner_module.hpp | 28 ++++++- .../goal_planner/goal_planner_module.cpp | 80 ++++++++++++++++--- 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 6ba90f54e48fe..a6f9565c08aae 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -81,13 +81,16 @@ struct PullOverStatus size_t current_path_idx{0}; bool require_increment_{true}; // if false, keep current path idx. std::shared_ptr prev_stop_path{nullptr}; + std::shared_ptr prev_stop_path_after_approval{nullptr}; + // stop path after approval, stop path is not updated until safety is confirmed lanelet::ConstLanelets current_lanes{}; // TODO(someone): explain lanelet::ConstLanelets pull_over_lanes{}; // TODO(someone): explain std::vector lanes{}; // current + pull_over - bool has_decided_path{false}; // if true, the path is decided and safe against static objects + bool has_decided_path{false}; // if true, the path has is decided and safe against static objects bool is_safe_static_objects{false}; // current path is safe against *static* objects bool is_safe_dynamic_objects{false}; // current path is safe against *dynamic* objects bool prev_is_safe{false}; + bool prev_is_safe_dynamic_objects{false}; bool has_decided_velocity{false}; bool has_requested_approval{false}; bool is_ready{false}; @@ -230,6 +233,18 @@ class GoalPlannerModule : public SceneModuleInterface const Pose & search_start_offset_pose, PathWithLaneId & path) const; PathWithLaneId generateStopPath(); PathWithLaneId generateFeasibleStopPath(); + + /** + * @brief Generate a stop point in the current path based on the vehicle's pose and constraints. + * + * This function generates a stop point in the current path. If no lanes are available or if + * stopping is not feasible due to constraints (maximum deceleration, maximum jerk), no stop point + * is inserted into the path. + * + * @return the modified path with the stop point inserted. If no feasible stop point can be + * determined, returns an empty optional. + */ + std::optional generateStopInsertedCurrentPath(); void keepStoppedWithCurrentPath(PathWithLaneId & path); double calcSignedArcLengthFromEgo(const PathWithLaneId & path, const Pose & pose) const; @@ -277,6 +292,17 @@ class GoalPlannerModule : public SceneModuleInterface // output setter void setOutput(BehaviorModuleOutput & output); void setStopPath(BehaviorModuleOutput & output); + + /** + * @brief Sets a stop path in the current path based on safety conditions and previous paths. + * + * This function sets a stop path in the current path. Depending on whether the previous safety + * judgement against dynamic objects were safe or if a previous stop path existed, it either + * generates a new stop path or uses the previous stop path. + * + * @param output BehaviorModuleOutput + */ + void setStopPathFromCurrentPath(BehaviorModuleOutput & output); void setModifiedGoal(BehaviorModuleOutput & output) const; void setTurnSignalInfo(BehaviorModuleOutput & output) const; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 54a052c91f180..12f6f243e9a9c 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -664,18 +664,25 @@ void GoalPlannerModule::setLanes() void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) { if (status_.is_safe_static_objects) { - // clear stop pose when the path is safe and activated - if (isActivated()) { - resetWallPoses(); + if (isSafePath()) { + // clear stop pose when the path is safe against static/dynamic objects and activated + if (isActivated()) { + resetWallPoses(); + } + // keep stop if not enough time passed, + // because it takes time for the trajectory to be reflected + auto current_path = getCurrentPath(); + keepStoppedWithCurrentPath(current_path); + + output.path = std::make_shared(current_path); + output.reference_path = getPreviousModuleOutput().reference_path; + } else if (status_.has_decided_path && isActivated()) { + // situation : not safe against dynamic objects after approval + // insert stop point in current path if ego is able to stop with acceleration and jerk + // constraints + setStopPathFromCurrentPath(output); } - // keep stop if not enough time passed, - // because it takes time for the trajectory to be reflected - auto current_path = getCurrentPath(); - keepStoppedWithCurrentPath(current_path); - - output.path = std::make_shared(current_path); - output.reference_path = getPreviousModuleOutput().reference_path; } else { // not safe: use stop_path setStopPath(output); @@ -693,6 +700,7 @@ void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) // for the next loop setOutput(). // this is used to determine whether to generate a new stop path or keep the current stop path. status_.prev_is_safe = status_.is_safe_static_objects; + status_.prev_is_safe_dynamic_objects = status_.is_safe_dynamic_objects; } void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) @@ -720,6 +728,30 @@ void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) output.reference_path = getPreviousModuleOutput().reference_path; } +void GoalPlannerModule::setStopPathFromCurrentPath(BehaviorModuleOutput & output) +{ + // safe or not safe(no feasible stop_path found) -> not_safe: try to find new feasible stop_path + if (status_.prev_is_safe_dynamic_objects || status_.prev_stop_path_after_approval == nullptr) { + if (const auto stop_inserted_path = generateStopInsertedCurrentPath()) { + output.path = std::make_shared(*stop_inserted_path); + status_.prev_stop_path_after_approval = output.path; + RCLCPP_WARN_THROTTLE(getLogger(), *clock_, 5000, "Collision detected, generate stop path"); + } else { + output.path = std::make_shared(getCurrentPath()); + RCLCPP_WARN_THROTTLE( + getLogger(), *clock_, 5000, + "Collision detected, no feasible stop path found, cannot stop."); + } + std::lock_guard lock(mutex_); + last_path_update_time_ = std::make_unique(clock_->now()); + } else { + // not_safe safe(no feasible stop path found) -> not_safe: use previous stop path + output.path = status_.prev_stop_path_after_approval; + RCLCPP_WARN_THROTTLE(getLogger(), *clock_, 5000, "Collision detected, use previous stop path"); + } + output.reference_path = getPreviousModuleOutput().reference_path; +} + void GoalPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) const { if (status_.pull_over_path->type == PullOverPlannerType::FREESPACE) { @@ -1079,6 +1111,34 @@ PathWithLaneId GoalPlannerModule::generateFeasibleStopPath() return stop_path; } +std::optional GoalPlannerModule::generateStopInsertedCurrentPath() +{ + auto current_path = getCurrentPath(); + if (current_path.points.empty()) { + return {}; + } + + // try to insert stop point in current_path after approval + // but if can't stop with constraints(maximum deceleration, maximum jerk), don't insert stop point + const auto min_stop_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); + if (!min_stop_distance) { + return {}; + } + + // set stop point + const auto stop_idx = motion_utils::insertStopPoint( + planner_data_->self_odometry->pose.pose, *min_stop_distance, current_path.points); + + if (!stop_idx) { + return {}; + } else { + stop_pose_ = current_path.points.at(*stop_idx).point.pose; + } + + return current_path; +} + void GoalPlannerModule::transitionToNextPathIfFinishingCurrentPath() { if (isActivated() && last_approved_time_ != nullptr) { From 5b4ade30297ce787b1c5103d9292d010452d16b8 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 12 Sep 2023 19:37:18 +0900 Subject: [PATCH 186/547] fix(behavior_path_planner): use findSegment for segment search (#4963) Signed-off-by: Takamasa Horibe --- planning/behavior_path_planner/src/utils/utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 617d4a224fdd5..d6af88f39a8d2 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -2851,7 +2851,7 @@ BehaviorModuleOutput getReferencePath( // clip backward length // NOTE: In order to keep backward_path_length at least, resampling interval is added to the // backward. - const size_t current_seg_idx = motion_utils::findFirstNearestIndexWithSoftConstraints( + const size_t current_seg_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( reference_path.points, no_shift_pose, p.ego_nearest_dist_threshold, p.ego_nearest_yaw_threshold); reference_path.points = motion_utils::cropPoints( From cd83dc6c078864e6c476212af8db9d488263e5a8 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 12 Sep 2023 20:08:17 +0900 Subject: [PATCH 187/547] fix(start_planner): keep max back distace whithin lanes (#4962) * fix(start_planner): keep max back distace whithin lanes Signed-off-by: kosuke55 * Update planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp Co-authored-by: Kyoichi Sugahara * Update planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp * style(pre-commit): autofix --------- Signed-off-by: kosuke55 Co-authored-by: Kyoichi Sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../scene_module/start_planner/start_planner_module.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 29e16fd570720..a285224404f12 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -690,9 +690,16 @@ std::vector StartPlannerModule::searchPullOutStartPoses( const auto pull_out_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_out_lane_objects, parameters_->th_moving_object_velocity); + // Set the maximum backward distance less than the distance from the vehicle's base_link to the + // lane's rearmost point to prevent lane departure. + const double s_current = + lanelet::utils::getArcCoordinates(status_.pull_out_lanes, current_pose).length; + const double max_back_distance = std::clamp( + s_current - planner_data_->parameters.base_link2rear, 0.0, parameters_->max_back_distance); + // check collision between footprint and object at the backed pose const auto local_vehicle_footprint = createVehicleFootprint(vehicle_info_); - for (double back_distance = 0.0; back_distance <= parameters_->max_back_distance; + for (double back_distance = 0.0; back_distance <= max_back_distance; back_distance += parameters_->backward_search_resolution) { const auto backed_pose = calcLongitudinalOffsetPose( start_pose_candidates.points, current_pose.position, -back_distance); From db96db894e9248771c7a78853ab2f74728d0b27a Mon Sep 17 00:00:00 2001 From: badai nguyen <94814556+badai-nguyen@users.noreply.github.com> Date: Tue, 12 Sep 2023 21:14:24 +0900 Subject: [PATCH 188/547] feat(image_projection_based_fusion): add roi based clustering for small unknown object detection (#4681) * feat: add roi_pointcloud_fusion node Signed-off-by: badai-nguyen fix: postprocess Signed-off-by: badai-nguyen fix: launch file Signed-off-by: badai-nguyen chores: refactor Signed-off-by: badai-nguyen fix: closest cluster Signed-off-by: badai-nguyen * chores: refactor Signed-off-by: badai-nguyen * docs: add readme Signed-off-by: badai-nguyen * fix: add missed parameter declare Signed-off-by: badai-nguyen * fix: add center transform Signed-off-by: badai-nguyen * fix: typos in launch Signed-off-by: badai-nguyen * docs: update docs Signed-off-by: badai-nguyen * fix: change roi pointcloud fusion output to clusters Signed-off-by: badai-nguyen * fix: add cluster debug roi pointcloud fusion Signed-off-by: badai-nguyen * fix: use IoU_x in roi cluster fusion Signed-off-by: badai-nguyen * feat: add cluster merger package Signed-off-by: badai-nguyen * fix: camera lidar launch Signed-off-by: badai-nguyen * style(pre-commit): autofix * fix: cluster merger Signed-off-by: badai-nguyen * fix: roi cluster fusion unknown object fix Signed-off-by: badai-nguyen * chore: typo Signed-off-by: badai-nguyen * docs: add readme cluster_merger Signed-off-by: badai-nguyen * docs: update roi pointcloud fusion readme Signed-off-by: badai-nguyen * chore: typo Signed-off-by: badai-nguyen * fix: multiple definition bug Signed-off-by: badai-nguyen * chore: refactor Signed-off-by: badai-nguyen * docs: update docs Signed-off-by: badai-nguyen * chore: refactor Signed-off-by: badai-nguyen * chore: pre-commit Signed-off-by: badai-nguyen * fix: update camera_lidar_radar mode launch Signed-off-by: badai-nguyen --------- Signed-off-by: badai-nguyen Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../object_recognition_utils/transform.hpp | 64 ++++++ common/object_recognition_utils/package.xml | 6 + ...ra_lidar_fusion_based_detection.launch.xml | 59 +++++- ...ar_radar_fusion_based_detection.launch.xml | 44 +++- launch/tier4_perception_launch/package.xml | 1 + perception/cluster_merger/CMakeLists.txt | 27 +++ perception/cluster_merger/README.md | 72 +++++++ .../include/cluster_merger/node.hpp | 73 +++++++ .../launch/cluster_merger.launch.xml | 14 ++ perception/cluster_merger/package.xml | 28 +++ .../src/cluster_merger/node.cpp | 73 +++++++ .../CMakeLists.txt | 6 + .../image_projection_based_fusion/README.md | 1 + .../docs/images/roi_pointcloud_fusion.png | Bin 0 -> 118768 bytes .../docs/roi-pointcloud-fusion.md | 93 +++++++++ .../fusion_node.hpp | 5 + .../roi_pointcloud_fusion/node.hpp | 53 +++++ .../utils/utils.hpp | 36 ++++ .../launch/roi_pointcloud_fusion.launch.xml | 77 +++++++ .../image_projection_based_fusion/package.xml | 1 + .../src/fusion_node.cpp | 1 + .../src/roi_cluster_fusion/node.cpp | 8 +- .../src/roi_pointcloud_fusion/node.cpp | 165 +++++++++++++++ .../src/utils/utils.cpp | 194 +++++++++++++++++- 24 files changed, 1094 insertions(+), 7 deletions(-) create mode 100644 perception/cluster_merger/CMakeLists.txt create mode 100644 perception/cluster_merger/README.md create mode 100644 perception/cluster_merger/include/cluster_merger/node.hpp create mode 100644 perception/cluster_merger/launch/cluster_merger.launch.xml create mode 100644 perception/cluster_merger/package.xml create mode 100644 perception/cluster_merger/src/cluster_merger/node.cpp create mode 100644 perception/image_projection_based_fusion/docs/images/roi_pointcloud_fusion.png create mode 100644 perception/image_projection_based_fusion/docs/roi-pointcloud-fusion.md create mode 100644 perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_pointcloud_fusion/node.hpp create mode 100644 perception/image_projection_based_fusion/launch/roi_pointcloud_fusion.launch.xml create mode 100644 perception/image_projection_based_fusion/src/roi_pointcloud_fusion/node.cpp diff --git a/common/object_recognition_utils/include/object_recognition_utils/transform.hpp b/common/object_recognition_utils/include/object_recognition_utils/transform.hpp index 4446c87427e88..31892853a855f 100644 --- a/common/object_recognition_utils/include/object_recognition_utils/transform.hpp +++ b/common/object_recognition_utils/include/object_recognition_utils/transform.hpp @@ -15,15 +15,22 @@ #ifndef OBJECT_RECOGNITION_UTILS__TRANSFORM_HPP_ #define OBJECT_RECOGNITION_UTILS__TRANSFORM_HPP_ +#include + #include +#include +#include #include #include #include #ifdef ROS_DISTRO_GALACTIC +#include #include #else +#include + #include #endif @@ -45,6 +52,23 @@ namespace detail return boost::none; } } + +[[maybe_unused]] inline boost::optional getTransformMatrix( + const std::string & in_target_frame, const std_msgs::msg::Header & in_cloud_header, + const tf2_ros::Buffer & tf_buffer) +{ + try { + geometry_msgs::msg::TransformStamped transform_stamped; + transform_stamped = tf_buffer.lookupTransform( + in_target_frame, in_cloud_header.frame_id, in_cloud_header.stamp, + rclcpp::Duration::from_seconds(1.0)); + Eigen::Matrix4f mat = tf2::transformToEigen(transform_stamped.transform).matrix().cast(); + return mat; + } catch (tf2::TransformException & e) { + RCLCPP_WARN_STREAM(rclcpp::get_logger("detail::getTransformMatrix"), e.what()); + return boost::none; + } +} } // namespace detail namespace object_recognition_utils @@ -79,6 +103,46 @@ bool transformObjects( } return true; } +template +bool transformObjectsWithFeature( + const T & input_msg, const std::string & target_frame_id, const tf2_ros::Buffer & tf_buffer, + T & output_msg) +{ + output_msg = input_msg; + if (input_msg.header.frame_id != target_frame_id) { + output_msg.header.frame_id = target_frame_id; + tf2::Transform tf_target2objects_world; + tf2::Transform tf_target2objects; + tf2::Transform tf_objects_world2objects; + const auto ros_target2objects_world = detail::getTransform( + tf_buffer, input_msg.header.frame_id, target_frame_id, input_msg.header.stamp); + if (!ros_target2objects_world) { + return false; + } + const auto tf_matrix = detail::getTransformMatrix(target_frame_id, input_msg.header, tf_buffer); + if (!tf_matrix) { + RCLCPP_WARN( + rclcpp::get_logger("object_recognition_utils:"), "failed to get transformed matrix"); + return false; + } + for (auto & feature_object : output_msg.feature_objects) { + // transform object + tf2::fromMsg( + feature_object.object.kinematics.pose_with_covariance.pose, tf_objects_world2objects); + tf_target2objects = tf_target2objects_world * tf_objects_world2objects; + tf2::toMsg(tf_target2objects, feature_object.object.kinematics.pose_with_covariance.pose); + + // transform cluster + sensor_msgs::msg::PointCloud2 transformed_cluster; + pcl_ros::transformPointCloud(*tf_matrix, feature_object.feature.cluster, transformed_cluster); + transformed_cluster.header.frame_id = target_frame_id; + feature_object.feature.cluster = transformed_cluster; + } + output_msg.header.frame_id = target_frame_id; + return true; + } + return true; +} } // namespace object_recognition_utils #endif // OBJECT_RECOGNITION_UTILS__TRANSFORM_HPP_ diff --git a/common/object_recognition_utils/package.xml b/common/object_recognition_utils/package.xml index 95925e846a55c..2f2472515ebad 100644 --- a/common/object_recognition_utils/package.xml +++ b/common/object_recognition_utils/package.xml @@ -17,7 +17,13 @@ geometry_msgs interpolation libboost-dev + pcl_conversions + pcl_ros rclcpp + sensor_msgs + std_msgs + tf2 + tf2_eigen tier4_autoware_utils ament_cmake_ros diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index c74c0fdcc63b7..5f92d25022a22 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -79,12 +79,54 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -96,6 +138,8 @@ + + @@ -295,11 +339,22 @@ + + + + + + + + + + + - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index 99f09cfe6abbd..463340efdecfe 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -143,12 +143,54 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/launch/tier4_perception_launch/package.xml b/launch/tier4_perception_launch/package.xml index c1723c1fa07e8..336588891d9b2 100644 --- a/launch/tier4_perception_launch/package.xml +++ b/launch/tier4_perception_launch/package.xml @@ -12,6 +12,7 @@ ament_cmake_auto autoware_cmake + cluster_merger compare_map_segmentation crosswalk_traffic_light_estimator detected_object_feature_remover diff --git a/perception/cluster_merger/CMakeLists.txt b/perception/cluster_merger/CMakeLists.txt new file mode 100644 index 0000000000000..49506f4b439fb --- /dev/null +++ b/perception/cluster_merger/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.8) +project(cluster_merger) + +# find dependencies +find_package(autoware_cmake REQUIRED) +autoware_package() + +# Targets +ament_auto_add_library(cluster_merger_node_component SHARED + src/cluster_merger/node.cpp +) + +rclcpp_components_register_node(cluster_merger_node_component + PLUGIN "cluster_merger::ClusterMergerNode" + EXECUTABLE cluster_merger_node) + + +if(BUILD_TESTING) + list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package( + INSTALL_TO_SHARE + launch +) diff --git a/perception/cluster_merger/README.md b/perception/cluster_merger/README.md new file mode 100644 index 0000000000000..b46f7401b70ec --- /dev/null +++ b/perception/cluster_merger/README.md @@ -0,0 +1,72 @@ +# cluster merger + +## Purpose + +cluster_merger is a package for merging pointcloud clusters as detected objects with feature type. + +## Inner-working / Algorithms + +The clusters of merged topics are simply concatenated from clusters of input topics. + +## Input / Output + +### Input + +| Name | Type | Description | +| ---------------- | -------------------------------------------------------- | ------------------- | +| `input/cluster0` | `tier4_perception_msgs::msg::DetectedObjectsWithFeature` | pointcloud clusters | +| `input/cluster1` | `tier4_perception_msgs::msg::DetectedObjectsWithFeature` | pointcloud clusters | + +### Output + +| Name | Type | Description | +| ----------------- | ----------------------------------------------------- | --------------- | +| `output/clusters` | `autoware_auto_perception_msgs::msg::DetectedObjects` | merged clusters | + +## Parameters + +| Name | Type | Description | Default value | +| :---------------- | :----- | :----------------------------------- | :------------ | +| `output_frame_id` | string | The header frame_id of output topic. | base_link | + +## Assumptions / Known limits + + + +## (Optional) Error detection and handling + + + +## (Optional) Performance characterization + + + +## (Optional) References/External links + + + +## (Optional) Future extensions / Unimplemented parts diff --git a/perception/cluster_merger/include/cluster_merger/node.hpp b/perception/cluster_merger/include/cluster_merger/node.hpp new file mode 100644 index 0000000000000..8da5999f00384 --- /dev/null +++ b/perception/cluster_merger/include/cluster_merger/node.hpp @@ -0,0 +1,73 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLUSTER_MERGER__NODE_HPP_ +#define CLUSTER_MERGER__NODE_HPP_ + +#include "message_filters/subscriber.h" +#include "message_filters/sync_policies/approximate_time.h" +#include "message_filters/synchronizer.h" +#include "rclcpp/rclcpp.hpp" +#include "tier4_autoware_utils/tier4_autoware_utils.hpp" + +#include "tier4_perception_msgs/msg/detected_objects_with_feature.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace cluster_merger +{ +using tier4_perception_msgs::msg::DetectedObjectsWithFeature; +using tier4_perception_msgs::msg::DetectedObjectWithFeature; + +class ClusterMergerNode : public rclcpp::Node +{ +public: + explicit ClusterMergerNode(const rclcpp::NodeOptions & node_options); + +private: + // Subscriber + + tf2_ros::Buffer tf_buffer_; + tf2_ros::TransformListener tf_listener_; + + rclcpp::Subscription::SharedPtr sub_objects_{}; + message_filters::Subscriber objects0_sub_; + message_filters::Subscriber objects1_sub_; + typedef message_filters::sync_policies::ApproximateTime< + DetectedObjectsWithFeature, DetectedObjectsWithFeature> + SyncPolicy; + typedef message_filters::Synchronizer Sync; + Sync sync_; + + std::string output_frame_id_; + + std::vector::SharedPtr> sub_objects_array{}; + std::shared_ptr transform_listener_; + + void objectsCallback( + const DetectedObjectsWithFeature::ConstSharedPtr & input_objects0_msg, + const DetectedObjectsWithFeature::ConstSharedPtr & input_objects1_msg); + // Publisher + rclcpp::Publisher::SharedPtr pub_objects_; +}; + +} // namespace cluster_merger + +#endif // CLUSTER_MERGER__NODE_HPP_ diff --git a/perception/cluster_merger/launch/cluster_merger.launch.xml b/perception/cluster_merger/launch/cluster_merger.launch.xml new file mode 100644 index 0000000000000..1bbd0ebd91e12 --- /dev/null +++ b/perception/cluster_merger/launch/cluster_merger.launch.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/perception/cluster_merger/package.xml b/perception/cluster_merger/package.xml new file mode 100644 index 0000000000000..14826ad07e098 --- /dev/null +++ b/perception/cluster_merger/package.xml @@ -0,0 +1,28 @@ + + + + cluster_merger + 0.1.0 + The ROS 2 cluster merger package + Yukihiro Saito + Shunsuke Miura + autoware + Apache License 2.0 + + ament_cmake_auto + + geometry_msgs + message_filters + object_recognition_utils + rclcpp + rclcpp_components + tier4_autoware_utils + tier4_perception_msgs + autoware_cmake + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/perception/cluster_merger/src/cluster_merger/node.cpp b/perception/cluster_merger/src/cluster_merger/node.cpp new file mode 100644 index 0000000000000..48bf953027510 --- /dev/null +++ b/perception/cluster_merger/src/cluster_merger/node.cpp @@ -0,0 +1,73 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cluster_merger/node.hpp" + +#include "object_recognition_utils/object_recognition_utils.hpp" + +#include +#include +#include +namespace cluster_merger +{ + +ClusterMergerNode::ClusterMergerNode(const rclcpp::NodeOptions & node_options) +: rclcpp::Node("cluster_merger_node", node_options), + tf_buffer_(get_clock()), + tf_listener_(tf_buffer_), + objects0_sub_(this, "input/cluster0", rclcpp::QoS{1}.get_rmw_qos_profile()), + objects1_sub_(this, "input/cluster1", rclcpp::QoS{1}.get_rmw_qos_profile()), + sync_(SyncPolicy(10), objects0_sub_, objects1_sub_) +{ + output_frame_id_ = declare_parameter("output_frame_id"); + using std::placeholders::_1; + using std::placeholders::_2; + sync_.registerCallback(std::bind(&ClusterMergerNode::objectsCallback, this, _1, _2)); + + // Publisher + pub_objects_ = create_publisher("~/output/clusters", rclcpp::QoS{1}); +} + +void ClusterMergerNode::objectsCallback( + const DetectedObjectsWithFeature::ConstSharedPtr & input_objects0_msg, + const DetectedObjectsWithFeature::ConstSharedPtr & input_objects1_msg) +{ + if (pub_objects_->get_subscription_count() < 1) { + return; + } + // TODO(badai-nguyen): transform input topics to desired frame before concatenating + /* transform to the same with cluster0 frame id*/ + DetectedObjectsWithFeature transformed_objects0; + DetectedObjectsWithFeature transformed_objects1; + if ( + !object_recognition_utils::transformObjectsWithFeature( + *input_objects0_msg, output_frame_id_, tf_buffer_, transformed_objects0) || + !object_recognition_utils::transformObjectsWithFeature( + *input_objects1_msg, output_frame_id_, tf_buffer_, transformed_objects1)) { + return; + } + + DetectedObjectsWithFeature output_objects; + output_objects.header = input_objects0_msg->header; + // add check frame id and transform if they are different + output_objects.feature_objects = transformed_objects0.feature_objects; + output_objects.feature_objects.insert( + output_objects.feature_objects.end(), transformed_objects1.feature_objects.begin(), + transformed_objects1.feature_objects.end()); + pub_objects_->publish(output_objects); +} +} // namespace cluster_merger + +#include "rclcpp_components/register_node_macro.hpp" +RCLCPP_COMPONENTS_REGISTER_NODE(cluster_merger::ClusterMergerNode) diff --git a/perception/image_projection_based_fusion/CMakeLists.txt b/perception/image_projection_based_fusion/CMakeLists.txt index 906ad71d21732..ce7c3b5ea12a9 100644 --- a/perception/image_projection_based_fusion/CMakeLists.txt +++ b/perception/image_projection_based_fusion/CMakeLists.txt @@ -22,6 +22,7 @@ ament_auto_add_library(${PROJECT_NAME} SHARED src/utils/utils.cpp src/roi_cluster_fusion/node.cpp src/roi_detected_object_fusion/node.cpp + src/roi_pointcloud_fusion/node.cpp ) target_link_libraries(${PROJECT_NAME} @@ -39,6 +40,11 @@ rclcpp_components_register_node(${PROJECT_NAME} EXECUTABLE roi_cluster_fusion_node ) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "image_projection_based_fusion::RoiPointCloudFusionNode" + EXECUTABLE roi_pointcloud_fusion_node +) + set(CUDA_VERBOSE OFF) # set flags for CUDA availability diff --git a/perception/image_projection_based_fusion/README.md b/perception/image_projection_based_fusion/README.md index 1bbee35ab44f8..207989d8a6f25 100644 --- a/perception/image_projection_based_fusion/README.md +++ b/perception/image_projection_based_fusion/README.md @@ -58,3 +58,4 @@ The rclcpp::TimerBase timer could not break a for loop, therefore even if time i | roi_cluster_fusion | Overwrite a classification label of clusters by that of ROIs from a 2D object detector. | [link](./docs/roi-cluster-fusion.md) | | roi_detected_object_fusion | Overwrite a classification label of detected objects by that of ROIs from a 2D object detector. | [link](./docs/roi-detected-object-fusion.md) | | pointpainting_fusion | Paint the point cloud with the ROIs from a 2D object detector and feed to a 3D object detector. | [link](./docs/pointpainting-fusion.md) | +| roi_pointcloud_fusion | Matching pointcloud with ROIs from a 2D object detector to detect unknown-labeled objects | [link](./docs/roi-pointcloud-fusion.md) | diff --git a/perception/image_projection_based_fusion/docs/images/roi_pointcloud_fusion.png b/perception/image_projection_based_fusion/docs/images/roi_pointcloud_fusion.png new file mode 100644 index 0000000000000000000000000000000000000000..c529de7c728fba996a08f3718aef2a7a6d705ccc GIT binary patch literal 118768 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 z

    Q`R}!D?4d@xbR=R32R1lOQ$>E#1vd%sNh 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 b(9@yOF1 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¥#Igh} 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 literal 0 HcmV?d00001 diff --git a/perception/image_projection_based_fusion/docs/roi-pointcloud-fusion.md b/perception/image_projection_based_fusion/docs/roi-pointcloud-fusion.md new file mode 100644 index 0000000000000..5d0b241a578d6 --- /dev/null +++ b/perception/image_projection_based_fusion/docs/roi-pointcloud-fusion.md @@ -0,0 +1,93 @@ +# roi_pointcloud_fusion + +## Purpose + +The node `roi_pointcloud_fusion` is to cluster the pointcloud based on Region Of Interests (ROIs) detected by a 2D object detector, specific for unknown labeled ROI. + +## Inner-workings / Algorithms + +- The pointclouds are projected onto image planes and extracted as cluster if they are inside the unknown labeled ROIs. +- Since the cluster might contain unrelated points from background, then a refinement step is added to filter the background pointcloud by distance to camera. + +![roi_pointcloud_fusion_image](./images/roi_pointcloud_fusion.png) + +## Inputs / Outputs + +### Input + +| Name | Type | Description | +| ------------------------ | -------------------------------------------------------- | --------------------------------------------------------- | +| `input` | `sensor_msgs::msg::PointCloud2` | input pointcloud | +| `input/camera_info[0-7]` | `sensor_msgs::msg::CameraInfo` | camera information to project 3d points onto image planes | +| `input/rois[0-7]` | `tier4_perception_msgs::msg::DetectedObjectsWithFeature` | ROIs from each image | +| `input/image_raw[0-7]` | `sensor_msgs::msg::Image` | images for visualization | + +### Output + +| Name | Type | Description | +| ----------------- | -------------------------------------------------------- | -------------------------------------------- | +| `output` | `sensor_msgs::msg::PointCloud2` | output pointcloud as default of interface | +| `output_clusters` | `tier4_perception_msgs::msg::DetectedObjectsWithFeature` | output clusters | +| `debug/clusters` | `sensor_msgs/msg/PointCloud2` | colored cluster pointcloud for visualization | + +## Parameters + +### Core Parameters + +| Name | Type | Description | +| ---------------------- | ------ | -------------------------------------------------------------------------------------------- | +| `min_cluster_size` | int | the minimum number of points that a cluster needs to contain in order to be considered valid | +| `cluster_2d_tolerance` | double | cluster tolerance measured in radial direction | +| `rois_number` | int | the number of input rois | + +## Assumptions / Known limits + +- Currently, the refinement is only based on distance to camera, the roi based clustering is supposed to work well with small object ROIs. Others criteria for refinement might needed in the future. + + + +## (Optional) Error detection and handling + + + +## (Optional) Performance characterization + + + +## (Optional) References/External links + + + +## (Optional) Future extensions / Unimplemented parts + + diff --git a/perception/image_projection_based_fusion/include/image_projection_based_fusion/fusion_node.hpp b/perception/image_projection_based_fusion/include/image_projection_based_fusion/fusion_node.hpp index 016633fa4ef92..5174aebe069a8 100644 --- a/perception/image_projection_based_fusion/include/image_projection_based_fusion/fusion_node.hpp +++ b/perception/image_projection_based_fusion/include/image_projection_based_fusion/fusion_node.hpp @@ -29,6 +29,9 @@ #include #include #include +#include +#include +#include #include #include @@ -49,6 +52,8 @@ using autoware_auto_perception_msgs::msg::DetectedObjects; using sensor_msgs::msg::PointCloud2; using tier4_perception_msgs::msg::DetectedObjectsWithFeature; using tier4_perception_msgs::msg::DetectedObjectWithFeature; +using PointCloud = pcl::PointCloud; +using autoware_auto_perception_msgs::msg::ObjectClassification; template class FusionNode : public rclcpp::Node diff --git a/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_pointcloud_fusion/node.hpp b/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_pointcloud_fusion/node.hpp new file mode 100644 index 0000000000000..2b4eb822a9edb --- /dev/null +++ b/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_pointcloud_fusion/node.hpp @@ -0,0 +1,53 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef IMAGE_PROJECTION_BASED_FUSION__ROI_POINTCLOUD_FUSION__NODE_HPP_ +#define IMAGE_PROJECTION_BASED_FUSION__ROI_POINTCLOUD_FUSION__NODE_HPP_ + +#include "image_projection_based_fusion/fusion_node.hpp" + +#include +#include +namespace image_projection_based_fusion +{ +class RoiPointCloudFusionNode +: public FusionNode +{ +private: + int min_cluster_size_{1}; + bool fuse_unknown_only_{true}; + double cluster_2d_tolerance_; + + rclcpp::Publisher::SharedPtr pub_objects_ptr_; + std::vector output_fused_objects_; + rclcpp::Publisher::SharedPtr cluster_debug_pub_; + + /* data */ +public: + explicit RoiPointCloudFusionNode(const rclcpp::NodeOptions & options); + +protected: + void preprocess(sensor_msgs::msg::PointCloud2 & pointcloud_msg) override; + + void postprocess(sensor_msgs::msg::PointCloud2 & pointcloud_msg) override; + + void fuseOnSingleImage( + const PointCloud2 & input_pointcloud_msg, const std::size_t image_id, + const DetectedObjectsWithFeature & input_roi_msg, + const sensor_msgs::msg::CameraInfo & camera_info, PointCloud2 & output_pointcloud_msg) override; + bool out_of_scope(const DetectedObjectWithFeature & obj); +}; + +} // namespace image_projection_based_fusion +#endif // IMAGE_PROJECTION_BASED_FUSION__ROI_POINTCLOUD_FUSION__NODE_HPP_ diff --git a/perception/image_projection_based_fusion/include/image_projection_based_fusion/utils/utils.hpp b/perception/image_projection_based_fusion/include/image_projection_based_fusion/utils/utils.hpp index 541cf997412c2..d7fd3c3882046 100644 --- a/perception/image_projection_based_fusion/include/image_projection_based_fusion/utils/utils.hpp +++ b/perception/image_projection_based_fusion/include/image_projection_based_fusion/utils/utils.hpp @@ -32,18 +32,54 @@ #include #endif +#include "image_projection_based_fusion/fusion_node.hpp" + +#include +#include +#include +#include + +#include "autoware_auto_perception_msgs/msg/shape.hpp" +#include "tier4_perception_msgs/msg/detected_object_with_feature.hpp" + +#include +#include +#include + #include #include +#include namespace image_projection_based_fusion { +using PointCloud = pcl::PointCloud; std::optional getTransformStamped( const tf2_ros::Buffer & tf_buffer, const std::string & target_frame_id, const std::string & source_frame_id, const rclcpp::Time & time); Eigen::Affine3d transformToEigen(const geometry_msgs::msg::Transform & t); +void convertCluster2FeatureObject( + const std_msgs::msg::Header & header, const PointCloud & cluster, + DetectedObjectWithFeature & feature_obj); +PointCloud closest_cluster( + PointCloud & cluster, const double cluster_2d_tolerance, const int min_cluster_size, + const pcl::PointXYZ & center); + +void updateOutputFusedObjects( + std::vector & output_objs, const std::vector & clusters, + const std_msgs::msg::Header & in_cloud_header, const std_msgs::msg::Header & in_roi_header, + const tf2_ros::Buffer & tf_buffer, const int min_cluster_size, const float cluster_2d_tolerance, + std::vector & output_fused_objects); + +geometry_msgs::msg::Point getCentroid(const sensor_msgs::msg::PointCloud2 & pointcloud); + +pcl::PointXYZ getClosestPoint(const pcl::PointCloud & cluster); +void addShapeAndKinematic( + const pcl::PointCloud & cluster, + tier4_perception_msgs::msg::DetectedObjectWithFeature & feature_obj); + } // namespace image_projection_based_fusion #endif // IMAGE_PROJECTION_BASED_FUSION__UTILS__UTILS_HPP_ diff --git a/perception/image_projection_based_fusion/launch/roi_pointcloud_fusion.launch.xml b/perception/image_projection_based_fusion/launch/roi_pointcloud_fusion.launch.xml new file mode 100644 index 0000000000000..181f4ccb88320 --- /dev/null +++ b/perception/image_projection_based_fusion/launch/roi_pointcloud_fusion.launch.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/perception/image_projection_based_fusion/package.xml b/perception/image_projection_based_fusion/package.xml index 32d7a5633b811..5ff99af2ebb21 100644 --- a/perception/image_projection_based_fusion/package.xml +++ b/perception/image_projection_based_fusion/package.xml @@ -14,6 +14,7 @@ autoware_auto_perception_msgs autoware_point_types cv_bridge + euclidean_cluster image_transport lidar_centerpoint message_filters diff --git a/perception/image_projection_based_fusion/src/fusion_node.cpp b/perception/image_projection_based_fusion/src/fusion_node.cpp index 1bc351b08c01b..a540688f7e751 100644 --- a/perception/image_projection_based_fusion/src/fusion_node.cpp +++ b/perception/image_projection_based_fusion/src/fusion_node.cpp @@ -422,4 +422,5 @@ void FusionNode::publish(const Msg & output_msg) template class FusionNode; template class FusionNode; template class FusionNode; +template class FusionNode; } // namespace image_projection_based_fusion diff --git a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp index 77f6a5c506d92..e9cb76b050427 100644 --- a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp +++ b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp @@ -171,12 +171,16 @@ void RoiClusterFusionNode::fuseOnSingleImage( for (const auto & feature_obj : input_roi_msg.feature_objects) { int index = 0; double max_iou = 0.0; + bool is_roi_label_known = + feature_obj.object.classification.front().label != ObjectClassification::UNKNOWN; for (const auto & cluster_map : m_cluster_roi) { double iou(0.0), iou_x(0.0), iou_y(0.0); if (use_iou_) { iou = calcIoU(cluster_map.second, feature_obj.feature.roi); } - if (use_iou_x_) { + // use for unknown roi to improve small objects like traffic cone detect + // TODO(badai-nguyen): add option to disable roi_cluster mode + if (use_iou_x_ || !is_roi_label_known) { iou_x = calcIoUX(cluster_map.second, feature_obj.feature.roi); } if (use_iou_y_) { @@ -192,8 +196,6 @@ void RoiClusterFusionNode::fuseOnSingleImage( } } if (!output_cluster_msg.feature_objects.empty()) { - bool is_roi_label_known = feature_obj.object.classification.front().label != - autoware_auto_perception_msgs::msg::ObjectClassification::UNKNOWN; bool is_roi_existence_prob_higher = output_cluster_msg.feature_objects.at(index).object.existence_probability <= feature_obj.object.existence_probability; diff --git a/perception/image_projection_based_fusion/src/roi_pointcloud_fusion/node.cpp b/perception/image_projection_based_fusion/src/roi_pointcloud_fusion/node.cpp new file mode 100644 index 0000000000000..233e568ebee0b --- /dev/null +++ b/perception/image_projection_based_fusion/src/roi_pointcloud_fusion/node.cpp @@ -0,0 +1,165 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "image_projection_based_fusion/roi_pointcloud_fusion/node.hpp" + +#include "image_projection_based_fusion/utils/geometry.hpp" +#include "image_projection_based_fusion/utils/utils.hpp" + +#ifdef ROS_DISTRO_GALACTIC +#include +#include +#else +#include +#include +#endif + +#include "euclidean_cluster/utils.hpp" + +namespace image_projection_based_fusion +{ +RoiPointCloudFusionNode::RoiPointCloudFusionNode(const rclcpp::NodeOptions & options) +: FusionNode("roi_pointcloud_fusion", options) +{ + fuse_unknown_only_ = declare_parameter("fuse_unknown_only"); + min_cluster_size_ = declare_parameter("min_cluster_size"); + cluster_2d_tolerance_ = declare_parameter("cluster_2d_tolerance"); + pub_objects_ptr_ = + this->create_publisher("output_clusters", rclcpp::QoS{1}); + cluster_debug_pub_ = this->create_publisher("debug/clusters", 1); +} + +void RoiPointCloudFusionNode::preprocess(__attribute__((unused)) + sensor_msgs::msg::PointCloud2 & pointcloud_msg) +{ + return; +} + +void RoiPointCloudFusionNode::postprocess(__attribute__((unused)) + sensor_msgs::msg::PointCloud2 & pointcloud_msg) +{ + const auto objects_sub_count = pub_objects_ptr_->get_subscription_count() + + pub_objects_ptr_->get_intra_process_subscription_count(); + if (objects_sub_count < 1) { + return; + } + + DetectedObjectsWithFeature output_msg; + output_msg.header = pointcloud_msg.header; + output_msg.feature_objects = output_fused_objects_; + + if (objects_sub_count > 0) { + pub_objects_ptr_->publish(output_msg); + } + output_fused_objects_.clear(); + // publish debug cluster + if (cluster_debug_pub_->get_subscription_count() > 0) { + sensor_msgs::msg::PointCloud2 debug_cluster_msg; + euclidean_cluster::convertObjectMsg2SensorMsg(output_msg, debug_cluster_msg); + cluster_debug_pub_->publish(debug_cluster_msg); + } +} +void RoiPointCloudFusionNode::fuseOnSingleImage( + const sensor_msgs::msg::PointCloud2 & input_pointcloud_msg, + __attribute__((unused)) const std::size_t image_id, + const DetectedObjectsWithFeature & input_roi_msg, + const sensor_msgs::msg::CameraInfo & camera_info, + __attribute__((unused)) sensor_msgs::msg::PointCloud2 & output_pointcloud_msg) +{ + if (input_pointcloud_msg.data.empty()) { + return; + } + std::vector output_objs; + // select ROIs for fusion + for (const auto & feature_obj : input_roi_msg.feature_objects) { + if (fuse_unknown_only_) { + bool is_roi_label_unknown = feature_obj.object.classification.front().label == + autoware_auto_perception_msgs::msg::ObjectClassification::UNKNOWN; + if (is_roi_label_unknown) { + output_objs.push_back(feature_obj); + } + } else { + // TODO(badai-nguyen): selected class from a list + output_objs.push_back(feature_obj); + } + } + if (output_objs.empty()) { + return; + } + + // transform pointcloud to camera optical frame id + Eigen::Matrix4d projection; + projection << camera_info.p.at(0), camera_info.p.at(1), camera_info.p.at(2), camera_info.p.at(3), + camera_info.p.at(4), camera_info.p.at(5), camera_info.p.at(6), camera_info.p.at(7), + camera_info.p.at(8), camera_info.p.at(9), camera_info.p.at(10), camera_info.p.at(11); + geometry_msgs::msg::TransformStamped transform_stamped; + { + const auto transform_stamped_optional = getTransformStamped( + tf_buffer_, input_roi_msg.header.frame_id, input_pointcloud_msg.header.frame_id, + input_roi_msg.header.stamp); + if (!transform_stamped_optional) { + return; + } + transform_stamped = transform_stamped_optional.value(); + } + + sensor_msgs::msg::PointCloud2 transformed_cloud; + tf2::doTransform(input_pointcloud_msg, transformed_cloud, transform_stamped); + + std::vector clusters; + clusters.resize(output_objs.size()); + + for (sensor_msgs::PointCloud2ConstIterator iter_x(transformed_cloud, "x"), + iter_y(transformed_cloud, "y"), iter_z(transformed_cloud, "z"), + iter_orig_x(input_pointcloud_msg, "x"), iter_orig_y(input_pointcloud_msg, "y"), + iter_orig_z(input_pointcloud_msg, "z"); + iter_x != iter_x.end(); + ++iter_x, ++iter_y, ++iter_z, ++iter_orig_x, ++iter_orig_y, ++iter_orig_z) { + if (*iter_z <= 0.0) { + continue; + } + Eigen::Vector4d projected_point = projection * Eigen::Vector4d(*iter_x, *iter_y, *iter_z, 1.0); + Eigen::Vector2d normalized_projected_point = Eigen::Vector2d( + projected_point.x() / projected_point.z(), projected_point.y() / projected_point.z()); + + for (std::size_t i = 0; i < output_objs.size(); ++i) { + auto & feature_obj = output_objs.at(i); + const auto & check_roi = feature_obj.feature.roi; + auto & cluster = clusters.at(i); + + if ( + check_roi.x_offset <= normalized_projected_point.x() && + check_roi.y_offset <= normalized_projected_point.y() && + check_roi.x_offset + check_roi.width >= normalized_projected_point.x() && + check_roi.y_offset + check_roi.height >= normalized_projected_point.y()) { + cluster.push_back(pcl::PointXYZ(*iter_orig_x, *iter_orig_y, *iter_orig_z)); + } + } + } + + // refine and update output_fused_objects_ + updateOutputFusedObjects( + output_objs, clusters, input_pointcloud_msg.header, input_roi_msg.header, tf_buffer_, + min_cluster_size_, cluster_2d_tolerance_, output_fused_objects_); +} + +bool RoiPointCloudFusionNode::out_of_scope(__attribute__((unused)) + const DetectedObjectWithFeature & obj) +{ + return false; +} +} // namespace image_projection_based_fusion + +#include +RCLCPP_COMPONENTS_REGISTER_NODE(image_projection_based_fusion::RoiPointCloudFusionNode) diff --git a/perception/image_projection_based_fusion/src/utils/utils.cpp b/perception/image_projection_based_fusion/src/utils/utils.cpp index 670c5596001fb..76cd1e3c61e82 100644 --- a/perception/image_projection_based_fusion/src/utils/utils.cpp +++ b/perception/image_projection_based_fusion/src/utils/utils.cpp @@ -13,7 +13,6 @@ // limitations under the License. #include "image_projection_based_fusion/utils/utils.hpp" - namespace image_projection_based_fusion { @@ -39,4 +38,197 @@ Eigen::Affine3d transformToEigen(const geometry_msgs::msg::Transform & t) return a; } +void convertCluster2FeatureObject( + const std_msgs::msg::Header & header, const PointCloud & cluster, + DetectedObjectWithFeature & feature_obj) +{ + PointCloud2 ros_cluster; + pcl::toROSMsg(cluster, ros_cluster); + ros_cluster.header = header; + feature_obj.feature.cluster = ros_cluster; + feature_obj.object.kinematics.pose_with_covariance.pose.position = getCentroid(ros_cluster); + feature_obj.object.existence_probability = 1.0f; +} + +PointCloud closest_cluster( + PointCloud & cluster, const double cluster_2d_tolerance, const int min_cluster_size, + const pcl::PointXYZ & center) +{ + // sort point by distance to camera origin + + auto func = [center](const pcl::PointXYZ & p1, const pcl::PointXYZ & p2) { + return tier4_autoware_utils::calcDistance2d(center, p1) < + tier4_autoware_utils::calcDistance2d(center, p2); + }; + std::sort(cluster.begin(), cluster.end(), func); + PointCloud out_cluster; + for (auto & point : cluster) { + if (out_cluster.empty()) { + out_cluster.push_back(point); + continue; + } + if (tier4_autoware_utils::calcDistance2d(out_cluster.back(), point) < cluster_2d_tolerance) { + out_cluster.push_back(point); + continue; + } + if (out_cluster.size() >= static_cast(min_cluster_size)) { + return out_cluster; + } + out_cluster.clear(); + out_cluster.push_back(point); + } + return out_cluster; +} + +void updateOutputFusedObjects( + std::vector & output_objs, const std::vector & clusters, + const std_msgs::msg::Header & in_cloud_header, const std_msgs::msg::Header & in_roi_header, + const tf2_ros::Buffer & tf_buffer, const int min_cluster_size, const float cluster_2d_tolerance, + std::vector & output_fused_objects) +{ + if (output_objs.size() != clusters.size()) { + return; + } + Eigen::Vector3d orig_camera_frame, orig_point_frame; + Eigen::Affine3d camera2lidar_affine; + orig_camera_frame << 0.0, 0.0, 0.0; + { + const auto transform_stamped_optional = getTransformStamped( + tf_buffer, in_cloud_header.frame_id, in_roi_header.frame_id, in_roi_header.stamp); + if (!transform_stamped_optional) { + return; + } + camera2lidar_affine = transformToEigen(transform_stamped_optional.value().transform); + } + orig_point_frame = camera2lidar_affine * orig_camera_frame; + pcl::PointXYZ camera_orig_point_frame = + pcl::PointXYZ(orig_point_frame.x(), orig_point_frame.y(), orig_point_frame.z()); + + for (std::size_t i = 0; i < output_objs.size(); ++i) { + PointCloud cluster = clusters.at(i); + auto & feature_obj = output_objs.at(i); + if (cluster.points.size() < std::size_t(min_cluster_size)) { + continue; + } + + // TODO(badai-nguyen): change to interface to select refine criteria like closest, largest + // to output refine cluster and centroid + auto refine_cluster = + closest_cluster(cluster, cluster_2d_tolerance, min_cluster_size, camera_orig_point_frame); + if (refine_cluster.points.size() < std::size_t(min_cluster_size)) { + continue; + } + + refine_cluster.width = refine_cluster.points.size(); + refine_cluster.height = 1; + refine_cluster.is_dense = false; + // convert cluster to object + convertCluster2FeatureObject(in_cloud_header, refine_cluster, feature_obj); + output_fused_objects.push_back(feature_obj); + } +} + +void addShapeAndKinematic( + const pcl::PointCloud & cluster, + tier4_perception_msgs::msg::DetectedObjectWithFeature & feature_obj) +{ + if (cluster.empty()) { + return; + } + pcl::PointXYZ centroid = pcl::PointXYZ(0.0, 0.0, 0.0); + float max_z = -1e6; + float min_z = 1e6; + for (const auto & point : cluster) { + centroid.x += point.x; + centroid.y += point.y; + centroid.z += point.z; + max_z = max_z < point.z ? point.z : max_z; + min_z = min_z > point.z ? point.z : min_z; + } + centroid.x = centroid.x / static_cast(cluster.size()); + centroid.y = centroid.y / static_cast(cluster.size()); + centroid.z = centroid.z / static_cast(cluster.size()); + + std::vector cluster2d; + std::vector cluster2d_convex; + + for (size_t i = 0; i < cluster.size(); ++i) { + cluster2d.push_back( + cv::Point((cluster.at(i).x - centroid.x) * 1000.0, (cluster.at(i).y - centroid.y) * 1000.)); + } + cv::convexHull(cluster2d, cluster2d_convex); + if (cluster2d_convex.empty()) { + return; + } + pcl::PointXYZ polygon_centroid = pcl::PointXYZ(0.0, 0.0, 0.0); + for (size_t i = 0; i < cluster2d_convex.size(); ++i) { + polygon_centroid.x += static_cast(cluster2d_convex.at(i).x) / 1000.0; + polygon_centroid.y += static_cast(cluster2d_convex.at(i).y) / 1000.0; + } + polygon_centroid.x = polygon_centroid.x / static_cast(cluster2d_convex.size()); + polygon_centroid.y = polygon_centroid.y / static_cast(cluster2d_convex.size()); + + autoware_auto_perception_msgs::msg::Shape shape; + for (size_t i = 0; i < cluster2d_convex.size(); ++i) { + geometry_msgs::msg::Point32 point; + point.x = cluster2d_convex.at(i).x / 1000.0; + point.y = cluster2d_convex.at(i).y / 1000.0; + point.z = 0.0; + shape.footprint.points.push_back(point); + } + shape.type = autoware_auto_perception_msgs::msg::Shape::POLYGON; + constexpr float eps = 0.01; + shape.dimensions.x = 0; + shape.dimensions.y = 0; + shape.dimensions.z = std::max((max_z - min_z), eps); + feature_obj.object.shape = shape; + feature_obj.object.kinematics.pose_with_covariance.pose.position.x = + centroid.x + polygon_centroid.x; + feature_obj.object.kinematics.pose_with_covariance.pose.position.y = + centroid.y + polygon_centroid.y; + feature_obj.object.kinematics.pose_with_covariance.pose.position.z = + min_z + shape.dimensions.z * 0.5; + feature_obj.object.existence_probability = 1.0; + feature_obj.object.kinematics.pose_with_covariance.pose.orientation.x = 0; + feature_obj.object.kinematics.pose_with_covariance.pose.orientation.y = 0; + feature_obj.object.kinematics.pose_with_covariance.pose.orientation.z = 0; + feature_obj.object.kinematics.pose_with_covariance.pose.orientation.w = 1; +} + +geometry_msgs::msg::Point getCentroid(const sensor_msgs::msg::PointCloud2 & pointcloud) +{ + geometry_msgs::msg::Point centroid; + centroid.x = 0.0f; + centroid.y = 0.0f; + centroid.z = 0.0f; + for (sensor_msgs::PointCloud2ConstIterator iter_x(pointcloud, "x"), + iter_y(pointcloud, "y"), iter_z(pointcloud, "z"); + iter_x != iter_x.end(); ++iter_x, ++iter_y, ++iter_z) { + centroid.x += *iter_x; + centroid.y += *iter_y; + centroid.z += *iter_z; + } + const size_t size = pointcloud.width * pointcloud.height; + centroid.x = centroid.x / static_cast(size); + centroid.y = centroid.y / static_cast(size); + centroid.z = centroid.z / static_cast(size); + return centroid; +} + +pcl::PointXYZ getClosestPoint(const pcl::PointCloud & cluster) +{ + pcl::PointXYZ closest_point; + double min_dist = 1e6; + pcl::PointXYZ orig_point = pcl::PointXYZ(0.0, 0.0, 0.0); + for (std::size_t i = 0; i < cluster.points.size(); ++i) { + pcl::PointXYZ point = cluster.points.at(i); + double dist_closest_point = tier4_autoware_utils::calcDistance2d(point, orig_point); + if (min_dist > dist_closest_point) { + min_dist = dist_closest_point; + closest_point = pcl::PointXYZ(point.x, point.y, point.z); + } + } + return closest_point; +} + } // namespace image_projection_based_fusion From 4e55ad302ee9c285d1c9db6346f69a210fa07bd0 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 12 Sep 2023 22:59:16 +0900 Subject: [PATCH 189/547] feat(obstacle_cruise_planner): approching stop on curve (#4952) * feat(obstacle_cruise_planner): approching stop on curve Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/obstacle_cruise_planner.param.yaml | 8 +- .../common_structs.hpp | 7 +- .../include/obstacle_cruise_planner/node.hpp | 3 + .../planner_interface.hpp | 12 +- planning/obstacle_cruise_planner/src/node.cpp | 24 +++- .../src/planner_interface.cpp | 126 ++++++++++++++++-- 6 files changed, 161 insertions(+), 19 deletions(-) diff --git a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml index 08ebea4284bf1..f3f1932c44c43 100644 --- a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml +++ b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml @@ -20,7 +20,11 @@ nearest_dist_deviation_threshold: 3.0 # [m] for finding nearest index nearest_yaw_deviation_threshold: 1.57 # [rad] for finding nearest index min_behavior_stop_margin: 3.0 # [m] - suppress_sudden_obstacle_stop: false + stop_on_curve: + enable_approaching: true # false + additional_safe_distance_margin: 0.0 # [m] + min_safe_distance_margin: 3.0 # [m] + suppress_sudden_obstacle_stop: true stop_obstacle_type: unknown: true @@ -54,7 +58,7 @@ pedestrian: false slow_down_obstacle_type: - unknown: true + unknown: false car: true truck: true bus: true diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp index 9b7d318879e5f..e958074971f2a 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp @@ -108,12 +108,15 @@ struct StopObstacle : public TargetObstacleInterface { StopObstacle( const std::string & arg_uuid, const rclcpp::Time & arg_stamp, - const geometry_msgs::msg::Pose & arg_pose, const double arg_lon_velocity, - const double arg_lat_velocity, const geometry_msgs::msg::Point arg_collision_point) + const geometry_msgs::msg::Pose & arg_pose, const Shape & arg_shape, + const double arg_lon_velocity, const double arg_lat_velocity, + const geometry_msgs::msg::Point arg_collision_point) : TargetObstacleInterface(arg_uuid, arg_stamp, arg_pose, arg_lon_velocity, arg_lat_velocity), + shape(arg_shape), collision_point(arg_collision_point) { } + Shape shape; geometry_msgs::msg::Point collision_point; }; diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp index ac6684d163aea..00afc11985d72 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp @@ -100,6 +100,9 @@ class ObstacleCruisePlannerNode : public rclcpp::Node bool enable_debug_info_; bool enable_calculation_time_info_; double min_behavior_stop_margin_; + bool enable_approaching_on_curve_; + double additional_safe_distance_margin_on_curve_; + double min_safe_distance_margin_on_curve_; bool suppress_sudden_obstacle_stop_; std::vector stop_obstacle_types_; diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp index 8d67a89e369e9..9a5a6521a6494 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp @@ -54,11 +54,16 @@ class PlannerInterface void setParam( const bool enable_debug_info, const bool enable_calculation_time_info, - const double min_behavior_stop_margin, const bool suppress_sudden_obstacle_stop) + const double min_behavior_stop_margin, const double enable_approaching_on_curve, + const double additional_safe_distance_margin_on_curve, + const double min_safe_distance_margin_on_curve, const bool suppress_sudden_obstacle_stop) { enable_debug_info_ = enable_debug_info; enable_calculation_time_info_ = enable_calculation_time_info; min_behavior_stop_margin_ = min_behavior_stop_margin; + enable_approaching_on_curve_ = enable_approaching_on_curve; + additional_safe_distance_margin_on_curve_ = additional_safe_distance_margin_on_curve; + min_safe_distance_margin_on_curve_ = min_safe_distance_margin_on_curve; suppress_sudden_obstacle_stop_ = suppress_sudden_obstacle_stop; } @@ -103,6 +108,9 @@ class PlannerInterface bool enable_calculation_time_info_{false}; LongitudinalInfo longitudinal_info_; double min_behavior_stop_margin_; + bool enable_approaching_on_curve_; + double additional_safe_distance_margin_on_curve_; + double min_safe_distance_margin_on_curve_; bool suppress_sudden_obstacle_stop_; // stop watch @@ -195,6 +203,8 @@ class PlannerInterface std::optional start_point{std::nullopt}; std::optional end_point{std::nullopt}; }; + double calculateMarginFromObstacleOnCurve( + const PlannerData & planner_data, const StopObstacle & stop_obstacle) const; double calculateSlowDownVelocity( const SlowDownObstacle & obstacle, const std::optional & prev_output) const; std::optional> calculateDistanceToSlowDownWithConstraints( diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index fc454e1ed6426..14324709ba9e1 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -398,11 +398,18 @@ ObstacleCruisePlannerNode::ObstacleCruisePlannerNode(const rclcpp::NodeOptions & } min_behavior_stop_margin_ = declare_parameter("common.min_behavior_stop_margin"); + additional_safe_distance_margin_on_curve_ = + declare_parameter("common.stop_on_curve.additional_safe_distance_margin"); + enable_approaching_on_curve_ = + declare_parameter("common.stop_on_curve.enable_approaching"); + min_safe_distance_margin_on_curve_ = + declare_parameter("common.stop_on_curve.min_safe_distance_margin"); suppress_sudden_obstacle_stop_ = declare_parameter("common.suppress_sudden_obstacle_stop"); planner_ptr_->setParam( enable_debug_info_, enable_calculation_time_info_, min_behavior_stop_margin_, - suppress_sudden_obstacle_stop_); + enable_approaching_on_curve_, additional_safe_distance_margin_on_curve_, + min_safe_distance_margin_on_curve_, suppress_sudden_obstacle_stop_); } { // stop/cruise/slow down obstacle type @@ -439,9 +446,20 @@ rcl_interfaces::msg::SetParametersResult ObstacleCruisePlannerNode::onParam( parameters, "common.enable_debug_info", enable_debug_info_); tier4_autoware_utils::updateParam( parameters, "common.enable_calculation_time_info", enable_calculation_time_info_); + + tier4_autoware_utils::updateParam( + parameters, "common.stop_on_curve.enable_approaching", enable_approaching_on_curve_); + tier4_autoware_utils::updateParam( + parameters, "common.stop_on_curve.additional_safe_distance_margin", + additional_safe_distance_margin_on_curve_); + tier4_autoware_utils::updateParam( + parameters, "common.stop_on_curve.min_safe_distance_margin", + min_safe_distance_margin_on_curve_); + planner_ptr_->setParam( enable_debug_info_, enable_calculation_time_info_, min_behavior_stop_margin_, - suppress_sudden_obstacle_stop_); + enable_approaching_on_curve_, additional_safe_distance_margin_on_curve_, + min_safe_distance_margin_on_curve_, suppress_sudden_obstacle_stop_); tier4_autoware_utils::updateParam( parameters, "common.enable_slow_down_planning", enable_slow_down_planning_); @@ -912,7 +930,7 @@ std::optional ObstacleCruisePlannerNode::createStopObstacle( } const auto [tangent_vel, normal_vel] = projectObstacleVelocityToTrajectory(traj_points, obstacle); - return StopObstacle{obstacle.uuid, obstacle.stamp, obstacle.pose, + return StopObstacle{obstacle.uuid, obstacle.stamp, obstacle.pose, obstacle.shape, tangent_vel, normal_vel, *collision_point}; } diff --git a/planning/obstacle_cruise_planner/src/planner_interface.cpp b/planning/obstacle_cruise_planner/src/planner_interface.cpp index f4f729f1fe8b0..5ea637655a01a 100644 --- a/planning/obstacle_cruise_planner/src/planner_interface.cpp +++ b/planning/obstacle_cruise_planner/src/planner_interface.cpp @@ -16,6 +16,9 @@ #include "motion_utils/distance/distance.hpp" #include "motion_utils/marker/marker_helper.hpp" +#include "motion_utils/resample/resample.hpp" +#include "motion_utils/trajectory/tmp_conversion.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "signal_processing/lowpass_filter_1d.hpp" #include "tier4_autoware_utils/ros/marker_helper.hpp" @@ -206,6 +209,19 @@ double calcDecelerationVelocityFromDistanceToTarget( } return current_velocity; } + +std::vector resampleTrajectoryPoints( + const std::vector & traj_points, const double interval) +{ + const auto traj = motion_utils::convertToTrajectory(traj_points); + const auto resampled_traj = motion_utils::resampleTrajectory(traj, interval); + return motion_utils::convertToTrajectoryPointArray(resampled_traj); +} + +tier4_autoware_utils::Point2d convertPoint(const geometry_msgs::msg::Point & p) +{ + return tier4_autoware_utils::Point2d{p.x, p.y}; +} } // namespace std::vector PlannerInterface::generateStopTrajectory( @@ -261,16 +277,19 @@ std::vector PlannerInterface::generateStopTrajectory( planner_data.traj_points, planner_data.ego_pose.position, ego_segment_idx, 0); const double dist_to_ego = -negative_dist_to_ego; + const double margin_from_obstacle = + calculateMarginFromObstacleOnCurve(planner_data, *closest_stop_obstacle); + // If behavior stop point is ahead of the closest_obstacle_stop point within a certain margin // we set closest_obstacle_stop_distance to closest_behavior_stop_distance - const double margin_from_obstacle = [&]() { + const double margin_from_obstacle_considering_behavior_module = [&]() { const size_t nearest_segment_idx = findEgoSegmentIndex(planner_data.traj_points, planner_data.ego_pose); const auto closest_behavior_stop_idx = motion_utils::searchZeroVelocityIndex(planner_data.traj_points, nearest_segment_idx + 1); if (!closest_behavior_stop_idx) { - return longitudinal_info_.safe_distance_margin; + return margin_from_obstacle; } const double closest_behavior_stop_dist_from_ego = motion_utils::calcSignedArcLength( @@ -284,29 +303,28 @@ std::vector PlannerInterface::generateStopTrajectory( abs_ego_offset; const double stop_dist_diff = closest_behavior_stop_dist_from_ego - closest_obstacle_stop_dist_from_ego; - if (stop_dist_diff < longitudinal_info_.safe_distance_margin) { + if (stop_dist_diff < margin_from_obstacle) { // Use terminal margin (terminal_safe_distance_margin) for obstacle stop return longitudinal_info_.terminal_safe_distance_margin; } } else { - const double closest_obstacle_stop_dist_from_ego = closest_obstacle_dist - dist_to_ego - - longitudinal_info_.safe_distance_margin - - abs_ego_offset; + const double closest_obstacle_stop_dist_from_ego = + closest_obstacle_dist - dist_to_ego - margin_from_obstacle - abs_ego_offset; const double stop_dist_diff = closest_behavior_stop_dist_from_ego - closest_obstacle_stop_dist_from_ego; - if (0.0 < stop_dist_diff && stop_dist_diff < longitudinal_info_.safe_distance_margin) { + if (0.0 < stop_dist_diff && stop_dist_diff < margin_from_obstacle) { // Use shorter margin (min_behavior_stop_margin) for obstacle stop return min_behavior_stop_margin_; } } - return longitudinal_info_.safe_distance_margin; + return margin_from_obstacle; }(); const auto [stop_margin_from_obstacle, will_collide_with_obstacle] = [&]() { // Check feasibility to stop if (suppress_sudden_obstacle_stop_) { const double closest_obstacle_stop_dist = - closest_obstacle_dist - margin_from_obstacle - abs_ego_offset; + closest_obstacle_dist - margin_from_obstacle_considering_behavior_module - abs_ego_offset; // Calculate feasible stop margin (Check the feasibility) const double feasible_stop_dist = calcMinimumDistanceToStop( @@ -316,11 +334,12 @@ std::vector PlannerInterface::generateStopTrajectory( if (closest_obstacle_stop_dist < feasible_stop_dist) { const auto feasible_margin_from_obstacle = - margin_from_obstacle - (feasible_stop_dist - closest_obstacle_stop_dist); + margin_from_obstacle_considering_behavior_module - + (feasible_stop_dist - closest_obstacle_stop_dist); return std::make_pair(feasible_margin_from_obstacle, true); } } - return std::make_pair(margin_from_obstacle, false); + return std::make_pair(margin_from_obstacle_considering_behavior_module, false); }(); // Generate Output Trajectory @@ -397,6 +416,91 @@ std::vector PlannerInterface::generateStopTrajectory( return output_traj_points; } +double PlannerInterface::calculateMarginFromObstacleOnCurve( + const PlannerData & planner_data, const StopObstacle & stop_obstacle) const +{ + if (!enable_approaching_on_curve_) { + return longitudinal_info_.safe_distance_margin; + } + + const double abs_ego_offset = planner_data.is_driving_forward + ? std::abs(vehicle_info_.max_longitudinal_offset_m) + : std::abs(vehicle_info_.min_longitudinal_offset_m); + + // calculate short trajectory points towards obstacle + const size_t obj_segment_idx = + motion_utils::findNearestSegmentIndex(planner_data.traj_points, stop_obstacle.collision_point); + std::vector short_traj_points{planner_data.traj_points.at(obj_segment_idx + 1)}; + double sum_short_traj_length{0.0}; + for (int i = obj_segment_idx; 0 <= i; --i) { + short_traj_points.push_back(planner_data.traj_points.at(i)); + + if ( + 1 < short_traj_points.size() && + longitudinal_info_.safe_distance_margin + abs_ego_offset < sum_short_traj_length) { + break; + } + sum_short_traj_length += tier4_autoware_utils::calcDistance2d( + planner_data.traj_points.at(i), planner_data.traj_points.at(i + 1)); + } + std::reverse(short_traj_points.begin(), short_traj_points.end()); + if (short_traj_points.size() < 2) { + return longitudinal_info_.safe_distance_margin; + } + + // calculate collision index between straight line from ego pose and object + const auto calculate_distance_from_straight_ego_path = + [&](const auto & ego_pose, const auto & object_polygon) { + const auto forward_ego_pose = tier4_autoware_utils::calcOffsetPose( + ego_pose, longitudinal_info_.safe_distance_margin + 3.0, 0.0, 0.0); + const auto ego_straight_segment = tier4_autoware_utils::Segment2d{ + convertPoint(ego_pose.position), convertPoint(forward_ego_pose.position)}; + return boost::geometry::distance(ego_straight_segment, object_polygon); + }; + const auto resampled_short_traj_points = resampleTrajectoryPoints(short_traj_points, 0.5); + const auto object_polygon = + tier4_autoware_utils::toPolygon2d(stop_obstacle.pose, stop_obstacle.shape); + const auto collision_idx = [&]() -> std::optional { + for (size_t i = 0; i < resampled_short_traj_points.size(); ++i) { + const double dist_to_obj = calculate_distance_from_straight_ego_path( + resampled_short_traj_points.at(i).pose, object_polygon); + if (dist_to_obj < vehicle_info_.vehicle_width_m / 2.0) { + return i; + } + } + return std::nullopt; + }(); + if (!collision_idx) { + return min_safe_distance_margin_on_curve_; + } + if (*collision_idx == 0) { + return longitudinal_info_.safe_distance_margin; + } + + // calculate margin from obstacle + const double partial_segment_length = [&]() { + const double collision_segment_length = tier4_autoware_utils::calcDistance2d( + resampled_short_traj_points.at(*collision_idx - 1), + resampled_short_traj_points.at(*collision_idx)); + const double prev_dist = calculate_distance_from_straight_ego_path( + resampled_short_traj_points.at(*collision_idx - 1).pose, object_polygon); + const double next_dist = calculate_distance_from_straight_ego_path( + resampled_short_traj_points.at(*collision_idx).pose, object_polygon); + return (next_dist - vehicle_info_.vehicle_width_m / 2.0) / (next_dist - prev_dist) * + collision_segment_length; + }(); + + const double short_margin_from_obstacle = + partial_segment_length + + motion_utils::calcSignedArcLength( + resampled_short_traj_points, *collision_idx, stop_obstacle.collision_point) - + abs_ego_offset + additional_safe_distance_margin_on_curve_; + + return std::min( + longitudinal_info_.safe_distance_margin, + std::max(min_safe_distance_margin_on_curve_, short_margin_from_obstacle)); +} + double PlannerInterface::calcDistanceToCollisionPoint( const PlannerData & planner_data, const geometry_msgs::msg::Point & collision_point) { From e1c04a6b4543d0dec3dcd770f324c8c4b605f6c0 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 13 Sep 2023 00:10:20 +0900 Subject: [PATCH 190/547] refactor(goal_planner): remove unused variables (#4966) Signed-off-by: kosuke55 --- .../src/utils/goal_planner/goal_searcher.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index 2dd81eba7e6c7..131eb3dd36731 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -75,10 +75,6 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) route_handler->getCenterLinePath(pull_over_lanes, s_start, s_end), parameters_.goal_search_interval); - const auto [shoulder_lane_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets( - *(planner_data_->dynamic_object), pull_over_lanes); - std::vector original_search_poses{}; // for search area visualizing size_t goal_id = 0; for (const auto & p : center_line_path.points) { From c7b5140137a9ed8278069f486ba68e9a68405b12 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 13 Sep 2023 00:11:42 +0900 Subject: [PATCH 191/547] fix(goal_planner): fix occpancy grid initializing check (#4969) Signed-off-by: kosuke55 --- .../src/scene_module/goal_planner/goal_planner_module.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 12f6f243e9a9c..488b23ac0aec9 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -1292,7 +1292,7 @@ TurnSignalInfo GoalPlannerModule::calcTurnSignalInfo() const bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const { - if (parameters_->use_occupancy_grid || !occupancy_grid_map_) { + if (parameters_->use_occupancy_grid && occupancy_grid_map_) { const bool check_out_of_range = false; if (occupancy_grid_map_->hasObstacleOnPath(path, check_out_of_range)) { return true; From 9402bb53f66c53d335587a790b900354e3158549 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 13 Sep 2023 00:45:25 +0900 Subject: [PATCH 192/547] feat(goal_planner): use only static objects in pull over lanes to path generation (#4968) Signed-off-by: kosuke55 --- .../goal_planner/goal_planner.param.yaml | 1 + .../goal_planner/goal_planner_parameters.hpp | 1 + .../goal_planner/goal_planner_module.cpp | 10 +++++++++- .../src/scene_module/goal_planner/manager.cpp | 1 + .../src/utils/goal_planner/goal_searcher.cpp | 18 ++++++++++++++---- 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index e77f7726015af..64f88e064451f 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -32,6 +32,7 @@ use_object_recognition: true object_recognition_collision_check_margin: 0.6 # this should be larger than `surround_check_distance` of surround_obstacle_checker object_recognition_collision_check_max_extra_stopping_margin: 1.0 + th_moving_object_velocity: 1.0 # pull over pull_over: diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index c458d017f518c..d2577c68cabfb 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -69,6 +69,7 @@ struct GoalPlannerParameters bool use_object_recognition; double object_recognition_collision_check_margin; double object_recognition_collision_check_max_extra_stopping_margin; + double th_moving_object_velocity; // pull over general params double pull_over_minimum_request_length; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 488b23ac0aec9..50daf381cf0dd 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -1300,12 +1300,20 @@ bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const } if (parameters_->use_object_recognition) { + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, + parameters_->forward_goal_search_length); + const auto [pull_over_lane_objects, others] = + utils::path_safety_checker::separateObjectsByLanelets( + *(planner_data_->dynamic_object), pull_over_lanes); + const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( + pull_over_lane_objects, parameters_->th_moving_object_velocity); const auto common_parameters = planner_data_->parameters; const double base_link2front = common_parameters.base_link2front; const double base_link2rear = common_parameters.base_link2rear; const double vehicle_width = common_parameters.vehicle_width; if (utils::path_safety_checker::checkCollisionWithExtraStoppingMargin( - path, *(planner_data_->dynamic_object), base_link2front, base_link2rear, vehicle_width, + path, pull_over_lane_stop_objects, base_link2front, base_link2rear, vehicle_width, parameters_->maximum_deceleration, parameters_->object_recognition_collision_check_margin, parameters_->object_recognition_collision_check_max_extra_stopping_margin)) { return true; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index ba147a28173c3..cd01aeb522c07 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -90,6 +90,7 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( p.object_recognition_collision_check_max_extra_stopping_margin = node->declare_parameter( ns + "object_recognition_collision_check_max_extra_stopping_margin"); + p.th_moving_object_velocity = node->declare_parameter(ns + "th_moving_object_velocity"); } // pull over general params diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index 131eb3dd36731..ade4a08b163c8 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -155,16 +155,18 @@ void GoalSearcher::update(GoalCandidates & goal_candidates) const continue; } - // check margin with pull over lane objects + // check longitudinal margin with pull over lane objects const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, parameters_.forward_goal_search_length); - const auto [shoulder_lane_objects, others] = + const auto [pull_over_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( *(planner_data_->dynamic_object), pull_over_lanes); + const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( + pull_over_lane_objects, parameters_.th_moving_object_velocity); constexpr bool filter_inside = true; const auto target_objects = goal_planner_utils::filterObjectsByLateralDistance( - goal_pose, planner_data_->parameters.vehicle_width, shoulder_lane_objects, + goal_pose, planner_data_->parameters.vehicle_width, pull_over_lane_stop_objects, parameters_.object_recognition_collision_check_margin, filter_inside); if (checkCollisionWithLongitudinalDistance(goal_pose, target_objects)) { goal_candidate.is_safe = false; @@ -188,8 +190,16 @@ bool GoalSearcher::checkCollision(const Pose & pose) const } if (parameters_.use_object_recognition) { + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, + parameters_.forward_goal_search_length); + const auto [pull_over_lane_objects, others] = + utils::path_safety_checker::separateObjectsByLanelets( + *(planner_data_->dynamic_object), pull_over_lanes); + const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( + pull_over_lane_objects, parameters_.th_moving_object_velocity); if (utils::checkCollisionBetweenFootprintAndObjects( - vehicle_footprint_, pose, *(planner_data_->dynamic_object), + vehicle_footprint_, pose, pull_over_lane_stop_objects, parameters_.object_recognition_collision_check_margin)) { return true; } From 535537db1ab593aaff931c0866f4c92d05e21695 Mon Sep 17 00:00:00 2001 From: badai nguyen <94814556+badai-nguyen@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:51:17 +0900 Subject: [PATCH 193/547] fix(tensorrt_yolox): update yolox-s model (#4949) * fix(tensorrt_yolox): update yolox-s model Signed-off-by: badai-nguyen * fix: cspell check Signed-off-by: badai-nguyen * fix: typo Signed-off-by: badai-nguyen * fix: lint_cmake check Signed-off-by: badai-nguyen --------- Signed-off-by: badai-nguyen --- perception/tensorrt_yolox/CMakeLists.txt | 17 ++++++++++------- .../launch/yolox_s_plus_opt.launch.xml | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/perception/tensorrt_yolox/CMakeLists.txt b/perception/tensorrt_yolox/CMakeLists.txt index 01ade9bac43d6..80a7f0964b4bc 100644 --- a/perception/tensorrt_yolox/CMakeLists.txt +++ b/perception/tensorrt_yolox/CMakeLists.txt @@ -30,7 +30,7 @@ set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") if(NOT EXISTS "${DATA_PATH}") execute_process(COMMAND mkdir -p ${DATA_PATH}) endif() -function(download FILE_NAME FILE_HASH) +function(download SUB_DIR FILE_NAME FILE_HASH) message(STATUS "Checking and downloading ${FILE_NAME}") set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) set(STATUS_CODE 0) @@ -44,14 +44,14 @@ function(download FILE_NAME FILE_HASH) else() message(STATUS "diff ${FILE_NAME}") message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) + file(DOWNLOAD https://awf.ml.dev.web.auto/perception/${SUB_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) list(GET DOWNLOAD_STATUS 0 STATUS_CODE) list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) endif() else() message(STATUS "not found ${FILE_NAME}") message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) + file(DOWNLOAD https://awf.ml.dev.web.auto/perception/${SUB_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) list(GET DOWNLOAD_STATUS 0 STATUS_CODE) list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) endif() @@ -62,10 +62,13 @@ function(download FILE_NAME FILE_HASH) endif() endfunction() -download(yolox-tiny.onnx 97028baf73ce55e115599c9c60651b08) -download(yolox-sPlus-opt.onnx bf3b0155351f90fcdca2626acbfd3bcf) -download(yolox-sPlus-opt.EntropyV2-calibration.table c6e6f1999d5724a017516a956096701f) -download(label.txt 9ceadca8b72b6169ee6aabb861fe3e1e) +set(V1_PATH models/object_detection_yolox_s/v1) +download(models yolox-tiny.onnx 97028baf73ce55e115599c9c60651b08) +download(models yolox-sPlus-opt.onnx bf3b0155351f90fcdca2626acbfd3bcf) +download(models yolox-sPlus-opt.EntropyV2-calibration.table c6e6f1999d5724a017516a956096701f) +download(${V1_PATH} yolox-sPlus-T4-960x960-pseudo-finetune.onnx 37165a7e67bdaf6acff93925c628f2b2) +download(${V1_PATH} yolox-sPlus-T4-960x960-pseudo-finetune.EntropyV2-calibration.table d266ea9846b10e4dab53572152c6fd8f) +download(models label.txt 9ceadca8b72b6169ee6aabb861fe3e1e) ########## # tensorrt_yolox diff --git a/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml b/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml index 43f59e944f889..cb89f5829c65d 100644 --- a/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml +++ b/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml @@ -1,8 +1,9 @@ + - + From 4247a70cc1b3a202b67f2d4d7a7bf72ceeebc4a7 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:09:12 +0900 Subject: [PATCH 194/547] feat(tier4_system_rviz_plugin): add package (#4945) * feat(tier4_system_rviz_plugin): add package Signed-off-by: kminoda * update maintainer Signed-off-by: kminoda * style(pre-commit): autofix * minor update Signed-off-by: kminoda * style(pre-commit): autofix * update some arguments Signed-off-by: kminoda * style(pre-commit): autofix * fix pre-commit Signed-off-by: kminoda * style(pre-commit): autofix * update maintainer Signed-off-by: kminoda * fix test Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../tier4_system_rviz_plugin/CMakeLists.txt | 36 +++ common/tier4_system_rviz_plugin/README.md | 1 + common/tier4_system_rviz_plugin/package.xml | 30 +++ .../plugins/plugin_description.xml | 6 + .../src/jsk_overlay_utils.cpp | 222 ++++++++++++++++ .../src/jsk_overlay_utils.hpp | 138 ++++++++++ .../src/mrm_summary_overlay_display.cpp | 251 ++++++++++++++++++ .../src/mrm_summary_overlay_display.hpp | 109 ++++++++ 8 files changed, 793 insertions(+) create mode 100644 common/tier4_system_rviz_plugin/CMakeLists.txt create mode 100644 common/tier4_system_rviz_plugin/README.md create mode 100644 common/tier4_system_rviz_plugin/package.xml create mode 100644 common/tier4_system_rviz_plugin/plugins/plugin_description.xml create mode 100644 common/tier4_system_rviz_plugin/src/jsk_overlay_utils.cpp create mode 100644 common/tier4_system_rviz_plugin/src/jsk_overlay_utils.hpp create mode 100644 common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp create mode 100644 common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp diff --git a/common/tier4_system_rviz_plugin/CMakeLists.txt b/common/tier4_system_rviz_plugin/CMakeLists.txt new file mode 100644 index 0000000000000..a65379c553131 --- /dev/null +++ b/common/tier4_system_rviz_plugin/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.14) +project(tier4_system_rviz_plugin) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +find_package(Qt5 REQUIRED Core Widgets) +set(QT_LIBRARIES Qt5::Widgets) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +add_definitions(-DQT_NO_KEYWORDS) + +set(HEADERS + src/mrm_summary_overlay_display.hpp + src/jsk_overlay_utils.hpp +) + +## Declare a C++ library +ament_auto_add_library(tier4_system_rviz_plugin SHARED + src/mrm_summary_overlay_display.cpp + src/jsk_overlay_utils.cpp + ${HEADERS} +) + +target_link_libraries(tier4_system_rviz_plugin + ${QT_LIBRARIES} +) + +# Export the plugin to be imported by rviz2 +pluginlib_export_plugin_description_file(rviz_common plugins/plugin_description.xml) + +ament_auto_package( + INSTALL_TO_SHARE + plugins +) diff --git a/common/tier4_system_rviz_plugin/README.md b/common/tier4_system_rviz_plugin/README.md new file mode 100644 index 0000000000000..098844c8b4091 --- /dev/null +++ b/common/tier4_system_rviz_plugin/README.md @@ -0,0 +1 @@ +# tier4_system_rviz_plugin diff --git a/common/tier4_system_rviz_plugin/package.xml b/common/tier4_system_rviz_plugin/package.xml new file mode 100644 index 0000000000000..adae997ea07aa --- /dev/null +++ b/common/tier4_system_rviz_plugin/package.xml @@ -0,0 +1,30 @@ + + + + tier4_system_rviz_plugin + 0.1.0 + The tier4_vehicle_rviz_plugin package + Koji Minoda + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + + autoware_auto_system_msgs + diagnostic_msgs + libqt5-core + libqt5-gui + libqt5-widgets + qtbase5-dev + rviz_common + rviz_default_plugins + rviz_ogre_vendor + tier4_autoware_utils + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/common/tier4_system_rviz_plugin/plugins/plugin_description.xml b/common/tier4_system_rviz_plugin/plugins/plugin_description.xml new file mode 100644 index 0000000000000..a3ac26d1a304d --- /dev/null +++ b/common/tier4_system_rviz_plugin/plugins/plugin_description.xml @@ -0,0 +1,6 @@ + + + + diff --git a/common/tier4_system_rviz_plugin/src/jsk_overlay_utils.cpp b/common/tier4_system_rviz_plugin/src/jsk_overlay_utils.cpp new file mode 100644 index 0000000000000..36c5d1ce82693 --- /dev/null +++ b/common/tier4_system_rviz_plugin/src/jsk_overlay_utils.cpp @@ -0,0 +1,222 @@ +// Copyright 2022 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2014, JSK Lab +// All rights reserved. +// +// Software License Agreement (BSD License) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of {copyright_holder} nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE.S SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "jsk_overlay_utils.hpp" + +#include + +namespace jsk_rviz_plugins +{ +ScopedPixelBuffer::ScopedPixelBuffer(Ogre::HardwarePixelBufferSharedPtr pixel_buffer) +: pixel_buffer_(pixel_buffer) +{ + pixel_buffer_->lock(Ogre::HardwareBuffer::HBL_NORMAL); +} + +ScopedPixelBuffer::~ScopedPixelBuffer() +{ + pixel_buffer_->unlock(); +} + +Ogre::HardwarePixelBufferSharedPtr ScopedPixelBuffer::getPixelBuffer() +{ + return pixel_buffer_; +} + +QImage ScopedPixelBuffer::getQImage(unsigned int width, unsigned int height) +{ + const Ogre::PixelBox & pixelBox = pixel_buffer_->getCurrentLock(); + Ogre::uint8 * pDest = static_cast(pixelBox.data); + memset(pDest, 0, width * height); + return QImage(pDest, width, height, QImage::Format_ARGB32); +} + +QImage ScopedPixelBuffer::getQImage(unsigned int width, unsigned int height, QColor & bg_color) +{ + QImage Hud = getQImage(width, height); + for (unsigned int i = 0; i < width; i++) { + for (unsigned int j = 0; j < height; j++) { + Hud.setPixel(i, j, bg_color.rgba()); + } + } + return Hud; +} + +QImage ScopedPixelBuffer::getQImage(OverlayObject & overlay) +{ + return getQImage(overlay.getTextureWidth(), overlay.getTextureHeight()); +} + +QImage ScopedPixelBuffer::getQImage(OverlayObject & overlay, QColor & bg_color) +{ + return getQImage(overlay.getTextureWidth(), overlay.getTextureHeight(), bg_color); +} + +OverlayObject::OverlayObject(const std::string & name) : name_(name) +{ + std::string material_name = name_ + "Material"; + Ogre::OverlayManager * mOverlayMgr = Ogre::OverlayManager::getSingletonPtr(); + overlay_ = mOverlayMgr->create(name_); + panel_ = static_cast( + mOverlayMgr->createOverlayElement("Panel", name_ + "Panel")); + panel_->setMetricsMode(Ogre::GMM_PIXELS); + + panel_material_ = Ogre::MaterialManager::getSingleton().create( + material_name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + panel_->setMaterialName(panel_material_->getName()); + overlay_->add2D(panel_); +} + +OverlayObject::~OverlayObject() +{ + hide(); + panel_material_->unload(); + Ogre::MaterialManager::getSingleton().remove(panel_material_->getName()); + // Ogre::OverlayManager* mOverlayMgr = Ogre::OverlayManager::getSingletonPtr(); + // mOverlayMgr->destroyOverlayElement(panel_); + // delete panel_; + // delete overlay_; +} + +std::string OverlayObject::getName() +{ + return name_; +} + +void OverlayObject::hide() +{ + if (overlay_->isVisible()) { + overlay_->hide(); + } +} + +void OverlayObject::show() +{ + if (!overlay_->isVisible()) { + overlay_->show(); + } +} + +bool OverlayObject::isTextureReady() +{ + return static_cast(texture_); +} + +void OverlayObject::updateTextureSize(unsigned int width, unsigned int height) +{ + const std::string texture_name = name_ + "Texture"; + if (width == 0) { + RCLCPP_WARN(rclcpp::get_logger("OverlayObject"), "width=0 is specified as texture size"); + width = 1; + } + if (height == 0) { + RCLCPP_WARN(rclcpp::get_logger("OverlayObject"), "height=0 is specified as texture size"); + height = 1; + } + if (!isTextureReady() || ((width != texture_->getWidth()) || (height != texture_->getHeight()))) { + if (isTextureReady()) { + Ogre::TextureManager::getSingleton().remove(texture_name); + panel_material_->getTechnique(0)->getPass(0)->removeAllTextureUnitStates(); + } + texture_ = Ogre::TextureManager::getSingleton().createManual( + texture_name, // name + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, // type + width, height, // width & height of the render window + 0, // number of mipmaps + Ogre::PF_A8R8G8B8, // pixel format chosen to match a format Qt can use + Ogre::TU_DEFAULT // usage + ); + panel_material_->getTechnique(0)->getPass(0)->createTextureUnitState(texture_name); + + panel_material_->getTechnique(0)->getPass(0)->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + } +} + +ScopedPixelBuffer OverlayObject::getBuffer() +{ + if (isTextureReady()) { + return ScopedPixelBuffer(texture_->getBuffer()); + } else { + return ScopedPixelBuffer(Ogre::HardwarePixelBufferSharedPtr()); + } +} + +void OverlayObject::setPosition(double left, double top) +{ + panel_->setPosition(left, top); +} + +void OverlayObject::setDimensions(double width, double height) +{ + panel_->setDimensions(width, height); +} + +bool OverlayObject::isVisible() +{ + return overlay_->isVisible(); +} + +unsigned int OverlayObject::getTextureWidth() +{ + if (isTextureReady()) { + return texture_->getWidth(); + } else { + return 0; + } +} + +unsigned int OverlayObject::getTextureHeight() +{ + if (isTextureReady()) { + return texture_->getHeight(); + } else { + return 0; + } +} + +} // namespace jsk_rviz_plugins diff --git a/common/tier4_system_rviz_plugin/src/jsk_overlay_utils.hpp b/common/tier4_system_rviz_plugin/src/jsk_overlay_utils.hpp new file mode 100644 index 0000000000000..568111f241a28 --- /dev/null +++ b/common/tier4_system_rviz_plugin/src/jsk_overlay_utils.hpp @@ -0,0 +1,138 @@ +// Copyright 2022 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2014, JSK Lab +// All rights reserved. +// +// Software License Agreement (BSD License) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of {copyright_holder} nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE.S SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef JSK_OVERLAY_UTILS_HPP_ +#define JSK_OVERLAY_UTILS_HPP_ + +#include +#include +#include +#include +#include + +#include +#include +// see OGRE/OgrePrerequisites.h +// #define OGRE_VERSION +// ((OGRE_VERSION_MAJOR << 16) | (OGRE_VERSION_MINOR << 8) | OGRE_VERSION_PATCH) +#if OGRE_VERSION < ((1 << 16) | (9 << 8) | 0) +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +namespace jsk_rviz_plugins +{ +class OverlayObject; + +class ScopedPixelBuffer +{ +public: + explicit ScopedPixelBuffer(Ogre::HardwarePixelBufferSharedPtr pixel_buffer); + virtual ~ScopedPixelBuffer(); + virtual Ogre::HardwarePixelBufferSharedPtr getPixelBuffer(); + virtual QImage getQImage(unsigned int width, unsigned int height); + virtual QImage getQImage(OverlayObject & overlay); + virtual QImage getQImage(unsigned int width, unsigned int height, QColor & bg_color); + virtual QImage getQImage(OverlayObject & overlay, QColor & bg_color); + +protected: + Ogre::HardwarePixelBufferSharedPtr pixel_buffer_; + +private: +}; + +// this is a class for put overlay object on rviz 3D panel. +// This class suppose to be instantiated in onInitialize method +// of rviz::Display class. +class OverlayObject +{ +public: + typedef std::shared_ptr Ptr; + + explicit OverlayObject(const std::string & name); + virtual ~OverlayObject(); + + virtual std::string getName(); + virtual void hide(); + virtual void show(); + virtual bool isTextureReady(); + virtual void updateTextureSize(unsigned int width, unsigned int height); + virtual ScopedPixelBuffer getBuffer(); + virtual void setPosition(double left, double top); + virtual void setDimensions(double width, double height); + virtual bool isVisible(); + virtual unsigned int getTextureWidth(); + virtual unsigned int getTextureHeight(); + +protected: + const std::string name_; + Ogre::Overlay * overlay_; + Ogre::PanelOverlayElement * panel_; + Ogre::MaterialPtr panel_material_; + Ogre::TexturePtr texture_; + +private: +}; + +// Ogre::Overlay* createOverlay(std::string name); +// Ogre::PanelOverlayElement* createOverlayPanel(Ogre::Overlay* overlay); +// Ogre::MaterialPtr createOverlayMaterial(Ogre::Overlay* overlay); +} // namespace jsk_rviz_plugins + +#endif // JSK_OVERLAY_UTILS_HPP_ diff --git a/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp new file mode 100644 index 0000000000000..b80f06f645632 --- /dev/null +++ b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp @@ -0,0 +1,251 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2014, JSK Lab +// All rights reserved. +// +// Software License Agreement (BSD License) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of {copyright_holder} nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE.S SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include "mrm_summary_overlay_display.hpp" + +#include +#include + +#include + +#include + +#include +#include +#include +#include + +namespace rviz_plugins +{ +void insertNewlines(std::string & str, const size_t max_line_length) +{ + size_t index = max_line_length; + + while (index < str.size()) { + str.insert(index, "\n"); + index = index + max_line_length + 1; + } +} + +std::optional generateMrmMessage(diagnostic_msgs::msg::DiagnosticStatus diag_status) +{ + if (diag_status.hardware_id == "" || diag_status.hardware_id == "system_error_monitor") { + return std::nullopt; + } else if (diag_status.level == diagnostic_msgs::msg::DiagnosticStatus::ERROR) { + std::string msg = "- ERROR: " + diag_status.name + " (" + diag_status.message + ")"; + insertNewlines(msg, 100); + return msg; + } else if (diag_status.level == diagnostic_msgs::msg::DiagnosticStatus::WARN) { + std::string msg = "- WARN: " + diag_status.name + " (" + diag_status.message + ")"; + insertNewlines(msg, 100); + return msg; + } + return std::nullopt; +} + +MrmSummaryOverlayDisplay::MrmSummaryOverlayDisplay() +{ + const Screen * screen_info = DefaultScreenOfDisplay(XOpenDisplay(NULL)); + + constexpr float hight_4k = 2160.0; + const float scale = static_cast(screen_info->height) / hight_4k; + const auto left = static_cast(std::round(1024 * scale)); + const auto top = static_cast(std::round(128 * scale)); + + property_text_color_ = new rviz_common::properties::ColorProperty( + "Text Color", QColor(25, 255, 240), "text color", this, SLOT(updateVisualization()), this); + property_left_ = new rviz_common::properties::IntProperty( + "Left", left, "Left of the plotter window", this, SLOT(updateVisualization()), this); + property_left_->setMin(0); + property_top_ = new rviz_common::properties::IntProperty( + "Top", top, "Top of the plotter window", this, SLOT(updateVisualization())); + property_top_->setMin(0); + + property_value_height_offset_ = new rviz_common::properties::IntProperty( + "Value height offset", 0, "Height offset of the plotter window", this, + SLOT(updateVisualization())); + property_font_size_ = new rviz_common::properties::IntProperty( + "Font Size", 10, "Font Size", this, SLOT(updateVisualization()), this); + property_font_size_->setMin(1); + property_max_letter_num_ = new rviz_common::properties::IntProperty( + "Max Letter Num", 100, "Max Letter Num", this, SLOT(updateVisualization()), this); + property_max_letter_num_->setMin(10); +} + +MrmSummaryOverlayDisplay::~MrmSummaryOverlayDisplay() +{ + if (initialized()) { + overlay_->hide(); + } +} + +void MrmSummaryOverlayDisplay::onInitialize() +{ + RTDClass::onInitialize(); + static int count = 0; + rviz_common::UniformStringStream ss; + ss << "MrmSummaryOverlayDisplayObject" << count++; + overlay_.reset(new jsk_rviz_plugins::OverlayObject(ss.str())); + + overlay_->show(); + + const int texture_size = property_font_size_->getInt() * property_max_letter_num_->getInt(); + overlay_->updateTextureSize(texture_size, texture_size); + overlay_->setPosition(property_left_->getInt(), property_top_->getInt()); + overlay_->setDimensions(overlay_->getTextureWidth(), overlay_->getTextureHeight()); +} + +void MrmSummaryOverlayDisplay::onEnable() +{ + subscribe(); + overlay_->show(); +} + +void MrmSummaryOverlayDisplay::onDisable() +{ + unsubscribe(); + reset(); + overlay_->hide(); +} + +void MrmSummaryOverlayDisplay::update(float wall_dt, float ros_dt) +{ + (void)wall_dt; + (void)ros_dt; + + // MRM summary + std::vector mrm_comfortable_stop_summary_list = {}; + std::vector mrm_emergency_stop_summary_list = {}; + { + std::lock_guard message_lock(mutex_); + if (last_msg_ptr_) { + for (const auto & diag_status : last_msg_ptr_->status.diag_latent_fault) { + const std::optional msg = generateMrmMessage(diag_status); + if (msg != std::nullopt) { + mrm_comfortable_stop_summary_list.push_back(msg.value()); + } + } + for (const auto & diag_status : last_msg_ptr_->status.diag_single_point_fault) { + const std::optional msg = generateMrmMessage(diag_status); + if (msg != std::nullopt) { + mrm_emergency_stop_summary_list.push_back(msg.value()); + } + } + } + } + + // Display + QColor background_color; + background_color.setAlpha(0); + jsk_rviz_plugins::ScopedPixelBuffer buffer = overlay_->getBuffer(); + QImage hud = buffer.getQImage(*overlay_); + hud.fill(background_color); + + QPainter painter(&hud); + painter.setRenderHint(QPainter::Antialiasing, true); + + const int w = overlay_->getTextureWidth() - line_width_; + const int h = overlay_->getTextureHeight() - line_width_; + + // text + QColor text_color(property_text_color_->getColor()); + text_color.setAlpha(255); + painter.setPen(QPen(text_color, static_cast(2), Qt::SolidLine)); + QFont font = painter.font(); + font.setPixelSize(property_font_size_->getInt()); + font.setBold(true); + painter.setFont(font); + + std::ostringstream output_text; + output_text << std::fixed + << "Comfortable Stop MRM Summary: " << int(mrm_comfortable_stop_summary_list.size()) + << std::endl; + for (const auto & mrm_element : mrm_comfortable_stop_summary_list) { + output_text << mrm_element << std::endl; + } + output_text << "Emergency Stop MRM Summary: " << int(mrm_emergency_stop_summary_list.size()) + << std::endl; + for (const auto & mrm_element : mrm_emergency_stop_summary_list) { + output_text << mrm_element << std::endl; + } + + // same as above, but align on right side + painter.drawText( + 0, std::min(property_value_height_offset_->getInt(), h - 1), w, + std::max(h - property_value_height_offset_->getInt(), 1), Qt::AlignLeft | Qt::AlignTop, + output_text.str().c_str()); + painter.end(); + updateVisualization(); +} + +void MrmSummaryOverlayDisplay::processMessage( + const autoware_auto_system_msgs::msg::HazardStatusStamped::ConstSharedPtr msg_ptr) +{ + if (!isEnabled()) { + return; + } + + { + std::lock_guard message_lock(mutex_); + last_msg_ptr_ = msg_ptr; + } + + queueRender(); +} + +void MrmSummaryOverlayDisplay::updateVisualization() +{ + const int texture_size = property_font_size_->getInt() * property_max_letter_num_->getInt(); + overlay_->updateTextureSize(texture_size, texture_size); + overlay_->setPosition(property_left_->getInt(), property_top_->getInt()); + overlay_->setDimensions(overlay_->getTextureWidth(), overlay_->getTextureHeight()); +} + +} // namespace rviz_plugins + +#include +PLUGINLIB_EXPORT_CLASS(rviz_plugins::MrmSummaryOverlayDisplay, rviz_common::Display) diff --git a/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp new file mode 100644 index 0000000000000..2ca0364e35d83 --- /dev/null +++ b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp @@ -0,0 +1,109 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright (c) 2014, JSK Lab +// All rights reserved. +// +// Software License Agreement (BSD License) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of {copyright_holder} nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE.S SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef MRM_SUMMARY_OVERLAY_DISPLAY_HPP_ +#define MRM_SUMMARY_OVERLAY_DISPLAY_HPP_ + +#include +#include + +#ifndef Q_MOC_RUN +#include "jsk_overlay_utils.hpp" + +#include +#include +#include +#include +#include + +#include + +#endif + +namespace rviz_plugins +{ +class MrmSummaryOverlayDisplay +: public rviz_common::RosTopicDisplay + +{ + Q_OBJECT + +public: + MrmSummaryOverlayDisplay(); + ~MrmSummaryOverlayDisplay() override; + + void onInitialize() override; + void onEnable() override; + void onDisable() override; + +private Q_SLOTS: + void updateVisualization(); + +protected: + void update(float wall_dt, float ros_dt) override; + void processMessage( + const autoware_auto_system_msgs::msg::HazardStatusStamped::ConstSharedPtr msg_ptr) override; + jsk_rviz_plugins::OverlayObject::Ptr overlay_; + rviz_common::properties::ColorProperty * property_text_color_; + rviz_common::properties::IntProperty * property_left_; + rviz_common::properties::IntProperty * property_top_; + rviz_common::properties::IntProperty * property_value_height_offset_; + rviz_common::properties::FloatProperty * property_value_scale_; + rviz_common::properties::IntProperty * property_font_size_; + rviz_common::properties::IntProperty * property_max_letter_num_; + // QImage hud_; + +private: + static constexpr int line_width_ = 2; + static constexpr int hand_width_ = 4; + + std::mutex mutex_; + autoware_auto_system_msgs::msg::HazardStatusStamped::ConstSharedPtr last_msg_ptr_; +}; +} // namespace rviz_plugins + +#endif // MRM_SUMMARY_OVERLAY_DISPLAY_HPP_ From a959ae711d2e49064c25f4bd9f21a6fd9455d2db Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 13 Sep 2023 18:14:05 +0900 Subject: [PATCH 195/547] chore(build): only include forward declaration of lanelet stuff in control/ header files (#4973) --- .../lane_departure_checker/lane_departure_checker_node.hpp | 4 ++-- .../lane_departure_checker_node.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp index c146957bbd314..dd05ab226f6b7 100644 --- a/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp +++ b/control/lane_departure_checker/include/lane_departure_checker/lane_departure_checker_node.hpp @@ -18,8 +18,6 @@ #include "lane_departure_checker/lane_departure_checker.hpp" #include -#include -#include #include #include #include @@ -34,6 +32,8 @@ #include #include +#include +#include #include #include diff --git a/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp b/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp index f788e64316171..b40a0d0473135 100644 --- a/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp +++ b/control/lane_departure_checker/src/lane_departure_checker_node/lane_departure_checker_node.cpp @@ -14,8 +14,10 @@ #include "lane_departure_checker/lane_departure_checker_node.hpp" +#include #include #include +#include #include #include #include From dd1ec18d2ed9e8e194355b1e2e1c611d7312a75c Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:16:25 +0900 Subject: [PATCH 196/547] fix(lane_change): fix target object filter (#4930) * fix(lane_change): fix target object filter Signed-off-by: Muhammad Zulfaqar Azmi * used lateral distance from centerline instead Signed-off-by: Zulfaqar Azmi --------- Signed-off-by: Muhammad Zulfaqar Azmi Signed-off-by: Zulfaqar Azmi --- .../src/scene_module/lane_change/normal.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index a81f6227e4064..a91da98b11999 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -666,6 +666,8 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( utils::lane_change::createPolygon(target_lanes, 0.0, std::numeric_limits::max()); const auto target_backward_polygon = utils::lane_change::createPolygon( target_backward_lanes, 0.0, std::numeric_limits::max()); + const auto dist_ego_to_current_lanes_center = + lanelet::utils::getLateralDistanceToClosestLanelet(current_lanes, current_pose); LaneChangeTargetObjectIndices filtered_obj_indices; for (size_t i = 0; i < objects.objects.size(); ++i) { @@ -694,6 +696,14 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( min_dist_ego_to_obj = std::min(dist_ego_to_obj, min_dist_ego_to_obj); } + const auto is_lateral_far = [&]() { + const auto dist_object_to_current_lanes_center = + lanelet::utils::getLateralDistanceToClosestLanelet( + current_lanes, object.kinematics.initial_pose_with_covariance.pose); + const auto lateral = dist_object_to_current_lanes_center - dist_ego_to_current_lanes_center; + return std::abs(lateral) > (common_parameters.vehicle_width / 2); + }; + // ignore static object that are behind the ego vehicle if (obj_velocity_norm < 1.0 && max_dist_ego_to_obj < 0.0) { continue; @@ -704,8 +714,10 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( // TODO(watanabe): ignore static parked object that are in front of the ego vehicle in target // lanes - filtered_obj_indices.target_lane.push_back(i); - continue; + if (max_dist_ego_to_obj >= 0 || is_lateral_far()) { + filtered_obj_indices.target_lane.push_back(i); + continue; + } } // check if the object intersects with target backward lanes From be5fa16e53047a61a590fddd8fdc2951b2657b46 Mon Sep 17 00:00:00 2001 From: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:25:37 +0900 Subject: [PATCH 197/547] feat(traffic_light_visualization): update visualization for w/o fine_detection (#4967) * feat(traffic_light_visualization): update visualization for w/o fine_detection Signed-off-by: Shunsuke Miura * add comment Signed-off-by: Shunsuke Miura * style(pre-commit): autofix --------- Signed-off-by: Shunsuke Miura Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../traffic_light_roi_visualizer/nodelet.cpp | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/perception/traffic_light_visualization/src/traffic_light_roi_visualizer/nodelet.cpp b/perception/traffic_light_visualization/src/traffic_light_roi_visualizer/nodelet.cpp index a3b1a2b16a743..2f2d80ba43233 100644 --- a/perception/traffic_light_visualization/src/traffic_light_roi_visualizer/nodelet.cpp +++ b/perception/traffic_light_visualization/src/traffic_light_roi_visualizer/nodelet.cpp @@ -127,13 +127,25 @@ void TrafficLightRoiVisualizerNodelet::imageRoiCallback( { cv_bridge::CvImagePtr cv_ptr; try { - cv_ptr = cv_bridge::toCvCopy(input_image_msg, input_image_msg->encoding); + // try to convert to RGB8 from any input encoding, since createRect() only supports RGB8 based + // bbox drawing + cv_ptr = cv_bridge::toCvCopy(input_image_msg, sensor_msgs::image_encodings::RGB8); for (auto tl_roi : input_tl_roi_msg->rois) { - createRect(cv_ptr->image, tl_roi, cv::Scalar(0, 255, 0)); + ClassificationResult result; + bool has_correspond_traffic_signal = + getClassificationResult(tl_roi.traffic_light_id, *input_traffic_signals_msg, result); + + if (!has_correspond_traffic_signal) { + // does not have classification result + createRect(cv_ptr->image, tl_roi, cv::Scalar(255, 255, 255)); + } else { + // has classification result + createRect(cv_ptr->image, tl_roi, result); + } } } catch (cv_bridge::Exception & e) { RCLCPP_ERROR( - get_logger(), "Could not convert from '%s' to 'bgr8'.", input_image_msg->encoding.c_str()); + get_logger(), "Could not convert from '%s' to 'rgb8'.", input_image_msg->encoding.c_str()); } image_pub_.publish(cv_ptr->toImageMsg()); } @@ -182,7 +194,9 @@ void TrafficLightRoiVisualizerNodelet::imageRoughRoiCallback( { cv_bridge::CvImagePtr cv_ptr; try { - cv_ptr = cv_bridge::toCvCopy(input_image_msg, input_image_msg->encoding); + // try to convert to RGB8 from any input encoding, since createRect() only supports RGB8 based + // bbox drawing + cv_ptr = cv_bridge::toCvCopy(input_image_msg, sensor_msgs::image_encodings::RGB8); for (auto tl_rough_roi : input_tl_rough_roi_msg->rois) { // visualize rough roi createRect(cv_ptr->image, tl_rough_roi, cv::Scalar(0, 255, 0)); @@ -208,7 +222,7 @@ void TrafficLightRoiVisualizerNodelet::imageRoughRoiCallback( } } catch (cv_bridge::Exception & e) { RCLCPP_ERROR( - get_logger(), "Could not convert from '%s' to 'bgr8'.", input_image_msg->encoding.c_str()); + get_logger(), "Could not convert from '%s' to 'rgb8'.", input_image_msg->encoding.c_str()); } image_pub_.publish(cv_ptr->toImageMsg()); } From 33f62b43431ba18c353f2df09e7243ca71040969 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:25:28 +0200 Subject: [PATCH 198/547] build(imu_corrector): add missing diagnostic_updater dependency (#4980) Signed-off-by: Esteve Fernandez --- sensing/imu_corrector/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/sensing/imu_corrector/package.xml b/sensing/imu_corrector/package.xml index 7466d158da2df..3dff79bbf9f7f 100644 --- a/sensing/imu_corrector/package.xml +++ b/sensing/imu_corrector/package.xml @@ -13,6 +13,7 @@ ament_cmake_auto autoware_cmake + diagnostic_updater rclcpp rclcpp_components sensor_msgs From 424879c9b73a0b428486b233480edde6289bf28d Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:52:27 +0900 Subject: [PATCH 199/547] chore(multi_object_tracker): fix spell-check errors (#4985) * chore(multi_object_tracker): fix spell-check errors Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- perception/multi_object_tracker/models.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/perception/multi_object_tracker/models.md b/perception/multi_object_tracker/models.md index 8ca12e3191dc5..c83038ffa7b59 100644 --- a/perception/multi_object_tracker/models.md +++ b/perception/multi_object_tracker/models.md @@ -2,6 +2,8 @@ ## Tracking model + + ### CTRV model [1] CTRV model is a model that assumes constant turn rate and velocity magnitude. @@ -106,5 +108,10 @@ In other words, the offset value must be adjusted so that the input BBOX and the ## References -[1] Schubert, Robin & Richter, Eric & Wanielik, Gerd. (2008). Comparison and evaluation of advanced motion models for vehicle tracking. 1 - 6. 10.1109/ICIF.2008.4632283. + + +[1] Schubert, Robin & Richter, Eric & Wanielik, Gerd. (2008). Comparison and evaluation of advanced motion models for vehicle tracking. 1 - 6. 10.1109/ICIF.2008.4632283. + + + [2] Kong, Jason & Pfeiffer, Mark & Schildbach, Georg & Borrelli, Francesco. (2015). Kinematic and dynamic vehicle models for autonomous driving control design. 1094-1099. 10.1109/IVS.2015.7225830. From 06dd0517d2e7509debb9bc05dcc79a1b3df03a44 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:52:38 +0900 Subject: [PATCH 200/547] chore(lidar_centerpoint): fix spell-check errors (#4986) * chore(lidar_centerpoint): fix spell-check errors Signed-off-by: kminoda * update _tiny as well Signed-off-by: kminoda --------- Signed-off-by: kminoda --- .../centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz | 2 +- .../centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz index 9a390d10895f8..2fc71be9d34dc 100644 --- a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz +++ b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz @@ -1233,7 +1233,7 @@ Visualization Manager: Durability Policy: Volatile History Policy: Keep Last Reliability Policy: Reliable - Value: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/debug/startplanner + Value: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/debug/start_planner Value: false - Class: rviz_default_plugins/MarkerArray Enabled: false diff --git a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz index 8ec93bb5b50d6..4a4c42f2b5e8e 100644 --- a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz +++ b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz @@ -1234,7 +1234,7 @@ Visualization Manager: Durability Policy: Volatile History Policy: Keep Last Reliability Policy: Reliable - Value: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/debug/startplanner + Value: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner/debug/start_planner Value: false - Class: rviz_default_plugins/MarkerArray Enabled: false From 6e510b4448e4e6fc8856981578f591c3cd56ce82 Mon Sep 17 00:00:00 2001 From: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:14:18 +0900 Subject: [PATCH 201/547] fix(tier4_perception_launch): camera lidar fusion launch (#4983) fix: camera lidar fusion launch Signed-off-by: Shunsuke Miura --- .../camera_lidar_fusion_based_detection.launch.xml | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index 5f92d25022a22..eab2a56607dee 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -339,22 +339,11 @@ - - - - - - - - - - - - + From fc433340e4600107d47eeb741213befc78176041 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 14 Sep 2023 11:03:12 +0900 Subject: [PATCH 202/547] fix(rtc_manager_panel): fix panel chattering (#4988) Signed-off-by: satoshi-ota --- .../src/rtc_manager_panel.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/common/rtc_manager_rviz_plugin/src/rtc_manager_panel.cpp b/common/rtc_manager_rviz_plugin/src/rtc_manager_panel.cpp index ff448e694eb15..058a5d5deb5d6 100644 --- a/common/rtc_manager_rviz_plugin/src/rtc_manager_panel.cpp +++ b/common/rtc_manager_rviz_plugin/src/rtc_manager_panel.cpp @@ -366,7 +366,10 @@ void RTCManagerPanel::onRTCStatus(const CooperateStatusArray::ConstSharedPtr msg rtc_table_->clearContents(); num_rtc_status_ptr_->setText( QString::fromStdString("The Number of RTC Statuses: " + std::to_string(msg->statuses.size()))); - if (msg->statuses.empty()) return; + if (msg->statuses.empty()) { + rtc_table_->update(); + return; + } // this is to stable rtc display not to occupy too much size_t min_display_size{5}; size_t max_display_size{10}; @@ -374,8 +377,17 @@ void RTCManagerPanel::onRTCStatus(const CooperateStatusArray::ConstSharedPtr msg rtc_table_->setRowCount( std::max(min_display_size, std::min(msg->statuses.size(), max_display_size))); int cnt = 0; - for (auto status : msg->statuses) { - if (static_cast(cnt) >= max_display_size) return; + + auto sorted_statuses = msg->statuses; + std::partition(sorted_statuses.begin(), sorted_statuses.end(), [](const auto & status) { + return !status.auto_mode && !uint2bool(status.command_status.type); + }); + + for (auto status : sorted_statuses) { + if (static_cast(cnt) >= max_display_size) { + rtc_table_->update(); + return; + } // uuid { std::stringstream uuid; From 40d5e47a453fae074f0604e1065780d8b971fa5b Mon Sep 17 00:00:00 2001 From: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Date: Thu, 14 Sep 2023 13:59:30 +0900 Subject: [PATCH 203/547] change WARN to DEBUG (#4979) Signed-off-by: Shunsuke Miura --- perception/lidar_centerpoint/lib/centerpoint_trt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perception/lidar_centerpoint/lib/centerpoint_trt.cpp b/perception/lidar_centerpoint/lib/centerpoint_trt.cpp index 9b01ea8f53675..53e19500ff428 100644 --- a/perception/lidar_centerpoint/lib/centerpoint_trt.cpp +++ b/perception/lidar_centerpoint/lib/centerpoint_trt.cpp @@ -189,7 +189,7 @@ void CenterPointTRT::postProcess(std::vector & det_boxes3d) head_out_heatmap_d_.get(), head_out_offset_d_.get(), head_out_z_d_.get(), head_out_dim_d_.get(), head_out_rot_d_.get(), head_out_vel_d_.get(), det_boxes3d, stream_)); if (det_boxes3d.size() == 0) { - RCLCPP_WARN_STREAM(rclcpp::get_logger("lidar_centerpoint"), "No detected boxes."); + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("lidar_centerpoint"), "No detected boxes."); } } From eded1f187e4b742a095932b5b5c4646861042e0a Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:19:18 +0900 Subject: [PATCH 204/547] refactor(avoidance): align function style (#4976) * refactor(avoidance): align avoidance debug marker namespace Signed-off-by: satoshi-ota * refactor(avoidance): align function style Signed-off-by: satoshi-ota * chore(avoidance): add description Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.hpp | 47 ++-- .../utils/avoidance/avoidance_module_data.hpp | 39 +-- .../avoidance/avoidance_module.cpp | 237 ++++++++---------- 3 files changed, 161 insertions(+), 162 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp index 096ae61a9432e..a719ab341b88b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp @@ -325,10 +325,16 @@ class AvoidanceModule : public SceneModuleInterface * 3. merge raw shirt lines. * 4. trim unnecessary shift lines. */ - AvoidLineArray applyPreProcessToRawShiftLines( + AvoidLineArray applyPreProcess( AvoidLineArray & current_raw_shift_points, DebugData & debug) const; - AvoidLineArray getFillGapShiftLines(const AvoidLineArray & shift_lines) const; + /* + * @brief fill gap among shift lines. + * @param original shift lines. + * @param debug data. + * @return processed shift lines. + */ + AvoidLineArray applyFillGapProcess(const AvoidLineArray & shift_lines, DebugData & debug) const; /* * @brief merge negative & positive shift lines. @@ -336,7 +342,18 @@ class AvoidanceModule : public SceneModuleInterface * @param debug data. * @return processed shift lines. */ - AvoidLineArray mergeShiftLines(const AvoidLineArray & raw_shift_lines, DebugData & debug) const; + AvoidLineArray applyMergeProcess(const AvoidLineArray & shift_lines, DebugData & debug) const; + + /* + * @brief add return shift line from ego position. + * @param shift lines which the return shift is added. + * Pick up the last shift point, which is the most farthest from ego, from the current candidate + * avoidance points and registered points in the shifter. If the last shift length of the point is + * non-zero, add a return-shift to center line from the point. If there is no shift point in + * candidate avoidance points nor registered points, and base_shift > 0, add a return-shift to + * center line from ego. + */ + AvoidLineArray addReturnShiftLine(const AvoidLineArray & shift_lines, DebugData & debug) const; /* * @brief extract shift lines from total shift lines based on their gradient. @@ -356,7 +373,7 @@ class AvoidanceModule : public SceneModuleInterface * - Change the shift length to the previous one if the deviation is small. * - Remove unnecessary return shift (back to the center line). */ - AvoidLineArray trimShiftLine(const AvoidLineArray & shift_lines, DebugData & debug) const; + AvoidLineArray applyTrimProcess(const AvoidLineArray & shift_lines, DebugData & debug) const; /* * @brief extract new shift lines based on current shifted path. the module makes a RTC request @@ -366,17 +383,6 @@ class AvoidanceModule : public SceneModuleInterface */ AvoidLineArray findNewShiftLine(const AvoidLineArray & shift_lines) const; - /* - * @brief add return shift line from ego position. - * @param shift lines which the return shift is added. - * Pick up the last shift point, which is the most farthest from ego, from the current candidate - * avoidance points and registered points in the shifter. If the last shift length of the point is - * non-zero, add a return-shift to center line from the point. If there is no shift point in - * candidate avoidance points nor registered points, and base_shift > 0, add a return-shift to - * center line from ego. - */ - void addReturnShiftLineFromEgo(AvoidLineArray & shift_lines) const; - /* * @brief fill gap between two shift lines. * @param original shift lines. @@ -397,27 +403,27 @@ class AvoidanceModule : public SceneModuleInterface * @param threshold. shift length is quantized by this value. (if it is 0.3[m], the output shift * length is 0.0, 0.3, 0.6...) */ - void quantizeShiftLine(AvoidLineArray & shift_lines, const double threshold) const; + void applyQuantizeProcess(AvoidLineArray & shift_lines, const double threshold) const; /* * @brief trim shift line whose relative longitudinal distance is less than threshold. * @param target shift lines. * @param threshold. */ - void trimSmallShiftLine(AvoidLineArray & shift_lines, const double threshold) const; + void applySmallShiftFilter(AvoidLineArray & shift_lines, const double threshold) const; /* * @brief merge multiple shift lines whose relative gradient is less than threshold. * @param target shift lines. * @param threshold. */ - void trimSimilarGradShiftLine(AvoidLineArray & shift_lines, const double threshold) const; + void applySimilarGradFilter(AvoidLineArray & shift_lines, const double threshold) const; /* * @brief trim invalid shift lines whose gradient it too large to follow. * @param target shift lines. */ - void trimSharpReturn(AvoidLineArray & shift_lines, const double threshold) const; + void applySharpShiftFilter(AvoidLineArray & shift_lines, const double threshold) const; /** * @brief add new shift line to path shifter if the RTC status is activated. @@ -432,7 +438,8 @@ class AvoidanceModule : public SceneModuleInterface void addNewShiftLines(PathShifter & path_shifter, const AvoidLineArray & shift_lines) const; /** - * @brief validate shift lines. + * @brief once generate avoidance path from new shift lines, and calculate lateral offset between + * ego and the path. * @param new shift lines. * @param path shifter. * @return result. if there is huge gap between the ego position and candidate path, return false. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 215a8bfd97cc0..9a47a6728ddb7 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -525,22 +525,29 @@ struct DebugData lanelet::ConstLineStrings3d bounds; - AvoidLineArray current_shift_lines; // in path shifter - AvoidLineArray new_shift_lines; // in path shifter - - AvoidLineArray registered_raw_shift; - AvoidLineArray current_raw_shift; - AvoidLineArray extra_return_shift; - - AvoidLineArray merged; - AvoidLineArray gap_filled; - AvoidLineArray trim_similar_grad_shift; - AvoidLineArray quantized; - AvoidLineArray trim_small_shift; - AvoidLineArray trim_similar_grad_shift_second; - AvoidLineArray trim_similar_grad_shift_third; - AvoidLineArray trim_momentary_return; - AvoidLineArray trim_too_sharp_shift; + // combine process + AvoidLineArray step1_registered_shift_line; + AvoidLineArray step1_current_raw_shift_line; + AvoidLineArray step1_filled_shift_line; + AvoidLineArray step1_merged_shift_line; + AvoidLineArray step1_combined_shift_line; + AvoidLineArray step1_return_shift_line; + AvoidLineArray step1_front_shift_line; + AvoidLineArray step1_shift_line; + + // create outline process + AvoidLineArray step2_merged_shift_line; + + // trimming process + AvoidLineArray step3_quantized_shift_line; + AvoidLineArray step3_noise_removed; + AvoidLineArray step3_grad_filtered_first; + AvoidLineArray step3_grad_filtered_second; + AvoidLineArray step3_grad_filtered_third; + + // registered process + AvoidLineArray step4_new_shift_line; + AvoidLineArray step4_valid_shift_line; // shift length std::vector pos_shift; diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 51871b9f6c4f0..eee87a09a94db 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -395,7 +395,7 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de * STEP 2 * Modify the raw shift points. (Merging, Trimming) */ - const auto processed_raw_sp = applyPreProcessToRawShiftLines(data.unapproved_raw_sl, debug); + const auto processed_raw_sp = applyPreProcess(data.unapproved_raw_sl, debug); /** * STEP 3 @@ -533,9 +533,8 @@ void AvoidanceModule::fillEgoStatus( void AvoidanceModule::fillDebugData(const AvoidancePlanningData & data, DebugData & debug) const { - debug.output_shift = data.candidate_path.shift_length; - debug.current_raw_shift = data.unapproved_raw_sl; - debug.new_shift_lines = data.unapproved_new_sl; + debug.step1_current_raw_shift_line = data.unapproved_raw_sl; + debug.step4_new_shift_line = data.unapproved_new_sl; if (!data.stop_target_object) { return; @@ -678,13 +677,13 @@ void AvoidanceModule::updateRegisteredRawShiftLines() printShiftLines(avoid_lines, "registered_raw_shift_lines_ (after)"); registered_raw_shift_lines_ = avoid_lines; - debug_data_.registered_raw_shift = registered_raw_shift_lines_; + debug_data_.step1_registered_shift_line = registered_raw_shift_lines_; } -AvoidLineArray AvoidanceModule::applyPreProcessToRawShiftLines( +AvoidLineArray AvoidanceModule::applyPreProcess( AvoidLineArray & raw_shift_lines, DebugData & debug) const { - const auto fill_gap_shift_lines = getFillGapShiftLines(raw_shift_lines); + const auto fill_gap_shift_lines = applyFillGapProcess(raw_shift_lines, debug); /** * Use all registered points. For the current points, if the similar one of the current @@ -709,7 +708,7 @@ AvoidLineArray AvoidanceModule::applyPreProcessToRawShiftLines( // It is temporally solved by changing the threshold of trimSimilarGrad, but it needs to be // fixed in a proper way. // Maybe after merge, all shift points before the prepare distance can be deleted. - addReturnShiftLineFromEgo(raw_shift_lines); + raw_shift_lines = addReturnShiftLine(raw_shift_lines, debug); /* * Add gap filled shift lines so that merged shift lines connect smoothly. @@ -717,7 +716,7 @@ AvoidLineArray AvoidanceModule::applyPreProcessToRawShiftLines( fillShiftLineGap(raw_shift_lines); raw_shift_lines.insert( raw_shift_lines.end(), fill_gap_shift_lines.begin(), fill_gap_shift_lines.end()); - debug.gap_filled = raw_shift_lines; + debug.step1_filled_shift_line = raw_shift_lines; /** * On each path point, compute shift length with considering the raw shift points. @@ -728,8 +727,8 @@ AvoidLineArray AvoidanceModule::applyPreProcessToRawShiftLines( * Note: Because this function just foolishly extracts points, it includes * insignificant small (useless) shift points, which should be removed in post-process. */ - auto merged_shift_lines = mergeShiftLines(raw_shift_lines, debug); - debug.merged = merged_shift_lines; + auto merged_shift_lines = applyMergeProcess(raw_shift_lines, debug); + debug.step2_merged_shift_line = merged_shift_lines; /* * Remove unnecessary shift points @@ -738,7 +737,7 @@ AvoidLineArray AvoidanceModule::applyPreProcessToRawShiftLines( * - Combine shift points that have almost same gradient * - Remove unnecessary return shift (back to the center line). */ - auto shift_lines = trimShiftLine(merged_shift_lines, debug); + auto shift_lines = applyTrimProcess(merged_shift_lines, debug); DEBUG_PRINT("final shift point size = %lu", shift_lines.size()); return shift_lines; @@ -1222,7 +1221,8 @@ AvoidLineArray AvoidanceModule::extractShiftLinesFromLine(ShiftLineData & shift_ return merged_avoid_lines; } -AvoidLineArray AvoidanceModule::getFillGapShiftLines(const AvoidLineArray & shift_lines) const +AvoidLineArray AvoidanceModule::applyFillGapProcess( + const AvoidLineArray & shift_lines, [[maybe_unused]] DebugData & debug) const { AvoidLineArray ret{}; @@ -1301,7 +1301,7 @@ void AvoidanceModule::fillShiftLineGap(AvoidLineArray & shift_lines) const helper_.alignShiftLinesOrder(shift_lines, false); } -AvoidLineArray AvoidanceModule::mergeShiftLines( +AvoidLineArray AvoidanceModule::applyMergeProcess( const AvoidLineArray & raw_shift_lines, DebugData & debug) const { // Generate shift line by merging raw_shift_lines. @@ -1333,7 +1333,7 @@ AvoidLineArray AvoidanceModule::mergeShiftLines( return merged_shift_lines; } -AvoidLineArray AvoidanceModule::trimShiftLine( +AvoidLineArray AvoidanceModule::applyTrimProcess( const AvoidLineArray & shift_lines, DebugData & debug) const { if (shift_lines.empty()) { @@ -1348,41 +1348,37 @@ AvoidLineArray AvoidanceModule::trimShiftLine( // - Change the shift length to the previous one if the deviation is small. { constexpr double SHIFT_DIFF_THRES = 1.0; - trimSmallShiftLine(sl_array_trimmed, SHIFT_DIFF_THRES); + applySmallShiftFilter(sl_array_trimmed, SHIFT_DIFF_THRES); } // - Combine avoid points that have almost same gradient. // this is to remove the noise. { const auto THRESHOLD = parameters_->same_grad_filter_1_threshold; - trimSimilarGradShiftLine(sl_array_trimmed, THRESHOLD); - debug.trim_similar_grad_shift = sl_array_trimmed; - printShiftLines(sl_array_trimmed, "after trim_similar_grad_shift"); + applySimilarGradFilter(sl_array_trimmed, THRESHOLD); + debug.step3_grad_filtered_first = sl_array_trimmed; } // - Quantize the shift length to reduce the shift point noise // This is to remove the noise coming from detection accuracy, interpolation, resampling, etc. { const auto THRESHOLD = parameters_->quantize_filter_threshold; - quantizeShiftLine(sl_array_trimmed, THRESHOLD); - printShiftLines(sl_array_trimmed, "after sl_array_trimmed"); - debug.quantized = sl_array_trimmed; + applyQuantizeProcess(sl_array_trimmed, THRESHOLD); + debug.step3_quantized_shift_line = sl_array_trimmed; } // - Change the shift length to the previous one if the deviation is small. { constexpr double SHIFT_DIFF_THRES = 1.0; - trimSmallShiftLine(sl_array_trimmed, SHIFT_DIFF_THRES); - debug.trim_small_shift = sl_array_trimmed; - printShiftLines(sl_array_trimmed, "after trim_small_shift"); + applySmallShiftFilter(sl_array_trimmed, SHIFT_DIFF_THRES); + debug.step3_noise_removed = sl_array_trimmed; } // - Combine avoid points that have almost same gradient (again) { const auto THRESHOLD = parameters_->same_grad_filter_2_threshold; - trimSimilarGradShiftLine(sl_array_trimmed, THRESHOLD); - debug.trim_similar_grad_shift_second = sl_array_trimmed; - printShiftLines(sl_array_trimmed, "after trim_similar_grad_shift_second"); + applySimilarGradFilter(sl_array_trimmed, THRESHOLD); + debug.step3_grad_filtered_second = sl_array_trimmed; } // - trimTooSharpShift @@ -1390,23 +1386,21 @@ AvoidLineArray AvoidanceModule::trimShiftLine( // If the shift is sharp, it is combined with the next shift point until it gets non-sharp. { const auto THRESHOLD = parameters_->sharp_shift_filter_threshold; - trimSharpReturn(sl_array_trimmed, THRESHOLD); - debug.trim_too_sharp_shift = sl_array_trimmed; - printShiftLines(sl_array_trimmed, "after trimSharpReturn"); + applySharpShiftFilter(sl_array_trimmed, THRESHOLD); } // - Combine avoid points that have almost same gradient (again) { const auto THRESHOLD = parameters_->same_grad_filter_3_threshold; - trimSimilarGradShiftLine(sl_array_trimmed, THRESHOLD); - debug.trim_similar_grad_shift_third = sl_array_trimmed; - printShiftLines(sl_array_trimmed, "after trim_similar_grad_shift_second"); + applySimilarGradFilter(sl_array_trimmed, THRESHOLD); + debug.step3_grad_filtered_third = sl_array_trimmed; } return sl_array_trimmed; } -void AvoidanceModule::quantizeShiftLine(AvoidLineArray & shift_lines, const double threshold) const +void AvoidanceModule::applyQuantizeProcess( + AvoidLineArray & shift_lines, const double threshold) const { if (threshold < 1.0e-5) { return; // no need to process @@ -1419,7 +1413,8 @@ void AvoidanceModule::quantizeShiftLine(AvoidLineArray & shift_lines, const doub helper_.alignShiftLinesOrder(shift_lines); } -void AvoidanceModule::trimSmallShiftLine(AvoidLineArray & shift_lines, const double threshold) const +void AvoidanceModule::applySmallShiftFilter( + AvoidLineArray & shift_lines, const double threshold) const { if (shift_lines.empty()) { return; @@ -1437,7 +1432,7 @@ void AvoidanceModule::trimSmallShiftLine(AvoidLineArray & shift_lines, const dou } } -void AvoidanceModule::trimSimilarGradShiftLine( +void AvoidanceModule::applySimilarGradFilter( AvoidLineArray & avoid_lines, const double threshold) const { if (avoid_lines.empty()) { @@ -1501,7 +1496,8 @@ void AvoidanceModule::trimSimilarGradShiftLine( DEBUG_PRINT("size %lu -> %lu", input.size(), avoid_lines.size()); } -void AvoidanceModule::trimSharpReturn(AvoidLineArray & shift_lines, const double threshold) const +void AvoidanceModule::applySharpShiftFilter( + AvoidLineArray & shift_lines, const double threshold) const { AvoidLineArray shift_lines_orig = shift_lines; shift_lines.clear(); @@ -1638,30 +1634,31 @@ void AvoidanceModule::trimSharpReturn(AvoidLineArray & shift_lines, const double } helper_.alignShiftLinesOrder(shift_lines); - - DEBUG_PRINT("trimSharpReturn: size %lu -> %lu", shift_lines_orig.size(), shift_lines.size()); } -void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) const +AvoidLineArray AvoidanceModule::addReturnShiftLine( + const AvoidLineArray & shift_lines, DebugData & debug) const { constexpr double ep = 1.0e-3; const auto & data = avoid_data_; - const bool has_candidate_point = !sl_candidates.empty(); + const bool has_candidate_point = !shift_lines.empty(); const bool has_registered_point = !path_shifter_.getShiftLines().empty(); // If the return-to-center shift points are already registered, do nothing. if (!has_registered_point && std::fabs(getCurrentBaseShift()) < ep) { DEBUG_PRINT("No shift points, not base offset. Do not have to add return-shift."); - return; + return shift_lines; } constexpr double RETURN_SHIFT_THRESHOLD = 0.1; DEBUG_PRINT("registered last shift = %f", path_shifter_.getLastShiftLength()); if (std::abs(path_shifter_.getLastShiftLength()) < RETURN_SHIFT_THRESHOLD) { DEBUG_PRINT("Return shift is already registered. do nothing."); - return; + return shift_lines; } + AvoidLineArray ret = shift_lines; + // From here, the return-to-center is not registered. But perhaps the candidate is // already generated. @@ -1671,8 +1668,8 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) { // avoidance points: Yes, shift points: No -> select last avoidance point. if (has_candidate_point && !has_registered_point) { - helper_.alignShiftLinesOrder(sl_candidates, false); - last_sl = sl_candidates.back(); + helper_.alignShiftLinesOrder(ret, false); + last_sl = ret.back(); } // avoidance points: No, shift points: Yes -> select last shift point. @@ -1683,8 +1680,8 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) // avoidance points: Yes, shift points: Yes -> select the last one from both. if (has_candidate_point && has_registered_point) { - helper_.alignShiftLinesOrder(sl_candidates, false); - const auto & al = sl_candidates.back(); + helper_.alignShiftLinesOrder(ret, false); + const auto & al = ret.back(); const auto & sl = utils::avoidance::fillAdditionalInfo( data, AvoidLine{path_shifter_.getLastShiftLine().get()}); last_sl = (sl.end_longitudinal > al.end_longitudinal) ? sl : al; @@ -1698,7 +1695,6 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) last_sl.end_shift_length = getCurrentBaseShift(); } } - printShiftLines(ShiftLineArray{last_sl}, "last shift point"); // There already is a shift point candidates to go back to center line, but it could be too sharp // due to detection noise or timing. @@ -1707,7 +1703,7 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) const auto current_base_shift = helper_.getEgoShift(); if (std::abs(current_base_shift) < ep) { DEBUG_PRINT("last shift almost is zero, and current base_shift is zero. do nothing."); - return; + return ret; } // Is there a shift point in the opposite direction of the current_base_shift? @@ -1715,21 +1711,21 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) // the shift length are for return-shift. // Yes -> we can NOT overwrite, because it might be not a return-shift, but a avoiding // shift to the opposite direction which can not be overwritten by the return-shift. - for (const auto & sl : sl_candidates) { + for (const auto & sl : ret) { if ( (current_base_shift > 0.0 && sl.end_shift_length < -ep) || (current_base_shift < 0.0 && sl.end_shift_length > ep)) { DEBUG_PRINT( "try to put overwrite return shift, but there is shift for opposite direction. Skip " "adding return shift."); - return; + return ret; } } // If return shift already exists in candidate or registered shift lines, skip adding return // shift. if (has_candidate_point || has_registered_point) { - return; + return ret; } // set the return-shift from ego. @@ -1746,7 +1742,7 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) const auto nominal_avoid_distance = helper_.getMaxAvoidanceDistance(last_sl.end_shift_length); if (arclength_from_ego.empty()) { - return; + return ret; } const auto remaining_distance = arclength_from_ego.back() - parameters_->remain_buffer_distance; @@ -1757,7 +1753,7 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) // check if there is enough distance for return. if (last_sl_distance > remaining_distance) { // tmp: add some small number (+1.0) RCLCPP_WARN_THROTTLE(getLogger(), *clock_, 1000, "No enough distance for return."); - return; + return ret; } // If the remaining distance is not enough, the return shift needs to be shrunk. @@ -1809,9 +1805,8 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) al.end_longitudinal = arclength_from_ego.at(al.end_idx); al.end_shift_length = last_sl.end_shift_length; al.start_shift_length = last_sl.end_shift_length; - sl_candidates.push_back(al); - printShiftLines(AvoidLineArray{al}, "prepare for return"); - debug_data_.extra_return_shift.push_back(al); + ret.push_back(al); + debug.step1_return_shift_line.push_back(al); } // shift point for return to center line @@ -1828,12 +1823,11 @@ void AvoidanceModule::addReturnShiftLineFromEgo(AvoidLineArray & sl_candidates) al.end_longitudinal = arclength_from_ego.at(al.end_idx); al.end_shift_length = 0.0; al.start_shift_length = last_sl.end_shift_length; - sl_candidates.push_back(al); - printShiftLines(AvoidLineArray{al}, "return point"); - debug_data_.extra_return_shift.push_back(al); + ret.push_back(al); + debug.step1_return_shift_line.push_back(al); } - DEBUG_PRINT("Return Shift is added."); + return ret; } bool AvoidanceModule::isSafePath( @@ -2473,6 +2467,9 @@ bool AvoidanceModule::isValidShiftLine( ShiftedPath proposed_shift_path; shifter_for_validate.generate(&proposed_shift_path); + debug_data_.proposed_spline_shift = proposed_shift_path.shift_length; + debug_data_.step4_valid_shift_line = shift_lines; + // check offset between new shift path and ego position. { const auto new_idx = planner_data_->findEgoIndex(proposed_shift_path.path.points); @@ -2487,8 +2484,6 @@ bool AvoidanceModule::isValidShiftLine( } } - debug_data_.proposed_spline_shift = proposed_shift_path.shift_length; - return true; // valid shift line. } @@ -2734,6 +2729,8 @@ void AvoidanceModule::updateDebugMarker( return; } + const auto & path = data.reference_path; + const auto current_time = rclcpp::Clock{RCL_ROS_TIME}.now(); const auto add = [this](const MarkerArray & added) { appendMarkerArray(added, &debug_marker_); }; @@ -2752,20 +2749,16 @@ void AvoidanceModule::updateDebugMarker( add(createOtherObjectsMarkerArray(objects, ns)); }; - add(createEgoStatusMarkerArray(data, getEgoPose(), "ego_status")); - - const auto & path = data.reference_path; - add(createPathMarkerArray(debug.center_line, "centerline", 0, 0.0, 0.5, 0.9)); - add(createPathMarkerArray(path, "centerline_resampled", 0, 0.0, 0.9, 0.5)); - add(createPathMarkerArray( - helper_.getPreviousLinearShiftPath().path, "prev_linear_shift", 0, 0.5, 0.4, 0.6)); - add(createPoseMarkerArray(data.reference_pose, "reference_pose", 0, 0.9, 0.3, 0.3)); - - add(createLaneletsAreaMarkerArray(*debug.current_lanelets, "current_lanelet", 0.0, 1.0, 0.0)); - add(createPolygonMarkerArray(debug.detection_area, "detection_area", 0L, 0.16, 1.0, 0.69, 0.1)); + const auto addShiftLength = + [&](const auto & shift_length, const auto & ns, auto r, auto g, auto b) { + add(createShiftLengthMarkerArray(shift_length, path, ns, r, g, b)); + }; - add(makeOverhangToRoadShoulderMarkerArray(data.target_objects, "overhang")); - add(createOverhangFurthestLineStringMarkerArray(debug.bounds, "bounds", 1.0, 0.0, 1.0)); + const auto addShiftGrad = [&]( + const auto & shift_grad, const auto & shift_length, const auto & ns, + auto r, auto g, auto b) { + add(createShiftGradMarkerArray(shift_grad, shift_length, path, ns, r, g, b)); + }; // ignore objects { @@ -2782,11 +2775,33 @@ void AvoidanceModule::updateDebugMarker( addObjects(data.other_objects, std::string("LessThanExecutionThreshold")); } - // parent object info + // shift line pre-process + { + addAvoidLine(debug.step1_registered_shift_line, "step1_registered_shift_line", 1.0, 1.0, 1.0); + addAvoidLine(debug.step1_current_raw_shift_line, "step1_current_raw_shift_line", 0.9, 1.0, 1.0); + addAvoidLine(debug.step1_return_shift_line, "step1_return_shift_line", 0.8, 1.0, 1.0); + addAvoidLine(debug.step1_filled_shift_line, "step1_filled_shift_line", 0.7, 1.0, 1.0); + } + + // merge process { - addAvoidLine(debug.registered_raw_shift, "p_registered_shift", 0.8, 0.8, 0.0); - addAvoidLine(debug.current_raw_shift, "p_current_raw_shift", 0.5, 0.2, 0.2); - addAvoidLine(debug.extra_return_shift, "p_extra_return_shift", 0.0, 0.5, 0.8); + addAvoidLine(debug.step2_merged_shift_line, "step2_merged_shift_line", 0.0, 1.0, 1.0); + } + + // trimming process + { + addAvoidLine(debug.step3_grad_filtered_first, "step3_grad_filtered_first", 0.0, 0.0, 1.0); + addAvoidLine(debug.step3_grad_filtered_second, "step3_grad_filtered_second", 0.0, 0.1, 1.0); + addAvoidLine(debug.step3_grad_filtered_third, "step3_grad_filtered_third", 0.0, 0.2, 1.0); + addAvoidLine(debug.step3_quantized_shift_line, "step3_quantized_shift_line", 0.0, 0.3, 1.0); + addAvoidLine(debug.step3_noise_removed, "step3_noise_removed", 0.0, 0.4, 1.0); + } + + // registering process + { + addShiftLine(shifter.getShiftLines(), "step4_old_shift_line", 1.0, 1.0, 0.0, 0.3); + addAvoidLine(debug.step4_new_shift_line, "step4_new_shift_line", 1.0, 0.0, 0.0, 0.3); + addAvoidLine(debug.step4_valid_shift_line, "step4_valid_shift_line", 1.0, 0.0, 0.0, 0.3); } // safety check @@ -2798,58 +2813,28 @@ void AvoidanceModule::updateDebugMarker( // shift length { - const std::string ns = "shift_length"; - add(createShiftLengthMarkerArray(debug.pos_shift, path, ns + "_pos", 0.0, 0.7, 0.5)); - add(createShiftLengthMarkerArray(debug.neg_shift, path, ns + "_neg", 0.0, 0.5, 0.7)); - add(createShiftLengthMarkerArray(debug.total_shift, path, ns + "_total", 0.99, 0.4, 0.2)); + addShiftLength(debug.pos_shift, "merged_length_pos", 0.0, 0.7, 0.5); + addShiftLength(debug.neg_shift, "merged_length_neg", 0.0, 0.5, 0.7); + addShiftLength(debug.total_shift, "merged_length_total", 0.99, 0.4, 0.2); } // shift grad { - const std::string ns = "shift_grad"; - add(createShiftGradMarkerArray( - debug.pos_shift_grad, debug.pos_shift, path, ns + "_pos", 0.0, 0.7, 0.5)); - add(createShiftGradMarkerArray( - debug.neg_shift_grad, debug.neg_shift, path, ns + "_neg", 0.0, 0.5, 0.7)); - add(createShiftGradMarkerArray( - debug.total_forward_grad, debug.total_shift, path, ns + "_total_forward", 0.99, 0.4, 0.2)); - add(createShiftGradMarkerArray( - debug.total_backward_grad, debug.total_shift, path, ns + "_total_backward", 0.4, 0.2, 0.99)); - } - - // shift path - { - const std::string ns = "shift_line"; - add(createShiftLengthMarkerArray( - helper_.getPreviousLinearShiftPath().shift_length, path, ns + "_linear_registered", 0.9, 0.3, - 0.3)); - add(createShiftLengthMarkerArray( - debug.proposed_spline_shift, path, ns + "_spline_proposed", 1.0, 1.0, 1.0)); + addShiftGrad(debug.pos_shift_grad, debug.pos_shift, "merged_grad_pos", 0.0, 0.7, 0.5); + addShiftGrad(debug.neg_shift_grad, debug.neg_shift, "merged_grad_neg", 0.0, 0.5, 0.7); + addShiftGrad(debug.total_forward_grad, debug.total_shift, "grad_forward", 0.99, 0.4, 0.2); + addShiftGrad(debug.total_backward_grad, debug.total_shift, "grad_backward", 0.4, 0.2, 0.9); } - // child shift points + // misc { - const std::string ns = "pipeline"; - add(createAvoidLineMarkerArray(debug.gap_filled, ns + "_1_gap_filled", 0.5, 0.8, 1.0, 0.05)); - add(createAvoidLineMarkerArray(debug.merged, ns + "_2_merge", 0.345, 0.968, 1.0, 0.05)); - add(createAvoidLineMarkerArray( - debug.trim_similar_grad_shift, ns + "_3_concat_by_grad", 0.976, 0.328, 0.910, 0.05)); - add( - createAvoidLineMarkerArray(debug.quantized, ns + "_4_quantized", 0.505, 0.745, 0.969, 0.05)); - add(createAvoidLineMarkerArray( - debug.trim_small_shift, ns + "_5_trim_small_shift", 0.663, 0.525, 0.941, 0.05)); - add(createAvoidLineMarkerArray( - debug.trim_similar_grad_shift_second, ns + "_6_concat_by_grad", 0.97, 0.32, 0.91, 0.05)); - add(createAvoidLineMarkerArray( - debug.trim_momentary_return, ns + "_7_trim_momentary_return", 0.976, 0.078, 0.878, 0.05)); - add(createAvoidLineMarkerArray( - debug.trim_too_sharp_shift, ns + "_8_trim_sharp_shift", 0.576, 0.0, 0.978, 0.05)); - add(createAvoidLineMarkerArray( - debug.trim_similar_grad_shift_third, ns + "_9_concat_by_grad", 1.0, 0.0, 0.0, 0.05)); - } - - addShiftLine(shifter.getShiftLines(), "path_shifter_registered_points", 0.99, 0.99, 0.0, 0.5); - addAvoidLine(debug.new_shift_lines, "path_shifter_proposed_points", 0.99, 0.0, 0.0, 0.5); + add(createEgoStatusMarkerArray(data, getEgoPose(), "ego_status")); + add(createPathMarkerArray(path, "centerline_resampled", 0, 0.0, 0.9, 0.5)); + add(createLaneletsAreaMarkerArray(*debug.current_lanelets, "current_lanelet", 0.0, 1.0, 0.0)); + add(createPolygonMarkerArray(debug.detection_area, "detection_area", 0L, 0.16, 1.0, 0.69, 0.1)); + add(makeOverhangToRoadShoulderMarkerArray(data.target_objects, "overhang")); + add(createOverhangFurthestLineStringMarkerArray(debug.bounds, "bounds", 1.0, 0.0, 1.0)); + } } void AvoidanceModule::updateAvoidanceDebugData( From 0ef13881df1fd4bf417801c745376ed39c85363c Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 14 Sep 2023 16:28:17 +0900 Subject: [PATCH 205/547] chore(build): only include forward declaration of lanelet stuff in perception/ header files (#4984) Signed-off-by: Mamoru Sobue --- .../crosswalk_traffic_light_estimator/node.hpp | 4 +--- .../crosswalk_traffic_light_estimator/src/node.cpp | 1 + .../detected_object_filter/object_lanelet_filter.hpp | 3 +-- .../object_position_filter.hpp | 2 -- .../src/object_lanelet_filter.cpp | 2 ++ .../elevation_map_loader_node.hpp | 3 +-- .../src/elevation_map_loader_node.cpp | 2 ++ .../map_based_prediction_node.hpp | 12 +++--------- .../src/map_based_prediction_node.cpp | 7 +++++++ .../traffic_light_map_based_detector/node.hpp | 1 - .../traffic_light_multi_camera_fusion/node.hpp | 4 +--- .../traffic_light_multi_camera_fusion/src/node.cpp | 3 +++ .../occlusion_predictor.hpp | 4 +--- .../include/traffic_light_map_visualizer/node.hpp | 1 - .../src/traffic_light_map_visualizer/node.cpp | 1 + 15 files changed, 24 insertions(+), 26 deletions(-) diff --git a/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp b/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp index 161a4d19b96ef..e38b747a6ce67 100644 --- a/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp +++ b/perception/crosswalk_traffic_light_estimator/include/crosswalk_traffic_light_estimator/node.hpp @@ -15,9 +15,6 @@ #ifndef CROSSWALK_TRAFFIC_LIGHT_ESTIMATOR__NODE_HPP_ #define CROSSWALK_TRAFFIC_LIGHT_ESTIMATOR__NODE_HPP_ -#include -#include -#include #include #include #include @@ -32,6 +29,7 @@ #include #include +#include #include #include #include diff --git a/perception/crosswalk_traffic_light_estimator/src/node.cpp b/perception/crosswalk_traffic_light_estimator/src/node.cpp index 62c8ac1cb7551..e45e3494dadce 100644 --- a/perception/crosswalk_traffic_light_estimator/src/node.cpp +++ b/perception/crosswalk_traffic_light_estimator/src/node.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "crosswalk_traffic_light_estimator/node.hpp" +#include #include #include diff --git a/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp b/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp index d1d3cf9e904c5..600da31df5868 100644 --- a/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp +++ b/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp @@ -17,14 +17,13 @@ #include "utils/utils.hpp" -#include -#include #include #include #include #include +#include #include #include diff --git a/perception/detected_object_validation/include/detected_object_filter/object_position_filter.hpp b/perception/detected_object_validation/include/detected_object_filter/object_position_filter.hpp index b3ab5d104aee7..b9384e0f70379 100644 --- a/perception/detected_object_validation/include/detected_object_filter/object_position_filter.hpp +++ b/perception/detected_object_validation/include/detected_object_filter/object_position_filter.hpp @@ -17,8 +17,6 @@ #include "utils/utils.hpp" -#include -#include #include #include diff --git a/perception/detected_object_validation/src/object_lanelet_filter.cpp b/perception/detected_object_validation/src/object_lanelet_filter.cpp index 2e393f27c37a7..3b78ae449e2da 100644 --- a/perception/detected_object_validation/src/object_lanelet_filter.cpp +++ b/perception/detected_object_validation/src/object_lanelet_filter.cpp @@ -14,6 +14,8 @@ #include "detected_object_filter/object_lanelet_filter.hpp" +#include +#include #include #include diff --git a/perception/elevation_map_loader/include/elevation_map_loader/elevation_map_loader_node.hpp b/perception/elevation_map_loader/include/elevation_map_loader/elevation_map_loader_node.hpp index 3f383ce3d6d0e..7c0609ee1cdce 100644 --- a/perception/elevation_map_loader/include/elevation_map_loader/elevation_map_loader_node.hpp +++ b/perception/elevation_map_loader/include/elevation_map_loader/elevation_map_loader_node.hpp @@ -19,8 +19,6 @@ #include #include #include -#include -#include #include #include @@ -30,6 +28,7 @@ #include #include +#include #include #include diff --git a/perception/elevation_map_loader/src/elevation_map_loader_node.cpp b/perception/elevation_map_loader/src/elevation_map_loader_node.cpp index 096e084a70286..425af519a80ea 100644 --- a/perception/elevation_map_loader/src/elevation_map_loader_node.cpp +++ b/perception/elevation_map_loader/src/elevation_map_loader_node.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include diff --git a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp index f694892541f4a..8ef6d628aa2f6 100644 --- a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp @@ -17,9 +17,6 @@ #include "map_based_prediction/path_generator.hpp" -#include -#include -#include #include #include #include @@ -33,12 +30,9 @@ #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include #include diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index c1cb9d9dd3910..0ef1443fde519 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -15,6 +15,9 @@ #include "map_based_prediction/map_based_prediction_node.hpp" #include +#include +#include +#include #include #include #include @@ -26,6 +29,10 @@ #include #include +#include +#include +#include +#include #include #ifdef ROS_DISTRO_GALACTIC diff --git a/perception/traffic_light_map_based_detector/include/traffic_light_map_based_detector/node.hpp b/perception/traffic_light_map_based_detector/include/traffic_light_map_based_detector/node.hpp index 9db8d7242c64d..e46431a7b199e 100644 --- a/perception/traffic_light_map_based_detector/include/traffic_light_map_based_detector/node.hpp +++ b/perception/traffic_light_map_based_detector/include/traffic_light_map_based_detector/node.hpp @@ -16,7 +16,6 @@ #define TRAFFIC_LIGHT_MAP_BASED_DETECTOR__NODE_HPP_ #include -#include #include #include diff --git a/perception/traffic_light_multi_camera_fusion/include/traffic_light_multi_camera_fusion/node.hpp b/perception/traffic_light_multi_camera_fusion/include/traffic_light_multi_camera_fusion/node.hpp index bdfd07ae54b28..390b0f590b637 100644 --- a/perception/traffic_light_multi_camera_fusion/include/traffic_light_multi_camera_fusion/node.hpp +++ b/perception/traffic_light_multi_camera_fusion/include/traffic_light_multi_camera_fusion/node.hpp @@ -15,8 +15,6 @@ #ifndef TRAFFIC_LIGHT_MULTI_CAMERA_FUSION__NODE_HPP_ #define TRAFFIC_LIGHT_MULTI_CAMERA_FUSION__NODE_HPP_ -#include -#include #include #include @@ -25,7 +23,7 @@ #include #include -#include +#include #include #include #include diff --git a/perception/traffic_light_multi_camera_fusion/src/node.cpp b/perception/traffic_light_multi_camera_fusion/src/node.cpp index 6c29be9858a9a..1582040648624 100644 --- a/perception/traffic_light_multi_camera_fusion/src/node.cpp +++ b/perception/traffic_light_multi_camera_fusion/src/node.cpp @@ -14,6 +14,9 @@ #include "traffic_light_multi_camera_fusion/node.hpp" +#include +#include + #include #include #include diff --git a/perception/traffic_light_occlusion_predictor/include/traffic_light_occlusion_predictor/occlusion_predictor.hpp b/perception/traffic_light_occlusion_predictor/include/traffic_light_occlusion_predictor/occlusion_predictor.hpp index e37083f81decd..7e2825197f3a7 100644 --- a/perception/traffic_light_occlusion_predictor/include/traffic_light_occlusion_predictor/occlusion_predictor.hpp +++ b/perception/traffic_light_occlusion_predictor/include/traffic_light_occlusion_predictor/occlusion_predictor.hpp @@ -25,9 +25,7 @@ #include #include -#include -#include -#include +#include #include #include #include diff --git a/perception/traffic_light_visualization/include/traffic_light_map_visualizer/node.hpp b/perception/traffic_light_visualization/include/traffic_light_map_visualizer/node.hpp index 07cb21e60b137..3f83cf6e926ad 100644 --- a/perception/traffic_light_visualization/include/traffic_light_map_visualizer/node.hpp +++ b/perception/traffic_light_visualization/include/traffic_light_map_visualizer/node.hpp @@ -16,7 +16,6 @@ #define TRAFFIC_LIGHT_MAP_VISUALIZER__NODE_HPP_ #include -#include #include #include diff --git a/perception/traffic_light_visualization/src/traffic_light_map_visualizer/node.cpp b/perception/traffic_light_visualization/src/traffic_light_map_visualizer/node.cpp index a9d107e78a1c4..53e06e47a1873 100644 --- a/perception/traffic_light_visualization/src/traffic_light_map_visualizer/node.cpp +++ b/perception/traffic_light_visualization/src/traffic_light_map_visualizer/node.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include From e4e24c44e48a695b49c343b386cbb0a6cdd03d56 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Thu, 14 Sep 2023 16:40:11 +0900 Subject: [PATCH 206/547] feat(ar_tag_based_localizer): add ekf_pose subscriber (#4946) * Fixed qos Signed-off-by: Shintaro Sakoda * Fixed camera_frame_ Signed-off-by: Shintaro Sakoda * Fixed for awsim Signed-off-by: Shintaro Sakoda * Removed camera_frame Signed-off-by: Shintaro SAKODA * Fixed parameters Signed-off-by: Shintaro SAKODA * Fixed variable name Signed-off-by: Shintaro SAKODA * Updated README.md and added sample result Signed-off-by: Shintaro SAKODA * Updated README.md Signed-off-by: Shintaro SAKODA * Fixed distance_threshold to 13m Signed-off-by: Shintaro SAKODA * Implemented sub ekf_pose Signed-off-by: Shintaro Sakoda * style(pre-commit): autofix * Fixed the type of second to double Signed-off-by: Shintaro Sakoda * Fixed initializing Signed-off-by: Shintaro Sakoda * Fix to use rclcpp::Time and rclcpp::Duration Signed-off-by: Shintaro SAKODA * Added detail description about ekf_pose Signed-off-by: Shintaro SAKODA * style(pre-commit): autofix * Fixed nanoseconds Signed-off-by: Shintaro Sakoda * Added comments to param.yaml Signed-off-by: Shintaro Sakoda --------- Signed-off-by: Shintaro Sakoda Signed-off-by: Shintaro SAKODA Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../ar_tag_based_localizer.launch.xml | 1 + localization/ar_tag_based_localizer/README.md | 9 ++-- .../config/ar_tag_based_localizer.param.yaml | 13 ++++++ .../doc_image/node_diagram.drawio.svg | 8 +++- .../ar_tag_based_localizer_core.hpp | 5 +++ .../launch/ar_tag_based_localizer.launch.xml | 2 + .../src/ar_tag_based_localizer_core.cpp | 45 +++++++++++++++++++ 7 files changed, 78 insertions(+), 5 deletions(-) diff --git a/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml b/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml index 3b32d080ad51c..642edb4f68ab8 100644 --- a/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml +++ b/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml @@ -6,6 +6,7 @@ + diff --git a/localization/ar_tag_based_localizer/README.md b/localization/ar_tag_based_localizer/README.md index c7b970ebbcdcf..81bc11a7cbc9b 100644 --- a/localization/ar_tag_based_localizer/README.md +++ b/localization/ar_tag_based_localizer/README.md @@ -18,10 +18,11 @@ This package includes two nodes. #### Input -| Name | Type | Description | -| :-------------------- | :----------------------------- | :----------- | -| `~/input/image` | `sensor_msgs::msg::Image` | Camera Image | -| `~/input/camera_info` | `sensor_msgs::msg::CameraInfo` | Camera Info | +| Name | Type | Description | +| :-------------------- | :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `~/input/image` | `sensor_msgs::msg::Image` | Camera Image | +| `~/input/camera_info` | `sensor_msgs::msg::CameraInfo` | Camera Info | +| `~/input/ekf_pose` | `geometry_msgs::msg::PoseWithCovarianceStamped` | EKF Pose without IMU correction. It is used to validate detected AR tags by filtering out False Positives. Only if the EKF Pose and the AR tag-detected Pose are within a certain temporal and spatial range, the AR tag-detected Pose is considered valid and published. | #### Output diff --git a/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml b/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml index d0eef44229e0d..29260e27cd43c 100644 --- a/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml +++ b/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml @@ -22,3 +22,16 @@ # See https://github.com/pal-robotics/aruco_ros/blob/7787a6794d30c248bc546d1582e65dd47bc40c12/aruco/include/aruco/markerdetector.h#L106-L126 detection_mode: "DM_NORMAL" # select from [DM_NORMAL, DM_FAST, DM_VIDEO_FAST] min_marker_size: 0.02 + + # Parameters for comparison with EKF Pose + # If the difference between the EKF pose and the current pose is within the range of values set below, the current pose is published. + # [How to determine the value] + # * ekf_time_tolerance: Since it is abnormal if the data comes too old from EKF, the tentative tolerance value is set at 5 seconds. + # This value is assumed to be unaffected even if it is increased or decreased by some amount. + # * ekf_position_tolerance: Since it is possible that multiple AR tags with the same ID could be placed, the tolerance should be as small as possible. + # And if the vehicle is running only on odometry in a section without AR tags, + # it is possible that self-position estimation could be off by a few meters. + # it should be fixed by AR tag detection, so tolerance should not be smaller than 10 meters. + # Therefore, the tolerance is set at 10 meters. + ekf_time_tolerance: 5.0 # [s] + ekf_position_tolerance: 10.0 # [m] diff --git a/localization/ar_tag_based_localizer/doc_image/node_diagram.drawio.svg b/localization/ar_tag_based_localizer/doc_image/node_diagram.drawio.svg index a339951663b29..e41ca0ad019ce 100644 --- a/localization/ar_tag_based_localizer/doc_image/node_diagram.drawio.svg +++ b/localization/ar_tag_based_localizer/doc_image/node_diagram.drawio.svg @@ -6,7 +6,7 @@ width="721px" height="331px" viewBox="-0.5 -0.5 721 331" - content="<mxfile><diagram id="z_GJx55S16-dNHi1224P" name="ページ1">1Zhdk5sgFIZ/TS6bEdF8XDYm2V5spzvdnWl75RBFZdaIg+Srv74QMcZAdt3EbKZeJHIAhec9nAP2oLfcPjCUJ99piNOebYXbHpz2bBtYliv+pGVXWhwwKA0xI6FqVBueyV9c9VTWFQlx0WjIKU05yZvGgGYZDnjDhhijm2aziKbNt+YoxprhOUCpbv1FQp6U1pE9rO3fMImT6s1gMC5rlqhqrGZSJCikmyMTnPWgxyjl5d1y6+FUwqu4lP3mZ2oPA2M442062GWHNUpXam5qXHxXTZbRVRZi2d7qwUlEM64EAbYqezSlbN8YWvtL2PWBqLGtMeN4e2RSA3vAdIk524kmqtZ2FSTlJVAVNzXyg0ckR7gHyoaUyvHhyTUIcaNYmLk4GpevP0X5BcXid4IKwcMWPguBnOsjFa4hmLD32JE0PWLleTN3LsYzKTijr/ioZhCM8CK6LV04aNJ1dLpjA1ynA7iuBvcHTyQ96+uK042Y1RHcHAWvYj0VH2U7n3ueie3IXsDB4Maeez+2cGhY0XO+IQX3N4QnfkDXiBGUBVgjikMR5VSRMp7QmGYondXWiUDCdr8V8n3hjyz0h25Vnm6Pa6c7VToLlSMWY2WCyibH8SZmhlPEyboZjU3UVNcnSsRrz7o+dEbNRxR0xQKsep2wPwyjlRxVnG/KQZYywbyFP6PZW7ztTniD4efwdqDb4G0PTxy9HJTGW3uQbTWFsyG8mXAjk3A5LfCHllGl45bwvYx9V5WkjkDd1xrKwu5Y0E61L+k0tb9o/Sk5vlh9IK6y07U+Mmz6CIQf9ZGqIY2iAl8rPzCGUcR8jmJ/IfO/n16Z9c/lH303sL+6yUzO6Qqy9dQEXENucrvITcAEFb9Gl7NUWf4uLAen+1PwmSyN8SlAS8yQT7KIXpNeuknnn5VetKzgtkvnhjzVfBAYua1i0AXxxYEG+d4XrFVoh2NdC6fzrVVrRx1rM31EGU4x12eMirw8rkdkK9f8JMeMiDdiuXrFY8X5Hj/VpomIGXEmqgKBY284HKelk4aoSA6RoxDnCJLFLzQXBigM5R7s8D8lS3G0m6dkIeN8IBH4IWFiOFTOdx4ijmTcL/rFOu4mfoxOvM2Wu4PTAGKKH874elkc/RAmzwmRX3Chf9DGGQ98QVvPrA44t40SrREY9+YywQsMASr4/3WgP03tAIwN/nSzhOTon0w6i2iOIbtU6t3Bb4zbmFQFNX+JcrGfQeHFm5n7fLI49R5jNOrKe0Sx/sZZps/6SzGc/QM=</diagram></mxfile>" + content="<mxfile><diagram id="z_GJx55S16-dNHi1224P" name="ページ1">1ZlLc5swEIB/jY/1WAiwfaydOD2k00yTmbYnRgYBmmDECPnVX1/JCDBIjomNkykHBy0SSN+u9qEM4Hy1e2Aoi7/TACcDaxTsBvBuYFnTiSt+pWBfCGygBBEjQSECteCZ/MVKOFLSNQlw3ujIKU04yZpCn6Yp9nlDhhij22a3kCbNr2Yowprg2UeJLv1FAh4X0ok1ruXfMIni8svAnRZPVqjsrFaSxyig2yMRvB/AOaOUF3er3Rwnkl3JpRi3OPG0mhjDKe8ywCoGbFCyVmtT8+L7crGMrtMAy/6jAZyFNOVKIcBS7TlNKDt0hqPDJeT6RNTcNphxvDsSqYk9YLrCnO1FF/XUchQkZSVQNbc18soi4iPcrpIhpeWoenMNQtwoFmYutsbl60/RfkGR+J2hXPCwhM1CINf6SIVpCCbsHDuSJEes5vN7ZyHmM8s5o6/46InrT/AyvC1d6Dbp2jrdqQGu3QNcR4P7g8eS3ujrmtOtWNUR3Az5r2I/5e9lu1jM5ya2E2sJXffGlvt5bOHYsKMXfEty7m0Jjz2fbhAjKPWxRhQHwsupJmU8phFNUXJfS2cCCdv/VsgPjT+yMRw7Zftud/z0bq9aJ6FyxCKsRFDJ5DzexMxwgjjZNL2xiZoa+kSJ+OxJ04f2pPmKnK6Zj9WoFvtqGp3UUfr5pjrISgaYt/CnNH2Lt9ULbzD+GN42dBq8rXHL0ItJaby1F1mjpuIsCG+muIlJcRnN8bu2UanHHeEHNQ4d1ZJ6BOq+1qFs7I8V2qvuCzpN3V+0/5Q6voyGQFzFoGttZNy0EQjfayNlRxqGOb5W/cDoRhHzOIq8pYz/XnJl1D8Vf/Rs4HD1E5ns9g6y9NAEHENscvqITcAEFb+Gl7NUUf5TWLrt/BR8JEujf/LRCjPkkTSk14SXfsL5R4UXLSo43cK5IU41XwQmTicfdIF/saFBfecV1sm1w6muC7v31KqzoU61lT6iFCeY6ytGeVaU6yHZyT0/yzAj4otY7l7xWlHf46daNBM+I0rFI1/gOAiqcloaaYDyuPIcuagjSBq90EwIoBAUOVj1946sRGm3SMhS+nlfIvACwsR0qFzvIkAcSb+fD/NN1I//mLSszZLZQduBmPyHPb1eLbZehMk6IfRyLvTvdzHGii/oapllgXNbL9EZgTE3lwFeYPBRzv+vgr4d2gGYGuzpZgHJ1o9MevNotiG6lNr7BLsxpjGJcmreCmUin0HBxcnM5xxZtK3H6I1uZz2uialMDd9dcp05uQDNVOdcpmOu3KpizVi5GQ3zfNg+mUL1fwAybmWul2ZMbvskxRkP+8qZRLM+By+61/9MgPf/AA==</diagram></mxfile>" > @@ -82,5 +82,11 @@ /lanelet2_map_loader + + + + + /ekf_pose_with_covariance + diff --git a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp b/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp index b6a513b981e14..65c1283a82728 100644 --- a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp +++ b/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp @@ -69,6 +69,7 @@ class ArTagBasedLocalizer : public rclcpp::Node private: void image_callback(const sensor_msgs::msg::Image::ConstSharedPtr & msg); void cam_info_callback(const sensor_msgs::msg::CameraInfo & msg); + void ekf_pose_callback(const geometry_msgs::msg::PoseWithCovarianceStamped & msg); void publish_pose_as_base_link( const geometry_msgs::msg::PoseStamped & msg, const std::string & camera_frame_id); @@ -77,6 +78,8 @@ class ArTagBasedLocalizer : public rclcpp::Node std::vector target_tag_ids_; std::vector base_covariance_; double distance_threshold_squared_{}; + double ekf_time_tolerance_{}; + double ekf_position_tolerance_{}; // tf std::unique_ptr tf_buffer_; @@ -89,6 +92,7 @@ class ArTagBasedLocalizer : public rclcpp::Node // Subscribers rclcpp::Subscription::SharedPtr image_sub_; rclcpp::Subscription::SharedPtr cam_info_sub_; + rclcpp::Subscription::SharedPtr ekf_pose_sub_; // Publishers image_transport::Publisher image_pub_; @@ -98,6 +102,7 @@ class ArTagBasedLocalizer : public rclcpp::Node aruco::MarkerDetector detector_; aruco::CameraParameters cam_param_; bool cam_info_received_; + geometry_msgs::msg::PoseWithCovarianceStamped latest_ekf_pose_{}; }; #endif // AR_TAG_BASED_LOCALIZER__AR_TAG_BASED_LOCALIZER_CORE_HPP_ diff --git a/localization/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml b/localization/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml index 940f6ed53d8b8..f6c10050b1826 100644 --- a/localization/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml +++ b/localization/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml @@ -5,6 +5,7 @@ + @@ -12,6 +13,7 @@ + diff --git a/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp b/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp index c97e1171949e7..cf0236c1a1e48 100644 --- a/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp +++ b/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp @@ -71,6 +71,8 @@ bool ArTagBasedLocalizer::setup() target_tag_ids_ = this->declare_parameter>("target_tag_ids"); base_covariance_ = this->declare_parameter>("base_covariance"); distance_threshold_squared_ = std::pow(this->declare_parameter("distance_threshold"), 2); + ekf_time_tolerance_ = this->declare_parameter("ekf_time_tolerance"); + ekf_position_tolerance_ = this->declare_parameter("ekf_position_tolerance"); std::string detection_mode = this->declare_parameter("detection_mode"); float min_marker_size = static_cast(this->declare_parameter("min_marker_size")); if (detection_mode == "DM_NORMAL") { @@ -116,6 +118,9 @@ bool ArTagBasedLocalizer::setup() cam_info_sub_ = this->create_subscription( "~/input/camera_info", qos_sub, std::bind(&ArTagBasedLocalizer::cam_info_callback, this, std::placeholders::_1)); + ekf_pose_sub_ = this->create_subscription( + "~/input/ekf_pose", qos_sub, + std::bind(&ArTagBasedLocalizer::ekf_pose_callback, this, std::placeholders::_1)); /* Publishers @@ -207,6 +212,12 @@ void ArTagBasedLocalizer::cam_info_callback(const sensor_msgs::msg::CameraInfo & cam_info_received_ = true; } +void ArTagBasedLocalizer::ekf_pose_callback( + const geometry_msgs::msg::PoseWithCovarianceStamped & msg) +{ + latest_ekf_pose_ = msg; +} + void ArTagBasedLocalizer::publish_pose_as_base_link( const geometry_msgs::msg::PoseStamped & msg, const std::string & camera_frame_id) { @@ -268,6 +279,40 @@ void ArTagBasedLocalizer::publish_pose_as_base_link( pose_with_covariance_stamped.header.frame_id = "map"; pose_with_covariance_stamped.pose.pose = tf2::toMsg(map_to_base_link); + // If latest_ekf_pose_ is older than seconds compared to current frame, it + // will not be published. + const rclcpp::Duration tolerance{ + static_cast(ekf_time_tolerance_), + static_cast((ekf_time_tolerance_ - std::floor(ekf_time_tolerance_)) * 1e9)}; + if (rclcpp::Time(latest_ekf_pose_.header.stamp) + tolerance < rclcpp::Time(msg.header.stamp)) { + RCLCPP_INFO( + this->get_logger(), + "latest_ekf_pose_ is older than %f seconds compared to current frame. " + "latest_ekf_pose_.header.stamp: %d.%d, msg.header.stamp: %d.%d", + ekf_time_tolerance_, latest_ekf_pose_.header.stamp.sec, latest_ekf_pose_.header.stamp.nanosec, + msg.header.stamp.sec, msg.header.stamp.nanosec); + return; + } + + // If curr_pose differs from latest_ekf_pose_ by more than , it will not + // be published. + const geometry_msgs::msg::Pose curr_pose = pose_with_covariance_stamped.pose.pose; + const geometry_msgs::msg::Pose latest_ekf_pose = latest_ekf_pose_.pose.pose; + const double diff_x = curr_pose.position.x - latest_ekf_pose.position.x; + const double diff_y = curr_pose.position.y - latest_ekf_pose.position.y; + const double diff_z = curr_pose.position.z - latest_ekf_pose.position.z; + const double diff_distance_squared = diff_x * diff_x + diff_y * diff_y + diff_z * diff_z; + const double threshold = ekf_position_tolerance_ * ekf_position_tolerance_; + if (threshold < diff_distance_squared) { + RCLCPP_INFO( + this->get_logger(), + "curr_pose differs from latest_ekf_pose_ by more than %f m. " + "curr_pose: (%f, %f, %f), latest_ekf_pose: (%f, %f, %f)", + ekf_position_tolerance_, curr_pose.position.x, curr_pose.position.y, curr_pose.position.z, + latest_ekf_pose.position.x, latest_ekf_pose.position.y, latest_ekf_pose.position.z); + return; + } + // ~5[m]: base_covariance // 5~[m]: scaling base_covariance by std::pow(distance/5, 3) const double distance = std::sqrt(distance_squared); From b2e9ee3fb47796c2e89fd7682ff7a56573f18fb4 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Thu, 14 Sep 2023 16:55:40 +0900 Subject: [PATCH 207/547] feat(goal_planner): add options of occupancy grid map to use only for goal search (#4970) Signed-off-by: kosuke55 --- .../config/goal_planner/goal_planner.param.yaml | 7 ++++--- .../behavior_path_planner_goal_planner_design.md | 15 ++++++++------- .../goal_planner/goal_planner_parameters.hpp | 5 +++-- .../goal_planner/goal_planner_module.cpp | 6 ++++-- .../src/scene_module/goal_planner/manager.cpp | 9 ++++++--- .../src/utils/goal_planner/goal_searcher.cpp | 6 ++++-- 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 64f88e064451f..709f271ac265c 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -21,15 +21,16 @@ # occupancy grid map occupancy_grid: - use_occupancy_grid: true - use_occupancy_grid_for_longitudinal_margin: false + use_occupancy_grid_for_goal_search: true + use_occupancy_grid_for_goal_longitudinal_margin: false + use_occupancy_grid_for_path_collision_check: false occupancy_grid_collision_check_margin: 0.0 theta_size: 360 obstacle_threshold: 60 # object recognition object_recognition: - use_object_recognition: true + use_object_recognition: false object_recognition_collision_check_margin: 0.6 # this should be larger than `surround_check_distance` of surround_obstacle_checker object_recognition_collision_check_max_extra_stopping_margin: 1.0 th_moving_object_velocity: 1.0 diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index 4cc8910acf82b..18b77f3faacf0 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -132,13 +132,14 @@ Generate footprints from ego-vehicle path points and determine obstacle collisio #### Parameters for occupancy grid based collision check -| Name | Unit | Type | Description | Default value | -| :----------------------------------------- | :--- | :----- | :-------------------------------------------------------------------------------------------------------------- | :------------ | -| use_occupancy_grid | [-] | bool | flag whether to use occupancy grid for collision check | true | -| use_occupancy_grid_for_longitudinal_margin | [-] | bool | flag whether to use occupancy grid for keeping longitudinal margin | false | -| occupancy_grid_collision_check_margin | [m] | double | margin to calculate ego-vehicle cells from footprint. | 0.0 | -| theta_size | [-] | int | size of theta angle to be considered. angular resolution for collision check will be 2$\pi$ / theta_size [rad]. | 360 | -| obstacle_threshold | [-] | int | threshold of cell values to be considered as obstacles | 60 | +| Name | Unit | Type | Description | Default value | +| :---------------------------------------------- | :--- | :----- | :-------------------------------------------------------------------------------------------------------------- | :------------ | +| use_occupancy_grid_for_goal_search | [-] | bool | flag whether to use occupancy grid for goal search collision check | true | +| use_occupancy_grid_for_goal_longitudinal_margin | [-] | bool | flag whether to use occupancy grid for keeping longitudinal margin | false | +| use_occupancy_grid_for_path_collision_check | [-] | bool | flag whether to use occupancy grid for collision check | false | +| occupancy_grid_collision_check_margin | [m] | double | margin to calculate ego-vehicle cells from footprint. | 0.0 | +| theta_size | [-] | int | size of theta angle to be considered. angular resolution for collision check will be 2$\pi$ / theta_size [rad]. | 360 | +| obstacle_threshold | [-] | int | threshold of cell values to be considered as obstacles | 60 | ### **object recognition based collision check** diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index d2577c68cabfb..4e40267ede4df 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -59,8 +59,9 @@ struct GoalPlannerParameters double margin_from_boundary; // occupancy grid map - bool use_occupancy_grid; - bool use_occupancy_grid_for_longitudinal_margin; + bool use_occupancy_grid_for_goal_search; + bool use_occupancy_grid_for_goal_longitudinal_margin; + bool use_occupancy_grid_for_path_collision_check; double occupancy_grid_collision_check_margin; int theta_size; int obstacle_threshold; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 50daf381cf0dd..bed4aecf733b9 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -264,7 +264,9 @@ void GoalPlannerModule::initializeSafetyCheckParameters() void GoalPlannerModule::processOnEntry() { // Initialize occupancy grid map - if (parameters_->use_occupancy_grid) { + if ( + parameters_->use_occupancy_grid_for_goal_search || + parameters_->use_occupancy_grid_for_path_collision_check) { initializeOccupancyGridMap(); } // Initialize safety checker @@ -1292,7 +1294,7 @@ TurnSignalInfo GoalPlannerModule::calcTurnSignalInfo() const bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const { - if (parameters_->use_occupancy_grid && occupancy_grid_map_) { + if (parameters_->use_occupancy_grid_for_path_collision_check && occupancy_grid_map_) { const bool check_out_of_range = false; if (occupancy_grid_map_->hasObstacleOnPath(path, check_out_of_range)) { return true; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index cd01aeb522c07..9d794b178b60e 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -72,9 +72,12 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // occupancy grid map { const std::string ns = base_ns + "occupancy_grid."; - p.use_occupancy_grid = node->declare_parameter(ns + "use_occupancy_grid"); - p.use_occupancy_grid_for_longitudinal_margin = - node->declare_parameter(ns + "use_occupancy_grid_for_longitudinal_margin"); + p.use_occupancy_grid_for_goal_search = + node->declare_parameter(ns + "use_occupancy_grid_for_goal_search"); + p.use_occupancy_grid_for_path_collision_check = + node->declare_parameter(ns + "use_occupancy_grid_for_path_collision_check"); + p.use_occupancy_grid_for_goal_longitudinal_margin = + node->declare_parameter(ns + "use_occupancy_grid_for_goal_longitudinal_margin"); p.occupancy_grid_collision_check_margin = node->declare_parameter(ns + "occupancy_grid_collision_check_margin"); p.theta_size = node->declare_parameter(ns + "theta_size"); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index ade4a08b163c8..eb4a2eb1e7cbc 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -179,7 +179,7 @@ void GoalSearcher::update(GoalCandidates & goal_candidates) const bool GoalSearcher::checkCollision(const Pose & pose) const { - if (parameters_.use_occupancy_grid) { + if (parameters_.use_occupancy_grid_for_goal_search) { const Pose pose_grid_coords = global2local(occupancy_grid_map_->getMap(), pose); const auto idx = pose2index( occupancy_grid_map_->getMap(), pose_grid_coords, occupancy_grid_map_->getParam().theta_size); @@ -210,7 +210,9 @@ bool GoalSearcher::checkCollision(const Pose & pose) const bool GoalSearcher::checkCollisionWithLongitudinalDistance( const Pose & ego_pose, const PredictedObjects & dynamic_objects) const { - if (parameters_.use_occupancy_grid && parameters_.use_occupancy_grid_for_longitudinal_margin) { + if ( + parameters_.use_occupancy_grid_for_goal_search && + parameters_.use_occupancy_grid_for_goal_longitudinal_margin) { constexpr bool check_out_of_range = false; const double offset = std::max( parameters_.longitudinal_margin - parameters_.occupancy_grid_collision_check_margin, 0.0); From ca6c46c860ddc913b2cf34aa65eece4a5b98c69a Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Thu, 14 Sep 2023 18:19:55 +0900 Subject: [PATCH 208/547] fix(simple_object_merger): fix timeout (#4990) Signed-off-by: scepter914 --- .../src/simple_object_merger_node/simple_object_merger_node.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp b/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp index 73a96da454833..693fccb7e937c 100644 --- a/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp +++ b/perception/simple_object_merger/src/simple_object_merger_node/simple_object_merger_node.cpp @@ -170,7 +170,7 @@ void SimpleObjectMergerNode::onTimer() output_objects.header.frame_id = node_param_.new_frame_id; for (size_t i = 0; i < input_topic_size; i++) { - double time_diff = (this->get_clock()->now()).seconds() - + double time_diff = rclcpp::Time(objects_data_.at(i)->header.stamp).seconds() - rclcpp::Time(objects_data_.at(0)->header.stamp).seconds(); if (std::abs(time_diff) < node_param_.timeout_threshold) { transform_ = transform_listener_->getTransform( From ac614b546bbf047162d5e4206df764a05f134233 Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Thu, 14 Sep 2023 18:33:19 +0900 Subject: [PATCH 209/547] feat(object_merger): add queue size parameter (#4994) Signed-off-by: scepter914 --- .../object_association_merger/node.hpp | 16 +++++++------- .../object_association_merger.launch.xml | 2 ++ .../src/object_association_merger/node.cpp | 21 +++++++++++-------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/perception/object_merger/include/object_association_merger/node.hpp b/perception/object_merger/include/object_association_merger/node.hpp index 1e5b9fad9c9ca..6815b59894083 100644 --- a/perception/object_merger/include/object_association_merger/node.hpp +++ b/perception/object_merger/include/object_association_merger/node.hpp @@ -59,14 +59,16 @@ class ObjectAssociationMergerNode : public rclcpp::Node tf2_ros::TransformListener tf_listener_; rclcpp::Publisher::SharedPtr merged_object_pub_; - message_filters::Subscriber object0_sub_; - message_filters::Subscriber object1_sub_; - typedef message_filters::sync_policies::ApproximateTime< + message_filters::Subscriber object0_sub_{}; + message_filters::Subscriber object1_sub_{}; + + using SyncPolicy = message_filters::sync_policies::ApproximateTime< autoware_auto_perception_msgs::msg::DetectedObjects, - autoware_auto_perception_msgs::msg::DetectedObjects> - SyncPolicy; - typedef message_filters::Synchronizer Sync; - Sync sync_; + autoware_auto_perception_msgs::msg::DetectedObjects>; + using Sync = message_filters::Synchronizer; + typename std::shared_ptr sync_ptr_; + + int sync_queue_size_; std::unique_ptr data_association_; std::string base_link_frame_id_; // associated with the base_link frame diff --git a/perception/object_merger/launch/object_association_merger.launch.xml b/perception/object_merger/launch/object_association_merger.launch.xml index 3418f7d5a5e61..a21ae8f7fa327 100644 --- a/perception/object_merger/launch/object_association_merger.launch.xml +++ b/perception/object_merger/launch/object_association_merger.launch.xml @@ -6,6 +6,7 @@ + @@ -14,6 +15,7 @@ + diff --git a/perception/object_merger/src/object_association_merger/node.cpp b/perception/object_merger/src/object_association_merger/node.cpp index 0ad2a76f74754..f776d2d940045 100644 --- a/perception/object_merger/src/object_association_merger/node.cpp +++ b/perception/object_merger/src/object_association_merger/node.cpp @@ -77,20 +77,13 @@ ObjectAssociationMergerNode::ObjectAssociationMergerNode(const rclcpp::NodeOptio tf_buffer_(get_clock()), tf_listener_(tf_buffer_), object0_sub_(this, "input/object0", rclcpp::QoS{1}.get_rmw_qos_profile()), - object1_sub_(this, "input/object1", rclcpp::QoS{1}.get_rmw_qos_profile()), - sync_(SyncPolicy(10), object0_sub_, object1_sub_) + object1_sub_(this, "input/object1", rclcpp::QoS{1}.get_rmw_qos_profile()) { - // Create publishers and subscribers - using std::placeholders::_1; - using std::placeholders::_2; - sync_.registerCallback(std::bind(&ObjectAssociationMergerNode::objectsCallback, this, _1, _2)); - merged_object_pub_ = create_publisher( - "output/object", rclcpp::QoS{1}); - // Parameters base_link_frame_id_ = declare_parameter("base_link_frame_id", "base_link"); priority_mode_ = static_cast( declare_parameter("priority_mode", static_cast(PriorityMode::Confidence))); + sync_queue_size_ = declare_parameter("sync_queue_size", 20); remove_overlapped_unknown_objects_ = declare_parameter("remove_overlapped_unknown_objects", true); overlapped_judge_param_.precision_threshold = @@ -115,6 +108,16 @@ ObjectAssociationMergerNode::ObjectAssociationMergerNode(const rclcpp::NodeOptio const auto min_iou_matrix = this->declare_parameter>("min_iou_matrix"); data_association_ = std::make_unique( can_assign_matrix, max_dist_matrix, max_rad_matrix, min_iou_matrix); + + // Create publishers and subscribers + using std::placeholders::_1; + using std::placeholders::_2; + sync_ptr_ = std::make_shared(SyncPolicy(sync_queue_size_), object0_sub_, object1_sub_); + sync_ptr_->registerCallback( + std::bind(&ObjectAssociationMergerNode::objectsCallback, this, _1, _2)); + + merged_object_pub_ = create_publisher( + "output/object", rclcpp::QoS{1}); } void ObjectAssociationMergerNode::objectsCallback( From 8d639b0e3f192f90f9cd4c1e0eaf5439a17e3de8 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 14 Sep 2023 19:44:29 +0900 Subject: [PATCH 210/547] fix(resample): remove duplicated poinnt before resampling (#4965) Signed-off-by: Takamasa Horibe --- common/motion_utils/src/resample/resample.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/motion_utils/src/resample/resample.cpp b/common/motion_utils/src/resample/resample.cpp index 091f3405e2815..1a800a38d1051 100644 --- a/common/motion_utils/src/resample/resample.cpp +++ b/common/motion_utils/src/resample/resample.cpp @@ -109,13 +109,16 @@ std::vector resamplePointVector( } std::vector resamplePoseVector( - const std::vector & points, + const std::vector & points_raw, const std::vector & resampled_arclength, const bool use_akima_spline_for_xy, const bool use_lerp_for_z) { + // Remove overlap points for resampling + const auto points = motion_utils::removeOverlapPoints(points_raw); + // validate arguments if (!resample_utils::validate_arguments(points, resampled_arclength)) { - return points; + return points_raw; } std::vector position(points.size()); From ed0da3205aa57ddf3492337e6858d61a52a51976 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:05:46 +0300 Subject: [PATCH 211/547] fix(lidar_apollo_segmentation_tvm): add include tier4_autoware_utils and dependency (#4908) * add include tier4_autoware_utils and dependency Signed-off-by: Alexey Panferov * style(pre-commit): autofix --------- Signed-off-by: Alexey Panferov Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- perception/lidar_apollo_segmentation_tvm/package.xml | 1 + .../src/lidar_apollo_segmentation_tvm.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/perception/lidar_apollo_segmentation_tvm/package.xml b/perception/lidar_apollo_segmentation_tvm/package.xml index f4f283abe9450..21cd79c27b205 100755 --- a/perception/lidar_apollo_segmentation_tvm/package.xml +++ b/perception/lidar_apollo_segmentation_tvm/package.xml @@ -21,6 +21,7 @@ tf2_eigen tf2_geometry_msgs tf2_ros + tier4_autoware_utils tier4_perception_msgs tvm_utility tvm_vendor diff --git a/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp b/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp index fb7ad38d7d642..905d70235e33e 100644 --- a/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp +++ b/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include From b5db5bc8f122fd0ea4e92d66eab7b3ad8fad73a5 Mon Sep 17 00:00:00 2001 From: Tomoya Kimura Date: Fri, 15 Sep 2023 12:37:26 +0900 Subject: [PATCH 212/547] fix(adaptive_cruise_planner): prevent large vibration of acceleration command (#4755) Signed-off-by: tomoya.kimura --- .../src/adaptive_cruise_control.cpp | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp b/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp index 8064aa4764a6d..b33342b9dea4f 100644 --- a/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp +++ b/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp @@ -14,6 +14,8 @@ #include "obstacle_stop_planner/adaptive_cruise_control.hpp" +#include "motion_utils/trajectory/trajectory.hpp" + #include #include #include @@ -371,9 +373,7 @@ void AdaptiveCruiseController::calcDistanceToNearestPointOnPath( /* get total distance to collision point */ double dist_to_point = 0; // get distance from self to next nearest point - dist_to_point += boost::geometry::distance( - convertPointRosToBoost(self_pose.position), - convertPointRosToBoost(trajectory.at(1).pose.position)); + dist_to_point += motion_utils::calcSignedArcLength(trajectory, self_pose.position, size_t(1)); // add distance from next self-nearest-point(=idx:0) to prev point of nearest_point_idx for (int i = 1; i < nearest_point_idx - 1; i++) { @@ -678,15 +678,13 @@ void AdaptiveCruiseController::insertMaxVelocityToPath( const geometry_msgs::msg::Pose self_pose, const double current_vel, const double target_vel, const double dist_to_collision_point, TrajectoryPoints * output_trajectory) { - // plus distance from self to next nearest point - double dist = dist_to_collision_point; + // signed distance from self pose to the point of index 1 double dist_to_first_point = 0.0; + if (output_trajectory->size() > 1) { - dist_to_first_point = boost::geometry::distance( - convertPointRosToBoost(self_pose.position), - convertPointRosToBoost(output_trajectory->at(1).pose.position)); + dist_to_first_point = + motion_utils::calcSignedArcLength(*output_trajectory, self_pose.position, size_t(1)); } - dist += dist_to_first_point; double margin_to_insert = dist_to_collision_point * param_.margin_rate_to_change_vel; // accel = (v_after^2 - v_before^2 ) / 2x @@ -695,7 +693,7 @@ void AdaptiveCruiseController::insertMaxVelocityToPath( const double clipped_acc = boost::algorithm::clamp( target_acc, param_.min_standard_acceleration, param_.max_standard_acceleration); double pre_vel = current_vel; - double total_dist = 0.0; + double total_dist = dist_to_first_point; for (size_t i = 1; i < output_trajectory->size(); i++) { // calc velocity of each point by gradient deceleration const auto current_p = output_trajectory->at(i); @@ -708,7 +706,8 @@ void AdaptiveCruiseController::insertMaxVelocityToPath( next_pre_vel = pre_vel; } else { // v_after = sqrt (2x*accel + v_before^2) - next_pre_vel = std::sqrt(2 * p_dist * clipped_acc + std::pow(pre_vel, 2)); + next_pre_vel = + std::sqrt(2 * std::min(p_dist, total_dist) * clipped_acc + std::pow(pre_vel, 2)); } if (target_acc >= 0) { next_pre_vel = std::min(next_pre_vel, target_vel); From 7ad98f38a3c2a3d327417d7a9d7e23305448dcef Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:30:28 +0900 Subject: [PATCH 213/547] feat: support transverse mercator projection (#4883) * feat: support transverse mercator projection Signed-off-by: kminoda * fix some Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../src/lanelet2_projector.cpp | 16 +- map/map_loader/README.md | 6 +- map/map_projection_loader/CMakeLists.txt | 4 + map/map_projection_loader/README.md | 14 ++ .../src/map_projection_loader.cpp | 12 +- ...ap_projector_info_transverse_mercator.yaml | 6 + ...load_transverse_mercator_from_yaml.test.py | 150 ++++++++++++++++++ 7 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 map/map_projection_loader/test/data/map_projector_info_transverse_mercator.yaml create mode 100644 map/map_projection_loader/test/test_node_load_transverse_mercator_from_yaml.test.py diff --git a/common/geography_utils/src/lanelet2_projector.cpp b/common/geography_utils/src/lanelet2_projector.cpp index d487a5273ce79..4958d661b0ce3 100644 --- a/common/geography_utils/src/lanelet2_projector.cpp +++ b/common/geography_utils/src/lanelet2_projector.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -30,12 +31,23 @@ std::unique_ptr get_lanelet2_projector(const MapProjectorInf lanelet::Origin origin{position}; lanelet::projection::UtmProjector projector{origin}; return std::make_unique(projector); + } else if (projector_info.projector_type == MapProjectorInfo::MGRS) { lanelet::projection::MGRSProjector projector{}; return std::make_unique(projector); + + } else if (projector_info.projector_type == MapProjectorInfo::TRANSVERSE_MERCATOR) { + lanelet::GPSPoint position{ + projector_info.map_origin.latitude, projector_info.map_origin.longitude, + projector_info.map_origin.altitude}; + lanelet::Origin origin{position}; + lanelet::projection::TransverseMercatorProjector projector{origin}; + return std::make_unique(projector); + } else { - const std::string error_msg = "Invalid map projector type: " + projector_info.projector_type + - ". Currently supported types: MGRS, and LocalCartesianUTM"; + const std::string error_msg = + "Invalid map projector type: " + projector_info.projector_type + + ". Currently supported types: MGRS, LocalCartesianUTM, and TransverseMercator"; throw std::invalid_argument(error_msg); } } diff --git a/map/map_loader/README.md b/map/map_loader/README.md index 816cfc418f016..e39b6d66af86c 100644 --- a/map/map_loader/README.md +++ b/map/map_loader/README.md @@ -141,11 +141,7 @@ Please see [the description of `GetSelectedPointCloudMap.srv`](https://github.co lanelet2_map_loader loads Lanelet2 file and publishes the map data as autoware_auto_mapping_msgs/HADMapBin message. The node projects lan/lon coordinates into arbitrary coordinates defined in `/map/map_projector_info` from `map_projection_loader`. -The node supports the following three types of coordinate systems: - -- MGRS -- LocalCartesianUTM -- local +Please see [tier4_autoware_msgs/msg/MapProjectorInfo.msg](https://github.com/tier4/tier4_autoware_msgs/blob/tier4/universe/tier4_map_msgs/msg/MapProjectorInfo.msg) for supported projector types. ### How to run diff --git a/map/map_projection_loader/CMakeLists.txt b/map/map_projection_loader/CMakeLists.txt index 8658ba172fde2..700d468ed4431 100644 --- a/map/map_projection_loader/CMakeLists.txt +++ b/map/map_projection_loader/CMakeLists.txt @@ -42,6 +42,10 @@ if(BUILD_TESTING) test/test_node_load_local_cartesian_utm_from_yaml.test.py TIMEOUT "30" ) + add_launch_test( + test/test_node_load_transverse_mercator_from_yaml.test.py + TIMEOUT "30" + ) install(DIRECTORY test/data/ DESTINATION share/${PROJECT_NAME}/test/data/ diff --git a/map/map_projection_loader/README.md b/map/map_projection_loader/README.md index 13808e6bf0228..0b0c41821d036 100644 --- a/map/map_projection_loader/README.md +++ b/map/map_projection_loader/README.md @@ -53,6 +53,20 @@ map_origin: altitude: 0.0 # [m] ``` +### Using TransverseMercator + +If you want to use Transverse Mercator projection, please specify the map origin as well. + +```yaml +# map_projector_info.yaml +projector_type: TransverseMercator +vertical_datum: WGS84 +map_origin: + latitude: 35.6762 # [deg] + longitude: 139.6503 # [deg] + altitude: 0.0 # [m] +``` + ## Published Topics - ~/map_projector_info (tier4_map_msgs/MapProjectorInfo) : Topic for defining map projector information diff --git a/map/map_projection_loader/src/map_projection_loader.cpp b/map/map_projection_loader/src/map_projection_loader.cpp index 74c340ca53557..9fdba288601cc 100644 --- a/map/map_projection_loader/src/map_projection_loader.cpp +++ b/map/map_projection_loader/src/map_projection_loader.cpp @@ -31,16 +31,26 @@ tier4_map_msgs::msg::MapProjectorInfo load_info_from_yaml(const std::string & fi if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::MGRS) { msg.vertical_datum = data["vertical_datum"].as(); msg.mgrs_grid = data["mgrs_grid"].as(); + } else if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL_CARTESIAN_UTM) { msg.vertical_datum = data["vertical_datum"].as(); msg.map_origin.latitude = data["map_origin"]["latitude"].as(); msg.map_origin.longitude = data["map_origin"]["longitude"].as(); msg.map_origin.altitude = data["map_origin"]["altitude"].as(); + + } else if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::TRANSVERSE_MERCATOR) { + msg.vertical_datum = data["vertical_datum"].as(); + msg.map_origin.latitude = data["map_origin"]["latitude"].as(); + msg.map_origin.longitude = data["map_origin"]["longitude"].as(); + msg.map_origin.altitude = data["map_origin"]["altitude"].as(); + } else if (msg.projector_type == tier4_map_msgs::msg::MapProjectorInfo::LOCAL) { ; // do nothing + } else { throw std::runtime_error( - "Invalid map projector type. Currently supported types: MGRS, LocalCartesianUTM, and local"); + "Invalid map projector type. Currently supported types: MGRS, LocalCartesianUTM, " + "TransverseMercator, and local"); } return msg; } diff --git a/map/map_projection_loader/test/data/map_projector_info_transverse_mercator.yaml b/map/map_projection_loader/test/data/map_projector_info_transverse_mercator.yaml new file mode 100644 index 0000000000000..de1febeb0a283 --- /dev/null +++ b/map/map_projection_loader/test/data/map_projector_info_transverse_mercator.yaml @@ -0,0 +1,6 @@ +projector_type: TransverseMercator +vertical_datum: WGS84 +map_origin: + latitude: 35.6762 # [deg] + longitude: 139.6503 # [deg] + altitude: 0.0 # [m] diff --git a/map/map_projection_loader/test/test_node_load_transverse_mercator_from_yaml.test.py b/map/map_projection_loader/test/test_node_load_transverse_mercator_from_yaml.test.py new file mode 100644 index 0000000000000..7883ae6aa3c99 --- /dev/null +++ b/map/map_projection_loader/test/test_node_load_transverse_mercator_from_yaml.test.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 + +# Copyright 2022 TIER IV, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +from ament_index_python import get_package_share_directory +import launch +from launch import LaunchDescription +from launch.logging import get_logger +from launch_ros.actions import Node +import launch_testing +import pytest +import rclpy +from rclpy.qos import QoSDurabilityPolicy +from rclpy.qos import QoSHistoryPolicy +from rclpy.qos import QoSProfile +from tier4_map_msgs.msg import MapProjectorInfo +import yaml + +logger = get_logger(__name__) + +YAML_FILE_PATH = "test/data/map_projector_info_transverse_mercator.yaml" + + +@pytest.mark.launch_test +def generate_test_description(): + map_projector_info_path = os.path.join( + get_package_share_directory("map_projection_loader"), YAML_FILE_PATH + ) + + map_projection_loader_node = Node( + package="map_projection_loader", + executable="map_projection_loader", + output="screen", + parameters=[ + { + "map_projector_info_path": map_projector_info_path, + "lanelet2_map_path": "", + "use_local_projector": False, + }, + ], + ) + + context = {} + + return ( + LaunchDescription( + [ + map_projection_loader_node, + # Start test after 1s - gives time for the static_centerline_optimizer to finish initialization + launch.actions.TimerAction( + period=1.0, actions=[launch_testing.actions.ReadyToTest()] + ), + ] + ), + context, + ) + + +class TestLoadUTMFromYaml(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Initialize the ROS context for the test node + rclpy.init() + + @classmethod + def tearDownClass(cls): + # Shutdown the ROS context + rclpy.shutdown() + + def setUp(self): + # Create a ROS node for tests + self.test_node = rclpy.create_node("test_node") + self.evaluation_time = 0.2 # 200ms + self.received_message = None + + def tearDown(self): + self.test_node.destroy_node() + + def callback(self, msg): + self.received_message = msg + + @staticmethod + def print_message(stat): + logger.debug("===========================") + logger.debug(stat) + + def test_node_link(self): + # Create custom QoS profile for subscription + custom_qos_profile = QoSProfile( + depth=1, + history=QoSHistoryPolicy.KEEP_LAST, + durability=QoSDurabilityPolicy.TRANSIENT_LOCAL, + ) + + # Create subscription to map_projector_info topic + subscription = self.test_node.create_subscription( + MapProjectorInfo, + "/map/map_projector_info", + self.callback, + custom_qos_profile, + ) + + # Give time for the message to be received and processed + rclpy.spin_until_future_complete( + self.test_node, rclpy.task.Future(), timeout_sec=self.evaluation_time + ) + + # Load the yaml file directly + map_projector_info_path = os.path.join( + get_package_share_directory("map_projection_loader"), YAML_FILE_PATH + ) + with open(map_projector_info_path) as f: + yaml_data = yaml.load(f, Loader=yaml.FullLoader) + + # Test if message received + self.assertIsNotNone( + self.received_message, "No message received on map_projector_info topic" + ) + self.assertEqual(self.received_message.projector_type, yaml_data["projector_type"]) + self.assertEqual(self.received_message.vertical_datum, yaml_data["vertical_datum"]) + self.assertEqual( + self.received_message.map_origin.latitude, yaml_data["map_origin"]["latitude"] + ) + self.assertEqual( + self.received_message.map_origin.longitude, yaml_data["map_origin"]["longitude"] + ) + + self.test_node.destroy_subscription(subscription) + + +@launch_testing.post_shutdown_test() +class TestProcessOutput(unittest.TestCase): + def test_exit_code(self, proc_info): + # Check that process exits with code 0: no error + launch_testing.asserts.assertExitCodes(proc_info) From 229ce22449dce65c8c4e66893f94f3ad1dccf5f6 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:57:07 +0900 Subject: [PATCH 214/547] refactor(safety_check): check if the object is oncoming (#4997) * refactor(safety_check): add utils function Signed-off-by: satoshi-ota * refactor(avoidance): use common function Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../utils/path_safety_checker/safety_check.hpp | 5 +++++ .../src/scene_module/avoidance/avoidance_module.cpp | 6 +++--- .../src/utils/path_safety_checker/safety_check.cpp | 6 ++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index a9404038d831a..f9fbf70b2113b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -20,6 +20,7 @@ #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include +#include #include #include @@ -46,12 +47,16 @@ using autoware_auto_perception_msgs::msg::Shape; using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; +using tier4_autoware_utils::calcYawDeviation; using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; using vehicle_info_util::VehicleInfo; namespace bg = boost::geometry; +bool isTargetObjectOncoming( + const geometry_msgs::msg::Pose & vehicle_pose, const geometry_msgs::msg::Pose & object_pose); + bool isTargetObjectFront( const geometry_msgs::msg::Pose & ego_pose, const Polygon2d & obj_polygon, const vehicle_info_util::VehicleInfo & vehicle_info); diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index eee87a09a94db..239682c7f5cd4 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1879,13 +1879,13 @@ bool AvoidanceModule::isSafePath( const auto is_object_front = utils::path_safety_checker::isTargetObjectFront(getEgoPose(), obj_polygon, p.vehicle_info); - const auto is_object_incoming = - std::abs(calcYawDeviation(getEgoPose(), object.initial_pose.pose)) > M_PI_2; + const auto is_object_oncoming = + utils::path_safety_checker::isTargetObjectOncoming(getEgoPose(), object.initial_pose.pose); const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( object, parameters_->check_all_predicted_path); - const auto & ego_predicted_path = is_object_front && !is_object_incoming + const auto & ego_predicted_path = is_object_front && !is_object_oncoming ? ego_predicted_path_for_front_object : ego_predicted_path_for_rear_object; diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index dddcc672e7107..f0f494756863a 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -32,6 +32,12 @@ void appendPointToPolygon(Polygon2d & polygon, const geometry_msgs::msg::Point & bg::append(polygon.outer(), point); } +bool isTargetObjectOncoming( + const geometry_msgs::msg::Pose & vehicle_pose, const geometry_msgs::msg::Pose & object_pose) +{ + return std::abs(calcYawDeviation(vehicle_pose, object_pose)) > M_PI_2; +} + bool isTargetObjectFront( const geometry_msgs::msg::Pose & ego_pose, const Polygon2d & obj_polygon, const vehicle_info_util::VehicleInfo & vehicle_info) From afe8990441ad9c583a6fade5e0c3bb74697f8b92 Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Fri, 15 Sep 2023 16:04:23 +0900 Subject: [PATCH 215/547] fix(rviz config): remove initial_pose_button_panel (#4959) Signed-off-by: yamato-ando Co-authored-by: yamato-ando --- .../centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz | 4 ---- .../rviz/centerpoint_tiny.rviz | 4 ---- .../rviz/obstacle_avoidance_planner.rviz | 4 ---- .../rviz/static_centerline_optimizer.rviz | 4 ---- .../accel_brake_map_calibrator/rviz/occupancy.rviz | 6 ------ 5 files changed, 22 deletions(-) diff --git a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz index 2fc71be9d34dc..879837e101d47 100644 --- a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz +++ b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint.rviz @@ -24,8 +24,6 @@ Panels: - /Current View1 Name: Views Splitter Ratio: 0.5 - - Class: tier4_localization_rviz_plugin/InitialPoseButtonPanel - Name: InitialPoseButtonPanel - Class: AutowareDateTimePanel Name: AutowareDateTimePanel Visualization Manager: @@ -1820,8 +1818,6 @@ Window Geometry: Height: 1028 Hide Left Dock: true Hide Right Dock: false - InitialPoseButtonPanel: - collapsed: false QMainWindow State: 000000ff00000000fd0000000400000000000001bc000003b6fc020000000efb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006e00fffffffb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073000000002d0000022a000000f100fffffffc0000025e000001320000000000fffffffaffffffff0100000002fb0000000a0056006900650077007300000000000000033c0000010200fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730000000000ffffffff000000b400fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000c00430061006d0065007200610100000682000000eb0000000000000000fb0000000a0049006d0061006700650100000505000002680000000000000000fb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000008d00fffffffb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000000000000000fb00000030005200650063006f0067006e006900740069006f006e0052006500730075006c0074004f006e0049006d00610067006500000004f2000001130000002f00fffffffb0000002a004100750074006f0077006100720065004400610074006500540069006d006500500061006e0065006c00000003970000004c0000004c00ffffff000000010000015f000006fffc0200000002fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000e7a0000005afc0100000001fb0000000a00560069006500770073030000004e00000080000002e1000001970000000300000e7a0000005afc0100000002fb0000000800540069006d0065010000000000000e7a0000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003a0000003b600000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 RecognitionResultOnImage: collapsed: false diff --git a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz index 4a4c42f2b5e8e..539f89305e3c6 100644 --- a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz +++ b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/rviz/centerpoint_tiny.rviz @@ -25,8 +25,6 @@ Panels: - /Current View1 Name: Views Splitter Ratio: 0.5 - - Class: tier4_localization_rviz_plugin/InitialPoseButtonPanel - Name: InitialPoseButtonPanel - Class: AutowareDateTimePanel Name: AutowareDateTimePanel Visualization Manager: @@ -1821,8 +1819,6 @@ Window Geometry: Height: 1028 Hide Left Dock: true Hide Right Dock: false - InitialPoseButtonPanel: - collapsed: false QMainWindow State: 000000ff00000000fd000000040000000000000363000003b6fc020000000efb0000001200530065006c0065006300740069006f006e00000001e10000009b0000006e00fffffffb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073000000002d0000020a000000f100fffffffc0000023e0000011e0000000000fffffffaffffffff0100000002fb0000000a0056006900650077007300000000000000033c0000010200fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730000000000ffffffff000000b400fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000c00430061006d0065007200610100000682000000eb0000000000000000fb0000000a0049006d0061006700650100000505000002680000000000000000fb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000008d00fffffffb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000000000000000fb00000030005200650063006f0067006e006900740069006f006e0052006500730075006c0074004f006e0049006d00610067006500000004f2000001130000002f00fffffffb0000002a004100750074006f0077006100720065004400610074006500540069006d006500500061006e0065006c0000000363000000800000004c00ffffff000000010000015f000006fffc0200000002fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000e7a0000005afc0100000001fb0000000a00560069006500770073030000004e00000080000002e1000001970000000300000e7a0000005afc0100000002fb0000000800540069006d0065010000000000000e7a0000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003a0000003b600000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 RecognitionResultOnImage: collapsed: false diff --git a/planning/obstacle_avoidance_planner/rviz/obstacle_avoidance_planner.rviz b/planning/obstacle_avoidance_planner/rviz/obstacle_avoidance_planner.rviz index 8e62886d255ea..c3cd89bbce9b9 100644 --- a/planning/obstacle_avoidance_planner/rviz/obstacle_avoidance_planner.rviz +++ b/planning/obstacle_avoidance_planner/rviz/obstacle_avoidance_planner.rviz @@ -24,8 +24,6 @@ Panels: - /Current View1 Name: Views Splitter Ratio: 0.5 - - Class: tier4_localization_rviz_plugin/InitialPoseButtonPanel - Name: InitialPoseButtonPanel - Class: AutowareDateTimePanel Name: AutowareDateTimePanel - Class: rviz_plugins::AutowareStatePanel @@ -1451,8 +1449,6 @@ Window Geometry: Height: 2032 Hide Left Dock: false Hide Right Dock: false - InitialPoseButtonPanel: - collapsed: false QMainWindow State: 000000ff00000000fd00000004000000000000034400000754fc020000000ffb0000001200530065006c0065006300740069006f006e00000001e10000009b000000b000fffffffb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000006e000001ef0000018200fffffffc00000269000001d30000016c01000039fa000000000100000002fb0000000a0056006900650077007301000000000000033c0000015f00fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000000ffffffff0000010b00fffffffb00000024004100750074006f00770061007200650053007400610074006500500061006e0065006c01000004480000037a000002b400fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000c00430061006d0065007200610100000682000000eb0000000000000000fb0000000a0049006d0061006700650100000505000002680000000000000000fb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de000000de00fffffffb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000000000000000fb00000030005200650063006f0067006e006900740069006f006e0052006500730075006c0074004f006e0049006d006100670065000000035a000000530000004a00fffffffb0000002a004100750074006f0077006100720065004400610074006500540069006d006500500061006e0065006c00000003b3000000420000007400ffffff000000010000015f000006fffc0200000002fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000e7a0000005afc0100000001fb0000000a00560069006500770073030000004e00000080000002e1000001970000000300000e7a0000005afc0100000002fb0000000800540069006d0065010000000000000e7a0000000000000000fb0000000800540069006d0065010000000000000450000000000000000000000b1c0000075400000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 RecognitionResultOnImage: collapsed: false diff --git a/planning/static_centerline_optimizer/rviz/static_centerline_optimizer.rviz b/planning/static_centerline_optimizer/rviz/static_centerline_optimizer.rviz index c2df606fbaf41..1e7573d561ba1 100644 --- a/planning/static_centerline_optimizer/rviz/static_centerline_optimizer.rviz +++ b/planning/static_centerline_optimizer/rviz/static_centerline_optimizer.rviz @@ -18,8 +18,6 @@ Panels: - /Current View1 Name: Views Splitter Ratio: 0.5 - - Class: tier4_localization_rviz_plugin/InitialPoseButtonPanel - Name: InitialPoseButtonPanel - Class: rviz_common/Time Experimental: false Name: Time @@ -423,8 +421,6 @@ Window Geometry: Height: 1043 Hide Left Dock: false Hide Right Dock: false - InitialPoseButtonPanel: - collapsed: false QMainWindow State: 000000ff00000000fd00000004000000000000019200000357fc020000000efb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003e000001c0000000ca00fffffffc0000020400000191000000c40100001dfa000000000100000002fb0000000a0056006900650077007301000000000000033c0000010000fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000000ffffffff0000008a00fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000c00430061006d0065007200610100000682000000eb0000000000000000fb0000000a0049006d0061006700650100000505000002680000000000000000fb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000007a00fffffffb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000000000000000fb00000030005200650063006f0067006e006900740069006f006e0052006500730075006c0074004f006e0049006d00610067006500000001d3000000af0000000000000000fb00000024004100750074006f00770061007200650053007400610074006500500061006e0065006c000000029b000000fb0000017c00ffffff000000010000015f000006fffc0200000002fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000e7a0000005afc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000007800000005afc0100000002fb0000000800540069006d00650100000000000007800000027100fffffffb0000000800540069006d00650100000000000004500000000000000000000005e80000035700000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Selection: collapsed: false diff --git a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/rviz/occupancy.rviz b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/rviz/occupancy.rviz index c430c21562f32..ffcb63f428f1b 100644 --- a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/rviz/occupancy.rviz +++ b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/rviz/occupancy.rviz @@ -17,10 +17,6 @@ Panels: - /Current View1 Name: Views Splitter Ratio: 0.5 - - Class: tier4_localization_rviz_plugin/InitialPoseButtonPanel - Name: InitialPoseButtonPanel - - Class: tier4_localization_rviz_plugin/InitialPoseButtonPanel - Name: InitialPoseButtonPanel Preferences: PromptSaveOnExit: true Toolbars: @@ -133,8 +129,6 @@ Window Geometry: Height: 1862 Hide Left Dock: true Hide Right Dock: true - InitialPoseButtonPanel: - collapsed: false QMainWindow State: 000000ff00000000fd0000000400000000000005070000068cfc020000000efb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073000000003d0000068c000000c900fffffffb0000000a00560069006500770073000000030c000003bd000000a400fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007300000005470000012b0000000000000000fb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000c00430061006d0065007200610100000682000000eb0000000000000000fb0000000a0049006d00610067006501000005d6000000f30000000000000000fb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000007700fffffffb0000002c0049006e0069007400690061006c0050006f007300650042007500740074006f006e00500061006e0065006c000000068f000000de0000007700fffffffb0000000a0049006d00610067006500000002ef000003da0000000000000000000000010000015f000006fffc0200000002fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000006fb0000005afc0100000002fb0000000800540069006d00650100000000000006fb000002eb00fffffffb0000000800540069006d00650100000000000004500000000000000000000006fb0000068c00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 Selection: collapsed: false From fd7db70e55a19adad1d1a53302b76969bd8f6925 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Fri, 15 Sep 2023 20:37:14 +0900 Subject: [PATCH 216/547] chore(build): only include forward declaration of lanelet stuff in route_handler header files (#4996) Signed-off-by: Mamoru Sobue --- .../behavior_path_planner/data_manager.hpp | 2 +- .../marker_utils/avoidance/debug.hpp | 3 +- .../marker_utils/utils.hpp | 4 +-- .../behavior_path_planner/planner_manager.hpp | 30 ++----------------- .../start_planner/start_planner_module.hpp | 2 -- .../turn_signal_decider.hpp | 2 +- .../utils/avoidance/avoidance_module_data.hpp | 3 +- .../geometric_parallel_parking.hpp | 3 +- .../utils/goal_planner/util.hpp | 2 +- .../lane_change/lane_change_module_data.hpp | 2 ++ .../utils/lane_change/utils.hpp | 2 +- .../utils/path_utils.hpp | 2 +- .../utils/start_planner/util.hpp | 2 +- .../behavior_path_planner/utils/utils.hpp | 3 +- .../src/marker_utils/avoidance/debug.cpp | 1 + .../src/marker_utils/utils.cpp | 6 +++- .../src/planner_manager.cpp | 30 ++++++++++++++++++- .../avoidance/avoidance_module.cpp | 3 ++ .../dynamic_avoidance_module.cpp | 3 ++ .../goal_planner/goal_planner_module.cpp | 1 + .../lane_change/avoidance_by_lane_change.cpp | 4 +++ .../src/scene_module/lane_change/normal.cpp | 3 ++ .../start_planner/start_planner_module.cpp | 5 ++++ .../src/turn_signal_decider.cpp | 1 + .../src/utils/avoidance/utils.cpp | 2 ++ .../geometric_parallel_parking.cpp | 5 +++- .../src/utils/goal_planner/goal_searcher.cpp | 4 +++ .../src/utils/goal_planner/util.cpp | 2 +- .../src/utils/lane_change/utils.cpp | 4 ++- .../src/utils/path_utils.cpp | 1 + .../behavior_path_planner/src/utils/utils.cpp | 7 ++++- .../test/test_drivable_area_expansion.cpp | 1 + .../behavior_path_planner/test/test_utils.cpp | 1 + .../src/scene_crosswalk.cpp | 3 ++ .../src/scene_crosswalk.hpp | 7 ++--- .../src/util.cpp | 6 ++-- .../src/scene.cpp | 2 ++ .../src/util.cpp | 2 ++ .../src/scene_no_stopping_area.cpp | 2 +- .../src/decisions.cpp | 7 ++++- .../src/decisions.hpp | 1 + .../src/filter_predicted_objects.hpp | 2 ++ .../src/lanelets_selection.cpp | 1 + .../behavior_velocity_planner/src/node.cpp | 2 +- .../planner_data.hpp | 5 ---- .../utilization/util.hpp | 17 ++++------- .../src/utilization/util.cpp | 4 +++ .../src/debug.cpp | 1 + .../src/scene.hpp | 1 + .../src/util.cpp | 2 ++ .../src/mission_planner/mission_planner.cpp | 2 ++ .../include/route_handler/route_handler.hpp | 17 ++++------- planning/route_handler/package.xml | 1 + planning/route_handler/src/route_handler.cpp | 9 +++++- .../static_centerline_optimizer/utils.hpp | 2 ++ .../src/static_centerline_optimizer_node.cpp | 2 ++ .../static_centerline_optimizer/src/utils.cpp | 3 ++ 57 files changed, 157 insertions(+), 90 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp index 78172c6fc0750..0a7b59d52ff5a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp @@ -36,7 +36,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp index d308f55799ce0..100e8046f8d7f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/avoidance/debug.hpp @@ -25,8 +25,7 @@ #include #include -#include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index c53735486efb0..244e1a8a71616 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -18,15 +18,13 @@ #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" -#include - #include #include #include #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index 4bdfc807aa7c4..589ccebf5b0cb 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -21,12 +21,13 @@ #include "behavior_path_planner/utils/lane_following/module_data.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" -#include #include #include #include +#include + #include #include #include @@ -279,32 +280,7 @@ class PlannerManager * @param planner data. * @return reference path. */ - BehaviorModuleOutput getReferencePath(const std::shared_ptr & data) const - { - const auto & route_handler = data->route_handler; - const auto & pose = data->self_odometry->pose.pose; - const auto p = data->parameters; - - constexpr double extra_margin = 10.0; - const auto backward_length = - std::max(p.backward_path_length, p.backward_path_length + extra_margin); - - const auto lanelet_sequence = route_handler->getLaneletSequence( - root_lanelet_.get(), pose, backward_length, std::numeric_limits::max()); - - lanelet::ConstLanelet closest_lane{}; - if (lanelet::utils::query::getClosestLaneletWithConstrains( - lanelet_sequence, pose, &closest_lane, p.ego_nearest_dist_threshold, - p.ego_nearest_yaw_threshold)) { - return utils::getReferencePath(closest_lane, data); - } - - if (lanelet::utils::query::getClosestLanelet(lanelet_sequence, pose, &closest_lane)) { - return utils::getReferencePath(closest_lane, data); - } - - return {}; // something wrong. - } + BehaviorModuleOutput getReferencePath(const std::shared_ptr & data) const; /** * @brief stop and unregister the module from manager. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index e9a94126d9f0e..6fd2045b4f097 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -28,8 +28,6 @@ #include "behavior_path_planner/utils/utils.hpp" #include -#include -#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/turn_signal_decider.hpp b/planning/behavior_path_planner/include/behavior_path_planner/turn_signal_decider.hpp index b550f132c27da..3a983d66eb98c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/turn_signal_decider.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/turn_signal_decider.hpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 9a47a6728ddb7..ed44e83608991 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -27,7 +27,8 @@ #include #include -#include +#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp index 9cae5e18f1352..0f8a08339978e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp @@ -28,8 +28,7 @@ #include #include -#include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp index 0adda77a2ad72..b7a9248d569c1 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index 0556a780467c0..65e4ad1160725 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -21,6 +21,8 @@ #include "autoware_auto_planning_msgs/msg/path_point_with_lane_id.hpp" +#include + #include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index f59e0170bdf14..f180afc2d1ffd 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp index 191cd3e10def5..e907df8c7a58a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp @@ -26,7 +26,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp index d85e6181764d5..09fb9911f7386 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp @@ -28,7 +28,7 @@ #include #include -#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp index d5e1d22fdcc88..3d81c584b7c01 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp @@ -39,8 +39,7 @@ #include #include -#include -#include +#include #include #ifdef ROS_DISTRO_GALACTIC diff --git a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp index b2c326fda7a69..14a7d5be6a58a 100644 --- a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp @@ -20,6 +20,7 @@ #include +#include #include #include diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index b51fc585ce935..4cec9b958f904 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -19,9 +19,13 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" -#include #include #include + +#include +#include +#include + namespace marker_utils { using behavior_path_planner::ShiftLine; diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index ee9c445e9fd2f..7a7ee046ca069 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -18,7 +18,7 @@ #include "behavior_path_planner/utils/utils.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" -#include +#include #include #include @@ -342,6 +342,34 @@ std::vector PlannerManager::getRequestModules( return request_modules; } +BehaviorModuleOutput PlannerManager::getReferencePath( + const std::shared_ptr & data) const +{ + const auto & route_handler = data->route_handler; + const auto & pose = data->self_odometry->pose.pose; + const auto p = data->parameters; + + constexpr double extra_margin = 10.0; + const auto backward_length = + std::max(p.backward_path_length, p.backward_path_length + extra_margin); + + const auto lanelet_sequence = route_handler->getLaneletSequence( + root_lanelet_.get(), pose, backward_length, std::numeric_limits::max()); + + lanelet::ConstLanelet closest_lane{}; + if (lanelet::utils::query::getClosestLaneletWithConstrains( + lanelet_sequence, pose, &closest_lane, p.ego_nearest_dist_threshold, + p.ego_nearest_yaw_threshold)) { + return utils::getReferencePath(closest_lane, data); + } + + if (lanelet::utils::query::getClosestLanelet(lanelet_sequence, pose, &closest_lane)) { + return utils::getReferencePath(closest_lane, data); + } + + return {}; // something wrong. +} + SceneModulePtr PlannerManager::selectHighestPriorityModule( std::vector & request_modules) const { diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 239682c7f5cd4..0f2605735eda1 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -32,6 +32,9 @@ #include #include +#include +#include + #include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index b5ecffbcc9791..a4464d9d834b4 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -22,6 +22,9 @@ #include #include +#include +#include + #include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index bed4aecf733b9..629ba240792e5 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -24,6 +24,7 @@ #include "tier4_autoware_utils/math/unit_conversion.hpp" #include +#include #include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp index a8ff0c0bbcf02..6d06b26cd1e17 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp @@ -19,6 +19,10 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include +#include + +#include +#include #include diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index a91da98b11999..2f030689a29f5 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -25,6 +25,9 @@ #include #include +#include +#include + #include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index a285224404f12..58e493bf40f50 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -21,10 +21,15 @@ #include "behavior_path_planner/utils/start_planner/util.hpp" #include "motion_utils/trajectory/trajectory.hpp" +#include #include #include #include +#include + +#include + #include #include #include diff --git a/planning/behavior_path_planner/src/turn_signal_decider.cpp b/planning/behavior_path_planner/src/turn_signal_decider.cpp index 65e3e8343f49c..3fe30b11e3a57 100644 --- a/planning/behavior_path_planner/src/turn_signal_decider.cpp +++ b/planning/behavior_path_planner/src/turn_signal_decider.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 4afe5381daa64..058059037c6d6 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -28,6 +28,8 @@ #include +#include + #include #include #include diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index 5b054de43ef74..32affebe1aebd 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -22,7 +22,6 @@ #include "tier4_autoware_utils/math/unit_conversion.hpp" #include -#include #include #ifdef ROS_DISTRO_GALACTIC @@ -32,8 +31,12 @@ #include #include + +#include #endif +#include + #include #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index eb4a2eb1e7cbc..ab5476c542e4e 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -17,12 +17,16 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "lanelet2_extension/regulatory_elements/no_parking_area.hpp" +#include "lanelet2_extension/regulatory_elements/no_stopping_area.hpp" #include "lanelet2_extension/utility/utilities.hpp" #include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include +#include + #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index aa3380c2b99df..4f24d1b682f3d 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -20,11 +20,11 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index ffd5116a5d2dd..3e098c651feef 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -22,7 +22,6 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" -#include #include #include #include @@ -32,6 +31,9 @@ #include #include +#include +#include +#include #include #include diff --git a/planning/behavior_path_planner/src/utils/path_utils.cpp b/planning/behavior_path_planner/src/utils/path_utils.cpp index 112c7e49c7c86..7673c9a1de9b4 100644 --- a/planning/behavior_path_planner/src/utils/path_utils.cpp +++ b/planning/behavior_path_planner/src/utils/path_utils.cpp @@ -23,6 +23,7 @@ #include #include +// #include #include #include diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index d6af88f39a8d2..09e0e6cba011f 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -20,6 +20,9 @@ #include #include #include +// #include +// #include + #include #include #include @@ -27,7 +30,9 @@ #include "autoware_auto_perception_msgs/msg/predicted_object.hpp" #include "autoware_auto_perception_msgs/msg/predicted_path.hpp" -#include +#include +#include +#include #include #include diff --git a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp index 7a5bb68decdfa..e0a16089b8d84 100644 --- a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp @@ -20,6 +20,7 @@ #include "lanelet2_extension/utility/message_conversion.hpp" #include +#include using drivable_area_expansion::linestring_t; using drivable_area_expansion::point_t; diff --git a/planning/behavior_path_planner/test/test_utils.cpp b/planning/behavior_path_planner/test/test_utils.cpp index 1452e45980793..4eefa27e88806 100644 --- a/planning/behavior_path_planner/test/test_utils.cpp +++ b/planning/behavior_path_planner/test/test_utils.cpp @@ -14,6 +14,7 @@ #include "behavior_path_planner/utils/utils.hpp" #include "input.hpp" #include "lanelet2_core/Attribute.h" +#include "lanelet2_core/geometry/LineString.h" #include "lanelet2_core/geometry/Point.h" #include "lanelet2_core/primitives/Lanelet.h" #include "motion_utils/trajectory/path_with_lane_id.hpp" diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index ab84caa782d1c..dc37046e1a4d3 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -24,6 +24,9 @@ #include #include +#include +#include + #include #include #include diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp index 11e2d600c687b..667298cbb4c58 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp @@ -19,8 +19,6 @@ #include #include -#include -#include #include #include #include @@ -32,9 +30,8 @@ #include #include -#include -#include -#include +#include +#include #include #include #include diff --git a/planning/behavior_velocity_crosswalk_module/src/util.cpp b/planning/behavior_velocity_crosswalk_module/src/util.cpp index 6fabc4c201687..76d59ac895d5a 100644 --- a/planning/behavior_velocity_crosswalk_module/src/util.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/util.cpp @@ -15,6 +15,7 @@ #include "behavior_velocity_crosswalk_module/util.hpp" #include +#include #include #include #include @@ -27,6 +28,8 @@ #include #include +#include + #include #include #include @@ -37,9 +40,8 @@ #include #include #include -#include -#include +#include #include namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_detection_area_module/src/scene.cpp b/planning/behavior_velocity_detection_area_module/src/scene.cpp index 53a6deb6f26a4..07e9419fe0308 100644 --- a/planning/behavior_velocity_detection_area_module/src/scene.cpp +++ b/planning/behavior_velocity_detection_area_module/src/scene.cpp @@ -24,6 +24,8 @@ #include #endif +#include + #include #include #include diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp index 889d0dbb064c5..68aaff80827e2 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp @@ -19,6 +19,8 @@ #include +#include + #include namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp b/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp index 3e24689f2e774..efa2ce60162f6 100644 --- a/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp +++ b/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp @@ -21,8 +21,8 @@ #include #include +#include #include - #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp index 2ebfa4ecbe431..331fe66103d8d 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp @@ -20,12 +20,17 @@ #include #include +#include + +#include +#include +#include + #include #include #include #include #include - namespace behavior_velocity_planner::out_of_lane { double distance_along_path(const EgoData & ego_data, const size_t target_idx) diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp index 1d1fe8bea94a6..2898595c2e74c 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.hpp @@ -25,6 +25,7 @@ #include #include +#include #include #include diff --git a/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp index 48869e5e3926d..13bad0d047922 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp @@ -17,6 +17,8 @@ #include "types.hpp" +#include + #include namespace behavior_velocity_planner::out_of_lane diff --git a/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp b/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp index 9924319b141f7..7f3b69f40840b 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp @@ -17,6 +17,7 @@ #include #include +#include #include diff --git a/planning/behavior_velocity_planner/src/node.cpp b/planning/behavior_velocity_planner/src/node.cpp index c4f4edd26416c..1bbe3eeeb1b78 100644 --- a/planning/behavior_velocity_planner/src/node.cpp +++ b/planning/behavior_velocity_planner/src/node.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -27,7 +28,6 @@ #include #include #include - #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp index 2668d83b0f510..7e48f410f363a 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp @@ -18,7 +18,6 @@ #include "route_handler/route_handler.hpp" #include -#include #include #include @@ -38,10 +37,6 @@ #include -#include -#include -#include -#include #include #include #include diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp index 6513a3c9a43a9..46798b7f9f15b 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp @@ -16,7 +16,6 @@ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__UTIL_HPP_ #include -#include #include #include @@ -25,8 +24,6 @@ #include #include #include -#include -#include #include #include #include @@ -34,14 +31,9 @@ #include #include -#include -#include -#include - +#include #include -#include -#include -#include +#include #include #include @@ -85,7 +77,10 @@ struct TrafficSignalStamped autoware_perception_msgs::msg::TrafficSignal signal; }; -using geometry_msgs::msg::Pose; +using Pose = geometry_msgs::msg::Pose; +using Point2d = tier4_autoware_utils::Point2d; +using LineString2d = tier4_autoware_utils::LineString2d; +using Polygon2d = tier4_autoware_utils::Polygon2d; using BasicPolygons2d = std::vector; using Polygons2d = std::vector; using autoware_auto_perception_msgs::msg::PredictedObject; diff --git a/planning/behavior_velocity_planner_common/src/utilization/util.cpp b/planning/behavior_velocity_planner_common/src/utilization/util.cpp index 3f8180956cf10..15216fcf469b6 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/util.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/util.cpp @@ -13,8 +13,12 @@ // limitations under the License. #include +#include #include +#include +#include + #include #include #include diff --git a/planning/behavior_velocity_speed_bump_module/src/debug.cpp b/planning/behavior_velocity_speed_bump_module/src/debug.cpp index 3f9a60d1d27e4..a2f78dfe85cd3 100644 --- a/planning/behavior_velocity_speed_bump_module/src/debug.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/debug.cpp @@ -15,6 +15,7 @@ #include "scene.hpp" #include +#include #include #include #include diff --git a/planning/behavior_velocity_speed_bump_module/src/scene.hpp b/planning/behavior_velocity_speed_bump_module/src/scene.hpp index 1a797adf2cd61..34de722055d3d 100644 --- a/planning/behavior_velocity_speed_bump_module/src/scene.hpp +++ b/planning/behavior_velocity_speed_bump_module/src/scene.hpp @@ -18,6 +18,7 @@ #include "util.hpp" #include +#include #include #include diff --git a/planning/behavior_velocity_speed_bump_module/src/util.cpp b/planning/behavior_velocity_speed_bump_module/src/util.cpp index c815dab29af75..21b283a397ead 100644 --- a/planning/behavior_velocity_speed_bump_module/src/util.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/util.cpp @@ -24,6 +24,8 @@ #include #include +#include + #include #include #include diff --git a/planning/mission_planner/src/mission_planner/mission_planner.cpp b/planning/mission_planner/src/mission_planner/mission_planner.cpp index 08da1260b000c..9e6ef248af336 100644 --- a/planning/mission_planner/src/mission_planner/mission_planner.cpp +++ b/planning/mission_planner/src/mission_planner/mission_planner.cpp @@ -24,6 +24,8 @@ #include #include +#include + #include #include #include diff --git a/planning/route_handler/include/route_handler/route_handler.hpp b/planning/route_handler/include/route_handler/route_handler.hpp index 6bd9756098558..b12ca7f648a7d 100644 --- a/planning/route_handler/include/route_handler/route_handler.hpp +++ b/planning/route_handler/include/route_handler/route_handler.hpp @@ -15,24 +15,19 @@ #ifndef ROUTE_HANDLER__ROUTE_HANDLER_HPP_ #define ROUTE_HANDLER__ROUTE_HANDLER_HPP_ -#include -#include +#include #include -#include -#include #include -#include #include #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -41,8 +36,6 @@ namespace route_handler { using autoware_auto_mapping_msgs::msg::HADMapBin; -using autoware_auto_planning_msgs::msg::Path; -using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using autoware_auto_planning_msgs::msg::PathWithLaneId; using autoware_planning_msgs::msg::LaneletRoute; using autoware_planning_msgs::msg::LaneletSegment; diff --git a/planning/route_handler/package.xml b/planning/route_handler/package.xml index 6304e21932676..29f0411a5aa80 100644 --- a/planning/route_handler/package.xml +++ b/planning/route_handler/package.xml @@ -9,6 +9,7 @@ Yutaka Shimizu Kosuke Takeuchi Takayuki Murooka + Mamoru Sobue Apache License 2.0 Fumiya Watanabe diff --git a/planning/route_handler/src/route_handler.cpp b/planning/route_handler/src/route_handler.cpp index 3d9dd8fe7a64a..ba9c46f157f2b 100644 --- a/planning/route_handler/src/route_handler.cpp +++ b/planning/route_handler/src/route_handler.cpp @@ -21,9 +21,15 @@ #include #include -#include +#include +#include +#include + #include #include +#include +#include +#include #include #include @@ -36,6 +42,7 @@ namespace { using autoware_auto_planning_msgs::msg::Path; +using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using autoware_auto_planning_msgs::msg::PathWithLaneId; using autoware_planning_msgs::msg::LaneletPrimitive; using geometry_msgs::msg::Pose; diff --git a/planning/static_centerline_optimizer/include/static_centerline_optimizer/utils.hpp b/planning/static_centerline_optimizer/include/static_centerline_optimizer/utils.hpp index 91b27c74a7fcb..8c8c346557fce 100644 --- a/planning/static_centerline_optimizer/include/static_centerline_optimizer/utils.hpp +++ b/planning/static_centerline_optimizer/include/static_centerline_optimizer/utils.hpp @@ -18,6 +18,8 @@ #include "route_handler/route_handler.hpp" #include "static_centerline_optimizer/type_alias.hpp" +#include + #include #include #include diff --git a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp index 8c3b84854a6d0..4532eb66978cb 100644 --- a/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp +++ b/planning/static_centerline_optimizer/src/static_centerline_optimizer_node.cpp @@ -32,8 +32,10 @@ #include #include +#include #include +#include #include #include diff --git a/planning/static_centerline_optimizer/src/utils.cpp b/planning/static_centerline_optimizer/src/utils.cpp index 7871eb9e95163..d5169ac17af8a 100644 --- a/planning/static_centerline_optimizer/src/utils.cpp +++ b/planning/static_centerline_optimizer/src/utils.cpp @@ -18,6 +18,9 @@ #include "behavior_path_planner/utils/utils.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/ros/marker_helper.hpp" + +#include +#include namespace static_centerline_optimizer { namespace From 2776b570bb571003d4c4b4d6356802049e72007b Mon Sep 17 00:00:00 2001 From: TakumIto <61740530+TakumIto@users.noreply.github.com> Date: Fri, 15 Sep 2023 20:58:05 +0900 Subject: [PATCH 217/547] feat(behavior_path_planner): extend geometric parallel parking feature to both sides (#4881) * Expand geometric parallel parking feature into both side parking enable. Signed-off-by: Takumi Ito * Modify slight problems in parallel pull over. Signed-off-by: Takumi Ito * feat(behavior_path_planner): Extend geometric parallel parking feature to both sides (#4881) Signed-off-by: Takumi Ito * run: pre-commit run -a Signed-off-by: Takumi Ito * modify small mistake in solving confricts Signed-off-by: Takumi Ito * Update planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp * rename to fix spell check Signed-off-by: kosuke55 * reafactor Signed-off-by: kosuke55 * modify comments Signed-off-by: kosuke55 * const Signed-off-by: kosuke55 --------- Signed-off-by: Takumi Ito Signed-off-by: kosuke55 Co-authored-by: Takumi Ito Co-authored-by: Kosuke Takeuchi --- .../geometric_parallel_parking.hpp | 19 ++- .../goal_planner/goal_planner_module.cpp | 24 +-- .../geometric_parallel_parking.cpp | 160 ++++++++++-------- .../goal_planner/geometric_pull_over.cpp | 5 +- .../start_planner/geometric_pull_out.cpp | 5 +- 5 files changed, 122 insertions(+), 91 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp index 0f8a08339978e..3b6cca56e774f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp @@ -77,10 +77,11 @@ class GeometricParallelParking bool isParking() const; bool planPullOver( const Pose & goal_pose, const lanelet::ConstLanelets & road_lanes, - const lanelet::ConstLanelets & shoulder_lanes, const bool is_forward); + const lanelet::ConstLanelets & shoulder_lanes, const bool is_forward, + const bool left_side_parking); bool planPullOut( const Pose & start_pose, const Pose & goal_pose, const lanelet::ConstLanelets & road_lanes, - const lanelet::ConstLanelets & shoulder_lanes); + const lanelet::ConstLanelets & shoulder_lanes, const bool left_side_start); void setParameters(const ParallelParkingParameters & parameters) { parameters_ = parameters; } void setPlannerData(const std::shared_ptr & planner_data) { @@ -120,10 +121,10 @@ class GeometricParallelParking void clearPaths(); std::vector planOneTrial( - const Pose & start_pose, const Pose & goal_pose, const double R_E_r, + const Pose & start_pose, const Pose & goal_pose, const double R_E_far, const lanelet::ConstLanelets & road_lanes, const lanelet::ConstLanelets & shoulder_lanes, - bool is_forward, const double end_pose_offset, const double lane_departure_margin, - const double arc_path_interval); + const bool is_forward, const bool left_side_parking, const double end_pose_offset, + const double lane_departure_margin, const double arc_path_interval); PathWithLaneId generateArcPath( const Pose & center, const double radius, const double start_yaw, double end_yaw, const double arc_path_interval, const bool is_left_turn, const bool is_forward); @@ -132,11 +133,13 @@ class GeometricParallelParking const bool is_forward); boost::optional calcStartPose( const Pose & goal_pose, const lanelet::ConstLanelets & road_lanes, - const double start_pose_offset, const double R_E_r, const bool is_forward); + const double start_pose_offset, const double R_E_far, const bool is_forward, + const bool left_side_parking); std::vector generatePullOverPaths( - const Pose & start_pose, const Pose & goal_pose, const double R_E_r, + const Pose & start_pose, const Pose & goal_pose, const double R_E_far, const lanelet::ConstLanelets & road_lanes, const lanelet::ConstLanelets & shoulder_lanes, - const bool is_forward, const double end_pose_offset, const double velocity); + const bool is_forward, const bool left_side_parking, const double end_pose_offset, + const double velocity); PathWithLaneId generateStraightPath( const Pose & start_pose, const lanelet::ConstLanelets & road_lanes); void setVelocityToArcPaths( diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 629ba240792e5..1d61462aa60ec 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -73,19 +73,19 @@ GoalPlannerModule::GoalPlannerModule( pull_over_planners_.push_back(std::make_shared( node, *parameters, lane_departure_checker, occupancy_grid_map_)); } - // currently only support geometric_parallel_parking for left side parking - if (left_side_parking_) { - if (parameters_->enable_arc_forward_parking) { - constexpr bool is_forward = true; - pull_over_planners_.push_back(std::make_shared( - node, *parameters, lane_departure_checker, occupancy_grid_map_, is_forward)); - } - if (parameters_->enable_arc_backward_parking) { - constexpr bool is_forward = false; - pull_over_planners_.push_back(std::make_shared( - node, *parameters, lane_departure_checker, occupancy_grid_map_, is_forward)); - } + + // set geometric pull over + if (parameters_->enable_arc_forward_parking) { + constexpr bool is_forward = true; + pull_over_planners_.push_back(std::make_shared( + node, *parameters, lane_departure_checker, occupancy_grid_map_, is_forward)); } + if (parameters_->enable_arc_backward_parking) { + constexpr bool is_forward = false; + pull_over_planners_.push_back(std::make_shared( + node, *parameters, lane_departure_checker, occupancy_grid_map_, is_forward)); + } + if (pull_over_planners_.empty()) { RCLCPP_ERROR(getLogger(), "Not found enabled planner"); } diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index 32affebe1aebd..4742e5c8d3d29 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -121,9 +121,10 @@ void GeometricParallelParking::setVelocityToArcPaths( } std::vector GeometricParallelParking::generatePullOverPaths( - const Pose & start_pose, const Pose & goal_pose, const double R_E_r, + const Pose & start_pose, const Pose & goal_pose, const double R_E_far, const lanelet::ConstLanelets & road_lanes, const lanelet::ConstLanelets & shoulder_lanes, - const bool is_forward, const double end_pose_offset, const double velocity) + const bool is_forward, const bool left_side_parking, const double end_pose_offset, + const double velocity) { const double lane_departure_margin = is_forward ? parameters_.forward_parking_lane_departure_margin @@ -131,8 +132,8 @@ std::vector GeometricParallelParking::generatePullOverPaths( const double arc_path_interval = is_forward ? parameters_.forward_parking_path_interval : parameters_.backward_parking_path_interval; auto arc_paths = planOneTrial( - start_pose, goal_pose, R_E_r, road_lanes, shoulder_lanes, is_forward, end_pose_offset, - lane_departure_margin, arc_path_interval); + start_pose, goal_pose, R_E_far, road_lanes, shoulder_lanes, is_forward, left_side_parking, + end_pose_offset, lane_departure_margin, arc_path_interval); if (arc_paths.empty()) { return std::vector{}; } @@ -172,7 +173,8 @@ void GeometricParallelParking::clearPaths() bool GeometricParallelParking::planPullOver( const Pose & goal_pose, const lanelet::ConstLanelets & road_lanes, - const lanelet::ConstLanelets & shoulder_lanes, const bool is_forward) + const lanelet::ConstLanelets & shoulder_lanes, const bool is_forward, + const bool left_side_parking) { const auto & common_params = planner_data_->parameters; const double end_pose_offset = is_forward ? -parameters_.after_forward_parking_straight_distance @@ -193,16 +195,16 @@ bool GeometricParallelParking::planPullOver( constexpr double steer_interval = 0.1; for (double steer = parameters_.forward_parking_max_steer_angle; steer > min_steer_rad; steer -= steer_interval) { - const double R_E_r = common_params.wheel_base / std::tan(steer); - const auto start_pose = - calcStartPose(arc_end_pose, road_lanes, start_pose_offset, R_E_r, is_forward); + const double R_E_far = common_params.wheel_base / std::tan(steer); + const auto start_pose = calcStartPose( + arc_end_pose, road_lanes, start_pose_offset, R_E_far, is_forward, left_side_parking); if (!start_pose) { continue; } const auto paths = generatePullOverPaths( - *start_pose, goal_pose, R_E_r, road_lanes, shoulder_lanes, is_forward, end_pose_offset, - parameters_.forward_parking_velocity); + *start_pose, goal_pose, R_E_far, road_lanes, shoulder_lanes, is_forward, left_side_parking, + end_pose_offset, parameters_.forward_parking_velocity); if (!paths.empty()) { paths_ = paths; return true; @@ -216,15 +218,15 @@ bool GeometricParallelParking::planPullOver( constexpr double offset_interval = 1.0; for (double start_pose_offset = 0; start_pose_offset < max_offset; start_pose_offset += offset_interval) { - const auto start_pose = - calcStartPose(arc_end_pose, road_lanes, start_pose_offset, R_E_min_, is_forward); + const auto start_pose = calcStartPose( + arc_end_pose, road_lanes, start_pose_offset, R_E_min_, is_forward, left_side_parking); if (!start_pose) { continue; } const auto paths = generatePullOverPaths( - *start_pose, goal_pose, R_E_min_, road_lanes, shoulder_lanes, is_forward, end_pose_offset, - parameters_.backward_parking_velocity); + *start_pose, goal_pose, R_E_min_, road_lanes, shoulder_lanes, is_forward, left_side_parking, + end_pose_offset, parameters_.backward_parking_velocity); if (!paths.empty()) { paths_ = paths; return true; @@ -237,7 +239,7 @@ bool GeometricParallelParking::planPullOver( bool GeometricParallelParking::planPullOut( const Pose & start_pose, const Pose & goal_pose, const lanelet::ConstLanelets & road_lanes, - const lanelet::ConstLanelets & shoulder_lanes) + const lanelet::ConstLanelets & shoulder_lanes, const bool left_side_start) { constexpr bool is_forward = false; // parking backward means pull_out forward constexpr double start_pose_offset = 0.0; // start_pose is current_pose @@ -248,15 +250,16 @@ bool GeometricParallelParking::planPullOut( end_pose_offset += offset_interval) { // pull_out end pose which is the second arc path end const auto end_pose = - calcStartPose(start_pose, road_lanes, end_pose_offset, R_E_min_, is_forward); + calcStartPose(start_pose, road_lanes, end_pose_offset, R_E_min_, is_forward, left_side_start); if (!end_pose) { continue; } // plan reverse path of parking. end_pose <-> start_pose auto arc_paths = planOneTrial( - *end_pose, start_pose, R_E_min_, road_lanes, shoulder_lanes, is_forward, start_pose_offset, - parameters_.pull_out_lane_departure_margin, parameters_.pull_out_path_interval); + *end_pose, start_pose, R_E_min_, road_lanes, shoulder_lanes, is_forward, left_side_start, + start_pose_offset, parameters_.pull_out_lane_departure_margin, + parameters_.pull_out_path_interval); if (arc_paths.empty()) { // not found path continue; @@ -323,7 +326,7 @@ bool GeometricParallelParking::planPullOut( boost::optional GeometricParallelParking::calcStartPose( const Pose & goal_pose, const lanelet::ConstLanelets & road_lanes, const double start_pose_offset, - const double R_E_r, const bool is_forward) + const double R_E_far, const bool is_forward, const bool left_side_parking) { const auto arc_coordinates = lanelet::utils::getArcCoordinates(road_lanes, goal_pose); @@ -332,7 +335,8 @@ boost::optional GeometricParallelParking::calcStartPose( // But the left turn should also have a minimum turning radius. // see https://www.sciencedirect.com/science/article/pii/S1474667016436852 for the dx detail const double squared_distance_to_arc_connect = - std::pow(R_E_r, 2) - std::pow(-arc_coordinates.distance / 2 + R_E_r, 2); + left_side_parking ? std::pow(R_E_far, 2) - std::pow(-arc_coordinates.distance / 2 + R_E_far, 2) + : std::pow(R_E_far, 2) - std::pow(arc_coordinates.distance / 2 + R_E_far, 2); if (squared_distance_to_arc_connect < 0) { // may be current_pose is behind the lane return boost::none; @@ -365,10 +369,10 @@ PathWithLaneId GeometricParallelParking::generateStraightPath( } std::vector GeometricParallelParking::planOneTrial( - const Pose & start_pose, const Pose & goal_pose, const double R_E_r, + const Pose & start_pose, const Pose & goal_pose, const double R_E_far, const lanelet::ConstLanelets & road_lanes, const lanelet::ConstLanelets & shoulder_lanes, - const bool is_forward, const double end_pose_offset, const double lane_departure_margin, - const double arc_path_interval) + const bool is_forward, const bool left_side_parking, const double end_pose_offset, + const double lane_departure_margin, const double arc_path_interval) { clearPaths(); @@ -380,18 +384,21 @@ std::vector GeometricParallelParking::planOneTrial( const double goal_yaw = tf2::getYaw(arc_end_pose.orientation); const double psi = normalizeRadian(self_yaw - goal_yaw); - const Pose Cr = calcOffsetPose(arc_end_pose, 0, -R_E_r, 0); - const double d_Cr_Einit = calcDistance2d(Cr, start_pose); + const Pose C_far = left_side_parking ? calcOffsetPose(arc_end_pose, 0, -R_E_far, 0) + : calcOffsetPose(arc_end_pose, 0, R_E_far, 0); + const double d_C_far_Einit = calcDistance2d(C_far, start_pose); - const Point Cr_goal_coords = inverseTransformPoint(Cr.position, arc_end_pose); + const Point C_far_goal_coords = inverseTransformPoint(C_far.position, arc_end_pose); const Point self_point_goal_coords = inverseTransformPoint(start_pose.position, arc_end_pose); const double alpha = - M_PI_2 - psi + std::asin((self_point_goal_coords.y - Cr_goal_coords.y) / d_Cr_Einit); + left_side_parking + ? M_PI_2 - psi + std::asin((self_point_goal_coords.y - C_far_goal_coords.y) / d_C_far_Einit) + : M_PI_2 + psi - std::asin((self_point_goal_coords.y - C_far_goal_coords.y) / d_C_far_Einit); - const double R_E_l = - (std::pow(d_Cr_Einit, 2) - std::pow(R_E_r, 2)) / (2 * (R_E_r + d_Cr_Einit * std::cos(alpha))); - if (R_E_l <= 0) { + const double R_E_near = (std::pow(d_C_far_Einit, 2) - std::pow(R_E_far, 2)) / + (2 * (R_E_far + d_C_far_Einit * std::cos(alpha))); + if (R_E_near <= 0) { return std::vector{}; } @@ -410,48 +417,65 @@ std::vector GeometricParallelParking::planOneTrial( // If start_pose is parallel to goal_pose, we can know lateral deviation of edges of vehicle, // and detect lane departure. - if (is_forward) { // Check left bound - const double R_front_left = - std::hypot(R_E_r + common_params.vehicle_width / 2, common_params.base_link2front); - const double distance_to_left_bound = - utils::getSignedDistanceFromBoundary(shoulder_lanes, arc_end_pose, true); - const double left_deviation = R_front_left - R_E_r; - if (std::abs(distance_to_left_bound) - left_deviation < lane_departure_margin) { + if (is_forward) { // Check near bound + const double R_front_near = + std::hypot(R_E_far + common_params.vehicle_width / 2, common_params.base_link2front); + const double distance_to_near_bound = + utils::getSignedDistanceFromBoundary(shoulder_lanes, arc_end_pose, left_side_parking); + const double near_deviation = R_front_near - R_E_far; + if (std::abs(distance_to_near_bound) - near_deviation < lane_departure_margin) { return std::vector{}; } - } else { // Check right bound - const double R_front_right = - std::hypot(R_E_l + common_params.vehicle_width / 2, common_params.base_link2front); - const double right_deviation = R_front_right - R_E_l; - const double distance_to_right_bound = - utils::getSignedDistanceFromBoundary(lanes, start_pose, false); - if (distance_to_right_bound - right_deviation < lane_departure_margin) { + } else { // Check far bound + const double R_front_far = + std::hypot(R_E_near + common_params.vehicle_width / 2, common_params.base_link2front); + const double far_deviation = R_front_far - R_E_near; + const double distance_to_far_bound = + utils::getSignedDistanceFromBoundary(lanes, start_pose, !left_side_parking); + if (std::abs(distance_to_far_bound) - far_deviation < lane_departure_margin) { return std::vector{}; } } - // Generate arc path(left turn -> right turn) - const Pose Cl = calcOffsetPose(start_pose, 0, R_E_l, 0); - double theta_l = std::acos( - (std::pow(R_E_l, 2) + std::pow(R_E_l + R_E_r, 2) - std::pow(d_Cr_Einit, 2)) / - (2 * R_E_l * (R_E_l + R_E_r))); - theta_l = is_forward ? theta_l : -theta_l; - - PathWithLaneId path_turn_left = generateArcPath( - Cl, R_E_l, -M_PI_2, normalizeRadian(-M_PI_2 + theta_l), arc_path_interval, is_forward, - is_forward); - path_turn_left.header = route_handler->getRouteHeader(); + // Generate arc path(first turn -> second turn) + const Pose C_near = left_side_parking ? calcOffsetPose(start_pose, 0, R_E_near, 0) + : calcOffsetPose(start_pose, 0, -R_E_near, 0); + const double theta_near = + std::acos( + (std::pow(R_E_near, 2) + std::pow(R_E_near + R_E_far, 2) - std::pow(d_C_far_Einit, 2)) / + (2 * R_E_near * (R_E_near + R_E_far))) * + (is_forward == left_side_parking ? 1 : -1); + + const auto generateArcPathWithHeader = + [&]( + const auto & C, const auto & R_E, const auto & start_angle, const auto & end_angle, + bool is_forward_first, bool is_forward_second) -> PathWithLaneId { + auto path = generateArcPath( + C, R_E, start_angle, end_angle, arc_path_interval, is_forward_first, is_forward_second); + path.header = route_handler->getRouteHeader(); + return path; + }; - PathWithLaneId path_turn_right = generateArcPath( - Cr, R_E_r, normalizeRadian(psi + M_PI_2 + theta_l), M_PI_2, arc_path_interval, !is_forward, - is_forward); - path_turn_right.header = route_handler->getRouteHeader(); + PathWithLaneId path_turn_first = + left_side_parking + ? generateArcPathWithHeader( + C_near, R_E_near, -M_PI_2, normalizeRadian(-M_PI_2 + theta_near), is_forward, is_forward) + : generateArcPathWithHeader( + C_near, R_E_near, M_PI_2, normalizeRadian(M_PI_2 + theta_near), !is_forward, is_forward); + + PathWithLaneId path_turn_second = + left_side_parking ? generateArcPathWithHeader( + C_far, R_E_far, normalizeRadian(psi + M_PI_2 + theta_near), M_PI_2, + !is_forward, is_forward) + : generateArcPathWithHeader( + C_far, R_E_far, normalizeRadian(psi - M_PI_2 + theta_near), -M_PI_2, + is_forward, is_forward); // Need to add straight path to last right_turning for parking in parallel if (std::abs(end_pose_offset) > 0) { PathPointWithLaneId straight_point{}; straight_point.point.pose = goal_pose; - path_turn_right.points.push_back(straight_point); + path_turn_second.points.push_back(straight_point); } // Populate lane ids for a given path. @@ -469,8 +493,8 @@ std::vector GeometricParallelParking::planOneTrial( } } }; - populateLaneIds(path_turn_left); - populateLaneIds(path_turn_right); + populateLaneIds(path_turn_first); + populateLaneIds(path_turn_second); // Set lane ids to each point in a given path. // It assigns the accumulated lane ids from path_lane_ids to each point's lane_ids member. @@ -479,12 +503,12 @@ std::vector GeometricParallelParking::planOneTrial( p.lane_ids = path_lane_ids; } }; - setLaneIdsToPath(path_turn_left); - setLaneIdsToPath(path_turn_right); + setLaneIdsToPath(path_turn_first); + setLaneIdsToPath(path_turn_second); // generate arc path vector - paths_.push_back(path_turn_left); - paths_.push_back(path_turn_right); + paths_.push_back(path_turn_first); + paths_.push_back(path_turn_second); // set terminal velocity and acceleration(temporary implementation) if (is_forward) { @@ -505,8 +529,8 @@ std::vector GeometricParallelParking::planOneTrial( arc_end_pose_ = arc_end_pose; // debug - Cr_ = Cr; - Cl_ = Cl; + Cr_ = left_side_parking ? C_far : C_near; + Cl_ = left_side_parking ? C_near : C_far; return paths_; } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp index f350262cf4b80..a80f7892b0bef 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp @@ -35,7 +35,8 @@ GeometricPullOver::GeometricPullOver( parallel_parking_parameters_{parameters.parallel_parking_parameters}, lane_departure_checker_{lane_departure_checker}, occupancy_grid_map_{occupancy_grid_map}, - is_forward_{is_forward} + is_forward_{is_forward}, + left_side_parking_{parameters.parking_policy == ParkingPolicy::LEFT_SIDE} { planner_.setParameters(parallel_parking_parameters_); } @@ -63,7 +64,7 @@ boost::optional GeometricPullOver::plan(const Pose & goal_pose) planner_.setPlannerData(planner_data_); const bool found_valid_path = - planner_.planPullOver(goal_pose, road_lanes, pull_over_lanes, is_forward_); + planner_.planPullOver(goal_pose, road_lanes, pull_over_lanes, is_forward_, left_side_parking_); if (!found_valid_path) { return {}; } diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index 0d75391f9d4a6..00d6295e65aeb 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -49,11 +49,14 @@ boost::optional GeometricPullOut::plan(const Pose & start_pose, con /*forward_only_in_route*/ true); const auto pull_out_lanes = getPullOutLanes(planner_data_, backward_path_length); + // check if the ego is at left or right side of road lane center + const bool left_side_start = 0 < getArcCoordinates(road_lanes, start_pose).distance; + planner_.setTurningRadius( planner_data_->parameters, parallel_parking_parameters_.pull_out_max_steer_angle); planner_.setPlannerData(planner_data_); const bool found_valid_path = - planner_.planPullOut(start_pose, goal_pose, road_lanes, pull_out_lanes); + planner_.planPullOut(start_pose, goal_pose, road_lanes, pull_out_lanes, left_side_start); if (!found_valid_path) { return {}; } From 15d272ad7b2934b6159485e4d7ac595500c2308c Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Fri, 15 Sep 2023 21:07:03 +0900 Subject: [PATCH 218/547] feat(tier4_autoware_utils): add function to print backtrace in runtime (#4752) --- .../tier4_autoware_utils/system/backtrace.hpp | 53 +++++++++++++++++++ .../tier4_autoware_utils.hpp | 1 + 2 files changed, 54 insertions(+) create mode 100644 common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp new file mode 100644 index 0000000000000..b0b526e8c8b9c --- /dev/null +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp @@ -0,0 +1,53 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TIER4_AUTOWARE_UTILS__SYSTEM__BACKTRACE_HPP_ +#define TIER4_AUTOWARE_UTILS__SYSTEM__BACKTRACE_HPP_ + +#include + +#include +#include +#include +#include + +namespace tier4_autoware_utils +{ + +inline void print_backtrace() +{ + constexpr size_t max_frames = 100; + void * addrlist[max_frames + 1]; + + int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void *)); + + if (addrlen == 0) { + return; + } + + char ** symbol_list = backtrace_symbols(addrlist, addrlen); + + std::stringstream ss; + ss << " ********** back trace **********" << std::endl; + for (int i = 1; i < addrlen; i++) { + ss << " @ " << symbol_list[i] << std::endl; + } + std::cerr << ss.str() << std::endl; + + free(symbol_list); +} + +} // namespace tier4_autoware_utils + +#endif // TIER4_AUTOWARE_UTILS__SYSTEM__BACKTRACE_HPP_ diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp index 2aa5ae0c7e6ef..937cedc5534bf 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp @@ -38,6 +38,7 @@ #include "tier4_autoware_utils/ros/update_param.hpp" #include "tier4_autoware_utils/ros/uuid_helper.hpp" #include "tier4_autoware_utils/ros/wait_for_param.hpp" +#include "tier4_autoware_utils/system/backtrace.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" #include "tier4_autoware_utils/transform/transforms.hpp" From 5b40aa5199d278c28f195be3954eb210b9a3e9cf Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Sat, 16 Sep 2023 01:52:13 +0900 Subject: [PATCH 219/547] fix(behavior_path_planner): fix condition when collision detected before approval (#5007) * fix condition when collision detected before approval Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../start_planner_parameters.hpp | 1 + .../goal_planner/goal_planner_module.cpp | 44 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp index 771fbd93f7196..e9e7194821322 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp @@ -35,6 +35,7 @@ using freespace_planning_algorithms::RRTStarParam; struct StartPlannerParameters { + // TODO(someone): initialize variables double th_arrived_distance; double th_stopped_velocity; double th_stopped_time; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 1d61462aa60ec..587bf1f7ba875 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -666,29 +666,27 @@ void GoalPlannerModule::setLanes() void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) { - if (status_.is_safe_static_objects) { - if (isSafePath()) { - // clear stop pose when the path is safe against static/dynamic objects and activated - if (isActivated()) { - resetWallPoses(); - } - // keep stop if not enough time passed, - // because it takes time for the trajectory to be reflected - auto current_path = getCurrentPath(); - keepStoppedWithCurrentPath(current_path); - - output.path = std::make_shared(current_path); - output.reference_path = getPreviousModuleOutput().reference_path; - } else if (status_.has_decided_path && isActivated()) { - // situation : not safe against dynamic objects after approval - // insert stop point in current path if ego is able to stop with acceleration and jerk - // constraints - setStopPathFromCurrentPath(output); + if (!status_.is_safe_static_objects) { + // situation : not safe against static objects use stop_path + setStopPath(output); + } else if (!isSafePath() && status_.has_decided_path && isActivated()) { + // situation : not safe against dynamic objects after approval + // insert stop point in current path if ego is able to stop with acceleration and jerk + // constraints + setStopPathFromCurrentPath(output); + } else { + // situation : (safe against static and dynamic objects) or (safe against static objects and + // before approval) don't stop + if (isActivated()) { + resetWallPoses(); } + // keep stop if not enough time passed, + // because it takes time for the trajectory to be reflected + auto current_path = getCurrentPath(); + keepStoppedWithCurrentPath(current_path); - } else { - // not safe: use stop_path - setStopPath(output); + output.path = std::make_shared(current_path); + output.reference_path = getPreviousModuleOutput().reference_path; } setDrivableAreaInfo(output); @@ -703,7 +701,7 @@ void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) // for the next loop setOutput(). // this is used to determine whether to generate a new stop path or keep the current stop path. status_.prev_is_safe = status_.is_safe_static_objects; - status_.prev_is_safe_dynamic_objects = status_.is_safe_dynamic_objects; + status_.prev_is_safe_dynamic_objects = isSafePath(); } void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) @@ -1601,7 +1599,7 @@ bool GoalPlannerModule::isSafePath() const pull_over_lanes, route_handler, filtered_objects, objects_filtering_params_); const double hysteresis_factor = - status_.is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; + status_.prev_is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; utils::start_goal_planner_common::updateSafetyCheckTargetObjectsData( goal_planner_data_, filtered_objects, target_objects_on_lane, ego_predicted_path); From 3ae201804336108a7bbfbfdf8c4e44dcd24ac562 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Sat, 16 Sep 2023 16:34:40 +0900 Subject: [PATCH 220/547] chore(perception_reproducer): load only rosbag file (#4991) --- .../scripts/perception_replayer/perception_replayer.py | 3 +++ .../scripts/perception_replayer/perception_replayer_common.py | 3 ++- .../scripts/perception_replayer/perception_reproducer.py | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py index 79a97f0033b18..18f903323619f 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py @@ -182,6 +182,9 @@ def publish_recorded_ego_pose(self): parser.add_argument( "-t", "--tracked-object", help="publish tracked object", action="store_true" ) + parser.add_argument( + "-f", "--rosbag-format", help="rosbag data format (default is db3)", default="db3" + ) args = parser.parse_args() app = QApplication(sys.argv) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index 6b758bfba24ee..8f7cf38772fbb 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -74,7 +74,8 @@ def __init__(self, args, name): print("Stared loading rosbag") if os.path.isdir(args.bag): for bag_file in sorted(os.listdir(args.bag)): - self.load_rosbag(args.bag + "/" + bag_file) + if bag_file.endswith(self.args.rosbag_format): + self.load_rosbag(args.bag + "/" + bag_file) else: self.load_rosbag(args.bag) print("Ended loading rosbag") diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py index 1b75c86de2709..454ad033082ac 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py @@ -112,6 +112,9 @@ def find_nearest_ego_odom_by_observation(self, ego_pose): parser.add_argument( "-t", "--tracked-object", help="publish tracked object", action="store_true" ) + parser.add_argument( + "-f", "--rosbag-format", help="rosbag data format (default is db3)", default="db3" + ) args = parser.parse_args() rclpy.init() From 67eae01a947f0e9752700e8e2881e7ef0147133d Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Sat, 16 Sep 2023 18:17:49 +0900 Subject: [PATCH 221/547] fix(behavior_path_planner): define hysteresis_factor_expand_rate (#5002) * define hysteresis_factor_expand_rate Signed-off-by: kyoichi-sugahara * add hysteresis_factor_expand_rate in SafetyCheckParams Signed-off-by: kyoichi-sugahara * style(pre-commit): autofix * revert unnecessary change Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../config/goal_planner/goal_planner.param.yaml | 2 ++ .../config/start_planner/start_planner.param.yaml | 2 ++ .../path_safety_checker/path_safety_checker_parameters.hpp | 4 +++- .../src/scene_module/goal_planner/manager.cpp | 2 ++ .../src/scene_module/start_planner/manager.cpp | 2 ++ .../src/scene_module/start_planner/start_planner_module.cpp | 2 +- 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 709f271ac265c..779e7af61699f 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -162,6 +162,8 @@ lateral_distance_max_threshold: 1.0 longitudinal_distance_min_threshold: 1.0 longitudinal_velocity_delta_time: 1.0 + # hysteresis factor to expand/shrink polygon with the value + hysteresis_factor_expand_rate: 1.0 # temporary backward_path_length: 30.0 forward_path_length: 100.0 diff --git a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml index b62262423fa72..586676fbbb0f0 100644 --- a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml +++ b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml @@ -129,6 +129,8 @@ lateral_distance_max_threshold: 1.0 longitudinal_distance_min_threshold: 1.0 longitudinal_velocity_delta_time: 1.0 + # hysteresis factor to expand/shrink polygon + hysteresis_factor_expand_rate: 1.0 # temporary backward_path_length: 30.0 forward_path_length: 100.0 diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index 475b38f5bb824..b2433c076b1a8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -169,7 +169,9 @@ struct ObjectsFilteringParams */ struct SafetyCheckParams { - bool enable_safety_check; ///< Enable safety checks. + bool enable_safety_check; ///< Enable safety checks. + double + hysteresis_factor_expand_rate; ///< Hysteresis factor to expand/shrink polygon with the value. double backward_path_length; ///< Length of the backward lane for path generation. double forward_path_length; ///< Length of the forward path lane for path generation. RSSparams rss_params; ///< Parameters related to the RSS model. diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index 9d794b178b60e..2c7053cfb3629 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -316,6 +316,8 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( { p.safety_check_params.enable_safety_check = node->declare_parameter(safety_check_ns + "enable_safety_check"); + p.safety_check_params.hysteresis_factor_expand_rate = + node->declare_parameter(safety_check_ns + "hysteresis_factor_expand_rate"); p.safety_check_params.backward_path_length = node->declare_parameter(safety_check_ns + "backward_path_length"); p.safety_check_params.forward_path_length = diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp index 09166cd72a83e..57c9690e60d4b 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp @@ -243,6 +243,8 @@ StartPlannerModuleManager::StartPlannerModuleManager( { p.safety_check_params.enable_safety_check = node->declare_parameter(safety_check_ns + "enable_safety_check"); + p.safety_check_params.hysteresis_factor_expand_rate = + node->declare_parameter(safety_check_ns + "hysteresis_factor_expand_rate"); p.safety_check_params.backward_path_length = node->declare_parameter(safety_check_ns + "backward_path_length"); p.safety_check_params.forward_path_length = diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 58e493bf40f50..3e480e0f517ea 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -1004,7 +1004,7 @@ bool StartPlannerModule::isSafePath() const current_lanes, route_handler, filtered_objects, objects_filtering_params_); const double hysteresis_factor = - status_.is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; + status_.is_safe_dynamic_objects ? 1.0 : safety_check_params_->hysteresis_factor_expand_rate; utils::start_goal_planner_common::updateSafetyCheckTargetObjectsData( start_planner_data_, filtered_objects, target_objects_on_lane, ego_predicted_path); From ea9d4b0ada2d531720d42a04b3f0fb191036e925 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 17 Sep 2023 02:20:28 +0900 Subject: [PATCH 222/547] fix(behaivor_path_planner): fix uninitialized variables (#5009) Signed-off-by: kosuke55 --- .../dynamic_avoidance_module.hpp | 20 ++-- .../goal_planner/goal_planner_module.hpp | 6 +- .../utils/avoidance/avoidance_module_data.hpp | 86 ++++++++-------- .../goal_planner/goal_planner_parameters.hpp | 98 +++++++++---------- .../lane_change/lane_change_module_data.hpp | 6 +- .../utils/lane_following/module_data.hpp | 6 +- .../path_safety_checker_parameters.hpp | 52 +++++----- .../start_planner_parameters.hpp | 79 ++++++++------- 8 files changed, 176 insertions(+), 177 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp index fccc27051380e..b00818f2f79d1 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp @@ -37,8 +37,8 @@ namespace behavior_path_planner { struct MinMaxValue { - double min_value; - double max_value; + double min_value{0.0}; + double max_value{0.0}; }; struct DynamicAvoidanceParameters @@ -116,12 +116,12 @@ class DynamicAvoidanceModule : public SceneModuleInterface } } - std::string uuid; - geometry_msgs::msg::Pose pose; + std::string uuid{}; + geometry_msgs::msg::Pose pose{}; autoware_auto_perception_msgs::msg::Shape shape; - double vel; - double lat_vel; - bool is_object_on_ego_path; + double vel{0.0}; + double lat_vel{0.0}; + bool is_object_on_ego_path{false}; std::optional latest_time_inside_ego_path{std::nullopt}; std::vector predicted_paths{}; @@ -129,7 +129,7 @@ class DynamicAvoidanceModule : public SceneModuleInterface // Therefore, they has to be initialized as nullopt. std::optional lon_offset_to_avoid{std::nullopt}; std::optional lat_offset_to_avoid{std::nullopt}; - bool is_collision_left; + bool is_collision_left{false}; bool should_be_avoided{false}; PolygonGenerationMethod polygon_generation_method{PolygonGenerationMethod::OBJECT_PATH_BASE}; @@ -152,8 +152,8 @@ class DynamicAvoidanceModule : public SceneModuleInterface : max_count_(arg_max_count), min_count_(arg_min_count) { } - int max_count_; - int min_count_; + int max_count_{0}; + int min_count_{0}; void initialize() { current_uuids_.clear(); } void updateObject(const std::string & uuid, const DynamicAvoidanceObject & object) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index a6f9565c08aae..3c42a036fc63d 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -164,7 +164,7 @@ class GoalPlannerModule : public SceneModuleInterface mutable std::shared_ptr objects_filtering_params_; mutable std::shared_ptr safety_check_params_; - vehicle_info_util::VehicleInfo vehicle_info_; + vehicle_info_util::VehicleInfo vehicle_info_{}; // planner std::vector> pull_over_planners_; @@ -174,8 +174,8 @@ class GoalPlannerModule : public SceneModuleInterface // goal searcher std::shared_ptr goal_searcher_; std::optional modified_goal_pose_; - Pose refined_goal_pose_; - GoalCandidates goal_candidates_; + Pose refined_goal_pose_{}; + GoalCandidates goal_candidates_{}; // collision detector // need to be shared_ptr to be used in planner and goal searcher diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index ed44e83608991..afa97e3752d7b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -117,65 +117,65 @@ struct AvoidanceParameters // bool use_relaxed_margin_immediately{false}; // max deceleration for - double max_deceleration; + double max_deceleration{0.0}; // max jerk - double max_jerk; + double max_jerk{0.0}; // comfortable deceleration - double nominal_deceleration; + double nominal_deceleration{0.0}; // comfortable jerk - double nominal_jerk; + double nominal_jerk{0.0}; // To prevent large acceleration while avoidance. - double max_acceleration; + double max_acceleration{0.0}; // upper distance for envelope polygon expansion. - double upper_distance_for_polygon_expansion; + double upper_distance_for_polygon_expansion{0.0}; // lower distance for envelope polygon expansion. - double lower_distance_for_polygon_expansion; + double lower_distance_for_polygon_expansion{0.0}; // Vehicles whose distance to the center of the path is // less than this will not be considered for avoidance. - double threshold_distance_object_is_on_center; + double threshold_distance_object_is_on_center{0.0}; // execute only when there is no intersection behind of the stopped vehicle. - double object_ignore_section_traffic_light_in_front_distance; + double object_ignore_section_traffic_light_in_front_distance{0.0}; // execute only when there is no crosswalk near the stopped vehicle. - double object_ignore_section_crosswalk_in_front_distance; + double object_ignore_section_crosswalk_in_front_distance{0.0}; // execute only when there is no crosswalk near the stopped vehicle. - double object_ignore_section_crosswalk_behind_distance; + double object_ignore_section_crosswalk_behind_distance{0.0}; // distance to avoid object detection - double object_check_forward_distance; + double object_check_forward_distance{0.0}; // continue to detect backward vehicles as avoidance targets until they are this distance away - double object_check_backward_distance; + double object_check_backward_distance{0.0}; // if the distance between object and goal position is less than this parameter, the module ignore // the object. - double object_check_goal_distance; + double object_check_goal_distance{0.0}; // use in judge whether the vehicle is parking object on road shoulder - double object_check_shiftable_ratio; + double object_check_shiftable_ratio{0.0}; // minimum road shoulder width. maybe 0.5 [m] - double object_check_min_road_shoulder_width; + double object_check_min_road_shoulder_width{0.0}; // force avoidance - double threshold_time_force_avoidance_for_stopped_vehicle; + double threshold_time_force_avoidance_for_stopped_vehicle{0.0}; // when complete avoidance motion, there is a distance margin with the object // for longitudinal direction - double longitudinal_collision_margin_min_distance; + double longitudinal_collision_margin_min_distance{0.0}; // when complete avoidance motion, there is a time margin with the object // for longitudinal direction - double longitudinal_collision_margin_time; + double longitudinal_collision_margin_time{0.0}; // parameters for safety check area bool enable_safety_check{false}; @@ -194,38 +194,38 @@ struct AvoidanceParameters double safety_check_time_resolution{0.0}; // find adjacent lane vehicles - double safety_check_backward_distance; + double safety_check_backward_distance{0.0}; // transit hysteresis (unsafe to safe) size_t hysteresis_factor_safe_count; - double hysteresis_factor_expand_rate; + double hysteresis_factor_expand_rate{0.0}; // keep target velocity in yield maneuver - double yield_velocity; + double yield_velocity{0.0}; // maximum stop distance - double stop_max_distance; + double stop_max_distance{0.0}; // stop buffer - double stop_buffer; + double stop_buffer{0.0}; // start avoidance after this time to avoid sudden path change - double prepare_time; + double prepare_time{0.0}; // Even if the vehicle speed is zero, avoidance will start after a distance of this much. - double min_prepare_distance; + double min_prepare_distance{0.0}; // minimum slow down speed - double min_slow_down_speed; + double min_slow_down_speed{0.0}; // slow down speed buffer - double buf_slow_down_speed; + double buf_slow_down_speed{0.0}; // nominal avoidance sped - double nominal_avoidance_speed; + double nominal_avoidance_speed{0.0}; // module try to return original path to keep this distance from edge point of the path. - double remain_buffer_distance; + double remain_buffer_distance{0.0}; // The margin is configured so that the generated avoidance trajectory does not come near to the // road shoulder. @@ -236,46 +236,46 @@ struct AvoidanceParameters double hard_road_shoulder_margin{1.0}; // Even if the obstacle is very large, it will not avoid more than this length for right direction - double max_right_shift_length; + double max_right_shift_length{0.0}; // Even if the obstacle is very large, it will not avoid more than this length for left direction - double max_left_shift_length; + double max_left_shift_length{0.0}; // To prevent large acceleration while avoidance. - double max_lateral_acceleration; + double max_lateral_acceleration{0.0}; // For the compensation of the detection lost. Once an object is observed, it is registered and // will be used for planning from the next time. If the object is not observed, it counts up the // lost_count and the registered object will be removed when the count exceeds this max count. - double object_last_seen_threshold; + double object_last_seen_threshold{0.0}; // The avoidance path generation is performed when the shift distance of the // avoidance points is greater than this threshold. // In multiple targets case: if there are multiple vehicles in a row to be avoided, no new // avoidance path will be generated unless their lateral margin difference exceeds this value. - double lateral_execution_threshold; + double lateral_execution_threshold{0.0}; // shift lines whose shift length is less than threshold is added a request with other large shift // line. - double lateral_small_shift_threshold; + double lateral_small_shift_threshold{0.0}; // use for judge if the ego is shifting or not. - double lateral_avoid_check_threshold; + double lateral_avoid_check_threshold{0.0}; // For shift line generation process. The continuous shift length is quantized by this value. - double quantize_filter_threshold; + double quantize_filter_threshold{0.0}; // For shift line generation process. Merge small shift lines. (First step) - double same_grad_filter_1_threshold; + double same_grad_filter_1_threshold{0.0}; // For shift line generation process. Merge small shift lines. (Second step) - double same_grad_filter_2_threshold; + double same_grad_filter_2_threshold{0.0}; // For shift line generation process. Merge small shift lines. (Third step) - double same_grad_filter_3_threshold; + double same_grad_filter_3_threshold{0.0}; // For shift line generation process. Remove sharp(=jerky) shift line. - double sharp_shift_filter_threshold; + double sharp_shift_filter_threshold{0.0}; // policy bool use_shorten_margin_immediately{false}; @@ -302,7 +302,7 @@ struct AvoidanceParameters std::unordered_map object_parameters; // rss parameters - utils::path_safety_checker::RSSparams rss_params; + utils::path_safety_checker::RSSparams rss_params{}; // clip left and right bounds for objects bool enable_bound_clipping{false}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index 4e40267ede4df..22c65270c39ea 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -41,81 +41,81 @@ enum class ParkingPolicy { struct GoalPlannerParameters { // general params - double th_arrived_distance; - double th_stopped_velocity; - double th_stopped_time; - double th_blinker_on_lateral_offset; + double th_arrived_distance{0.0}; + double th_stopped_velocity{0.0}; + double th_stopped_time{0.0}; + double th_blinker_on_lateral_offset{0.0}; // goal search std::string search_priority; // "efficient_path" or "close_goal" ParkingPolicy parking_policy; // "left_side" or "right_side" - double forward_goal_search_length; - double backward_goal_search_length; - double goal_search_interval; - double longitudinal_margin; - double max_lateral_offset; - double lateral_offset_interval; - double ignore_distance_from_lane_start; - double margin_from_boundary; + double forward_goal_search_length{0.0}; + double backward_goal_search_length{0.0}; + double goal_search_interval{0.0}; + double longitudinal_margin{0.0}; + double max_lateral_offset{0.0}; + double lateral_offset_interval{0.0}; + double ignore_distance_from_lane_start{0.0}; + double margin_from_boundary{0.0}; // occupancy grid map - bool use_occupancy_grid_for_goal_search; - bool use_occupancy_grid_for_goal_longitudinal_margin; - bool use_occupancy_grid_for_path_collision_check; - double occupancy_grid_collision_check_margin; - int theta_size; - int obstacle_threshold; + bool use_occupancy_grid_for_goal_search{false}; + bool use_occupancy_grid_for_goal_longitudinal_margin{false}; + bool use_occupancy_grid_for_path_collision_check{false}; + double occupancy_grid_collision_check_margin{0.0}; + int theta_size{0}; + int obstacle_threshold{0}; // object recognition - bool use_object_recognition; - double object_recognition_collision_check_margin; - double object_recognition_collision_check_max_extra_stopping_margin; - double th_moving_object_velocity; + bool use_object_recognition{false}; + double object_recognition_collision_check_margin{0.0}; + double object_recognition_collision_check_max_extra_stopping_margin{0.0}; + double th_moving_object_velocity{0.0}; // pull over general params - double pull_over_minimum_request_length; - double pull_over_velocity; - double pull_over_minimum_velocity; - double decide_path_distance; - double maximum_deceleration; - double maximum_jerk; + double pull_over_minimum_request_length{0.0}; + double pull_over_velocity{0.0}; + double pull_over_minimum_velocity{0.0}; + double decide_path_distance{0.0}; + double maximum_deceleration{0.0}; + double maximum_jerk{0.0}; // shift path - bool enable_shift_parking; - int shift_sampling_num; - double maximum_lateral_jerk; - double minimum_lateral_jerk; - double deceleration_interval; - double after_shift_straight_distance; + bool enable_shift_parking{false}; + int shift_sampling_num{0}; + double maximum_lateral_jerk{0.0}; + double minimum_lateral_jerk{0.0}; + double deceleration_interval{0.0}; + double after_shift_straight_distance{0.0}; // parallel parking - bool enable_arc_forward_parking; - bool enable_arc_backward_parking; + bool enable_arc_forward_parking{false}; + bool enable_arc_backward_parking{false}; ParallelParkingParameters parallel_parking_parameters; // freespace parking - bool enable_freespace_parking; + bool enable_freespace_parking{false}; std::string freespace_parking_algorithm; - double freespace_parking_velocity; - double vehicle_shape_margin; - PlannerCommonParam freespace_parking_common_parameters; - AstarParam astar_parameters; - RRTStarParam rrt_star_parameters; + double freespace_parking_velocity{0.0}; + double vehicle_shape_margin{0.0}; + PlannerCommonParam freespace_parking_common_parameters{}; + AstarParam astar_parameters{}; + RRTStarParam rrt_star_parameters{}; // stop condition - double maximum_deceleration_for_stop; - double maximum_jerk_for_stop; + double maximum_deceleration_for_stop{0.0}; + double maximum_jerk_for_stop{0.0}; // hysteresis parameter - double hysteresis_factor_expand_rate; + double hysteresis_factor_expand_rate{0.0}; // path safety checker - utils::path_safety_checker::EgoPredictedPathParams ego_predicted_path_params; - utils::path_safety_checker::ObjectsFilteringParams objects_filtering_params; - utils::path_safety_checker::SafetyCheckParams safety_check_params; + utils::path_safety_checker::EgoPredictedPathParams ego_predicted_path_params{}; + utils::path_safety_checker::ObjectsFilteringParams objects_filtering_params{}; + utils::path_safety_checker::SafetyCheckParams safety_check_params{}; // debug - bool print_debug_info; + bool print_debug_info{false}; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index 65e4ad1160725..e10ba6c274887 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -79,11 +79,11 @@ struct LaneChangeParameters bool check_pedestrian{true}; // check object pedestrian // safety check - utils::path_safety_checker::RSSparams rss_params; - utils::path_safety_checker::RSSparams rss_params_for_abort; + utils::path_safety_checker::RSSparams rss_params{}; + utils::path_safety_checker::RSSparams rss_params_for_abort{}; // abort - LaneChangeCancelParameters cancel; + LaneChangeCancelParameters cancel{}; double finish_judge_lateral_threshold{0.2}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp index 118fb39606012..93e4542568745 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp @@ -23,11 +23,11 @@ namespace behavior_path_planner struct LaneFollowingParameters { - double lane_change_prepare_duration; + double lane_change_prepare_duration{0.0}; // finding closest lanelet - double distance_threshold; - double yaw_threshold; + double distance_threshold{0.0}; + double yaw_threshold{0.0}; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index b2433c076b1a8..ebdc0b552a87d 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -128,8 +128,8 @@ struct RSSparams double longitudinal_distance_min_threshold{ 0.0}; ///< Minimum threshold for longitudinal distance. double longitudinal_velocity_delta_time{0.0}; ///< Delta time for longitudinal velocity. - double front_vehicle_deceleration; ///< brake parameter - double rear_vehicle_deceleration; ///< brake parameter + double front_vehicle_deceleration{0.0}; ///< brake parameter + double rear_vehicle_deceleration{0.0}; ///< brake parameter }; /** @@ -137,12 +137,12 @@ struct RSSparams */ struct EgoPredictedPathParams { - double acceleration; ///< Acceleration value. - double time_horizon; ///< Time horizon for prediction. - double time_resolution; ///< Time resolution for prediction. - double min_slow_speed; ///< Minimum slow speed. - double delay_until_departure; ///< Delay before departure. - double target_velocity; ///< Target velocity. + double acceleration{0.0}; ///< Acceleration value. + double time_horizon{0.0}; ///< Time horizon for prediction. + double time_resolution{0.0}; ///< Time resolution for prediction. + double min_slow_speed{0.0}; ///< Minimum slow speed. + double delay_until_departure{0.0}; ///< Delay before departure. + double target_velocity{0.0}; ///< Target velocity. }; /** @@ -150,18 +150,18 @@ struct EgoPredictedPathParams */ struct ObjectsFilteringParams { - double safety_check_time_horizon; ///< Time horizon for object's prediction. - double safety_check_time_resolution; ///< Time resolution for object's prediction. - double object_check_forward_distance; ///< Forward distance for object checks. - double object_check_backward_distance; ///< Backward distance for object checks. - double ignore_object_velocity_threshold; ///< Velocity threshold for ignoring objects. - ObjectTypesToCheck object_types_to_check; ///< Specifies which object types to check. - ObjectLaneConfiguration object_lane_configuration; ///< Configuration for which lanes to check. - bool include_opposite_lane; ///< Include the opposite lane in checks. - bool invert_opposite_lane; ///< Invert the opposite lane in checks. - bool check_all_predicted_path; ///< Check all predicted paths. - bool use_all_predicted_path; ///< Use all predicted paths. - bool use_predicted_path_outside_lanelet; ///< Use predicted paths outside of lanelets. + double safety_check_time_horizon{0.0}; ///< Time horizon for object's prediction. + double safety_check_time_resolution{0.0}; ///< Time resolution for object's prediction. + double object_check_forward_distance{0.0}; ///< Forward distance for object checks. + double object_check_backward_distance{0.0}; ///< Backward distance for object checks. + double ignore_object_velocity_threshold{0.0}; ///< Velocity threshold for ignoring objects. + ObjectTypesToCheck object_types_to_check{}; ///< Specifies which object types to check. + ObjectLaneConfiguration object_lane_configuration{}; ///< Configuration for which lanes to check. + bool include_opposite_lane{false}; ///< Include the opposite lane in checks. + bool invert_opposite_lane{false}; ///< Invert the opposite lane in checks. + bool check_all_predicted_path{false}; ///< Check all predicted paths. + bool use_all_predicted_path{false}; ///< Use all predicted paths. + bool use_predicted_path_outside_lanelet{false}; ///< Use predicted paths outside of lanelets. }; /** @@ -169,12 +169,12 @@ struct ObjectsFilteringParams */ struct SafetyCheckParams { - bool enable_safety_check; ///< Enable safety checks. - double - hysteresis_factor_expand_rate; ///< Hysteresis factor to expand/shrink polygon with the value. - double backward_path_length; ///< Length of the backward lane for path generation. - double forward_path_length; ///< Length of the forward path lane for path generation. - RSSparams rss_params; ///< Parameters related to the RSS model. + bool enable_safety_check{false}; ///< Enable safety checks. + double hysteresis_factor_expand_rate{ + 0.0}; ///< Hysteresis factor to expand/shrink polygon with the value. + double backward_path_length{0.0}; ///< Length of the backward lane for path generation. + double forward_path_length{0.0}; ///< Length of the forward path lane for path generation. + RSSparams rss_params{}; ///< Parameters related to the RSS model. bool publish_debug_marker{false}; ///< Option to publish debug markers. }; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp index e9e7194821322..ce8f9698324d0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp @@ -35,61 +35,60 @@ using freespace_planning_algorithms::RRTStarParam; struct StartPlannerParameters { - // TODO(someone): initialize variables - double th_arrived_distance; - double th_stopped_velocity; - double th_stopped_time; - double th_turn_signal_on_lateral_offset; - double intersection_search_length; - double length_ratio_for_turn_signal_deactivation_near_intersection; - double collision_check_margin; - double collision_check_distance_from_end; - double th_moving_object_velocity; + double th_arrived_distance{0.0}; + double th_stopped_velocity{0.0}; + double th_stopped_time{0.0}; + double th_turn_signal_on_lateral_offset{0.0}; + double intersection_search_length{0.0}; + double length_ratio_for_turn_signal_deactivation_near_intersection{0.0}; + double collision_check_margin{0.0}; + double collision_check_distance_from_end{0.0}; + double th_moving_object_velocity{0.0}; // shift pull out - bool enable_shift_pull_out; - bool check_shift_path_lane_departure; - double minimum_shift_pull_out_distance; - int lateral_acceleration_sampling_num; - double lateral_jerk; - double maximum_lateral_acc; - double minimum_lateral_acc; - double maximum_curvature; // maximum curvature considered in the path generation - double deceleration_interval; + bool enable_shift_pull_out{false}; + bool check_shift_path_lane_departure{false}; + double minimum_shift_pull_out_distance{0.0}; + int lateral_acceleration_sampling_num{0}; + double lateral_jerk{0.0}; + double maximum_lateral_acc{0.0}; + double minimum_lateral_acc{0.0}; + double maximum_curvature{0.0}; // maximum curvature considered in the path generation + double deceleration_interval{0.0}; // geometric pull out - bool enable_geometric_pull_out; - bool divide_pull_out_path; - ParallelParkingParameters parallel_parking_parameters; + bool enable_geometric_pull_out{false}; + bool divide_pull_out_path{false}; + ParallelParkingParameters parallel_parking_parameters{}; // search start pose backward std::string search_priority; // "efficient_path" or "short_back_distance" - bool enable_back; - double backward_velocity; - double max_back_distance; - double backward_search_resolution; - double backward_path_update_duration; - double ignore_distance_from_lane_end; + bool enable_back{false}; + double backward_velocity{0.0}; + double max_back_distance{0.0}; + double backward_search_resolution{0.0}; + double backward_path_update_duration{0.0}; + double ignore_distance_from_lane_end{0.0}; // freespace planner - bool enable_freespace_planner; + bool enable_freespace_planner{false}; std::string freespace_planner_algorithm; - double end_pose_search_start_distance; - double end_pose_search_end_distance; - double end_pose_search_interval; - double freespace_planner_velocity; - double vehicle_shape_margin; + double end_pose_search_start_distance{0.0}; + double end_pose_search_end_distance{0.0}; + double end_pose_search_interval{0.0}; + double freespace_planner_velocity{0.0}; + double vehicle_shape_margin{0.0}; PlannerCommonParam freespace_planner_common_parameters; AstarParam astar_parameters; RRTStarParam rrt_star_parameters; // stop condition - double maximum_deceleration_for_stop; - double maximum_jerk_for_stop; + double maximum_deceleration_for_stop{0.0}; + double maximum_jerk_for_stop{0.0}; // hysteresis parameter - double hysteresis_factor_expand_rate; + double hysteresis_factor_expand_rate{0.0}; // path safety checker - utils::path_safety_checker::EgoPredictedPathParams ego_predicted_path_params; - utils::path_safety_checker::ObjectsFilteringParams objects_filtering_params; - utils::path_safety_checker::SafetyCheckParams safety_check_params; + utils::path_safety_checker::EgoPredictedPathParams ego_predicted_path_params{}; + utils::path_safety_checker::ObjectsFilteringParams objects_filtering_params{}; + utils::path_safety_checker::SafetyCheckParams safety_check_params{}; }; } // namespace behavior_path_planner From 38bbc67f4172b3e650c8231d04950d3260366ee1 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Mon, 18 Sep 2023 16:28:28 +0900 Subject: [PATCH 223/547] feat(behavior_path_planner): update function of createPredictedPath (#5014) update function of create predicted path Signed-off-by: kyoichi-sugahara --- .../goal_planner/goal_planner.param.yaml | 7 +++--- .../start_planner/start_planner.param.yaml | 7 +++--- .../path_safety_checker/objects_filtering.hpp | 23 +++++++++++++------ .../path_safety_checker_parameters.hpp | 13 ++++++----- .../goal_planner/goal_planner_module.cpp | 5 +++- .../src/scene_module/goal_planner/manager.cpp | 14 ++++++----- .../scene_module/start_planner/manager.cpp | 14 ++++++----- .../start_planner/start_planner_module.cpp | 7 ++++-- .../path_safety_checker/objects_filtering.cpp | 14 +++++++---- .../utils/start_goal_planner_common/utils.cpp | 2 +- 10 files changed, 67 insertions(+), 39 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 779e7af61699f..625cd6ab74ff7 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -112,12 +112,13 @@ path_safety_check: # EgoPredictedPath ego_predicted_path: + min_velocity: 0.0 acceleration: 1.0 - time_horizon: 10.0 + max_velocity: 1.0 + time_horizon_for_front_object: 10.0 + time_horizon_for_rear_object: 10.0 time_resolution: 0.5 - min_slow_speed: 0.0 delay_until_departure: 1.0 - target_velocity: 1.0 # For target object filtering target_filtering: safety_check_time_horizon: 5.0 diff --git a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml index 586676fbbb0f0..492bf1ddca69e 100644 --- a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml +++ b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml @@ -79,12 +79,13 @@ path_safety_check: # EgoPredictedPath ego_predicted_path: + min_velocity: 0.0 acceleration: 1.0 - time_horizon: 10.0 + max_velocity: 1.0 + time_horizon_for_front_object: 10.0 + time_horizon_for_rear_object: 10.0 time_resolution: 0.5 - min_slow_speed: 0.0 delay_until_departure: 1.0 - target_velocity: 1.0 # For target object filtering target_filtering: safety_check_time_horizon: 5.0 diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp index 8a3b7094c69d1..344822dce113a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp @@ -100,6 +100,7 @@ void filterObjectsByPosition( PredictedObjects & objects, const std::vector & path_points, const geometry_msgs::msg::Point & current_pose, const double forward_distance, const double backward_distance); + /** * @brief Filters the provided objects based on their classification. * @@ -137,19 +138,27 @@ std::vector getPredictedPathFromObj( const ExtendedPredictedObject & obj, const bool & is_use_all_predicted_path); /** - * @brief Create a predicted path based on ego vehicle parameters. + * @brief Create a predicted path using the provided parameters. * - * @param ego_predicted_path_params Parameters for ego's predicted path. - * @param path_points Points on the path. - * @param vehicle_pose Current pose of the ego-vehicle. + * The function predicts the path based on the current vehicle pose, its current velocity, + * and certain parameters related to the vehicle's behavior and environment. The prediction + * considers acceleration and maximum velocity constraints. + * + * @param ego_predicted_path_params Parameters associated with the ego's predicted path behavior. + * @param path_points Path points to be followed by the vehicle. + * @param vehicle_pose Current pose of the vehicle. * @param current_velocity Current velocity of the vehicle. - * @param ego_seg_idx Index of the ego segment. - * @return std::vector The predicted path. + * @param ego_seg_idx Segment index where the ego vehicle is currently located on the path. + * @param is_object_front Flag indicating if there is an object in front of the ego vehicle. + * @param limit_to_max_velocity Flag indicating if the predicted path should consider the + * maximum allowable velocity. + * @return std::vector Predicted path based on the input parameters. */ std::vector createPredictedPath( const std::shared_ptr & ego_predicted_path_params, const std::vector & path_points, - const geometry_msgs::msg::Pose & vehicle_pose, const double current_velocity, size_t ego_seg_idx); + const geometry_msgs::msg::Pose & vehicle_pose, const double current_velocity, + const size_t ego_seg_idx, const bool is_object_front, const bool limit_to_max_velocity); /** * @brief Checks if the centroid of a given object is within the provided lanelets. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index ebdc0b552a87d..aa470eb2dd80c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -137,12 +137,13 @@ struct RSSparams */ struct EgoPredictedPathParams { - double acceleration{0.0}; ///< Acceleration value. - double time_horizon{0.0}; ///< Time horizon for prediction. - double time_resolution{0.0}; ///< Time resolution for prediction. - double min_slow_speed{0.0}; ///< Minimum slow speed. - double delay_until_departure{0.0}; ///< Delay before departure. - double target_velocity{0.0}; ///< Target velocity. + double min_velocity{0.0}; ///< Minimum velocity. + double acceleration{0.0}; ///< Acceleration value. + double max_velocity{0.0}; ///< Maximum velocity. + double time_horizon_for_front_object{0.0}; ///< Time horizon for front object. + double time_horizon_for_rear_object{0.0}; ///< Time horizon for rear object. + double time_resolution{0.0}; ///< Time resolution for prediction. + double delay_until_departure{0.0}; ///< Delay before departure. }; /** diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 587bf1f7ba875..c4463e57334cf 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -1584,10 +1584,13 @@ bool GoalPlannerModule::isSafePath() const RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.current_path_idx); utils::start_goal_planner_common::updatePathProperty( ego_predicted_path_params_, terminal_velocity_and_accel); + // TODO(Sugahara): shoule judge is_object_front properly + const bool is_object_front = true; + const bool limit_to_max_velocity = true; const auto ego_predicted_path = behavior_path_planner::utils::path_safety_checker::createPredictedPath( ego_predicted_path_params_, pull_over_path.points, current_pose, current_velocity, - ego_seg_idx); + ego_seg_idx, is_object_front, limit_to_max_velocity); // filtering objects with velocity, position and class const auto & filtered_objects = utils::path_safety_checker::filterObjects( diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index 2c7053cfb3629..da1b8bf5792ee 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -236,18 +236,20 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // EgoPredictedPath std::string ego_path_ns = path_safety_check_ns + "ego_predicted_path."; { + p.ego_predicted_path_params.min_velocity = + node->declare_parameter(ego_path_ns + "min_velocity"); p.ego_predicted_path_params.acceleration = node->declare_parameter(ego_path_ns + "acceleration"); - p.ego_predicted_path_params.time_horizon = - node->declare_parameter(ego_path_ns + "time_horizon"); + p.ego_predicted_path_params.max_velocity = + node->declare_parameter(ego_path_ns + "max_velocity"); + p.ego_predicted_path_params.time_horizon_for_front_object = + node->declare_parameter(ego_path_ns + "time_horizon_for_front_object"); + p.ego_predicted_path_params.time_horizon_for_rear_object = + node->declare_parameter(ego_path_ns + "time_horizon_for_rear_object"); p.ego_predicted_path_params.time_resolution = node->declare_parameter(ego_path_ns + "time_resolution"); - p.ego_predicted_path_params.min_slow_speed = - node->declare_parameter(ego_path_ns + "min_slow_speed"); p.ego_predicted_path_params.delay_until_departure = node->declare_parameter(ego_path_ns + "delay_until_departure"); - p.ego_predicted_path_params.target_velocity = - node->declare_parameter(ego_path_ns + "target_velocity"); } // ObjectFilteringParams diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp index 57c9690e60d4b..28f35a1224b91 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp @@ -163,18 +163,20 @@ StartPlannerModuleManager::StartPlannerModuleManager( // EgoPredictedPath std::string ego_path_ns = base_ns + "ego_predicted_path."; { + p.ego_predicted_path_params.min_velocity = + node->declare_parameter(ego_path_ns + "min_velocity"); p.ego_predicted_path_params.acceleration = node->declare_parameter(ego_path_ns + "acceleration"); - p.ego_predicted_path_params.time_horizon = - node->declare_parameter(ego_path_ns + "time_horizon"); + p.ego_predicted_path_params.max_velocity = + node->declare_parameter(ego_path_ns + "max_velocity"); + p.ego_predicted_path_params.time_horizon_for_front_object = + node->declare_parameter(ego_path_ns + "time_horizon_for_front_object"); + p.ego_predicted_path_params.time_horizon_for_rear_object = + node->declare_parameter(ego_path_ns + "time_horizon_for_rear_object"); p.ego_predicted_path_params.time_resolution = node->declare_parameter(ego_path_ns + "time_resolution"); - p.ego_predicted_path_params.min_slow_speed = - node->declare_parameter(ego_path_ns + "min_slow_speed"); p.ego_predicted_path_params.delay_until_departure = node->declare_parameter(ego_path_ns + "delay_until_departure"); - p.ego_predicted_path_params.target_velocity = - node->declare_parameter(ego_path_ns + "target_velocity"); } // ObjectFilteringParams diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 3e480e0f517ea..27ee78c7dad98 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -990,10 +990,13 @@ bool StartPlannerModule::isSafePath() const RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.current_path_idx); utils::start_goal_planner_common::updatePathProperty( ego_predicted_path_params_, terminal_velocity_and_accel); + // TODO(Sugahara): shoule judge is_object_front properly + const bool is_object_front = true; + const bool limit_to_max_velocity = true; const auto ego_predicted_path = behavior_path_planner::utils::path_safety_checker::createPredictedPath( - ego_predicted_path_params_, pull_out_path.points, current_pose, current_velocity, - ego_seg_idx); + ego_predicted_path_params_, pull_out_path.points, current_pose, current_velocity, ego_seg_idx, + is_object_front, limit_to_max_velocity); // filtering objects with velocity, position and class const auto & filtered_objects = utils::path_safety_checker::filterObjects( diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index 7205b273ff281..92e4c9c54d8ea 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -225,15 +225,20 @@ std::vector getPredictedPathFromObj( std::vector createPredictedPath( const std::shared_ptr & ego_predicted_path_params, const std::vector & path_points, - const geometry_msgs::msg::Pose & vehicle_pose, const double current_velocity, size_t ego_seg_idx) + const geometry_msgs::msg::Pose & vehicle_pose, const double current_velocity, + const size_t ego_seg_idx, const bool is_object_front, const bool limit_to_max_velocity) { if (path_points.empty()) { return {}; } - const double min_slow_down_speed = ego_predicted_path_params->min_slow_speed; + const double min_velocity = ego_predicted_path_params->min_velocity; const double acceleration = ego_predicted_path_params->acceleration; - const double time_horizon = ego_predicted_path_params->time_horizon; + const double max_velocity = limit_to_max_velocity ? ego_predicted_path_params->max_velocity + : std::numeric_limits::infinity(); + const double time_horizon = is_object_front + ? ego_predicted_path_params->time_horizon_for_front_object + : ego_predicted_path_params->time_horizon_for_rear_object; const double time_resolution = ego_predicted_path_params->time_resolution; std::vector predicted_path; @@ -241,7 +246,8 @@ std::vector createPredictedPath( convertToFrenetPoint(path_points, vehicle_pose.position, ego_seg_idx); for (double t = 0.0; t < time_horizon + 1e-3; t += time_resolution) { - const double velocity = std::max(current_velocity + acceleration * t, min_slow_down_speed); + const double velocity = + std::clamp(current_velocity + acceleration * t, min_velocity, max_velocity); const double length = current_velocity * t + 0.5 * acceleration * t * t; const auto pose = motion_utils::calcInterpolatedPose(path_points, vehicle_pose_frenet.length + length); diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index bd695055b2079..9c54f4243b0ea 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -140,7 +140,7 @@ void updatePathProperty( const double acceleration = std::max(pairs_terminal_velocity_and_accel.second, min_accel_for_ego_predicted_path); - ego_predicted_path_params->target_velocity = pairs_terminal_velocity_and_accel.first; + ego_predicted_path_params->max_velocity = pairs_terminal_velocity_and_accel.first; ego_predicted_path_params->acceleration = acceleration; } From ee7a97e8f0f182f1ea237ed68dbdad293807ca8e Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 18 Sep 2023 18:23:58 +0900 Subject: [PATCH 224/547] fix(goal_planner): fix disabling safety check (#5020) Signed-off-by: kosuke55 --- .../src/scene_module/goal_planner/goal_planner_module.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index c4463e57334cf..d9426d6fece82 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -669,7 +669,9 @@ void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) if (!status_.is_safe_static_objects) { // situation : not safe against static objects use stop_path setStopPath(output); - } else if (!isSafePath() && status_.has_decided_path && isActivated()) { + } else if ( + parameters_->safety_check_params.enable_safety_check && !isSafePath() && + status_.has_decided_path && isActivated()) { // situation : not safe against dynamic objects after approval // insert stop point in current path if ego is able to stop with acceleration and jerk // constraints @@ -701,7 +703,8 @@ void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) // for the next loop setOutput(). // this is used to determine whether to generate a new stop path or keep the current stop path. status_.prev_is_safe = status_.is_safe_static_objects; - status_.prev_is_safe_dynamic_objects = isSafePath(); + status_.prev_is_safe_dynamic_objects = + parameters_->safety_check_params.enable_safety_check ? isSafePath() : true; } void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) From 498dbef4fed1a74af2bfd79802a9eeb02f18987f Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 19 Sep 2023 08:18:21 +0900 Subject: [PATCH 225/547] feat(behavior_path_planner): use common function of createPredictedPath from avoidance module (#5015) * update function of create predicted path Signed-off-by: kyoichi-sugahara * util common function of create predicted path from avoidance module Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../utils/avoidance/utils.hpp | 3 +- .../avoidance/avoidance_module.cpp | 9 ++-- .../src/utils/avoidance/utils.cpp | 42 +++++++++---------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index 0dc07f0950716..1009540062a99 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -89,7 +89,8 @@ std::vector generateObstaclePolygonsForDrivableArea( std::vector convertToPredictedPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, - const bool is_object_front, const std::shared_ptr & parameters); + const bool is_object_front, const bool limit_to_max_velocity, + const std::shared_ptr & parameters); double getLongitudinalVelocity(const Pose & p_ref, const Pose & p_target, const double v); diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 0f2605735eda1..ce5d13d17aedf 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1842,10 +1842,11 @@ bool AvoidanceModule::isSafePath( return true; // if safety check is disabled, it always return safe. } - const auto ego_predicted_path_for_front_object = - utils::avoidance::convertToPredictedPath(shifted_path.path, planner_data_, true, parameters_); - const auto ego_predicted_path_for_rear_object = - utils::avoidance::convertToPredictedPath(shifted_path.path, planner_data_, false, parameters_); + const bool limit_to_max_velocity = false; + const auto ego_predicted_path_for_front_object = utils::avoidance::convertToPredictedPath( + shifted_path.path, planner_data_, true, limit_to_max_velocity, parameters_); + const auto ego_predicted_path_for_rear_object = utils::avoidance::convertToPredictedPath( + shifted_path.path, planner_data_, false, limit_to_max_velocity, parameters_); const auto ego_idx = planner_data_->findEgoIndex(shifted_path.path.points); const auto is_right_shift = [&]() -> std::optional { diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 058059037c6d6..1c1c155a7b661 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/avoidance/avoidance_module_data.hpp" #include "behavior_path_planner/utils/avoidance/utils.hpp" +#include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "motion_utils/trajectory/path_with_lane_id.hpp" #include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" @@ -1445,34 +1446,29 @@ AvoidLineArray combineRawShiftLinesWithUniqueCheck( std::vector convertToPredictedPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, - const bool is_object_front, const std::shared_ptr & parameters) + const bool is_object_front, const bool limit_to_max_velocity, + const std::shared_ptr & parameters) { - if (path.points.empty()) { - return {}; - } - - const auto & acceleration = parameters->max_acceleration; const auto & vehicle_pose = planner_data->self_odometry->pose.pose; const auto & initial_velocity = std::abs(planner_data->self_odometry->twist.twist.linear.x); - const auto & time_horizon = is_object_front ? parameters->time_horizon_for_front_object - : parameters->time_horizon_for_rear_object; - const auto & time_resolution = parameters->safety_check_time_resolution; - const size_t ego_seg_idx = planner_data->findEgoSegmentIndex(path.points); - std::vector predicted_path; - const auto vehicle_pose_frenet = - convertToFrenetPoint(path.points, vehicle_pose.position, ego_seg_idx); - - for (double t = 0.0; t < time_horizon + 1e-3; t += time_resolution) { - const double velocity = - std::max(initial_velocity + acceleration * t, parameters->min_slow_down_speed); - const double length = initial_velocity * t + 0.5 * acceleration * t * t; - const auto pose = - motion_utils::calcInterpolatedPose(path.points, vehicle_pose_frenet.length + length); - predicted_path.emplace_back(t, pose, velocity); - } - return predicted_path; + auto ego_predicted_path_params = + std::make_shared(); + + ego_predicted_path_params->min_velocity = parameters->min_slow_down_speed; + ego_predicted_path_params->acceleration = parameters->max_acceleration; + ego_predicted_path_params->max_velocity = std::numeric_limits::infinity(); + ego_predicted_path_params->time_horizon_for_front_object = + parameters->time_horizon_for_front_object; + ego_predicted_path_params->time_horizon_for_rear_object = + parameters->time_horizon_for_rear_object; + ego_predicted_path_params->time_resolution = parameters->safety_check_time_resolution; + ego_predicted_path_params->delay_until_departure = 0.0; + + return behavior_path_planner::utils::path_safety_checker::createPredictedPath( + ego_predicted_path_params, path.points, vehicle_pose, initial_velocity, ego_seg_idx, + is_object_front, limit_to_max_velocity); } ExtendedPredictedObject transform( From 8eeb11dff08cd027be69084777215b7f321a2f1f Mon Sep 17 00:00:00 2001 From: badai nguyen <94814556+badai-nguyen@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:38:20 +0900 Subject: [PATCH 226/547] refactor(perception): rearrange clustering pipeline (#4999) * fix: change downsample filter Signed-off-by: badai-nguyen * fix: remove downsamle after compare map Signed-off-by: badai-nguyen * fix: add low range cropbox Signed-off-by: badai-nguyen * refactor: use_pointcloud_map Signed-off-by: badai-nguyen * chore: refactor Signed-off-by: badai-nguyen * fix: add roi based clustering option Signed-off-by: badai-nguyen * chore: change node name Signed-off-by: badai-nguyen * fix: launch argument pasrer Signed-off-by: badai-nguyen --------- Signed-off-by: badai-nguyen --- ...ra_lidar_fusion_based_detection.launch.xml | 23 +- ...ar_radar_fusion_based_detection.launch.xml | 22 +- .../detection/detection.launch.xml | 7 +- .../lidar_based_detection.launch.xml | 14 +- .../lidar_radar_based_detection.launch.xml | 2 +- .../detection/pointcloud_map_filter.launch.py | 39 ++- .../launch/perception.launch.xml | 10 +- .../config/compare_map.param.yaml | 20 -- .../config/outlier.param.yaml | 8 - .../config/voxel_grid.param.yaml | 7 - ...el_grid_based_euclidean_cluster.param.yaml | 14 +- .../launch/euclidean_cluster.launch.py | 73 ++---- .../launch/euclidean_cluster.launch.xml | 6 +- ...xel_grid_based_euclidean_cluster.launch.py | 239 ++---------------- ...el_grid_based_euclidean_cluster.launch.xml | 12 +- 15 files changed, 103 insertions(+), 393 deletions(-) delete mode 100644 perception/euclidean_cluster/config/compare_map.param.yaml delete mode 100644 perception/euclidean_cluster/config/outlier.param.yaml delete mode 100644 perception/euclidean_cluster/config/voxel_grid.param.yaml diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index eab2a56607dee..7f2fc45396f7f 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -11,13 +11,12 @@ - - + @@ -59,15 +58,16 @@ --> - + - + + @@ -75,19 +75,18 @@ - - + + - - - - + + + - + @@ -120,7 +119,7 @@ - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index 463340efdecfe..2bd007b658978 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -11,8 +11,6 @@ - - @@ -78,15 +76,16 @@ - + - + + @@ -94,13 +93,12 @@ - - + + - - - - + + + @@ -151,7 +149,7 @@ - + @@ -184,7 +182,7 @@ - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml index ca5d1d0f7e8bb..ab55047132482 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml @@ -5,7 +5,6 @@ - @@ -65,6 +64,8 @@ + + @@ -94,6 +95,8 @@ + + @@ -107,6 +110,7 @@ + @@ -119,6 +123,7 @@ + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml index 5b6134dc45584..ccc236d9cdc94 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml @@ -4,8 +4,6 @@ - - @@ -19,29 +17,27 @@ - + - + + - - - + - - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml index a47e6a86f1c7b..4756fc9338e46 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml @@ -3,7 +3,6 @@ - @@ -19,6 +18,7 @@ + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/pointcloud_map_filter.launch.py b/launch/tier4_perception_launch/launch/object_recognition/detection/pointcloud_map_filter.launch.py index a5d8d2113d2db..93d395ca3e466 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/pointcloud_map_filter.launch.py +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/pointcloud_map_filter.launch.py @@ -35,7 +35,6 @@ def __init__(self, context): ) with open(pointcloud_map_filter_param_path, "r") as f: self.pointcloud_map_filter_param = yaml.safe_load(f)["/**"]["ros__parameters"] - self.use_down_sample_filter = self.pointcloud_map_filter_param["use_down_sample_filter"] self.voxel_size = self.pointcloud_map_filter_param["down_sample_voxel_size"] self.distance_threshold = self.pointcloud_map_filter_param["distance_threshold"] self.downsize_ratio_z_axis = self.pointcloud_map_filter_param["downsize_ratio_z_axis"] @@ -46,47 +45,40 @@ def __init__(self, context): ] self.map_loader_radius = self.pointcloud_map_filter_param["map_loader_radius"] self.publish_debug_pcd = self.pointcloud_map_filter_param["publish_debug_pcd"] + self.use_pointcloud_map = LaunchConfiguration("use_pointcloud_map").perform(context) def create_pipeline(self): - if self.use_down_sample_filter: - return self.create_down_sample_pipeline() + if self.use_pointcloud_map == "true": + return self.create_compare_map_pipeline() else: - return self.create_normal_pipeline() + return self.create_no_compare_map_pipeline() - def create_normal_pipeline(self): + def create_no_compare_map_pipeline(self): components = [] components.append( ComposableNode( - package="compare_map_segmentation", - plugin="compare_map_segmentation::VoxelBasedCompareMapFilterComponent", - name="voxel_based_compare_map_filter", + package="pointcloud_preprocessor", + plugin="pointcloud_preprocessor::ApproximateDownsampleFilterComponent", + name="voxel_grid_downsample_filter", remappings=[ ("input", LaunchConfiguration("input_topic")), - ("map", "/map/pointcloud_map"), ("output", LaunchConfiguration("output_topic")), - ("map_loader_service", "/map/get_differential_pointcloud_map"), - ("kinematic_state", "/localization/kinematic_state"), ], parameters=[ { - "distance_threshold": self.distance_threshold, - "downsize_ratio_z_axis": self.downsize_ratio_z_axis, - "timer_interval_ms": self.timer_interval_ms, - "use_dynamic_map_loading": self.use_dynamic_map_loading, - "map_update_distance_threshold": self.map_update_distance_threshold, - "map_loader_radius": self.map_loader_radius, - "publish_debug_pcd": self.publish_debug_pcd, - "input_frame": "map", + "voxel_size_x": self.voxel_size, + "voxel_size_y": self.voxel_size, + "voxel_size_z": self.voxel_size, } ], extra_arguments=[ - {"use_intra_process_comms": False}, + {"use_intra_process_comms": LaunchConfiguration("use_intra_process")} ], - ) + ), ) return components - def create_down_sample_pipeline(self): + def create_compare_map_pipeline(self): components = [] down_sample_topic = ( "/perception/obstacle_segmentation/pointcloud_map_filtered/downsampled/pointcloud" @@ -94,7 +86,7 @@ def create_down_sample_pipeline(self): components.append( ComposableNode( package="pointcloud_preprocessor", - plugin="pointcloud_preprocessor::VoxelGridDownsampleFilterComponent", + plugin="pointcloud_preprocessor::ApproximateDownsampleFilterComponent", name="voxel_grid_downsample_filter", remappings=[ ("input", LaunchConfiguration("input_topic")), @@ -177,6 +169,7 @@ def add_launch_arg(name: str, default_value=None): add_launch_arg("use_intra_process", "True") add_launch_arg("use_pointcloud_container", "False") add_launch_arg("container_name", "pointcloud_map_filter_pipeline_container") + add_launch_arg("use_pointcloud_map", "true") set_container_executable = SetLaunchConfiguration( "container_executable", "component_container", diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index c41177e51116b..a30b87a1068b5 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -1,10 +1,8 @@ - - @@ -59,8 +57,9 @@ - + + - - - + + diff --git a/perception/euclidean_cluster/config/compare_map.param.yaml b/perception/euclidean_cluster/config/compare_map.param.yaml deleted file mode 100644 index 3dd303464a2c1..0000000000000 --- a/perception/euclidean_cluster/config/compare_map.param.yaml +++ /dev/null @@ -1,20 +0,0 @@ -/**: - ros__parameters: - - # distance threshold for compare compare - distance_threshold: 0.5 - - # publish voxelized map pointcloud for debug - publish_debug_pcd: False - - # use dynamic map loading - use_dynamic_map_loading: True - - # time interval to check dynamic map loading - timer_interval_ms: 100 - - # distance threshold for dynamic map update - map_update_distance_threshold: 10.0 - - # radius map for dynamic map loading - map_loader_radius: 150.0 diff --git a/perception/euclidean_cluster/config/outlier.param.yaml b/perception/euclidean_cluster/config/outlier.param.yaml deleted file mode 100644 index 1962fba1f332a..0000000000000 --- a/perception/euclidean_cluster/config/outlier.param.yaml +++ /dev/null @@ -1,8 +0,0 @@ -/**: - ros__parameters: - input_frame: base_link - output_frame: base_link - voxel_size_x: 0.3 - voxel_size_y: 0.3 - voxel_size_z: 100.0 - voxel_points_threshold: 3 diff --git a/perception/euclidean_cluster/config/voxel_grid.param.yaml b/perception/euclidean_cluster/config/voxel_grid.param.yaml deleted file mode 100644 index 3ff32bfbb7146..0000000000000 --- a/perception/euclidean_cluster/config/voxel_grid.param.yaml +++ /dev/null @@ -1,7 +0,0 @@ -/**: - ros__parameters: - input_frame: base_link - output_frame: base_link - voxel_size_x: 0.15 - voxel_size_y: 0.15 - voxel_size_z: 0.15 diff --git a/perception/euclidean_cluster/config/voxel_grid_based_euclidean_cluster.param.yaml b/perception/euclidean_cluster/config/voxel_grid_based_euclidean_cluster.param.yaml index 2f3de2b789eb3..26b027f0077aa 100644 --- a/perception/euclidean_cluster/config/voxel_grid_based_euclidean_cluster.param.yaml +++ b/perception/euclidean_cluster/config/voxel_grid_based_euclidean_cluster.param.yaml @@ -7,9 +7,11 @@ max_cluster_size: 3000 use_height: false input_frame: "base_link" - max_x: 70.0 - min_x: -70.0 - max_y: 70.0 - min_y: -70.0 - max_z: 4.5 - min_z: -4.5 + + # low height crop box filter param + max_x: 200.0 + min_x: -200.0 + max_y: 200.0 + min_y: -200.0 + max_z: 2.0 + min_z: -10.0 diff --git a/perception/euclidean_cluster/launch/euclidean_cluster.launch.py b/perception/euclidean_cluster/launch/euclidean_cluster.launch.py index db86bbf80d250..66c25396a656e 100644 --- a/perception/euclidean_cluster/launch/euclidean_cluster.launch.py +++ b/perception/euclidean_cluster/launch/euclidean_cluster.launch.py @@ -35,61 +35,35 @@ def load_composable_node_param(param_path): ns = "" pkg = "euclidean_cluster" - # set voxel grid filter as a component - voxel_grid_filter_component = ComposableNode( + low_height_cropbox_filter_component = ComposableNode( package="pointcloud_preprocessor", - plugin="pointcloud_preprocessor::VoxelGridDownsampleFilterComponent", - name=AnonName("voxel_grid_filter"), + namespace=ns, + plugin="pointcloud_preprocessor::CropBoxFilterComponent", + name="low_height_crop_box_filter", remappings=[ ("input", LaunchConfiguration("input_pointcloud")), - ("output", "voxel_grid_filtered/pointcloud"), - ], - parameters=[load_composable_node_param("voxel_grid_param_path")], - ) - - # set compare map filter as a component - compare_map_filter_component = ComposableNode( - package="compare_map_segmentation", - plugin="compare_map_segmentation::VoxelBasedCompareMapFilterComponent", - name=AnonName("compare_map_filter"), - remappings=[ - ("input", "voxel_grid_filtered/pointcloud"), - ("map", LaunchConfiguration("input_map")), - ("output", "compare_map_filtered/pointcloud"), - ("map_loader_service", "/map/get_differential_pointcloud_map"), - ("kinematic_state", "/localization/kinematic_state"), - ], - parameters=[ - { - "distance_threshold": 0.5, - "timer_interval_ms": 100, - "use_dynamic_map_loading": True, - "downsize_ratio_z_axis": 0.5, - "map_update_distance_threshold": 10.0, - "map_loader_radius": 150.0, - "publish_debug_pcd": True, - "input_frame": "map", - } + ("output", "low_height/pointcloud"), ], + parameters=[load_composable_node_param("voxel_grid_based_euclidean_param_path")], ) - use_map_euclidean_cluster_component = ComposableNode( + use_low_height_euclidean_component = ComposableNode( package=pkg, plugin="euclidean_cluster::EuclideanClusterNode", name=AnonName("euclidean_cluster"), remappings=[ - ("input", "compare_map_filtered/pointcloud"), + ("input", "low_height/pointcloud"), ("output", LaunchConfiguration("output_clusters")), ], parameters=[load_composable_node_param("euclidean_param_path")], ) - disuse_map_euclidean_cluster_component = ComposableNode( + disuse_low_height_euclidean_component = ComposableNode( package=pkg, plugin="euclidean_cluster::EuclideanClusterNode", name=AnonName("euclidean_cluster"), remappings=[ - ("input", "voxel_grid_filtered/pointcloud"), + ("input", LaunchConfiguration("input_pointcloud")), ("output", LaunchConfiguration("output_clusters")), ], parameters=[load_composable_node_param("euclidean_param_path")], @@ -100,26 +74,26 @@ def load_composable_node_param(param_path): namespace=ns, package="rclcpp_components", executable="component_container", - composable_node_descriptions=[voxel_grid_filter_component], + composable_node_descriptions=[], output="screen", ) - use_map_loader = LoadComposableNodes( + use_low_height_pointcloud_loader = LoadComposableNodes( composable_node_descriptions=[ - compare_map_filter_component, - use_map_euclidean_cluster_component, + low_height_cropbox_filter_component, + use_low_height_euclidean_component, ], target_container=container, - condition=IfCondition(LaunchConfiguration("use_pointcloud_map")), + condition=IfCondition(LaunchConfiguration("use_low_height_cropbox")), ) - disuse_map_loader = LoadComposableNodes( - composable_node_descriptions=[disuse_map_euclidean_cluster_component], + disuse_low_height_pointcloud_loader = LoadComposableNodes( + composable_node_descriptions=[disuse_low_height_euclidean_component], target_container=container, - condition=UnlessCondition(LaunchConfiguration("use_pointcloud_map")), + condition=UnlessCondition(LaunchConfiguration("use_low_height_cropbox")), ) - return [container, use_map_loader, disuse_map_loader] + return [container, use_low_height_pointcloud_loader, disuse_low_height_pointcloud_loader] def generate_launch_description(): @@ -131,14 +105,7 @@ def add_launch_arg(name: str, default_value=None): add_launch_arg("input_pointcloud", "/perception/obstacle_segmentation/pointcloud"), add_launch_arg("input_map", "/map/pointcloud_map"), add_launch_arg("output_clusters", "clusters"), - add_launch_arg("use_pointcloud_map", "false"), - add_launch_arg( - "voxel_grid_param_path", - [ - FindPackageShare("autoware_launch"), - "/config/perception/object_recognition/detection/clustering/voxel_grid.param.yaml", - ], - ), + add_launch_arg("use_low_height_cropbox", "false"), add_launch_arg( "euclidean_param_path", [ diff --git a/perception/euclidean_cluster/launch/euclidean_cluster.launch.xml b/perception/euclidean_cluster/launch/euclidean_cluster.launch.xml index 7159fb4c42793..fd1ea82befae0 100644 --- a/perception/euclidean_cluster/launch/euclidean_cluster.launch.xml +++ b/perception/euclidean_cluster/launch/euclidean_cluster.launch.xml @@ -3,16 +3,14 @@ - - + - - + diff --git a/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.py b/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.py index 82ce5605b67cd..00bcd4422bd3c 100644 --- a/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.py +++ b/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.py @@ -16,10 +16,8 @@ from launch.actions import DeclareLaunchArgument from launch.actions import OpaqueFunction from launch.conditions import IfCondition -from launch.substitutions import AndSubstitution -from launch.substitutions import AnonName +from launch.conditions import UnlessCondition from launch.substitutions import LaunchConfiguration -from launch.substitutions import NotSubstitution from launch_ros.actions import ComposableNodeContainer from launch_ros.actions import LoadComposableNodes from launch_ros.descriptions import ComposableNode @@ -36,159 +34,31 @@ def load_composable_node_param(param_path): ns = "" pkg = "euclidean_cluster" - # set compare map filter as a component - compare_map_filter_component = ComposableNode( - package="compare_map_segmentation", - namespace=ns, - plugin="compare_map_segmentation::VoxelBasedCompareMapFilterComponent", - name=AnonName("compare_map_filter"), - remappings=[ - ("input", LaunchConfiguration("input_pointcloud")), - ("map", LaunchConfiguration("input_map")), - ("output", "map_filter/pointcloud"), - ("map_loader_service", "/map/get_differential_pointcloud_map"), - ("kinematic_state", "/localization/kinematic_state"), - ], - parameters=[load_composable_node_param("compare_map_param_path")], - ) - - # separate range of pointcloud when map_filter used - use_map_short_range_crop_box_filter_component = ComposableNode( - package="pointcloud_preprocessor", - namespace=ns, - plugin="pointcloud_preprocessor::CropBoxFilterComponent", - name="short_distance_crop_box_range", - remappings=[ - ("input", "map_filter/pointcloud"), - ("output", "short_range/pointcloud"), - ], - parameters=[ - load_composable_node_param("voxel_grid_based_euclidean_param_path"), - { - "negative": False, - }, - ], - ) - - use_map_long_range_crop_box_filter_component = ComposableNode( - package="pointcloud_preprocessor", - namespace=ns, - plugin="pointcloud_preprocessor::CropBoxFilterComponent", - name="long_distance_crop_box_range", - remappings=[ - ("input", LaunchConfiguration("input_pointcloud")), - ("output", "long_range/pointcloud"), - ], - parameters=[ - load_composable_node_param("voxel_grid_based_euclidean_param_path"), - { - "negative": True, - }, - ], - ) - - # disuse_map_voxel_grid_filter - disuse_map_short_range_crop_box_filter_component = ComposableNode( + low_height_cropbox_filter_component = ComposableNode( package="pointcloud_preprocessor", namespace=ns, plugin="pointcloud_preprocessor::CropBoxFilterComponent", - name="short_distance_crop_box_range", + name="low_height_crop_box_filter", remappings=[ ("input", LaunchConfiguration("input_pointcloud")), - ("output", "short_range/pointcloud"), - ], - parameters=[ - load_composable_node_param("voxel_grid_based_euclidean_param_path"), - { - "negative": False, - }, - ], - ) - - disuse_map_long_range_crop_box_filter_component = ComposableNode( - package="pointcloud_preprocessor", - namespace=ns, - plugin="pointcloud_preprocessor::CropBoxFilterComponent", - name="long_distance_crop_box_range", - remappings=[ - ("input", LaunchConfiguration("input_pointcloud")), - ("output", "long_range/pointcloud"), - ], - parameters=[ - load_composable_node_param("voxel_grid_based_euclidean_param_path"), - { - "negative": True, - }, - ], - ) - - # set voxel grid filter as a component - voxel_grid_filter_component = ComposableNode( - package="pointcloud_preprocessor", - namespace=ns, - plugin="pointcloud_preprocessor::ApproximateDownsampleFilterComponent", - name=AnonName("voxel_grid_filter"), - remappings=[ - ("input", "short_range/pointcloud"), - ("output", "downsampled/short_range/pointcloud"), - ], - parameters=[load_composable_node_param("voxel_grid_param_path")], - ) - - # set outlier filter as a component - outlier_filter_component = ComposableNode( - package="pointcloud_preprocessor", - namespace=ns, - plugin="pointcloud_preprocessor::VoxelGridOutlierFilterComponent", - name="outlier_filter", - remappings=[ - ("input", "downsampled/short_range/pointcloud"), - ("output", "outlier_filter/pointcloud"), - ], - parameters=[load_composable_node_param("outlier_param_path")], - ) - - # concat with-outlier pointcloud and without-outlier pcl - downsample_concat_component = ComposableNode( - package="pointcloud_preprocessor", - namespace=ns, - plugin="pointcloud_preprocessor::PointCloudConcatenateDataSynchronizerComponent", - name="concat_downsampled_pcl", - remappings=[("output", "downsampled/concatenated/pointcloud")], - parameters=[ - { - "input_topics": ["long_range/pointcloud", "outlier_filter/pointcloud"], - "output_frame": "base_link", - } - ], - extra_arguments=[{"use_intra_process_comms": True}], - ) - - # set euclidean cluster as a component - use_downsample_euclidean_cluster_component = ComposableNode( - package=pkg, - namespace=ns, - plugin="euclidean_cluster::VoxelGridBasedEuclideanClusterNode", - name="euclidean_cluster", - remappings=[ - ("input", "downsampled/concatenated/pointcloud"), - ("output", LaunchConfiguration("output_clusters")), + ("output", "low_height/pointcloud"), ], parameters=[load_composable_node_param("voxel_grid_based_euclidean_param_path")], ) - use_map_disuse_downsample_euclidean_component = ComposableNode( + use_low_height_euclidean_component = ComposableNode( package=pkg, namespace=ns, plugin="euclidean_cluster::VoxelGridBasedEuclideanClusterNode", name="euclidean_cluster", remappings=[ - ("input", "map_filter/pointcloud"), + ("input", "low_height/pointcloud"), ("output", LaunchConfiguration("output_clusters")), ], parameters=[load_composable_node_param("voxel_grid_based_euclidean_param_path")], ) - disuse_map_disuse_downsample_euclidean_component = ComposableNode( + + disuse_low_height_euclidean_component = ComposableNode( package=pkg, namespace=ns, plugin="euclidean_cluster::VoxelGridBasedEuclideanClusterNode", @@ -209,75 +79,24 @@ def load_composable_node_param(param_path): output="screen", ) - use_map_use_downsample_loader = LoadComposableNodes( + use_low_height_pointcloud_loader = LoadComposableNodes( composable_node_descriptions=[ - compare_map_filter_component, - use_map_short_range_crop_box_filter_component, - use_map_long_range_crop_box_filter_component, - voxel_grid_filter_component, - outlier_filter_component, - downsample_concat_component, - use_downsample_euclidean_cluster_component, + low_height_cropbox_filter_component, + use_low_height_euclidean_component, ], target_container=container, - condition=IfCondition( - AndSubstitution( - LaunchConfiguration("use_pointcloud_map"), - LaunchConfiguration("use_downsample_pointcloud"), - ) - ), + condition=IfCondition(LaunchConfiguration("use_low_height_cropbox")), ) - disuse_map_use_downsample_loader = LoadComposableNodes( - composable_node_descriptions=[ - disuse_map_short_range_crop_box_filter_component, - disuse_map_long_range_crop_box_filter_component, - voxel_grid_filter_component, - outlier_filter_component, - downsample_concat_component, - use_downsample_euclidean_cluster_component, - ], + disuse_low_height_pointcloud_loader = LoadComposableNodes( + composable_node_descriptions=[disuse_low_height_euclidean_component], target_container=container, - condition=IfCondition( - AndSubstitution( - NotSubstitution(LaunchConfiguration("use_pointcloud_map")), - LaunchConfiguration("use_downsample_pointcloud"), - ) - ), - ) - - use_map_disuse_downsample_loader = LoadComposableNodes( - composable_node_descriptions=[ - compare_map_filter_component, - use_map_disuse_downsample_euclidean_component, - ], - target_container=container, - condition=IfCondition( - AndSubstitution( - (LaunchConfiguration("use_pointcloud_map")), - NotSubstitution(LaunchConfiguration("use_downsample_pointcloud")), - ) - ), - ) - - disuse_map_disuse_downsample_loader = LoadComposableNodes( - composable_node_descriptions=[ - disuse_map_disuse_downsample_euclidean_component, - ], - target_container=container, - condition=IfCondition( - AndSubstitution( - NotSubstitution(LaunchConfiguration("use_pointcloud_map")), - NotSubstitution(LaunchConfiguration("use_downsample_pointcloud")), - ) - ), + condition=UnlessCondition(LaunchConfiguration("use_low_height_cropbox")), ) return [ container, - use_map_use_downsample_loader, - disuse_map_use_downsample_loader, - use_map_disuse_downsample_loader, - disuse_map_disuse_downsample_loader, + use_low_height_pointcloud_loader, + disuse_low_height_pointcloud_loader, ] @@ -290,29 +109,7 @@ def add_launch_arg(name: str, default_value=None): add_launch_arg("input_pointcloud", "/perception/obstacle_segmentation/pointcloud"), add_launch_arg("input_map", "/map/pointcloud_map"), add_launch_arg("output_clusters", "clusters"), - add_launch_arg("use_pointcloud_map", "false"), - add_launch_arg("use_downsample_pointcloud", "false"), - add_launch_arg( - "voxel_grid_param_path", - [ - FindPackageShare("autoware_launch"), - "/config/perception/object_recognition/detection/clustering/voxel_grid.param.yaml", - ], - ), - add_launch_arg( - "outlier_param_path", - [ - FindPackageShare("autoware_launch"), - "/config/perception/object_recognition/detection/clustering/outlier.param.yaml", - ], - ), - add_launch_arg( - "compare_map_param_path", - [ - FindPackageShare("autoware_launch"), - "/config/perception/object_recognition/detection/clustering/compare_map.param.yaml", - ], - ), + add_launch_arg("use_low_height_cropbox", "false"), add_launch_arg( "voxel_grid_based_euclidean_param_path", [ diff --git a/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.xml b/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.xml index 2833fed81c28e..b6a426dabfd12 100644 --- a/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.xml +++ b/perception/euclidean_cluster/launch/voxel_grid_based_euclidean_cluster.launch.xml @@ -3,22 +3,14 @@ - - - - - + - - - - - + From f4929164abdd4ecbf383ffe80eee5fffea945578 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 19 Sep 2023 09:38:46 +0900 Subject: [PATCH 227/547] refactor(behavior_path_planner): use util function convertToPredictedPath from lane_change (#5013) --- .../utils/lane_change/utils.hpp | 3 --- .../src/scene_module/lane_change/normal.cpp | 2 +- .../src/utils/lane_change/utils.cpp | 13 ------------- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index f180afc2d1ffd..0c1fff52c74e2 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -160,9 +160,6 @@ std::vector convertToPredictedPath( const LaneChangePath & lane_change_path, const Twist & vehicle_twist, const Pose & pose, const BehaviorPathPlannerParameters & common_parameters, const double resolution); -PredictedPath convertToPredictedPath( - const std::vector & path, const double time_resolution); - bool isParkedObject( const PathWithLaneId & path, const RouteHandler & route_handler, const ExtendedPredictedObject & object, const double object_check_min_road_shoulder_width, diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 2f030689a29f5..333231e0ed0d4 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1346,7 +1346,7 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( const auto ego_predicted_path = utils::lane_change::convertToPredictedPath( lane_change_path, current_twist, current_pose, common_parameters, time_resolution); const auto debug_predicted_path = - utils::lane_change::convertToPredictedPath(ego_predicted_path, time_resolution); + utils::path_safety_checker::convertToPredictedPath(ego_predicted_path, time_resolution); auto collision_check_objects = target_objects.target_lane; diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index 3e098c651feef..29c139ab5a654 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -829,19 +829,6 @@ std::vector convertToPredictedPath( return predicted_path; } -PredictedPath convertToPredictedPath( - const std::vector & path, const double time_resolution) -{ - PredictedPath predicted_path; - predicted_path.time_step = rclcpp::Duration::from_seconds(time_resolution); - predicted_path.path.resize(path.size()); - - for (size_t i = 0; i < path.size(); ++i) { - predicted_path.path.at(i) = path.at(i).pose; - } - return predicted_path; -} - bool isParkedObject( const PathWithLaneId & path, const RouteHandler & route_handler, const ExtendedPredictedObject & object, const double object_check_min_road_shoulder_width, From 7eabe35c85a6a7b45c9ab7ccb41adf7f6998fb54 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 19 Sep 2023 10:19:32 +0900 Subject: [PATCH 228/547] fix(lanelet2_projector): fix werror return-type (#5019) Signed-off-by: kosuke55 Co-authored-by: kminoda <44218668+kminoda@users.noreply.github.com> --- common/geography_utils/src/lanelet2_projector.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/common/geography_utils/src/lanelet2_projector.cpp b/common/geography_utils/src/lanelet2_projector.cpp index 4958d661b0ce3..3e982ac2ccf4d 100644 --- a/common/geography_utils/src/lanelet2_projector.cpp +++ b/common/geography_utils/src/lanelet2_projector.cpp @@ -43,13 +43,11 @@ std::unique_ptr get_lanelet2_projector(const MapProjectorInf lanelet::Origin origin{position}; lanelet::projection::TransverseMercatorProjector projector{origin}; return std::make_unique(projector); - - } else { - const std::string error_msg = - "Invalid map projector type: " + projector_info.projector_type + - ". Currently supported types: MGRS, LocalCartesianUTM, and TransverseMercator"; - throw std::invalid_argument(error_msg); } + const std::string error_msg = + "Invalid map projector type: " + projector_info.projector_type + + ". Currently supported types: MGRS, LocalCartesianUTM, and TransverseMercator"; + throw std::invalid_argument(error_msg); } } // namespace geography_utils From 1506a947971371df964944ea3a19cc759f4dd7df Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:41:33 +0900 Subject: [PATCH 229/547] refactor(avoidance): generate drivable lanes in utils (#5018) * refactor(avoidance): generate drivable lanes in utils Signed-off-by: satoshi-ota * refactor(avoidance): use std::any_of Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.hpp | 2 +- .../utils/avoidance/utils.hpp | 4 + .../avoidance/avoidance_module.cpp | 162 +----------------- .../src/utils/avoidance/utils.cpp | 155 +++++++++++++++++ 4 files changed, 165 insertions(+), 158 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp index a719ab341b88b..c12f310fda088 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp @@ -460,7 +460,7 @@ class AvoidanceModule : public SceneModuleInterface // TODO(murooka) freespace during turning in intersection where there is no neighbor lanes // NOTE: Assume that there is no situation where there is an object in the middle lane of more // than two lanes since which way to avoid is not obvious - void generateExtendedDrivableArea(BehaviorModuleOutput & output) const; + void generateExpandDrivableLanes(BehaviorModuleOutput & output) const; /** * @brief fill debug markers. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index 1009540062a99..10ce16b00e2ae 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -168,6 +168,10 @@ std::pair separateObjectsByPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, const AvoidancePlanningData & data, const std::shared_ptr & parameters, DebugData & debug); + +DrivableLanes generateExpandDrivableLanes( + const lanelet::ConstLanelet & lanelet, const std::shared_ptr & planner_data, + const std::shared_ptr & parameters); } // namespace behavior_path_planner::utils::avoidance #endif // BEHAVIOR_PATH_PLANNER__UTILS__AVOIDANCE__UTILS_HPP_ diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index ce5d13d17aedf..eb6a1e125f412 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -69,22 +69,6 @@ using tier4_planning_msgs::msg::AvoidanceDebugFactor; namespace { -bool isEndPointsConnected( - const lanelet::ConstLanelet & left_lane, const lanelet::ConstLanelet & right_lane) -{ - const auto & left_back_point_2d = right_lane.leftBound2d().back().basicPoint(); - const auto & right_back_point_2d = left_lane.rightBound2d().back().basicPoint(); - - constexpr double epsilon = 1e-5; - return (right_back_point_2d - left_back_point_2d).norm() < epsilon; -} - -template -void pushUniqueVector(T & base_vector, const T & additional_vector) -{ - base_vector.insert(base_vector.end(), additional_vector.begin(), additional_vector.end()); -} - bool isDrivingSameLane( const lanelet::ConstLanelets & previous_lanes, const lanelet::ConstLanelets & current_lanes) { @@ -1912,148 +1896,12 @@ bool AvoidanceModule::isSafePath( return safe_ || safe_count_ > parameters_->hysteresis_factor_safe_count; } -void AvoidanceModule::generateExtendedDrivableArea(BehaviorModuleOutput & output) const +void AvoidanceModule::generateExpandDrivableLanes(BehaviorModuleOutput & output) const { - const auto has_same_lane = - [](const lanelet::ConstLanelets lanes, const lanelet::ConstLanelet & lane) { - if (lanes.empty()) return false; - const auto has_same = [&](const auto & ll) { return ll.id() == lane.id(); }; - return std::find_if(lanes.begin(), lanes.end(), has_same) != lanes.end(); - }; - - const auto & route_handler = planner_data_->route_handler; - const auto & current_lanes = avoid_data_.current_lanelets; - const auto & enable_opposite = parameters_->use_opposite_lane; std::vector drivable_lanes; - - for (const auto & current_lane : current_lanes) { - DrivableLanes current_drivable_lanes; - current_drivable_lanes.left_lane = current_lane; - current_drivable_lanes.right_lane = current_lane; - - if (!parameters_->use_adjacent_lane) { - drivable_lanes.push_back(current_drivable_lanes); - continue; - } - - // 1. get left/right side lanes - const auto update_left_lanelets = [&](const lanelet::ConstLanelet & target_lane) { - const auto all_left_lanelets = - route_handler->getAllLeftSharedLinestringLanelets(target_lane, enable_opposite, true); - if (!all_left_lanelets.empty()) { - current_drivable_lanes.left_lane = all_left_lanelets.back(); // leftmost lanelet - pushUniqueVector( - current_drivable_lanes.middle_lanes, - lanelet::ConstLanelets(all_left_lanelets.begin(), all_left_lanelets.end() - 1)); - } - }; - const auto update_right_lanelets = [&](const lanelet::ConstLanelet & target_lane) { - const auto all_right_lanelets = - route_handler->getAllRightSharedLinestringLanelets(target_lane, enable_opposite, true); - if (!all_right_lanelets.empty()) { - current_drivable_lanes.right_lane = all_right_lanelets.back(); // rightmost lanelet - pushUniqueVector( - current_drivable_lanes.middle_lanes, - lanelet::ConstLanelets(all_right_lanelets.begin(), all_right_lanelets.end() - 1)); - } - }; - - update_left_lanelets(current_lane); - update_right_lanelets(current_lane); - - // 2.1 when there are multiple lanes whose previous lanelet is the same - const auto get_next_lanes_from_same_previous_lane = - [&route_handler](const lanelet::ConstLanelet & lane) { - // get previous lane, and return false if previous lane does not exist - lanelet::ConstLanelets prev_lanes; - if (!route_handler->getPreviousLaneletsWithinRoute(lane, &prev_lanes)) { - return lanelet::ConstLanelets{}; - } - - lanelet::ConstLanelets next_lanes; - for (const auto & prev_lane : prev_lanes) { - const auto next_lanes_from_prev = route_handler->getNextLanelets(prev_lane); - pushUniqueVector(next_lanes, next_lanes_from_prev); - } - return next_lanes; - }; - - const auto next_lanes_for_right = - get_next_lanes_from_same_previous_lane(current_drivable_lanes.right_lane); - const auto next_lanes_for_left = - get_next_lanes_from_same_previous_lane(current_drivable_lanes.left_lane); - - // 2.2 look for neighbor lane recursively, where end line of the lane is connected to end line - // of the original lane - const auto update_drivable_lanes = - [&](const lanelet::ConstLanelets & next_lanes, const bool is_left) { - for (const auto & next_lane : next_lanes) { - const auto & edge_lane = - is_left ? current_drivable_lanes.left_lane : current_drivable_lanes.right_lane; - if (next_lane.id() == edge_lane.id()) { - continue; - } - - const auto & left_lane = is_left ? next_lane : edge_lane; - const auto & right_lane = is_left ? edge_lane : next_lane; - if (!isEndPointsConnected(left_lane, right_lane)) { - continue; - } - - if (is_left) { - current_drivable_lanes.left_lane = next_lane; - } else { - current_drivable_lanes.right_lane = next_lane; - } - - if (!has_same_lane(current_drivable_lanes.middle_lanes, edge_lane)) { - if (is_left) { - if (current_drivable_lanes.right_lane.id() != edge_lane.id()) { - current_drivable_lanes.middle_lanes.push_back(edge_lane); - } - } else { - if (current_drivable_lanes.left_lane.id() != edge_lane.id()) { - current_drivable_lanes.middle_lanes.push_back(edge_lane); - } - } - } - - return true; - } - return false; - }; - - const auto expand_drivable_area_recursively = - [&](const lanelet::ConstLanelets & next_lanes, const bool is_left) { - // NOTE: set max search num to avoid infinity loop for drivable area expansion - constexpr size_t max_recursive_search_num = 3; - for (size_t i = 0; i < max_recursive_search_num; ++i) { - const bool is_update_kept = update_drivable_lanes(next_lanes, is_left); - if (!is_update_kept) { - break; - } - if (i == max_recursive_search_num - 1) { - RCLCPP_ERROR( - rclcpp::get_logger("behavior_path_planner").get_child("avoidance"), - "Drivable area expansion reaches max iteration."); - } - } - }; - expand_drivable_area_recursively(next_lanes_for_right, false); - expand_drivable_area_recursively(next_lanes_for_left, true); - - // 3. update again for new left/right lanes - update_left_lanelets(current_drivable_lanes.left_lane); - update_right_lanelets(current_drivable_lanes.right_lane); - - // 4. compensate that current_lane is in either of left_lane, right_lane or middle_lanes. - if ( - current_drivable_lanes.left_lane.id() != current_lane.id() && - current_drivable_lanes.right_lane.id() != current_lane.id()) { - current_drivable_lanes.middle_lanes.push_back(current_lane); - } - - drivable_lanes.push_back(current_drivable_lanes); + for (const auto & lanelet : avoid_data_.current_lanelets) { + drivable_lanes.push_back( + utils::avoidance::generateExpandDrivableLanes(lanelet, planner_data_, parameters_)); } { // for new architecture @@ -2206,7 +2054,7 @@ BehaviorModuleOutput AvoidanceModule::plan() utils::clipPathLength(*output.path, ego_idx, planner_data_->parameters); // Drivable area generation. - generateExtendedDrivableArea(output); + generateExpandDrivableLanes(output); setDrivableLanes(output.drivable_area_info.drivable_lanes); return output; diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 1c1c155a7b661..331c8912fddf6 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -202,6 +202,22 @@ Polygon2d createOneStepPolygon( return hull_polygon; } + +bool isEndPointsConnected( + const lanelet::ConstLanelet & left_lane, const lanelet::ConstLanelet & right_lane) +{ + const auto & left_back_point_2d = right_lane.leftBound2d().back().basicPoint(); + const auto & right_back_point_2d = left_lane.rightBound2d().back().basicPoint(); + + constexpr double epsilon = 1e-5; + return (right_back_point_2d - left_back_point_2d).norm() < epsilon; +} + +template +void pushUniqueVector(T & base_vector, const T & additional_vector) +{ + base_vector.insert(base_vector.end(), additional_vector.begin(), additional_vector.end()); +} } // namespace bool isOnRight(const ObjectData & obj) @@ -1673,4 +1689,143 @@ std::pair separateObjectsByPath( return std::make_pair(target_objects, other_objects); } + +DrivableLanes generateExpandDrivableLanes( + const lanelet::ConstLanelet & lanelet, const std::shared_ptr & planner_data, + const std::shared_ptr & parameters) +{ + const auto & route_handler = planner_data->route_handler; + + DrivableLanes current_drivable_lanes; + current_drivable_lanes.left_lane = lanelet; + current_drivable_lanes.right_lane = lanelet; + + if (!parameters->use_adjacent_lane) { + return current_drivable_lanes; + } + + // 1. get left/right side lanes + const auto update_left_lanelets = [&](const lanelet::ConstLanelet & target_lane) { + const auto all_left_lanelets = route_handler->getAllLeftSharedLinestringLanelets( + target_lane, parameters->use_opposite_lane, true); + if (!all_left_lanelets.empty()) { + current_drivable_lanes.left_lane = all_left_lanelets.back(); // leftmost lanelet + pushUniqueVector( + current_drivable_lanes.middle_lanes, + lanelet::ConstLanelets(all_left_lanelets.begin(), all_left_lanelets.end() - 1)); + } + }; + const auto update_right_lanelets = [&](const lanelet::ConstLanelet & target_lane) { + const auto all_right_lanelets = route_handler->getAllRightSharedLinestringLanelets( + target_lane, parameters->use_opposite_lane, true); + if (!all_right_lanelets.empty()) { + current_drivable_lanes.right_lane = all_right_lanelets.back(); // rightmost lanelet + pushUniqueVector( + current_drivable_lanes.middle_lanes, + lanelet::ConstLanelets(all_right_lanelets.begin(), all_right_lanelets.end() - 1)); + } + }; + + update_left_lanelets(lanelet); + update_right_lanelets(lanelet); + + // 2.1 when there are multiple lanes whose previous lanelet is the same + const auto get_next_lanes_from_same_previous_lane = + [&route_handler](const lanelet::ConstLanelet & lane) { + // get previous lane, and return false if previous lane does not exist + lanelet::ConstLanelets prev_lanes; + if (!route_handler->getPreviousLaneletsWithinRoute(lane, &prev_lanes)) { + return lanelet::ConstLanelets{}; + } + + lanelet::ConstLanelets next_lanes; + for (const auto & prev_lane : prev_lanes) { + const auto next_lanes_from_prev = route_handler->getNextLanelets(prev_lane); + pushUniqueVector(next_lanes, next_lanes_from_prev); + } + return next_lanes; + }; + + const auto next_lanes_for_right = + get_next_lanes_from_same_previous_lane(current_drivable_lanes.right_lane); + const auto next_lanes_for_left = + get_next_lanes_from_same_previous_lane(current_drivable_lanes.left_lane); + + // 2.2 look for neighbor lane recursively, where end line of the lane is connected to end line + // of the original lane + const auto update_drivable_lanes = + [&](const lanelet::ConstLanelets & next_lanes, const bool is_left) { + for (const auto & next_lane : next_lanes) { + const auto & edge_lane = + is_left ? current_drivable_lanes.left_lane : current_drivable_lanes.right_lane; + if (next_lane.id() == edge_lane.id()) { + continue; + } + + const auto & left_lane = is_left ? next_lane : edge_lane; + const auto & right_lane = is_left ? edge_lane : next_lane; + if (!isEndPointsConnected(left_lane, right_lane)) { + continue; + } + + if (is_left) { + current_drivable_lanes.left_lane = next_lane; + } else { + current_drivable_lanes.right_lane = next_lane; + } + + const auto & middle_lanes = current_drivable_lanes.middle_lanes; + const auto has_same_lane = std::any_of( + middle_lanes.begin(), middle_lanes.end(), + [&edge_lane](const auto & lane) { return lane.id() == edge_lane.id(); }); + + if (!has_same_lane) { + if (is_left) { + if (current_drivable_lanes.right_lane.id() != edge_lane.id()) { + current_drivable_lanes.middle_lanes.push_back(edge_lane); + } + } else { + if (current_drivable_lanes.left_lane.id() != edge_lane.id()) { + current_drivable_lanes.middle_lanes.push_back(edge_lane); + } + } + } + + return true; + } + return false; + }; + + const auto expand_drivable_area_recursively = + [&](const lanelet::ConstLanelets & next_lanes, const bool is_left) { + // NOTE: set max search num to avoid infinity loop for drivable area expansion + constexpr size_t max_recursive_search_num = 3; + for (size_t i = 0; i < max_recursive_search_num; ++i) { + const bool is_update_kept = update_drivable_lanes(next_lanes, is_left); + if (!is_update_kept) { + break; + } + if (i == max_recursive_search_num - 1) { + RCLCPP_ERROR( + rclcpp::get_logger("behavior_path_planner").get_child("avoidance"), + "Drivable area expansion reaches max iteration."); + } + } + }; + expand_drivable_area_recursively(next_lanes_for_right, false); + expand_drivable_area_recursively(next_lanes_for_left, true); + + // 3. update again for new left/right lanes + update_left_lanelets(current_drivable_lanes.left_lane); + update_right_lanelets(current_drivable_lanes.right_lane); + + // 4. compensate that current_lane is in either of left_lane, right_lane or middle_lanes. + if ( + current_drivable_lanes.left_lane.id() != lanelet.id() && + current_drivable_lanes.right_lane.id() != lanelet.id()) { + current_drivable_lanes.middle_lanes.push_back(lanelet); + } + + return current_drivable_lanes; +} } // namespace behavior_path_planner::utils::avoidance From 31dbd2ed859d052dbcc514eb5b9a8c7df7a87917 Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Tue, 19 Sep 2023 11:58:56 +0900 Subject: [PATCH 230/547] fix(radar_fusion_to_detected_object): fix confidence (#4992) Signed-off-by: scepter914 --- .../src/radar_fusion_to_detected_object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp b/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp index 839a606c0e307..75684d51039fd 100644 --- a/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp +++ b/perception/radar_fusion_to_detected_object/src/radar_fusion_to_detected_object.cpp @@ -296,7 +296,7 @@ TwistWithCovariance RadarFusionToDetectedObject::estimateTwist( bool RadarFusionToDetectedObject::isQualified( const DetectedObject & object, std::shared_ptr> & radars) { - if (object.classification[0].probability > param_.threshold_probability) { + if (object.existence_probability > param_.threshold_probability) { return true; } else { if (!radars || !(*radars).empty()) { From 51fa0f55050c10c374bf09fe6023d5dc5cc989ae Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:16:06 +0900 Subject: [PATCH 231/547] fix(radar_tracks_msg_converter): fix twist coordinate conversion (#4993) Signed-off-by: scepter914 --- .../radar_tracks_msgs_converter_node.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp b/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp index 75556fbb0c372..1122bf7b4b18e 100644 --- a/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp +++ b/perception/radar_tracks_msgs_converter/src/radar_tracks_msgs_converter_node/radar_tracks_msgs_converter_node.cpp @@ -214,8 +214,15 @@ TrackedObjects RadarTracksMsgsConverterNode::convertRadarTrackToTrackedObjects() kinematics.orientation_availability = TrackedObjectKinematics::AVAILABLE; kinematics.is_stationary = false; - // Twist conversion - geometry_msgs::msg::Vector3 compensated_velocity = radar_track.velocity; + geometry_msgs::msg::Vector3 compensated_velocity{}; + { + double rotate_yaw = tf2::getYaw(transform_->transform.rotation); + const geometry_msgs::msg::Vector3 & vel = radar_track.velocity; + compensated_velocity.x = vel.x * std::cos(rotate_yaw) - vel.y * std::sin(rotate_yaw); + compensated_velocity.y = vel.x * std::sin(rotate_yaw) + vel.y * std::cos(rotate_yaw); + compensated_velocity.z = radar_track.velocity.z; + } + if (node_param_.use_twist_compensation) { if (odometry_data_) { compensated_velocity.x += odometry_data_->twist.twist.linear.x; @@ -235,12 +242,12 @@ TrackedObjects RadarTracksMsgsConverterNode::convertRadarTrackToTrackedObjects() double yaw = tier4_autoware_utils::normalizeRadian( std::atan2(compensated_velocity.y, compensated_velocity.x)); - radar_pose_stamped.pose.orientation = tier4_autoware_utils::createQuaternionFromYaw(yaw); geometry_msgs::msg::PoseStamped transformed_pose_stamped{}; tf2::doTransform(radar_pose_stamped, transformed_pose_stamped, *transform_); kinematics.pose_with_covariance.pose = transformed_pose_stamped.pose; - + kinematics.pose_with_covariance.pose.orientation = + tier4_autoware_utils::createQuaternionFromYaw(yaw); { auto & pose_cov = kinematics.pose_with_covariance.covariance; auto & radar_position_cov = radar_track.position_covariance; From cca5fee5a87ed3d9d72a029dc4c1cd0834dc5a2e Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:17:42 +0900 Subject: [PATCH 232/547] fix(tier4_perception_launch): add object_merger of far_objects to fusion for Camera-LiDAR-Radar fusion (#5026) * fix(tier4_perception_launch): add object_merger of far_objects to fusion for Camera-LiDAR-Radar fusion Signed-off-by: scepter914 * fix conflict Signed-off-by: scepter914 --------- Signed-off-by: scepter914 --- ...ra_lidar_fusion_based_detection.launch.xml | 6 ++-- ...ar_radar_fusion_based_detection.launch.xml | 33 ++++++++++++------ .../detection/detection.launch.xml | 34 ++++++++++++------- .../lidar_based_detection.launch.xml | 4 +-- .../lidar_radar_based_detection.launch.xml | 15 ++++---- .../radar_based_detection.launch.xml | 14 ++++---- .../launch/perception.launch.xml | 28 ++++++++------- 7 files changed, 79 insertions(+), 55 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index 7f2fc45396f7f..178e193dc2d99 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -1,9 +1,9 @@ - + - + @@ -18,7 +18,7 @@ - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index 2bd007b658978..df833296e3f66 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -1,15 +1,15 @@ - + - + - + @@ -17,7 +17,7 @@ - + @@ -44,8 +44,9 @@ - + + - + @@ -218,7 +219,7 @@ - + @@ -290,7 +291,7 @@ - + @@ -299,7 +300,7 @@ - + @@ -381,7 +382,7 @@ - + @@ -389,8 +390,18 @@ - + + + + + + + + + + + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml index ab55047132482..77a3e345ec5e8 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml @@ -1,15 +1,16 @@ + - + - + @@ -28,14 +29,15 @@ - + + + + + - - - - + @@ -63,13 +65,16 @@ - + + + + - + @@ -100,7 +105,7 @@ - + @@ -109,12 +114,14 @@ - + + + - + @@ -127,11 +134,12 @@ - + + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml index ccc236d9cdc94..23e0297dc5e44 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml @@ -8,7 +8,7 @@ - + @@ -70,7 +70,7 @@ - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml index 4756fc9338e46..cee6829ea3b4a 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_radar_based_detection.launch.xml @@ -1,17 +1,18 @@ - + - + - + + - + @@ -22,11 +23,11 @@ - + - + @@ -42,7 +43,7 @@ - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml index 8e2e23b510a07..52c8aedff8ef9 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml @@ -2,14 +2,14 @@ - - - - - + + + + + - - + + diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index a30b87a1068b5..36d43bab74894 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -11,6 +11,7 @@ + @@ -22,11 +23,11 @@ - + - + @@ -69,7 +70,8 @@ - + + @@ -87,15 +89,15 @@ description="options: `traffic_light_classifier_mobilenetv2_batch_*` or `traffic_light_classifier_efficientNet_b1_batch_*`. The batch number must be either one of 1, 4, 6" /> - + - + - + @@ -108,7 +110,7 @@ - + @@ -126,10 +128,10 @@ - + - + @@ -158,6 +160,7 @@ + @@ -169,14 +172,15 @@ - + - + + @@ -192,7 +196,7 @@ - + From 7080d7e12f04ad923c79857383aa76d300a892d7 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 19 Sep 2023 15:46:02 +0900 Subject: [PATCH 233/547] fix(mission_planner/behavior_path_planner): use transient local for modified goal (#5012) fix(mission_planner/behavior_path_planner): use trasient local for modified goal Signed-off-by: kosuke55 --- .../src/behavior_path_planner_node.cpp | 4 +++- .../src/mission_planner/mission_planner.cpp | 9 +++++---- .../src/mission_planner/mission_planner.hpp | 5 +++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index 41df95378c52f..c14daa30b784d 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -66,7 +66,9 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod turn_signal_publisher_ = create_publisher("~/output/turn_indicators_cmd", 1); hazard_signal_publisher_ = create_publisher("~/output/hazard_lights_cmd", 1); - modified_goal_publisher_ = create_publisher("~/output/modified_goal", 1); + const auto durable_qos = rclcpp::QoS(1).transient_local(); + modified_goal_publisher_ = + create_publisher("~/output/modified_goal", durable_qos); stop_reason_publisher_ = create_publisher("~/output/stop_reasons", 1); reroute_availability_publisher_ = create_publisher("~/output/is_reroute_available", 1); diff --git a/planning/mission_planner/src/mission_planner/mission_planner.cpp b/planning/mission_planner/src/mission_planner/mission_planner.cpp index 9e6ef248af336..02c7903a12249 100644 --- a/planning/mission_planner/src/mission_planner/mission_planner.cpp +++ b/planning/mission_planner/src/mission_planner/mission_planner.cpp @@ -99,12 +99,14 @@ MissionPlanner::MissionPlanner(const rclcpp::NodeOptions & options) rclcpp::QoS(1), std::bind(&MissionPlanner::on_reroute_availability, this, std::placeholders::_1)); - auto qos_transient_local = rclcpp::QoS{1}.transient_local(); + const auto durable_qos = rclcpp::QoS(1).transient_local(); sub_vector_map_ = create_subscription( - "input/vector_map", qos_transient_local, + "input/vector_map", durable_qos, std::bind(&MissionPlanner::on_map, this, std::placeholders::_1)); + sub_modified_goal_ = create_subscription( + "input/modified_goal", durable_qos, + std::bind(&MissionPlanner::on_modified_goal, this, std::placeholders::_1)); - const auto durable_qos = rclcpp::QoS(1).transient_local(); pub_marker_ = create_publisher("debug/route_marker", durable_qos); const auto adaptor = component_interface_utils::NodeAdaptor(this); @@ -119,7 +121,6 @@ MissionPlanner::MissionPlanner(const rclcpp::NodeOptions & options) adaptor.init_srv(srv_change_route_points_, this, &MissionPlanner::on_change_route_points); adaptor.init_srv(srv_set_mrm_route_, this, &MissionPlanner::on_set_mrm_route); adaptor.init_srv(srv_clear_mrm_route_, this, &MissionPlanner::on_clear_mrm_route); - adaptor.init_sub(sub_modified_goal_, this, &MissionPlanner::on_modified_goal); change_state(RouteState::Message::UNSET); } diff --git a/planning/mission_planner/src/mission_planner/mission_planner.hpp b/planning/mission_planner/src/mission_planner/mission_planner.hpp index fd1b317970749..41b03b09e0755 100644 --- a/planning/mission_planner/src/mission_planner/mission_planner.hpp +++ b/planning/mission_planner/src/mission_planner/mission_planner.hpp @@ -75,12 +75,15 @@ class MissionPlanner : public rclcpp::Node rclcpp::Subscription::SharedPtr sub_odometry_; rclcpp::Subscription::SharedPtr sub_vector_map_; rclcpp::Subscription::SharedPtr sub_reroute_availability_; + rclcpp::Subscription::SharedPtr sub_modified_goal_; + Odometry::ConstSharedPtr odometry_; HADMapBin::ConstSharedPtr map_ptr_; RerouteAvailability::ConstSharedPtr reroute_availability_; void on_odometry(const Odometry::ConstSharedPtr msg); void on_map(const HADMapBin::ConstSharedPtr msg); void on_reroute_availability(const RerouteAvailability::ConstSharedPtr msg); + void on_modified_goal(const PoseWithUuidStamped::ConstSharedPtr msg); rclcpp::Publisher::SharedPtr pub_marker_; void clear_route(); @@ -128,8 +131,6 @@ class MissionPlanner : public rclcpp::Node const ClearMrmRoute::Service::Request::SharedPtr req, const ClearMrmRoute::Service::Response::SharedPtr res); - component_interface_utils::Subscription::SharedPtr sub_modified_goal_; - void on_modified_goal(const ModifiedGoal::Message::ConstSharedPtr msg); void on_change_route( const SetRoute::Service::Request::SharedPtr req, const SetRoute::Service::Response::SharedPtr res); From c33fc1dbed01d3b3af3613a6bc132cda9b1e75b6 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:19:31 +0900 Subject: [PATCH 234/547] feat(avoidance): support s-shape avoidance (#4974) * feat(avoidance): define avoid line outline Signed-off-by: satoshi-ota * feat(avoidance): support s-shape avoidance maneuver Signed-off-by: satoshi-ota * fix(avoidance): remove unnecessary process Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.hpp | 55 +- .../utils/avoidance/avoidance_module_data.hpp | 20 +- .../utils/avoidance/utils.hpp | 5 + .../avoidance/avoidance_module.cpp | 587 +++++++++--------- .../src/utils/avoidance/utils.cpp | 39 +- 5 files changed, 383 insertions(+), 323 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp index c12f310fda088..a5f666099a861 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp @@ -311,8 +311,32 @@ class AvoidanceModule : public SceneModuleInterface * @param debug data. * @return processed shift lines. */ - AvoidLineArray calcRawShiftLinesFromObjects( - AvoidancePlanningData & data, DebugData & debug) const; + AvoidOutlines generateAvoidOutline(AvoidancePlanningData & data, DebugData & debug) const; + + /* + * @brief merge avoid outlines. + * @param original shift lines. + * @param debug data. + * @return processed shift lines. + */ + AvoidOutlines applyMergeProcess(const AvoidOutlines & outlines, DebugData & debug) const; + + /* + * @brief fill gap between two shift lines. + * @param original shift lines. + * @param debug data. + * @return processed shift lines. + */ + AvoidOutlines applyFillGapProcess(const AvoidOutlines & outlines, DebugData & debug) const; + + /* + * @brief generate candidate shift lines. + * @param one-shot shift lines. + * @param path shifter. + * @param debug data. + */ + AvoidLineArray generateCandidateShiftLine( + const AvoidLineArray & shift_lines, const PathShifter & path_shifter, DebugData & debug) const; /** * @brief clean up raw shift lines. @@ -325,8 +349,7 @@ class AvoidanceModule : public SceneModuleInterface * 3. merge raw shirt lines. * 4. trim unnecessary shift lines. */ - AvoidLineArray applyPreProcess( - AvoidLineArray & current_raw_shift_points, DebugData & debug) const; + AvoidLineArray applyPreProcess(const AvoidOutlines & outlines, DebugData & debug) const; /* * @brief fill gap among shift lines. @@ -344,6 +367,16 @@ class AvoidanceModule : public SceneModuleInterface */ AvoidLineArray applyMergeProcess(const AvoidLineArray & shift_lines, DebugData & debug) const; + /* + * @brief add return shift line from ego position. + * @param current raw shift line. + * @param current registered shift line. + * @param debug data. + */ + AvoidLineArray applyCombineProcess( + const AvoidLineArray & shift_lines, const AvoidLineArray & registered_lines, + [[maybe_unused]] DebugData & debug) const; + /* * @brief add return shift line from ego position. * @param shift lines which the return shift is added. @@ -381,13 +414,7 @@ class AvoidanceModule : public SceneModuleInterface * @param candidate shift lines. * @return new shift lines. */ - AvoidLineArray findNewShiftLine(const AvoidLineArray & shift_lines) const; - - /* - * @brief fill gap between two shift lines. - * @param original shift lines. - */ - void fillShiftLineGap(AvoidLineArray & shift_lines) const; + AvoidLineArray findNewShiftLine(const AvoidLineArray & shift_lines, DebugData & debug) const; /* * @brief generate total shift line. total shift line has shift length and gradient array. @@ -419,12 +446,6 @@ class AvoidanceModule : public SceneModuleInterface */ void applySimilarGradFilter(AvoidLineArray & shift_lines, const double threshold) const; - /* - * @brief trim invalid shift lines whose gradient it too large to follow. - * @param target shift lines. - */ - void applySharpShiftFilter(AvoidLineArray & shift_lines, const double threshold) const; - /** * @brief add new shift line to path shifter if the RTC status is activated. * @param new shift lines. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index afa97e3752d7b..83880aa6e4f82 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -396,6 +396,9 @@ using ObjectDataArray = std::vector; */ struct AvoidLine : public ShiftLine { + // object side + bool object_on_right = true; + // Distance from ego to start point in Frenet double start_longitudinal = 0.0; @@ -419,6 +422,21 @@ struct AvoidLine : public ShiftLine }; using AvoidLineArray = std::vector; +struct AvoidOutline +{ + AvoidOutline(const AvoidLine & avoid_line, const AvoidLine & return_line) + : avoid_line{avoid_line}, return_line{return_line} + { + } + + AvoidLine avoid_line{}; + + AvoidLine return_line{}; + + AvoidLineArray middle_lines{}; +}; +using AvoidOutlines = std::vector; + /* * avoidance state */ @@ -534,7 +552,6 @@ struct DebugData AvoidLineArray step1_combined_shift_line; AvoidLineArray step1_return_shift_line; AvoidLineArray step1_front_shift_line; - AvoidLineArray step1_shift_line; // create outline process AvoidLineArray step2_merged_shift_line; @@ -548,7 +565,6 @@ struct DebugData // registered process AvoidLineArray step4_new_shift_line; - AvoidLineArray step4_valid_shift_line; // shift length std::vector pos_shift; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index 10ce16b00e2ae..44d5d6f6b4e56 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -150,6 +150,11 @@ double extendToRoadShoulderDistanceWithPolygon( void fillAdditionalInfoFromPoint(const AvoidancePlanningData & data, AvoidLineArray & lines); +void fillAdditionalInfoFromLongitudinal(const AvoidancePlanningData & data, AvoidLine & line); + +void fillAdditionalInfoFromLongitudinal( + const AvoidancePlanningData & data, AvoidOutlines & outlines); + void fillAdditionalInfoFromLongitudinal(const AvoidancePlanningData & data, AvoidLineArray & lines); AvoidLine fillAdditionalInfo(const AvoidancePlanningData & data, const AvoidLine & line); diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index eb6a1e125f412..26a0df487ef78 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -92,6 +92,56 @@ bool isBestEffort(const std::string & policy) { return policy == "best_effort"; } + +AvoidLine merge(const AvoidLine & line1, const AvoidLine & line2, const uint64_t id) +{ + AvoidLine ret{}; + + ret.start_idx = line1.start_idx; + ret.start_shift_length = line1.start_shift_length; + ret.start_longitudinal = line1.start_longitudinal; + + ret.end_idx = line2.end_idx; + ret.end_shift_length = line2.end_shift_length; + ret.end_longitudinal = line2.end_longitudinal; + + ret.id = id; + ret.object = line1.object; + + return ret; +} + +AvoidLine fill(const AvoidLine & line1, const AvoidLine & line2, const uint64_t id) +{ + AvoidLine ret{}; + + ret.start_idx = line1.end_idx; + ret.start_shift_length = line1.end_shift_length; + ret.start_longitudinal = line1.end_longitudinal; + + ret.end_idx = line2.start_idx; + ret.end_shift_length = line2.start_shift_length; + ret.end_longitudinal = line2.start_longitudinal; + + ret.id = id; + ret.object = line1.object; + + return ret; +} + +AvoidLineArray toArray(const AvoidOutlines & outlines) +{ + AvoidLineArray ret{}; + for (const auto & outline : outlines) { + ret.push_back(outline.avoid_line); + ret.push_back(outline.return_line); + + std::for_each( + outline.middle_lines.begin(), outline.middle_lines.end(), + [&ret](const auto & line) { ret.push_back(line); }); + } + return ret; +} } // namespace AvoidanceModule::AvoidanceModule( @@ -372,42 +422,36 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de auto path_shifter = path_shifter_; /** - * STEP 1 - * Create raw shift points from target object. The lateral margin between the ego and the target - * object varies depending on the relative speed between the ego and the target object. + * STEP1: Generate avoid outlines. + * Basically, avoid outlines are generated per target objects. */ - data.unapproved_raw_sl = calcRawShiftLinesFromObjects(data, debug); + const auto outlines = generateAvoidOutline(data, debug); /** - * STEP 2 - * Modify the raw shift points. (Merging, Trimming) + * STEP2: Create rough shift lines. */ - const auto processed_raw_sp = applyPreProcess(data.unapproved_raw_sl, debug); + data.unapproved_raw_sl = applyPreProcess(outlines, debug); /** - * STEP 3 - * Find new shift point + * STEP3: Create candidate shift lines. + * Merge rough shift lines, and extract new shift lines. */ - const auto new_sp = findNewShiftLine(processed_raw_sp); - if (isValidShiftLine(new_sp, path_shifter)) { - data.unapproved_new_sl = new_sp; - } - + data.unapproved_new_sl = generateCandidateShiftLine(data.unapproved_raw_sl, path_shifter, debug); const auto found_new_sl = data.unapproved_new_sl.size() > 0; const auto registered = path_shifter.getShiftLines().size() > 0; data.found_avoidance_path = found_new_sl || registered; /** - * STEP 4 - * If there are new shift points, these shift points are registered in path_shifter. + * STEP4: Set new shift lines. + * If there are new shift points, these shift points are registered in path_shifter in order to + * generate candidate avoidance path. */ if (!data.unapproved_new_sl.empty()) { addNewShiftLines(path_shifter, data.unapproved_new_sl); } /** - * STEP 5 - * Generate avoidance path. + * STEP5: Generate avoidance path. */ ShiftedPath spline_shift_path = utils::avoidance::toShiftedPath(data.reference_path); const auto success_spline_path_generation = @@ -417,8 +461,8 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de : utils::avoidance::toShiftedPath(data.reference_path); /** - * STEP 6 - * Check avoidance path safety. For each target objects and the objects in adjacent lanes, + * STEP6: Check avoidance path safety. + * For each target objects and the objects in adjacent lanes, * check that there is a certain amount of margin in the lateral and longitudinal direction. */ data.comfortable = isComfortable(data.unapproved_new_sl); @@ -518,11 +562,9 @@ void AvoidanceModule::fillEgoStatus( } } -void AvoidanceModule::fillDebugData(const AvoidancePlanningData & data, DebugData & debug) const +void AvoidanceModule::fillDebugData( + const AvoidancePlanningData & data, [[maybe_unused]] DebugData & debug) const { - debug.step1_current_raw_shift_line = data.unapproved_raw_sl; - debug.step4_new_shift_line = data.unapproved_new_sl; - if (!data.stop_target_object) { return; } @@ -668,66 +710,81 @@ void AvoidanceModule::updateRegisteredRawShiftLines() } AvoidLineArray AvoidanceModule::applyPreProcess( - AvoidLineArray & raw_shift_lines, DebugData & debug) const + const AvoidOutlines & outlines, DebugData & debug) const { - const auto fill_gap_shift_lines = applyFillGapProcess(raw_shift_lines, debug); + AvoidOutlines processed_outlines = outlines; + + /** + * Step1: Rough merge process. + * Merge multiple avoid outlines. If an avoid outlines' return shift line conflicts other + * outline's avoid shift line, those avoid outlines are merged. + */ + processed_outlines = applyMergeProcess(processed_outlines, debug); + + /** + * Step2: Fill gap process. + * Create and add new shift line to avoid outline in order to fill gaps between avoid shift line + * and middle shift lines, return shift line and middle shift lines. + */ + processed_outlines = applyFillGapProcess(processed_outlines, debug); + + /** + * Step3: Convert to AvoidLineArray from AvoidOutlines. + */ + AvoidLineArray processed_raw_lines = toArray(processed_outlines); /** + * Step4: Combine process. * Use all registered points. For the current points, if the similar one of the current * points are already registered, will not use it. - * TODO(Horibe): enrich this logic to be able to consider the removal of the registered - * shift, because it cannot handle the case like "we don't have to avoid - * the object anymore". */ - raw_shift_lines = utils::avoidance::combineRawShiftLinesWithUniqueCheck( - registered_raw_shift_lines_, raw_shift_lines); - - printShiftLines(raw_shift_lines, "raw_shift_lines"); - printShiftLines(registered_raw_shift_lines_, "registered_raw_shift_lines"); + processed_raw_lines = + applyCombineProcess(processed_raw_lines, registered_raw_shift_lines_, debug); /* + * Step5: Add return shift line. * Add return-to-center shift point from the last shift point, if needed. * If there is no shift points, set return-to center shift from ego. */ - // TODO(Horibe) Here, the return point is calculated considering the prepare distance, - // but there is an issue that sometimes this prepare distance is erased by the trimSimilarGrad, - // and it suddenly tries to return from ego. Then steer rotates aggressively. - // It is temporally solved by changing the threshold of trimSimilarGrad, but it needs to be - // fixed in a proper way. - // Maybe after merge, all shift points before the prepare distance can be deleted. - raw_shift_lines = addReturnShiftLine(raw_shift_lines, debug); + processed_raw_lines = addReturnShiftLine(processed_raw_lines, debug); /* - * Add gap filled shift lines so that merged shift lines connect smoothly. + * Step6: Fill gap process. + * Create and add new shift line to avoid lines. */ - fillShiftLineGap(raw_shift_lines); - raw_shift_lines.insert( - raw_shift_lines.end(), fill_gap_shift_lines.begin(), fill_gap_shift_lines.end()); - debug.step1_filled_shift_line = raw_shift_lines; + return applyFillGapProcess(processed_raw_lines, debug); +} + +AvoidLineArray AvoidanceModule::generateCandidateShiftLine( + const AvoidLineArray & shift_lines, const PathShifter & path_shifter, DebugData & debug) const +{ + AvoidLineArray processed_shift_lines = shift_lines; /** - * On each path point, compute shift length with considering the raw shift points. - * Then create a merged shift points by finding the change point of the gradient of shifting. - * - take maximum shift length if there is duplicate shift point - * - take sum if there are shifts for opposite direction (right and left) - * - shift length is interpolated linearly. - * Note: Because this function just foolishly extracts points, it includes - * insignificant small (useless) shift points, which should be removed in post-process. + * Step1: Merge process. + * Merge positive shift avoid lines and negative shift avoid lines. */ - auto merged_shift_lines = applyMergeProcess(raw_shift_lines, debug); - debug.step2_merged_shift_line = merged_shift_lines; + processed_shift_lines = applyMergeProcess(processed_shift_lines, debug); - /* - * Remove unnecessary shift points - * - Quantize the shift length to reduce the shift point noise - * - Change the shift length to the previous one if the deviation is small. - * - Combine shift points that have almost same gradient - * - Remove unnecessary return shift (back to the center line). + /** + * Step2: Clean up process. + * Remove noisy shift line and concat same gradient shift lines. */ - auto shift_lines = applyTrimProcess(merged_shift_lines, debug); - DEBUG_PRINT("final shift point size = %lu", shift_lines.size()); + processed_shift_lines = applyTrimProcess(processed_shift_lines, debug); - return shift_lines; + /** + * Step3: Extract new shift lines. + * Compare processed shift lines and registered shift lines in order to find new shift lines. + */ + processed_shift_lines = findNewShiftLine(processed_shift_lines, debug); + + /** + * Step4: Validate new shift lines. + * Output new shift lines only when the avoidance path which is generated from them doesn't have + * huge offset from ego. + */ + return isValidShiftLine(processed_shift_lines, path_shifter) ? processed_shift_lines + : AvoidLineArray{}; } void AvoidanceModule::registerRawShiftLines(const AvoidLineArray & future) @@ -787,7 +844,7 @@ void AvoidanceModule::registerRawShiftLines(const AvoidLineArray & future) DEBUG_PRINT("registered object size: %lu -> %lu", old_size, registered_raw_shift_lines_.size()); } -AvoidLineArray AvoidanceModule::calcRawShiftLinesFromObjects( +AvoidOutlines AvoidanceModule::generateAvoidOutline( AvoidancePlanningData & data, [[maybe_unused]] DebugData & debug) const { // To be consistent with changes in the ego position, the current shift length is considered. @@ -907,7 +964,7 @@ AvoidLineArray AvoidanceModule::calcRawShiftLinesFromObjects( return s.start_longitudinal > 0.0 && s.start_longitudinal < s.end_longitudinal; }; - AvoidLineArray avoid_lines; + AvoidOutlines outlines; for (auto & o : data.target_objects) { if (!o.avoid_margin) { o.reason = AvoidanceDebugFactor::INSUFFICIENT_LATERAL_MARGIN; @@ -966,6 +1023,7 @@ AvoidLineArray AvoidanceModule::calcRawShiftLinesFromObjects( al_avoid.end_longitudinal = o.longitudinal - offset; al_avoid.id = getOriginalShiftLineUniqueId(); al_avoid.object = o; + al_avoid.object_on_right = utils::avoidance::isOnRight(o); } AvoidLine al_return; @@ -985,11 +1043,11 @@ AvoidLineArray AvoidanceModule::calcRawShiftLinesFromObjects( o.longitudinal + offset + std::min(feasible_return_distance, return_remaining_distance); al_return.id = getOriginalShiftLineUniqueId(); al_return.object = o; + al_return.object_on_right = utils::avoidance::isOnRight(o); } if (is_valid_shift_line(al_avoid) && is_valid_shift_line(al_return)) { - avoid_lines.push_back(al_avoid); - avoid_lines.push_back(al_return); + outlines.emplace_back(al_avoid, al_return); } else { o.reason = "InvalidShiftLine"; continue; @@ -1020,9 +1078,11 @@ AvoidLineArray AvoidanceModule::calcRawShiftLinesFromObjects( debug_avoidance_initializer_for_shift_line_time_ = clock_->now(); } - utils::avoidance::fillAdditionalInfoFromLongitudinal(data, avoid_lines); + utils::avoidance::fillAdditionalInfoFromLongitudinal(data, outlines); + + debug.step1_current_raw_shift_line = toArray(outlines); - return avoid_lines; + return outlines; } void AvoidanceModule::generateTotalShiftLine( @@ -1208,99 +1268,183 @@ AvoidLineArray AvoidanceModule::extractShiftLinesFromLine(ShiftLineData & shift_ return merged_avoid_lines; } -AvoidLineArray AvoidanceModule::applyFillGapProcess( - const AvoidLineArray & shift_lines, [[maybe_unused]] DebugData & debug) const +AvoidOutlines AvoidanceModule::applyMergeProcess( + const AvoidOutlines & outlines, DebugData & debug) const { - AvoidLineArray ret{}; + AvoidOutlines ret{}; - if (shift_lines.empty()) { - return ret; + if (outlines.size() < 2) { + return outlines; } - const auto calc_gap_shift_line = [&](const auto & line1, const auto & line2) { - AvoidLine gap_filled_line{}; - gap_filled_line.start_shift_length = line1.end_shift_length; - gap_filled_line.start_longitudinal = line1.end_longitudinal; - gap_filled_line.end_shift_length = line2.start_shift_length; - gap_filled_line.end_longitudinal = line2.start_longitudinal; - gap_filled_line.id = getOriginalShiftLineUniqueId(); + const auto no_conflict = [](const auto & line1, const auto & line2) { + return line1.end_idx < line2.start_idx || line2.end_idx < line1.start_idx; + }; - return gap_filled_line; + const auto same_side_shift = [](const auto & line1, const auto & line2) { + return line1.object_on_right == line2.object_on_right; }; - // fill gap among shift lines. - for (size_t i = 0; i < shift_lines.size() - 1; i += 2) { - if (shift_lines.at(i).end_longitudinal > shift_lines.at(i + 1).start_longitudinal) { + const auto within = [](const auto & line, const size_t idx) { + return line.start_idx < idx && idx < line.end_idx; + }; + + ret.push_back(outlines.front()); + + for (size_t i = 1; i < outlines.size(); i++) { + auto & last_outline = ret.back(); + auto & next_outline = outlines.at(i); + + const auto & return_line = last_outline.return_line; + const auto & avoid_line = next_outline.avoid_line; + + if (no_conflict(return_line, avoid_line)) { + ret.push_back(outlines.at(i)); + continue; + } + + const auto merged_shift_line = merge(return_line, avoid_line, getOriginalShiftLineUniqueId()); + + if (!isComfortable(AvoidLineArray{merged_shift_line})) { + ret.push_back(outlines.at(i)); + continue; + } + + if (same_side_shift(return_line, avoid_line)) { + last_outline.middle_lines.push_back(merged_shift_line); + last_outline.return_line = next_outline.return_line; + debug.step1_merged_shift_line.push_back(merged_shift_line); + continue; + } + + if (within(return_line, avoid_line.end_idx) && within(avoid_line, return_line.start_idx)) { + last_outline.middle_lines.push_back(merged_shift_line); + last_outline.return_line = next_outline.return_line; + debug.step1_merged_shift_line.push_back(merged_shift_line); + continue; + } + + if (within(return_line, avoid_line.start_idx) && within(avoid_line, return_line.end_idx)) { + last_outline.middle_lines.push_back(merged_shift_line); + last_outline.return_line = next_outline.return_line; + debug.step1_merged_shift_line.push_back(merged_shift_line); continue; } - ret.push_back(calc_gap_shift_line(shift_lines.at(i), shift_lines.at(i + 1))); } utils::avoidance::fillAdditionalInfoFromLongitudinal(avoid_data_, ret); + utils::avoidance::fillAdditionalInfoFromLongitudinal(avoid_data_, debug.step1_merged_shift_line); return ret; } -void AvoidanceModule::fillShiftLineGap(AvoidLineArray & shift_lines) const +AvoidOutlines AvoidanceModule::applyFillGapProcess( + const AvoidOutlines & outlines, DebugData & debug) const { - using utils::avoidance::setEndData; + AvoidOutlines ret = outlines; - if (shift_lines.empty()) { - return; - } + for (auto & outline : ret) { + if (outline.middle_lines.empty()) { + const auto new_line = + fill(outline.avoid_line, outline.return_line, getOriginalShiftLineUniqueId()); + outline.middle_lines.push_back(new_line); + debug.step1_filled_shift_line.push_back(new_line); + } - const auto & data = avoid_data_; + helper_.alignShiftLinesOrder(outline.middle_lines, false); + + if (outline.avoid_line.end_longitudinal < outline.middle_lines.front().start_longitudinal) { + const auto new_line = + fill(outline.avoid_line, outline.middle_lines.front(), getOriginalShiftLineUniqueId()); + outline.middle_lines.push_back(new_line); + debug.step1_filled_shift_line.push_back(new_line); + } - helper_.alignShiftLinesOrder(shift_lines, false); + helper_.alignShiftLinesOrder(outline.middle_lines, false); - const auto fill_gap = [&shift_lines, this](const auto & front_line, const auto & back_line) { - const auto has_gap = back_line.start_longitudinal - front_line.end_longitudinal > 0.0; - if (!has_gap) { - return; + if (outline.middle_lines.back().end_longitudinal < outline.return_line.start_longitudinal) { + const auto new_line = + fill(outline.middle_lines.back(), outline.return_line, getOriginalShiftLineUniqueId()); + outline.middle_lines.push_back(new_line); + debug.step1_filled_shift_line.push_back(new_line); } - AvoidLine new_line{}; - new_line.start_shift_length = front_line.end_shift_length; - new_line.start_longitudinal = front_line.end_longitudinal; - new_line.end_shift_length = back_line.start_shift_length; - new_line.end_longitudinal = back_line.start_longitudinal; - new_line.id = getOriginalShiftLineUniqueId(); + helper_.alignShiftLinesOrder(outline.middle_lines, false); + } - shift_lines.push_back(new_line); - }; + utils::avoidance::fillAdditionalInfoFromLongitudinal(avoid_data_, ret); + utils::avoidance::fillAdditionalInfoFromLongitudinal(avoid_data_, debug.step1_filled_shift_line); + + return ret; +} + +AvoidLineArray AvoidanceModule::applyFillGapProcess( + const AvoidLineArray & shift_lines, DebugData & debug) const +{ + AvoidLineArray sorted = shift_lines; + + helper_.alignShiftLinesOrder(sorted, false); + + AvoidLineArray ret = sorted; + + if (shift_lines.empty()) { + return ret; + } + + const auto & data = avoid_data_; // fill gap between ego and nearest shift line. - { + if (sorted.front().start_longitudinal > 0.0) { AvoidLine ego_line{}; - setEndData( + utils::avoidance::setEndData( ego_line, helper_.getEgoLinearShift(), data.reference_pose, data.ego_closest_path_index, 0.0); - fill_gap(ego_line, shift_lines.front()); + const auto new_line = fill(ego_line, sorted.front(), getOriginalShiftLineUniqueId()); + ret.push_back(new_line); + debug.step1_front_shift_line.push_back(new_line); } + helper_.alignShiftLinesOrder(sorted, false); + // fill gap among shift lines. - for (size_t i = 0; i < shift_lines.size() - 1; ++i) { - fill_gap(shift_lines.at(i), shift_lines.at(i + 1)); + for (size_t i = 0; i < sorted.size() - 1; ++i) { + if (sorted.at(i + 1).start_longitudinal < sorted.at(i).end_longitudinal) { + continue; + } + + const auto new_line = fill(sorted.at(i), sorted.at(i + 1), getOriginalShiftLineUniqueId()); + ret.push_back(new_line); + debug.step1_front_shift_line.push_back(new_line); } - utils::avoidance::fillAdditionalInfoFromLongitudinal(data, shift_lines); + helper_.alignShiftLinesOrder(ret, false); + + utils::avoidance::fillAdditionalInfoFromLongitudinal(avoid_data_, ret); + utils::avoidance::fillAdditionalInfoFromLongitudinal(avoid_data_, debug.step1_front_shift_line); - helper_.alignShiftLinesOrder(shift_lines, false); + return ret; +} + +AvoidLineArray AvoidanceModule::applyCombineProcess( + const AvoidLineArray & shift_lines, const AvoidLineArray & registered_lines, + [[maybe_unused]] DebugData & debug) const +{ + return utils::avoidance::combineRawShiftLinesWithUniqueCheck(registered_lines, shift_lines); } AvoidLineArray AvoidanceModule::applyMergeProcess( - const AvoidLineArray & raw_shift_lines, DebugData & debug) const + const AvoidLineArray & shift_lines, DebugData & debug) const { - // Generate shift line by merging raw_shift_lines. + // Generate shift line by merging shift_lines. ShiftLineData shift_line_data; - generateTotalShiftLine(raw_shift_lines, shift_line_data); + generateTotalShiftLine(shift_lines, shift_line_data); // Re-generate shift points by detecting gradient-change point of the shift line. auto merged_shift_lines = extractShiftLinesFromLine(shift_line_data); // set parent id for (auto & al : merged_shift_lines) { - al.parent_ids = utils::avoidance::calcParentIds(raw_shift_lines, al); + al.parent_ids = utils::avoidance::calcParentIds(shift_lines, al); } // sort by distance from ego. @@ -1315,6 +1459,7 @@ AvoidLineArray AvoidanceModule::applyMergeProcess( debug.neg_shift_grad = shift_line_data.neg_shift_line_grad; debug.total_forward_grad = shift_line_data.forward_grad; debug.total_backward_grad = shift_line_data.backward_grad; + debug.step2_merged_shift_line = merged_shift_lines; } return merged_shift_lines; @@ -1368,14 +1513,6 @@ AvoidLineArray AvoidanceModule::applyTrimProcess( debug.step3_grad_filtered_second = sl_array_trimmed; } - // - trimTooSharpShift - // Check if it is not too sharp for the return-to-center shift point. - // If the shift is sharp, it is combined with the next shift point until it gets non-sharp. - { - const auto THRESHOLD = parameters_->sharp_shift_filter_threshold; - applySharpShiftFilter(sl_array_trimmed, THRESHOLD); - } - // - Combine avoid points that have almost same gradient (again) { const auto THRESHOLD = parameters_->same_grad_filter_3_threshold; @@ -1483,169 +1620,37 @@ void AvoidanceModule::applySimilarGradFilter( DEBUG_PRINT("size %lu -> %lu", input.size(), avoid_lines.size()); } -void AvoidanceModule::applySharpShiftFilter( - AvoidLineArray & shift_lines, const double threshold) const -{ - AvoidLineArray shift_lines_orig = shift_lines; - shift_lines.clear(); - - const auto isZero = [](double v) { return std::abs(v) < 0.01; }; - - // check if the shift point is positive (avoiding) shift - const auto isPositive = [&](const auto & sl) { - constexpr auto POSITIVE_SHIFT_THR = 0.1; - return std::abs(sl.end_shift_length) - std::abs(sl.start_shift_length) > POSITIVE_SHIFT_THR; - }; - - // check if the shift point is negative (returning) shift - const auto isNegative = [&](const auto & sl) { - constexpr auto NEGATIVE_SHIFT_THR = -0.1; - return std::abs(sl.end_shift_length) - std::abs(sl.start_shift_length) < NEGATIVE_SHIFT_THR; - }; - - // combine two shift points. Be careful the order of "now" and "next". - const auto combineShiftLine = [this](const auto & sl_next, const auto & sl_now) { - auto sl_modified = sl_now; - utils::avoidance::setEndData( - sl_modified, sl_next.end_shift_length, sl_next.end, sl_next.end_idx, - sl_next.end_longitudinal); - sl_modified.parent_ids = - utils::avoidance::concatParentIds(sl_modified.parent_ids, sl_now.parent_ids); - return sl_modified; - }; - - // Check if the merged shift has a conflict with the original shifts. - const auto hasViolation = [&threshold](const auto & combined, const auto & combined_src) { - for (const auto & sl : combined_src) { - const auto combined_shift = - utils::avoidance::lerpShiftLengthOnArc(sl.end_longitudinal, combined); - if (sl.end_shift_length < -0.01 && combined_shift > sl.end_shift_length + threshold) { - return true; - } - if (sl.end_shift_length > 0.01 && combined_shift < sl.end_shift_length - threshold) { - return true; - } - } - return false; - }; - - // check for all shift points - for (size_t i = 0; i < shift_lines_orig.size(); ++i) { - auto sl_now = shift_lines_orig.at(i); - sl_now.start_shift_length = - shift_lines.empty() ? helper_.getEgoLinearShift() : shift_lines.back().end_shift_length; - - if (sl_now.end_shift_length * sl_now.start_shift_length < -0.01) { - DEBUG_PRINT("i = %lu, This is avoid shift for opposite direction. take this one", i); - continue; - } - - // Do nothing for non-reduce shift point - if (!isNegative(sl_now)) { - shift_lines.push_back(sl_now); - DEBUG_PRINT( - "i = %lu, positive shift. take this one. sl_now.length * sl_now.start_length = %f", i, - sl_now.end_shift_length * sl_now.start_shift_length); - continue; - } - - // The last point is out of target of this function. - if (i == shift_lines_orig.size() - 1) { - shift_lines.push_back(sl_now); - DEBUG_PRINT("i = %lu, last shift. take this one.", i); - continue; - } - - // ----------------------------------------------------------------------- - // ------------ From here, the shift point is "negative" ----------------- - // ----------------------------------------------------------------------- - - // if next shift is negative, combine them. loop until combined shift line - // exceeds merged shift point. - DEBUG_PRINT("i = %lu, found negative dist. search.", i); - { - auto sl_combined = sl_now; - auto sl_combined_prev = sl_combined; - AvoidLineArray sl_combined_array{sl_now}; - size_t j = i + 1; - for (; i < shift_lines_orig.size(); ++j) { - const auto sl_combined = combineShiftLine(shift_lines_orig.at(j), sl_now); - - { - std::stringstream ss; - ss << "i = " << i << ", j = " << j << ": sl_combined = " << toStrInfo(sl_combined); - DEBUG_PRINT("%s", ss.str().c_str()); - } - - // it gets positive. Finish merging. - if (isPositive(sl_combined)) { - shift_lines.push_back(sl_combined); - DEBUG_PRINT("reach positive."); - break; - } - - // Still negative, but it violates the original shift points. - // Finish with the previous merge result. - if (hasViolation(sl_combined, sl_combined_array)) { - shift_lines.push_back(sl_combined_prev); - DEBUG_PRINT("violation found."); - --j; - break; - } - - // Still negative, but it has an enough long distance. Finish merging. - const auto nominal_distance = - helper_.getMaxAvoidanceDistance(sl_combined.getRelativeLength()); - const auto long_distance = - isZero(sl_combined.end_shift_length) ? nominal_distance : nominal_distance * 5.0; - if (sl_combined.getRelativeLongitudinal() > long_distance) { - shift_lines.push_back(sl_combined); - DEBUG_PRINT("still negative, but long enough. Threshold = %f", long_distance); - break; - } - - // It reaches the last point. Still the shift is sharp, but merge with the current result. - if (j == shift_lines_orig.size() - 1) { - shift_lines.push_back(sl_combined); - DEBUG_PRINT("reach end point."); - break; - } - - // Still negative shift, and the distance is not enough. Search next. - sl_combined_prev = sl_combined; - sl_combined_array.push_back(shift_lines_orig.at(j)); - } - i = j; - continue; - } - } - - helper_.alignShiftLinesOrder(shift_lines); -} - AvoidLineArray AvoidanceModule::addReturnShiftLine( const AvoidLineArray & shift_lines, DebugData & debug) const { + AvoidLineArray ret = shift_lines; + constexpr double ep = 1.0e-3; const auto & data = avoid_data_; - const bool has_candidate_point = !shift_lines.empty(); + const bool has_candidate_point = !ret.empty(); const bool has_registered_point = !path_shifter_.getShiftLines().empty(); + const auto exist_unavoidable_object = std::any_of( + data.target_objects.begin(), data.target_objects.end(), + [](const auto & o) { return !o.is_avoidable && o.longitudinal > 0.0; }); + + if (exist_unavoidable_object) { + return ret; + } + // If the return-to-center shift points are already registered, do nothing. if (!has_registered_point && std::fabs(getCurrentBaseShift()) < ep) { DEBUG_PRINT("No shift points, not base offset. Do not have to add return-shift."); - return shift_lines; + return ret; } constexpr double RETURN_SHIFT_THRESHOLD = 0.1; DEBUG_PRINT("registered last shift = %f", path_shifter_.getLastShiftLength()); if (std::abs(path_shifter_.getLastShiftLength()) < RETURN_SHIFT_THRESHOLD) { DEBUG_PRINT("Return shift is already registered. do nothing."); - return shift_lines; + return ret; } - AvoidLineArray ret = shift_lines; - // From here, the return-to-center is not registered. But perhaps the candidate is // already generated. @@ -2189,7 +2194,7 @@ void AvoidanceModule::addNewShiftLines( continue; } - if (sl.end_idx >= new_shift_end_idx) { + if (sl.end_idx > new_shift_end_idx) { if ( sl.end_shift_length > -1e-3 && new_shift_length > -1e-3 && sl.end_shift_length < new_shift_length) { @@ -2221,18 +2226,19 @@ void AvoidanceModule::addNewShiftLines( path_shifter.setLateralAccelerationLimit(helper_.getLateralMaxAccelLimit()); } -AvoidLineArray AvoidanceModule::findNewShiftLine(const AvoidLineArray & candidates) const +AvoidLineArray AvoidanceModule::findNewShiftLine( + const AvoidLineArray & shift_lines, DebugData & debug) const { - if (candidates.empty()) { + if (shift_lines.empty()) { return {}; } // add small shift lines. const auto add_straight_shift = [&, this](auto & subsequent, bool has_large_shift, const size_t start_idx) { - for (size_t i = start_idx; i < candidates.size(); ++i) { + for (size_t i = start_idx; i < shift_lines.size(); ++i) { if ( - std::abs(candidates.at(i).getRelativeLength()) > + std::abs(shift_lines.at(i).getRelativeLength()) > parameters_->lateral_small_shift_threshold) { if (has_large_shift) { return; @@ -2241,37 +2247,38 @@ AvoidLineArray AvoidanceModule::findNewShiftLine(const AvoidLineArray & candidat has_large_shift = true; } - if (!isComfortable(AvoidLineArray{candidates.at(i)})) { + if (!isComfortable(AvoidLineArray{shift_lines.at(i)})) { return; } - subsequent.push_back(candidates.at(i)); + subsequent.push_back(shift_lines.at(i)); } }; // get subsequent shift lines. const auto get_subsequent_shift = [&, this](size_t i) { - AvoidLineArray subsequent{candidates.at(i)}; + AvoidLineArray subsequent{shift_lines.at(i)}; if (!isComfortable(subsequent)) { return subsequent; } - if (candidates.size() == i + 1) { + if (shift_lines.size() == i + 1) { return subsequent; } - if (!isComfortable(AvoidLineArray{candidates.at(i + 1)})) { + if (!isComfortable(AvoidLineArray{shift_lines.at(i + 1)})) { return subsequent; } if ( - std::abs(candidates.at(i).getRelativeLength()) < parameters_->lateral_small_shift_threshold) { + std::abs(shift_lines.at(i).getRelativeLength()) < + parameters_->lateral_small_shift_threshold) { const auto has_large_shift = - candidates.at(i + 1).getRelativeLength() > parameters_->lateral_small_shift_threshold; + shift_lines.at(i + 1).getRelativeLength() > parameters_->lateral_small_shift_threshold; // candidate.at(i) is small length shift line. add large length shift line. - subsequent.push_back(candidates.at(i + 1)); + subsequent.push_back(shift_lines.at(i + 1)); add_straight_shift(subsequent, has_large_shift, i + 2); } else { // candidate.at(i) is large length shift line. add small length shift lines. @@ -2286,18 +2293,19 @@ AvoidLineArray AvoidanceModule::findNewShiftLine(const AvoidLineArray & candidat return std::abs(helper_.getRelativeShiftToPath(s)) < parameters_->lateral_execution_threshold; }; - for (size_t i = 0; i < candidates.size(); ++i) { - const auto & candidate = candidates.at(i); + for (size_t i = 0; i < shift_lines.size(); ++i) { + const auto & candidate = shift_lines.at(i); // new shift points must exist in front of Ego // this value should be larger than -eps consider path shifter calculation error. - const double eps = 0.01; - if (candidate.start_longitudinal < -eps) { + if (candidate.start_idx < avoid_data_.ego_closest_path_index) { break; } if (!is_ignore_shift(candidate)) { - return get_subsequent_shift(i); + const auto new_shift_lines = get_subsequent_shift(i); + debug.step4_new_shift_line = new_shift_lines; + return new_shift_lines; } } @@ -2320,7 +2328,6 @@ bool AvoidanceModule::isValidShiftLine( shifter_for_validate.generate(&proposed_shift_path); debug_data_.proposed_spline_shift = proposed_shift_path.shift_length; - debug_data_.step4_valid_shift_line = shift_lines; // check offset between new shift path and ego position. { @@ -2653,7 +2660,7 @@ void AvoidanceModule::updateDebugMarker( { addShiftLine(shifter.getShiftLines(), "step4_old_shift_line", 1.0, 1.0, 0.0, 0.3); addAvoidLine(debug.step4_new_shift_line, "step4_new_shift_line", 1.0, 0.0, 0.0, 0.3); - addAvoidLine(debug.step4_valid_shift_line, "step4_valid_shift_line", 1.0, 0.0, 0.0, 0.3); + addAvoidLine(data.unapproved_new_sl, "step4_valid_shift_line", 1.0, 0.0, 0.0, 0.3); } // safety check diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 331c8912fddf6..2c2373d9fc644 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -382,20 +382,7 @@ std::vector calcParentIds(const AvoidLineArray & lines1, const AvoidLine continue; } - // Id the shift is overlapped, insert the shift point. Additionally, the shift which refers - // to the same object id (created by the same object) will be set. - // - // Why? : think that there are two shifts, avoiding and . - // If you register only the avoiding shift, the return-to-center shift will not be generated - // when you get too close to or over the obstacle. The return-shift can be handled with - // addReturnShift(), but it maybe reasonable to register the return-to-center shift for the - // object at the same time as registering the avoidance shift to remove the complexity of the - // addReturnShift(). - for (const auto & al_local : lines1) { - if (al_local.object.object.object_id == al.object.object.object_id) { - ids.insert(al_local.id); - } - } + ids.insert(al.id); } return std::vector(ids.begin(), ids.end()); } @@ -1408,6 +1395,30 @@ void fillAdditionalInfoFromPoint(const AvoidancePlanningData & data, AvoidLineAr } } +void fillAdditionalInfoFromLongitudinal(const AvoidancePlanningData & data, AvoidLine & line) +{ + const auto & path = data.reference_path; + const auto & arc = data.arclength_from_ego; + + line.start_idx = findPathIndexFromArclength(arc, line.start_longitudinal); + line.start = path.points.at(line.start_idx).point.pose; + line.end_idx = findPathIndexFromArclength(arc, line.end_longitudinal); + line.end = path.points.at(line.end_idx).point.pose; +} + +void fillAdditionalInfoFromLongitudinal( + const AvoidancePlanningData & data, AvoidOutlines & outlines) +{ + for (auto & outline : outlines) { + fillAdditionalInfoFromLongitudinal(data, outline.avoid_line); + fillAdditionalInfoFromLongitudinal(data, outline.return_line); + + std::for_each(outline.middle_lines.begin(), outline.middle_lines.end(), [&](auto & line) { + fillAdditionalInfoFromLongitudinal(data, line); + }); + } +} + void fillAdditionalInfoFromLongitudinal(const AvoidancePlanningData & data, AvoidLineArray & lines) { const auto & path = data.reference_path; From 2067a6c3cb7e54353676f3a1b6345e2c95d39681 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Tue, 19 Sep 2023 17:25:23 +0900 Subject: [PATCH 235/547] chore(build): simplify common/tier4_autoware_utils header (#5008) Signed-off-by: Mamoru Sobue --- .../motion_utils/trajectory/trajectory.hpp | 3 +- common/tier4_autoware_utils/CMakeLists.txt | 3 + .../geometry/boost_geometry.hpp | 1 - .../geometry/boost_polygon_utils.hpp | 3 - .../geometry/geometry.hpp | 361 +++-------------- .../geometry/pose_deviation.hpp | 61 +-- .../ros/marker_helper.hpp | 51 +-- common/tier4_autoware_utils/package.xml | 1 + .../src/geometry/boost_polygon_utils.cpp | 2 + .../src/geometry/geometry.cpp | 381 ++++++++++++++++++ .../src/geometry/pose_deviation.cpp | 85 ++++ .../src/ros/marker_helper.cpp | 71 ++++ .../test/src/geometry/test_pose_deviation.cpp | 1 + .../autonomous_emergency_braking/src/node.cpp | 4 + .../dynamic_avoidance_module.cpp | 3 +- .../src/utils/avoidance/utils.cpp | 5 + .../path_safety_checker/objects_filtering.cpp | 2 + .../path_safety_checker/safety_check.cpp | 4 + .../behavior_path_planner/src/utils/utils.cpp | 2 + .../src/planner_interface.cpp | 2 + .../src/planner_utils.cpp | 2 + 21 files changed, 645 insertions(+), 403 deletions(-) create mode 100644 common/tier4_autoware_utils/src/geometry/geometry.cpp create mode 100644 common/tier4_autoware_utils/src/geometry/pose_deviation.cpp create mode 100644 common/tier4_autoware_utils/src/ros/marker_helper.cpp diff --git a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp index c65a7eae189e3..b9cf012492a55 100644 --- a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp @@ -20,6 +20,8 @@ #include "tier4_autoware_utils/geometry/pose_deviation.hpp" #include "tier4_autoware_utils/math/constants.hpp" +#include + #include #include @@ -27,7 +29,6 @@ #include #include #include - namespace motion_utils { diff --git a/common/tier4_autoware_utils/CMakeLists.txt b/common/tier4_autoware_utils/CMakeLists.txt index 071b55177a581..212097d7785e0 100644 --- a/common/tier4_autoware_utils/CMakeLists.txt +++ b/common/tier4_autoware_utils/CMakeLists.txt @@ -8,10 +8,13 @@ find_package(Boost REQUIRED) ament_auto_add_library(tier4_autoware_utils SHARED src/tier4_autoware_utils.cpp + src/geometry/geometry.cpp + src/geometry/pose_deviation.cpp src/geometry/boost_polygon_utils.cpp src/math/sin_table.cpp src/math/trigonometry.cpp src/ros/msg_operation.cpp + src/ros/marker_helper.cpp ) if(BUILD_TESTING) diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_geometry.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_geometry.hpp index 91f9ab09a1e08..04bcfbacbbc17 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_geometry.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_geometry.hpp @@ -21,7 +21,6 @@ #define EIGEN_MPL2_ONLY #include -#include #include diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_polygon_utils.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_polygon_utils.hpp index 46cbd1f141e87..5d9001affa308 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_polygon_utils.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/boost_polygon_utils.hpp @@ -22,9 +22,6 @@ #include #include -#include -#include - #include namespace tier4_autoware_utils diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp index 8c3bcd966cc7a..5d73367b3af87 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp @@ -15,18 +15,17 @@ #ifndef TIER4_AUTOWARE_UTILS__GEOMETRY__GEOMETRY_HPP_ #define TIER4_AUTOWARE_UTILS__GEOMETRY__GEOMETRY_HPP_ -#include -#include -#include - -#define EIGEN_MPL2_ONLY #include "tier4_autoware_utils/geometry/boost_geometry.hpp" #include "tier4_autoware_utils/math/constants.hpp" #include "tier4_autoware_utils/math/normalization.hpp" #include "tier4_autoware_utils/ros/msg_covariance.hpp" +#include +#include +#include + +#define EIGEN_MPL2_ONLY #include -#include #include #include @@ -51,14 +50,7 @@ // after they are implemented in ros2 geometry2. namespace tf2 { -inline void fromMsg(const geometry_msgs::msg::PoseStamped & msg, tf2::Stamped & out) -{ - out.stamp_ = tf2_ros::fromMsg(msg.header.stamp); - out.frame_id_ = msg.header.frame_id; - tf2::Transform tmp; - fromMsg(msg.pose, tmp); - out.setData(tmp); -} +void fromMsg(const geometry_msgs::msg::PoseStamped & msg, tf2::Stamped & out); #ifdef ROS_DISTRO_GALACTIC // Remove after this commit is released // https://github.com/ros2/geometry2/commit/e9da371d81e388a589540357c050e262442f1b4a @@ -274,66 +266,25 @@ inline geometry_msgs::msg::Point createPoint(const double x, const double y, con return p; } -inline geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::Quaternion & quat) -{ - geometry_msgs::msg::Vector3 rpy; - tf2::Quaternion q(quat.x, quat.y, quat.z, quat.w); - tf2::Matrix3x3(q).getRPY(rpy.x, rpy.y, rpy.z); - return rpy; -} +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::Quaternion & quat); -inline geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::Pose & pose) -{ - return getRPY(pose.orientation); -} +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::Pose & pose); -inline geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::PoseStamped & pose) -{ - return getRPY(pose.pose); -} +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::PoseStamped & pose); -inline geometry_msgs::msg::Vector3 getRPY( - const geometry_msgs::msg::PoseWithCovarianceStamped & pose) -{ - return getRPY(pose.pose.pose); -} +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::PoseWithCovarianceStamped & pose); -inline geometry_msgs::msg::Quaternion createQuaternion( - const double x, const double y, const double z, const double w) -{ - geometry_msgs::msg::Quaternion q; - q.x = x; - q.y = y; - q.z = z; - q.w = w; - return q; -} +geometry_msgs::msg::Quaternion createQuaternion( + const double x, const double y, const double z, const double w); -inline geometry_msgs::msg::Vector3 createTranslation(const double x, const double y, const double z) -{ - geometry_msgs::msg::Vector3 v; - v.x = x; - v.y = y; - v.z = z; - return v; -} +geometry_msgs::msg::Vector3 createTranslation(const double x, const double y, const double z); // Revival of tf::createQuaternionFromRPY // https://answers.ros.org/question/304397/recommended-way-to-construct-quaternion-from-rollpitchyaw-with-tf2/ -inline geometry_msgs::msg::Quaternion createQuaternionFromRPY( - const double roll, const double pitch, const double yaw) -{ - tf2::Quaternion q; - q.setRPY(roll, pitch, yaw); - return tf2::toMsg(q); -} +geometry_msgs::msg::Quaternion createQuaternionFromRPY( + const double roll, const double pitch, const double yaw); -inline geometry_msgs::msg::Quaternion createQuaternionFromYaw(const double yaw) -{ - tf2::Quaternion q; - q.setRPY(0, 0, yaw); - return tf2::toMsg(q); -} +geometry_msgs::msg::Quaternion createQuaternionFromYaw(const double yaw); template double calcDistance2d(const Point1 & point1, const Point2 & point2) @@ -371,13 +322,8 @@ double calcDistance3d(const Point1 & point1, const Point2 & point2) * @param p_to target point * @return -pi/2 <= elevation angle <= pi/2 */ -inline double calcElevationAngle( - const geometry_msgs::msg::Point & p_from, const geometry_msgs::msg::Point & p_to) -{ - const double dz = p_to.z - p_from.z; - const double dist_2d = calcDistance2d(p_from, p_to); - return std::atan2(dz, dist_2d); -} +double calcElevationAngle( + const geometry_msgs::msg::Point & p_from, const geometry_msgs::msg::Point & p_to); /** * @brief calculate azimuth angle of two points. @@ -388,52 +334,18 @@ inline double calcElevationAngle( * @param p_to target point * @return -pi < azimuth angle < pi. */ -inline double calcAzimuthAngle( - const geometry_msgs::msg::Point & p_from, const geometry_msgs::msg::Point & p_to) -{ - const double dx = p_to.x - p_from.x; - const double dy = p_to.y - p_from.y; - return std::atan2(dy, dx); -} +double calcAzimuthAngle( + const geometry_msgs::msg::Point & p_from, const geometry_msgs::msg::Point & p_to); -inline geometry_msgs::msg::Pose transform2pose(const geometry_msgs::msg::Transform & transform) -{ - geometry_msgs::msg::Pose pose; - pose.position.x = transform.translation.x; - pose.position.y = transform.translation.y; - pose.position.z = transform.translation.z; - pose.orientation = transform.rotation; - return pose; -} +geometry_msgs::msg::Pose transform2pose(const geometry_msgs::msg::Transform & transform); -inline geometry_msgs::msg::PoseStamped transform2pose( - const geometry_msgs::msg::TransformStamped & transform) -{ - geometry_msgs::msg::PoseStamped pose; - pose.header = transform.header; - pose.pose = transform2pose(transform.transform); - return pose; -} +geometry_msgs::msg::PoseStamped transform2pose( + const geometry_msgs::msg::TransformStamped & transform); -inline geometry_msgs::msg::Transform pose2transform(const geometry_msgs::msg::Pose & pose) -{ - geometry_msgs::msg::Transform transform; - transform.translation.x = pose.position.x; - transform.translation.y = pose.position.y; - transform.translation.z = pose.position.z; - transform.rotation = pose.orientation; - return transform; -} +geometry_msgs::msg::Transform pose2transform(const geometry_msgs::msg::Pose & pose); -inline geometry_msgs::msg::TransformStamped pose2transform( - const geometry_msgs::msg::PoseStamped & pose, const std::string & child_frame_id) -{ - geometry_msgs::msg::TransformStamped transform; - transform.header = pose.header; - transform.transform = pose2transform(pose.pose); - transform.child_frame_id = child_frame_id; - return transform; -} +geometry_msgs::msg::TransformStamped pose2transform( + const geometry_msgs::msg::PoseStamped & pose, const std::string & child_frame_id); template tf2::Vector3 point2tfVector(const Point1 & src, const Point2 & dst) @@ -447,65 +359,18 @@ tf2::Vector3 point2tfVector(const Point1 & src, const Point2 & dst) return tf2::Vector3(dx, dy, dz); } -inline Point3d transformPoint( - const Point3d & point, const geometry_msgs::msg::Transform & transform) -{ - const auto & translation = transform.translation; - const auto & rotation = transform.rotation; - - const Eigen::Translation3d T(translation.x, translation.y, translation.z); - const Eigen::Quaterniond R(rotation.w, rotation.x, rotation.y, rotation.z); - - const Eigen::Vector3d transformed(T * R * point); - - return Point3d{transformed.x(), transformed.y(), transformed.z()}; -} - -inline Point2d transformPoint( - const Point2d & point, const geometry_msgs::msg::Transform & transform) -{ - Point3d point_3d{point.x(), point.y(), 0}; - const auto transformed = transformPoint(point_3d, transform); - return Point2d{transformed.x(), transformed.y()}; -} - -inline Eigen::Vector3d transformPoint( - const Eigen::Vector3d & point, const geometry_msgs::msg::Pose & pose) -{ - geometry_msgs::msg::Transform transform; - transform.translation.x = pose.position.x; - transform.translation.y = pose.position.y; - transform.translation.z = pose.position.z; - transform.rotation = pose.orientation; +Point3d transformPoint(const Point3d & point, const geometry_msgs::msg::Transform & transform); - Point3d p = transformPoint(Point3d(point.x(), point.y(), point.z()), transform); - return Eigen::Vector3d(p.x(), p.y(), p.z()); -} +Point2d transformPoint(const Point2d & point, const geometry_msgs::msg::Transform & transform); -inline geometry_msgs::msg::Point transformPoint( - const geometry_msgs::msg::Point & point, const geometry_msgs::msg::Pose & pose) -{ - const Eigen::Vector3d vec = Eigen::Vector3d(point.x, point.y, point.z); - auto transformed_vec = transformPoint(vec, pose); +Eigen::Vector3d transformPoint( + const Eigen::Vector3d & point, const geometry_msgs::msg::Pose & pose); - geometry_msgs::msg::Point transformed_point; - transformed_point.x = transformed_vec.x(); - transformed_point.y = transformed_vec.y(); - transformed_point.z = transformed_vec.z(); - return transformed_point; -} +geometry_msgs::msg::Point transformPoint( + const geometry_msgs::msg::Point & point, const geometry_msgs::msg::Pose & pose); -inline geometry_msgs::msg::Point32 transformPoint( - const geometry_msgs::msg::Point32 & point32, const geometry_msgs::msg::Pose & pose) -{ - const auto point = - geometry_msgs::build().x(point32.x).y(point32.y).z(point32.z); - const auto transformed_point = tier4_autoware_utils::transformPoint(point, pose); - return geometry_msgs::build() - .x(transformed_point.x) - .y(transformed_point.y) - .z(transformed_point.z); -} +geometry_msgs::msg::Point32 transformPoint( + const geometry_msgs::msg::Point32 & point32, const geometry_msgs::msg::Pose & pose); template T transformVector(const T & points, const geometry_msgs::msg::Transform & transform) @@ -517,110 +382,40 @@ T transformVector(const T & points, const geometry_msgs::msg::Transform & transf return transformed; } -inline geometry_msgs::msg::Pose transformPose( - const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::TransformStamped & transform) -{ - geometry_msgs::msg::Pose transformed_pose; - tf2::doTransform(pose, transformed_pose, transform); - - return transformed_pose; -} - -inline geometry_msgs::msg::Pose transformPose( - const geometry_msgs::msg::Pose & pose, geometry_msgs::msg::Transform & transform) -{ - geometry_msgs::msg::TransformStamped transform_stamped; - transform_stamped.transform = transform; - - return transformPose(pose, transform_stamped); -} - -inline geometry_msgs::msg::Pose transformPose( - const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Pose & pose_transform) -{ - tf2::Transform transform; - tf2::convert(pose_transform, transform); +geometry_msgs::msg::Pose transformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::TransformStamped & transform); - geometry_msgs::msg::TransformStamped transform_msg; - transform_msg.transform = tf2::toMsg(transform); +geometry_msgs::msg::Pose transformPose( + const geometry_msgs::msg::Pose & pose, geometry_msgs::msg::Transform & transform); - return transformPose(pose, transform_msg); -} +geometry_msgs::msg::Pose transformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Pose & pose_transform); // Transform pose in world coordinates to local coordinates -inline geometry_msgs::msg::Pose inverseTransformPose( - const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::TransformStamped & transform) -{ - tf2::Transform tf; - tf2::fromMsg(transform, tf); - geometry_msgs::msg::TransformStamped transform_stamped; - transform_stamped.transform = tf2::toMsg(tf.inverse()); - - return transformPose(pose, transform_stamped); -} +/* +geometry_msgs::msg::Pose inverseTransformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::TransformStamped & transform); +*/ // Transform pose in world coordinates to local coordinates -inline geometry_msgs::msg::Pose inverseTransformPose( - const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Transform & transform) -{ - tf2::Transform tf; - tf2::fromMsg(transform, tf); - geometry_msgs::msg::TransformStamped transform_stamped; - transform_stamped.transform = tf2::toMsg(tf.inverse()); - - return transformPose(pose, transform_stamped); -} +geometry_msgs::msg::Pose inverseTransformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Transform & transform); // Transform pose in world coordinates to local coordinates -inline geometry_msgs::msg::Pose inverseTransformPose( - const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Pose & transform_pose) -{ - tf2::Transform transform; - tf2::convert(transform_pose, transform); - - return inverseTransformPose(pose, tf2::toMsg(transform)); -} +geometry_msgs::msg::Pose inverseTransformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Pose & transform_pose); // Transform point in world coordinates to local coordinates -inline Eigen::Vector3d inverseTransformPoint( - const Eigen::Vector3d & point, const geometry_msgs::msg::Pose & pose) -{ - const Eigen::Quaterniond q( - pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z); - const Eigen::Matrix3d R = q.normalized().toRotationMatrix(); - - const Eigen::Vector3d local_origin(pose.position.x, pose.position.y, pose.position.z); - Eigen::Vector3d local_point = R.transpose() * point - R.transpose() * local_origin; - - return local_point; -} +Eigen::Vector3d inverseTransformPoint( + const Eigen::Vector3d & point, const geometry_msgs::msg::Pose & pose); // Transform point in world coordinates to local coordinates -inline geometry_msgs::msg::Point inverseTransformPoint( - const geometry_msgs::msg::Point & point, const geometry_msgs::msg::Pose & pose) -{ - const Eigen::Vector3d local_vec = - inverseTransformPoint(Eigen::Vector3d(point.x, point.y, point.z), pose); - geometry_msgs::msg::Point local_point; - local_point.x = local_vec.x(); - local_point.y = local_vec.y(); - local_point.z = local_vec.z(); - return local_point; -} +geometry_msgs::msg::Point inverseTransformPoint( + const geometry_msgs::msg::Point & point, const geometry_msgs::msg::Pose & pose); -inline double calcCurvature( +double calcCurvature( const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2, - const geometry_msgs::msg::Point & p3) -{ - // Calculation details are described in the following page - // https://en.wikipedia.org/wiki/Menger_curvature - const double denominator = - calcDistance2d(p1, p2) * calcDistance2d(p2, p3) * calcDistance2d(p3, p1); - if (std::fabs(denominator) < 1e-10) { - throw std::runtime_error("points are too close for curvature calculation."); - } - return 2.0 * ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)) / denominator; -} + const geometry_msgs::msg::Point & p3); template bool isDrivingForward(const Pose1 & src_pose, const Pose2 & dst_pose) @@ -635,20 +430,8 @@ bool isDrivingForward(const Pose1 & src_pose, const Pose2 & dst_pose) * @brief Calculate offset pose. The offset values are defined in the local coordinate of the input * pose. */ -inline geometry_msgs::msg::Pose calcOffsetPose( - const geometry_msgs::msg::Pose & p, const double x, const double y, const double z) -{ - geometry_msgs::msg::Pose pose; - geometry_msgs::msg::Transform transform; - transform.translation = createTranslation(x, y, z); - transform.rotation = createQuaternion(0.0, 0.0, 0.0, 1.0); - tf2::Transform tf_pose; - tf2::Transform tf_offset; - tf2::fromMsg(transform, tf_offset); - tf2::fromMsg(p, tf_pose); - tf2::toMsg(tf_pose * tf_offset, pose); - return pose; -} +geometry_msgs::msg::Pose calcOffsetPose( + const geometry_msgs::msg::Pose & p, const double x, const double y, const double z); /** * @brief Calculate a point by linear interpolation. @@ -759,39 +542,13 @@ inline double calcNorm(const geometry_msgs::msg::Vector3 & v) * @return If all element of covariance is 0, return false. */ // -inline bool isTwistCovarianceValid( - const geometry_msgs::msg::TwistWithCovariance & twist_with_covariance) -{ - for (const auto & c : twist_with_covariance.covariance) { - if (c != 0.0) { - return true; - } - } - return false; -} +bool isTwistCovarianceValid(const geometry_msgs::msg::TwistWithCovariance & twist_with_covariance); // NOTE: much faster than boost::geometry::intersects() -inline std::optional intersect( +std::optional intersect( const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2, - const geometry_msgs::msg::Point & p3, const geometry_msgs::msg::Point & p4) -{ - // calculate intersection point - const double det = (p1.x - p2.x) * (p4.y - p3.y) - (p4.x - p3.x) * (p1.y - p2.y); - if (det == 0.0) { - return std::nullopt; - } + const geometry_msgs::msg::Point & p3, const geometry_msgs::msg::Point & p4); - const double t = ((p4.y - p3.y) * (p4.x - p2.x) + (p3.x - p4.x) * (p4.y - p2.y)) / det; - const double s = ((p2.y - p1.y) * (p4.x - p2.x) + (p1.x - p2.x) * (p4.y - p2.y)) / det; - if (t < 0 || 1 < t || s < 0 || 1 < s) { - return std::nullopt; - } - - geometry_msgs::msg::Point intersect_point; - intersect_point.x = t * p1.x + (1.0 - t) * p2.x; - intersect_point.y = t * p1.y + (1.0 - t) * p2.y; - return intersect_point; -} } // namespace tier4_autoware_utils #endif // TIER4_AUTOWARE_UTILS__GEOMETRY__GEOMETRY_HPP_ diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/pose_deviation.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/pose_deviation.hpp index 32b4ba663fcd7..dfa678eaa07d9 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/pose_deviation.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/pose_deviation.hpp @@ -15,10 +15,8 @@ #ifndef TIER4_AUTOWARE_UTILS__GEOMETRY__POSE_DEVIATION_HPP_ #define TIER4_AUTOWARE_UTILS__GEOMETRY__POSE_DEVIATION_HPP_ -#include "tier4_autoware_utils/geometry/geometry.hpp" -#include "tier4_autoware_utils/math/normalization.hpp" - -#include +#include +#include namespace tier4_autoware_utils { @@ -29,57 +27,18 @@ struct PoseDeviation double yaw{0.0}; }; -inline double calcLateralDeviation( - const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Point & target_point) -{ - const auto & base_point = base_pose.position; - - const auto yaw = tf2::getYaw(base_pose.orientation); - const Eigen::Vector3d base_unit_vec{std::cos(yaw), std::sin(yaw), 0}; - - const auto dx = target_point.x - base_point.x; - const auto dy = target_point.y - base_point.y; - const Eigen::Vector3d diff_vec{dx, dy, 0}; +double calcLateralDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Point & target_point); - const Eigen::Vector3d cross_vec = base_unit_vec.cross(diff_vec); - - return cross_vec.z(); -} - -inline double calcLongitudinalDeviation( - const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Point & target_point) -{ - const auto & base_point = base_pose.position; +double calcLongitudinalDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Point & target_point); - const auto yaw = tf2::getYaw(base_pose.orientation); - const Eigen::Vector3d base_unit_vec{std::cos(yaw), std::sin(yaw), 0}; - - const auto dx = target_point.x - base_point.x; - const auto dy = target_point.y - base_point.y; - const Eigen::Vector3d diff_vec{dx, dy, 0}; - - return base_unit_vec.dot(diff_vec); -} - -inline double calcYawDeviation( - const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Pose & target_pose) -{ - const auto base_yaw = tf2::getYaw(base_pose.orientation); - const auto target_yaw = tf2::getYaw(target_pose.orientation); - return normalizeRadian(target_yaw - base_yaw); -} - -inline PoseDeviation calcPoseDeviation( - const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Pose & target_pose) -{ - PoseDeviation deviation{}; +double calcYawDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Pose & target_pose); - deviation.lateral = calcLateralDeviation(base_pose, target_pose.position); - deviation.longitudinal = calcLongitudinalDeviation(base_pose, target_pose.position); - deviation.yaw = calcYawDeviation(base_pose, target_pose); +PoseDeviation calcPoseDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Pose & target_pose); - return deviation; -} } // namespace tier4_autoware_utils #endif // TIER4_AUTOWARE_UTILS__GEOMETRY__POSE_DEVIATION_HPP_ diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/marker_helper.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/marker_helper.hpp index 78e7c5f9288da..1d72b49a553d1 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/marker_helper.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/marker_helper.hpp @@ -15,7 +15,7 @@ #ifndef TIER4_AUTOWARE_UTILS__ROS__MARKER_HELPER_HPP_ #define TIER4_AUTOWARE_UTILS__ROS__MARKER_HELPER_HPP_ -#include +#include #include @@ -64,56 +64,19 @@ inline std_msgs::msg::ColorRGBA createMarkerColor(float r, float g, float b, flo return color; } -inline visualization_msgs::msg::Marker createDefaultMarker( +visualization_msgs::msg::Marker createDefaultMarker( const std::string & frame_id, const rclcpp::Time & now, const std::string & ns, const int32_t id, const int32_t type, const geometry_msgs::msg::Vector3 & scale, - const std_msgs::msg::ColorRGBA & color) -{ - visualization_msgs::msg::Marker marker; - - marker.header.frame_id = frame_id; - marker.header.stamp = now; - marker.ns = ns; - marker.id = id; - marker.type = type; - marker.action = visualization_msgs::msg::Marker::ADD; - marker.lifetime = rclcpp::Duration::from_seconds(0.5); - - marker.pose.position = createMarkerPosition(0.0, 0.0, 0.0); - marker.pose.orientation = createMarkerOrientation(0.0, 0.0, 0.0, 1.0); - marker.scale = scale; - marker.color = color; - marker.frame_locked = true; + const std_msgs::msg::ColorRGBA & color); - return marker; -} - -inline visualization_msgs::msg::Marker createDeletedDefaultMarker( - const rclcpp::Time & now, const std::string & ns, const int32_t id) -{ - visualization_msgs::msg::Marker marker; +visualization_msgs::msg::Marker createDeletedDefaultMarker( + const rclcpp::Time & now, const std::string & ns, const int32_t id); - marker.header.stamp = now; - marker.ns = ns; - marker.id = id; - marker.action = visualization_msgs::msg::Marker::DELETE; - - return marker; -} - -inline void appendMarkerArray( +void appendMarkerArray( const visualization_msgs::msg::MarkerArray & additional_marker_array, visualization_msgs::msg::MarkerArray * marker_array, - const boost::optional & current_time = {}) -{ - for (const auto & marker : additional_marker_array.markers) { - marker_array->markers.push_back(marker); + const boost::optional & current_time = {}); - if (current_time) { - marker_array->markers.back().header.stamp = current_time.get(); - } - } -} } // namespace tier4_autoware_utils #endif // TIER4_AUTOWARE_UTILS__ROS__MARKER_HELPER_HPP_ diff --git a/common/tier4_autoware_utils/package.xml b/common/tier4_autoware_utils/package.xml index fd9b315f8e4d5..6a6b3812cd69b 100644 --- a/common/tier4_autoware_utils/package.xml +++ b/common/tier4_autoware_utils/package.xml @@ -7,6 +7,7 @@ Takamasa Horibe Takayuki Murooka Yutaka Shimizu + Mamoru Sobue Apache License 2.0 ament_cmake_auto diff --git a/common/tier4_autoware_utils/src/geometry/boost_polygon_utils.cpp b/common/tier4_autoware_utils/src/geometry/boost_polygon_utils.cpp index c212eb269d655..79c9f9937cd4d 100644 --- a/common/tier4_autoware_utils/src/geometry/boost_polygon_utils.cpp +++ b/common/tier4_autoware_utils/src/geometry/boost_polygon_utils.cpp @@ -16,6 +16,8 @@ #include "tier4_autoware_utils/geometry/geometry.hpp" +#include + #include namespace diff --git a/common/tier4_autoware_utils/src/geometry/geometry.cpp b/common/tier4_autoware_utils/src/geometry/geometry.cpp new file mode 100644 index 0000000000000..6048527142018 --- /dev/null +++ b/common/tier4_autoware_utils/src/geometry/geometry.cpp @@ -0,0 +1,381 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tier4_autoware_utils/geometry/geometry.hpp" + +#include + +#include +namespace tf2 +{ +void fromMsg(const geometry_msgs::msg::PoseStamped & msg, tf2::Stamped & out) +{ + out.stamp_ = tf2_ros::fromMsg(msg.header.stamp); + out.frame_id_ = msg.header.frame_id; + tf2::Transform tmp; + fromMsg(msg.pose, tmp); + out.setData(tmp); +} +} // namespace tf2 + +namespace tier4_autoware_utils +{ +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::Quaternion & quat) +{ + geometry_msgs::msg::Vector3 rpy; + tf2::Quaternion q(quat.x, quat.y, quat.z, quat.w); + tf2::Matrix3x3(q).getRPY(rpy.x, rpy.y, rpy.z); + return rpy; +} + +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::Pose & pose) +{ + return getRPY(pose.orientation); +} + +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::PoseStamped & pose) +{ + return getRPY(pose.pose); +} + +geometry_msgs::msg::Vector3 getRPY(const geometry_msgs::msg::PoseWithCovarianceStamped & pose) +{ + return getRPY(pose.pose.pose); +} + +geometry_msgs::msg::Quaternion createQuaternion( + const double x, const double y, const double z, const double w) +{ + geometry_msgs::msg::Quaternion q; + q.x = x; + q.y = y; + q.z = z; + q.w = w; + return q; +} + +geometry_msgs::msg::Vector3 createTranslation(const double x, const double y, const double z) +{ + geometry_msgs::msg::Vector3 v; + v.x = x; + v.y = y; + v.z = z; + return v; +} + +// Revival of tf::createQuaternionFromRPY +// https://answers.ros.org/question/304397/recommended-way-to-construct-quaternion-from-rollpitchyaw-with-tf2/ +geometry_msgs::msg::Quaternion createQuaternionFromRPY( + const double roll, const double pitch, const double yaw) +{ + tf2::Quaternion q; + q.setRPY(roll, pitch, yaw); + return tf2::toMsg(q); +} + +geometry_msgs::msg::Quaternion createQuaternionFromYaw(const double yaw) +{ + tf2::Quaternion q; + q.setRPY(0, 0, yaw); + return tf2::toMsg(q); +} + +double calcElevationAngle( + const geometry_msgs::msg::Point & p_from, const geometry_msgs::msg::Point & p_to) +{ + const double dz = p_to.z - p_from.z; + const double dist_2d = calcDistance2d(p_from, p_to); + return std::atan2(dz, dist_2d); +} + +/** + * @brief calculate azimuth angle of two points. + * @details This function returns the azimuth angle of the position of the two input points + * with respect to the origin of their coordinate system. + * If x and y of the two points are the same, the calculation result will be unstable. + * @param p_from source point + * @param p_to target point + * @return -pi < azimuth angle < pi. + */ +double calcAzimuthAngle( + const geometry_msgs::msg::Point & p_from, const geometry_msgs::msg::Point & p_to) +{ + const double dx = p_to.x - p_from.x; + const double dy = p_to.y - p_from.y; + return std::atan2(dy, dx); +} + +geometry_msgs::msg::Pose transform2pose(const geometry_msgs::msg::Transform & transform) +{ + geometry_msgs::msg::Pose pose; + pose.position.x = transform.translation.x; + pose.position.y = transform.translation.y; + pose.position.z = transform.translation.z; + pose.orientation = transform.rotation; + return pose; +} + +geometry_msgs::msg::PoseStamped transform2pose( + const geometry_msgs::msg::TransformStamped & transform) +{ + geometry_msgs::msg::PoseStamped pose; + pose.header = transform.header; + pose.pose = transform2pose(transform.transform); + return pose; +} + +geometry_msgs::msg::Transform pose2transform(const geometry_msgs::msg::Pose & pose) +{ + geometry_msgs::msg::Transform transform; + transform.translation.x = pose.position.x; + transform.translation.y = pose.position.y; + transform.translation.z = pose.position.z; + transform.rotation = pose.orientation; + return transform; +} + +geometry_msgs::msg::TransformStamped pose2transform( + const geometry_msgs::msg::PoseStamped & pose, const std::string & child_frame_id) +{ + geometry_msgs::msg::TransformStamped transform; + transform.header = pose.header; + transform.transform = pose2transform(pose.pose); + transform.child_frame_id = child_frame_id; + return transform; +} + +Point3d transformPoint(const Point3d & point, const geometry_msgs::msg::Transform & transform) +{ + const auto & translation = transform.translation; + const auto & rotation = transform.rotation; + + const Eigen::Translation3d T(translation.x, translation.y, translation.z); + const Eigen::Quaterniond R(rotation.w, rotation.x, rotation.y, rotation.z); + + const Eigen::Vector3d transformed(T * R * point); + + return Point3d{transformed.x(), transformed.y(), transformed.z()}; +} + +Point2d transformPoint(const Point2d & point, const geometry_msgs::msg::Transform & transform) +{ + Point3d point_3d{point.x(), point.y(), 0}; + const auto transformed = transformPoint(point_3d, transform); + return Point2d{transformed.x(), transformed.y()}; +} + +Eigen::Vector3d transformPoint(const Eigen::Vector3d & point, const geometry_msgs::msg::Pose & pose) +{ + geometry_msgs::msg::Transform transform; + transform.translation.x = pose.position.x; + transform.translation.y = pose.position.y; + transform.translation.z = pose.position.z; + transform.rotation = pose.orientation; + + Point3d p = transformPoint(Point3d(point.x(), point.y(), point.z()), transform); + return Eigen::Vector3d(p.x(), p.y(), p.z()); +} + +geometry_msgs::msg::Point transformPoint( + const geometry_msgs::msg::Point & point, const geometry_msgs::msg::Pose & pose) +{ + const Eigen::Vector3d vec = Eigen::Vector3d(point.x, point.y, point.z); + auto transformed_vec = transformPoint(vec, pose); + + geometry_msgs::msg::Point transformed_point; + transformed_point.x = transformed_vec.x(); + transformed_point.y = transformed_vec.y(); + transformed_point.z = transformed_vec.z(); + return transformed_point; +} + +geometry_msgs::msg::Point32 transformPoint( + const geometry_msgs::msg::Point32 & point32, const geometry_msgs::msg::Pose & pose) +{ + const auto point = + geometry_msgs::build().x(point32.x).y(point32.y).z(point32.z); + const auto transformed_point = tier4_autoware_utils::transformPoint(point, pose); + return geometry_msgs::build() + .x(transformed_point.x) + .y(transformed_point.y) + .z(transformed_point.z); +} + +geometry_msgs::msg::Pose transformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::TransformStamped & transform) +{ + geometry_msgs::msg::Pose transformed_pose; + tf2::doTransform(pose, transformed_pose, transform); + + return transformed_pose; +} + +geometry_msgs::msg::Pose transformPose( + const geometry_msgs::msg::Pose & pose, geometry_msgs::msg::Transform & transform) +{ + geometry_msgs::msg::TransformStamped transform_stamped; + transform_stamped.transform = transform; + + return transformPose(pose, transform_stamped); +} + +geometry_msgs::msg::Pose transformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Pose & pose_transform) +{ + tf2::Transform transform; + tf2::convert(pose_transform, transform); + + geometry_msgs::msg::TransformStamped transform_msg; + transform_msg.transform = tf2::toMsg(transform); + + return transformPose(pose, transform_msg); +} + +// Transform pose in world coordinates to local coordinates +/* +geometry_msgs::msg::Pose inverseTransformPose( +const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::TransformStamped & transform) +{ +tf2::Transform tf; +tf2::fromMsg(transform, tf); +geometry_msgs::msg::TransformStamped transform_stamped; +transform_stamped.transform = tf2::toMsg(tf.inverse()); + +return transformPose(pose, transform_stamped); +} +*/ + +// Transform pose in world coordinates to local coordinates +geometry_msgs::msg::Pose inverseTransformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Transform & transform) +{ + tf2::Transform tf; + tf2::fromMsg(transform, tf); + geometry_msgs::msg::TransformStamped transform_stamped; + transform_stamped.transform = tf2::toMsg(tf.inverse()); + + return transformPose(pose, transform_stamped); +} + +// Transform pose in world coordinates to local coordinates +geometry_msgs::msg::Pose inverseTransformPose( + const geometry_msgs::msg::Pose & pose, const geometry_msgs::msg::Pose & transform_pose) +{ + tf2::Transform transform; + tf2::convert(transform_pose, transform); + + return inverseTransformPose(pose, tf2::toMsg(transform)); +} + +// Transform point in world coordinates to local coordinates +Eigen::Vector3d inverseTransformPoint( + const Eigen::Vector3d & point, const geometry_msgs::msg::Pose & pose) +{ + const Eigen::Quaterniond q( + pose.orientation.w, pose.orientation.x, pose.orientation.y, pose.orientation.z); + const Eigen::Matrix3d R = q.normalized().toRotationMatrix(); + + const Eigen::Vector3d local_origin(pose.position.x, pose.position.y, pose.position.z); + Eigen::Vector3d local_point = R.transpose() * point - R.transpose() * local_origin; + + return local_point; +} + +// Transform point in world coordinates to local coordinates +geometry_msgs::msg::Point inverseTransformPoint( + const geometry_msgs::msg::Point & point, const geometry_msgs::msg::Pose & pose) +{ + const Eigen::Vector3d local_vec = + inverseTransformPoint(Eigen::Vector3d(point.x, point.y, point.z), pose); + geometry_msgs::msg::Point local_point; + local_point.x = local_vec.x(); + local_point.y = local_vec.y(); + local_point.z = local_vec.z(); + return local_point; +} + +double calcCurvature( + const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2, + const geometry_msgs::msg::Point & p3) +{ + // Calculation details are described in the following page + // https://en.wikipedia.org/wiki/Menger_curvature + const double denominator = + calcDistance2d(p1, p2) * calcDistance2d(p2, p3) * calcDistance2d(p3, p1); + if (std::fabs(denominator) < 1e-10) { + throw std::runtime_error("points are too close for curvature calculation."); + } + return 2.0 * ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)) / denominator; +} + +/** + * @brief Calculate offset pose. The offset values are defined in the local coordinate of the input + * pose. + */ +geometry_msgs::msg::Pose calcOffsetPose( + const geometry_msgs::msg::Pose & p, const double x, const double y, const double z) +{ + geometry_msgs::msg::Pose pose; + geometry_msgs::msg::Transform transform; + transform.translation = createTranslation(x, y, z); + transform.rotation = createQuaternion(0.0, 0.0, 0.0, 1.0); + tf2::Transform tf_pose; + tf2::Transform tf_offset; + tf2::fromMsg(transform, tf_offset); + tf2::fromMsg(p, tf_pose); + tf2::toMsg(tf_pose * tf_offset, pose); + return pose; +} + +/** + * @brief Judge whether twist covariance is valid. + * + * @param twist_with_covariance source twist with covariance + * @return If all element of covariance is 0, return false. + */ +// +bool isTwistCovarianceValid(const geometry_msgs::msg::TwistWithCovariance & twist_with_covariance) +{ + for (const auto & c : twist_with_covariance.covariance) { + if (c != 0.0) { + return true; + } + } + return false; +} + +// NOTE: much faster than boost::geometry::intersects() +std::optional intersect( + const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2, + const geometry_msgs::msg::Point & p3, const geometry_msgs::msg::Point & p4) +{ + // calculate intersection point + const double det = (p1.x - p2.x) * (p4.y - p3.y) - (p4.x - p3.x) * (p1.y - p2.y); + if (det == 0.0) { + return std::nullopt; + } + + const double t = ((p4.y - p3.y) * (p4.x - p2.x) + (p3.x - p4.x) * (p4.y - p2.y)) / det; + const double s = ((p2.y - p1.y) * (p4.x - p2.x) + (p1.x - p2.x) * (p4.y - p2.y)) / det; + if (t < 0 || 1 < t || s < 0 || 1 < s) { + return std::nullopt; + } + + geometry_msgs::msg::Point intersect_point; + intersect_point.x = t * p1.x + (1.0 - t) * p2.x; + intersect_point.y = t * p1.y + (1.0 - t) * p2.y; + return intersect_point; +} + +} // namespace tier4_autoware_utils diff --git a/common/tier4_autoware_utils/src/geometry/pose_deviation.cpp b/common/tier4_autoware_utils/src/geometry/pose_deviation.cpp new file mode 100644 index 0000000000000..dda8afb974d26 --- /dev/null +++ b/common/tier4_autoware_utils/src/geometry/pose_deviation.cpp @@ -0,0 +1,85 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tier4_autoware_utils/geometry/pose_deviation.hpp" + +#include "tier4_autoware_utils/math/normalization.hpp" + +#include + +#define EIGEN_MPL2_ONLY +#include +#include + +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif + +namespace tier4_autoware_utils +{ + +double calcLateralDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Point & target_point) +{ + const auto & base_point = base_pose.position; + + const auto yaw = tf2::getYaw(base_pose.orientation); + const Eigen::Vector3d base_unit_vec{std::cos(yaw), std::sin(yaw), 0}; + + const auto dx = target_point.x - base_point.x; + const auto dy = target_point.y - base_point.y; + const Eigen::Vector3d diff_vec{dx, dy, 0}; + + const Eigen::Vector3d cross_vec = base_unit_vec.cross(diff_vec); + + return cross_vec.z(); +} + +double calcLongitudinalDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Point & target_point) +{ + const auto & base_point = base_pose.position; + + const auto yaw = tf2::getYaw(base_pose.orientation); + const Eigen::Vector3d base_unit_vec{std::cos(yaw), std::sin(yaw), 0}; + + const auto dx = target_point.x - base_point.x; + const auto dy = target_point.y - base_point.y; + const Eigen::Vector3d diff_vec{dx, dy, 0}; + + return base_unit_vec.dot(diff_vec); +} + +double calcYawDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Pose & target_pose) +{ + const auto base_yaw = tf2::getYaw(base_pose.orientation); + const auto target_yaw = tf2::getYaw(target_pose.orientation); + return normalizeRadian(target_yaw - base_yaw); +} + +PoseDeviation calcPoseDeviation( + const geometry_msgs::msg::Pose & base_pose, const geometry_msgs::msg::Pose & target_pose) +{ + PoseDeviation deviation{}; + + deviation.lateral = calcLateralDeviation(base_pose, target_pose.position); + deviation.longitudinal = calcLongitudinalDeviation(base_pose, target_pose.position); + deviation.yaw = calcYawDeviation(base_pose, target_pose); + + return deviation; +} +} // namespace tier4_autoware_utils diff --git a/common/tier4_autoware_utils/src/ros/marker_helper.cpp b/common/tier4_autoware_utils/src/ros/marker_helper.cpp new file mode 100644 index 0000000000000..fe3a579e41d19 --- /dev/null +++ b/common/tier4_autoware_utils/src/ros/marker_helper.cpp @@ -0,0 +1,71 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tier4_autoware_utils/ros/marker_helper.hpp" + +namespace tier4_autoware_utils +{ + +visualization_msgs::msg::Marker createDefaultMarker( + const std::string & frame_id, const rclcpp::Time & now, const std::string & ns, const int32_t id, + const int32_t type, const geometry_msgs::msg::Vector3 & scale, + const std_msgs::msg::ColorRGBA & color) +{ + visualization_msgs::msg::Marker marker; + + marker.header.frame_id = frame_id; + marker.header.stamp = now; + marker.ns = ns; + marker.id = id; + marker.type = type; + marker.action = visualization_msgs::msg::Marker::ADD; + marker.lifetime = rclcpp::Duration::from_seconds(0.5); + + marker.pose.position = createMarkerPosition(0.0, 0.0, 0.0); + marker.pose.orientation = createMarkerOrientation(0.0, 0.0, 0.0, 1.0); + marker.scale = scale; + marker.color = color; + marker.frame_locked = true; + + return marker; +} + +visualization_msgs::msg::Marker createDeletedDefaultMarker( + const rclcpp::Time & now, const std::string & ns, const int32_t id) +{ + visualization_msgs::msg::Marker marker; + + marker.header.stamp = now; + marker.ns = ns; + marker.id = id; + marker.action = visualization_msgs::msg::Marker::DELETE; + + return marker; +} + +void appendMarkerArray( + const visualization_msgs::msg::MarkerArray & additional_marker_array, + visualization_msgs::msg::MarkerArray * marker_array, + const boost::optional & current_time) +{ + for (const auto & marker : additional_marker_array.markers) { + marker_array->markers.push_back(marker); + + if (current_time) { + marker_array->markers.back().header.stamp = current_time.get(); + } + } +} + +} // namespace tier4_autoware_utils diff --git a/common/tier4_autoware_utils/test/src/geometry/test_pose_deviation.cpp b/common/tier4_autoware_utils/test/src/geometry/test_pose_deviation.cpp index 383c35921c994..76e742edde59c 100644 --- a/common/tier4_autoware_utils/test/src/geometry/test_pose_deviation.cpp +++ b/common/tier4_autoware_utils/test/src/geometry/test_pose_deviation.cpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/geometry/pose_deviation.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" diff --git a/control/autonomous_emergency_braking/src/node.cpp b/control/autonomous_emergency_braking/src/node.cpp index 6eff270e78326..e68dff6b0ec1d 100644 --- a/control/autonomous_emergency_braking/src/node.cpp +++ b/control/autonomous_emergency_braking/src/node.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include #include @@ -31,6 +33,8 @@ #include #endif +#include +#include namespace autoware::motion::control::autonomous_emergency_braking { using diagnostic_msgs::msg::DiagnosticStatus; diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index a4464d9d834b4..181b9fbfd5260 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -22,6 +22,8 @@ #include #include +#include + #include #include @@ -31,7 +33,6 @@ #include #include #include - namespace behavior_path_planner { namespace diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 2c2373d9fc644..b2b4ecd2ea42a 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -29,6 +29,11 @@ #include +#include +#include +#include +#include + #include #include diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index 92e4c9c54d8ea..8d160b77a4fb9 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -20,6 +20,8 @@ #include #include +#include + namespace behavior_path_planner::utils::path_safety_checker { diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index f0f494756863a..9fb7d0acdf432 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -21,6 +21,10 @@ #include "object_recognition_utils/predicted_path_utils.hpp" #include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" +#include +#include +#include + namespace behavior_path_planner::utils::path_safety_checker { void appendPointToPolygon(Polygon2d & polygon, const geometry_msgs::msg::Point & geom_point) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 09e0e6cba011f..3b006cc120efb 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -30,6 +30,8 @@ #include "autoware_auto_perception_msgs/msg/predicted_object.hpp" #include "autoware_auto_perception_msgs/msg/predicted_path.hpp" +#include + #include #include #include diff --git a/planning/obstacle_cruise_planner/src/planner_interface.cpp b/planning/obstacle_cruise_planner/src/planner_interface.cpp index 5ea637655a01a..86f4517beef63 100644 --- a/planning/obstacle_cruise_planner/src/planner_interface.cpp +++ b/planning/obstacle_cruise_planner/src/planner_interface.cpp @@ -22,6 +22,8 @@ #include "signal_processing/lowpass_filter_1d.hpp" #include "tier4_autoware_utils/ros/marker_helper.hpp" +#include +#include namespace { StopSpeedExceeded createStopSpeedExceededMsg( diff --git a/planning/obstacle_stop_planner/src/planner_utils.cpp b/planning/obstacle_stop_planner/src/planner_utils.cpp index 21e45cac79fdf..be0ef13626f92 100644 --- a/planning/obstacle_stop_planner/src/planner_utils.cpp +++ b/planning/obstacle_stop_planner/src/planner_utils.cpp @@ -22,8 +22,10 @@ #include #include +#include #include #include +#include #include From e8f72332db0553bd9c2a188d43b9bc43e15387d3 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:39:39 +0900 Subject: [PATCH 236/547] chore: fix spell-check errors in apollo-related packages (#4987) Signed-off-by: kminoda --- .../lidar_apollo_instance_segmentation/cluster2d.hpp | 4 ++-- .../lidar_apollo_instance_segmentation/src/cluster2d.cpp | 6 +++--- .../include/lidar_apollo_segmentation_tvm/cluster2d.hpp | 4 ++-- perception/lidar_apollo_segmentation_tvm/src/cluster2d.cpp | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/perception/lidar_apollo_instance_segmentation/include/lidar_apollo_instance_segmentation/cluster2d.hpp b/perception/lidar_apollo_instance_segmentation/include/lidar_apollo_instance_segmentation/cluster2d.hpp index 53cab7409578e..1bb6da0374fe9 100644 --- a/perception/lidar_apollo_instance_segmentation/include/lidar_apollo_instance_segmentation/cluster2d.hpp +++ b/perception/lidar_apollo_instance_segmentation/include/lidar_apollo_instance_segmentation/cluster2d.hpp @@ -79,12 +79,12 @@ struct Obstacle float height; float heading; MetaType meta_type; - std::vector meta_type_probs; + std::vector meta_type_probabilities; Obstacle() : score(0.0), height(-5.0), heading(0.0), meta_type(META_UNKNOWN) { cloud_ptr.reset(new pcl::PointCloud); - meta_type_probs.assign(MAX_META_TYPE, 0.0); + meta_type_probabilities.assign(MAX_META_TYPE, 0.0); } }; diff --git a/perception/lidar_apollo_instance_segmentation/src/cluster2d.cpp b/perception/lidar_apollo_instance_segmentation/src/cluster2d.cpp index a7b1bee69aebb..8d758531758eb 100644 --- a/perception/lidar_apollo_instance_segmentation/src/cluster2d.cpp +++ b/perception/lidar_apollo_instance_segmentation/src/cluster2d.cpp @@ -235,13 +235,13 @@ void Cluster2D::classify(const float * inferred_data) for (size_t grid_id = 0; grid_id < obs->grids.size(); grid_id++) { int grid = obs->grids[grid_id]; for (int k = 0; k < num_classes; k++) { - obs->meta_type_probs[k] += classify_pt_data[k * size_ + grid]; + obs->meta_type_probabilities[k] += classify_pt_data[k * size_ + grid]; } } int meta_type_id = 0; for (int k = 0; k < num_classes; k++) { - obs->meta_type_probs[k] /= obs->grids.size(); - if (obs->meta_type_probs[k] > obs->meta_type_probs[meta_type_id]) { + obs->meta_type_probabilities[k] /= obs->grids.size(); + if (obs->meta_type_probabilities[k] > obs->meta_type_probabilities[meta_type_id]) { meta_type_id = k; } } diff --git a/perception/lidar_apollo_segmentation_tvm/include/lidar_apollo_segmentation_tvm/cluster2d.hpp b/perception/lidar_apollo_segmentation_tvm/include/lidar_apollo_segmentation_tvm/cluster2d.hpp index 35655cd16cb7c..4587184ecfb1f 100644 --- a/perception/lidar_apollo_segmentation_tvm/include/lidar_apollo_segmentation_tvm/cluster2d.hpp +++ b/perception/lidar_apollo_segmentation_tvm/include/lidar_apollo_segmentation_tvm/cluster2d.hpp @@ -57,12 +57,12 @@ struct Obstacle float height; float heading; MetaType meta_type; - std::vector meta_type_probs; + std::vector meta_type_probabilities; Obstacle() : score(0.0), height(-5.0), heading(0.0), meta_type(MetaType::META_UNKNOWN) { cloud_ptr.reset(new pcl::PointCloud); - meta_type_probs.assign(static_cast(MetaType::MAX_META_TYPE), 0.0); + meta_type_probabilities.assign(static_cast(MetaType::MAX_META_TYPE), 0.0); } }; diff --git a/perception/lidar_apollo_segmentation_tvm/src/cluster2d.cpp b/perception/lidar_apollo_segmentation_tvm/src/cluster2d.cpp index 39c0da04a9c39..f96a32a5bb465 100644 --- a/perception/lidar_apollo_segmentation_tvm/src/cluster2d.cpp +++ b/perception/lidar_apollo_segmentation_tvm/src/cluster2d.cpp @@ -218,13 +218,13 @@ void Cluster2D::classify(const float * inferred_data) for (size_t grid_id = 0; grid_id < obs->grids.size(); grid_id++) { int32_t grid = obs->grids[grid_id]; for (int k = 0; k < num_classes; k++) { - obs->meta_type_probs[k] += classify_pt_data[k * siz_ + grid]; + obs->meta_type_probabilities[k] += classify_pt_data[k * siz_ + grid]; } } int meta_type_id = 0; for (int k = 0; k < num_classes; k++) { - obs->meta_type_probs[k] /= static_cast(obs->grids.size()); - if (obs->meta_type_probs[k] > obs->meta_type_probs[meta_type_id]) { + obs->meta_type_probabilities[k] /= static_cast(obs->grids.size()); + if (obs->meta_type_probabilities[k] > obs->meta_type_probabilities[meta_type_id]) { meta_type_id = k; } } From 6790c2ca79be8e48a4b33af6882c9f23a5cb43c6 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 19 Sep 2023 20:26:44 +0900 Subject: [PATCH 237/547] fix(obstacle_cruise_planner): fix backward virtual wall (#5028) Signed-off-by: kosuke55 --- .../motion_utils/marker/marker_helper.hpp | 9 +++++--- .../marker/virtual_wall_marker_creator.hpp | 3 ++- .../motion_utils/src/marker/marker_helper.cpp | 21 +++++++++++-------- .../marker/virtual_wall_marker_creator.cpp | 2 +- .../src/planner_interface.cpp | 8 +++---- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/common/motion_utils/include/motion_utils/marker/marker_helper.hpp b/common/motion_utils/include/motion_utils/marker/marker_helper.hpp index b6d10bbf40de5..159f8a33bf325 100644 --- a/common/motion_utils/include/motion_utils/marker/marker_helper.hpp +++ b/common/motion_utils/include/motion_utils/marker/marker_helper.hpp @@ -29,15 +29,18 @@ using geometry_msgs::msg::Pose; visualization_msgs::msg::MarkerArray createStopVirtualWallMarker( const Pose & pose, const std::string & module_name, const rclcpp::Time & now, const int32_t id, - const double longitudinal_offset = 0.0, const std::string & ns_prefix = ""); + const double longitudinal_offset = 0.0, const std::string & ns_prefix = "", + const bool is_driving_forward = true); visualization_msgs::msg::MarkerArray createSlowDownVirtualWallMarker( const Pose & pose, const std::string & module_name, const rclcpp::Time & now, const int32_t id, - const double longitudinal_offset = 0.0, const std::string & ns_prefix = ""); + const double longitudinal_offset = 0.0, const std::string & ns_prefix = "", + const bool is_driving_forward = true); visualization_msgs::msg::MarkerArray createDeadLineVirtualWallMarker( const Pose & pose, const std::string & module_name, const rclcpp::Time & now, const int32_t id, - const double longitudinal_offset = 0.0, const std::string & ns_prefix = ""); + const double longitudinal_offset = 0.0, const std::string & ns_prefix = "", + const bool is_driving_forward = true); visualization_msgs::msg::MarkerArray createDeletedStopVirtualWallMarker( const rclcpp::Time & now, const int32_t id); diff --git a/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp b/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp index afead3a3614d4..5495dbfcfcf57 100644 --- a/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp +++ b/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp @@ -36,6 +36,7 @@ struct VirtualWall std::string ns{}; VirtualWallType style = stop; double longitudinal_offset{}; + bool is_driving_forward{true}; }; typedef std::vector VirtualWalls; @@ -52,7 +53,7 @@ class VirtualWallMarkerCreator using create_wall_function = std::function; + const std::string & ns_prefix, const bool is_driving_forward)>; VirtualWalls virtual_walls; std::unordered_map marker_count_per_namespace; diff --git a/common/motion_utils/src/marker/marker_helper.cpp b/common/motion_utils/src/marker/marker_helper.cpp index c869271222387..3516fc71f219f 100644 --- a/common/motion_utils/src/marker/marker_helper.cpp +++ b/common/motion_utils/src/marker/marker_helper.cpp @@ -90,10 +90,11 @@ namespace motion_utils { visualization_msgs::msg::MarkerArray createStopVirtualWallMarker( const geometry_msgs::msg::Pose & pose, const std::string & module_name, const rclcpp::Time & now, - const int32_t id, const double longitudinal_offset, const std::string & ns_prefix) + const int32_t id, const double longitudinal_offset, const std::string & ns_prefix, + const bool is_driving_forward) { - const auto pose_with_offset = - tier4_autoware_utils::calcOffsetPose(pose, longitudinal_offset, 0.0, 0.0); + const auto pose_with_offset = tier4_autoware_utils::calcOffsetPose( + pose, longitudinal_offset * (is_driving_forward ? 1.0 : -1.0), 0.0, 0.0); return createVirtualWallMarkerArray( pose_with_offset, module_name, ns_prefix + "stop_", now, id, createMarkerColor(1.0, 0.0, 0.0, 0.5)); @@ -101,10 +102,11 @@ visualization_msgs::msg::MarkerArray createStopVirtualWallMarker( visualization_msgs::msg::MarkerArray createSlowDownVirtualWallMarker( const geometry_msgs::msg::Pose & pose, const std::string & module_name, const rclcpp::Time & now, - const int32_t id, const double longitudinal_offset, const std::string & ns_prefix) + const int32_t id, const double longitudinal_offset, const std::string & ns_prefix, + const bool is_driving_forward) { - const auto pose_with_offset = - tier4_autoware_utils::calcOffsetPose(pose, longitudinal_offset, 0.0, 0.0); + const auto pose_with_offset = tier4_autoware_utils::calcOffsetPose( + pose, longitudinal_offset * (is_driving_forward ? 1.0 : -1.0), 0.0, 0.0); return createVirtualWallMarkerArray( pose_with_offset, module_name, ns_prefix + "slow_down_", now, id, createMarkerColor(1.0, 1.0, 0.0, 0.5)); @@ -112,10 +114,11 @@ visualization_msgs::msg::MarkerArray createSlowDownVirtualWallMarker( visualization_msgs::msg::MarkerArray createDeadLineVirtualWallMarker( const geometry_msgs::msg::Pose & pose, const std::string & module_name, const rclcpp::Time & now, - const int32_t id, const double longitudinal_offset, const std::string & ns_prefix) + const int32_t id, const double longitudinal_offset, const std::string & ns_prefix, + const bool is_driving_forward) { - const auto pose_with_offset = - tier4_autoware_utils::calcOffsetPose(pose, longitudinal_offset, 0.0, 0.0); + const auto pose_with_offset = tier4_autoware_utils::calcOffsetPose( + pose, longitudinal_offset * (is_driving_forward ? 1.0 : -1.0), 0.0, 0.0); return createVirtualWallMarkerArray( pose_with_offset, module_name, ns_prefix + "dead_line_", now, id, createMarkerColor(0.0, 1.0, 0.0, 0.5)); diff --git a/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp b/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp index 114c4f87907c0..d70caf9b057e6 100644 --- a/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp +++ b/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp @@ -66,7 +66,7 @@ visualization_msgs::msg::MarkerArray VirtualWallMarkerCreator::create_markers( } auto markers = create_fn( virtual_wall.pose, virtual_wall.text, now, 0, virtual_wall.longitudinal_offset, - virtual_wall.ns); + virtual_wall.ns, virtual_wall.is_driving_forward); for (auto & marker : markers.markers) { marker.id = marker_count_per_namespace[marker.ns].current++; marker_array.markers.push_back(marker); diff --git a/planning/obstacle_cruise_planner/src/planner_interface.cpp b/planning/obstacle_cruise_planner/src/planner_interface.cpp index 86f4517beef63..287df10dcebb5 100644 --- a/planning/obstacle_cruise_planner/src/planner_interface.cpp +++ b/planning/obstacle_cruise_planner/src/planner_interface.cpp @@ -380,7 +380,7 @@ std::vector PlannerInterface::generateStopTrajectory( // virtual wall marker for stop obstacle const auto markers = motion_utils::createStopVirtualWallMarker( output_traj_points.at(*zero_vel_idx).pose, "obstacle stop", planner_data.current_time, 0, - abs_ego_offset); + abs_ego_offset, "", planner_data.is_driving_forward); tier4_autoware_utils::appendMarkerArray(markers, &debug_data_ptr_->stop_wall_marker); debug_data_ptr_->obstacles_to_stop.push_back(*closest_stop_obstacle); @@ -623,7 +623,7 @@ std::vector PlannerInterface::generateSlowDownTrajectory( const auto markers = motion_utils::createSlowDownVirtualWallMarker( slow_down_traj_points.at(slow_down_wall_idx).pose, "obstacle slow down", - planner_data.current_time, i, abs_ego_offset); + planner_data.current_time, i, abs_ego_offset, "", planner_data.is_driving_forward); tier4_autoware_utils::appendMarkerArray(markers, &debug_data_ptr_->slow_down_wall_marker); } @@ -631,14 +631,14 @@ std::vector PlannerInterface::generateSlowDownTrajectory( if (slow_down_start_idx) { const auto markers = motion_utils::createSlowDownVirtualWallMarker( slow_down_traj_points.at(*slow_down_start_idx).pose, "obstacle slow down start", - planner_data.current_time, i * 2, abs_ego_offset); + planner_data.current_time, i * 2, abs_ego_offset, "", planner_data.is_driving_forward); tier4_autoware_utils::appendMarkerArray( markers, &debug_data_ptr_->slow_down_debug_wall_marker); } if (slow_down_end_idx) { const auto markers = motion_utils::createSlowDownVirtualWallMarker( slow_down_traj_points.at(*slow_down_end_idx).pose, "obstacle slow down end", - planner_data.current_time, i * 2 + 1, abs_ego_offset); + planner_data.current_time, i * 2 + 1, abs_ego_offset, "", planner_data.is_driving_forward); tier4_autoware_utils::appendMarkerArray( markers, &debug_data_ptr_->slow_down_debug_wall_marker); } From a66e40f0516c8eb9fc6b12c07f5894f55c8999fc Mon Sep 17 00:00:00 2001 From: Kento Yabuuchi Date: Tue, 19 Sep 2023 20:46:05 +0900 Subject: [PATCH 238/547] refactor(yabloc_pose_initializer): use cpp DNN module instead of python (#5025) * cannot include yabloc_pose_initializer.srv Signed-off-by: Kento Yabuuchi * launch semantic_segmentation_cpp Signed-off-by: Kento Yabuuchi * implement DNN inference as C++ Signed-off-by: Kento Yabuuchi * use C++ inference Signed-off-by: Kento Yabuuchi * remove python files Signed-off-by: Kento Yabuuchi * add segmentation.cpp Signed-off-by: Kento Yabuuchi * add license Signed-off-by: Kento Yabuuchi * remove segmentation service Signed-off-by: Kento Yabuuchi * style(pre-commit): autofix * reflect pre-commit's review Signed-off-by: Kento Yabuuchi * add include guard Signed-off-by: Kento Yabuuchi * update README Signed-off-by: Kento Yabuuchi * style(pre-commit): autofix --------- Signed-off-by: Kento Yabuuchi Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../yabloc_pose_initializer/CMakeLists.txt | 30 +---- .../yabloc/yabloc_pose_initializer/README.md | 21 +-- .../camera/camera_pose_initializer.hpp | 6 +- .../camera/projector_module.hpp | 2 +- .../camera/semantic_segmentation.hpp | 46 +++++++ .../launch/yabloc_pose_initializer.launch.xml | 10 +- .../camera/camera_pose_initializer_core.cpp | 53 ++++---- .../src/camera/projector_module.cpp | 4 +- .../src/camera/semantic_segmentation.cpp | 123 ++++++++++++++++++ .../semantic_segmentation_core.py | 69 ---------- .../semantic_segmentation_server.py | 91 ------------- .../srv/SemanticSegmentation.srv | 4 - 12 files changed, 202 insertions(+), 257 deletions(-) create mode 100644 localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/semantic_segmentation.hpp create mode 100644 localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp delete mode 100755 localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_core.py delete mode 100755 localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_server.py delete mode 100644 localization/yabloc/yabloc_pose_initializer/srv/SemanticSegmentation.srv diff --git a/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt b/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt index cd54a3c5f2ef2..ad33912164359 100644 --- a/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt +++ b/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt @@ -18,20 +18,6 @@ find_package(Sophus REQUIRED) # Download DNN model include(download.cmake) -# =================================================== -# Clear ${PYTHON_EXECUTABLE} defined by mrt_cmake_module so that rosidl_generate_interfaces can look for it properly -unset(PYTHON_EXECUTABLE) -message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}") - -# =================================================== -# Service -rosidl_generate_interfaces(${PROJECT_NAME} - "srv/SemanticSegmentation.srv" - DEPENDENCIES - std_msgs - sensor_msgs -) - # =================================================== # Executable # Camera @@ -40,26 +26,12 @@ ament_auto_add_executable(${TARGET} src/camera/lane_image.cpp src/camera/marker_module.cpp src/camera/projector_module.cpp + src/camera/semantic_segmentation.cpp src/camera/camera_pose_initializer_core.cpp src/camera/camera_pose_initializer_node.cpp) target_include_directories(${TARGET} PUBLIC include) target_include_directories(${TARGET} SYSTEM PRIVATE ${EIGEN3_INCLUDE_DIRS} ${PCL_INCLUDE_DIRS}) target_link_libraries(${TARGET} ${PCL_LIBRARIES} Sophus::Sophus) -# ros2idl typesupport -if(${rosidl_cmake_VERSION} VERSION_LESS 2.5.0) - rosidl_target_interfaces(${TARGET} ${PROJECT_NAME} "rosidl_typesupport_cpp") -else() - rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} "rosidl_typesupport_cpp") - target_link_libraries(${TARGET} "${cpp_typesupport_target}") -endif() - -# Semantic segmentation -install(PROGRAMS - src/semantic_segmentation/semantic_segmentation_core.py - src/semantic_segmentation/semantic_segmentation_server.py - DESTINATION lib/${PROJECT_NAME} -) - # =================================================== ament_auto_package(INSTALL_TO_SHARE config data launch) diff --git a/localization/yabloc/yabloc_pose_initializer/README.md b/localization/yabloc/yabloc_pose_initializer/README.md index 4e3c0a0363402..6a7c21fc554cf 100644 --- a/localization/yabloc/yabloc_pose_initializer/README.md +++ b/localization/yabloc/yabloc_pose_initializer/README.md @@ -1,9 +1,8 @@ # yabloc_pose_initializer -This package contains some nodes related to initial pose estimation. +This package contains a node related to initial pose estimation. - [camera_pose_initializer](#camera_pose_initializer) -- [semantic_segmentation_server](#semantic_segmentation_server) Ideally, this package downloads a pre-trained semantic segmentation model during the build and loads it at runtime for initialization. However, to handle cases where network connectivity is not available at build time, **the default behavior is not to download the model during build.** @@ -71,21 +70,3 @@ Converted model URL | Name | Type | Description | | ------------------ | --------------------------------------------------------- | ------------------------------- | | `yabloc_align_srv` | `tier4_localization_msgs::srv::PoseWithCovarianceStamped` | initial pose estimation request | - -### Clients - -| Name | Type | Description | -| --------------------------- | ---------------------------------------------------- | ----------------------------- | -| `semantic_segmentation_srv` | `yabloc_pose_initializer::srv::SemanticSegmentation` | semantic segmentation request | - -## semantic_segmentation_server - -### Purpose - -- This node performs semantic segmentation. - -### Services - -| Name | Type | Description | -| --------------------------- | ---------------------------------------------------- | ----------------------------- | -| `semantic_segmentation_srv` | `yabloc_pose_initializer::srv::SemanticSegmentation` | semantic segmentation request | diff --git a/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/camera_pose_initializer.hpp b/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/camera_pose_initializer.hpp index ccb55e79b8ea9..917a6ecaaf291 100644 --- a/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/camera_pose_initializer.hpp +++ b/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/camera_pose_initializer.hpp @@ -18,10 +18,10 @@ #include "yabloc_pose_initializer/camera/lane_image.hpp" #include "yabloc_pose_initializer/camera/marker_module.hpp" #include "yabloc_pose_initializer/camera/projector_module.hpp" +#include "yabloc_pose_initializer/camera/semantic_segmentation.hpp" #include #include -#include #include #include @@ -42,7 +42,6 @@ class CameraPoseInitializer : public rclcpp::Node using Image = sensor_msgs::msg::Image; using HADMapBin = autoware_auto_mapping_msgs::msg::HADMapBin; using RequestPoseAlignment = tier4_localization_msgs::srv::PoseWithCovarianceStamped; - using SegmentationSrv = yabloc_pose_initializer::srv::SemanticSegmentation; CameraPoseInitializer(); @@ -57,12 +56,13 @@ class CameraPoseInitializer : public rclcpp::Node rclcpp::Subscription::SharedPtr sub_image_; rclcpp::Service::SharedPtr align_server_; - rclcpp::Client::SharedPtr segmentation_client_; rclcpp::CallbackGroup::SharedPtr service_callback_group_; std::optional latest_image_msg_{std::nullopt}; lanelet::ConstLanelets const_lanelets_; + std::unique_ptr semantic_segmentation_{nullptr}; + void on_map(const HADMapBin & msg); void on_service( const RequestPoseAlignment::Request::SharedPtr, diff --git a/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/projector_module.hpp b/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/projector_module.hpp index cc084990a0ff3..071d55482f564 100644 --- a/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/projector_module.hpp +++ b/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/projector_module.hpp @@ -33,7 +33,7 @@ class ProjectorModule bool define_project_func(); - cv::Mat project_image(const sensor_msgs::msg::Image & image_msg); + cv::Mat project_image(const cv::Mat & mask_image); private: common::CameraInfoSubscriber info_; diff --git a/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/semantic_segmentation.hpp b/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/semantic_segmentation.hpp new file mode 100644 index 0000000000000..061d0548c6f18 --- /dev/null +++ b/localization/yabloc/yabloc_pose_initializer/include/yabloc_pose_initializer/camera/semantic_segmentation.hpp @@ -0,0 +1,46 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef YABLOC_POSE_INITIALIZER__CAMERA__SEMANTIC_SEGMENTATION_HPP_ +#define YABLOC_POSE_INITIALIZER__CAMERA__SEMANTIC_SEGMENTATION_HPP_ +#include +#include + +#include +#include +#include + +class SemanticSegmentation +{ +public: + explicit SemanticSegmentation(const std::string & model_path); + + cv::Mat inference(const cv::Mat & image, double score_threshold = 0.5); + + static void print_error_message(const rclcpp::Logger & logger); + +private: + cv::Mat make_blob(const cv::Mat & image); + + cv::Mat convert_blob_to_image(const cv::Mat & blob); + + cv::Mat normalize(const cv::Mat & mask, double score_threshold = 0.5); + + cv::Mat draw_overlay(const cv::Mat & image, const cv::Mat & segmentation); + + struct Impl; + std::shared_ptr impl_{nullptr}; +}; + +#endif // YABLOC_POSE_INITIALIZER__CAMERA__SEMANTIC_SEGMENTATION_HPP_ diff --git a/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml b/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml index a1d5d59b46094..667f436bbabb5 100644 --- a/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml +++ b/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml @@ -1,17 +1,13 @@ + + + - - - - - - - diff --git a/localization/yabloc/yabloc_pose_initializer/src/camera/camera_pose_initializer_core.cpp b/localization/yabloc/yabloc_pose_initializer/src/camera/camera_pose_initializer_core.cpp index 5fab9763328e3..19d3c8d88f260 100644 --- a/localization/yabloc/yabloc_pose_initializer/src/camera/camera_pose_initializer_core.cpp +++ b/localization/yabloc/yabloc_pose_initializer/src/camera/camera_pose_initializer_core.cpp @@ -20,6 +20,10 @@ #include #include +#include + +#include + namespace yabloc { CameraPoseInitializer::CameraPoseInitializer() @@ -39,17 +43,17 @@ CameraPoseInitializer::CameraPoseInitializer() sub_map_ = create_subscription("~/input/vector_map", map_qos, on_map); sub_image_ = create_subscription("~/input/image_raw", 10, on_image); - // Client - segmentation_client_ = create_client( - "~/semantic_segmentation_srv", rmw_qos_profile_services_default, service_callback_group_); - // Server auto on_service = std::bind(&CameraPoseInitializer::on_service, this, _1, _2); align_server_ = create_service("~/yabloc_align_srv", on_service); - using namespace std::literals::chrono_literals; - while (!segmentation_client_->wait_for_service(1s) && rclcpp::ok()) { - RCLCPP_INFO_STREAM(get_logger(), "Waiting for " << segmentation_client_->get_service_name()); + const std::string model_path = declare_parameter("model_path"); + RCLCPP_INFO_STREAM(get_logger(), "segmentation model path: " + model_path); + if (std::filesystem::exists(model_path)) { + semantic_segmentation_ = std::make_unique(model_path); + } else { + semantic_segmentation_ = nullptr; + SemanticSegmentation::print_error_message(get_logger()); } } @@ -97,31 +101,20 @@ std::optional CameraPoseInitializer::estimate_pose( return std::nullopt; } - Image segmented_image; + cv::Mat segmented_image; { - // Call semantic segmentation service - auto request = std::make_shared(); - request->src_image = *latest_image_msg_.value(); - auto result_future = segmentation_client_->async_send_request(request); - using namespace std::literals::chrono_literals; - std::future_status status = result_future.wait_for(1000ms); - if (status == std::future_status::ready) { - const auto response = result_future.get(); - if (response->success) { - segmented_image = response->dst_image; - } else { - RCLCPP_WARN_STREAM(get_logger(), "segmentation service failed unexpectedly"); - // NOTE: Even if the segmentation service fails, the function will still return the - // yaw_angle_rad as it is and complete the initialization. The service fails - // when the DNN model is not downloaded. Ideally, initialization should rely on - // segmentation, but this implementation allows initialization even in cases where network - // connectivity is not available. - return yaw_angle_rad; - } + if (semantic_segmentation_) { + const cv::Mat src_image = + cv_bridge::toCvCopy(*latest_image_msg_.value(), sensor_msgs::image_encodings::BGR8)->image; + segmented_image = semantic_segmentation_->inference(src_image); } else { - RCLCPP_ERROR_STREAM( - get_logger(), "segmentation service did not return within the expected time"); - return std::nullopt; + RCLCPP_WARN_STREAM(get_logger(), "segmentation service failed unexpectedly"); + // NOTE: Even if the segmentation service fails, the function will still return the + // yaw_angle_rad as it is and complete the initialization. The service fails + // when the DNN model is not downloaded. Ideally, initialization should rely on + // segmentation, but this implementation allows initialization even in cases where network + // connectivity is not available. + return yaw_angle_rad; } } diff --git a/localization/yabloc/yabloc_pose_initializer/src/camera/projector_module.cpp b/localization/yabloc/yabloc_pose_initializer/src/camera/projector_module.cpp index 00f2da473b7ed..86a403f68a957 100644 --- a/localization/yabloc/yabloc_pose_initializer/src/camera/projector_module.cpp +++ b/localization/yabloc/yabloc_pose_initializer/src/camera/projector_module.cpp @@ -36,10 +36,8 @@ cv::Point2i to_cv_point(const Eigen::Vector3f & v) return pt; } -cv::Mat ProjectorModule::project_image(const sensor_msgs::msg::Image & image_msg) +cv::Mat ProjectorModule::project_image(const cv::Mat & mask_image) { - cv::Mat mask_image = common::decompress_to_cv_mat(image_msg); - // project semantics on plane std::vector masks; cv::split(mask_image, masks); diff --git a/localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp b/localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp new file mode 100644 index 0000000000000..b6062727d1de9 --- /dev/null +++ b/localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp @@ -0,0 +1,123 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "yabloc_pose_initializer/camera/semantic_segmentation.hpp" + +#include +#include +#include +#include + +#include +#include + +struct SemanticSegmentation::Impl +{ + cv::dnn::Net net; +}; + +SemanticSegmentation::SemanticSegmentation(const std::string & model_path) +{ + impl_ = std::make_shared(); + impl_->net = cv::dnn::readNet(model_path); + impl_->net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV); + impl_->net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU); +} + +cv::Mat SemanticSegmentation::make_blob(const cv::Mat & image) +{ + double scale = 1.0; + cv::Size size = cv::Size(896, 512); + cv::Scalar mean = cv::Scalar(0, 0, 0); + bool swap = true; + bool crop = false; + return cv::dnn::blobFromImage(image, scale, size, mean, swap, crop, CV_32F); +} + +cv::Mat SemanticSegmentation::convert_blob_to_image(const cv::Mat & blob) +{ + if (blob.size.dims() != 4) { + throw std::runtime_error("blob has invalid size"); + } + const int channels = blob.size[1]; + const int height = blob.size[2]; + const int width = blob.size[3]; + cv::Mat image = cv::Mat(height, width, CV_32FC4); + + for (int h = 0; h < height; ++h) { + for (int w = 0; w < width; ++w) { + cv::Vec4f vec4f; + for (int c = 0; c < channels; ++c) { + vec4f[c] = blob.at(c * height * width + h * width + w); + } + image.at(h, w) = vec4f; + } + } + return image; +} + +cv::Mat SemanticSegmentation::inference(const cv::Mat & image, double score_threshold) +{ + cv::Mat blob = make_blob(image); + impl_->net.setInput(blob); + std::vector output_layers = impl_->net.getUnconnectedOutLayersNames(); + std::vector masks; + impl_->net.forward(masks, output_layers); + + cv::Mat mask = masks[0]; + cv::Mat output = convert_blob_to_image(mask); + + cv::resize(output, output, cv::Size(image.cols, image.rows), 0, 0, cv::INTER_LINEAR); + + return normalize(output, score_threshold); +} + +cv::Mat SemanticSegmentation::normalize(const cv::Mat & mask, double score_threshold) +{ + std::vector masks; + cv::split(mask, masks); + std::vector bin_masks; + + for (size_t i = 1; i < masks.size(); ++i) { + cv::Mat bin_mask; + cv::threshold(masks[i], bin_mask, score_threshold, 255, cv::THRESH_BINARY_INV); + bin_mask.convertTo(bin_mask, CV_8UC1); + bin_masks.push_back(255 - bin_mask); + } + + cv::Mat result; + cv::merge(bin_masks, result); + return result; +} + +cv::Mat SemanticSegmentation::draw_overlay(const cv::Mat & image, const cv::Mat & segmentation) +{ + cv::Mat overlay_image = image.clone(); + return overlay_image * 0.5 + segmentation * 0.5; +} + +void SemanticSegmentation::print_error_message(const rclcpp::Logger & logger) +{ + // cspell: ignore DDOWNLOAD + const std::string ERROR_MESSAGE = + R"(The yabloc_pose_initializer is not working correctly because the DNN model has not been downloaded correctly. +To download models, "-DDOWNLOAD_ARTIFACTS=ON" is required at build time. +Please see the README of yabloc_pose_initializer for more information.)"; + + std::istringstream stream(ERROR_MESSAGE); + std::string line; + while (std::getline(stream, line)) { + RCLCPP_ERROR_STREAM(logger, line); + } +} diff --git a/localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_core.py b/localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_core.py deleted file mode 100755 index 70d116fe0b295..0000000000000 --- a/localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_core.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2023 TIER IV, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import copy - -import cv2 -import numpy as np - - -class SemanticSegmentationCore: - def __init__(self, model_path): - self.net_ = cv2.dnn.readNet(model_path) - self.net_.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) - self.net_.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) - - def makeBlob(self, image: np.ndarray) -> np.ndarray: - scale = 1.0 - size = (896, 512) - mean = (0, 0, 0) - swap = True - crop = False - depth = cv2.CV_32F - return cv2.dnn.blobFromImage(image, scale, size, mean, swap, crop, depth) - - def inference(self, image: np.ndarray, score_threshold=0.5) -> np.ndarray: - blob = self.makeBlob(image) - self.net_.setInput(blob) - output_layers = self.net_.getUnconnectedOutLayersNames() - mask = self.net_.forward(output_layers)[0] - mask = np.squeeze(mask).transpose(1, 2, 0) - - mask = cv2.resize( - mask, dsize=(image.shape[1], image.shape[0]), interpolation=cv2.INTER_LINEAR - ) - - return self.__normalize(mask, score_threshold) - - def __normalize(self, mask, score_threshold=0.5) -> np.ndarray: - masks = cv2.split(mask)[1:] - bin_masks = [] - for mask in masks: - bin_mask = np.where(mask > score_threshold, 0, 1).astype("uint8") - bin_masks.append(255 - 255 * bin_mask) - return cv2.merge(bin_masks) - - def drawOverlay(self, image, segmentation) -> np.ndarray: - overlay_image = copy.deepcopy(image) - return cv2.addWeighted(overlay_image, 0.5, segmentation, 0.5, 1.0) - - -def main(): - print("This script is not intended to be run alone.") - - -if __name__ == "__main__": - main() diff --git a/localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_server.py b/localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_server.py deleted file mode 100755 index 03de9de572910..0000000000000 --- a/localization/yabloc/yabloc_pose_initializer/src/semantic_segmentation/semantic_segmentation_server.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2023 TIER IV, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys - -from cv_bridge import CvBridge -import rclpy -from rclpy.node import Node -import semantic_segmentation_core as core -from sensor_msgs.msg import Image -from yabloc_pose_initializer.srv import SemanticSegmentation - -# cspell: ignore DDOWNLOAD -ERROR_MESSAGE = """\ -The yabloc_pose_initializer is not working correctly because the DNN model has not been downloaded correctly. -To download models, "-DDOWNLOAD_ARTIFACTS=ON" is required at build time. -Please see the README of yabloc_pose_initializer for more information.""" - - -class SemanticSegmentationServer(Node): - def __init__(self): - super().__init__("segmentation_server_node") - - model_path = self.declare_parameter("model_path", "").value - - self.get_logger().info("model path: " + model_path) - self.bridge_ = CvBridge() - - if os.path.exists(model_path): - self.dnn_ = core.SemanticSegmentationCore(model_path) - else: - self.dnn_ = None - self.__print_error_message() - - self.srv = self.create_service( - SemanticSegmentation, "semantic_segmentation_srv", self.on_service - ) - - def __print_error_message(self): - messages = ERROR_MESSAGE.split("\n") - for message in messages: - self.get_logger().error(message) - - def on_service(self, request, response): - if self.dnn_: - response.dst_image = self.__inference(request.src_image) - response.success = True - else: - self.__print_error_message() - response.success = False - response.dst_image = request.src_image - - return response - - def __inference(self, msg: Image): - stamp = msg.header.stamp - self.get_logger().info("Subscribed image: " + str(stamp)) - src_image = self.bridge_.imgmsg_to_cv2(msg) - - mask = self.dnn_.inference(src_image) - dst_msg = self.bridge_.cv2_to_imgmsg(mask) - dst_msg.encoding = "bgr8" - - return dst_msg - - -def main(): - rclpy.init(args=sys.argv) - - server_node = SemanticSegmentationServer() - rclpy.spin(server_node) - server_node.destroy_node() - rclpy.shutdown() - - -if __name__ == "__main__": - main() diff --git a/localization/yabloc/yabloc_pose_initializer/srv/SemanticSegmentation.srv b/localization/yabloc/yabloc_pose_initializer/srv/SemanticSegmentation.srv deleted file mode 100644 index 3daa611fd18ce..0000000000000 --- a/localization/yabloc/yabloc_pose_initializer/srv/SemanticSegmentation.srv +++ /dev/null @@ -1,4 +0,0 @@ -sensor_msgs/Image src_image ---- -bool success -sensor_msgs/Image dst_image From a4d12dc4d77d63e7fc148bc5e54a8265bfc1d181 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 19 Sep 2023 23:49:29 +0900 Subject: [PATCH 239/547] fix(obstacle_cruise_planner): fix stop against objects after backward trajectory terminal (#5031) * fix(obstacle_cruise_planner): fix backward virtual wall Signed-off-by: kosuke55 * fix(obstacle_cruise): fix stop margin after backward terminal Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- planning/obstacle_cruise_planner/src/node.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index 14324709ba9e1..d0c240a862a74 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -131,11 +131,11 @@ double calcObstacleMaxLength(const Shape & shape) } TrajectoryPoint getExtendTrajectoryPoint( - const double extend_distance, const TrajectoryPoint & goal_point) + const double extend_distance, const TrajectoryPoint & goal_point, const bool is_driving_forward) { TrajectoryPoint extend_trajectory_point; - extend_trajectory_point.pose = - tier4_autoware_utils::calcOffsetPose(goal_point.pose, extend_distance, 0.0, 0.0); + extend_trajectory_point.pose = tier4_autoware_utils::calcOffsetPose( + goal_point.pose, extend_distance * (is_driving_forward ? 1.0 : -1.0), 0.0, 0.0); extend_trajectory_point.longitudinal_velocity_mps = goal_point.longitudinal_velocity_mps; extend_trajectory_point.lateral_velocity_mps = goal_point.lateral_velocity_mps; extend_trajectory_point.acceleration_mps2 = goal_point.acceleration_mps2; @@ -147,6 +147,8 @@ std::vector extendTrajectoryPoints( const double step_length) { auto output_points = input_points; + const auto is_driving_forward_opt = motion_utils::isDrivingForwardWithTwist(input_points); + const bool is_driving_forward = is_driving_forward_opt ? *is_driving_forward_opt : true; if (extend_distance < std::numeric_limits::epsilon()) { return output_points; @@ -156,11 +158,13 @@ std::vector extendTrajectoryPoints( double extend_sum = 0.0; while (extend_sum <= (extend_distance - step_length)) { - const auto extend_trajectory_point = getExtendTrajectoryPoint(extend_sum, goal_point); + const auto extend_trajectory_point = + getExtendTrajectoryPoint(extend_sum, goal_point, is_driving_forward); output_points.push_back(extend_trajectory_point); extend_sum += step_length; } - const auto extend_trajectory_point = getExtendTrajectoryPoint(extend_distance, goal_point); + const auto extend_trajectory_point = + getExtendTrajectoryPoint(extend_distance, goal_point, is_driving_forward); output_points.push_back(extend_trajectory_point); return output_points; From aae343baf8652ce727d803199192bfa33212e2bc Mon Sep 17 00:00:00 2001 From: Tomoya Kimura Date: Wed, 20 Sep 2023 00:55:37 +0900 Subject: [PATCH 240/547] fix(pull_out): add empty check (#5041) Signed-off-by: tomoya.kimura --- .../geometric_parallel_parking/geometric_parallel_parking.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index 4742e5c8d3d29..6b1a4a82b314e 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -289,6 +289,10 @@ bool GeometricParallelParking::planPullOut( PathWithLaneId road_center_line_path = planner_data_->route_handler->getCenterLinePath(road_lanes, s_start, s_end, true); + if (road_center_line_path.points.empty()) { + continue; + } + // check the continuity of straight path and arc path const Pose & road_path_first_pose = road_center_line_path.points.front().point.pose; const Pose & arc_path_last_pose = arc_paths.back().points.back().point.pose; From 551913106971e70080a5f65e5cfa370f7d4a0d89 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Wed, 20 Sep 2023 09:21:54 +0900 Subject: [PATCH 241/547] feat(behavior_path_planner): account for delay before departure in predicted path (#5017) * update function of create predicted path Signed-off-by: kyoichi-sugahara * account for delay before departure Signed-off-by: kyoichi-sugahara * fix time horizon length Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../path_safety_checker/objects_filtering.hpp | 6 ++++- .../path_safety_checker/objects_filtering.cpp | 22 ++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp index 344822dce113a..efcf375720924 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp @@ -142,7 +142,11 @@ std::vector getPredictedPathFromObj( * * The function predicts the path based on the current vehicle pose, its current velocity, * and certain parameters related to the vehicle's behavior and environment. The prediction - * considers acceleration and maximum velocity constraints. + * considers acceleration, delay before departure, and maximum velocity constraints. + * + * During the delay before departure, the vehicle's velocity is assumed to be zero, and it does + * not move. After the delay, the vehicle starts to accelerate as per the provided parameters + * until it reaches the maximum allowable velocity or the specified time horizon. * * @param ego_predicted_path_params Parameters associated with the ego's predicted path behavior. * @param path_points Path points to be followed by the vehicle. diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index 8d160b77a4fb9..068acc04240f1 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -223,7 +223,6 @@ std::vector getPredictedPathFromObj( return obj.predicted_paths; } -// TODO(Sugahara): should consider delay before departure std::vector createPredictedPath( const std::shared_ptr & ego_predicted_path_params, const std::vector & path_points, @@ -242,15 +241,28 @@ std::vector createPredictedPath( ? ego_predicted_path_params->time_horizon_for_front_object : ego_predicted_path_params->time_horizon_for_rear_object; const double time_resolution = ego_predicted_path_params->time_resolution; + const double delay_until_departure = ego_predicted_path_params->delay_until_departure; std::vector predicted_path; const auto vehicle_pose_frenet = convertToFrenetPoint(path_points, vehicle_pose.position, ego_seg_idx); - for (double t = 0.0; t < time_horizon + 1e-3; t += time_resolution) { - const double velocity = - std::clamp(current_velocity + acceleration * t, min_velocity, max_velocity); - const double length = current_velocity * t + 0.5 * acceleration * t * t; + for (double t = 0.0; t < time_horizon; t += time_resolution) { + double velocity; + double length; + + if (t < delay_until_departure) { + // Before the departure, the velocity is 0 and there's no change in position. + velocity = 0.0; + length = 0.0; + } else { + // Adjust time to consider the delay. + double t_with_delay = t - delay_until_departure; + velocity = + std::clamp(current_velocity + acceleration * t_with_delay, min_velocity, max_velocity); + length = current_velocity * t_with_delay + 0.5 * acceleration * t_with_delay * t_with_delay; + } + const auto pose = motion_utils::calcInterpolatedPose(path_points, vehicle_pose_frenet.length + length); predicted_path.emplace_back(t, pose, velocity); From f1b35388d3375d47b15f55b94018ee89fc12a182 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Wed, 20 Sep 2023 09:48:58 +0900 Subject: [PATCH 242/547] refactor(avoidance): clean up markers (#4975) * feat(avoidance): clean up markers Signed-off-by: satoshi-ota * feat(avoidance): clean up Signed-off-by: satoshi-ota * chore(avoidance): fix description Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.hpp | 4 +- .../utils/avoidance/avoidance_module_data.hpp | 24 +++--- .../avoidance/avoidance_module.cpp | 77 +++++++++---------- 3 files changed, 49 insertions(+), 56 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp index a5f666099a861..a90bec18e093e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp @@ -263,10 +263,10 @@ class AvoidanceModule : public SceneModuleInterface * @brief fill candidate shift lines. * @param avoidance data. * @param debug data. - * @details in this function, three shift line sets are generated. + * @details in this function, following two shift line arrays are generated. * - unapproved raw shift lines. * - unapproved new shift lines. - * - safe new shift lines. (avoidance path is generated from this shift line set.) + * and check whether the new shift lines are safe or not. */ void fillShiftLine(AvoidancePlanningData & data, DebugData & debug) const; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 83880aa6e4f82..634ac69bae12c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -110,12 +110,6 @@ struct AvoidanceParameters // use intersection area for avoidance bool use_intersection_areas{false}; - // // constrains - // bool use_constraints_for_decel{false}; - - // // policy - // bool use_relaxed_margin_immediately{false}; - // max deceleration for double max_deceleration{0.0}; @@ -488,13 +482,13 @@ struct AvoidancePlanningData boost::optional stop_target_object{boost::none}; // raw shift point - AvoidLineArray unapproved_raw_sl{}; + AvoidLineArray raw_shift_line{}; // new shift point - AvoidLineArray unapproved_new_sl{}; + AvoidLineArray new_shift_line{}; // safe shift point - AvoidLineArray safe_new_sl{}; + AvoidLineArray safe_shift_line{}; bool safe{false}; @@ -546,7 +540,7 @@ struct DebugData // combine process AvoidLineArray step1_registered_shift_line; - AvoidLineArray step1_current_raw_shift_line; + AvoidLineArray step1_current_shift_line; AvoidLineArray step1_filled_shift_line; AvoidLineArray step1_merged_shift_line; AvoidLineArray step1_combined_shift_line; @@ -557,11 +551,11 @@ struct DebugData AvoidLineArray step2_merged_shift_line; // trimming process - AvoidLineArray step3_quantized_shift_line; - AvoidLineArray step3_noise_removed; - AvoidLineArray step3_grad_filtered_first; - AvoidLineArray step3_grad_filtered_second; - AvoidLineArray step3_grad_filtered_third; + AvoidLineArray step3_quantize_filtered; + AvoidLineArray step3_noise_filtered; + AvoidLineArray step3_grad_filtered_1st; + AvoidLineArray step3_grad_filtered_2nd; + AvoidLineArray step3_grad_filtered_3rd; // registered process AvoidLineArray step4_new_shift_line; diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 26a0df487ef78..00c8c55c97918 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -166,7 +166,7 @@ bool AvoidanceModule::isExecutionRequested() const return true; } - if (avoid_data_.unapproved_new_sl.empty()) { + if (avoid_data_.new_shift_line.empty()) { return false; } @@ -430,14 +430,14 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de /** * STEP2: Create rough shift lines. */ - data.unapproved_raw_sl = applyPreProcess(outlines, debug); + data.raw_shift_line = applyPreProcess(outlines, debug); /** * STEP3: Create candidate shift lines. * Merge rough shift lines, and extract new shift lines. */ - data.unapproved_new_sl = generateCandidateShiftLine(data.unapproved_raw_sl, path_shifter, debug); - const auto found_new_sl = data.unapproved_new_sl.size() > 0; + data.new_shift_line = generateCandidateShiftLine(data.raw_shift_line, path_shifter, debug); + const auto found_new_sl = data.new_shift_line.size() > 0; const auto registered = path_shifter.getShiftLines().size() > 0; data.found_avoidance_path = found_new_sl || registered; @@ -446,8 +446,8 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de * If there are new shift points, these shift points are registered in path_shifter in order to * generate candidate avoidance path. */ - if (!data.unapproved_new_sl.empty()) { - addNewShiftLines(path_shifter, data.unapproved_new_sl); + if (!data.new_shift_line.empty()) { + addNewShiftLines(path_shifter, data.new_shift_line); } /** @@ -465,7 +465,7 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de * For each target objects and the objects in adjacent lanes, * check that there is a certain amount of margin in the lateral and longitudinal direction. */ - data.comfortable = isComfortable(data.unapproved_new_sl); + data.comfortable = isComfortable(data.new_shift_line); data.safe = isSafePath(data.candidate_path, debug); } @@ -496,7 +496,7 @@ void AvoidanceModule::fillEgoStatus( * If the output path is locked by outside of this module, don't update output path. */ if (isOutputPathLocked()) { - data.safe_new_sl.clear(); + data.safe_shift_line.clear(); data.candidate_path = helper_.getPreviousSplineShiftPath(); RCLCPP_DEBUG_THROTTLE( getLogger(), *clock_, 500, "this module is locked now. keep current path."); @@ -508,7 +508,7 @@ void AvoidanceModule::fillEgoStatus( */ if (data.safe) { data.yield_required = false; - data.safe_new_sl = data.unapproved_new_sl; + data.safe_shift_line = data.new_shift_line; return; } @@ -518,7 +518,7 @@ void AvoidanceModule::fillEgoStatus( */ if (!parameters_->enable_yield_maneuver) { data.yield_required = false; - data.safe_new_sl = data.unapproved_new_sl; + data.safe_shift_line = data.new_shift_line; return; } @@ -530,7 +530,7 @@ void AvoidanceModule::fillEgoStatus( if (!can_yield_maneuver) { data.safe = true; // overwrite safety judge. data.yield_required = false; - data.safe_new_sl = data.unapproved_new_sl; + data.safe_shift_line = data.new_shift_line; RCLCPP_WARN_THROTTLE(getLogger(), *clock_, 500, "unsafe. but could not transit yield status."); return; } @@ -540,7 +540,7 @@ void AvoidanceModule::fillEgoStatus( */ { data.yield_required = true; - data.safe_new_sl = data.unapproved_new_sl; + data.safe_shift_line = data.new_shift_line; } /** @@ -573,7 +573,7 @@ void AvoidanceModule::fillDebugData( return; } - if (data.unapproved_new_sl.empty()) { + if (data.new_shift_line.empty()) { return; } @@ -1080,7 +1080,7 @@ AvoidOutlines AvoidanceModule::generateAvoidOutline( utils::avoidance::fillAdditionalInfoFromLongitudinal(data, outlines); - debug.step1_current_raw_shift_line = toArray(outlines); + debug.step1_current_shift_line = toArray(outlines); return outlines; } @@ -1488,7 +1488,7 @@ AvoidLineArray AvoidanceModule::applyTrimProcess( { const auto THRESHOLD = parameters_->same_grad_filter_1_threshold; applySimilarGradFilter(sl_array_trimmed, THRESHOLD); - debug.step3_grad_filtered_first = sl_array_trimmed; + debug.step3_grad_filtered_1st = sl_array_trimmed; } // - Quantize the shift length to reduce the shift point noise @@ -1496,28 +1496,28 @@ AvoidLineArray AvoidanceModule::applyTrimProcess( { const auto THRESHOLD = parameters_->quantize_filter_threshold; applyQuantizeProcess(sl_array_trimmed, THRESHOLD); - debug.step3_quantized_shift_line = sl_array_trimmed; + debug.step3_quantize_filtered = sl_array_trimmed; } // - Change the shift length to the previous one if the deviation is small. { constexpr double SHIFT_DIFF_THRES = 1.0; applySmallShiftFilter(sl_array_trimmed, SHIFT_DIFF_THRES); - debug.step3_noise_removed = sl_array_trimmed; + debug.step3_noise_filtered = sl_array_trimmed; } // - Combine avoid points that have almost same gradient (again) { const auto THRESHOLD = parameters_->same_grad_filter_2_threshold; applySimilarGradFilter(sl_array_trimmed, THRESHOLD); - debug.step3_grad_filtered_second = sl_array_trimmed; + debug.step3_grad_filtered_2nd = sl_array_trimmed; } // - Combine avoid points that have almost same gradient (again) { const auto THRESHOLD = parameters_->same_grad_filter_3_threshold; applySimilarGradFilter(sl_array_trimmed, THRESHOLD); - debug.step3_grad_filtered_third = sl_array_trimmed; + debug.step3_grad_filtered_3rd = sl_array_trimmed; } return sl_array_trimmed; @@ -1989,7 +1989,7 @@ BehaviorModuleOutput AvoidanceModule::plan() resetPathCandidate(); resetPathReference(); - updatePathShifter(data.safe_new_sl); + updatePathShifter(data.safe_shift_line); if (data.yield_required) { removeRegisteredShiftLines(); @@ -2073,14 +2073,14 @@ CandidateOutput AvoidanceModule::planCandidate() const auto shifted_path = data.candidate_path; - if (!data.safe_new_sl.empty()) { // clip from shift start index for visualize + if (!data.safe_shift_line.empty()) { // clip from shift start index for visualize utils::clipPathLength( - shifted_path.path, data.safe_new_sl.front().start_idx, std::numeric_limits::max(), + shifted_path.path, data.safe_shift_line.front().start_idx, std::numeric_limits::max(), 0.0); - const auto sl = helper_.getMainShiftLine(data.safe_new_sl); - const auto sl_front = data.safe_new_sl.front(); - const auto sl_back = data.safe_new_sl.back(); + const auto sl = helper_.getMainShiftLine(data.safe_shift_line); + const auto sl_front = data.safe_shift_line.front(); + const auto sl_back = data.safe_shift_line.back(); output.lateral_shift = helper_.getRelativeShiftToPath(sl); output.start_distance_to_path_change = sl_front.start_longitudinal; @@ -2137,7 +2137,7 @@ void AvoidanceModule::updatePathShifter(const AvoidLineArray & shift_lines) addNewShiftLines(path_shifter_, shift_lines); - current_raw_shift_lines_ = avoid_data_.unapproved_raw_sl; + current_raw_shift_lines_ = avoid_data_.raw_shift_line; registerRawShiftLines(shift_lines); @@ -2432,7 +2432,7 @@ void AvoidanceModule::updateRTCData() updateRegisteredRTCStatus(helper_.getPreviousSplineShiftPath().path); - const auto candidates = data.safe ? data.safe_new_sl : data.unapproved_new_sl; + const auto candidates = data.safe ? data.safe_shift_line : data.new_shift_line; if (candidates.empty()) { removeCandidateRTCStatus(); @@ -2636,31 +2636,30 @@ void AvoidanceModule::updateDebugMarker( // shift line pre-process { - addAvoidLine(debug.step1_registered_shift_line, "step1_registered_shift_line", 1.0, 1.0, 1.0); - addAvoidLine(debug.step1_current_raw_shift_line, "step1_current_raw_shift_line", 0.9, 1.0, 1.0); - addAvoidLine(debug.step1_return_shift_line, "step1_return_shift_line", 0.8, 1.0, 1.0); - addAvoidLine(debug.step1_filled_shift_line, "step1_filled_shift_line", 0.7, 1.0, 1.0); + addAvoidLine(debug.step1_registered_shift_line, "step1_registered_shift_line", 0.2, 0.2, 1.0); + addAvoidLine(debug.step1_current_shift_line, "step1_current_shift_line", 0.2, 0.4, 0.8, 0.3); + addAvoidLine(debug.step1_merged_shift_line, "step1_merged_shift_line", 0.2, 0.6, 0.6, 0.3); + addAvoidLine(debug.step1_filled_shift_line, "step1_filled_shift_line", 0.2, 0.8, 0.4, 0.3); + addAvoidLine(debug.step1_return_shift_line, "step1_return_shift_line", 0.2, 1.0, 0.2, 0.3); } // merge process { - addAvoidLine(debug.step2_merged_shift_line, "step2_merged_shift_line", 0.0, 1.0, 1.0); + addAvoidLine(debug.step2_merged_shift_line, "step2_merged_shift_line", 0.2, 1.0, 0.0, 0.3); } // trimming process { - addAvoidLine(debug.step3_grad_filtered_first, "step3_grad_filtered_first", 0.0, 0.0, 1.0); - addAvoidLine(debug.step3_grad_filtered_second, "step3_grad_filtered_second", 0.0, 0.1, 1.0); - addAvoidLine(debug.step3_grad_filtered_third, "step3_grad_filtered_third", 0.0, 0.2, 1.0); - addAvoidLine(debug.step3_quantized_shift_line, "step3_quantized_shift_line", 0.0, 0.3, 1.0); - addAvoidLine(debug.step3_noise_removed, "step3_noise_removed", 0.0, 0.4, 1.0); + addAvoidLine(debug.step3_grad_filtered_1st, "step3_grad_filtered_1st", 0.2, 0.8, 0.0, 0.3); + addAvoidLine(debug.step3_grad_filtered_2nd, "step3_grad_filtered_2nd", 0.4, 0.6, 0.0, 0.3); + addAvoidLine(debug.step3_grad_filtered_3rd, "step3_grad_filtered_3rd", 0.6, 0.4, 0.0, 0.3); } // registering process { addShiftLine(shifter.getShiftLines(), "step4_old_shift_line", 1.0, 1.0, 0.0, 0.3); - addAvoidLine(debug.step4_new_shift_line, "step4_new_shift_line", 1.0, 0.0, 0.0, 0.3); - addAvoidLine(data.unapproved_new_sl, "step4_valid_shift_line", 1.0, 0.0, 0.0, 0.3); + addAvoidLine(data.raw_shift_line, "step4_raw_shift_line", 1.0, 0.0, 0.0, 0.3); + addAvoidLine(data.new_shift_line, "step4_new_shift_line", 1.0, 0.0, 0.0, 0.3); } // safety check From 33dcea4aecaddf5ffad0a4666e6fff1d387cbd71 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Wed, 20 Sep 2023 09:56:29 +0900 Subject: [PATCH 243/547] chore(radar_object_tracker): fix spell-check error (#5024) * chore(radar_object_tracker): fix spell-check errors Signed-off-by: kminoda * Finally perception directory has been removed from cspell ignorance Signed-off-by: kminoda * fix Signed-off-by: kminoda * style(pre-commit): autofix * revert fix of spell-check dict Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../tracker/model/linear_motion_tracker.cpp | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp b/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp index bfd84159706fb..62880d9471047 100644 --- a/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp +++ b/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp @@ -91,15 +91,15 @@ LinearMotionTracker::LinearMotionTracker( R << cos_yaw, -sin_yaw, sin_yaw, cos_yaw; // covariance matrix in the target vehicle coordinate system - Eigen::Matrix2d P_xy_local, P_vxy_local, P_axy_local; + Eigen::Matrix2d P_xy_local, P_v_xy_local, P_a_xy_local; P_xy_local << ekf_params_.p0_cov_x, 0.0, 0.0, ekf_params_.p0_cov_y; - P_vxy_local << ekf_params_.p0_cov_vx, 0.0, 0.0, ekf_params_.p0_cov_vy; - P_axy_local << ekf_params_.p0_cov_ax, 0.0, 0.0, ekf_params_.p0_cov_ay; + P_v_xy_local << ekf_params_.p0_cov_vx, 0.0, 0.0, ekf_params_.p0_cov_vy; + P_a_xy_local << ekf_params_.p0_cov_ax, 0.0, 0.0, ekf_params_.p0_cov_ay; // Rotated covariance matrix // covariance is rotated by 2D rotation matrix R // P′=R P RT - Eigen::Matrix2d P_xy, P_vxy, P_axy; + Eigen::Matrix2d P_xy, P_v_xy, P_a_xy; // Rotate the covariance matrix according to the vehicle yaw // because p0_cov_x and y are in the vehicle coordinate system. @@ -117,12 +117,12 @@ LinearMotionTracker::LinearMotionTracker( if (object.kinematics.has_twist_covariance) { const auto vx_cov = object.kinematics.twist_with_covariance.covariance[utils::MSG_COV_IDX::X_X]; const auto vy_cov = object.kinematics.twist_with_covariance.covariance[utils::MSG_COV_IDX::Y_Y]; - P_vxy_local << vx_cov, 0.0, 0.0, vy_cov; - P_vxy = R * P_vxy_local * R.transpose(); + P_v_xy_local << vx_cov, 0.0, 0.0, vy_cov; + P_v_xy = R * P_v_xy_local * R.transpose(); } else { - P_vxy = R * P_vxy_local * R.transpose(); + P_v_xy = R * P_v_xy_local * R.transpose(); } - // acceleration covariance often writen in object frame + // acceleration covariance often written in object frame const bool has_acceleration_covariance = false; // currently message does not have acceleration covariance if (has_acceleration_covariance) { @@ -132,18 +132,18 @@ LinearMotionTracker::LinearMotionTracker( // const auto ay_cov = // object.kinematics.acceleration_with_covariance.covariance[utils::MSG_COV_IDX::Y_Y]; // This // is future update - // Eigen::Matrix2d P_axy_local; - // P_axy_local << ax_cov, 0.0, 0.0, ay_cov; - P_axy = R * P_axy_local * R.transpose(); + // Eigen::Matrix2d P_a_xy_local; + // P_a_xy_local << ax_cov, 0.0, 0.0, ay_cov; + P_a_xy = R * P_a_xy_local * R.transpose(); } else { - P_axy = R * P_axy_local * R.transpose(); + P_a_xy = R * P_a_xy_local * R.transpose(); } // put value in P matrix // use block description. This assume x,y,vx,vy,ax,ay in this order P.block<2, 2>(IDX::X, IDX::X) = P_xy; - P.block<2, 2>(IDX::VX, IDX::VX) = P_vxy; - P.block<2, 2>(IDX::AX, IDX::AX) = P_axy; + P.block<2, 2>(IDX::VX, IDX::VX) = P_v_xy; + P.block<2, 2>(IDX::AX, IDX::AX) = P_a_xy; // init shape if (object.shape.type == autoware_auto_perception_msgs::msg::Shape::BOUNDING_BOX) { @@ -292,8 +292,8 @@ bool LinearMotionTracker::predict(const double dt, KalmanFilter & ekf) const // system noise in local coordinate // we assume acceleration random walk model // - // Q_local = [dt^3/6 0 dt^2/2 0 dt 0] ^ T qcov_ax [dt^3/6 0 dt^2/2 0 dt 0] - // + [0 dt^3/6 0 dt^2/2 0 dt] ^ T qcov_ay [0 dt^3/6 0 dt^2/2 0 dt] + // Q_local = [dt^3/6 0 dt^2/2 0 dt 0] ^ T q_cov_ax [dt^3/6 0 dt^2/2 0 dt 0] + // + [0 dt^3/6 0 dt^2/2 0 dt] ^ T q_cov_ay [0 dt^3/6 0 dt^2/2 0 dt] // Eigen::MatrixXd qx = Eigen::MatrixXd::Zero(ekf_params_.dim_x, 1); // Eigen::MatrixXd qy = Eigen::MatrixXd::Zero(ekf_params_.dim_x, 1); // qx << dt * dt * dt / 6, 0, dt * dt / 2, 0, dt, 0; @@ -386,10 +386,10 @@ bool LinearMotionTracker::measureWithPose( // 2. add linear velocity measurement const bool enable_velocity_measurement = object.kinematics.has_twist; if (enable_velocity_measurement) { - Eigen::MatrixXd Cvxvy = Eigen::MatrixXd::Zero(2, ekf_params_.dim_x); - Cvxvy(0, IDX::VX) = 1; - Cvxvy(1, IDX::VY) = 1; - C_list.push_back(Cvxvy); + Eigen::MatrixXd C_vx_vy = Eigen::MatrixXd::Zero(2, ekf_params_.dim_x); + C_vx_vy(0, IDX::VX) = 1; + C_vx_vy(1, IDX::VY) = 1; + C_list.push_back(C_vx_vy); // velocity is in the target vehicle coordinate system Eigen::MatrixXd Vxy_local = Eigen::MatrixXd::Zero(2, 1); @@ -399,19 +399,19 @@ bool LinearMotionTracker::measureWithPose( Vxy = RotationYaw * Vxy_local; Y_list.push_back(Vxy); - Eigen::Matrix2d Rvxy_local = Eigen::MatrixXd::Zero(2, 2); + Eigen::Matrix2d R_v_xy_local = Eigen::MatrixXd::Zero(2, 2); if (!object.kinematics.has_twist_covariance) { - Rvxy_local << ekf_params_.r_cov_vx, 0, 0, ekf_params_.r_cov_vy; + R_v_xy_local << ekf_params_.r_cov_vx, 0, 0, ekf_params_.r_cov_vy; } else { - Rvxy_local << object.kinematics.twist_with_covariance.covariance[utils::MSG_COV_IDX::X_X], 0, - 0, object.kinematics.twist_with_covariance.covariance[utils::MSG_COV_IDX::Y_Y]; + R_v_xy_local << object.kinematics.twist_with_covariance.covariance[utils::MSG_COV_IDX::X_X], + 0, 0, object.kinematics.twist_with_covariance.covariance[utils::MSG_COV_IDX::Y_Y]; } - Eigen::MatrixXd Rvxy = Eigen::MatrixXd::Zero(2, 2); - Rvxy = RotationYaw * Rvxy_local * RotationYaw.transpose(); - R_block_list.push_back(Rvxy); + Eigen::MatrixXd R_v_xy = Eigen::MatrixXd::Zero(2, 2); + R_v_xy = RotationYaw * R_v_xy_local * RotationYaw.transpose(); + R_block_list.push_back(R_v_xy); } - // 3. sumup matrices + // 3. sum up matrices const auto row_number = std::accumulate( C_list.begin(), C_list.end(), 0, [](const auto & sum, const auto & C) { return sum + C.rows(); }); @@ -522,9 +522,9 @@ bool LinearMotionTracker::getTrackedObject( auto & twist_with_cov = object.kinematics.twist_with_covariance; auto & acceleration_with_cov = object.kinematics.acceleration_with_covariance; // rotation matrix with yaw_ - Eigen::Matrix2d Ryaw = Eigen::Matrix2d::Zero(); - Ryaw << std::cos(yaw_), -std::sin(yaw_), std::sin(yaw_), std::cos(yaw_); - const Eigen::Matrix2d Ryaw_inv = Ryaw.transpose(); + Eigen::Matrix2d R_yaw = Eigen::Matrix2d::Zero(); + R_yaw << std::cos(yaw_), -std::sin(yaw_), std::sin(yaw_), std::cos(yaw_); + const Eigen::Matrix2d R_yaw_inv = R_yaw.transpose(); // position pose_with_cov.pose.position.x = X_t(IDX::X); @@ -550,41 +550,41 @@ bool LinearMotionTracker::getTrackedObject( const auto vx = X_t(IDX::VX); const auto vy = X_t(IDX::VY); // rotate this vector with -yaw - Eigen::Vector2d vxy = Ryaw_inv * Eigen::Vector2d(vx, vy); - twist_with_cov.twist.linear.x = vxy(0); - twist_with_cov.twist.linear.y = vxy(1); + Eigen::Vector2d v_local = R_yaw_inv * Eigen::Vector2d(vx, vy); + twist_with_cov.twist.linear.x = v_local(0); + twist_with_cov.twist.linear.y = v_local(1); // acceleration // acceleration need to converted to local coordinate const auto ax = X_t(IDX::AX); const auto ay = X_t(IDX::AY); // rotate this vector with -yaw - Eigen::Vector2d axy = Ryaw_inv * Eigen::Vector2d(ax, ay); - acceleration_with_cov.accel.linear.x = axy(0); - acceleration_with_cov.accel.linear.y = axy(1); + Eigen::Vector2d a_local = R_yaw_inv * Eigen::Vector2d(ax, ay); + acceleration_with_cov.accel.linear.x = a_local(0); + acceleration_with_cov.accel.linear.y = a_local(1); // ===== covariance transformation ===== // since covariance in EKF is in map coordinate and output should be in object coordinate, // we need to transform covariance - Eigen::Matrix2d Pxy_map, Pvxy_map, Paxy_map; - Pxy_map << P(IDX::X, IDX::X), P(IDX::X, IDX::Y), P(IDX::Y, IDX::X), P(IDX::Y, IDX::Y); - Pvxy_map << P(IDX::VX, IDX::VX), P(IDX::VX, IDX::VY), P(IDX::VY, IDX::VX), P(IDX::VY, IDX::VY); - Paxy_map << P(IDX::AX, IDX::AX), P(IDX::AX, IDX::AY), P(IDX::AY, IDX::AX), P(IDX::AY, IDX::AY); + Eigen::Matrix2d P_xy_map, P_v_xy_map, P_a_xy_map; + P_xy_map << P(IDX::X, IDX::X), P(IDX::X, IDX::Y), P(IDX::Y, IDX::X), P(IDX::Y, IDX::Y); + P_v_xy_map << P(IDX::VX, IDX::VX), P(IDX::VX, IDX::VY), P(IDX::VY, IDX::VX), P(IDX::VY, IDX::VY); + P_a_xy_map << P(IDX::AX, IDX::AX), P(IDX::AX, IDX::AY), P(IDX::AY, IDX::AX), P(IDX::AY, IDX::AY); // rotate covariance with -yaw - Eigen::Matrix2d Pxy = Ryaw_inv * Pxy_map * Ryaw_inv.transpose(); - Eigen::Matrix2d Pvxy = Ryaw_inv * Pvxy_map * Ryaw_inv.transpose(); - Eigen::Matrix2d Paxy = Ryaw_inv * Paxy_map * Ryaw_inv.transpose(); + Eigen::Matrix2d P_xy = R_yaw_inv * P_xy_map * R_yaw_inv.transpose(); + Eigen::Matrix2d P_v_xy = R_yaw_inv * P_v_xy_map * R_yaw_inv.transpose(); + Eigen::Matrix2d P_a_xy = R_yaw_inv * P_a_xy_map * R_yaw_inv.transpose(); // position covariance constexpr double no_info_cov = 1e9; // no information constexpr double z_cov = 1. * 1.; // TODO(yukkysaito) Currently tentative constexpr double yaw_cov = 0.1 * 0.1; // TODO(yukkysaito) Currently tentative - pose_with_cov.covariance[utils::MSG_COV_IDX::X_X] = Pxy(IDX::X, IDX::X); - pose_with_cov.covariance[utils::MSG_COV_IDX::X_Y] = Pxy(IDX::X, IDX::Y); - pose_with_cov.covariance[utils::MSG_COV_IDX::Y_X] = Pxy(IDX::Y, IDX::X); - pose_with_cov.covariance[utils::MSG_COV_IDX::Y_Y] = Pxy(IDX::Y, IDX::Y); + pose_with_cov.covariance[utils::MSG_COV_IDX::X_X] = P_xy(IDX::X, IDX::X); + pose_with_cov.covariance[utils::MSG_COV_IDX::X_Y] = P_xy(IDX::X, IDX::Y); + pose_with_cov.covariance[utils::MSG_COV_IDX::Y_X] = P_xy(IDX::Y, IDX::X); + pose_with_cov.covariance[utils::MSG_COV_IDX::Y_Y] = P_xy(IDX::Y, IDX::Y); pose_with_cov.covariance[utils::MSG_COV_IDX::Z_Z] = z_cov; pose_with_cov.covariance[utils::MSG_COV_IDX::ROLL_ROLL] = no_info_cov; pose_with_cov.covariance[utils::MSG_COV_IDX::PITCH_PITCH] = no_info_cov; @@ -594,20 +594,20 @@ bool LinearMotionTracker::getTrackedObject( constexpr double vz_cov = 0.2 * 0.2; // TODO(Yoshi Ri) Currently tentative constexpr double wz_cov = 0.2 * 0.2; // TODO(yukkysaito) Currently tentative - twist_with_cov.covariance[utils::MSG_COV_IDX::X_X] = Pvxy(IDX::X, IDX::X); - twist_with_cov.covariance[utils::MSG_COV_IDX::X_Y] = Pvxy(IDX::X, IDX::Y); - twist_with_cov.covariance[utils::MSG_COV_IDX::Y_X] = Pvxy(IDX::Y, IDX::X); - twist_with_cov.covariance[utils::MSG_COV_IDX::Y_Y] = Pvxy(IDX::Y, IDX::Y); + twist_with_cov.covariance[utils::MSG_COV_IDX::X_X] = P_v_xy(IDX::X, IDX::X); + twist_with_cov.covariance[utils::MSG_COV_IDX::X_Y] = P_v_xy(IDX::X, IDX::Y); + twist_with_cov.covariance[utils::MSG_COV_IDX::Y_X] = P_v_xy(IDX::Y, IDX::X); + twist_with_cov.covariance[utils::MSG_COV_IDX::Y_Y] = P_v_xy(IDX::Y, IDX::Y); twist_with_cov.covariance[utils::MSG_COV_IDX::Z_Z] = vz_cov; twist_with_cov.covariance[utils::MSG_COV_IDX::ROLL_ROLL] = no_info_cov; twist_with_cov.covariance[utils::MSG_COV_IDX::PITCH_PITCH] = no_info_cov; twist_with_cov.covariance[utils::MSG_COV_IDX::YAW_YAW] = wz_cov; // acceleration covariance - acceleration_with_cov.covariance[utils::MSG_COV_IDX::X_X] = Paxy(IDX::X, IDX::X); - acceleration_with_cov.covariance[utils::MSG_COV_IDX::X_Y] = Paxy(IDX::X, IDX::Y); - acceleration_with_cov.covariance[utils::MSG_COV_IDX::Y_X] = Paxy(IDX::Y, IDX::X); - acceleration_with_cov.covariance[utils::MSG_COV_IDX::Y_Y] = Paxy(IDX::Y, IDX::Y); + acceleration_with_cov.covariance[utils::MSG_COV_IDX::X_X] = P_a_xy(IDX::X, IDX::X); + acceleration_with_cov.covariance[utils::MSG_COV_IDX::X_Y] = P_a_xy(IDX::X, IDX::Y); + acceleration_with_cov.covariance[utils::MSG_COV_IDX::Y_X] = P_a_xy(IDX::Y, IDX::X); + acceleration_with_cov.covariance[utils::MSG_COV_IDX::Y_Y] = P_a_xy(IDX::Y, IDX::Y); acceleration_with_cov.covariance[utils::MSG_COV_IDX::Z_Z] = no_info_cov; acceleration_with_cov.covariance[utils::MSG_COV_IDX::ROLL_ROLL] = no_info_cov; acceleration_with_cov.covariance[utils::MSG_COV_IDX::PITCH_PITCH] = no_info_cov; From fa96a3204fb2559357daac639034443f5781040f Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 20 Sep 2023 10:03:22 +0900 Subject: [PATCH 244/547] fix(behavior_path_planner): fix Werror=maybe-uninitialized (#5039) Signed-off-by: kosuke55 --- .../behavior_path_planner/src/utils/utils.cpp | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 3b006cc120efb..ba6eb45267ba3 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -2443,21 +2443,16 @@ std::optional getSignedDistanceFromBoundary( const auto & bound_line_2d = left_side ? lanelet::utils::to2D(combined_lane.leftBound3d()) : lanelet::utils::to2D(combined_lane.rightBound3d()); - // Initialize the lateral distance to the maximum (for left side) or the minimum (for right side) - // possible value. - double lateral_distance = - left_side ? -std::numeric_limits::max() : std::numeric_limits::max(); - // Find the closest bound segment that contains the corner point in the X-direction // and calculate the lateral distance from that segment. - const auto calcLateralDistanceFromBound = [&]( - const Point & vehicle_corner_point, - boost::optional & lateral_distance, - boost::optional & segment_idx) { + const auto calcLateralDistanceFromBound = + [&](const Point & vehicle_corner_point) -> boost::optional> { Pose vehicle_corner_pose{}; vehicle_corner_pose.position = vehicle_corner_point; vehicle_corner_pose.orientation = vehicle_pose.orientation; + boost::optional> lateral_distance_with_idx{}; + // Euclidean distance to find the closest segment containing the corner point. double min_distance = std::numeric_limits::max(); @@ -2483,38 +2478,45 @@ std::optional getSignedDistanceFromBoundary( min_distance = std::min(distance1, distance2); // Update lateral distance using the formula derived from similar triangles in the lateral // cross-section view. - lateral_distance = -1.0 * (dy_p1 * dx_p2 + dy_p2 * -dx_p1) / (dx_p2 - dx_p1); - segment_idx = i; + lateral_distance_with_idx = + std::make_pair(-1.0 * (dy_p1 * dx_p2 + dy_p2 * -dx_p1) / (dx_p2 - dx_p1), i); } } + if (lateral_distance_with_idx) { + return lateral_distance_with_idx; + } + return boost::optional>{}; }; // Calculate the lateral distance for both the rear and front corners of the vehicle. - boost::optional rear_segment_idx{}; - boost::optional rear_lateral_distance{}; - calcLateralDistanceFromBound(rear_corner_point, rear_lateral_distance, rear_segment_idx); - boost::optional front_segment_idx{}; - boost::optional front_lateral_distance{}; - calcLateralDistanceFromBound(front_corner_point, front_lateral_distance, front_segment_idx); + const boost::optional> rear_lateral_distance_with_idx = + calcLateralDistanceFromBound(rear_corner_point); + const boost::optional> front_lateral_distance_with_idx = + calcLateralDistanceFromBound(front_corner_point); // If no closest bound segment was found for both corners, return an empty optional. - if (!rear_lateral_distance && !front_lateral_distance) { + if (!rear_lateral_distance_with_idx && !front_lateral_distance_with_idx) { return {}; } // If only one of them found the closest bound, return the found lateral distance. - if (!rear_lateral_distance) { - return *front_lateral_distance; - } else if (!front_lateral_distance) { - return *rear_lateral_distance; + if (!rear_lateral_distance_with_idx) { + return front_lateral_distance_with_idx.get().first; + } else if (!front_lateral_distance_with_idx) { + return rear_lateral_distance_with_idx.get().first; } // If both corners found their closest bound, return the maximum (for left side) or the minimum // (for right side) lateral distance. - lateral_distance = left_side ? std::max(*rear_lateral_distance, *front_lateral_distance) - : std::min(*rear_lateral_distance, *front_lateral_distance); + double lateral_distance = + left_side + ? std::max( + rear_lateral_distance_with_idx.get().first, front_lateral_distance_with_idx.get().first) + : std::min( + rear_lateral_distance_with_idx.get().first, front_lateral_distance_with_idx.get().first); // Iterate through all segments between the segments closest to the rear and front corners. // Update the lateral distance in case any of these inner segments are closer to the vehicle. - for (size_t i = *rear_segment_idx + 1; i < *front_segment_idx; i++) { + for (size_t i = rear_lateral_distance_with_idx.get().second + 1; + i < front_lateral_distance_with_idx.get().second; i++) { Pose bound_pose; bound_pose.position = lanelet::utils::conversion::toGeomMsgPt(bound_line_2d[i]); bound_pose.orientation = vehicle_pose.orientation; From 909f12f353377d3e6c104cdab7671be008dac481 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:24:03 +0900 Subject: [PATCH 245/547] refactor(behavior_path_planner): make object type filter method common (#5035) Signed-off-by: Zulfaqar Azmi --- .../lane_change/lane_change_module_data.hpp | 11 ++----- .../utils/lane_change/utils.hpp | 2 -- .../path_safety_checker/objects_filtering.hpp | 14 +++++++++ .../src/scene_module/lane_change/manager.cpp | 18 ++++++----- .../src/scene_module/lane_change/normal.cpp | 14 ++++----- .../src/utils/lane_change/utils.cpp | 16 ---------- .../path_safety_checker/objects_filtering.cpp | 30 ++++++++++--------- 7 files changed, 49 insertions(+), 56 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index e10ba6c274887..c78be341b4e16 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -68,15 +68,8 @@ struct LaneChangeParameters bool check_objects_on_other_lanes{true}; bool use_all_predicted_path{false}; - // true by default - bool check_car{true}; // check object car - bool check_truck{true}; // check object truck - bool check_bus{true}; // check object bus - bool check_trailer{true}; // check object trailer - bool check_unknown{true}; // check object unknown - bool check_bicycle{true}; // check object bicycle - bool check_motorcycle{true}; // check object motorbike - bool check_pedestrian{true}; // check object pedestrian + // true by default for all objects + utils::path_safety_checker::ObjectTypesToCheck object_types_to_check; // safety check utils::path_safety_checker::RSSparams rss_params{}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index 0c1fff52c74e2..a4c2c2e695752 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -142,8 +142,6 @@ lanelet::ConstLanelets getBackwardLanelets( const RouteHandler & route_handler, const lanelet::ConstLanelets & target_lanes, const Pose & current_pose, const double backward_length); -bool isTargetObjectType(const PredictedObject & object, const LaneChangeParameters & parameters); - double calcLateralBufferForFiltering(const double vehicle_width, const double lateral_buffer = 0.0); double calcLateralBufferForFiltering(const double vehicle_width, const double lateral_buffer); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp index efcf375720924..e1256fd774027 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp @@ -200,6 +200,20 @@ TargetObjectsOnLane createTargetObjectsOnLane( const PredictedObjects & filtered_objects, const std::shared_ptr & params); +/** + * @brief Determines whether the predicted object type matches any of the target object types + * specified by the user. + * + * @param object The predicted object whose type is to be checked. + * @param target_object_types A structure containing boolean flags for each object type that the + * user is interested in checking. + * + * @return Returns true if the predicted object's highest probability label matches any of the + * specified target object types. + */ +bool isTargetObjectType( + const PredictedObject & object, const ObjectTypesToCheck & target_object_types); + } // namespace behavior_path_planner::utils::path_safety_checker #endif // BEHAVIOR_PATH_PLANNER__UTILS__PATH_SAFETY_CHECKER__OBJECTS_FILTERING_HPP_ diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index f4a75daa6c764..e72691ea55391 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -111,14 +111,16 @@ LaneChangeModuleManager::LaneChangeModuleManager( // target object { std::string ns = "lane_change.target_object."; - p.check_car = getOrDeclareParameter(*node, ns + "car"); - p.check_truck = getOrDeclareParameter(*node, ns + "truck"); - p.check_bus = getOrDeclareParameter(*node, ns + "bus"); - p.check_trailer = getOrDeclareParameter(*node, ns + "trailer"); - p.check_unknown = getOrDeclareParameter(*node, ns + "unknown"); - p.check_bicycle = getOrDeclareParameter(*node, ns + "bicycle"); - p.check_motorcycle = getOrDeclareParameter(*node, ns + "motorcycle"); - p.check_pedestrian = getOrDeclareParameter(*node, ns + "pedestrian"); + p.object_types_to_check.check_car = getOrDeclareParameter(*node, ns + "car"); + p.object_types_to_check.check_truck = getOrDeclareParameter(*node, ns + "truck"); + p.object_types_to_check.check_bus = getOrDeclareParameter(*node, ns + "bus"); + p.object_types_to_check.check_trailer = getOrDeclareParameter(*node, ns + "trailer"); + p.object_types_to_check.check_unknown = getOrDeclareParameter(*node, ns + "unknown"); + p.object_types_to_check.check_bicycle = getOrDeclareParameter(*node, ns + "bicycle"); + p.object_types_to_check.check_motorcycle = + getOrDeclareParameter(*node, ns + "motorcycle"); + p.object_types_to_check.check_pedestrian = + getOrDeclareParameter(*node, ns + "pedestrian"); } // lane change cancel diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 333231e0ed0d4..376bac3ed65fb 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -672,20 +672,20 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( const auto dist_ego_to_current_lanes_center = lanelet::utils::getLateralDistanceToClosestLanelet(current_lanes, current_pose); + auto filtered_objects = objects; + + utils::path_safety_checker::filterObjectsByClass( + filtered_objects, lane_change_parameters_->object_types_to_check); + LaneChangeTargetObjectIndices filtered_obj_indices; - for (size_t i = 0; i < objects.objects.size(); ++i) { - const auto & object = objects.objects.at(i); + for (size_t i = 0; i < filtered_objects.objects.size(); ++i) { + const auto & object = filtered_objects.objects.at(i); const auto & obj_velocity_norm = std::hypot( object.kinematics.initial_twist_with_covariance.twist.linear.x, object.kinematics.initial_twist_with_covariance.twist.linear.y); const auto extended_object = utils::lane_change::transform(object, common_parameters, *lane_change_parameters_); - // ignore specific object types - if (!utils::lane_change::isTargetObjectType(object, *lane_change_parameters_)) { - continue; - } - const auto obj_polygon = tier4_autoware_utils::toPolygon2d(object); // calc distance from the current ego position diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index 29c139ab5a654..e4fbf41e5af00 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -657,22 +657,6 @@ lanelet::ConstLanelets getBackwardLanelets( return backward_lanes; } -bool isTargetObjectType(const PredictedObject & object, const LaneChangeParameters & parameters) -{ - using autoware_auto_perception_msgs::msg::ObjectClassification; - const auto t = utils::getHighestProbLabel(object.classification); - const auto is_object_type = - ((t == ObjectClassification::CAR && parameters.check_car) || - (t == ObjectClassification::TRUCK && parameters.check_truck) || - (t == ObjectClassification::BUS && parameters.check_bus) || - (t == ObjectClassification::TRAILER && parameters.check_trailer) || - (t == ObjectClassification::UNKNOWN && parameters.check_unknown) || - (t == ObjectClassification::BICYCLE && parameters.check_bicycle) || - (t == ObjectClassification::MOTORCYCLE && parameters.check_motorcycle) || - (t == ObjectClassification::PEDESTRIAN && parameters.check_pedestrian)); - return is_object_type; -} - double calcLateralBufferForFiltering(const double vehicle_width, const double lateral_buffer) { return lateral_buffer + 0.5 * vehicle_width; diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index 068acc04240f1..e65e76b82a998 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -113,21 +113,10 @@ void filterObjectsByPosition( void filterObjectsByClass( PredictedObjects & objects, const ObjectTypesToCheck & target_object_types) { - using autoware_auto_perception_msgs::msg::ObjectClassification; - PredictedObjects filtered_objects; for (auto & object : objects.objects) { - const auto t = utils::getHighestProbLabel(object.classification); - const auto is_object_type = - ((t == ObjectClassification::CAR && target_object_types.check_car) || - (t == ObjectClassification::TRUCK && target_object_types.check_truck) || - (t == ObjectClassification::BUS && target_object_types.check_bus) || - (t == ObjectClassification::TRAILER && target_object_types.check_trailer) || - (t == ObjectClassification::UNKNOWN && target_object_types.check_unknown) || - (t == ObjectClassification::BICYCLE && target_object_types.check_bicycle) || - (t == ObjectClassification::MOTORCYCLE && target_object_types.check_motorcycle) || - (t == ObjectClassification::PEDESTRIAN && target_object_types.check_pedestrian)); + const auto is_object_type = isTargetObjectType(object, target_object_types); // If the object type matches any of the target types, add it to the filtered list if (is_object_type) { @@ -137,8 +126,6 @@ void filterObjectsByClass( // Replace the original objects with the filtered list objects = std::move(filtered_objects); - - return; } std::pair, std::vector> separateObjectIndicesByLanelets( @@ -380,4 +367,19 @@ TargetObjectsOnLane createTargetObjectsOnLane( return target_objects_on_lane; } +bool isTargetObjectType( + const PredictedObject & object, const ObjectTypesToCheck & target_object_types) +{ + using autoware_auto_perception_msgs::msg::ObjectClassification; + const auto t = utils::getHighestProbLabel(object.classification); + return ( + (t == ObjectClassification::CAR && target_object_types.check_car) || + (t == ObjectClassification::TRUCK && target_object_types.check_truck) || + (t == ObjectClassification::BUS && target_object_types.check_bus) || + (t == ObjectClassification::TRAILER && target_object_types.check_trailer) || + (t == ObjectClassification::UNKNOWN && target_object_types.check_unknown) || + (t == ObjectClassification::BICYCLE && target_object_types.check_bicycle) || + (t == ObjectClassification::MOTORCYCLE && target_object_types.check_motorcycle) || + (t == ObjectClassification::PEDESTRIAN && target_object_types.check_pedestrian)); +} } // namespace behavior_path_planner::utils::path_safety_checker From cddc8f23ad43ab7d861c54c60d9e76f239f6a2ad Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Wed, 20 Sep 2023 11:34:41 +0900 Subject: [PATCH 246/547] feat(ekf_localizer): add diagnostics (#4914) * feat(ekf_localizer): add diagnostics Signed-off-by: yamato-ando * update readme Signed-off-by: yamato-ando * style(pre-commit): autofix * update diag message Signed-off-by: yamato-ando * refactor Signed-off-by: yamato-ando * style(pre-commit): autofix * add OK message Signed-off-by: yamato-ando * fix typo Signed-off-by: yamato-ando * fix typo Signed-off-by: yamato-ando * Update localization/ekf_localizer/src/diagnostics.cpp Co-authored-by: Kento Yabuuchi --------- Signed-off-by: yamato-ando Co-authored-by: yamato-ando Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kento Yabuuchi --- localization/ekf_localizer/CMakeLists.txt | 1 + localization/ekf_localizer/README.md | 30 +++ .../config/ekf_localizer.param.yaml | 6 + .../include/ekf_localizer/diagnostics.hpp | 40 ++++ .../include/ekf_localizer/ekf_localizer.hpp | 29 ++- .../ekf_localizer/hyper_parameters.hpp | 14 +- .../ekf_localizer/media/ekf_diagnostics.png | Bin 0 -> 103904 bytes localization/ekf_localizer/package.xml | 1 + .../ekf_localizer/src/diagnostics.cpp | 169 +++++++++++++++ .../ekf_localizer/src/ekf_localizer.cpp | 107 +++++++++- .../ekf_localizer/test/test_diagnostics.cpp | 192 ++++++++++++++++++ 11 files changed, 576 insertions(+), 13 deletions(-) create mode 100644 localization/ekf_localizer/include/ekf_localizer/diagnostics.hpp create mode 100644 localization/ekf_localizer/media/ekf_diagnostics.png create mode 100644 localization/ekf_localizer/src/diagnostics.cpp create mode 100644 localization/ekf_localizer/test/test_diagnostics.cpp diff --git a/localization/ekf_localizer/CMakeLists.txt b/localization/ekf_localizer/CMakeLists.txt index d5cd85aa2ed85..05c91bff13867 100644 --- a/localization/ekf_localizer/CMakeLists.txt +++ b/localization/ekf_localizer/CMakeLists.txt @@ -16,6 +16,7 @@ ament_auto_find_build_dependencies() ament_auto_add_library(ekf_localizer_lib SHARED src/ekf_localizer.cpp src/covariance.cpp + src/diagnostics.cpp src/mahalanobis.cpp src/measurement.cpp src/state_transition.cpp diff --git a/localization/ekf_localizer/README.md b/localization/ekf_localizer/README.md index 748b5ee5becc0..977e0fceafd9e 100644 --- a/localization/ekf_localizer/README.md +++ b/localization/ekf_localizer/README.md @@ -88,6 +88,10 @@ The parameters and input topic names can be set in the `ekf_localizer.launch` fi The estimated twist with covariance. +- diagnostics (diagnostic_msgs/DiagnosticArray) + + The diagnostic information. + ### Published TF - base_link @@ -148,6 +152,15 @@ The parameters are set in `launch/ekf_localizer.launch` . note: process noise for positions x & y are calculated automatically from nonlinear dynamics. +### For diagnostics + +| Name | Type | Description | Default value | +| :------------------------------------ | :----- | :----------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| pose_no_update_count_threshold_warn | size_t | The threshold at which a WARN state is triggered due to the Pose Topic update not happening continuously for a certain number of times. | 50 | +| pose_no_update_count_threshold_error | size_t | The threshold at which an ERROR state is triggered due to the Pose Topic update not happening continuously for a certain number of times. | 250 | +| twist_no_update_count_threshold_warn | size_t | The threshold at which a WARN state is triggered due to the Twist Topic update not happening continuously for a certain number of times. | 50 | +| twist_no_update_count_threshold_error | size_t | The threshold at which an ERROR state is triggered due to the Twist Topic update not happening continuously for a certain number of times. | 250 | + ## How to tune EKF parameters ### 0. Preliminaries @@ -194,6 +207,23 @@ Note that, although the dimension gets larger since the analytical expansion can

    +## Diagnostics + +

    + +

    + +### The conditions that result in a WARN state + +- The node is not in the activate state. +- The number of consecutive no measurement update via the Pose/Twist topic exceeds the `pose_no_update_count_threshold_warn`/`twist_no_update_count_threshold_warn`. +- The timestamp of the Pose/Twist topic is beyond the delay compensation range. +- The Pose/Twist topic is beyond the range of Mahalanobis distance for covariance estimation. + +### The conditions that result in an ERROR state + +- The number of consecutive no measurement update via the Pose/Twist topic exceeds the `pose_no_update_count_threshold_error`/`twist_no_update_count_threshold_error`. + ## Known issues - In the presence of multiple inputs with yaw estimation, yaw bias `b_k` in the current EKF state would not make any sense, since it is intended to capture the extrinsic parameter's calibration error of a sensor. Thus, future work includes introducing yaw bias for each sensor with yaw estimation. diff --git a/localization/ekf_localizer/config/ekf_localizer.param.yaml b/localization/ekf_localizer/config/ekf_localizer.param.yaml index 4d3f5b9643462..8b24b79e71829 100644 --- a/localization/ekf_localizer/config/ekf_localizer.param.yaml +++ b/localization/ekf_localizer/config/ekf_localizer.param.yaml @@ -21,3 +21,9 @@ proc_stddev_yaw_c: 0.005 proc_stddev_vx_c: 10.0 proc_stddev_wz_c: 5.0 + + # for diagnostics + pose_no_update_count_threshold_warn: 50 + pose_no_update_count_threshold_error: 250 + twist_no_update_count_threshold_warn: 50 + twist_no_update_count_threshold_error: 250 diff --git a/localization/ekf_localizer/include/ekf_localizer/diagnostics.hpp b/localization/ekf_localizer/include/ekf_localizer/diagnostics.hpp new file mode 100644 index 0000000000000..f4dc6436f6a40 --- /dev/null +++ b/localization/ekf_localizer/include/ekf_localizer/diagnostics.hpp @@ -0,0 +1,40 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef EKF_LOCALIZER__DIAGNOSTICS_HPP_ +#define EKF_LOCALIZER__DIAGNOSTICS_HPP_ + +#include + +#include +#include + +diagnostic_msgs::msg::DiagnosticStatus checkProcessActivated(const bool is_activated); + +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementUpdated( + const std::string & measurement_type, const size_t no_update_count, + const size_t no_update_count_threshold_warn, const size_t no_update_count_threshold_error); +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementQueueSize( + const std::string & measurement_type, const size_t queue_size); +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementDelayGate( + const std::string & measurement_type, const bool is_passed_delay_gate, const double delay_time, + const double delay_time_threshold); +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementMahalanobisGate( + const std::string & measurement_type, const bool is_passed_mahalanobis_gate, + const double mahalanobis_distance, const double mahalanobis_distance_threshold); + +diagnostic_msgs::msg::DiagnosticStatus mergeDiagnosticStatus( + const std::vector & stat_array); + +#endif // EKF_LOCALIZER__DIAGNOSTICS_HPP_ diff --git a/localization/ekf_localizer/include/ekf_localizer/ekf_localizer.hpp b/localization/ekf_localizer/include/ekf_localizer/ekf_localizer.hpp index a4ae47b670897..4fc2305cc7adc 100644 --- a/localization/ekf_localizer/include/ekf_localizer/ekf_localizer.hpp +++ b/localization/ekf_localizer/include/ekf_localizer/ekf_localizer.hpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -126,6 +127,8 @@ class EKFLocalizer : public rclcpp::Node rclcpp::Publisher::SharedPtr pub_biased_pose_; //!< @brief ekf estimated yaw bias publisher rclcpp::Publisher::SharedPtr pub_biased_pose_cov_; + //!< @brief diagnostics publisher + rclcpp::Publisher::SharedPtr pub_diag_; //!< @brief initial pose subscriber rclcpp::Subscription::SharedPtr sub_initialpose_; //!< @brief measurement pose with covariance subscriber @@ -144,6 +147,7 @@ class EKFLocalizer : public rclcpp::Node rclcpp::TimerBase::SharedPtr timer_tf_; //!< @brief tf broadcaster std::shared_ptr tf_br_; + //!< @brief extended kalman filter instance. TimeDelayKalmanFilter ekf_; Simple1DFilter z_filter_; @@ -167,6 +171,22 @@ class EKFLocalizer : public rclcpp::Node bool is_activated_; + size_t pose_no_update_count_; + size_t pose_queue_size_; + bool pose_is_passed_delay_gate_; + double pose_delay_time_; + double pose_delay_time_threshold_; + bool pose_is_passed_mahalanobis_gate_; + double pose_mahalanobis_distance_; + + size_t twist_no_update_count_; + size_t twist_queue_size_; + bool twist_is_passed_delay_gate_; + double twist_delay_time_; + double twist_delay_time_threshold_; + bool twist_is_passed_mahalanobis_gate_; + double twist_mahalanobis_distance_; + AgedObjectQueue pose_queue_; AgedObjectQueue twist_queue_; @@ -221,13 +241,13 @@ class EKFLocalizer : public rclcpp::Node * @brief compute EKF update with pose measurement * @param pose measurement value */ - void measurementUpdatePose(const geometry_msgs::msg::PoseWithCovarianceStamped & pose); + bool measurementUpdatePose(const geometry_msgs::msg::PoseWithCovarianceStamped & pose); /** * @brief compute EKF update with pose measurement * @param twist measurement value */ - void measurementUpdateTwist(const geometry_msgs::msg::TwistWithCovarianceStamped & twist); + bool measurementUpdateTwist(const geometry_msgs::msg::TwistWithCovarianceStamped & twist); /** * @brief get transform from frame_id @@ -246,6 +266,11 @@ class EKFLocalizer : public rclcpp::Node */ void publishEstimateResult(); + /** + * @brief publish diagnostics message + */ + void publishDiagnostics(); + /** * @brief for debug */ diff --git a/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp b/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp index c74fa9be79525..9fa877c8fd2f6 100644 --- a/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp +++ b/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp @@ -39,7 +39,15 @@ class HyperParameters twist_smoothing_steps(node->declare_parameter("twist_smoothing_steps", 2)), proc_stddev_vx_c(node->declare_parameter("proc_stddev_vx_c", 5.0)), proc_stddev_wz_c(node->declare_parameter("proc_stddev_wz_c", 1.0)), - proc_stddev_yaw_c(node->declare_parameter("proc_stddev_yaw_c", 0.005)) + proc_stddev_yaw_c(node->declare_parameter("proc_stddev_yaw_c", 0.005)), + pose_no_update_count_threshold_warn( + node->declare_parameter("pose_no_update_count_threshold_warn", 50)), + pose_no_update_count_threshold_error( + node->declare_parameter("pose_no_update_count_threshold_error", 250)), + twist_no_update_count_threshold_warn( + node->declare_parameter("twist_no_update_count_threshold_warn", 50)), + twist_no_update_count_threshold_error( + node->declare_parameter("twist_no_update_count_threshold_error", 250)) { } @@ -59,6 +67,10 @@ class HyperParameters const double proc_stddev_vx_c; //!< @brief vx process noise const double proc_stddev_wz_c; //!< @brief wz process noise const double proc_stddev_yaw_c; //!< @brief yaw process noise + const size_t pose_no_update_count_threshold_warn; + const size_t pose_no_update_count_threshold_error; + const size_t twist_no_update_count_threshold_warn; + const size_t twist_no_update_count_threshold_error; }; #endif // EKF_LOCALIZER__HYPER_PARAMETERS_HPP_ diff --git a/localization/ekf_localizer/media/ekf_diagnostics.png b/localization/ekf_localizer/media/ekf_diagnostics.png new file mode 100644 index 0000000000000000000000000000000000000000..2580d6d973290235f1d131251b815db319c07af8 GIT binary patch literal 103904 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�zqS*Uy=xB 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 literal 0 HcmV?d00001 diff --git a/localization/ekf_localizer/package.xml b/localization/ekf_localizer/package.xml index 5bc9c5e42712d..005bddd3eb22b 100644 --- a/localization/ekf_localizer/package.xml +++ b/localization/ekf_localizer/package.xml @@ -18,6 +18,7 @@ eigen + diagnostic_msgs fmt geometry_msgs kalman_filter diff --git a/localization/ekf_localizer/src/diagnostics.cpp b/localization/ekf_localizer/src/diagnostics.cpp new file mode 100644 index 0000000000000..9ff36561abaa9 --- /dev/null +++ b/localization/ekf_localizer/src/diagnostics.cpp @@ -0,0 +1,169 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ekf_localizer/diagnostics.hpp" + +#include + +#include +#include + +diagnostic_msgs::msg::DiagnosticStatus checkProcessActivated(const bool is_activated) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = "is_activated"; + key_value.value = is_activated ? "True" : "False"; + stat.values.push_back(key_value); + + stat.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat.message = "OK"; + if (!is_activated) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat.message = "[WARN]process is not activated"; + } + + return stat; +} + +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementUpdated( + const std::string & measurement_type, const size_t no_update_count, + const size_t no_update_count_threshold_warn, const size_t no_update_count_threshold_error) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = measurement_type + "_no_update_count"; + key_value.value = std::to_string(no_update_count); + stat.values.push_back(key_value); + key_value.key = measurement_type + "_no_update_count_threshold_warn"; + key_value.value = std::to_string(no_update_count_threshold_warn); + stat.values.push_back(key_value); + key_value.key = measurement_type + "_no_update_count_threshold_error"; + key_value.value = std::to_string(no_update_count_threshold_error); + stat.values.push_back(key_value); + + stat.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat.message = "OK"; + if (no_update_count >= no_update_count_threshold_warn) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat.message = "[WARN]" + measurement_type + " is not updated"; + } + if (no_update_count >= no_update_count_threshold_error) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat.message = "[ERROR]" + measurement_type + " is not updated"; + } + + return stat; +} + +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementQueueSize( + const std::string & measurement_type, const size_t queue_size) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = measurement_type + "_queue_size"; + key_value.value = std::to_string(queue_size); + stat.values.push_back(key_value); + + stat.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat.message = "OK"; + + return stat; +} + +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementDelayGate( + const std::string & measurement_type, const bool is_passed_delay_gate, const double delay_time, + const double delay_time_threshold) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = measurement_type + "_is_passed_delay_gate"; + key_value.value = is_passed_delay_gate ? "True" : "False"; + stat.values.push_back(key_value); + key_value.key = measurement_type + "_delay_time"; + key_value.value = std::to_string(delay_time); + stat.values.push_back(key_value); + key_value.key = measurement_type + "_delay_time_threshold"; + key_value.value = std::to_string(delay_time_threshold); + stat.values.push_back(key_value); + + stat.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat.message = "OK"; + if (!is_passed_delay_gate) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat.message = "[WARN]" + measurement_type + " topic is delay"; + } + + return stat; +} + +diagnostic_msgs::msg::DiagnosticStatus checkMeasurementMahalanobisGate( + const std::string & measurement_type, const bool is_passed_mahalanobis_gate, + const double mahalanobis_distance, const double mahalanobis_distance_threshold) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = measurement_type + "_is_passed_mahalanobis_gate"; + key_value.value = is_passed_mahalanobis_gate ? "True" : "False"; + stat.values.push_back(key_value); + key_value.key = measurement_type + "_mahalanobis_distance"; + key_value.value = std::to_string(mahalanobis_distance); + stat.values.push_back(key_value); + key_value.key = measurement_type + "_mahalanobis_distance_threshold"; + key_value.value = std::to_string(mahalanobis_distance_threshold); + stat.values.push_back(key_value); + + stat.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat.message = "OK"; + if (!is_passed_mahalanobis_gate) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat.message = "[WARN]mahalanobis distance of " + measurement_type + " topic is large"; + } + + return stat; +} + +// The highest level within the stat_array will be reflected in the merged_stat. +// When all stat_array entries are 'OK,' the message of merged_stat will be "OK" +diagnostic_msgs::msg::DiagnosticStatus mergeDiagnosticStatus( + const std::vector & stat_array) +{ + diagnostic_msgs::msg::DiagnosticStatus merged_stat; + + for (const auto & stat : stat_array) { + if ((stat.level > diagnostic_msgs::msg::DiagnosticStatus::OK)) { + if (!merged_stat.message.empty()) { + merged_stat.message += "; "; + } + merged_stat.message += stat.message; + } + if (stat.level > merged_stat.level) { + merged_stat.level = stat.level; + } + for (const auto & value : stat.values) { + merged_stat.values.push_back(value); + } + } + + if (merged_stat.level == diagnostic_msgs::msg::DiagnosticStatus::OK) { + merged_stat.message = "OK"; + } + + return merged_stat; +} diff --git a/localization/ekf_localizer/src/ekf_localizer.cpp b/localization/ekf_localizer/src/ekf_localizer.cpp index 0e46a26add852..68a31bcdded1a 100644 --- a/localization/ekf_localizer/src/ekf_localizer.cpp +++ b/localization/ekf_localizer/src/ekf_localizer.cpp @@ -15,6 +15,7 @@ #include "ekf_localizer/ekf_localizer.hpp" #include "ekf_localizer/covariance.hpp" +#include "ekf_localizer/diagnostics.hpp" #include "ekf_localizer/mahalanobis.hpp" #include "ekf_localizer/matrix_types.hpp" #include "ekf_localizer/measurement.hpp" @@ -87,6 +88,7 @@ EKFLocalizer::EKFLocalizer(const std::string & node_name, const rclcpp::NodeOpti pub_biased_pose_ = create_publisher("ekf_biased_pose", 1); pub_biased_pose_cov_ = create_publisher( "ekf_biased_pose_with_covariance", 1); + pub_diag_ = this->create_publisher("/diagnostics", 10); sub_initialpose_ = create_subscription( "initialpose", 1, std::bind(&EKFLocalizer::callbackInitialPose, this, _1)); sub_pose_with_cov_ = create_subscription( @@ -143,6 +145,7 @@ void EKFLocalizer::timerCallback() if (!is_activated_) { warning_.warnThrottle( "The node is not activated. Provide initial pose to pose_initializer", 2000); + publishDiagnostics(); return; } @@ -176,6 +179,16 @@ void EKFLocalizer::timerCallback() DEBUG_INFO(get_logger(), "------------------------- end prediction -------------------------\n"); /* pose measurement update */ + + pose_queue_size_ = pose_queue_.size(); + pose_is_passed_delay_gate_ = true; + pose_delay_time_ = 0.0; + pose_delay_time_threshold_ = 0.0; + pose_is_passed_mahalanobis_gate_ = true; + pose_mahalanobis_distance_ = 0.0; + + bool pose_is_updated = false; + if (!pose_queue_.empty()) { DEBUG_INFO(get_logger(), "------------------------- start Pose -------------------------"); stop_watch_.tic(); @@ -184,13 +197,27 @@ void EKFLocalizer::timerCallback() const size_t n = pose_queue_.size(); for (size_t i = 0; i < n; ++i) { const auto pose = pose_queue_.pop_increment_age(); - measurementUpdatePose(*pose); + bool is_updated = measurementUpdatePose(*pose); + if (is_updated) { + pose_is_updated = true; + } } DEBUG_INFO(get_logger(), "[EKF] measurementUpdatePose calc time = %f [ms]", stop_watch_.toc()); DEBUG_INFO(get_logger(), "------------------------- end Pose -------------------------\n"); } + pose_no_update_count_ = pose_is_updated ? 0 : (pose_no_update_count_ + 1); /* twist measurement update */ + + twist_queue_size_ = twist_queue_.size(); + twist_is_passed_delay_gate_ = true; + twist_delay_time_ = 0.0; + twist_delay_time_threshold_ = 0.0; + twist_is_passed_mahalanobis_gate_ = true; + twist_mahalanobis_distance_ = 0.0; + + bool twist_is_updated = false; + if (!twist_queue_.empty()) { DEBUG_INFO(get_logger(), "------------------------- start Twist -------------------------"); stop_watch_.tic(); @@ -199,11 +226,15 @@ void EKFLocalizer::timerCallback() const size_t n = twist_queue_.size(); for (size_t i = 0; i < n; ++i) { const auto twist = twist_queue_.pop_increment_age(); - measurementUpdateTwist(*twist); + bool is_updated = measurementUpdateTwist(*twist); + if (is_updated) { + twist_is_updated = true; + } } DEBUG_INFO(get_logger(), "[EKF] measurementUpdateTwist calc time = %f [ms]", stop_watch_.toc()); DEBUG_INFO(get_logger(), "------------------------- end Twist -------------------------\n"); } + twist_no_update_count_ = twist_is_updated ? 0 : (twist_no_update_count_ + 1); const double x = ekf_.getXelement(IDX::X); const double y = ekf_.getXelement(IDX::Y); @@ -235,6 +266,7 @@ void EKFLocalizer::timerCallback() /* publish ekf result */ publishEstimateResult(); + publishDiagnostics(); } void EKFLocalizer::showCurrentX() @@ -376,7 +408,7 @@ void EKFLocalizer::initEKF() /* * measurementUpdatePose */ -void EKFLocalizer::measurementUpdatePose(const geometry_msgs::msg::PoseWithCovarianceStamped & pose) +bool EKFLocalizer::measurementUpdatePose(const geometry_msgs::msg::PoseWithCovarianceStamped & pose) { if (pose.header.frame_id != params_.pose_frame_id) { warning_.warnThrottle( @@ -400,10 +432,14 @@ void EKFLocalizer::measurementUpdatePose(const geometry_msgs::msg::PoseWithCovar delay_time = std::max(delay_time, 0.0); int delay_step = std::roundf(delay_time / ekf_dt_); + + pose_delay_time_ = std::max(delay_time, pose_delay_time_); + pose_delay_time_threshold_ = params_.extend_state_step * ekf_dt_; if (delay_step >= params_.extend_state_step) { + pose_is_passed_delay_gate_ = false; warning_.warnThrottle( poseDelayStepWarningMessage(delay_time, params_.extend_state_step, ekf_dt_), 2000); - return; + return false; } DEBUG_INFO(get_logger(), "delay_time: %f [s]", delay_time); @@ -420,7 +456,7 @@ void EKFLocalizer::measurementUpdatePose(const geometry_msgs::msg::PoseWithCovar if (hasNan(y) || hasInf(y)) { warning_.warn( "[EKF] pose measurement matrix includes NaN of Inf. ignore update. check pose message."); - return; + return false; } /* Gate */ @@ -431,10 +467,12 @@ void EKFLocalizer::measurementUpdatePose(const geometry_msgs::msg::PoseWithCovar const Eigen::MatrixXd P_y = P_curr.block(0, 0, dim_y, dim_y); const double distance = mahalanobis(y_ekf, y, P_y); + pose_mahalanobis_distance_ = std::max(distance, pose_mahalanobis_distance_); if (distance > params_.pose_gate_dist) { + pose_is_passed_mahalanobis_gate_ = false; warning_.warnThrottle(mahalanobisWarningMessage(distance, params_.pose_gate_dist), 2000); warning_.warnThrottle("Ignore the measurement data.", 2000); - return; + return false; } DEBUG_PRINT_MAT(y.transpose()); @@ -460,12 +498,14 @@ void EKFLocalizer::measurementUpdatePose(const geometry_msgs::msg::PoseWithCovar const Eigen::MatrixXd X_result = ekf_.getLatestX(); DEBUG_PRINT_MAT(X_result.transpose()); DEBUG_PRINT_MAT((X_result - X_curr).transpose()); + + return true; } /* * measurementUpdateTwist */ -void EKFLocalizer::measurementUpdateTwist( +bool EKFLocalizer::measurementUpdateTwist( const geometry_msgs::msg::TwistWithCovarianceStamped & twist) { if (twist.header.frame_id != "base_link") { @@ -488,10 +528,14 @@ void EKFLocalizer::measurementUpdateTwist( delay_time = std::max(delay_time, 0.0); int delay_step = std::roundf(delay_time / ekf_dt_); + + twist_delay_time_ = std::max(delay_time, twist_delay_time_); + twist_delay_time_threshold_ = params_.extend_state_step * ekf_dt_; if (delay_step >= params_.extend_state_step) { + twist_is_passed_delay_gate_ = false; warning_.warnThrottle( twistDelayStepWarningMessage(delay_time, params_.extend_state_step, ekf_dt_), 2000); - return; + return false; } DEBUG_INFO(get_logger(), "delay_time: %f [s]", delay_time); @@ -502,7 +546,7 @@ void EKFLocalizer::measurementUpdateTwist( if (hasNan(y) || hasInf(y)) { warning_.warn( "[EKF] twist measurement matrix includes NaN of Inf. ignore update. check twist message."); - return; + return false; } const Eigen::Vector2d y_ekf( @@ -512,10 +556,12 @@ void EKFLocalizer::measurementUpdateTwist( const Eigen::MatrixXd P_y = P_curr.block(4, 4, dim_y, dim_y); const double distance = mahalanobis(y_ekf, y, P_y); + twist_mahalanobis_distance_ = std::max(distance, twist_mahalanobis_distance_); if (distance > params_.twist_gate_dist) { + twist_is_passed_mahalanobis_gate_ = false; warning_.warnThrottle(mahalanobisWarningMessage(distance, params_.twist_gate_dist), 2000); warning_.warnThrottle("Ignore the measurement data.", 2000); - return; + return false; } DEBUG_PRINT_MAT(y.transpose()); @@ -532,6 +578,8 @@ void EKFLocalizer::measurementUpdateTwist( const Eigen::MatrixXd X_result = ekf_.getLatestX(); DEBUG_PRINT_MAT(X_result.transpose()); DEBUG_PRINT_MAT((X_result - X_curr).transpose()); + + return true; } /* @@ -607,6 +655,45 @@ void EKFLocalizer::publishEstimateResult() pub_debug_->publish(msg); } +void EKFLocalizer::publishDiagnostics() +{ + std::vector diag_status_array; + + diag_status_array.push_back(checkProcessActivated(is_activated_)); + + if (is_activated_) { + diag_status_array.push_back(checkMeasurementUpdated( + "pose", pose_no_update_count_, params_.pose_no_update_count_threshold_warn, + params_.pose_no_update_count_threshold_error)); + diag_status_array.push_back(checkMeasurementQueueSize("pose", pose_queue_size_)); + diag_status_array.push_back(checkMeasurementDelayGate( + "pose", pose_is_passed_delay_gate_, pose_delay_time_, pose_delay_time_threshold_)); + diag_status_array.push_back(checkMeasurementMahalanobisGate( + "pose", pose_is_passed_mahalanobis_gate_, pose_mahalanobis_distance_, + params_.pose_gate_dist)); + + diag_status_array.push_back(checkMeasurementUpdated( + "twist", twist_no_update_count_, params_.twist_no_update_count_threshold_warn, + params_.twist_no_update_count_threshold_error)); + diag_status_array.push_back(checkMeasurementQueueSize("twist", twist_queue_size_)); + diag_status_array.push_back(checkMeasurementDelayGate( + "twist", twist_is_passed_delay_gate_, twist_delay_time_, twist_delay_time_threshold_)); + diag_status_array.push_back(checkMeasurementMahalanobisGate( + "twist", twist_is_passed_mahalanobis_gate_, twist_mahalanobis_distance_, + params_.twist_gate_dist)); + } + + diagnostic_msgs::msg::DiagnosticStatus diag_merged_status; + diag_merged_status = mergeDiagnosticStatus(diag_status_array); + diag_merged_status.name = "localization: " + std::string(this->get_name()); + diag_merged_status.hardware_id = this->get_name(); + + diagnostic_msgs::msg::DiagnosticArray diag_msg; + diag_msg.header.stamp = this->now(); + diag_msg.status.push_back(diag_merged_status); + pub_diag_->publish(diag_msg); +} + void EKFLocalizer::updateSimple1DFilters( const geometry_msgs::msg::PoseWithCovarianceStamped & pose, const size_t smoothing_step) { diff --git a/localization/ekf_localizer/test/test_diagnostics.cpp b/localization/ekf_localizer/test/test_diagnostics.cpp new file mode 100644 index 0000000000000..f506dca1cb230 --- /dev/null +++ b/localization/ekf_localizer/test/test_diagnostics.cpp @@ -0,0 +1,192 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ekf_localizer/diagnostics.hpp" + +#include + +TEST(TestEkfDiagnostics, CheckProcessActivated) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + bool is_activated = true; + stat = checkProcessActivated(is_activated); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + is_activated = false; + stat = checkProcessActivated(is_activated); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); +} + +TEST(TestEkfDiagnostics, checkMeasurementUpdated) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + const std::string measurement_type = "pose"; // not effect for stat.level + const size_t no_update_count_threshold_warn = 50; + const size_t no_update_count_threshold_error = 250; + + size_t no_update_count = 0; + stat = checkMeasurementUpdated( + measurement_type, no_update_count, no_update_count_threshold_warn, + no_update_count_threshold_error); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + no_update_count = 1; + stat = checkMeasurementUpdated( + measurement_type, no_update_count, no_update_count_threshold_warn, + no_update_count_threshold_error); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + no_update_count = 49; + stat = checkMeasurementUpdated( + measurement_type, no_update_count, no_update_count_threshold_warn, + no_update_count_threshold_error); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + no_update_count = 50; + stat = checkMeasurementUpdated( + measurement_type, no_update_count, no_update_count_threshold_warn, + no_update_count_threshold_error); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + + no_update_count = 249; + stat = checkMeasurementUpdated( + measurement_type, no_update_count, no_update_count_threshold_warn, + no_update_count_threshold_error); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + + no_update_count = 250; + stat = checkMeasurementUpdated( + measurement_type, no_update_count, no_update_count_threshold_warn, + no_update_count_threshold_error); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); +} + +TEST(TestEkfDiagnostics, CheckMeasurementQueueSize) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + const std::string measurement_type = "pose"; // not effect for stat.level + + size_t queue_size = 0; // not effect for stat.level + stat = checkMeasurementQueueSize(measurement_type, queue_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + queue_size = 1; // not effect for stat.level + stat = checkMeasurementQueueSize(measurement_type, queue_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); +} + +TEST(TestEkfDiagnostics, CheckMeasurementDelayGate) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + const std::string measurement_type = "pose"; // not effect for stat.level + const double delay_time = 0.1; // not effect for stat.level + const double delay_time_threshold = 1.0; // not effect for stat.level + + bool is_passed_delay_gate = true; + stat = checkMeasurementDelayGate( + measurement_type, is_passed_delay_gate, delay_time, delay_time_threshold); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + is_passed_delay_gate = false; + stat = checkMeasurementDelayGate( + measurement_type, is_passed_delay_gate, delay_time, delay_time_threshold); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); +} + +TEST(TestEkfDiagnostics, CheckMeasurementMahalanobisGate) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + const std::string measurement_type = "pose"; // not effect for stat.level + const double mahalanobis_distance = 0.1; // not effect for stat.level + const double mahalanobis_distance_threshold = 1.0; // not effect for stat.level + + bool is_passed_mahalanobis_gate = true; + stat = checkMeasurementMahalanobisGate( + measurement_type, is_passed_mahalanobis_gate, mahalanobis_distance, + mahalanobis_distance_threshold); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + is_passed_mahalanobis_gate = false; + stat = checkMeasurementMahalanobisGate( + measurement_type, is_passed_mahalanobis_gate, mahalanobis_distance, + mahalanobis_distance_threshold); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); +} + +TEST(TestLocalizationErrorMonitorDiagnostics, MergeDiagnosticStatus) +{ + diagnostic_msgs::msg::DiagnosticStatus merged_stat; + std::vector stat_array(2); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(0).message = "OK"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(1).message = "OK"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + EXPECT_EQ(merged_stat.message, "OK"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(0).message = "WARN0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(1).message = "OK"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + EXPECT_EQ(merged_stat.message, "WARN0"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(0).message = "OK"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(1).message = "WARN1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + EXPECT_EQ(merged_stat.message, "WARN1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(0).message = "WARN0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(1).message = "WARN1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + EXPECT_EQ(merged_stat.message, "WARN0; WARN1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(0).message = "OK"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(1).message = "ERROR1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); + EXPECT_EQ(merged_stat.message, "ERROR1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(0).message = "WARN0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(1).message = "ERROR1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); + EXPECT_EQ(merged_stat.message, "WARN0; ERROR1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(0).message = "ERROR0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(1).message = "ERROR1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); + EXPECT_EQ(merged_stat.message, "ERROR0; ERROR1"); +} From 0f441c1abe0697ac8255462ef1beda036054b4e8 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:46:58 +0900 Subject: [PATCH 247/547] feat(avoidance): insert return dead line point (#5029) Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.hpp | 2 + .../avoidance/avoidance_module.cpp | 73 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp index a90bec18e093e..d362a5e758a3e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp @@ -219,6 +219,8 @@ class AvoidanceModule : public SceneModuleInterface */ void insertStopPoint(const bool use_constraints_for_decel, ShiftedPath & shifted_path) const; + void insertReturnDeadLine(const bool use_constraints_for_decel, ShiftedPath & shifted_path) const; + /** * @brief insert stop point in output path. * @param target path. diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 00c8c55c97918..22a12f141b4ee 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -654,6 +654,8 @@ void AvoidanceModule::updateEgoBehavior(const AvoidancePlanningData & data, Shif throw std::domain_error("invalid behavior"); } + insertReturnDeadLine(isBestEffort(parameters_->policy_deceleration), path); + setStopReason(StopReason::AVOIDANCE, path.path); } @@ -2746,6 +2748,77 @@ double AvoidanceModule::calcDistanceToStopLine(const ObjectData & object) const return object.longitudinal - std::min(variable + constant, p->stop_max_distance); } +void AvoidanceModule::insertReturnDeadLine( + const bool use_constraints_for_decel, ShiftedPath & shifted_path) const +{ + const auto & data = avoid_data_; + + if (!planner_data_->route_handler->isInGoalRouteSection(data.current_lanelets.back())) { + RCLCPP_DEBUG(getLogger(), "goal is far enough."); + return; + } + + const auto shift_length = path_shifter_.getLastShiftLength(); + + if (std::abs(shift_length) < 1e-3) { + RCLCPP_DEBUG(getLogger(), "don't have to consider return shift."); + return; + } + + const auto min_return_distance = helper_.getMinAvoidanceDistance(shift_length); + + const auto to_goal = calcSignedArcLength( + shifted_path.path.points, getEgoPosition(), shifted_path.path.points.size() - 1); + const auto to_stop_line = to_goal - min_return_distance - parameters_->remain_buffer_distance; + + // If we don't need to consider deceleration constraints, insert a deceleration point + // and return immediately + if (!use_constraints_for_decel) { + utils::avoidance::insertDecelPoint( + getEgoPosition(), to_stop_line - parameters_->stop_buffer, 0.0, shifted_path.path, + stop_pose_); + return; + } + + // If the stop distance is not enough for comfortable stop, don't insert wait point. + const auto is_comfortable_stop = helper_.getFeasibleDecelDistance(0.0) < to_stop_line; + if (!is_comfortable_stop) { + RCLCPP_DEBUG(getLogger(), "stop distance is not enough."); + return; + } + + utils::avoidance::insertDecelPoint( + getEgoPosition(), to_stop_line - parameters_->stop_buffer, 0.0, shifted_path.path, stop_pose_); + + // insert slow down speed. + const double current_target_velocity = PathShifter::calcFeasibleVelocityFromJerk( + shift_length, helper_.getLateralMinJerkLimit(), to_stop_line); + if (current_target_velocity < getEgoSpeed()) { + RCLCPP_DEBUG(getLogger(), "current velocity exceeds target slow down speed."); + return; + } + + const auto start_idx = planner_data_->findEgoIndex(shifted_path.path.points); + for (size_t i = start_idx; i < shifted_path.path.points.size(); ++i) { + const auto distance_from_ego = calcSignedArcLength(shifted_path.path.points, start_idx, i); + + // slow down speed is inserted only in front of the object. + const auto shift_longitudinal_distance = to_stop_line - distance_from_ego; + if (shift_longitudinal_distance < 0.0) { + break; + } + + // target speed with nominal jerk limits. + const double v_target = PathShifter::calcFeasibleVelocityFromJerk( + shift_length, helper_.getLateralMinJerkLimit(), shift_longitudinal_distance); + const double v_original = shifted_path.path.points.at(i).point.longitudinal_velocity_mps; + const double v_insert = + std::max(v_target - parameters_->buf_slow_down_speed, parameters_->min_slow_down_speed); + + shifted_path.path.points.at(i).point.longitudinal_velocity_mps = std::min(v_original, v_insert); + } +} + void AvoidanceModule::insertWaitPoint( const bool use_constraints_for_decel, ShiftedPath & shifted_path) const { From c7525bb36e8fde3c643e056235316de44505c136 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Wed, 20 Sep 2023 16:13:53 +0900 Subject: [PATCH 248/547] feat(ndt_scan_matcher): added logging initial pose (#5045) * Added logging initial pose Signed-off-by: Shintaro SAKODA * Added #include to util_func.hpp Signed-off-by: Shintaro SAKODA --------- Signed-off-by: Shintaro SAKODA --- .../include/ndt_scan_matcher/util_func.hpp | 5 +++++ .../src/ndt_scan_matcher_core.cpp | 6 ++++++ .../ndt_scan_matcher/src/util_func.cpp | 21 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/util_func.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/util_func.hpp index 933375d5796f2..5c37cef36c422 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/util_func.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/util_func.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include // ref by http://takacity.blog.fc2.com/blog-entry-69.html @@ -86,4 +87,8 @@ T transform(const T & input, const geometry_msgs::msg::TransformStamped & transf double norm(const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2); +void output_pose_with_cov_to_log( + const rclcpp::Logger logger, const std::string & prefix, + const geometry_msgs::msg::PoseWithCovarianceStamped & pose_with_cov); + #endif // NDT_SCAN_MATCHER__UTIL_FUNC_HPP_ diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 103df19c2b5db..580d1e59b65a6 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -783,6 +783,8 @@ geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_ return geometry_msgs::msg::PoseWithCovarianceStamped(); } + output_pose_with_cov_to_log(get_logger(), "align_using_monte_carlo_input", initial_pose_with_cov); + // generateParticle const auto initial_poses = create_random_pose_array(initial_pose_with_cov, initial_estimate_particles_num_); @@ -820,5 +822,9 @@ geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_ result_pose_with_cov_msg.header.frame_id = map_frame_; result_pose_with_cov_msg.pose.pose = best_particle_ptr->result_pose; + output_pose_with_cov_to_log( + get_logger(), "align_using_monte_carlo_output", result_pose_with_cov_msg); + RCLCPP_INFO_STREAM(get_logger(), "best_score," << best_particle_ptr->score); + return result_pose_with_cov_msg; } diff --git a/localization/ndt_scan_matcher/src/util_func.cpp b/localization/ndt_scan_matcher/src/util_func.cpp index d947c56dfe183..52cb7844ab241 100644 --- a/localization/ndt_scan_matcher/src/util_func.cpp +++ b/localization/ndt_scan_matcher/src/util_func.cpp @@ -287,3 +287,24 @@ double norm(const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Poin const double dz = p1.z - p2.z; return std::sqrt(dx * dx + dy * dy + dz * dz); } + +void output_pose_with_cov_to_log( + const rclcpp::Logger logger, const std::string & prefix, + const geometry_msgs::msg::PoseWithCovarianceStamped & pose_with_cov) +{ + const Eigen::Map covariance = + make_eigen_covariance(pose_with_cov.pose.covariance); + const geometry_msgs::msg::Pose pose = pose_with_cov.pose.pose; + geometry_msgs::msg::Vector3 rpy = get_rpy(pose); + rpy.x = rpy.x * 180.0 / M_PI; + rpy.y = rpy.y * 180.0 / M_PI; + rpy.z = rpy.z * 180.0 / M_PI; + + RCLCPP_INFO_STREAM( + logger, std::fixed << prefix << "," << pose.position.x << "," << pose.position.y << "," + << pose.position.z << "," << pose.orientation.x << "," << pose.orientation.y + << "," << pose.orientation.z << "," << pose.orientation.w << "," << rpy.x + << "," << rpy.y << "," << rpy.z << "," << covariance(0, 0) << "," + << covariance(1, 1) << "," << covariance(2, 2) << "," << covariance(3, 3) + << "," << covariance(4, 4) << "," << covariance(5, 5)); +} From 9f4ccf6abb5e404e22213cbc3146fdd11c21dad7 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:21:45 +0900 Subject: [PATCH 249/547] feat(ekf_localizer): ignore dead band of velocity sensor (#5042) * feat(ekf_localizer): ignore dead band of velocity sensor Signed-off-by: kminoda * update Signed-off-by: kminoda * update readme Signed-off-by: kminoda * style(pre-commit): autofix * update stop_filter as well Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- localization/ekf_localizer/README.md | 6 ++++++ localization/ekf_localizer/config/ekf_localizer.param.yaml | 3 +++ .../include/ekf_localizer/hyper_parameters.hpp | 5 ++++- localization/ekf_localizer/src/ekf_localizer.cpp | 5 +++++ localization/stop_filter/CMakeLists.txt | 1 + localization/stop_filter/config/stop_filter.param.yaml | 4 ++++ localization/stop_filter/launch/stop_filter.launch.xml | 6 ++---- localization/stop_filter/src/stop_filter.cpp | 4 ++-- 8 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 localization/stop_filter/config/stop_filter.param.yaml diff --git a/localization/ekf_localizer/README.md b/localization/ekf_localizer/README.md index 977e0fceafd9e..6492f20331a66 100644 --- a/localization/ekf_localizer/README.md +++ b/localization/ekf_localizer/README.md @@ -161,6 +161,12 @@ note: process noise for positions x & y are calculated automatically from nonlin | twist_no_update_count_threshold_warn | size_t | The threshold at which a WARN state is triggered due to the Twist Topic update not happening continuously for a certain number of times. | 50 | | twist_no_update_count_threshold_error | size_t | The threshold at which an ERROR state is triggered due to the Twist Topic update not happening continuously for a certain number of times. | 250 | +### Misc + +| Name | Type | Description | Default value | +| :-------------------------------- | :----- | :------------------------------------------------------------------------------------------------- | :------------- | +| threshold_observable_velocity_mps | double | Minimum value for velocity that will be used for EKF. Mainly used for dead zone in velocity sensor | 0.0 (disabled) | + ## How to tune EKF parameters ### 0. Preliminaries diff --git a/localization/ekf_localizer/config/ekf_localizer.param.yaml b/localization/ekf_localizer/config/ekf_localizer.param.yaml index 8b24b79e71829..667217d2591dc 100644 --- a/localization/ekf_localizer/config/ekf_localizer.param.yaml +++ b/localization/ekf_localizer/config/ekf_localizer.param.yaml @@ -27,3 +27,6 @@ pose_no_update_count_threshold_error: 250 twist_no_update_count_threshold_warn: 50 twist_no_update_count_threshold_error: 250 + + # for velocity measurement limitation (Set 0.0 if you want to ignore) + threshold_observable_velocity_mps: 0.0 # [m/s] diff --git a/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp b/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp index 9fa877c8fd2f6..01ef658cf445d 100644 --- a/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp +++ b/localization/ekf_localizer/include/ekf_localizer/hyper_parameters.hpp @@ -47,7 +47,9 @@ class HyperParameters twist_no_update_count_threshold_warn( node->declare_parameter("twist_no_update_count_threshold_warn", 50)), twist_no_update_count_threshold_error( - node->declare_parameter("twist_no_update_count_threshold_error", 250)) + node->declare_parameter("twist_no_update_count_threshold_error", 250)), + threshold_observable_velocity_mps( + node->declare_parameter("threshold_observable_velocity_mps", 0.5)) { } @@ -71,6 +73,7 @@ class HyperParameters const size_t pose_no_update_count_threshold_error; const size_t twist_no_update_count_threshold_warn; const size_t twist_no_update_count_threshold_error; + const double threshold_observable_velocity_mps; }; #endif // EKF_LOCALIZER__HYPER_PARAMETERS_HPP_ diff --git a/localization/ekf_localizer/src/ekf_localizer.cpp b/localization/ekf_localizer/src/ekf_localizer.cpp index 68a31bcdded1a..3c3c38dcb1561 100644 --- a/localization/ekf_localizer/src/ekf_localizer.cpp +++ b/localization/ekf_localizer/src/ekf_localizer.cpp @@ -385,6 +385,11 @@ void EKFLocalizer::callbackPoseWithCovariance( void EKFLocalizer::callbackTwistWithCovariance( geometry_msgs::msg::TwistWithCovarianceStamped::SharedPtr msg) { + // Ignore twist if velocity is too small. + // Note that this inequality must not include "equal". + if (msg->twist.twist.linear.x < params_.threshold_observable_velocity_mps) { + msg->twist.covariance[0 * 6 + 0] = 10000.0; + } twist_queue_.push(msg); } diff --git a/localization/stop_filter/CMakeLists.txt b/localization/stop_filter/CMakeLists.txt index 2d1867b8cd0bc..97a0443195ae5 100644 --- a/localization/stop_filter/CMakeLists.txt +++ b/localization/stop_filter/CMakeLists.txt @@ -13,4 +13,5 @@ ament_target_dependencies(stop_filter) ament_auto_package( INSTALL_TO_SHARE launch + config ) diff --git a/localization/stop_filter/config/stop_filter.param.yaml b/localization/stop_filter/config/stop_filter.param.yaml new file mode 100644 index 0000000000000..ded090b75b5bd --- /dev/null +++ b/localization/stop_filter/config/stop_filter.param.yaml @@ -0,0 +1,4 @@ +/**: + ros__parameters: + vx_threshold: 0.1 # [m/s] + wz_threshold: 0.02 # [rad/s] diff --git a/localization/stop_filter/launch/stop_filter.launch.xml b/localization/stop_filter/launch/stop_filter.launch.xml index 36a66a2c143c0..0ea92d26c9677 100644 --- a/localization/stop_filter/launch/stop_filter.launch.xml +++ b/localization/stop_filter/launch/stop_filter.launch.xml @@ -1,6 +1,5 @@ - - + @@ -10,7 +9,6 @@ - - + diff --git a/localization/stop_filter/src/stop_filter.cpp b/localization/stop_filter/src/stop_filter.cpp index 111d460be737e..ac0960b8cb67b 100644 --- a/localization/stop_filter/src/stop_filter.cpp +++ b/localization/stop_filter/src/stop_filter.cpp @@ -27,8 +27,8 @@ using std::placeholders::_1; StopFilter::StopFilter(const std::string & node_name, const rclcpp::NodeOptions & node_options) : rclcpp::Node(node_name, node_options) { - vx_threshold_ = declare_parameter("vx_threshold", 0.01); - wz_threshold_ = declare_parameter("wz_threshold", 0.01); + vx_threshold_ = declare_parameter("vx_threshold"); + wz_threshold_ = declare_parameter("wz_threshold"); sub_odom_ = create_subscription( "input/odom", 1, std::bind(&StopFilter::callbackOdometry, this, _1)); From a5cbb3a2663494c1f6259d69a34ce3ab01586679 Mon Sep 17 00:00:00 2001 From: beginningfan <103237402+beginningfan@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:29:47 +0800 Subject: [PATCH 250/547] feat(autoware_auto_msgs_adapter): add perception adapter (#4555) * feat(autoware_auto_msgs_adapter): add perception adapter Signed-off-by: beginningfan * style(pre-commit): autofix * feat(autoware_auto_msgs_adapter): add perception to launch file Signed-off-by: beginningfan * style(pre-commit): autofix * fix(autoware_auto_msgs_adapter): fix typos Signed-off-by: beginningfan --------- Signed-off-by: beginningfan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../autoware_auto_msgs_adapter/CMakeLists.txt | 1 + .../config/adapter_perception.param.yaml | 5 + .../autoware_auto_msgs_adapter.launch.xml | 10 +- system/autoware_auto_msgs_adapter/package.xml | 2 + .../autoware_auto_msgs_adapter.schema.json | 5 +- .../src/autoware_auto_msgs_adapter_core.cpp | 5 + .../src/include/adapter_perception.hpp | 97 +++++++ .../autoware_auto_msgs_adapter_core.hpp | 1 + .../test_msg_ackermann_control_command.cpp | 2 +- .../test/test_msg_predicted_objects.cpp | 267 ++++++++++++++++++ 10 files changed, 390 insertions(+), 5 deletions(-) create mode 100644 system/autoware_auto_msgs_adapter/config/adapter_perception.param.yaml create mode 100644 system/autoware_auto_msgs_adapter/src/include/adapter_perception.hpp create mode 100644 system/autoware_auto_msgs_adapter/test/test_msg_predicted_objects.cpp diff --git a/system/autoware_auto_msgs_adapter/CMakeLists.txt b/system/autoware_auto_msgs_adapter/CMakeLists.txt index 9b48112227cbe..ff8dcf6cedbb3 100644 --- a/system/autoware_auto_msgs_adapter/CMakeLists.txt +++ b/system/autoware_auto_msgs_adapter/CMakeLists.txt @@ -12,6 +12,7 @@ ament_auto_add_library(${NODE_NAME} src/include/adapter_base.hpp src/include/adapter_base_interface.hpp src/include/adapter_control.hpp + src/include/adapter_perception.hpp src/include/autoware_auto_msgs_adapter_core.hpp src/autoware_auto_msgs_adapter_core.cpp) diff --git a/system/autoware_auto_msgs_adapter/config/adapter_perception.param.yaml b/system/autoware_auto_msgs_adapter/config/adapter_perception.param.yaml new file mode 100644 index 0000000000000..d8b6e31543fad --- /dev/null +++ b/system/autoware_auto_msgs_adapter/config/adapter_perception.param.yaml @@ -0,0 +1,5 @@ +/**: + ros__parameters: + msg_type_target: "autoware_auto_perception_msgs/msg/PredictedObjects" + topic_name_source: "/perception/object_recognition/objects" + topic_name_target: "/perception/object_recognition/objects_auto" diff --git a/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml b/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml index 89b58e2d60c28..f914fe6e824c4 100755 --- a/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml +++ b/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml @@ -1,8 +1,12 @@ - + + - - + + + + + diff --git a/system/autoware_auto_msgs_adapter/package.xml b/system/autoware_auto_msgs_adapter/package.xml index 0deba020ad7a3..de19032e1aee9 100644 --- a/system/autoware_auto_msgs_adapter/package.xml +++ b/system/autoware_auto_msgs_adapter/package.xml @@ -22,7 +22,9 @@ autoware_lint_common autoware_auto_control_msgs + autoware_auto_perception_msgs autoware_control_msgs + autoware_perception_msgs rclcpp rclcpp_components diff --git a/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json b/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json index cf853ee1da130..35e3aef1511cc 100644 --- a/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json +++ b/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json @@ -9,7 +9,10 @@ "msg_type_target": { "type": "string", "description": "Target message type", - "enum": ["autoware_auto_control_msgs/msg/AckermannControlCommand"], + "enum": [ + "autoware_auto_control_msgs/msg/AckermannControlCommand", + "autoware_auto_perception_msgs/msg/PredictedObjects" + ], "default": "autoware_auto_control_msgs/msg/AckermannControlCommand" }, "topic_name_source": { diff --git a/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp b/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp index 98fe916903a60..0f65571aba07b 100644 --- a/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp +++ b/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp @@ -57,6 +57,11 @@ MapStringAdapter AutowareAutoMsgsAdapterNode::create_adapter_map( return std::static_pointer_cast( std::make_shared(*this, topic_name_source, topic_name_target)); }}, + {"autoware_auto_perception_msgs/msg/PredictedObjects", + [&] { + return std::static_pointer_cast( + std::make_shared(*this, topic_name_source, topic_name_target)); + }}, }; } diff --git a/system/autoware_auto_msgs_adapter/src/include/adapter_perception.hpp b/system/autoware_auto_msgs_adapter/src/include/adapter_perception.hpp new file mode 100644 index 0000000000000..3821bbad8ce38 --- /dev/null +++ b/system/autoware_auto_msgs_adapter/src/include/adapter_perception.hpp @@ -0,0 +1,97 @@ +// Copyright 2023 The Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef ADAPTER_PERCEPTION_HPP_ +#define ADAPTER_PERCEPTION_HPP_ + +#include "adapter_base.hpp" + +#include + +#include +#include + +#include + +namespace autoware_auto_msgs_adapter +{ +using Objects_auto = autoware_auto_perception_msgs::msg::PredictedObjects; +using Objects = autoware_perception_msgs::msg::PredictedObjects; + +class AdapterPerception : public autoware_auto_msgs_adapter::AdapterBase +{ +public: + AdapterPerception( + rclcpp::Node & node, const std::string & topic_name_source, + const std::string & topic_name_target, const rclcpp::QoS & qos = rclcpp::QoS{1}) + : AdapterBase(node, topic_name_source, topic_name_target, qos) + { + RCLCPP_DEBUG( + node.get_logger(), "AdapterPerception is initialized to convert: %s -> %s", + topic_name_source.c_str(), topic_name_target.c_str()); + } + +protected: + Objects_auto convert(const Objects & msg_source) override + { + Objects_auto msg_auto; + msg_auto.header = msg_source.header; + + autoware_auto_perception_msgs::msg::PredictedObject object_auto; + for (size_t it_of_objects = 0; it_of_objects < msg_source.objects.size(); it_of_objects++) { + // convert id and probability + object_auto.object_id = msg_source.objects[it_of_objects].object_id; + object_auto.existence_probability = msg_source.objects[it_of_objects].existence_probability; + // convert classification + autoware_auto_perception_msgs::msg::ObjectClassification classification; + for (size_t i = 0; i < msg_source.objects[it_of_objects].classification.size(); i++) { + classification.label = msg_source.objects[it_of_objects].classification[i].label; + classification.probability = + msg_source.objects[it_of_objects].classification[i].probability; + object_auto.classification.push_back(classification); + } + // convert kinematics + object_auto.kinematics.initial_pose_with_covariance = + msg_source.objects[it_of_objects].kinematics.initial_pose_with_covariance; + object_auto.kinematics.initial_twist_with_covariance = + msg_source.objects[it_of_objects].kinematics.initial_twist_with_covariance; + object_auto.kinematics.initial_acceleration_with_covariance = + msg_source.objects[it_of_objects].kinematics.initial_acceleration_with_covariance; + for (size_t j = 0; j < msg_source.objects[it_of_objects].kinematics.predicted_paths.size(); + j++) { + autoware_auto_perception_msgs::msg::PredictedPath predicted_path; + for (size_t k = 0; + k < msg_source.objects[it_of_objects].kinematics.predicted_paths[j].path.size(); k++) { + predicted_path.path.push_back( + msg_source.objects[it_of_objects].kinematics.predicted_paths[j].path[k]); + } + predicted_path.time_step = + msg_source.objects[it_of_objects].kinematics.predicted_paths[j].time_step; + predicted_path.confidence = + msg_source.objects[it_of_objects].kinematics.predicted_paths[j].confidence; + object_auto.kinematics.predicted_paths.push_back(predicted_path); + } + // convert shape + object_auto.shape.type = msg_source.objects[it_of_objects].shape.type; + object_auto.shape.footprint = msg_source.objects[it_of_objects].shape.footprint; + object_auto.shape.dimensions = msg_source.objects[it_of_objects].shape.dimensions; + + // add to objects list + msg_auto.objects.push_back(object_auto); + } + return msg_auto; + } +}; +} // namespace autoware_auto_msgs_adapter + +#endif // ADAPTER_PERCEPTION_HPP_ diff --git a/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp b/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp index a33a88c68618f..f595359dcc1e7 100644 --- a/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp +++ b/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp @@ -15,6 +15,7 @@ #define AUTOWARE_AUTO_MSGS_ADAPTER_CORE_HPP_ #include "adapter_control.hpp" +#include "adapter_perception.hpp" #include diff --git a/system/autoware_auto_msgs_adapter/test/test_msg_ackermann_control_command.cpp b/system/autoware_auto_msgs_adapter/test/test_msg_ackermann_control_command.cpp index e33ae07e6aca1..430b9a26b60e1 100644 --- a/system/autoware_auto_msgs_adapter/test/test_msg_ackermann_control_command.cpp +++ b/system/autoware_auto_msgs_adapter/test/test_msg_ackermann_control_command.cpp @@ -112,5 +112,5 @@ TEST(AutowareAutoMsgsAdapter, TestMsgAckermannControlCommand) // NOLINT for gte EXPECT_TRUE(test_completed); EXPECT_FALSE(timed_out); - rclcpp::shutdown(); + // rclcpp::shutdown(); } diff --git a/system/autoware_auto_msgs_adapter/test/test_msg_predicted_objects.cpp b/system/autoware_auto_msgs_adapter/test/test_msg_predicted_objects.cpp new file mode 100644 index 0000000000000..9f7f0ce8056f5 --- /dev/null +++ b/system/autoware_auto_msgs_adapter/test/test_msg_predicted_objects.cpp @@ -0,0 +1,267 @@ +// Copyright 2023 The Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include + +autoware_perception_msgs::msg::PredictedObjects generate_perception_msg() +{ + // generate deterministic random int + std::mt19937 gen(0); + std::uniform_int_distribution<> dis_int(0, 1000000); + auto rand_int = [&dis_int, &gen]() { return dis_int(gen); }; + + autoware_perception_msgs::msg::PredictedObjects msg_perception; + msg_perception.header.stamp = rclcpp::Time(rand_int()); + msg_perception.header.frame_id = "test_frame"; + + autoware_perception_msgs::msg::PredictedObject obj; + // // { + unique_identifier_msgs::msg::UUID uuid_; + std::independent_bits_engine bit_eng(gen); + std::generate(uuid_.uuid.begin(), uuid_.uuid.end(), bit_eng); + obj.object_id = uuid_; + obj.existence_probability = 0.5; + autoware_perception_msgs::msg::ObjectClassification obj_class; + obj_class.label = 0; + obj_class.probability = 0.5; + obj.classification.push_back(obj_class); + + // { + autoware_perception_msgs::msg::PredictedObjectKinematics kin; + kin.initial_pose_with_covariance.pose.position.x = 10; + kin.initial_pose_with_covariance.pose.position.y = 10; + kin.initial_pose_with_covariance.pose.position.z = 0; + kin.initial_pose_with_covariance.pose.orientation.x = 0; + kin.initial_pose_with_covariance.pose.orientation.y = 0; + kin.initial_pose_with_covariance.pose.orientation.z = 0; + kin.initial_pose_with_covariance.pose.orientation.w = 1; + + kin.initial_twist_with_covariance.twist.linear.x = 1; + kin.initial_twist_with_covariance.twist.linear.y = 0; + kin.initial_twist_with_covariance.twist.linear.z = 0; + kin.initial_twist_with_covariance.twist.angular.x = 0; + kin.initial_twist_with_covariance.twist.angular.y = 0; + kin.initial_twist_with_covariance.twist.angular.z = 0; + + kin.initial_acceleration_with_covariance.accel.linear.x = 0; + kin.initial_acceleration_with_covariance.accel.linear.y = 0; + kin.initial_acceleration_with_covariance.accel.linear.z = 0; + kin.initial_acceleration_with_covariance.accel.angular.x = 0; + kin.initial_acceleration_with_covariance.accel.angular.y = 0; + kin.initial_acceleration_with_covariance.accel.angular.z = 0; + + for (size_t i = 0; i < 10; i++) { + kin.predicted_paths[0].path[i].position.x = i; + kin.predicted_paths[0].path[i].position.y = 0; + kin.predicted_paths[0].path[i].position.z = 0; + } + obj.kinematics = kin; + // } + // { + autoware_perception_msgs::msg::Shape s; + s.type = 1; + geometry_msgs::msg::Point32 p; + p.x = 9.0f; + p.y = 11.0f; + p.z = 0.0f; + s.footprint.points.push_back(p); + p.x = 11.0f; + p.y = 11.0f; + p.z = 0.0f; + s.footprint.points.push_back(p); + p.x = 11.0f; + p.y = 9.0f; + p.z = 0.0f; + s.footprint.points.push_back(p); + p.x = 9.0f; + p.y = 9.0f; + p.z = 0.0f; + s.footprint.points.push_back(p); + + s.dimensions.x = 2; + s.dimensions.y = 2; + s.dimensions.z = 2; + + obj.shape = s; + // } + // } + + msg_perception.objects.push_back(obj); + return msg_perception; +} + +TEST(AutowareAutoMsgsAdapter, TestPredictedObjects) // NOLINT for gtest +{ + const std::string msg_type_target = "autoware_auto_perception_msgs/msg/PredictedObjects"; + const std::string topic_name_source = "topic_name_source"; + const std::string topic_name_target = "topic_name_target"; + + std::cout << "Creating the adapter node..." << std::endl; + + rclcpp::NodeOptions node_options; + node_options.append_parameter_override("msg_type_target", msg_type_target); + node_options.append_parameter_override("topic_name_source", topic_name_source); + node_options.append_parameter_override("topic_name_target", topic_name_target); + + using autoware_auto_msgs_adapter::AutowareAutoMsgsAdapterNode; + AutowareAutoMsgsAdapterNode::SharedPtr node_adapter; + node_adapter = std::make_shared(node_options); + + std::cout << "Creating the subscriber node..." << std::endl; + + auto node_subscriber = std::make_shared("node_subscriber", rclcpp::NodeOptions{}); + + bool test_completed = false; + + const auto msg_perception = generate_perception_msg(); + auto sub = node_subscriber->create_subscription< + autoware_auto_perception_msgs::msg::PredictedObjects>( + topic_name_target, rclcpp::QoS{1}, + [&msg_perception, + &test_completed](const autoware_auto_perception_msgs::msg::PredictedObjects::SharedPtr msg) { + EXPECT_EQ(msg->header.stamp, msg_perception.header.stamp); + EXPECT_EQ(msg->header.frame_id, msg_perception.header.frame_id); + EXPECT_EQ(msg->objects[0].object_id.uuid, msg_perception.objects[0].object_id.uuid); + EXPECT_FLOAT_EQ( + msg->objects[0].existence_probability, msg_perception.objects[0].existence_probability); + EXPECT_EQ( + msg->objects[0].classification[0].label, msg_perception.objects[0].classification[0].label); + EXPECT_FLOAT_EQ( + msg->objects[0].classification[0].probability, + msg_perception.objects[0].classification[0].probability); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_pose_with_covariance.pose.position.x, + msg_perception.objects[0].kinematics.initial_pose_with_covariance.pose.position.x); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_pose_with_covariance.pose.position.y, + msg_perception.objects[0].kinematics.initial_pose_with_covariance.pose.position.y); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_pose_with_covariance.pose.position.z, + msg_perception.objects[0].kinematics.initial_pose_with_covariance.pose.position.z); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_pose_with_covariance.pose.orientation.x, + msg_perception.objects[0].kinematics.initial_pose_with_covariance.pose.orientation.x); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_pose_with_covariance.pose.orientation.y, + msg_perception.objects[0].kinematics.initial_pose_with_covariance.pose.orientation.y); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_pose_with_covariance.pose.orientation.z, + msg_perception.objects[0].kinematics.initial_pose_with_covariance.pose.orientation.z); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_pose_with_covariance.pose.orientation.w, + msg_perception.objects[0].kinematics.initial_pose_with_covariance.pose.orientation.w); + + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_twist_with_covariance.twist.linear.x, + msg_perception.objects[0].kinematics.initial_twist_with_covariance.twist.linear.x); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_twist_with_covariance.twist.linear.y, + msg_perception.objects[0].kinematics.initial_twist_with_covariance.twist.linear.y); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_twist_with_covariance.twist.linear.z, + msg_perception.objects[0].kinematics.initial_twist_with_covariance.twist.linear.z); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_twist_with_covariance.twist.angular.x, + msg_perception.objects[0].kinematics.initial_twist_with_covariance.twist.angular.x); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_twist_with_covariance.twist.angular.y, + msg_perception.objects[0].kinematics.initial_twist_with_covariance.twist.angular.y); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_twist_with_covariance.twist.angular.z, + msg_perception.objects[0].kinematics.initial_twist_with_covariance.twist.angular.z); + + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_acceleration_with_covariance.accel.linear.x, + msg_perception.objects[0].kinematics.initial_acceleration_with_covariance.accel.linear.x); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_acceleration_with_covariance.accel.linear.y, + msg_perception.objects[0].kinematics.initial_acceleration_with_covariance.accel.linear.y); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_acceleration_with_covariance.accel.linear.z, + msg_perception.objects[0].kinematics.initial_acceleration_with_covariance.accel.linear.z); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_acceleration_with_covariance.accel.angular.x, + msg_perception.objects[0].kinematics.initial_acceleration_with_covariance.accel.angular.x); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_acceleration_with_covariance.accel.angular.y, + msg_perception.objects[0].kinematics.initial_acceleration_with_covariance.accel.angular.y); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.initial_acceleration_with_covariance.accel.angular.z, + msg_perception.objects[0].kinematics.initial_acceleration_with_covariance.accel.angular.z); + + for (size_t i = 0; i < msg->objects[0].kinematics.predicted_paths[0].path.size(); i++) { + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.predicted_paths[0].path[i].position.x, + msg_perception.objects[0].kinematics.predicted_paths[0].path[i].position.x); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.predicted_paths[0].path[i].position.y, + msg_perception.objects[0].kinematics.predicted_paths[0].path[i].position.y); + EXPECT_FLOAT_EQ( + msg->objects[0].kinematics.predicted_paths[0].path[i].position.z, + msg_perception.objects[0].kinematics.predicted_paths[0].path[i].position.z); + } + + EXPECT_EQ(msg->objects[0].shape.type, msg_perception.objects[0].shape.type); + for (size_t i = 0; i < msg_perception.objects[0].shape.footprint.points.size(); i++) { + EXPECT_FLOAT_EQ( + msg->objects[0].shape.footprint.points[i].x, + msg_perception.objects[0].shape.footprint.points[i].x); + EXPECT_FLOAT_EQ( + msg->objects[0].shape.footprint.points[i].y, + msg_perception.objects[0].shape.footprint.points[i].y); + EXPECT_FLOAT_EQ( + msg->objects[0].shape.footprint.points[i].z, + msg_perception.objects[0].shape.footprint.points[i].z); + } + EXPECT_FLOAT_EQ( + msg->objects[0].shape.dimensions.x, msg_perception.objects[0].shape.dimensions.x); + EXPECT_FLOAT_EQ( + msg->objects[0].shape.dimensions.y, msg_perception.objects[0].shape.dimensions.y); + EXPECT_FLOAT_EQ( + msg->objects[0].shape.dimensions.z, msg_perception.objects[0].shape.dimensions.z); + + test_completed = true; + }); + + std::cout << "Creating the publisher node..." << std::endl; + + auto node_publisher = std::make_shared("node_publisher", rclcpp::NodeOptions{}); + auto pub = node_publisher->create_publisher( + topic_name_source, rclcpp::QoS{1}); + pub->publish(msg_perception); + + auto start_time = std::chrono::system_clock::now(); + auto max_test_dur = std::chrono::seconds(5); + auto timed_out = false; + + while (rclcpp::ok() && !test_completed) { + rclcpp::spin_some(node_subscriber); + rclcpp::spin_some(node_adapter); + rclcpp::spin_some(node_publisher); + rclcpp::sleep_for(std::chrono::milliseconds(50)); + if (std::chrono::system_clock::now() - start_time > max_test_dur) { + timed_out = true; + break; + } + } + + EXPECT_TRUE(test_completed); + EXPECT_FALSE(timed_out); + + // rclcpp::shutdown(); +} From 33463ceea54f05b91843c88694bba85905b34645 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:50:38 +0900 Subject: [PATCH 251/547] chore: add maintainer in localization_error_monitor and gnss_poser (#5048) Signed-off-by: kminoda --- localization/localization_error_monitor/package.xml | 1 + sensing/gnss_poser/package.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/localization/localization_error_monitor/package.xml b/localization/localization_error_monitor/package.xml index 23e71f2a844cd..4a186bdf95ed1 100644 --- a/localization/localization_error_monitor/package.xml +++ b/localization/localization_error_monitor/package.xml @@ -5,6 +5,7 @@ 0.1.0 ros node for monitoring localization error Yamato Ando + Koji Minoda Masahiro Sakamoto Apache License 2.0 diff --git a/sensing/gnss_poser/package.xml b/sensing/gnss_poser/package.xml index 7d80bc903a550..5bd75020985da 100644 --- a/sensing/gnss_poser/package.xml +++ b/sensing/gnss_poser/package.xml @@ -5,6 +5,7 @@ 1.0.0 The ROS 2 gnss_poser package Yamato Ando + Koji Minoda Apache License 2.0 Ryo Watanabe From ac9521d3052d6a20e614428c728877804659ca90 Mon Sep 17 00:00:00 2001 From: "awf-autoware-bot[bot]" <94889083+awf-autoware-bot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 08:53:53 +0000 Subject: [PATCH 252/547] chore: update CODEOWNERS (#4862) Signed-off-by: GitHub Co-authored-by: kminoda --- .github/CODEOWNERS | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 03f9f22b56015..ef9667865c3a1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -25,7 +25,7 @@ common/motion_utils/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp satoshi common/object_recognition_utils/** satoshi.tanaka@tier4.jp shunsuke.miura@tier4.jp takayuki.murooka@tier4.jp yusuke.muramatsu@tier4.jp common/osqp_interface/** fumiya.watanabe@tier4.jp maxime.clement@tier4.jp satoshi.ota@tier4.jp takayuki.murooka@tier4.jp common/path_distance_calculator/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp makoto.yabuta@tier4.jp -common/perception_utils/** mingyu.li@tier4.jp satoshi.tanaka@tier4.jp shunsuke.miura@tier4.jp yusuke.muramatsu@tier4.jp +common/perception_utils/** satoshi.tanaka@tier4.jp shunsuke.miura@tier4.jp yusuke.muramatsu@tier4.jp common/polar_grid/** yukihiro.saito@tier4.jp common/qp_interface/** fumiya.watanabe@tier4.jp maxime.clement@tier4.jp satoshi.ota@tier4.jp takayuki.murooka@tier4.jp common/rtc_manager_rviz_plugin/** taiki.tanaka@tier4.jp tomoya.kimura@tier4.jp @@ -34,7 +34,7 @@ common/tensorrt_common/** daisuke.nishimatsu@tier4.jp dan.umeda@tier4.jp manato. common/tier4_adapi_rviz_plugin/** hiroki.ota@tier4.jp isamu.takagi@tier4.jp kosuke.takeuchi@tier4.jp common/tier4_api_utils/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp makoto.yabuta@tier4.jp common/tier4_automatic_goal_rviz_plugin/** dawid.moszynski@robotec.ai shumpei.wakabayashi@tier4.jp -common/tier4_autoware_utils/** takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp +common/tier4_autoware_utils/** mamoru.sobue@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp common/tier4_calibration_rviz_plugin/** tomoya.kimura@tier4.jp common/tier4_control_rviz_plugin/** taiki.tanaka@tier4.jp common/tier4_datetime_rviz_plugin/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp makoto.yabuta@tier4.jp @@ -46,6 +46,8 @@ common/tier4_planning_rviz_plugin/** takayuki.murooka@tier4.jp yukihiro.saito@ti common/tier4_screen_capture_rviz_plugin/** taiki.tanaka@tier4.jp common/tier4_simulated_clock_rviz_plugin/** maxime.clement@tier4.jp common/tier4_state_rviz_plugin/** hiroki.ota@tier4.jp isamu.takagi@tier4.jp +common/tier4_system_rviz_plugin/** koji.minoda@tier4.jp +common/tier4_target_object_type_rviz_plugin/** takamasa.horibe@tier4.jp common/tier4_traffic_light_rviz_plugin/** satoshi.ota@tier4.jp common/tier4_vehicle_rviz_plugin/** yukihiro.saito@tier4.jp common/time_utils/** christopherj.ho@gmail.com shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp @@ -85,7 +87,7 @@ launch/tier4_vehicle_launch/** yukihiro.saito@tier4.jp localization/ar_tag_based_localizer/** masahiro.sakamoto@tier4.jp shintaro.sakoda@tier4.jp localization/ekf_localizer/** koji.minoda@tier4.jp masahiro.sakamoto@tier4.jp takamasa.horibe@tier4.jp takeshi.ishita@tier4.jp yamato.ando@tier4.jp localization/gyro_odometer/** masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp -localization/localization_error_monitor/** masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp +localization/localization_error_monitor/** koji.minoda@tier4.jp masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp localization/ndt_scan_matcher/** kento.yabuuchi.2@tier4.jp koji.minoda@tier4.jp masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp localization/pose2twist/** masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp localization/pose_initializer/** isamu.takagi@tier4.jp masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp @@ -102,8 +104,9 @@ map/map_projection_loader/** koji.minoda@tier4.jp masahiro.sakamoto@tier4.jp yam map/map_tf_generator/** azumi.suzuki@tier4.jp map/util/lanelet2_map_preprocessor/** ryohsuke.mitsudome@tier4.jp perception/bytetrack/** manato.hirabayashi@tier4.jp +perception/cluster_merger/** dai.nguyen@tier4.jp shunsuke.miura@tier4.jp yukihiro.saito@tier4.jp perception/compare_map_segmentation/** abrahammonrroy@yahoo.com dai.nguyen@tier4.jp yukihiro.saito@tier4.jp -perception/crosswalk_traffic_light_estimator/** satoshi.ota@tier4.jp +perception/crosswalk_traffic_light_estimator/** satoshi.ota@tier4.jp shunsuke.miura@tier4.jp tao.zhong@tier4.jp perception/detected_object_feature_remover/** tomoya.kimura@tier4.jp perception/detected_object_validation/** shunsuke.miura@tier4.jp yukihiro.saito@tier4.jp perception/detection_by_tracker/** yoshi.ri@tier4.jp yukihiro.saito@tier4.jp @@ -118,7 +121,7 @@ perception/lidar_apollo_segmentation_tvm/** ambroise.vincent@arm.com xinyu.wang@ perception/lidar_apollo_segmentation_tvm_nodes/** ambroise.vincent@arm.com xinyu.wang@tier4.jp perception/lidar_centerpoint/** kenzo.lobos@tier4.jp yusuke.muramatsu@tier4.jp perception/lidar_centerpoint_tvm/** carl.liu@autocore.ai xinyu.wang@tier4.jp -perception/map_based_prediction/** kyoichi.sugahara@tier4.jp shunsuke.miura@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp yoshi.ri@tier4.jp yutaka.shimizu@tier4.jp +perception/map_based_prediction/** kotaro.uetake@tier4.jp kyoichi.sugahara@tier4.jp shunsuke.miura@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp yoshi.ri@tier4.jp yutaka.shimizu@tier4.jp perception/multi_object_tracker/** yoshi.ri@tier4.jp yukihiro.saito@tier4.jp perception/object_merger/** yoshi.ri@tier4.jp yukihiro.saito@tier4.jp perception/object_range_splitter/** yukihiro.saito@tier4.jp @@ -137,10 +140,10 @@ perception/tensorrt_yolo/** daisuke.nishimatsu@tier4.jp perception/tensorrt_yolox/** daisuke.nishimatsu@tier4.jp dan.umeda@tier4.jp manato.hirabayashi@tier4.jp perception/traffic_light_arbiter/** kenzo.lobos@tier4.jp shunsuke.miura@tier4.jp perception/traffic_light_classifier/** shunsuke.miura@tier4.jp yukihiro.saito@tier4.jp -perception/traffic_light_fine_detector/** mingyu.li@tier4.jp shunsuke.miura@tier4.jp +perception/traffic_light_fine_detector/** shunsuke.miura@tier4.jp tao.zhong@tier4.jp perception/traffic_light_map_based_detector/** yukihiro.saito@tier4.jp -perception/traffic_light_multi_camera_fusion/** mingyu.li@tier4.jp shunsuke.miura@tier4.jp -perception/traffic_light_occlusion_predictor/** mingyu.li@tier4.jp shunsuke.miura@tier4.jp +perception/traffic_light_multi_camera_fusion/** shunsuke.miura@tier4.jp tao.zhong@tier4.jp +perception/traffic_light_occlusion_predictor/** shunsuke.miura@tier4.jp tao.zhong@tier4.jp perception/traffic_light_ssd_fine_detector/** daisuke.nishimatsu@tier4.jp perception/traffic_light_visualization/** yukihiro.saito@tier4.jp planning/behavior_path_planner/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp kyoichi.sugahara@tier4.jp satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp yutaka.shimizu@tier4.jp zulfaqar.azmi@tier4.jp @@ -174,7 +177,7 @@ planning/path_smoother/** maxime.clement@tier4.jp takayuki.murooka@tier4.jp planning/planning_debug_tools/** taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp planning/planning_test_utils/** kyoichi.sugahara@tier4.jp takamasa.horibe@tier4.jp planning/planning_validator/** takamasa.horibe@tier4.jp yutaka.shimizu@tier4.jp -planning/route_handler/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp zulfaqar.azmi@tier4.jp +planning/route_handler/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp mamoru.sobue@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp zulfaqar.azmi@tier4.jp planning/rtc_auto_mode_manager/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp planning/rtc_interface/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp planning/rtc_replayer/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp @@ -185,7 +188,7 @@ planning/sampling_based_planner/sampler_common/** maxime.clement@tier4.jp planning/scenario_selector/** fumiya.watanabe@tier4.jp satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp planning/static_centerline_optimizer/** kosuke.takeuchi@tier4.jp takayuki.murooka@tier4.jp planning/surround_obstacle_checker/** satoshi.ota@tier4.jp -sensing/gnss_poser/** yamato.ando@tier4.jp +sensing/gnss_poser/** koji.minoda@tier4.jp yamato.ando@tier4.jp sensing/image_diagnostics/** dai.nguyen@tier4.jp sensing/image_transport_decompressor/** yukihiro.saito@tier4.jp sensing/imu_corrector/** koji.minoda@tier4.jp taiki.yamada@tier4.jp yamato.ando@tier4.jp From a5feaba5495bb377d5f9880d3d5e1250240f13b6 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:10:22 +0200 Subject: [PATCH 253/547] build(tensorrt_yolo): remove download logic from CMake (#3142) Signed-off-by: Esteve Fernandez --- perception/tensorrt_yolo/CMakeLists.txt | 48 ------------------------- 1 file changed, 48 deletions(-) diff --git a/perception/tensorrt_yolo/CMakeLists.txt b/perception/tensorrt_yolo/CMakeLists.txt index f97a65834fbf8..b059ab44d8ece 100755 --- a/perception/tensorrt_yolo/CMakeLists.txt +++ b/perception/tensorrt_yolo/CMakeLists.txt @@ -70,52 +70,6 @@ else() set(CUDNN_AVAIL OFF) endif() -# Download onnx -set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") -if(NOT EXISTS "${DATA_PATH}") - execute_process(COMMAND mkdir -p ${DATA_PATH}) -endif() -function(download FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if(${FILE_HASH} STREQUAL ${EXISTING_FILE_HASH}) - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() -endfunction() - -download(yolov3.onnx bde3b120f51db251d9a89cd25205e062) -download(yolov4.onnx 99ed8c5d18acad2ebb5ee80860d9cbaf) -download(yolov4-tiny.onnx d16dfd56dddfce75f25d5aaf4c8f4c40) -download(yolov5s.onnx 646b295db6089597e79163446d6eedca) -download(yolov5m.onnx 8fc91d70599ac59ae92c6490001fd84b) -download(yolov5l.onnx 99ff1fc966bda8efbc45c3b8bf00eea2) -download(yolov5x.onnx c51beebed276735cb16a18ca32959e9f) -download(coco.names 8fc50561361f8bcf96b0177086e7616c) - # create calib image directory for int8 calibration set(CALIB_IMAGE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/calib_image") if(NOT EXISTS "${CALIB_IMAGE_PATH}") @@ -186,7 +140,6 @@ if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) ament_auto_package(INSTALL_TO_SHARE config - data launch ) @@ -204,7 +157,6 @@ else() message("TensorrtYolo won't be built, CUDA and/or TensorRT were not found.") ament_auto_package(INSTALL_TO_SHARE config - data launch ) endif() From 32a3d86a446aa62fdd7ad8f4bbc602e01a783c06 Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Wed, 20 Sep 2023 19:11:19 +0900 Subject: [PATCH 254/547] fix(ndt_scan_matcher): pose_array_interpolator (#5053) Signed-off-by: yamato-ando Co-authored-by: yamato-ando --- localization/ndt_scan_matcher/src/pose_array_interpolator.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/localization/ndt_scan_matcher/src/pose_array_interpolator.cpp b/localization/ndt_scan_matcher/src/pose_array_interpolator.cpp index 59ecedc18cb5a..f09b71523e804 100644 --- a/localization/ndt_scan_matcher/src/pose_array_interpolator.cpp +++ b/localization/ndt_scan_matcher/src/pose_array_interpolator.cpp @@ -51,6 +51,7 @@ PoseArrayInterpolator::PoseArrayInterpolator( // all validations must be true if (!(is_old_pose_valid && is_new_pose_valid && is_pose_diff_valid)) { + success_ = false; RCLCPP_WARN(logger_, "Validation error."); } } From bbb52719d264c309d8d6fb256ecaef6393c70783 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Wed, 20 Sep 2023 13:34:37 +0300 Subject: [PATCH 255/547] build(image_projection_based_fusion): remove downloading logic from Cmake (#4904) remove downloading logic from Cmake Signed-off-by: Alexey Panferov --- .../CMakeLists.txt | 47 +------------------ 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/perception/image_projection_based_fusion/CMakeLists.txt b/perception/image_projection_based_fusion/CMakeLists.txt index ce7c3b5ea12a9..53aafa44b75d6 100644 --- a/perception/image_projection_based_fusion/CMakeLists.txt +++ b/perception/image_projection_based_fusion/CMakeLists.txt @@ -107,52 +107,8 @@ else() set(CUDNN_AVAIL OFF) endif() -# Create folder to store trained models. -# NOTE: This must be created regardless of CUDA_AVAIL to be specified in ament_auto_package() -set(DATA_PATH ${CMAKE_CURRENT_SOURCE_DIR}/data) -execute_process(COMMAND mkdir -p ${DATA_PATH}) - if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) - # Download trained models - - message(STATUS "start to download") - function(download FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if("${FILE_HASH}" STREQUAL "${EXISTING_FILE_HASH}") - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/pointpainting/v4/${FILE_NAME} - ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/pointpainting/v4/${FILE_NAME} - ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() - endfunction() - - # default model - download(pts_voxel_encoder_pointpainting.onnx 25c70f76a45a64944ccd19f604c99410) - download(pts_backbone_neck_head_pointpainting.onnx 2c7108245240fbd7bf0104dd68225868) + find_package(OpenCV REQUIRED) find_package(Eigen3 REQUIRED) @@ -197,5 +153,4 @@ endif() ament_auto_package(INSTALL_TO_SHARE launch config - data ) From 2476395ecedd78c827def971521b72fba1246cfb Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 20 Sep 2023 12:38:23 +0200 Subject: [PATCH 256/547] build(traffic_light_ssd_fine_detector): remove download logic from CMake (#3145) Signed-off-by: Esteve Fernandez --- .../CMakeLists.txt | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/perception/traffic_light_ssd_fine_detector/CMakeLists.txt b/perception/traffic_light_ssd_fine_detector/CMakeLists.txt index 76a0e3d75c20f..61fa7bcbe0b74 100644 --- a/perception/traffic_light_ssd_fine_detector/CMakeLists.txt +++ b/perception/traffic_light_ssd_fine_detector/CMakeLists.txt @@ -70,48 +70,6 @@ else() set(CUDNN_AVAIL OFF) endif() -# Download caffemodel and prototxt -set(PRETRAINED_MODEL_HASH 34ce7f2cbacbf6da8bc35769f027b73f) -set(LAMP_LABEL_HASH e9f45efb02f2a9aa8ac27b3d5c164905) - -set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") -if(NOT EXISTS "${DATA_PATH}") - execute_process(COMMAND mkdir -p ${DATA_PATH}) -endif() -function(download FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if(${FILE_HASH} STREQUAL ${EXISTING_FILE_HASH}) - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/models/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() -endfunction() -download(mb2-ssd-lite-tlr.onnx 34ce7f2cbacbf6da8bc35769f027b73f) -download(voc_labels_tl.txt e9f45efb02f2a9aa8ac27b3d5c164905) - if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) include_directories( ${OpenCV_INCLUDE_DIRS} @@ -174,7 +132,6 @@ if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) ) ament_auto_package(INSTALL_TO_SHARE - data launch ) From 81983ebf3259daeeef18c4819c01dbd44f4ceb31 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 20 Sep 2023 20:01:36 +0900 Subject: [PATCH 257/547] refactor(common): extern template for motion_utils / remove tier4_autoware_utils.hpp / remove motion_utis.hpp (#5027) --- common/motion_utils/CMakeLists.txt | 3 +- common/motion_utils/README.md | 6 + .../motion_utils/marker/marker_helper.hpp | 4 +- .../marker/virtual_wall_marker_creator.hpp | 4 +- .../include/motion_utils/motion_utils.hpp | 27 - .../motion_utils/resample/resample.hpp | 11 - .../motion_utils/resample/resample_utils.hpp | 1 - .../trajectory/path_with_lane_id.hpp | 82 +-- .../trajectory/tmp_conversion.hpp | 29 +- .../motion_utils/trajectory/trajectory.hpp | 665 +++++++++++++++++- common/motion_utils/package.xml | 1 + .../motion_utils/src/marker/marker_helper.cpp | 3 +- .../marker/virtual_wall_marker_creator.cpp | 2 +- common/motion_utils/src/motion_utils.cpp | 15 - common/motion_utils/src/resample/resample.cpp | 7 + .../src/trajectory/path_with_lane_id.cpp | 82 +++ .../src/trajectory/tmp_conversion.cpp | 60 ++ .../src/trajectory/trajectory.cpp | 625 ++++++++++++++++ .../test/src/resample/test_resample.cpp | 1 + .../test/src/test_motion_utils.cpp | 2 +- .../src/trajectory/test_path_with_lane_id.cpp | 3 +- common/tier4_autoware_utils/CMakeLists.txt | 1 - common/tier4_autoware_utils/README.md | 4 + .../geometry/geometry.hpp | 36 + .../geometry/path_with_lane_id_geometry.hpp | 60 -- .../tier4_autoware_utils.hpp | 45 -- .../src/tier4_autoware_utils.cpp | 15 - .../test_path_with_lane_id_geometry.cpp | 2 +- .../test/src/test_autoware_utils.cpp | 2 +- .../include/utils.hpp | 1 - .../src/mrm_summary_overlay_display.hpp | 1 - .../src/tools/acceleration_meter.hpp | 2 +- .../src/control_validator.cpp | 3 - .../control_validator/src/debug_marker.cpp | 4 +- control/control_validator/src/utils.cpp | 2 +- .../include/cluster_merger/node.hpp | 2 +- .../src/map_based_prediction_node.cpp | 1 + .../test/test_utils.cpp | 2 - ...dar_crossing_objects_noise_filter_node.cpp | 3 +- .../radar_object_tracker/utils/utils.hpp | 1 - .../tracker/model/linear_motion_tracker.cpp | 3 +- .../marker_utils/colors.hpp | 2 +- .../drivable_area_expansion.cpp | 1 - .../goal_planner/freespace_pull_over.cpp | 1 - .../path_safety_checker/objects_filtering.cpp | 1 - .../utils/start_goal_planner_common/utils.cpp | 2 - .../start_planner/geometric_pull_out.cpp | 1 - .../behavior_path_planner/src/utils/utils.cpp | 3 - .../test/test_turn_signal.cpp | 4 +- .../src/scene.cpp | 1 - .../src/scene.cpp | 1 - .../src/util.cpp | 1 + .../src/occlusion_spot_utils.cpp | 1 - .../src/decisions.cpp | 1 - .../src/utilization/path_utilization.cpp | 1 + .../src/utilization/util.cpp | 1 - .../test/src/test_arc_lane_util.cpp | 1 - .../src/debug.cpp | 6 +- .../src/obstacle_velocity_limiter_node.cpp | 2 +- planning/path_smoother/src/replan_checker.cpp | 5 +- .../trajectory_analyzer.hpp | 1 - .../include/planning_debug_tools/util.hpp | 1 - .../src/debug_marker.cpp | 1 + .../accel_brake_map_calibrator_node.hpp | 2 +- 64 files changed, 1517 insertions(+), 347 deletions(-) delete mode 100644 common/motion_utils/include/motion_utils/motion_utils.hpp delete mode 100644 common/motion_utils/src/motion_utils.cpp create mode 100644 common/motion_utils/src/trajectory/tmp_conversion.cpp create mode 100644 common/motion_utils/src/trajectory/trajectory.cpp delete mode 100644 common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp delete mode 100644 common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp delete mode 100644 common/tier4_autoware_utils/src/tier4_autoware_utils.cpp diff --git a/common/motion_utils/CMakeLists.txt b/common/motion_utils/CMakeLists.txt index d1abf9dd730b9..43c8158b2f98b 100644 --- a/common/motion_utils/CMakeLists.txt +++ b/common/motion_utils/CMakeLists.txt @@ -7,13 +7,14 @@ autoware_package() find_package(Boost REQUIRED) ament_auto_add_library(motion_utils SHARED - src/motion_utils.cpp src/distance/distance.cpp src/marker/marker_helper.cpp src/marker/virtual_wall_marker_creator.cpp src/resample/resample.cpp + src/trajectory/trajectory.cpp src/trajectory/interpolation.cpp src/trajectory/path_with_lane_id.cpp + src/trajectory/tmp_conversion.cpp src/vehicle/vehicle_state_checker.cpp ) diff --git a/common/motion_utils/README.md b/common/motion_utils/README.md index 430014a300298..925ee5b162db3 100644 --- a/common/motion_utils/README.md +++ b/common/motion_utils/README.md @@ -109,3 +109,9 @@ const size_t ego_nearest_seg_idx = findFirstNearestSegmentIndex(points, ego_pose const size_t dyn_obj_nearest_seg_idx = findFirstNearestSegmentIndex(points, dyn_obj_pose, dyn_obj_nearest_dist_threshold); const double length_from_ego_to_obj = calcSignedArcLength(points, ego_pose, ego_nearest_seg_idx, dyn_obj_pose, dyn_obj_nearest_seg_idx); ``` + +## For developers + +Some of the template functions in `trajectory.hpp` are mostly used for specific types (`autoware_auto_planning_msgs::msg::PathPoint`, `autoware_auto_planning_msgs::msg::PathPoint`, `autoware_auto_planning_msgs::msg::TrajectoryPoint`), so they are exported as `extern template` functions to speed-up compilation time. + +`motion_utils.hpp` header file was removed because the source files that directly/indirectly include this file took a long time for preprocessing. diff --git a/common/motion_utils/include/motion_utils/marker/marker_helper.hpp b/common/motion_utils/include/motion_utils/marker/marker_helper.hpp index 159f8a33bf325..0604eee65372a 100644 --- a/common/motion_utils/include/motion_utils/marker/marker_helper.hpp +++ b/common/motion_utils/include/motion_utils/marker/marker_helper.hpp @@ -15,13 +15,11 @@ #ifndef MOTION_UTILS__MARKER__MARKER_HELPER_HPP_ #define MOTION_UTILS__MARKER__MARKER_HELPER_HPP_ -#include "motion_utils/resample/resample_utils.hpp" +#include #include -#include #include -#include namespace motion_utils { diff --git a/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp b/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp index 5495dbfcfcf57..95bbcd93e30df 100644 --- a/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp +++ b/common/motion_utils/include/motion_utils/marker/virtual_wall_marker_creator.hpp @@ -15,14 +15,14 @@ #ifndef MOTION_UTILS__MARKER__VIRTUAL_WALL_MARKER_CREATOR_HPP_ #define MOTION_UTILS__MARKER__VIRTUAL_WALL_MARKER_CREATOR_HPP_ -#include "motion_utils/marker/marker_helper.hpp" +#include #include +#include #include #include #include - namespace motion_utils { diff --git a/common/motion_utils/include/motion_utils/motion_utils.hpp b/common/motion_utils/include/motion_utils/motion_utils.hpp deleted file mode 100644 index f6c94fbdacc93..0000000000000 --- a/common/motion_utils/include/motion_utils/motion_utils.hpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2022 Tier IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef MOTION_UTILS__MOTION_UTILS_HPP_ -#define MOTION_UTILS__MOTION_UTILS_HPP_ - -#include "motion_utils/constants.hpp" -#include "motion_utils/marker/marker_helper.hpp" -#include "motion_utils/resample/resample.hpp" -#include "motion_utils/trajectory/interpolation.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" -#include "motion_utils/trajectory/tmp_conversion.hpp" -#include "motion_utils/trajectory/trajectory.hpp" -#include "motion_utils/vehicle/vehicle_state_checker.hpp" - -#endif // MOTION_UTILS__MOTION_UTILS_HPP_ diff --git a/common/motion_utils/include/motion_utils/resample/resample.hpp b/common/motion_utils/include/motion_utils/resample/resample.hpp index cfb945270ed7f..106ed32b18576 100644 --- a/common/motion_utils/include/motion_utils/resample/resample.hpp +++ b/common/motion_utils/include/motion_utils/resample/resample.hpp @@ -15,17 +15,6 @@ #ifndef MOTION_UTILS__RESAMPLE__RESAMPLE_HPP_ #define MOTION_UTILS__RESAMPLE__RESAMPLE_HPP_ -#include "interpolation/interpolation_utils.hpp" -#include "interpolation/linear_interpolation.hpp" -#include "interpolation/spline_interpolation.hpp" -#include "interpolation/zero_order_hold.hpp" -#include "motion_utils/trajectory/trajectory.hpp" -#include "tier4_autoware_utils/geometry/geometry.hpp" -#include "tier4_autoware_utils/geometry/pose_deviation.hpp" -#include "tier4_autoware_utils/math/constants.hpp" - -#include - #include "autoware_auto_planning_msgs/msg/path.hpp" #include "autoware_auto_planning_msgs/msg/path_with_lane_id.hpp" #include "autoware_auto_planning_msgs/msg/trajectory.hpp" diff --git a/common/motion_utils/include/motion_utils/resample/resample_utils.hpp b/common/motion_utils/include/motion_utils/resample/resample_utils.hpp index 2221bd0147104..1e4d0d633325e 100644 --- a/common/motion_utils/include/motion_utils/resample/resample_utils.hpp +++ b/common/motion_utils/include/motion_utils/resample/resample_utils.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include diff --git a/common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp b/common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp index bd6b9f10facca..74862a6229a46 100644 --- a/common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/path_with_lane_id.hpp @@ -15,90 +15,24 @@ #ifndef MOTION_UTILS__TRAJECTORY__PATH_WITH_LANE_ID_HPP_ #define MOTION_UTILS__TRAJECTORY__PATH_WITH_LANE_ID_HPP_ -#include "motion_utils/trajectory/trajectory.hpp" -#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" +#include "autoware_auto_planning_msgs/msg/path_with_lane_id.hpp" +#include #include -#include #include -#include - namespace motion_utils { -inline boost::optional> getPathIndexRangeWithLaneId( - const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int64_t target_lane_id) -{ - size_t start_idx = 0; // NOTE: to prevent from maybe-uninitialized error - size_t end_idx = 0; // NOTE: to prevent from maybe-uninitialized error - - bool found_first_idx = false; - for (size_t i = 0; i < path.points.size(); ++i) { - const auto & p = path.points.at(i); - for (const auto & id : p.lane_ids) { - if (id == target_lane_id) { - if (!found_first_idx) { - start_idx = i; - found_first_idx = true; - } - end_idx = i; - } - } - } - - if (found_first_idx) { - // NOTE: In order to fully cover lanes with target_lane_id, start_idx and end_idx are expanded. - start_idx = static_cast(std::max(0, static_cast(start_idx) - 1)); - end_idx = std::min(path.points.size() - 1, end_idx + 1); +boost::optional> getPathIndexRangeWithLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int64_t target_lane_id); - return std::make_pair(start_idx, end_idx); - } - - return {}; -} - -inline size_t findNearestIndexFromLaneId( +size_t findNearestIndexFromLaneId( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const geometry_msgs::msg::Point & pos, const int64_t lane_id) -{ - const auto opt_range = getPathIndexRangeWithLaneId(path, lane_id); - if (opt_range) { - const size_t start_idx = opt_range->first; - const size_t end_idx = opt_range->second; - - validateNonEmpty(path.points); + const geometry_msgs::msg::Point & pos, const int64_t lane_id); - const auto sub_points = std::vector{ - path.points.begin() + start_idx, path.points.begin() + end_idx + 1}; - validateNonEmpty(sub_points); - - return start_idx + findNearestIndex(sub_points, pos); - } - - return findNearestIndex(path.points, pos); -} - -inline size_t findNearestSegmentIndexFromLaneId( +size_t findNearestSegmentIndexFromLaneId( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const geometry_msgs::msg::Point & pos, const int64_t lane_id) -{ - const size_t nearest_idx = findNearestIndexFromLaneId(path, pos, lane_id); - - if (nearest_idx == 0) { - return 0; - } - if (nearest_idx == path.points.size() - 1) { - return path.points.size() - 2; - } - - const double signed_length = calcLongitudinalOffsetToSegment(path.points, nearest_idx, pos); - - if (signed_length <= 0) { - return nearest_idx - 1; - } - - return nearest_idx; -} + const geometry_msgs::msg::Point & pos, const int64_t lane_id); // @brief Calculates the path to be followed by the rear wheel center in order to make the vehicle // center follow the input path diff --git a/common/motion_utils/include/motion_utils/trajectory/tmp_conversion.hpp b/common/motion_utils/include/motion_utils/trajectory/tmp_conversion.hpp index 3bf06fa241315..6921c56e0199e 100644 --- a/common/motion_utils/include/motion_utils/trajectory/tmp_conversion.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/tmp_conversion.hpp @@ -15,15 +15,9 @@ #ifndef MOTION_UTILS__TRAJECTORY__TMP_CONVERSION_HPP_ #define MOTION_UTILS__TRAJECTORY__TMP_CONVERSION_HPP_ -#include "tier4_autoware_utils/geometry/geometry.hpp" -#include "tier4_autoware_utils/geometry/pose_deviation.hpp" - #include "autoware_auto_planning_msgs/msg/trajectory.hpp" #include "autoware_auto_planning_msgs/msg/trajectory_point.hpp" -#include - -#include #include namespace motion_utils @@ -39,30 +33,15 @@ namespace motion_utils * @todo Decide how to handle the situation that we need to use the trajectory with the size of * points larger than the capacity. (Tier IV) */ -inline autoware_auto_planning_msgs::msg::Trajectory convertToTrajectory( - const std::vector & trajectory) -{ - autoware_auto_planning_msgs::msg::Trajectory output{}; - for (const auto & pt : trajectory) { - output.points.push_back(pt); - if (output.points.size() >= output.CAPACITY) { - break; - } - } - return output; -} +autoware_auto_planning_msgs::msg::Trajectory convertToTrajectory( + const std::vector & trajectory); /** * @brief Convert autoware_auto_planning_msgs::msg::Trajectory to * std::vector. */ -inline std::vector convertToTrajectoryPointArray( - const autoware_auto_planning_msgs::msg::Trajectory & trajectory) -{ - std::vector output(trajectory.points.size()); - std::copy(trajectory.points.begin(), trajectory.points.end(), output.begin()); - return output; -} +std::vector convertToTrajectoryPointArray( + const autoware_auto_planning_msgs::msg::Trajectory & trajectory); } // namespace motion_utils diff --git a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp index b9cf012492a55..83221f4d2f0ae 100644 --- a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp @@ -15,13 +15,16 @@ #ifndef MOTION_UTILS__TRAJECTORY__TRAJECTORY_HPP_ #define MOTION_UTILS__TRAJECTORY__TRAJECTORY_HPP_ -#include "interpolation/spline_interpolation_points_2d.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/geometry/pose_deviation.hpp" #include "tier4_autoware_utils/math/constants.hpp" #include +#include +#include +#include + #include #include @@ -44,6 +47,15 @@ void validateNonEmpty(const T & points) } } +extern template void validateNonEmpty>( + const std::vector &); +extern template void +validateNonEmpty>( + const std::vector &); +extern template void +validateNonEmpty>( + const std::vector &); + /** * @brief validate a point is in a non-sharp angle between two points or not * @param point1 front point @@ -78,7 +90,7 @@ void validateNonSharpAngle( * @return (forward / backward) driving (true / false) */ template -boost::optional isDrivingForward(const T points) +boost::optional isDrivingForward(const T & points) { if (points.size() < 2) { return boost::none; @@ -91,6 +103,16 @@ boost::optional isDrivingForward(const T points) return tier4_autoware_utils::isDrivingForward(first_pose, second_pose); } +extern template boost::optional +isDrivingForward>( + const std::vector &); +extern template boost::optional +isDrivingForward>( + const std::vector &); +extern template boost::optional +isDrivingForward>( + const std::vector &); + /** * @brief checks whether a path of trajectory has forward driving direction using its longitudinal * velocity @@ -98,7 +120,7 @@ boost::optional isDrivingForward(const T points) * @return (forward / backward) driving (true, false, none "if velocity is zero") */ template -boost::optional isDrivingForwardWithTwist(const T points_with_twist) +boost::optional isDrivingForwardWithTwist(const T & points_with_twist) { if (points_with_twist.empty()) { return boost::none; @@ -116,6 +138,16 @@ boost::optional isDrivingForwardWithTwist(const T points_with_twist) return isDrivingForward(points_with_twist); } +extern template boost::optional +isDrivingForwardWithTwist>( + const std::vector &); +extern template boost::optional +isDrivingForwardWithTwist>( + const std::vector &); +extern template boost::optional +isDrivingForwardWithTwist>( + const std::vector &); + /** * @brief remove overlapping points through points container. * Overlapping is determined by calculating the distance between 2 consecutive points. @@ -126,7 +158,7 @@ boost::optional isDrivingForwardWithTwist(const T points_with_twist) * @return points container without overlapping points */ template -T removeOverlapPoints(const T & points, const size_t & start_idx = 0) +T removeOverlapPoints(const T & points, const size_t start_idx = 0) { if (points.size() < start_idx + 1) { return points; @@ -152,6 +184,19 @@ T removeOverlapPoints(const T & points, const size_t & start_idx = 0) return dst; } +extern template std::vector +removeOverlapPoints>( + const std::vector & points, + const size_t start_idx = 0); +extern template std::vector +removeOverlapPoints>( + const std::vector & points, + const size_t start_idx = 0); +extern template std::vector +removeOverlapPoints>( + const std::vector & points, + const size_t start_idx = 0); + /** * @brief search through points container from specified start and end indices about first matching * index of a zero longitudinal velocity point. @@ -181,6 +226,11 @@ boost::optional searchZeroVelocityIndex( return {}; } +extern template boost::optional +searchZeroVelocityIndex>( + const std::vector & points_with_twist, + const size_t src_idx, const size_t dst_idx); + /** * @brief search through points container from specified start index till end of points container * about first matching index of a zero longitudinal velocity point. @@ -189,7 +239,7 @@ boost::optional searchZeroVelocityIndex( * @return first matching index of a zero velocity point inside the points container. */ template -boost::optional searchZeroVelocityIndex(const T & points_with_twist, const size_t & src_idx) +boost::optional searchZeroVelocityIndex(const T & points_with_twist, const size_t src_idx) { try { validateNonEmpty(points_with_twist); @@ -201,6 +251,11 @@ boost::optional searchZeroVelocityIndex(const T & points_with_twist, con return searchZeroVelocityIndex(points_with_twist, src_idx, points_with_twist.size()); } +extern template boost::optional +searchZeroVelocityIndex>( + const std::vector & points_with_twist, + const size_t src_idx); + /** * @brief search through points container from its start to end about first matching index of a zero * longitudinal velocity point. @@ -213,6 +268,10 @@ boost::optional searchZeroVelocityIndex(const T & points_with_twist) return searchZeroVelocityIndex(points_with_twist, 0, points_with_twist.size()); } +extern template boost::optional +searchZeroVelocityIndex>( + const std::vector & points_with_twist); + /** * @brief find nearest point index through points container for a given point. * Finding nearest point is determined by looping through the points container, @@ -241,6 +300,18 @@ size_t findNearestIndex(const T & points, const geometry_msgs::msg::Point & poin return min_idx; } +extern template size_t findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +extern template size_t +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +extern template size_t +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); + /** * @brief find nearest point index through points container for a given pose. * Finding nearest point is determined by looping through the points container, @@ -292,6 +363,22 @@ boost::optional findNearestIndex( return is_nearest_found ? boost::optional(min_idx) : boost::none; } +extern template boost::optional +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); +extern template boost::optional +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); +extern template boost::optional +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); + /** * @brief calculate longitudinal offset (length along trajectory from seg_idx point to nearest point * to p_target on trajectory). If seg_idx point is after that nearest point, length is negative. @@ -347,6 +434,21 @@ double calcLongitudinalOffsetToSegment( return segment_vec.dot(target_vec) / segment_vec.norm(); } +extern template double +calcLongitudinalOffsetToSegment>( + const std::vector & points, const size_t seg_idx, + const geometry_msgs::msg::Point & p_target, const bool throw_exception = false); +extern template double +calcLongitudinalOffsetToSegment>( + const std::vector & points, + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + const bool throw_exception = false); +extern template double +calcLongitudinalOffsetToSegment>( + const std::vector & points, + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + const bool throw_exception = false); + /** * @brief find nearest segment index to point. * Segment is straight path between two continuous points of trajectory. @@ -376,6 +478,19 @@ size_t findNearestSegmentIndex(const T & points, const geometry_msgs::msg::Point return nearest_idx; } +extern template size_t +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +extern template size_t +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +extern template size_t +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); + /** * @brief find nearest segment index to pose * Segment is straight path between two continuous points of trajectory. @@ -414,6 +529,22 @@ boost::optional findNearestSegmentIndex( return *nearest_idx; } +extern template boost::optional +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); +extern template boost::optional +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); +extern template boost::optional +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); + /** * @brief calculate lateral offset from p_target (length from p_target to trajectory) using given * segment index. Segment is straight path between two continuous points of trajectory. @@ -460,6 +591,21 @@ double calcLateralOffset( return cross_vec(2) / segment_vec.norm(); } +extern template double calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const size_t seg_idx, + const bool throw_exception = false); +extern template double +calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const size_t seg_idx, + const bool throw_exception = false); +extern template double +calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const size_t seg_idx, + const bool throw_exception = false); + /** * @brief calculate lateral offset from p_target (length from p_target to trajectory). * The function gets the nearest segment index between the points of trajectory and the given target @@ -500,6 +646,18 @@ double calcLateralOffset( return calcLateralOffset(points, p_target, seg_idx, throw_exception); } +extern template double calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const bool throw_exception = false); +extern template double +calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const bool throw_exception = false); +extern template double +calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const bool throw_exception = false); + /** * @brief calculate length of 2D distance between two points, specified by start and end points * indicies through points container. @@ -531,6 +689,19 @@ double calcSignedArcLength(const T & points, const size_t src_idx, const size_t return dist_sum; } +extern template double +calcSignedArcLength>( + const std::vector & points, const size_t src_idx, + const size_t dst_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); + /** * @brief Computes the partial sums of the elements in the sub-ranges of the range [src_idx, * dst_idx) and return these sum as vector @@ -568,6 +739,19 @@ std::vector calcSignedArcLengthPartialSum( return partial_dist; } +extern template std::vector +calcSignedArcLengthPartialSum>( + const std::vector & points, const size_t src_idx, + const size_t dst_idx); +extern template std::vector +calcSignedArcLengthPartialSum>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); +extern template std::vector +calcSignedArcLengthPartialSum>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); + /** * @brief calculate length of 2D distance between two points, specified by start point and end point * index of points container. @@ -598,6 +782,19 @@ double calcSignedArcLength( return signed_length_on_traj - signed_length_src_offset; } +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t dst_idx); +extern template double +calcSignedArcLength>( + const std::vector &, + const geometry_msgs::msg::Point & src_point, const size_t dst_idx); +extern template double +calcSignedArcLength>( + const std::vector &, + const geometry_msgs::msg::Point & src_point, const size_t dst_idx); + /** * @brief calculate length of 2D distance between two points, specified by start index of points * container and end point. @@ -622,6 +819,19 @@ double calcSignedArcLength( return -calcSignedArcLength(points, dst_point, src_idx); } +extern template double +calcSignedArcLength>( + const std::vector & points, const size_t src_idx, + const geometry_msgs::msg::Point & dst_point); +extern template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point); +extern template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point); + /** * @brief calculate length of 2D distance between two points, specified by start point and end * point. @@ -657,6 +867,19 @@ double calcSignedArcLength( return signed_length_on_traj - signed_length_src_offset + signed_length_dst_offset; } +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const geometry_msgs::msg::Point & dst_point); +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const geometry_msgs::msg::Point & dst_point); +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const geometry_msgs::msg::Point & dst_point); + /** * @brief calculate length of 2D distance for whole points container, from its start to its end. * @param points points of trajectory, path, ... @@ -675,6 +898,15 @@ double calcArcLength(const T & points) return calcSignedArcLength(points, 0, points.size() - 1); } +extern template double calcArcLength>( + const std::vector & points); +extern template double +calcArcLength>( + const std::vector & points); +extern template double +calcArcLength>( + const std::vector & points); + /** * @brief calculate curvature through points container. * The method used for calculating the curvature is using 3 consecutive points through the points @@ -685,7 +917,7 @@ double calcArcLength(const T & points) * @return calculated curvature container through points container */ template -inline std::vector calcCurvature(const T & points) +std::vector calcCurvature(const T & points) { std::vector curvature_vec(points.size()); @@ -701,6 +933,16 @@ inline std::vector calcCurvature(const T & points) return curvature_vec; } +extern template std::vector +calcCurvature>( + const std::vector & points); +extern template std::vector +calcCurvature>( + const std::vector & points); +extern template std::vector +calcCurvature>( + const std::vector & points); + /** * @brief calculate curvature through points container and length of 2d distance for segment used * for curvature calculation. The method used for calculating the curvature is using 3 consecutive @@ -712,7 +954,7 @@ inline std::vector calcCurvature(const T & points) * curvature calculation */ template -inline std::vector> calcCurvatureAndArcLength(const T & points) +std::vector> calcCurvatureAndArcLength(const T & points) { // Note that arclength is for the segment, not the sum. std::vector> curvature_arc_length_vec; @@ -731,6 +973,16 @@ inline std::vector> calcCurvatureAndArcLength(const T return curvature_arc_length_vec; } +extern template std::vector> +calcCurvatureAndArcLength>( + const std::vector & points); +extern template std::vector> +calcCurvatureAndArcLength>( + const std::vector & points); +extern template std::vector> +calcCurvatureAndArcLength>( + const std::vector & points); + /** * @brief calculate length of 2D distance between given start point index in points container and * first point in container with zero longitudinal velocity @@ -758,6 +1010,11 @@ boost::optional calcDistanceToForwardStopPoint( return std::max(0.0, calcSignedArcLength(points_with_twist, src_idx, *closest_stop_idx)); } +extern template boost::optional +calcDistanceToForwardStopPoint>( + const std::vector & points_with_twist, + const size_t src_idx = 0); + /** * @brief calculate the point offset from source point index along the trajectory (or path) (points * container) @@ -768,7 +1025,7 @@ boost::optional calcDistanceToForwardStopPoint( * @return offset point */ template -inline boost::optional calcLongitudinalOffsetPoint( +boost::optional calcLongitudinalOffsetPoint( const T & points, const size_t src_idx, const double offset, const bool throw_exception = false) { try { @@ -822,6 +1079,19 @@ inline boost::optional calcLongitudinalOffsetPoint( return {}; } +extern template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, const size_t src_idx, + const double offset, const bool throw_exception = false); +extern template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const size_t src_idx, const double offset, const bool throw_exception = false); +extern template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const size_t src_idx, const double offset, const bool throw_exception = false); + /** * @brief calculate the point offset from source point along the trajectory (or path) (points * container) @@ -831,7 +1101,7 @@ inline boost::optional calcLongitudinalOffsetPoint( * @return offset point */ template -inline boost::optional calcLongitudinalOffsetPoint( +boost::optional calcLongitudinalOffsetPoint( const T & points, const geometry_msgs::msg::Point & src_point, const double offset) { try { @@ -854,6 +1124,19 @@ inline boost::optional calcLongitudinalOffsetPoint( return calcLongitudinalOffsetPoint(points, src_seg_idx, offset + signed_length_src_offset); } +extern template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset); +extern template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset); +extern template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset); + /** * @brief calculate the point offset from source point index along the trajectory (or path) (points * container) @@ -865,7 +1148,7 @@ inline boost::optional calcLongitudinalOffsetPoint( * @return offset pose */ template -inline boost::optional calcLongitudinalOffsetPose( +boost::optional calcLongitudinalOffsetPose( const T & points, const size_t src_idx, const double offset, const bool set_orientation_from_position_direction = true, const bool throw_exception = false) { @@ -936,6 +1219,22 @@ inline boost::optional calcLongitudinalOffsetPose( return {}; } +extern template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, const size_t src_idx, + const double offset, const bool set_orientation_from_position_direction = true, + const bool throw_exception = false); +extern template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const size_t src_idx, const double offset, + const bool set_orientation_from_position_direction = true, const bool throw_exception = false); +extern template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const size_t src_idx, const double offset, + const bool set_orientation_from_position_direction = true, const bool throw_exception = false); + /** * @brief calculate the point offset from source point along the trajectory (or path) (points * container) @@ -947,7 +1246,7 @@ inline boost::optional calcLongitudinalOffsetPose( * @return offset pase */ template -inline boost::optional calcLongitudinalOffsetPose( +boost::optional calcLongitudinalOffsetPose( const T & points, const geometry_msgs::msg::Point & src_point, const double offset, const bool set_orientation_from_position_direction = true) { @@ -967,6 +1266,22 @@ inline boost::optional calcLongitudinalOffsetPose( set_orientation_from_position_direction); } +extern template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset, + const bool set_orientation_from_position_direction = true); +extern template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset, + const bool set_orientation_from_position_direction = true); +extern template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset, + const bool set_orientation_from_position_direction = true); + /** * @brief insert a point in points container (trajectory, path, ...) using segment id * @param seg_idx segment index of point at beginning of length @@ -977,7 +1292,7 @@ inline boost::optional calcLongitudinalOffsetPose( * @return index of segment id, where point is inserted */ template -inline boost::optional insertTargetPoint( +boost::optional insertTargetPoint( const size_t seg_idx, const geometry_msgs::msg::Point & p_target, T & points, const double overlap_threshold = 1e-3) { @@ -1053,6 +1368,22 @@ inline boost::optional insertTargetPoint( return seg_idx; } +extern template boost::optional +insertTargetPoint>( + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold = 1e-3); + /** * @brief insert a point in points container (trajectory, path, ...) using length of point to be * inserted @@ -1064,7 +1395,7 @@ inline boost::optional insertTargetPoint( * @return index of segment id, where point is inserted */ template -inline boost::optional insertTargetPoint( +boost::optional insertTargetPoint( const double insert_point_length, const geometry_msgs::msg::Point & p_target, T & points, const double overlap_threshold = 1e-3) { @@ -1077,6 +1408,7 @@ inline boost::optional insertTargetPoint( // Get Nearest segment index boost::optional segment_idx = boost::none; for (size_t i = 1; i < points.size(); ++i) { + // TODO(Mamoru Sobue): find accumulated sum beforehand const double length = calcSignedArcLength(points, 0, i); if (insert_point_length <= length) { segment_idx = i - 1; @@ -1091,6 +1423,22 @@ inline boost::optional insertTargetPoint( return insertTargetPoint(*segment_idx, p_target, points, overlap_threshold); } +extern template boost::optional +insertTargetPoint>( + const double insert_point_length, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const double insert_point_length, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const double insert_point_length, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold = 1e-3); + /** * @brief insert a point in points container (trajectory, path, ...) using segment index and length * of point to be inserted @@ -1102,7 +1450,7 @@ inline boost::optional insertTargetPoint( * @return index of insert point */ template -inline boost::optional insertTargetPoint( +boost::optional insertTargetPoint( const size_t src_segment_idx, const double insert_point_length, T & points, const double overlap_threshold = 1e-3) { @@ -1148,6 +1496,22 @@ inline boost::optional insertTargetPoint( return insertTargetPoint(*segment_idx, p_target, points, overlap_threshold); } +extern template boost::optional +insertTargetPoint>( + const size_t src_segment_idx, const double insert_point_length, + std::vector & points, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const size_t src_segment_idx, const double insert_point_length, + std::vector & points, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const size_t src_segment_idx, const double insert_point_length, + std::vector & points, + const double overlap_threshold = 1e-3); + /** * @brief Insert a target point from a source pose on the trajectory * @param src_pose source pose on the trajectory @@ -1162,7 +1526,7 @@ inline boost::optional insertTargetPoint( * @return index of insert point */ template -inline boost::optional insertTargetPoint( +boost::optional insertTargetPoint( const geometry_msgs::msg::Pose & src_pose, const double insert_point_length, T & points, const double max_dist = std::numeric_limits::max(), const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3) @@ -1185,6 +1549,25 @@ inline boost::optional insertTargetPoint( *nearest_segment_idx, insert_point_length + offset_length, points, overlap_threshold); } +extern template boost::optional +insertTargetPoint>( + const geometry_msgs::msg::Pose & src_pose, const double insert_point_length, + std::vector & points, + const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const geometry_msgs::msg::Pose & src_pose, const double insert_point_length, + std::vector & points, + const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3); +extern template boost::optional +insertTargetPoint>( + const geometry_msgs::msg::Pose & src_pose, const double insert_point_length, + std::vector & points, + const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3); + /** * @brief Insert stop point from the source segment index * @param src_segment_idx start segment index on the trajectory @@ -1195,7 +1578,7 @@ inline boost::optional insertTargetPoint( * @return index of stop point */ template -inline boost::optional insertStopPoint( +boost::optional insertStopPoint( const size_t src_segment_idx, const double distance_to_stop_point, T & points_with_twist, const double overlap_threshold = 1e-3) { @@ -1218,6 +1601,22 @@ inline boost::optional insertStopPoint( return stop_idx; } +extern template boost::optional +insertStopPoint>( + const size_t src_segment_idx, const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertStopPoint>( + const size_t src_segment_idx, const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertStopPoint>( + const size_t src_segment_idx, const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); + /** * @brief Insert stop point from the front point of the path * @param distance_to_stop_point distance to stop point from the front point of the path @@ -1227,7 +1626,7 @@ inline boost::optional insertStopPoint( * @return index of stop point */ template -inline boost::optional insertStopPoint( +boost::optional insertStopPoint( const double distance_to_stop_point, T & points_with_twist, const double overlap_threshold = 1e-3) { validateNonEmpty(points_with_twist); @@ -1251,6 +1650,22 @@ inline boost::optional insertStopPoint( return boost::none; } +extern template boost::optional +insertStopPoint>( + const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertStopPoint>( + const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); +extern template boost::optional +insertStopPoint>( + const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); + /** * @brief Insert Stop point from the source pose * @param src_pose source pose @@ -1265,7 +1680,7 @@ inline boost::optional insertStopPoint( * @return index of stop point */ template -inline boost::optional insertStopPoint( +boost::optional insertStopPoint( const geometry_msgs::msg::Pose & src_pose, const double distance_to_stop_point, T & points_with_twist, const double max_dist = std::numeric_limits::max(), const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3) @@ -1290,6 +1705,25 @@ inline boost::optional insertStopPoint( return stop_idx; } +extern template boost::optional +insertStopPoint>( + const geometry_msgs::msg::Pose & src_pose, const double distance_to_stop_point, + std::vector & points_with_twist, + const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3); +extern template boost::optional +insertStopPoint>( + const geometry_msgs::msg::Pose & src_pose, const double distance_to_stop_point, + std::vector & points_with_twist, + const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3); +extern template boost::optional +insertStopPoint>( + const geometry_msgs::msg::Pose & src_pose, const double distance_to_stop_point, + std::vector & points_with_twist, + const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max(), const double overlap_threshold = 1e-3); + /** * @brief Insert Stop point that is in the stop segment index * @param stop_seg_idx segment index of the stop pose @@ -1318,6 +1752,12 @@ boost::optional insertStopPoint( return insert_idx; } +extern template boost::optional +insertStopPoint>( + const size_t stop_seg_idx, const geometry_msgs::msg::Point & stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); + /** * @brief Insert deceleration point from the source pose * @param src_point source point @@ -1354,6 +1794,12 @@ boost::optional insertDecelPoint( return insert_idx; } +extern template boost::optional +insertDecelPoint>( + const geometry_msgs::msg::Point & src_point, const double distance_to_decel_point, + const double velocity, + std::vector & points_with_twist); + /** * @brief Insert orientation to each point in points container (trajectory, path, ...) * @param points points of trajectory, path, ... (input / output) @@ -1391,6 +1837,17 @@ void insertOrientation(T & points, const bool is_driving_forward) } } +extern template void insertOrientation>( + std::vector & points, const bool is_driving_forward); +extern template void +insertOrientation>( + std::vector & points, + const bool is_driving_forward); +extern template void +insertOrientation>( + std::vector & points, + const bool is_driving_forward); + /** * @brief Remove points with invalid orientation differences from a given points container * (trajectory, path, ...) @@ -1441,6 +1898,22 @@ double calcSignedArcLength( return signed_length_on_traj - signed_length_src_offset + signed_length_dst_offset; } +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); + /** * @brief calculate length of 2D distance between two points, specified by start point and its * segment index in points container and end point index in points container @@ -1466,6 +1939,19 @@ double calcSignedArcLength( return signed_length_on_traj - signed_length_src_offset; } +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, const size_t dst_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, const size_t dst_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, const size_t dst_idx); + /** * @brief calculate length of 2D distance between two points, specified by start point index in * points container and end point and its segment index in points container @@ -1491,6 +1977,19 @@ double calcSignedArcLength( return signed_length_on_traj + signed_length_dst_offset; } +extern template double +calcSignedArcLength>( + const std::vector & points, const size_t src_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +extern template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); + /** * @brief find first nearest point index through points container for a given pose with soft * distance and yaw constraints. Finding nearest point is determined by looping through the points @@ -1581,6 +2080,25 @@ size_t findFirstNearestIndexWithSoftConstraints( return findNearestIndex(points, pose.position); } +extern template size_t +findFirstNearestIndexWithSoftConstraints>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()); +extern template size_t findFirstNearestIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()); +extern template size_t findFirstNearestIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()); + /** * @brief find nearest segment index to pose with soft constraints * Segment is straight path between two continuous points of trajectory @@ -1618,6 +2136,25 @@ size_t findFirstNearestSegmentIndexWithSoftConstraints( return nearest_idx; } +extern template size_t findFirstNearestSegmentIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()); +extern template size_t findFirstNearestSegmentIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()); +extern template size_t findFirstNearestSegmentIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, + const double dist_threshold = std::numeric_limits::max(), + const double yaw_threshold = std::numeric_limits::max()); + /** * @brief calculate the point offset from source point along the trajectory (or path) * @brief calculate length of 2D distance between given pose and first point in container with zero @@ -1664,6 +2201,22 @@ boost::optional calcDistanceToForwardStopPoint( return std::max(0.0, closest_stop_dist); } +extern template boost::optional +calcDistanceToForwardStopPoint>( + const std::vector & points_with_twist, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); +extern template boost::optional +calcDistanceToForwardStopPoint>( + const std::vector & points_with_twist, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); +extern template boost::optional +calcDistanceToForwardStopPoint>( + const std::vector & points_with_twist, + const geometry_msgs::msg::Pose & pose, const double max_dist = std::numeric_limits::max(), + const double max_yaw = std::numeric_limits::max()); + // NOTE: Points after forward length from the point will be cropped // forward_length is assumed to be positive. template @@ -1688,6 +2241,22 @@ T cropForwardPoints( return points; } +extern template std::vector +cropForwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length); +extern template std::vector +cropForwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length); +extern template std::vector +cropForwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length); + // NOTE: Points before backward length from the point will be cropped // backward_length is assumed to be positive. template @@ -1712,6 +2281,22 @@ T cropBackwardPoints( return points; } +extern template std::vector +cropBackwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double backward_length); +extern template std::vector +cropBackwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double backward_length); +extern template std::vector +cropBackwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double backward_length); + template T cropPoints( const T & points, const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, @@ -1740,6 +2325,22 @@ T cropPoints( return cropped_points; } +extern template std::vector +cropPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length, const double backward_length); +extern template std::vector +cropPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length, const double backward_length); +extern template std::vector +cropPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length, const double backward_length); + /** * @brief Calculate the angle of the input pose with respect to the nearest trajectory segment. * The function gets the nearest segment index between the points of the trajectory and the given @@ -1786,6 +2387,18 @@ double calcYawDeviation( return tier4_autoware_utils::normalizeRadian(pose_yaw - path_yaw); } +extern template double calcYawDeviation>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const bool throw_exception = false); +extern template double +calcYawDeviation>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const bool throw_exception = false); +extern template double +calcYawDeviation>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const bool throw_exception = false); + /** * @brief Check if the given target point is in front of the based pose from the trajectory. * if the points is empty, the function returns false @@ -1809,6 +2422,22 @@ bool isTargetPointFront( return s_target - s_base > threshold; } + +extern template bool isTargetPointFront>( + const std::vector & points, + const geometry_msgs::msg::Point & base_point, const geometry_msgs::msg::Point & target_point, + const double threshold = 0.0); +extern template bool +isTargetPointFront>( + const std::vector & points, + const geometry_msgs::msg::Point & base_point, const geometry_msgs::msg::Point & target_point, + const double threshold = 0.0); +extern template bool +isTargetPointFront>( + const std::vector & points, + const geometry_msgs::msg::Point & base_point, const geometry_msgs::msg::Point & target_point, + const double threshold = 0.0); + } // namespace motion_utils #endif // MOTION_UTILS__TRAJECTORY__TRAJECTORY_HPP_ diff --git a/common/motion_utils/package.xml b/common/motion_utils/package.xml index 834342624bfcd..334b023f7f29a 100644 --- a/common/motion_utils/package.xml +++ b/common/motion_utils/package.xml @@ -13,6 +13,7 @@ Taiki Tanaka Takamasa Horibe Tomoya Kimura + Mamoru Sobue Apache License 2.0 Yutaka Shimizu diff --git a/common/motion_utils/src/marker/marker_helper.cpp b/common/motion_utils/src/marker/marker_helper.cpp index 3516fc71f219f..50def253b6d16 100644 --- a/common/motion_utils/src/marker/marker_helper.cpp +++ b/common/motion_utils/src/marker/marker_helper.cpp @@ -14,11 +14,12 @@ #include "motion_utils/marker/marker_helper.hpp" +#include "motion_utils/resample/resample_utils.hpp" #include "tier4_autoware_utils/ros/marker_helper.hpp" #include -#include +#include using tier4_autoware_utils::appendMarkerArray; using tier4_autoware_utils::createDefaultMarker; diff --git a/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp b/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp index d70caf9b057e6..c22aaa0d5e87c 100644 --- a/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp +++ b/common/motion_utils/src/marker/virtual_wall_marker_creator.cpp @@ -14,7 +14,7 @@ #include "motion_utils/marker/virtual_wall_marker_creator.hpp" -#include +#include "motion_utils/marker/marker_helper.hpp" namespace motion_utils { diff --git a/common/motion_utils/src/motion_utils.cpp b/common/motion_utils/src/motion_utils.cpp deleted file mode 100644 index e1c86d1ad2bfd..0000000000000 --- a/common/motion_utils/src/motion_utils.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 Tier IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "motion_utils/motion_utils.hpp" diff --git a/common/motion_utils/src/resample/resample.cpp b/common/motion_utils/src/resample/resample.cpp index 1a800a38d1051..6bada921804d4 100644 --- a/common/motion_utils/src/resample/resample.cpp +++ b/common/motion_utils/src/resample/resample.cpp @@ -14,8 +14,15 @@ #include "motion_utils/resample/resample.hpp" +#include "interpolation/interpolation_utils.hpp" +#include "interpolation/linear_interpolation.hpp" +#include "interpolation/spline_interpolation.hpp" +#include "interpolation/zero_order_hold.hpp" #include "motion_utils/resample/resample_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/geometry/pose_deviation.hpp" +#include "tier4_autoware_utils/math/constants.hpp" namespace motion_utils { diff --git a/common/motion_utils/src/trajectory/path_with_lane_id.cpp b/common/motion_utils/src/trajectory/path_with_lane_id.cpp index 759f0ef4ed790..c74db0f61d6fb 100644 --- a/common/motion_utils/src/trajectory/path_with_lane_id.cpp +++ b/common/motion_utils/src/trajectory/path_with_lane_id.cpp @@ -14,8 +14,90 @@ #include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "interpolation/spline_interpolation_points_2d.hpp" +#include "motion_utils/trajectory/trajectory.hpp" + +#include +#include +#include + namespace motion_utils { + +boost::optional> getPathIndexRangeWithLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int64_t target_lane_id) +{ + size_t start_idx = 0; // NOTE: to prevent from maybe-uninitialized error + size_t end_idx = 0; // NOTE: to prevent from maybe-uninitialized error + + bool found_first_idx = false; + for (size_t i = 0; i < path.points.size(); ++i) { + const auto & p = path.points.at(i); + for (const auto & id : p.lane_ids) { + if (id == target_lane_id) { + if (!found_first_idx) { + start_idx = i; + found_first_idx = true; + } + end_idx = i; + } + } + } + + if (found_first_idx) { + // NOTE: In order to fully cover lanes with target_lane_id, start_idx and end_idx are expanded. + start_idx = static_cast(std::max(0, static_cast(start_idx) - 1)); + end_idx = std::min(path.points.size() - 1, end_idx + 1); + + return std::make_pair(start_idx, end_idx); + } + + return {}; +} + +size_t findNearestIndexFromLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const geometry_msgs::msg::Point & pos, const int64_t lane_id) +{ + const auto opt_range = getPathIndexRangeWithLaneId(path, lane_id); + if (opt_range) { + const size_t start_idx = opt_range->first; + const size_t end_idx = opt_range->second; + + validateNonEmpty(path.points); + + const auto sub_points = std::vector{ + path.points.begin() + start_idx, path.points.begin() + end_idx + 1}; + validateNonEmpty(sub_points); + + return start_idx + findNearestIndex(sub_points, pos); + } + + return findNearestIndex(path.points, pos); +} + +size_t findNearestSegmentIndexFromLaneId( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, + const geometry_msgs::msg::Point & pos, const int64_t lane_id) +{ + const size_t nearest_idx = findNearestIndexFromLaneId(path, pos, lane_id); + + if (nearest_idx == 0) { + return 0; + } + if (nearest_idx == path.points.size() - 1) { + return path.points.size() - 2; + } + + const double signed_length = calcLongitudinalOffsetToSegment(path.points, nearest_idx, pos); + + if (signed_length <= 0) { + return nearest_idx - 1; + } + + return nearest_idx; +} + // NOTE: rear_to_cog is supposed to be positive autoware_auto_planning_msgs::msg::PathWithLaneId convertToRearWheelCenter( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const double rear_to_cog, diff --git a/common/motion_utils/src/trajectory/tmp_conversion.cpp b/common/motion_utils/src/trajectory/tmp_conversion.cpp new file mode 100644 index 0000000000000..5c907a5926046 --- /dev/null +++ b/common/motion_utils/src/trajectory/tmp_conversion.cpp @@ -0,0 +1,60 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "motion_utils/trajectory/tmp_conversion.hpp" + +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/geometry/pose_deviation.hpp" + +#include + +namespace motion_utils +{ +/** + * @brief Convert std::vector to + * autoware_auto_planning_msgs::msg::Trajectory. This function is temporarily added for porting to + * autoware_auto_msgs. We should consider whether to remove this function after the porting is done. + * @attention This function just clips + * std::vector up to the capacity of Trajectory. + * Therefore, the error handling out of this function is necessary if the size of the input greater + * than the capacity. + * @todo Decide how to handle the situation that we need to use the trajectory with the size of + * points larger than the capacity. (Tier IV) + */ +autoware_auto_planning_msgs::msg::Trajectory convertToTrajectory( + const std::vector & trajectory) +{ + autoware_auto_planning_msgs::msg::Trajectory output{}; + for (const auto & pt : trajectory) { + output.points.push_back(pt); + if (output.points.size() >= output.CAPACITY) { + break; + } + } + return output; +} + +/** + * @brief Convert autoware_auto_planning_msgs::msg::Trajectory to + * std::vector. + */ +std::vector convertToTrajectoryPointArray( + const autoware_auto_planning_msgs::msg::Trajectory & trajectory) +{ + std::vector output(trajectory.points.size()); + std::copy(trajectory.points.begin(), trajectory.points.end(), output.begin()); + return output; +} + +} // namespace motion_utils diff --git a/common/motion_utils/src/trajectory/trajectory.cpp b/common/motion_utils/src/trajectory/trajectory.cpp new file mode 100644 index 0000000000000..12074e537c106 --- /dev/null +++ b/common/motion_utils/src/trajectory/trajectory.cpp @@ -0,0 +1,625 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "motion_utils/trajectory/trajectory.hpp" + +namespace motion_utils +{ + +// +template void validateNonEmpty>( + const std::vector &); +template void validateNonEmpty>( + const std::vector &); +template void validateNonEmpty>( + const std::vector &); + +// +template boost::optional +isDrivingForward>( + const std::vector &); +template boost::optional +isDrivingForward>( + const std::vector &); +template boost::optional +isDrivingForward>( + const std::vector &); + +// +template boost::optional +isDrivingForwardWithTwist>( + const std::vector &); +template boost::optional +isDrivingForwardWithTwist>( + const std::vector &); +template boost::optional +isDrivingForwardWithTwist>( + const std::vector &); + +// +template std::vector +removeOverlapPoints>( + const std::vector & points, const size_t start_idx); +template std::vector +removeOverlapPoints>( + const std::vector & points, + const size_t start_idx); +template std::vector +removeOverlapPoints>( + const std::vector & points, + const size_t start_idx); + +// +template boost::optional +searchZeroVelocityIndex>( + const std::vector & points_with_twist, + const size_t src_idx, const size_t dst_idx); + +// +template boost::optional +searchZeroVelocityIndex>( + const std::vector & points_with_twist, + const size_t src_idx); + +// +template boost::optional +searchZeroVelocityIndex>( + const std::vector & points_with_twist); + +// +template size_t findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +template size_t +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +template size_t findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); + +// +template boost::optional +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist, const double max_yaw); +template boost::optional +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist, const double max_yaw); +template boost::optional +findNearestIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist, const double max_yaw); + +// +template double +calcLongitudinalOffsetToSegment>( + const std::vector & points, const size_t seg_idx, + const geometry_msgs::msg::Point & p_target, const bool throw_exception); +template double +calcLongitudinalOffsetToSegment>( + const std::vector & points, + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, const bool throw_exception); +template double +calcLongitudinalOffsetToSegment>( + const std::vector & points, + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, const bool throw_exception); + +// +template size_t findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +template size_t +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); +template size_t +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Point & point); + +// +template boost::optional +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist, const double max_yaw); +template boost::optional +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist, const double max_yaw); +template boost::optional +findNearestSegmentIndex>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double max_dist, const double max_yaw); + +// +template double calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const size_t seg_idx, const bool throw_exception); +template double +calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const size_t seg_idx, const bool throw_exception); +template double calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const size_t seg_idx, const bool throw_exception); + +// +template double calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const bool throw_exception); +template double +calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const bool throw_exception); +template double calcLateralOffset>( + const std::vector & points, + const geometry_msgs::msg::Point & p_target, const bool throw_exception); + +// +template double calcSignedArcLength>( + const std::vector & points, const size_t src_idx, + const size_t dst_idx); +template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); +template double calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); + +// +template std::vector +calcSignedArcLengthPartialSum>( + const std::vector & points, const size_t src_idx, + const size_t dst_idx); +template std::vector +calcSignedArcLengthPartialSum>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); +template std::vector +calcSignedArcLengthPartialSum>( + const std::vector & points, + const size_t src_idx, const size_t dst_idx); + +// +template double calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t dst_idx); +template double +calcSignedArcLength>( + const std::vector &, + const geometry_msgs::msg::Point & src_point, const size_t dst_idx); +template double calcSignedArcLength>( + const std::vector &, + const geometry_msgs::msg::Point & src_point, const size_t dst_idx); + +// +template double calcSignedArcLength>( + const std::vector & points, const size_t src_idx, + const geometry_msgs::msg::Point & dst_point); +template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point); +template double calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point); + +// +template double calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const geometry_msgs::msg::Point & dst_point); +template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const geometry_msgs::msg::Point & dst_point); +template double calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const geometry_msgs::msg::Point & dst_point); + +// +template double calcArcLength>( + const std::vector & points); +template double calcArcLength>( + const std::vector & points); +template double calcArcLength>( + const std::vector & points); + +// +template std::vector +calcCurvature>( + const std::vector & points); +template std::vector +calcCurvature>( + const std::vector & points); +template std::vector +calcCurvature>( + const std::vector & points); + +// +template std::vector> +calcCurvatureAndArcLength>( + const std::vector & points); +template std::vector> +calcCurvatureAndArcLength>( + const std::vector & points); +template std::vector> +calcCurvatureAndArcLength>( + const std::vector & points); + +// +template boost::optional +calcDistanceToForwardStopPoint>( + const std::vector & points_with_twist, + const size_t src_idx); + +// +template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, const size_t src_idx, + const double offset, const bool throw_exception); +template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const size_t src_idx, const double offset, const bool throw_exception); +template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const size_t src_idx, const double offset, const bool throw_exception); + +// +template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset); +template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset); +template boost::optional +calcLongitudinalOffsetPoint>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset); + +// +template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, const size_t src_idx, + const double offset, const bool set_orientation_from_position_direction, + const bool throw_exception); +template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const size_t src_idx, const double offset, const bool set_orientation_from_position_direction, + const bool throw_exception); +template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const size_t src_idx, const double offset, const bool set_orientation_from_position_direction, + const bool throw_exception); + +// +template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset, + const bool set_orientation_from_position_direction); +template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset, + const bool set_orientation_from_position_direction); +template boost::optional +calcLongitudinalOffsetPose>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const double offset, + const bool set_orientation_from_position_direction); + +// +template boost::optional +insertTargetPoint>( + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const size_t seg_idx, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold); + +// +template boost::optional +insertTargetPoint>( + const double insert_point_length, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const double insert_point_length, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const double insert_point_length, const geometry_msgs::msg::Point & p_target, + std::vector & points, + const double overlap_threshold); + +// +template boost::optional +insertTargetPoint>( + const size_t src_segment_idx, const double insert_point_length, + std::vector & points, + const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const size_t src_segment_idx, const double insert_point_length, + std::vector & points, + const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const size_t src_segment_idx, const double insert_point_length, + std::vector & points, + const double overlap_threshold); + +// +template boost::optional +insertTargetPoint>( + const geometry_msgs::msg::Pose & src_pose, const double insert_point_length, + std::vector & points, const double max_dist, + const double max_yaw, const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const geometry_msgs::msg::Pose & src_pose, const double insert_point_length, + std::vector & points, + const double max_dist, const double max_yaw, const double overlap_threshold); +template boost::optional +insertTargetPoint>( + const geometry_msgs::msg::Pose & src_pose, const double insert_point_length, + std::vector & points, const double max_dist, + const double max_yaw, const double overlap_threshold); + +// +template boost::optional +insertStopPoint>( + const size_t src_segment_idx, const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); +template boost::optional +insertStopPoint>( + const size_t src_segment_idx, const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); +template boost::optional +insertStopPoint>( + const size_t src_segment_idx, const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold = 1e-3); + +// +template boost::optional +insertStopPoint>( + const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold); +template boost::optional +insertStopPoint>( + const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold); +template boost::optional +insertStopPoint>( + const double distance_to_stop_point, + std::vector & points_with_twist, + const double overlap_threshold); + +// +template boost::optional +insertStopPoint>( + const geometry_msgs::msg::Pose & src_pose, const double distance_to_stop_point, + std::vector & points_with_twist, + const double max_dist, const double max_yaw, const double overlap_threshold); +template boost::optional +insertStopPoint>( + const geometry_msgs::msg::Pose & src_pose, const double distance_to_stop_point, + std::vector & points_with_twist, + const double max_dist, const double max_yaw, const double overlap_threshold); +template boost::optional +insertStopPoint>( + const geometry_msgs::msg::Pose & src_pose, const double distance_to_stop_point, + std::vector & points_with_twist, + const double max_dist, const double max_yaw, const double overlap_threshold); + +// +template boost::optional +insertStopPoint>( + const size_t stop_seg_idx, const geometry_msgs::msg::Point & stop_point, + std::vector & points_with_twist, + const double overlap_threshold); + +// +template boost::optional +insertDecelPoint>( + const geometry_msgs::msg::Point & src_point, const double distance_to_decel_point, + const double velocity, + std::vector & points_with_twist); + +// +template void insertOrientation>( + std::vector & points, const bool is_driving_forward); +template void insertOrientation>( + std::vector & points, + const bool is_driving_forward); +template void insertOrientation>( + std::vector & points, + const bool is_driving_forward); + +// +template double calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +template double calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); + +// +template double calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, const size_t dst_idx); +template double +calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, const size_t dst_idx); +template double calcSignedArcLength>( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, const size_t src_seg_idx, const size_t dst_idx); + +// +template double calcSignedArcLength>( + const std::vector & points, const size_t src_idx, + const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +template double +calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); +template double calcSignedArcLength>( + const std::vector & points, + const size_t src_idx, const geometry_msgs::msg::Point & dst_point, const size_t dst_seg_idx); + +// +template size_t +findFirstNearestIndexWithSoftConstraints>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double dist_threshold, const double yaw_threshold); +template size_t findFirstNearestIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double dist_threshold, const double yaw_threshold); +template size_t findFirstNearestIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double dist_threshold, const double yaw_threshold); + +// +template size_t findFirstNearestSegmentIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double dist_threshold, const double yaw_threshold); +template size_t findFirstNearestSegmentIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double dist_threshold, const double yaw_threshold); +template size_t findFirstNearestSegmentIndexWithSoftConstraints< + std::vector>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const double dist_threshold, const double yaw_threshold); + +// +template boost::optional +calcDistanceToForwardStopPoint>( + const std::vector & points_with_twist, + const geometry_msgs::msg::Pose & pose, const double max_dist, const double max_yaw); + +// +template std::vector +cropForwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length); +template std::vector +cropForwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length); +template std::vector +cropForwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length); + +// +template std::vector +cropBackwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double backward_length); +template std::vector +cropBackwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double backward_length); +template std::vector +cropBackwardPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double backward_length); + +// +template std::vector +cropPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length, const double backward_length); +template std::vector +cropPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length, const double backward_length); +template std::vector +cropPoints>( + const std::vector & points, + const geometry_msgs::msg::Point & target_pos, const size_t target_seg_idx, + const double forward_length, const double backward_length); + +// +template double calcYawDeviation>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const bool throw_exception); +template double +calcYawDeviation>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const bool throw_exception); +template double calcYawDeviation>( + const std::vector & points, + const geometry_msgs::msg::Pose & pose, const bool throw_exception); + +// +template bool isTargetPointFront>( + const std::vector & points, + const geometry_msgs::msg::Point & base_point, const geometry_msgs::msg::Point & target_point, + const double threshold); +template bool +isTargetPointFront>( + const std::vector & points, + const geometry_msgs::msg::Point & base_point, const geometry_msgs::msg::Point & target_point, + const double threshold); +template bool isTargetPointFront>( + const std::vector & points, + const geometry_msgs::msg::Point & base_point, const geometry_msgs::msg::Point & target_point, + const double threshold); + +} // namespace motion_utils diff --git a/common/motion_utils/test/src/resample/test_resample.cpp b/common/motion_utils/test/src/resample/test_resample.cpp index d7ce15b4c30d9..373a12c5bbd76 100644 --- a/common/motion_utils/test/src/resample/test_resample.cpp +++ b/common/motion_utils/test/src/resample/test_resample.cpp @@ -15,6 +15,7 @@ #include "motion_utils/constants.hpp" #include "motion_utils/resample/resample.hpp" #include "tier4_autoware_utils/geometry/boost_geometry.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/math/constants.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" diff --git a/common/motion_utils/test/src/test_motion_utils.cpp b/common/motion_utils/test/src/test_motion_utils.cpp index a8d9f76116018..d8b9b0ac78981 100644 --- a/common/motion_utils/test/src/test_motion_utils.cpp +++ b/common/motion_utils/test/src/test_motion_utils.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "motion_utils/motion_utils.hpp" +#include #include diff --git a/common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp b/common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp index 121a9fa69beab..4df1ef0a094f7 100644 --- a/common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp +++ b/common/motion_utils/test/src/trajectory/test_path_with_lane_id.cpp @@ -13,8 +13,7 @@ // limitations under the License. #include "motion_utils/trajectory/path_with_lane_id.hpp" -// #include "tier4_autoware_utils/geometry/geometry.hpp" -// #include "tier4_autoware_utils/math/unit_conversion.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include diff --git a/common/tier4_autoware_utils/CMakeLists.txt b/common/tier4_autoware_utils/CMakeLists.txt index 212097d7785e0..938a78692d9a1 100644 --- a/common/tier4_autoware_utils/CMakeLists.txt +++ b/common/tier4_autoware_utils/CMakeLists.txt @@ -7,7 +7,6 @@ autoware_package() find_package(Boost REQUIRED) ament_auto_add_library(tier4_autoware_utils SHARED - src/tier4_autoware_utils.cpp src/geometry/geometry.cpp src/geometry/pose_deviation.cpp src/geometry/boost_polygon_utils.cpp diff --git a/common/tier4_autoware_utils/README.md b/common/tier4_autoware_utils/README.md index 7a352e971a58d..ffcc414cd76ce 100644 --- a/common/tier4_autoware_utils/README.md +++ b/common/tier4_autoware_utils/README.md @@ -3,3 +3,7 @@ ## Purpose This package contains many common functions used by other packages, so please refer to them as needed. + +## For developers + +`tier4_autoware_utils.hpp` header file was removed because the source files that directly/indirectly include this file took a long time for preprocessing. diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp index 5d73367b3af87..b42bc509db3c1 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -138,6 +139,13 @@ inline geometry_msgs::msg::Point getPoint(const autoware_auto_planning_msgs::msg return p.pose.position; } +template <> +inline geometry_msgs::msg::Point getPoint( + const autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) +{ + return p.point.pose.position; +} + template <> inline geometry_msgs::msg::Point getPoint( const autoware_auto_planning_msgs::msg::TrajectoryPoint & p) @@ -170,6 +178,13 @@ inline geometry_msgs::msg::Pose getPose(const autoware_auto_planning_msgs::msg:: return p.pose; } +template <> +inline geometry_msgs::msg::Pose getPose( + const autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) +{ + return p.point.pose; +} + template <> inline geometry_msgs::msg::Pose getPose(const autoware_auto_planning_msgs::msg::TrajectoryPoint & p) { @@ -189,6 +204,13 @@ inline double getLongitudinalVelocity(const autoware_auto_planning_msgs::msg::Pa return p.longitudinal_velocity_mps; } +template <> +inline double getLongitudinalVelocity( + const autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) +{ + return p.point.longitudinal_velocity_mps; +} + template <> inline double getLongitudinalVelocity(const autoware_auto_planning_msgs::msg::TrajectoryPoint & p) { @@ -221,6 +243,13 @@ inline void setPose( p.pose = pose; } +template <> +inline void setPose( + const geometry_msgs::msg::Pose & pose, autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) +{ + p.point.pose = pose; +} + template <> inline void setPose( const geometry_msgs::msg::Pose & pose, autoware_auto_planning_msgs::msg::TrajectoryPoint & p) @@ -257,6 +286,13 @@ inline void setLongitudinalVelocity( p.longitudinal_velocity_mps = velocity; } +template <> +inline void setLongitudinalVelocity( + const double velocity, autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) +{ + p.point.longitudinal_velocity_mps = velocity; +} + inline geometry_msgs::msg::Point createPoint(const double x, const double y, const double z) { geometry_msgs::msg::Point p; diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp deleted file mode 100644 index 91ba0c4d5de47..0000000000000 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2022 TIER IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef TIER4_AUTOWARE_UTILS__GEOMETRY__PATH_WITH_LANE_ID_GEOMETRY_HPP_ -#define TIER4_AUTOWARE_UTILS__GEOMETRY__PATH_WITH_LANE_ID_GEOMETRY_HPP_ - -#include "tier4_autoware_utils/geometry/geometry.hpp" - -#include - -namespace tier4_autoware_utils -{ -template <> -inline geometry_msgs::msg::Point getPoint( - const autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) -{ - return p.point.pose.position; -} - -template <> -inline geometry_msgs::msg::Pose getPose( - const autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) -{ - return p.point.pose; -} - -template <> -inline double getLongitudinalVelocity( - const autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) -{ - return p.point.longitudinal_velocity_mps; -} - -template <> -inline void setPose( - const geometry_msgs::msg::Pose & pose, autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) -{ - p.point.pose = pose; -} - -template <> -inline void setLongitudinalVelocity( - const double velocity, autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) -{ - p.point.longitudinal_velocity_mps = velocity; -} -} // namespace tier4_autoware_utils - -#endif // TIER4_AUTOWARE_UTILS__GEOMETRY__PATH_WITH_LANE_ID_GEOMETRY_HPP_ diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp deleted file mode 100644 index 937cedc5534bf..0000000000000 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/tier4_autoware_utils.hpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020 Tier IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef TIER4_AUTOWARE_UTILS__TIER4_AUTOWARE_UTILS_HPP_ -#define TIER4_AUTOWARE_UTILS__TIER4_AUTOWARE_UTILS_HPP_ - -#include "tier4_autoware_utils/geometry/boost_geometry.hpp" -#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" -#include "tier4_autoware_utils/geometry/geometry.hpp" -#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" -#include "tier4_autoware_utils/geometry/pose_deviation.hpp" -#include "tier4_autoware_utils/math/constants.hpp" -#include "tier4_autoware_utils/math/normalization.hpp" -#include "tier4_autoware_utils/math/range.hpp" -#include "tier4_autoware_utils/math/sin_table.hpp" -#include "tier4_autoware_utils/math/trigonometry.hpp" -#include "tier4_autoware_utils/math/unit_conversion.hpp" -#include "tier4_autoware_utils/ros/debug_publisher.hpp" -#include "tier4_autoware_utils/ros/debug_traits.hpp" -#include "tier4_autoware_utils/ros/marker_helper.hpp" -#include "tier4_autoware_utils/ros/msg_covariance.hpp" -#include "tier4_autoware_utils/ros/msg_operation.hpp" -#include "tier4_autoware_utils/ros/parameter.hpp" -#include "tier4_autoware_utils/ros/processing_time_publisher.hpp" -#include "tier4_autoware_utils/ros/self_pose_listener.hpp" -#include "tier4_autoware_utils/ros/transform_listener.hpp" -#include "tier4_autoware_utils/ros/update_param.hpp" -#include "tier4_autoware_utils/ros/uuid_helper.hpp" -#include "tier4_autoware_utils/ros/wait_for_param.hpp" -#include "tier4_autoware_utils/system/backtrace.hpp" -#include "tier4_autoware_utils/system/stop_watch.hpp" -#include "tier4_autoware_utils/transform/transforms.hpp" - -#endif // TIER4_AUTOWARE_UTILS__TIER4_AUTOWARE_UTILS_HPP_ diff --git a/common/tier4_autoware_utils/src/tier4_autoware_utils.cpp b/common/tier4_autoware_utils/src/tier4_autoware_utils.cpp deleted file mode 100644 index a541d314aa365..0000000000000 --- a/common/tier4_autoware_utils/src/tier4_autoware_utils.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2020 Tier IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" diff --git a/common/tier4_autoware_utils/test/src/geometry/test_path_with_lane_id_geometry.cpp b/common/tier4_autoware_utils/test/src/geometry/test_path_with_lane_id_geometry.cpp index d6e35ea5a863f..a8179f3b3cd60 100644 --- a/common/tier4_autoware_utils/test/src/geometry/test_path_with_lane_id_geometry.cpp +++ b/common/tier4_autoware_utils/test/src/geometry/test_path_with_lane_id_geometry.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include diff --git a/common/tier4_autoware_utils/test/src/test_autoware_utils.cpp b/common/tier4_autoware_utils/test/src/test_autoware_utils.cpp index 34018ed730005..4ecfcb6bee091 100644 --- a/common/tier4_autoware_utils/test/src/test_autoware_utils.cpp +++ b/common/tier4_autoware_utils/test/src/test_autoware_utils.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include #include diff --git a/common/tier4_planning_rviz_plugin/include/utils.hpp b/common/tier4_planning_rviz_plugin/include/utils.hpp index 8a16059cbf275..76f9da0685908 100644 --- a/common/tier4_planning_rviz_plugin/include/utils.hpp +++ b/common/tier4_planning_rviz_plugin/include/utils.hpp @@ -16,7 +16,6 @@ #define UTILS_HPP_ #include -#include #include #include diff --git a/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp index 2ca0364e35d83..d0b0e32c3788f 100644 --- a/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp +++ b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.hpp @@ -58,7 +58,6 @@ #include #include #include -#include #include diff --git a/common/tier4_vehicle_rviz_plugin/src/tools/acceleration_meter.hpp b/common/tier4_vehicle_rviz_plugin/src/tools/acceleration_meter.hpp index 371480eccdbce..8a962cea575e8 100644 --- a/common/tier4_vehicle_rviz_plugin/src/tools/acceleration_meter.hpp +++ b/common/tier4_vehicle_rviz_plugin/src/tools/acceleration_meter.hpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #endif diff --git a/control/control_validator/src/control_validator.cpp b/control/control_validator/src/control_validator.cpp index 6c3996bfd274b..ffce38f009a41 100644 --- a/control/control_validator/src/control_validator.cpp +++ b/control/control_validator/src/control_validator.cpp @@ -16,9 +16,6 @@ #include "control_validator/utils.hpp" -#include -#include - #include #include #include diff --git a/control/control_validator/src/debug_marker.cpp b/control/control_validator/src/debug_marker.cpp index c94c22c807648..1b92aee0bb416 100644 --- a/control/control_validator/src/debug_marker.cpp +++ b/control/control_validator/src/debug_marker.cpp @@ -14,8 +14,8 @@ #include "control_validator/debug_marker.hpp" -#include -#include +#include +#include #include #include diff --git a/control/control_validator/src/utils.cpp b/control/control_validator/src/utils.cpp index 1b11d1e55c376..fa8261e9c3f95 100644 --- a/control/control_validator/src/utils.cpp +++ b/control/control_validator/src/utils.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include diff --git a/perception/cluster_merger/include/cluster_merger/node.hpp b/perception/cluster_merger/include/cluster_merger/node.hpp index 8da5999f00384..f36116378af78 100644 --- a/perception/cluster_merger/include/cluster_merger/node.hpp +++ b/perception/cluster_merger/include/cluster_merger/node.hpp @@ -19,7 +19,7 @@ #include "message_filters/sync_policies/approximate_time.h" #include "message_filters/synchronizer.h" #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/transform_listener.hpp" #include "tier4_perception_msgs/msg/detected_objects_with_feature.hpp" diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 0ef1443fde519..6a414c26b8806 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/perception/probabilistic_occupancy_grid_map/test/test_utils.cpp b/perception/probabilistic_occupancy_grid_map/test/test_utils.cpp index 3b1edca14e146..367dcee807e7b 100644 --- a/perception/probabilistic_occupancy_grid_map/test/test_utils.cpp +++ b/perception/probabilistic_occupancy_grid_map/test/test_utils.cpp @@ -24,8 +24,6 @@ // autoware #include "utils/utils.hpp" -#include - // create pointcloud function pcl::PointCloud createPCLPointCloudWithIteratedHeight(const size_t width) { diff --git a/perception/radar_crossing_objects_noise_filter/src/radar_crossing_objects_noise_filter_node/radar_crossing_objects_noise_filter_node.cpp b/perception/radar_crossing_objects_noise_filter/src/radar_crossing_objects_noise_filter_node/radar_crossing_objects_noise_filter_node.cpp index c530ee0b2b851..947856e5d2a4d 100644 --- a/perception/radar_crossing_objects_noise_filter/src/radar_crossing_objects_noise_filter_node/radar_crossing_objects_noise_filter_node.cpp +++ b/perception/radar_crossing_objects_noise_filter/src/radar_crossing_objects_noise_filter_node/radar_crossing_objects_noise_filter_node.cpp @@ -14,7 +14,8 @@ #include "radar_crossing_objects_noise_filter/radar_crossing_objects_noise_filter_node.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/math/normalization.hpp" #include diff --git a/perception/radar_object_tracker/include/radar_object_tracker/utils/utils.hpp b/perception/radar_object_tracker/include/radar_object_tracker/utils/utils.hpp index 3fe9172ab5ceb..c43792908aedf 100644 --- a/perception/radar_object_tracker/include/radar_object_tracker/utils/utils.hpp +++ b/perception/radar_object_tracker/include/radar_object_tracker/utils/utils.hpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include diff --git a/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp b/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp index 62880d9471047..f6814ceb8c246 100644 --- a/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp +++ b/perception/radar_object_tracker/src/tracker/model/linear_motion_tracker.cpp @@ -20,7 +20,8 @@ #include "radar_object_tracker/utils/utils.hpp" -#include +#include +#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp index 82c387d04e525..0dee53d4f68af 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/colors.hpp @@ -14,7 +14,7 @@ #ifndef BEHAVIOR_PATH_PLANNER__MARKER_UTILS__COLORS_HPP_ #define BEHAVIOR_PATH_PLANNER__MARKER_UTILS__COLORS_HPP_ -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/ros/marker_helper.hpp" #include "std_msgs/msg/color_rgba.hpp" diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index 52be0ac5a81ef..d0673d8bb9ea8 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp index 5bb4e924b6d89..9322350877ad1 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/freespace_pull_over.cpp @@ -17,7 +17,6 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" -#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index e65e76b82a998..f69aa186dc377 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -18,7 +18,6 @@ #include #include -#include #include diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index 9c54f4243b0ea..2ddcff98afea0 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -14,8 +14,6 @@ #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" -#include - namespace behavior_path_planner::utils::start_goal_planner_common { diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index 00d6295e65aeb..5472885359c31 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -19,7 +19,6 @@ #include "behavior_path_planner/utils/utils.hpp" #include -#include using lanelet::utils::getArcCoordinates; using motion_utils::findNearestIndex; diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index ba6eb45267ba3..4a2a5cd4af6b3 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -20,9 +20,6 @@ #include #include #include -// #include -// #include - #include #include #include diff --git a/planning/behavior_path_planner/test/test_turn_signal.cpp b/planning/behavior_path_planner/test/test_turn_signal.cpp index 4917443064e78..454be6b75fc73 100644 --- a/planning/behavior_path_planner/test/test_turn_signal.cpp +++ b/planning/behavior_path_planner/test/test_turn_signal.cpp @@ -12,11 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. #include "behavior_path_planner/turn_signal_decider.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "motion_utils/trajectory/trajectory.hpp" -#include #include -#include #include "autoware_auto_planning_msgs/msg/path_point.hpp" #include diff --git a/planning/behavior_velocity_blind_spot_module/src/scene.cpp b/planning/behavior_velocity_blind_spot_module/src/scene.cpp index 440c360e712a3..94e11bc144a32 100644 --- a/planning/behavior_velocity_blind_spot_module/src/scene.cpp +++ b/planning/behavior_velocity_blind_spot_module/src/scene.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include diff --git a/planning/behavior_velocity_detection_area_module/src/scene.cpp b/planning/behavior_velocity_detection_area_module/src/scene.cpp index 07e9419fe0308..4d635d3d6d641 100644 --- a/planning/behavior_velocity_detection_area_module/src/scene.cpp +++ b/planning/behavior_velocity_detection_area_module/src/scene.cpp @@ -17,7 +17,6 @@ #include #include #include - #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp index 68aaff80827e2..4c5efad94dfc8 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/util.cpp @@ -18,6 +18,7 @@ #include "motion_utils/trajectory/trajectory.hpp" #include +#include #include diff --git a/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp b/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp index 029b516e93219..fa8be96c02859 100644 --- a/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp +++ b/planning/behavior_velocity_occlusion_spot_module/src/occlusion_spot_utils.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp index 331fe66103d8d..62114f5a95b00 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include diff --git a/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp b/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp index 34d4cf0efa7ac..74bd2f005dd89 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_planner_common/src/utilization/util.cpp b/planning/behavior_velocity_planner_common/src/utilization/util.cpp index 15216fcf469b6..a45331f53d7c2 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/util.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/util.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include diff --git a/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp b/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp index 60a0ff878b478..c4fdb83e00609 100644 --- a/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp +++ b/planning/behavior_velocity_planner_common/test/src/test_arc_lane_util.cpp @@ -16,7 +16,6 @@ #include #include -#include #include diff --git a/planning/behavior_velocity_walkway_module/src/debug.cpp b/planning/behavior_velocity_walkway_module/src/debug.cpp index 51db3c204f2a4..8814fa8271866 100644 --- a/planning/behavior_velocity_walkway_module/src/debug.cpp +++ b/planning/behavior_velocity_walkway_module/src/debug.cpp @@ -15,8 +15,10 @@ #include "scene_walkway.hpp" #include -#include -#include +#include +#include +#include +#include #include diff --git a/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp b/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp index 9ab609378621a..123ed997d4445 100644 --- a/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp +++ b/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp @@ -23,7 +23,7 @@ #include "obstacle_velocity_limiter/types.hpp" #include -#include +#include #include #include #include diff --git a/planning/path_smoother/src/replan_checker.cpp b/planning/path_smoother/src/replan_checker.cpp index 95c4e1eb1c002..11a4a3d5f4dad 100644 --- a/planning/path_smoother/src/replan_checker.cpp +++ b/planning/path_smoother/src/replan_checker.cpp @@ -14,9 +14,10 @@ #include "path_smoother/replan_checker.hpp" -#include "motion_utils/motion_utils.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "path_smoother/utils/trajectory_utils.hpp" -#include "tier4_autoware_utils/tier4_autoware_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tier4_autoware_utils/ros/update_param.hpp" #include diff --git a/planning/planning_debug_tools/include/planning_debug_tools/trajectory_analyzer.hpp b/planning/planning_debug_tools/include/planning_debug_tools/trajectory_analyzer.hpp index a70ab56d98a94..d460cbf0c10e2 100644 --- a/planning/planning_debug_tools/include/planning_debug_tools/trajectory_analyzer.hpp +++ b/planning/planning_debug_tools/include/planning_debug_tools/trajectory_analyzer.hpp @@ -20,7 +20,6 @@ #include "planning_debug_tools/util.hpp" #include "rclcpp/rclcpp.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" -#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" #include "autoware_auto_planning_msgs/msg/path.hpp" #include "autoware_auto_planning_msgs/msg/path_with_lane_id.hpp" diff --git a/planning/planning_debug_tools/include/planning_debug_tools/util.hpp b/planning/planning_debug_tools/include/planning_debug_tools/util.hpp index 1e332e7127dc4..1f0b9a9345854 100644 --- a/planning/planning_debug_tools/include/planning_debug_tools/util.hpp +++ b/planning/planning_debug_tools/include/planning_debug_tools/util.hpp @@ -18,7 +18,6 @@ #include "motion_utils/trajectory/trajectory.hpp" #include "rclcpp/rclcpp.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" -#include "tier4_autoware_utils/geometry/path_with_lane_id_geometry.hpp" #include "autoware_auto_planning_msgs/msg/path.hpp" #include "autoware_auto_planning_msgs/msg/path_with_lane_id.hpp" diff --git a/planning/surround_obstacle_checker/src/debug_marker.cpp b/planning/surround_obstacle_checker/src/debug_marker.cpp index d3d73a89e0a94..ebe5552c44c92 100644 --- a/planning/surround_obstacle_checker/src/debug_marker.cpp +++ b/planning/surround_obstacle_checker/src/debug_marker.cpp @@ -14,6 +14,7 @@ #include "surround_obstacle_checker/debug_marker.hpp" +#include #include #include #include diff --git a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp index e2fc1b3a37362..1d8ec80c46e17 100644 --- a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp +++ b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp @@ -25,7 +25,6 @@ #include "tier4_autoware_utils/ros/transform_listener.hpp" #include -#include #include "autoware_auto_vehicle_msgs/msg/steering_report.hpp" #include "autoware_auto_vehicle_msgs/msg/velocity_report.hpp" @@ -44,6 +43,7 @@ #include "tier4_vehicle_msgs/msg/actuation_command_stamped.hpp" #include "tier4_vehicle_msgs/msg/actuation_status_stamped.hpp" #include "tier4_vehicle_msgs/srv/update_accel_brake_map.hpp" +#include "visualization_msgs/msg/marker_array.hpp" #include #include From 47049dcf417696029d143475cf82de0c634bae97 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:14:53 +0200 Subject: [PATCH 258/547] build(lidar_centerpoint): remove download logic from CMake (#3141) Signed-off-by: Esteve Fernandez --- perception/lidar_centerpoint/CMakeLists.txt | 48 --------------------- 1 file changed, 48 deletions(-) diff --git a/perception/lidar_centerpoint/CMakeLists.txt b/perception/lidar_centerpoint/CMakeLists.txt index f2935ae98a4fa..7ee5622414366 100644 --- a/perception/lidar_centerpoint/CMakeLists.txt +++ b/perception/lidar_centerpoint/CMakeLists.txt @@ -69,54 +69,7 @@ else() set(CUDNN_AVAIL OFF) endif() -message(STATUS "start to download") if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) -# Download trained models - set(DATA_PATH ${CMAKE_CURRENT_SOURCE_DIR}/data) - set(DOWNLOAD_BASE_URL https://awf.ml.dev.web.auto/perception/models/centerpoint) - execute_process(COMMAND mkdir -p ${DATA_PATH}) - - function(download VERSION FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME} ") - set(DOWNLOAD_URL ${DOWNLOAD_BASE_URL}/${VERSION}/${FILE_NAME}) - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if("${FILE_HASH}" STREQUAL "${EXISTING_FILE_HASH}") - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD ${DOWNLOAD_URL} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD ${DOWNLOAD_URL} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE} ${DOWNLOAD_URL}") - endif() - endfunction() - - # centerpoint - download(v2 pts_voxel_encoder_centerpoint.onnx e8369d7e0f59951cf87c0274830de1fe) - download(v2 pts_backbone_neck_head_centerpoint.onnx 44e44d26781a69c741bf9bddde57fbb3) - - # centerpoint_tiny - download(v2 pts_voxel_encoder_centerpoint_tiny.onnx 5fb774fac44dc83949d05eb3ba077309) - download(v2 pts_backbone_neck_head_centerpoint_tiny.onnx e4658325b70222f7c3637fe00e586b82) - find_package(ament_cmake_auto REQUIRED) ament_auto_find_build_dependencies() @@ -206,7 +159,6 @@ if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) ament_auto_package( INSTALL_TO_SHARE launch - data config ) From 27220a839a23485487bf1c59b1cb302d1cc1a955 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 20 Sep 2023 21:16:47 +0900 Subject: [PATCH 259/547] chore(planning_validator): add maintainer (#5050) chore(planning_validator) add maintainer Signed-off-by: Takamasa Horibe --- planning/planning_validator/package.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/planning_validator/package.xml b/planning/planning_validator/package.xml index 685c2823796cc..1f1c1cc55e93c 100644 --- a/planning/planning_validator/package.xml +++ b/planning/planning_validator/package.xml @@ -5,7 +5,7 @@ 0.1.0 ros node for planning_validator Takamasa Horibe - Yutaka Shimizu + Kosuke Takeuchi Apache License 2.0 Takamasa Horibe From 43fbcf71916b4e79bb7a090d7fa84f7ef4ff9c73 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 20 Sep 2023 21:19:13 +0900 Subject: [PATCH 260/547] fix(operation_mode_transition_manager): check trajectory_follower_cmd for engage condition (#5038) Signed-off-by: Takamasa Horibe --- control/operation_mode_transition_manager/src/state.cpp | 9 ++++++++- control/operation_mode_transition_manager/src/state.hpp | 2 ++ launch/tier4_control_launch/launch/control.launch.py | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/control/operation_mode_transition_manager/src/state.cpp b/control/operation_mode_transition_manager/src/state.cpp index 55b672aa48964..d57cb8c78c740 100644 --- a/control/operation_mode_transition_manager/src/state.cpp +++ b/control/operation_mode_transition_manager/src/state.cpp @@ -38,6 +38,10 @@ AutonomousMode::AutonomousMode(rclcpp::Node * node) sub_control_cmd_ = node->create_subscription( "control_cmd", 1, [this](const AckermannControlCommand::SharedPtr msg) { control_cmd_ = *msg; }); + sub_trajectory_follower_control_cmd_ = node->create_subscription( + "trajectory_follower_control_cmd", 1, [this](const AckermannControlCommand::SharedPtr msg) { + trajectory_follower_control_cmd_ = *msg; + }); sub_kinematics_ = node->create_subscription( "kinematics", 1, [this](const Odometry::SharedPtr msg) { kinematics_ = *msg; }); @@ -261,7 +265,10 @@ bool AutonomousMode::isModeChangeAvailable() const bool speed_lower_deviation_ok = speed_deviation >= param.speed_lower_threshold; // No engagement if the vehicle is moving but the target speed is zero. - const bool stop_ok = !(std::abs(current_speed) > 0.1 && std::abs(target_control_speed) < 0.01); + const bool is_stop_cmd_indicated = + std::abs(target_control_speed) < 0.01 || + std::abs(trajectory_follower_control_cmd_.longitudinal.speed) < 0.01; + const bool stop_ok = !(std::abs(current_speed) > 0.1 && is_stop_cmd_indicated); // No engagement if the large acceleration is commanded. const bool large_acceleration_ok = !hasDangerAcceleration(); diff --git a/control/operation_mode_transition_manager/src/state.hpp b/control/operation_mode_transition_manager/src/state.hpp index de0dd8a543b15..e10d64e367f8d 100644 --- a/control/operation_mode_transition_manager/src/state.hpp +++ b/control/operation_mode_transition_manager/src/state.hpp @@ -67,6 +67,7 @@ class AutonomousMode : public ModeChangeBase using Odometry = nav_msgs::msg::Odometry; using Trajectory = autoware_auto_planning_msgs::msg::Trajectory; rclcpp::Subscription::SharedPtr sub_control_cmd_; + rclcpp::Subscription::SharedPtr sub_trajectory_follower_control_cmd_; rclcpp::Subscription::SharedPtr sub_kinematics_; rclcpp::Subscription::SharedPtr sub_trajectory_; rclcpp::Logger logger_; @@ -79,6 +80,7 @@ class AutonomousMode : public ModeChangeBase EngageAcceptableParam engage_acceptable_param_; StableCheckParam stable_check_param_; AckermannControlCommand control_cmd_; + AckermannControlCommand trajectory_follower_control_cmd_; Odometry kinematics_; Trajectory trajectory_; vehicle_info_util::VehicleInfo vehicle_info_; diff --git a/launch/tier4_control_launch/launch/control.launch.py b/launch/tier4_control_launch/launch/control.launch.py index 116055dd9e87e..375819f8e7579 100644 --- a/launch/tier4_control_launch/launch/control.launch.py +++ b/launch/tier4_control_launch/launch/control.launch.py @@ -239,6 +239,7 @@ def launch_setup(context, *args, **kwargs): ("steering", "/vehicle/status/steering_status"), ("trajectory", "/planning/scenario_planning/trajectory"), ("control_cmd", "/control/command/control_cmd"), + ("trajectory_follower_control_cmd", "/control/trajectory_follower/control_cmd"), ("control_mode_report", "/vehicle/status/control_mode"), ("gate_operation_mode", "/control/vehicle_cmd_gate/operation_mode"), # output From 607b8e7ec890834bc9804ce7e84ce1e41bdc16cc Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:37:29 +0200 Subject: [PATCH 261/547] build(lidar_apollo_instance_segmentation): remove download logic from CMake (#3140) Signed-off-by: Esteve Fernandez --- .../CMakeLists.txt | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/perception/lidar_apollo_instance_segmentation/CMakeLists.txt b/perception/lidar_apollo_instance_segmentation/CMakeLists.txt index 24f97e4201e00..4ae15948ad09e 100644 --- a/perception/lidar_apollo_instance_segmentation/CMakeLists.txt +++ b/perception/lidar_apollo_instance_segmentation/CMakeLists.txt @@ -24,55 +24,6 @@ if(NOT ${tensorrt_common_FOUND}) endif() find_package(cuda_utils REQUIRED) - -# download weight files -set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") -if(NOT EXISTS "${DATA_PATH}") - execute_process(COMMAND mkdir -p ${DATA_PATH}) -endif() - -function(download FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if(${FILE_HASH} STREQUAL ${EXISTING_FILE_HASH}) - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD - https://awf.ml.dev.web.auto/perception/models/lidar_apollo_instance_segmentation/${FILE_NAME} ${FILE_PATH} - STATUS DOWNLOAD_STATUS TIMEOUT 300 - ) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD - https://awf.ml.dev.web.auto/perception/models/lidar_apollo_instance_segmentation/${FILE_NAME} ${FILE_PATH} - STATUS DOWNLOAD_STATUS TIMEOUT 300 - ) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() -endfunction() - -download(vlp-16.onnx 63a5a1bb73f7dbb64cf70d04eca45fb4) -download(hdl-64.onnx 009745e33b1df44b68296431cc384cd2) -download(vls-128.onnx b2d709f56f73ae2518c9bf4d0214468f) - add_library(${PROJECT_NAME} SHARED src/node.cpp src/detector.cpp @@ -129,7 +80,6 @@ ament_package() install( DIRECTORY config - data launch DESTINATION share/${PROJECT_NAME} ) From e68e6e2b3bdb11dbea44a1a565cff8fea73acbbd Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:39:27 +0300 Subject: [PATCH 262/547] build(traffic_light_fine_detector): remove download logic from CMake (#4896) * remove downloading from Cmake Signed-off-by: Alexey Panferov * build(traffic_light_fine_detector):remove downloading from Cmake Signed-off-by: Alexey Panferov --------- Signed-off-by: Alexey Panferov --- .../CMakeLists.txt | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/perception/traffic_light_fine_detector/CMakeLists.txt b/perception/traffic_light_fine_detector/CMakeLists.txt index a0605a6aa1a90..f1f30a9da3195 100644 --- a/perception/traffic_light_fine_detector/CMakeLists.txt +++ b/perception/traffic_light_fine_detector/CMakeLists.txt @@ -69,53 +69,6 @@ else() set(CUDNN_AVAIL OFF) endif() -# Download caffemodel and prototxt -set(PRETRAINED_MODEL_BATCH_1_HASH 2b72d085022b8ee6aacff06bd722cfda) -set(PRETRAINED_MODEL_BATCH_4_HASH 4044daa86e7776ce241e94d98a09cc0e) -set(PRETRAINED_MODEL_BATCH_6_HASH 47255a11bde479320d703f1f45db1242) -set(LAMP_LABEL_HASH e9f45efb02f2a9aa8ac27b3d5c164905) -set(DATA_DIR https://awf.ml.dev.web.auto/perception/models/tlr_yolox_s/v2) - -set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") -if(NOT EXISTS "${DATA_PATH}") - execute_process(COMMAND mkdir -p ${DATA_PATH}) -endif() -function(download FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if(${FILE_HASH} STREQUAL ${EXISTING_FILE_HASH}) - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD ${DATA_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD ${DATA_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() -endfunction() -download(tlr_yolox_s_batch_1.onnx ${PRETRAINED_MODEL_BATCH_1_HASH}) -download(tlr_yolox_s_batch_4.onnx ${PRETRAINED_MODEL_BATCH_4_HASH}) -download(tlr_yolox_s_batch_6.onnx ${PRETRAINED_MODEL_BATCH_6_HASH}) -download(tlr_labels.txt ${LAMP_LABEL_HASH}) - if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) include_directories( ${OpenCV_INCLUDE_DIRS} @@ -147,7 +100,6 @@ if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) ) ament_auto_package(INSTALL_TO_SHARE - data launch ) From 326868b1611c350273653c114c0054d97a735cd6 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:15:31 +0900 Subject: [PATCH 263/547] fix(avoidance): exit if there is no avoidable object (#5051) Signed-off-by: satoshi-ota --- .../src/scene_module/avoidance/avoidance_module.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 22a12f141b4ee..e71888e768f23 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -207,7 +207,10 @@ bool AvoidanceModule::canTransitSuccessState() } } - const bool has_avoidance_target = !data.target_objects.empty(); + const bool has_avoidance_target = + std::any_of(data.target_objects.begin(), data.target_objects.end(), [](const auto & o) { + return o.is_avoidable || o.reason == AvoidanceDebugFactor::TOO_LARGE_JERK; + }); const bool has_shift_point = !path_shifter_.getShiftLines().empty(); const bool has_base_offset = std::abs(path_shifter_.getBaseOffset()) > parameters_->lateral_avoid_check_threshold; @@ -2949,6 +2952,12 @@ void AvoidanceModule::insertPrepareVelocity(ShiftedPath & shifted_path) const const auto object = data.target_objects.front(); + const auto enough_space = + object.is_avoidable || object.reason == AvoidanceDebugFactor::TOO_LARGE_JERK; + if (!enough_space) { + return; + } + // calculate shift length for front object. const auto & vehicle_width = planner_data_->parameters.vehicle_width; const auto object_type = utils::getHighestProbLabel(object.object.classification); From d2c2de298c32f79915d0c690c2a7c6c656d09f00 Mon Sep 17 00:00:00 2001 From: Albers Franz Date: Thu, 21 Sep 2023 02:53:15 +0200 Subject: [PATCH 264/547] fix(object_merger): fixed sync_queue_size parameter (#5058) --- .../object_merger/launch/object_association_merger.launch.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perception/object_merger/launch/object_association_merger.launch.xml b/perception/object_merger/launch/object_association_merger.launch.xml index a21ae8f7fa327..5754c256fef3c 100644 --- a/perception/object_merger/launch/object_association_merger.launch.xml +++ b/perception/object_merger/launch/object_association_merger.launch.xml @@ -15,7 +15,7 @@ - + From 057c2d50b59aee1d3f31e3f4dce27d543fc1fa6c Mon Sep 17 00:00:00 2001 From: "awf-autoware-bot[bot]" <94889083+awf-autoware-bot[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 00:57:43 +0000 Subject: [PATCH 265/547] chore: update CODEOWNERS (#5059) Signed-off-by: GitHub Co-authored-by: kenji-miyake --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ef9667865c3a1..82f7197d87aed 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -21,7 +21,7 @@ common/goal_distance_calculator/** taiki.tanaka@tier4.jp common/grid_map_utils/** maxime.clement@tier4.jp common/interpolation/** fumiya.watanabe@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp common/kalman_filter/** koji.minoda@tier4.jp takeshi.ishita@tier4.jp yukihiro.saito@tier4.jp -common/motion_utils/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp satoshi.ota@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp yutaka.shimizu@tier4.jp +common/motion_utils/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp mamoru.sobue@tier4.jp satoshi.ota@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp yutaka.shimizu@tier4.jp common/object_recognition_utils/** satoshi.tanaka@tier4.jp shunsuke.miura@tier4.jp takayuki.murooka@tier4.jp yusuke.muramatsu@tier4.jp common/osqp_interface/** fumiya.watanabe@tier4.jp maxime.clement@tier4.jp satoshi.ota@tier4.jp takayuki.murooka@tier4.jp common/path_distance_calculator/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp makoto.yabuta@tier4.jp @@ -176,7 +176,7 @@ planning/obstacle_velocity_limiter/** maxime.clement@tier4.jp planning/path_smoother/** maxime.clement@tier4.jp takayuki.murooka@tier4.jp planning/planning_debug_tools/** taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp planning/planning_test_utils/** kyoichi.sugahara@tier4.jp takamasa.horibe@tier4.jp -planning/planning_validator/** takamasa.horibe@tier4.jp yutaka.shimizu@tier4.jp +planning/planning_validator/** kosuke.takeuchi@tier4.jp takamasa.horibe@tier4.jp planning/route_handler/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp mamoru.sobue@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp zulfaqar.azmi@tier4.jp planning/rtc_auto_mode_manager/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp planning/rtc_interface/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp From e942c2de3db90652b4dfdee888ca9f5dc35c8d7c Mon Sep 17 00:00:00 2001 From: Yuntianyi Chen Date: Wed, 20 Sep 2023 18:11:48 -0700 Subject: [PATCH 266/547] refactor(control_performance_analysis): rework parameters (#4730) * refactor the configuration files of the node control_performance_analysis according to the new ROS node config guideline. Rename controller_performance_analysis.launch.xml->control_performance_analysis.launch.xml update the parameter information in the README.md Signed-off-by: yuntianyi-chen * style(pre-commit): autofix * Update the type of a parameter in schema file. Signed-off-by: yuntianyi-chen * revert copyright info Signed-off-by: yuntianyi-chen --------- Signed-off-by: yuntianyi-chen Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ...> control_performance_analysis.launch.xml} | 0 .../control_performance_analysis.schema.json | 62 +++++++++++++++++++ .../src/control_performance_analysis_node.cpp | 2 +- 3 files changed, 63 insertions(+), 1 deletion(-) rename control/control_performance_analysis/launch/{controller_performance_analysis.launch.xml => control_performance_analysis.launch.xml} (100%) create mode 100644 control/control_performance_analysis/schema/control_performance_analysis.schema.json diff --git a/control/control_performance_analysis/launch/controller_performance_analysis.launch.xml b/control/control_performance_analysis/launch/control_performance_analysis.launch.xml similarity index 100% rename from control/control_performance_analysis/launch/controller_performance_analysis.launch.xml rename to control/control_performance_analysis/launch/control_performance_analysis.launch.xml diff --git a/control/control_performance_analysis/schema/control_performance_analysis.schema.json b/control/control_performance_analysis/schema/control_performance_analysis.schema.json new file mode 100644 index 0000000000000..f7acf3778bf46 --- /dev/null +++ b/control/control_performance_analysis/schema/control_performance_analysis.schema.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Control Performance Analysis Node", + "type": "object", + "definitions": { + "control_performance_analysis": { + "type": "object", + "properties": { + "curvature_interval_length": { + "type": "number", + "default": 5.0, + "description": "Used for estimating current curvature." + }, + "prevent_zero_division_value": { + "type": "number", + "default": 0.001, + "description": "Value to avoid zero division.." + }, + "odom_interval": { + "type": "integer", + "default": 0, + "description": "Interval between odom messages, increase it for smoother curve." + }, + "acceptable_max_distance_to_waypoint": { + "type": "number", + "default": 2.0, + "description": "Maximum distance between trajectory point and vehicle [m]." + }, + "acceptable_max_yaw_difference_rad": { + "type": "number", + "default": 0.95, + "description": "Maximum yaw difference between trajectory point and vehicle [rad]." + }, + "low_pass_filter_gain": { + "type": "number", + "default": 1.0472, + "description": "Low pass filter gain." + } + }, + "required": [ + "curvature_interval_length", + "prevent_zero_division_value", + "odom_interval", + "acceptable_max_distance_to_waypoint", + "acceptable_max_yaw_difference_rad", + "low_pass_filter_gain" + ] + } + }, + "properties": { + "/**": { + "type": "object", + "properties": { + "ros__parameters": { + "$ref": "#/definitions/control_performance_analysis" + } + }, + "required": ["ros__parameters"] + } + }, + "required": ["/**"] +} diff --git a/control/control_performance_analysis/src/control_performance_analysis_node.cpp b/control/control_performance_analysis/src/control_performance_analysis_node.cpp index ad845c86fc19a..768aed321b4f0 100644 --- a/control/control_performance_analysis/src/control_performance_analysis_node.cpp +++ b/control/control_performance_analysis/src/control_performance_analysis_node.cpp @@ -47,7 +47,7 @@ ControlPerformanceAnalysisNode::ControlPerformanceAnalysisNode( // Node Parameters. param_.curvature_interval_length_ = declare_parameter("curvature_interval_length"); param_.prevent_zero_division_value_ = declare_parameter("prevent_zero_division_value"); - param_.odom_interval_ = declare_parameter("odom_interval"); + param_.odom_interval_ = declare_parameter("odom_interval"); param_.acceptable_max_distance_to_waypoint_ = declare_parameter("acceptable_max_distance_to_waypoint"); param_.acceptable_max_yaw_difference_rad_ = From 89b16e8b93cca0dfe7ce82bee52dd64f8dc20cec Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:30:39 +0900 Subject: [PATCH 267/547] fix(avoidance): fix candidate path trimming process (#5022) fix(avoidance): fix candidate path visualization Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.cpp | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index e71888e768f23..eff1eaa121290 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -2078,37 +2078,35 @@ CandidateOutput AvoidanceModule::planCandidate() const auto shifted_path = data.candidate_path; - if (!data.safe_shift_line.empty()) { // clip from shift start index for visualize - utils::clipPathLength( - shifted_path.path, data.safe_shift_line.front().start_idx, std::numeric_limits::max(), - 0.0); - - const auto sl = helper_.getMainShiftLine(data.safe_shift_line); - const auto sl_front = data.safe_shift_line.front(); - const auto sl_back = data.safe_shift_line.back(); - - output.lateral_shift = helper_.getRelativeShiftToPath(sl); - output.start_distance_to_path_change = sl_front.start_longitudinal; - output.finish_distance_to_path_change = sl_back.end_longitudinal; - - const uint16_t steering_factor_direction = std::invoke([&output]() { - if (output.lateral_shift > 0.0) { - return SteeringFactor::LEFT; - } - return SteeringFactor::RIGHT; - }); - steering_factor_interface_ptr_->updateSteeringFactor( - {sl_front.start, sl_back.end}, - {output.start_distance_to_path_change, output.finish_distance_to_path_change}, - SteeringFactor::AVOIDANCE_PATH_CHANGE, steering_factor_direction, SteeringFactor::APPROACHING, - ""); + if (data.safe_shift_line.empty()) { + const size_t ego_idx = planner_data_->findEgoIndex(shifted_path.path.points); + utils::clipPathLength(shifted_path.path, ego_idx, planner_data_->parameters); + + output.path_candidate = shifted_path.path; + return output; } - const size_t ego_idx = planner_data_->findEgoIndex(shifted_path.path.points); - utils::clipPathLength(shifted_path.path, ego_idx, planner_data_->parameters); + const auto sl = helper_.getMainShiftLine(data.safe_shift_line); + const auto sl_front = data.safe_shift_line.front(); + const auto sl_back = data.safe_shift_line.back(); - output.path_candidate = shifted_path.path; + utils::clipPathLength( + shifted_path.path, sl_front.start_idx, std::numeric_limits::max(), 0.0); + + output.lateral_shift = helper_.getRelativeShiftToPath(sl); + output.start_distance_to_path_change = sl_front.start_longitudinal; + output.finish_distance_to_path_change = sl_back.end_longitudinal; + const uint16_t steering_factor_direction = std::invoke([&output]() { + return output.lateral_shift > 0.0 ? SteeringFactor::LEFT : SteeringFactor::RIGHT; + }); + steering_factor_interface_ptr_->updateSteeringFactor( + {sl_front.start, sl_back.end}, + {output.start_distance_to_path_change, output.finish_distance_to_path_change}, + SteeringFactor::AVOIDANCE_PATH_CHANGE, steering_factor_direction, SteeringFactor::APPROACHING, + ""); + + output.path_candidate = shifted_path.path; return output; } From 08122d90bc21b7b6e20e9d8e7b0b1f4efd072e17 Mon Sep 17 00:00:00 2001 From: Lukasz Chojnacki Date: Thu, 21 Sep 2023 03:45:06 +0200 Subject: [PATCH 268/547] refactor(mission_planner): rework parameters (#4579) * feat: add init version of json schema Signed-off-by: Lukasz Chojnacki * style(pre-commit): autofix * add more params to json schema Signed-off-by: Lukasz Chojnacki * style(pre-commit): autofix * apply review Signed-off-by: Lukasz Chojnacki --------- Signed-off-by: Lukasz Chojnacki Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../schema/mission_planner_nodes.schema.json | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 planning/mission_planner/schema/mission_planner_nodes.schema.json diff --git a/planning/mission_planner/schema/mission_planner_nodes.schema.json b/planning/mission_planner/schema/mission_planner_nodes.schema.json new file mode 100644 index 0000000000000..3d0bdc41c5c34 --- /dev/null +++ b/planning/mission_planner/schema/mission_planner_nodes.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Mission Planner nodes", + "type": "object", + "definitions": { + "mission_planner_nodes": { + "type": "object", + "properties": { + "map_frame": { + "type": "string", + "description": "The frame name for map ", + "default": "map" + }, + "arrival_check_angle_deg": { + "type": "number", + "description": "Angle threshold for goal check ", + "default": "45.0" + }, + "arrival_check_distance": { + "type": "number", + "description": "Distance threshold for goal check", + "default": "1.0", + "minimum": 0.0 + }, + "arrival_check_duration": { + "type": "number", + "description": "Duration threshold for goal check", + "default": "1.0", + "minimum": 0.0 + }, + "goal_angle_threshold_deg": { + "type": "number", + "description": "Max goal pose angle for goal approve", + "default": "45.0" + }, + "enable_correct_goal_pose": { + "type": "boolean", + "description": "Enabling correction of goal pose according to the closest lanelet orientation", + "default": "false" + }, + "reroute_time_threshold": { + "type": "number", + "description": "If the time to the rerouting point at the current velocity is greater than this threshold, rerouting is possible", + "default": "10.0", + "minimum": 0.0 + }, + "minimum_reroute_length": { + "type": "number", + "description": "Minimum Length for publishing a new route", + "default": "30.0" + }, + "consider_no_drivable_lanes": { + "type": "boolean", + "description": "This flag is for considering no_drivable_lanes in planning or not", + "default": "false" + } + }, + "required": [ + "map_frame", + "arrival_check_angle_deg", + "arrival_check_distance", + "arrival_check_duration", + "goal_angle_threshold_deg", + "enable_correct_goal_pose", + "reroute_time_threshold", + "minimum_reroute_length", + "consider_no_drivable_lanes" + ] + } + }, + "properties": { + "/**": { + "type": "object", + "properties": { + "ros__parameters": { + "$ref": "#/definitions/mission_planner_nodes" + } + }, + "required": ["ros__parameters"] + } + }, + "required": ["/**"] +} From c4a7e5466a35c38458d8494e7bfc4360aad16b2d Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 21 Sep 2023 11:44:33 +0900 Subject: [PATCH 269/547] fix(planning_validator): improve curvature calculation for less noise (#5049) Signed-off-by: Takamasa Horibe --- .../include/planning_validator/utils.hpp | 4 +- planning/planning_validator/src/utils.cpp | 77 +++++++++++++------ 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/planning/planning_validator/include/planning_validator/utils.hpp b/planning/planning_validator/include/planning_validator/utils.hpp index 6c0d2e86943dd..5831993d748b6 100644 --- a/planning/planning_validator/include/planning_validator/utils.hpp +++ b/planning/planning_validator/include/planning_validator/utils.hpp @@ -36,7 +36,9 @@ std::pair getAbsMaxValAndIdx(const std::vector & v); Trajectory resampleTrajectory(const Trajectory & trajectory, const double min_interval); -void calcCurvature(const Trajectory & trajectory, std::vector & curvatures); +void calcCurvature( + const Trajectory & trajectory, std::vector & curvatures, + const double curvature_distance = 1.0); void calcSteeringAngles( const Trajectory & trajectory, const double wheelbase, std::vector & steering_array); diff --git a/planning/planning_validator/src/utils.cpp b/planning/planning_validator/src/utils.cpp index 5a90e950bb616..3155a6f2e0bc8 100644 --- a/planning/planning_validator/src/utils.cpp +++ b/planning/planning_validator/src/utils.cpp @@ -89,26 +89,68 @@ Trajectory resampleTrajectory(const Trajectory & trajectory, const double min_in return resampled; } -void calcCurvature(const Trajectory & trajectory, std::vector & curvature_arr) +// calculate curvature from three points with curvature_distance +void calcCurvature( + const Trajectory & trajectory, std::vector & curvature_arr, + const double curvature_distance) { if (trajectory.points.size() < 3) { curvature_arr = std::vector(trajectory.points.size(), 0.0); return; } - curvature_arr.push_back(0.0); + // calc arc length array: arc_length(3) - arc_length(0) is distance from point(3) to point(0) + std::vector arc_length(trajectory.points.size(), 0.0); + for (size_t i = 1; i < trajectory.points.size(); ++i) { + arc_length.at(i) = + arc_length.at(i - 1) + calcDistance2d(trajectory.points.at(i - 1), trajectory.points.at(i)); + } + + // initialize with 0 curvature + curvature_arr = std::vector(trajectory.points.size(), 0.0); + + size_t first_distant_index = 0; + size_t last_distant_index = trajectory.points.size() - 1; for (size_t i = 1; i < trajectory.points.size() - 1; ++i) { - const auto p1 = getPoint(trajectory.points.at(i - 1)); + // find the previous point + size_t prev_idx = 0; + for (size_t j = i - 1; j > 0; --j) { + if (arc_length.at(i) - arc_length.at(j) > curvature_distance) { + if (first_distant_index == 0) { + first_distant_index = i; // save first index that meets distance requirement + } + prev_idx = j; + break; + } + } + + // find the next point + size_t next_idx = trajectory.points.size() - 1; + for (size_t j = i + 1; j < trajectory.points.size(); ++j) { + if (arc_length.at(j) - arc_length.at(i) > curvature_distance) { + last_distant_index = i; // save last index that meets distance requirement + next_idx = j; + break; + } + } + + const auto p1 = getPoint(trajectory.points.at(prev_idx)); const auto p2 = getPoint(trajectory.points.at(i)); - const auto p3 = getPoint(trajectory.points.at(i + 1)); + const auto p3 = getPoint(trajectory.points.at(next_idx)); try { - curvature_arr.push_back(tier4_autoware_utils::calcCurvature(p1, p2, p3)); + curvature_arr.at(i) = tier4_autoware_utils::calcCurvature(p1, p2, p3); } catch (...) { - // maybe distance is too close - curvature_arr.push_back(0.0); + curvature_arr.at(i) = 0.0; // maybe distance is too close } } - curvature_arr.push_back(0.0); + + // use previous or last curvature where the distance is not enough + for (size_t i = first_distant_index; i > 0; --i) { + curvature_arr.at(i - 1) = curvature_arr.at(i); + } + for (size_t i = last_distant_index; i < curvature_arr.size() - 1; ++i) { + curvature_arr.at(i + 1) = curvature_arr.at(i); + } } std::pair calcMaxCurvature(const Trajectory & trajectory) @@ -117,22 +159,13 @@ std::pair calcMaxCurvature(const Trajectory & trajectory) return {0.0, 0}; } - double max_curvature = 0.0; - size_t max_index = 0; - for (size_t i = 1; i < trajectory.points.size() - 1; ++i) { - const auto p1 = getPoint(trajectory.points.at(i - 1)); - const auto p2 = getPoint(trajectory.points.at(i)); - const auto p3 = getPoint(trajectory.points.at(i + 1)); + std::vector curvature_arr; + calcCurvature(trajectory, curvature_arr); - try { - const auto k = tier4_autoware_utils::calcCurvature(p1, p2, p3); - takeBigger(max_curvature, max_index, std::abs(k), i); - } catch (...) { - // maybe distance is too close - } - } + const auto max_curvature_it = std::max_element(curvature_arr.begin(), curvature_arr.end()); + const size_t index = std::distance(curvature_arr.begin(), max_curvature_it); - return {max_curvature, max_index}; + return {*max_curvature_it, index}; } std::pair calcMaxIntervalDistance(const Trajectory & trajectory) From 7956761ea210e620c6111cb41d55e3ce361b497e Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:15:55 +0900 Subject: [PATCH 270/547] fix(avoidance): fix bug in backward length calculation (#5046) * fix(avoidance): calc distance along path Signed-off-by: satoshi-ota * chore(avoidance): remove misreading comment Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../avoidance/avoidance_module.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index eff1eaa121290..83789818c456b 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1432,8 +1432,9 @@ AvoidLineArray AvoidanceModule::applyFillGapProcess( AvoidLineArray AvoidanceModule::applyCombineProcess( const AvoidLineArray & shift_lines, const AvoidLineArray & registered_lines, - [[maybe_unused]] DebugData & debug) const + DebugData & debug) const { + debug.step1_registered_shift_line = registered_lines; return utils::avoidance::combineRawShiftLinesWithUniqueCheck(registered_lines, shift_lines); } @@ -1936,14 +1937,17 @@ void AvoidanceModule::generateExpandDrivableLanes(BehaviorModuleOutput & output) PathWithLaneId AvoidanceModule::extendBackwardLength(const PathWithLaneId & original_path) const { - // special for avoidance: take behind distance upt ot shift-start-point if it exist. + const auto previous_path = helper_.getPreviousReferencePath(); + const auto longest_dist_to_shift_point = [&]() { double max_dist = 0.0; for (const auto & pnt : path_shifter_.getShiftLines()) { - max_dist = std::max(max_dist, calcDistance2d(getEgoPose(), pnt.start)); + max_dist = std::max( + max_dist, calcSignedArcLength(previous_path.points, pnt.start.position, getEgoPosition())); } for (const auto & sp : registered_raw_shift_lines_) { - max_dist = std::max(max_dist, calcDistance2d(getEgoPose(), sp.start)); + max_dist = std::max( + max_dist, calcSignedArcLength(previous_path.points, sp.start.position, getEgoPosition())); } return max_dist; }(); @@ -1951,11 +1955,11 @@ PathWithLaneId AvoidanceModule::extendBackwardLength(const PathWithLaneId & orig const auto extra_margin = 10.0; // Since distance does not consider arclength, but just line. const auto backward_length = std::max( planner_data_->parameters.backward_path_length, longest_dist_to_shift_point + extra_margin); - const auto previous_path = helper_.getPreviousReferencePath(); const size_t orig_ego_idx = planner_data_->findEgoIndex(original_path.points); - const size_t prev_ego_idx = - findNearestSegmentIndex(previous_path.points, getPoint(original_path.points.at(orig_ego_idx))); + const size_t prev_ego_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( + previous_path.points, getPose(original_path.points.at(orig_ego_idx)), + std::numeric_limits::max(), planner_data_->parameters.ego_nearest_yaw_threshold); size_t clip_idx = 0; for (size_t i = 0; i < prev_ego_idx; ++i) { From 08c809ce2e8227581754738c719504f08628f10d Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Thu, 21 Sep 2023 12:58:24 +0900 Subject: [PATCH 271/547] fix(mpc): precise resampling around ego for stabling control (#5003) * fix(mpc): precise resampling around ego Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka * update test Signed-off-by: Takayuki Murooka * Update control/mpc_lateral_controller/src/mpc_utils.cpp Co-authored-by: Takamasa Horibe * Update control/mpc_lateral_controller/src/mpc_utils.cpp Co-authored-by: Takamasa Horibe --------- Signed-off-by: Takayuki Murooka Co-authored-by: Takamasa Horibe --- .../include/mpc_lateral_controller/mpc.hpp | 3 +- .../mpc_lateral_controller.hpp | 2 +- .../mpc_lateral_controller/mpc_utils.hpp | 9 +- control/mpc_lateral_controller/src/mpc.cpp | 17 ++- .../src/mpc_lateral_controller.cpp | 9 +- .../mpc_lateral_controller/src/mpc_utils.cpp | 109 ++++++++++++------ .../mpc_lateral_controller/test/test_mpc.cpp | 22 +++- 7 files changed, 114 insertions(+), 57 deletions(-) diff --git a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc.hpp b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc.hpp index 0854340ed24a5..186481e92cdc4 100644 --- a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc.hpp +++ b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc.hpp @@ -447,7 +447,8 @@ class MPC * @param param Trajectory filtering parameters. */ void setReferenceTrajectory( - const Trajectory & trajectory_msg, const TrajectoryFilteringParam & param); + const Trajectory & trajectory_msg, const TrajectoryFilteringParam & param, + const Odometry & current_kinematics); /** * @brief Reset the previous result of MPC. diff --git a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp index b108e7602939c..441101abfc243 100644 --- a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp +++ b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_lateral_controller.hpp @@ -187,7 +187,7 @@ class MpcLateralController : public trajectory_follower::LateralControllerBase * @brief Set the current trajectory using the received message. * @param msg Received trajectory message. */ - void setTrajectory(const Trajectory & msg); + void setTrajectory(const Trajectory & msg, const Odometry & current_kinematics); /** * @brief Check if the received data is valid. diff --git a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_utils.hpp b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_utils.hpp index 89136f294aa18..036e6a32a9b83 100644 --- a/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_utils.hpp +++ b/control/mpc_lateral_controller/include/mpc_lateral_controller/mpc_utils.hpp @@ -99,7 +99,8 @@ void calcMPCTrajectoryArcLength(const MPCTrajectory & trajectory, std::vector resampleMPCTrajectoryByDistance( - const MPCTrajectory & input, const double resample_interval_dist); + const MPCTrajectory & input, const double resample_interval_dist, const size_t nearest_seg_idx, + const double ego_offset_to_segment); /** * @brief linearly interpolate the given trajectory assuming a base indexing and a new desired @@ -122,14 +123,14 @@ bool calcMPCTrajectoryTime(MPCTrajectory & traj); /** * @brief recalculate the velocity field (vx) of the MPCTrajectory with dynamic smoothing - * @param [in] start_idx index of the trajectory point from which to start smoothing - * @param [in] start_vel initial velocity to set at the start_idx + * @param [in] start_seg_idx segment index of the trajectory point from which to start smoothing + * @param [in] start_vel initial velocity to set at the start_seg_idx * @param [in] acc_lim limit on the acceleration * @param [in] tau constant to control the smoothing (high-value = very smooth) * @param [inout] traj MPCTrajectory for which to calculate the smoothed velocity */ void dynamicSmoothingVelocity( - const size_t start_idx, const double start_vel, const double acc_lim, const double tau, + const size_t start_seg_idx, const double start_vel, const double acc_lim, const double tau, MPCTrajectory & traj); /** diff --git a/control/mpc_lateral_controller/src/mpc.cpp b/control/mpc_lateral_controller/src/mpc.cpp index 82ebadafa17bf..0d7aa8aa8b2e6 100644 --- a/control/mpc_lateral_controller/src/mpc.cpp +++ b/control/mpc_lateral_controller/src/mpc.cpp @@ -175,13 +175,20 @@ Float32MultiArrayStamped MPC::generateDiagData( } void MPC::setReferenceTrajectory( - const Trajectory & trajectory_msg, const TrajectoryFilteringParam & param) + const Trajectory & trajectory_msg, const TrajectoryFilteringParam & param, + const Odometry & current_kinematics) { + const size_t nearest_seg_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( + trajectory_msg.points, current_kinematics.pose.pose, ego_nearest_dist_threshold, + ego_nearest_yaw_threshold); + const double ego_offset_to_segment = motion_utils::calcLongitudinalOffsetToSegment( + trajectory_msg.points, nearest_seg_idx, current_kinematics.pose.pose.position); + const auto mpc_traj_raw = MPCUtils::convertToMPCTrajectory(trajectory_msg); // resampling - const auto [success_resample, mpc_traj_resampled] = - MPCUtils::resampleMPCTrajectoryByDistance(mpc_traj_raw, param.traj_resample_dist); + const auto [success_resample, mpc_traj_resampled] = MPCUtils::resampleMPCTrajectoryByDistance( + mpc_traj_raw, param.traj_resample_dist, nearest_seg_idx, ego_offset_to_segment); if (!success_resample) { warn_throttle("[setReferenceTrajectory] spline error when resampling by distance"); return; @@ -391,13 +398,13 @@ MPCTrajectory MPC::applyVelocityDynamicsFilter( return input; } - const size_t nearest_idx = motion_utils::findFirstNearestIndexWithSoftConstraints( + const size_t nearest_seg_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( autoware_traj.points, current_kinematics.pose.pose, ego_nearest_dist_threshold, ego_nearest_yaw_threshold); MPCTrajectory output = input; MPCUtils::dynamicSmoothingVelocity( - nearest_idx, current_kinematics.twist.twist.linear.x, m_param.acceleration_limit, + nearest_seg_idx, current_kinematics.twist.twist.linear.x, m_param.acceleration_limit, m_param.velocity_time_constant, output); auto last_point = output.back(); diff --git a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp index 751873cacbf12..ce3529b4849ae 100644 --- a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp +++ b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp @@ -227,7 +227,7 @@ trajectory_follower::LateralOutput MpcLateralController::run( trajectory_follower::InputData const & input_data) { // set input data - setTrajectory(input_data.current_trajectory); + setTrajectory(input_data.current_trajectory, input_data.current_odometry); m_current_kinematic_state = input_data.current_odometry; m_current_steering = input_data.current_steering; @@ -319,7 +319,7 @@ bool MpcLateralController::isSteerConverged(const AckermannLateralCommand & cmd) bool MpcLateralController::isReady(const trajectory_follower::InputData & input_data) { - setTrajectory(input_data.current_trajectory); + setTrajectory(input_data.current_trajectory, input_data.current_odometry); m_current_kinematic_state = input_data.current_odometry; m_current_steering = input_data.current_steering; @@ -339,7 +339,8 @@ bool MpcLateralController::isReady(const trajectory_follower::InputData & input_ return true; } -void MpcLateralController::setTrajectory(const Trajectory & msg) +void MpcLateralController::setTrajectory( + const Trajectory & msg, const Odometry & current_kinematics) { m_current_trajectory = msg; @@ -353,7 +354,7 @@ void MpcLateralController::setTrajectory(const Trajectory & msg) return; } - m_mpc.setReferenceTrajectory(msg, m_trajectory_filtering_param); + m_mpc.setReferenceTrajectory(msg, m_trajectory_filtering_param, current_kinematics); // update trajectory buffer to check the trajectory shape change. m_trajectory_buffer.push_back(m_current_trajectory); diff --git a/control/mpc_lateral_controller/src/mpc_utils.cpp b/control/mpc_lateral_controller/src/mpc_utils.cpp index 6d6e980675086..d3fc5fba0b014 100644 --- a/control/mpc_lateral_controller/src/mpc_utils.cpp +++ b/control/mpc_lateral_controller/src/mpc_utils.cpp @@ -27,6 +27,19 @@ namespace autoware::motion::control::mpc_lateral_controller { +namespace +{ +double calcLongitudinalOffset( + const geometry_msgs::msg::Point & p_front, const geometry_msgs::msg::Point & p_back, + const geometry_msgs::msg::Point & p_target) +{ + const Eigen::Vector3d segment_vec{p_back.x - p_front.x, p_back.y - p_front.y, 0}; + const Eigen::Vector3d target_vec{p_target.x - p_front.x, p_target.y - p_front.y, 0}; + + return segment_vec.dot(target_vec) / segment_vec.norm(); +} +} // namespace + namespace MPCUtils { using tier4_autoware_utils::calcDistance2d; @@ -77,7 +90,8 @@ void calcMPCTrajectoryArcLength(const MPCTrajectory & trajectory, std::vector resampleMPCTrajectoryByDistance( - const MPCTrajectory & input, const double resample_interval_dist) + const MPCTrajectory & input, const double resample_interval_dist, const size_t nearest_seg_idx, + const double ego_offset_to_segment) { MPCTrajectory output; @@ -92,7 +106,18 @@ std::pair resampleMPCTrajectoryByDistance( } std::vector output_arclength; - for (double s = 0; s < input_arclength.back(); s += resample_interval_dist) { + // To accurately sample the ego point, resample separately in the forward direction and the + // backward direction from the current position. + for (double s = std::clamp( + input_arclength.at(nearest_seg_idx) + ego_offset_to_segment, 0.0, + input_arclength.back() - 1e-6); + 0 <= s; s -= resample_interval_dist) { + output_arclength.push_back(s); + } + std::reverse(output_arclength.begin(), output_arclength.end()); + for (double s = std::max(input_arclength.at(nearest_seg_idx) + ego_offset_to_segment, 0.0) + + resample_interval_dist; + s < input_arclength.back(); s += resample_interval_dist) { output_arclength.push_back(s); } @@ -274,13 +299,17 @@ bool calcMPCTrajectoryTime(MPCTrajectory & traj) } void dynamicSmoothingVelocity( - const size_t start_idx, const double start_vel, const double acc_lim, const double tau, + const size_t start_seg_idx, const double start_vel, const double acc_lim, const double tau, MPCTrajectory & traj) { double curr_v = start_vel; - traj.vx.at(start_idx) = start_vel; + // set current velocity in both start and end point of the segment + traj.vx.at(start_seg_idx) = start_vel; + if (1 < traj.vx.size()) { + traj.vx.at(start_seg_idx + 1) = start_vel; + } - for (size_t i = start_idx + 1; i < traj.size(); ++i) { + for (size_t i = start_seg_idx + 2; i < traj.size(); ++i) { const double ds = calcDistance2d(traj, i, i - 1); const double dt = ds / std::max(std::fabs(curr_v), std::numeric_limits::epsilon()); const double a = tau / std::max(tau + dt, std::numeric_limits::epsilon()); @@ -320,29 +349,40 @@ bool calcNearestPoseInterp( return true; } - auto calcSquaredDist = [](const Pose & p, const MPCTrajectory & t, const size_t idx) { - const double dx = p.position.x - t.x.at(idx); - const double dy = p.position.y - t.y.at(idx); - return dx * dx + dy * dy; - }; - /* get second nearest index = next to nearest_index */ - const size_t next = static_cast( - std::min(static_cast(*nearest_index) + 1, static_cast(traj_size) - 1)); - const size_t prev = - static_cast(std::max(static_cast(*nearest_index) - 1, static_cast(0))); - const double dist_to_next = calcSquaredDist(self_pose, traj, next); - const double dist_to_prev = calcSquaredDist(self_pose, traj, prev); - const size_t second_nearest_index = (dist_to_next < dist_to_prev) ? next : prev; - - const double a_sq = calcSquaredDist(self_pose, traj, *nearest_index); - const double b_sq = calcSquaredDist(self_pose, traj, second_nearest_index); - const double dx3 = traj.x.at(*nearest_index) - traj.x.at(second_nearest_index); - const double dy3 = traj.y.at(*nearest_index) - traj.y.at(second_nearest_index); - const double c_sq = dx3 * dx3 + dy3 * dy3; + const auto [prev, next] = [&]() -> std::pair { + if (*nearest_index == 0) { + return std::make_pair(0, 1); + } + if (*nearest_index == traj_size - 1) { + return std::make_pair(traj_size - 2, traj_size - 1); + } + geometry_msgs::msg::Point nearest_traj_point; + nearest_traj_point.x = traj.x.at(*nearest_index); + nearest_traj_point.y = traj.y.at(*nearest_index); + geometry_msgs::msg::Point next_nearest_traj_point; + next_nearest_traj_point.x = traj.x.at(*nearest_index + 1); + next_nearest_traj_point.y = traj.y.at(*nearest_index + 1); + + const double signed_length = + calcLongitudinalOffset(nearest_traj_point, next_nearest_traj_point, self_pose.position); + if (signed_length <= 0) { + return std::make_pair(*nearest_index - 1, *nearest_index); + } + return std::make_pair(*nearest_index, *nearest_index + 1); + }(); + + geometry_msgs::msg::Point next_traj_point; + next_traj_point.x = traj.x.at(next); + next_traj_point.y = traj.y.at(next); + geometry_msgs::msg::Point prev_traj_point; + prev_traj_point.x = traj.x.at(prev); + prev_traj_point.y = traj.y.at(prev); + const double traj_seg_length = + tier4_autoware_utils::calcDistance2d(prev_traj_point, next_traj_point); /* if distance between two points are too close */ - if (c_sq < 1.0E-5) { + if (traj_seg_length < 1.0E-5) { nearest_pose->position.x = traj.x.at(*nearest_index); nearest_pose->position.y = traj.y.at(*nearest_index); nearest_pose->orientation = createQuaternionFromYaw(traj.yaw.at(*nearest_index)); @@ -351,18 +391,15 @@ bool calcNearestPoseInterp( } /* linear interpolation */ - const double alpha = std::max(std::min(0.5 * (c_sq - a_sq + b_sq) / c_sq, 1.0), 0.0); - nearest_pose->position.x = - alpha * traj.x.at(*nearest_index) + (1 - alpha) * traj.x.at(second_nearest_index); - nearest_pose->position.y = - alpha * traj.y.at(*nearest_index) + (1 - alpha) * traj.y.at(second_nearest_index); - const double tmp_yaw_err = - normalizeRadian(traj.yaw.at(*nearest_index) - traj.yaw.at(second_nearest_index)); - const double nearest_yaw = - normalizeRadian(traj.yaw.at(second_nearest_index) + alpha * tmp_yaw_err); + const double ratio = std::clamp( + calcLongitudinalOffset(prev_traj_point, next_traj_point, self_pose.position) / traj_seg_length, + 0.0, 1.0); + nearest_pose->position.x = (1 - ratio) * traj.x.at(prev) + ratio * traj.x.at(next); + nearest_pose->position.y = (1 - ratio) * traj.y.at(prev) + ratio * traj.y.at(next); + const double tmp_yaw_err = normalizeRadian(traj.yaw.at(prev) - traj.yaw.at(next)); + const double nearest_yaw = normalizeRadian(traj.yaw.at(next) + (1 - ratio) * tmp_yaw_err); nearest_pose->orientation = createQuaternionFromYaw(nearest_yaw); - *nearest_time = alpha * traj.relative_time.at(*nearest_index) + - (1 - alpha) * traj.relative_time.at(second_nearest_index); + *nearest_time = (1 - ratio) * traj.relative_time.at(prev) + ratio * traj.relative_time.at(next); return true; } diff --git a/control/mpc_lateral_controller/test/test_mpc.cpp b/control/mpc_lateral_controller/test/test_mpc.cpp index 6f8a6fb598058..dade035daf26c 100644 --- a/control/mpc_lateral_controller/test/test_mpc.cpp +++ b/control/mpc_lateral_controller/test/test_mpc.cpp @@ -163,7 +163,9 @@ class MPCTest : public ::testing::Test mpc.initializeSteeringPredictor(); // Init trajectory - mpc.setReferenceTrajectory(dummy_straight_trajectory, trajectory_param); + const auto current_kinematics = + makeOdometry(dummy_straight_trajectory.points.front().pose, 0.0); + mpc.setReferenceTrajectory(dummy_straight_trajectory, trajectory_param, current_kinematics); } nav_msgs::msg::Odometry makeOdometry(const geometry_msgs::msg::Pose & pose, const double velocity) @@ -221,7 +223,9 @@ TEST_F(MPCTest, InitializeAndCalculateRightTurn) // Init parameters and reference trajectory initializeMPC(mpc); - mpc.setReferenceTrajectory(dummy_right_turn_trajectory, trajectory_param); + const auto current_kinematics = + makeOdometry(dummy_right_turn_trajectory.points.front().pose, 0.0); + mpc.setReferenceTrajectory(dummy_right_turn_trajectory, trajectory_param, current_kinematics); // Calculate MPC AckermannLateralCommand ctrl_cmd; @@ -237,7 +241,8 @@ TEST_F(MPCTest, OsqpCalculate) { MPC mpc; initializeMPC(mpc); - mpc.setReferenceTrajectory(dummy_straight_trajectory, trajectory_param); + const auto current_kinematics = makeOdometry(dummy_straight_trajectory.points.front().pose, 0.0); + mpc.setReferenceTrajectory(dummy_straight_trajectory, trajectory_param, current_kinematics); std::shared_ptr vehicle_model_ptr = std::make_shared(wheelbase, steer_limit, steer_tau); @@ -262,7 +267,9 @@ TEST_F(MPCTest, OsqpCalculateRightTurn) { MPC mpc; initializeMPC(mpc); - mpc.setReferenceTrajectory(dummy_right_turn_trajectory, trajectory_param); + const auto current_kinematics = + makeOdometry(dummy_right_turn_trajectory.points.front().pose, 0.0); + mpc.setReferenceTrajectory(dummy_right_turn_trajectory, trajectory_param, current_kinematics); std::shared_ptr vehicle_model_ptr = std::make_shared(wheelbase, steer_limit, steer_tau); @@ -300,7 +307,8 @@ TEST_F(MPCTest, KinematicsNoDelayCalculate) // Init filters mpc.initializeLowPassFilters(steering_lpf_cutoff_hz, error_deriv_lpf_cutoff_hz); // Init trajectory - mpc.setReferenceTrajectory(dummy_straight_trajectory, trajectory_param); + const auto current_kinematics = makeOdometry(dummy_straight_trajectory.points.front().pose, 0.0); + mpc.setReferenceTrajectory(dummy_straight_trajectory, trajectory_param, current_kinematics); // Calculate MPC AckermannLateralCommand ctrl_cmd; Trajectory pred_traj; @@ -315,7 +323,9 @@ TEST_F(MPCTest, KinematicsNoDelayCalculateRightTurn) { MPC mpc; initializeMPC(mpc); - mpc.setReferenceTrajectory(dummy_right_turn_trajectory, trajectory_param); + const auto current_kinematics = + makeOdometry(dummy_right_turn_trajectory.points.front().pose, 0.0); + mpc.setReferenceTrajectory(dummy_right_turn_trajectory, trajectory_param, current_kinematics); std::shared_ptr vehicle_model_ptr = std::make_shared(wheelbase, steer_limit); From a84418ac2e4c8fd3baa98420b468b903726b6390 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 21 Sep 2023 14:13:15 +0900 Subject: [PATCH 272/547] feat(vehicle_cmd_gate)!: add steer and steer_rate filter (#5044) * feat(vehicle_cmd_gate): add steering angle and rate filter Signed-off-by: Takamasa Horibe * update test Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../config/vehicle_cmd_gate.param.yaml | 4 + .../src/vehicle_cmd_filter.cpp | 57 +++++++++- .../src/vehicle_cmd_filter.hpp | 7 ++ .../vehicle_cmd_gate/src/vehicle_cmd_gate.cpp | 4 + .../test_filter_in_vehicle_cmd_gate_node.cpp | 4 + .../test/src/test_vehicle_cmd_filter.cpp | 107 ++++++++++++++++-- 6 files changed, 170 insertions(+), 13 deletions(-) diff --git a/control/vehicle_cmd_gate/config/vehicle_cmd_gate.param.yaml b/control/vehicle_cmd_gate/config/vehicle_cmd_gate.param.yaml index 92844c61f6f4f..191e894622af7 100644 --- a/control/vehicle_cmd_gate/config/vehicle_cmd_gate.param.yaml +++ b/control/vehicle_cmd_gate/config/vehicle_cmd_gate.param.yaml @@ -15,6 +15,8 @@ nominal: vel_lim: 25.0 reference_speed_points: [20.0, 30.0] + steer_lim: [1.0, 0.8] + steer_rate_lim: [1.0, 0.8] lon_acc_lim: [5.0, 4.0] lon_jerk_lim: [5.0, 4.0] lat_acc_lim: [5.0, 4.0] @@ -23,6 +25,8 @@ on_transition: vel_lim: 50.0 reference_speed_points: [20.0, 30.0] + steer_lim: [1.0, 0.8] + steer_rate_lim: [1.0, 0.8] lon_acc_lim: [1.0, 0.9] lon_jerk_lim: [0.5, 0.4] lat_acc_lim: [2.0, 1.8] diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp index 6a33855fb4d0a..4431534a76a5e 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.cpp @@ -29,7 +29,8 @@ bool VehicleCmdFilter::setParameterWithValidation(const VehicleCmdFilterParam & const auto s = p.reference_speed_points.size(); if ( p.lon_acc_lim.size() != s || p.lon_jerk_lim.size() != s || p.lat_acc_lim.size() != s || - p.lat_jerk_lim.size() != s || p.actual_steer_diff_lim.size() != s) { + p.lat_jerk_lim.size() != s || p.actual_steer_diff_lim.size() != s || p.steer_lim.size() != s || + p.steer_rate_lim.size() != s) { std::cerr << "VehicleCmdFilter::setParam() There is a size mismatch in the parameter. " "Parameter initialization failed." << std::endl; @@ -39,7 +40,18 @@ bool VehicleCmdFilter::setParameterWithValidation(const VehicleCmdFilterParam & param_ = p; return true; } - +void VehicleCmdFilter::setSteerLim(LimitArray v) +{ + auto tmp = param_; + tmp.steer_lim = v; + setParameterWithValidation(tmp); +} +void VehicleCmdFilter::setSteerRateLim(LimitArray v) +{ + auto tmp = param_; + tmp.steer_rate_lim = v; + setParameterWithValidation(tmp); +} void VehicleCmdFilter::setLonAccLim(LimitArray v) { auto tmp = param_; @@ -151,12 +163,36 @@ void VehicleCmdFilter::limitActualSteerDiff( void VehicleCmdFilter::limitLateralSteer(AckermannControlCommand & input) const { - // TODO(Horibe): parametrize the max steering angle. - // TODO(Horibe): support steering greater than PI/2. Now the lateral acceleration - // calculation does not support bigger steering value than PI/2 due to tan/atan calculation. - constexpr float steer_limit = M_PI_2; + const float steer_limit = getSteerLim(); + input.lateral.steering_tire_angle = std::clamp(input.lateral.steering_tire_angle, -steer_limit, steer_limit); + + // TODO(Horibe): support steering greater than PI/2. Now the lateral acceleration + // calculation does not support bigger steering value than PI/2 due to tan/atan calculation. + if (std::abs(input.lateral.steering_tire_angle) > M_PI_2f) { + std::cerr << "[vehicle_Cmd_gate] limitLateralSteer(): steering limit is set to pi/2 since the " + "current filtering logic can not handle the steering larger than pi/2. Please " + "check the steering angle limit." + << std::endl; + + std::clamp(input.lateral.steering_tire_angle, -M_PI_2f, M_PI_2f); + } +} + +void VehicleCmdFilter::limitLateralSteerRate(const double dt, AckermannControlCommand & input) const +{ + const float steer_rate_limit = getSteerRateLim(); + + // for steering angle rate + input.lateral.steering_tire_rotation_rate = + std::clamp(input.lateral.steering_tire_rotation_rate, -steer_rate_limit, steer_rate_limit); + + // for steering angle + const float steer_diff_limit = steer_rate_limit * dt; + float ds = input.lateral.steering_tire_angle - prev_cmd_.lateral.steering_tire_angle; + ds = std::clamp(ds, -steer_diff_limit, steer_diff_limit); + input.lateral.steering_tire_angle = prev_cmd_.lateral.steering_tire_angle + ds; } void VehicleCmdFilter::filterAll( @@ -165,6 +201,7 @@ void VehicleCmdFilter::filterAll( { const auto cmd_orig = cmd; limitLateralSteer(cmd); + limitLateralSteerRate(dt, cmd); limitLongitudinalWithJerk(dt, cmd); limitLongitudinalWithAcc(dt, cmd); limitLongitudinalWithVel(cmd); @@ -267,6 +304,14 @@ double VehicleCmdFilter::getLatJerkLim() const { return interpolateFromSpeed(param_.lat_jerk_lim); } +double VehicleCmdFilter::getSteerLim() const +{ + return interpolateFromSpeed(param_.steer_lim); +} +double VehicleCmdFilter::getSteerRateLim() const +{ + return interpolateFromSpeed(param_.steer_rate_lim); +} double VehicleCmdFilter::getSteerDiffLim() const { return interpolateFromSpeed(param_.actual_steer_diff_lim); diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp index a79b8a38bae8d..cf7a1f627a68a 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_filter.hpp @@ -37,6 +37,8 @@ struct VehicleCmdFilterParam LimitArray lon_jerk_lim; LimitArray lat_acc_lim; LimitArray lat_jerk_lim; + LimitArray steer_lim; + LimitArray steer_rate_lim; LimitArray actual_steer_diff_lim; }; class VehicleCmdFilter @@ -47,6 +49,8 @@ class VehicleCmdFilter void setWheelBase(double v) { param_.wheel_base = v; } void setVelLim(double v) { param_.vel_lim = v; } + void setSteerLim(LimitArray v); + void setSteerRateLim(LimitArray v); void setLonAccLim(LimitArray v); void setLonJerkLim(LimitArray v); void setLatAccLim(LimitArray v); @@ -64,6 +68,7 @@ class VehicleCmdFilter void limitActualSteerDiff( const double current_steer_angle, AckermannControlCommand & input) const; void limitLateralSteer(AckermannControlCommand & input) const; + void limitLateralSteerRate(const double dt, AckermannControlCommand & input) const; void filterAll( const double dt, const double current_steer_angle, AckermannControlCommand & input, IsFilterActivated & is_activated) const; @@ -90,6 +95,8 @@ class VehicleCmdFilter double getLonJerkLim() const; double getLatAccLim() const; double getLatJerkLim() const; + double getSteerLim() const; + double getSteerRateLim() const; double getSteerDiffLim() const; }; } // namespace vehicle_cmd_gate diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp index 6d3893cbb9ca9..2f2083f626f6b 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp @@ -169,6 +169,8 @@ VehicleCmdGate::VehicleCmdGate(const rclcpp::NodeOptions & node_options) p.vel_lim = declare_parameter("nominal.vel_lim"); p.reference_speed_points = declare_parameter>("nominal.reference_speed_points"); + p.steer_lim = declare_parameter>("nominal.steer_lim"); + p.steer_rate_lim = declare_parameter>("nominal.steer_rate_lim"); p.lon_acc_lim = declare_parameter>("nominal.lon_acc_lim"); p.lon_jerk_lim = declare_parameter>("nominal.lon_jerk_lim"); p.lat_acc_lim = declare_parameter>("nominal.lat_acc_lim"); @@ -184,6 +186,8 @@ VehicleCmdGate::VehicleCmdGate(const rclcpp::NodeOptions & node_options) p.vel_lim = declare_parameter("on_transition.vel_lim"); p.reference_speed_points = declare_parameter>("on_transition.reference_speed_points"); + p.steer_lim = declare_parameter>("on_transition.steer_lim"); + p.steer_rate_lim = declare_parameter>("on_transition.steer_rate_lim"); p.lon_acc_lim = declare_parameter>("on_transition.lon_acc_lim"); p.lon_jerk_lim = declare_parameter>("on_transition.lon_jerk_lim"); p.lat_acc_lim = declare_parameter>("on_transition.lat_acc_lim"); diff --git a/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp b/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp index 1156656385117..f02235ed1ecbf 100644 --- a/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp +++ b/control/vehicle_cmd_gate/test/src/test_filter_in_vehicle_cmd_gate_node.cpp @@ -45,6 +45,8 @@ void print_values(int i, const T1 & name, const T2 & a, const T3 &... b) // global params const std::vector reference_speed_points = {5., 10., 15., 20.}; +const std::vector steer_lim = {0.5, 0.3, 0.2, 0.1}; +const std::vector steer_rate_lim = {0.5, 0.3, 0.2, 0.1}; const std::vector lon_acc_lim = {1.5, 1.0, 0.8, 0.6}; const std::vector lon_jerk_lim = {1.4, 0.9, 0.7, 0.5}; const std::vector lat_acc_lim = {2.0, 1.6, 1.2, 0.8}; @@ -336,6 +338,8 @@ std::shared_ptr generateNode() node_options.append_parameter_override("wheel_base", wheelbase); override("nominal.reference_speed_points", reference_speed_points); override("nominal.reference_speed_points", reference_speed_points); + override("nominal.steer_lim", steer_lim); + override("nominal.steer_rate_lim", steer_rate_lim); override("nominal.lon_acc_lim", lon_acc_lim); override("nominal.lon_jerk_lim", lon_jerk_lim); override("nominal.lat_acc_lim", lat_acc_lim); diff --git a/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp b/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp index 1791493aeb17b..b8200490dd1d5 100644 --- a/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp +++ b/control/vehicle_cmd_gate/test/src/test_vehicle_cmd_filter.cpp @@ -31,12 +31,15 @@ constexpr double NOMINAL_INTERVAL = 1.0; void setFilterParams( vehicle_cmd_gate::VehicleCmdFilter & f, double v, LimitArray speed_points, LimitArray a, - LimitArray j, LimitArray lat_a, LimitArray lat_j, LimitArray steer_diff, const double wheelbase) + LimitArray j, LimitArray lat_a, LimitArray lat_j, LimitArray steer_diff, LimitArray steer_lim, + LimitArray steer_rate_lim, const double wheelbase) { vehicle_cmd_gate::VehicleCmdFilterParam p; p.vel_lim = v; p.wheel_base = wheelbase; p.reference_speed_points = speed_points; + p.steer_lim = steer_lim; + p.steer_rate_lim = steer_rate_lim; p.lat_acc_lim = lat_a; p.lat_jerk_lim = lat_j; p.lon_acc_lim = a; @@ -97,8 +100,8 @@ double calcLatJerk( void test_1d_limit( double ego_v, double V_LIM, double A_LIM, double J_LIM, double LAT_A_LIM, double LAT_J_LIM, - double STEER_DIFF, const AckermannControlCommand & prev_cmd, - const AckermannControlCommand & raw_cmd) + double STEER_DIFF, double STEER_LIM, double STEER_RATE_LIM, + const AckermannControlCommand & prev_cmd, const AckermannControlCommand & raw_cmd) { const double WHEELBASE = 3.0; const double DT = 0.1; // [s] @@ -106,7 +109,8 @@ void test_1d_limit( vehicle_cmd_gate::VehicleCmdFilter filter; filter.setCurrentSpeed(ego_v); setFilterParams( - filter, V_LIM, {0.0}, {A_LIM}, {J_LIM}, {LAT_A_LIM}, {LAT_J_LIM}, {STEER_DIFF}, WHEELBASE); + filter, V_LIM, {0.0}, {A_LIM}, {J_LIM}, {LAT_A_LIM}, {LAT_J_LIM}, {STEER_DIFF}, {STEER_LIM}, + {STEER_RATE_LIM}, WHEELBASE); filter.setPrevCmd(prev_cmd); // velocity filter @@ -233,6 +237,8 @@ TEST(VehicleCmdFilter, VehicleCmdFilter) const std::vector lat_a_arr = {0.01, 1.0, 100.0}; const std::vector lat_j_arr = {0.01, 1.0, 100.0}; const std::vector steer_diff_arr = {0.01, 1.0, 100.0}; + const std::vector steer_lim_arr = {0.01, 1.0, 100.0}; + const std::vector steer_rate_lim_arr = {0.01, 1.0, 100.0}; const std::vector ego_v_arr = {0.0, 0.1, 1.0, 3.0, 15.0}; const std::vector prev_cmd_arr = { @@ -249,8 +255,13 @@ TEST(VehicleCmdFilter, VehicleCmdFilter) for (const auto & prev_cmd : prev_cmd_arr) { for (const auto & raw_cmd : raw_cmd_arr) { for (const auto & steer_diff : steer_diff_arr) { - for (const auto & ego_v : ego_v_arr) { - test_1d_limit(ego_v, v, a, j, la, lj, steer_diff, prev_cmd, raw_cmd); + for (const auto & steer : steer_lim_arr) { + for (const auto & steer_rate : steer_rate_lim_arr) { + for (const auto & ego_v : ego_v_arr) { + test_1d_limit( + ego_v, v, a, j, la, lj, steer_diff, steer, steer_rate, prev_cmd, raw_cmd); + } + } } } } @@ -271,6 +282,8 @@ TEST(VehicleCmdFilter, VehicleCmdFilterInterpolate) p.wheel_base = WHEELBASE; p.vel_lim = 20.0; p.reference_speed_points = std::vector{2.0, 4.0, 10.0}; + p.steer_lim = std::vector{0.1, 0.2, 0.3}; + p.steer_rate_lim = std::vector{0.2, 0.1, 0.05}; p.lon_acc_lim = std::vector{0.3, 0.4, 0.5}; p.lon_jerk_lim = std::vector{0.4, 0.4, 0.7}; p.lat_acc_lim = std::vector{0.1, 0.2, 0.3}; @@ -293,7 +306,16 @@ TEST(VehicleCmdFilter, VehicleCmdFilterInterpolate) const auto set_speed_and_reset_prev = [&](const auto & current_vel) { filter.setCurrentSpeed(current_vel); }; - + const auto _limitSteer = [&](const auto & in) { + auto out = in; + filter.limitLateralSteer(out); + return out; + }; + const auto _limitSteerRate = [&](const auto & in) { + auto out = in; + filter.limitLateralSteerRate(DT, out); + return out; + }; const auto _limitLongitudinalWithVel = [&](const auto & in) { auto out = in; filter.limitLongitudinalWithVel(out); @@ -334,6 +356,77 @@ TEST(VehicleCmdFilter, VehicleCmdFilterInterpolate) EXPECT_NEAR(_limitLongitudinalWithVel(orig_cmd).longitudinal.speed, 20.0, ep); } + // steer angle lim + // p.reference_speed_points = std::vector{2.0, 4.0, 10.0}; + // p.steer_lim = std::vector{0.1, 0.2, 0.3}; + { + set_speed_and_reset_prev(0.0); + EXPECT_NEAR(_limitSteer(orig_cmd).lateral.steering_tire_angle, 0.1, ep); + + set_speed_and_reset_prev(2.0); + EXPECT_NEAR(_limitSteer(orig_cmd).lateral.steering_tire_angle, 0.1, ep); + + set_speed_and_reset_prev(3.0); + EXPECT_NEAR(_limitSteer(orig_cmd).lateral.steering_tire_angle, 0.15, ep); + + set_speed_and_reset_prev(5.0); + EXPECT_NEAR(_limitSteer(orig_cmd).lateral.steering_tire_angle, 0.2 + 0.1 / 6.0, ep); + + set_speed_and_reset_prev(8.0); + EXPECT_NEAR(_limitSteer(orig_cmd).lateral.steering_tire_angle, 0.2 + 0.1 * 4.0 / 6.0, ep); + + set_speed_and_reset_prev(10.0); + EXPECT_NEAR(_limitSteer(orig_cmd).lateral.steering_tire_angle, 0.3, ep); + + set_speed_and_reset_prev(15.0); + EXPECT_NEAR(_limitSteer(orig_cmd).lateral.steering_tire_angle, 0.3, ep); + } + + // steer angle rate lim + // p.reference_speed_points = std::vector{2.0, 4.0, 10.0}; + // p.steer_rate_lim = std::vector{0.2, 0.1, 0.05}; + { + const auto calcSteerRateFromAngle = [&](const auto & cmd) { + return (cmd.steering_tire_angle - 0.0) / DT; + }; + autoware_auto_control_msgs::msg::AckermannLateralCommand filtered; + + set_speed_and_reset_prev(0.0); + filtered = _limitSteerRate(orig_cmd).lateral; + EXPECT_NEAR(calcSteerRateFromAngle(filtered), 0.2, ep); + EXPECT_NEAR(filtered.steering_tire_rotation_rate, 0.2, ep); + + set_speed_and_reset_prev(2.0); + filtered = _limitSteerRate(orig_cmd).lateral; + EXPECT_NEAR(calcSteerRateFromAngle(filtered), 0.2, ep); + EXPECT_NEAR(filtered.steering_tire_rotation_rate, 0.2, ep); + + set_speed_and_reset_prev(3.0); + filtered = _limitSteerRate(orig_cmd).lateral; + EXPECT_NEAR(calcSteerRateFromAngle(filtered), 0.15, ep); + EXPECT_NEAR(filtered.steering_tire_rotation_rate, 0.15, ep); + + set_speed_and_reset_prev(5.0); + filtered = _limitSteerRate(orig_cmd).lateral; + EXPECT_NEAR(calcSteerRateFromAngle(filtered), 0.1 - 0.05 * 1.0 / 6.0, ep); + EXPECT_NEAR(filtered.steering_tire_rotation_rate, 0.1 - 0.05 * 1.0 / 6.0, ep); + + set_speed_and_reset_prev(8.0); + filtered = _limitSteerRate(orig_cmd).lateral; + EXPECT_NEAR(calcSteerRateFromAngle(filtered), 0.1 - 0.05 * 4.0 / 6.0, ep); + EXPECT_NEAR(filtered.steering_tire_rotation_rate, 0.1 - 0.05 * 4.0 / 6.0, ep); + + set_speed_and_reset_prev(10.0); + filtered = _limitSteerRate(orig_cmd).lateral; + EXPECT_NEAR(calcSteerRateFromAngle(filtered), 0.05, ep); + EXPECT_NEAR(filtered.steering_tire_rotation_rate, 0.05, ep); + + set_speed_and_reset_prev(15.0); + filtered = _limitSteerRate(orig_cmd).lateral; + EXPECT_NEAR(calcSteerRateFromAngle(filtered), 0.05, ep); + EXPECT_NEAR(filtered.steering_tire_rotation_rate, 0.05, ep); + } + // longitudinal acc lim { set_speed_and_reset_prev(0.0); From ffcbae075164f2cbbec53784ade90578a8a705ab Mon Sep 17 00:00:00 2001 From: Amadeusz Szymko Date: Thu, 21 Sep 2023 07:18:46 +0200 Subject: [PATCH 273/547] fix(concatenate_data): replace data empty check to avoid nullptr (#5011) Signed-off-by: amadeuszsz --- .../concatenate_and_time_sync_nodelet.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp b/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp index d88665a4c4c98..8ae0bdacb983a 100644 --- a/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp +++ b/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp @@ -400,12 +400,6 @@ void PointCloudConcatenateDataSynchronizerComponent::convertToXYZICloud( { output_ptr->header = input_ptr->header; - if (input_ptr->data.empty()) { - RCLCPP_WARN_STREAM_THROTTLE( - this->get_logger(), *this->get_clock(), 1000, "Empty sensor points!"); - return; - } - PointCloud2Modifier output_modifier{*output_ptr, input_ptr->header.frame_id}; output_modifier.reserve(input_ptr->width); @@ -460,6 +454,12 @@ void PointCloudConcatenateDataSynchronizerComponent::cloud_callback( { std::lock_guard lock(mutex_); auto input = std::make_shared(*input_ptr); + if (input->data.empty()) { + RCLCPP_WARN_STREAM_THROTTLE( + this->get_logger(), *this->get_clock(), 1000, "Empty sensor points!"); + return; + } + sensor_msgs::msg::PointCloud2::SharedPtr xyzi_input_ptr(new sensor_msgs::msg::PointCloud2()); convertToXYZICloud(input, xyzi_input_ptr); From 55fba784cb3c15b240e3bbef322990da1dc2c30f Mon Sep 17 00:00:00 2001 From: danielsanchezaran Date: Thu, 21 Sep 2023 14:36:49 +0900 Subject: [PATCH 274/547] feat(start_planner): prevent start planner execution in the middle of the road (#5004) prevent start planner execution in the middle of the road Signed-off-by: Daniel Sanchez --- .../start_planner/start_planner.param.yaml | 1 + ...avior_path_planner_start_planner_design.md | 21 ++++++++++--------- .../start_planner_parameters.hpp | 2 ++ .../scene_module/start_planner/manager.cpp | 2 ++ .../start_planner/start_planner_module.cpp | 12 ++++++++--- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml index 492bf1ddca69e..a529eefac67af 100644 --- a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml +++ b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml @@ -7,6 +7,7 @@ collision_check_margin: 1.0 collision_check_distance_from_end: 1.0 th_moving_object_velocity: 1.0 + th_distance_to_middle_of_the_road: 0.1 # shift pull out enable_shift_pull_out: true check_shift_path_lane_departure: false diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md index 2b98775e4fe81..0b0e01378b61a 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md @@ -59,16 +59,17 @@ PullOutPath --o PullOutPlannerBase ## General parameters for start_planner -| Name | Unit | Type | Description | Default value | -| :---------------------------------------------------------- | :---- | :----- | :------------------------------------------------------------------- | :------------ | -| th_arrived_distance_m | [m] | double | distance threshold for arrival of path termination | 1.0 | -| th_stopped_velocity_mps | [m/s] | double | velocity threshold for arrival of path termination | 0.01 | -| th_stopped_time_sec | [s] | double | time threshold for arrival of path termination | 1.0 | -| th_turn_signal_on_lateral_offset | [m] | double | lateral distance threshold for turning on blinker | 1.0 | -| intersection_search_length | [m] | double | check if intersections exist within this length | 30.0 | -| length_ratio_for_turn_signal_deactivation_near_intersection | [m] | double | deactivate turn signal of this module near intersection | 0.5 | -| collision_check_margin | [m] | double | Obstacle collision check margin | 1.0 | -| collision_check_distance_from_end | [m] | double | collision check distance from end point. currently only for pull out | 15.0 | +| Name | Unit | Type | Description | Default value | +| :---------------------------------------------------------- | :---- | :----- | :-------------------------------------------------------------------------- | :------------ | +| th_arrived_distance_m | [m] | double | distance threshold for arrival of path termination | 1.0 | +| th_distance_to_middle_of_the_road | [m] | double | distance threshold to determine if the vehicle is on the middle of the road | +| th_stopped_velocity_mps | [m/s] | double | velocity threshold for arrival of path termination | 0.01 | +| th_stopped_time_sec | [s] | double | time threshold for arrival of path termination | 1.0 | +| th_turn_signal_on_lateral_offset | [m] | double | lateral distance threshold for turning on blinker | 1.0 | +| intersection_search_length | [m] | double | check if intersections exist within this length | 30.0 | +| length_ratio_for_turn_signal_deactivation_near_intersection | [m] | double | deactivate turn signal of this module near intersection | 0.5 | +| collision_check_margin | [m] | double | Obstacle collision check margin | 1.0 | +| collision_check_distance_from_end | [m] | double | collision check distance from end point. currently only for pull out | 15.0 | ## Safety check with static obstacles diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp index ce8f9698324d0..e2d80759a18d0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp @@ -39,11 +39,13 @@ struct StartPlannerParameters double th_stopped_velocity{0.0}; double th_stopped_time{0.0}; double th_turn_signal_on_lateral_offset{0.0}; + double th_distance_to_middle_of_the_road{0.0}; double intersection_search_length{0.0}; double length_ratio_for_turn_signal_deactivation_near_intersection{0.0}; double collision_check_margin{0.0}; double collision_check_distance_from_end{0.0}; double th_moving_object_velocity{0.0}; + // shift pull out bool enable_shift_pull_out{false}; bool check_shift_path_lane_departure{false}; diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp index 28f35a1224b91..e00a1041e5316 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp @@ -38,6 +38,8 @@ StartPlannerModuleManager::StartPlannerModuleManager( p.th_stopped_time = node->declare_parameter(ns + "th_stopped_time"); p.th_turn_signal_on_lateral_offset = node->declare_parameter(ns + "th_turn_signal_on_lateral_offset"); + p.th_distance_to_middle_of_the_road = + node->declare_parameter(ns + "th_distance_to_middle_of_the_road"); p.intersection_search_length = node->declare_parameter(ns + "intersection_search_length"); p.length_ratio_for_turn_signal_deactivation_near_intersection = node->declare_parameter( ns + "length_ratio_for_turn_signal_deactivation_near_intersection"); diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 27ee78c7dad98..8f8976e41bd54 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -122,10 +122,16 @@ void StartPlannerModule::processOnExit() bool StartPlannerModule::isExecutionRequested() const { - // TODO(Sugahara): if required lateral shift distance is small, don't engage this module. - // Execute when current pose is near route start pose - const Pose start_pose = planner_data_->route_handler->getOriginalStartPose(); const Pose & current_pose = planner_data_->self_odometry->pose.pose; + const lanelet::ConstLanelets current_lanes = utils::getCurrentLanes(planner_data_); + const double lateral_distance_to_center_lane = + lanelet::utils::getArcCoordinates(current_lanes, current_pose).distance; + + if (std::abs(lateral_distance_to_center_lane) < parameters_->th_distance_to_middle_of_the_road) { + return false; + } + + const Pose start_pose = planner_data_->route_handler->getOriginalStartPose(); if ( tier4_autoware_utils::calcDistance2d(start_pose.position, current_pose.position) > parameters_->th_arrived_distance) { From 9991b9ae525b79eb9c12f687baf8eab340c79166 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 21 Sep 2023 16:49:36 +0900 Subject: [PATCH 275/547] refactor(behavior_path_planner): generarize generateFeasibleStopPath function (#5060) * generarize generateFeasibleStopPath function Signed-off-by: kyoichi-sugahara * delete definition in header Signed-off-by: kyoichi-sugahara * Update planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp Co-authored-by: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> * style(pre-commit): autofix --------- Signed-off-by: kyoichi-sugahara Co-authored-by: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../goal_planner/goal_planner_module.hpp | 11 ------ .../utils/start_goal_planner_common/utils.hpp | 5 +++ .../goal_planner/goal_planner_module.cpp | 37 ++++--------------- .../utils/start_goal_planner_common/utils.cpp | 32 ++++++++++++++++ 4 files changed, 44 insertions(+), 41 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 3c42a036fc63d..dbdfc77f59e38 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -234,17 +234,6 @@ class GoalPlannerModule : public SceneModuleInterface PathWithLaneId generateStopPath(); PathWithLaneId generateFeasibleStopPath(); - /** - * @brief Generate a stop point in the current path based on the vehicle's pose and constraints. - * - * This function generates a stop point in the current path. If no lanes are available or if - * stopping is not feasible due to constraints (maximum deceleration, maximum jerk), no stop point - * is inserted into the path. - * - * @return the modified path with the stop point inserted. If no feasible stop point can be - * determined, returns an empty optional. - */ - std::optional generateStopInsertedCurrentPath(); void keepStoppedWithCurrentPath(PathWithLaneId & path); double calcSignedArcLengthFromEgo(const PathWithLaneId & path, const Pose & pose) const; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp index 217b3748fa250..913018d25f8f8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp @@ -113,6 +113,11 @@ std::pair getPairsTerminalVelocityAndAccel( */ PathWithLaneId removeInverseOrderPathPoints(const PathWithLaneId & path); +std::optional generateFeasibleStopPath( + PathWithLaneId & current_path, std::shared_ptr planner_data, + geometry_msgs::msg::Pose & stop_pose, const double maximum_deceleration, + const double maximum_jerk); + } // namespace behavior_path_planner::utils::start_goal_planner_common #endif // BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__UTILS_HPP_ diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index d9426d6fece82..d9d0b4f0d8154 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -736,8 +736,13 @@ void GoalPlannerModule::setStopPathFromCurrentPath(BehaviorModuleOutput & output { // safe or not safe(no feasible stop_path found) -> not_safe: try to find new feasible stop_path if (status_.prev_is_safe_dynamic_objects || status_.prev_stop_path_after_approval == nullptr) { - if (const auto stop_inserted_path = generateStopInsertedCurrentPath()) { - output.path = std::make_shared(*stop_inserted_path); + auto current_path = getCurrentPath(); + const auto stop_path = + behavior_path_planner::utils::start_goal_planner_common::generateFeasibleStopPath( + current_path, planner_data_, *stop_pose_, parameters_->maximum_deceleration_for_stop, + parameters_->maximum_jerk_for_stop); + if (stop_path) { + output.path = std::make_shared(*stop_path); status_.prev_stop_path_after_approval = output.path; RCLCPP_WARN_THROTTLE(getLogger(), *clock_, 5000, "Collision detected, generate stop path"); } else { @@ -1115,34 +1120,6 @@ PathWithLaneId GoalPlannerModule::generateFeasibleStopPath() return stop_path; } -std::optional GoalPlannerModule::generateStopInsertedCurrentPath() -{ - auto current_path = getCurrentPath(); - if (current_path.points.empty()) { - return {}; - } - - // try to insert stop point in current_path after approval - // but if can't stop with constraints(maximum deceleration, maximum jerk), don't insert stop point - const auto min_stop_distance = calcFeasibleDecelDistance( - planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, 0.0); - if (!min_stop_distance) { - return {}; - } - - // set stop point - const auto stop_idx = motion_utils::insertStopPoint( - planner_data_->self_odometry->pose.pose, *min_stop_distance, current_path.points); - - if (!stop_idx) { - return {}; - } else { - stop_pose_ = current_path.points.at(*stop_idx).point.pose; - } - - return current_path; -} - void GoalPlannerModule::transitionToNextPathIfFinishingCurrentPath() { if (isActivated() && last_approved_time_ != nullptr) { diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index 2ddcff98afea0..d52cebc6ce5eb 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -182,4 +182,36 @@ PathWithLaneId removeInverseOrderPathPoints(const PathWithLaneId & path) return fixed_path; } +std::optional generateFeasibleStopPath( + PathWithLaneId & current_path, std::shared_ptr planner_data, + geometry_msgs::msg::Pose & stop_pose, const double maximum_deceleration, + const double maximum_jerk) +{ + if (current_path.points.empty()) { + return {}; + } + + // try to insert stop point in current_path after approval + // but if can't stop with constraints(maximum deceleration, maximum jerk), don't insert stop point + const auto min_stop_distance = + behavior_path_planner::utils::start_goal_planner_common::calcFeasibleDecelDistance( + planner_data, maximum_deceleration, maximum_jerk, 0.0); + + if (!min_stop_distance) { + return {}; + } + + // set stop point + const auto stop_idx = motion_utils::insertStopPoint( + planner_data->self_odometry->pose.pose, *min_stop_distance, current_path.points); + + if (!stop_idx) { + return {}; + } + + stop_pose = current_path.points.at(*stop_idx).point.pose; + + return current_path; +} + } // namespace behavior_path_planner::utils::start_goal_planner_common From 18ad7447e4930e3d9fc38231dd24cf3be32b7818 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 21 Sep 2023 16:49:50 +0900 Subject: [PATCH 276/547] refactor(behavior_path_planner): visualize debug information (#5061) * visualize debug information Signed-off-by: kyoichi-sugahara * delete properly debug marker Signed-off-by: kyoichi-sugahara * delete duplicated implementation Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../goal_planner/goal_planner_module.cpp | 14 ++++++- .../start_planner/start_planner_module.cpp | 39 +++++++------------ 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index d9d0b4f0d8154..0470b149f2ec3 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -1626,6 +1626,9 @@ void GoalPlannerModule::setDebugData() using marker_utils::createPathMarkerArray; using marker_utils::createPoseMarkerArray; using marker_utils::createPredictedPathMarkerArray; + using marker_utils::showPolygon; + using marker_utils::showPredictedPath; + using marker_utils::showSafetyCheckInfo; using motion_utils::createStopVirtualWallMarker; using tier4_autoware_utils::createDefaultMarker; using tier4_autoware_utils::createMarkerColor; @@ -1667,17 +1670,26 @@ void GoalPlannerModule::setDebugData() add( createPathMarkerArray(partial_path, "partial_path_" + std::to_string(i), 0, 0.9, 0.5, 0.9)); } + } + // safety check + if (parameters_->safety_check_params.enable_safety_check) { if (goal_planner_data_.ego_predicted_path.size() > 0) { const auto & ego_predicted_path = utils::path_safety_checker::convertToPredictedPath( goal_planner_data_.ego_predicted_path, ego_predicted_path_params_->time_resolution); add(createPredictedPathMarkerArray( - ego_predicted_path, vehicle_info_, "ego_predicted_path", 0, 0.0, 0.5, 0.9)); + ego_predicted_path, vehicle_info_, "ego_predicted_path_goal_planner", 0, 0.0, 0.5, 0.9)); } if (goal_planner_data_.filtered_objects.objects.size() > 0) { add(createObjectsMarkerArray( goal_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); } + + add(showSafetyCheckInfo(goal_planner_data_.collision_check, "object_debug_info")); + add(showPredictedPath(goal_planner_data_.collision_check, "ego_predicted_path")); + add(showPolygon(goal_planner_data_.collision_check, "ego_and_target_polygon_relation")); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + goal_planner_data_.collision_check); } // Visualize planner type text diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 8f8976e41bd54..8af4b76f619f3 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -1191,35 +1191,26 @@ void StartPlannerModule::setDebugData() const add(createPoseMarkerArray(status_.pull_out_path.start_pose, "start_pose", 0, 0.3, 0.9, 0.3)); add(createPoseMarkerArray(status_.pull_out_path.end_pose, "end_pose", 0, 0.9, 0.9, 0.3)); add(createPathMarkerArray(getFullPath(), "full_path", 0, 0.0, 0.5, 0.9)); - if (start_planner_data_.ego_predicted_path.size() > 0) { - const auto & ego_predicted_path = utils::path_safety_checker::convertToPredictedPath( - start_planner_data_.ego_predicted_path, ego_predicted_path_params_->time_resolution); - add(createPredictedPathMarkerArray( - ego_predicted_path, vehicle_info_, "ego_predicted_path", 0, 0.0, 0.5, 0.9)); - } - - if (start_planner_data_.filtered_objects.objects.size() > 0) { - add(createObjectsMarkerArray( - start_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); - } // safety check - { + if (parameters_->safety_check_params.enable_safety_check) { + if (start_planner_data_.ego_predicted_path.size() > 0) { + const auto & ego_predicted_path = utils::path_safety_checker::convertToPredictedPath( + start_planner_data_.ego_predicted_path, ego_predicted_path_params_->time_resolution); + add(createPredictedPathMarkerArray( + ego_predicted_path, vehicle_info_, "ego_predicted_path_start_planner", 0, 0.0, 0.5, 0.9)); + } + + if (start_planner_data_.filtered_objects.objects.size() > 0) { + add(createObjectsMarkerArray( + start_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); + } + add(showSafetyCheckInfo(start_planner_data_.collision_check, "object_debug_info")); add(showPredictedPath(start_planner_data_.collision_check, "ego_predicted_path")); add(showPolygon(start_planner_data_.collision_check, "ego_and_target_polygon_relation")); - } - - if (start_planner_data_.ego_predicted_path.size() > 0) { - const auto & ego_predicted_path = utils::path_safety_checker::convertToPredictedPath( - start_planner_data_.ego_predicted_path, ego_predicted_path_params_->time_resolution); - add(createPredictedPathMarkerArray( - ego_predicted_path, vehicle_info_, "ego_predicted_path", 0, 0.0, 0.5, 0.9)); - } - - if (start_planner_data_.filtered_objects.objects.size() > 0) { - add(createObjectsMarkerArray( - start_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + start_planner_data_.collision_check); } // Visualize planner type text From 4dd69604b465c0662fc7dcb56f26e375af1d321e Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:21:35 +0200 Subject: [PATCH 277/547] build(tensorrt_yolox): remove download logic from CMake (#3143) Signed-off-by: Esteve Fernandez --- perception/tensorrt_yolox/CMakeLists.txt | 47 ------------------------ 1 file changed, 47 deletions(-) diff --git a/perception/tensorrt_yolox/CMakeLists.txt b/perception/tensorrt_yolox/CMakeLists.txt index 80a7f0964b4bc..21860854ccd5f 100644 --- a/perception/tensorrt_yolox/CMakeLists.txt +++ b/perception/tensorrt_yolox/CMakeLists.txt @@ -24,52 +24,6 @@ if(OpenMP_FOUND) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") endif() -########## -# Download pretrained model -set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") -if(NOT EXISTS "${DATA_PATH}") - execute_process(COMMAND mkdir -p ${DATA_PATH}) -endif() -function(download SUB_DIR FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if(${FILE_HASH} STREQUAL ${EXISTING_FILE_HASH}) - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/${SUB_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD https://awf.ml.dev.web.auto/perception/${SUB_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() -endfunction() - -set(V1_PATH models/object_detection_yolox_s/v1) -download(models yolox-tiny.onnx 97028baf73ce55e115599c9c60651b08) -download(models yolox-sPlus-opt.onnx bf3b0155351f90fcdca2626acbfd3bcf) -download(models yolox-sPlus-opt.EntropyV2-calibration.table c6e6f1999d5724a017516a956096701f) -download(${V1_PATH} yolox-sPlus-T4-960x960-pseudo-finetune.onnx 37165a7e67bdaf6acff93925c628f2b2) -download(${V1_PATH} yolox-sPlus-T4-960x960-pseudo-finetune.EntropyV2-calibration.table d266ea9846b10e4dab53572152c6fd8f) -download(models label.txt 9ceadca8b72b6169ee6aabb861fe3e1e) - ########## # tensorrt_yolox ament_auto_add_library(${PROJECT_NAME} SHARED @@ -160,5 +114,4 @@ endif() ament_auto_package(INSTALL_TO_SHARE launch - data ) From 3fd7b7c542baea2de5963efc049efe51f29ad41f Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Thu, 21 Sep 2023 18:48:45 +0900 Subject: [PATCH 278/547] fix(ekf_localizer): fix bug in #5054 (#5066) * fix(ekf_localizer): fix bug in #5054 Signed-off-by: kminoda * fix bug Signed-off-by: kminoda --------- Signed-off-by: kminoda --- localization/ekf_localizer/src/ekf_localizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/ekf_localizer/src/ekf_localizer.cpp b/localization/ekf_localizer/src/ekf_localizer.cpp index 3c3c38dcb1561..b39112b1d8d62 100644 --- a/localization/ekf_localizer/src/ekf_localizer.cpp +++ b/localization/ekf_localizer/src/ekf_localizer.cpp @@ -387,7 +387,7 @@ void EKFLocalizer::callbackTwistWithCovariance( { // Ignore twist if velocity is too small. // Note that this inequality must not include "equal". - if (msg->twist.twist.linear.x < params_.threshold_observable_velocity_mps) { + if (std::abs(msg->twist.twist.linear.x) < params_.threshold_observable_velocity_mps) { msg->twist.covariance[0 * 6 + 0] = 10000.0; } twist_queue_.push(msg); From e0bcedc3651e45bb060c113f044e29ca9e4676c3 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 21 Sep 2023 19:35:21 +0900 Subject: [PATCH 279/547] fix(mission_planner): routing api uninitialization (#4942) * fix(mission_planner): publish route state after the node is ready Signed-off-by: Takamasa Horibe * add info log Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../src/mission_planner/mission_planner.cpp | 22 +++++++++++++++++++ .../src/mission_planner/mission_planner.hpp | 3 +++ 2 files changed, 25 insertions(+) diff --git a/planning/mission_planner/src/mission_planner/mission_planner.cpp b/planning/mission_planner/src/mission_planner/mission_planner.cpp index 02c7903a12249..7e071e3b926b2 100644 --- a/planning/mission_planner/src/mission_planner/mission_planner.cpp +++ b/planning/mission_planner/src/mission_planner/mission_planner.cpp @@ -122,7 +122,29 @@ MissionPlanner::MissionPlanner(const rclcpp::NodeOptions & options) adaptor.init_srv(srv_set_mrm_route_, this, &MissionPlanner::on_set_mrm_route); adaptor.init_srv(srv_clear_mrm_route_, this, &MissionPlanner::on_clear_mrm_route); + // Route state will be published when the node gets ready for route api after initialization, + // otherwise the mission planner rejects the request for the API. + data_check_timer_ = create_wall_timer( + std::chrono::milliseconds(100), std::bind(&MissionPlanner::checkInitialization, this)); +} + +void MissionPlanner::checkInitialization() +{ + if (!planner_->ready()) { + RCLCPP_INFO_THROTTLE( + get_logger(), *get_clock(), 5000, "waiting lanelet map... Route API is not ready."); + return; + } + if (!odometry_) { + RCLCPP_INFO_THROTTLE( + get_logger(), *get_clock(), 5000, "waiting odometry... Route API is not ready."); + return; + } + + // All data is ready. Now API is available. + RCLCPP_INFO(get_logger(), "Route API is ready."); change_state(RouteState::Message::UNSET); + data_check_timer_->cancel(); // stop timer callback } void MissionPlanner::on_odometry(const Odometry::ConstSharedPtr msg) diff --git a/planning/mission_planner/src/mission_planner/mission_planner.hpp b/planning/mission_planner/src/mission_planner/mission_planner.hpp index 41b03b09e0755..f7af76f43bbb0 100644 --- a/planning/mission_planner/src/mission_planner/mission_planner.hpp +++ b/planning/mission_planner/src/mission_planner/mission_planner.hpp @@ -85,6 +85,9 @@ class MissionPlanner : public rclcpp::Node void on_reroute_availability(const RerouteAvailability::ConstSharedPtr msg); void on_modified_goal(const PoseWithUuidStamped::ConstSharedPtr msg); + rclcpp::TimerBase::SharedPtr data_check_timer_; + void checkInitialization(); + rclcpp::Publisher::SharedPtr pub_marker_; void clear_route(); void clear_mrm_route(); From b87bfac785f4cbf8a8b29d5a4dc2240d456df466 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 21 Sep 2023 20:54:11 +0900 Subject: [PATCH 280/547] feat(behavior_velocity_planner): enable dynamic load/unload modules (#5063) * feat(behavior_velocity_planner): enable dynamically load and unload modules Signed-off-by: Takamasa Horibe * fix format Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../behavior_velocity_planner/CMakeLists.txt | 24 +++++++++++++-- .../behavior_velocity_planner/package.xml | 6 ++++ .../behavior_velocity_planner/src/node.cpp | 24 +++++++++++++++ .../behavior_velocity_planner/src/node.hpp | 14 +++++++++ .../src/planner_manager.cpp | 30 +++++++++++++++++++ .../src/planner_manager.hpp | 1 + .../srv/LoadPlugin.srv | 3 ++ .../srv/UnloadPlugin.srv | 3 ++ 8 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 planning/behavior_velocity_planner/srv/LoadPlugin.srv create mode 100644 planning/behavior_velocity_planner/srv/UnloadPlugin.srv diff --git a/planning/behavior_velocity_planner/CMakeLists.txt b/planning/behavior_velocity_planner/CMakeLists.txt index fbdb17f93db65..ee7dd7c07a77a 100644 --- a/planning/behavior_velocity_planner/CMakeLists.txt +++ b/planning/behavior_velocity_planner/CMakeLists.txt @@ -2,25 +2,43 @@ cmake_minimum_required(VERSION 3.14) project(behavior_velocity_planner) find_package(autoware_cmake REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +rosidl_generate_interfaces( + ${PROJECT_NAME} + "srv/LoadPlugin.srv" + "srv/UnloadPlugin.srv" + DEPENDENCIES +) + autoware_package() -ament_auto_add_library(${PROJECT_NAME} SHARED +ament_auto_add_library(${PROJECT_NAME}_lib SHARED src/node.cpp src/planner_manager.cpp ) -rclcpp_components_register_node(${PROJECT_NAME} +rclcpp_components_register_node(${PROJECT_NAME}_lib PLUGIN "behavior_velocity_planner::BehaviorVelocityPlannerNode" EXECUTABLE ${PROJECT_NAME}_node ) +if(${rosidl_cmake_VERSION} VERSION_LESS 2.5.0) + rosidl_target_interfaces(${PROJECT_NAME}_lib + ${PROJECT_NAME} "rosidl_typesupport_cpp") +else() + rosidl_get_typesupport_target( + cpp_typesupport_target ${PROJECT_NAME} "rosidl_typesupport_cpp") + target_link_libraries(${PROJECT_NAME}_lib "${cpp_typesupport_target}") +endif() + if(BUILD_TESTING) ament_add_ros_isolated_gtest(test_${PROJECT_NAME} test/src/test_node_interface.cpp ) target_link_libraries(test_${PROJECT_NAME} gtest_main - ${PROJECT_NAME} + ${PROJECT_NAME}_lib ) target_include_directories(test_${PROJECT_NAME} PRIVATE src) endif() diff --git a/planning/behavior_velocity_planner/package.xml b/planning/behavior_velocity_planner/package.xml index 5a307398e9afd..9a8b1a013738d 100644 --- a/planning/behavior_velocity_planner/package.xml +++ b/planning/behavior_velocity_planner/package.xml @@ -33,6 +33,8 @@ autoware_cmake eigen3_cmake_module + rosidl_default_generators + autoware_auto_mapping_msgs autoware_auto_perception_msgs autoware_auto_planning_msgs @@ -60,6 +62,8 @@ tier4_v2x_msgs visualization_msgs + rosidl_default_runtime + ament_cmake_ros ament_lint_auto autoware_lint_common @@ -78,6 +82,8 @@ behavior_velocity_virtual_traffic_light_module behavior_velocity_walkway_module + rosidl_interface_packages + ament_cmake diff --git a/planning/behavior_velocity_planner/src/node.cpp b/planning/behavior_velocity_planner/src/node.cpp index 1bbe3eeeb1b78..f30043ad42588 100644 --- a/planning/behavior_velocity_planner/src/node.cpp +++ b/planning/behavior_velocity_planner/src/node.cpp @@ -75,6 +75,8 @@ BehaviorVelocityPlannerNode::BehaviorVelocityPlannerNode(const rclcpp::NodeOptio planner_data_(*this) { using std::placeholders::_1; + using std::placeholders::_2; + // Trigger Subscriber trigger_sub_path_with_lane_id_ = this->create_subscription( @@ -118,6 +120,12 @@ BehaviorVelocityPlannerNode::BehaviorVelocityPlannerNode(const rclcpp::NodeOptio "~/input/occupancy_grid", 1, std::bind(&BehaviorVelocityPlannerNode::onOccupancyGrid, this, _1), createSubscriptionOptions(this)); + srv_load_plugin_ = create_service( + "~/service/load_plugin", std::bind(&BehaviorVelocityPlannerNode::onLoadPlugin, this, _1, _2)); + srv_unload_plugin_ = create_service( + "~/service/unload_plugin", + std::bind(&BehaviorVelocityPlannerNode::onUnloadPlugin, this, _1, _2)); + // set velocity smoother param onParam(); @@ -145,6 +153,22 @@ BehaviorVelocityPlannerNode::BehaviorVelocityPlannerNode(const rclcpp::NodeOptio } } +void BehaviorVelocityPlannerNode::onLoadPlugin( + const LoadPlugin::Request::SharedPtr request, + [[maybe_unused]] const LoadPlugin::Response::SharedPtr response) +{ + std::unique_lock lk(mutex_); + planner_manager_.launchScenePlugin(*this, request->plugin_name); +} + +void BehaviorVelocityPlannerNode::onUnloadPlugin( + const UnloadPlugin::Request::SharedPtr request, + [[maybe_unused]] const UnloadPlugin::Response::SharedPtr response) +{ + std::unique_lock lk(mutex_); + planner_manager_.removeScenePlugin(*this, request->plugin_name); +} + // NOTE: argument planner_data must not be referenced for multithreading bool BehaviorVelocityPlannerNode::isDataReady( const PlannerData planner_data, rclcpp::Clock clock) const diff --git a/planning/behavior_velocity_planner/src/node.hpp b/planning/behavior_velocity_planner/src/node.hpp index bf7c53f974ef6..bdc280434ef41 100644 --- a/planning/behavior_velocity_planner/src/node.hpp +++ b/planning/behavior_velocity_planner/src/node.hpp @@ -17,6 +17,8 @@ #include "planner_manager.hpp" +#include +#include #include #include @@ -38,11 +40,15 @@ #include #include #include +#include namespace behavior_velocity_planner { using autoware_auto_mapping_msgs::msg::HADMapBin; +using behavior_velocity_planner::srv::LoadPlugin; +using behavior_velocity_planner::srv::UnloadPlugin; using tier4_planning_msgs::msg::VelocityLimit; + class BehaviorVelocityPlannerNode : public rclcpp::Node { public: @@ -103,6 +109,14 @@ class BehaviorVelocityPlannerNode : public rclcpp::Node HADMapBin::ConstSharedPtr map_ptr_{nullptr}; bool has_received_map_; + rclcpp::Service::SharedPtr srv_load_plugin_; + rclcpp::Service::SharedPtr srv_unload_plugin_; + void onUnloadPlugin( + const UnloadPlugin::Request::SharedPtr request, + const UnloadPlugin::Response::SharedPtr response); + void onLoadPlugin( + const LoadPlugin::Request::SharedPtr request, const LoadPlugin::Response::SharedPtr response); + // mutex for planner_data_ std::mutex mutex_; diff --git a/planning/behavior_velocity_planner/src/planner_manager.cpp b/planning/behavior_velocity_planner/src/planner_manager.cpp index 39d4dc345e899..e1c801a27dca3 100644 --- a/planning/behavior_velocity_planner/src/planner_manager.cpp +++ b/planning/behavior_velocity_planner/src/planner_manager.cpp @@ -60,12 +60,42 @@ void BehaviorVelocityPlannerManager::launchScenePlugin( if (plugin_loader_.isClassAvailable(name)) { const auto plugin = plugin_loader_.createSharedInstance(name); plugin->init(node); + + // Check if the plugin is already registered. + for (const auto & running_plugin : scene_manager_plugins_) { + if (plugin->getModuleName() == running_plugin->getModuleName()) { + RCLCPP_WARN_STREAM(node.get_logger(), "The plugin '" << name << "' is already loaded."); + return; + } + } + + // register scene_manager_plugins_.push_back(plugin); + RCLCPP_INFO_STREAM(node.get_logger(), "The scene plugin '" << name << "' is loaded."); } else { RCLCPP_ERROR_STREAM(node.get_logger(), "The scene plugin '" << name << "' is not available."); } } +void BehaviorVelocityPlannerManager::removeScenePlugin( + rclcpp::Node & node, const std::string & name) +{ + auto it = std::remove_if( + scene_manager_plugins_.begin(), scene_manager_plugins_.end(), + [&](const std::shared_ptr plugin) { + return plugin->getModuleName() == name; + }); + + if (it == scene_manager_plugins_.end()) { + RCLCPP_WARN_STREAM( + node.get_logger(), + "The scene plugin '" << name << "' is not found in the registered modules."); + } else { + scene_manager_plugins_.erase(it, scene_manager_plugins_.end()); + RCLCPP_INFO_STREAM(node.get_logger(), "The scene plugin '" << name << "' is unloaded."); + } +} + autoware_auto_planning_msgs::msg::PathWithLaneId BehaviorVelocityPlannerManager::planPathVelocity( const std::shared_ptr & planner_data, const autoware_auto_planning_msgs::msg::PathWithLaneId & input_path_msg) diff --git a/planning/behavior_velocity_planner/src/planner_manager.hpp b/planning/behavior_velocity_planner/src/planner_manager.hpp index e57d90d5cdfee..7485e5faba8bf 100644 --- a/planning/behavior_velocity_planner/src/planner_manager.hpp +++ b/planning/behavior_velocity_planner/src/planner_manager.hpp @@ -43,6 +43,7 @@ class BehaviorVelocityPlannerManager public: BehaviorVelocityPlannerManager(); void launchScenePlugin(rclcpp::Node & node, const std::string & name); + void removeScenePlugin(rclcpp::Node & node, const std::string & name); autoware_auto_planning_msgs::msg::PathWithLaneId planPathVelocity( const std::shared_ptr & planner_data, diff --git a/planning/behavior_velocity_planner/srv/LoadPlugin.srv b/planning/behavior_velocity_planner/srv/LoadPlugin.srv new file mode 100644 index 0000000000000..7b9f08ab0ded8 --- /dev/null +++ b/planning/behavior_velocity_planner/srv/LoadPlugin.srv @@ -0,0 +1,3 @@ +string plugin_name +--- +bool success diff --git a/planning/behavior_velocity_planner/srv/UnloadPlugin.srv b/planning/behavior_velocity_planner/srv/UnloadPlugin.srv new file mode 100644 index 0000000000000..7b9f08ab0ded8 --- /dev/null +++ b/planning/behavior_velocity_planner/srv/UnloadPlugin.srv @@ -0,0 +1,3 @@ +string plugin_name +--- +bool success From 68e64acc2809b2970d78210bcce44c28518923a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kaan=20=C3=87olak?= Date: Thu, 21 Sep 2023 16:28:36 +0300 Subject: [PATCH 281/547] refactor(traffic_light_classifier): update training docs (#4917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(traffic_light_classifier): update training docs Signed-off-by: Kaan Çolak --------- Signed-off-by: Kaan Çolak --- perception/traffic_light_classifier/README.md | 261 +++++++++++++++--- 1 file changed, 224 insertions(+), 37 deletions(-) diff --git a/perception/traffic_light_classifier/README.md b/perception/traffic_light_classifier/README.md index 53ba0d3be7ba2..3d15af0cf7805 100644 --- a/perception/traffic_light_classifier/README.md +++ b/perception/traffic_light_classifier/README.md @@ -92,67 +92,254 @@ These colors and shapes are assigned to the message as follows: | `red_max_s` | int | the maximum saturation of red color | | `red_max_v` | int | the maximum value (brightness) of red color | -## Customization of CNN model +## Training Traffic Light Classifier Model + +### Overview + +This guide provides detailed instructions on training a traffic light classifier model using the **[mmlab/mmpretrain](https://github.com/open-mmlab/mmpretrain)** repository +and deploying it using **[mmlab/mmdeploy](https://github.com/open-mmlab/mmdeploy)**. +If you wish to create a custom traffic light classifier model with your own dataset, please follow the steps outlined below. + +### Data Preparation + +#### Use Sample Dataset + +Autoware offers a sample dataset that illustrates the training procedures for +traffic light classification. This dataset comprises 1045 images categorized +into red, green, and yellow labels. To utilize this sample dataset, +please download it from **[link](https://autoware-files.s3.us-west-2.amazonaws.com/dataset/traffic_light_sample_dataset.tar.gz)** and extract it to a designated folder of your choice. + +#### Use Your Custom Dataset + +To train a traffic light classifier, adopt a structured subfolder format where each +subfolder represents a distinct class. Below is an illustrative dataset structure example; + +```python +DATASET_ROOT + ├── TRAIN + │ ├── RED + │ │ ├── 001.png + │ │ ├── 002.png + │ │ └── ... + │ │ + │ ├── GREEN + │ │ ├── 001.png + │ │ ├── 002.png + │ │ └──... + │ │ + │ ├── YELLOW + │ │ ├── 001.png + │ │ ├── 002.png + │ │ └──... + │ └── ... + │ + ├── VAL + │ └──... + │ + │ + └── TEST + └── ... -Currently, in Autoware, [MobileNetV2](https://arxiv.org/abs/1801.04381v3) and [EfficientNet-b1](https://arxiv.org/abs/1905.11946v5) are provided. -The corresponding onnx files are `data/traffic_light_classifier_mobilenetv2.onnx` and `data/traffic_light_classifier_efficientNet_b1.onnx` (These files will be downloaded during the build process). -Also, you can apply the following models shown as below, for example. -- [ResNet](https://openaccess.thecvf.com/content_cvpr_2016/html/He_Deep_Residual_Learning_CVPR_2016_paper.html) -- [MobileNetV3](https://arxiv.org/abs/1905.02244) - ... +``` + +### Installation + +#### Prerequisites + +**Step 1.** Download and install Miniconda from the [official website](https://mmpretrain.readthedocs.io/en/latest/get_started.html). + +**Step 2.** Create a conda virtual environment and activate it -In order to train models and export onnx model, we recommend [open-mmlab/mmclassification](https://github.com/open-mmlab/mmclassification.git). -Please follow the [official document](https://mmclassification.readthedocs.io/en/latest/) to install and experiment with mmclassification. If you get into troubles, [FAQ page](https://mmclassification.readthedocs.io/en/latest/faq.html) would help you. +```bash +conda create --name tl-classifier python=3.8 -y +conda activate tl-classifier +``` -The following steps are example of a quick-start. +**Step 3.** Install PyTorch -### step 0. Install [MMCV](https://github.com/open-mmlab/mmcv.git) and [MIM](https://github.com/open-mmlab/mim.git) +Please ensure you have PyTorch installed, compatible with CUDA 11.6, as it is a requirement for current Autoware -_NOTE_ : First of all, install [PyTorch](https://pytorch.org/) suitable for your CUDA version (CUDA11.6 is supported in Autoware). +```bash +conda install pytorch==1.13.1 torchvision==0.14.1 pytorch-cuda=11.6 -c pytorch -c nvidia +``` -In order to install mmcv suitable for your CUDA version, install it specifying a url. +#### Install mmlab/mmpretrain -```shell -# Install mim -$ pip install -U openmim +**Step 1.** Install mmpretrain from source -# Install mmcv on a machine with CUDA11.6 and PyTorch1.13.0 -$ pip install mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu116/torch1.13/index.html +```bash +cd ~/ +git clone https://github.com/open-mmlab/mmpretrain.git +cd mmpretrain +pip install -U openmim && mim install -e . ``` -### step 1. Install MMClassification +### Training + +MMPretrain offers a training script that is controlled through a configuration file. +Leveraging an inheritance design pattern, you can effortlessly tailor the training script +using Python files as configuration files. -You can install MMClassification as a Python package or from source. +In the example, we demonstrate the training steps on the MobileNetV2 model, +but you have the flexibility to employ alternative classification models such as +EfficientNetV2, EfficientNetV3, ResNet, and more. -```shell -# As a Python package -$ pip install mmcls +#### Create a config file -# From source -$ git clone https://github.com/open-mmlab/mmclassification.git -$ cd mmclassification -$ pip install -v -e . +Generate a configuration file for your preferred model within the `configs` folder + +```bash +touch ~/mmpretrain/configs/mobilenet_v2/mobilenet-v2_8xb32_custom.py ``` -### step 2. Train your model +Open the configuration file in your preferred text editor and make a copy of +the provided content. Adjust the **data_root** variable to match the path of your dataset. +You are welcome to customize the configuration parameters for the model, dataset, and scheduler to +suit your preferences + +```python +# Inherit model, schedule and default_runtime from base model +_base_ = [ + '../_base_/models/mobilenet_v2_1x.py', + '../_base_/schedules/imagenet_bs256_epochstep.py', + '../_base_/default_runtime.py' +] + +# Set the number of classes to the model +# You can also change other model parameters here +# For detailed descriptions of model parameters, please refer to link below +# (Customize model)[https://mmpretrain.readthedocs.io/en/latest/advanced_guides/modules.html] +model = dict(head=dict(num_classes=3, topk=(1, 3))) + +# Set max epochs and validation interval +train_cfg = dict(by_epoch=True, max_epochs=50, val_interval=5) + +# Set optimizer and lr scheduler +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.001, momentum=0.9)) +param_scheduler = dict(type='StepLR', by_epoch=True, step_size=1, gamma=0.98) + +dataset_type = 'CustomDataset' +data_root = "/PATH/OF/YOUR/DATASET" + +# Customize data preprocessing and dataloader pipeline for training set +# These parameters calculated for the sample dataset +data_preprocessor = dict( + mean=[0.2888 * 256, 0.2570 * 256, 0.2329 * 256], + std=[0.2106 * 256, 0.2037 * 256, 0.1864 * 256], + num_classes=3, + to_rgb=True, +) + +# Customize data preprocessing and dataloader pipeline for train set +# For detailed descriptions of data pipeline, please refer to link below +# (Customize data pipeline)[https://mmpretrain.readthedocs.io/en/latest/advanced_guides/pipeline.html] +train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=224), + dict(type='RandomFlip', prob=0.5, direction='horizontal'), + dict(type='PackInputs'), +] +train_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='', + data_prefix='train', + with_label=True, + pipeline=train_pipeline, + ), + num_workers=8, + batch_size=32, + sampler=dict(type='DefaultSampler', shuffle=True) +) + +# Customize data preprocessing and dataloader pipeline for test set +test_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=224), + dict(type='PackInputs'), +] + +# Customize data preprocessing and dataloader pipeline for validation set +val_cfg = dict() +val_dataloader = dict( + dataset=dict( + type=dataset_type, + data_root=data_root, + ann_file='', + data_prefix='val', + with_label=True, + pipeline=test_pipeline, + ), + num_workers=8, + batch_size=32, + sampler=dict(type='DefaultSampler', shuffle=True) +) + +val_evaluator = dict(topk=(1, 3,), type='Accuracy') + +test_dataloader = val_dataloader +test_evaluator = val_evaluator + +``` -Train model with your experiment configuration file. For the details of config file, see [here](https://mmclassification.readthedocs.io/en/latest/tutorials/config.html). +#### Start training -```shell -# [] is optional, you can start training from pre-trained checkpoint -$ mim train mmcls YOUR_CONFIG.py [--resume-from YOUR_CHECKPOINT.pth] +```bash +cd ~/mmpretrain +python tools/train.py configs/mobilenet_v2/mobilenet-v2_8xb32_custom.py ``` -### step 3. Export onnx model +Training logs and weights will be saved in the `work_dirs/mobilenet-v2_8xb32_custom` folder. -In exporting onnx, use `mmclassification/tools/deployment/pytorch2onnx.py` or [open-mmlab/mmdeploy](https://github.com/open-mmlab/mmdeploy.git). +### Convert PyTorh model to ONNX model -```shell -cd ~/mmclassification/tools/deployment -python3 pytorch2onnx.py YOUR_CONFIG.py ... +#### Install mmdeploy + +The 'mmdeploy' toolset is designed for deploying your trained model onto various target devices. +With its capabilities, you can seamlessly convert PyTorch models into the ONNX format. + +```bash +# Activate your conda environment +conda activate tl-classifier + +# Install mmenigne and mmcv +mim install mmengine +mim install "mmcv>=2.0.0rc2" + +# Install mmdeploy +pip install mmdeploy==1.2.0 + +# Support onnxruntime +pip install mmdeploy-runtime==1.2.0 +pip install mmdeploy-runtime-gpu==1.2.0 +pip install onnxruntime-gpu==1.8.1 + +#Clone mmdeploy repository +cd ~/ +git clone -b main https://github.com/open-mmlab/mmdeploy.git ``` +#### Convert PyTorch model to ONNX model + +```bash +cd ~/mmdeploy + +# Run deploy.py script +# deploy.py script takes 5 main arguments with these order; config file path, train config file path, +# checkpoint file path, demo image path, and work directory path +python tools/deploy.py \ +~/mmdeploy/configs/mmpretrain/classification_onnxruntime_static.py\ +~/mmpretrain/configs/mobilenet_v2/train_mobilenet_v2.py \ +~/mmpretrain/work_dirs/train_mobilenet_v2/epoch_300.pth \ +/SAMPLE/IAMGE/DIRECTORY \ +--work-dir mmdeploy_model/mobilenet_v2 +``` + +Converted ONNX model will be saved in the `mmdeploy/mmdeploy_model/mobilenet_v2` folder. + After obtaining your onnx model, update parameters defined in the launch file (e.g. `model_file_path`, `label_file_path`, `input_h`, `input_w`...). Note that, we only support labels defined in [tier4_perception_msgs::msg::TrafficLightElement](https://github.com/tier4/tier4_autoware_msgs/blob/tier4/universe/tier4_perception_msgs/msg/traffic_light/TrafficLightElement.msg). From cd2cd521c9b3058e2c677f0f0e2a7ae997feaa7e Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Thu, 21 Sep 2023 22:55:18 +0900 Subject: [PATCH 282/547] refactor(behavior_path_planner): uninitialized values in createPredictedPath (#5065) Signed-off-by: Muhammad Zulfaqar Azmi --- .../path_safety_checker/objects_filtering.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index f69aa186dc377..ff54ce2d2dcc4 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -234,14 +234,12 @@ std::vector createPredictedPath( convertToFrenetPoint(path_points, vehicle_pose.position, ego_seg_idx); for (double t = 0.0; t < time_horizon; t += time_resolution) { - double velocity; - double length; - - if (t < delay_until_departure) { - // Before the departure, the velocity is 0 and there's no change in position. - velocity = 0.0; - length = 0.0; - } else { + double velocity = 0.0; + double length = 0.0; + + // If t < delay_until_departure, it means ego have not depart yet, therefore the velocity is + // 0 and there's no change in position. + if (t >= delay_until_departure) { // Adjust time to consider the delay. double t_with_delay = t - delay_until_departure; velocity = From 1a5aa69f46c8b40549b92cabce85efd3864768f2 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 21 Sep 2023 23:02:37 +0900 Subject: [PATCH 283/547] refactor(behavior_velocity_planner_common): simplify header files (#5055) Signed-off-by: Mamoru Sobue --- .../geometry/geometry.hpp | 7 +- .../src/geometry/geometry.cpp | 3 + .../src/debug.cpp | 2 + .../src/scene.cpp | 1 + .../util.hpp | 2 - .../src/scene_crosswalk.cpp | 1 + .../src/util.cpp | 1 + .../src/debug.cpp | 1 + .../src/manager.cpp | 1 + .../src/debug.cpp | 8 + .../src/scene_intersection.cpp | 6 +- .../src/scene_merge_from_private_road.cpp | 1 + .../src/util.cpp | 3 +- .../src/manager.cpp | 1 + .../src/scene.cpp | 1 + .../src/debug.cpp | 2 +- .../src/manager.cpp | 1 + .../src/scene_no_stopping_area.cpp | 1 + .../src/debug.cpp | 1 + .../src/scene_out_of_lane.cpp | 1 + .../CMakeLists.txt | 5 + .../planner_data.hpp | 1 - .../scene_module_interface.hpp | 237 ++-------------- .../utilization/arc_lane_util.hpp | 114 +------- .../utilization/boost_geometry_helper.hpp | 60 +--- .../utilization/debug.hpp | 6 +- .../utilization/path_utilization.hpp | 1 - .../utilization/trajectory_utils.hpp | 92 +----- .../utilization/util.hpp | 84 +----- .../velocity_factor_interface.hpp | 19 +- .../src/scene_module_interface.cpp | 264 ++++++++++++++++++ .../src/utilization/arc_lane_util.cpp | 138 +++++++++ .../src/utilization/boost_geometry_helper.cpp | 66 +++++ .../src/utilization/debug.cpp | 2 +- .../src/utilization/path_utilization.cpp | 11 - .../src/utilization/trajectory_utils.cpp | 121 ++++++++ .../src/utilization/util.cpp | 166 ++++++----- .../src/velocity_factor_interface.cpp | 33 +++ .../CMakeLists.txt | 1 + .../src/debug.cpp | 7 +- .../src/debug.hpp | 3 - .../src/dynamic_obstacle.cpp | 4 + .../src/dynamic_obstacle.hpp | 2 + .../src/path_utils.cpp | 39 +++ .../src/path_utils.hpp | 23 +- .../src/scene.cpp | 4 + .../src/utils.cpp | 13 + .../src/utils.hpp | 4 + .../src/debug.cpp | 1 + .../src/manager.cpp | 2 + .../src/debug.cpp | 2 +- .../src/debug.cpp | 2 +- .../src/manager.cpp | 2 +- .../src/scene.cpp | 2 + .../src/debug.cpp | 2 +- .../src/manager.cpp | 1 + .../motion_velocity_smoother/resample.hpp | 5 - .../smoother/smoother_base.hpp | 7 - .../trajectory_utils.hpp | 3 - .../src/motion_velocity_smoother_node.cpp | 2 +- .../motion_velocity_smoother/src/resample.cpp | 2 + .../analytical_jerk_constrained_smoother.cpp | 1 + .../src/smoother/smoother_base.cpp | 3 +- .../src/trajectory_utils.cpp | 2 + 64 files changed, 905 insertions(+), 699 deletions(-) create mode 100644 planning/behavior_velocity_planner_common/src/scene_module_interface.cpp create mode 100644 planning/behavior_velocity_planner_common/src/utilization/arc_lane_util.cpp create mode 100644 planning/behavior_velocity_planner_common/src/utilization/boost_geometry_helper.cpp create mode 100644 planning/behavior_velocity_planner_common/src/utilization/trajectory_utils.cpp create mode 100644 planning/behavior_velocity_planner_common/src/velocity_factor_interface.cpp create mode 100644 planning/behavior_velocity_run_out_module/src/path_utils.cpp diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp index b42bc509db3c1..17ce7e1f4de5a 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp @@ -38,15 +38,10 @@ #include #include #include +#include #include -#ifdef ROS_DISTRO_GALACTIC -#include -#else -#include -#endif - // TODO(wep21): Remove these apis // after they are implemented in ros2 geometry2. namespace tf2 diff --git a/common/tier4_autoware_utils/src/geometry/geometry.cpp b/common/tier4_autoware_utils/src/geometry/geometry.cpp index 6048527142018..2c2de2d07a3dc 100644 --- a/common/tier4_autoware_utils/src/geometry/geometry.cpp +++ b/common/tier4_autoware_utils/src/geometry/geometry.cpp @@ -16,7 +16,10 @@ #include +#include + #include + namespace tf2 { void fromMsg(const geometry_msgs::msg::PoseStamped & msg, tf2::Stamped & out) diff --git a/planning/behavior_velocity_blind_spot_module/src/debug.cpp b/planning/behavior_velocity_blind_spot_module/src/debug.cpp index 1652d9d56a8a0..f95ed73c2a184 100644 --- a/planning/behavior_velocity_blind_spot_module/src/debug.cpp +++ b/planning/behavior_velocity_blind_spot_module/src/debug.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_blind_spot_module/src/scene.cpp b/planning/behavior_velocity_blind_spot_module/src/scene.cpp index 94e11bc144a32..775f3b1c65973 100644 --- a/planning/behavior_velocity_blind_spot_module/src/scene.cpp +++ b/planning/behavior_velocity_blind_spot_module/src/scene.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp b/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp index dfe682d0aa0c9..6c70e7a7f6dc0 100644 --- a/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp +++ b/planning/behavior_velocity_crosswalk_module/include/behavior_velocity_crosswalk_module/util.hpp @@ -43,8 +43,6 @@ namespace behavior_velocity_planner using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using autoware_auto_planning_msgs::msg::PathWithLaneId; -using tier4_autoware_utils::createPoint; -using tier4_autoware_utils::Point2d; using tier4_planning_msgs::msg::StopFactor; enum class CollisionState { YIELD, EGO_PASS_FIRST, EGO_PASS_LATER, IGNORE }; diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index dc37046e1a4d3..9ea03996c88dd 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/planning/behavior_velocity_crosswalk_module/src/util.cpp b/planning/behavior_velocity_crosswalk_module/src/util.cpp index 76d59ac895d5a..47bbef7111da0 100644 --- a/planning/behavior_velocity_crosswalk_module/src/util.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/util.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_detection_area_module/src/debug.cpp b/planning/behavior_velocity_detection_area_module/src/debug.cpp index b3d3d5c781ff8..ee2af54e5ea2a 100644 --- a/planning/behavior_velocity_detection_area_module/src/debug.cpp +++ b/planning/behavior_velocity_detection_area_module/src/debug.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #ifdef ROS_DISTRO_GALACTIC diff --git a/planning/behavior_velocity_detection_area_module/src/manager.cpp b/planning/behavior_velocity_detection_area_module/src/manager.cpp index 16d32b8a49964..e7ec6b37f7f20 100644 --- a/planning/behavior_velocity_detection_area_module/src/manager.cpp +++ b/planning/behavior_velocity_detection_area_module/src/manager.cpp @@ -14,6 +14,7 @@ #include "manager.hpp" +#include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index dfaa10ec9d86f..9d8cb46fcf54f 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -20,6 +20,14 @@ #include #include +#include + +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif + #include #include diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 5f9d838b7c220..aa1ceca086bc2 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -24,8 +24,13 @@ #include #include #include +#include #include #include +#include +#include + +#include #include #include @@ -35,7 +40,6 @@ #include #include #include - namespace behavior_velocity_planner { namespace bg = boost::geometry; diff --git a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp index 10ad395953476..9c3a35033ea9b 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index ce2d7252ac93d..24c3454fd7e88 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -23,9 +23,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -40,7 +42,6 @@ #include #include #include - namespace behavior_velocity_planner { namespace bg = boost::geometry; diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp index 2c57ff9458833..f1fa4c47e951b 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/manager.cpp @@ -14,6 +14,7 @@ #include "manager.hpp" +#include #include #include diff --git a/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp b/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp index 8109b99166332..0d0e7cc6cadba 100644 --- a/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp +++ b/planning/behavior_velocity_no_drivable_lane_module/src/scene.cpp @@ -18,6 +18,7 @@ #include "tier4_autoware_utils/geometry/geometry.hpp" #include "util.hpp" +#include #include namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp b/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp index e8bf83868d122..9e51afca2d475 100644 --- a/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp +++ b/planning/behavior_velocity_no_stopping_area_module/src/debug.cpp @@ -17,8 +17,8 @@ #include #include #include +#include #include - #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp b/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp index 60d659497e55a..d5e9eeddb6377 100644 --- a/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp +++ b/planning/behavior_velocity_no_stopping_area_module/src/manager.cpp @@ -14,6 +14,7 @@ #include "manager.hpp" +#include #include #include diff --git a/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp b/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp index efa2ce60162f6..ceef50094e374 100644 --- a/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp +++ b/planning/behavior_velocity_no_stopping_area_module/src/scene_no_stopping_area.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp b/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp index 527f6f02140ea..a6e13d0dfdcdc 100644 --- a/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp +++ b/planning/behavior_velocity_occlusion_spot_module/src/debug.cpp @@ -32,6 +32,7 @@ using builtin_interfaces::msg::Time; using BasicPolygons = std::vector; using occlusion_spot_utils::PossibleCollisionInfo; using tier4_autoware_utils::appendMarkerArray; +using tier4_autoware_utils::calcOffsetPose; using tier4_autoware_utils::createDefaultMarker; using tier4_autoware_utils::createMarkerColor; using tier4_autoware_utils::createMarkerOrientation; diff --git a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp index 280a832f6d684..013643b6bd6d4 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/planning/behavior_velocity_planner_common/CMakeLists.txt b/planning/behavior_velocity_planner_common/CMakeLists.txt index 334ee35329ba3..e958df74afe2d 100644 --- a/planning/behavior_velocity_planner_common/CMakeLists.txt +++ b/planning/behavior_velocity_planner_common/CMakeLists.txt @@ -5,7 +5,12 @@ find_package(autoware_cmake REQUIRED) autoware_package() ament_auto_add_library(${PROJECT_NAME} SHARED + src/scene_module_interface.cpp + src/velocity_factor_interface.cpp src/utilization/path_utilization.cpp + src/utilization/trajectory_utils.cpp + src/utilization/arc_lane_util.cpp + src/utilization/boost_geometry_helper.cpp src/utilization/util.cpp src/utilization/debug.cpp ) diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp index 7e48f410f363a..620fc61ec5360 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/planner_data.hpp @@ -39,7 +39,6 @@ #include #include -#include #include #include diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp index 4735bb34c7b00..90e78215cae2d 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/scene_module_interface.hpp @@ -16,14 +16,11 @@ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__SCENE_MODULE_INTERFACE_HPP_ #include -#include #include #include #include #include #include -#include -#include #include #include @@ -35,10 +32,7 @@ #include #include -#include -#include #include -#include #include #include #include @@ -48,7 +42,6 @@ #include #include - namespace behavior_velocity_planner { @@ -56,7 +49,6 @@ using autoware_auto_planning_msgs::msg::PathWithLaneId; using builtin_interfaces::msg::Time; using rtc_interface::RTCInterface; using tier4_autoware_utils::DebugPublisher; -using tier4_autoware_utils::StopWatch; using tier4_debug_msgs::msg::Float64Stamped; using tier4_planning_msgs::msg::StopFactor; using tier4_planning_msgs::msg::StopReason; @@ -67,15 +59,7 @@ class SceneModuleInterface { public: explicit SceneModuleInterface( - const int64_t module_id, rclcpp::Logger logger, rclcpp::Clock::SharedPtr clock) - : module_id_(module_id), - activated_(false), - safe_(false), - distance_(std::numeric_limits::lowest()), - logger_(logger), - clock_(clock) - { - } + const int64_t module_id, rclcpp::Logger logger, rclcpp::Clock::SharedPtr clock); virtual ~SceneModuleInterface() = default; virtual bool modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) = 0; @@ -125,44 +109,14 @@ class SceneModuleInterface void setSafe(const bool safe) { safe_ = safe; } void setDistance(const double distance) { distance_ = distance; } - template - size_t findEgoSegmentIndex(const std::vector & points) const - { - const auto & p = planner_data_; - return motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( - points, p->current_odometry->pose, p->ego_nearest_dist_threshold); - } + size_t findEgoSegmentIndex( + const std::vector & points) const; }; class SceneModuleManagerInterface { public: - SceneModuleManagerInterface(rclcpp::Node & node, [[maybe_unused]] const char * module_name) - : node_(node), clock_(node.get_clock()), logger_(node.get_logger()) - { - const auto ns = std::string("~/debug/") + module_name; - pub_debug_ = node.create_publisher(ns, 1); - if (!node.has_parameter("is_publish_debug_path")) { - is_publish_debug_path_ = node.declare_parameter("is_publish_debug_path"); - } else { - is_publish_debug_path_ = node.get_parameter("is_publish_debug_path").as_bool(); - } - if (is_publish_debug_path_) { - pub_debug_path_ = node.create_publisher( - std::string("~/debug/path_with_lane_id/") + module_name, 1); - } - pub_virtual_wall_ = node.create_publisher( - std::string("~/virtual_wall/") + module_name, 5); - pub_velocity_factor_ = node.create_publisher( - std::string("/planning/velocity_factors/") + module_name, 1); - pub_stop_reason_ = - node.create_publisher("~/output/stop_reasons", 1); - pub_infrastructure_commands_ = - node.create_publisher( - "~/output/infrastructure_commands", 1); - - processing_time_publisher_ = std::make_shared(&node, "~/debug"); - } + SceneModuleManagerInterface(rclcpp::Node & node, [[maybe_unused]] const char * module_name); virtual ~SceneModuleManagerInterface() = default; @@ -172,13 +126,7 @@ class SceneModuleManagerInterface void updateSceneModuleInstances( const std::shared_ptr & planner_data, - const autoware_auto_planning_msgs::msg::PathWithLaneId & path) - { - planner_data_ = planner_data; - - launchNewModules(path); - deleteExpiredModules(path); - } + const autoware_auto_planning_msgs::msg::PathWithLaneId & path); virtual void plan(autoware_auto_planning_msgs::msg::PathWithLaneId * path) { @@ -186,120 +134,26 @@ class SceneModuleManagerInterface } protected: - virtual void modifyPathVelocity(autoware_auto_planning_msgs::msg::PathWithLaneId * path) - { - StopWatch stop_watch; - stop_watch.tic("Total"); - visualization_msgs::msg::MarkerArray debug_marker_array; - tier4_planning_msgs::msg::StopReasonArray stop_reason_array; - autoware_adapi_v1_msgs::msg::VelocityFactorArray velocity_factor_array; - stop_reason_array.header.frame_id = "map"; - stop_reason_array.header.stamp = clock_->now(); - velocity_factor_array.header.frame_id = "map"; - velocity_factor_array.header.stamp = clock_->now(); - - tier4_v2x_msgs::msg::InfrastructureCommandArray infrastructure_command_array; - infrastructure_command_array.stamp = clock_->now(); - - first_stop_path_point_index_ = static_cast(path->points.size()) - 1; - for (const auto & scene_module : scene_modules_) { - tier4_planning_msgs::msg::StopReason stop_reason; - scene_module->resetVelocityFactor(); - scene_module->setPlannerData(planner_data_); - scene_module->modifyPathVelocity(path, &stop_reason); - - // The velocity factor must be called after modifyPathVelocity. - const auto velocity_factor = scene_module->getVelocityFactor(); - if (velocity_factor.type != VelocityFactor::UNKNOWN) { - velocity_factor_array.factors.emplace_back(velocity_factor); - } - if (stop_reason.reason != "") { - stop_reason_array.stop_reasons.emplace_back(stop_reason); - } - - if (const auto command = scene_module->getInfrastructureCommand()) { - infrastructure_command_array.commands.push_back(*command); - } - - if (scene_module->getFirstStopPathPointIndex() < first_stop_path_point_index_) { - first_stop_path_point_index_ = scene_module->getFirstStopPathPointIndex(); - } - - for (const auto & marker : scene_module->createDebugMarkerArray().markers) { - debug_marker_array.markers.push_back(marker); - } - - virtual_wall_marker_creator_.add_virtual_walls(scene_module->createVirtualWalls()); - } - - if (!stop_reason_array.stop_reasons.empty()) { - pub_stop_reason_->publish(stop_reason_array); - } - pub_velocity_factor_->publish(velocity_factor_array); - pub_infrastructure_commands_->publish(infrastructure_command_array); - pub_debug_->publish(debug_marker_array); - if (is_publish_debug_path_) { - autoware_auto_planning_msgs::msg::PathWithLaneId debug_path; - debug_path.header = path->header; - debug_path.points = path->points; - pub_debug_path_->publish(debug_path); - } - pub_virtual_wall_->publish(virtual_wall_marker_creator_.create_markers(clock_->now())); - processing_time_publisher_->publish( - std::string(getModuleName()) + "/processing_time_ms", stop_watch.toc("Total")); - } + virtual void modifyPathVelocity(autoware_auto_planning_msgs::msg::PathWithLaneId * path); virtual void launchNewModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) = 0; virtual std::function &)> getModuleExpiredFunction(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) = 0; - virtual void deleteExpiredModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) - { - const auto isModuleExpired = getModuleExpiredFunction(path); - - // Copy container to avoid iterator corruption - // due to scene_modules_.erase() in unregisterModule() - const auto copied_scene_modules = scene_modules_; - - for (const auto & scene_module : copied_scene_modules) { - if (isModuleExpired(scene_module)) { - unregisterModule(scene_module); - } - } - } + virtual void deleteExpiredModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path); bool isModuleRegistered(const int64_t module_id) { return registered_module_id_set_.count(module_id) != 0; } - void registerModule(const std::shared_ptr & scene_module) - { - RCLCPP_INFO( - logger_, "register task: module = %s, id = %lu", getModuleName(), - scene_module->getModuleId()); - registered_module_id_set_.emplace(scene_module->getModuleId()); - scene_modules_.insert(scene_module); - } + void registerModule(const std::shared_ptr & scene_module); - void unregisterModule(const std::shared_ptr & scene_module) - { - RCLCPP_INFO( - logger_, "unregister task: module = %s, id = %lu", getModuleName(), - scene_module->getModuleId()); - registered_module_id_set_.erase(scene_module->getModuleId()); - scene_modules_.erase(scene_module); - } + void unregisterModule(const std::shared_ptr & scene_module); - template - size_t findEgoSegmentIndex(const std::vector & points) const - { - const auto & p = planner_data_; - return motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( - points, p->current_odometry->pose, p->ego_nearest_dist_threshold, - p->ego_nearest_yaw_threshold); - } + size_t findEgoSegmentIndex( + const std::vector & points) const; std::set> scene_modules_; std::set registered_module_id_set_; @@ -329,38 +183,17 @@ class SceneModuleManagerInterfaceWithRTC : public SceneModuleManagerInterface { public: SceneModuleManagerInterfaceWithRTC( - rclcpp::Node & node, const char * module_name, const bool enable_rtc = true) - : SceneModuleManagerInterface(node, module_name), rtc_interface_(&node, module_name, enable_rtc) - { - } + rclcpp::Node & node, const char * module_name, const bool enable_rtc = true); - void plan(autoware_auto_planning_msgs::msg::PathWithLaneId * path) override - { - setActivation(); - modifyPathVelocity(path); - sendRTC(path->header.stamp); - } + void plan(autoware_auto_planning_msgs::msg::PathWithLaneId * path) override; protected: RTCInterface rtc_interface_; std::unordered_map map_uuid_; - virtual void sendRTC(const Time & stamp) - { - for (const auto & scene_module : scene_modules_) { - const UUID uuid = getUUID(scene_module->getModuleId()); - updateRTCStatus(uuid, scene_module->isSafe(), scene_module->getDistance(), stamp); - } - publishRTCStatus(stamp); - } + virtual void sendRTC(const Time & stamp); - virtual void setActivation() - { - for (const auto & scene_module : scene_modules_) { - const UUID uuid = getUUID(scene_module->getModuleId()); - scene_module->setActivation(rtc_interface_.isActivated(uuid)); - } - } + virtual void setActivation(); void updateRTCStatus( const UUID & uuid, const bool safe, const double distance, const Time & stamp) @@ -372,45 +205,13 @@ class SceneModuleManagerInterfaceWithRTC : public SceneModuleManagerInterface void publishRTCStatus(const Time & stamp) { rtc_interface_.publishCooperateStatus(stamp); } - UUID getUUID(const int64_t & module_id) const - { - if (map_uuid_.count(module_id) == 0) { - const UUID uuid; - return uuid; - } - return map_uuid_.at(module_id); - } + UUID getUUID(const int64_t & module_id) const; - void generateUUID(const int64_t & module_id) - { - map_uuid_.insert({module_id, tier4_autoware_utils::generateUUID()}); - } + void generateUUID(const int64_t & module_id); - void removeUUID(const int64_t & module_id) - { - const auto result = map_uuid_.erase(module_id); - if (result == 0) { - RCLCPP_WARN_STREAM( - logger_, "[removeUUID] module_id = " << module_id << " is not registered."); - } - } + void removeUUID(const int64_t & module_id); - void deleteExpiredModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) override - { - const auto isModuleExpired = getModuleExpiredFunction(path); - - // Copy container to avoid iterator corruption - // due to scene_modules_.erase() in unregisterModule() - const auto copied_scene_modules = scene_modules_; - - for (const auto & scene_module : copied_scene_modules) { - if (isModuleExpired(scene_module)) { - removeRTCStatus(getUUID(scene_module->getModuleId())); - removeUUID(scene_module->getModuleId()); - unregisterModule(scene_module); - } - } - } + void deleteExpiredModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) override; }; } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/arc_lane_util.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/arc_lane_util.hpp index ffc216e863500..63d7c08b945fd 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/arc_lane_util.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/arc_lane_util.hpp @@ -16,22 +16,12 @@ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__ARC_LANE_UTIL_HPP_ #include -#include #include #include #include -#ifdef ROS_DISTRO_GALACTIC -#include -#include -#else -#include - -#include -#endif - #include #include #include @@ -46,7 +36,7 @@ namespace bg = boost::geometry; namespace { -geometry_msgs::msg::Point convertToGeomPoint(const Point2d & p) +geometry_msgs::msg::Point convertToGeomPoint(const tier4_autoware_utils::Point2d & p) { geometry_msgs::msg::Point geom_p; geom_p.x = p.x(); @@ -55,73 +45,23 @@ geometry_msgs::msg::Point convertToGeomPoint(const Point2d & p) return geom_p; } -geometry_msgs::msg::Point operator+( - const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2) -{ - geometry_msgs::msg::Point p; - p.x = p1.x + p2.x; - p.y = p1.y + p2.y; - p.z = p1.z + p2.z; - - return p; -} - -[[maybe_unused]] geometry_msgs::msg::Point operator*( - const geometry_msgs::msg::Point & p, const double v) -{ - geometry_msgs::msg::Point multiplied_p; - multiplied_p.x = p.x * v; - multiplied_p.y = p.y * v; - multiplied_p.z = p.z * v; - - return multiplied_p; -} - -[[maybe_unused]] geometry_msgs::msg::Point operator*( - const double v, const geometry_msgs::msg::Point & p) -{ - return p * v; -} } // namespace namespace arc_lane_utils { -using PathIndexWithPose = std::pair; // front index, pose -using PathIndexWithPoint2d = std::pair; // front index, point2d +using PathIndexWithPose = std::pair; // front index, pose +using PathIndexWithPoint2d = + std::pair; // front index, point2d using PathIndexWithPoint = std::pair; // front index, point2d using PathIndexWithOffset = std::pair; // front index, offset -inline double calcSignedDistance( - const geometry_msgs::msg::Pose & p1, const geometry_msgs::msg::Point & p2) -{ - Eigen::Affine3d map2p1; - tf2::fromMsg(p1, map2p1); - const auto basecoords_p2 = map2p1.inverse() * Eigen::Vector3d(p2.x, p2.y, p2.z); - return basecoords_p2.x() >= 0 ? basecoords_p2.norm() : -basecoords_p2.norm(); -} +double calcSignedDistance( + const geometry_msgs::msg::Pose & p1, const geometry_msgs::msg::Point & p2); // calculate one collision point between the line (from p1 to p2) and the line (from p3 to p4) -inline boost::optional checkCollision( +boost::optional checkCollision( const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2, - const geometry_msgs::msg::Point & p3, const geometry_msgs::msg::Point & p4) -{ - const double det = (p2.x - p1.x) * (p4.y - p3.y) - (p2.y - p1.y) * (p4.x - p3.x); - - if (det == 0.0) { - // collision is not one point. - return boost::none; - } - - const double t1 = ((p4.y - p3.y) * (p4.x - p1.x) - (p4.x - p3.x) * (p4.y - p1.y)) / det; - const double t2 = ((p2.x - p1.x) * (p4.y - p1.y) - (p2.y - p1.y) * (p4.x - p1.x)) / det; - - // check collision is outside the segment line - if (t1 < 0.0 || 1.0 < t1 || t2 < 0.0 || 1.0 < t2) { - return boost::none; - } - - return p1 * (1.0 - t1) + p2 * t1; -} + const geometry_msgs::msg::Point & p3, const geometry_msgs::msg::Point & p4); template boost::optional findCollisionSegment( @@ -223,16 +163,9 @@ boost::optional findOffsetSegment( tier4_autoware_utils::calcDistance2d(path.points.at(collision_idx + 1), collision_point)); } -inline boost::optional findOffsetSegment( +boost::optional findOffsetSegment( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t index, - const double offset) -{ - if (offset >= 0) { - return findForwardOffsetSegment(path, index, offset); - } - - return findBackwardOffsetSegment(path, index, -offset); -} + const double offset); template geometry_msgs::msg::Pose calcTargetPose(const T & path, const PathIndexWithOffset & offset_segment) @@ -263,33 +196,10 @@ geometry_msgs::msg::Pose calcTargetPose(const T & path, const PathIndexWithOffse return target_pose; } -inline boost::optional createTargetPoint( +boost::optional createTargetPoint( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const LineString2d & stop_line, - const size_t lane_id, const double margin, const double vehicle_offset) -{ - // Find collision segment - const auto collision_segment = findCollisionSegment(path, stop_line, lane_id); - if (!collision_segment) { - // No collision - return {}; - } - - // Calculate offset length from stop line - // Use '-' to make the positive direction is forward - const double offset_length = -(margin + vehicle_offset); + const size_t lane_id, const double margin, const double vehicle_offset); - // Find offset segment - const auto offset_segment = findOffsetSegment(path, *collision_segment, offset_length); - if (!offset_segment) { - // No enough path length - return {}; - } - - const auto target_pose = calcTargetPose(path, *offset_segment); - - const auto front_idx = offset_segment->first; - return std::make_pair(front_idx, target_pose); -} } // namespace arc_lane_utils } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/boost_geometry_helper.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/boost_geometry_helper.hpp index 193890ec70ceb..0f3cc7203ef1f 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/boost_geometry_helper.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/boost_geometry_helper.hpp @@ -24,24 +24,11 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include -#include -#include - -#include #include - // cppcheck-suppress unknownMacro BOOST_GEOMETRY_REGISTER_POINT_3D(geometry_msgs::msg::Point, double, cs::cartesian, x, y, z) BOOST_GEOMETRY_REGISTER_POINT_3D( @@ -83,48 +70,13 @@ LineString2d to_bg2d(const std::vector & vec) return ps; } -inline Polygon2d lines2polygon(const LineString2d & left_line, const LineString2d & right_line) -{ - Polygon2d polygon; - - polygon.outer().push_back(left_line.front()); +Polygon2d lines2polygon(const LineString2d & left_line, const LineString2d & right_line); - for (auto itr = right_line.begin(); itr != right_line.end(); ++itr) { - polygon.outer().push_back(*itr); - } - - for (auto itr = left_line.rbegin(); itr != left_line.rend(); ++itr) { - polygon.outer().push_back(*itr); - } +Polygon2d upScalePolygon( + const geometry_msgs::msg::Point & position, const Polygon2d & polygon, const double scale); - bg::correct(polygon); - return polygon; -} +geometry_msgs::msg::Polygon toGeomPoly(const Polygon2d & polygon); -inline Polygon2d upScalePolygon( - const geometry_msgs::msg::Point & position, const Polygon2d & polygon, const double scale) -{ - Polygon2d transformed_polygon; - // upscale - for (size_t i = 0; i < polygon.outer().size(); i++) { - const double upscale_x = (polygon.outer().at(i).x() - position.x) * scale + position.x; - const double upscale_y = (polygon.outer().at(i).y() - position.y) * scale + position.y; - transformed_polygon.outer().emplace_back(Point2d(upscale_x, upscale_y)); - } - return transformed_polygon; -} - -inline geometry_msgs::msg::Polygon toGeomPoly(const Polygon2d & polygon) -{ - geometry_msgs::msg::Polygon polygon_msg; - geometry_msgs::msg::Point32 point_msg; - for (const auto & p : polygon.outer()) { - point_msg.x = p.x(); - point_msg.y = p.y(); - polygon_msg.points.push_back(point_msg); - } - return polygon_msg; -} } // namespace behavior_velocity_planner #endif // BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__BOOST_GEOMETRY_HELPER_HPP_ diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/debug.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/debug.hpp index e840b6f8a5995..98df29d1c44c0 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/debug.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/debug.hpp @@ -15,8 +15,12 @@ #ifndef BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__DEBUG_HPP_ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__DEBUG_HPP_ -#include +#include +#include +#include +#include +#include #include #include diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/path_utilization.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/path_utilization.hpp index 4156f7df9540b..591ef8667b81b 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/path_utilization.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/path_utilization.hpp @@ -16,7 +16,6 @@ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__PATH_UTILIZATION_HPP_ #include -#include #include #include diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/trajectory_utils.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/trajectory_utils.hpp index 7d5278c3cf972..c86272cff1c2e 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/trajectory_utils.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/trajectory_utils.hpp @@ -16,32 +16,18 @@ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__TRAJECTORY_UTILS_HPP_ #include -#include -#include -#include -#include -#include -#include #include #include #include -#include #include -#include -#include - -#include -#include #include -#include #include #include namespace behavior_velocity_planner { -using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using autoware_auto_planning_msgs::msg::PathWithLaneId; using autoware_auto_planning_msgs::msg::Trajectory; using autoware_auto_planning_msgs::msg::TrajectoryPoint; @@ -49,83 +35,15 @@ using TrajectoryPoints = std::vector; using geometry_msgs::msg::Quaternion; using TrajectoryPointWithIdx = std::pair; -inline TrajectoryPoints convertPathToTrajectoryPoints(const PathWithLaneId & path) -{ - TrajectoryPoints tps; - for (const auto & p : path.points) { - TrajectoryPoint tp; - tp.pose = p.point.pose; - tp.longitudinal_velocity_mps = p.point.longitudinal_velocity_mps; - // since path point doesn't have acc for now - tp.acceleration_mps2 = 0; - tps.emplace_back(tp); - } - return tps; -} -inline PathWithLaneId convertTrajectoryPointsToPath(const TrajectoryPoints & trajectory) -{ - PathWithLaneId path; - for (const auto & p : trajectory) { - PathPointWithLaneId pp; - pp.point.pose = p.pose; - pp.point.longitudinal_velocity_mps = p.longitudinal_velocity_mps; - path.points.emplace_back(pp); - } - return path; -} - -inline Quaternion lerpOrientation( - const Quaternion & o_from, const Quaternion & o_to, const double ratio) -{ - tf2::Quaternion q_from, q_to; - tf2::fromMsg(o_from, q_from); - tf2::fromMsg(o_to, q_to); +TrajectoryPoints convertPathToTrajectoryPoints(const PathWithLaneId & path); +PathWithLaneId convertTrajectoryPointsToPath(const TrajectoryPoints & trajectory); - const auto q_interpolated = q_from.slerp(q_to, ratio); - return tf2::toMsg(q_interpolated); -} +Quaternion lerpOrientation(const Quaternion & o_from, const Quaternion & o_to, const double ratio); //! smooth path point with lane id starts from ego position on path to the path end -inline bool smoothPath( +bool smoothPath( const PathWithLaneId & in_path, PathWithLaneId & out_path, - const std::shared_ptr & planner_data) -{ - const geometry_msgs::msg::Pose current_pose = planner_data->current_odometry->pose; - const double v0 = planner_data->current_velocity->twist.linear.x; - const double a0 = planner_data->current_acceleration->accel.accel.linear.x; - const auto & external_v_limit = planner_data->external_velocity_limit; - const auto & smoother = planner_data->velocity_smoother_; - - auto trajectory = convertPathToTrajectoryPoints(in_path); - if (external_v_limit) { - motion_velocity_smoother::trajectory_utils::applyMaximumVelocityLimit( - 0, trajectory.size(), external_v_limit->max_velocity, trajectory); - } - const auto traj_lateral_acc_filtered = smoother->applyLateralAccelerationFilter(trajectory); - - // Resample trajectory with ego-velocity based interval distances - auto traj_resampled = smoother->resampleTrajectory( - traj_lateral_acc_filtered, v0, current_pose, planner_data->ego_nearest_dist_threshold, - planner_data->ego_nearest_yaw_threshold); - const size_t traj_resampled_closest = motion_utils::findFirstNearestIndexWithSoftConstraints( - traj_resampled, current_pose, planner_data->ego_nearest_dist_threshold, - planner_data->ego_nearest_yaw_threshold); - std::vector debug_trajectories; - // Clip trajectory from closest point - TrajectoryPoints clipped; - TrajectoryPoints traj_smoothed; - clipped.insert( - clipped.end(), traj_resampled.begin() + traj_resampled_closest, traj_resampled.end()); - if (!smoother->apply(v0, a0, clipped, traj_smoothed, debug_trajectories)) { - std::cerr << "[behavior_velocity][trajectory_utils]: failed to smooth" << std::endl; - return false; - } - traj_smoothed.insert( - traj_smoothed.begin(), traj_resampled.begin(), traj_resampled.begin() + traj_resampled_closest); - - out_path = convertTrajectoryPointsToPath(traj_smoothed); - return true; -} + const std::shared_ptr & planner_data); } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp index 46798b7f9f15b..ceba86c27f864 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/utilization/util.hpp @@ -15,27 +15,17 @@ #ifndef BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__UTIL_HPP_ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__UTILIZATION__UTIL_HPP_ -#include -#include -#include +#include -#include #include -#include -#include #include #include #include -#include -#include #include -#include #include #include #include -#include -#include #include #include @@ -48,11 +38,6 @@ namespace behavior_velocity_planner { -struct SearchRangeIndex -{ - size_t min_idx; - size_t max_idx; -}; struct DetectionRange { bool use_right = true; @@ -65,11 +50,6 @@ struct DetectionRange double right_overhang; double left_overhang; }; -struct PointWithSearchRangeIndex -{ - geometry_msgs::msg::Point point; - SearchRangeIndex index; -}; struct TrafficSignalStamped { @@ -83,72 +63,24 @@ using LineString2d = tier4_autoware_utils::LineString2d; using Polygon2d = tier4_autoware_utils::Polygon2d; using BasicPolygons2d = std::vector; using Polygons2d = std::vector; -using autoware_auto_perception_msgs::msg::PredictedObject; using autoware_auto_perception_msgs::msg::PredictedObjects; -using autoware_auto_perception_msgs::msg::Shape; -using autoware_auto_planning_msgs::msg::Path; -using autoware_auto_planning_msgs::msg::PathPoint; using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using autoware_auto_planning_msgs::msg::PathWithLaneId; -using autoware_auto_planning_msgs::msg::Trajectory; -using motion_utils::calcLongitudinalOffsetToSegment; -using motion_utils::calcSignedArcLength; -using motion_utils::validateNonEmpty; -using tier4_autoware_utils::calcAzimuthAngle; -using tier4_autoware_utils::calcDistance2d; -using tier4_autoware_utils::calcOffsetPose; -using tier4_autoware_utils::calcSquaredDistance2d; -using tier4_autoware_utils::createQuaternionFromYaw; -using tier4_autoware_utils::getPoint; using tier4_planning_msgs::msg::StopFactor; using tier4_planning_msgs::msg::StopReason; namespace planning_utils { -template -size_t calcPointIndexFromSegmentIndex( - const std::vector & points, const geometry_msgs::msg::Point & point, const size_t seg_idx) -{ - const size_t prev_point_idx = seg_idx; - const size_t next_point_idx = seg_idx + 1; - - const double prev_dist = tier4_autoware_utils::calcDistance2d(point, points.at(prev_point_idx)); - const double next_dist = tier4_autoware_utils::calcDistance2d(point, points.at(next_point_idx)); - - if (prev_dist < next_dist) { - return prev_point_idx; - } - return next_point_idx; -} - -template size_t calcSegmentIndexFromPointIndex( - const std::vector & points, const geometry_msgs::msg::Point & point, const size_t idx) -{ - if (idx == 0) { - return 0; - } - if (idx == points.size() - 1) { - return idx - 1; - } - if (points.size() < 3) { - return 0; - } - - const double offset_to_seg = motion_utils::calcLongitudinalOffsetToSegment(points, idx, point); - if (0 < offset_to_seg) { - return idx; - } - return idx - 1; -} - + const std::vector & points, + const geometry_msgs::msg::Point & point, const size_t idx); // create detection area from given range return false if creation failure bool createDetectionAreaPolygons( Polygons2d & da_polys, const PathWithLaneId & path, const geometry_msgs::msg::Pose & target_pose, const size_t target_seg_idx, const DetectionRange & da_range, const double obstacle_vel_mps, const double min_velocity = 1.0); -PathPoint getLerpPathPointWithLaneId(const PathPoint p0, const PathPoint p1, const double ratio); -Point2d calculateOffsetPoint2d(const Pose & pose, const double offset_x, const double offset_y); +Point2d calculateOffsetPoint2d( + const geometry_msgs::msg::Pose & pose, const double offset_x, const double offset_y); void extractClosePartition( const geometry_msgs::msg::Point position, const BasicPolygons2d & all_partitions, BasicPolygons2d & close_partition, const double distance_thresh = 30.0); @@ -162,12 +94,6 @@ inline int64_t bitShift(int64_t original_id) return original_id << (sizeof(int32_t) * 8 / 2); } -geometry_msgs::msg::Pose transformRelCoordinate2D( - const geometry_msgs::msg::Pose & target, const geometry_msgs::msg::Pose & origin); -geometry_msgs::msg::Pose transformAbsCoordinate2D( - const geometry_msgs::msg::Pose & relative, const geometry_msgs::msg::Pose & origin); -SearchRangeIndex getPathIndexRangeIncludeLaneId(const PathWithLaneId & path, const int64_t lane_id); - bool isAheadOf(const geometry_msgs::msg::Pose & target, const geometry_msgs::msg::Pose & origin); geometry_msgs::msg::Pose getAheadPose( const size_t start_idx, const double ahead_dist, diff --git a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/velocity_factor_interface.hpp b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/velocity_factor_interface.hpp index 28ac40f0868a0..5804c0cdafcf4 100644 --- a/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/velocity_factor_interface.hpp +++ b/planning/behavior_velocity_planner_common/include/behavior_velocity_planner_common/velocity_factor_interface.hpp @@ -16,13 +16,11 @@ #ifndef BEHAVIOR_VELOCITY_PLANNER_COMMON__VELOCITY_FACTOR_INTERFACE_HPP_ #define BEHAVIOR_VELOCITY_PLANNER_COMMON__VELOCITY_FACTOR_INTERFACE_HPP_ -#include - #include #include +#include #include -#include #include #include @@ -44,19 +42,10 @@ class VelocityFactorInterface void init(const VelocityFactorType type) { type_ = type; } void reset() { velocity_factor_.type = VelocityFactor::UNKNOWN; } - template void set( - const T & points, const Pose & curr_pose, const Pose & stop_pose, - const VelocityFactorStatus status, const std::string detail = "") - { - const auto & curr_point = curr_pose.position; - const auto & stop_point = stop_pose.position; - velocity_factor_.type = type_; - velocity_factor_.pose = stop_pose; - velocity_factor_.distance = motion_utils::calcSignedArcLength(points, curr_point, stop_point); - velocity_factor_.status = status; - velocity_factor_.detail = detail; - } + const std::vector & points, + const Pose & curr_pose, const Pose & stop_pose, const VelocityFactorStatus status, + const std::string detail = ""); private: VelocityFactorType type_; diff --git a/planning/behavior_velocity_planner_common/src/scene_module_interface.cpp b/planning/behavior_velocity_planner_common/src/scene_module_interface.cpp new file mode 100644 index 0000000000000..6ba982294ccfd --- /dev/null +++ b/planning/behavior_velocity_planner_common/src/scene_module_interface.cpp @@ -0,0 +1,264 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include +#include + +namespace behavior_velocity_planner +{ + +using tier4_autoware_utils::StopWatch; + +SceneModuleInterface::SceneModuleInterface( + const int64_t module_id, rclcpp::Logger logger, rclcpp::Clock::SharedPtr clock) +: module_id_(module_id), + activated_(false), + safe_(false), + distance_(std::numeric_limits::lowest()), + logger_(logger), + clock_(clock) +{ +} + +size_t SceneModuleInterface::findEgoSegmentIndex( + const std::vector & points) const +{ + const auto & p = planner_data_; + return motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( + points, p->current_odometry->pose, p->ego_nearest_dist_threshold); +} + +SceneModuleManagerInterface::SceneModuleManagerInterface( + rclcpp::Node & node, [[maybe_unused]] const char * module_name) +: node_(node), clock_(node.get_clock()), logger_(node.get_logger()) +{ + const auto ns = std::string("~/debug/") + module_name; + pub_debug_ = node.create_publisher(ns, 1); + if (!node.has_parameter("is_publish_debug_path")) { + is_publish_debug_path_ = node.declare_parameter("is_publish_debug_path"); + } else { + is_publish_debug_path_ = node.get_parameter("is_publish_debug_path").as_bool(); + } + if (is_publish_debug_path_) { + pub_debug_path_ = node.create_publisher( + std::string("~/debug/path_with_lane_id/") + module_name, 1); + } + pub_virtual_wall_ = node.create_publisher( + std::string("~/virtual_wall/") + module_name, 5); + pub_velocity_factor_ = node.create_publisher( + std::string("/planning/velocity_factors/") + module_name, 1); + pub_stop_reason_ = + node.create_publisher("~/output/stop_reasons", 1); + pub_infrastructure_commands_ = + node.create_publisher( + "~/output/infrastructure_commands", 1); + + processing_time_publisher_ = std::make_shared(&node, "~/debug"); +} + +size_t SceneModuleManagerInterface::findEgoSegmentIndex( + const std::vector & points) const +{ + const auto & p = planner_data_; + return motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( + points, p->current_odometry->pose, p->ego_nearest_dist_threshold, p->ego_nearest_yaw_threshold); +} + +void SceneModuleManagerInterface::updateSceneModuleInstances( + const std::shared_ptr & planner_data, + const autoware_auto_planning_msgs::msg::PathWithLaneId & path) +{ + planner_data_ = planner_data; + + launchNewModules(path); + deleteExpiredModules(path); +} + +void SceneModuleManagerInterface::modifyPathVelocity( + autoware_auto_planning_msgs::msg::PathWithLaneId * path) +{ + StopWatch stop_watch; + stop_watch.tic("Total"); + visualization_msgs::msg::MarkerArray debug_marker_array; + tier4_planning_msgs::msg::StopReasonArray stop_reason_array; + autoware_adapi_v1_msgs::msg::VelocityFactorArray velocity_factor_array; + stop_reason_array.header.frame_id = "map"; + stop_reason_array.header.stamp = clock_->now(); + velocity_factor_array.header.frame_id = "map"; + velocity_factor_array.header.stamp = clock_->now(); + + tier4_v2x_msgs::msg::InfrastructureCommandArray infrastructure_command_array; + infrastructure_command_array.stamp = clock_->now(); + + first_stop_path_point_index_ = static_cast(path->points.size()) - 1; + for (const auto & scene_module : scene_modules_) { + tier4_planning_msgs::msg::StopReason stop_reason; + scene_module->resetVelocityFactor(); + scene_module->setPlannerData(planner_data_); + scene_module->modifyPathVelocity(path, &stop_reason); + + // The velocity factor must be called after modifyPathVelocity. + const auto velocity_factor = scene_module->getVelocityFactor(); + if (velocity_factor.type != VelocityFactor::UNKNOWN) { + velocity_factor_array.factors.emplace_back(velocity_factor); + } + if (stop_reason.reason != "") { + stop_reason_array.stop_reasons.emplace_back(stop_reason); + } + + if (const auto command = scene_module->getInfrastructureCommand()) { + infrastructure_command_array.commands.push_back(*command); + } + + if (scene_module->getFirstStopPathPointIndex() < first_stop_path_point_index_) { + first_stop_path_point_index_ = scene_module->getFirstStopPathPointIndex(); + } + + for (const auto & marker : scene_module->createDebugMarkerArray().markers) { + debug_marker_array.markers.push_back(marker); + } + + virtual_wall_marker_creator_.add_virtual_walls(scene_module->createVirtualWalls()); + } + + if (!stop_reason_array.stop_reasons.empty()) { + pub_stop_reason_->publish(stop_reason_array); + } + pub_velocity_factor_->publish(velocity_factor_array); + pub_infrastructure_commands_->publish(infrastructure_command_array); + pub_debug_->publish(debug_marker_array); + if (is_publish_debug_path_) { + autoware_auto_planning_msgs::msg::PathWithLaneId debug_path; + debug_path.header = path->header; + debug_path.points = path->points; + pub_debug_path_->publish(debug_path); + } + pub_virtual_wall_->publish(virtual_wall_marker_creator_.create_markers(clock_->now())); + processing_time_publisher_->publish( + std::string(getModuleName()) + "/processing_time_ms", stop_watch.toc("Total")); +} + +void SceneModuleManagerInterface::deleteExpiredModules( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path) +{ + const auto isModuleExpired = getModuleExpiredFunction(path); + + // Copy container to avoid iterator corruption + // due to scene_modules_.erase() in unregisterModule() + const auto copied_scene_modules = scene_modules_; + + for (const auto & scene_module : copied_scene_modules) { + if (isModuleExpired(scene_module)) { + unregisterModule(scene_module); + } + } +} + +void SceneModuleManagerInterface::registerModule( + const std::shared_ptr & scene_module) +{ + RCLCPP_INFO( + logger_, "register task: module = %s, id = %lu", getModuleName(), scene_module->getModuleId()); + registered_module_id_set_.emplace(scene_module->getModuleId()); + scene_modules_.insert(scene_module); +} + +void SceneModuleManagerInterface::unregisterModule( + const std::shared_ptr & scene_module) +{ + RCLCPP_INFO( + logger_, "unregister task: module = %s, id = %lu", getModuleName(), + scene_module->getModuleId()); + registered_module_id_set_.erase(scene_module->getModuleId()); + scene_modules_.erase(scene_module); +} + +SceneModuleManagerInterfaceWithRTC::SceneModuleManagerInterfaceWithRTC( + rclcpp::Node & node, const char * module_name, const bool enable_rtc) +: SceneModuleManagerInterface(node, module_name), rtc_interface_(&node, module_name, enable_rtc) +{ +} + +void SceneModuleManagerInterfaceWithRTC::plan( + autoware_auto_planning_msgs::msg::PathWithLaneId * path) +{ + setActivation(); + modifyPathVelocity(path); + sendRTC(path->header.stamp); +} + +void SceneModuleManagerInterfaceWithRTC::sendRTC(const Time & stamp) +{ + for (const auto & scene_module : scene_modules_) { + const UUID uuid = getUUID(scene_module->getModuleId()); + updateRTCStatus(uuid, scene_module->isSafe(), scene_module->getDistance(), stamp); + } + publishRTCStatus(stamp); +} + +void SceneModuleManagerInterfaceWithRTC::setActivation() +{ + for (const auto & scene_module : scene_modules_) { + const UUID uuid = getUUID(scene_module->getModuleId()); + scene_module->setActivation(rtc_interface_.isActivated(uuid)); + } +} + +UUID SceneModuleManagerInterfaceWithRTC::getUUID(const int64_t & module_id) const +{ + if (map_uuid_.count(module_id) == 0) { + const UUID uuid; + return uuid; + } + return map_uuid_.at(module_id); +} + +void SceneModuleManagerInterfaceWithRTC::generateUUID(const int64_t & module_id) +{ + map_uuid_.insert({module_id, tier4_autoware_utils::generateUUID()}); +} + +void SceneModuleManagerInterfaceWithRTC::removeUUID(const int64_t & module_id) +{ + const auto result = map_uuid_.erase(module_id); + if (result == 0) { + RCLCPP_WARN_STREAM(logger_, "[removeUUID] module_id = " << module_id << " is not registered."); + } +} + +void SceneModuleManagerInterfaceWithRTC::deleteExpiredModules( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path) +{ + const auto isModuleExpired = getModuleExpiredFunction(path); + + // Copy container to avoid iterator corruption + // due to scene_modules_.erase() in unregisterModule() + const auto copied_scene_modules = scene_modules_; + + for (const auto & scene_module : copied_scene_modules) { + if (isModuleExpired(scene_module)) { + removeRTCStatus(getUUID(scene_module->getModuleId())); + removeUUID(scene_module->getModuleId()); + unregisterModule(scene_module); + } + } +} + +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_planner_common/src/utilization/arc_lane_util.cpp b/planning/behavior_velocity_planner_common/src/utilization/arc_lane_util.cpp new file mode 100644 index 0000000000000..05f634a81c5b7 --- /dev/null +++ b/planning/behavior_velocity_planner_common/src/utilization/arc_lane_util.cpp @@ -0,0 +1,138 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#ifdef ROS_DISTRO_GALACTIC +#include +#include +#else +#include + +#include +#endif + +#include +#include +#include +#include + +namespace +{ +geometry_msgs::msg::Point operator+( + const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2) +{ + geometry_msgs::msg::Point p; + p.x = p1.x + p2.x; + p.y = p1.y + p2.y; + p.z = p1.z + p2.z; + + return p; +} + +geometry_msgs::msg::Point operator*(const geometry_msgs::msg::Point & p, const double v) +{ + geometry_msgs::msg::Point multiplied_p; + multiplied_p.x = p.x * v; + multiplied_p.y = p.y * v; + multiplied_p.z = p.z * v; + + return multiplied_p; +} + +/* +geometry_msgs::msg::Point operator*(const double v, const geometry_msgs::msg::Point & p) +{ +return p * v; +} +*/ +} // namespace + +namespace behavior_velocity_planner::arc_lane_utils +{ + +double calcSignedDistance(const geometry_msgs::msg::Pose & p1, const geometry_msgs::msg::Point & p2) +{ + Eigen::Affine3d map2p1; + tf2::fromMsg(p1, map2p1); + const auto basecoords_p2 = map2p1.inverse() * Eigen::Vector3d(p2.x, p2.y, p2.z); + return basecoords_p2.x() >= 0 ? basecoords_p2.norm() : -basecoords_p2.norm(); +} + +// calculate one collision point between the line (from p1 to p2) and the line (from p3 to p4) + +boost::optional checkCollision( + const geometry_msgs::msg::Point & p1, const geometry_msgs::msg::Point & p2, + const geometry_msgs::msg::Point & p3, const geometry_msgs::msg::Point & p4) +{ + const double det = (p2.x - p1.x) * (p4.y - p3.y) - (p2.y - p1.y) * (p4.x - p3.x); + + if (det == 0.0) { + // collision is not one point. + return boost::none; + } + + const double t1 = ((p4.y - p3.y) * (p4.x - p1.x) - (p4.x - p3.x) * (p4.y - p1.y)) / det; + const double t2 = ((p2.x - p1.x) * (p4.y - p1.y) - (p2.y - p1.y) * (p4.x - p1.x)) / det; + + // check collision is outside the segment line + if (t1 < 0.0 || 1.0 < t1 || t2 < 0.0 || 1.0 < t2) { + return boost::none; + } + + return p1 * (1.0 - t1) + p2 * t1; +} + +boost::optional findOffsetSegment( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const size_t index, + const double offset) +{ + if (offset >= 0) { + return findForwardOffsetSegment(path, index, offset); + } + + return findBackwardOffsetSegment(path, index, -offset); +} + +boost::optional createTargetPoint( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const LineString2d & stop_line, + const size_t lane_id, const double margin, const double vehicle_offset) +{ + // Find collision segment + const auto collision_segment = findCollisionSegment(path, stop_line, lane_id); + if (!collision_segment) { + // No collision + return {}; + } + + // Calculate offset length from stop line + // Use '-' to make the positive direction is forward + const double offset_length = -(margin + vehicle_offset); + + // Find offset segment + const auto offset_segment = findOffsetSegment(path, *collision_segment, offset_length); + if (!offset_segment) { + // No enough path length + return {}; + } + + const auto target_pose = calcTargetPose(path, *offset_segment); + + const auto front_idx = offset_segment->first; + return std::make_pair(front_idx, target_pose); +} +} // namespace behavior_velocity_planner::arc_lane_utils diff --git a/planning/behavior_velocity_planner_common/src/utilization/boost_geometry_helper.cpp b/planning/behavior_velocity_planner_common/src/utilization/boost_geometry_helper.cpp new file mode 100644 index 0000000000000..903cf5aab80e8 --- /dev/null +++ b/planning/behavior_velocity_planner_common/src/utilization/boost_geometry_helper.cpp @@ -0,0 +1,66 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include +#include +namespace behavior_velocity_planner +{ + +Polygon2d lines2polygon(const LineString2d & left_line, const LineString2d & right_line) +{ + Polygon2d polygon; + + polygon.outer().push_back(left_line.front()); + + for (auto itr = right_line.begin(); itr != right_line.end(); ++itr) { + polygon.outer().push_back(*itr); + } + + for (auto itr = left_line.rbegin(); itr != left_line.rend(); ++itr) { + polygon.outer().push_back(*itr); + } + + bg::correct(polygon); + return polygon; +} + +Polygon2d upScalePolygon( + const geometry_msgs::msg::Point & position, const Polygon2d & polygon, const double scale) +{ + Polygon2d transformed_polygon; + // upscale + for (size_t i = 0; i < polygon.outer().size(); i++) { + const double upscale_x = (polygon.outer().at(i).x() - position.x) * scale + position.x; + const double upscale_y = (polygon.outer().at(i).y() - position.y) * scale + position.y; + transformed_polygon.outer().emplace_back(Point2d(upscale_x, upscale_y)); + } + return transformed_polygon; +} + +geometry_msgs::msg::Polygon toGeomPoly(const Polygon2d & polygon) +{ + geometry_msgs::msg::Polygon polygon_msg; + geometry_msgs::msg::Point32 point_msg; + for (const auto & p : polygon.outer()) { + point_msg.x = p.x(); + point_msg.y = p.y(); + polygon_msg.points.push_back(point_msg); + } + return polygon_msg; +} +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_planner_common/src/utilization/debug.cpp b/planning/behavior_velocity_planner_common/src/utilization/debug.cpp index 845b93576bec8..6ff2ef53176c4 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/debug.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/debug.cpp @@ -13,8 +13,8 @@ // limitations under the License. #include +#include #include - namespace behavior_velocity_planner { namespace debug diff --git a/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp b/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp index 74bd2f005dd89..59c9e4861fbfd 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/path_utilization.cpp @@ -13,21 +13,10 @@ // limitations under the License. #include -#include -#include -#include #include #include #include -#include - -#ifdef ROS_DISTRO_GALACTIC -#include -#else -#include -#endif - #include #include #include diff --git a/planning/behavior_velocity_planner_common/src/utilization/trajectory_utils.cpp b/planning/behavior_velocity_planner_common/src/utilization/trajectory_utils.cpp new file mode 100644 index 0000000000000..71cdd9650a598 --- /dev/null +++ b/planning/behavior_velocity_planner_common/src/utilization/trajectory_utils.cpp @@ -0,0 +1,121 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// #include +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif + +#include + +namespace behavior_velocity_planner +{ +using autoware_auto_planning_msgs::msg::PathPointWithLaneId; +using autoware_auto_planning_msgs::msg::PathWithLaneId; +using autoware_auto_planning_msgs::msg::Trajectory; +using autoware_auto_planning_msgs::msg::TrajectoryPoint; +using TrajectoryPoints = std::vector; +using geometry_msgs::msg::Quaternion; +using TrajectoryPointWithIdx = std::pair; + +TrajectoryPoints convertPathToTrajectoryPoints(const PathWithLaneId & path) +{ + TrajectoryPoints tps; + for (const auto & p : path.points) { + TrajectoryPoint tp; + tp.pose = p.point.pose; + tp.longitudinal_velocity_mps = p.point.longitudinal_velocity_mps; + // since path point doesn't have acc for now + tp.acceleration_mps2 = 0; + tps.emplace_back(tp); + } + return tps; +} +PathWithLaneId convertTrajectoryPointsToPath(const TrajectoryPoints & trajectory) +{ + PathWithLaneId path; + for (const auto & p : trajectory) { + PathPointWithLaneId pp; + pp.point.pose = p.pose; + pp.point.longitudinal_velocity_mps = p.longitudinal_velocity_mps; + path.points.emplace_back(pp); + } + return path; +} + +Quaternion lerpOrientation(const Quaternion & o_from, const Quaternion & o_to, const double ratio) +{ + tf2::Quaternion q_from, q_to; + tf2::fromMsg(o_from, q_from); + tf2::fromMsg(o_to, q_to); + + const auto q_interpolated = q_from.slerp(q_to, ratio); + return tf2::toMsg(q_interpolated); +} + +//! smooth path point with lane id starts from ego position on path to the path end +bool smoothPath( + const PathWithLaneId & in_path, PathWithLaneId & out_path, + const std::shared_ptr & planner_data) +{ + const geometry_msgs::msg::Pose current_pose = planner_data->current_odometry->pose; + const double v0 = planner_data->current_velocity->twist.linear.x; + const double a0 = planner_data->current_acceleration->accel.accel.linear.x; + const auto & external_v_limit = planner_data->external_velocity_limit; + const auto & smoother = planner_data->velocity_smoother_; + + auto trajectory = convertPathToTrajectoryPoints(in_path); + if (external_v_limit) { + motion_velocity_smoother::trajectory_utils::applyMaximumVelocityLimit( + 0, trajectory.size(), external_v_limit->max_velocity, trajectory); + } + const auto traj_lateral_acc_filtered = smoother->applyLateralAccelerationFilter(trajectory); + + // Resample trajectory with ego-velocity based interval distances + auto traj_resampled = smoother->resampleTrajectory( + traj_lateral_acc_filtered, v0, current_pose, planner_data->ego_nearest_dist_threshold, + planner_data->ego_nearest_yaw_threshold); + const size_t traj_resampled_closest = motion_utils::findFirstNearestIndexWithSoftConstraints( + traj_resampled, current_pose, planner_data->ego_nearest_dist_threshold, + planner_data->ego_nearest_yaw_threshold); + std::vector debug_trajectories; + // Clip trajectory from closest point + TrajectoryPoints clipped; + TrajectoryPoints traj_smoothed; + clipped.insert( + clipped.end(), traj_resampled.begin() + traj_resampled_closest, traj_resampled.end()); + if (!smoother->apply(v0, a0, clipped, traj_smoothed, debug_trajectories)) { + std::cerr << "[behavior_velocity][trajectory_utils]: failed to smooth" << std::endl; + return false; + } + traj_smoothed.insert( + traj_smoothed.begin(), traj_resampled.begin(), traj_resampled.begin() + traj_resampled_closest); + + out_path = convertTrajectoryPointsToPath(traj_smoothed); + return true; +} + +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_planner_common/src/utilization/util.cpp b/planning/behavior_velocity_planner_common/src/utilization/util.cpp index a45331f53d7c2..a3a5a8a169759 100644 --- a/planning/behavior_velocity_planner_common/src/utilization/util.cpp +++ b/planning/behavior_velocity_planner_common/src/utilization/util.cpp @@ -12,26 +12,50 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include +#include + +#include + +#include #include #include +#include + +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif #include #include #include #include -namespace behavior_velocity_planner -{ -namespace planning_utils +namespace { -Point2d calculateOffsetPoint2d(const Pose & pose, const double offset_x, const double offset_y) +size_t calcPointIndexFromSegmentIndex( + const std::vector & points, + const geometry_msgs::msg::Point & point, const size_t seg_idx) { - return to_bg2d(calcOffsetPose(pose, offset_x, offset_y, 0.0)); + const size_t prev_point_idx = seg_idx; + const size_t next_point_idx = seg_idx + 1; + + const double prev_dist = tier4_autoware_utils::calcDistance2d(point, points.at(prev_point_idx)); + const double next_dist = tier4_autoware_utils::calcDistance2d(point, points.at(next_point_idx)); + + if (prev_dist < next_dist) { + return prev_point_idx; + } + return next_point_idx; } +using autoware_auto_planning_msgs::msg::PathPoint; + PathPoint getLerpPathPointWithLaneId(const PathPoint p0, const PathPoint p1, const double ratio) { auto lerp = [](const double a, const double b, const double t) { return a + t * (b - a); }; @@ -42,6 +66,71 @@ PathPoint getLerpPathPointWithLaneId(const PathPoint p0, const PathPoint p1, con return p; } +geometry_msgs::msg::Pose transformRelCoordinate2D( + const geometry_msgs::msg::Pose & target, const geometry_msgs::msg::Pose & origin) +{ + // translation + geometry_msgs::msg::Point trans_p; + trans_p.x = target.position.x - origin.position.x; + trans_p.y = target.position.y - origin.position.y; + + // rotation (use inverse matrix of rotation) + double yaw = tf2::getYaw(origin.orientation); + + geometry_msgs::msg::Pose res; + res.position.x = (std::cos(yaw) * trans_p.x) + (std::sin(yaw) * trans_p.y); + res.position.y = ((-1.0) * std::sin(yaw) * trans_p.x) + (std::cos(yaw) * trans_p.y); + res.position.z = target.position.z - origin.position.z; + res.orientation = + tier4_autoware_utils::createQuaternionFromYaw(tf2::getYaw(target.orientation) - yaw); + + return res; +} + +} // namespace + +namespace behavior_velocity_planner +{ +namespace planning_utils +{ +using autoware_auto_planning_msgs::msg::PathPoint; +using motion_utils::calcLongitudinalOffsetToSegment; +using motion_utils::calcSignedArcLength; +using motion_utils::validateNonEmpty; +using tier4_autoware_utils::calcAzimuthAngle; +using tier4_autoware_utils::calcDistance2d; +using tier4_autoware_utils::calcOffsetPose; +using tier4_autoware_utils::calcSquaredDistance2d; +using tier4_autoware_utils::createQuaternionFromYaw; +using tier4_autoware_utils::getPoint; + +size_t calcSegmentIndexFromPointIndex( + const std::vector & points, + const geometry_msgs::msg::Point & point, const size_t idx) +{ + if (idx == 0) { + return 0; + } + if (idx == points.size() - 1) { + return idx - 1; + } + if (points.size() < 3) { + return 0; + } + + const double offset_to_seg = motion_utils::calcLongitudinalOffsetToSegment(points, idx, point); + if (0 < offset_to_seg) { + return idx; + } + return idx - 1; +} + +Point2d calculateOffsetPoint2d( + const geometry_msgs::msg::Pose & pose, const double offset_x, const double offset_y) +{ + return to_bg2d(calcOffsetPose(pose, offset_x, offset_y, 0.0)); +} + bool createDetectionAreaPolygons( Polygons2d & da_polys, const PathWithLaneId & path, const geometry_msgs::msg::Pose & target_pose, const size_t target_seg_idx, const DetectionRange & da_range, const double obstacle_vel_mps, @@ -171,30 +260,6 @@ void getAllPartitionLanelets(const lanelet::LaneletMapConstPtr ll, BasicPolygons } } -SearchRangeIndex getPathIndexRangeIncludeLaneId(const PathWithLaneId & path, const int64_t lane_id) -{ - /** - * @brief find path index range include given lane_id - * |<-min_idx |<-max_idx - * ------|oooooooooooooooo|------- - */ - SearchRangeIndex search_range = {0, path.points.size() - 1}; - bool found_first_idx = false; - for (size_t i = 0; i < path.points.size(); i++) { - const auto & p = path.points.at(i); - for (const auto & id : p.lane_ids) { - if (id == lane_id) { - if (!found_first_idx) { - search_range.min_idx = i; - found_first_idx = true; - } - search_range.max_idx = i; - } - } - } - return search_range; -} - void setVelocityFromIndex(const size_t begin_idx, const double vel, PathWithLaneId * input) { for (size_t i = begin_idx; i < input->points.size(); ++i) { @@ -232,7 +297,7 @@ void insertVelocity( bool isAheadOf(const geometry_msgs::msg::Pose & target, const geometry_msgs::msg::Pose & origin) { - geometry_msgs::msg::Pose p = planning_utils::transformRelCoordinate2D(target, origin); + geometry_msgs::msg::Pose p = transformRelCoordinate2D(target, origin); const bool is_target_ahead = (p.position.x > 0.0); return is_target_ahead; } @@ -317,47 +382,6 @@ lanelet::ConstLanelet generatePathLanelet( return lanelet::Lanelet(lanelet::InvalId, left, right); } -geometry_msgs::msg::Pose transformRelCoordinate2D( - const geometry_msgs::msg::Pose & target, const geometry_msgs::msg::Pose & origin) -{ - // translation - geometry_msgs::msg::Point trans_p; - trans_p.x = target.position.x - origin.position.x; - trans_p.y = target.position.y - origin.position.y; - - // rotation (use inverse matrix of rotation) - double yaw = tf2::getYaw(origin.orientation); - - geometry_msgs::msg::Pose res; - res.position.x = (std::cos(yaw) * trans_p.x) + (std::sin(yaw) * trans_p.y); - res.position.y = ((-1.0) * std::sin(yaw) * trans_p.x) + (std::cos(yaw) * trans_p.y); - res.position.z = target.position.z - origin.position.z; - res.orientation = - tier4_autoware_utils::createQuaternionFromYaw(tf2::getYaw(target.orientation) - yaw); - - return res; -} - -geometry_msgs::msg::Pose transformAbsCoordinate2D( - const geometry_msgs::msg::Pose & relative, const geometry_msgs::msg::Pose & origin) -{ - // rotation - geometry_msgs::msg::Point rot_p; - double yaw = tf2::getYaw(origin.orientation); - rot_p.x = (std::cos(yaw) * relative.position.x) + (-std::sin(yaw) * relative.position.y); - rot_p.y = (std::sin(yaw) * relative.position.x) + (std::cos(yaw) * relative.position.y); - - // translation - geometry_msgs::msg::Pose absolute; - absolute.position.x = rot_p.x + origin.position.x; - absolute.position.y = rot_p.y + origin.position.y; - absolute.position.z = relative.position.z + origin.position.z; - absolute.orientation = - tier4_autoware_utils::createQuaternionFromYaw(tf2::getYaw(relative.orientation) + yaw); - - return absolute; -} - double calcJudgeLineDistWithAccLimit( const double velocity, const double max_stop_acceleration, const double delay_response_time) { diff --git a/planning/behavior_velocity_planner_common/src/velocity_factor_interface.cpp b/planning/behavior_velocity_planner_common/src/velocity_factor_interface.cpp new file mode 100644 index 0000000000000..35c4bc00d8096 --- /dev/null +++ b/planning/behavior_velocity_planner_common/src/velocity_factor_interface.cpp @@ -0,0 +1,33 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +namespace behavior_velocity_planner +{ +void VelocityFactorInterface::set( + const std::vector & points, + const Pose & curr_pose, const Pose & stop_pose, const VelocityFactorStatus status, + const std::string detail) +{ + const auto & curr_point = curr_pose.position; + const auto & stop_point = stop_pose.position; + velocity_factor_.type = type_; + velocity_factor_.pose = stop_pose; + velocity_factor_.distance = motion_utils::calcSignedArcLength(points, curr_point, stop_point); + velocity_factor_.status = status; + velocity_factor_.detail = detail; +} + +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_run_out_module/CMakeLists.txt b/planning/behavior_velocity_run_out_module/CMakeLists.txt index 89745a18065a3..21758c4d0198c 100644 --- a/planning/behavior_velocity_run_out_module/CMakeLists.txt +++ b/planning/behavior_velocity_run_out_module/CMakeLists.txt @@ -12,6 +12,7 @@ ament_auto_add_library(${PROJECT_NAME} SHARED src/scene.cpp src/state_machine.cpp src/utils.cpp + src/path_utils.cpp ) ament_auto_package(INSTALL_TO_SHARE config) diff --git a/planning/behavior_velocity_run_out_module/src/debug.cpp b/planning/behavior_velocity_run_out_module/src/debug.cpp index 90f69e0faae5e..c0d026b5ffaf4 100644 --- a/planning/behavior_velocity_run_out_module/src/debug.cpp +++ b/planning/behavior_velocity_run_out_module/src/debug.cpp @@ -17,6 +17,7 @@ #include "scene.hpp" #include +#include #include using tier4_autoware_utils::appendMarkerArray; @@ -75,7 +76,6 @@ RunOutDebug::RunOutDebug(rclcpp::Node & node) : node_(node) pub_debug_values_ = node.create_publisher("~/debug/run_out/debug_values", 1); pub_accel_reason_ = node.create_publisher("~/debug/run_out/accel_reason", 1); - pub_debug_trajectory_ = node.create_publisher("~/debug/run_out/trajectory", 1); pub_debug_pointcloud_ = node.create_publisher( "~/debug/run_out/filtered_pointcloud", rclcpp::SensorDataQoS().keep_last(1)); } @@ -305,11 +305,6 @@ void RunOutDebug::publishDebugValue() pub_accel_reason_->publish(accel_reason); } -void RunOutDebug::publishDebugTrajectory(const Trajectory & trajectory) -{ - pub_debug_trajectory_->publish(trajectory); -} - void RunOutDebug::publishFilteredPointCloud( const pcl::PointCloud & pointcloud, const std_msgs::msg::Header header) { diff --git a/planning/behavior_velocity_run_out_module/src/debug.hpp b/planning/behavior_velocity_run_out_module/src/debug.hpp index 0dcd3099b616a..91656d542ea8e 100644 --- a/planning/behavior_velocity_run_out_module/src/debug.hpp +++ b/planning/behavior_velocity_run_out_module/src/debug.hpp @@ -26,7 +26,6 @@ #include namespace behavior_velocity_planner { -using autoware_auto_planning_msgs::msg::Trajectory; using sensor_msgs::msg::PointCloud2; using tier4_debug_msgs::msg::Float32MultiArrayStamped; using tier4_debug_msgs::msg::Int32Stamped; @@ -113,7 +112,6 @@ class RunOutDebug const double travel_time, const geometry_msgs::msg::Pose pose, const float lateral_offset); void setAccelReason(const AccelReason & accel_reason); void publishDebugValue(); - void publishDebugTrajectory(const Trajectory & trajectory); void publishFilteredPointCloud(const PointCloud2 & pointcloud); void publishFilteredPointCloud( const pcl::PointCloud & pointcloud, const std_msgs::msg::Header header); @@ -130,7 +128,6 @@ class RunOutDebug rclcpp::Node & node_; rclcpp::Publisher::SharedPtr pub_debug_values_; rclcpp::Publisher::SharedPtr pub_accel_reason_; - rclcpp::Publisher::SharedPtr pub_debug_trajectory_; rclcpp::Publisher::SharedPtr pub_debug_pointcloud_; std::vector collision_points_; std::vector nearest_collision_point_; diff --git a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp index 728e0fbad92a5..16013efc5fd97 100644 --- a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp +++ b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.cpp @@ -14,11 +14,15 @@ #include "dynamic_obstacle.hpp" +#include #include #include #include #include +#include +#include + #include #include diff --git a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp index 7eece8fac29af..5217193c9673e 100644 --- a/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp +++ b/planning/behavior_velocity_run_out_module/src/dynamic_obstacle.hpp @@ -36,6 +36,8 @@ #include #endif +#include + #include #include diff --git a/planning/behavior_velocity_run_out_module/src/path_utils.cpp b/planning/behavior_velocity_run_out_module/src/path_utils.cpp new file mode 100644 index 0000000000000..1cec20297683c --- /dev/null +++ b/planning/behavior_velocity_run_out_module/src/path_utils.cpp @@ -0,0 +1,39 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "path_utils.hpp" + +#include +namespace behavior_velocity_planner::run_out_utils +{ +geometry_msgs::msg::Point findLongitudinalNearestPoint( + const std::vector & points, + const geometry_msgs::msg::Point & src_point, + const std::vector & target_points) +{ + float min_dist = std::numeric_limits::max(); + geometry_msgs::msg::Point min_dist_point{}; + + for (const auto & p : target_points) { + const float dist = motion_utils::calcSignedArcLength(points, src_point, p); + if (dist < min_dist) { + min_dist = dist; + min_dist_point = p; + } + } + + return min_dist_point; +} + +} // namespace behavior_velocity_planner::run_out_utils diff --git a/planning/behavior_velocity_run_out_module/src/path_utils.hpp b/planning/behavior_velocity_run_out_module/src/path_utils.hpp index 11ed2b7282ab3..92aca946c13ef 100644 --- a/planning/behavior_velocity_run_out_module/src/path_utils.hpp +++ b/planning/behavior_velocity_run_out_module/src/path_utils.hpp @@ -15,6 +15,9 @@ #ifndef PATH_UTILS_HPP_ #define PATH_UTILS_HPP_ +#include +#include + #include #include #include @@ -27,24 +30,10 @@ namespace behavior_velocity_planner namespace run_out_utils { -template geometry_msgs::msg::Point findLongitudinalNearestPoint( - const T & points, const geometry_msgs::msg::Point & src_point, - const std::vector & target_points) -{ - float min_dist = std::numeric_limits::max(); - geometry_msgs::msg::Point min_dist_point{}; - - for (const auto & p : target_points) { - const float dist = motion_utils::calcSignedArcLength(points, src_point, p); - if (dist < min_dist) { - min_dist = dist; - min_dist_point = p; - } - } - - return min_dist_point; -} + const std::vector & points, + const geometry_msgs::msg::Point & src_point, + const std::vector & target_points); } // namespace run_out_utils } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_run_out_module/src/scene.cpp b/planning/behavior_velocity_run_out_module/src/scene.cpp index b062bf9e3175e..b91d1d1fe1ae7 100644 --- a/planning/behavior_velocity_run_out_module/src/scene.cpp +++ b/planning/behavior_velocity_run_out_module/src/scene.cpp @@ -19,6 +19,10 @@ #include #include #include +#include +#include + +#include #include #include diff --git a/planning/behavior_velocity_run_out_module/src/utils.cpp b/planning/behavior_velocity_run_out_module/src/utils.cpp index 71ab07922b27d..61396da250c8d 100644 --- a/planning/behavior_velocity_run_out_module/src/utils.cpp +++ b/planning/behavior_velocity_run_out_module/src/utils.cpp @@ -17,9 +17,22 @@ #include #include +#include +#include +#include + +#include + #include #include +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif +#include +#include namespace behavior_velocity_planner { namespace run_out_utils diff --git a/planning/behavior_velocity_run_out_module/src/utils.hpp b/planning/behavior_velocity_run_out_module/src/utils.hpp index 74bc2489d7c32..8fc3d48de4858 100644 --- a/planning/behavior_velocity_run_out_module/src/utils.hpp +++ b/planning/behavior_velocity_run_out_module/src/utils.hpp @@ -19,6 +19,8 @@ #include #include +#include +#include #include #include @@ -31,6 +33,8 @@ namespace run_out_utils namespace bg = boost::geometry; using autoware_auto_perception_msgs::msg::ObjectClassification; using autoware_auto_perception_msgs::msg::PredictedObjects; +using autoware_auto_perception_msgs::msg::Shape; +using autoware_auto_planning_msgs::msg::PathPoint; using autoware_auto_planning_msgs::msg::PathWithLaneId; using tier4_autoware_utils::Box2d; using tier4_autoware_utils::LineString2d; diff --git a/planning/behavior_velocity_speed_bump_module/src/debug.cpp b/planning/behavior_velocity_speed_bump_module/src/debug.cpp index a2f78dfe85cd3..7409c24e7e8bf 100644 --- a/planning/behavior_velocity_speed_bump_module/src/debug.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/debug.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include diff --git a/planning/behavior_velocity_speed_bump_module/src/manager.cpp b/planning/behavior_velocity_speed_bump_module/src/manager.cpp index dacc99d55b68e..5949357c90ff3 100644 --- a/planning/behavior_velocity_speed_bump_module/src/manager.cpp +++ b/planning/behavior_velocity_speed_bump_module/src/manager.cpp @@ -14,6 +14,8 @@ #include "manager.hpp" +#include +#include #include #include diff --git a/planning/behavior_velocity_stop_line_module/src/debug.cpp b/planning/behavior_velocity_stop_line_module/src/debug.cpp index 8d7de00635d4f..f63bdca5068a2 100644 --- a/planning/behavior_velocity_stop_line_module/src/debug.cpp +++ b/planning/behavior_velocity_stop_line_module/src/debug.cpp @@ -16,8 +16,8 @@ #include #include +#include #include - #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/planning/behavior_velocity_traffic_light_module/src/debug.cpp b/planning/behavior_velocity_traffic_light_module/src/debug.cpp index 6afd5381ce315..9a0ba4f37c3c0 100644 --- a/planning/behavior_velocity_traffic_light_module/src/debug.cpp +++ b/planning/behavior_velocity_traffic_light_module/src/debug.cpp @@ -16,8 +16,8 @@ #include #include +#include #include - #ifdef ROS_DISTRO_GALACTIC #include #else diff --git a/planning/behavior_velocity_traffic_light_module/src/manager.cpp b/planning/behavior_velocity_traffic_light_module/src/manager.cpp index f035544b81592..b943feddc103e 100644 --- a/planning/behavior_velocity_traffic_light_module/src/manager.cpp +++ b/planning/behavior_velocity_traffic_light_module/src/manager.cpp @@ -14,6 +14,7 @@ #include "manager.hpp" +#include #include #include @@ -24,7 +25,6 @@ #include #include #include - namespace behavior_velocity_planner { using lanelet::TrafficLight; diff --git a/planning/behavior_velocity_traffic_light_module/src/scene.cpp b/planning/behavior_velocity_traffic_light_module/src/scene.cpp index e0633926e35df..7c4e947a42e7b 100644 --- a/planning/behavior_velocity_traffic_light_module/src/scene.cpp +++ b/planning/behavior_velocity_traffic_light_module/src/scene.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include // To be replaced by std::optional in C++17 #include diff --git a/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp b/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp index e5cb4dcbc42e7..83b5e6317aeb0 100644 --- a/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp +++ b/planning/behavior_velocity_virtual_traffic_light_module/src/debug.cpp @@ -16,8 +16,8 @@ #include #include +#include #include - using motion_utils::createStopVirtualWallMarker; using tier4_autoware_utils::appendMarkerArray; using tier4_autoware_utils::createDefaultMarker; diff --git a/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp b/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp index 3fe6c19a5f485..5310fae78c294 100644 --- a/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp +++ b/planning/behavior_velocity_virtual_traffic_light_module/src/manager.cpp @@ -14,6 +14,7 @@ #include "manager.hpp" +#include #include #include diff --git a/planning/motion_velocity_smoother/include/motion_velocity_smoother/resample.hpp b/planning/motion_velocity_smoother/include/motion_velocity_smoother/resample.hpp index 24632c06bad6f..80f691b5f7b40 100644 --- a/planning/motion_velocity_smoother/include/motion_velocity_smoother/resample.hpp +++ b/planning/motion_velocity_smoother/include/motion_velocity_smoother/resample.hpp @@ -15,14 +15,9 @@ #ifndef MOTION_VELOCITY_SMOOTHER__RESAMPLE_HPP_ #define MOTION_VELOCITY_SMOOTHER__RESAMPLE_HPP_ -#include "motion_utils/trajectory/trajectory.hpp" -#include "motion_velocity_smoother/trajectory_utils.hpp" - #include "autoware_auto_planning_msgs/msg/trajectory_point.hpp" #include -#include "boost/optional.hpp" - #include namespace motion_velocity_smoother diff --git a/planning/motion_velocity_smoother/include/motion_velocity_smoother/smoother/smoother_base.hpp b/planning/motion_velocity_smoother/include/motion_velocity_smoother/smoother/smoother_base.hpp index a7416faa3c652..beb571635f70c 100644 --- a/planning/motion_velocity_smoother/include/motion_velocity_smoother/smoother/smoother_base.hpp +++ b/planning/motion_velocity_smoother/include/motion_velocity_smoother/smoother/smoother_base.hpp @@ -15,17 +15,11 @@ #ifndef MOTION_VELOCITY_SMOOTHER__SMOOTHER__SMOOTHER_BASE_HPP_ #define MOTION_VELOCITY_SMOOTHER__SMOOTHER__SMOOTHER_BASE_HPP_ -#include "motion_utils/trajectory/trajectory.hpp" #include "motion_velocity_smoother/resample.hpp" -#include "motion_velocity_smoother/trajectory_utils.hpp" #include "rclcpp/rclcpp.hpp" -#include "tier4_autoware_utils/geometry/geometry.hpp" -#include "vehicle_info_util/vehicle_info_util.hpp" #include "autoware_auto_planning_msgs/msg/trajectory_point.hpp" -#include "boost/optional.hpp" - #include #include @@ -33,7 +27,6 @@ namespace motion_velocity_smoother { using autoware_auto_planning_msgs::msg::TrajectoryPoint; using TrajectoryPoints = std::vector; -using vehicle_info_util::VehicleInfoUtil; class SmootherBase { diff --git a/planning/motion_velocity_smoother/include/motion_velocity_smoother/trajectory_utils.hpp b/planning/motion_velocity_smoother/include/motion_velocity_smoother/trajectory_utils.hpp index 58322fda322bb..ec8a89c5a3e9c 100644 --- a/planning/motion_velocity_smoother/include/motion_velocity_smoother/trajectory_utils.hpp +++ b/planning/motion_velocity_smoother/include/motion_velocity_smoother/trajectory_utils.hpp @@ -15,9 +15,6 @@ #ifndef MOTION_VELOCITY_SMOOTHER__TRAJECTORY_UTILS_HPP_ #define MOTION_VELOCITY_SMOOTHER__TRAJECTORY_UTILS_HPP_ -#include "motion_utils/trajectory/trajectory.hpp" -#include "tier4_autoware_utils/geometry/geometry.hpp" - #include "autoware_auto_planning_msgs/msg/trajectory_point.hpp" #include "geometry_msgs/msg/pose.hpp" diff --git a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp index d3c77aa0faa84..5ed176bd2e215 100644 --- a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp +++ b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp @@ -39,7 +39,7 @@ MotionVelocitySmootherNode::MotionVelocitySmootherNode(const rclcpp::NodeOptions using std::placeholders::_1; // set common params - const auto vehicle_info = VehicleInfoUtil(*this).getVehicleInfo(); + const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(*this).getVehicleInfo(); wheelbase_ = vehicle_info.wheel_base_m; initCommonParam(); over_stop_velocity_warn_thr_ = declare_parameter("over_stop_velocity_warn_thr"); diff --git a/planning/motion_velocity_smoother/src/resample.cpp b/planning/motion_velocity_smoother/src/resample.cpp index fbf129f105c7f..37f3ce953bd48 100644 --- a/planning/motion_velocity_smoother/src/resample.cpp +++ b/planning/motion_velocity_smoother/src/resample.cpp @@ -16,6 +16,8 @@ #include "motion_utils/resample/resample.hpp" #include "motion_utils/trajectory/tmp_conversion.hpp" +#include "motion_utils/trajectory/trajectory.hpp" +#include "motion_velocity_smoother/trajectory_utils.hpp" #include #include diff --git a/planning/motion_velocity_smoother/src/smoother/analytical_jerk_constrained_smoother/analytical_jerk_constrained_smoother.cpp b/planning/motion_velocity_smoother/src/smoother/analytical_jerk_constrained_smoother/analytical_jerk_constrained_smoother.cpp index a02e432e1e584..d1694d7420ca2 100644 --- a/planning/motion_velocity_smoother/src/smoother/analytical_jerk_constrained_smoother/analytical_jerk_constrained_smoother.cpp +++ b/planning/motion_velocity_smoother/src/smoother/analytical_jerk_constrained_smoother/analytical_jerk_constrained_smoother.cpp @@ -16,6 +16,7 @@ #include "motion_utils/resample/resample.hpp" #include "motion_utils/trajectory/tmp_conversion.hpp" +#include "motion_velocity_smoother/trajectory_utils.hpp" #include #include diff --git a/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp b/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp index 2e58961d54c00..7f058d0d138d0 100644 --- a/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp +++ b/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp @@ -16,8 +16,10 @@ #include "motion_utils/resample/resample.hpp" #include "motion_utils/trajectory/tmp_conversion.hpp" +#include "motion_utils/trajectory/trajectory.hpp" #include "motion_velocity_smoother/resample.hpp" #include "motion_velocity_smoother/trajectory_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" #include @@ -26,7 +28,6 @@ namespace motion_velocity_smoother { -using vehicle_info_util::VehicleInfoUtil; SmootherBase::SmootherBase(rclcpp::Node & node) { diff --git a/planning/motion_velocity_smoother/src/trajectory_utils.cpp b/planning/motion_velocity_smoother/src/trajectory_utils.cpp index ab5db29ad2fc0..dd361696a5dad 100644 --- a/planning/motion_velocity_smoother/src/trajectory_utils.cpp +++ b/planning/motion_velocity_smoother/src/trajectory_utils.cpp @@ -16,6 +16,8 @@ #include "interpolation/linear_interpolation.hpp" #include "interpolation/spline_interpolation.hpp" +#include "motion_utils/trajectory/trajectory.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" #include #include From 2aa9c7b1db4c0fc7d602feb4984b4d64db5265d4 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:04:48 +0200 Subject: [PATCH 284/547] build(traffic_light_classifier): remove download logic from CMake (#3144) Signed-off-by: Esteve Fernandez --- .../traffic_light_classifier/CMakeLists.txt | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/perception/traffic_light_classifier/CMakeLists.txt b/perception/traffic_light_classifier/CMakeLists.txt index 72dba86a00d33..22bc7521c6e31 100644 --- a/perception/traffic_light_classifier/CMakeLists.txt +++ b/perception/traffic_light_classifier/CMakeLists.txt @@ -65,59 +65,6 @@ else() set(CUDNN_AVAIL OFF) endif() -# cspell: ignore EFFICIENTNET, MOBILENET -# Download caffemodel and prototxt -set(EFFICIENTNET_BATCH_1_HASH 82baba4fcf1abe0c040cd55005e34510) -set(EFFICIENTNET_BATCH_4_HASH 21b549c2fe4fbb20d32cc019e6d70cd7) -set(EFFICIENTNET_BATCH_6_HASH 28b408710bcb24f4cdd4d746301d4e78) -set(MOBILENET_BATCH_1_HASH caa51f2080aa2df943e4f884c41898eb) -set(MOBILENET_BATCH_4_HASH c2beaf60210f471debfe72b86d076ca0) -set(MOBILENET_BATCH_6_HASH 47255a11bde479320d703f1f45db1242) -set(LAMP_LABEL_HASH e9f45efb02f2a9aa8ac27b3d5c164905) -set(AWS_DIR https://awf.ml.dev.web.auto/perception/models/traffic_light_classifier/v2) -set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") -if(NOT EXISTS "${DATA_PATH}") - execute_process(COMMAND mkdir -p ${DATA_PATH}) -endif() -function(download FILE_NAME FILE_HASH) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - if(${FILE_HASH} STREQUAL ${EXISTING_FILE_HASH}) - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD ${AWS_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD ${AWS_DIR}/${FILE_NAME} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 300) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() -endfunction() -download(traffic_light_classifier_mobilenetv2_batch_1.onnx ${MOBILENET_BATCH_1_HASH}) -download(traffic_light_classifier_mobilenetv2_batch_4.onnx ${MOBILENET_BATCH_4_HASH}) -download(traffic_light_classifier_mobilenetv2_batch_6.onnx ${MOBILENET_BATCH_6_HASH}) -download(traffic_light_classifier_efficientNet_b1_batch_1.onnx ${EFFICIENTNET_BATCH_1_HASH}) -download(traffic_light_classifier_efficientNet_b1_batch_4.onnx ${EFFICIENTNET_BATCH_4_HASH}) -download(traffic_light_classifier_efficientNet_b1_batch_6.onnx ${EFFICIENTNET_BATCH_6_HASH}) -download(lamp_labels.txt 4b2cf910d97d05d464e7c26901af3d4c) - find_package(ament_cmake_auto REQUIRED) ament_auto_find_build_dependencies() find_package(OpenCV REQUIRED) @@ -175,7 +122,6 @@ if(TRT_AVAIL AND CUDA_AVAIL AND CUDNN_AVAIL) ) ament_auto_package(INSTALL_TO_SHARE - data launch ) @@ -200,7 +146,6 @@ else() ) ament_auto_package(INSTALL_TO_SHARE - data launch ) From df6228617d1348828eaea592b74c10e32a752524 Mon Sep 17 00:00:00 2001 From: Yusuke Muramatsu Date: Fri, 22 Sep 2023 10:30:52 +0900 Subject: [PATCH 285/547] fix(image_projection_based_fusion): fix unexpected matches between roi and cluster (#4445) * fix(image_projection_based_fusion): fix error caused by empty cluster Signed-off-by: yukke42 * style(pre-commit): autofix * chore: remove to return Signed-off-by: yukke42 * apply suggestion Signed-off-by: yukke42 --------- Signed-off-by: yukke42 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../src/roi_cluster_fusion/node.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp index e9cb76b050427..316d29dae1301 100644 --- a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp +++ b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp @@ -169,7 +169,8 @@ void RoiClusterFusionNode::fuseOnSingleImage( } for (const auto & feature_obj : input_roi_msg.feature_objects) { - int index = 0; + int index = -1; + bool associated = false; double max_iou = 0.0; bool is_roi_label_known = feature_obj.object.classification.front().label != ObjectClassification::UNKNOWN; @@ -193,8 +194,14 @@ void RoiClusterFusionNode::fuseOnSingleImage( if (max_iou < iou + iou_x + iou_y && passed_inside_cluster_gate) { index = cluster_map.first; max_iou = iou + iou_x + iou_y; + associated = true; } } + + if (!associated) { + continue; + } + if (!output_cluster_msg.feature_objects.empty()) { bool is_roi_existence_prob_higher = output_cluster_msg.feature_objects.at(index).object.existence_probability <= From 4c9146eec95a319ecbed2618e24f6a3e72734b2d Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Fri, 22 Sep 2023 10:42:21 +0900 Subject: [PATCH 286/547] fix(euclidean_cluster): print warning on empty cloud, prevent log spam (#4029) solves #4001 Signed-off-by: Vincent Richard --- .../src/voxel_grid_based_euclidean_cluster_node.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp b/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp index 53fb4df8977d8..a28791e5d4057 100644 --- a/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp +++ b/perception/euclidean_cluster/src/voxel_grid_based_euclidean_cluster_node.cpp @@ -56,11 +56,19 @@ void VoxelGridBasedEuclideanClusterNode::onPointCloud( // convert ros to pcl pcl::PointCloud::Ptr raw_pointcloud_ptr(new pcl::PointCloud); - pcl::fromROSMsg(*input_msg, *raw_pointcloud_ptr); + if (input_msg->data.empty()) { + // NOTE: prevent pcl log spam + RCLCPP_WARN_STREAM_THROTTLE( + this->get_logger(), *this->get_clock(), 1000, "Empty sensor points!"); + } else { + pcl::fromROSMsg(*input_msg, *raw_pointcloud_ptr); + } // clustering std::vector> clusters; - cluster_->cluster(raw_pointcloud_ptr, clusters); + if (!raw_pointcloud_ptr->empty()) { + cluster_->cluster(raw_pointcloud_ptr, clusters); + } // build output msg tier4_perception_msgs::msg::DetectedObjectsWithFeature output; From bf03fe31bdfbc036a661fdcfd8c062a1d90fb306 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:01:06 +0900 Subject: [PATCH 287/547] fix(simple_planning_simulator): fix build error (#5062) Signed-off-by: satoshi-ota --- .../simple_planning_simulator_core.hpp | 2 +- .../simple_planning_simulator_core.cpp | 12 +++++++----- .../test/test_simple_planning_simulator.cpp | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp b/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp index 8eca49d9ffadf..4b25dba8b16ee 100644 --- a/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp +++ b/simulator/simple_planning_simulator/include/simple_planning_simulator/simple_planning_simulator_core.hpp @@ -269,7 +269,7 @@ class PLANNING_SIMULATOR_PUBLIC SimplePlanningSimulator : public rclcpp::Node * @brief ControlModeRequest server */ void on_control_mode_request( - const ControlModeCommand::Request::SharedPtr request, + const ControlModeCommand::Request::ConstSharedPtr request, const ControlModeCommand::Response::SharedPtr response); /** diff --git a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp index fe28d063eedd3..8c2688928b4c9 100644 --- a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp +++ b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp @@ -117,16 +117,18 @@ SimplePlanningSimulator::SimplePlanningSimulator(const rclcpp::NodeOptions & opt "input/initialtwist", QoS{1}, std::bind(&SimplePlanningSimulator::on_initialtwist, this, _1)); sub_ackermann_cmd_ = create_subscription( "input/ackermann_control_command", QoS{1}, - [this](const AckermannControlCommand::SharedPtr msg) { current_ackermann_cmd_ = *msg; }); + [this](const AckermannControlCommand::ConstSharedPtr msg) { current_ackermann_cmd_ = *msg; }); sub_manual_ackermann_cmd_ = create_subscription( "input/manual_ackermann_control_command", QoS{1}, - [this](const AckermannControlCommand::SharedPtr msg) { current_manual_ackermann_cmd_ = *msg; }); + [this](const AckermannControlCommand::ConstSharedPtr msg) { + current_manual_ackermann_cmd_ = *msg; + }); sub_gear_cmd_ = create_subscription( "input/gear_command", QoS{1}, - [this](const GearCommand::SharedPtr msg) { current_gear_cmd_ = *msg; }); + [this](const GearCommand::ConstSharedPtr msg) { current_gear_cmd_ = *msg; }); sub_manual_gear_cmd_ = create_subscription( "input/manual_gear_command", QoS{1}, - [this](const GearCommand::SharedPtr msg) { current_manual_gear_cmd_ = *msg; }); + [this](const GearCommand::ConstSharedPtr msg) { current_manual_gear_cmd_ = *msg; }); sub_turn_indicators_cmd_ = create_subscription( "input/turn_indicators_command", QoS{1}, std::bind(&SimplePlanningSimulator::on_turn_indicators_cmd, this, _1)); @@ -484,7 +486,7 @@ void SimplePlanningSimulator::on_engage(const Engage::ConstSharedPtr msg) } void SimplePlanningSimulator::on_control_mode_request( - const ControlModeCommand::Request::SharedPtr request, + const ControlModeCommand::Request::ConstSharedPtr request, const ControlModeCommand::Response::SharedPtr response) { const auto m = request->mode; diff --git a/simulator/simple_planning_simulator/test/test_simple_planning_simulator.cpp b/simulator/simple_planning_simulator/test/test_simple_planning_simulator.cpp index e57a3683b97a9..cedfec395110e 100644 --- a/simulator/simple_planning_simulator/test/test_simple_planning_simulator.cpp +++ b/simulator/simple_planning_simulator/test/test_simple_planning_simulator.cpp @@ -49,7 +49,7 @@ class PubSubNode : public rclcpp::Node { current_odom_sub_ = create_subscription( "output/odometry", rclcpp::QoS{1}, - [this](const Odometry::SharedPtr msg) { current_odom_ = msg; }); + [this](const Odometry::ConstSharedPtr msg) { current_odom_ = msg; }); pub_ackermann_command_ = create_publisher("input/ackermann_control_command", rclcpp::QoS{1}); pub_initialpose_ = @@ -62,7 +62,7 @@ class PubSubNode : public rclcpp::Node rclcpp::Publisher::SharedPtr pub_gear_cmd_; rclcpp::Publisher::SharedPtr pub_initialpose_; - Odometry::SharedPtr current_odom_; + Odometry::ConstSharedPtr current_odom_; }; /** From f09e1b75addde381e7bbfb32444a75927a832169 Mon Sep 17 00:00:00 2001 From: TaikiYamada4 <129915538+TaikiYamada4@users.noreply.github.com> Date: Fri, 22 Sep 2023 13:32:06 +0900 Subject: [PATCH 288/547] refactor(ndt_scan_matcher): rework parameters (#5030) * Removed all default values from the c++ source code. Modified the declaration of ndt_base_frame_ so that it works if it is not declared in the param.yaml file Signed-off-by: TaikiYamada4 * Added ndt_scan_matcher.schema.json file Signed-off-by: TaikiYamada4 * Removed redundant static_cast on declare_paramter Signed-off-by: TaikiYamada4 * style(pre-commit): autofix * Added neighborhood_search_method parameter to ndt_scan_matcher.param.yaml. This is same to the one in autoware_launch Signed-off-by: TaikiYamada4 * Deleted schema.json for now Signed-off-by: TaikiYamada4 * Delete ndt_scan_matcher.schema.json for now Signed-off-by: TaikiYamada4 * Deleted parameter neighborhood_search_method from ndt_scan_matcher.param.yaml, which was inserted by mistake. Signed-off-by: TaikiYamada4 * Added ndt_base_frame to ndt_scan_matcher.param.yaml Signed-off-by: TaikiYamada4 --------- Signed-off-by: TaikiYamada4 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../config/ndt_scan_matcher.param.yaml | 3 ++ .../src/ndt_scan_matcher_core.cpp | 42 ++++++++----------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml index b5affd72b2158..c8afa9ea1a916 100644 --- a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml +++ b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml @@ -6,6 +6,9 @@ # Vehicle reference frame base_frame: "base_link" + # NDT reference frame + ndt_base_frame: "ndt_base_link" + # Subscriber queue size input_sensor_points_queue_size: 1 diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 580d1e59b65a6..7bb2b79e88f33 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -97,31 +97,30 @@ NDTScanMatcher::NDTScanMatcher() inversion_vector_threshold_(-0.9), oscillation_threshold_(10), output_pose_covariance_(), - regularization_enabled_(declare_parameter("regularization_enabled", false)), + regularization_enabled_(declare_parameter("regularization_enabled")), estimate_scores_for_degrounded_scan_( - declare_parameter("estimate_scores_for_degrounded_scan", false)), - z_margin_for_ground_removal_(declare_parameter("z_margin_for_ground_removal", 0.8)) + declare_parameter("estimate_scores_for_degrounded_scan")), + z_margin_for_ground_removal_(declare_parameter("z_margin_for_ground_removal")) { (*state_ptr_)["state"] = "Initializing"; is_activated_ = false; - int points_queue_size = - static_cast(this->declare_parameter("input_sensor_points_queue_size", 0)); + int points_queue_size = this->declare_parameter("input_sensor_points_queue_size"); points_queue_size = std::max(points_queue_size, 0); RCLCPP_INFO(get_logger(), "points_queue_size: %d", points_queue_size); - base_frame_ = this->declare_parameter("base_frame", base_frame_); + base_frame_ = this->declare_parameter("base_frame"); RCLCPP_INFO(get_logger(), "base_frame_id: %s", base_frame_.c_str()); - ndt_base_frame_ = this->declare_parameter("ndt_base_frame", ndt_base_frame_); + ndt_base_frame_ = this->declare_parameter("ndt_base_frame"); RCLCPP_INFO(get_logger(), "ndt_base_frame_id: %s", ndt_base_frame_.c_str()); pclomp::NdtParams ndt_params{}; ndt_params.trans_epsilon = this->declare_parameter("trans_epsilon"); ndt_params.step_size = this->declare_parameter("step_size"); ndt_params.resolution = this->declare_parameter("resolution"); - ndt_params.max_iterations = static_cast(this->declare_parameter("max_iterations")); - ndt_params.num_threads = static_cast(this->declare_parameter("num_threads")); + ndt_params.max_iterations = this->declare_parameter("max_iterations"); + ndt_params.num_threads = this->declare_parameter("num_threads"); ndt_params.num_threads = std::max(ndt_params.num_threads, 1); ndt_params.regularization_scale_factor = static_cast(this->declare_parameter("regularization_scale_factor")); @@ -132,24 +131,20 @@ NDTScanMatcher::NDTScanMatcher() ndt_params.trans_epsilon, ndt_params.step_size, ndt_params.resolution, ndt_params.max_iterations); - int converged_param_type_tmp = - static_cast(this->declare_parameter("converged_param_type", 0)); + int converged_param_type_tmp = this->declare_parameter("converged_param_type"); converged_param_type_ = static_cast(converged_param_type_tmp); - converged_param_transform_probability_ = this->declare_parameter( - "converged_param_transform_probability", converged_param_transform_probability_); - converged_param_nearest_voxel_transformation_likelihood_ = this->declare_parameter( - "converged_param_nearest_voxel_transformation_likelihood", - converged_param_nearest_voxel_transformation_likelihood_); + converged_param_transform_probability_ = + this->declare_parameter("converged_param_transform_probability"); + converged_param_nearest_voxel_transformation_likelihood_ = + this->declare_parameter("converged_param_nearest_voxel_transformation_likelihood"); - lidar_topic_timeout_sec_ = - this->declare_parameter("lidar_topic_timeout_sec", lidar_topic_timeout_sec_); + lidar_topic_timeout_sec_ = this->declare_parameter("lidar_topic_timeout_sec"); - initial_pose_timeout_sec_ = - this->declare_parameter("initial_pose_timeout_sec", initial_pose_timeout_sec_); + initial_pose_timeout_sec_ = this->declare_parameter("initial_pose_timeout_sec"); - initial_pose_distance_tolerance_m_ = this->declare_parameter( - "initial_pose_distance_tolerance_m", initial_pose_distance_tolerance_m_); + initial_pose_distance_tolerance_m_ = + this->declare_parameter("initial_pose_distance_tolerance_m"); std::vector output_pose_covariance = this->declare_parameter>("output_pose_covariance"); @@ -157,8 +152,7 @@ NDTScanMatcher::NDTScanMatcher() output_pose_covariance_[i] = output_pose_covariance[i]; } - initial_estimate_particles_num_ = static_cast( - this->declare_parameter("initial_estimate_particles_num", initial_estimate_particles_num_)); + initial_estimate_particles_num_ = this->declare_parameter("initial_estimate_particles_num"); rclcpp::CallbackGroup::SharedPtr initial_pose_callback_group; initial_pose_callback_group = From 12d83338a2d66bf23157c0d8f109a217cc0a3a88 Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Fri, 22 Sep 2023 16:39:45 +0900 Subject: [PATCH 289/547] feat(lane_change): regulate lane change on crosswalk and intersection (#5068) * fix(lane_change): fix hasEnoughDistance() Signed-off-by: Fumiya Watanabe * feat(lane_change): regulate lane change on crosswalk and intersection Signed-off-by: Fumiya Watanabe * fix(lane_change): remove unnecessary value Signed-off-by: Fumiya Watanabe * Update planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp Co-authored-by: Kyoichi Sugahara * style(pre-commit): autofix * Update planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp Co-authored-by: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> * style(pre-commit): autofix * fix(lane_change): separate functions and fix Signed-off-by: Fumiya Watanabe --------- Signed-off-by: Fumiya Watanabe Co-authored-by: Kyoichi Sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> --- .../config/lane_change/lane_change.param.yaml | 5 + ...ehavior_path_planner_lane_change_design.md | 12 + .../scene_module/lane_change/normal.hpp | 8 +- .../lane_change/lane_change_module_data.hpp | 4 + .../src/scene_module/lane_change/manager.cpp | 5 + .../src/scene_module/lane_change/normal.cpp | 397 +++++++++++------- .../behavior_path_planner/src/utils/utils.cpp | 5 +- 7 files changed, 276 insertions(+), 160 deletions(-) diff --git a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml index 925e70d9084d6..d7179cd384008 100644 --- a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml +++ b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml @@ -55,6 +55,11 @@ motorcycle: true pedestrian: true + # lane change regulations + regulation: + crosswalk: false + intersection: false + # collision check enable_prepare_segment_collision_check: true prepare_segment_ignore_object_velocity_thresh: 0.1 # [m/s] diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md index 2fd1ff1cfd376..88266a2f4fb2a 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md @@ -186,6 +186,11 @@ The following figure illustrates when the lane is blocked in multiple lane chang ![multiple-lane-changes](../image/lane_change/lane_change-when_cannot_change_lanes.png) +### Lane change regulations + +If you want to regulate lane change on crosswalks or intersections, the lane change module finds a lane change path excluding it includes crosswalks or intersections. +To regulate lane change on crosswalks or intersections, change `regulation.crosswalk` or `regulation.intersection` to `true`. + ### Aborting lane change The abort process may result in three different outcome; Cancel, Abort and Stop/Cruise. @@ -294,6 +299,13 @@ The following parameters are configurable in `lane_change.param.yaml`. | `target_object.motorcycle` | [-] | boolean | Include motorcycle objects for safety check | true | | `target_object.pedestrian` | [-] | boolean | Include pedestrian objects for safety check | true | +### Lane change regulations + +| Name | Unit | Type | Description | Default value | +| :------------------------ | ---- | ------- | ------------------------------------- | ------------- | +| `regulation.crosswalk` | [-] | boolean | Regulate lane change on crosswalks | false | +| `regulation.intersection` | [-] | boolean | Regulate lane change on intersections | false | + ### Collision checks during lane change The following parameters are configurable in `behavior_path_planner.param.yaml` and `lane_change.param.yaml`. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index ef57c31c28b5e..747c75babb573 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -108,7 +108,7 @@ class NormalLaneChange : public LaneChangeBase const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes) const; - double calcPrepareDuration( + std::vector calcPrepareDuration( const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes) const; @@ -129,6 +129,12 @@ class NormalLaneChange : public LaneChangeBase const LaneChangePath & path, const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, const Direction direction = Direction::NONE) const; + bool hasEnoughLengthToCrosswalk( + const LaneChangePath & path, const lanelet::ConstLanelets & current_lanes) const; + + bool hasEnoughLengthToIntersection( + const LaneChangePath & path, const lanelet::ConstLanelets & current_lanes) const; + bool getLaneChangePaths( const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, Direction direction, LaneChangePaths * candidate_paths, diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index c78be341b4e16..ce515be800900 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -68,6 +68,10 @@ struct LaneChangeParameters bool check_objects_on_other_lanes{true}; bool use_all_predicted_path{false}; + // regulatory elements + bool regulate_on_crosswalk{false}; + bool regulate_on_intersection{false}; + // true by default for all objects utils::path_safety_checker::ObjectTypesToCheck object_types_to_check; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index e72691ea55391..57986a8f02265 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -78,6 +78,11 @@ LaneChangeModuleManager::LaneChangeModuleManager( p.use_all_predicted_path = getOrDeclareParameter(*node, parameter("use_all_predicted_path")); + // lane change regulations + p.regulate_on_crosswalk = getOrDeclareParameter(*node, parameter("regulation.crosswalk")); + p.regulate_on_intersection = + getOrDeclareParameter(*node, parameter("regulation.intersection")); + p.rss_params.longitudinal_distance_min_threshold = getOrDeclareParameter( *node, parameter("safety_check.longitudinal_distance_min_threshold")); p.rss_params.longitudinal_velocity_delta_time = getOrDeclareParameter( diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 376bac3ed65fb..36127bbdf20b6 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -567,19 +567,26 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( return utils::lane_change::getAccelerationValues(min_acc, max_acc, longitudinal_acc_sampling_num); } -double NormalLaneChange::calcPrepareDuration( +std::vector NormalLaneChange::calcPrepareDuration( const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes) const { const auto & common_parameters = planner_data_->parameters; - const auto threshold = lane_change_parameters_->min_length_for_turn_signal_activation; - const auto current_vel = getEgoVelocity(); + const auto base_link2front = planner_data_->parameters.base_link2front; + const auto threshold = + lane_change_parameters_->min_length_for_turn_signal_activation + base_link2front; - // if the ego vehicle is close to the end of the lane at a low speed - if (isNearEndOfCurrentLanes(current_lanes, target_lanes, threshold) && current_vel < 1.0) { - return 0.0; + std::vector prepare_durations; + constexpr double step = 0.5; + + for (double duration = common_parameters.lane_change_prepare_duration; duration >= 0.0; + duration -= step) { + prepare_durations.push_back(duration); + if (!isNearEndOfCurrentLanes(current_lanes, target_lanes, threshold)) { + break; + } } - return common_parameters.lane_change_prepare_duration; + return prepare_durations; } PathWithLaneId NormalLaneChange::getPrepareSegment( @@ -800,6 +807,7 @@ bool NormalLaneChange::hasEnoughLength( { const auto current_pose = getEgoPose(); const auto & route_handler = *getRouteHandler(); + const auto overall_graphs_ptr = route_handler.getOverallGraphPtr(); const auto & common_parameters = planner_data_->parameters; const double lane_change_length = path.info.length.sum(); const auto shift_intervals = @@ -830,6 +838,68 @@ bool NormalLaneChange::hasEnoughLength( return false; } + if (lane_change_parameters_->regulate_on_crosswalk) { + const double dist_to_crosswalk_from_lane_change_start_pose = + utils::getDistanceToCrosswalk(current_pose, current_lanes, *overall_graphs_ptr) - + path.info.length.prepare; + // Check lane changing section includes crosswalk + if ( + dist_to_crosswalk_from_lane_change_start_pose > 0.0 && + dist_to_crosswalk_from_lane_change_start_pose < path.info.length.lane_changing) { + return false; + } + } + + if (lane_change_parameters_->regulate_on_intersection) { + const double dist_to_intersection_from_lane_change_start_pose = + utils::getDistanceToNextIntersection(current_pose, current_lanes) - path.info.length.prepare; + // Check lane changing section includes intersection + if ( + dist_to_intersection_from_lane_change_start_pose > 0.0 && + dist_to_intersection_from_lane_change_start_pose < path.info.length.lane_changing) { + return false; + } + } + + return true; +} + +bool NormalLaneChange::hasEnoughLengthToCrosswalk( + const LaneChangePath & path, const lanelet::ConstLanelets & current_lanes) const +{ + const auto current_pose = getEgoPose(); + const auto & route_handler = *getRouteHandler(); + const auto overall_graphs_ptr = route_handler.getOverallGraphPtr(); + + const double dist_to_crosswalk_from_lane_change_start_pose = + utils::getDistanceToCrosswalk(current_pose, current_lanes, *overall_graphs_ptr) - + path.info.length.prepare; + // Check lane changing section includes crosswalk + if ( + dist_to_crosswalk_from_lane_change_start_pose > 0.0 && + dist_to_crosswalk_from_lane_change_start_pose < path.info.length.lane_changing) { + return false; + } + + return true; +} + +bool NormalLaneChange::hasEnoughLengthToIntersection( + const LaneChangePath & path, const lanelet::ConstLanelets & current_lanes) const +{ + const auto current_pose = getEgoPose(); + const auto & route_handler = *getRouteHandler(); + const auto overall_graphs_ptr = route_handler.getOverallGraphPtr(); + + const double dist_to_intersection_from_lane_change_start_pose = + utils::getDistanceToNextIntersection(current_pose, current_lanes) - path.info.length.prepare; + // Check lane changing section includes intersection + if ( + dist_to_intersection_from_lane_change_start_pose > 0.0 && + dist_to_intersection_from_lane_change_start_pose < path.info.length.lane_changing) { + return false; + } + return true; } @@ -876,182 +946,199 @@ bool NormalLaneChange::getLaneChangePaths( const auto target_objects = getTargetObjects(current_lanes, target_lanes); - candidate_paths->reserve(longitudinal_acc_sampling_values.size() * lateral_acc_sampling_num); + const auto prepare_durations = calcPrepareDuration(current_lanes, target_lanes); - const auto prepare_duration = calcPrepareDuration(current_lanes, target_lanes); + candidate_paths->reserve( + longitudinal_acc_sampling_values.size() * lateral_acc_sampling_num * prepare_durations.size()); - for (const auto & sampled_longitudinal_acc : longitudinal_acc_sampling_values) { - // get path on original lanes - const auto prepare_velocity = std::max( - current_velocity + sampled_longitudinal_acc * prepare_duration, - minimum_lane_changing_velocity); + for (const auto & prepare_duration : prepare_durations) { + for (const auto & sampled_longitudinal_acc : longitudinal_acc_sampling_values) { + // get path on original lanes + const auto prepare_velocity = std::max( + current_velocity + sampled_longitudinal_acc * prepare_duration, + minimum_lane_changing_velocity); - // compute actual longitudinal acceleration - const double longitudinal_acc_on_prepare = - (prepare_duration < 1e-3) ? 0.0 : ((prepare_velocity - current_velocity) / prepare_duration); + // compute actual longitudinal acceleration + const double longitudinal_acc_on_prepare = + (prepare_duration < 1e-3) ? 0.0 + : ((prepare_velocity - current_velocity) / prepare_duration); - const double prepare_length = current_velocity * prepare_duration + - 0.5 * longitudinal_acc_on_prepare * std::pow(prepare_duration, 2); + const double prepare_length = + current_velocity * prepare_duration + + 0.5 * longitudinal_acc_on_prepare * std::pow(prepare_duration, 2); - auto prepare_segment = getPrepareSegment(current_lanes, backward_path_length, prepare_length); + auto prepare_segment = getPrepareSegment(current_lanes, backward_path_length, prepare_length); - if (prepare_segment.points.empty()) { - RCLCPP_DEBUG(logger_, "prepare segment is empty!!"); - continue; - } - - // lane changing start getEgoPose() is at the end of prepare segment - const auto & lane_changing_start_pose = prepare_segment.points.back().point.pose; - const auto target_length_from_lane_change_start_pose = utils::getArcLengthToTargetLanelet( - current_lanes, target_lanes.front(), lane_changing_start_pose); + if (prepare_segment.points.empty()) { + RCLCPP_DEBUG(logger_, "prepare segment is empty!!"); + continue; + } - // Check if the lane changing start point is not on the lanes next to target lanes, - if (target_length_from_lane_change_start_pose > 0.0) { - RCLCPP_DEBUG( - logger_, "[only new arch] lane change start getEgoPose() is behind target lanelet!!"); - break; - } + // lane changing start getEgoPose() is at the end of prepare segment + const auto & lane_changing_start_pose = prepare_segment.points.back().point.pose; + const auto target_length_from_lane_change_start_pose = utils::getArcLengthToTargetLanelet( + current_lanes, target_lanes.front(), lane_changing_start_pose); - const auto shift_length = - lanelet::utils::getLateralDistanceToClosestLanelet(target_lanes, lane_changing_start_pose); - - const auto initial_lane_changing_velocity = prepare_velocity; - const auto & max_path_velocity = prepare_segment.points.back().point.longitudinal_velocity_mps; - - // get lateral acceleration range - const auto [min_lateral_acc, max_lateral_acc] = - common_parameters.lane_change_lat_acc_map.find(initial_lane_changing_velocity); - const auto lateral_acc_resolution = - std::abs(max_lateral_acc - min_lateral_acc) / lateral_acc_sampling_num; - constexpr double lateral_acc_epsilon = 0.01; - - for (double lateral_acc = min_lateral_acc; lateral_acc < max_lateral_acc + lateral_acc_epsilon; - lateral_acc += lateral_acc_resolution) { - const auto lane_changing_time = PathShifter::calcShiftTimeFromJerk( - shift_length, common_parameters.lane_changing_lateral_jerk, lateral_acc); - const double longitudinal_acc_on_lane_changing = - utils::lane_change::calcLaneChangingAcceleration( - initial_lane_changing_velocity, max_path_velocity, lane_changing_time, - sampled_longitudinal_acc); - const auto lane_changing_length = - initial_lane_changing_velocity * lane_changing_time + - 0.5 * longitudinal_acc_on_lane_changing * lane_changing_time * lane_changing_time; - const auto terminal_lane_changing_velocity = - initial_lane_changing_velocity + longitudinal_acc_on_lane_changing * lane_changing_time; - utils::lane_change::setPrepareVelocity( - prepare_segment, current_velocity, terminal_lane_changing_velocity); - - if (lane_changing_length + prepare_length > dist_to_end_of_current_lanes) { - RCLCPP_DEBUG(logger_, "length of lane changing path is longer than length to goal!!"); - continue; + // Check if the lane changing start point is not on the lanes next to target lanes, + if (target_length_from_lane_change_start_pose > 0.0) { + RCLCPP_DEBUG( + logger_, "[only new arch] lane change start getEgoPose() is behind target lanelet!!"); + break; } - if (is_goal_in_route) { - const double s_start = - lanelet::utils::getArcCoordinates(target_lanes, lane_changing_start_pose).length; - const double s_goal = - lanelet::utils::getArcCoordinates(target_lanes, route_handler.getGoalPose()).length; - const auto num = - std::abs(route_handler.getNumLaneToPreferredLane(target_lanes.back(), direction)); - const double backward_buffer = - num == 0 ? 0.0 : common_parameters.backward_length_buffer_for_end_of_lane; - const double finish_judge_buffer = common_parameters.lane_change_finish_judge_buffer; - if ( - s_start + lane_changing_length + finish_judge_buffer + backward_buffer + - next_lane_change_buffer > - s_goal) { + const auto shift_length = + lanelet::utils::getLateralDistanceToClosestLanelet(target_lanes, lane_changing_start_pose); + + const auto initial_lane_changing_velocity = prepare_velocity; + const auto max_path_velocity = prepare_segment.points.back().point.longitudinal_velocity_mps; + + // get lateral acceleration range + const auto [min_lateral_acc, max_lateral_acc] = + common_parameters.lane_change_lat_acc_map.find(initial_lane_changing_velocity); + const auto lateral_acc_resolution = + std::abs(max_lateral_acc - min_lateral_acc) / lateral_acc_sampling_num; + constexpr double lateral_acc_epsilon = 0.01; + + for (double lateral_acc = min_lateral_acc; + lateral_acc < max_lateral_acc + lateral_acc_epsilon; + lateral_acc += lateral_acc_resolution) { + const auto lane_changing_time = PathShifter::calcShiftTimeFromJerk( + shift_length, common_parameters.lane_changing_lateral_jerk, lateral_acc); + const double longitudinal_acc_on_lane_changing = + utils::lane_change::calcLaneChangingAcceleration( + initial_lane_changing_velocity, max_path_velocity, lane_changing_time, + sampled_longitudinal_acc); + const auto lane_changing_length = + initial_lane_changing_velocity * lane_changing_time + + 0.5 * longitudinal_acc_on_lane_changing * lane_changing_time * lane_changing_time; + const auto terminal_lane_changing_velocity = + initial_lane_changing_velocity + longitudinal_acc_on_lane_changing * lane_changing_time; + utils::lane_change::setPrepareVelocity( + prepare_segment, current_velocity, terminal_lane_changing_velocity); + + if (lane_changing_length + prepare_length > dist_to_end_of_current_lanes) { RCLCPP_DEBUG(logger_, "length of lane changing path is longer than length to goal!!"); continue; } - } - const auto target_segment = getTargetSegment( - target_lanes, lane_changing_start_pose, target_lane_length, lane_changing_length, - initial_lane_changing_velocity, next_lane_change_buffer); + if (is_goal_in_route) { + const double s_start = + lanelet::utils::getArcCoordinates(target_lanes, lane_changing_start_pose).length; + const double s_goal = + lanelet::utils::getArcCoordinates(target_lanes, route_handler.getGoalPose()).length; + const auto num = + std::abs(route_handler.getNumLaneToPreferredLane(target_lanes.back(), direction)); + const double backward_buffer = + num == 0 ? 0.0 : common_parameters.backward_length_buffer_for_end_of_lane; + const double finish_judge_buffer = common_parameters.lane_change_finish_judge_buffer; + if ( + s_start + lane_changing_length + finish_judge_buffer + backward_buffer + + next_lane_change_buffer > + s_goal) { + RCLCPP_DEBUG(logger_, "length of lane changing path is longer than length to goal!!"); + continue; + } + } - if (target_segment.points.empty()) { - RCLCPP_DEBUG(logger_, "target segment is empty!! something wrong..."); - continue; - } + const auto target_segment = getTargetSegment( + target_lanes, lane_changing_start_pose, target_lane_length, lane_changing_length, + initial_lane_changing_velocity, next_lane_change_buffer); - const lanelet::BasicPoint2d lc_start_point( - prepare_segment.points.back().point.pose.position.x, - prepare_segment.points.back().point.pose.position.y); + if (target_segment.points.empty()) { + RCLCPP_DEBUG(logger_, "target segment is empty!! something wrong..."); + continue; + } - const auto target_lane_polygon = lanelet::utils::getPolygonFromArcLength( - target_lanes, 0, std::numeric_limits::max()); - const auto target_lane_poly_2d = lanelet::utils::to2D(target_lane_polygon).basicPolygon(); + const lanelet::BasicPoint2d lc_start_point( + prepare_segment.points.back().point.pose.position.x, + prepare_segment.points.back().point.pose.position.y); - const auto is_valid_start_point = - boost::geometry::covered_by(lc_start_point, target_neighbor_preferred_lane_poly_2d) || - boost::geometry::covered_by(lc_start_point, target_lane_poly_2d); + const auto target_lane_polygon = lanelet::utils::getPolygonFromArcLength( + target_lanes, 0, std::numeric_limits::max()); + const auto target_lane_poly_2d = lanelet::utils::to2D(target_lane_polygon).basicPolygon(); - if (!is_valid_start_point) { - // lane changing points are not inside of the target preferred lanes or its neighbors - continue; - } + const auto is_valid_start_point = + boost::geometry::covered_by(lc_start_point, target_neighbor_preferred_lane_poly_2d) || + boost::geometry::covered_by(lc_start_point, target_lane_poly_2d); - const auto resample_interval = utils::lane_change::calcLaneChangeResampleInterval( - lane_changing_length, initial_lane_changing_velocity); - const auto target_lane_reference_path = utils::lane_change::getReferencePathFromTargetLane( - route_handler, target_lanes, lane_changing_start_pose, target_lane_length, - lane_changing_length, forward_path_length, resample_interval, is_goal_in_route, - next_lane_change_buffer); + if (!is_valid_start_point) { + // lane changing points are not inside of the target preferred lanes or its neighbors + continue; + } - if (target_lane_reference_path.points.empty()) { - RCLCPP_DEBUG(logger_, "target_lane_reference_path is empty!!"); - continue; - } + const auto resample_interval = utils::lane_change::calcLaneChangeResampleInterval( + lane_changing_length, initial_lane_changing_velocity); + const auto target_lane_reference_path = utils::lane_change::getReferencePathFromTargetLane( + route_handler, target_lanes, lane_changing_start_pose, target_lane_length, + lane_changing_length, forward_path_length, resample_interval, is_goal_in_route, + next_lane_change_buffer); - LaneChangeInfo lane_change_info; - lane_change_info.longitudinal_acceleration = - LaneChangePhaseInfo{longitudinal_acc_on_prepare, longitudinal_acc_on_lane_changing}; - lane_change_info.duration = LaneChangePhaseInfo{prepare_duration, lane_changing_time}; - lane_change_info.velocity = - LaneChangePhaseInfo{prepare_velocity, initial_lane_changing_velocity}; - lane_change_info.length = LaneChangePhaseInfo{prepare_length, lane_changing_length}; - lane_change_info.current_lanes = current_lanes; - lane_change_info.target_lanes = target_lanes; - lane_change_info.lane_changing_start = prepare_segment.points.back().point.pose; - lane_change_info.lane_changing_end = target_segment.points.front().point.pose; - lane_change_info.shift_line = utils::lane_change::getLaneChangingShiftLine( - prepare_segment, target_segment, target_lane_reference_path, shift_length); - lane_change_info.lateral_acceleration = lateral_acc; - lane_change_info.terminal_lane_changing_velocity = terminal_lane_changing_velocity; - - const auto candidate_path = utils::lane_change::constructCandidatePath( - lane_change_info, prepare_segment, target_segment, target_lane_reference_path, - sorted_lane_ids); - - if (!candidate_path) { - RCLCPP_DEBUG(logger_, "no candidate path!!"); - continue; - } + if (target_lane_reference_path.points.empty()) { + RCLCPP_DEBUG(logger_, "target_lane_reference_path is empty!!"); + continue; + } - const auto is_valid = - hasEnoughLength(*candidate_path, current_lanes, target_lanes, direction); + LaneChangeInfo lane_change_info; + lane_change_info.longitudinal_acceleration = + LaneChangePhaseInfo{longitudinal_acc_on_prepare, longitudinal_acc_on_lane_changing}; + lane_change_info.duration = LaneChangePhaseInfo{prepare_duration, lane_changing_time}; + lane_change_info.velocity = + LaneChangePhaseInfo{prepare_velocity, initial_lane_changing_velocity}; + lane_change_info.length = LaneChangePhaseInfo{prepare_length, lane_changing_length}; + lane_change_info.current_lanes = current_lanes; + lane_change_info.target_lanes = target_lanes; + lane_change_info.lane_changing_start = prepare_segment.points.back().point.pose; + lane_change_info.lane_changing_end = target_segment.points.front().point.pose; + lane_change_info.shift_line = utils::lane_change::getLaneChangingShiftLine( + prepare_segment, target_segment, target_lane_reference_path, shift_length); + lane_change_info.lateral_acceleration = lateral_acc; + lane_change_info.terminal_lane_changing_velocity = terminal_lane_changing_velocity; + + const auto candidate_path = utils::lane_change::constructCandidatePath( + lane_change_info, prepare_segment, target_segment, target_lane_reference_path, + sorted_lane_ids); + + if (!candidate_path) { + RCLCPP_DEBUG(logger_, "no candidate path!!"); + continue; + } - if (!is_valid) { - RCLCPP_DEBUG(logger_, "invalid candidate path!!"); - continue; - } + if (!hasEnoughLength(*candidate_path, current_lanes, target_lanes, direction)) { + RCLCPP_DEBUG(logger_, "invalid candidate path!!"); + continue; + } - if (utils::lane_change::passParkedObject( - route_handler, *candidate_path, target_objects.target_lane, lane_change_buffer, - is_goal_in_route, *lane_change_parameters_)) { - return false; - } - candidate_paths->push_back(*candidate_path); + if ( + lane_change_parameters_->regulate_on_crosswalk && + !hasEnoughLengthToCrosswalk(*candidate_path, current_lanes)) { + RCLCPP_DEBUG(logger_, "Including crosswalk!!"); + continue; + } - if (!check_safety) { - return false; - } + if ( + lane_change_parameters_->regulate_on_intersection && + !hasEnoughLengthToIntersection(*candidate_path, current_lanes)) { + RCLCPP_DEBUG(logger_, "Including intersection!!"); + continue; + } - const auto [is_safe, is_object_coming_from_rear] = isLaneChangePathSafe( - *candidate_path, target_objects, lane_change_parameters_->rss_params, object_debug_); + if (utils::lane_change::passParkedObject( + route_handler, *candidate_path, target_objects.target_lane, lane_change_buffer, + is_goal_in_route, *lane_change_parameters_)) { + return false; + } + candidate_paths->push_back(*candidate_path); + + if (!check_safety) { + return false; + } - if (is_safe) { - return true; + const auto [is_safe, is_object_coming_from_rear] = isLaneChangePathSafe( + *candidate_path, target_objects, lane_change_parameters_->rss_params, object_debug_); + + if (is_safe) { + return true; + } } } } diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 4a2a5cd4af6b3..a4667e1641ca0 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -3147,15 +3147,12 @@ double calcMinimumLaneChangeLength( const double & max_lateral_acc = lat_acc.second; const double & lateral_jerk = common_param.lane_changing_lateral_jerk; const double & finish_judge_buffer = common_param.lane_change_finish_judge_buffer; - const auto prepare_length = 0.5 * common_param.max_acc * - common_param.lane_change_prepare_duration * - common_param.lane_change_prepare_duration; double accumulated_length = length_to_intersection; for (const auto & shift_interval : shift_intervals) { const double t = PathShifter::calcShiftTimeFromJerk(shift_interval, lateral_jerk, max_lateral_acc); - accumulated_length += vel * t + prepare_length + finish_judge_buffer; + accumulated_length += vel * t + finish_judge_buffer; } accumulated_length += common_param.backward_length_buffer_for_end_of_lane * (shift_intervals.size() - 1.0); From 44fb0fd3b0da4cf39da19216cd5dada4dbcecbb0 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Fri, 22 Sep 2023 17:56:06 +0900 Subject: [PATCH 290/547] feat(ar_tag_based_localizer): split the package `ar_tag_based_localizer` (#5043) * Fix package name Signed-off-by: Shintaro SAKODA * Removed utils Signed-off-by: Shintaro SAKODA * Renamed tag_tf_caster to landmark_tf_caster Signed-off-by: Shintaro SAKODA * Updated node_diagram Signed-off-by: Shintaro SAKODA * Fixed documents Signed-off-by: Shintaro SAKODA * style(pre-commit): autofix * Fixed the directory name Signed-off-by: Shintaro SAKODA * Fixed to split packages Signed-off-by: Shintaro SAKODA * Removed unused package dependency Signed-off-by: Shintaro SAKODA * style(pre-commit): autofix * Fixed directory structure Signed-off-by: Shintaro SAKODA * style(pre-commit): autofix * Fixed ArTagDetector to ArTagBasedLocalizer Signed-off-by: Shintaro SAKODA --------- Signed-off-by: Shintaro SAKODA Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../ar_tag_based_localizer.launch.xml | 4 +- launch/tier4_localization_launch/package.xml | 2 +- .../include/ar_tag_based_localizer/utils.hpp | 66 ---------- .../launch/tag_tf_caster.launch.xml | 12 -- .../ar_tag_based_localizer/src/utils.cpp | 116 ----------------- .../landmark_based_localizer/README.md | 123 ++++++++++++++++++ .../ar_tag_based_localizer/CMakeLists.txt | 16 --- .../ar_tag_based_localizer/README.md | 109 +--------------- .../config/ar_tag_based_localizer.param.yaml | 0 .../doc_image/ar_tag_image.png | Bin .../doc_image/sample_result.png | Bin .../doc_image/sample_result_in_awsim.png | Bin .../ar_tag_based_localizer_core.hpp | 1 + .../launch/ar_tag_based_localizer.launch.xml | 0 .../ar_tag_based_localizer/package.xml | 2 - .../src/ar_tag_based_localizer_core.cpp | 48 ++++++- .../src/ar_tag_based_localizer_node.cpp | 0 .../lanelet2_data_structure.drawio.svg | 6 +- .../doc_image/node_diagram.drawio.svg | 18 +-- .../doc_image/principle.png | Bin .../landmark_tf_caster/CMakeLists.txt | 30 +++++ .../config/landmark_tf_caster.param.yaml} | 0 .../landmark_tf_caster_core.hpp} | 10 +- .../launch/landmark_tf_caster.launch.xml | 12 ++ .../landmark_tf_caster/package.xml | 25 ++++ .../src/landmark_tf_caster_core.cpp} | 10 +- .../src/landmark_tf_caster_node.cpp} | 4 +- 27 files changed, 267 insertions(+), 347 deletions(-) delete mode 100644 localization/ar_tag_based_localizer/include/ar_tag_based_localizer/utils.hpp delete mode 100644 localization/ar_tag_based_localizer/launch/tag_tf_caster.launch.xml delete mode 100644 localization/ar_tag_based_localizer/src/utils.cpp create mode 100644 localization/landmark_based_localizer/README.md rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/CMakeLists.txt (74%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/README.md (52%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml (100%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/doc_image/ar_tag_image.png (100%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/doc_image/sample_result.png (100%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png (100%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp (98%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml (100%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/package.xml (92%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp (89%) rename localization/{ => landmark_based_localizer}/ar_tag_based_localizer/src/ar_tag_based_localizer_node.cpp (100%) rename localization/{ar_tag_based_localizer => landmark_based_localizer}/doc_image/lanelet2_data_structure.drawio.svg (91%) rename localization/{ar_tag_based_localizer => landmark_based_localizer}/doc_image/node_diagram.drawio.svg (85%) rename localization/{ar_tag_based_localizer => landmark_based_localizer}/doc_image/principle.png (100%) create mode 100644 localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt rename localization/{ar_tag_based_localizer/config/tag_tf_caster.param.yaml => landmark_based_localizer/landmark_tf_caster/config/landmark_tf_caster.param.yaml} (100%) rename localization/{ar_tag_based_localizer/include/ar_tag_based_localizer/tag_tf_caster_core.hpp => landmark_based_localizer/landmark_tf_caster/include/landmark_tf_caster/landmark_tf_caster_core.hpp} (84%) create mode 100644 localization/landmark_based_localizer/landmark_tf_caster/launch/landmark_tf_caster.launch.xml create mode 100644 localization/landmark_based_localizer/landmark_tf_caster/package.xml rename localization/{ar_tag_based_localizer/src/tag_tf_caster_core.cpp => landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_core.cpp} (93%) rename localization/{ar_tag_based_localizer/src/tag_tf_caster_node.cpp => landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_node.cpp} (85%) diff --git a/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml b/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml index 642edb4f68ab8..a767d2d253cff 100644 --- a/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml +++ b/launch/tier4_localization_launch/launch/pose_twist_estimator/ar_tag_based_localizer.launch.xml @@ -14,9 +14,9 @@
    - + - +
    diff --git a/launch/tier4_localization_launch/package.xml b/launch/tier4_localization_launch/package.xml index f00752d1c14fc..7991ffb0d8adf 100644 --- a/launch/tier4_localization_launch/package.xml +++ b/launch/tier4_localization_launch/package.xml @@ -14,13 +14,13 @@ ament_cmake_auto autoware_cmake - ar_tag_based_localizer automatic_pose_initializer eagleye_fix2pose eagleye_gnss_converter eagleye_rt ekf_localizer gyro_odometer + landmark_based_localizer ndt_scan_matcher pointcloud_preprocessor pose_initializer diff --git a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/utils.hpp b/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/utils.hpp deleted file mode 100644 index 06401dad8fdd1..0000000000000 --- a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/utils.hpp +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2023 Autoware Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This source code is derived from the https://github.com/pal-robotics/aruco_ros. -// Here is the license statement. -/***************************** - Copyright 2011 Rafael Muñoz Salinas. All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are - permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - The views and conclusions contained in the software and documentation are those of the - authors and should not be interpreted as representing official policies, either expressed - or implied, of Rafael Muñoz Salinas. - ********************************/ - -#ifndef AR_TAG_BASED_LOCALIZER__UTILS_HPP_ -#define AR_TAG_BASED_LOCALIZER__UTILS_HPP_ - -#include - -#include -#include - -/** - * @brief ros_camera_info_to_aruco_cam_params gets the camera intrinsics from a CameraInfo message - * and copies them to aruco's data structure - * @param cam_info - * @param use_rectified_parameters if true, the intrinsics are taken from cam_info.P and the - * distortion parameters are set to 0. Otherwise, cam_info.K and cam_info.D are taken. - * @return - */ -aruco::CameraParameters ros_camera_info_to_aruco_cam_params( - const sensor_msgs::msg::CameraInfo & cam_info, bool use_rectified_parameters); - -tf2::Transform aruco_marker_to_tf2(const aruco::Marker & marker); - -#endif // AR_TAG_BASED_LOCALIZER__UTILS_HPP_ diff --git a/localization/ar_tag_based_localizer/launch/tag_tf_caster.launch.xml b/localization/ar_tag_based_localizer/launch/tag_tf_caster.launch.xml deleted file mode 100644 index 6a9c3dcd17e2e..0000000000000 --- a/localization/ar_tag_based_localizer/launch/tag_tf_caster.launch.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/localization/ar_tag_based_localizer/src/utils.cpp b/localization/ar_tag_based_localizer/src/utils.cpp deleted file mode 100644 index 9f582830280d9..0000000000000 --- a/localization/ar_tag_based_localizer/src/utils.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2023 Autoware Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This source code is derived from the https://github.com/pal-robotics/aruco_ros. -// Here is the license statement. -/***************************** - Copyright 2011 Rafael Muñoz Salinas. All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are - permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY Rafael Muñoz Salinas ''AS IS'' AND ANY EXPRESS OR IMPLIED - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Rafael Muñoz Salinas OR - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - The views and conclusions contained in the software and documentation are those of the - authors and should not be interpreted as representing official policies, either expressed - or implied, of Rafael Muñoz Salinas. - ********************************/ - -#include "ar_tag_based_localizer/utils.hpp" - -#include -#include - -aruco::CameraParameters ros_camera_info_to_aruco_cam_params( - const sensor_msgs::msg::CameraInfo & cam_info, bool use_rectified_parameters) -{ - cv::Mat camera_matrix(3, 4, CV_64FC1, 0.0); - cv::Mat distortion_coeff(4, 1, CV_64FC1); - cv::Size size(static_cast(cam_info.width), static_cast(cam_info.height)); - - if (use_rectified_parameters) { - camera_matrix.setTo(0); - camera_matrix.at(0, 0) = cam_info.p[0]; - camera_matrix.at(0, 1) = cam_info.p[1]; - camera_matrix.at(0, 2) = cam_info.p[2]; - camera_matrix.at(0, 3) = cam_info.p[3]; - camera_matrix.at(1, 0) = cam_info.p[4]; - camera_matrix.at(1, 1) = cam_info.p[5]; - camera_matrix.at(1, 2) = cam_info.p[6]; - camera_matrix.at(1, 3) = cam_info.p[7]; - camera_matrix.at(2, 0) = cam_info.p[8]; - camera_matrix.at(2, 1) = cam_info.p[9]; - camera_matrix.at(2, 2) = cam_info.p[10]; - camera_matrix.at(2, 3) = cam_info.p[11]; - - for (int i = 0; i < 4; ++i) { - distortion_coeff.at(i, 0) = 0; - } - } else { - cv::Mat camera_matrix_from_k(3, 3, CV_64FC1, 0.0); - for (int i = 0; i < 9; ++i) { - camera_matrix_from_k.at(i % 3, i - (i % 3) * 3) = cam_info.k[i]; - } - camera_matrix_from_k.copyTo(camera_matrix(cv::Rect(0, 0, 3, 3))); - - if (cam_info.d.size() == 4) { - for (int i = 0; i < 4; ++i) { - distortion_coeff.at(i, 0) = cam_info.d[i]; - } - } else { - RCLCPP_WARN( - rclcpp::get_logger("ar_tag_based_localizer"), - "length of camera_info D vector is not 4, assuming zero distortion..."); - for (int i = 0; i < 4; ++i) { - distortion_coeff.at(i, 0) = 0; - } - } - } - - return {camera_matrix, distortion_coeff, size}; -} - -tf2::Transform aruco_marker_to_tf2(const aruco::Marker & marker) -{ - cv::Mat rot(3, 3, CV_64FC1); - cv::Mat r_vec64; - marker.Rvec.convertTo(r_vec64, CV_64FC1); - cv::Rodrigues(r_vec64, rot); - cv::Mat tran64; - marker.Tvec.convertTo(tran64, CV_64FC1); - - tf2::Matrix3x3 tf_rot( - rot.at(0, 0), rot.at(0, 1), rot.at(0, 2), rot.at(1, 0), - rot.at(1, 1), rot.at(1, 2), rot.at(2, 0), rot.at(2, 1), - rot.at(2, 2)); - - tf2::Vector3 tf_orig(tran64.at(0, 0), tran64.at(1, 0), tran64.at(2, 0)); - - return tf2::Transform(tf_rot, tf_orig); -} diff --git a/localization/landmark_based_localizer/README.md b/localization/landmark_based_localizer/README.md new file mode 100644 index 0000000000000..a8ebdc0c8f1e5 --- /dev/null +++ b/localization/landmark_based_localizer/README.md @@ -0,0 +1,123 @@ +# Landmark Based Localizer + +This directory contains packages for landmark-based localization. + +Landmarks are, for example + +- AR tags detected by camera +- Boards characterized by intensity detected by LiDAR + +etc. + +Since these landmarks are easy to detect and estimate pose, the ego pose can be calculated from the pose of the detected landmark if the pose of the landmark is written on the map in advance. + +Currently, landmarks are assumed to be flat. + +The following figure shows the principle of localization in the case of `ar_tag_based_localizer`. + +![principle](./doc_image/principle.png) + +This calculated ego pose is passed to the EKF, where it is fused with the twist information and used to estimate a more accurate ego pose. + +## Node diagram + +![node diagram](./doc_image/node_diagram.drawio.svg) + +### `landmark_tf_caster` node + +The definitions of the landmarks written to the map are introduced in the next section. See `Map Specifications`. + +The `landmark_tf_caster` node publishes the TF from the map to the landmark. + +- Translation : The center of the four vertices of the landmark +- Rotation : Let the vertex numbers be 1, 2, 3, 4 counterclockwise as shown in the next section. Direction is defined as the cross product of the vector from 1 to 2 and the vector from 2 to 3. + +Users can define landmarks as Lanelet2 4-vertex polygons. +In this case, it is possible to define an arrangement in which the four vertices cannot be considered to be on the same plane. The direction of the landmark in that case is difficult to calculate. +So, if the 4 vertices are considered as forming a tetrahedron and its volume exceeds the `volume_threshold` parameter, the landmark will not publish tf_static. + +### Landmark based localizer packages + +- ar_tag_based_localizer +- etc. + +## Inputs / Outputs + +### `landmark_tf_caster` node + +#### Input + +| Name | Type | Description | +| :--------------------- | :------------------------------------------- | :--------------- | +| `~/input/lanelet2_map` | `autoware_auto_mapping_msgs::msg::HADMapBin` | Data of lanelet2 | + +#### Output + +| Name | Type | Description | +| :---------- | :------------------------------------- | :----------------- | +| `tf_static` | `geometry_msgs::msg::TransformStamped` | TF from map to tag | + +## Map specifications + +For this package to work correctly, the poses of the landmarks must be specified in the Lanelet2 map format that `map_loader` and `landmark_tf_caster` can interpret. + +The four vertices of a landmark are defined counterclockwise. + +The order of the four vertices is defined as follows. In the coordinate system of a landmark, + +- the x-axis is parallel to the vector from the first vertex to the second vertex +- the y-axis is parallel to the vector from the second vertex to the third vertex + +![lanelet2 data structure](./doc_image/lanelet2_data_structure.drawio.svg) + +### Example of `lanelet2_map.osm` + +The values provided below are placeholders. +Ensure to input the correct coordinates corresponding to the actual location where the landmark is placed, such as `lat`, `lon`, `mgrs_code`, `local_x`, `local_y`. + +For example, when using the AR tag, it would look like this. + +```xml +... + + + + + + + + + + + + + + + + + + + + + + + + + + +... + + + + + + + + + + + + +... + +``` diff --git a/localization/ar_tag_based_localizer/CMakeLists.txt b/localization/landmark_based_localizer/ar_tag_based_localizer/CMakeLists.txt similarity index 74% rename from localization/ar_tag_based_localizer/CMakeLists.txt rename to localization/landmark_based_localizer/ar_tag_based_localizer/CMakeLists.txt index 89337b9bb966c..15aecd1f8ded9 100644 --- a/localization/ar_tag_based_localizer/CMakeLists.txt +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/CMakeLists.txt @@ -14,13 +14,9 @@ ament_auto_find_build_dependencies() find_package(OpenCV REQUIRED) -# -# ar_tag_based_localizer -# ament_auto_add_executable(ar_tag_based_localizer src/ar_tag_based_localizer_node.cpp src/ar_tag_based_localizer_core.cpp - src/utils.cpp ) target_include_directories(ar_tag_based_localizer SYSTEM PUBLIC @@ -28,18 +24,6 @@ target_include_directories(ar_tag_based_localizer ) target_link_libraries(ar_tag_based_localizer ${OpenCV_LIBRARIES}) -# -# tag_tf_caster -# -ament_auto_add_executable(tag_tf_caster - src/tag_tf_caster_node.cpp - src/tag_tf_caster_core.cpp -) -target_include_directories(tag_tf_caster - SYSTEM PUBLIC - ${OpenCV_INCLUDE_DIRS} -) - ament_auto_package( INSTALL_TO_SHARE launch diff --git a/localization/ar_tag_based_localizer/README.md b/localization/landmark_based_localizer/ar_tag_based_localizer/README.md similarity index 52% rename from localization/ar_tag_based_localizer/README.md rename to localization/landmark_based_localizer/ar_tag_based_localizer/README.md index 81bc11a7cbc9b..8c51febc6f60c 100644 --- a/localization/ar_tag_based_localizer/README.md +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/README.md @@ -1,17 +1,12 @@ # AR Tag Based Localizer -**ArTagBasedLocalizer** is a vision-based localization package. +**ArTagBasedLocalizer** is a vision-based localization node. -This package uses [the ArUco library](https://index.ros.org/p/aruco/) to detect AR-Tags from camera images and calculates and publishes the pose of the ego vehicle based on these detections. +This node uses [the ArUco library](https://index.ros.org/p/aruco/) to detect AR-Tags from camera images and calculates and publishes the pose of the ego vehicle based on these detections. The positions and orientations of the AR-Tags are assumed to be written in the Lanelet2 format. -This package includes two nodes. - -- `ar_tag_based_localizer` : A node that detects AR-Tags from camera images and publishes the pose of the ego vehicle. -- `tag_tf_caster` : A node that publishes the pose of the AR-Tags applied in Lanelet2 as `tf_static`. - ## Inputs / Outputs ### `ar_tag_based_localizer` node @@ -32,23 +27,9 @@ This package includes two nodes. | `~/debug/result` | `sensor_msgs::msg::Image` | [debug topic] Image in which marker detection results are superimposed on the input image | | `tf` | `geometry_msgs::msg::TransformStamped` | [debug topic] TF from camera to detected tag | -### `tag_tf_caster` node - -#### Input - -| Name | Type | Description | -| :--------------------- | :------------------------------------------- | :--------------- | -| `~/input/lanelet2_map` | `autoware_auto_mapping_msgs::msg::HADMapBin` | Data of lanelet2 | - -#### Output - -| Name | Type | Description | -| :---------- | :------------------------------------- | :----------------- | -| `tf_static` | `geometry_msgs::msg::TransformStamped` | TF from map to tag | - ## How to launch -When launching Autoware, specify `artag` for `pose_source`. +When launching Autoware, set `artag` for `pose_source`. ```bash ros2 launch autoware_launch ... \ @@ -87,88 +68,6 @@ The pull request video below should also be helpful. -## Architecture - -![node diagram](./doc_image/node_diagram.drawio.svg) - ## Principle -### `ar_tag_based_localizer` node - -![principle](./doc_image/principle.png) - -### `tag_tf_caster` node - -The definitions of the tags written to the map are introduced in the next section. See `Map Specifications`. - -The `tag_tf_caster` node publishes the TF from the map to the tag. - -- Translation : The center of the four vertices of the tag -- Rotation : Let the vertex numbers be 1, 2, 3, 4 counterclockwise as shown in the next section. Direction is defined as the cross product of the vector from 1 to 2 and the vector from 2 to 3. - -Users can define tags as Lanelet2 4-vertex polygons. -In this case, it is possible to define an arrangement in which the four vertices cannot be considered to be on the same plane. The direction of the tag in that case is difficult to calculate. -So, if the 4 vertices are considered as forming a tetrahedron and its volume exceeds the `volume_threshold` parameter, the tag will not publish tf_static. - -## Map specifications - -For this package to function correctly, the pose of the AR-Tags must be specified in the Lanelet2 map format that Autoware can interpret. - -The four vertices of AR-Tag are defined counterclockwise. - -The order of the four vertices is defined as follows. In the coordinate system of AR-Tag, - -- the x-axis is parallel to the vector from the first vertex to the second vertex -- the y-axis is parallel to the vector from the second vertex to the third vertex - -![lanelet2 data structure](./doc_image/lanelet2_data_structure.drawio.svg) - -### example of `lanelet2_map.osm` - -The values provided below are placeholders. -Ensure to input the correct coordinates corresponding to the actual location where the AR-Tag is placed, such as `lat`, `lon`, `mgrs_code`, `local_x`, `local_y`. - -```xml -... - - - - - - - - - - - - - - - - - - - - - - - - - - -... - - - - - - - - - - - - -... - -``` +![principle](../doc_image/principle.png) diff --git a/localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml b/localization/landmark_based_localizer/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml similarity index 100% rename from localization/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml rename to localization/landmark_based_localizer/ar_tag_based_localizer/config/ar_tag_based_localizer.param.yaml diff --git a/localization/ar_tag_based_localizer/doc_image/ar_tag_image.png b/localization/landmark_based_localizer/ar_tag_based_localizer/doc_image/ar_tag_image.png similarity index 100% rename from localization/ar_tag_based_localizer/doc_image/ar_tag_image.png rename to localization/landmark_based_localizer/ar_tag_based_localizer/doc_image/ar_tag_image.png diff --git a/localization/ar_tag_based_localizer/doc_image/sample_result.png b/localization/landmark_based_localizer/ar_tag_based_localizer/doc_image/sample_result.png similarity index 100% rename from localization/ar_tag_based_localizer/doc_image/sample_result.png rename to localization/landmark_based_localizer/ar_tag_based_localizer/doc_image/sample_result.png diff --git a/localization/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png b/localization/landmark_based_localizer/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png similarity index 100% rename from localization/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png rename to localization/landmark_based_localizer/ar_tag_based_localizer/doc_image/sample_result_in_awsim.png diff --git a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp b/localization/landmark_based_localizer/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp similarity index 98% rename from localization/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp rename to localization/landmark_based_localizer/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp index 65c1283a82728..6af9fefae23f7 100644 --- a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp @@ -72,6 +72,7 @@ class ArTagBasedLocalizer : public rclcpp::Node void ekf_pose_callback(const geometry_msgs::msg::PoseWithCovarianceStamped & msg); void publish_pose_as_base_link( const geometry_msgs::msg::PoseStamped & msg, const std::string & camera_frame_id); + static tf2::Transform aruco_marker_to_tf2(const aruco::Marker & marker); // Parameters float marker_size_{}; diff --git a/localization/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml b/localization/landmark_based_localizer/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml similarity index 100% rename from localization/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml rename to localization/landmark_based_localizer/ar_tag_based_localizer/launch/ar_tag_based_localizer.launch.xml diff --git a/localization/ar_tag_based_localizer/package.xml b/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml similarity index 92% rename from localization/ar_tag_based_localizer/package.xml rename to localization/landmark_based_localizer/ar_tag_based_localizer/package.xml index 4527d2ad9b086..e68fd21ed340a 100644 --- a/localization/ar_tag_based_localizer/package.xml +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml @@ -12,11 +12,9 @@ autoware_cmake aruco - autoware_auto_mapping_msgs cv_bridge geometry_msgs image_transport - lanelet2_extension rclcpp sensor_msgs tf2_eigen diff --git a/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp b/localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp similarity index 89% rename from localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp rename to localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp index cf0236c1a1e48..f664ef6378a69 100644 --- a/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp @@ -44,12 +44,12 @@ #include "ar_tag_based_localizer/ar_tag_based_localizer_core.hpp" -#include "ar_tag_based_localizer/utils.hpp" - #include #include +#include #include +#include #ifdef ROS_DISTRO_GALACTIC #include @@ -208,7 +208,30 @@ void ArTagBasedLocalizer::cam_info_callback(const sensor_msgs::msg::CameraInfo & return; } - cam_param_ = ros_camera_info_to_aruco_cam_params(msg, true); + cv::Mat camera_matrix(3, 4, CV_64FC1, 0.0); + cv::Mat distortion_coeff(4, 1, CV_64FC1); + cv::Size size(static_cast(msg.width), static_cast(msg.height)); + + camera_matrix.setTo(0); + camera_matrix.at(0, 0) = msg.p[0]; + camera_matrix.at(0, 1) = msg.p[1]; + camera_matrix.at(0, 2) = msg.p[2]; + camera_matrix.at(0, 3) = msg.p[3]; + camera_matrix.at(1, 0) = msg.p[4]; + camera_matrix.at(1, 1) = msg.p[5]; + camera_matrix.at(1, 2) = msg.p[6]; + camera_matrix.at(1, 3) = msg.p[7]; + camera_matrix.at(2, 0) = msg.p[8]; + camera_matrix.at(2, 1) = msg.p[9]; + camera_matrix.at(2, 2) = msg.p[10]; + camera_matrix.at(2, 3) = msg.p[11]; + + for (int i = 0; i < 4; ++i) { + distortion_coeff.at(i, 0) = 0; + } + + cam_param_ = aruco::CameraParameters(camera_matrix, distortion_coeff, size); + cam_info_received_ = true; } @@ -324,3 +347,22 @@ void ArTagBasedLocalizer::publish_pose_as_base_link( pose_pub_->publish(pose_with_covariance_stamped); } + +tf2::Transform ArTagBasedLocalizer::aruco_marker_to_tf2(const aruco::Marker & marker) +{ + cv::Mat rot(3, 3, CV_64FC1); + cv::Mat r_vec64; + marker.Rvec.convertTo(r_vec64, CV_64FC1); + cv::Rodrigues(r_vec64, rot); + cv::Mat tran64; + marker.Tvec.convertTo(tran64, CV_64FC1); + + tf2::Matrix3x3 tf_rot( + rot.at(0, 0), rot.at(0, 1), rot.at(0, 2), rot.at(1, 0), + rot.at(1, 1), rot.at(1, 2), rot.at(2, 0), rot.at(2, 1), + rot.at(2, 2)); + + tf2::Vector3 tf_orig(tran64.at(0, 0), tran64.at(1, 0), tran64.at(2, 0)); + + return tf2::Transform(tf_rot, tf_orig); +} diff --git a/localization/ar_tag_based_localizer/src/ar_tag_based_localizer_node.cpp b/localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_node.cpp similarity index 100% rename from localization/ar_tag_based_localizer/src/ar_tag_based_localizer_node.cpp rename to localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_node.cpp diff --git a/localization/ar_tag_based_localizer/doc_image/lanelet2_data_structure.drawio.svg b/localization/landmark_based_localizer/doc_image/lanelet2_data_structure.drawio.svg similarity index 91% rename from localization/ar_tag_based_localizer/doc_image/lanelet2_data_structure.drawio.svg rename to localization/landmark_based_localizer/doc_image/lanelet2_data_structure.drawio.svg index bc8ba297558bb..5895d8ef65073 100644 --- a/localization/ar_tag_based_localizer/doc_image/lanelet2_data_structure.drawio.svg +++ b/localization/landmark_based_localizer/doc_image/lanelet2_data_structure.drawio.svg @@ -6,7 +6,7 @@ width="336px" height="336px" viewBox="-0.5 -0.5 336 336" - content="<mxfile><diagram id="z_GJx55S16-dNHi1224P" name="ページ1">5ZhNj5swEIZ/DVJ7wzYBcsymu+2lUtVU6tmCCVg1ODLOhuyvr1kMBDsROdBk1eQQ4ddfzDPj8SQeWRf1V0l3+XeRAvewn9Ye+eJhHC1i/d0Ix1Ygy6gVMsnSVkKDsGFvYETfqHuWQjUaqITgiu3GYiLKEhI10qiU4jAethV8vOuOZuAIm4RyV/3NUpW3aoyjQf8GLMu7nVG4bHsK2g02llQ5TcXhRCLPHllLIVT7VNRr4A27jks77+VCb/9iEkp1zYRg0c54pXxvjDMvpo6dtVLsyxSaCb5Hng45U7DZ0aTpPWj3ai1XBdctpB/NciAV1BffCfWW6ggBUYCSRz2kmxAaOCY6UAfrMLDux+SnnDuRGv9m/doDAv1gKFwgEjpEkINET9GxBtM4aLVrA3DL6gbhLHyCMR8cu3zwGTx4DjqRQ8cNmLvSIfiOdGKHDvnYdJB/QzpLh07wsejYJ+uWdBb+dCaGMl0115duJZxWFUvOJV9IndtrEsCJgYszBnaaBE4Vex0vf85qs8MPwfTGPd/YOpq+xa0Se5mAmXR6a1nrBP7EQorKDJSz0LsPequvcwty3FI7ftERp8aeqJQUf2AtuJBaKUXZRPSWcW5JlLOsbNypHQRaf2ril+lKY2U6Cpam/NJxGN/MW1EqUyuhcKZ0EVkHInIPBDkTL2SOA4Ed8sfHId/Xuscu5G8Hnvz/mQhbmZ7YpeO1qcheKPh3mSi4wi33vD2xXVvcsPJauFX7jHBO04enz9j7Zy5okQUtcqCFLrNwBmRuKf/2OPkVLS3uIbldgnV/Jqx+6vYvmn16kdrWz4/jh3jymkNoHjfo5vC/Spt/hz+nyPNf</diagram></mxfile>" + content="<mxfile><diagram id="z_GJx55S16-dNHi1224P" name="ページ1">5ZhNb6MwEIZ/DdLuDWwC9Nhm2+5hV1ophz1beAJWDY6Mk5D++jXFfNmJkgNNqk0OEbz+wPPMeDzg4WVRv0qyyX8LCtxDPq09/MNDCCeJ/m+EQytEKGyFTDLaSsEgrNg7GNE36pZRqCYdlRBcsc1UTEVZQqomGpFS7Kfd1oJPn7ohGTjCKiXcVf8yqvJWTVA86D+BZXn35CB6aFsK0nU2llQ5oWI/kvCzh5dSCNVeFfUSeMOu49KOeznR2i9MQqkuGRAu2hE7wrfGOLMwdeislWJbUmgG+B5+2udMwWpD0qZ1r92rtVwVXN8F+tJdgFnTDqSCeiSZBb2CKEDJg+7StUYGjomOoIO1H1j3ffIx504kxr9ZP/eAQF8YCieIRA6RwEGih+hYg/M4SLVpA3DN6gbhLHzCKR+UuHzQETxoDjqxQ8cNmJvSweiGdBKHDv7adAL/inQeHDrh16Jj76xr0ln45zMxlPSxOb70XcpJVbH0wuQLdHKguQBGBi6OGNhpEjhRbDfNi8esNk/4I5heSc83sbamb3GrxFamYAaNTy1rntA/M5EiMgPlTPThg97qy9wSOG6pHb/o8FJTT1RKijdYCi6kVkpRNhG9ZpxbEuEsKxt3ao+B1p+aYGW60ng0DQWjlJ/aDtOTeS1KZWqlIJopXcTWhojdDYGPxAueY0Mgh/zhfsj3te6hC/nrgcf/fyZCVqbHdul4aSqyJwo/LxOFF7jllqcnsmuLK1ZeC7dqnxHOOH14eo99/OaCFlvQYgda5DKLZkDmlvLv95NfgweLe4Svl2Dd14RfpKQFkW/fXqS29fv9+CE5e8wFwTxu0LfDd5U2/w4fp/DzPw==</diagram></mxfile>" > @@ -138,12 +138,12 @@
    - AR Tag(Front) + Landmark(Front)
    - AR Tag(Front) + Landmark(Front)
    diff --git a/localization/ar_tag_based_localizer/doc_image/node_diagram.drawio.svg b/localization/landmark_based_localizer/doc_image/node_diagram.drawio.svg similarity index 85% rename from localization/ar_tag_based_localizer/doc_image/node_diagram.drawio.svg rename to localization/landmark_based_localizer/doc_image/node_diagram.drawio.svg index e41ca0ad019ce..120a1f2aa3391 100644 --- a/localization/ar_tag_based_localizer/doc_image/node_diagram.drawio.svg +++ b/localization/landmark_based_localizer/doc_image/node_diagram.drawio.svg @@ -6,20 +6,20 @@ width="721px" height="331px" viewBox="-0.5 -0.5 721 331" - content="<mxfile><diagram id="z_GJx55S16-dNHi1224P" name="ページ1">1ZlLc5swEIB/jY/1WAiwfaydOD2k00yTmbYnRgYBmmDECPnVX1/JCDBIjomNkykHBy0SSN+u9qEM4Hy1e2Aoi7/TACcDaxTsBvBuYFnTiSt+pWBfCGygBBEjQSECteCZ/MVKOFLSNQlw3ujIKU04yZpCn6Yp9nlDhhij22a3kCbNr2Yowprg2UeJLv1FAh4X0ok1ruXfMIni8svAnRZPVqjsrFaSxyig2yMRvB/AOaOUF3er3Rwnkl3JpRi3OPG0mhjDKe8ywCoGbFCyVmtT8+L7crGMrtMAy/6jAZyFNOVKIcBS7TlNKDt0hqPDJeT6RNTcNphxvDsSqYk9YLrCnO1FF/XUchQkZSVQNbc18soi4iPcrpIhpeWoenMNQtwoFmYutsbl60/RfkGR+J2hXPCwhM1CINf6SIVpCCbsHDuSJEes5vN7ZyHmM8s5o6/46InrT/AyvC1d6Dbp2jrdqQGu3QNcR4P7g8eS3ujrmtOtWNUR3Az5r2I/5e9lu1jM5ya2E2sJXffGlvt5bOHYsKMXfEty7m0Jjz2fbhAjKPWxRhQHwsupJmU8phFNUXJfS2cCCdv/VsgPjT+yMRw7Zftud/z0bq9aJ6FyxCKsRFDJ5DzexMxwgjjZNL2xiZoa+kSJ+OxJ04f2pPmKnK6Zj9WoFvtqGp3UUfr5pjrISgaYt/CnNH2Lt9ULbzD+GN42dBq8rXHL0ItJaby1F1mjpuIsCG+muIlJcRnN8bu2UanHHeEHNQ4d1ZJ6BOq+1qFs7I8V2qvuCzpN3V+0/5Q6voyGQFzFoGttZNy0EQjfayNlRxqGOb5W/cDoRhHzOIq8pYz/XnJl1D8Vf/Rs4HD1E5ns9g6y9NAEHENscvqITcAEFb+Gl7NUUf5TWLrt/BR8JEujf/LRCjPkkTSk14SXfsL5R4UXLSo43cK5IU41XwQmTicfdIF/saFBfecV1sm1w6muC7v31KqzoU61lT6iFCeY6ytGeVaU6yHZyT0/yzAj4otY7l7xWlHf46daNBM+I0rFI1/gOAiqcloaaYDyuPIcuagjSBq90EwIoBAUOVj1946sRGm3SMhS+nlfIvACwsR0qFzvIkAcSb+fD/NN1I//mLSszZLZQduBmPyHPb1eLbZehMk6IfRyLvTvdzHGii/oapllgXNbL9EZgTE3lwFeYPBRzv+vgr4d2gGYGuzpZgHJ1o9MevNotiG6lNr7BLsxpjGJcmreCmUin0HBxcnM5xxZtK3H6I1uZz2uialMDd9dcp05uQDNVOdcpmOu3KpizVi5GQ3zfNg+mUL1fwAybmWul2ZMbvskxRkP+8qZRLM+By+61/9MgPf/AA==</diagram></mxfile>" + content="<mxfile><diagram id="z_GJx55S16-dNHi1224P" name="ページ1">1VnLjpswFP2aLBsFG0iybF7TxVQdaSq1XSEHDFgDGBnn1a+vHQwE8ExIQiYqm+BjA/Y51/fhDOA83j8xlIbfqYejARh5+wFcDAAwgA3Ej0QOOWIDMwcCRjw1qAJeyV+swJFCN8TDWW0gpzTiJK2DLk0S7PIahhiju/own0b1r6YowC3g1UVRG/1FPB7m6ASMK/wbJkFYfNmwp3lPjIrBaiVZiDy6O4HgcgDnjFKe38X7OY4keQUv+XOrd3rLiTGc8C4PKCG2KNqotal58UOxWEY3iYfl+NEAznyacCWIAVR7TiPKjoPh6HgJvD0RNbctZhzvTyA1sSdMY8zZQQxRvcBSJCkrgaq5qygvsfCEblthSKkclG+uiBA3igs9L2aLl2eUeDFibwKdoUzwAWyxXkOu9ZkK0xCcsHPckSg64Wo+X1orMZ9Zxhl9wyc9tjvBa/++7MJxnV2zza4x0rBr9sCu1WL3Bw8lfaOvG053Ylkn7KbIfRMbKruU3NVqPteROwFraNt3Nl37ceTCsWZPr/iOZNzZER46Lt0iRlDi4hal2BN+TjUp4yENaIKiZYXOBCfs8Ftxfmz8kY3h2Crai/1p7+KgWu+yyhELsIKgwuQ8PuSZ4Qhxsq37Yx1r6tEXSsRnK+Nv6APNSf0VGd0wF6unGtyX0+gkR+Hp63KQWIaYj+hPaPIR36AXvo3x5/BtQqvGNxg3DD2fVIvv1ovAqC4cgPBuwk10wqU0wxdto0LHPeFHGYeWakkdDXVfaSgbh1NBe9U+Z6eu/VX7T8nxZTQ0xJU/dKuNjOs2AuGlNlIMpL6f4VvlN7RuFDGHo8BZywzAiW6M++8FoHY+cLz6CU1mcwcBTWyyNLHJ6iM2GTpS8Zt/PZcqzD+ES7uZoRqfyaXWP7koxgw5JPHpLeGln3D+WeGlFRWsbuFcE6fqLzImVicfdIV/MaFGvvOCdXLtcNrWwuw9tepsqNPWSkUxhSPM2ytGWZoX7D7Zyz0/SzEj4otY7l7xWlHh45cKmgmfESSiyxV0HIGyoJZG6qEsLD1HJgoJkgQ/aSoAKIA8Byt/FyQOxFIispZ+3pUUOB5hYjpUrnflIY6k38+G2Tbox39MGtYGZHbQdCA6/2FOb5fFbFdhsk7wnYwL/d0uxljya3S1zKLAua+X6EyBNjePVKXvCC5clPH/q65vxnfDmGqM6m5RyWyfnPTm1kxNiCkkfIDxaHOZSHk2J0apSGqQd3VG85iDi6b1aF3S/azH1nEq88OL664zxxdGPd85l+7oy7eyYtOWb91C9QPTpuYRILw2bbKbxynWeNhX4iSa1XF4Prz6UwEu/wE=</diagram></mxfile>" > - - + + - AR Tag Based - Localizer + Landmark Based + Localizer - + - Other Autoware - packages + Other Autoware + packages @@ -74,7 +74,7 @@ - /tag_tf_caster + /landmark_tf_caster diff --git a/localization/ar_tag_based_localizer/doc_image/principle.png b/localization/landmark_based_localizer/doc_image/principle.png similarity index 100% rename from localization/ar_tag_based_localizer/doc_image/principle.png rename to localization/landmark_based_localizer/doc_image/principle.png diff --git a/localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt b/localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt new file mode 100644 index 0000000000000..47899d716e411 --- /dev/null +++ b/localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.14) +project(landmark_tf_caster) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +find_package(OpenCV REQUIRED) + +ament_auto_add_executable(landmark_tf_caster + src/landmark_tf_caster_node.cpp + src/landmark_tf_caster_core.cpp +) +target_include_directories(landmark_tf_caster + SYSTEM PUBLIC + ${OpenCV_INCLUDE_DIRS} +) + +ament_auto_package( + INSTALL_TO_SHARE + launch + config +) diff --git a/localization/ar_tag_based_localizer/config/tag_tf_caster.param.yaml b/localization/landmark_based_localizer/landmark_tf_caster/config/landmark_tf_caster.param.yaml similarity index 100% rename from localization/ar_tag_based_localizer/config/tag_tf_caster.param.yaml rename to localization/landmark_based_localizer/landmark_tf_caster/config/landmark_tf_caster.param.yaml diff --git a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/tag_tf_caster_core.hpp b/localization/landmark_based_localizer/landmark_tf_caster/include/landmark_tf_caster/landmark_tf_caster_core.hpp similarity index 84% rename from localization/ar_tag_based_localizer/include/ar_tag_based_localizer/tag_tf_caster_core.hpp rename to localization/landmark_based_localizer/landmark_tf_caster/include/landmark_tf_caster/landmark_tf_caster_core.hpp index 5b10fe5b1dc0f..0eb6706a0b5b0 100644 --- a/localization/ar_tag_based_localizer/include/ar_tag_based_localizer/tag_tf_caster_core.hpp +++ b/localization/landmark_based_localizer/landmark_tf_caster/include/landmark_tf_caster/landmark_tf_caster_core.hpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef AR_TAG_BASED_LOCALIZER__TAG_TF_CASTER_CORE_HPP_ -#define AR_TAG_BASED_LOCALIZER__TAG_TF_CASTER_CORE_HPP_ +#ifndef LANDMARK_TF_CASTER__LANDMARK_TF_CASTER_CORE_HPP_ +#define LANDMARK_TF_CASTER__LANDMARK_TF_CASTER_CORE_HPP_ #include @@ -26,10 +26,10 @@ #include -class TagTfCaster : public rclcpp::Node +class LandmarkTfCaster : public rclcpp::Node { public: - TagTfCaster(); + LandmarkTfCaster(); private: void map_bin_callback(const autoware_auto_mapping_msgs::msg::HADMapBin::ConstSharedPtr & msg); @@ -46,4 +46,4 @@ class TagTfCaster : public rclcpp::Node rclcpp::Subscription::SharedPtr map_bin_sub_; }; -#endif // AR_TAG_BASED_LOCALIZER__TAG_TF_CASTER_CORE_HPP_ +#endif // LANDMARK_TF_CASTER__LANDMARK_TF_CASTER_CORE_HPP_ diff --git a/localization/landmark_based_localizer/landmark_tf_caster/launch/landmark_tf_caster.launch.xml b/localization/landmark_based_localizer/landmark_tf_caster/launch/landmark_tf_caster.launch.xml new file mode 100644 index 0000000000000..eeaba555cb6f5 --- /dev/null +++ b/localization/landmark_based_localizer/landmark_tf_caster/launch/landmark_tf_caster.launch.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/localization/landmark_based_localizer/landmark_tf_caster/package.xml b/localization/landmark_based_localizer/landmark_tf_caster/package.xml new file mode 100644 index 0000000000000..9740e26e2d04a --- /dev/null +++ b/localization/landmark_based_localizer/landmark_tf_caster/package.xml @@ -0,0 +1,25 @@ + + + + landmark_tf_caster + 0.0.0 + The landmark_tf_caster package + Shintaro Sakoda + Masahiro Sakamoto + Apache License 2.0 + + ament_cmake + autoware_cmake + + autoware_auto_mapping_msgs + geometry_msgs + lanelet2_extension + rclcpp + tf2_eigen + tf2_geometry_msgs + tf2_ros + + + ament_cmake + + diff --git a/localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp b/localization/landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_core.cpp similarity index 93% rename from localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp rename to localization/landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_core.cpp index 427bf513058d4..801e036415326 100644 --- a/localization/ar_tag_based_localizer/src/tag_tf_caster_core.cpp +++ b/localization/landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_core.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ar_tag_based_localizer/tag_tf_caster_core.hpp" +#include "landmark_tf_caster/landmark_tf_caster_core.hpp" #include "lanelet2_extension/utility/message_conversion.hpp" @@ -26,7 +26,7 @@ #include #endif -TagTfCaster::TagTfCaster() : Node("tag_tf_caster") +LandmarkTfCaster::LandmarkTfCaster() : Node("landmark_tf_caster") { // Parameters volume_threshold_ = this->declare_parameter("volume_threshold", 1e-5); @@ -38,11 +38,11 @@ TagTfCaster::TagTfCaster() : Node("tag_tf_caster") // Subscribers map_bin_sub_ = this->create_subscription( "~/input/lanelet2_map", rclcpp::QoS(10).durability(rclcpp::DurabilityPolicy::TransientLocal), - std::bind(&TagTfCaster::map_bin_callback, this, std::placeholders::_1)); + std::bind(&LandmarkTfCaster::map_bin_callback, this, std::placeholders::_1)); RCLCPP_INFO(this->get_logger(), "finish setup"); } -void TagTfCaster::map_bin_callback( +void LandmarkTfCaster::map_bin_callback( const autoware_auto_mapping_msgs::msg::HADMapBin::ConstSharedPtr & msg) { RCLCPP_INFO_STREAM(this->get_logger(), "msg->format_version: " << msg->format_version); @@ -60,7 +60,7 @@ void TagTfCaster::map_bin_callback( } } -void TagTfCaster::publish_tf(const lanelet::Polygon3d & poly) +void LandmarkTfCaster::publish_tf(const lanelet::Polygon3d & poly) { // Get marker_id const std::string marker_id = poly.attributeOr("marker_id", "none"); diff --git a/localization/ar_tag_based_localizer/src/tag_tf_caster_node.cpp b/localization/landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_node.cpp similarity index 85% rename from localization/ar_tag_based_localizer/src/tag_tf_caster_node.cpp rename to localization/landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_node.cpp index ce83c572d6a6e..4c34e593e7552 100644 --- a/localization/ar_tag_based_localizer/src/tag_tf_caster_node.cpp +++ b/localization/landmark_based_localizer/landmark_tf_caster/src/landmark_tf_caster_node.cpp @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ar_tag_based_localizer/tag_tf_caster_core.hpp" +#include "landmark_tf_caster/landmark_tf_caster_core.hpp" int main(int argc, char ** argv) { rclcpp::init(argc, argv); - rclcpp::spin(std::make_shared()); + rclcpp::spin(std::make_shared()); rclcpp::shutdown(); } From b58c009ce5875b73cf385884f3eafe75b983c8a9 Mon Sep 17 00:00:00 2001 From: TaikiYamada4 <129915538+TaikiYamada4@users.noreply.github.com> Date: Fri, 22 Sep 2023 18:02:39 +0900 Subject: [PATCH 291/547] docs(ndt_scan_matcher): update readme (#5074) Updated README by adding ndt_base_frame in the parameters list Signed-off-by: TaikiYamada4 --- localization/ndt_scan_matcher/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/localization/ndt_scan_matcher/README.md b/localization/ndt_scan_matcher/README.md index fcbe4804ca7d2..c5a14d94ebed3 100644 --- a/localization/ndt_scan_matcher/README.md +++ b/localization/ndt_scan_matcher/README.md @@ -57,6 +57,7 @@ One optional function is regularization. Please see the regularization chapter i | Name | Type | Description | | --------------------------------------- | ------ | ----------------------------------------------------------------------------------------------- | | `base_frame` | string | Vehicle reference frame | +| `ndt_base_frame` | string | NDT reference frame | | `input_sensor_points_queue_size` | int | Subscriber queue size | | `trans_epsilon` | double | The maximum difference between two consecutive transformations in order to consider convergence | | `step_size` | double | The newton line search maximum step length | From d8885f92c69dae3a10623675d1bda58eb913b8e9 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Fri, 22 Sep 2023 18:19:52 +0900 Subject: [PATCH 292/547] fix(tier4_localization_launch): fixed exec_depend (#5075) * Fixed exec_depend Signed-off-by: Shintaro Sakoda * style(pre-commit): autofix --------- Signed-off-by: Shintaro Sakoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- launch/tier4_localization_launch/package.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launch/tier4_localization_launch/package.xml b/launch/tier4_localization_launch/package.xml index 7991ffb0d8adf..6b3b3d3bbbe01 100644 --- a/launch/tier4_localization_launch/package.xml +++ b/launch/tier4_localization_launch/package.xml @@ -14,13 +14,14 @@ ament_cmake_auto autoware_cmake + ar_tag_based_localizer automatic_pose_initializer eagleye_fix2pose eagleye_gnss_converter eagleye_rt ekf_localizer gyro_odometer - landmark_based_localizer + landmark_tf_caster ndt_scan_matcher pointcloud_preprocessor pose_initializer From fe19de9e13e01bd7400259a86cb40b3e45c9d2c0 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Fri, 22 Sep 2023 14:12:46 +0200 Subject: [PATCH 293/547] build(tier4_target_object_type_rviz_plugin): add missing cv_bridge dependency (#5000) Signed-off-by: Esteve Fernandez --- common/tier4_target_object_type_rviz_plugin/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/common/tier4_target_object_type_rviz_plugin/package.xml b/common/tier4_target_object_type_rviz_plugin/package.xml index 6209a5aaaee6b..04d2f28d9ba3e 100644 --- a/common/tier4_target_object_type_rviz_plugin/package.xml +++ b/common/tier4_target_object_type_rviz_plugin/package.xml @@ -10,6 +10,7 @@ ament_cmake_auto autoware_cmake + cv_bridge libqt5-core libqt5-gui libqt5-widgets From 19114642146db7312a35f7b5841878207776ea6a Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sat, 23 Sep 2023 16:59:52 +0900 Subject: [PATCH 294/547] refactor(goal_planner): do not finish goal planner (#5037) Signed-off-by: kosuke55 --- .../goal_planner/goal_planner_module.hpp | 1 - .../goal_planner/goal_planner_module.cpp | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index dbdfc77f59e38..f6b2fa8666f2e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -242,7 +242,6 @@ class GoalPlannerModule : public SceneModuleInterface bool isStopped( std::deque & odometry_buffer, const double time); bool hasFinishedCurrentPath(); - bool hasFinishedGoalPlanner(); bool isOnModifiedGoal() const; double calcModuleRequestLength() const; void resetStatus(); diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 0470b149f2ec3..d6e5c8902084f 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -451,13 +451,6 @@ Pose GoalPlannerModule::calcRefinedGoal(const Pose & goal_pose) const ModuleStatus GoalPlannerModule::updateState() { - // finish module only when the goal is fixed - if ( - !goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler) && - hasFinishedGoalPlanner()) { - return ModuleStatus::SUCCESS; - } - // start_planner module will be run when setting new goal, so not need finishing pull_over module. // Finishing it causes wrong lane_following path generation. return current_state_; @@ -1227,11 +1220,6 @@ bool GoalPlannerModule::isOnModifiedGoal() const parameters_->th_arrived_distance; } -bool GoalPlannerModule::hasFinishedGoalPlanner() -{ - return isOnModifiedGoal() && isStopped(); -} - TurnSignalInfo GoalPlannerModule::calcTurnSignalInfo() const { TurnSignalInfo turn_signal{}; // output From 499152149e3e01263f93845e947e73f748a7e065 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 24 Sep 2023 17:16:04 +0900 Subject: [PATCH 295/547] chore(geometric_parallel_parking): remove unused params and initialize variables (#5084) chore(geometric_parallel_parking): remove unused parames and initialize variables Signed-off-by: kosuke55 --- .../geometric_parallel_parking.hpp | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp index 3b6cca56e774f..ef1301beccf00 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp @@ -46,29 +46,25 @@ using geometry_msgs::msg::PoseArray; struct ParallelParkingParameters { - double th_arrived_distance; - double th_stopped_velocity; - double maximum_deceleration; - // forward parking - double after_forward_parking_straight_distance; - double forward_parking_velocity; - double forward_parking_lane_departure_margin; - double forward_parking_path_interval; - double forward_parking_max_steer_angle; + double after_forward_parking_straight_distance{0.0}; + double forward_parking_velocity{0.0}; + double forward_parking_lane_departure_margin{0.0}; + double forward_parking_path_interval{0.0}; + double forward_parking_max_steer_angle{0.0}; // backward parking - double after_backward_parking_straight_distance; - double backward_parking_velocity; - double backward_parking_lane_departure_margin; - double backward_parking_path_interval; - double backward_parking_max_steer_angle; + double after_backward_parking_straight_distance{0.0}; + double backward_parking_velocity{0.0}; + double backward_parking_lane_departure_margin{0.0}; + double backward_parking_path_interval{0.0}; + double backward_parking_max_steer_angle{0.0}; // pull_out - double pull_out_velocity; - double pull_out_lane_departure_margin; - double pull_out_path_interval; - double pull_out_max_steer_angle; + double pull_out_velocity{0.0}; + double pull_out_lane_departure_margin{0.0}; + double pull_out_path_interval{0.0}; + double pull_out_max_steer_angle{0.0}; }; class GeometricParallelParking @@ -108,11 +104,11 @@ class GeometricParallelParking Pose getArcEndPose() const { return arc_end_pose_; } private: - std::shared_ptr planner_data_; - ParallelParkingParameters parameters_; + std::shared_ptr planner_data_{nullptr}; + ParallelParkingParameters parameters_{}; - double R_E_min_; // base_link - double R_Bl_min_; // front_left + double R_E_min_{0.0}; // base_link + double R_Bl_min_{0.0}; // front_left std::vector arc_paths_; std::vector paths_; @@ -146,10 +142,10 @@ class GeometricParallelParking std::vector & arc_paths, const double velocity, const bool set_stop_end); // debug - Pose Cr_; - Pose Cl_; - Pose start_pose_; - Pose arc_end_pose_; + Pose Cr_{}; + Pose Cl_{}; + Pose start_pose_{}; + Pose arc_end_pose_{}; }; } // namespace behavior_path_planner From 837699ba34d8ae06c2ff436b8b2c48dd840f6dbb Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 24 Sep 2023 17:16:40 +0900 Subject: [PATCH 296/547] feat(start_planner): visualize backward driving path (#5088) Signed-off-by: kosuke55 --- .../src/scene_module/start_planner/start_planner_module.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 8af4b76f619f3..17068ee392d3b 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -1191,6 +1191,7 @@ void StartPlannerModule::setDebugData() const add(createPoseMarkerArray(status_.pull_out_path.start_pose, "start_pose", 0, 0.3, 0.9, 0.3)); add(createPoseMarkerArray(status_.pull_out_path.end_pose, "end_pose", 0, 0.9, 0.9, 0.3)); add(createPathMarkerArray(getFullPath(), "full_path", 0, 0.0, 0.5, 0.9)); + add(createPathMarkerArray(status_.backward_path, "backward_driving_path", 0, 0.0, 0.9, 0.0)); // safety check if (parameters_->safety_check_params.enable_safety_check) { From a4c94ccc7b02c8aea3a899dd6dbc4cf1ce406945 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 24 Sep 2023 18:06:59 +0900 Subject: [PATCH 297/547] fix(start/goal_planner): resample path and make params (#5085) * chore(geometric_parallel_parking): remove unused parames and initialize variables Signed-off-by: kosuke55 * fix(start/goal_planner): resample path and make params Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 Signed-off-by: kyoichi-sugahara Co-authored-by: kyoichi-sugahara --- .../goal_planner/goal_planner.param.yaml | 1 + .../start_planner/start_planner.param.yaml | 1 + ...havior_path_planner_goal_planner_design.md | 11 +++--- ...avior_path_planner_start_planner_design.md | 1 + .../geometric_parallel_parking.hpp | 3 ++ .../goal_planner/goal_planner_parameters.hpp | 1 + .../utils/goal_planner/shift_pull_over.hpp | 1 - .../utils/start_planner/shift_pull_out.hpp | 4 +-- .../start_planner_parameters.hpp | 1 + .../src/scene_module/goal_planner/manager.cpp | 9 +++++ .../scene_module/start_planner/manager.cpp | 3 ++ .../geometric_parallel_parking.cpp | 11 +++--- .../utils/goal_planner/shift_pull_over.cpp | 2 +- .../start_planner/freespace_pull_out.cpp | 4 ++- .../utils/start_planner/shift_pull_out.cpp | 34 +++++++++++-------- 15 files changed, 57 insertions(+), 30 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 625cd6ab74ff7..f16799d7ea206 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -5,6 +5,7 @@ th_arrived_distance: 1.0 th_stopped_velocity: 0.01 th_stopped_time: 2.0 # It must be greater than the state_machine's. + center_line_path_interval: 1.0 # goal search goal_search: diff --git a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml index a529eefac67af..6760ec787bb03 100644 --- a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml +++ b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml @@ -8,6 +8,7 @@ collision_check_distance_from_end: 1.0 th_moving_object_velocity: 1.0 th_distance_to_middle_of_the_road: 0.1 + center_line_path_interval: 1.0 # shift pull out enable_shift_pull_out: true check_shift_path_lane_departure: false diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index 18b77f3faacf0..44abdb4267672 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -118,11 +118,12 @@ Either one is activated when all conditions are met. ## General parameters for goal_planner -| Name | Unit | Type | Description | Default value | -| :------------------ | :---- | :----- | :------------------------------------------------- | :------------ | -| th_arrived_distance | [m] | double | distance threshold for arrival of path termination | 1.0 | -| th_stopped_velocity | [m/s] | double | velocity threshold for arrival of path termination | 0.01 | -| th_stopped_time | [s] | double | time threshold for arrival of path termination | 2.0 | +| Name | Unit | Type | Description | Default value | +| :------------------------ | :---- | :----- | :------------------------------------------------- | :------------ | +| th_arrived_distance | [m] | double | distance threshold for arrival of path termination | 1.0 | +| th_stopped_velocity | [m/s] | double | velocity threshold for arrival of path termination | 0.01 | +| th_stopped_time | [s] | double | time threshold for arrival of path termination | 2.0 | +| center_line_path_interval | [m] | double | reference center line path point interval | 1.0 | ## **collision check** diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md index 0b0e01378b61a..8322952a0be76 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md @@ -70,6 +70,7 @@ PullOutPath --o PullOutPlannerBase | length_ratio_for_turn_signal_deactivation_near_intersection | [m] | double | deactivate turn signal of this module near intersection | 0.5 | | collision_check_margin | [m] | double | Obstacle collision check margin | 1.0 | | collision_check_distance_from_end | [m] | double | collision check distance from end point. currently only for pull out | 15.0 | +| center_line_path_interval | [m] | double | reference center line path point interval | 1.0 | ## Safety check with static obstacles diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp index ef1301beccf00..c2d36fdd6e0d7 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp @@ -46,6 +46,9 @@ using geometry_msgs::msg::PoseArray; struct ParallelParkingParameters { + // common + double center_line_path_interval{0.0}; + // forward parking double after_forward_parking_straight_distance{0.0}; double forward_parking_velocity{0.0}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index 22c65270c39ea..8ef426d4f2488 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -45,6 +45,7 @@ struct GoalPlannerParameters double th_stopped_velocity{0.0}; double th_stopped_time{0.0}; double th_blinker_on_lateral_offset{0.0}; + double center_line_path_interval{0.0}; // goal search std::string search_priority; // "efficient_path" or "close_goal" diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/shift_pull_over.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/shift_pull_over.hpp index 445161f631bb7..dccaed3184e77 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/shift_pull_over.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/shift_pull_over.hpp @@ -57,7 +57,6 @@ class ShiftPullOver : public PullOverPlannerBase LaneDepartureChecker lane_departure_checker_{}; std::shared_ptr occupancy_grid_map_{}; - static constexpr double resample_interval_{1.0}; bool left_side_parking_{true}; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp index 0842d0a17bd97..78a64feb2c1ec 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/shift_pull_out.hpp @@ -41,9 +41,7 @@ class ShiftPullOut : public PullOutPlannerBase std::vector calcPullOutPaths( const RouteHandler & route_handler, const lanelet::ConstLanelets & road_lanes, - const Pose & start_pose, const Pose & goal_pose, - const BehaviorPathPlannerParameters & common_parameter, - const behavior_path_planner::StartPlannerParameters & parameter); + const Pose & start_pose, const Pose & goal_pose); double calcBeforeShiftedArcLength( const PathWithLaneId & path, const double target_after_arc_length, const double dr); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp index e2d80759a18d0..6978a7d494f30 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/start_planner_parameters.hpp @@ -45,6 +45,7 @@ struct StartPlannerParameters double collision_check_margin{0.0}; double collision_check_distance_from_end{0.0}; double th_moving_object_velocity{0.0}; + double center_line_path_interval{0.0}; // shift pull out bool enable_shift_pull_out{false}; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index da1b8bf5792ee..88fef3a6c6508 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -38,6 +38,8 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( p.th_stopped_velocity = node->declare_parameter(base_ns + "th_stopped_velocity"); p.th_arrived_distance = node->declare_parameter(base_ns + "th_arrived_distance"); p.th_stopped_time = node->declare_parameter(base_ns + "th_stopped_time"); + p.center_line_path_interval = + node->declare_parameter(base_ns + "center_line_path_interval"); } // goal search @@ -121,6 +123,13 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( node->declare_parameter(ns + "after_shift_straight_distance"); } + // parallel parking common + { + const std::string ns = base_ns + "pull_over.parallel_parking."; + p.parallel_parking_parameters.center_line_path_interval = + p.center_line_path_interval; // for geometric parallel parking + } + // forward parallel parking forward { const std::string ns = base_ns + "pull_over.parallel_parking.forward."; diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp index e00a1041e5316..30810bda252c3 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp @@ -47,6 +47,7 @@ StartPlannerModuleManager::StartPlannerModuleManager( p.collision_check_distance_from_end = node->declare_parameter(ns + "collision_check_distance_from_end"); p.th_moving_object_velocity = node->declare_parameter(ns + "th_moving_object_velocity"); + p.center_line_path_interval = node->declare_parameter(ns + "center_line_path_interval"); // shift pull out p.enable_shift_pull_out = node->declare_parameter(ns + "enable_shift_pull_out"); p.check_shift_path_lane_departure = @@ -71,6 +72,8 @@ StartPlannerModuleManager::StartPlannerModuleManager( node->declare_parameter(ns + "lane_departure_margin"); p.parallel_parking_parameters.pull_out_max_steer_angle = node->declare_parameter(ns + "pull_out_max_steer_angle"); // 15deg + p.parallel_parking_parameters.center_line_path_interval = + p.center_line_path_interval; // for geometric parallel parking // search start pose backward p.search_priority = node->declare_parameter( ns + "search_priority"); // "efficient_path" or "short_back_distance" diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index 6b1a4a82b314e..3d315e7213f96 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -286,8 +286,9 @@ bool GeometricParallelParking::planPullOut( s_start, planner_data_->parameters.forward_path_length, road_lanes, goal_pose); const double s_end = path_end_info.first; const bool path_terminal_is_goal = path_end_info.second; - PathWithLaneId road_center_line_path = - planner_data_->route_handler->getCenterLinePath(road_lanes, s_start, s_end, true); + const PathWithLaneId road_center_line_path = utils::resamplePathWithSpline( + planner_data_->route_handler->getCenterLinePath(road_lanes, s_start, s_end, true), + parameters_.center_line_path_interval); if (road_center_line_path.points.empty()) { continue; @@ -362,8 +363,10 @@ PathWithLaneId GeometricParallelParking::generateStraightPath( const Pose current_pose = planner_data_->self_odometry->pose.pose; const auto current_arc_position = lanelet::utils::getArcCoordinates(road_lanes, current_pose); - auto path = planner_data_->route_handler->getCenterLinePath( - road_lanes, current_arc_position.length, start_arc_position.length, true); + auto path = utils::resamplePathWithSpline( + planner_data_->route_handler->getCenterLinePath( + road_lanes, current_arc_position.length, start_arc_position.length, true), + parameters_.center_line_path_interval); path.header = planner_data_->route_handler->getRouteHeader(); if (!path.points.empty()) { path.points.back().point.longitudinal_velocity_mps = 0; diff --git a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp index d6a3746187b11..1a690362fbe25 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp @@ -112,7 +112,7 @@ boost::optional ShiftPullOver::generatePullOverPath( // generate road lane reference path to shift end const auto road_lane_reference_path_to_shift_end = utils::resamplePathWithSpline( - generateReferencePath(road_lanes, shift_end_pose), resample_interval_); + generateReferencePath(road_lanes, shift_end_pose), parameters_.center_line_path_interval); // calculate shift length const Pose & shift_end_pose_road_lane = diff --git a/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp index 36fb6381ac967..8e5d6b7545c1d 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp @@ -96,7 +96,9 @@ boost::optional FreespacePullOut::plan(const Pose & start_pose, con const bool path_terminal_is_goal = path_end_info.second; const auto road_center_line_path = route_handler->getCenterLinePath(road_lanes, s_start, s_end); - last_path = start_planner_utils::combineReferencePath(last_path, road_center_line_path); + last_path = utils::resamplePathWithSpline( + start_planner_utils::combineReferencePath(last_path, road_center_line_path), + parameters_.center_line_path_interval); const double original_terminal_velocity = last_path.points.back().point.longitudinal_velocity_mps; utils::correctDividedPathVelocity(partial_paths); diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index 6225ce598afe3..a4e4a2378e8f1 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -59,8 +59,7 @@ boost::optional ShiftPullOut::plan(const Pose & start_pose, const P planner_data_, backward_path_length, std::numeric_limits::max(), /*forward_only_in_route*/ true); // find candidate paths - auto pull_out_paths = calcPullOutPaths( - *route_handler, road_lanes, start_pose, goal_pose, common_parameters, parameters_); + auto pull_out_paths = calcPullOutPaths(*route_handler, road_lanes, start_pose, goal_pose); if (pull_out_paths.empty()) { return boost::none; } @@ -160,8 +159,7 @@ boost::optional ShiftPullOut::plan(const Pose & start_pose, const P std::vector ShiftPullOut::calcPullOutPaths( const RouteHandler & route_handler, const lanelet::ConstLanelets & road_lanes, - const Pose & start_pose, const Pose & goal_pose, - const BehaviorPathPlannerParameters & common_parameter, const StartPlannerParameters & parameter) + const Pose & start_pose, const Pose & goal_pose) { std::vector candidate_paths{}; @@ -170,12 +168,16 @@ std::vector ShiftPullOut::calcPullOutPaths( } // rename parameter - const double forward_path_length = common_parameter.forward_path_length; - const double backward_path_length = common_parameter.backward_path_length; - const double lateral_jerk = parameter.lateral_jerk; - const double minimum_lateral_acc = parameter.minimum_lateral_acc; - const double maximum_lateral_acc = parameter.maximum_lateral_acc; - const int lateral_acceleration_sampling_num = parameter.lateral_acceleration_sampling_num; + const auto & common_parameters = planner_data_->parameters; + const double forward_path_length = common_parameters.forward_path_length; + const double backward_path_length = common_parameters.backward_path_length; + const double lateral_jerk = parameters_.lateral_jerk; + const double minimum_lateral_acc = parameters_.minimum_lateral_acc; + const double maximum_lateral_acc = parameters_.maximum_lateral_acc; + const double maximum_curvature = parameters_.maximum_curvature; + const double minimum_shift_pull_out_distance = parameters_.minimum_shift_pull_out_distance; + const int lateral_acceleration_sampling_num = parameters_.lateral_acceleration_sampling_num; + // set minimum acc for breaking loop when sampling num is 1 const double acc_resolution = std::max( std::abs(maximum_lateral_acc - minimum_lateral_acc) / lateral_acceleration_sampling_num, @@ -189,9 +191,9 @@ std::vector ShiftPullOut::calcPullOutPaths( const double s_end = path_end_info.first; const bool path_terminal_is_goal = path_end_info.second; - constexpr double RESAMPLE_INTERVAL = 1.0; PathWithLaneId road_lane_reference_path = utils::resamplePathWithSpline( - route_handler.getCenterLinePath(road_lanes, s_start, s_end), RESAMPLE_INTERVAL); + route_handler.getCenterLinePath(road_lanes, s_start, s_end), + parameters_.center_line_path_interval); // non_shifted_path for when shift length or pull out distance is too short const PullOutPath non_shifted_path = std::invoke([&]() { @@ -230,8 +232,8 @@ std::vector ShiftPullOut::calcPullOutPaths( PathShifter::calcShiftTimeFromJerk(shift_length, lateral_jerk, lateral_acc); const double longitudinal_acc = std::clamp(road_velocity / shift_time, 0.0, /* max acc */ 1.0); const auto pull_out_distance = calcPullOutLongitudinalDistance( - longitudinal_acc, shift_time, shift_length, parameter.maximum_curvature, - parameter.minimum_shift_pull_out_distance); + longitudinal_acc, shift_time, shift_length, maximum_curvature, + minimum_shift_pull_out_distance); const double terminal_velocity = longitudinal_acc * shift_time; // clip from ego pose @@ -253,7 +255,9 @@ std::vector ShiftPullOut::calcPullOutPaths( std::max(pull_out_distance, pull_out_distance_converted); // if before_shifted_pull_out_distance is too short, shifting path fails, so add non shifted - if (before_shifted_pull_out_distance < RESAMPLE_INTERVAL && !has_non_shifted_path) { + if ( + before_shifted_pull_out_distance < parameters_.center_line_path_interval && + !has_non_shifted_path) { candidate_paths.push_back(non_shifted_path); has_non_shifted_path = true; continue; From 1ada5673e3ba561a23cf41cee825b9eec9a76dfd Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Sun, 24 Sep 2023 19:01:52 +0900 Subject: [PATCH 298/547] feat(behavior_path_planner): add public member function to get running modules (#3868) * add getter function to confirm which module is running Signed-off-by: kyoichi-sugahara * delete change for old architecture Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../behavior_path_planner_node.hpp | 3 +++ .../src/behavior_path_planner_node.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp b/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp index 54cfccc494e8a..0f51144c0b792 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp @@ -87,6 +87,9 @@ class BehaviorPathPlannerNode : public rclcpp::Node // Getter method for waiting approval modules std::vector getWaitingApprovalModules(); + // Getter method for running modules + std::vector getRunningModules(); + private: rclcpp::Subscription::SharedPtr route_subscriber_; rclcpp::Subscription::SharedPtr vector_map_subscriber_; diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index c14daa30b784d..8302779313feb 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -272,6 +272,18 @@ std::vector BehaviorPathPlannerNode::getWaitingApprovalModules() return waiting_approval_modules; } +std::vector BehaviorPathPlannerNode::getRunningModules() +{ + auto all_scene_module_ptr = planner_manager_->getSceneModuleStatus(); + std::vector running_modules; + for (const auto & module : all_scene_module_ptr) { + if (module->status == ModuleStatus::RUNNING) { + running_modules.push_back(module->module_name); + } + } + return running_modules; +} + BehaviorPathPlannerParameters BehaviorPathPlannerNode::getCommonParam() { BehaviorPathPlannerParameters p{}; From ec99b2f2218f6b27a0f9567480df73fe1ed2aea6 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 24 Sep 2023 21:14:58 +0900 Subject: [PATCH 299/547] fix(start_planner): fix backward drving path cutting (#5098) Signed-off-by: kosuke55 --- .../scene_module/start_planner/start_planner_module.cpp | 7 +++++-- planning/behavior_path_planner/src/utils/utils.cpp | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 17068ee392d3b..5247a24579212 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -1160,8 +1160,11 @@ void StartPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) cons DrivableAreaInfo current_drivable_area_info; current_drivable_area_info.drivable_lanes = target_drivable_lanes; - output.drivable_area_info = utils::combineDrivableAreaInfo( - current_drivable_area_info, getPreviousModuleOutput().drivable_area_info); + output.drivable_area_info = + status_.back_finished + ? utils::combineDrivableAreaInfo( + current_drivable_area_info, getPreviousModuleOutput().drivable_area_info) + : current_drivable_area_info; } } diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index a4667e1641ca0..9c11469c0f9c9 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1544,7 +1544,9 @@ void generateDrivableArea( // make bound longitudinally monotonic // TODO(Murooka) Fix makeBoundLongitudinallyMonotonic - if (enable_expanding_hatched_road_markings || enable_expanding_intersection_areas) { + if ( + is_driving_forward && + (enable_expanding_hatched_road_markings || enable_expanding_intersection_areas)) { makeBoundLongitudinallyMonotonic(path, planner_data, true); // for left bound makeBoundLongitudinallyMonotonic(path, planner_data, false); // for right bound } From 1276da93b382e3f3a64d3154f53b7f9562ad074c Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 25 Sep 2023 09:35:06 +0900 Subject: [PATCH 300/547] refactor(perception_reproducer): improve computational efficiency (#5067) * refactor(perception_reproducer): improve computational efficiency Signed-off-by: Takamasa Horibe * use numpy for nearest search, add stopwatch for verbose Signed-off-by: Takamasa Horibe * fix order Signed-off-by: Takamasa Horibe * add rosbag ego pose publisher Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../perception_replayer.py | 2 +- .../perception_replayer_common.py | 37 +++++++--- .../perception_reproducer.py | 74 +++++++++++++------ .../scripts/perception_replayer/utils.py | 27 +++++++ 4 files changed, 105 insertions(+), 35 deletions(-) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py index 18f903323619f..498f7e458b2e2 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer.py @@ -169,7 +169,7 @@ def publish_recorded_ego_pose(self): 0.06853892326654787, ] - self.recorded_ego_pub.publish(recorded_ego_pose) + self.recorded_ego_pub_as_initialpose.publish(recorded_ego_pose) print("Published recorded ego pose as /initialpose") diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index 8f7cf38772fbb..01079fdc3f8c1 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -68,7 +68,13 @@ def __init__(self, args, name): self.pointcloud_pub = self.create_publisher( PointCloud2, "/perception/obstacle_segmentation/pointcloud", 1 ) - self.recorded_ego_pub = self.create_publisher(PoseWithCovarianceStamped, "/initialpose", 1) + self.recorded_ego_pub_as_initialpose = self.create_publisher( + PoseWithCovarianceStamped, "/initialpose", 1 + ) + + self.recorded_ego_pub = self.create_publisher( + Odometry, "/perception_reproducer/rosbag_ego_odom", 1 + ) # load rosbag print("Stared loading rosbag") @@ -146,19 +152,26 @@ def kill_online_perception_node(self): except CalledProcessError: pass - def find_topics_by_timestamp(self, timestamp): - objects_data = None - for data in self.rosbag_objects_data: - if timestamp < data[0]: - objects_data = data[1] - break + def binary_search(self, data, timestamp): + low, high = 0, len(data) - 1 - traffic_signals_data = None - for data in self.rosbag_traffic_signals_data: - if timestamp < data[0]: - traffic_signals_data = data[1] - break + while low <= high: + mid = (low + high) // 2 + if data[mid][0] < timestamp: + low = mid + 1 + elif data[mid][0] > timestamp: + high = mid - 1 + else: + return data[mid][1] + # Return the next timestamp's data if available + if low < len(data): + return data[low][1] + return None + + def find_topics_by_timestamp(self, timestamp): + objects_data = self.binary_search(self.rosbag_objects_data, timestamp) + traffic_signals_data = self.binary_search(self.rosbag_traffic_signals_data, timestamp) return objects_data, traffic_signals_data def find_ego_odom_by_timestamp(self, timestamp): diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py index 454ad033082ac..d837abccc0670 100755 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_reproducer.py @@ -15,11 +15,12 @@ # limitations under the License. import argparse -import copy +import pickle +import numpy as np from perception_replayer_common import PerceptionReplayerCommon import rclpy -from utils import calc_squared_distance +from utils import StopWatch from utils import create_empty_pointcloud from utils import translate_objects_coordinate @@ -28,18 +29,39 @@ class PerceptionReproducer(PerceptionReplayerCommon): def __init__(self, args): super().__init__(args, "perception_reproducer") - self.ego_pose_idx = None self.prev_traffic_signals_msg = None + self.stopwatch = StopWatch(self.args.verbose) # for debug - # start timer callback + # to make some data to accelerate computation + self.preprocess_data() + + # start main timer callback self.timer = self.create_timer(0.1, self.on_timer) + + # kill perception process to avoid a conflict of the perception topics + self.timer_check_perception_process = self.create_timer(3.0, self.on_timer_kill_perception) + print("Start timer callback") - def on_timer(self): - timestamp = self.get_clock().now().to_msg() + def preprocess_data(self): + # closest search with numpy data is much faster than usual + self.rosbag_ego_odom_data_numpy = np.array( + [ + [data[1].pose.pose.position.x, data[1].pose.pose.position.y] + for data in self.rosbag_ego_odom_data + ] + ) + def on_timer_kill_perception(self): self.kill_online_perception_node() + def on_timer(self): + if self.args.verbose: + print("\n-- on_timer start --") + self.stopwatch.tic("total on_timer") + + timestamp = self.get_clock().now().to_msg() + if self.args.detected_object: pointcloud_msg = create_empty_pointcloud(timestamp) self.pointcloud_pub.publish(pointcloud_msg) @@ -49,15 +71,25 @@ def on_timer(self): return # find nearest ego odom by simulation observation + self.stopwatch.tic("find_nearest_ego_odom_by_observation") ego_odom = self.find_nearest_ego_odom_by_observation(self.ego_pose) pose_timestamp = ego_odom[0] log_ego_pose = ego_odom[1].pose.pose + self.stopwatch.toc("find_nearest_ego_odom_by_observation") # extract message by the nearest ego odom timestamp - msgs = copy.deepcopy(self.find_topics_by_timestamp(pose_timestamp)) + self.stopwatch.tic("find_topics_by_timestamp") + msgs_orig = self.find_topics_by_timestamp(pose_timestamp) + self.stopwatch.toc("find_topics_by_timestamp") + + # copy the messages + self.stopwatch.tic("message deepcopy") + msgs = pickle.loads(pickle.dumps(msgs_orig)) # this is x5 faster than deepcopy objects_msg = msgs[0] traffic_signals_msg = msgs[1] + self.stopwatch.toc("message deepcopy") + self.stopwatch.tic("transform and publish") # objects if objects_msg: objects_msg.header.stamp = timestamp @@ -65,6 +97,9 @@ def on_timer(self): translate_objects_coordinate(self.ego_pose, log_ego_pose, objects_msg) self.objects_pub.publish(objects_msg) + # ego odom + self.recorded_ego_pub.publish(ego_odom[1]) + # traffic signals # temporary support old auto msgs if traffic_signals_msg: @@ -82,23 +117,15 @@ def on_timer(self): else: self.prev_traffic_signals_msg.stamp = timestamp self.traffic_signals_pub.publish(self.prev_traffic_signals_msg) + self.stopwatch.toc("transform and publish") + + self.stopwatch.toc("total on_timer") def find_nearest_ego_odom_by_observation(self, ego_pose): - if self.ego_pose_idx: - start_idx = self.ego_pose_idx - 10 - end_idx = self.ego_pose_idx + 10 - else: - start_idx = 0 - end_idx = len(self.rosbag_ego_odom_data) - 1 - - nearest_idx = 0 - nearest_dist = float("inf") - for idx in range(start_idx, end_idx + 1): - data = self.rosbag_ego_odom_data[idx] - dist = calc_squared_distance(data[1].pose.pose.position, ego_pose.position) - if dist < nearest_dist: - nearest_dist = dist - nearest_idx = idx + # nearest search with numpy format is much (~ x100) faster than regular for loop + self_pose = np.array([ego_pose.position.x, ego_pose.position.y]) + dists_squared = np.sum((self.rosbag_ego_odom_data_numpy - self_pose) ** 2, axis=1) + nearest_idx = np.argmin(dists_squared) return self.rosbag_ego_odom_data[nearest_idx] @@ -115,6 +142,9 @@ def find_nearest_ego_odom_by_observation(self, ego_pose): parser.add_argument( "-f", "--rosbag-format", help="rosbag data format (default is db3)", default="db3" ) + parser.add_argument( + "-v", "--verbose", help="output debug data", action="store_true", default=False + ) args = parser.parse_args() rclpy.init() diff --git a/planning/planning_debug_tools/scripts/perception_replayer/utils.py b/planning/planning_debug_tools/scripts/perception_replayer/utils.py index 572922d4c7abb..5ded8d2ea409c 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/utils.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/utils.py @@ -15,6 +15,7 @@ # limitations under the License. import math +import time from geometry_msgs.msg import Quaternion import numpy as np @@ -123,3 +124,29 @@ def translate_objects_coordinate(ego_pose, log_ego_pose, objects_msg): object_pose.position.x = object_pos_vec[0] object_pose.position.y = object_pos_vec[1] object_pose.orientation = get_quaternion_from_yaw(log_object_yaw + log_ego_yaw - ego_yaw) + + +class StopWatch: + def __init__(self, verbose): + # A dictionary to store the starting times + self.start_times = {} + self.verbose = verbose + + def tic(self, name): + """Store the current time with the given name.""" + self.start_times[name] = time.perf_counter() + + def toc(self, name): + """Print the elapsed time since the last call to tic() with the same name.""" + if name not in self.start_times: + print(f"No start time found for {name}!") + return + + elapsed_time = ( + time.perf_counter() - self.start_times[name] + ) * 1000 # Convert to milliseconds + if self.verbose: + print(f"Time for {name}: {elapsed_time:.2f} ms") + + # Reset the starting time for the name + del self.start_times[name] From 7af40fc9ff9beabae4c8842b21153ba977513cbf Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 25 Sep 2023 09:35:49 +0900 Subject: [PATCH 301/547] chore(motion_velocity_smoother): add enable curve filtering param (#5064) * chore(motion_velocity_smoother): add enable curve filtering param Signed-off-by: Takamasa Horibe * update readme Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- planning/motion_velocity_smoother/README.md | 14 ++++++++------ .../default_motion_velocity_smoother.param.yaml | 2 ++ .../motion_velocity_smoother_node.hpp | 3 +++ .../src/motion_velocity_smoother_node.cpp | 17 ++++++++++++++--- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/planning/motion_velocity_smoother/README.md b/planning/motion_velocity_smoother/README.md index d52a6431c48d8..1ff7f5982b4eb 100644 --- a/planning/motion_velocity_smoother/README.md +++ b/planning/motion_velocity_smoother/README.md @@ -140,6 +140,7 @@ After the optimization, a resampling called `post resampling` is performed befor | Name | Type | Description | Default value | | :------------------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| `enable_lateral_acc_limit` | `bool` | To toggle the lateral acceleration filter on and off. You can switch it dynamically at runtime. | true | | `max_lateral_accel` | `double` | Max lateral acceleration limit [m/ss] | 0.5 | | `min_curve_velocity` | `double` | Min velocity at lateral acceleration limit [m/ss] | 2.74 | | `decel_distance_before_curve` | `double` | Distance to slowdown before a curve for lateral acceleration limit [m] | 3.5 | @@ -197,12 +198,13 @@ After the optimization, a resampling called `post resampling` is performed befor ### Limit steering angle rate parameters -| Name | Type | Description | Default value | -| :------------------------------- | :------- | :----------------------------------------------------------------------- | :------------ | -| `max_steering_angle_rate` | `double` | Maximum steering angle rate [degree/s] | 40.0 | -| `resample_ds` | `double` | Distance between trajectory points [m] | 0.1 | -| `curvature_threshold` | `double` | If curvature > curvature_threshold, steeringRateLimit is triggered [1/m] | 0.02 | -| `curvature_calculation_distance` | `double` | Distance of points while curvature is calculating [m] | 1.0 | +| Name | Type | Description | Default value | +| :------------------------------- | :------- | :------------------------------------------------------------------------------------ | :------------ | +| `enable_steering_rate_limit` | `bool` | To toggle the steer rate filter on and off. You can switch it dynamically at runtime. | true | +| `max_steering_angle_rate` | `double` | Maximum steering angle rate [degree/s] | 40.0 | +| `resample_ds` | `double` | Distance between trajectory points [m] | 0.1 | +| `curvature_threshold` | `double` | If curvature > curvature_threshold, steeringRateLimit is triggered [1/m] | 0.02 | +| `curvature_calculation_distance` | `double` | Distance of points while curvature is calculating [m] | 1.0 | ### Weights for optimization diff --git a/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml b/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml index bcbf0a5bb6c06..e56845c1c9bd6 100644 --- a/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml +++ b/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml @@ -8,6 +8,7 @@ margin_to_insert_external_velocity_limit: 0.3 # margin distance to insert external velocity limit [m] # curve parameters + enable_lateral_acc_limit: true # To toggle the lateral acc filter on and off. You can switch it dynamically at runtime. max_lateral_accel: 0.8 # max lateral acceleration limit [m/ss] min_curve_velocity: 2.74 # min velocity at lateral acceleration limit [m/ss] decel_distance_before_curve: 3.5 # slow speed distance before a curve for lateral acceleration limit @@ -49,6 +50,7 @@ post_sparse_min_interval_distance: 1.0 # minimum points-interval length for sparse sampling [m] # steering angle rate limit parameters + enable_steering_rate_limit: true # To toggle the steer rate filter on and off. You can switch it dynamically at runtime. max_steering_angle_rate: 40.0 # maximum steering angle rate [degree/s] resample_ds: 0.1 # distance between trajectory points [m] curvature_threshold: 0.02 # if curvature > curvature_threshold, steeringRateLimit is triggered [1/m] diff --git a/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp b/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp index 503fd4450cdbd..bb49700464715 100644 --- a/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp +++ b/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp @@ -123,6 +123,9 @@ class MotionVelocitySmootherNode : public rclcpp::Node struct Param { + bool enable_lateral_acc_limit; + bool enable_steering_rate_limit; + double max_velocity; // max velocity [m/s] double margin_to_insert_external_velocity_limit; // for external velocity limit [m] double replan_vel_deviation; // if speed error exceeds this [m/s], diff --git a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp index 5ed176bd2e215..a4ad118ffe012 100644 --- a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp +++ b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp @@ -165,6 +165,9 @@ rcl_interfaces::msg::SetParametersResult MotionVelocitySmootherNode::onParameter { auto & p = node_param_; + update_param_bool("enable_lateral_acc_limit", p.enable_lateral_acc_limit); + update_param_bool("enable_steering_rate_limit", p.enable_steering_rate_limit); + update_param("max_velocity", p.max_velocity); update_param( "margin_to_insert_external_velocity_limit", p.margin_to_insert_external_velocity_limit); @@ -266,6 +269,9 @@ rcl_interfaces::msg::SetParametersResult MotionVelocitySmootherNode::onParameter void MotionVelocitySmootherNode::initCommonParam() { auto & p = node_param_; + p.enable_lateral_acc_limit = declare_parameter("enable_lateral_acc_limit"); + p.enable_steering_rate_limit = declare_parameter("enable_steering_rate_limit"); + p.max_velocity = declare_parameter("max_velocity"); // 72.0 kmph p.margin_to_insert_external_velocity_limit = declare_parameter("margin_to_insert_external_velocity_limit"); @@ -572,12 +578,17 @@ bool MotionVelocitySmootherNode::smoothVelocity( // Lateral acceleration limit constexpr bool enable_smooth_limit = true; constexpr bool use_resampling = true; - const auto traj_lateral_acc_filtered = smoother_->applyLateralAccelerationFilter( - input, initial_motion.vel, initial_motion.acc, enable_smooth_limit, use_resampling); + const auto traj_lateral_acc_filtered = + node_param_.enable_lateral_acc_limit + ? smoother_->applyLateralAccelerationFilter( + input, initial_motion.vel, initial_motion.acc, enable_smooth_limit, use_resampling) + : input; // Steering angle rate limit (Note: set use_resample = false since it is resampled above) const auto traj_steering_rate_limited = - smoother_->applySteeringRateLimit(traj_lateral_acc_filtered, false); + node_param_.enable_steering_rate_limit + ? smoother_->applySteeringRateLimit(traj_lateral_acc_filtered, false) + : traj_lateral_acc_filtered; // Resample trajectory with ego-velocity based interval distance auto traj_resampled = smoother_->resampleTrajectory( From 6006146dcff9cf68265f9f155adcf7dc2fa69528 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 10:49:51 +0900 Subject: [PATCH 302/547] feat(mission_planner): add param of check_footprint_inside_lanes (#5097) * feat(mission_planner): add param of check_footprint_inside_lanes Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- planning/mission_planner/config/mission_planner.param.yaml | 1 + .../mission_planner/src/lanelet2_plugins/default_planner.cpp | 3 +++ .../mission_planner/src/lanelet2_plugins/default_planner.hpp | 1 + 3 files changed, 5 insertions(+) diff --git a/planning/mission_planner/config/mission_planner.param.yaml b/planning/mission_planner/config/mission_planner.param.yaml index 98c28344ea25c..9b7dcffbc687b 100644 --- a/planning/mission_planner/config/mission_planner.param.yaml +++ b/planning/mission_planner/config/mission_planner.param.yaml @@ -9,3 +9,4 @@ reroute_time_threshold: 10.0 minimum_reroute_length: 30.0 consider_no_drivable_lanes: false # This flag is for considering no_drivable_lanes in planning or not. + check_footprint_inside_lanes: true diff --git a/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp b/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp index 979800aea0754..673519b6f7a0e 100644 --- a/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp +++ b/planning/mission_planner/src/lanelet2_plugins/default_planner.cpp @@ -152,6 +152,8 @@ void DefaultPlanner::initialize_common(rclcpp::Node * node) param_.goal_angle_threshold_deg = node_->declare_parameter("goal_angle_threshold_deg"); param_.enable_correct_goal_pose = node_->declare_parameter("enable_correct_goal_pose"); param_.consider_no_drivable_lanes = node_->declare_parameter("consider_no_drivable_lanes"); + param_.check_footprint_inside_lanes = + node_->declare_parameter("check_footprint_inside_lanes"); } void DefaultPlanner::initialize(rclcpp::Node * node) @@ -349,6 +351,7 @@ bool DefaultPlanner::is_goal_valid( // check if goal footprint exceeds lane when the goal isn't in parking_lot if ( + param_.check_footprint_inside_lanes && !check_goal_footprint( closest_lanelet, combined_prev_lanelet, polygon_footprint, next_lane_length) && !is_in_parking_lot( diff --git a/planning/mission_planner/src/lanelet2_plugins/default_planner.hpp b/planning/mission_planner/src/lanelet2_plugins/default_planner.hpp index cf5a19443dd82..251d0db533182 100644 --- a/planning/mission_planner/src/lanelet2_plugins/default_planner.hpp +++ b/planning/mission_planner/src/lanelet2_plugins/default_planner.hpp @@ -38,6 +38,7 @@ struct DefaultPlannerParameters double goal_angle_threshold_deg; bool enable_correct_goal_pose; bool consider_no_drivable_lanes; + bool check_footprint_inside_lanes; }; class DefaultPlanner : public mission_planner::PlannerPlugin From 7b1999ab8a7c1ab7fa1402b39d423e11b38cfc91 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 10:51:56 +0900 Subject: [PATCH 303/547] refactor(pid_longitudinal_controller): use common elevetation angle calculation (#5090) * refactor(pid_longitudinal_controller): use common elevetation angle calculation Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../longitudinal_controller_utils.hpp | 5 ---- .../src/longitudinal_controller_utils.cpp | 21 +++++---------- .../src/pid_longitudinal_controller.cpp | 2 +- .../test_longitudinal_controller_utils.cpp | 27 ------------------- 4 files changed, 7 insertions(+), 48 deletions(-) diff --git a/control/pid_longitudinal_controller/include/pid_longitudinal_controller/longitudinal_controller_utils.hpp b/control/pid_longitudinal_controller/include/pid_longitudinal_controller/longitudinal_controller_utils.hpp index 8685d6264993b..9c01f7bc26c4b 100644 --- a/control/pid_longitudinal_controller/include/pid_longitudinal_controller/longitudinal_controller_utils.hpp +++ b/control/pid_longitudinal_controller/include/pid_longitudinal_controller/longitudinal_controller_utils.hpp @@ -66,11 +66,6 @@ double getPitchByPose(const Quaternion & quaternion); double getPitchByTraj( const Trajectory & trajectory, const size_t closest_idx, const double wheel_base); -/** - * @brief calculate elevation angle - */ -double calcElevationAngle(const TrajectoryPoint & p_from, const TrajectoryPoint & p_to); - /** * @brief calculate vehicle pose after time delay by moving the vehicle at current velocity and * acceleration for delayed time diff --git a/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp b/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp index 70cdbcaf17bd2..41bbb48ff5d08 100644 --- a/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp +++ b/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp @@ -96,7 +96,8 @@ double getPitchByTraj( trajectory.points.at(nearest_idx), trajectory.points.at(i)); if (dist > wheel_base) { // calculate pitch from trajectory between rear wheel (nearest) and front center (i) - return calcElevationAngle(trajectory.points.at(nearest_idx), trajectory.points.at(i)); + return tier4_autoware_utils::calcElevationAngle( + trajectory.points.at(nearest_idx).pose.position, trajectory.points.at(i).pose.position); } } @@ -108,24 +109,14 @@ double getPitchByTraj( if (dist > wheel_base) { // calculate pitch from trajectory // between wheelbase behind the end of trajectory (i) and the end of trajectory (back) - return calcElevationAngle(trajectory.points.at(i), trajectory.points.back()); + return tier4_autoware_utils::calcElevationAngle( + trajectory.points.at(i).pose.position, trajectory.points.back().pose.position); } } // calculate pitch from trajectory between the beginning and end of trajectory - return calcElevationAngle(trajectory.points.at(0), trajectory.points.back()); -} - -double calcElevationAngle(const TrajectoryPoint & p_from, const TrajectoryPoint & p_to) -{ - const double dx = p_from.pose.position.x - p_to.pose.position.x; - const double dy = p_from.pose.position.y - p_to.pose.position.y; - const double dz = p_from.pose.position.z - p_to.pose.position.z; - - const double dxy = std::max(std::hypot(dx, dy), std::numeric_limits::epsilon()); - const double pitch = std::atan2(dz, dxy); - - return pitch; + return tier4_autoware_utils::calcElevationAngle( + trajectory.points.at(0).pose.position, trajectory.points.back().pose.position); } Pose calcPoseAfterTimeDelay( diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index 6d3489811b195..985a22d26f9f4 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -817,7 +817,7 @@ double PidLongitudinalController::applySlopeCompensation( const double pitch_limited = std::min(std::max(pitch, m_min_pitch_rad), m_max_pitch_rad); // Acceleration command is always positive independent of direction (= shift) when car is running - double sign = (shift == Shift::Forward) ? -1 : (shift == Shift::Reverse ? 1 : 0); + double sign = (shift == Shift::Forward) ? 1.0 : (shift == Shift::Reverse ? -1.0 : 0); double compensated_acc = input_acc + sign * 9.81 * std::sin(pitch_limited); return compensated_acc; } diff --git a/control/pid_longitudinal_controller/test/test_longitudinal_controller_utils.cpp b/control/pid_longitudinal_controller/test/test_longitudinal_controller_utils.cpp index d9fc404ce6abe..5c7698180f82b 100644 --- a/control/pid_longitudinal_controller/test/test_longitudinal_controller_utils.cpp +++ b/control/pid_longitudinal_controller/test/test_longitudinal_controller_utils.cpp @@ -158,33 +158,6 @@ TEST(TestLongitudinalControllerUtils, getPitchByTraj) std::atan2(0.5, 1)); } -TEST(TestLongitudinalControllerUtils, calcElevationAngle) -{ - using autoware_auto_planning_msgs::msg::TrajectoryPoint; - TrajectoryPoint p_from; - p_from.pose.position.x = 0.0; - p_from.pose.position.y = 0.0; - TrajectoryPoint p_to; - p_to.pose.position.x = 1.0; - p_to.pose.position.y = 0.0; - EXPECT_DOUBLE_EQ(longitudinal_utils::calcElevationAngle(p_from, p_to), 0.0); - p_to.pose.position.x = 1.0; - p_to.pose.position.z = 1.0; - EXPECT_DOUBLE_EQ(longitudinal_utils::calcElevationAngle(p_from, p_to), -M_PI_4); - p_to.pose.position.x = -1.0; - p_to.pose.position.z = 1.0; - EXPECT_DOUBLE_EQ(longitudinal_utils::calcElevationAngle(p_from, p_to), -M_PI_4); - p_to.pose.position.x = 0.0; - p_to.pose.position.z = 1.0; - EXPECT_DOUBLE_EQ(longitudinal_utils::calcElevationAngle(p_from, p_to), -M_PI_2); - p_to.pose.position.x = 1.0; - p_to.pose.position.z = -1.0; - EXPECT_DOUBLE_EQ(longitudinal_utils::calcElevationAngle(p_from, p_to), M_PI_4); - p_to.pose.position.x = -1.0; - p_to.pose.position.z = -1.0; - EXPECT_DOUBLE_EQ(longitudinal_utils::calcElevationAngle(p_from, p_to), M_PI_4); -} - TEST(TestLongitudinalControllerUtils, calcPoseAfterTimeDelay) { using geometry_msgs::msg::Pose; From 460fc46c290746e7a04a6148f74b7ef828c668a9 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 10:53:39 +0900 Subject: [PATCH 304/547] feat(merge_from_private): make stop_distance_threshold tunable (#5086) * feat(merge_from_private): make stop_distance_threshold tunable Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/intersection.param.yaml | 15 ++++++++------- .../src/manager.cpp | 1 + .../src/scene_merge_from_private_road.cpp | 5 ++--- .../src/scene_merge_from_private_road.hpp | 1 + 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index c30c5d70f8c60..def2335e20358 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -2,7 +2,7 @@ ros__parameters: intersection: common: - attention_area_margin: 0.5 # [m] + attention_area_margin: 0.75 # [m] attention_area_length: 200.0 # [m] attention_area_angle_threshold: 0.785 # [rad] stop_line_margin: 3.0 @@ -14,7 +14,7 @@ consider_wrong_direction_vehicle: false stuck_vehicle: use_stuck_stopline: true # stopline generated before the first conflicting area - stuck_vehicle_detect_dist: 3.0 # this should be the length between cars when they are stopped. The actual stuck vehicle detection length will be this value + vehicle_length. + stuck_vehicle_detect_dist: 5.0 # this should be the length between cars when they are stopped. The actual stuck vehicle detection length will be this value + vehicle_length. stuck_vehicle_vel_thr: 0.833 # 0.833m/s = 3.0km/h # enable_front_car_decel_prediction: false # By default this feature is disabled # assumed_front_car_decel: 1.0 # [m/ss] the expected deceleration of front car when front car as well as ego are turning @@ -45,14 +45,15 @@ min_vehicle_brake_for_rss: -2.5 # [m/s^2] max_vehicle_velocity_for_rss: 16.66 # [m/s] == 60kmph denoise_kernel: 1.0 # [m] - possible_object_bbox: [1.0, 2.0] # [m x m] - ignore_parked_vehicle_speed_threshold: 0.333 # == 1.2km/h - stop_release_margin_time: 1.0 # [s] + possible_object_bbox: [1.5, 2.5] # [m x m] + ignore_parked_vehicle_speed_threshold: 0.8333 # == 3.0km/h + stop_release_margin_time: 1.5 # [s] - enable_rtc: # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval - intersection: true + enable_rtc: + intersection: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval intersection_to_occlusion: true merge_from_private: stop_line_margin: 3.0 stop_duration_sec: 1.0 + stop_distance_threshold: 1.0 diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index bf0970f3640d1..b2cdcf5db79eb 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -273,6 +273,7 @@ MergeFromPrivateModuleManager::MergeFromPrivateModuleManager(rclcpp::Node & node mp.stop_line_margin = getOrDeclareParameter(node, ns + ".stop_line_margin"); mp.path_interpolation_ds = node.get_parameter("intersection.common.path_interpolation_ds").as_double(); + mp.stop_distance_threshold = getOrDeclareParameter(node, ns + ".stop_distance_threshold"); } void MergeFromPrivateModuleManager::launchNewModules( diff --git a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp index 9c3a35033ea9b..bb54d829cc477 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp @@ -135,12 +135,11 @@ bool MergeFromPrivateRoadModule::modifyPathVelocity(PathWithLaneId * path, StopR const double signed_arc_dist_to_stop_point = motion_utils::calcSignedArcLength( path->points, current_pose.position, path->points.at(stop_line_idx).point.pose.position); - constexpr double distance_threshold = 2.0; if ( - signed_arc_dist_to_stop_point < distance_threshold && + signed_arc_dist_to_stop_point < planner_param_.stop_distance_threshold && planner_data_->isVehicleStopped(planner_param_.stop_duration_sec)) { state_machine_.setState(StateMachine::State::GO); - if (signed_arc_dist_to_stop_point < -distance_threshold) { + if (signed_arc_dist_to_stop_point < -planner_param_.stop_distance_threshold) { RCLCPP_ERROR(logger_, "Failed to stop near stop line but ego stopped. Change state to GO"); } } diff --git a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp index e00b2a8b886d1..19e7b9201093f 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.hpp @@ -58,6 +58,7 @@ class MergeFromPrivateRoadModule : public SceneModuleInterface double attention_area_length; double stop_line_margin; double stop_duration_sec; + double stop_distance_threshold; double path_interpolation_ds; double occlusion_attention_area_length; bool consider_wrong_direction_vehicle; From 617a6273be90e2130618b08eeae5f97121137c72 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 11:07:08 +0900 Subject: [PATCH 305/547] fix(pid_longitudinal_controller): use is_autoware_control_enabled for manual driving (#5092) * fix(pid_longitudinal_controller): use is_autoware_control_enabled for manual driving Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../src/pid_longitudinal_controller.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index 985a22d26f9f4..fc10611e74287 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -923,7 +923,8 @@ double PidLongitudinalController::applyVelocityFeedback( { const double current_vel_abs = std::fabs(current_vel); const double target_vel_abs = std::fabs(target_motion.vel); - const bool is_under_control = m_current_operation_mode.mode == OperationModeState::AUTONOMOUS; + const bool is_under_control = m_current_operation_mode.is_autoware_control_enabled && + m_current_operation_mode.mode == OperationModeState::AUTONOMOUS; const bool enable_integration = (current_vel_abs > m_current_vel_threshold_pid_integrate) && is_under_control; const double error_vel_filtered = m_lpf_vel_error->filter(target_vel_abs - current_vel_abs); From a73bbf97c9d833a05bb8533e7bebb8f9b8d51fb6 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 11:08:10 +0900 Subject: [PATCH 306/547] feat(pid_longitudinal_controller): transit state from emergency under the looser condition on manual driving (#5093) * feat(pid_longitudinal_controller): transit from emergency under the looser condition on manual driving Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka * add NOTE Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../src/pid_longitudinal_controller.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index fc10611e74287..8636448515061 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -531,10 +531,8 @@ void PidLongitudinalController::updateControlState(const ControlData & control_d RCLCPP_INFO_SKIPFIRST_THROTTLE(logger_, *clock_, 5000, "%s", s); }; - // if current operation mode is not autonomous mode, then change state to stopped - if (m_current_operation_mode.mode != OperationModeState::AUTONOMOUS) { - return changeState(ControlState::STOPPED); - } + const bool is_under_control = m_current_operation_mode.is_autoware_control_enabled && + m_current_operation_mode.mode == OperationModeState::AUTONOMOUS; // transit state // in DRIVE state @@ -543,6 +541,12 @@ void PidLongitudinalController::updateControlState(const ControlData & control_d return changeState(ControlState::EMERGENCY); } + if (!is_under_control && stopped_condition && keep_stopped_condition) { + // NOTE: When the ego is stopped on manual driving, since the driving state may transit to + // autonomous, keep_stopped_condition should be checked. + return changeState(ControlState::STOPPED); + } + if (m_enable_smooth_stop) { if (stopping_condition) { // predictions after input time delay @@ -609,8 +613,14 @@ void PidLongitudinalController::updateControlState(const ControlData & control_d // in EMERGENCY state if (m_control_state == ControlState::EMERGENCY) { - if (stopped_condition && !emergency_condition) { - return changeState(ControlState::STOPPED); + if (!emergency_condition) { + if (stopped_condition) { + return changeState(ControlState::STOPPED); + } + if (!is_under_control) { + // NOTE: On manual driving, no need stopping to exit the emergency. + return changeState(ControlState::DRIVE); + } } return; } From b6446b0e91d5230d27a8bbc2c64ec19b082e40de Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 25 Sep 2023 11:34:18 +0900 Subject: [PATCH 307/547] fix(motion_velocity_smoother): change curvature calculation distance parameter (#4924) * fix(motion_velocity_smoother): use curvature calculation distance parameter Signed-off-by: Takamasa Horibe * fix param format Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- ...efault_motion_velocity_smoother.param.yaml | 19 ++++++++++--------- .../src/smoother/smoother_base.cpp | 6 ++---- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml b/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml index e56845c1c9bd6..21ad85137ef36 100644 --- a/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml +++ b/planning/motion_velocity_smoother/config/default_motion_velocity_smoother.param.yaml @@ -7,13 +7,21 @@ # external velocity limit parameter margin_to_insert_external_velocity_limit: 0.3 # margin distance to insert external velocity limit [m] - # curve parameters + # ---- curve parameters ---- + # - common parameters - + curvature_calculation_distance: 5.0 # distance of points while curvature is calculating for the steer rate and lateral acceleration limit [m] + # - lateral acceleration limit parameters - enable_lateral_acc_limit: true # To toggle the lateral acc filter on and off. You can switch it dynamically at runtime. max_lateral_accel: 0.8 # max lateral acceleration limit [m/ss] - min_curve_velocity: 2.74 # min velocity at lateral acceleration limit [m/ss] + min_curve_velocity: 2.74 # min velocity at lateral acceleration limit and steering angle rate limit [m/s] decel_distance_before_curve: 3.5 # slow speed distance before a curve for lateral acceleration limit decel_distance_after_curve: 2.0 # slow speed distance after a curve for lateral acceleration limit min_decel_for_lateral_acc_lim_filter: -2.5 # deceleration limit applied in the lateral acceleration filter to avoid sudden braking [m/ss] + # - steering angle rate limit parameters - + enable_steering_rate_limit: true # To toggle the steer rate filter on and off. You can switch it dynamically at runtime. + max_steering_angle_rate: 40.0 # maximum steering angle rate [degree/s] + resample_ds: 0.1 # distance between trajectory points [m] + curvature_threshold: 0.02 # if curvature > curvature_threshold, steeringRateLimit is triggered [1/m] # engage & replan parameters replan_vel_deviation: 5.53 # velocity deviation to replan initial velocity [m/s] @@ -49,13 +57,6 @@ post_sparse_resample_dt: 0.1 # resample time interval for sparse sampling [s] post_sparse_min_interval_distance: 1.0 # minimum points-interval length for sparse sampling [m] - # steering angle rate limit parameters - enable_steering_rate_limit: true # To toggle the steer rate filter on and off. You can switch it dynamically at runtime. - max_steering_angle_rate: 40.0 # maximum steering angle rate [degree/s] - resample_ds: 0.1 # distance between trajectory points [m] - curvature_threshold: 0.02 # if curvature > curvature_threshold, steeringRateLimit is triggered [1/m] - curvature_calculation_distance: 1.0 # distance of points while curvature is calculating [m] - # system over_stop_velocity_warn_thr: 1.389 # used to check if the optimization exceeds the input velocity on the stop point diff --git a/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp b/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp index 7f058d0d138d0..4e5efdbfc32e4 100644 --- a/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp +++ b/planning/motion_velocity_smoother/src/smoother/smoother_base.cpp @@ -103,8 +103,6 @@ TrajectoryPoints SmootherBase::applyLateralAccelerationFilter( return input; // cannot calculate lateral acc. do nothing. } - constexpr double curvature_calc_dist = 5.0; // [m] calc curvature with 5m away points - // Interpolate with constant interval distance for lateral acceleration calculation. TrajectoryPoints output; const double points_interval = @@ -124,8 +122,8 @@ TrajectoryPoints SmootherBase::applyLateralAccelerationFilter( output = input; } - const size_t idx_dist = - static_cast(std::max(static_cast((curvature_calc_dist) / points_interval), 1)); + const size_t idx_dist = static_cast( + std::max(static_cast((base_param_.curvature_calculation_distance) / points_interval), 1)); // Calculate curvature assuming the trajectory points interval is constant const auto curvature_v = trajectory_utils::calcTrajectoryCurvatureFrom3Points(output, idx_dist); From e2123a2146f0ba44be65b3604a26b5d3f2c60699 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:12:21 +0900 Subject: [PATCH 308/547] feat(imu_corrector): add gyro_bias estimation in diag (#5054) * feat(imu_corrector): add gyro_bias estimation in diag Signed-off-by: kminoda * update Signed-off-by: kminoda --------- Signed-off-by: kminoda --- sensing/imu_corrector/src/gyro_bias_estimator.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.cpp b/sensing/imu_corrector/src/gyro_bias_estimator.cpp index 581eccb0097d4..5f41a9089b6ee 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimator.cpp +++ b/sensing/imu_corrector/src/gyro_bias_estimator.cpp @@ -83,6 +83,14 @@ void GyroBiasEstimator::update_diagnostics(diagnostic_updater::DiagnosticStatusW stat.add("gyro_bias", "Bias estimation is not yet ready because of insufficient data."); stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "Not initialized"); } else { + stat.add("gyro_bias_x_for_imu_corrector", gyro_bias_.value().x); + stat.add("gyro_bias_y_for_imu_corrector", gyro_bias_.value().y); + stat.add("gyro_bias_z_for_imu_corrector", gyro_bias_.value().z); + + stat.add("estimated_gyro_bias_x", gyro_bias_.value().x - angular_velocity_offset_x_); + stat.add("estimated_gyro_bias_y", gyro_bias_.value().y - angular_velocity_offset_y_); + stat.add("estimated_gyro_bias_z", gyro_bias_.value().z - angular_velocity_offset_z_); + // Validation const bool is_bias_small_enough = std::abs(gyro_bias_.value().x - angular_velocity_offset_x_) < gyro_bias_threshold_ && From 5b92c71ba22aea5c0564d21fe6ea2f1e92cf5640 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 14:04:56 +0900 Subject: [PATCH 309/547] feat(intersection): consider amber traffic signal for collision detection level (#5096) * feat(intersection): consider amber traffic signal for collision detection level Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka * protected -> prioritized Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/intersection.param.yaml | 11 ++++-- .../src/manager.cpp | 25 ++++++++---- .../src/scene_intersection.cpp | 39 ++++++++++++------- .../src/scene_intersection.hpp | 19 +++++---- .../src/util.cpp | 31 ++++++++------- .../src/util.hpp | 3 +- .../src/util_type.hpp | 18 ++++++--- 7 files changed, 93 insertions(+), 53 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index def2335e20358..5b626ec19d917 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -24,12 +24,15 @@ state_transit_margin_time: 1.0 min_predicted_path_confidence: 0.05 minimum_ego_predicted_velocity: 1.388 # [m/s] - normal: - collision_start_margin_time: 4.0 # [s] this + state_transit_margin_time should be higher to account for collision with fast/accelerating object - collision_end_margin_time: 6.0 # [s] this + state_transit_margin_time should be higher to account for collision with slow/decelerating object - relaxed: + fully_prioritized: collision_start_margin_time: 2.0 collision_end_margin_time: 0.0 + partially_prioritized: + collision_start_margin_time: 2.0 + collision_end_margin_time: 2.0 + not_prioritized: + collision_start_margin_time: 4.0 # [s] this + state_transit_margin_time should be higher to account for collision with fast/accelerating object + collision_end_margin_time: 6.0 # [s] this + state_transit_margin_time should be higher to account for collision with slow/decelerating object keep_detection_vel_thr: 0.833 # == 3.0km/h. keep detection if ego is ego.vel < keep_detection_vel_thr occlusion: diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index b2cdcf5db79eb..633ab3115b94c 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -84,14 +84,23 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".collision_detection.minimum_ego_predicted_velocity"); ip.collision_detection.state_transit_margin_time = getOrDeclareParameter(node, ns + ".collision_detection.state_transit_margin_time"); - ip.collision_detection.normal.collision_start_margin_time = getOrDeclareParameter( - node, ns + ".collision_detection.normal.collision_start_margin_time"); - ip.collision_detection.normal.collision_end_margin_time = getOrDeclareParameter( - node, ns + ".collision_detection.normal.collision_end_margin_time"); - ip.collision_detection.relaxed.collision_start_margin_time = getOrDeclareParameter( - node, ns + ".collision_detection.relaxed.collision_start_margin_time"); - ip.collision_detection.relaxed.collision_end_margin_time = getOrDeclareParameter( - node, ns + ".collision_detection.relaxed.collision_end_margin_time"); + ip.collision_detection.fully_prioritized.collision_start_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.fully_prioritized.collision_start_margin_time"); + ip.collision_detection.fully_prioritized.collision_end_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.fully_prioritized.collision_end_margin_time"); + ip.collision_detection.partially_prioritized.collision_start_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.partially_prioritized.collision_start_margin_time"); + ip.collision_detection.partially_prioritized.collision_end_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.partially_prioritized.collision_end_margin_time"); + ip.collision_detection.not_prioritized.collision_start_margin_time = + getOrDeclareParameter( + node, ns + ".collision_detection.not_prioritized.collision_start_margin_time"); + ip.collision_detection.not_prioritized.collision_end_margin_time = getOrDeclareParameter( + node, ns + ".collision_detection.not_prioritized.collision_end_margin_time"); ip.collision_detection.keep_detection_vel_thr = getOrDeclareParameter(node, ns + ".collision_detection.keep_detection_vel_thr"); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index aa1ceca086bc2..f335970f4ffda 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -757,9 +757,11 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( planner_param_.occlusion.occlusion_attention_area_length, planner_param_.common.consider_wrong_direction_vehicle); } - const bool tl_arrow_solid_on = - util::isTrafficLightArrowActivated(assigned_lanelet, planner_data_->traffic_light_id_map); - intersection_lanelets_.value().update(tl_arrow_solid_on, interpolated_path_info); + const auto traffic_prioritized_level = + util::getTrafficPrioritizedLevel(assigned_lanelet, planner_data_->traffic_light_id_map); + const bool is_prioritized = + traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED; + intersection_lanelets_.value().update(is_prioritized, interpolated_path_info); const auto & conflicting_lanelets = intersection_lanelets_.value().conflicting(); const auto & first_conflicting_area_opt = intersection_lanelets_.value().first_conflicting_area(); @@ -890,7 +892,7 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } // calculate dynamic collision around detection area - const double time_delay = (is_go_out_ || tl_arrow_solid_on) + const double time_delay = (is_go_out_ || is_prioritized) ? 0.0 : (planner_param_.collision_detection.state_transit_margin_time - collision_state_machine_.getDuration()); @@ -898,14 +900,14 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( filterTargetObjects(attention_lanelets, adjacent_lanelets, intersection_area); const bool has_collision = checkCollision( - *path, target_objects, path_lanelets, closest_idx, time_delay, tl_arrow_solid_on); + *path, target_objects, path_lanelets, closest_idx, time_delay, traffic_prioritized_level); collision_state_machine_.setStateWithMarginTime( has_collision ? StateMachine::State::STOP : StateMachine::State::GO, logger_.get_child("collision state_machine"), *clock_); const bool has_collision_with_margin = collision_state_machine_.getState() == StateMachine::State::STOP; - if (tl_arrow_solid_on) { + if (is_prioritized) { return TrafficLightArrowSolidOn{ has_collision_with_margin, closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; } @@ -931,7 +933,7 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( object.kinematics.initial_twist_with_covariance.twist.linear.y) <= thresh; }); const bool is_occlusion_cleared = - (enable_occlusion_detection_ && !occlusion_attention_lanelets.empty() && !tl_arrow_solid_on) + (enable_occlusion_detection_ && !occlusion_attention_lanelets.empty() && !is_prioritized) ? isOcclusionCleared( *planner_data_->occupancy_grid, occlusion_attention_area, adjacent_lanelets, first_attention_area, interpolated_path_info, occlusion_attention_divisions, @@ -1058,7 +1060,7 @@ bool IntersectionModule::checkCollision( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const autoware_auto_perception_msgs::msg::PredictedObjects & objects, const util::PathLanelets & path_lanelets, const int closest_idx, const double time_delay, - const bool tl_arrow_solid_on) + const util::TrafficPrioritizedLevel & traffic_prioritized_level) { using lanelet::utils::getArcCoordinates; using lanelet::utils::getPolygonFromArcLength; @@ -1081,12 +1083,21 @@ bool IntersectionModule::checkCollision( const auto ego_poly = ego_lane.polygon2d().basicPolygon(); // check collision between predicted_path and ego_area - const double collision_start_margin_time = - tl_arrow_solid_on ? planner_param_.collision_detection.relaxed.collision_start_margin_time - : planner_param_.collision_detection.normal.collision_start_margin_time; - const double collision_end_margin_time = - tl_arrow_solid_on ? planner_param_.collision_detection.relaxed.collision_end_margin_time - : planner_param_.collision_detection.normal.collision_end_margin_time; + const auto [collision_start_margin_time, collision_end_margin_time] = [&]() { + if (traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED) { + return std::make_pair( + planner_param_.collision_detection.fully_prioritized.collision_start_margin_time, + planner_param_.collision_detection.fully_prioritized.collision_end_margin_time); + } + if (traffic_prioritized_level == util::TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED) { + return std::make_pair( + planner_param_.collision_detection.partially_prioritized.collision_start_margin_time, + planner_param_.collision_detection.partially_prioritized.collision_end_margin_time); + } + return std::make_pair( + planner_param_.collision_detection.not_prioritized.collision_start_margin_time, + planner_param_.collision_detection.not_prioritized.collision_end_margin_time); + }(); bool collision_detected = false; for (const auto & object : target_objects.objects) { for (const auto & predicted_path : object.kinematics.predicted_paths) { diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 8063300b9b787..268bdb5fe1f72 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -79,16 +79,21 @@ class IntersectionModule : public SceneModuleInterface //! minimum confidence value of predicted path to use for collision detection double minimum_ego_predicted_velocity; //! used to calculate ego's future velocity profile double state_transit_margin_time; - struct Normal + struct FullyPrioritized { double collision_start_margin_time; //! start margin time to check collision double collision_end_margin_time; //! end margin time to check collision - } normal; - struct Relaxed + } fully_prioritized; + struct PartiallyPrioritized { - double collision_start_margin_time; - double collision_end_margin_time; - } relaxed; + double collision_start_margin_time; //! start margin time to check collision + double collision_end_margin_time; //! end margin time to check collision + } partially_prioritized; + struct NotPrioritized + { + double collision_start_margin_time; //! start margin time to check collision + double collision_end_margin_time; //! end margin time to check collision + } not_prioritized; double keep_detection_vel_thr; //! keep detection if ego is ego.vel < keep_detection_vel_thr } collision_detection; struct Occlusion @@ -250,7 +255,7 @@ class IntersectionModule : public SceneModuleInterface const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const autoware_auto_perception_msgs::msg::PredictedObjects & target_objects, const util::PathLanelets & path_lanelets, const int closest_idx, const double time_delay, - const bool tl_arrow_solid_on); + const util::TrafficPrioritizedLevel & traffic_prioritized_level); bool isOcclusionCleared( const nav_msgs::msg::OccupancyGrid & occ_grid, diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 24c3454fd7e88..a59c11deb55d1 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -765,12 +765,11 @@ bool hasAssociatedTrafficLight(lanelet::ConstLanelet lane) return tl_id.has_value(); } -bool isTrafficLightArrowActivated( +TrafficPrioritizedLevel getTrafficPrioritizedLevel( lanelet::ConstLanelet lane, const std::map & tl_infos) { using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; - const auto & turn_direction = lane.attributeOr("turn_direction", "else"); std::optional tl_id = std::nullopt; for (auto && tl_reg_elem : lane.regulatoryElementsAs()) { tl_id = tl_reg_elem->id(); @@ -778,24 +777,28 @@ bool isTrafficLightArrowActivated( } if (!tl_id) { // this lane has no traffic light - return false; + return TrafficPrioritizedLevel::NOT_PRIORITIZED; } const auto tl_info_it = tl_infos.find(tl_id.value()); if (tl_info_it == tl_infos.end()) { // the info of this traffic light is not available - return false; + return TrafficPrioritizedLevel::NOT_PRIORITIZED; } const auto & tl_info = tl_info_it->second; + bool has_amber_signal{false}; for (auto && tl_light : tl_info.signal.elements) { - if (tl_light.color != TrafficSignalElement::GREEN) continue; - if (tl_light.status != TrafficSignalElement::SOLID_ON) continue; - if (turn_direction == std::string("left") && tl_light.shape == TrafficSignalElement::LEFT_ARROW) - return true; - if ( - turn_direction == std::string("right") && tl_light.shape == TrafficSignalElement::RIGHT_ARROW) - return true; + if (tl_light.color == TrafficSignalElement::AMBER) { + has_amber_signal = true; + } + if (tl_light.color == TrafficSignalElement::RED) { + // NOTE: Return here since the red signal has the highest priority. + return TrafficPrioritizedLevel::FULLY_PRIORITIZED; + } } - return false; + if (has_amber_signal) { + return TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED; + } + return TrafficPrioritizedLevel::NOT_PRIORITIZED; } std::vector generateDetectionLaneDivisions( @@ -1178,9 +1181,9 @@ double calcDistanceUntilIntersectionLanelet( } void IntersectionLanelets::update( - const bool tl_arrow_solid_on, const InterpolatedPathInfo & interpolated_path_info) + const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info) { - tl_arrow_solid_on_ = tl_arrow_solid_on; + is_prioritized_ = is_prioritized; // find the first conflicting/detection area polygon intersecting the path const auto & path = interpolated_path_info.path; const auto & lane_interval = interpolated_path_info.lane_id_interval.value(); diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index e766da6651417..72e96876f02c9 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -111,7 +111,8 @@ std::optional getIntersectionArea( lanelet::ConstLanelet assigned_lane, lanelet::LaneletMapConstPtr lanelet_map_ptr); bool hasAssociatedTrafficLight(lanelet::ConstLanelet lane); -bool isTrafficLightArrowActivated( + +TrafficPrioritizedLevel getTrafficPrioritizedLevel( lanelet::ConstLanelet lane, const std::map & tl_infos); std::vector generateDetectionLaneDivisions( diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 64be330a99232..643ac884d9361 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -64,20 +64,20 @@ struct InterpolatedPathInfo struct IntersectionLanelets { public: - void update(const bool tl_arrow_solid_on, const InterpolatedPathInfo & interpolated_path_info); + void update(const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info); const lanelet::ConstLanelets & attention() const { - return tl_arrow_solid_on_ ? attention_non_preceding_ : attention_; + return is_prioritized_ ? attention_non_preceding_ : attention_; } const lanelet::ConstLanelets & conflicting() const { return conflicting_; } const lanelet::ConstLanelets & adjacent() const { return adjacent_; } const lanelet::ConstLanelets & occlusion_attention() const { - return tl_arrow_solid_on_ ? attention_non_preceding_ : occlusion_attention_; + return is_prioritized_ ? attention_non_preceding_ : occlusion_attention_; } const std::vector & attention_area() const { - return tl_arrow_solid_on_ ? attention_non_preceding_area_ : attention_area_; + return is_prioritized_ ? attention_non_preceding_area_ : attention_area_; } const std::vector & conflicting_area() const { @@ -110,7 +110,7 @@ struct IntersectionLanelets // the first area intersecting with the path // even if lane change/re-routing happened on the intersection, these areas area are supposed to // be invariant under the 'associative' lanes. - bool tl_arrow_solid_on_ = false; + bool is_prioritized_ = false; std::optional first_conflicting_area_ = std::nullopt; std::optional first_attention_area_ = std::nullopt; }; @@ -151,6 +151,14 @@ struct PathLanelets // conflicting lanelets plus the next lane part of the path }; +enum class TrafficPrioritizedLevel { + // The target lane's traffic signal is red or the ego's traffic signal has an arrow. + FULLY_PRIORITIZED = 0, + // The target lane's traffic signal is amber + PARTIALLY_PRIORITIZED, + // The target lane's traffic signal is green + NOT_PRIORITIZED +}; } // namespace behavior_velocity_planner::util #endif // UTIL_TYPE_HPP_ From 367686bc8c8454bcc47f7962408ffd643f4fa7d1 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 14:15:46 +0900 Subject: [PATCH 310/547] fix(pid_longitudinal_controller): fix the sign of velocity (#5094) Signed-off-by: Takayuki Murooka --- .../pid_longitudinal_controller.hpp | 2 +- .../src/pid_longitudinal_controller.cpp | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp b/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp index 41d98a4d15541..921cd3995f6a9 100644 --- a/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp +++ b/control/pid_longitudinal_controller/include/pid_longitudinal_controller/pid_longitudinal_controller.hpp @@ -365,7 +365,7 @@ class PidLongitudinalController : public trajectory_follower::LongitudinalContro * @param [in] current_vel current velocity of the vehicle */ double applyVelocityFeedback( - const Motion target_motion, const double dt, const double current_vel); + const Motion target_motion, const double dt, const double current_vel, const Shift & shift); /** * @brief update variables for debugging about pitch diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index 8636448515061..a943aa05be5ff 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -654,7 +654,8 @@ PidLongitudinalController::Motion PidLongitudinalController::calcCtrlCmd( m_debug_values.setValues(DebugValues::TYPE::PREDICTED_VEL, pred_vel_in_target); raw_ctrl_cmd.vel = target_motion.vel; - raw_ctrl_cmd.acc = applyVelocityFeedback(target_motion, control_data.dt, pred_vel_in_target); + raw_ctrl_cmd.acc = + applyVelocityFeedback(target_motion, control_data.dt, pred_vel_in_target, control_data.shift); RCLCPP_DEBUG( logger_, "[feedback control] vel: %3.3f, acc: %3.3f, dt: %3.3f, v_curr: %3.3f, v_ref: %3.3f " @@ -929,15 +930,16 @@ double PidLongitudinalController::predictedVelocityInTargetPoint( } double PidLongitudinalController::applyVelocityFeedback( - const Motion target_motion, const double dt, const double current_vel) + const Motion target_motion, const double dt, const double current_vel, const Shift & shift) { - const double current_vel_abs = std::fabs(current_vel); - const double target_vel_abs = std::fabs(target_motion.vel); + // NOTE: Acceleration command is always positive even if the ego drives backward. + const double vel_sign = (shift == Shift::Forward) ? 1.0 : (shift == Shift::Reverse ? -1.0 : 0.0); + const double diff_vel = (target_motion.vel - current_vel) * vel_sign; const bool is_under_control = m_current_operation_mode.is_autoware_control_enabled && m_current_operation_mode.mode == OperationModeState::AUTONOMOUS; const bool enable_integration = - (current_vel_abs > m_current_vel_threshold_pid_integrate) && is_under_control; - const double error_vel_filtered = m_lpf_vel_error->filter(target_vel_abs - current_vel_abs); + (std::abs(current_vel) > m_current_vel_threshold_pid_integrate) && is_under_control; + const double error_vel_filtered = m_lpf_vel_error->filter(diff_vel); std::vector pid_contributions(3); const double pid_acc = @@ -950,8 +952,8 @@ double PidLongitudinalController::applyVelocityFeedback( // deviation will be bigger. constexpr double ff_scale_max = 2.0; // for safety constexpr double ff_scale_min = 0.5; // for safety - const double ff_scale = - std::clamp(current_vel_abs / std::max(target_vel_abs, 0.1), ff_scale_min, ff_scale_max); + const double ff_scale = std::clamp( + std::abs(current_vel) / std::max(std::abs(target_motion.vel), 0.1), ff_scale_min, ff_scale_max); const double ff_acc = target_motion.acc * ff_scale; const double feedback_acc = ff_acc + pid_acc; From 7f98ecfd9e6a12f4a506fe52f1bdd1a6302d17cc Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 14:16:22 +0900 Subject: [PATCH 311/547] fix(pid_longitudinal_controller): better slope compensation (#5095) Signed-off-by: Takayuki Murooka --- .../src/longitudinal_controller_utils.cpp | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp b/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp index 41bbb48ff5d08..9791a1f0b5e3e 100644 --- a/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp +++ b/control/pid_longitudinal_controller/src/longitudinal_controller_utils.cpp @@ -91,32 +91,22 @@ double getPitchByTraj( return 0.0; } - for (size_t i = nearest_idx + 1; i < trajectory.points.size(); ++i) { - const double dist = tier4_autoware_utils::calcDistance2d( - trajectory.points.at(nearest_idx), trajectory.points.at(i)); - if (dist > wheel_base) { - // calculate pitch from trajectory between rear wheel (nearest) and front center (i) - return tier4_autoware_utils::calcElevationAngle( - trajectory.points.at(nearest_idx).pose.position, trajectory.points.at(i).pose.position); + const auto [prev_idx, next_idx] = [&]() { + for (size_t i = nearest_idx + 1; i < trajectory.points.size(); ++i) { + const double dist = tier4_autoware_utils::calcDistance2d( + trajectory.points.at(nearest_idx), trajectory.points.at(i)); + if (dist > wheel_base) { + // calculate pitch from trajectory between rear wheel (nearest) and front center (i) + return std::make_pair(nearest_idx, i); + } } - } - - // close to goal - for (size_t i = trajectory.points.size() - 1; i > 0; --i) { - const double dist = - tier4_autoware_utils::calcDistance2d(trajectory.points.back(), trajectory.points.at(i)); - - if (dist > wheel_base) { - // calculate pitch from trajectory - // between wheelbase behind the end of trajectory (i) and the end of trajectory (back) - return tier4_autoware_utils::calcElevationAngle( - trajectory.points.at(i).pose.position, trajectory.points.back().pose.position); - } - } + // NOTE: The ego pose is close to the goal. + return std::make_pair( + std::min(nearest_idx, trajectory.points.size() - 2), trajectory.points.size() - 1); + }(); - // calculate pitch from trajectory between the beginning and end of trajectory return tier4_autoware_utils::calcElevationAngle( - trajectory.points.at(0).pose.position, trajectory.points.back().pose.position); + trajectory.points.at(prev_idx).pose.position, trajectory.points.at(next_idx).pose.position); } Pose calcPoseAfterTimeDelay( From 092ecea44c3e38242ca4b1d5b27a9fc4c86a8a26 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Mon, 25 Sep 2023 14:16:47 +0900 Subject: [PATCH 312/547] feat(tier4_planning_rviz_plugin): visualize text of slope angle (#5091) --- .../include/path/display_base.hpp | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/common/tier4_planning_rviz_plugin/include/path/display_base.hpp b/common/tier4_planning_rviz_plugin/include/path/display_base.hpp index ccedcceefaaf9..74c2a60df3f32 100644 --- a/common/tier4_planning_rviz_plugin/include/path/display_base.hpp +++ b/common/tier4_planning_rviz_plugin/include/path/display_base.hpp @@ -133,7 +133,10 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay property_point_alpha_{"Alpha", 1.0, "", &property_point_view_}, property_point_color_{"Color", QColor(0, 60, 255), "", &property_point_view_}, property_point_radius_{"Radius", 0.1, "", &property_point_view_}, - property_point_offset_{"Offset", 0.0, "", &property_point_view_} + property_point_offset_{"Offset", 0.0, "", &property_point_view_}, + // slope + property_slope_text_view_{"View Text Slope", false, "", this}, + property_slope_text_scale_{"Scale", 0.3, "", &property_slope_text_view_} { // path property_path_width_.setMin(0.0); @@ -171,6 +174,12 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay node->detachAllObjects(); this->scene_manager_->destroySceneNode(node); } + for (size_t i = 0; i < slope_text_nodes_.size(); i++) { + Ogre::SceneNode * node = slope_text_nodes_.at(i); + node->removeAndDestroyAllChildren(); + node->detachAllObjects(); + this->scene_manager_->destroySceneNode(node); + } this->scene_manager_->destroyManualObject(footprint_manual_object_); this->scene_manager_->destroyManualObject(point_manual_object_); } @@ -198,6 +207,7 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay rviz_common::MessageFilterDisplay::MFDClass::reset(); path_manual_object_->clear(); velocity_manual_object_->clear(); + for (size_t i = 0; i < velocity_texts_.size(); i++) { Ogre::SceneNode * node = velocity_text_nodes_.at(i); node->detachAllObjects(); @@ -206,6 +216,16 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay } velocity_text_nodes_.clear(); velocity_texts_.clear(); + + for (size_t i = 0; i < slope_texts_.size(); i++) { + Ogre::SceneNode * node = slope_text_nodes_.at(i); + node->detachAllObjects(); + node->removeAndDestroyAllChildren(); + this->scene_manager_->destroySceneNode(node); + } + slope_text_nodes_.clear(); + slope_texts_.clear(); + footprint_manual_object_->clear(); point_manual_object_->clear(); } @@ -300,6 +320,29 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay velocity_text_nodes_.resize(msg_ptr->points.size()); } + if (msg_ptr->points.size() > slope_texts_.size()) { + for (size_t i = slope_texts_.size(); i < msg_ptr->points.size(); i++) { + Ogre::SceneNode * node = this->scene_node_->createChildSceneNode(); + rviz_rendering::MovableText * text = + new rviz_rendering::MovableText("not initialized", "Liberation Sans", 0.1); + text->setVisible(false); + text->setTextAlignment( + rviz_rendering::MovableText::H_CENTER, rviz_rendering::MovableText::V_ABOVE); + node->attachObject(text); + slope_texts_.push_back(text); + slope_text_nodes_.push_back(node); + } + } else if (msg_ptr->points.size() < slope_texts_.size()) { + for (size_t i = slope_texts_.size() - 1; i >= msg_ptr->points.size(); i--) { + Ogre::SceneNode * node = slope_text_nodes_.at(i); + node->detachAllObjects(); + node->removeAndDestroyAllChildren(); + this->scene_manager_->destroySceneNode(node); + } + slope_texts_.resize(msg_ptr->points.size()); + slope_text_nodes_.resize(msg_ptr->points.size()); + } + const auto info = vehicle_footprint_info_; const float left = property_path_width_view_.getBool() ? -property_path_width_.getFloat() / 2.0 : -info->width / 2.0; @@ -395,6 +438,38 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay rviz_rendering::MovableText * text = velocity_texts_.at(point_idx); text->setVisible(false); } + + // slope text + if (property_slope_text_view_.getBool() && 1 < msg_ptr->points.size()) { + const size_t prev_idx = + (point_idx != msg_ptr->points.size() - 1) ? point_idx : point_idx - 1; + const size_t next_idx = + (point_idx != msg_ptr->points.size() - 1) ? point_idx + 1 : point_idx; + + const auto & prev_path_pos = + tier4_autoware_utils::getPose(msg_ptr->points.at(prev_idx)).position; + const auto & next_path_pos = + tier4_autoware_utils::getPose(msg_ptr->points.at(next_idx)).position; + + Ogre::Vector3 position; + position.x = pose.position.x; + position.y = pose.position.y; + position.z = pose.position.z; + Ogre::SceneNode * node = slope_text_nodes_.at(point_idx); + node->setPosition(position); + + rviz_rendering::MovableText * text = slope_texts_.at(point_idx); + const double slope = tier4_autoware_utils::calcElevationAngle(prev_path_pos, next_path_pos); + + std::stringstream ss; + ss << std::fixed << std::setprecision(2) << slope; + text->setCaption(ss.str()); + text->setCharacterHeight(property_slope_text_scale_.getFloat()); + text->setVisible(true); + } else { + rviz_rendering::MovableText * text = slope_texts_.at(point_idx); + text->setVisible(false); + } } path_manual_object_->end(); @@ -526,6 +601,8 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay Ogre::ManualObject * point_manual_object_{nullptr}; std::vector velocity_texts_; std::vector velocity_text_nodes_; + std::vector slope_texts_; + std::vector slope_text_nodes_; rviz_common::properties::BoolProperty property_path_view_; rviz_common::properties::BoolProperty property_path_width_view_; @@ -556,6 +633,9 @@ class AutowarePathBaseDisplay : public rviz_common::MessageFilterDisplay rviz_common::properties::FloatProperty property_point_radius_; rviz_common::properties::FloatProperty property_point_offset_; + rviz_common::properties::BoolProperty property_slope_text_view_; + rviz_common::properties::FloatProperty property_slope_text_scale_; + std::shared_ptr vehicle_info_; private: From eeab6c09cdda6bc5321397fb1ee125c3b62ff248 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 25 Sep 2023 15:35:35 +0900 Subject: [PATCH 313/547] fix(start_planner): remove inverse orientation points (#5100) * fix(start_planner): remove inverse orientation points Signed-off-by: kosuke55 * use loop Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../motion_utils/trajectory/trajectory.hpp | 15 ++++++++++----- .../utils/start_goal_planner_common/utils.hpp | 15 --------------- .../src/utils/goal_planner/shift_pull_over.cpp | 3 --- .../src/utils/path_shifter/path_shifter.cpp | 14 ++++++++++++-- .../src/utils/start_goal_planner_common/utils.cpp | 15 --------------- .../src/utils/start_planner/shift_pull_out.cpp | 3 --- 6 files changed, 22 insertions(+), 43 deletions(-) diff --git a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp index 83221f4d2f0ae..94cf48f29c57a 100644 --- a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp @@ -1850,7 +1850,8 @@ insertOrientation /** * @brief Remove points with invalid orientation differences from a given points container - * (trajectory, path, ...) + * (trajectory, path, ...). Check the difference between the angles of two points and the difference + * between the azimuth angle between the two points and the angle of the next point. * @param points Points of trajectory, path, or other point container (input / output) * @param max_yaw_diff Maximum acceptable yaw angle difference between two consecutive points in * radians (default: M_PI_2) @@ -1859,10 +1860,14 @@ template void removeInvalidOrientationPoints(T & points, const double max_yaw_diff = M_PI_2) { for (size_t i = 1; i < points.size();) { - const double yaw1 = tf2::getYaw(tier4_autoware_utils::getPose(points.at(i - 1)).orientation); - const double yaw2 = tf2::getYaw(tier4_autoware_utils::getPose(points.at(i)).orientation); - - if (max_yaw_diff < std::abs(tier4_autoware_utils::normalizeRadian(yaw1 - yaw2))) { + const auto p1 = tier4_autoware_utils::getPose(points.at(i - 1)); + const auto p2 = tier4_autoware_utils::getPose(points.at(i)); + const double yaw1 = tf2::getYaw(p1.orientation); + const double yaw2 = tf2::getYaw(p2.orientation); + + if ( + max_yaw_diff < std::abs(tier4_autoware_utils::normalizeRadian(yaw1 - yaw2)) || + !tier4_autoware_utils::isDrivingForward(p1, p2)) { points.erase(points.begin() + i); } else { ++i; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp index 913018d25f8f8..37206c79542ff 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/utils.hpp @@ -98,21 +98,6 @@ std::pair getPairsTerminalVelocityAndAccel( const std::vector> & pairs_terminal_velocity_and_accel, const size_t current_path_idx); -/** - * @brief removeInverseOrderPathPoints function - * - * This function is designed to handle a situation that can arise when shifting paths on a curve, - * where the positions of the path points may become inverted (i.e., a point further along the path - * comes to be located before an earlier point). It corrects for this by using the function - * tier4_autoware_utils::isDrivingForward(p1, p2) to check if each pair of adjacent points is in - * the correct order (with the second point being 'forward' of the first). Any points which fail - * this test are omitted from the returned PathWithLaneId. - * - * @param path A path with points possibly in incorrect order. - * @return Returns a new PathWithLaneId that has all points in the correct order. - */ -PathWithLaneId removeInverseOrderPathPoints(const PathWithLaneId & path); - std::optional generateFeasibleStopPath( PathWithLaneId & current_path, std::shared_ptr planner_data, geometry_msgs::msg::Pose & stop_pose, const double maximum_deceleration, diff --git a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp index 1a690362fbe25..e1bdaf977dad8 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp @@ -172,9 +172,6 @@ boost::optional ShiftPullOver::generatePullOverPath( shifted_path.path.points.push_back(p); } - shifted_path.path = - utils::start_goal_planner_common::removeInverseOrderPathPoints(shifted_path.path); - // set the same z as the goal for (auto & p : shifted_path.path.points) { p.point.pose.position.z = goal_pose.position.z; diff --git a/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp b/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp index 44ba6bd364bc4..21d4ff666e28a 100644 --- a/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp +++ b/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp @@ -56,6 +56,7 @@ namespace behavior_path_planner using motion_utils::findNearestIndex; using motion_utils::insertOrientation; using motion_utils::removeInvalidOrientationPoints; +using motion_utils::removeOverlapPoints; void PathShifter::setPath(const PathWithLaneId & path) { @@ -147,9 +148,18 @@ bool PathShifter::generate( type == SHIFT_TYPE::SPLINE ? applySplineShifter(shifted_path, offset_back) : applyLinearShifter(shifted_path); - const bool is_driving_forward = true; - insertOrientation(shifted_path->path.points, is_driving_forward); + shifted_path->path.points = removeOverlapPoints(shifted_path->path.points); + // Use orientation before shift to remove points in reverse order + // before setting wrong azimuth orientation removeInvalidOrientationPoints(shifted_path->path.points); + size_t previous_size{shifted_path->path.points.size()}; + do { + previous_size = shifted_path->path.points.size(); + // Set the azimuth orientation to the next point at each point + insertOrientation(shifted_path->path.points, true); + // Use azimuth orientation to remove points in reverse order + removeInvalidOrientationPoints(shifted_path->path.points); + } while (previous_size != shifted_path->path.points.size()); // DEBUG RCLCPP_DEBUG_STREAM_THROTTLE( diff --git a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp index d52cebc6ce5eb..1eaea06c023f9 100644 --- a/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp +++ b/planning/behavior_path_planner/src/utils/start_goal_planner_common/utils.cpp @@ -167,21 +167,6 @@ std::pair getPairsTerminalVelocityAndAccel( return pairs_terminal_velocity_and_accel.at(current_path_idx); } -PathWithLaneId removeInverseOrderPathPoints(const PathWithLaneId & path) -{ - PathWithLaneId fixed_path; - fixed_path.header = path.header; - fixed_path.points.push_back(path.points.at(0)); - for (size_t i = 1; i < path.points.size(); i++) { - const auto p1 = path.points.at(i - 1).point.pose; - const auto p2 = path.points.at(i).point.pose; - if (tier4_autoware_utils::isDrivingForward(p1, p2)) { - fixed_path.points.push_back(path.points.at(i)); - } - } - return fixed_path; -} - std::optional generateFeasibleStopPath( PathWithLaneId & current_path, std::shared_ptr planner_data, geometry_msgs::msg::Pose & stop_pose, const double maximum_deceleration, diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index a4e4a2378e8f1..2de72d781994a 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -303,9 +303,6 @@ std::vector ShiftPullOut::calcPullOutPaths( continue; } - shifted_path.path = - utils::start_goal_planner_common::removeInverseOrderPathPoints(shifted_path.path); - // set velocity const size_t pull_out_end_idx = findNearestIndex(shifted_path.path.points, shift_end_pose_ptr->position); From f7a1b16d2c61c4aba6e1c63b9ef0fa848e640352 Mon Sep 17 00:00:00 2001 From: Yuki TAKAGI <141538661+yuki-takagi-66@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:24:33 +0900 Subject: [PATCH 314/547] feat(obstacle_cruise_planner)!: add ego pose consideration (#5036) * feat: add the feature to consider the current ego position Signed-off-by: Yuki Takagi * feat: add ego pose consideration. Both the lateral error and the yaw error are assumed to decrease to zero by a specified constant time (e.g 1.5 second). Signed-off-by: Yuki Takagi * feat: (continue) implement parameter settings for "cosider-current-ego-pose" Signed-off-by: Yuki Takagi * cleaned up the code Signed-off-by: Yuki Takagi * change description of the parameters Signed-off-by: Yuki Takagi * align format Signed-off-by: Yuki Takagi * Update planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp Co-authored-by: Kosuke Takeuchi * Update planning/obstacle_cruise_planner/src/node.cpp Co-authored-by: Kosuke Takeuchi --------- Signed-off-by: Yuki Takagi Co-authored-by: Kosuke Takeuchi --- .../config/obstacle_cruise_planner.param.yaml | 7 ++ .../include/obstacle_cruise_planner/node.hpp | 7 ++ .../obstacle_cruise_planner/polygon_utils.hpp | 8 +- planning/obstacle_cruise_planner/src/node.cpp | 71 ++++++++++++++-- .../src/polygon_utils.cpp | 83 +++++++------------ 5 files changed, 117 insertions(+), 59 deletions(-) diff --git a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml index f3f1932c44c43..73bc4a83249a4 100644 --- a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml +++ b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml @@ -106,6 +106,13 @@ successive_num_to_entry_slow_down_condition: 5 successive_num_to_exit_slow_down_condition: 5 + # consider the current ego pose (it is not the nearest pose on the reference trajectory) + # Both the lateral error and the yaw error are assumed to decrease to zero by the time duration "time_to_convergence" + # The both errors decrease with constant rates against the time. + consider_current_pose: + enable_to_consider_current_pose: false + time_to_convergence: 1.5 #[s] + cruise: pid_based_planner: use_velocity_limit_based_planner: true diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp index 00afc11985d72..5786a96f72c41 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp @@ -48,6 +48,10 @@ class ObstacleCruisePlannerNode : public rclcpp::Node void onSmoothedTrajectory(const Trajectory::ConstSharedPtr msg); // main functions + std::vector createOneStepPolygons( + const std::vector & traj_points, + const vehicle_info_util::VehicleInfo & vehicle_info, + const geometry_msgs::msg::Pose & current_ego_pose, const double lat_margin = 0.0) const; std::vector convertToObstacles(const std::vector & traj_points) const; std::tuple, std::vector, std::vector> determineEgoBehaviorAgainstObstacles( @@ -193,6 +197,9 @@ class ObstacleCruisePlannerNode : public rclcpp::Node double lat_hysteresis_margin_for_slow_down; int successive_num_to_entry_slow_down_condition; int successive_num_to_exit_slow_down_condition; + // consideration for the current ego pose + bool enable_to_consider_current_pose{false}; + double time_to_convergence{1.5}; }; BehaviorDeterminationParam behavior_determination_param_; diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp index 5178597bc4588..c2b14cc2e875f 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/polygon_utils.hpp @@ -33,6 +33,11 @@ namespace bg = boost::geometry; using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; +Polygon2d createOneStepPolygon( + const std::vector & last_poses, + const std::vector & current_poses, + const vehicle_info_util::VehicleInfo & vehicle_info, const double lat_margin); + std::optional getCollisionPoint( const std::vector & traj_points, const std::vector & traj_polygons, const Obstacle & obstacle, const bool is_driving_forward); @@ -45,9 +50,6 @@ std::vector getCollisionPoints( const double max_lat_dist = std::numeric_limits::max(), const double max_prediction_time_for_collision_check = std::numeric_limits::max()); -std::vector createOneStepPolygons( - const std::vector & traj_points, - const vehicle_info_util::VehicleInfo & vehicle_info, const double lat_margin = 0.0); } // namespace polygon_utils #endif // OBSTACLE_CRUISE_PLANNER__POLYGON_UTILS_HPP_ diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index d0c240a862a74..8ef610cc9df10 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -274,6 +274,10 @@ ObstacleCruisePlannerNode::BehaviorDeterminationParam::BehaviorDeterminationPara "behavior_determination.slow_down.successive_num_to_entry_slow_down_condition"); successive_num_to_exit_slow_down_condition = node.declare_parameter( "behavior_determination.slow_down.successive_num_to_exit_slow_down_condition"); + enable_to_consider_current_pose = node.declare_parameter( + "behavior_determination.consider_current_pose.enable_to_consider_current_pose"); + time_to_convergence = node.declare_parameter( + "behavior_determination.consider_current_pose.time_to_convergence"); } void ObstacleCruisePlannerNode::BehaviorDeterminationParam::onParam( @@ -332,6 +336,12 @@ void ObstacleCruisePlannerNode::BehaviorDeterminationParam::onParam( tier4_autoware_utils::updateParam( parameters, "behavior_determination.slow_down.successive_num_to_exit_slow_down_condition", successive_num_to_exit_slow_down_condition); + tier4_autoware_utils::updateParam( + parameters, "behavior_determination.consider_current_pose.enable_to_consider_current_pose", + enable_to_consider_current_pose); + tier4_autoware_utils::updateParam( + parameters, "behavior_determination.consider_current_pose.time_to_convergence", + time_to_convergence); } ObstacleCruisePlannerNode::ObstacleCruisePlannerNode(const rclcpp::NodeOptions & node_options) @@ -534,6 +544,57 @@ void ObstacleCruisePlannerNode::onTrajectory(const Trajectory::ConstSharedPtr ms get_logger(), enable_calculation_time_info_, "%s := %f [ms]", __func__, calculation_time); } +std::vector ObstacleCruisePlannerNode::createOneStepPolygons( + const std::vector & traj_points, + const vehicle_info_util::VehicleInfo & vehicle_info, + const geometry_msgs::msg::Pose & current_ego_pose, const double lat_margin) const +{ + const auto & p = behavior_determination_param_; + const bool is_enable_current_pose_consideration = p.enable_to_consider_current_pose; + const double step_length = p.decimate_trajectory_step_length; + const double time_to_convergence = p.time_to_convergence; + + std::vector polygons; + const double current_ego_lat_error = + motion_utils::calcLateralOffset(traj_points, current_ego_pose.position); + const double current_ego_yaw_error = + motion_utils::calcYawDeviation(traj_points, current_ego_pose); + double time_elapsed = 0.0; + + std::vector last_poses = {traj_points.at(0).pose}; + if (is_enable_current_pose_consideration) { + last_poses.push_back(current_ego_pose); + } + + for (size_t i = 0; i < traj_points.size(); ++i) { + std::vector current_poses = {traj_points.at(i).pose}; + + // estimate the future ego pose with assuming that the pose error against the reference path + // will decrease to zero by the time_to_convergence + if (is_enable_current_pose_consideration && time_elapsed < time_to_convergence) { + const double rem_ratio = (time_to_convergence - time_elapsed) / time_to_convergence; + geometry_msgs::msg::Pose indexed_pose_err; + indexed_pose_err.set__orientation( + tier4_autoware_utils::createQuaternionFromYaw(current_ego_yaw_error * rem_ratio)); + indexed_pose_err.set__position( + tier4_autoware_utils::createPoint(0.0, current_ego_lat_error * rem_ratio, 0.0)); + + current_poses.push_back( + tier4_autoware_utils::transformPose(indexed_pose_err, traj_points.at(i).pose)); + + if (traj_points.at(i).longitudinal_velocity_mps != 0.0) { + time_elapsed += step_length / traj_points.at(i).longitudinal_velocity_mps; + } else { + time_elapsed = std::numeric_limits::max(); + } + } + polygons.push_back( + polygon_utils::createOneStepPolygon(last_poses, current_poses, vehicle_info, lat_margin)); + last_poses = current_poses; + } + return polygons; +} + std::vector ObstacleCruisePlannerNode::convertToObstacles( const std::vector & traj_points) const { @@ -653,7 +714,7 @@ ObstacleCruisePlannerNode::determineEgoBehaviorAgainstObstacles( // calculated decimated trajectory points and trajectory polygon const auto decimated_traj_points = decimateTrajectoryPoints(traj_points); const auto decimated_traj_polys = - polygon_utils::createOneStepPolygons(decimated_traj_points, vehicle_info_); + createOneStepPolygons(decimated_traj_points, vehicle_info_, ego_odom_ptr_->pose.pose); debug_data_ptr_->detection_polygons = decimated_traj_polys; // determine ego's behavior from stop, cruise and slow down @@ -985,8 +1046,8 @@ ObstacleCruisePlannerNode::createCollisionPointForStopObstacle( } // calculate collision points with trajectory with lateral stop margin - const auto traj_polys_with_lat_margin = - polygon_utils::createOneStepPolygons(traj_points, vehicle_info_, p.max_lat_margin_for_stop); + const auto traj_polys_with_lat_margin = createOneStepPolygons( + traj_points, vehicle_info_, ego_odom_ptr_->pose.pose, p.max_lat_margin_for_stop); const auto collision_point = polygon_utils::getCollisionPoint( traj_points, traj_polys_with_lat_margin, obstacle, is_driving_forward_); return collision_point; @@ -1047,8 +1108,8 @@ std::optional ObstacleCruisePlannerNode::createSlowDownObstacl // calculate collision points with trajectory with lateral stop margin // NOTE: For additional margin, hysteresis is not divided by two. - const auto traj_polys_with_lat_margin = polygon_utils::createOneStepPolygons( - traj_points, vehicle_info_, + const auto traj_polys_with_lat_margin = createOneStepPolygons( + traj_points, vehicle_info_, ego_odom_ptr_->pose.pose, p.max_lat_margin_for_slow_down + p.lat_hysteresis_margin_for_slow_down); std::vector front_collision_polygons; diff --git a/planning/obstacle_cruise_planner/src/polygon_utils.cpp b/planning/obstacle_cruise_planner/src/polygon_utils.cpp index 077b783f0e65e..e6b4ed89ca460 100644 --- a/planning/obstacle_cruise_planner/src/polygon_utils.cpp +++ b/planning/obstacle_cruise_planner/src/polygon_utils.cpp @@ -31,36 +31,6 @@ geometry_msgs::msg::Point calcOffsetPosition( return tier4_autoware_utils::calcOffsetPose(pose, offset_x, offset_y, 0.0).position; } -Polygon2d createOneStepPolygon( - const geometry_msgs::msg::Pose & base_step_pose, const geometry_msgs::msg::Pose & next_step_pose, - const vehicle_info_util::VehicleInfo & vehicle_info, const double lat_margin) -{ - Polygon2d polygon; - - const double base_to_front = vehicle_info.max_longitudinal_offset_m; - const double width = vehicle_info.vehicle_width_m / 2.0 + lat_margin; - const double base_to_rear = vehicle_info.rear_overhang_m; - - // base step - appendPointToPolygon(polygon, calcOffsetPosition(base_step_pose, base_to_front, width)); - appendPointToPolygon(polygon, calcOffsetPosition(base_step_pose, base_to_front, -width)); - appendPointToPolygon(polygon, calcOffsetPosition(base_step_pose, -base_to_rear, -width)); - appendPointToPolygon(polygon, calcOffsetPosition(base_step_pose, -base_to_rear, width)); - - // next step - appendPointToPolygon(polygon, calcOffsetPosition(next_step_pose, base_to_front, width)); - appendPointToPolygon(polygon, calcOffsetPosition(next_step_pose, base_to_front, -width)); - appendPointToPolygon(polygon, calcOffsetPosition(next_step_pose, -base_to_rear, -width)); - appendPointToPolygon(polygon, calcOffsetPosition(next_step_pose, -base_to_rear, width)); - - bg::correct(polygon); - - Polygon2d hull_polygon; - bg::convex_hull(polygon, hull_polygon); - - return hull_polygon; -} - PointWithStamp calcNearestCollisionPoint( const size_t first_within_idx, const std::vector & collision_points, const std::vector & decimated_traj_points, const bool is_driving_forward) @@ -132,6 +102,38 @@ std::optional>> getCollisionIndex( namespace polygon_utils { +Polygon2d createOneStepPolygon( + const std::vector & last_poses, + const std::vector & current_poses, + const vehicle_info_util::VehicleInfo & vehicle_info, const double lat_margin) +{ + Polygon2d polygon; + + const double base_to_front = vehicle_info.max_longitudinal_offset_m; + const double width = vehicle_info.vehicle_width_m / 2.0 + lat_margin; + const double base_to_rear = vehicle_info.rear_overhang_m; + + for (auto & pose : last_poses) { + appendPointToPolygon(polygon, calcOffsetPosition(pose, base_to_front, width)); + appendPointToPolygon(polygon, calcOffsetPosition(pose, base_to_front, -width)); + appendPointToPolygon(polygon, calcOffsetPosition(pose, -base_to_rear, -width)); + appendPointToPolygon(polygon, calcOffsetPosition(pose, -base_to_rear, width)); + } + for (auto & pose : current_poses) { + appendPointToPolygon(polygon, calcOffsetPosition(pose, base_to_front, width)); + appendPointToPolygon(polygon, calcOffsetPosition(pose, base_to_front, -width)); + appendPointToPolygon(polygon, calcOffsetPosition(pose, -base_to_rear, -width)); + appendPointToPolygon(polygon, calcOffsetPosition(pose, -base_to_rear, width)); + } + + bg::correct(polygon); + + Polygon2d hull_polygon; + bg::convex_hull(polygon, hull_polygon); + + return hull_polygon; +} + std::optional getCollisionPoint( const std::vector & traj_points, const std::vector & traj_polygons, const Obstacle & obstacle, const bool is_driving_forward) @@ -184,25 +186,4 @@ std::vector getCollisionPoints( return collision_points; } -std::vector createOneStepPolygons( - const std::vector & traj_points, - const vehicle_info_util::VehicleInfo & vehicle_info, const double lat_margin) -{ - std::vector polygons; - - for (size_t i = 0; i < traj_points.size(); ++i) { - const auto polygon = [&]() { - if (i == 0) { - return createOneStepPolygon( - traj_points.at(i).pose, traj_points.at(i).pose, vehicle_info, lat_margin); - } - return createOneStepPolygon( - traj_points.at(i - 1).pose, traj_points.at(i).pose, vehicle_info, lat_margin); - }(); - - polygons.push_back(polygon); - } - return polygons; -} - } // namespace polygon_utils From 71da68ce1dc1a8c32a9ce5103908df9b65850af5 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:41:52 +0900 Subject: [PATCH 315/547] feat(lane_change): object filter visualization (#5082) Signed-off-by: Zulfaqar Azmi --- .../marker_utils/lane_change/debug.hpp | 6 +++ .../marker_utils/utils.hpp | 6 +++ .../scene_module/lane_change/base_class.hpp | 6 +++ .../path_safety_checker_parameters.hpp | 1 + .../src/marker_utils/lane_change/debug.cpp | 24 ++++++++++++ .../src/marker_utils/utils.cpp | 38 +++++++++++++++++++ .../scene_module/lane_change/interface.cpp | 6 +++ .../src/scene_module/lane_change/normal.cpp | 2 + 8 files changed, 89 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp index d337bae17ca6c..1d0c9f5c2ce65 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/lane_change/debug.hpp @@ -17,6 +17,7 @@ #include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/utils/lane_change/lane_change_path.hpp" +#include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include #include @@ -25,11 +26,16 @@ namespace marker_utils::lane_change_markers { using behavior_path_planner::LaneChangePath; +using behavior_path_planner::utils::path_safety_checker::ExtendedPredictedObjects; using visualization_msgs::msg::MarkerArray; MarkerArray showAllValidLaneChangePath( const std::vector & lanes, std::string && ns); MarkerArray createLaneChangingVirtualWallMarker( const geometry_msgs::msg::Pose & lane_changing_pose, const std::string & module_name, const rclcpp::Time & now, const std::string & ns); +MarkerArray showFilteredObjects( + const ExtendedPredictedObjects & current_lane_objects, + const ExtendedPredictedObjects & target_lane_objects, + const ExtendedPredictedObjects & other_lane_objects, const std::string & ns); } // namespace marker_utils::lane_change_markers #endif // BEHAVIOR_PATH_PLANNER__MARKER_UTILS__LANE_CHANGE__DEBUG_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp index 244e1a8a71616..86f1a390261c6 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/marker_utils/utils.hpp @@ -26,6 +26,7 @@ #include +#include #include #include @@ -39,6 +40,7 @@ using behavior_path_planner::ShiftLineArray; using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugMap; using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebugPair; using behavior_path_planner::utils::path_safety_checker::ExtendedPredictedObject; +using behavior_path_planner::utils::path_safety_checker::ExtendedPredictedObjects; using geometry_msgs::msg::Point; using geometry_msgs::msg::Polygon; using geometry_msgs::msg::Pose; @@ -106,6 +108,10 @@ MarkerArray showPolygon(const CollisionCheckDebugMap & obj_debug_vec, std::strin MarkerArray showPredictedPath(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); MarkerArray showSafetyCheckInfo(const CollisionCheckDebugMap & obj_debug_vec, std::string && ns); + +MarkerArray showFilteredObjects( + const ExtendedPredictedObjects & predicted_objects, const std::string & ns, + const ColorRGBA & color, int32_t id); } // namespace marker_utils #endif // BEHAVIOR_PATH_PLANNER__MARKER_UTILS__UTILS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp index 1a7350d82aa4e..a3469dee2909b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp @@ -154,6 +154,11 @@ class LaneChangeBase return object_debug_after_approval_; } + const LaneChangeTargetObjects & getDebugFilteredObjects() const + { + return debug_filtered_objects_; + } + const Pose & getEgoPose() const { return planner_data_->self_odometry->pose.pose; } const Point & getEgoPosition() const { return getEgoPose().position; } @@ -262,6 +267,7 @@ class LaneChangeBase mutable LaneChangePaths debug_valid_path_{}; mutable CollisionCheckDebugMap object_debug_{}; mutable CollisionCheckDebugMap object_debug_after_approval_{}; + mutable LaneChangeTargetObjects debug_filtered_objects_{}; mutable double object_debug_lifetime_{0.0}; mutable StopWatch stop_watch_; }; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index aa470eb2dd80c..889f016e7182f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -77,6 +77,7 @@ struct ExtendedPredictedObject autoware_auto_perception_msgs::msg::Shape shape; std::vector predicted_paths; }; +using ExtendedPredictedObjects = std::vector; /** * @brief Specifies which object class should be checked. diff --git a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp index 7493982db78b1..a1b77b1802999 100644 --- a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp @@ -98,4 +98,28 @@ MarkerArray createLaneChangingVirtualWallMarker( return marker_array; } +MarkerArray showFilteredObjects( + const ExtendedPredictedObjects & current_lane_objects, + const ExtendedPredictedObjects & target_lane_objects, + const ExtendedPredictedObjects & other_lane_objects, const std::string & ns) +{ + int32_t update_id = 0; + auto current_marker = + marker_utils::showFilteredObjects(current_lane_objects, ns, colors::yellow(), update_id); + update_id += static_cast(current_marker.markers.size()); + auto target_marker = + marker_utils::showFilteredObjects(target_lane_objects, ns, colors::aqua(), update_id); + update_id += static_cast(target_marker.markers.size()); + auto other_marker = + marker_utils::showFilteredObjects(other_lane_objects, ns, colors::medium_orchid(), update_id); + + MarkerArray marker_array; + marker_array.markers.insert( + marker_array.markers.end(), current_marker.markers.begin(), current_marker.markers.end()); + marker_array.markers.insert( + marker_array.markers.end(), target_marker.markers.begin(), target_marker.markers.end()); + marker_array.markers.insert( + marker_array.markers.end(), other_marker.markers.begin(), other_marker.markers.end()); + return marker_array; +} } // namespace marker_utils::lane_change_markers diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index 4cec9b958f904..a2883403e1ccd 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace marker_utils { using behavior_path_planner::ShiftLine; @@ -650,4 +652,40 @@ MarkerArray showSafetyCheckInfo(const CollisionCheckDebugMap & obj_debug_vec, st return marker_array; } +MarkerArray showFilteredObjects( + const ExtendedPredictedObjects & predicted_objects, const std::string & ns, + const ColorRGBA & color, int32_t id) +{ + using behavior_path_planner::utils::path_safety_checker::ExtendedPredictedObject; + if (predicted_objects.empty()) { + return MarkerArray{}; + } + + const auto now = rclcpp::Clock{RCL_ROS_TIME}.now(); + + auto default_cube_marker = + [&](const auto & width, const auto & depth, const auto & color = colors::green()) { + return createDefaultMarker( + "map", now, ns + "_cube", ++id, visualization_msgs::msg::Marker::CUBE, + createMarkerScale(width, depth, 1.0), color); + }; + + MarkerArray marker_array; + marker_array.markers.reserve( + predicted_objects.size()); // poly ego, text ego, poly obj, text obj, cube obj + + std::for_each( + predicted_objects.begin(), predicted_objects.end(), [&](const ExtendedPredictedObject & obj) { + const auto insert_cube_marker = [&](const auto & pose) { + marker_array.markers.emplace_back(); + auto & cube_marker = marker_array.markers.back(); + cube_marker = default_cube_marker(1.0, 1.0, color); + cube_marker.pose = pose; + }; + insert_cube_marker(obj.initial_pose.pose); + }); + + return marker_array; +} + } // namespace marker_utils diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 74af83c2db2a1..51875952d40fb 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -15,6 +15,7 @@ #include "behavior_path_planner/scene_module/lane_change/interface.hpp" #include "behavior_path_planner/marker_utils/lane_change/debug.hpp" +#include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/scene_module/scene_module_interface.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/lane_change/utils.hpp" @@ -293,10 +294,12 @@ void LaneChangeInterface::setObjectDebugVisualization() const using marker_utils::showPredictedPath; using marker_utils::showSafetyCheckInfo; using marker_utils::lane_change_markers::showAllValidLaneChangePath; + using marker_utils::lane_change_markers::showFilteredObjects; const auto debug_data = module_type_->getDebugData(); const auto debug_after_approval = module_type_->getAfterApprovalDebugData(); const auto debug_valid_path = module_type_->getDebugValidPath(); + const auto debug_filtered_objects = module_type_->getDebugFilteredObjects(); debug_marker_.markers.clear(); const auto add = [this](const MarkerArray & added) { @@ -304,6 +307,9 @@ void LaneChangeInterface::setObjectDebugVisualization() const }; add(showAllValidLaneChangePath(debug_valid_path, "lane_change_valid_paths")); + add(showFilteredObjects( + debug_filtered_objects.current_lane, debug_filtered_objects.target_lane, + debug_filtered_objects.other_lane, "object_filtered")); if (!debug_data.empty()) { add(showSafetyCheckInfo(debug_data, "object_debug_info")); add(showPredictedPath(debug_data, "ego_predicted_path")); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 36127bbdf20b6..568538967f2ac 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -945,6 +945,7 @@ bool NormalLaneChange::getLaneChangePaths( utils::lane_change::getTargetNeighborLanesPolygon(route_handler, current_lanes, type_); const auto target_objects = getTargetObjects(current_lanes, target_lanes); + debug_filtered_objects_ = target_objects; const auto prepare_durations = calcPrepareDuration(current_lanes, target_lanes); @@ -1153,6 +1154,7 @@ PathSafetyStatus NormalLaneChange::isApprovedPathSafe() const const auto & target_lanes = status_.target_lanes; const auto target_objects = getTargetObjects(current_lanes, target_lanes); + debug_filtered_objects_ = target_objects; CollisionCheckDebugMap debug_data; const auto safety_status = isLaneChangePathSafe( From 29e67e7f562534ad0a00563d7f3263fc780fb4bf Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Mon, 25 Sep 2023 23:27:14 +0900 Subject: [PATCH 316/547] fix(utils): add flag check in drivable bound generation (#5109) Signed-off-by: satoshi-ota --- planning/behavior_path_planner/src/utils/utils.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 9c11469c0f9c9..1b14741db4a56 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1675,6 +1675,13 @@ std::vector calcBound( continue; } + if (!enable_expanding_hatched_road_markings) { + for (const auto & point : bound) { + output_points.push_back(lanelet::utils::conversion::toGeomMsgPt(point)); + } + continue; + } + // expand drivable area by hatched road markings. for (size_t bound_point_idx = 0; bound_point_idx < bound.size(); ++bound_point_idx) { const auto & bound_point = bound[bound_point_idx]; From 770cf1714bae4f7d2367bbb499d9a6a5f6ea33e3 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 25 Sep 2023 23:30:46 +0900 Subject: [PATCH 317/547] feat(intersection): modify occlusion distance calculation and publish projection line (#5107) Signed-off-by: Mamoru Sobue --- .../src/debug.cpp | 46 ++- .../src/scene_intersection.cpp | 366 +++++++----------- .../src/util_type.hpp | 24 +- 3 files changed, 196 insertions(+), 240 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index 9d8cb46fcf54f..af8972424f54a 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -113,7 +113,33 @@ visualization_msgs::msg::MarkerArray createPoseMarkerArray( return msg; } -visualization_msgs::msg::Marker createPointMarkerArray( +visualization_msgs::msg::MarkerArray createLineMarkerArray( + const geometry_msgs::msg::Point & point_start, const geometry_msgs::msg::Point & point_end, + const std::string & ns, const int64_t id, const double r, const double g, const double b) +{ + visualization_msgs::msg::MarkerArray msg; + + visualization_msgs::msg::Marker marker; + marker.header.frame_id = "map"; + marker.ns = ns + "_line"; + marker.id = id; + marker.lifetime = rclcpp::Duration::from_seconds(0.3); + marker.type = visualization_msgs::msg::Marker::ARROW; + marker.action = visualization_msgs::msg::Marker::ADD; + geometry_msgs::msg::Vector3 arrow; + arrow.x = 1.0; + arrow.y = 1.0; + arrow.z = 1.0; + marker.scale = arrow; + marker.color = createMarkerColor(r, g, b, 0.999); + marker.points.push_back(point_start); + marker.points.push_back(point_end); + + msg.markers.push_back(marker); + return msg; +} + +[[maybe_unused]] visualization_msgs::msg::Marker createPointMarkerArray( const geometry_msgs::msg::Point & point, const std::string & ns, const int64_t id, const double r, const double g, const double b) { @@ -185,17 +211,6 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( &debug_marker_array, now); } - if (debug_data_.nearest_occlusion_point) { - debug_marker_array.markers.push_back(createPointMarkerArray( - debug_data_.nearest_occlusion_point.value(), "nearest_occlusion", module_id_, 0.5, 0.5, 0.0)); - } - - if (debug_data_.nearest_occlusion_projection_point) { - debug_marker_array.markers.push_back(createPointMarkerArray( - debug_data_.nearest_occlusion_projection_point.value(), "nearest_occlusion_projection", - module_id_, 0.5, 0.5, 0.0)); - } - size_t i{0}; for (const auto & p : debug_data_.candidate_collision_object_polygons) { appendMarkerArray( @@ -238,6 +253,13 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( &debug_marker_array, now); } + if (debug_data_.nearest_occlusion_projection) { + const auto [point_start, point_end] = debug_data_.nearest_occlusion_projection.value(); + appendMarkerArray( + createLineMarkerArray( + point_start, point_end, "nearest_occlusion_projection", lane_id_, 0.5, 0.5, 0.0), + &debug_marker_array, now); + } return debug_marker_array; } diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index f335970f4ffda..f795a113672e0 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -19,11 +19,7 @@ #include #include #include -#include -#include -#include #include -#include #include #include #include @@ -31,15 +27,18 @@ #include #include +#include +#include #include -#include +#include #include #include #include #include #include + namespace behavior_velocity_planner { namespace bg = boost::geometry; @@ -1197,7 +1196,8 @@ bool IntersectionModule::isOcclusionCleared( const lanelet::CompoundPolygon3d & first_attention_area, const util::InterpolatedPathInfo & interpolated_path_info, const std::vector & lane_divisions, - const std::vector & parked_attention_objects, + [[maybe_unused]] const std::vector & + blocking_attention_objects, const double occlusion_dist_thr) { const auto & path_ip = interpolated_path_info.path; @@ -1215,19 +1215,13 @@ bool IntersectionModule::isOcclusionCleared( first_inside_attention_idx_ip_opt ? std::make_pair(first_inside_attention_idx_ip_opt.value(), std::get<1>(lane_interval_ip)) : lane_interval_ip; + const auto [lane_start_idx, lane_end_idx] = lane_attention_interval_ip; const int width = occ_grid.info.width; const int height = occ_grid.info.height; const double resolution = occ_grid.info.resolution; const auto & origin = occ_grid.info.origin.position; - // NOTE: interesting area is set to 0 for later masking - cv::Mat attention_mask(width, height, CV_8UC1, cv::Scalar(0)); - cv::Mat unknown_mask(width, height, CV_8UC1, cv::Scalar(0)); - - // (1) prepare detection area mask - // attention: 255 - // non-attention: 0 Polygon2d grid_poly; grid_poly.outer().emplace_back(origin.x, origin.y); grid_poly.outer().emplace_back(origin.x + (width - 1) * resolution, origin.y); @@ -1237,10 +1231,9 @@ bool IntersectionModule::isOcclusionCleared( grid_poly.outer().emplace_back(origin.x, origin.y); bg::correct(grid_poly); - std::vector> attention_area_cv_polygons; - for (const auto & attention_area : attention_areas) { - const auto area2d = lanelet::utils::to2D(attention_area); - Polygon2d area2d_poly; + auto findCommonCvPolygons = + [&](const auto & area2d, std::vector> & cv_polygons) -> void { + tier4_autoware_utils::Polygon2d area2d_poly; for (const auto & p : area2d) { area2d_poly.outer().emplace_back(p.x(), p.y()); } @@ -1249,21 +1242,32 @@ bool IntersectionModule::isOcclusionCleared( std::vector common_areas; bg::intersection(area2d_poly, grid_poly, common_areas); if (common_areas.empty()) { - continue; + return; } for (size_t i = 0; i < common_areas.size(); ++i) { common_areas[i].outer().push_back(common_areas[i].outer().front()); bg::correct(common_areas[i]); } for (const auto & common_area : common_areas) { - std::vector attention_area_cv_polygon; + std::vector cv_polygon; for (const auto & p : common_area.outer()) { const int idx_x = static_cast((p.x() - origin.x) / resolution); const int idx_y = static_cast((p.y() - origin.y) / resolution); - attention_area_cv_polygon.emplace_back(idx_x, height - 1 - idx_y); + cv_polygon.emplace_back(idx_x, height - 1 - idx_y); } - attention_area_cv_polygons.push_back(attention_area_cv_polygon); + cv_polygons.push_back(cv_polygon); } + }; + + // (1) prepare detection area mask + // attention: 255 + // non-attention: 0 + // NOTE: interesting area is set to 255 for later masking + cv::Mat attention_mask(width, height, CV_8UC1, cv::Scalar(0)); + std::vector> attention_area_cv_polygons; + for (const auto & attention_area : attention_areas) { + const auto area2d = lanelet::utils::to2D(attention_area); + findCommonCvPolygons(area2d, attention_area_cv_polygons); } for (const auto & poly : attention_area_cv_polygons) { cv::fillPoly(attention_mask, poly, cv::Scalar(255), cv::LINE_AA); @@ -1273,30 +1277,7 @@ bool IntersectionModule::isOcclusionCleared( std::vector> adjacent_lane_cv_polygons; for (const auto & adjacent_lanelet : adjacent_lanelets) { const auto area2d = adjacent_lanelet.polygon2d().basicPolygon(); - Polygon2d area2d_poly; - for (const auto & p : area2d) { - area2d_poly.outer().emplace_back(p.x(), p.y()); - } - area2d_poly.outer().push_back(area2d_poly.outer().front()); - bg::correct(area2d_poly); - std::vector common_areas; - bg::intersection(area2d_poly, grid_poly, common_areas); - if (common_areas.empty()) { - continue; - } - for (size_t i = 0; i < common_areas.size(); ++i) { - common_areas[i].outer().push_back(common_areas[i].outer().front()); - bg::correct(common_areas[i]); - } - for (const auto & common_area : common_areas) { - std::vector adjacent_lane_cv_polygon; - for (const auto & p : common_area.outer()) { - const int idx_x = std::floor((p.x() - origin.x) / resolution); - const int idx_y = std::floor((p.y() - origin.y) / resolution); - adjacent_lane_cv_polygon.emplace_back(idx_x, height - 1 - idx_y); - } - adjacent_lane_cv_polygons.push_back(adjacent_lane_cv_polygon); - } + findCommonCvPolygons(area2d, adjacent_lane_cv_polygons); } for (const auto & poly : adjacent_lane_cv_polygons) { cv::fillPoly(attention_mask, poly, cv::Scalar(0), cv::LINE_AA); @@ -1304,6 +1285,10 @@ bool IntersectionModule::isOcclusionCleared( // (2) prepare unknown mask // In OpenCV the pixel at (X=x, Y=y) (with left-upper origin) is accessed by img[y, x] + // unknown: 255 + // not-unknown: 0 + cv::Mat unknown_mask_raw(width, height, CV_8UC1, cv::Scalar(0)); + cv::Mat unknown_mask(width, height, CV_8UC1, cv::Scalar(0)); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { const int idx = y * width + x; @@ -1311,159 +1296,21 @@ bool IntersectionModule::isOcclusionCleared( if ( planner_param_.occlusion.free_space_max <= intensity && intensity < planner_param_.occlusion.occupied_min) { - unknown_mask.at(height - 1 - y, x) = 255; + unknown_mask_raw.at(height - 1 - y, x) = 255; } } } - - // (3) occlusion mask - cv::Mat occlusion_mask_raw(width, height, CV_8UC1, cv::Scalar(0)); - cv::bitwise_and(attention_mask, unknown_mask, occlusion_mask_raw); - // (3.1) apply morphologyEx - cv::Mat occlusion_mask; + // (2.1) apply morphologyEx const int morph_size = static_cast(planner_param_.occlusion.denoise_kernel / resolution); cv::morphologyEx( - occlusion_mask_raw, occlusion_mask, cv::MORPH_OPEN, + unknown_mask_raw, unknown_mask, cv::MORPH_OPEN, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(morph_size, morph_size))); - // (4) create distance grid - // value: 0 - 254: signed distance representing [distance_min, distance_max] - // 255: undefined value - const double distance_max = std::hypot(width * resolution / 2, height * resolution / 2); - const double distance_min = -distance_max; - const int undef_pixel = 255; - const int max_cost_pixel = 254; - auto dist2pixel = [=](const double dist) { - return std::min( - max_cost_pixel, - static_cast((dist - distance_min) / (distance_max - distance_min) * max_cost_pixel)); - }; - auto pixel2dist = [=](const int pixel) { - return pixel * 1.0 / max_cost_pixel * (distance_max - distance_min) + distance_min; - }; - const int zero_dist_pixel = dist2pixel(0.0); - const int parked_vehicle_pixel = zero_dist_pixel - 1; // magic - - auto coord2index = [&](const double x, const double y) { - const int idx_x = (x - origin.x) / resolution; - const int idx_y = (y - origin.y) / resolution; - if (idx_x < 0 || idx_x >= width) return std::make_tuple(false, -1, -1); - if (idx_y < 0 || idx_y >= height) return std::make_tuple(false, -1, -1); - return std::make_tuple(true, idx_x, idx_y); - }; - - cv::Mat distance_grid(width, height, CV_8UC1, cv::Scalar(undef_pixel)); - cv::Mat projection_ind_grid(width, height, CV_32S, cv::Scalar(-1)); - - // (4.1) fill zero_dist_pixel on the path - const auto [lane_start, lane_end] = lane_attention_interval_ip; - for (int i = static_cast(lane_end); i >= static_cast(lane_start); i--) { - const auto & path_pos = path_ip.points.at(i).point.pose.position; - const int idx_x = (path_pos.x - origin.x) / resolution; - const int idx_y = (path_pos.y - origin.y) / resolution; - if (idx_x < 0 || idx_x >= width) continue; - if (idx_y < 0 || idx_y >= height) continue; - distance_grid.at(height - 1 - idx_y, idx_x) = zero_dist_pixel; - projection_ind_grid.at(height - 1 - idx_y, idx_x) = i; - } - - // (4.2) fill parked_vehicle_pixel to parked_vehicles (both positive and negative) - for (const auto & parked_attention_object : parked_attention_objects) { - const auto obj_poly = tier4_autoware_utils::toPolygon2d(parked_attention_object); - std::vector common_areas; - bg::intersection(obj_poly, grid_poly, common_areas); - if (common_areas.empty()) continue; - for (size_t i = 0; i < common_areas.size(); ++i) { - common_areas[i].outer().push_back(common_areas[i].outer().front()); - bg::correct(common_areas[i]); - } - std::vector> parked_attention_object_area_cv_polygons; - for (const auto & common_area : common_areas) { - std::vector parked_attention_object_area_cv_polygon; - for (const auto & p : common_area.outer()) { - const int idx_x = static_cast((p.x() - origin.x) / resolution); - const int idx_y = static_cast((p.y() - origin.y) / resolution); - parked_attention_object_area_cv_polygon.emplace_back(idx_x, height - 1 - idx_y); - } - parked_attention_object_area_cv_polygons.push_back(parked_attention_object_area_cv_polygon); - } - for (const auto & poly : parked_attention_object_area_cv_polygons) { - cv::fillPoly(distance_grid, poly, cv::Scalar(parked_vehicle_pixel), cv::LINE_AA); - } - } - - for (const auto & lane_division : lane_divisions) { - const auto & divisions = lane_division.divisions; - for (const auto & division : divisions) { - bool is_in_grid = false; - bool zero_dist_cell_found = false; - int projection_ind = -1; - std::optional> cost_prev_checkpoint = - std::nullopt; // cost, x, y, projection_ind - for (auto point = division.begin(); point != division.end(); point++) { - const double x = point->x(), y = point->y(); - const auto [valid, idx_x, idx_y] = coord2index(x, y); - // exited grid just now - if (is_in_grid && !valid) break; - - // still not entering grid - if (!is_in_grid && !valid) continue; - - // From here, "valid" - const int pixel = distance_grid.at(height - 1 - idx_y, idx_x); - - // entered grid for 1st time - if (!is_in_grid) { - assert(pixel == undef_pixel || pixel == zero_dist_pixel); - is_in_grid = true; - if (pixel == undef_pixel) { - continue; - } - } - - if (pixel == zero_dist_pixel) { - zero_dist_cell_found = true; - projection_ind = projection_ind_grid.at(height - 1 - idx_y, idx_x); - assert(projection_ind >= 0); - cost_prev_checkpoint = - std::make_optional>(0.0, x, y, projection_ind); - continue; - } - - // hit positive parked vehicle - if (zero_dist_cell_found && pixel == parked_vehicle_pixel) { - while (point != division.end()) { - const double x = point->x(), y = point->y(); - const auto [valid, idx_x, idx_y] = coord2index(x, y); - if (valid) occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; - point++; - } - break; - } - - if (zero_dist_cell_found) { - // finally traversed to defined cell (first half) - const auto [prev_cost, prev_checkpoint_x, prev_checkpoint_y, prev_projection_ind] = - cost_prev_checkpoint.value(); - const double dy = y - prev_checkpoint_y, dx = x - prev_checkpoint_x; - double new_dist = prev_cost + std::hypot(dy, dx); - const int new_projection_ind = projection_ind_grid.at(height - 1 - idx_y, idx_x); - const double cur_dist = pixel2dist(pixel); - if (planner_param_.occlusion.do_dp && cur_dist < new_dist) { - new_dist = cur_dist; - if (new_projection_ind > 0) { - projection_ind = std::min(prev_projection_ind, new_projection_ind); - } - } - projection_ind_grid.at(height - 1 - idx_y, idx_x) = projection_ind; - distance_grid.at(height - 1 - idx_y, idx_x) = dist2pixel(new_dist); - cost_prev_checkpoint = std::make_optional>( - new_dist, x, y, projection_ind); - } - } - } - } + // (3) occlusion mask + cv::Mat occlusion_mask(width, height, CV_8UC1, cv::Scalar(0)); + cv::bitwise_and(attention_mask, unknown_mask, occlusion_mask); + // (4) extract occlusion polygons const auto & possible_object_bbox = planner_param_.occlusion.possible_object_bbox; const double possible_object_bbox_x = possible_object_bbox.at(0) / resolution; const double possible_object_bbox_y = possible_object_bbox.at(1) / resolution; @@ -1472,19 +1319,12 @@ bool IntersectionModule::isOcclusionCleared( cv::findContours(occlusion_mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); std::vector> valid_contours; for (const auto & contour : contours) { - std::vector valid_contour; - for (const auto & p : contour) { - if (distance_grid.at(p.y, p.x) == undef_pixel) { - continue; - } - valid_contour.push_back(p); - } - if (valid_contour.size() <= 2) { + if (contour.size() <= 2) { continue; } std::vector approx_contour; cv::approxPolyDP( - valid_contour, approx_contour, + contour, approx_contour, std::round(std::min(possible_object_bbox_x, possible_object_bbox_y) / std::sqrt(2.0)), true); if (approx_contour.size() <= 2) continue; // check area @@ -1511,32 +1351,126 @@ bool IntersectionModule::isOcclusionCleared( } debug_data_.occlusion_polygons.push_back(polygon_msg); } + // (4.1) re-draw occluded cells using valid_contours + occlusion_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); + for (unsigned i = 0; i < valid_contours.size(); ++i) { + // NOTE: drawContour does not work well + cv::fillPoly(occlusion_mask, valid_contours[i], cv::Scalar(255), cv::LINE_AA); + } - const int min_cost_thr = dist2pixel(occlusion_dist_thr); - int min_cost = undef_pixel - 1; - geometry_msgs::msg::Point nearest_occlusion_point; - for (const auto & occlusion_contour : valid_contours) { - for (const auto & p : occlusion_contour) { - const int pixel = static_cast(distance_grid.at(p.y, p.x)); - const bool occluded = (occlusion_mask.at(p.y, p.x) == 255); - if (pixel == undef_pixel || !occluded) { + auto coord2index = [&](const double x, const double y) { + const int idx_x = (x - origin.x) / resolution; + const int idx_y = (y - origin.y) / resolution; + if (idx_x < 0 || idx_x >= width) return std::make_tuple(false, -1, -1); + if (idx_y < 0 || idx_y >= height) return std::make_tuple(false, -1, -1); + return std::make_tuple(true, idx_x, idx_y); + }; + + // (5) find distance + // (5.1) discretize path_ip with resolution for computational cost + LineString2d path_linestring; + path_linestring.emplace_back( + path_ip.points.at(lane_start_idx).point.pose.position.x, + path_ip.points.at(lane_start_idx).point.pose.position.y); + { + auto prev_path_linestring_point = path_ip.points.at(lane_start_idx).point.pose.position; + for (auto i = lane_start_idx + 1; i <= lane_end_idx; i++) { + const auto path_linestring_point = path_ip.points.at(i).point.pose.position; + if ( + tier4_autoware_utils::calcDistance2d(prev_path_linestring_point, path_linestring_point) < + 1.0 /* rough tick for computational cost */) { + continue; + } + path_linestring.emplace_back(path_linestring_point.x, path_linestring_point.y); + prev_path_linestring_point = path_linestring_point; + } + } + + auto findNearestPointToProjection = + [](lanelet::ConstLineString2d division, const Point2d & projection, const double dist_thresh) { + double min_dist = std::numeric_limits::infinity(); + auto nearest = division.end(); + for (auto it = division.begin(); it != division.end(); it++) { + const double dist = std::hypot(it->x() - projection.x(), it->y() - projection.y()); + if (dist < min_dist) { + min_dist = dist; + nearest = it; + } + if (dist < dist_thresh) { + break; + } + } + return nearest; + }; + struct NearestOcclusionPoint + { + int lane_id; + int64 division_index; + double dist; + geometry_msgs::msg::Point point; + geometry_msgs::msg::Point projection; + } nearest_occlusion_point; + double min_dist = std::numeric_limits::infinity(); + for (const auto & lane_division : lane_divisions) { + const auto & divisions = lane_division.divisions; + const auto lane_id = lane_division.lane_id; + for (unsigned division_index = 0; division_index < divisions.size(); ++division_index) { + const auto & division = divisions.at(division_index); + LineString2d division_linestring; + auto division_point_it = division.begin(); + division_linestring.emplace_back(division_point_it->x(), division_point_it->y()); + for (auto point_it = division.begin(); point_it != division.end(); point_it++) { + if ( + std::hypot( + point_it->x() - division_point_it->x(), point_it->y() - division_point_it->y()) < + 3.0 /* rough tick for computational cost */) { + continue; + } + division_linestring.emplace_back(point_it->x(), point_it->y()); + division_point_it = point_it; + } + + // find the intersection point of lane_line and path + std::vector intersection_points; + boost::geometry::intersection(division_linestring, path_linestring, intersection_points); + if (intersection_points.empty()) { continue; } - if (pixel < min_cost) { - min_cost = pixel; - nearest_occlusion_point.x = origin.x + p.x * resolution; - nearest_occlusion_point.y = origin.y + (height - 1 - p.y) * resolution; - nearest_occlusion_point.z = origin.z; + const auto & projection_point = intersection_points.at(0); + const auto projection_it = + findNearestPointToProjection(division, projection_point, resolution); + if (projection_it == division.end()) { + continue; + } + double acc_dist = 0.0; + auto acc_dist_it = projection_it; + for (auto point_it = projection_it; point_it != division.end(); point_it++) { + const double dist = + std::hypot(point_it->x() - acc_dist_it->x(), point_it->y() - acc_dist_it->y()); + acc_dist += dist; + acc_dist_it = point_it; + const auto [valid, idx_x, idx_y] = coord2index(point_it->x(), point_it->y()); + // TODO(Mamoru Sobue): add handling for blocking vehicles + if (!valid) continue; + if (occlusion_mask.at(height - 1 - idx_y, idx_x) == 255) { + if (acc_dist < min_dist) { + min_dist = acc_dist; + nearest_occlusion_point = { + lane_id, std::distance(division.begin(), point_it), acc_dist, + tier4_autoware_utils::createPoint(point_it->x(), point_it->y(), origin.z), + tier4_autoware_utils::createPoint(projection_it->x(), projection_it->y(), origin.z)}; + } + } } } } - if (min_cost > min_cost_thr) { + if (min_dist == std::numeric_limits::infinity() || min_dist > occlusion_dist_thr) { return true; - } else { - debug_data_.nearest_occlusion_point = nearest_occlusion_point; - return false; } + debug_data_.nearest_occlusion_projection = + std::make_pair(nearest_occlusion_point.point, nearest_occlusion_point.projection); + return false; } /* diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 643ac884d9361..212081af43111 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -34,22 +34,22 @@ namespace behavior_velocity_planner::util struct DebugData { - std::optional collision_stop_wall_pose = std::nullopt; - std::optional occlusion_stop_wall_pose = std::nullopt; - std::optional occlusion_first_stop_wall_pose = std::nullopt; - std::optional pass_judge_wall_pose = std::nullopt; - std::optional> attention_area = std::nullopt; - std::optional intersection_area = std::nullopt; - std::optional ego_lane = std::nullopt; - std::optional> adjacent_area = std::nullopt; - std::optional stuck_vehicle_detect_area = std::nullopt; - std::optional candidate_collision_ego_lane_polygon = std::nullopt; + std::optional collision_stop_wall_pose{std::nullopt}; + std::optional occlusion_stop_wall_pose{std::nullopt}; + std::optional occlusion_first_stop_wall_pose{std::nullopt}; + std::optional pass_judge_wall_pose{std::nullopt}; + std::optional> attention_area{std::nullopt}; + std::optional intersection_area{std::nullopt}; + std::optional ego_lane{std::nullopt}; + std::optional> adjacent_area{std::nullopt}; + std::optional stuck_vehicle_detect_area{std::nullopt}; + std::optional candidate_collision_ego_lane_polygon{std::nullopt}; std::vector candidate_collision_object_polygons; autoware_auto_perception_msgs::msg::PredictedObjects conflicting_targets; autoware_auto_perception_msgs::msg::PredictedObjects stuck_targets; - std::optional nearest_occlusion_point = std::nullopt; - std::optional nearest_occlusion_projection_point = std::nullopt; std::vector occlusion_polygons; + std::optional> + nearest_occlusion_projection{std::nullopt}; }; struct InterpolatedPathInfo From 9e4b4e924f17bd40ec97b975cbb8ada9fc06ff8d Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 25 Sep 2023 23:31:21 +0900 Subject: [PATCH 318/547] feat(intersection): ensure temporal stop before peeking into oncoming lane (#5047) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 1 + .../src/manager.cpp | 2 + .../src/scene_intersection.cpp | 77 ++++++++++++++----- .../src/scene_intersection.hpp | 6 ++ .../src/util.cpp | 8 ++ .../src/util_type.hpp | 4 +- 6 files changed, 78 insertions(+), 20 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 5b626ec19d917..a932254140878 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -51,6 +51,7 @@ possible_object_bbox: [1.5, 2.5] # [m x m] ignore_parked_vehicle_speed_threshold: 0.8333 # == 3.0km/h stop_release_margin_time: 1.5 # [s] + temporal_stop_before_attention_area: false enable_rtc: intersection: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 633ab3115b94c..28cc7a1ad2881 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -130,6 +130,8 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".occlusion.ignore_parked_vehicle_speed_threshold"); ip.occlusion.stop_release_margin_time = getOrDeclareParameter(node, ns + ".occlusion.stop_release_margin_time"); + ip.occlusion.temporal_stop_before_attention_area = + getOrDeclareParameter(node, ns + ".occlusion.temporal_stop_before_attention_area"); } void IntersectionModuleManager::launchNewModules( diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index f795a113672e0..fec7589f3c18f 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -98,6 +98,11 @@ IntersectionModule::IntersectionModule( stuck_private_area_timeout_.setMarginTime(planner_param_.stuck_vehicle.timeout_private_area); stuck_private_area_timeout_.setState(StateMachine::State::STOP); } + { + temporal_stop_before_attention_state_machine_.setMarginTime( + planner_param_.occlusion.before_creep_stop_time); + temporal_stop_before_attention_state_machine_.setState(StateMachine::State::STOP); + } decision_state_pub_ = node_.create_publisher("~/debug/intersection/decision_state", 1); @@ -480,7 +485,10 @@ void reactRTCApprovalByDecisionResult( // NOTE: creep_velocity should be inserted first at closest_idx if !rtc_default_approved if (!rtc_occlusion_approved && planner_param.occlusion.enable) { - const size_t occlusion_peeking_stop_line = decision_result.occlusion_stop_line_idx; + const size_t occlusion_peeking_stop_line = + decision_result.temporal_stop_before_attention_required + ? decision_result.first_attention_stop_line_idx + : decision_result.occlusion_stop_line_idx; if (planner_param.occlusion.enable_creeping) { const size_t closest_idx = decision_result.closest_idx; for (size_t i = closest_idx; i < occlusion_peeking_stop_line; i++) { @@ -544,7 +552,9 @@ void reactRTCApprovalByDecisionResult( } } if (!rtc_occlusion_approved && planner_param.occlusion.enable) { - const auto stop_line_idx = decision_result.occlusion_stop_line_idx; + const auto stop_line_idx = decision_result.temporal_stop_before_attention_required + ? decision_result.first_attention_stop_line_idx + : decision_result.occlusion_stop_line_idx; planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); @@ -784,7 +794,8 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const auto & intersection_stop_lines = intersection_stop_lines_opt.value(); const auto [closest_idx, stuck_stop_line_idx_opt, default_stop_line_idx_opt, - occlusion_peeking_stop_line_idx_opt, pass_judge_line_idx] = intersection_stop_lines; + first_attention_stop_line_idx_opt, occlusion_peeking_stop_line_idx_opt, pass_judge_line_idx] = + intersection_stop_lines; const auto & conflicting_area = intersection_lanelets_.value().conflicting_area(); const auto path_lanelets_opt = util::generatePathLanelets( @@ -866,12 +877,13 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( return IntersectionModule::Indecisive{}; } - if (!occlusion_peeking_stop_line_idx_opt) { + if (!first_attention_stop_line_idx_opt || !occlusion_peeking_stop_line_idx_opt) { RCLCPP_DEBUG(logger_, "occlusion stop line is null"); return IntersectionModule::Indecisive{}; } const auto collision_stop_line_idx = is_over_default_stop_line ? closest_idx : default_stop_line_idx; + const auto first_attention_stop_line_idx = first_attention_stop_line_idx_opt.value(); const auto occlusion_stop_line_idx = occlusion_peeking_stop_line_idx_opt.value(); const auto & attention_lanelets = intersection_lanelets_.value().attention(); @@ -949,35 +961,62 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( if ( occlusion_stop_state_machine_.getState() == StateMachine::State::STOP || ext_occlusion_requested) { - const double dist_stopline = motion_utils::calcSignedArcLength( + const double dist_default_stopline = motion_utils::calcSignedArcLength( path->points, path->points.at(closest_idx).point.pose.position, path->points.at(default_stop_line_idx).point.pose.position); - const bool approached_stop_line = - (std::fabs(dist_stopline) < planner_param_.common.stop_overshoot_margin); - const bool over_stop_line = (dist_stopline < 0.0); - const bool is_stopped = + const bool approached_default_stop_line = + (std::fabs(dist_default_stopline) < planner_param_.common.stop_overshoot_margin); + const bool over_default_stop_line = (dist_default_stopline < 0.0); + const bool is_stopped_at_default = planner_data_->isVehicleStopped(planner_param_.occlusion.before_creep_stop_time); - if (over_stop_line) { + if (over_default_stop_line) { before_creep_state_machine_.setState(StateMachine::State::GO); } if (before_creep_state_machine_.getState() == StateMachine::State::GO) { + const double dist_first_attention_stopline = motion_utils::calcSignedArcLength( + path->points, path->points.at(closest_idx).point.pose.position, + path->points.at(first_attention_stop_line_idx).point.pose.position); + const bool approached_first_attention_stop_line = + (std::fabs(dist_first_attention_stopline) < planner_param_.common.stop_overshoot_margin); + const bool over_first_attention_stop_line = (dist_first_attention_stopline < 0.0); + const bool is_stopped_at_first_attention = + planner_data_->isVehicleStopped(planner_param_.occlusion.before_creep_stop_time); + if (planner_param_.occlusion.temporal_stop_before_attention_area) { + if (over_first_attention_stop_line) { + temporal_stop_before_attention_state_machine_.setState(StateMachine::State::GO); + } + if (is_stopped_at_first_attention && approached_first_attention_stop_line) { + temporal_stop_before_attention_state_machine_.setState(StateMachine::State::GO); + } + } + const bool temporal_stop_before_attention_required = + planner_param_.occlusion.temporal_stop_before_attention_area && + temporal_stop_before_attention_state_machine_.getState() == StateMachine::State::STOP; if (has_collision_with_margin) { - return IntersectionModule::OccludedCollisionStop{ - is_occlusion_cleared_with_margin, closest_idx, collision_stop_line_idx, - occlusion_stop_line_idx}; + return IntersectionModule::OccludedCollisionStop{is_occlusion_cleared_with_margin, + temporal_stop_before_attention_required, + closest_idx, + collision_stop_line_idx, + first_attention_stop_line_idx, + occlusion_stop_line_idx}; } else { - return IntersectionModule::PeekingTowardOcclusion{ - is_occlusion_cleared_with_margin, closest_idx, collision_stop_line_idx, - occlusion_stop_line_idx}; + return IntersectionModule::PeekingTowardOcclusion{is_occlusion_cleared_with_margin, + temporal_stop_before_attention_required, + closest_idx, + collision_stop_line_idx, + first_attention_stop_line_idx, + occlusion_stop_line_idx}; } } else { - if (is_stopped && approached_stop_line) { + if (is_stopped_at_default && approached_default_stop_line) { // start waiting at the first stop line before_creep_state_machine_.setState(StateMachine::State::GO); } + const auto occlusion_stop_line = planner_param_.occlusion.temporal_stop_before_attention_area + ? first_attention_stop_line_idx + : occlusion_stop_line_idx; return IntersectionModule::FirstWaitBeforeOcclusion{ - is_occlusion_cleared_with_margin, closest_idx, default_stop_line_idx, - occlusion_stop_line_idx}; + is_occlusion_cleared_with_margin, closest_idx, default_stop_line_idx, occlusion_stop_line}; } } else if (has_collision_with_margin) { return IntersectionModule::NonOccludedCollisionStop{ diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 268bdb5fe1f72..89444bb71c947 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -113,6 +113,7 @@ class IntersectionModule : public SceneModuleInterface std::vector possible_object_bbox; double ignore_parked_vehicle_speed_threshold; double stop_release_margin_time; + bool temporal_stop_before_attention_area; } occlusion; }; @@ -141,15 +142,19 @@ class IntersectionModule : public SceneModuleInterface // NOTE: if intersection_occlusion is disapproved externally through RTC, // it indicates "is_forcefully_occluded" bool is_actually_occlusion_cleared{false}; + bool temporal_stop_before_attention_required{false}; size_t closest_idx{0}; size_t collision_stop_line_idx{0}; + size_t first_attention_stop_line_idx{0}; size_t occlusion_stop_line_idx{0}; }; struct OccludedCollisionStop { bool is_actually_occlusion_cleared{false}; + bool temporal_stop_before_attention_required{false}; size_t closest_idx{0}; size_t collision_stop_line_idx{0}; + size_t first_attention_stop_line_idx{0}; size_t occlusion_stop_line_idx{0}; }; struct Safe @@ -222,6 +227,7 @@ class IntersectionModule : public SceneModuleInterface StateMachine collision_state_machine_; //! for stable collision checking StateMachine before_creep_state_machine_; //! for two phase stop StateMachine occlusion_stop_state_machine_; + StateMachine temporal_stop_before_attention_state_machine_; // NOTE: uuid_ is base member // for stuck vehicle detection diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index a59c11deb55d1..a3bebecbd2bca 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -279,6 +279,8 @@ std::optional generateIntersectionStopLines( } } } + const auto first_attention_stop_line_ip = static_cast(occlusion_peeking_line_ip_int); + const bool first_attention_stop_line_valid = occlusion_peeking_line_valid; occlusion_peeking_line_ip_int += std::ceil(peeking_offset / ds); const auto occlusion_peeking_line_ip = static_cast( std::clamp(occlusion_peeking_line_ip_int, 0, static_cast(path_ip.points.size()) - 1)); @@ -324,6 +326,7 @@ std::optional generateIntersectionStopLines( size_t closest_idx{0}; size_t stuck_stop_line{0}; size_t default_stop_line{0}; + size_t first_attention_stop_line{0}; size_t occlusion_peeking_stop_line{0}; size_t pass_judge_line{0}; }; @@ -333,6 +336,7 @@ std::optional generateIntersectionStopLines( {&closest_idx_ip, &intersection_stop_lines_temp.closest_idx}, {&stuck_stop_line_ip, &intersection_stop_lines_temp.stuck_stop_line}, {&default_stop_line_ip, &intersection_stop_lines_temp.default_stop_line}, + {&first_attention_stop_line_ip, &intersection_stop_lines_temp.first_attention_stop_line}, {&occlusion_peeking_line_ip, &intersection_stop_lines_temp.occlusion_peeking_stop_line}, {&pass_judge_line_ip, &intersection_stop_lines_temp.pass_judge_line}, }; @@ -367,6 +371,10 @@ std::optional generateIntersectionStopLines( if (default_stop_line_valid) { intersection_stop_lines.default_stop_line = intersection_stop_lines_temp.default_stop_line; } + if (first_attention_stop_line_valid) { + intersection_stop_lines.first_attention_stop_line = + intersection_stop_lines_temp.first_attention_stop_line; + } if (occlusion_peeking_line_valid) { intersection_stop_lines.occlusion_peeking_stop_line = intersection_stop_lines_temp.occlusion_peeking_stop_line; diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 212081af43111..4135884f03c74 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -128,8 +128,10 @@ struct IntersectionStopLines size_t closest_idx{0}; // NOTE: null if path does not conflict with first_conflicting_area std::optional stuck_stop_line{std::nullopt}; - // NOTE: null if path is over map stop_line OR its value is calculated negative area + // NOTE: null if path is over map stop_line OR its value is calculated negative std::optional default_stop_line{std::nullopt}; + // NOTE: null if the index is calculated negative + std::optional first_attention_stop_line{std::nullopt}; // NOTE: null if footprints do not change from outside to inside of detection area std::optional occlusion_peeking_stop_line{std::nullopt}; // if the value is calculated negative, its value is 0 From 403c4de75e4eec4733af66ea904431af213d91b4 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 26 Sep 2023 00:56:46 +0900 Subject: [PATCH 319/547] feat(logging_level_configure): add rviz plugin to change logging level (#5112) * feat(logging_level_configure): add rviz plugin to change logging level Signed-off-by: Takamasa Horibe * change file names Signed-off-by: Takamasa Horibe * move initialization code from constructor to onInitialize Signed-off-by: Takamasa Horibe * add maintainer Signed-off-by: Takamasa Horibe * add maintainer Signed-off-by: Takamasa Horibe * fix include Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../CMakeLists.txt | 27 ++ .../README.md | 5 + .../logging_level_configure.hpp | 74 ++++++ .../package.xml | 32 +++ .../plugins/plugin_description.xml | 7 + .../src/logging_level_configure.cpp | 250 ++++++++++++++++++ ...r4_logging_level_configure_rviz_plugin.png | Bin 0 -> 74882 bytes 7 files changed, 395 insertions(+) create mode 100644 common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt create mode 100644 common/tier4_logging_level_configure_rviz_plugin/README.md create mode 100644 common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp create mode 100644 common/tier4_logging_level_configure_rviz_plugin/package.xml create mode 100644 common/tier4_logging_level_configure_rviz_plugin/plugins/plugin_description.xml create mode 100644 common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp create mode 100644 common/tier4_logging_level_configure_rviz_plugin/tier4_logging_level_configure_rviz_plugin.png diff --git a/common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt b/common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt new file mode 100644 index 0000000000000..f86b6d4d2efdc --- /dev/null +++ b/common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.14) +project(tier4_logging_level_configure_rviz_plugin) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +find_package(Qt5 REQUIRED Core Widgets) +set(QT_LIBRARIES Qt5::Widgets) +set(CMAKE_AUTOMOC ON) +add_definitions(-DQT_NO_KEYWORDS) + +ament_auto_add_library(tier4_logging_level_configure_rviz_plugin SHARED + include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp + src/logging_level_configure.cpp +) + +target_link_libraries(tier4_logging_level_configure_rviz_plugin + ${QT_LIBRARIES} +) + +# Export the plugin to be imported by rviz2 +pluginlib_export_plugin_description_file(rviz_common plugins/plugin_description.xml) + +ament_auto_package( + INSTALL_TO_SHARE + plugins +) diff --git a/common/tier4_logging_level_configure_rviz_plugin/README.md b/common/tier4_logging_level_configure_rviz_plugin/README.md new file mode 100644 index 0000000000000..c3523ed802205 --- /dev/null +++ b/common/tier4_logging_level_configure_rviz_plugin/README.md @@ -0,0 +1,5 @@ +# tier4_logging_level_configure_rviz_plugin + +This package provides an rviz_plugin that can easily change the logger level of each node + +![tier4_logging_level_configure_rviz_plugin](tier4_logging_level_configure_rviz_plugin.png) diff --git a/common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp b/common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp new file mode 100644 index 0000000000000..f513a7e04a248 --- /dev/null +++ b/common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp @@ -0,0 +1,74 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TIER4_LOGGING_LEVEL_CONFIGURE_RVIZ_PLUGIN__LOGGING_LEVEL_CONFIGURE_HPP_ +#define TIER4_LOGGING_LEVEL_CONFIGURE_RVIZ_PLUGIN__LOGGING_LEVEL_CONFIGURE_HPP_ + +#include "logging_demo/srv/config_logger.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace rviz_plugin +{ + +class LoggingLevelConfigureRvizPlugin : public rviz_common::Panel +{ + Q_OBJECT // This macro is needed for Qt to handle slots and signals + + public : LoggingLevelConfigureRvizPlugin(QWidget * parent = nullptr); + void onInitialize() override; + void save(rviz_common::Config config) const override; + void load(const rviz_common::Config & config) override; + +private: + QMap buttonGroups_; + rclcpp::Node::SharedPtr raw_node_; + + // logger_node_map_[target_name] = {container_name, logger_name} + std::map>> logger_node_map_; + + // client_map_[container_name] = service_client + std::unordered_map::SharedPtr> + client_map_; + + // button_map_[target_name][logging_level] = Q_button_pointer + std::unordered_map> button_map_; + + QStringList getContainerList(); + int getMaxModuleNameWidth(QLabel * containerLabel); + void setLoggerNodeMap(); + void attachLoggingComponent(); + +private Q_SLOTS: + void onButtonClick(QPushButton * button, const QString & name, const QString & level); + void updateButtonColors(const QString & target_module_name, QPushButton * active_button); + void changeLogLevel(const QString & container, const QString & level); +}; + +} // namespace rviz_plugin + +#endif // TIER4_LOGGING_LEVEL_CONFIGURE_RVIZ_PLUGIN__LOGGING_LEVEL_CONFIGURE_HPP_ diff --git a/common/tier4_logging_level_configure_rviz_plugin/package.xml b/common/tier4_logging_level_configure_rviz_plugin/package.xml new file mode 100644 index 0000000000000..4ba5bb1579e13 --- /dev/null +++ b/common/tier4_logging_level_configure_rviz_plugin/package.xml @@ -0,0 +1,32 @@ + + + + tier4_logging_level_configure_rviz_plugin + 0.1.0 + The tier4_logging_level_configure_rviz_plugin package + Takamasa Horibe + Satoshi Ota + Kosuke Takeuchi + Apache License 2.0 + + ament_cmake + autoware_cmake + + libqt5-core + libqt5-gui + libqt5-widgets + logging_demo + qtbase5-dev + rclcpp + rviz_common + rviz_default_plugins + rviz_rendering + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + + diff --git a/common/tier4_logging_level_configure_rviz_plugin/plugins/plugin_description.xml b/common/tier4_logging_level_configure_rviz_plugin/plugins/plugin_description.xml new file mode 100644 index 0000000000000..ce5cbd13fc131 --- /dev/null +++ b/common/tier4_logging_level_configure_rviz_plugin/plugins/plugin_description.xml @@ -0,0 +1,7 @@ + + + tier4_logging_level_configure_rviz_plugin + + diff --git a/common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp b/common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp new file mode 100644 index 0000000000000..e645ff682d50e --- /dev/null +++ b/common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp @@ -0,0 +1,250 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +namespace rviz_plugin +{ + +LoggingLevelConfigureRvizPlugin::LoggingLevelConfigureRvizPlugin(QWidget * parent) +: rviz_common::Panel(parent) +{ +} + +// Calculate the maximum width among all target_module_name. +int LoggingLevelConfigureRvizPlugin::getMaxModuleNameWidth(QLabel * containerLabel) +{ + int max_width = 0; + QFontMetrics metrics(containerLabel->font()); + for (const auto & item : logger_node_map_) { + const auto & target_module_name = item.first; + int width = metrics.horizontalAdvance(target_module_name); + if (width > max_width) { + max_width = width; + } + } + return max_width; +} + +// create container list in logger_node_map_ without +QStringList LoggingLevelConfigureRvizPlugin::getContainerList() +{ + QStringList containers; + for (const auto & item : logger_node_map_) { + const auto & container_logger_vec = item.second; + for (const auto & container_logger_pair : container_logger_vec) { + if (!containers.contains(container_logger_pair.first)) { + containers.append(container_logger_pair.first); + } + } + } + return containers; +} + +void LoggingLevelConfigureRvizPlugin::onInitialize() +{ + setLoggerNodeMap(); + + attachLoggingComponent(); + + QVBoxLayout * layout = new QVBoxLayout; + + QStringList levels = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; + + QStringList loaded_container; + constexpr int height = 20; + for (const auto & item : logger_node_map_) { + const auto & target_module_name = item.first; + + QHBoxLayout * hLayout = new QHBoxLayout; + + // Create a QLabel to display the container name. + QLabel * containerLabel = new QLabel(target_module_name); + containerLabel->setFixedHeight(height); // Set fixed height for the button + containerLabel->setFixedWidth(getMaxModuleNameWidth(containerLabel)); + + hLayout->addWidget(containerLabel); // Add the QLabel to the hLayout. + + QButtonGroup * group = new QButtonGroup(this); + for (const QString & level : levels) { + QPushButton * btn = new QPushButton(level); + btn->setFixedHeight(height); // Set fixed height for the button + hLayout->addWidget(btn); // Add each QPushButton to the hLayout. + group->addButton(btn); + button_map_[target_module_name][level] = btn; + connect(btn, &QPushButton::clicked, this, [this, btn, target_module_name, level]() { + this->onButtonClick(btn, target_module_name, level); + }); + } + // Set the "INFO" button as checked by default and change its color. + updateButtonColors(target_module_name, button_map_[target_module_name]["INFO"]); + + buttonGroups_[target_module_name] = group; + layout->addLayout(hLayout); + } + + setLayout(layout); + + // set up service clients + raw_node_ = this->getDisplayContext()->getRosNodeAbstraction().lock()->get_raw_node(); + const auto & containers = getContainerList(); + for (const QString & container : containers) { + const auto client = raw_node_->create_client( + container.toStdString() + "/config_logger"); + client_map_[container] = client; + } +} + +void LoggingLevelConfigureRvizPlugin::attachLoggingComponent() +{ + const auto & containers = getContainerList(); + for (const auto & container_name : containers) { + // Load the component for each container + QString command = "ros2 component load --node-namespace " + container_name + + " --node-name logging_configure " + container_name + + " logging_demo logging_demo::LoggerConfig"; + int result = system(qPrintable(command)); + std::cerr << "load logger in " << container_name.toStdString() << ": result = " << result + << std::endl; + } +} + +// Modify the signature of the onButtonClick function: +void LoggingLevelConfigureRvizPlugin::onButtonClick( + QPushButton * button, const QString & target_module_name, const QString & level) +{ + if (button) { + const auto callback = + [&](rclcpp::Client::SharedFuture future) { + std::cerr << "change logging level: " + << std::string(future.get()->success ? "success!" : "failed...") << std::endl; + }; + + for (const auto & container_logger_map : logger_node_map_[target_module_name]) { + const auto container_node = container_logger_map.first; + const auto logger_name = container_logger_map.second; + const auto req = std::make_shared(); + + req->logger_name = logger_name.toStdString(); + req->level = level.toStdString(); + std::cerr << "logger level of " << req->logger_name << " is set to " << req->level + << std::endl; + client_map_[container_node]->async_send_request(req, callback); + } + + updateButtonColors( + target_module_name, button); // Modify updateButtonColors to accept QPushButton only. + } +} + +void LoggingLevelConfigureRvizPlugin::updateButtonColors( + const QString & target_module_name, QPushButton * active_button) +{ + const QString LIGHT_GREEN = "rgb(181, 255, 20)"; + const QString LIGHT_GRAY = "rgb(211, 211, 211)"; + const QString LIGHT_GRAY_TEXT = "rgb(180, 180, 180)"; + + for (const auto & button : button_map_[target_module_name]) { + if (button.second == active_button) { + button.second->setStyleSheet("background-color: " + LIGHT_GREEN + "; color: black"); + } else { + button.second->setStyleSheet( + "background-color: " + LIGHT_GRAY + "; color: " + LIGHT_GRAY_TEXT); + } + } +} + +void LoggingLevelConfigureRvizPlugin::save(rviz_common::Config config) const +{ + Panel::save(config); +} + +void LoggingLevelConfigureRvizPlugin::load(const rviz_common::Config & config) +{ + Panel::load(config); +} + +void LoggingLevelConfigureRvizPlugin::setLoggerNodeMap() +{ + // =============================================================================================== + // ====================================== Planning =============================================== + // =============================================================================================== + + QString behavior_planning_container = + "/planning/scenario_planning/lane_driving/behavior_planning/behavior_planning_container"; + QString motion_planning_container = + "/planning/scenario_planning/lane_driving/motion_planning/motion_planning_container"; + + // behavior_path_planner (all) + logger_node_map_["behavior_path_planner"] = { + {behavior_planning_container, + "planning.scenario_planning.lane_driving.behavior_planning.behavior_path_planner"}, + {behavior_planning_container, "tier4_autoware_utils"}}; + + // behavior_path_planner: avoidance + logger_node_map_["behavior_path_planner: avoidance"] = { + {behavior_planning_container, + "planning.scenario_planning.lane_driving.behavior_planning.behavior_path_planner." + "avoidance"}}; + + // behavior_velocity_planner (all) + logger_node_map_["behavior_velocity_planner"] = { + {behavior_planning_container, + "planning.scenario_planning.lane_driving.behavior_planning.behavior_velocity_planner"}, + {behavior_planning_container, "tier4_autoware_utils"}}; + + // behavior_velocity_planner: intersection + logger_node_map_["behavior_velocity_planner: intersection"] = { + {behavior_planning_container, + "planning.scenario_planning.lane_driving.behavior_planning.behavior_velocity_planner." + "intersection"}}; + + // obstacle_avoidance_planner + logger_node_map_["motion: obstacle_avoidance"] = { + {motion_planning_container, + "planning.scenario_planning.lane_driving.motion_planning.obstacle_avoidance_planner"}, + {motion_planning_container, "tier4_autoware_utils"}}; + + // motion_velocity_smoother + QString container = "/planning/scenario_planning/motion_velocity_smoother_container"; + logger_node_map_["motion: velocity_smoother"] = { + {container, "planning.scenario_planning.motion_velocity_smoother"}, + {container, "tier4_autoware_utils"}}; + + // =============================================================================================== + // ======================================= Control =============================================== + // =============================================================================================== + + QString control_container = "/control/control_container"; + + // lateral_controller + logger_node_map_["lateral_controller"] = { + {control_container, "control.trajectory_follower.controller_node_exe.lateral_controller"}, + {control_container, "tier4_autoware_utils"}, + }; + + // longitudinal_controller + logger_node_map_["longitudinal_controller"] = { + {control_container, "control.trajectory_follower.controller_node_exe.longitudinal_controller"}, + {control_container, "tier4_autoware_utils"}, + }; +} + +} // namespace rviz_plugin +#include +PLUGINLIB_EXPORT_CLASS(rviz_plugin::LoggingLevelConfigureRvizPlugin, rviz_common::Panel) diff --git a/common/tier4_logging_level_configure_rviz_plugin/tier4_logging_level_configure_rviz_plugin.png b/common/tier4_logging_level_configure_rviz_plugin/tier4_logging_level_configure_rviz_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..a93aff724bb196368e355edb10291ca4616547bb GIT binary patch literal 74882 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%0D5X?Awo6+0jZTAWhBlh{DDP` zmn|*M7H3ig5O6X{N$#i?`82V^Lgn`yZ>3gUka=LbYZJ+znk0u2f{4MIpPTYwXVYVv zD;1W=W=d1^t)2_cu2-u4Yk!^Q1y{sut)0j2AuUYDof}>c2^JC0G+J0Q{ zNKv)wh0%Vd_0<9c!O2ZrIO*UDOZTzQ?-sg<0N`~5VUf#PGR6oJVrJUJyqZjNo7n< zy>2TdZWcvZ)`E;+EUG~AE$BOTLsis44xGns-9Ghnm7ahrCT_XVif%#~ttXy~N4GJx zY7X=n0Ke7M_OR4pamqrjVClHOpRjwAm4KGWPs2?`OU*|Ls77miKV0Q`^y9DR?1X6@ zg0Ija?br$-8*zBm9LVt4HcU;{8};z@yB=yKEtb!c*aHR@gSFOZgsQpfj)r-<(JqNT z;sDHhH+phkHpX2j-T5$@Wsi>jPJ6bvfZH&`3-THv=Xv}SMWfdo7Knu#aKwdmma~9= zshxx)YS;zk=F5mmm7|efPQE)>fA4#&s%GcfN?43N_IZhU>32%3c0OA4!>`&F9j3CV zAg4nrAcEvor;%;q95+r!O(p{Ra`S9eW%bV^W>7>|D`iaRT=~u@tpMqXJvQm{&v{nD znil9rbJjstuy{5oJy{TEP%+Y9t*_5z&U%CaW@~Z{Ym(t`Lh$C1Q5}^?(6_D)($dB1 zZJ3ga0(WDrB}{L!v}_^%I25;wC}^VUwaSd1XiR;inGN|IGhpiWKhpgC}X z34sqTs0*qI;}t-5E|#@i;IfvFSkNi6en7=Vk!+lM2rhUJu#-^7VnDEVHjGg*Ap=|< z-GjIFMJ2q@ZZU-o!(ks@OyniwX0i2aE&a9nwjQi_8+(Dce)b{Why-Kt><1os+*Hx8 z40jw|1d&Gj8Vr(f{GN5Jv$Ba1&RuGPgP7;(Z!})!lFDpk`2`z6)Q1^f8zpod{`2H< zSgqU5`a*#pY@1QVhO}632NLRA<)puQxOxjl{R$?(> zNfb71HzKFXWMEtOP~9T~iB1@g^BZbY#YDF)Wt28;vA)H&U?oX#i?LqCUUCv%q4BBd zhTB(40vPJO8B>+WsduBlRj%9*k^N}KKuIBzJY}pv%*zMwZ^ZRg`;RVc#Y(TX&B#&zz^W!hm#XG!8M`Bj2xuh&B%N^P&Ye2s!erEny0^)DVRvO24W?5*PU9Ub? zl|NSWk%Vft1YzZ7S6HCpkC;o;`Y|9YvE@sv*&iMQBT~nzl$$17Q8{9mm?bR2^AvUj zYY_hjL8iDna`R;sB?Oyiq=T}ls7R=eTZo$^2e@c<#nbvaw5_}nJP}sB3z=WDsD~wM z0~#u5D0~40e4b=9s3;tck+8jLbegWegxzS?YoMD1M2uHu0nfe83rksAmiPD_HrE>! z78Vy6(IHrybYYFC;m#^S77d2Nxmg+)@$GfybEUF?;u7vr+0JwOI=Zwv8Cr@8OEKBH zrFz;{*WCyTbrVYcy`VPW+5F$2lvSmln43m7nb}*l~U*9^`P3aD{b4Eip8n!MTe2gz(Y~Rdb@$IJuLVI{6BQqy=OAUEagfEDa!KM7W z7Eg{wXBk_}y5CW&H}VHKNdW}h3T0VRn;Wf}NkIaiMR)uJpV6jX#BS^65SYZx(;^-xq-6Uw?i9+B&alX^L!b&Xy;QGjcoFRo>i-;@uNb}rv0YuhYQ>{F+egs|{< zXHLTZTlSxs>DJaGZd+-C{KsFURKsqlyQhjv6r4`TkyP5 z17vO?W_fg3@E&B{4D(uL9#NK2!eP~L>aE4kroCNG8jSyiHK)VyUmjS#N2{x_>6r=3 z$r!!XoOnJC(3MHwj)Kf1Pimk~QrDv!^6pm{CT((8>g@IqcgMF#T8vcD6ZUZ6U2Nc= zMZ02c0pFoFQ&zJUu!^q__3HN4qBS|BizZ|g0dMhjW>3B(uIq|kL*PamTdyO<1usGb z5$h+uU@=CEPckFqo7-l7+v6^|?B>50l*3ufI6z-2Fu0{tIP2)C2Gp23)}KxEyFz+PEC3gUA^>`qH(>h@P9 z%=X)^TsIGA?tlWb)m%qZ`CPn>=I+u>ZA}gUXW+=w$nIMITimpX^ttE;KOgAoLf8Ui z%F_{VpAdi~F~~Xjf>Lx~+N&WTIhqB=ZW2(RHlML1Pu9xdk35Apmx9GwWbD^>Cr@s_ zl#g*xJ;d|zXZkts6B;l$CV)UlsBpVpMzb{+H=i^ok=kS2xA9V}lPuU)5s)K#VS6*n z`7XbEO0C4X-+m&~@*Y+(LXNaUX9}<-E-H<+Bc>6*b}OBTf@M|EZB|id4J_SrT~yRn z6l`7>P!Pdj8{6NVMTvWAsISeeR5L0Fwq`J3bH}f~1h=fLnzg{H+$*f@2)xCDirNg- zQ&gDQ$3GX>)Fae*)l5w8JUTjVKzwl1T+EeT|>vdjUmU-@+=Gi>9bh&5D=~;v$!w*}`oYH-V>|}7& z|9B`9OEgF)+l=qu0o>~KB29-IZ#WKGg!wyyVlRm|ZK5P5)vA^FOh}Q2;}xPU=lcba zKR-PmQ&I!Z#pB2J!Bnlr%bl^IZd-q?Tp3h8JqfF83TnE9o1Ohui~0qu3-owx*#Lb@ z`8&h@^`z@5%*?cY=!|9`9WS(*E$_7SuCt&W_oQ>PVP(e6`kGeXY|whH_0iW72s{v{ zV8IwQN!^^7Z6ok}vdqGv9#mq9;T*X60PRT8GL~otX!sWdVKZm4@kIe41*BgTu%^v@ zG`?yKhHxR|qh2SOwiRCsTNoE)?W(wg9%E{HyxbK%y!dDjU(;eP8P}FPF97#A!BtUF zFUmOP&$hXFcSJBDke@V^s0r%A8yG9>&Vs7?>^NuxaMbDyZ7=y;H=e~<+`%rfFdKOspjFgkDlkl6$AaeT
    0g&$db_h; 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 CP)@l3 literal 0 HcmV?d00001 From dcbd0626243aaf6f14725085cd22b953373b6261 Mon Sep 17 00:00:00 2001 From: danielsanchezaran Date: Tue, 26 Sep 2023 08:09:00 +0900 Subject: [PATCH 320/547] feat(new module): behavior velocity module template (#5073) * first commit: copy speed_bump_module Signed-off-by: Daniel Sanchez * [refactor] rename variables to "template" Signed-off-by: Daniel Sanchez * [refactor] delete unnecessary code Signed-off-by: Daniel Sanchez * [refactor] simplify module even more Signed-off-by: Daniel Sanchez * [refactoring] eliminate unused code Signed-off-by: Daniel Sanchez * more simplification of code Signed-off-by: Daniel Sanchez * Delete unused files Signed-off-by: Daniel Sanchez * Compilation and parameter changes for template Signed-off-by: Daniel Sanchez * change param call Signed-off-by: Daniel Sanchez * fix compilation issues Signed-off-by: Daniel Sanchez * Guarantee true is returned Signed-off-by: Daniel Sanchez * Add logs Signed-off-by: Daniel Sanchez * Fix override function definitions Signed-off-by: Daniel Sanchez * fixed dependency path after rbase Signed-off-by: Daniel Sanchez * pre-commit changes Signed-off-by: Daniel Sanchez * use maybe_unused to eliminate redundant code Signed-off-by: Daniel Sanchez * Change Log message to log only once Signed-off-by: Daniel Sanchez * Fix copyright Signed-off-by: Daniel Sanchez * Fix copyright year Signed-off-by: Daniel Sanchez * Fix copyright, again Signed-off-by: Daniel Sanchez * Comment bvm template from sample implementation Signed-off-by: Daniel Sanchez * Update documentation] Signed-off-by: Daniel Sanchez * Update documentation Signed-off-by: Daniel Sanchez * pre-commit changes Signed-off-by: Daniel Sanchez * Update Readme Signed-off-by: Daniel Sanchez * Add briefs for documentation Signed-off-by: Daniel Sanchez --------- Signed-off-by: Daniel Sanchez --- .../behavior_velocity_planner.param.yaml | 1 + .../behavior_velocity_planner.launch.xml | 3 + .../behavior_velocity_planner/package.xml | 1 + .../CMakeLists.txt | 13 +++ .../README.md | 85 +++++++++++++++++ .../config/template.param.yaml | 4 + .../package.xml | 37 ++++++++ .../plugins.xml | 3 + .../src/manager.cpp | 63 +++++++++++++ .../src/manager.hpp | 93 +++++++++++++++++++ .../src/scene.cpp | 52 +++++++++++ .../src/scene.hpp | 69 ++++++++++++++ 12 files changed, 424 insertions(+) create mode 100644 planning/behavior_velocity_template_module/CMakeLists.txt create mode 100644 planning/behavior_velocity_template_module/README.md create mode 100644 planning/behavior_velocity_template_module/config/template.param.yaml create mode 100644 planning/behavior_velocity_template_module/package.xml create mode 100644 planning/behavior_velocity_template_module/plugins.xml create mode 100644 planning/behavior_velocity_template_module/src/manager.cpp create mode 100644 planning/behavior_velocity_template_module/src/manager.hpp create mode 100644 planning/behavior_velocity_template_module/src/scene.cpp create mode 100644 planning/behavior_velocity_template_module/src/scene.hpp diff --git a/planning/behavior_velocity_planner/config/behavior_velocity_planner.param.yaml b/planning/behavior_velocity_planner/config/behavior_velocity_planner.param.yaml index e8377f841aa77..276e8f8e3145a 100644 --- a/planning/behavior_velocity_planner/config/behavior_velocity_planner.param.yaml +++ b/planning/behavior_velocity_planner/config/behavior_velocity_planner.param.yaml @@ -22,5 +22,6 @@ # behavior_velocity_planner::OcclusionSpotModulePlugin # behavior_velocity_planner::RunOutModulePlugin # behavior_velocity_planner::SpeedBumpModulePlugin + # behavior_velocity_planner::TemplateModulePlugin - behavior_velocity_planner::OutOfLaneModulePlugin - behavior_velocity_planner::NoDrivableLaneModulePlugin diff --git a/planning/behavior_velocity_planner/launch/behavior_velocity_planner.launch.xml b/planning/behavior_velocity_planner/launch/behavior_velocity_planner.launch.xml index b517b77b328c7..69d9c27331e52 100644 --- a/planning/behavior_velocity_planner/launch/behavior_velocity_planner.launch.xml +++ b/planning/behavior_velocity_planner/launch/behavior_velocity_planner.launch.xml @@ -19,6 +19,7 @@ + @@ -40,6 +41,7 @@ + @@ -68,6 +70,7 @@ + diff --git a/planning/behavior_velocity_planner/package.xml b/planning/behavior_velocity_planner/package.xml index 9a8b1a013738d..d83f74a71b66f 100644 --- a/planning/behavior_velocity_planner/package.xml +++ b/planning/behavior_velocity_planner/package.xml @@ -81,6 +81,7 @@ behavior_velocity_traffic_light_module behavior_velocity_virtual_traffic_light_module behavior_velocity_walkway_module + rosidl_interface_packages diff --git a/planning/behavior_velocity_template_module/CMakeLists.txt b/planning/behavior_velocity_template_module/CMakeLists.txt new file mode 100644 index 0000000000000..e0beb42daf50d --- /dev/null +++ b/planning/behavior_velocity_template_module/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.14) +project(behavior_velocity_template_module) + +find_package(autoware_cmake REQUIRED) +autoware_package() +pluginlib_export_plugin_description_file(behavior_velocity_planner plugins.xml) + +ament_auto_add_library(${PROJECT_NAME} SHARED + src/manager.cpp + src/scene.cpp +) + +ament_auto_package(INSTALL_TO_SHARE config) diff --git a/planning/behavior_velocity_template_module/README.md b/planning/behavior_velocity_template_module/README.md new file mode 100644 index 0000000000000..1995b3e040061 --- /dev/null +++ b/planning/behavior_velocity_template_module/README.md @@ -0,0 +1,85 @@ +## Template + +A template for behavior velocity modules based on the behavior_velocity_speed_bump_module. + +# Autoware Behavior Velocity Module Template + +## `Scene` + +### `TemplateModule` Class + +The `TemplateModule` class serves as a foundation for creating a scene module within the Autoware behavior velocity planner. It defines the core methods and functionality needed for the module's behavior. You should replace the placeholder code with actual implementations tailored to your specific behavior velocity module. + +#### Constructor + +- The constructor for `TemplateModule` takes the essential parameters to create a module: `const int64_t module_id`, `const rclcpp::Logger & logger`, and `const rclcpp::Clock::SharedPtr clock`. These parameters are supplied by the `TemplateModuleManager` when registering a new module. Other parameters can be added to the constructor, if required by your specific module implementation. + +#### `modifyPathVelocity` Method + +- This method, defined in the `TemplateModule` class, is expected to modify the velocity of the input path based on certain conditions. In the provided code, it logs an informational message once when the template module is executing. +- The specific logic for velocity modification should be implemented in this method based on the module's requirements. + +#### `createDebugMarkerArray` Method + +- This method, also defined in the `TemplateModule` class, is responsible for creating a visualization of debug markers and returning them as a `visualization_msgs::msg::MarkerArray`. In the provided code, it returns an empty `MarkerArray`. +- You should implement the logic to generate debug markers specific to your module's functionality. + +#### `createVirtualWalls` Method + +- The `createVirtualWalls` method creates virtual walls for the scene and returns them as `motion_utils::VirtualWalls`. In the provided code, it returns an empty `VirtualWalls` object. +- You should implement the logic to create virtual walls based on your module's requirements. + +## `Manager` + +The managing of your modules is defined in manager.hpp and manager.cpp. The managing is handled by two classes: + +- The `TemplateModuleManager` class defines the core logic for managing and launching the behavior_velocity_template scenes (defined in behavior_velocity_template_module/src/scene.cpp/hpp). It inherits essential manager attributes from its parent class `SceneModuleManagerInterface`. +- The `TemplateModulePlugin` class provides a way to integrate the `TemplateModuleManager` into the logic of the Behavior Velocity Planner. + +### `TemplateModuleManager` Class + +#### Constructor `TemplateModuleManager` + +- This is the constructor of the `TemplateModuleManager` class, and it takes an `rclcpp::Node` reference as a parameter. +- It initializes a member variable `dummy_parameter` to 0.0. + +#### `getModuleName()` Method + +- This method is an override of a virtual method from the `SceneModuleManagerInterface` class. +- It returns a pointer to a constant character string, which is the name of the module. In this case, it returns "template" as the module name. + +#### `launchNewModules()` Method + +- This is a private method that takes an argument of type `autoware_auto_planning_msgs::msg::PathWithLaneId`. +- It is responsible for launching new modules based on the provided path information (PathWithLaneId). The implementation of this method involves initializing and configuring modules specific to your behavior velocity planner by using the `TemplateModule` class. +- In the provided source code, it initializes a `module_id` to 0 and checks if a module with the same ID is already registered. If not, it registers a new `TemplateModule` with the module ID. Note that each module managed by the `TemplateModuleManager` should have a unique ID. The template code registers a single module, so the `module_id` is set as 0 for simplicity. + +#### `getModuleExpiredFunction()` Method + +- This is a private method that takes an argument of type `autoware_auto_planning_msgs::msg::PathWithLaneId`. +- It returns a `std::function&)>`. This function is used by the behavior velocity planner to determine whether a particular module has expired or not based on the given path. +- The implementation of this method is expected to return a function that can be used to check the expiration status of modules. + +Please note that the specific functionality of the methods `launchNewModules()` and `getModuleExpiredFunction()` would depend on the details of your behavior velocity modules and how they are intended to be managed within the Autoware system. You would need to implement these methods according to your module's requirements. + +### `TemplateModulePlugin` Class + +#### `TemplateModulePlugin` Class + +- This class inherits from `PluginWrapper`. It essentially wraps your `TemplateModuleManager` class within a plugin, which can be loaded and managed dynamically. + +## `Example Usage` + +In the following example, we take each point of the path, and multiply it by 2. Essentially duplicating the speed. Note that the velocity smoother will further modify the path speed after all the behavior velocity modules are executed. + +```cpp +bool TemplateModule::modifyPathVelocity( + [[maybe_unused]] PathWithLaneId * path, [[maybe_unused]] StopReason * stop_reason) +{ + for (auto & p : path->points) { + p.point.longitudinal_velocity_mps *= 2.0; + } + + return false; +} +``` diff --git a/planning/behavior_velocity_template_module/config/template.param.yaml b/planning/behavior_velocity_template_module/config/template.param.yaml new file mode 100644 index 0000000000000..7045eb28e98a2 --- /dev/null +++ b/planning/behavior_velocity_template_module/config/template.param.yaml @@ -0,0 +1,4 @@ +/**: + ros__parameters: + template: + dummy: 1.2 diff --git a/planning/behavior_velocity_template_module/package.xml b/planning/behavior_velocity_template_module/package.xml new file mode 100644 index 0000000000000..93a1cb4042e93 --- /dev/null +++ b/planning/behavior_velocity_template_module/package.xml @@ -0,0 +1,37 @@ + + + + behavior_velocity_template_module + 0.1.0 + The behavior_velocity_template_module package + + Daniel Sanchez + + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + eigen3_cmake_module + + autoware_auto_planning_msgs + behavior_velocity_planner_common + eigen + geometry_msgs + lanelet2_extension + libboost-dev + motion_utils + pluginlib + rclcpp + route_handler + sensor_msgs + tf2 + tier4_autoware_utils + visualization_msgs + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/planning/behavior_velocity_template_module/plugins.xml b/planning/behavior_velocity_template_module/plugins.xml new file mode 100644 index 0000000000000..3560c84e6a080 --- /dev/null +++ b/planning/behavior_velocity_template_module/plugins.xml @@ -0,0 +1,3 @@ + + + diff --git a/planning/behavior_velocity_template_module/src/manager.cpp b/planning/behavior_velocity_template_module/src/manager.cpp new file mode 100644 index 0000000000000..22763f44af7bf --- /dev/null +++ b/planning/behavior_velocity_template_module/src/manager.cpp @@ -0,0 +1,63 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "manager.hpp" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace behavior_velocity_planner +{ +using tier4_autoware_utils::getOrDeclareParameter; + +TemplateModuleManager::TemplateModuleManager(rclcpp::Node & node) +: SceneModuleManagerInterface(node, getModuleName()) +{ + std::string ns(getModuleName()); + dummy_parameter = getOrDeclareParameter(node, ns + ".dummy"); +} + +void TemplateModuleManager::launchNewModules( + [[maybe_unused]] const autoware_auto_planning_msgs::msg::PathWithLaneId & path) +{ + int64_t module_id = 0; + if (!isModuleRegistered(module_id)) { + registerModule( + std::make_shared(module_id, logger_.get_child(getModuleName()), clock_)); + } +} + +std::function &)> +TemplateModuleManager::getModuleExpiredFunction( + [[maybe_unused]] const autoware_auto_planning_msgs::msg::PathWithLaneId & path) +{ + return []([[maybe_unused]] const std::shared_ptr & scene_module) -> bool { + return false; + }; +} + +} // namespace behavior_velocity_planner + +#include +PLUGINLIB_EXPORT_CLASS( + behavior_velocity_planner::TemplateModulePlugin, behavior_velocity_planner::PluginInterface) diff --git a/planning/behavior_velocity_template_module/src/manager.hpp b/planning/behavior_velocity_template_module/src/manager.hpp new file mode 100644 index 0000000000000..ef4e1f00cae92 --- /dev/null +++ b/planning/behavior_velocity_template_module/src/manager.hpp @@ -0,0 +1,93 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MANAGER_HPP_ +#define MANAGER_HPP_ + +#include "scene.hpp" + +#include +#include +#include +#include + +#include + +#include +#include + +namespace behavior_velocity_planner +{ +/** + * @brief Constructor for the TemplateModuleManager class. + * + * Initializes a TemplateModuleManager instance with the provided ROS node, setting up essential + * parameters. + * + * @param node A reference to the ROS node. + */ +class TemplateModuleManager : public SceneModuleManagerInterface +{ +public: + explicit TemplateModuleManager(rclcpp::Node & node); + + /** + * @brief Get the name of the module. + * + * This method returns a constant character string representing the name of the module, which in + * this case is "template." + * + * @return A pointer to a constant character string containing the module name. + */ + const char * getModuleName() override { return "template"; } + +private: + double dummy_parameter{0.0}; + + /** + * @brief Launch new modules based on the provided path. + * + * This method is responsible for launching new modules based on the information provided in the + * given path. + * + * @param path The path with lane ID information to determine module launch. + */ + void launchNewModules(const autoware_auto_planning_msgs::msg::PathWithLaneId & path) override; + + /** + * @brief Get a function to check module expiration. + * + * This method returns a function that can be used to determine whether a specific module has + * expired based on the given path information. + * + * @param path The path with lane ID information for module expiration check. + * @return A function for checking module expiration. + */ + std::function &)> getModuleExpiredFunction( + const autoware_auto_planning_msgs::msg::PathWithLaneId & path) override; +}; + +/** + * @brief Plugin class for TemplateModuleManager. + * + * The TemplateModulePlugin class is used to integrate the TemplateModuleManager into the Behavior + * Velocity Planner. + */ +class TemplateModulePlugin : public PluginWrapper +{ +}; + +} // namespace behavior_velocity_planner + +#endif // MANAGER_HPP_ diff --git a/planning/behavior_velocity_template_module/src/scene.cpp b/planning/behavior_velocity_template_module/src/scene.cpp new file mode 100644 index 0000000000000..558f67b49817c --- /dev/null +++ b/planning/behavior_velocity_template_module/src/scene.cpp @@ -0,0 +1,52 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "scene.hpp" + +#include "motion_utils/trajectory/trajectory.hpp" +// #include "tier4_autoware_utils/tier4_autoware_utils.hpp" + +#include + +#include + +namespace behavior_velocity_planner +{ + +TemplateModule::TemplateModule( + const int64_t module_id, const rclcpp::Logger & logger, const rclcpp::Clock::SharedPtr clock) +: SceneModuleInterface(module_id, logger, clock) +{ +} + +visualization_msgs::msg::MarkerArray TemplateModule::createDebugMarkerArray() +{ + visualization_msgs::msg::MarkerArray ma; + return ma; +}; + +motion_utils::VirtualWalls TemplateModule::createVirtualWalls() +{ + motion_utils::VirtualWalls vw; + return vw; +} + +bool TemplateModule::modifyPathVelocity( + [[maybe_unused]] PathWithLaneId * path, [[maybe_unused]] StopReason * stop_reason) +{ + RCLCPP_INFO_ONCE(logger_, "Template Module is executing!"); + return false; +} + +} // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_template_module/src/scene.hpp b/planning/behavior_velocity_template_module/src/scene.hpp new file mode 100644 index 0000000000000..fab0b23efb25f --- /dev/null +++ b/planning/behavior_velocity_template_module/src/scene.hpp @@ -0,0 +1,69 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SCENE_HPP_ +#define SCENE_HPP_ + +#include +#include + +#include +#include + +namespace behavior_velocity_planner +{ +using autoware_auto_planning_msgs::msg::PathWithLaneId; + +class TemplateModule : public SceneModuleInterface +{ +public: + TemplateModule( + const int64_t module_id, const rclcpp::Logger & logger, const rclcpp::Clock::SharedPtr clock); + + /** + * @brief Modify the velocity of path points. + * + * This method is responsible for adjusting the velocity of each point in the input path based on + * specific criteria. + * + * @param path A pointer to the path containing points to be modified. + * @param stop_reason A pointer to the stop reason data. + * @return [bool] wether the path velocity was modified or not. + */ + bool modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) override; + + /** + * @brief Create a visualization of debug markers. + * + * This method is responsible for generating a visualization of debug markers and returning them + * as a `visualization_msgs::msg::MarkerArray`. + * + * @return A `visualization_msgs::msg::MarkerArray` containing debug markers. + */ + visualization_msgs::msg::MarkerArray createDebugMarkerArray() override; + + /** + * @brief Create virtual walls for the scene. + * + * This method is responsible for generating virtual walls for the scene and returning them as a + * `motion_utils::VirtualWalls` object. + * + * @return A `motion_utils::VirtualWalls` object representing virtual walls in the scene. + */ + motion_utils::VirtualWalls createVirtualWalls() override; +}; + +} // namespace behavior_velocity_planner + +#endif // SCENE_HPP_ From 46f871fdc6df53f9dce615ce8a132e3d3d139c14 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Tue, 26 Sep 2023 02:22:51 +0300 Subject: [PATCH 321/547] build(yabloc_pose_initializer): remove downloading logic from CMake (#4905) * remove downloading logic from Cmake Signed-off-by: Alexey Panferov * build(yabloc_pose_initializer): remove downloading logic from CMake Signed-off-by: Alexey Panferov * build(yabloc_pose_initializer): update default model path in launch file Signed-off-by: Alexey Panferov --------- Signed-off-by: Alexey Panferov --- .../yabloc_pose_initializer/CMakeLists.txt | 6 +- .../yabloc/yabloc_pose_initializer/README.md | 18 +++-- .../yabloc_pose_initializer/download.cmake | 67 ------------------- .../launch/yabloc_pose_initializer.launch.xml | 3 +- .../src/camera/semantic_segmentation.cpp | 4 +- 5 files changed, 12 insertions(+), 86 deletions(-) delete mode 100644 localization/yabloc/yabloc_pose_initializer/download.cmake diff --git a/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt b/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt index ad33912164359..164f280becae8 100644 --- a/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt +++ b/localization/yabloc/yabloc_pose_initializer/CMakeLists.txt @@ -14,10 +14,6 @@ find_package(PCL REQUIRED COMPONENTS common kdtree) # Sophus find_package(Sophus REQUIRED) -# =================================================== -# Download DNN model -include(download.cmake) - # =================================================== # Executable # Camera @@ -34,4 +30,4 @@ target_include_directories(${TARGET} SYSTEM PRIVATE ${EIGEN3_INCLUDE_DIRS} ${PCL target_link_libraries(${TARGET} ${PCL_LIBRARIES} Sophus::Sophus) # =================================================== -ament_auto_package(INSTALL_TO_SHARE config data launch) +ament_auto_package(INSTALL_TO_SHARE config launch) diff --git a/localization/yabloc/yabloc_pose_initializer/README.md b/localization/yabloc/yabloc_pose_initializer/README.md index 6a7c21fc554cf..907b79c1459ec 100644 --- a/localization/yabloc/yabloc_pose_initializer/README.md +++ b/localization/yabloc/yabloc_pose_initializer/README.md @@ -4,23 +4,21 @@ This package contains a node related to initial pose estimation. - [camera_pose_initializer](#camera_pose_initializer) -Ideally, this package downloads a pre-trained semantic segmentation model during the build and loads it at runtime for initialization. -However, to handle cases where network connectivity is not available at build time, **the default behavior is not to download the model during build.** -Even if the model is not downloaded, initialization will still complete, but the accuracy may be compromised. +This package requires the pre-trained semantic segmentation model for runtime. This model is usually downloaded by `ansible` during env preparation phase of the [installation](https://autowarefoundation.github.io/autoware-documentation/main/installation/autoware/source-installation/). +It is also possible to download it manually. Even if the model is not downloaded, initialization will still complete, but the accuracy may be compromised. - - -To download the model, please specify `--cmake-args -DDOWNLOAD_ARTIFACTS=ON` to the build command. +To download and extract the model manually: ```bash -colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release -DDOWNLOAD_ARTIFACTS=ON --packages-select yabloc_pose_initializer +$ mkdir -p ~/autoware_data/yabloc_pose_initializer/ +$ wget -P ~/autoware_data/yabloc_pose_initializer/ \ + https://s3.ap-northeast-2.wasabisys.com/pinto-model-zoo/136_road-segmentation-adas-0001/resources.tar.gz +$ tar xzf ~/autoware_data/yabloc_pose_initializer/resources.tar.gz -C ~/autoware_data/yabloc_pose_initializer/ ``` -For detailed information about the downloaded contents, please consult the `download.cmake` file in this package. - ## Note -This package makes use of external code. The trained files are provided by apollo. The trained files are automatically downloaded when you build. +This package makes use of external code. The trained files are provided by apollo. The trained files are automatically downloaded during env preparation. Original model URL diff --git a/localization/yabloc/yabloc_pose_initializer/download.cmake b/localization/yabloc/yabloc_pose_initializer/download.cmake deleted file mode 100644 index 6ce997b3978d5..0000000000000 --- a/localization/yabloc/yabloc_pose_initializer/download.cmake +++ /dev/null @@ -1,67 +0,0 @@ -# Copyright 2023 the Autoware Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set(DOWNLOAD_ARTIFACTS OFF CACHE BOOL "enable artifacts download") - -set(DATA_URL "https://s3.ap-northeast-2.wasabisys.com/pinto-model-zoo/136_road-segmentation-adas-0001/resources.tar.gz") -set(DATA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/data") -set(FILE_HASH 146ed8af689a30b898dc5369870c40fb) -set(FILE_NAME "resources.tar.gz") - -function(download_and_extract) - message(STATUS "Checking and downloading ${FILE_NAME}") - set(FILE_PATH ${DATA_PATH}/${FILE_NAME}) - set(STATUS_CODE 0) - message(STATUS "start ${FILE_NAME}") - - if(EXISTS ${FILE_PATH}) - message(STATUS "found ${FILE_NAME}") - file(MD5 ${FILE_PATH} EXISTING_FILE_HASH) - - if(${FILE_HASH} STREQUAL ${EXISTING_FILE_HASH}) - message(STATUS "same ${FILE_NAME}") - message(STATUS "File already exists.") - else() - message(STATUS "diff ${FILE_NAME}") - message(STATUS "File hash changes. Downloading now ...") - file(DOWNLOAD ${DATA_URL} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - endif() - else() - if(DOWNLOAD_ARTIFACTS) - message(STATUS "not found ${FILE_NAME}") - message(STATUS "File doesn't exists. Downloading now ...") - file(DOWNLOAD ${DATA_URL} ${FILE_PATH} STATUS DOWNLOAD_STATUS TIMEOUT 3600) - list(GET DOWNLOAD_STATUS 0 STATUS_CODE) - list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE) - else() - message(WARNING "Skipped download for ${FILE_NAME} (enable by setting DOWNLOAD_ARTIFACTS)") - file(MAKE_DIRECTORY "${DATA_PATH}") - return() - endif() - endif() - - if(${STATUS_CODE} EQUAL 0) - message(STATUS "Download completed successfully!") - else() - message(FATAL_ERROR "Error occurred during download: ${ERROR_MESSAGE}") - endif() - - execute_process(COMMAND - ${CMAKE_COMMAND} -E - tar xzf "${DATA_PATH}/${FILE_NAME}" WORKING_DIRECTORY "${DATA_PATH}") -endfunction() - -download_and_extract() diff --git a/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml b/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml index 667f436bbabb5..83c2c8fe09287 100644 --- a/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml +++ b/localization/yabloc/yabloc_pose_initializer/launch/yabloc_pose_initializer.launch.xml @@ -1,5 +1,6 @@ - + + diff --git a/localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp b/localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp index b6062727d1de9..760ada6f8310a 100644 --- a/localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp +++ b/localization/yabloc/yabloc_pose_initializer/src/camera/semantic_segmentation.cpp @@ -109,11 +109,9 @@ cv::Mat SemanticSegmentation::draw_overlay(const cv::Mat & image, const cv::Mat void SemanticSegmentation::print_error_message(const rclcpp::Logger & logger) { - // cspell: ignore DDOWNLOAD const std::string ERROR_MESSAGE = R"(The yabloc_pose_initializer is not working correctly because the DNN model has not been downloaded correctly. -To download models, "-DDOWNLOAD_ARTIFACTS=ON" is required at build time. -Please see the README of yabloc_pose_initializer for more information.)"; +Please check the README of yabloc_pose_initializer to know how download models.)"; std::istringstream stream(ERROR_MESSAGE); std::string line; From d9ab02a8c7edce0f7bba452aeb8a97486d37c5d0 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 26 Sep 2023 12:07:49 +0900 Subject: [PATCH 322/547] refactor(controllers): use specific logger name (#5111) Signed-off-by: Takamasa Horibe --- .../mpc_lateral_controller/src/mpc_lateral_controller.cpp | 2 +- .../src/pid_longitudinal_controller.cpp | 2 +- .../src/pure_pursuit/pure_pursuit_lateral_controller.cpp | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp index ce3529b4849ae..3d19104112a71 100644 --- a/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp +++ b/control/mpc_lateral_controller/src/mpc_lateral_controller.cpp @@ -36,7 +36,7 @@ namespace autoware::motion::control::mpc_lateral_controller { MpcLateralController::MpcLateralController(rclcpp::Node & node) -: clock_(node.get_clock()), logger_(node.get_logger()) +: clock_(node.get_clock()), logger_(node.get_logger().get_child("lateral_controller")) { const auto dp_int = [&](const std::string & s) { return node.declare_parameter(s); }; const auto dp_bool = [&](const std::string & s) { return node.declare_parameter(s); }; diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index a943aa05be5ff..644effce50824 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -30,7 +30,7 @@ namespace autoware::motion::control::pid_longitudinal_controller PidLongitudinalController::PidLongitudinalController(rclcpp::Node & node) : node_parameters_(node.get_node_parameters_interface()), clock_(node.get_clock()), - logger_(node.get_logger()), + logger_(node.get_logger().get_child("longitudinal_controller")), diagnostic_updater_(&node) { using std::placeholders::_1; diff --git a/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp b/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp index 91b110b35b401..f0c49b07e675a 100644 --- a/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp +++ b/control/pure_pursuit/src/pure_pursuit/pure_pursuit_lateral_controller.cpp @@ -57,7 +57,10 @@ enum TYPE { namespace pure_pursuit { PurePursuitLateralController::PurePursuitLateralController(rclcpp::Node & node) -: clock_(node.get_clock()), logger_(node.get_logger()), tf_buffer_(clock_), tf_listener_(tf_buffer_) +: clock_(node.get_clock()), + logger_(node.get_logger().get_child("lateral_controller")), + tf_buffer_(clock_), + tf_listener_(tf_buffer_) { pure_pursuit_ = std::make_unique(); From 231459f3bb1bff2b07cac9aa367736a1bf52d712 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 26 Sep 2023 12:15:03 +0900 Subject: [PATCH 323/547] feat(goal_planner): sort goal candidates by weighted distance (#5110) * feat(goal_planner): sort goal candidates priority by weighted distance Signed-off-by: kosuke55 * style(pre-commit): autofix * Update planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp Co-authored-by: Kyoichi Sugahara * update from sugahara-san review Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kyoichi Sugahara --- .../goal_planner/goal_planner.param.yaml | 5 ++- ...havior_path_planner_goal_planner_design.md | 25 ++++++------ .../goal_planner/goal_planner_parameters.hpp | 5 ++- .../utils/goal_planner/goal_searcher_base.hpp | 11 ------ .../goal_planner/goal_planner_module.cpp | 10 ++--- .../src/scene_module/goal_planner/manager.cpp | 5 ++- .../src/utils/goal_planner/goal_searcher.cpp | 38 ++++++++++++++++++- 7 files changed, 66 insertions(+), 33 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index f16799d7ea206..babbdc60d1cf9 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -9,7 +9,10 @@ # goal search goal_search: - search_priority: "efficient_path" # "efficient_path" or "close_goal" + goal_priority: "minimum_weighted_distance" # "minimum_weighted_distance" or "minimum_longitudinal_distance" + minimum_weighted_distance: + lateral_weight: 40.0 + path_priority: "efficient_path" # "efficient_path" or "close_goal" parking_policy: "left_side" # "left_side" or "right_side" forward_goal_search_length: 20.0 backward_goal_search_length: 20.0 diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index 44abdb4267672..ee337555ceb58 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -161,18 +161,19 @@ searched for in certain range of the shoulder lane. ### Parameters for goal search -| Name | Unit | Type | Description | Default value | -| :------------------------------ | :--- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | -| search_priority | [-] | string | In case `efficient_path` use a goal that can generate an efficient path( priority is `shift_parking` -> `arc_forward_parking` -> `arc_backward_parking`). In case `close_goal` use the closest goal to the original one. | efficient_path | -| forward_goal_search_length | [m] | double | length of forward range to be explored from the original goal | 20.0 | -| backward_goal_search_length | [m] | double | length of backward range to be explored from the original goal | 20.0 | -| goal_search_interval | [m] | double | distance interval for goal search | 2.0 | -| longitudinal_margin | [m] | double | margin between ego-vehicle at the goal position and obstacles | 3.0 | -| max_lateral_offset | [m] | double | maximum offset of goal search in the lateral direction | 0.5 | -| lateral_offset_interval | [m] | double | distance interval of goal search in the lateral direction | 0.25 | -| ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 0.0 | -| ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 0.0 | -| margin_from_boundary | [m] | double | distance margin from edge of the shoulder lane | 0.5 | +| Name | Unit | Type | Description | Default value | +| :------------------------------ | :--- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------- | +| goal_priority | [-] | string | In case `minimum_weighted_distance`, sort with smaller longitudinal distances taking precedence over smaller lateral distances. In case `minimum_longitudinal_distance`, sort with weighted lateral distance against longitudinal distance. | `minimum_weighted_distance` | +| path_priority | [-] | string | In case `efficient_path` use a goal that can generate an efficient path( priority is `shift_parking` -> `arc_forward_parking` -> `arc_backward_parking`). In case `close_goal` use the closest goal to the original one. | efficient_path | +| forward_goal_search_length | [m] | double | length of forward range to be explored from the original goal | 20.0 | +| backward_goal_search_length | [m] | double | length of backward range to be explored from the original goal | 20.0 | +| goal_search_interval | [m] | double | distance interval for goal search | 2.0 | +| longitudinal_margin | [m] | double | margin between ego-vehicle at the goal position and obstacles | 3.0 | +| max_lateral_offset | [m] | double | maximum offset of goal search in the lateral direction | 0.5 | +| lateral_offset_interval | [m] | double | distance interval of goal search in the lateral direction | 0.25 | +| ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 0.0 | +| ignore_distance_from_lane_start | [m] | double | distance from start of pull over lanes for ignoring goal candidates | 0.0 | +| margin_from_boundary | [m] | double | distance margin from edge of the shoulder lane | 0.5 | ## **Pull Over** diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index 8ef426d4f2488..22624bd1e0a80 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -48,8 +48,11 @@ struct GoalPlannerParameters double center_line_path_interval{0.0}; // goal search - std::string search_priority; // "efficient_path" or "close_goal" + std::string goal_priority; // "minimum_weighted_distance" or "minimum_longitudinal_distance" + double minimum_weighted_distance_lateral_weight{0.0}; + std::string path_priority; // "efficient_path" or "close_goal" ParkingPolicy parking_policy; // "left_side" or "right_side" + double forward_goal_search_length{0.0}; double backward_goal_search_length{0.0}; double goal_search_interval{0.0}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp index bab13a287960a..cac1d37e342c2 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp @@ -37,17 +37,6 @@ struct GoalCandidate double lateral_offset{0.0}; size_t id{0}; bool is_safe{true}; - - bool operator<(const GoalCandidate & other) const noexcept - { - const double diff = distance_from_original_goal - other.distance_from_original_goal; - constexpr double eps = 0.01; - if (std::abs(diff) < eps) { - return lateral_offset < other.lateral_offset; - } - - return distance_from_original_goal < other.distance_from_original_goal; - } }; using GoalCandidates = std::vector; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index d6e5c8902084f..1f5a823386774 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -183,13 +183,13 @@ void GoalPlannerModule::onTimer() }; // plan candidate paths and set them to the member variable - if (parameters_->search_priority == "efficient_path") { + if (parameters_->path_priority == "efficient_path") { for (const auto & planner : pull_over_planners_) { for (const auto & goal_candidate : goal_candidates) { planCandidatePaths(planner, goal_candidate); } } - } else if (parameters_->search_priority == "close_goal") { + } else if (parameters_->path_priority == "close_goal") { for (const auto & goal_candidate : goal_candidates) { for (const auto & planner : pull_over_planners_) { planCandidatePaths(planner, goal_candidate); @@ -197,9 +197,9 @@ void GoalPlannerModule::onTimer() } } else { RCLCPP_ERROR( - getLogger(), "search_priority should be efficient_path or close_goal, but %s is given.", - parameters_->search_priority.c_str()); - throw std::domain_error("[pull_over] invalid search_priority"); + getLogger(), "path_priority should be efficient_path or close_goal, but %s is given.", + parameters_->path_priority.c_str()); + throw std::domain_error("[pull_over] invalid path_priority"); } // set member variables diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index 88fef3a6c6508..79459d6efb202 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -45,7 +45,10 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( // goal search { const std::string ns = base_ns + "goal_search."; - p.search_priority = node->declare_parameter(ns + "search_priority"); + p.goal_priority = node->declare_parameter(ns + "goal_priority"); + p.minimum_weighted_distance_lateral_weight = + node->declare_parameter(ns + "minimum_weighted_distance.lateral_weight"); + p.path_priority = node->declare_parameter(ns + "path_priority"); p.forward_goal_search_length = node->declare_parameter(ns + "forward_goal_search_length"); p.backward_goal_search_length = diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index ab5476c542e4e..c1d24f02ac140 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -38,6 +38,35 @@ using lanelet::autoware::NoStoppingArea; using tier4_autoware_utils::calcOffsetPose; using tier4_autoware_utils::inverseTransformPose; +// Sort with smaller longitudinal distances taking precedence over smaller lateral distances. +struct SortByLongitudinalDistance +{ + bool operator()(const GoalCandidate & a, const GoalCandidate & b) const noexcept + { + const double diff = a.distance_from_original_goal - b.distance_from_original_goal; + constexpr double eps = 0.01; + // If the longitudinal distances are approximately equal, sort based on lateral offset. + if (std::abs(diff) < eps) { + return a.lateral_offset < b.lateral_offset; + } + return a.distance_from_original_goal < b.distance_from_original_goal; + } +}; + +// Sort with the weighted sum of the longitudinal distance and the lateral distance weighted by +// lateral_cost. +struct SortByWeightedDistance +{ + double lateral_cost{0.0}; + explicit SortByWeightedDistance(double cost) : lateral_cost(cost) {} + + bool operator()(const GoalCandidate & a, const GoalCandidate & b) const noexcept + { + return a.distance_from_original_goal + lateral_cost * a.lateral_offset < + b.distance_from_original_goal + lateral_cost * b.lateral_offset; + } +}; + GoalSearcher::GoalSearcher( const GoalPlannerParameters & parameters, const LinearRing2d & vehicle_footprint, const std::shared_ptr & occupancy_grid_map) @@ -141,8 +170,13 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) } createAreaPolygons(original_search_poses); - // Sort with distance from original goal - std::sort(goal_candidates.begin(), goal_candidates.end()); + if (parameters_.goal_priority == "minimum_weighted_distance") { + std::sort( + goal_candidates.begin(), goal_candidates.end(), + SortByWeightedDistance(parameters_.minimum_weighted_distance_lateral_weight)); + } else if (parameters_.goal_priority == "minimum_longitudinal_distance") { + std::sort(goal_candidates.begin(), goal_candidates.end(), SortByLongitudinalDistance()); + } return goal_candidates; } From 443386dd2ace28cd21f12e350e82cea98df38c1f Mon Sep 17 00:00:00 2001 From: "Takagi, Isamu" <43976882+isamu-takagi@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:08:40 +0900 Subject: [PATCH 324/547] feat: system diagnostic monitor (#4722) Signed-off-by: Takagi, Isamu --- system/system_diagnostic_graph/CMakeLists.txt | 26 ++ system/system_diagnostic_graph/README.md | 45 ++ .../config/default.param.yaml | 5 + .../system_diagnostic_graph/doc/format/and.md | 11 + .../doc/format/diag.md | 11 + .../doc/format/graph-file.md | 10 + .../doc/format/node.md | 9 + .../system_diagnostic_graph/doc/format/or.md | 11 + .../doc/format/path.md | 10 + .../doc/format/unit.md | 10 + .../doc/overview.drawio.svg | 391 ++++++++++++++++++ .../example/example.launch.xml | 6 + .../example/example1.yaml | 41 ++ .../example/example2.yaml | 32 ++ .../example/publisher.py | 83 ++++ .../launch/system_diagnostic_graph.launch.xml | 9 + system/system_diagnostic_graph/package.xml | 24 ++ .../src/core/config.cpp | 122 ++++++ .../src/core/config.hpp | 68 +++ .../src/core/debug.cpp | 75 ++++ .../src/core/debug.hpp | 29 ++ .../system_diagnostic_graph/src/core/expr.cpp | 284 +++++++++++++ .../system_diagnostic_graph/src/core/expr.hpp | 114 +++++ .../src/core/graph.cpp | 118 ++++++ .../src/core/graph.hpp | 47 +++ .../system_diagnostic_graph/src/core/node.cpp | 94 +++++ .../system_diagnostic_graph/src/core/node.hpp | 84 ++++ .../src/core/types.hpp | 45 ++ .../src/core/update.cpp | 116 ++++++ .../src/core/update.hpp | 58 +++ system/system_diagnostic_graph/src/main.cpp | 74 ++++ system/system_diagnostic_graph/src/main.hpp | 43 ++ system/system_diagnostic_graph/src/tool.cpp | 81 ++++ system/system_diagnostic_graph/src/tool.hpp | 39 ++ system/system_diagnostic_graph/util/debug.py | 85 ++++ .../system_diagnostic_monitor/CMakeLists.txt | 7 + system/system_diagnostic_monitor/README.md | 1 + .../config/autoware.yaml | 74 ++++ .../config/control.yaml | 10 + .../config/localization.yaml | 5 + .../system_diagnostic_monitor/config/map.yaml | 7 + .../config/perception.yaml | 7 + .../config/planning.yaml | 15 + .../config/system.yaml | 7 + .../config/vehicle.yaml | 10 + .../system_diagnostic_monitor.launch.xml | 6 + system/system_diagnostic_monitor/package.xml | 23 ++ .../script/component_state_diagnostics.py | 79 ++++ 48 files changed, 2561 insertions(+) create mode 100644 system/system_diagnostic_graph/CMakeLists.txt create mode 100644 system/system_diagnostic_graph/README.md create mode 100644 system/system_diagnostic_graph/config/default.param.yaml create mode 100644 system/system_diagnostic_graph/doc/format/and.md create mode 100644 system/system_diagnostic_graph/doc/format/diag.md create mode 100644 system/system_diagnostic_graph/doc/format/graph-file.md create mode 100644 system/system_diagnostic_graph/doc/format/node.md create mode 100644 system/system_diagnostic_graph/doc/format/or.md create mode 100644 system/system_diagnostic_graph/doc/format/path.md create mode 100644 system/system_diagnostic_graph/doc/format/unit.md create mode 100644 system/system_diagnostic_graph/doc/overview.drawio.svg create mode 100644 system/system_diagnostic_graph/example/example.launch.xml create mode 100644 system/system_diagnostic_graph/example/example1.yaml create mode 100644 system/system_diagnostic_graph/example/example2.yaml create mode 100755 system/system_diagnostic_graph/example/publisher.py create mode 100644 system/system_diagnostic_graph/launch/system_diagnostic_graph.launch.xml create mode 100644 system/system_diagnostic_graph/package.xml create mode 100644 system/system_diagnostic_graph/src/core/config.cpp create mode 100644 system/system_diagnostic_graph/src/core/config.hpp create mode 100644 system/system_diagnostic_graph/src/core/debug.cpp create mode 100644 system/system_diagnostic_graph/src/core/debug.hpp create mode 100644 system/system_diagnostic_graph/src/core/expr.cpp create mode 100644 system/system_diagnostic_graph/src/core/expr.hpp create mode 100644 system/system_diagnostic_graph/src/core/graph.cpp create mode 100644 system/system_diagnostic_graph/src/core/graph.hpp create mode 100644 system/system_diagnostic_graph/src/core/node.cpp create mode 100644 system/system_diagnostic_graph/src/core/node.hpp create mode 100644 system/system_diagnostic_graph/src/core/types.hpp create mode 100644 system/system_diagnostic_graph/src/core/update.cpp create mode 100644 system/system_diagnostic_graph/src/core/update.hpp create mode 100644 system/system_diagnostic_graph/src/main.cpp create mode 100644 system/system_diagnostic_graph/src/main.hpp create mode 100644 system/system_diagnostic_graph/src/tool.cpp create mode 100644 system/system_diagnostic_graph/src/tool.hpp create mode 100755 system/system_diagnostic_graph/util/debug.py create mode 100644 system/system_diagnostic_monitor/CMakeLists.txt create mode 100644 system/system_diagnostic_monitor/README.md create mode 100644 system/system_diagnostic_monitor/config/autoware.yaml create mode 100644 system/system_diagnostic_monitor/config/control.yaml create mode 100644 system/system_diagnostic_monitor/config/localization.yaml create mode 100644 system/system_diagnostic_monitor/config/map.yaml create mode 100644 system/system_diagnostic_monitor/config/perception.yaml create mode 100644 system/system_diagnostic_monitor/config/planning.yaml create mode 100644 system/system_diagnostic_monitor/config/system.yaml create mode 100644 system/system_diagnostic_monitor/config/vehicle.yaml create mode 100644 system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml create mode 100644 system/system_diagnostic_monitor/package.xml create mode 100755 system/system_diagnostic_monitor/script/component_state_diagnostics.py diff --git a/system/system_diagnostic_graph/CMakeLists.txt b/system/system_diagnostic_graph/CMakeLists.txt new file mode 100644 index 0000000000000..54aebe6f37f73 --- /dev/null +++ b/system/system_diagnostic_graph/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.14) +project(system_diagnostic_graph) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +ament_auto_add_executable(aggregator + src/main.cpp + src/core/config.cpp + src/core/debug.cpp + src/core/graph.cpp + src/core/node.cpp + src/core/expr.cpp + src/core/update.cpp +) + +ament_auto_add_executable(converter + src/tool.cpp +) + +install( + PROGRAMS util/debug.py + DESTINATION lib/${PROJECT_NAME} +) + +ament_auto_package(INSTALL_TO_SHARE config example launch) diff --git a/system/system_diagnostic_graph/README.md b/system/system_diagnostic_graph/README.md new file mode 100644 index 0000000000000..c2c99938348e2 --- /dev/null +++ b/system/system_diagnostic_graph/README.md @@ -0,0 +1,45 @@ +# System diagnostic graph + +## Overview + +The system diagnostic graph node subscribes to diagnostic status and publishes aggregated diagnostic status. +As shown in the diagram below, this node introduces extra diagnostic status for intermediate functional unit. +Diagnostic status dependencies will be directed acyclic graph (DAG). + +![overview](./doc/overview.drawio.svg) + +## Interface + +| Interface Type | Interface Name | Data Type | Description | +| -------------- | --------------------------- | --------------------------------------- | ------------------ | +| subscription | `/diagnostics` | `diagnostic_msgs/msg/DiagnosticArray` | Input diagnostics. | +| publisher | `/diagnostics_graph/status` | `diagnostic_msgs/msg/DiagnosticArray` | Graph status. | +| publisher | `/diagnostics_graph/struct` | `tier4_system_msgs/msg/DiagnosticGraph` | Graph structure. | + +## Parameters + +| Parameter Name | Data Type | Description | +| ------------------ | --------- | ------------------------------------------ | +| `graph_file` | `string` | Path of the config file. | +| `rate` | `double` | Rate of aggregation and topic publication. | +| `status_qos_depth` | `uint` | QoS depth of status topic. | +| `source_qos_depth` | `uint` | QoS depth of source topic. | + +## Graph file format + +- [GraphFile](./doc/format/graph-file.md) +- [Path](./doc/format/path.md) +- [Node](./doc/format/node.md) + - [Diag](./doc/format/diag.md) + - [Unit](./doc/format/unit.md) + - [And](./doc/format/and.md) + - [Or](./doc/format/or.md) + +## Example + +- [example1.yaml](./example/example1.yaml) +- [example2.yaml](./example/example2.yaml) + +```bash +ros2 launch system_diagnostic_graph example.launch.xml +``` diff --git a/system/system_diagnostic_graph/config/default.param.yaml b/system/system_diagnostic_graph/config/default.param.yaml new file mode 100644 index 0000000000000..f02247374b14e --- /dev/null +++ b/system/system_diagnostic_graph/config/default.param.yaml @@ -0,0 +1,5 @@ +/**: + ros__parameters: + rate: 1.0 + input_qos_depth: 1000 + graph_qos_depth: 1 diff --git a/system/system_diagnostic_graph/doc/format/and.md b/system/system_diagnostic_graph/doc/format/and.md new file mode 100644 index 0000000000000..a92aa2817832e --- /dev/null +++ b/system/system_diagnostic_graph/doc/format/and.md @@ -0,0 +1,11 @@ +# Unit + +And is a node that is evaluated as the AND of the input nodes. + +## Format + +| Name | Type | Required | Description | +| ---- | ------------------------------------------ | -------- | ------------------------------------- | +| type | string | yes | Specify `and` when using this object. | +| name | string | yes | Name of diagnostic status. | +| list | List<[Diag](./diag.md)\|[Unit](./unit.md)> | yes | List of input node references. | diff --git a/system/system_diagnostic_graph/doc/format/diag.md b/system/system_diagnostic_graph/doc/format/diag.md new file mode 100644 index 0000000000000..9835924f08996 --- /dev/null +++ b/system/system_diagnostic_graph/doc/format/diag.md @@ -0,0 +1,11 @@ +# Diag + +Diag is a node that refers to a source diagnostics. + +## Format + +| Name | Type | Required | Description | +| -------- | ------ | -------- | -------------------------------------- | +| type | string | yes | Specify `diag` when using this object. | +| name | string | yes | Name of diagnostic status. | +| hardware | string | yes | Hardware ID of diagnostic status. | diff --git a/system/system_diagnostic_graph/doc/format/graph-file.md b/system/system_diagnostic_graph/doc/format/graph-file.md new file mode 100644 index 0000000000000..3c4cfec996a4a --- /dev/null +++ b/system/system_diagnostic_graph/doc/format/graph-file.md @@ -0,0 +1,10 @@ +# GraphFile + +GraphFile is the top level object that makes up the configuration file. + +## Format + +| Name | Type | Required | Description | +| ----- | ----------------------- | -------- | ------------------------------ | +| files | List<[Path](./path.md)> | no | Paths of the files to include. | +| nodes | List<[Node](./node.md)> | no | Nodes of the diagnostic graph. | diff --git a/system/system_diagnostic_graph/doc/format/node.md b/system/system_diagnostic_graph/doc/format/node.md new file mode 100644 index 0000000000000..da8e8e57b111f --- /dev/null +++ b/system/system_diagnostic_graph/doc/format/node.md @@ -0,0 +1,9 @@ +# Node + +Node is a base object that makes up the diagnostic graph. + +## Format + +| Name | Type | Required | Description | +| ---- | ------ | -------- | ------------------------------------------- | +| type | string | yes | Node type. See derived objects for details. | diff --git a/system/system_diagnostic_graph/doc/format/or.md b/system/system_diagnostic_graph/doc/format/or.md new file mode 100644 index 0000000000000..3e668b686c9e8 --- /dev/null +++ b/system/system_diagnostic_graph/doc/format/or.md @@ -0,0 +1,11 @@ +# Unit + +Or is a node that is evaluated as the OR of the input nodes. + +## Format + +| Name | Type | Required | Description | +| ---- | ------------------------------------------ | -------- | ------------------------------------ | +| type | string | yes | Specify `or` when using this object. | +| name | string | yes | Name of diagnostic status. | +| list | List<[Diag](./diag.md)\|[Unit](./unit.md)> | yes | List of input node references. | diff --git a/system/system_diagnostic_graph/doc/format/path.md b/system/system_diagnostic_graph/doc/format/path.md new file mode 100644 index 0000000000000..1f27accefa35a --- /dev/null +++ b/system/system_diagnostic_graph/doc/format/path.md @@ -0,0 +1,10 @@ +# Path + +Path is an object that indicates the path of the file to include. + +## Format + +| Name | Type | Required | Description | +| ------- | ------ | -------- | ----------------------------- | +| package | string | yes | Package name. | +| path | string | yes | Relative path in the package. | diff --git a/system/system_diagnostic_graph/doc/format/unit.md b/system/system_diagnostic_graph/doc/format/unit.md new file mode 100644 index 0000000000000..791689aa2d98a --- /dev/null +++ b/system/system_diagnostic_graph/doc/format/unit.md @@ -0,0 +1,10 @@ +# Unit + +Diag is a node that refers to a functional unit. + +## Format + +| Name | Type | Required | Description | +| ---- | ------ | -------- | -------------------------------------- | +| type | string | yes | Specify `unit` when using this object. | +| name | string | yes | Name of diagnostic status. | diff --git a/system/system_diagnostic_graph/doc/overview.drawio.svg b/system/system_diagnostic_graph/doc/overview.drawio.svg new file mode 100644 index 0000000000000..aa6be5a48b08e --- /dev/null +++ b/system/system_diagnostic_graph/doc/overview.drawio.svg @@ -0,0 +1,391 @@ + + + + + + + +
    +
    +
    + /diagnostics_graph_status +
    +
    +
    +
    + /diagnostics_graph_status +
    +
    + + + + +
    +
    +
    + /diagnostics +
    +
    +
    +
    + /diagnostics +
    +
    + + + + + + +
    +
    +
    + Top LiDAR +
    +
    +
    +
    + Top LiDAR +
    +
    + + + + + + + + +
    +
    +
    + Front LiDAR +
    +
    +
    +
    + Front LiDAR +
    +
    + + + + + + + + +
    +
    +
    + Front obstacle detection +
    +
    +
    +
    + Front obstacle detec... +
    +
    + + + + + + +
    +
    +
    + Pose estimation +
    +
    +
    +
    + Pose estimation +
    +
    + + + + + + +
    +
    +
    + Autonomous mode +
    +
    +
    +
    + Autonomous mode +
    +
    + + + + + + +
    +
    +
    + Comfortable stop +
    +
    +
    +
    + Comfortable stop +
    +
    + + + + + + +
    +
    +
    + Emergncy stop +
    +
    +
    +
    + Emergncy stop +
    +
    + + + + + + +
    +
    +
    + Route +
    +
    +
    +
    + Route +
    +
    + + + + + + +
    +
    +
    + Joystick mode +
    +
    +
    +
    + Joystick mode +
    +
    + + + + + + +
    +
    +
    + Joystick +
    +
    +
    +
    + Joystick +
    +
    + + + + +
    +
    +
    + AND +
    +
    +
    +
    + AND +
    +
    + + + + + + +
    +
    +
    + Filter by use case and system state +
    +
    +
    +
    + Filter by use case and system state +
    +
    + + + + + + +
    +
    +
    + Stop mode +
    +
    +
    +
    + Stop mode +
    +
    + + + + +
    +
    +
    + Error report +
    +
    +
    +
    + Error report +
    +
    + + + + + + +
    +
    +
    + Front radar +
    +
    +
    +
    + Front radar +
    +
    + + + + +
    +
    +
    + OR +
    +
    +
    +
    + OR +
    +
    + + + + + + +
    +
    +
    + Front obstacle prediction +
    +
    +
    +
    + Front obstacle predi... +
    +
    +
    + + + + Text is not SVG - cannot display + + +
    diff --git a/system/system_diagnostic_graph/example/example.launch.xml b/system/system_diagnostic_graph/example/example.launch.xml new file mode 100644 index 0000000000000..461842f020ded --- /dev/null +++ b/system/system_diagnostic_graph/example/example.launch.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/system/system_diagnostic_graph/example/example1.yaml b/system/system_diagnostic_graph/example/example1.yaml new file mode 100644 index 0000000000000..25053b5f7e12c --- /dev/null +++ b/system/system_diagnostic_graph/example/example1.yaml @@ -0,0 +1,41 @@ +files: + - { package: system_diagnostic_graph, path: example/example2.yaml } + +nodes: + - name: /autoware/diagnostics + type: and + list: + - { type: unit, name: /autoware/operation/stop } + - { type: unit, name: /autoware/operation/autonomous } + - { type: unit, name: /autoware/operation/local } + - { type: unit, name: /autoware/operation/remote } + - { type: unit, name: /autoware/operation/emergency-stop } + - { type: unit, name: /autoware/operation/comfortable-stop } + + - name: /autoware/operation/stop + type: debug-ok + + - name: /autoware/operation/autonomous + type: and + list: + - { type: unit, name: /autoware/localization } + - { type: unit, name: /autoware/planning } + - { type: unit, name: /autoware/perception } + + - name: /autoware/operation/local + type: debug-ok + + - name: /autoware/operation/remote + type: and + list: + - { type: diag, hardware: test, name: /external/remote_command } + + - name: /autoware/operation/emergency-stop + type: debug-ok + + - name: /autoware/operation/comfortable-stop + type: and + list: + - { type: unit, name: /autoware/localization } + - { type: unit, name: /autoware/planning } + - { type: unit, name: /autoware/perception/front-obstacle-detection } diff --git a/system/system_diagnostic_graph/example/example2.yaml b/system/system_diagnostic_graph/example/example2.yaml new file mode 100644 index 0000000000000..2af09c0cb69a2 --- /dev/null +++ b/system/system_diagnostic_graph/example/example2.yaml @@ -0,0 +1,32 @@ +nodes: + - name: /autoware/localization + type: and + list: + - { type: diag, hardware: test, name: /sensing/lidars/top } + + - name: /autoware/planning + type: and + list: + - { type: unit, name: /autoware/planning/route } + + - name: /autoware/planning/route + type: and + list: + - { type: diag, hardware: test, name: /planning/route } + + - name: /autoware/perception + type: and + list: + - { type: unit, name: /autoware/perception/front-obstacle-detection } + - { type: unit, name: /autoware/perception/front-obstacle-prediction } + + - name: /autoware/perception/front-obstacle-detection + type: or + list: + - { type: diag, hardware: test, name: /sensing/lidars/front } + - { type: diag, hardware: test, name: /sensing/radars/front } + + - name: /autoware/perception/front-obstacle-prediction + type: and + list: + - { type: diag, hardware: test, name: /sensing/lidars/front } diff --git a/system/system_diagnostic_graph/example/publisher.py b/system/system_diagnostic_graph/example/publisher.py new file mode 100755 index 0000000000000..2e73237284508 --- /dev/null +++ b/system/system_diagnostic_graph/example/publisher.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +# Copyright 2023 The Autoware Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse + +from diagnostic_msgs.msg import DiagnosticArray +from diagnostic_msgs.msg import DiagnosticStatus +import rclpy +import rclpy.node +import rclpy.qos + + +class DummyDiagnostics(rclpy.node.Node): + def __init__(self, array): + super().__init__("dummy_diagnostics") + qos = rclpy.qos.qos_profile_system_default + self.diags = self.create_publisher(DiagnosticArray, "/diagnostics", qos) + self.timer = self.create_timer(0.5, self.on_timer) + self.array = [self.create_status(*data) for data in array] + + def on_timer(self): + diagnostics = DiagnosticArray() + diagnostics.header.stamp = self.get_clock().now().to_msg() + diagnostics.status = self.array + self.diags.publish(diagnostics) + + @staticmethod + def create_status(name: str, level: int): + return DiagnosticStatus(level=level, name=name, message="OK", hardware_id="test") + + +if __name__ == "__main__": + data = { + "ok": [ + ("/sensing/lidars/top", DiagnosticStatus.OK), + ("/sensing/lidars/front", DiagnosticStatus.OK), + ("/sensing/radars/front", DiagnosticStatus.OK), + ("/planning/route", DiagnosticStatus.OK), + ("/external/remote_command", DiagnosticStatus.OK), + ], + "front-lidar": [ + ("/sensing/lidars/top", DiagnosticStatus.OK), + ("/sensing/lidars/front", DiagnosticStatus.ERROR), + ("/sensing/radars/front", DiagnosticStatus.OK), + ("/planning/route", DiagnosticStatus.OK), + ("/external/remote_command", DiagnosticStatus.OK), + ], + "front-radar": [ + ("/sensing/lidars/top", DiagnosticStatus.OK), + ("/sensing/lidars/front", DiagnosticStatus.OK), + ("/sensing/radars/front", DiagnosticStatus.ERROR), + ("/planning/route", DiagnosticStatus.OK), + ("/external/remote_command", DiagnosticStatus.OK), + ], + "front": [ + ("/sensing/lidars/top", DiagnosticStatus.OK), + ("/sensing/lidars/front", DiagnosticStatus.ERROR), + ("/sensing/radars/front", DiagnosticStatus.ERROR), + ("/planning/route", DiagnosticStatus.OK), + ("/external/remote_command", DiagnosticStatus.OK), + ], + } + + parser = argparse.ArgumentParser() + parser.add_argument("--data", default="ok") + args = parser.parse_args() + + rclpy.init() + rclpy.spin(DummyDiagnostics(data[args.data])) + rclpy.shutdown() diff --git a/system/system_diagnostic_graph/launch/system_diagnostic_graph.launch.xml b/system/system_diagnostic_graph/launch/system_diagnostic_graph.launch.xml new file mode 100644 index 0000000000000..5b9a84947d179 --- /dev/null +++ b/system/system_diagnostic_graph/launch/system_diagnostic_graph.launch.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/system/system_diagnostic_graph/package.xml b/system/system_diagnostic_graph/package.xml new file mode 100644 index 0000000000000..ffc0c6c7d5385 --- /dev/null +++ b/system/system_diagnostic_graph/package.xml @@ -0,0 +1,24 @@ + + + + system_diagnostic_graph + 0.1.0 + The system_diagnostic_graph package + Takagi, Isamu + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + + diagnostic_msgs + rclcpp + tier4_system_msgs + yaml_cpp_vendor + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/system/system_diagnostic_graph/src/core/config.cpp b/system/system_diagnostic_graph/src/core/config.cpp new file mode 100644 index 0000000000000..b33a7deb66fb6 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/config.cpp @@ -0,0 +1,122 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "config.hpp" + +#include + +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +ConfigError create_error(const FileConfig & config, const std::string & message) +{ + const std::string marker = config ? "File:" + config->path : "Parameter"; + return ConfigError(message + " (" + marker + ")"); +} + +ConfigError create_error(const NodeConfig & config, const std::string & message) +{ + const std::string marker = "File:" + config->path + ", Node:" + config->name; + return ConfigError(message + " (" + marker + ")"); +} + +NodeConfig parse_config_node(YAML::Node yaml, const FileConfig & scope) +{ + if (!yaml.IsMap()) { + throw create_error(scope, "node object is not a dict"); + } + if (!yaml["name"]) { + throw create_error(scope, "node object has no 'name' field"); + } + + const auto config = std::make_shared(); + config->path = scope->path; + config->name = take(yaml, "name"); + config->yaml = yaml; + return config; +} + +FileConfig parse_config_path(YAML::Node yaml, const FileConfig & scope) +{ + if (!yaml.IsMap()) { + throw create_error(scope, "file object is not a dict"); + } + if (!yaml["package"]) { + throw create_error(scope, "file object has no 'package' field"); + } + if (!yaml["path"]) { + throw create_error(scope, "file object has no 'path' field"); + } + + const auto package_name = yaml["package"].as(); + const auto package_path = ament_index_cpp::get_package_share_directory(package_name); + return parse_config_path(package_path + "/" + yaml["path"].as(), scope); +} + +FileConfig parse_config_path(const std::string & path, const FileConfig & scope) +{ + if (!std::filesystem::exists(path)) { + throw create_error(scope, "config file '" + path + "' does not exist"); + } + return parse_config_file(path); +} + +FileConfig parse_config_file(const std::string & path) +{ + const auto config = std::make_shared(); + config->path = path; + + const auto yaml = YAML::LoadFile(path); + if (!yaml.IsMap()) { + throw create_error(config, "config file is not a dict"); + } + + const auto files = yaml["files"] ? yaml["files"] : YAML::Node(YAML::NodeType::Sequence); + if (!files.IsSequence()) { + throw create_error(config, "files section is not a list"); + } + for (const auto file : files) { + config->files.push_back(parse_config_path(file, config)); + } + + const auto nodes = yaml["nodes"] ? yaml["nodes"] : YAML::Node(YAML::NodeType::Sequence); + if (!nodes.IsSequence()) { + throw create_error(config, "nodes section is not a list"); + } + for (const auto node : nodes) { + config->nodes.push_back(parse_config_node(node, config)); + } + + return config; +} + +void walk_config_tree(const FileConfig & config, std::vector & nodes) +{ + nodes.insert(nodes.end(), config->nodes.begin(), config->nodes.end()); + for (const auto & file : config->files) walk_config_tree(file, nodes); +} + +std::vector load_config_file(const std::string & path) +{ + std::vector nodes; + walk_config_tree(parse_config_path(path, nullptr), nodes); + return nodes; +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/config.hpp b/system/system_diagnostic_graph/src/core/config.hpp new file mode 100644 index 0000000000000..6393cb906b119 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/config.hpp @@ -0,0 +1,68 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__CONFIG_HPP_ +#define CORE__CONFIG_HPP_ + +#include + +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +struct ConfigError : public std::runtime_error +{ + using runtime_error::runtime_error; +}; + +struct NodeConfig_ +{ + std::string path; + std::string name; + YAML::Node yaml; +}; + +struct FileConfig_ +{ + std::string path; + std::vector> files; + std::vector> nodes; +}; + +template +T take(YAML::Node yaml, const std::string & field) +{ + const auto result = yaml[field].as(); + yaml.remove(field); + return result; +} + +using NodeConfig = std::shared_ptr; +using FileConfig = std::shared_ptr; +ConfigError create_error(const FileConfig & config, const std::string & message); +ConfigError create_error(const NodeConfig & config, const std::string & message); +std::vector load_config_file(const std::string & path); + +NodeConfig parse_config_node(YAML::Node yaml, const FileConfig & scope); +FileConfig parse_config_path(YAML::Node yaml, const FileConfig & scope); +FileConfig parse_config_path(const std::string & path, const FileConfig & scope); +FileConfig parse_config_file(const std::string & path); + +} // namespace system_diagnostic_graph + +#endif // CORE__CONFIG_HPP_ diff --git a/system/system_diagnostic_graph/src/core/debug.cpp b/system/system_diagnostic_graph/src/core/debug.cpp new file mode 100644 index 0000000000000..337be8c74aded --- /dev/null +++ b/system/system_diagnostic_graph/src/core/debug.cpp @@ -0,0 +1,75 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "debug.hpp" + +#include "node.hpp" +#include "types.hpp" +#include "update.hpp" + +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +const std::unordered_map level_names = { + {DiagnosticStatus::OK, "OK"}, + {DiagnosticStatus::WARN, "WARN"}, + {DiagnosticStatus::ERROR, "ERROR"}, + {DiagnosticStatus::STALE, "STALE"}}; + +void DiagGraph::debug() +{ + std::vector lines; + for (const auto & node : graph_.nodes()) { + lines.push_back(node->debug()); + } + + std::array widths = {}; + for (const auto & line : lines) { + for (size_t i = 0; i < diag_debug_size; ++i) { + widths[i] = std::max(widths[i], line[i].length()); + } + } + + const size_t total_width = std::accumulate(widths.begin(), widths.end(), 0); + std::cout << std::string(total_width + 3 * diag_debug_size + 1, '=') << std::endl; + + for (const auto & line : lines) { + for (size_t i = 0; i < diag_debug_size; ++i) { + std::cout << "| " << std::left << std::setw(widths[i]) << line[i] << " "; + } + std::cout << "|" << std::endl; + } +} + +DiagDebugData UnitNode::debug() const +{ + const auto & level = node_.status.level; + const auto & name = node_.status.name; + return DiagDebugData{std::to_string(index()), "unit", name, "-----", level_names.at(level)}; +} + +DiagDebugData DiagNode::debug() const +{ + const auto & level = node_.status.level; + const auto & name = node_.status.name; + const auto & hardware = node_.status.hardware_id; + return DiagDebugData{std::to_string(index()), "diag", name, hardware, level_names.at(level)}; +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/debug.hpp b/system/system_diagnostic_graph/src/core/debug.hpp new file mode 100644 index 0000000000000..bb1bbc6f14230 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/debug.hpp @@ -0,0 +1,29 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__DEBUG_HPP_ +#define CORE__DEBUG_HPP_ + +#include +#include + +namespace system_diagnostic_graph +{ + +constexpr size_t diag_debug_size = 5; +using DiagDebugData = std::array; + +} // namespace system_diagnostic_graph + +#endif // CORE__DEBUG_HPP_ diff --git a/system/system_diagnostic_graph/src/core/expr.cpp b/system/system_diagnostic_graph/src/core/expr.cpp new file mode 100644 index 0000000000000..dc7ebcf8dd859 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/expr.cpp @@ -0,0 +1,284 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "expr.hpp" + +#include "config.hpp" +#include "graph.hpp" +#include "node.hpp" + +#include +#include +#include +#include + +// +#include + +namespace system_diagnostic_graph +{ + +using LinkStatus = std::vector>; + +void extend(LinkStatus & a, const LinkStatus & b) +{ + a.insert(a.end(), b.begin(), b.end()); +} + +void extend_false(LinkStatus & a, const LinkStatus & b) +{ + for (const auto & p : b) { + a.push_back(std::make_pair(p.first, false)); + } +} + +std::unique_ptr BaseExpr::create(Graph & graph, YAML::Node yaml) +{ + if (!yaml.IsMap()) { + throw ConfigError("expr object is not a dict"); + } + if (!yaml["type"]) { + throw ConfigError("expr object has no 'type' field"); + } + + const auto type = take(yaml, "type"); + + if (type == "unit") { + return std::make_unique(graph, yaml); + } + if (type == "diag") { + return std::make_unique(graph, yaml); + } + if (type == "and") { + return std::make_unique(graph, yaml); + } + if (type == "or") { + return std::make_unique(graph, yaml); + } + if (type == "if") { + return std::make_unique(graph, yaml); + } + if (type == "debug-ok") { + return std::make_unique(DiagnosticStatus::OK); + } + if (type == "debug-warn") { + return std::make_unique(DiagnosticStatus::WARN); + } + if (type == "debug-error") { + return std::make_unique(DiagnosticStatus::ERROR); + } + if (type == "debug-stale") { + return std::make_unique(DiagnosticStatus::STALE); + } + throw ConfigError("unknown expr type: " + type); +} + +ConstExpr::ConstExpr(const DiagnosticLevel level) +{ + level_ = level; +} + +ExprStatus ConstExpr::eval() const +{ + ExprStatus status; + status.level = level_; + return status; +} + +std::vector ConstExpr::get_dependency() const +{ + return {}; +} + +UnitExpr::UnitExpr(Graph & graph, YAML::Node yaml) +{ + if (!yaml["name"]) { + throw ConfigError("unit object has no 'name' field"); + } + const auto name = take(yaml, "name"); + node_ = graph.find_unit(name); + if (!node_) { + throw ConfigError("unit node '" + name + "' does not exist"); + } +} + +ExprStatus UnitExpr::eval() const +{ + ExprStatus status; + status.level = node_->level(); + status.links.push_back(std::make_pair(node_, true)); + return status; +} + +std::vector UnitExpr::get_dependency() const +{ + return {node_}; +} + +DiagExpr::DiagExpr(Graph & graph, YAML::Node yaml) +{ + if (!yaml["name"]) { + throw ConfigError("diag object has no 'name' field"); + } + const auto name = yaml["name"].as(); + const auto hardware = yaml["hardware"].as(""); + node_ = graph.find_diag(name, hardware); + if (!node_) { + node_ = graph.make_diag(name, hardware); + } +} + +ExprStatus DiagExpr::eval() const +{ + ExprStatus status; + status.level = node_->level(); + status.links.push_back(std::make_pair(node_, true)); + return status; +} + +std::vector DiagExpr::get_dependency() const +{ + return {node_}; +} + +AndExpr::AndExpr(Graph & graph, YAML::Node yaml) +{ + if (!yaml["list"]) { + throw ConfigError("expr object has no 'list' field"); + } + if (!yaml["list"].IsSequence()) { + throw ConfigError("list field is not a list"); + } + + for (const auto & node : yaml["list"]) { + list_.push_back(BaseExpr::create(graph, node)); + } +} + +ExprStatus AndExpr::eval() const +{ + std::vector results; + for (const auto & expr : list_) { + results.push_back(expr->eval()); + } + std::vector levels; + for (const auto & result : results) { + levels.push_back(result.level); + } + ExprStatus status; + for (const auto & result : results) { + extend(status.links, result.links); + } + const auto level = *std::max_element(levels.begin(), levels.end()); + status.level = std::min(level, DiagnosticStatus::ERROR); + return status; +} + +std::vector AndExpr::get_dependency() const +{ + std::vector depends; + for (const auto & expr : list_) { + const auto nodes = expr->get_dependency(); + depends.insert(depends.end(), nodes.begin(), nodes.end()); + } + return depends; +} + +OrExpr::OrExpr(Graph & graph, YAML::Node yaml) +{ + if (!yaml["list"]) { + throw ConfigError("expr object has no 'list' field"); + } + if (!yaml["list"].IsSequence()) { + throw ConfigError("list field is not a list"); + } + + for (const auto & node : yaml["list"]) { + list_.push_back(BaseExpr::create(graph, node)); + } +} + +ExprStatus OrExpr::eval() const +{ + std::vector results; + for (const auto & expr : list_) { + results.push_back(expr->eval()); + } + std::vector levels; + for (const auto & result : results) { + levels.push_back(result.level); + } + ExprStatus status; + for (const auto & result : results) { + extend(status.links, result.links); + } + const auto level = *std::min_element(levels.begin(), levels.end()); + status.level = std::min(level, DiagnosticStatus::ERROR); + return status; +} + +std::vector OrExpr::get_dependency() const +{ + std::vector depends; + for (const auto & expr : list_) { + const auto nodes = expr->get_dependency(); + depends.insert(depends.end(), nodes.begin(), nodes.end()); + } + return depends; +} + +IfExpr::IfExpr(Graph & graph, YAML::Node yaml) +{ + if (!yaml["cond"]) { + throw ConfigError("expr object has no 'cond' field"); + } + if (!yaml["then"]) { + throw ConfigError("expr object has no 'then' field"); + } + cond_ = BaseExpr::create(graph, yaml["cond"]); + then_ = BaseExpr::create(graph, yaml["then"]); +} + +ExprStatus IfExpr::eval() const +{ + const auto cond = cond_->eval(); + const auto then = then_->eval(); + ExprStatus status; + if (cond.level == DiagnosticStatus::OK) { + status.level = std::min(then.level, DiagnosticStatus::ERROR); + extend(status.links, cond.links); + extend(status.links, then.links); + } else { + status.level = std::min(cond.level, DiagnosticStatus::ERROR); + extend(status.links, cond.links); + extend_false(status.links, then.links); + } + return status; +} + +std::vector IfExpr::get_dependency() const +{ + std::vector depends; + { + const auto nodes = cond_->get_dependency(); + depends.insert(depends.end(), nodes.begin(), nodes.end()); + } + { + const auto nodes = then_->get_dependency(); + depends.insert(depends.end(), nodes.begin(), nodes.end()); + } + return depends; +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/expr.hpp b/system/system_diagnostic_graph/src/core/expr.hpp new file mode 100644 index 0000000000000..541841582180a --- /dev/null +++ b/system/system_diagnostic_graph/src/core/expr.hpp @@ -0,0 +1,114 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__EXPR_HPP_ +#define CORE__EXPR_HPP_ + +#include "types.hpp" + +#include + +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +struct ExprStatus +{ + DiagnosticLevel level; + std::vector> links; +}; + +class BaseExpr +{ +public: + static std::unique_ptr create(Graph & graph, YAML::Node yaml); + virtual ~BaseExpr() = default; + virtual ExprStatus eval() const = 0; + virtual std::vector get_dependency() const = 0; +}; + +class ConstExpr : public BaseExpr +{ +public: + explicit ConstExpr(const DiagnosticLevel level); + ExprStatus eval() const override; + std::vector get_dependency() const override; + +private: + DiagnosticLevel level_; +}; + +class UnitExpr : public BaseExpr +{ +public: + UnitExpr(Graph & graph, YAML::Node yaml); + ExprStatus eval() const override; + std::vector get_dependency() const override; + +private: + UnitNode * node_; +}; + +class DiagExpr : public BaseExpr +{ +public: + DiagExpr(Graph & graph, YAML::Node yaml); + ExprStatus eval() const override; + std::vector get_dependency() const override; + +private: + DiagNode * node_; +}; + +class AndExpr : public BaseExpr +{ +public: + AndExpr(Graph & graph, YAML::Node yaml); + ExprStatus eval() const override; + std::vector get_dependency() const override; + +private: + std::vector> list_; +}; + +class OrExpr : public BaseExpr +{ +public: + OrExpr(Graph & graph, YAML::Node yaml); + ExprStatus eval() const override; + std::vector get_dependency() const override; + +private: + std::vector> list_; +}; + +class IfExpr : public BaseExpr +{ +public: + IfExpr(Graph & graph, YAML::Node yaml); + ExprStatus eval() const override; + std::vector get_dependency() const override; + +private: + std::unique_ptr cond_; + std::unique_ptr then_; +}; + +} // namespace system_diagnostic_graph + +#endif // CORE__EXPR_HPP_ diff --git a/system/system_diagnostic_graph/src/core/graph.cpp b/system/system_diagnostic_graph/src/core/graph.cpp new file mode 100644 index 0000000000000..ba0e3cfd2e016 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/graph.cpp @@ -0,0 +1,118 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "graph.hpp" + +#include "node.hpp" + +#include +#include +#include +#include + +// +#include + +namespace system_diagnostic_graph +{ + +UnitNode * Graph::make_unit(const std::string & name) +{ + const auto key = name; + auto unit = std::make_unique(key); + units_[key] = unit.get(); + nodes_.emplace_back(std::move(unit)); + return units_[key]; +} + +UnitNode * Graph::find_unit(const std::string & name) +{ + const auto key = name; + return units_.count(key) ? units_.at(key) : nullptr; +} + +DiagNode * Graph::make_diag(const std::string & name, const std::string & hardware) +{ + const auto key = std::make_pair(name, hardware); + auto diag = std::make_unique(name, hardware); + diags_[key] = diag.get(); + nodes_.emplace_back(std::move(diag)); + return diags_[key]; +} + +DiagNode * Graph::find_diag(const std::string & name, const std::string & hardware) +{ + const auto key = std::make_pair(name, hardware); + return diags_.count(key) ? diags_.at(key) : nullptr; +} + +void Graph::topological_sort() +{ + std::map degrees; + std::deque nodes; + std::deque result; + std::deque buffer; + + // Create a list of raw pointer nodes. + for (const auto & node : nodes_) { + nodes.push_back(node.get()); + } + + // Count degrees of each node. + for (const auto & node : nodes) { + for (const auto & link : node->links()) { + ++degrees[link]; + } + } + + // Find initial nodes that are zero degrees. + for (const auto & node : nodes) { + if (degrees[node] == 0) { + buffer.push_back(node); + } + } + + // Sort by topological order. + while (!buffer.empty()) { + const auto node = buffer.front(); + buffer.pop_front(); + for (const auto & link : node->links()) { + if (--degrees[link] == 0) { + buffer.push_back(link); + } + } + result.push_back(node); + } + + // Detect circulation because the result does not include the nodes on the loop. + if (result.size() != nodes.size()) { + throw ConfigError("detect graph circulation"); + } + + // Reverse the result to process from leaf node. + result = std::deque(result.rbegin(), result.rend()); + + // Replace node vector. + std::map indices; + for (size_t i = 0; i < result.size(); ++i) { + indices[result[i]] = i; + } + std::vector> temp(nodes_.size()); + for (auto & node : nodes_) { + temp[indices[node.get()]] = std::move(node); + } + nodes_ = std::move(temp); +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/graph.hpp b/system/system_diagnostic_graph/src/core/graph.hpp new file mode 100644 index 0000000000000..a3f46760388f7 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/graph.hpp @@ -0,0 +1,47 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__GRAPH_HPP_ +#define CORE__GRAPH_HPP_ + +#include "types.hpp" + +#include +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +class Graph +{ +public: + UnitNode * make_unit(const std::string & name); + UnitNode * find_unit(const std::string & name); + DiagNode * make_diag(const std::string & name, const std::string & hardware); + DiagNode * find_diag(const std::string & name, const std::string & hardware); + void topological_sort(); + const std::vector> & nodes() { return nodes_; } + +private: + std::vector> nodes_; + std::map units_; + std::map, DiagNode *> diags_; +}; + +} // namespace system_diagnostic_graph + +#endif // CORE__GRAPH_HPP_ diff --git a/system/system_diagnostic_graph/src/core/node.cpp b/system/system_diagnostic_graph/src/core/node.cpp new file mode 100644 index 0000000000000..1439188d93e18 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/node.cpp @@ -0,0 +1,94 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "node.hpp" + +#include "expr.hpp" + +#include + +#include +#include + +namespace system_diagnostic_graph +{ + +UnitNode::UnitNode(const std::string & name) +{ + node_.status.level = DiagnosticStatus::STALE; + node_.status.name = name; + node_.status.hardware_id = ""; +} + +UnitNode::~UnitNode() +{ + // for unique_ptr +} + +DiagnosticNode UnitNode::report() const +{ + return node_; +} + +void UnitNode::update() +{ + const auto result = expr_->eval(); + node_.status.level = result.level; + node_.links.clear(); + for (const auto & [node, used] : result.links) { + DiagnosticLink link; + link.index = node->index(); + link.used = used; + node_.links.push_back(link); + } +} + +void UnitNode::create(Graph & graph, const NodeConfig & config) +{ + try { + expr_ = BaseExpr::create(graph, config->yaml); + } catch (const ConfigError & error) { + throw create_error(config, error.what()); + } +} + +std::vector UnitNode::links() const +{ + return expr_->get_dependency(); +} + +DiagNode::DiagNode(const std::string & name, const std::string & hardware) +{ + node_.status.level = DiagnosticStatus::STALE; + node_.status.name = name; + node_.status.hardware_id = hardware; +} + +DiagnosticNode DiagNode::report() const +{ + return node_; +} + +void DiagNode::update() +{ + // TODO(Takagi, Isamu): timeout, error hold + // constexpr double timeout = 1.0; // TODO(Takagi, Isamu): parameterize +} + +void DiagNode::callback(const DiagnosticStatus & status) +{ + node_.status = status; +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/node.hpp b/system/system_diagnostic_graph/src/core/node.hpp new file mode 100644 index 0000000000000..359153fc2824a --- /dev/null +++ b/system/system_diagnostic_graph/src/core/node.hpp @@ -0,0 +1,84 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__NODE_HPP_ +#define CORE__NODE_HPP_ + +#include "config.hpp" +#include "debug.hpp" +#include "types.hpp" + +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +class BaseNode +{ +public: + virtual ~BaseNode() = default; + virtual void update() = 0; + virtual DiagnosticNode report() const = 0; + virtual DiagDebugData debug() const = 0; + virtual std::vector links() const = 0; + + DiagnosticLevel level() const { return node_.status.level; } + std::string name() const { return node_.status.name; } + + size_t index() const { return index_; } + void set_index(const size_t index) { index_ = index; } + +protected: + size_t index_ = 0; + DiagnosticNode node_; +}; + +class UnitNode : public BaseNode +{ +public: + explicit UnitNode(const std::string & name); + ~UnitNode() override; + + DiagnosticNode report() const override; + DiagDebugData debug() const override; + void update() override; + void create(Graph & graph, const NodeConfig & config); + + std::vector links() const override; + +private: + std::unique_ptr expr_; +}; + +class DiagNode : public BaseNode +{ +public: + explicit DiagNode(const std::string & name, const std::string & hardware); + + DiagnosticNode report() const override; + DiagDebugData debug() const override; + void update() override; + void callback(const DiagnosticStatus & status); + + std::vector links() const override { return {}; } + +private: +}; + +} // namespace system_diagnostic_graph + +#endif // CORE__NODE_HPP_ diff --git a/system/system_diagnostic_graph/src/core/types.hpp b/system/system_diagnostic_graph/src/core/types.hpp new file mode 100644 index 0000000000000..75167958e49bc --- /dev/null +++ b/system/system_diagnostic_graph/src/core/types.hpp @@ -0,0 +1,45 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__TYPES_HPP_ +#define CORE__TYPES_HPP_ + +#include +#include +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +using diagnostic_msgs::msg::DiagnosticArray; +using diagnostic_msgs::msg::DiagnosticStatus; +using tier4_system_msgs::msg::DiagnosticGraph; +using tier4_system_msgs::msg::DiagnosticLink; +using tier4_system_msgs::msg::DiagnosticNode; +using tier4_system_msgs::msg::OperationModeAvailability; + +using DiagnosticLevel = DiagnosticStatus::_level_type; + +class Graph; +class BaseNode; +class UnitNode; +class DiagNode; +class BaseExpr; + +} // namespace system_diagnostic_graph + +#endif // CORE__TYPES_HPP_ diff --git a/system/system_diagnostic_graph/src/core/update.cpp b/system/system_diagnostic_graph/src/core/update.cpp new file mode 100644 index 0000000000000..bb42dcba12192 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/update.cpp @@ -0,0 +1,116 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "update.hpp" + +#include "config.hpp" + +#include +#include +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +UnitNode * find_node(Graph & graph, const std::string & name) +{ + const auto node = graph.find_unit(name); + if (!node) { + throw ConfigError("summary node '" + name + "' does node exist"); + } + return node; +}; + +void DiagGraph::create(const std::string & file) +{ + const auto configs = load_config_file(file); + + // Create unit nodes first because it is necessary for the link. + std::vector> units; + for (const auto & config : configs) { + UnitNode * unit = graph_.make_unit(config->name); + units.push_back(std::make_pair(config, unit)); + } + + // Reflect the config after creating all the unit nodes, + for (auto & [config, unit] : units) { + unit->create(graph_, config); + } + + // Sort unit nodes in topological order for update dependencies. + graph_.topological_sort(); + + // Set the link index for the ros message. + const auto & nodes = graph_.nodes(); + for (size_t i = 0; i < nodes.size(); ++i) { + nodes[i]->set_index(i); + } + + // Get reserved unit node for summary. + summary_.stop_mode = find_node(graph_, "/autoware/operation/stop"); + summary_.autonomous_mode = find_node(graph_, "/autoware/operation/autonomous"); + summary_.local_mode = find_node(graph_, "/autoware/operation/local"); + summary_.remote_mode = find_node(graph_, "/autoware/operation/remote"); + summary_.emergency_stop_mrm = find_node(graph_, "/autoware/operation/emergency-stop"); + summary_.comfortable_stop_mrm = find_node(graph_, "/autoware/operation/comfortable-stop"); + summary_.pull_over_mrm = find_node(graph_, "/autoware/operation/pull-over"); +} + +DiagnosticGraph DiagGraph::report(const rclcpp::Time & stamp) +{ + DiagnosticGraph message; + message.stamp = stamp; + message.nodes.reserve(graph_.nodes().size()); + + for (const auto & node : graph_.nodes()) { + node->update(); + } + for (const auto & node : graph_.nodes()) { + message.nodes.push_back(node->report()); + } + return message; +} + +OperationModeAvailability DiagGraph::summary(const rclcpp::Time & stamp) +{ + const auto is_ok = [](const UnitNode * node) { return node->level() == DiagnosticStatus::OK; }; + + OperationModeAvailability message; + message.stamp = stamp; + message.stop = is_ok(summary_.stop_mode); + message.autonomous = is_ok(summary_.autonomous_mode); + message.local = is_ok(summary_.local_mode); + message.remote = is_ok(summary_.remote_mode); + message.emergency_stop = is_ok(summary_.emergency_stop_mrm); + message.comfortable_stop = is_ok(summary_.comfortable_stop_mrm); + message.pull_over = is_ok(summary_.pull_over_mrm); + return message; +} + +void DiagGraph::callback(const DiagnosticArray & array) +{ + for (const auto & status : array.status) { + auto diag = graph_.find_diag(status.name, status.hardware_id); + if (diag) { + diag->callback(status); + } else { + // TODO(Takagi, Isamu): handle unknown diagnostics + } + } +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/update.hpp b/system/system_diagnostic_graph/src/core/update.hpp new file mode 100644 index 0000000000000..3cba96ad8203a --- /dev/null +++ b/system/system_diagnostic_graph/src/core/update.hpp @@ -0,0 +1,58 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__UPDATE_HPP_ +#define CORE__UPDATE_HPP_ + +#include "graph.hpp" +#include "node.hpp" +#include "types.hpp" + +#include + +#include +#include + +namespace system_diagnostic_graph +{ + +struct Summary +{ + UnitNode * stop_mode; + UnitNode * autonomous_mode; + UnitNode * local_mode; + UnitNode * remote_mode; + UnitNode * emergency_stop_mrm; + UnitNode * comfortable_stop_mrm; + UnitNode * pull_over_mrm; +}; + +class DiagGraph +{ +public: + void create(const std::string & file); + void callback(const DiagnosticArray & array); + DiagnosticGraph report(const rclcpp::Time & stamp); + OperationModeAvailability summary(const rclcpp::Time & stamp); + + void debug(); + +private: + Graph graph_; + Summary summary_; +}; + +} // namespace system_diagnostic_graph + +#endif // CORE__UPDATE_HPP_ diff --git a/system/system_diagnostic_graph/src/main.cpp b/system/system_diagnostic_graph/src/main.cpp new file mode 100644 index 0000000000000..38b9fa5bacb3b --- /dev/null +++ b/system/system_diagnostic_graph/src/main.cpp @@ -0,0 +1,74 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "main.hpp" + +#include +#include + +namespace system_diagnostic_graph +{ + +MainNode::MainNode() : Node("system_diagnostic_graph_aggregator") +{ + // Init ros interface. + { + using std::placeholders::_1; + const auto qos_input = rclcpp::QoS(declare_parameter("input_qos_depth")); + const auto qos_graph = rclcpp::QoS(declare_parameter("graph_qos_depth")); + + const auto callback = std::bind(&MainNode::on_diag, this, _1); + sub_input_ = create_subscription("/diagnostics", qos_input, callback); + pub_graph_ = create_publisher("/diagnostics_graph", qos_graph); + pub_modes_ = create_publisher( + "/system/operation_mode/availability", rclcpp::QoS(1)); + + const auto rate = rclcpp::Rate(declare_parameter("rate")); + timer_ = rclcpp::create_timer(this, get_clock(), rate.period(), [this]() { on_timer(); }); + } + + // Init diagnostics graph. + { + const auto file = declare_parameter("graph_file"); + graph_.create(file); + graph_.debug(); + } +} + +void MainNode::on_timer() +{ + const auto data = graph_.report(now()); + graph_.debug(); + pub_graph_->publish(data); + pub_modes_->publish(graph_.summary(now())); +} + +void MainNode::on_diag(const DiagnosticArray::ConstSharedPtr msg) +{ + graph_.callback(*msg); +} + +} // namespace system_diagnostic_graph + +int main(int argc, char ** argv) +{ + using system_diagnostic_graph::MainNode; + rclcpp::init(argc, argv); + rclcpp::executors::SingleThreadedExecutor executor; + auto node = std::make_shared(); + executor.add_node(node); + executor.spin(); + executor.remove_node(node); + rclcpp::shutdown(); +} diff --git a/system/system_diagnostic_graph/src/main.hpp b/system/system_diagnostic_graph/src/main.hpp new file mode 100644 index 0000000000000..4cc659c978611 --- /dev/null +++ b/system/system_diagnostic_graph/src/main.hpp @@ -0,0 +1,43 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MAIN_HPP_ +#define MAIN_HPP_ + +#include "core/types.hpp" +#include "core/update.hpp" + +#include + +namespace system_diagnostic_graph +{ + +class MainNode : public rclcpp::Node +{ +public: + MainNode(); + +private: + DiagGraph graph_; + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Subscription::SharedPtr sub_input_; + rclcpp::Publisher::SharedPtr pub_graph_; + rclcpp::Publisher::SharedPtr pub_modes_; + void on_timer(); + void on_diag(const DiagnosticArray::ConstSharedPtr msg); +}; + +} // namespace system_diagnostic_graph + +#endif // MAIN_HPP_ diff --git a/system/system_diagnostic_graph/src/tool.cpp b/system/system_diagnostic_graph/src/tool.cpp new file mode 100644 index 0000000000000..fda2c88a691a2 --- /dev/null +++ b/system/system_diagnostic_graph/src/tool.cpp @@ -0,0 +1,81 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tool.hpp" + +#include +#include + +namespace system_diagnostic_graph +{ + +std::string level_to_string(DiagnosticLevel level) +{ + switch (level) { + case DiagnosticStatus::OK: + return "OK"; + case DiagnosticStatus::WARN: + return "WARN"; + case DiagnosticStatus::ERROR: + return "ERROR"; + case DiagnosticStatus::STALE: + return "STALE"; + } + return "UNKNOWN"; +} + +ToolNode::ToolNode() : Node("system_diagnostic_graph_converter") +{ + using std::placeholders::_1; + const auto qos_graph = rclcpp::QoS(1); + const auto qos_array = rclcpp::QoS(1); + + const auto callback = std::bind(&ToolNode::on_graph, this, _1); + sub_graph_ = create_subscription("/diagnostics_graph", qos_graph, callback); + pub_array_ = create_publisher("/diagnostics_array", qos_array); +} + +void ToolNode::on_graph(const DiagnosticGraph::ConstSharedPtr msg) +{ + DiagnosticArray message; + message.header.stamp = msg->stamp; + message.status.reserve(msg->nodes.size()); + for (const auto & node : msg->nodes) { + message.status.push_back(node.status); + for (const auto & link : node.links) { + diagnostic_msgs::msg::KeyValue kv; + const auto & status = msg->nodes[link.index].status; + kv.key = status.name; + kv.value = level_to_string(status.level); + if (link.used) { + message.status.back().values.push_back(kv); + } + } + } + pub_array_->publish(message); +} + +} // namespace system_diagnostic_graph + +int main(int argc, char ** argv) +{ + using system_diagnostic_graph::ToolNode; + rclcpp::init(argc, argv); + rclcpp::executors::SingleThreadedExecutor executor; + auto node = std::make_shared(); + executor.add_node(node); + executor.spin(); + executor.remove_node(node); + rclcpp::shutdown(); +} diff --git a/system/system_diagnostic_graph/src/tool.hpp b/system/system_diagnostic_graph/src/tool.hpp new file mode 100644 index 0000000000000..11f6a2632386b --- /dev/null +++ b/system/system_diagnostic_graph/src/tool.hpp @@ -0,0 +1,39 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TOOL_HPP_ +#define TOOL_HPP_ + +#include "core/graph.hpp" +#include "core/types.hpp" + +#include + +namespace system_diagnostic_graph +{ + +class ToolNode : public rclcpp::Node +{ +public: + ToolNode(); + +private: + rclcpp::Subscription::SharedPtr sub_graph_; + rclcpp::Publisher::SharedPtr pub_array_; + void on_graph(const DiagnosticGraph::ConstSharedPtr msg); +}; + +} // namespace system_diagnostic_graph + +#endif // TOOL_HPP_ diff --git a/system/system_diagnostic_graph/util/debug.py b/system/system_diagnostic_graph/util/debug.py new file mode 100755 index 0000000000000..dac917a26a2f6 --- /dev/null +++ b/system/system_diagnostic_graph/util/debug.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +# Copyright 2023 The Autoware Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import rclpy +from rclpy.node import Node +from rclpy.qos import QoSDurabilityPolicy +from rclpy.qos import QoSProfile +from tier4_system_msgs.msg import DiagnosticGraph + + +class StructNode(Node): + def __init__(self): + super().__init__("system_diagnostic_graph_tools_struct") + qos_struct = QoSProfile(depth=1, durability=QoSDurabilityPolicy.TRANSIENT_LOCAL) + self.sub_struct = self.create_subscription( + DiagnosticGraph, "/diagnostics_graph/struct", self.callback, qos_struct + ) + self.message = None + + def callback(self, msg): + self.message = msg + + +class NodeSpinner: + def __init__(self, time): + self.time = time + self.wait = True + + def on_timer(self): + self.wait = False + + def spin(self, node): + timer = node.create_timer(self.time, self.on_timer) + while self.wait: + rclpy.spin_once(node) + node.destroy_timer(timer) + + +class GraphNode: + def __init__(self, msg): + self.name = msg.name + self.links = msg.links + + +class GraphStruct: + def __init__(self, msg): + self.nodes = [GraphNode(node) for node in msg.nodes] + + @staticmethod + def Subscribe(): + spin = NodeSpinner(1.0) + node = StructNode() + spin.spin(node) + return GraphStruct(node.message) + + def visualize(self): + from graphviz import Digraph + + graph = Digraph() + graph.attr("node", shape="box") + for node in self.nodes: + graph.node(node.name) + for link in node.links: + graph.edge(node.name, self.nodes[link].name) + graph.view() + + +if __name__ == "__main__": + rclpy.init() + graph = GraphStruct.Subscribe() + rclpy.shutdown() + graph.visualize() diff --git a/system/system_diagnostic_monitor/CMakeLists.txt b/system/system_diagnostic_monitor/CMakeLists.txt new file mode 100644 index 0000000000000..909210f99d55e --- /dev/null +++ b/system/system_diagnostic_monitor/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.14) +project(system_diagnostic_monitor) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +ament_auto_package(INSTALL_TO_SHARE config launch script) diff --git a/system/system_diagnostic_monitor/README.md b/system/system_diagnostic_monitor/README.md new file mode 100644 index 0000000000000..8dccca34db8c5 --- /dev/null +++ b/system/system_diagnostic_monitor/README.md @@ -0,0 +1 @@ +# System diagnostic monitor diff --git a/system/system_diagnostic_monitor/config/autoware.yaml b/system/system_diagnostic_monitor/config/autoware.yaml new file mode 100644 index 0000000000000..892a5da851ba7 --- /dev/null +++ b/system/system_diagnostic_monitor/config/autoware.yaml @@ -0,0 +1,74 @@ +files: + - { package: system_diagnostic_monitor, path: config/map.yaml } + - { package: system_diagnostic_monitor, path: config/localization.yaml } + - { package: system_diagnostic_monitor, path: config/planning.yaml } + - { package: system_diagnostic_monitor, path: config/perception.yaml } + - { package: system_diagnostic_monitor, path: config/control.yaml } + - { package: system_diagnostic_monitor, path: config/vehicle.yaml } + - { package: system_diagnostic_monitor, path: config/system.yaml } + +nodes: + - name: /autoware + type: and + list: + - { type: unit, name: /autoware/operation/stop } + - { type: unit, name: /autoware/operation/autonomous } + - { type: unit, name: /autoware/operation/local } + - { type: unit, name: /autoware/operation/remote } + - { type: unit, name: /autoware/operation/emergency-stop } + - { type: unit, name: /autoware/operation/comfortable-stop } + - { type: unit, name: /autoware/operation/pull-over } + + - name: /autoware/operation/stop + type: debug-ok + + - name: /autoware/operation/autonomous + type: and + list: + - { type: unit, name: /autoware/map } + - { type: unit, name: /autoware/localization } + - { type: unit, name: /autoware/planning } + - { type: unit, name: /autoware/perception } + - { type: unit, name: /autoware/control } + - { type: unit, name: /autoware/vehicle } + - { type: unit, name: /autoware/system } + + - name: /autoware/operation/local + type: and + list: + - { type: unit, name: /autoware/vehicle } + - { type: unit, name: /autoware/system } + + - name: /autoware/operation/remote + type: and + list: + - { type: unit, name: /autoware/vehicle } + - { type: unit, name: /autoware/system } + + - name: /autoware/operation/emergency-stop + type: and + list: + - { type: unit, name: /autoware/vehicle } + - { type: unit, name: /autoware/system } + + - name: /autoware/operation/comfortable-stop + type: and + list: + - { type: unit, name: /autoware/map } + - { type: unit, name: /autoware/localization } + - { type: unit, name: /autoware/planning } + - { type: unit, name: /autoware/perception } + - { type: unit, name: /autoware/control } + - { type: unit, name: /autoware/vehicle } + - { type: unit, name: /autoware/system } + + - name: /autoware/operation/pull-over + type: and + list: + - { type: unit, name: /autoware/map } + - { type: unit, name: /autoware/localization } + - { type: unit, name: /autoware/planning } + - { type: unit, name: /autoware/perception } + - { type: unit, name: /autoware/control } + - { type: unit, name: /autoware/vehicle } + - { type: unit, name: /autoware/system } diff --git a/system/system_diagnostic_monitor/config/control.yaml b/system/system_diagnostic_monitor/config/control.yaml new file mode 100644 index 0000000000000..8884a79847c71 --- /dev/null +++ b/system/system_diagnostic_monitor/config/control.yaml @@ -0,0 +1,10 @@ +nodes: + - name: /autoware/control + type: and + list: + - type: diag + name: "topic_state_monitor_trajectory_follower_control_cmd: control_topic_status" + hardware: topic_state_monitor + - type: diag + name: "topic_state_monitor_control_command_control_cmd: control_topic_status" + hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/localization.yaml b/system/system_diagnostic_monitor/config/localization.yaml new file mode 100644 index 0000000000000..26d680b6c4f0f --- /dev/null +++ b/system/system_diagnostic_monitor/config/localization.yaml @@ -0,0 +1,5 @@ +nodes: + - name: /autoware/localization + type: and + list: + - { type: diag, name: "component_state_diagnostics: localization_state" } diff --git a/system/system_diagnostic_monitor/config/map.yaml b/system/system_diagnostic_monitor/config/map.yaml new file mode 100644 index 0000000000000..7bee7419200bd --- /dev/null +++ b/system/system_diagnostic_monitor/config/map.yaml @@ -0,0 +1,7 @@ +nodes: + - name: /autoware/map + type: and + list: + - type: diag + name: "topic_state_monitor_vector_map: map_topic_status" + hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/perception.yaml b/system/system_diagnostic_monitor/config/perception.yaml new file mode 100644 index 0000000000000..b6b4ec27d5596 --- /dev/null +++ b/system/system_diagnostic_monitor/config/perception.yaml @@ -0,0 +1,7 @@ +nodes: + - name: /autoware/perception + type: and + list: + - type: diag + name: "topic_state_monitor_object_recognition_objects: perception_topic_status" + hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/planning.yaml b/system/system_diagnostic_monitor/config/planning.yaml new file mode 100644 index 0000000000000..c29059193081b --- /dev/null +++ b/system/system_diagnostic_monitor/config/planning.yaml @@ -0,0 +1,15 @@ +nodes: + - name: /autoware/planning + type: if + cond: + type: diag + name: "component_state_diagnostics: route_state" + then: + type: and + list: + - type: diag + name: "topic_state_monitor_mission_planning_route: planning_topic_status" + hardware: topic_state_monitor + - type: diag + name: "topic_state_monitor_scenario_planning_trajectory: planning_topic_status" + hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/system.yaml b/system/system_diagnostic_monitor/config/system.yaml new file mode 100644 index 0000000000000..0377e68daaae6 --- /dev/null +++ b/system/system_diagnostic_monitor/config/system.yaml @@ -0,0 +1,7 @@ +nodes: + - name: /autoware/system + type: and + list: + - type: diag + name: "topic_state_monitor_system_emergency_control_cmd: system_topic_status" + hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/vehicle.yaml b/system/system_diagnostic_monitor/config/vehicle.yaml new file mode 100644 index 0000000000000..4826c96e5f72f --- /dev/null +++ b/system/system_diagnostic_monitor/config/vehicle.yaml @@ -0,0 +1,10 @@ +nodes: + - name: /autoware/vehicle + type: and + list: + - type: diag + name: "topic_state_monitor_vehicle_status_velocity_status: vehicle_topic_status" + hardware: topic_state_monitor + - type: diag + name: "topic_state_monitor_vehicle_status_steering_status: vehicle_topic_status" + hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml b/system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml new file mode 100644 index 0000000000000..a13b1dc9b78bc --- /dev/null +++ b/system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/system/system_diagnostic_monitor/package.xml b/system/system_diagnostic_monitor/package.xml new file mode 100644 index 0000000000000..d410e75000876 --- /dev/null +++ b/system/system_diagnostic_monitor/package.xml @@ -0,0 +1,23 @@ + + + + system_diagnostic_monitor + 0.1.0 + The system_diagnostic_monitor package + Takagi, Isamu + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + + system_diagnostic_graph + + autoware_adapi_v1_msgs + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/system/system_diagnostic_monitor/script/component_state_diagnostics.py b/system/system_diagnostic_monitor/script/component_state_diagnostics.py new file mode 100755 index 0000000000000..8e12ed6656674 --- /dev/null +++ b/system/system_diagnostic_monitor/script/component_state_diagnostics.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# Copyright 2023 The Autoware Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from autoware_adapi_v1_msgs.msg import LocalizationInitializationState as LocalizationState +from autoware_adapi_v1_msgs.msg import RouteState +from diagnostic_msgs.msg import DiagnosticArray +from diagnostic_msgs.msg import DiagnosticStatus +import rclpy +import rclpy.node +import rclpy.qos + + +class ComponentStateDiagnostics(rclpy.node.Node): + def __init__(self): + super().__init__("component_state_diagnostics") + durable_qos = rclpy.qos.QoSProfile( + depth=1, + durability=rclpy.qos.DurabilityPolicy.TRANSIENT_LOCAL, + reliability=rclpy.qos.ReliabilityPolicy.RELIABLE, + ) + + self.timer = self.create_timer(0.5, self.on_timer) + self.pub = self.create_publisher(DiagnosticArray, "/diagnostics", 1) + self.sub1 = self.create_subscription( + LocalizationState, + "/api/localization/initialization_state", + self.on_localization, + durable_qos, + ) + self.sub2 = self.create_subscription( + RouteState, "/api/routing/state", self.on_routing, durable_qos + ) + + self.diags = DiagnosticArray() + self.diags.status.append( + DiagnosticStatus( + level=DiagnosticStatus.STALE, name="component_state_diagnostics: localization_state" + ) + ) + self.diags.status.append( + DiagnosticStatus( + level=DiagnosticStatus.STALE, name="component_state_diagnostics: route_state" + ) + ) + + def on_timer(self): + self.diags.header.stamp = self.get_clock().now().to_msg() + self.pub.publish(self.diags) + + def on_localization(self, msg): + self.diags.status[0].level = ( + DiagnosticStatus.OK + if msg.state == LocalizationState.INITIALIZED + else DiagnosticStatus.ERROR + ) + + def on_routing(self, msg): + self.diags.status[1].level = ( + DiagnosticStatus.OK if msg.state != RouteState.UNSET else DiagnosticStatus.ERROR + ) + + +if __name__ == "__main__": + rclpy.init() + rclpy.spin(ComponentStateDiagnostics()) + rclpy.shutdown() From 16dc5358ee9ac3934190c0f09fb9d782500cb91d Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Tue, 26 Sep 2023 15:18:58 +0900 Subject: [PATCH 325/547] feat(lane_change): enable lane change in crosswalk/intersection if ego vehicle gets stuck (#5105) * feat(lane_change): enalbe lane change in crosswalk/intersection if ego vehicle stucks Signed-off-by: Fumiya Watanabe * docs(lane_change): fix document Signed-off-by: Fumiya Watanabe * docs(lane_change): fix words in document Signed-off-by: Fumiya Watanabe * refactor(lane_change): refactor initializing Signed-off-by: Fumiya Watanabe --------- Signed-off-by: Fumiya Watanabe --- .../config/lane_change/lane_change.param.yaml | 5 ++ ...ehavior_path_planner_lane_change_design.md | 10 ++++ .../scene_module/lane_change/normal.hpp | 5 ++ .../lane_change/lane_change_module_data.hpp | 4 ++ .../src/scene_module/lane_change/manager.cpp | 6 ++ .../src/scene_module/lane_change/normal.cpp | 57 +++++++++++-------- 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml index d7179cd384008..583bdeaaf8dc5 100644 --- a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml +++ b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml @@ -60,6 +60,11 @@ crosswalk: false intersection: false + # ego vehicle stuck detection + stuck_detection: + velocity: 0.1 # [m/s] + stop_time: 3.0 # [s] + # collision check enable_prepare_segment_collision_check: true prepare_segment_ignore_object_velocity_thresh: 0.1 # [m/s] diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md index 88266a2f4fb2a..2e5e486afc7c3 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_lane_change_design.md @@ -190,6 +190,9 @@ The following figure illustrates when the lane is blocked in multiple lane chang If you want to regulate lane change on crosswalks or intersections, the lane change module finds a lane change path excluding it includes crosswalks or intersections. To regulate lane change on crosswalks or intersections, change `regulation.crosswalk` or `regulation.intersection` to `true`. +If the ego vehicle gets stuck, to avoid stuck, it enables lane change in crosswalk/intersection. +If the ego vehicle stops more than `stuck_detection.stop_time` seconds, it is regarded as a stuck. +If the ego vehicle velocity is smaller than `stuck_detection.velocity`, it is regarded as stopping. ### Aborting lane change @@ -306,6 +309,13 @@ The following parameters are configurable in `lane_change.param.yaml`. | `regulation.crosswalk` | [-] | boolean | Regulate lane change on crosswalks | false | | `regulation.intersection` | [-] | boolean | Regulate lane change on intersections | false | +### Ego vehicle stuck detection + +| Name | Unit | Type | Description | Default value | +| :-------------------------- | ----- | ------ | --------------------------------------------------- | ------------- | +| `stuck_detection.velocity` | [m/s] | double | Velocity threshold for ego vehicle stuck detection | 0.1 | +| `stuck_detection.stop_time` | [s] | double | Stop time threshold for ego vehicle stuck detection | 3.0 | + ### Collision checks during lane change The following parameters are configurable in `behavior_path_planner.param.yaml` and `lane_change.param.yaml`. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index 747c75babb573..f1e4483c8bf51 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -155,7 +155,12 @@ class NormalLaneChange : public LaneChangeBase void setStopPose(const Pose & stop_pose); + void updateStopTime(); + + double getStopTime() const { return stop_time_; } + rclcpp::Logger logger_ = rclcpp::get_logger("lane_change").get_child(getModuleTypeStr()); + double stop_time_{0.0}; }; } // namespace behavior_path_planner #endif // BEHAVIOR_PATH_PLANNER__SCENE_MODULE__LANE_CHANGE__NORMAL_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index ce515be800900..27735777c27cc 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -72,6 +72,10 @@ struct LaneChangeParameters bool regulate_on_crosswalk{false}; bool regulate_on_intersection{false}; + // ego vehicle stuck detection + double stop_velocity_threshold{0.1}; + double stop_time_threshold{3.0}; + // true by default for all objects utils::path_safety_checker::ObjectTypesToCheck object_types_to_check; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index 57986a8f02265..a502c9d8e43ec 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -83,6 +83,12 @@ LaneChangeModuleManager::LaneChangeModuleManager( p.regulate_on_intersection = getOrDeclareParameter(*node, parameter("regulation.intersection")); + // ego vehicle stuck detection + p.stop_velocity_threshold = + getOrDeclareParameter(*node, parameter("stuck_detection.velocity")); + p.stop_time_threshold = + getOrDeclareParameter(*node, parameter("stuck_detection.stop_time")); + p.rss_params.longitudinal_distance_min_threshold = getOrDeclareParameter( *node, parameter("safety_check.longitudinal_distance_min_threshold")); p.rss_params.longitudinal_velocity_delta_time = getOrDeclareParameter( diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 568538967f2ac..984a45e7b3ecf 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -43,10 +43,12 @@ NormalLaneChange::NormalLaneChange( : LaneChangeBase(parameters, type, direction) { stop_watch_.tic(getModuleTypeStr()); + stop_watch_.tic("stop_time"); } void NormalLaneChange::updateLaneChangeStatus() { + updateStopTime(); const auto [found_valid_path, found_safe_path] = getSafePath(status_.lane_change_path); // Update status @@ -838,29 +840,6 @@ bool NormalLaneChange::hasEnoughLength( return false; } - if (lane_change_parameters_->regulate_on_crosswalk) { - const double dist_to_crosswalk_from_lane_change_start_pose = - utils::getDistanceToCrosswalk(current_pose, current_lanes, *overall_graphs_ptr) - - path.info.length.prepare; - // Check lane changing section includes crosswalk - if ( - dist_to_crosswalk_from_lane_change_start_pose > 0.0 && - dist_to_crosswalk_from_lane_change_start_pose < path.info.length.lane_changing) { - return false; - } - } - - if (lane_change_parameters_->regulate_on_intersection) { - const double dist_to_intersection_from_lane_change_start_pose = - utils::getDistanceToNextIntersection(current_pose, current_lanes) - path.info.length.prepare; - // Check lane changing section includes intersection - if ( - dist_to_intersection_from_lane_change_start_pose > 0.0 && - dist_to_intersection_from_lane_change_start_pose < path.info.length.lane_changing) { - return false; - } - } - return true; } @@ -1113,14 +1092,22 @@ bool NormalLaneChange::getLaneChangePaths( lane_change_parameters_->regulate_on_crosswalk && !hasEnoughLengthToCrosswalk(*candidate_path, current_lanes)) { RCLCPP_DEBUG(logger_, "Including crosswalk!!"); - continue; + if (getStopTime() < lane_change_parameters_->stop_time_threshold) { + continue; + } + RCLCPP_WARN_STREAM( + logger_, "Stop time is over threshold. Allow lane change in crosswalk."); } if ( lane_change_parameters_->regulate_on_intersection && !hasEnoughLengthToIntersection(*candidate_path, current_lanes)) { RCLCPP_DEBUG(logger_, "Including intersection!!"); - continue; + if (getStopTime() < lane_change_parameters_->stop_time_threshold) { + continue; + } + RCLCPP_WARN_STREAM( + logger_, "Stop time is over threshold. Allow lane change in intersection."); } if (utils::lane_change::passParkedObject( @@ -1481,4 +1468,24 @@ void NormalLaneChange::setStopPose(const Pose & stop_pose) lane_change_stop_pose_ = stop_pose; } +void NormalLaneChange::updateStopTime() +{ + const auto current_vel = getEgoVelocity(); + + if (std::abs(current_vel) > lane_change_parameters_->stop_velocity_threshold) { + stop_time_ = 0.0; + } else { + const double duration = stop_watch_.toc("stop_time"); + // clip stop time + if (stop_time_ + duration * 0.001 > lane_change_parameters_->stop_time_threshold) { + constexpr double eps = 0.1; + stop_time_ = lane_change_parameters_->stop_time_threshold + eps; + } else { + stop_time_ += duration * 0.001; + } + } + + stop_watch_.tic("stop_time"); +} + } // namespace behavior_path_planner From 0153e543633449a3ddbcbe57322b6ffb01def8dd Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 26 Sep 2023 18:31:16 +0900 Subject: [PATCH 326/547] fix(crosswalk): fix module launch condition (#5129) Signed-off-by: Takayuki Murooka --- .../src/manager.cpp | 51 +++++++------------ .../src/manager.hpp | 2 - .../src/manager.cpp | 2 +- .../src/manager.hpp | 2 - 4 files changed, 20 insertions(+), 37 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/manager.cpp b/planning/behavior_velocity_crosswalk_module/src/manager.cpp index 5a9de20d79477..304b4bdf6f5e7 100644 --- a/planning/behavior_velocity_crosswalk_module/src/manager.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/manager.cpp @@ -125,15 +125,8 @@ CrosswalkModuleManager::CrosswalkModuleManager(rclcpp::Node & node) void CrosswalkModuleManager::launchNewModules(const PathWithLaneId & path) { const auto rh = planner_data_->route_handler_; - if (!opt_use_regulatory_element_) { - opt_use_regulatory_element_ = checkRegulatoryElementExistence(rh->getLaneletMapPtr()); - std::ostringstream string_stream; - string_stream << "use crosswalk regulatory element: "; - string_stream << std::boolalpha << *opt_use_regulatory_element_; - RCLCPP_INFO_STREAM(logger_, string_stream.str()); - } - const auto launch = [this, &path](const auto id) { + const auto launch = [this, &path](const auto id, const auto & use_regulatory_element) { if (isModuleRegistered(id)) { return; } @@ -143,26 +136,23 @@ void CrosswalkModuleManager::launchNewModules(const PathWithLaneId & path) const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); registerModule(std::make_shared( - node_, id, lanelet_map_ptr, p, *opt_use_regulatory_element_, logger, clock_)); + node_, id, lanelet_map_ptr, p, use_regulatory_element, logger, clock_)); generateUUID(id); updateRTCStatus(getUUID(id), true, std::numeric_limits::lowest(), path.header.stamp); }; - if (*opt_use_regulatory_element_) { - const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( - path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); + const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( + path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); - for (const auto & crosswalk : crosswalk_leg_elem_map) { - launch(crosswalk.first->id()); - } - } else { - const auto crosswalk_lanelets = getCrosswalksOnPath( - planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), - rh->getOverallGraphPtr()); + for (const auto & crosswalk : crosswalk_leg_elem_map) { + launch(crosswalk.first->id(), true); + } - for (const auto & crosswalk : crosswalk_lanelets) { - launch(crosswalk.id()); - } + const auto crosswalk_lanelets = getCrosswalksOnPath( + planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), rh->getOverallGraphPtr()); + + for (const auto & crosswalk : crosswalk_lanelets) { + launch(crosswalk.id(), false); } } @@ -173,17 +163,14 @@ CrosswalkModuleManager::getModuleExpiredFunction(const PathWithLaneId & path) std::set crosswalk_id_set; - if (*opt_use_regulatory_element_) { - const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( - path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); + crosswalk_id_set = getCrosswalkIdSetOnPath( + planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), rh->getOverallGraphPtr()); - for (const auto & crosswalk : crosswalk_leg_elem_map) { - crosswalk_id_set.insert(crosswalk.first->id()); - } - } else { - crosswalk_id_set = getCrosswalkIdSetOnPath( - planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), - rh->getOverallGraphPtr()); + const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( + path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); + + for (const auto & crosswalk : crosswalk_leg_elem_map) { + crosswalk_id_set.insert(crosswalk.first->id()); } return [crosswalk_id_set](const std::shared_ptr & scene_module) { diff --git a/planning/behavior_velocity_crosswalk_module/src/manager.hpp b/planning/behavior_velocity_crosswalk_module/src/manager.hpp index 481a246af80dd..38bb63c121699 100644 --- a/planning/behavior_velocity_crosswalk_module/src/manager.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/manager.hpp @@ -51,8 +51,6 @@ class CrosswalkModuleManager : public SceneModuleManagerInterfaceWithRTC std::function &)> getModuleExpiredFunction( const PathWithLaneId & path) override; - - std::optional opt_use_regulatory_element_{std::nullopt}; }; class CrosswalkModulePlugin : public PluginWrapper diff --git a/planning/behavior_velocity_walkway_module/src/manager.cpp b/planning/behavior_velocity_walkway_module/src/manager.cpp index f9c222f662e82..8995c2362a8cd 100644 --- a/planning/behavior_velocity_walkway_module/src/manager.cpp +++ b/planning/behavior_velocity_walkway_module/src/manager.cpp @@ -45,7 +45,7 @@ void WalkwayModuleManager::launchNewModules(const PathWithLaneId & path) { const auto rh = planner_data_->route_handler_; - const auto launch = [this, &path](const auto & lanelet, const auto use_regulatory_element) { + const auto launch = [this, &path](const auto & lanelet, const auto & use_regulatory_element) { const auto attribute = lanelet.attributeOr(lanelet::AttributeNamesString::Subtype, std::string("")); if (attribute != lanelet::AttributeValueString::Walkway) { diff --git a/planning/behavior_velocity_walkway_module/src/manager.hpp b/planning/behavior_velocity_walkway_module/src/manager.hpp index 555e3a73db103..453fcdb40f0db 100644 --- a/planning/behavior_velocity_walkway_module/src/manager.hpp +++ b/planning/behavior_velocity_walkway_module/src/manager.hpp @@ -50,8 +50,6 @@ class WalkwayModuleManager : public SceneModuleManagerInterface std::function &)> getModuleExpiredFunction( const PathWithLaneId & path) override; - - std::optional opt_use_regulatory_element_{std::nullopt}; }; class WalkwayModulePlugin : public PluginWrapper From 1ca5dc1dffb6e95654cfe7528a1deee356c0e8b2 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Tue, 26 Sep 2023 18:43:47 +0900 Subject: [PATCH 327/547] fix(lane_change): object filter other lane object (#5083) * fix(lane_change): object filter other lane object Signed-off-by: Muhammad Zulfaqar Azmi * remove accidentally added code Signed-off-by: Zulfaqar Azmi * fix some missing code Signed-off-by: Zulfaqar Azmi --------- Signed-off-by: Muhammad Zulfaqar Azmi Signed-off-by: Zulfaqar Azmi --- .../src/scene_module/lane_change/normal.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 984a45e7b3ecf..d1de12a3bd5f9 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -676,10 +676,15 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( utils::lane_change::createPolygon(current_lanes, 0.0, std::numeric_limits::max()); const auto target_polygon = utils::lane_change::createPolygon(target_lanes, 0.0, std::numeric_limits::max()); - const auto target_backward_polygon = utils::lane_change::createPolygon( - target_backward_lanes, 0.0, std::numeric_limits::max()); const auto dist_ego_to_current_lanes_center = lanelet::utils::getLateralDistanceToClosestLanelet(current_lanes, current_pose); + std::vector> target_backward_polygons; + for (const auto & target_backward_lane : target_backward_lanes) { + lanelet::ConstLanelets lanelet{target_backward_lane}; + auto lane_polygon = + utils::lane_change::createPolygon(lanelet, 0.0, std::numeric_limits::max()); + target_backward_polygons.push_back(lane_polygon); + } auto filtered_objects = objects; @@ -732,10 +737,16 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( } } + const auto check_backward_polygon = [&obj_polygon](const auto & target_backward_polygon) { + return target_backward_polygon && + boost::geometry::intersects(target_backward_polygon.value(), obj_polygon); + }; + // check if the object intersects with target backward lanes if ( - target_backward_polygon && - boost::geometry::intersects(target_backward_polygon.value(), obj_polygon)) { + !target_backward_polygons.empty() && + std::any_of( + target_backward_polygons.begin(), target_backward_polygons.end(), check_backward_polygon)) { filtered_obj_indices.target_lane.push_back(i); continue; } From 32f0547d62e0e031708dc92ea59de254e1de56a5 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 26 Sep 2023 18:50:31 +0900 Subject: [PATCH 328/547] fix(elastic_band, obstacle_avoidance_planner): fix velocity update (#5117) * update elastic band Signed-off-by: Takayuki Murooka * update obstacle avoidance planner Signed-off-by: Takayuki Murooka * fix for scenario test Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../obstacle_avoidance_planner/src/node.cpp | 47 ++++++++++----- .../src/elastic_band_smoother.cpp | 60 +++++++++++++------ 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/planning/obstacle_avoidance_planner/src/node.cpp b/planning/obstacle_avoidance_planner/src/node.cpp index b5dd0e91a541e..f911fa87bedeb 100644 --- a/planning/obstacle_avoidance_planner/src/node.cpp +++ b/planning/obstacle_avoidance_planner/src/node.cpp @@ -443,7 +443,24 @@ void ObstacleAvoidancePlanner::applyInputVelocity( ego_nearest_param_.yaw_threshold); // calculate and insert stop pose on output trajectory - if (stop_seg_idx) { + const bool is_stop_point_inside_trajectory = [&]() { + if (!stop_seg_idx) { + return false; + } + if (*stop_seg_idx == output_traj_points.size() - 2) { + const double signed_projected_length_to_segment = + motion_utils::calcLongitudinalOffsetToSegment( + output_traj_points, *stop_seg_idx, input_stop_pose.position); + const double segment_length = + motion_utils::calcSignedArcLength(output_traj_points, *stop_seg_idx, *stop_seg_idx + 1); + if (segment_length < signed_projected_length_to_segment) { + // NOTE: input_stop_pose is outside output_traj_points. + return false; + } + } + return true; + }(); + if (is_stop_point_inside_trajectory) { trajectory_utils::insertStopPoint(output_traj_points, input_stop_pose, *stop_seg_idx); } } @@ -573,25 +590,25 @@ std::vector ObstacleAvoidancePlanner::extendTrajectory( const auto joint_end_traj_point_idx = trajectory_utils::getPointIndexAfter( traj_points, joint_start_pose.position, joint_start_traj_seg_idx, joint_traj_max_length_for_smoothing, joint_traj_min_length_for_smoothing); + if (!joint_end_traj_point_idx) { + return trajectory_utils::resampleTrajectoryPoints( + optimized_traj_points, traj_param_.output_delta_arc_length); + } // calculate full trajectory points const auto full_traj_points = [&]() { - if (!joint_end_traj_point_idx) { - return optimized_traj_points; - } - - const auto extended_traj_points = std::vector{ + auto extended_traj_points = std::vector{ traj_points.begin() + *joint_end_traj_point_idx, traj_points.end()}; - // NOTE: if optimized_traj_points's back is non zero velocity and extended_traj_points' front is - // zero velocity, the zero velocity will be inserted in the whole joint trajectory. - auto modified_optimized_traj_points = optimized_traj_points; - if (!extended_traj_points.empty() && !modified_optimized_traj_points.empty()) { - modified_optimized_traj_points.back().longitudinal_velocity_mps = - extended_traj_points.front().longitudinal_velocity_mps; + if (!extended_traj_points.empty() && !optimized_traj_points.empty()) { + // NOTE: Without this code, if optimized_traj_points's back is non zero velocity and + // extended_traj_points' front + // is zero velocity, the zero velocity will be inserted in the whole joint trajectory. + // The input stop point will be inserted explicitly in the latter part. + extended_traj_points.front().longitudinal_velocity_mps = + optimized_traj_points.back().longitudinal_velocity_mps; } - - return concatVectors(modified_optimized_traj_points, extended_traj_points); + return concatVectors(optimized_traj_points, extended_traj_points); }(); // resample trajectory points @@ -599,7 +616,7 @@ std::vector ObstacleAvoidancePlanner::extendTrajectory( full_traj_points, traj_param_.output_delta_arc_length); // update stop velocity on joint - for (size_t i = joint_start_traj_seg_idx + 1; i <= joint_end_traj_point_idx; ++i) { + for (size_t i = joint_start_traj_seg_idx + 1; i <= *joint_end_traj_point_idx; ++i) { if (hasZeroVelocity(traj_points.at(i))) { if (i != 0 && !hasZeroVelocity(traj_points.at(i - 1))) { // Here is when current point is 0 velocity, but previous point is not 0 velocity. diff --git a/planning/path_smoother/src/elastic_band_smoother.cpp b/planning/path_smoother/src/elastic_band_smoother.cpp index dbd0f2de92f29..31fe23a47b0ca 100644 --- a/planning/path_smoother/src/elastic_band_smoother.cpp +++ b/planning/path_smoother/src/elastic_band_smoother.cpp @@ -270,12 +270,36 @@ void ElasticBandSmoother::applyInputVelocity( // insert stop point explicitly const auto stop_idx = motion_utils::searchZeroVelocityIndex(forward_cropped_input_traj_points); if (stop_idx) { - const auto input_stop_pose = forward_cropped_input_traj_points.at(stop_idx.get()).pose; - const size_t stop_seg_idx = trajectory_utils::findEgoSegmentIndex( - output_traj_points, input_stop_pose, ego_nearest_param_); + const auto & input_stop_pose = forward_cropped_input_traj_points.at(stop_idx.get()).pose; + // NOTE: motion_utils::findNearestSegmentIndex is used instead of + // trajectory_utils::findEgoSegmentIndex + // for the case where input_traj_points is much longer than output_traj_points, and the + // former has a stop point but the latter will not have. + const auto stop_seg_idx = motion_utils::findNearestSegmentIndex( + output_traj_points, input_stop_pose, ego_nearest_param_.dist_threshold, + ego_nearest_param_.yaw_threshold); // calculate and insert stop pose on output trajectory - trajectory_utils::insertStopPoint(output_traj_points, input_stop_pose, stop_seg_idx); + const bool is_stop_point_inside_trajectory = [&]() { + if (!stop_seg_idx) { + return false; + } + if (*stop_seg_idx == output_traj_points.size() - 2) { + const double signed_projected_length_to_segment = + motion_utils::calcLongitudinalOffsetToSegment( + output_traj_points, *stop_seg_idx, input_stop_pose.position); + const double segment_length = + motion_utils::calcSignedArcLength(output_traj_points, *stop_seg_idx, *stop_seg_idx + 1); + if (segment_length < signed_projected_length_to_segment) { + // NOTE: input_stop_pose is outside output_traj_points. + return false; + } + } + return true; + }(); + if (is_stop_point_inside_trajectory) { + trajectory_utils::insertStopPoint(output_traj_points, input_stop_pose, *stop_seg_idx); + } } time_keeper_ptr_->toc(__func__, " "); @@ -299,25 +323,25 @@ std::vector ElasticBandSmoother::extendTrajectory( const auto joint_end_traj_point_idx = trajectory_utils::getPointIndexAfter( traj_points, joint_start_pose.position, joint_start_traj_seg_idx, joint_traj_max_length_for_smoothing, joint_traj_min_length_for_smoothing); + if (!joint_end_traj_point_idx) { + return trajectory_utils::resampleTrajectoryPoints( + optimized_traj_points, common_param_.output_delta_arc_length); + } // calculate full trajectory points const auto full_traj_points = [&]() { - if (!joint_end_traj_point_idx) { - return optimized_traj_points; - } - - const auto extended_traj_points = std::vector{ + auto extended_traj_points = std::vector{ traj_points.begin() + *joint_end_traj_point_idx, traj_points.end()}; - // NOTE: if optimized_traj_points's back is non zero velocity and extended_traj_points' front is - // zero velocity, the zero velocity will be inserted in the whole joint trajectory. - auto modified_optimized_traj_points = optimized_traj_points; - if (!extended_traj_points.empty() && !modified_optimized_traj_points.empty()) { - modified_optimized_traj_points.back().longitudinal_velocity_mps = - extended_traj_points.front().longitudinal_velocity_mps; + if (!extended_traj_points.empty() && !optimized_traj_points.empty()) { + // NOTE: Without this code, if optimized_traj_points's back is non zero velocity and + // extended_traj_points' front + // is zero velocity, the zero velocity will be inserted in the whole joint trajectory. + // The input stop point will be inserted explicitly in the latter part. + extended_traj_points.front().longitudinal_velocity_mps = + optimized_traj_points.back().longitudinal_velocity_mps; } - - return concatVectors(modified_optimized_traj_points, extended_traj_points); + return concatVectors(optimized_traj_points, extended_traj_points); }(); // resample trajectory points @@ -325,7 +349,7 @@ std::vector ElasticBandSmoother::extendTrajectory( full_traj_points, common_param_.output_delta_arc_length); // update stop velocity on joint - for (size_t i = joint_start_traj_seg_idx + 1; i <= joint_end_traj_point_idx; ++i) { + for (size_t i = joint_start_traj_seg_idx + 1; i <= *joint_end_traj_point_idx; ++i) { if (hasZeroVelocity(traj_points.at(i))) { if (i != 0 && !hasZeroVelocity(traj_points.at(i - 1))) { // Here is when current point is 0 velocity, but previous point is not 0 velocity. From 72e78559f669bf1c0646601ea4a2a432ddf6b86d Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Tue, 26 Sep 2023 19:03:51 +0900 Subject: [PATCH 329/547] feat(ar_tag_based_localizer): add diagnostic (#5132) * Added diagnostic Signed-off-by: Shintaro SAKODA * Fixed key name Signed-off-by: Shintaro SAKODA * Updated README.md Signed-off-by: Shintaro SAKODA * style(pre-commit): autofix --------- Signed-off-by: Shintaro SAKODA Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../ar_tag_based_localizer/README.md | 3 +- .../ar_tag_based_localizer_core.hpp | 2 ++ .../ar_tag_based_localizer/package.xml | 1 + .../src/ar_tag_based_localizer_core.cpp | 28 +++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/localization/landmark_based_localizer/ar_tag_based_localizer/README.md b/localization/landmark_based_localizer/ar_tag_based_localizer/README.md index 8c51febc6f60c..b212711f38a57 100644 --- a/localization/landmark_based_localizer/ar_tag_based_localizer/README.md +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/README.md @@ -25,7 +25,8 @@ The positions and orientations of the AR-Tags are assumed to be written in the L | :------------------------------ | :---------------------------------------------- | :---------------------------------------------------------------------------------------- | | `~/output/pose_with_covariance` | `geometry_msgs::msg::PoseWithCovarianceStamped` | Estimated Pose | | `~/debug/result` | `sensor_msgs::msg::Image` | [debug topic] Image in which marker detection results are superimposed on the input image | -| `tf` | `geometry_msgs::msg::TransformStamped` | [debug topic] TF from camera to detected tag | +| `/tf` | `geometry_msgs::msg::TransformStamped` | [debug topic] TF from camera to detected tag | +| `/diagnostics` | `diagnostic_msgs::msg::DiagnosticArray` | Diagnostics outputs | ## How to launch diff --git a/localization/landmark_based_localizer/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp b/localization/landmark_based_localizer/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp index 6af9fefae23f7..37725dd06c34e 100644 --- a/localization/landmark_based_localizer/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/include/ar_tag_based_localizer/ar_tag_based_localizer_core.hpp @@ -48,6 +48,7 @@ #include #include +#include #include #include @@ -98,6 +99,7 @@ class ArTagBasedLocalizer : public rclcpp::Node // Publishers image_transport::Publisher image_pub_; rclcpp::Publisher::SharedPtr pose_pub_; + rclcpp::Publisher::SharedPtr diag_pub_; // Others aruco::MarkerDetector detector_; diff --git a/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml b/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml index e68fd21ed340a..2c35be181d2f0 100644 --- a/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml @@ -13,6 +13,7 @@ aruco cv_bridge + diagnostic_msgs geometry_msgs image_transport rclcpp diff --git a/localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp b/localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp index f664ef6378a69..afa82ab3e0677 100644 --- a/localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/src/ar_tag_based_localizer_core.cpp @@ -129,6 +129,8 @@ bool ArTagBasedLocalizer::setup() image_pub_ = it_->advertise("~/debug/result", 1); pose_pub_ = this->create_publisher( "~/output/pose_with_covariance", qos_pub); + diag_pub_ = + this->create_publisher("/diagnostics", qos_pub); RCLCPP_INFO(this->get_logger(), "Setup of ar_tag_based_localizer node is successful!"); return true; @@ -199,6 +201,32 @@ void ArTagBasedLocalizer::image_callback(const sensor_msgs::msg::Image::ConstSha out_msg.image = in_image; image_pub_.publish(out_msg.toImageMsg()); } + + const int detected_tags = markers.size(); + + diagnostic_msgs::msg::DiagnosticStatus diag_status; + + if (detected_tags > 0) { + diag_status.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + diag_status.message = "AR tags detected. The number of tags: " + std::to_string(detected_tags); + } else { + diag_status.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + diag_status.message = "No AR tags detected."; + } + + diag_status.name = "localization: " + std::string(this->get_name()); + diag_status.hardware_id = this->get_name(); + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = "Number of Detected AR Tags"; + key_value.value = std::to_string(detected_tags); + diag_status.values.push_back(key_value); + + diagnostic_msgs::msg::DiagnosticArray diag_msg; + diag_msg.header.stamp = this->now(); + diag_msg.status.push_back(diag_status); + + diag_pub_->publish(diag_msg); } // wait for one camera info, then shut down that subscriber From 078be98b55162509f0f837b62a7581e03a77aa7a Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 26 Sep 2023 19:26:07 +0900 Subject: [PATCH 330/547] refactor(behavior_path_planner): refactor process in updateState (#5103) * refactor process in update state Signed-off-by: kyoichi-sugahara * add comment Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../start_planner/start_planner_module.hpp | 3 +- .../start_planner/start_planner_module.cpp | 41 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index 6fd2045b4f097..d4013fd97b316 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -168,11 +168,12 @@ class StartPlannerModule : public SceneModuleInterface lanelet::ConstLanelets getPathRoadLanes(const PathWithLaneId & path) const; std::vector generateDrivableLanes(const PathWithLaneId & path) const; void updatePullOutStatus(); + void updateStatusAfterBackwardDriving(); static bool isOverlappedWithLane( const lanelet::ConstLanelet & candidate_lanelet, const tier4_autoware_utils::LinearRing2d & vehicle_footprint); bool hasFinishedPullOut() const; - void checkBackFinished(); + bool isBackwardDrivingComplete() const; bool isStopped(); bool isStuck(); bool hasFinishedCurrentPath(); diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 5247a24579212..c2df11b4bede6 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -192,7 +192,10 @@ ModuleStatus StartPlannerModule::updateState() return ModuleStatus::SUCCESS; } - checkBackFinished(); + if (isBackwardDrivingComplete()) { + updateStatusAfterBackwardDriving(); + return ModuleStatus::SUCCESS; // for breaking loop + } return current_state_; } @@ -658,14 +661,28 @@ void StartPlannerModule::updatePullOutStatus() planWithPriority( start_pose_candidates, *refined_start_pose, goal_pose, parameters_->search_priority); - checkBackFinished(); - if (!status_.back_finished) { + if (isBackwardDrivingComplete()) { + updateStatusAfterBackwardDriving(); + current_state_ = ModuleStatus::SUCCESS; // for breaking loop + } else { status_.backward_path = start_planner_utils::getBackwardPath( *route_handler, status_.pull_out_lanes, current_pose, status_.pull_out_start_pose, parameters_->backward_velocity); } } +void StartPlannerModule::updateStatusAfterBackwardDriving() +{ + status_.back_finished = true; + // request start_planner approval + waitApproval(); + // To enable approval of the forward path, the RTC status is removed. + removeRTCStatus(); + for (auto itr = uuid_map_.begin(); itr != uuid_map_.end(); ++itr) { + itr->second = generateUUID(); + } +} + PathWithLaneId StartPlannerModule::calcStartPoseCandidatesBackwardPath() const { const Pose & current_pose = planner_data_->self_odometry->pose.pose; @@ -787,9 +804,9 @@ bool StartPlannerModule::hasFinishedPullOut() const return has_finished; } -void StartPlannerModule::checkBackFinished() +bool StartPlannerModule::isBackwardDrivingComplete() const { - // check ego car is close enough to pull out start pose + // check ego car is close enough to pull out start pose and stopped const auto current_pose = planner_data_->self_odometry->pose.pose; const auto distance = tier4_autoware_utils::calcDistance2d(current_pose, status_.pull_out_start_pose); @@ -798,18 +815,12 @@ void StartPlannerModule::checkBackFinished() const double ego_vel = utils::l2Norm(planner_data_->self_odometry->twist.twist.linear); const bool is_stopped = ego_vel < parameters_->th_stopped_velocity; - if (!status_.back_finished && is_near && is_stopped) { + const bool back_finished = !status_.back_finished && is_near && is_stopped; + if (back_finished) { RCLCPP_INFO(getLogger(), "back finished"); - status_.back_finished = true; - - // request start_planner approval - waitApproval(); - removeRTCStatus(); - for (auto itr = uuid_map_.begin(); itr != uuid_map_.end(); ++itr) { - itr->second = generateUUID(); - } - current_state_ = ModuleStatus::SUCCESS; // for breaking loop } + + return back_finished; } bool StartPlannerModule::isStopped() From 19ab508c3d590101d8fcb9ffca1248adebd895f3 Mon Sep 17 00:00:00 2001 From: Daisuke Nishimatsu <42202095+wep21@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:27:06 +0900 Subject: [PATCH 331/547] chore: add workaround for tf2 update (#5127) --- common/autoware_auto_geometry/CMakeLists.txt | 6 ++++++ common/autoware_auto_tf2/CMakeLists.txt | 5 +++++ .../include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp | 3 ++- planning/behavior_path_planner/CMakeLists.txt | 6 ++++++ planning/surround_obstacle_checker/CMakeLists.txt | 6 ++++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/common/autoware_auto_geometry/CMakeLists.txt b/common/autoware_auto_geometry/CMakeLists.txt index 454e0e7ef044f..bf48e8fcaca3f 100644 --- a/common/autoware_auto_geometry/CMakeLists.txt +++ b/common/autoware_auto_geometry/CMakeLists.txt @@ -12,6 +12,12 @@ ament_auto_add_library(${PROJECT_NAME} SHARED src/bounding_box.cpp ) +if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) + target_compile_definitions(${PROJECT_NAME} PRIVATE + DEFINE_LEGACY_FUNCTION + ) +endif() + if(BUILD_TESTING) set(GEOMETRY_GTEST geometry_gtest) set(GEOMETRY_SRC test/src/test_geometry.cpp diff --git a/common/autoware_auto_tf2/CMakeLists.txt b/common/autoware_auto_tf2/CMakeLists.txt index a8ae9ec2d962e..fe4cec1c0fc89 100755 --- a/common/autoware_auto_tf2/CMakeLists.txt +++ b/common/autoware_auto_tf2/CMakeLists.txt @@ -17,6 +17,11 @@ if(BUILD_TESTING) "tf2_geometry_msgs" "tf2_ros" ) + if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) + target_compile_definitions(test_tf2_autoware_auto_msgs PRIVATE + DEFINE_LEGACY_FUNCTION + ) + endif() endif() ament_auto_package() diff --git a/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp b/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp index 9b20953a7b3c5..ee8d52fa402c0 100644 --- a/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp +++ b/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp @@ -45,7 +45,7 @@ using BoundingBox = autoware_auto_perception_msgs::msg::BoundingBox; namespace tf2 { - +#ifdef DEFINE_LEGACY_FUNCTION /*************/ /** Point32 **/ /*************/ @@ -94,6 +94,7 @@ inline void doTransform( t_out.points[i].z = static_cast(v_out[2]); } } +#endif /******************/ /** Quaternion32 **/ diff --git a/planning/behavior_path_planner/CMakeLists.txt b/planning/behavior_path_planner/CMakeLists.txt index f62e418371401..542d4daa0c676 100644 --- a/planning/behavior_path_planner/CMakeLists.txt +++ b/planning/behavior_path_planner/CMakeLists.txt @@ -58,6 +58,12 @@ ament_auto_add_library(behavior_path_planner_node SHARED src/marker_utils/lane_change/debug.cpp ) +if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) + target_compile_definitions(behavior_path_planner_node PRIVATE + DEFINE_LEGACY_FUNCTION + ) +endif() + target_include_directories(behavior_path_planner_node SYSTEM PUBLIC ${EIGEN3_INCLUDE_DIR} ) diff --git a/planning/surround_obstacle_checker/CMakeLists.txt b/planning/surround_obstacle_checker/CMakeLists.txt index 0d7b636646783..694bddf421486 100644 --- a/planning/surround_obstacle_checker/CMakeLists.txt +++ b/planning/surround_obstacle_checker/CMakeLists.txt @@ -18,6 +18,12 @@ ament_auto_add_library(${PROJECT_NAME} SHARED src/node.cpp ) +if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) + target_compile_definitions(${PROJECT_NAME} PRIVATE + DEFINE_LEGACY_FUNCTION + ) +endif() + target_link_libraries(${PROJECT_NAME} ${PCL_LIBRARIES} ) From 68ee0f220fcd3e4f0ed9276270008d04fd579120 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 26 Sep 2023 20:16:43 +0900 Subject: [PATCH 332/547] fix(pointcloud_preprocessor): organize input twist topic (#5125) * fix(pointcloud_preprocessor): organize input twist topic (#25) * fix(pointcloud_preprocessor): organize input twist topic Signed-off-by: kminoda * style(pre-commit): autofix * fix build bug Signed-off-by: kminoda * fix format error Signed-off-by: kminoda * style(pre-commit): autofix * fix Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * minor fixes Signed-off-by: kminoda * style(pre-commit): autofix * add warning Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../ground_segmentation.launch.py | 18 ++++- .../docs/concatenate-data.md | 1 + .../concatenate_and_time_sync_nodelet.hpp | 6 ++ .../launch/preprocessor.launch.py | 6 +- .../concatenate_and_time_sync_nodelet.cpp | 66 +++++++++++++++++-- 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/launch/tier4_perception_launch/launch/obstacle_segmentation/ground_segmentation/ground_segmentation.launch.py b/launch/tier4_perception_launch/launch/obstacle_segmentation/ground_segmentation/ground_segmentation.launch.py index 7ed5860612601..12190fb659235 100644 --- a/launch/tier4_perception_launch/launch/obstacle_segmentation/ground_segmentation/ground_segmentation.launch.py +++ b/launch/tier4_perception_launch/launch/obstacle_segmentation/ground_segmentation/ground_segmentation.launch.py @@ -122,12 +122,16 @@ def create_ransac_pipeline(self): plugin="pointcloud_preprocessor::PointCloudConcatenateDataSynchronizerComponent", name="concatenate_data", namespace="plane_fitting", - remappings=[("output", "concatenated/pointcloud")], + remappings=[ + ("~/input/odom", "/localization/kinematic_state"), + ("output", "concatenated/pointcloud"), + ], parameters=[ { "input_topics": self.ground_segmentation_param["ransac_input_topics"], "output_frame": LaunchConfiguration("base_frame"), "timeout_sec": 1.0, + "input_twist_topic_type": "odom", } ], extra_arguments=[ @@ -432,11 +436,15 @@ def get_additional_lidars_concatenated_component(input_topics, output_topic): package="pointcloud_preprocessor", plugin="pointcloud_preprocessor::PointCloudConcatenateDataSynchronizerComponent", name="concatenate_data", - remappings=[("output", output_topic)], + remappings=[ + ("~/input/odom", "/localization/kinematic_state"), + ("output", output_topic), + ], parameters=[ { "input_topics": input_topics, "output_frame": LaunchConfiguration("base_frame"), + "input_twist_topic_type": "odom", } ], extra_arguments=[{"use_intra_process_comms": LaunchConfiguration("use_intra_process")}], @@ -448,11 +456,15 @@ def get_single_frame_obstacle_segmentation_concatenated_component(input_topics, package="pointcloud_preprocessor", plugin="pointcloud_preprocessor::PointCloudConcatenateDataSynchronizerComponent", name="concatenate_no_ground_data", - remappings=[("output", output_topic)], + remappings=[ + ("~/input/odom", "/localization/kinematic_state"), + ("output", output_topic), + ], parameters=[ { "input_topics": input_topics, "output_frame": LaunchConfiguration("base_frame"), + "input_twist_topic_type": "odom", } ], extra_arguments=[{"use_intra_process_comms": LaunchConfiguration("use_intra_process")}], diff --git a/sensing/pointcloud_preprocessor/docs/concatenate-data.md b/sensing/pointcloud_preprocessor/docs/concatenate-data.md index a3b5268815f24..74b4f3df17615 100644 --- a/sensing/pointcloud_preprocessor/docs/concatenate-data.md +++ b/sensing/pointcloud_preprocessor/docs/concatenate-data.md @@ -42,6 +42,7 @@ The figure below represents the reception time of each sensor data and how it is | `timeout_sec` | double | 0.1 | tolerance of time to publish next pointcloud [s]
    When this time limit is exceeded, the filter concatenates and publishes pointcloud, even if not all the point clouds are subscribed. | | `input_offset` | vector of double | [] | This parameter can control waiting time for each input sensor pointcloud [s]. You must to set the same length of offsets with input pointclouds numbers.
    For its tuning, please see [actual usage page](#how-to-tuning-timeout_sec-and-input_offset). | | `publish_synchronized_pointcloud` | bool | false | If true, publish the time synchronized pointclouds. All input pointclouds are transformed and then re-published as message named `_synchronized`. | +| `input_twist_topic_type` | std::string | twist | Topic type for twist. Currently support `twist` or `odom`. | ## Actual Usage diff --git a/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/concatenate_data/concatenate_and_time_sync_nodelet.hpp b/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/concatenate_data/concatenate_and_time_sync_nodelet.hpp index f5d55a2ebcbf7..be96aa94382dd 100644 --- a/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/concatenate_data/concatenate_and_time_sync_nodelet.hpp +++ b/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/concatenate_data/concatenate_and_time_sync_nodelet.hpp @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,7 @@ namespace pointcloud_preprocessor { using autoware_point_types::PointXYZI; using point_cloud_msg_wrapper::PointCloud2Modifier; + /** \brief @b PointCloudConcatenateDataSynchronizerComponent is a special form of data * synchronizer: it listens for a set of input PointCloud messages on the same topic, * checks their timestamps, and concatenates their fields together into a single @@ -131,10 +133,13 @@ class PointCloudConcatenateDataSynchronizerComponent : public rclcpp::Node std::vector::SharedPtr> filters_; rclcpp::Subscription::SharedPtr sub_twist_; + rclcpp::Subscription::SharedPtr sub_odom_; rclcpp::TimerBase::SharedPtr timer_; diagnostic_updater::Updater updater_{this}; + const std::string input_twist_topic_type_; + /** \brief Output TF frame the concatenated points should be transformed to. */ std::string output_frame_; @@ -170,6 +175,7 @@ class PointCloudConcatenateDataSynchronizerComponent : public rclcpp::Node const sensor_msgs::msg::PointCloud2::ConstSharedPtr & input_ptr, const std::string & topic_name); void twist_callback(const geometry_msgs::msg::TwistWithCovarianceStamped::ConstSharedPtr input); + void odom_callback(const nav_msgs::msg::Odometry::ConstSharedPtr input); void timer_callback(); void checkConcatStatus(diagnostic_updater::DiagnosticStatusWrapper & stat); diff --git a/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py b/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py index 6b1fc0f5bd976..0d65b45b1b3de 100644 --- a/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py +++ b/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py @@ -38,13 +38,17 @@ def launch_setup(context, *args, **kwargs): package=pkg, plugin="pointcloud_preprocessor::PointCloudConcatenateDataSynchronizerComponent", name="sync_and_concatenate_filter", - remappings=[("output", "points_raw/concatenated")], + remappings=[ + ("~/input/twist", "/sensing/vehicle_velocity_converter/twist_with_covariance"), + ("output", "points_raw/concatenated"), + ], parameters=[ { "input_topics": LaunchConfiguration("input_points_raw_list"), "output_frame": LaunchConfiguration("tf_output_frame"), "approximate_sync": True, "publish_synchronized_pointcloud": False, + "input_twist_topic_type": "twist", } ], ) diff --git a/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp b/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp index 8ae0bdacb983a..ff72e433e9602 100644 --- a/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp +++ b/sensing/pointcloud_preprocessor/src/concatenate_data/concatenate_and_time_sync_nodelet.cpp @@ -69,7 +69,8 @@ namespace pointcloud_preprocessor { PointCloudConcatenateDataSynchronizerComponent::PointCloudConcatenateDataSynchronizerComponent( const rclcpp::NodeOptions & node_options) -: Node("point_cloud_concatenator_component", node_options) +: Node("point_cloud_concatenator_component", node_options), + input_twist_topic_type_(declare_parameter("input_twist_topic_type", "twist")) { // initialize debug tool { @@ -164,10 +165,24 @@ PointCloudConcatenateDataSynchronizerComponent::PointCloudConcatenateDataSynchro filters_[d] = this->create_subscription( input_topics_[d], rclcpp::SensorDataQoS().keep_last(maximum_queue_size_), cb); } - auto twist_cb = std::bind( - &PointCloudConcatenateDataSynchronizerComponent::twist_callback, this, std::placeholders::_1); - sub_twist_ = this->create_subscription( - "~/input/twist", rclcpp::QoS{100}, twist_cb); + + if (input_twist_topic_type_ == "twist") { + auto twist_cb = std::bind( + &PointCloudConcatenateDataSynchronizerComponent::twist_callback, this, + std::placeholders::_1); + sub_twist_ = this->create_subscription( + "~/input/twist", rclcpp::QoS{100}, twist_cb); + } else if (input_twist_topic_type_ == "odom") { + auto odom_cb = std::bind( + &PointCloudConcatenateDataSynchronizerComponent::odom_callback, this, + std::placeholders::_1); + sub_odom_ = this->create_subscription( + "~/input/odom", rclcpp::QoS{100}, odom_cb); + } else { + RCLCPP_ERROR_STREAM( + get_logger(), "input_twist_topic_type is invalid: " << input_twist_topic_type_); + throw std::runtime_error("input_twist_topic_type is invalid: " + input_twist_topic_type_); + } } // Transformed Raw PointCloud2 Publisher @@ -227,8 +242,19 @@ Eigen::Matrix4f PointCloudConcatenateDataSynchronizerComponent::computeTransformToAdjustForOldTimestamp( const rclcpp::Time & old_stamp, const rclcpp::Time & new_stamp) { - // return identity if no twist is available or old_stamp is newer than new_stamp - if (twist_ptr_queue_.empty() || old_stamp > new_stamp) { + // return identity if no twist is available + if (twist_ptr_queue_.empty()) { + RCLCPP_WARN_STREAM_THROTTLE( + get_logger(), *get_clock(), std::chrono::milliseconds(10000).count(), + "No twist is available. Please confirm twist topic and timestamp"); + return Eigen::Matrix4f::Identity(); + } + + // return identity if old_stamp is newer than new_stamp + if (old_stamp > new_stamp) { + RCLCPP_WARN_STREAM_THROTTLE( + get_logger(), *get_clock(), std::chrono::milliseconds(10000).count(), + "old_stamp is newer than new_stamp,"); return Eigen::Matrix4f::Identity(); } @@ -558,6 +584,32 @@ void PointCloudConcatenateDataSynchronizerComponent::twist_callback( twist_ptr_queue_.push_back(twist_ptr); } +void PointCloudConcatenateDataSynchronizerComponent::odom_callback( + const nav_msgs::msg::Odometry::ConstSharedPtr input) +{ + // if rosbag restart, clear buffer + if (!twist_ptr_queue_.empty()) { + if (rclcpp::Time(twist_ptr_queue_.front()->header.stamp) > rclcpp::Time(input->header.stamp)) { + twist_ptr_queue_.clear(); + } + } + + // pop old data + while (!twist_ptr_queue_.empty()) { + if ( + rclcpp::Time(twist_ptr_queue_.front()->header.stamp) + rclcpp::Duration::from_seconds(1.0) > + rclcpp::Time(input->header.stamp)) { + break; + } + twist_ptr_queue_.pop_front(); + } + + auto twist_ptr = std::make_shared(); + twist_ptr->header = input->header; + twist_ptr->twist = input->twist.twist; + twist_ptr_queue_.push_back(twist_ptr); +} + void PointCloudConcatenateDataSynchronizerComponent::checkConcatStatus( diagnostic_updater::DiagnosticStatusWrapper & stat) { From 9a8a043f646b2909b321b2c727d683b21ce2eb5b Mon Sep 17 00:00:00 2001 From: Yuxuan Liu <619684051@qq.com> Date: Tue, 26 Sep 2023 21:15:57 +0800 Subject: [PATCH 333/547] perf(processing_time/behavior_path_planner): add processing time publishers to output results from the timer (#5136) * perf(processing_time/behavior_path_planner): add processing time publishers to output results from the timer Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix --------- Signed-off-by: Owen-Liuyuxuan Co-authored-by: Owen-Liuyuxuan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../include/behavior_path_planner/planner_manager.hpp | 6 ++++++ planning/behavior_path_planner/src/planner_manager.cpp | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index 589ccebf5b0cb..ee5771c98217e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -19,10 +19,12 @@ #include "behavior_path_planner/scene_module/scene_module_manager_interface.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/lane_following/module_data.hpp" +#include "tier4_autoware_utils/ros/debug_publisher.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" #include +#include "tier4_debug_msgs/msg/float64_stamped.hpp" #include #include @@ -44,6 +46,8 @@ using tier4_autoware_utils::StopWatch; using tier4_planning_msgs::msg::StopReasonArray; using SceneModulePtr = std::shared_ptr; using SceneModuleManagerPtr = std::shared_ptr; +using DebugPublisher = tier4_autoware_utils::DebugPublisher; +using DebugDoubleMsg = tier4_debug_msgs::msg::Float64Stamped; enum Action { ADD = 0, @@ -421,6 +425,8 @@ class PlannerManager std::vector candidate_module_ptrs_; + std::unique_ptr debug_publisher_ptr_; + mutable rclcpp::Logger logger_; mutable rclcpp::Clock clock_; diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index 7a7ee046ca069..5963ddb778874 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -16,6 +16,7 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "tier4_autoware_utils/ros/debug_publisher.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" #include @@ -34,6 +35,7 @@ PlannerManager::PlannerManager(rclcpp::Node & node, const bool verbose) verbose_{verbose} { processing_time_.emplace("total_time", 0.0); + debug_publisher_ptr_ = std::make_unique(&node, "behavior_planner_manager/debug"); } BehaviorModuleOutput PlannerManager::run(const std::shared_ptr & data) @@ -884,6 +886,8 @@ void PlannerManager::print() const string_stream << std::right << "[" << std::setw(max_string_num + 1) << std::left << t.first << ":" << std::setw(4) << std::right << t.second << "ms]\n" << std::setw(21); + std::string name = std::string("processing_time/") + t.first; + debug_publisher_ptr_->publish(name, t.second); } RCLCPP_INFO_STREAM(logger_, string_stream.str()); From 05f8ad257db57b0ee700783b8f532f0a1d08ac53 Mon Sep 17 00:00:00 2001 From: ito-san <57388357+ito-san@users.noreply.github.com> Date: Wed, 27 Sep 2023 00:03:16 +0900 Subject: [PATCH 334/547] fix(ntp_monitor): move chronyc command execution to a timer (#4634) * fix(ntp_monitor): move chronyc command execution to a timer Signed-off-by: ito-san * add newly added parameter timeout to config --------- Signed-off-by: ito-san Co-authored-by: Hiroki OTA --- .../config/ntp_monitor.param.yaml | 1 + .../ntp_monitor/ntp_monitor.hpp | 23 +++++ .../src/ntp_monitor/ntp_monitor.cpp | 93 ++++++++++++++++--- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/system/system_monitor/config/ntp_monitor.param.yaml b/system/system_monitor/config/ntp_monitor.param.yaml index db54f70d1ce59..5f46821f98aff 100644 --- a/system/system_monitor/config/ntp_monitor.param.yaml +++ b/system/system_monitor/config/ntp_monitor.param.yaml @@ -3,3 +3,4 @@ server: ntp.nict.jp offset_warn: 0.1 offset_error: 5.0 + timeout: 5 # The chronyc execution timeout will trigger a warning when this timer expires. diff --git a/system/system_monitor/include/system_monitor/ntp_monitor/ntp_monitor.hpp b/system/system_monitor/include/system_monitor/ntp_monitor/ntp_monitor.hpp index f4ee2de666c22..db264d4b7eda2 100644 --- a/system/system_monitor/include/system_monitor/ntp_monitor/ntp_monitor.hpp +++ b/system/system_monitor/include/system_monitor/ntp_monitor/ntp_monitor.hpp @@ -53,6 +53,16 @@ class NTPMonitor : public rclcpp::Node void checkOffset( diagnostic_updater::DiagnosticStatusWrapper & stat); // NOLINT(runtime/references) + /** + * @brief Timer callback to execute chronyc command + */ + void onTimer(); + + /** + * @brief Timeout callback function for executing chronyc + */ + void onTimeout(); + /** * @brief function to execute chronyc * @param [out] outOffset offset value of NTP time @@ -71,6 +81,19 @@ class NTPMonitor : public rclcpp::Node float offset_warn_; //!< @brief NTP offset(sec) to generate warning float offset_error_; //!< @brief NTP offset(sec) to generate error + int timeout_; //!< @brief Timeout duration for executing chronyc + + rclcpp::TimerBase::SharedPtr timer_; //!< @brief Timer to execute chronyc command + rclcpp::CallbackGroup::SharedPtr timer_callback_group_; //!< @brief Callback Group + std::mutex mutex_; //!< @brief Mutex for output from chronyc command + std::string error_str_; //!< @brief Error string + std::string pipe2_err_str_; //!< @brief Error string regarding pipe2 function call + float offset_; //!< @brief Offset value of NTP time + std::map tracking_map_; //!< @brief Output of chronyc tracking + double elapsed_ms_; //!< @brief Execution time of chronyc command + rclcpp::TimerBase::SharedPtr timeout_timer_; //!< @brief Timeout for executing chronyc + std::mutex timeout_mutex_; //!< @brief Mutex regarding timeout for executing chronyc + bool timeout_expired_; //!< @brief Timeout for executing chronyc has expired or not /** * @brief NTP offset status messages diff --git a/system/system_monitor/src/ntp_monitor/ntp_monitor.cpp b/system/system_monitor/src/ntp_monitor/ntp_monitor.cpp index 6ccf7a54b940f..0b91b28b2b5f9 100644 --- a/system/system_monitor/src/ntp_monitor/ntp_monitor.cpp +++ b/system/system_monitor/src/ntp_monitor/ntp_monitor.cpp @@ -19,7 +19,7 @@ #include "system_monitor/ntp_monitor/ntp_monitor.hpp" -#include "system_monitor/system_monitor_utility.hpp" +#include #include #include @@ -37,8 +37,12 @@ NTPMonitor::NTPMonitor(const rclcpp::NodeOptions & options) : Node("ntp_monitor", options), updater_(this), offset_warn_(declare_parameter("offset_warn", 0.1)), - offset_error_(declare_parameter("offset_error", 5.0)) + offset_error_(declare_parameter("offset_error", 5.0)), + timeout_(declare_parameter("timeout", 5)), + timeout_expired_(false) { + using namespace std::literals::chrono_literals; + gethostname(hostname_, sizeof(hostname_)); // Check if command exists @@ -47,18 +51,15 @@ NTPMonitor::NTPMonitor(const rclcpp::NodeOptions & options) updater_.setHardwareID(hostname_); updater_.add("NTP Offset", this, &NTPMonitor::checkOffset); -} -void NTPMonitor::update() -{ - updater_.force_update(); + // Start timer to execute top command + timer_callback_group_ = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); + timer_ = rclcpp::create_timer( + this, get_clock(), 1s, std::bind(&NTPMonitor::onTimer, this), timer_callback_group_); } void NTPMonitor::checkOffset(diagnostic_updater::DiagnosticStatusWrapper & stat) { - // Remember start time to measure elapsed time - const auto t_start = SystemMonitorUtility::startMeasurement(); - if (!chronyc_exists_) { stat.summary(DiagStatus::ERROR, "chronyc error"); stat.add( @@ -70,7 +71,18 @@ void NTPMonitor::checkOffset(diagnostic_updater::DiagnosticStatusWrapper & stat) std::string pipe2_err_str; float offset = 0.0f; std::map tracking_map; - error_str = executeChronyc(offset, tracking_map, pipe2_err_str); + double elapsed_ms; + + // thread-safe copy + { + std::lock_guard lock(mutex_); + error_str = error_str_; + pipe2_err_str = pipe2_err_str_; + offset = offset_; + tracking_map = tracking_map_; + elapsed_ms = elapsed_ms_; + } + if (!pipe2_err_str.empty()) { stat.summary(DiagStatus::ERROR, "pipe2 error"); stat.add("pipe2", pipe2_err_str); @@ -94,10 +106,65 @@ void NTPMonitor::checkOffset(diagnostic_updater::DiagnosticStatusWrapper & stat) for (auto itr = tracking_map.begin(); itr != tracking_map.end(); ++itr) { stat.add(itr->first, itr->second); } - stat.summary(level, offset_dict_.at(level)); - // Measure elapsed time since start time and report - SystemMonitorUtility::stopMeasurement(t_start, stat); + // Check timeout has expired regarding executing chronyc + bool timeout_expired = false; + { + std::lock_guard lock(timeout_mutex_); + timeout_expired = timeout_expired_; + } + + if (!timeout_expired) { + stat.summary(level, offset_dict_.at(level)); + } else { + stat.summary(DiagStatus::WARN, "chronyc timeout expired"); + } + + stat.addf("execution time", "%f ms", elapsed_ms); +} + +void NTPMonitor::onTimer() +{ + // Start to measure elapsed time + tier4_autoware_utils::StopWatch stop_watch; + stop_watch.tic("execution_time"); + + std::string error_str; + std::string pipe2_err_str; + float offset = 0.0f; + std::map tracking_map; + + // Start timeout timer for executing chronyc + { + std::lock_guard lock(timeout_mutex_); + timeout_expired_ = false; + } + timeout_timer_ = rclcpp::create_timer( + this, get_clock(), std::chrono::seconds(timeout_), std::bind(&NTPMonitor::onTimeout, this)); + + error_str = executeChronyc(offset, tracking_map, pipe2_err_str); + + // Returning from chronyc, stop timeout timer + timeout_timer_->cancel(); + + const double elapsed_ms = stop_watch.toc("execution_time"); + + // thread-safe copy + { + std::lock_guard lock(mutex_); + error_str_ = error_str; + pipe2_err_str_ = pipe2_err_str; + offset_ = offset; + tracking_map_ = tracking_map; + elapsed_ms_ = elapsed_ms; + } +} + +void NTPMonitor::onTimeout() +{ + RCLCPP_WARN(get_logger(), "Timeout occurred."); + std::lock_guard lock(timeout_mutex_); + timeout_expired_ = true; } std::string NTPMonitor::executeChronyc( From 79d76cac890d9ab969285eca85feb10ca52edb39 Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Tue, 26 Sep 2023 19:22:18 +0200 Subject: [PATCH 335/547] build(landmark_tf_caster): add missing libopencv-dev dependency (#5138) Signed-off-by: Esteve Fernandez --- .../landmark_tf_caster/CMakeLists.txt | 6 ------ .../landmark_based_localizer/landmark_tf_caster/package.xml | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt b/localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt index 47899d716e411..8d8cb546b6162 100644 --- a/localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt +++ b/localization/landmark_based_localizer/landmark_tf_caster/CMakeLists.txt @@ -12,16 +12,10 @@ endif() find_package(ament_cmake_auto REQUIRED) ament_auto_find_build_dependencies() -find_package(OpenCV REQUIRED) - ament_auto_add_executable(landmark_tf_caster src/landmark_tf_caster_node.cpp src/landmark_tf_caster_core.cpp ) -target_include_directories(landmark_tf_caster - SYSTEM PUBLIC - ${OpenCV_INCLUDE_DIRS} -) ament_auto_package( INSTALL_TO_SHARE diff --git a/localization/landmark_based_localizer/landmark_tf_caster/package.xml b/localization/landmark_based_localizer/landmark_tf_caster/package.xml index 9740e26e2d04a..64d81f744ce47 100644 --- a/localization/landmark_based_localizer/landmark_tf_caster/package.xml +++ b/localization/landmark_based_localizer/landmark_tf_caster/package.xml @@ -14,6 +14,7 @@ autoware_auto_mapping_msgs geometry_msgs lanelet2_extension + libopencv-dev rclcpp tf2_eigen tf2_geometry_msgs From 11206eb5d03157a880b56f2afa4841b7cdf4fe77 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 27 Sep 2023 07:53:34 +0900 Subject: [PATCH 336/547] feat(dynamic_avoidance): add max obstacle vel (#5142) * feat(dynamic_avoidance): add max obstacle vel Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/dynamic_avoidance/dynamic_avoidance.param.yaml | 1 + .../dynamic_avoidance/dynamic_avoidance_module.hpp | 1 + .../dynamic_avoidance/dynamic_avoidance_module.cpp | 6 ++++-- .../src/scene_module/dynamic_avoidance/manager.cpp | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml b/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml index fe30397683494..127b70fbf7bcb 100644 --- a/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml +++ b/planning/behavior_path_planner/config/dynamic_avoidance/dynamic_avoidance.param.yaml @@ -16,6 +16,7 @@ motorcycle: true pedestrian: false + max_obstacle_vel: 100.0 # [m/s] min_obstacle_vel: 0.0 # [m/s] successive_num_to_entry_dynamic_avoidance_condition: 5 diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp index b00818f2f79d1..ab8e3cfd70dff 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp @@ -56,6 +56,7 @@ struct DynamicAvoidanceParameters bool avoid_bicycle{false}; bool avoid_motorcycle{false}; bool avoid_pedestrian{false}; + double max_obstacle_vel{0.0}; double min_obstacle_vel{0.0}; int successive_num_to_entry_dynamic_avoidance_condition{0}; int successive_num_to_exit_dynamic_avoidance_condition{0}; diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index 181b9fbfd5260..24c074bcb629b 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -411,10 +411,12 @@ void DynamicAvoidanceModule::updateTargetObjects() continue; } - // 1.b. check if velocity is large enough + // 1.b. check obstacle velocity const auto [obj_tangent_vel, obj_normal_vel] = projectObstacleVelocityToTrajectory(prev_module_path->points, predicted_object); - if (std::abs(obj_tangent_vel) < parameters_->min_obstacle_vel) { + if ( + std::abs(obj_tangent_vel) < parameters_->min_obstacle_vel || + parameters_->max_obstacle_vel < std::abs(obj_tangent_vel)) { continue; } diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp index 616194ea66ac7..6f624a9f6f017 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/manager.cpp @@ -47,6 +47,7 @@ DynamicAvoidanceModuleManager::DynamicAvoidanceModuleManager( p.avoid_bicycle = node->declare_parameter(ns + "bicycle"); p.avoid_motorcycle = node->declare_parameter(ns + "motorcycle"); p.avoid_pedestrian = node->declare_parameter(ns + "pedestrian"); + p.max_obstacle_vel = node->declare_parameter(ns + "max_obstacle_vel"); p.min_obstacle_vel = node->declare_parameter(ns + "min_obstacle_vel"); p.successive_num_to_entry_dynamic_avoidance_condition = node->declare_parameter(ns + "successive_num_to_entry_dynamic_avoidance_condition"); @@ -136,6 +137,7 @@ void DynamicAvoidanceModuleManager::updateModuleParams( updateParam(parameters, ns + "motorcycle", p->avoid_motorcycle); updateParam(parameters, ns + "pedestrian", p->avoid_pedestrian); + updateParam(parameters, ns + "max_obstacle_vel", p->max_obstacle_vel); updateParam(parameters, ns + "min_obstacle_vel", p->min_obstacle_vel); updateParam( From a596ce2466be35b44a0a98cd73b622a7d22c3c6a Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 27 Sep 2023 09:30:57 +0900 Subject: [PATCH 337/547] fix(behavior_path_planner): add empty guard to combinePath (#5147) Signed-off-by: kosuke55 --- .../utils/goal_planner/util.hpp | 2 -- .../utils/lane_change/utils.hpp | 2 -- .../utils/path_utils.hpp | 2 ++ .../utils/start_planner/util.hpp | 1 - .../src/scene_module/lane_change/normal.cpp | 2 +- .../src/utils/goal_planner/util.cpp | 10 --------- .../src/utils/lane_change/utils.cpp | 13 +----------- .../src/utils/path_utils.cpp | 21 +++++++++++++++++++ .../start_planner/freespace_pull_out.cpp | 3 +-- .../start_planner/geometric_pull_out.cpp | 4 ++-- .../utils/start_planner/shift_pull_out.cpp | 1 - .../src/utils/start_planner/util.cpp | 13 ------------ 12 files changed, 28 insertions(+), 46 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp index b7a9248d569c1..396798ee18519 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp @@ -44,8 +44,6 @@ using geometry_msgs::msg::Twist; using visualization_msgs::msg::Marker; using visualization_msgs::msg::MarkerArray; -// TODO(sugahara) move to util -PathWithLaneId combineReferencePath(const PathWithLaneId & path1, const PathWithLaneId & path2); lanelet::ConstLanelets getPullOverLanes( const RouteHandler & route_handler, const bool left_side, const double backward_distance, const double forward_distance); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index a4c2c2e695752..adb180065f476 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -88,8 +88,6 @@ std::vector> getSortedLaneIds( const RouteHandler & route_handler, const Pose & current_pose, const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes); -PathWithLaneId combineReferencePath(const PathWithLaneId & path1, const PathWithLaneId & path2); - lanelet::ConstLanelets getTargetPreferredLanes( const RouteHandler & route_handler, const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, const Direction & direction, diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp index e907df8c7a58a..b42502d9edec8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_utils.hpp @@ -102,6 +102,8 @@ PathWithLaneId calcCenterLinePath( const std::shared_ptr & planner_data, const Pose & ref_pose, const double longest_dist_to_shift_line, const std::optional & prev_module_path = std::nullopt); + +PathWithLaneId combinePath(const PathWithLaneId & path1, const PathWithLaneId & path2); } // namespace behavior_path_planner::utils #endif // BEHAVIOR_PATH_PLANNER__UTILS__PATH_UTILS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp index 09fb9911f7386..f2dba684de991 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp @@ -44,7 +44,6 @@ using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; using route_handler::RouteHandler; -PathWithLaneId combineReferencePath(const PathWithLaneId path1, const PathWithLaneId path2); PathWithLaneId getBackwardPath( const RouteHandler & route_handler, const lanelet::ConstLanelets & target_lanes, const Pose & current_pose, const Pose & backed_pose, const double velocity); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index d1de12a3bd5f9..44eb74ed90f76 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -125,7 +125,7 @@ BehaviorModuleOutput NormalLaneChange::generateOutput() const auto found_extended_path = extendPath(); if (found_extended_path) { - *output.path = utils::lane_change::combineReferencePath(*output.path, *found_extended_path); + *output.path = utils::combinePath(*output.path, *found_extended_path); } extendOutputDrivableArea(output); output.reference_path = std::make_shared(getReferencePath()); diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index 4f24d1b682f3d..038ca76a7fffd 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -44,16 +44,6 @@ namespace behavior_path_planner { namespace goal_planner_utils { -PathWithLaneId combineReferencePath(const PathWithLaneId & path1, const PathWithLaneId & path2) -{ - PathWithLaneId path; - path.points.insert(path.points.end(), path1.points.begin(), path1.points.end()); - - // skip overlapping point - path.points.insert(path.points.end(), next(path2.points.begin()), path2.points.end()); - - return path; -} lanelet::ConstLanelets getPullOverLanes( const RouteHandler & route_handler, const bool left_side, const double backward_distance, diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index e4fbf41e5af00..01d6cf4acae88 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -180,17 +180,6 @@ std::vector getAccelerationValues( return sampled_values; } -PathWithLaneId combineReferencePath(const PathWithLaneId & path1, const PathWithLaneId & path2) -{ - PathWithLaneId path; - path.points.insert(path.points.end(), path1.points.begin(), path1.points.end()); - - // skip overlapping point - path.points.insert(path.points.end(), next(path2.points.begin()), path2.points.end()); - - return path; -} - lanelet::ConstLanelets getTargetPreferredLanes( const RouteHandler & route_handler, const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, const Direction & direction, @@ -355,7 +344,7 @@ std::optional constructCandidatePath( return std::nullopt; } - candidate_path.path = combineReferencePath(prepare_segment, shifted_path.path); + candidate_path.path = utils::combinePath(prepare_segment, shifted_path.path); candidate_path.shifted_path = shifted_path; return std::optional{candidate_path}; diff --git a/planning/behavior_path_planner/src/utils/path_utils.cpp b/planning/behavior_path_planner/src/utils/path_utils.cpp index 7673c9a1de9b4..509b31ad3da2b 100644 --- a/planning/behavior_path_planner/src/utils/path_utils.cpp +++ b/planning/behavior_path_planner/src/utils/path_utils.cpp @@ -561,4 +561,25 @@ PathWithLaneId calcCenterLinePath( return centerline_path; } + +PathWithLaneId combinePath(const PathWithLaneId & path1, const PathWithLaneId & path2) +{ + if (path1.points.empty()) { + return path2; + } + if (path2.points.empty()) { + return path1; + } + + PathWithLaneId path{}; + path.points.insert(path.points.end(), path1.points.begin(), path1.points.end()); + + // skip overlapping point + path.points.insert(path.points.end(), next(path2.points.begin()), path2.points.end()); + + PathWithLaneId filtered_path = path; + filtered_path.points = motion_utils::removeOverlapPoints(filtered_path.points); + return filtered_path; +} + } // namespace behavior_path_planner::utils diff --git a/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp index 8e5d6b7545c1d..945c386668dee 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/freespace_pull_out.cpp @@ -97,8 +97,7 @@ boost::optional FreespacePullOut::plan(const Pose & start_pose, con const auto road_center_line_path = route_handler->getCenterLinePath(road_lanes, s_start, s_end); last_path = utils::resamplePathWithSpline( - start_planner_utils::combineReferencePath(last_path, road_center_line_path), - parameters_.center_line_path_interval); + utils::combinePath(last_path, road_center_line_path), parameters_.center_line_path_interval); const double original_terminal_velocity = last_path.points.back().point.longitudinal_velocity_mps; utils::correctDividedPathVelocity(partial_paths); diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index 5472885359c31..bc071e2a2a288 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -15,6 +15,7 @@ #include "behavior_path_planner/utils/start_planner/geometric_pull_out.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" +#include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "behavior_path_planner/utils/utils.hpp" @@ -26,7 +27,6 @@ using tier4_autoware_utils::calcDistance2d; using tier4_autoware_utils::calcOffsetPose; namespace behavior_path_planner { -using start_planner_utils::combineReferencePath; using start_planner_utils::getPullOutLanes; GeometricPullOut::GeometricPullOut(rclcpp::Node & node, const StartPlannerParameters & parameters) @@ -106,7 +106,7 @@ boost::optional GeometricPullOut::plan(const Pose & start_pose, con std::make_pair(velocity, velocity * velocity / (2 * arc_length_on_second_arc_path))); } else { const auto partial_paths = planner_.getPaths(); - const auto combined_path = combineReferencePath(partial_paths.at(0), partial_paths.at(1)); + const auto combined_path = utils::combinePath(partial_paths.at(0), partial_paths.at(1)); output.partial_paths.push_back(combined_path); // Calculate the acceleration required to reach the forward parking velocity at the center of diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index 2de72d781994a..b82157cc17e41 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -32,7 +32,6 @@ using tier4_autoware_utils::calcDistance2d; using tier4_autoware_utils::calcOffsetPose; namespace behavior_path_planner { -using start_planner_utils::combineReferencePath; using start_planner_utils::getPullOutLanes; ShiftPullOut::ShiftPullOut( diff --git a/planning/behavior_path_planner/src/utils/start_planner/util.cpp b/planning/behavior_path_planner/src/utils/start_planner/util.cpp index f5b4c9b979ee3..40b100a1bc070 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/util.cpp @@ -38,19 +38,6 @@ namespace behavior_path_planner::start_planner_utils { -PathWithLaneId combineReferencePath(const PathWithLaneId path1, const PathWithLaneId path2) -{ - PathWithLaneId path; - path.points.insert(path.points.end(), path1.points.begin(), path1.points.end()); - - // skip overlapping point - path.points.insert(path.points.end(), next(path2.points.begin()), path2.points.end()); - - PathWithLaneId filtered_path = path; - filtered_path.points = motion_utils::removeOverlapPoints(filtered_path.points); - return filtered_path; -} - PathWithLaneId getBackwardPath( const RouteHandler & route_handler, const lanelet::ConstLanelets & shoulder_lanes, const Pose & current_pose, const Pose & backed_pose, const double velocity) From 5715e44352511f3c06ef364bd0294aa987b39d51 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:29:30 +0900 Subject: [PATCH 338/547] feat(behavior_path_planner): lane change collided polygon intersect lanes (#5135) * feat(behavior_path_planner): lane change collided polygon intersect lanes Signed-off-by: Muhammad Zulfaqar Azmi * use wrapper instead of std::optional Signed-off-by: Muhammad Zulfaqar Azmi * rearrange code Signed-off-by: Muhammad Zulfaqar Azmi * Revert "chore: add workaround for tf2 update (#5127)" This reverts commit 19ab508c3d590101d8fcb9ffca1248adebd895f3. * fix(autoware_auto_tf2): remove tf2 geometry function duplicated in tf2 geometry msgs Signed-off-by: wep21 * revet change for local build Signed-off-by: kyoichi-sugahara --------- Signed-off-by: Muhammad Zulfaqar Azmi Signed-off-by: wep21 Signed-off-by: kyoichi-sugahara Co-authored-by: wep21 Co-authored-by: kyoichi-sugahara --- .../utils/lane_change/utils.hpp | 3 ++ .../path_safety_checker/safety_check.hpp | 23 +++++++++++- .../src/scene_module/lane_change/normal.cpp | 35 +++++++++++++------ .../src/utils/lane_change/utils.cpp | 12 +++++++ .../path_safety_checker/safety_check.cpp | 24 ++++++++++--- 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index adb180065f476..8a78d2665fa8f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -183,5 +183,8 @@ std::optional createPolygon( ExtendedPredictedObject transform( const PredictedObject & object, const BehaviorPathPlannerParameters & common_parameters, const LaneChangeParameters & lane_change_parameters); + +bool isCollidedPolygonsInLanelet( + const std::vector & collided_polygons, const lanelet::ConstLanelets & lanes); } // namespace behavior_path_planner::utils::lane_change #endif // BEHAVIOR_PATH_PLANNER__UTILS__LANE_CHANGE__UTILS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index f9fbf70b2113b..9cc4afe22bdce 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -88,7 +88,6 @@ boost::optional calcInterpolatedPoseWithVelocity( boost::optional getInterpolatedPoseWithVelocityAndPolygonStamped( const std::vector & pred_path, const double current_time, const VehicleInfo & ego_info); - /** * @brief Iterate the points in the ego and target's predicted path and * perform safety check for each of the iterated points. @@ -111,6 +110,28 @@ bool checkCollision( const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, const double hysteresis_factor, CollisionCheckDebug & debug); +/** + * @brief Iterate the points in the ego and target's predicted path and + * perform safety check for each of the iterated points. + * @param planned_path The predicted path of the ego vehicle. + * @param predicted_ego_path Ego vehicle's predicted path + * @param ego_current_velocity Current velocity of the ego vehicle. + * @param target_object The predicted object to check collision with. + * @param target_object_path The predicted path of the target object. + * @param common_parameters The common parameters used in behavior path planner. + * @param front_object_deceleration The deceleration of the object in the front.(used in RSS) + * @param rear_object_deceleration The deceleration of the object in the rear.(used in RSS) + * @param debug The debug information for collision checking. + * @return a list of polygon when collision is expected. + */ +std::vector getCollidedPolygons( + const PathWithLaneId & planned_path, + const std::vector & predicted_ego_path, + const ExtendedPredictedObject & target_object, + const PredictedPathWithPolygon & target_object_path, + const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, + const double hysteresis_factor, CollisionCheckDebug & debug); + /** * @brief Check collision between ego path footprints with extra longitudinal stopping margin and * objects. diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 44eb74ed90f76..1bcd3e6b0cdcd 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1454,18 +1454,31 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( obj, lane_change_parameters_->use_all_predicted_path); for (const auto & obj_path : obj_predicted_paths) { - if (!utils::path_safety_checker::checkCollision( - path, ego_predicted_path, obj, obj_path, common_parameters, rss_params, 1.0, - current_debug_data.second)) { - path_safety_status.is_safe = false; - marker_utils::updateCollisionCheckDebugMap( - debug_data, current_debug_data, path_safety_status.is_safe); - const auto & obj_pose = obj.initial_pose.pose; - const auto obj_polygon = tier4_autoware_utils::toPolygon2d(obj_pose, obj.shape); - path_safety_status.is_object_coming_from_rear |= - !utils::path_safety_checker::isTargetObjectFront( - path, current_pose, common_parameters.vehicle_info, obj_polygon); + const auto collided_polygons = utils::path_safety_checker::getCollidedPolygons( + path, ego_predicted_path, obj, obj_path, common_parameters, rss_params, 1.0, + current_debug_data.second); + + if (collided_polygons.empty()) { + continue; } + + const auto collision_in_current_lanes = utils::lane_change::isCollidedPolygonsInLanelet( + collided_polygons, lane_change_path.info.current_lanes); + const auto collision_in_target_lanes = utils::lane_change::isCollidedPolygonsInLanelet( + collided_polygons, lane_change_path.info.target_lanes); + + if (!collision_in_current_lanes && !collision_in_target_lanes) { + continue; + } + + path_safety_status.is_safe = false; + marker_utils::updateCollisionCheckDebugMap( + debug_data, current_debug_data, path_safety_status.is_safe); + const auto & obj_pose = obj.initial_pose.pose; + const auto obj_polygon = tier4_autoware_utils::toPolygon2d(obj_pose, obj.shape); + path_safety_status.is_object_coming_from_rear |= + !utils::path_safety_checker::isTargetObjectFront( + path, current_pose, common_parameters.vehicle_info, obj_polygon); } marker_utils::updateCollisionCheckDebugMap( debug_data, current_debug_data, path_safety_status.is_safe); diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index 01d6cf4acae88..0ae599a74e439 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -1092,4 +1092,16 @@ ExtendedPredictedObject transform( return extended_object; } + +bool isCollidedPolygonsInLanelet( + const std::vector & collided_polygons, const lanelet::ConstLanelets & lanes) +{ + const auto lanes_polygon = createPolygon(lanes, 0.0, std::numeric_limits::max()); + + const auto is_in_lanes = [&](const auto & collided_polygon) { + return lanes_polygon && boost::geometry::intersects(lanes_polygon.value(), collided_polygon); + }; + + return std::any_of(collided_polygons.begin(), collided_polygons.end(), is_in_lanes); +} } // namespace behavior_path_planner::utils::lane_change diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 9fb7d0acdf432..4e671f9f4b599 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -258,6 +258,20 @@ boost::optional getInterpolatedPoseWithVeloci } bool checkCollision( + const PathWithLaneId & planned_path, + const std::vector & predicted_ego_path, + const ExtendedPredictedObject & target_object, + const PredictedPathWithPolygon & target_object_path, + const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, + const double hysteresis_factor, CollisionCheckDebug & debug) +{ + const auto collided_polygons = getCollidedPolygons( + planned_path, predicted_ego_path, target_object, target_object_path, common_parameters, + rss_parameters, hysteresis_factor, debug); + return collided_polygons.empty(); +} + +std::vector getCollidedPolygons( [[maybe_unused]] const PathWithLaneId & planned_path, const std::vector & predicted_ego_path, const ExtendedPredictedObject & target_object, @@ -271,6 +285,8 @@ bool checkCollision( debug.current_obj_pose = target_object.initial_pose.pose; } + std::vector collided_polygons{}; + collided_polygons.reserve(target_object_path.path.size()); for (const auto & obj_pose_with_poly : target_object_path.path) { const auto & current_time = obj_pose_with_poly.time; @@ -302,7 +318,8 @@ bool checkCollision( // check overlap if (boost::geometry::overlaps(ego_polygon, obj_polygon)) { debug.unsafe_reason = "overlap_polygon"; - return false; + collided_polygons.push_back(obj_polygon); + continue; } // compute which one is at the front of the other @@ -341,13 +358,12 @@ bool checkCollision( // check overlap with extended polygon if (boost::geometry::overlaps(extended_ego_polygon, extended_obj_polygon)) { debug.unsafe_reason = "overlap_extended_polygon"; - return false; + collided_polygons.push_back(obj_polygon); } } - return true; + return collided_polygons; } - bool checkCollisionWithExtraStoppingMargin( const PathWithLaneId & ego_path, const PredictedObjects & dynamic_objects, const double base_to_front, const double base_to_rear, const double width, From 7fdda423eb363f05625f7b26ac67b322cb8473a5 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 27 Sep 2023 10:44:28 +0900 Subject: [PATCH 339/547] feat(motion_utils): add debug backtrace print (#5101) * feat(motion_utils): add debug backtrace print Signed-off-by: Takamasa Horibe * fix style Signed-off-by: Takamasa Horibe * move implementation to cpp Signed-off-by: Takamasa Horibe * fix style Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../motion_utils/trajectory/trajectory.hpp | 10 ++++ common/tier4_autoware_utils/CMakeLists.txt | 1 + .../tier4_autoware_utils/system/backtrace.hpp | 30 +---------- .../src/system/backtrace.cpp | 52 +++++++++++++++++++ 4 files changed, 64 insertions(+), 29 deletions(-) create mode 100644 common/tier4_autoware_utils/src/system/backtrace.cpp diff --git a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp index 94cf48f29c57a..dcdefe61e4000 100644 --- a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp @@ -18,6 +18,7 @@ #include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/geometry/pose_deviation.hpp" #include "tier4_autoware_utils/math/constants.hpp" +#include "tier4_autoware_utils/system/backtrace.hpp" #include @@ -43,6 +44,7 @@ template void validateNonEmpty(const T & points) { if (points.empty()) { + tier4_autoware_utils::print_backtrace(); throw std::invalid_argument("Points is empty."); } } @@ -80,6 +82,7 @@ void validateNonSharpAngle( constexpr double epsilon = 1e-3; if (std::cos(angle_threshold) < product / dist_1to2 / dist_3to2 + epsilon) { + tier4_autoware_utils::print_backtrace(); throw std::invalid_argument("Sharp angle."); } } @@ -396,6 +399,7 @@ double calcLongitudinalOffsetToSegment( { if (seg_idx >= points.size() - 1) { const std::out_of_range e("Segment index is invalid."); + tier4_autoware_utils::print_backtrace(); if (throw_exception) { throw e; } @@ -418,6 +422,7 @@ double calcLongitudinalOffsetToSegment( if (seg_idx >= overlap_removed_points.size() - 1) { const std::runtime_error e("Same points are given."); + tier4_autoware_utils::print_backtrace(); if (throw_exception) { throw e; } @@ -574,6 +579,7 @@ double calcLateralOffset( if (overlap_removed_points.size() == 1) { const std::runtime_error e("Same points are given."); + tier4_autoware_utils::print_backtrace(); if (throw_exception) { throw e; } @@ -635,6 +641,7 @@ double calcLateralOffset( if (overlap_removed_points.size() == 1) { const std::runtime_error e("Same points are given."); + tier4_autoware_utils::print_backtrace(); if (throw_exception) { throw e; } @@ -1037,6 +1044,7 @@ boost::optional calcLongitudinalOffsetPoint( if (points.size() - 1 < src_idx) { const auto e = std::out_of_range("Invalid source index"); + tier4_autoware_utils::print_backtrace(); if (throw_exception) { throw e; } @@ -1161,6 +1169,7 @@ boost::optional calcLongitudinalOffsetPose( if (points.size() - 1 < src_idx) { const auto e = std::out_of_range("Invalid source index"); + tier4_autoware_utils::print_backtrace(); if (throw_exception) { throw e; } @@ -2375,6 +2384,7 @@ double calcYawDeviation( if (overlap_removed_points.size() <= 1) { const std::runtime_error e("points size is less than 2"); + tier4_autoware_utils::print_backtrace(); if (throw_exception) { throw e; } diff --git a/common/tier4_autoware_utils/CMakeLists.txt b/common/tier4_autoware_utils/CMakeLists.txt index 938a78692d9a1..f0a548754a10c 100644 --- a/common/tier4_autoware_utils/CMakeLists.txt +++ b/common/tier4_autoware_utils/CMakeLists.txt @@ -14,6 +14,7 @@ ament_auto_add_library(tier4_autoware_utils SHARED src/math/trigonometry.cpp src/ros/msg_operation.cpp src/ros/marker_helper.cpp + src/system/backtrace.cpp ) if(BUILD_TESTING) diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp index b0b526e8c8b9c..c784d71ad5060 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/system/backtrace.hpp @@ -15,38 +15,10 @@ #ifndef TIER4_AUTOWARE_UTILS__SYSTEM__BACKTRACE_HPP_ #define TIER4_AUTOWARE_UTILS__SYSTEM__BACKTRACE_HPP_ -#include - -#include -#include -#include -#include - namespace tier4_autoware_utils { -inline void print_backtrace() -{ - constexpr size_t max_frames = 100; - void * addrlist[max_frames + 1]; - - int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void *)); - - if (addrlen == 0) { - return; - } - - char ** symbol_list = backtrace_symbols(addrlist, addrlen); - - std::stringstream ss; - ss << " ********** back trace **********" << std::endl; - for (int i = 1; i < addrlen; i++) { - ss << " @ " << symbol_list[i] << std::endl; - } - std::cerr << ss.str() << std::endl; - - free(symbol_list); -} +void print_backtrace(); } // namespace tier4_autoware_utils diff --git a/common/tier4_autoware_utils/src/system/backtrace.cpp b/common/tier4_autoware_utils/src/system/backtrace.cpp new file mode 100644 index 0000000000000..343f200296cad --- /dev/null +++ b/common/tier4_autoware_utils/src/system/backtrace.cpp @@ -0,0 +1,52 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tier4_autoware_utils/system/backtrace.hpp" + +#include "rclcpp/rclcpp.hpp" + +#include + +#include +#include +#include +#include + +namespace tier4_autoware_utils +{ + +void print_backtrace() +{ + constexpr size_t max_frames = 100; + void * addrlist[max_frames + 1]; + + int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void *)); + + if (addrlen == 0) { + return; + } + + char ** symbol_list = backtrace_symbols(addrlist, addrlen); + + std::stringstream ss; + ss << "\n @ ********** back trace **********" << std::endl; + for (int i = 1; i < addrlen; i++) { + ss << " @ " << symbol_list[i] << std::endl; + } + RCLCPP_DEBUG_STREAM(rclcpp::get_logger("tier4_autoware_utils"), ss.str()); + + free(symbol_list); +} + +} // namespace tier4_autoware_utils From bb89363a631164aefbebb6dbf8698d8149b703ed Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 27 Sep 2023 12:46:54 +0900 Subject: [PATCH 340/547] feat(logger_level_configure): add util to set logger level externally in runtime (#5131) * add logger_level_configure Signed-off-by: Takamasa Horibe * add usage Signed-off-by: Takamasa Horibe * fix spell Signed-off-by: Takamasa Horibe * fix from reviewer comment Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- common/tier4_autoware_utils/CMakeLists.txt | 1 + .../ros/logger_level_configure.hpp | 68 +++++++++++++++++++ common/tier4_autoware_utils/package.xml | 1 + .../src/ros/logger_level_configure.cpp | 61 +++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 common/tier4_autoware_utils/include/tier4_autoware_utils/ros/logger_level_configure.hpp create mode 100644 common/tier4_autoware_utils/src/ros/logger_level_configure.cpp diff --git a/common/tier4_autoware_utils/CMakeLists.txt b/common/tier4_autoware_utils/CMakeLists.txt index f0a548754a10c..9cb54e52362a5 100644 --- a/common/tier4_autoware_utils/CMakeLists.txt +++ b/common/tier4_autoware_utils/CMakeLists.txt @@ -14,6 +14,7 @@ ament_auto_add_library(tier4_autoware_utils SHARED src/math/trigonometry.cpp src/ros/msg_operation.cpp src/ros/marker_helper.cpp + src/ros/logger_level_configure.cpp src/system/backtrace.cpp ) diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/logger_level_configure.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/logger_level_configure.hpp new file mode 100644 index 0000000000000..5aee3a251dad2 --- /dev/null +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/ros/logger_level_configure.hpp @@ -0,0 +1,68 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// =============== Note =============== +// This is a util class implementation of the logger_config_component provided by ROS 2 +// https://github.com/ros2/demos/blob/humble/logging_demo/src/logger_config_component.cpp +// +// When ROS 2 officially supports the set_logger_level option in release version, this class can be +// removed. +// https://github.com/ros2/ros2/issues/1355 + +// =============== How to use =============== +// ___In your_node.hpp___ +// #include "tier4_autoware_utils/ros/logger_level_configure.hpp" +// class YourNode : public rclcpp::Node { +// ... +// +// // Define logger_configure as a node class member variable +// std::unique_ptr logger_configure_; +// } +// +// ___In your_node.cpp___ +// YourNode::YourNode() { +// ... +// +// // Set up logger_configure +// logger_configure_ = std::make_unique(this); +// } + +#ifndef TIER4_AUTOWARE_UTILS__ROS__LOGGER_LEVEL_CONFIGURE_HPP_ +#define TIER4_AUTOWARE_UTILS__ROS__LOGGER_LEVEL_CONFIGURE_HPP_ + +#include "logging_demo/srv/config_logger.hpp" + +#include + +namespace tier4_autoware_utils +{ +class LoggerLevelConfigure +{ +private: + using ConfigLogger = logging_demo::srv::ConfigLogger; + +public: + explicit LoggerLevelConfigure(rclcpp::Node * node); + +private: + rclcpp::Logger ros_logger_; + rclcpp::Service::SharedPtr srv_config_logger_; + + void onLoggerConfigService( + const ConfigLogger::Request::SharedPtr request, + const ConfigLogger::Response::SharedPtr response); +}; + +} // namespace tier4_autoware_utils +#endif // TIER4_AUTOWARE_UTILS__ROS__LOGGER_LEVEL_CONFIGURE_HPP_ diff --git a/common/tier4_autoware_utils/package.xml b/common/tier4_autoware_utils/package.xml index 6a6b3812cd69b..701907929ecc0 100644 --- a/common/tier4_autoware_utils/package.xml +++ b/common/tier4_autoware_utils/package.xml @@ -20,6 +20,7 @@ diagnostic_msgs geometry_msgs libboost-dev + logging_demo pcl_conversions pcl_ros rclcpp diff --git a/common/tier4_autoware_utils/src/ros/logger_level_configure.cpp b/common/tier4_autoware_utils/src/ros/logger_level_configure.cpp new file mode 100644 index 0000000000000..d764299290d05 --- /dev/null +++ b/common/tier4_autoware_utils/src/ros/logger_level_configure.cpp @@ -0,0 +1,61 @@ +// Copyright 2023 Tier IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" + +#include + +namespace tier4_autoware_utils +{ +LoggerLevelConfigure::LoggerLevelConfigure(rclcpp::Node * node) : ros_logger_(node->get_logger()) +{ + using std::placeholders::_1; + using std::placeholders::_2; + + srv_config_logger_ = node->create_service( + "~/config_logger", std::bind(&LoggerLevelConfigure::onLoggerConfigService, this, _1, _2)); +} + +void LoggerLevelConfigure::onLoggerConfigService( + const ConfigLogger::Request::SharedPtr request, const ConfigLogger::Response::SharedPtr response) +{ + int logging_severity; + const auto ret_level = rcutils_logging_severity_level_from_string( + request->level.c_str(), rcl_get_default_allocator(), &logging_severity); + + if (ret_level != RCUTILS_RET_OK) { + response->success = false; + RCLCPP_WARN_STREAM( + ros_logger_, "Failed to change logger level for " + << request->logger_name + << " due to an invalid logging severity: " << request->level); + return; + } + + const auto ret_set = + rcutils_logging_set_logger_level(request->logger_name.c_str(), logging_severity); + + if (ret_set != RCUTILS_RET_OK) { + response->success = false; + RCLCPP_WARN_STREAM(ros_logger_, "Failed to set logger level for " << request->logger_name); + return; + } + + response->success = true; + RCLCPP_INFO_STREAM( + ros_logger_, "Logger level [" << request->level << "] is set for " << request->logger_name); + return; +} + +} // namespace tier4_autoware_utils From 3731fd08385cd391c30f35a44b9a94dedfc29ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Yavuz=20K=C3=B6seo=C4=9Flu?= Date: Wed, 27 Sep 2023 07:16:55 +0300 Subject: [PATCH 341/547] feat(ndt_scan_matcher): adding exe time to diagnostics for checking runtime_monitor (#4916) * adding exe time to diagnostics * Update localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml Co-authored-by: kminoda <44218668+kminoda@users.noreply.github.com> * order of the parameters in constructor corrected * spell check * Update localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp Co-authored-by: kminoda <44218668+kminoda@users.noreply.github.com> * Update localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml Co-authored-by: Motz <83898149+Motsu-san@users.noreply.github.com> * Update localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp Co-authored-by: Motz <83898149+Motsu-san@users.noreply.github.com> * rebase branch to main * the actual execution time added to diagnostics message * execution_time parameter corrected * integration with header file * style(pre-commit): autofix * parameter naming --------- Co-authored-by: kminoda <44218668+kminoda@users.noreply.github.com> Co-authored-by: Motz <83898149+Motsu-san@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../config/ndt_scan_matcher.param.yaml | 3 +++ .../ndt_scan_matcher/ndt_scan_matcher_core.hpp | 3 +++ .../src/ndt_scan_matcher_core.cpp | 18 ++++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml index c8afa9ea1a916..ecd6c65fa11df 100644 --- a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml +++ b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml @@ -85,3 +85,6 @@ # If lidar_point.z - base_link.z <= this threshold , the point will be removed z_margin_for_ground_removal: 0.8 + + # The execution time which means probably NDT cannot matches scans properly + critical_upper_bound_exe_time_ms: 100 # [ms] diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp index ed3b99019f7d9..011cda6ba3356 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp @@ -208,6 +208,9 @@ class NDTScanMatcher : public rclcpp::Node double z_margin_for_ground_removal_; bool use_dynamic_map_loading_; + + // The execution time which means probably NDT cannot matches scans properly + int critical_upper_bound_exe_time_ms_; }; #endif // NDT_SCAN_MATCHER__NDT_SCAN_MATCHER_CORE_HPP_ diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 7bb2b79e88f33..1d1b829bd5262 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -99,8 +99,9 @@ NDTScanMatcher::NDTScanMatcher() output_pose_covariance_(), regularization_enabled_(declare_parameter("regularization_enabled")), estimate_scores_for_degrounded_scan_( - declare_parameter("estimate_scores_for_degrounded_scan")), - z_margin_for_ground_removal_(declare_parameter("z_margin_for_ground_removal")) + declare_parameter("estimate_scores_for_degrounded_scan", false)), + z_margin_for_ground_removal_(declare_parameter("z_margin_for_ground_removal", 0.8)), + critical_upper_bound_exe_time_ms_(100) { (*state_ptr_)["state"] = "Initializing"; is_activated_ = false; @@ -140,6 +141,11 @@ NDTScanMatcher::NDTScanMatcher() this->declare_parameter("converged_param_nearest_voxel_transformation_likelihood"); lidar_topic_timeout_sec_ = this->declare_parameter("lidar_topic_timeout_sec"); + critical_upper_bound_exe_time_ms_ = + this->declare_parameter("critical_upper_bound_exe_time_ms", critical_upper_bound_exe_time_ms_); + + initial_pose_timeout_sec_ = + this->declare_parameter("initial_pose_timeout_sec", initial_pose_timeout_sec_); initial_pose_timeout_sec_ = this->declare_parameter("initial_pose_timeout_sec"); @@ -292,6 +298,13 @@ void NDTScanMatcher::timer_diagnostic() diag_status_msg.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; diag_status_msg.message += "NDT score is unreliably low. "; } + if ( + state_ptr_->count("execution_time") && + std::stod((*state_ptr_)["execution_time"]) >= critical_upper_bound_exe_time_ms_) { + diag_status_msg.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + diag_status_msg.message += + "NDT exe time is too long. (took " + (*state_ptr_)["execution_time"] + " [ms])"; + } // Ignore local optimal solution if ( state_ptr_->count("is_local_optimal_solution_oscillation") && @@ -528,6 +541,7 @@ void NDTScanMatcher::callback_sensor_points( } else { (*state_ptr_)["is_local_optimal_solution_oscillation"] = "0"; } + (*state_ptr_)["execution_time"] = std::to_string(exe_time); } void NDTScanMatcher::transform_sensor_measurement( From 0373d21a7f7dcbd64e96ebc7a3b08275e391656a Mon Sep 17 00:00:00 2001 From: Daisuke Nishimatsu <42202095+wep21@users.noreply.github.com> Date: Wed, 27 Sep 2023 14:18:55 +0900 Subject: [PATCH 342/547] fix(autoware_auto_tf2): remove tf2 geometry function duplicated in tf2 geometry msgs (#5089) --- common/autoware_auto_geometry/CMakeLists.txt | 6 --- common/autoware_auto_tf2/CMakeLists.txt | 5 -- .../tf2_autoware_auto_msgs.hpp | 51 ------------------- planning/behavior_path_planner/CMakeLists.txt | 6 --- .../surround_obstacle_checker/CMakeLists.txt | 6 --- 5 files changed, 74 deletions(-) diff --git a/common/autoware_auto_geometry/CMakeLists.txt b/common/autoware_auto_geometry/CMakeLists.txt index bf48e8fcaca3f..454e0e7ef044f 100644 --- a/common/autoware_auto_geometry/CMakeLists.txt +++ b/common/autoware_auto_geometry/CMakeLists.txt @@ -12,12 +12,6 @@ ament_auto_add_library(${PROJECT_NAME} SHARED src/bounding_box.cpp ) -if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) - target_compile_definitions(${PROJECT_NAME} PRIVATE - DEFINE_LEGACY_FUNCTION - ) -endif() - if(BUILD_TESTING) set(GEOMETRY_GTEST geometry_gtest) set(GEOMETRY_SRC test/src/test_geometry.cpp diff --git a/common/autoware_auto_tf2/CMakeLists.txt b/common/autoware_auto_tf2/CMakeLists.txt index fe4cec1c0fc89..a8ae9ec2d962e 100755 --- a/common/autoware_auto_tf2/CMakeLists.txt +++ b/common/autoware_auto_tf2/CMakeLists.txt @@ -17,11 +17,6 @@ if(BUILD_TESTING) "tf2_geometry_msgs" "tf2_ros" ) - if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) - target_compile_definitions(test_tf2_autoware_auto_msgs PRIVATE - DEFINE_LEGACY_FUNCTION - ) - endif() endif() ament_auto_package() diff --git a/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp b/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp index ee8d52fa402c0..b35eb6e93ce6e 100644 --- a/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp +++ b/common/autoware_auto_tf2/include/autoware_auto_tf2/tf2_autoware_auto_msgs.hpp @@ -45,57 +45,6 @@ using BoundingBox = autoware_auto_perception_msgs::msg::BoundingBox; namespace tf2 { -#ifdef DEFINE_LEGACY_FUNCTION -/*************/ -/** Point32 **/ -/*************/ - -/** \brief Apply a geometry_msgs TransformStamped to a geometry_msgs Point32 type. - * This function is a specialization of the doTransform template defined in tf2/convert.h. - * \param t_in The point to transform, as a Point32 message. - * \param t_out The transformed point, as a Point32 message. - * \param transform The timestamped transform to apply, as a TransformStamped message. - */ -template <> -inline void doTransform( - const geometry_msgs::msg::Point32 & t_in, geometry_msgs::msg::Point32 & t_out, - const geometry_msgs::msg::TransformStamped & transform) -{ - const KDL::Vector v_out = gmTransformToKDL(transform) * KDL::Vector(t_in.x, t_in.y, t_in.z); - t_out.x = static_cast(v_out[0]); - t_out.y = static_cast(v_out[1]); - t_out.z = static_cast(v_out[2]); -} - -/*************/ -/** Polygon **/ -/*************/ - -/** \brief Apply a geometry_msgs TransformStamped to a geometry_msgs Polygon type. - * This function is a specialization of the doTransform template defined in tf2/convert.h. - * \param t_in The polygon to transform. - * \param t_out The transformed polygon. - * \param transform The timestamped transform to apply, as a TransformStamped message. - */ -template <> -inline void doTransform( - const geometry_msgs::msg::Polygon & t_in, geometry_msgs::msg::Polygon & t_out, - const geometry_msgs::msg::TransformStamped & transform) -{ - // Don't call the Point32 doTransform to avoid doing this conversion every time - const auto kdl_frame = gmTransformToKDL(transform); - // We don't use std::back_inserter to allow aliasing between t_in and t_out - t_out.points.resize(t_in.points.size()); - for (size_t i = 0; i < t_in.points.size(); ++i) { - const KDL::Vector v_out = - kdl_frame * KDL::Vector(t_in.points[i].x, t_in.points[i].y, t_in.points[i].z); - t_out.points[i].x = static_cast(v_out[0]); - t_out.points[i].y = static_cast(v_out[1]); - t_out.points[i].z = static_cast(v_out[2]); - } -} -#endif - /******************/ /** Quaternion32 **/ /******************/ diff --git a/planning/behavior_path_planner/CMakeLists.txt b/planning/behavior_path_planner/CMakeLists.txt index 542d4daa0c676..f62e418371401 100644 --- a/planning/behavior_path_planner/CMakeLists.txt +++ b/planning/behavior_path_planner/CMakeLists.txt @@ -58,12 +58,6 @@ ament_auto_add_library(behavior_path_planner_node SHARED src/marker_utils/lane_change/debug.cpp ) -if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) - target_compile_definitions(behavior_path_planner_node PRIVATE - DEFINE_LEGACY_FUNCTION - ) -endif() - target_include_directories(behavior_path_planner_node SYSTEM PUBLIC ${EIGEN3_INCLUDE_DIR} ) diff --git a/planning/surround_obstacle_checker/CMakeLists.txt b/planning/surround_obstacle_checker/CMakeLists.txt index 694bddf421486..0d7b636646783 100644 --- a/planning/surround_obstacle_checker/CMakeLists.txt +++ b/planning/surround_obstacle_checker/CMakeLists.txt @@ -18,12 +18,6 @@ ament_auto_add_library(${PROJECT_NAME} SHARED src/node.cpp ) -if(tf2_geometry_msgs_VERSION VERSION_LESS 0.25.4) - target_compile_definitions(${PROJECT_NAME} PRIVATE - DEFINE_LEGACY_FUNCTION - ) -endif() - target_link_libraries(${PROJECT_NAME} ${PCL_LIBRARIES} ) From 0f19d71c8013ecf210a670dc8bf4d42f9e5d23bb Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Wed, 27 Sep 2023 17:04:50 +0900 Subject: [PATCH 343/547] fix(ndt_scan_matcher): removed duplicate parameter declarations "initial_pose_timeout_sec_" (#5153) Removed duplicate parameter declarations "initial_pose_timeout_sec_" Signed-off-by: Shintaro SAKODA --- localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 1d1b829bd5262..44c763721e159 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -144,9 +144,6 @@ NDTScanMatcher::NDTScanMatcher() critical_upper_bound_exe_time_ms_ = this->declare_parameter("critical_upper_bound_exe_time_ms", critical_upper_bound_exe_time_ms_); - initial_pose_timeout_sec_ = - this->declare_parameter("initial_pose_timeout_sec", initial_pose_timeout_sec_); - initial_pose_timeout_sec_ = this->declare_parameter("initial_pose_timeout_sec"); initial_pose_distance_tolerance_m_ = From 7149338b3de32122b8db394b193bce0a781a8865 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 27 Sep 2023 17:17:24 +0900 Subject: [PATCH 344/547] feat(planning/control modules): enable logging_level_configure (#5146) * add logger_level_configure Signed-off-by: Takamasa Horibe * add usage Signed-off-by: Takamasa Horibe * fix spell Signed-off-by: Takamasa Horibe * fix from reviewer comment Signed-off-by: Takamasa Horibe * feat(planning/control modules): enable logging_level_configure Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../include/trajectory_follower_node/controller_node.hpp | 3 +++ control/trajectory_follower_node/src/controller_node.cpp | 2 ++ control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp | 2 ++ control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp | 3 +++ .../behavior_path_planner/behavior_path_planner_node.hpp | 3 +++ .../behavior_path_planner/src/behavior_path_planner_node.cpp | 2 ++ planning/behavior_velocity_planner/src/node.cpp | 2 ++ planning/behavior_velocity_planner/src/node.hpp | 3 +++ .../include/freespace_planner/freespace_planner_node.hpp | 4 ++++ .../src/freespace_planner/freespace_planner_node.cpp | 2 ++ .../mission_planner/src/mission_planner/mission_planner.cpp | 2 ++ .../mission_planner/src/mission_planner/mission_planner.hpp | 3 +++ .../motion_velocity_smoother_node.hpp | 3 +++ .../src/motion_velocity_smoother_node.cpp | 2 ++ .../include/obstacle_avoidance_planner/node.hpp | 3 +++ planning/obstacle_avoidance_planner/src/node.cpp | 2 ++ .../include/obstacle_cruise_planner/node.hpp | 3 +++ planning/obstacle_cruise_planner/src/node.cpp | 2 ++ .../include/obstacle_stop_planner/node.hpp | 3 +++ planning/obstacle_stop_planner/src/node.cpp | 2 ++ .../obstacle_velocity_limiter_node.hpp | 3 +++ .../src/obstacle_velocity_limiter_node.cpp | 2 ++ .../include/path_smoother/elastic_band_smoother.hpp | 3 +++ planning/path_smoother/src/elastic_band_smoother.cpp | 2 ++ .../include/planning_validator/planning_validator.hpp | 3 +++ planning/planning_validator/src/planning_validator.cpp | 2 ++ .../include/surround_obstacle_checker/node.hpp | 3 +++ planning/surround_obstacle_checker/src/node.cpp | 2 ++ .../system_error_monitor/system_error_monitor_core.hpp | 5 +++++ .../system_error_monitor/src/system_error_monitor_core.cpp | 2 ++ .../accel_brake_map_calibrator_node.hpp | 3 +++ .../src/accel_brake_map_calibrator_node.cpp | 2 ++ .../include/raw_vehicle_cmd_converter/node.hpp | 3 +++ vehicle/raw_vehicle_cmd_converter/src/node.cpp | 2 ++ 34 files changed, 88 insertions(+) diff --git a/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp b/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp index 489709d1f5c4c..c108bec2671b3 100644 --- a/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp +++ b/control/trajectory_follower_node/include/trajectory_follower_node/controller_node.hpp @@ -19,6 +19,7 @@ #include "tf2/utils.h" #include "tf2_ros/buffer.h" #include "tf2_ros/transform_listener.h" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "trajectory_follower_base/lateral_controller_base.hpp" #include "trajectory_follower_base/longitudinal_controller_base.hpp" #include "trajectory_follower_node/visibility_control.hpp" @@ -111,6 +112,8 @@ class TRAJECTORY_FOLLOWER_PUBLIC Controller : public rclcpp::Node void publishDebugMarker( const trajectory_follower::InputData & input_data, const trajectory_follower::LateralOutput & lat_out) const; + + std::unique_ptr logger_configure_; }; } // namespace trajectory_follower_node } // namespace autoware::motion::control diff --git a/control/trajectory_follower_node/src/controller_node.cpp b/control/trajectory_follower_node/src/controller_node.cpp index 675ca9dffe3ae..e35848e9495af 100644 --- a/control/trajectory_follower_node/src/controller_node.cpp +++ b/control/trajectory_follower_node/src/controller_node.cpp @@ -85,6 +85,8 @@ Controller::Controller(const rclcpp::NodeOptions & node_options) : Node("control timer_control_ = rclcpp::create_timer( this, get_clock(), period_ns, std::bind(&Controller::callbackTimerControl, this)); } + + logger_configure_ = std::make_unique(this); } Controller::LateralControllerMode Controller::getLateralControllerMode( diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp index 2f2083f626f6b..e2ee0ec65eec0 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp @@ -233,6 +233,8 @@ VehicleCmdGate::VehicleCmdGate(const rclcpp::NodeOptions & node_options) rclcpp::create_timer(this, get_clock(), period_ns, std::bind(&VehicleCmdGate::onTimer, this)); timer_pub_status_ = rclcpp::create_timer( this, get_clock(), period_ns, std::bind(&VehicleCmdGate::publishStatus, this)); + + logger_configure_ = std::make_unique(this); } bool VehicleCmdGate::isHeartbeatTimeout( diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp index 78d91e266c516..8f0a6aa14d08b 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.hpp @@ -17,6 +17,7 @@ #include "adapi_pause_interface.hpp" #include "moderate_stop_interface.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "vehicle_cmd_filter.hpp" #include @@ -235,6 +236,8 @@ class VehicleCmdGate : public rclcpp::Node // debug MarkerArray createMarkerArray(const IsFilterActivated & filter_activated); + + std::unique_ptr logger_configure_; }; } // namespace vehicle_cmd_gate diff --git a/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp b/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp index 0f51144c0b792..e8ad6c7e2f987 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp @@ -25,6 +25,7 @@ #include "behavior_path_planner/scene_module/side_shift/manager.hpp" #include "behavior_path_planner/scene_module/start_planner/manager.hpp" #include "behavior_path_planner/steering_factor_interface.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "tier4_planning_msgs/msg/detail/lane_change_debug_msg_array__struct.hpp" #include @@ -221,6 +222,8 @@ class BehaviorPathPlannerNode : public rclcpp::Node Path convertToPath( const std::shared_ptr & path_candidate_ptr, const bool is_ready, const std::shared_ptr & planner_data); + + std::unique_ptr logger_configure_; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index 8302779313feb..f52e018f2eef7 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -258,6 +258,8 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod timer_ = rclcpp::create_timer( this, get_clock(), period_ns, std::bind(&BehaviorPathPlannerNode::run, this)); } + + logger_configure_ = std::make_unique(this); } std::vector BehaviorPathPlannerNode::getWaitingApprovalModules() diff --git a/planning/behavior_velocity_planner/src/node.cpp b/planning/behavior_velocity_planner/src/node.cpp index f30043ad42588..ec28656d8f866 100644 --- a/planning/behavior_velocity_planner/src/node.cpp +++ b/planning/behavior_velocity_planner/src/node.cpp @@ -151,6 +151,8 @@ BehaviorVelocityPlannerNode::BehaviorVelocityPlannerNode(const rclcpp::NodeOptio for (const auto & name : declare_parameter>("launch_modules")) { planner_manager_.launchScenePlugin(*this, name); } + + logger_configure_ = std::make_unique(this); } void BehaviorVelocityPlannerNode::onLoadPlugin( diff --git a/planning/behavior_velocity_planner/src/node.hpp b/planning/behavior_velocity_planner/src/node.hpp index bdc280434ef41..ded1d08700a1d 100644 --- a/planning/behavior_velocity_planner/src/node.hpp +++ b/planning/behavior_velocity_planner/src/node.hpp @@ -16,6 +16,7 @@ #define NODE_HPP_ #include "planner_manager.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include #include @@ -126,6 +127,8 @@ class BehaviorVelocityPlannerNode : public rclcpp::Node autoware_auto_planning_msgs::msg::Path generatePath( const autoware_auto_planning_msgs::msg::PathWithLaneId::ConstSharedPtr input_path_msg, const PlannerData & planner_data); + + std::unique_ptr logger_configure_; }; } // namespace behavior_velocity_planner diff --git a/planning/freespace_planner/include/freespace_planner/freespace_planner_node.hpp b/planning/freespace_planner/include/freespace_planner/freespace_planner_node.hpp index fde0767006e33..9c13f14d180d0 100644 --- a/planning/freespace_planner/include/freespace_planner/freespace_planner_node.hpp +++ b/planning/freespace_planner/include/freespace_planner/freespace_planner_node.hpp @@ -31,6 +31,8 @@ #ifndef FREESPACE_PLANNER__FREESPACE_PLANNER_NODE_HPP_ #define FREESPACE_PLANNER__FREESPACE_PLANNER_NODE_HPP_ +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" + #include #include #include @@ -158,6 +160,8 @@ class FreespacePlannerNode : public rclcpp::Node void initializePlanningAlgorithm(); TransformStamped getTransform(const std::string & from, const std::string & to); + + std::unique_ptr logger_configure_; }; } // namespace freespace_planner diff --git a/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp b/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp index 025cc32d4ed24..6400ae6d135ba 100644 --- a/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp +++ b/planning/freespace_planner/src/freespace_planner/freespace_planner_node.cpp @@ -285,6 +285,8 @@ FreespacePlannerNode::FreespacePlannerNode(const rclcpp::NodeOptions & node_opti timer_ = rclcpp::create_timer( this, get_clock(), period_ns, std::bind(&FreespacePlannerNode::onTimer, this)); } + + logger_configure_ = std::make_unique(this); } PlannerCommonParam FreespacePlannerNode::getPlannerCommonParam() diff --git a/planning/mission_planner/src/mission_planner/mission_planner.cpp b/planning/mission_planner/src/mission_planner/mission_planner.cpp index 7e071e3b926b2..513a5b4c25a2e 100644 --- a/planning/mission_planner/src/mission_planner/mission_planner.cpp +++ b/planning/mission_planner/src/mission_planner/mission_planner.cpp @@ -126,6 +126,8 @@ MissionPlanner::MissionPlanner(const rclcpp::NodeOptions & options) // otherwise the mission planner rejects the request for the API. data_check_timer_ = create_wall_timer( std::chrono::milliseconds(100), std::bind(&MissionPlanner::checkInitialization, this)); + + logger_configure_ = std::make_unique(this); } void MissionPlanner::checkInitialization() diff --git a/planning/mission_planner/src/mission_planner/mission_planner.hpp b/planning/mission_planner/src/mission_planner/mission_planner.hpp index f7af76f43bbb0..ea44d2643e186 100644 --- a/planning/mission_planner/src/mission_planner/mission_planner.hpp +++ b/planning/mission_planner/src/mission_planner/mission_planner.hpp @@ -16,6 +16,7 @@ #define MISSION_PLANNER__MISSION_PLANNER_HPP_ #include "arrival_checker.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include #include @@ -147,6 +148,8 @@ class MissionPlanner : public rclcpp::Node std::shared_ptr normal_route_{nullptr}; std::shared_ptr mrm_route_{nullptr}; + + std::unique_ptr logger_configure_; }; } // namespace mission_planner diff --git a/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp b/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp index bb49700464715..347d19f631588 100644 --- a/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp +++ b/planning/motion_velocity_smoother/include/motion_velocity_smoother/motion_velocity_smoother_node.hpp @@ -30,6 +30,7 @@ #include "tf2_ros/transform_listener.h" #include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "tier4_autoware_utils/ros/self_pose_listener.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" @@ -263,6 +264,8 @@ class MotionVelocitySmootherNode : public rclcpp::Node bool isReverse(const TrajectoryPoints & points) const; void flipVelocity(TrajectoryPoints & points) const; void publishStopWatchTime(); + + std::unique_ptr logger_configure_; }; } // namespace motion_velocity_smoother diff --git a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp index a4ad118ffe012..75c84d3c482f6 100644 --- a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp +++ b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp @@ -99,6 +99,8 @@ MotionVelocitySmootherNode::MotionVelocitySmootherNode(const rclcpp::NodeOptions pub_velocity_limit_->publish(max_vel_msg); clock_ = get_clock(); + + logger_configure_ = std::make_unique(this); } void MotionVelocitySmootherNode::setupSmoother(const double wheelbase) diff --git a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp index 14925a8facf88..d6f49afad4391 100644 --- a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp +++ b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp @@ -21,6 +21,7 @@ #include "obstacle_avoidance_planner/replan_checker.hpp" #include "obstacle_avoidance_planner/type_alias.hpp" #include "rclcpp/rclcpp.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include @@ -127,6 +128,8 @@ class ObstacleAvoidancePlanner : public rclcpp::Node private: double vehicle_stop_margin_outside_drivable_area_; + + std::unique_ptr logger_configure_; }; } // namespace obstacle_avoidance_planner diff --git a/planning/obstacle_avoidance_planner/src/node.cpp b/planning/obstacle_avoidance_planner/src/node.cpp index f911fa87bedeb..9cee933a1c9a7 100644 --- a/planning/obstacle_avoidance_planner/src/node.cpp +++ b/planning/obstacle_avoidance_planner/src/node.cpp @@ -140,6 +140,8 @@ ObstacleAvoidancePlanner::ObstacleAvoidancePlanner(const rclcpp::NodeOptions & n // initialized. set_param_res_ = this->add_on_set_parameters_callback( std::bind(&ObstacleAvoidancePlanner::onParam, this, std::placeholders::_1)); + + logger_configure_ = std::make_unique(this); } rcl_interfaces::msg::SetParametersResult ObstacleAvoidancePlanner::onParam( diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp index 5786a96f72c41..9b2de4c8f2de0 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/node.hpp @@ -20,6 +20,7 @@ #include "obstacle_cruise_planner/pid_based_planner/pid_based_planner.hpp" #include "obstacle_cruise_planner/type_alias.hpp" #include "signal_processing/lowpass_filter_1d.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" #include @@ -259,6 +260,8 @@ class ObstacleCruisePlannerNode : public rclcpp::Node // previous closest obstacle std::shared_ptr prev_closest_stop_obstacle_ptr_{nullptr}; + + std::unique_ptr logger_configure_; }; } // namespace motion_planning diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index 8ef610cc9df10..b5e0718f20a49 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -438,6 +438,8 @@ ObstacleCruisePlannerNode::ObstacleCruisePlannerNode(const rclcpp::NodeOptions & // set parameter callback set_param_res_ = this->add_on_set_parameters_callback( std::bind(&ObstacleCruisePlannerNode::onParam, this, std::placeholders::_1)); + + logger_configure_ = std::make_unique(this); } ObstacleCruisePlannerNode::PlanningAlgorithm ObstacleCruisePlannerNode::getPlanningAlgorithmType( diff --git a/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp b/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp index 4cb71c710a225..93a9736bcdb36 100644 --- a/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp +++ b/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp @@ -18,6 +18,7 @@ #include "obstacle_stop_planner/adaptive_cruise_control.hpp" #include "obstacle_stop_planner/debug_marker.hpp" #include "obstacle_stop_planner/planner_data.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include #include @@ -274,6 +275,8 @@ class ObstacleStopPlannerNode : public rclcpp::Node return ret; } + + std::unique_ptr logger_configure_; }; } // namespace motion_planning diff --git a/planning/obstacle_stop_planner/src/node.cpp b/planning/obstacle_stop_planner/src/node.cpp index bec94335c4739..4e4b5d2f91ef3 100644 --- a/planning/obstacle_stop_planner/src/node.cpp +++ b/planning/obstacle_stop_planner/src/node.cpp @@ -242,6 +242,8 @@ ObstacleStopPlannerNode::ObstacleStopPlannerNode(const rclcpp::NodeOptions & nod "~/input/expand_stop_range", 1, std::bind(&ObstacleStopPlannerNode::onExpandStopRange, this, std::placeholders::_1), createSubscriptionOptions(this)); + + logger_configure_ = std::make_unique(this); } void ObstacleStopPlannerNode::onPointCloud(const PointCloud2::ConstSharedPtr input_msg) diff --git a/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp b/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp index 8916137c84077..a2ff72d0da562 100644 --- a/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp +++ b/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp @@ -18,6 +18,7 @@ #include "obstacle_velocity_limiter/obstacles.hpp" #include "obstacle_velocity_limiter/parameters.hpp" #include "obstacle_velocity_limiter/types.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include #include @@ -101,6 +102,8 @@ class ObstacleVelocityLimiterNode : public rclcpp::Node /// @brief validate the inputs of the node /// @return true if the inputs are valid bool validInputs(); + + std::unique_ptr logger_configure_; }; } // namespace obstacle_velocity_limiter diff --git a/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp b/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp index 123ed997d4445..f4601979b9b8a 100644 --- a/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp +++ b/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp @@ -79,6 +79,8 @@ ObstacleVelocityLimiterNode::ObstacleVelocityLimiterNode(const rclcpp::NodeOptio set_param_res_ = add_on_set_parameters_callback([this](const auto & params) { return onParameter(params); }); + + logger_configure_ = std::make_unique(this); } rcl_interfaces::msg::SetParametersResult ObstacleVelocityLimiterNode::onParameter( diff --git a/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp b/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp index 8bd73292a56de..a67983393681f 100644 --- a/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp +++ b/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp @@ -21,6 +21,7 @@ #include "path_smoother/replan_checker.hpp" #include "path_smoother/type_alias.hpp" #include "rclcpp/rclcpp.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include #include @@ -104,6 +105,8 @@ class ElasticBandSmoother : public rclcpp::Node std::vector extendTrajectory( const std::vector & traj_points, const std::vector & optimized_points) const; + + std::unique_ptr logger_configure_; }; } // namespace path_smoother diff --git a/planning/path_smoother/src/elastic_band_smoother.cpp b/planning/path_smoother/src/elastic_band_smoother.cpp index 31fe23a47b0ca..cd314b7b141bf 100644 --- a/planning/path_smoother/src/elastic_band_smoother.cpp +++ b/planning/path_smoother/src/elastic_band_smoother.cpp @@ -95,6 +95,8 @@ ElasticBandSmoother::ElasticBandSmoother(const rclcpp::NodeOptions & node_option // set parameter callback set_param_res_ = this->add_on_set_parameters_callback( std::bind(&ElasticBandSmoother::onParam, this, std::placeholders::_1)); + + logger_configure_ = std::make_unique(this); } rcl_interfaces::msg::SetParametersResult ElasticBandSmoother::onParam( diff --git a/planning/planning_validator/include/planning_validator/planning_validator.hpp b/planning/planning_validator/include/planning_validator/planning_validator.hpp index 321eb23aa7fd4..3b09ebe51ff6b 100644 --- a/planning/planning_validator/include/planning_validator/planning_validator.hpp +++ b/planning/planning_validator/include/planning_validator/planning_validator.hpp @@ -17,6 +17,7 @@ #include "planning_validator/debug_marker.hpp" #include "planning_validator/msg/planning_validator_status.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include @@ -117,6 +118,8 @@ class PlanningValidator : public rclcpp::Node Odometry::ConstSharedPtr current_kinematics_; std::shared_ptr debug_pose_publisher_; + + std::unique_ptr logger_configure_; }; } // namespace planning_validator diff --git a/planning/planning_validator/src/planning_validator.cpp b/planning/planning_validator/src/planning_validator.cpp index db4510c2e5d0d..13a63a7e7d4ed 100644 --- a/planning/planning_validator/src/planning_validator.cpp +++ b/planning/planning_validator/src/planning_validator.cpp @@ -49,6 +49,8 @@ PlanningValidator::PlanningValidator(const rclcpp::NodeOptions & options) if (publish_diag_) { setupDiag(); } + + logger_configure_ = std::make_unique(this); } void PlanningValidator::setupParameters() diff --git a/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp b/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp index f31bb7ddee331..c40fef5b0c43e 100644 --- a/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp +++ b/planning/surround_obstacle_checker/include/surround_obstacle_checker/node.hpp @@ -16,6 +16,7 @@ #define SURROUND_OBSTACLE_CHECKER__NODE_HPP_ #include "surround_obstacle_checker/debug_marker.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include #include @@ -138,6 +139,8 @@ class SurroundObstacleCheckerNode : public rclcpp::Node // State Machine State state_ = State::PASS; std::shared_ptr last_obstacle_found_time_; + + std::unique_ptr logger_configure_; }; } // namespace surround_obstacle_checker diff --git a/planning/surround_obstacle_checker/src/node.cpp b/planning/surround_obstacle_checker/src/node.cpp index ab542bdfa73ed..1b981fcddb155 100644 --- a/planning/surround_obstacle_checker/src/node.cpp +++ b/planning/surround_obstacle_checker/src/node.cpp @@ -188,6 +188,8 @@ SurroundObstacleCheckerNode::SurroundObstacleCheckerNode(const rclcpp::NodeOptio p.state_clear_time = this->declare_parameter("state_clear_time"); p.publish_debug_footprints = this->declare_parameter("publish_debug_footprints"); p.debug_footprint_label = this->declare_parameter("debug_footprint_label"); + + logger_configure_ = std::make_unique(this); } vehicle_info_ = vehicle_info_util::VehicleInfoUtil(*this).getVehicleInfo(); diff --git a/system/system_error_monitor/include/system_error_monitor/system_error_monitor_core.hpp b/system/system_error_monitor/include/system_error_monitor/system_error_monitor_core.hpp index 75d80b56ea711..0bb95882700a5 100644 --- a/system/system_error_monitor/include/system_error_monitor/system_error_monitor_core.hpp +++ b/system/system_error_monitor/include/system_error_monitor/system_error_monitor_core.hpp @@ -15,6 +15,8 @@ #ifndef SYSTEM_ERROR_MONITOR__SYSTEM_ERROR_MONITOR_CORE_HPP_ #define SYSTEM_ERROR_MONITOR__SYSTEM_ERROR_MONITOR_CORE_HPP_ +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" + #include #include @@ -29,6 +31,7 @@ #include #include +#include #include #include #include @@ -144,6 +147,8 @@ class AutowareErrorMonitor : public rclcpp::Node void updateTimeoutHazardStatus(); bool canAutoRecovery() const; bool isEmergencyHoldingRequired() const; + + std::unique_ptr logger_configure_; }; #endif // SYSTEM_ERROR_MONITOR__SYSTEM_ERROR_MONITOR_CORE_HPP_ diff --git a/system/system_error_monitor/src/system_error_monitor_core.cpp b/system/system_error_monitor/src/system_error_monitor_core.cpp index 1bd7caa19a220..e74ac9c360164 100644 --- a/system/system_error_monitor/src/system_error_monitor_core.cpp +++ b/system/system_error_monitor/src/system_error_monitor_core.cpp @@ -274,6 +274,8 @@ AutowareErrorMonitor::AutowareErrorMonitor() const auto period_ns = rclcpp::Rate(params_.update_rate).period(); timer_ = rclcpp::create_timer( this, get_clock(), period_ns, std::bind(&AutowareErrorMonitor::onTimer, this)); + + logger_configure_ = std::make_unique(this); } void AutowareErrorMonitor::loadRequiredModules(const std::string & key) diff --git a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp index 1d8ec80c46e17..3580f0546bbfb 100644 --- a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp +++ b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/include/accel_brake_map_calibrator/accel_brake_map_calibrator_node.hpp @@ -22,6 +22,7 @@ #include "raw_vehicle_cmd_converter/brake_map.hpp" #include "rclcpp/rclcpp.hpp" #include "tf2/utils.h" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include "tier4_autoware_utils/ros/transform_listener.hpp" #include @@ -369,6 +370,8 @@ class AccelBrakeMapCalibrator : public rclcpp::Node COMMAND = 1, }; + std::unique_ptr logger_configure_; + public: explicit AccelBrakeMapCalibrator(const rclcpp::NodeOptions & node_options); }; diff --git a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/src/accel_brake_map_calibrator_node.cpp b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/src/accel_brake_map_calibrator_node.cpp index 1910631b11e53..ecfc312d2bdfd 100644 --- a/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/src/accel_brake_map_calibrator_node.cpp +++ b/vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/src/accel_brake_map_calibrator_node.cpp @@ -232,6 +232,8 @@ AccelBrakeMapCalibrator::AccelBrakeMapCalibrator(const rclcpp::NodeOptions & nod // timer initTimer(1.0 / update_hz_); initOutputCSVTimer(30.0); + + logger_configure_ = std::make_unique(this); } void AccelBrakeMapCalibrator::initOutputCSVTimer(double period_s) diff --git a/vehicle/raw_vehicle_cmd_converter/include/raw_vehicle_cmd_converter/node.hpp b/vehicle/raw_vehicle_cmd_converter/include/raw_vehicle_cmd_converter/node.hpp index 27346e99b60fb..98693337b5c28 100644 --- a/vehicle/raw_vehicle_cmd_converter/include/raw_vehicle_cmd_converter/node.hpp +++ b/vehicle/raw_vehicle_cmd_converter/include/raw_vehicle_cmd_converter/node.hpp @@ -19,6 +19,7 @@ #include "raw_vehicle_cmd_converter/brake_map.hpp" #include "raw_vehicle_cmd_converter/pid.hpp" #include "raw_vehicle_cmd_converter/steer_map.hpp" +#include "tier4_autoware_utils/ros/logger_level_configure.hpp" #include @@ -115,6 +116,8 @@ class RawVehicleCommandConverterNode : public rclcpp::Node // for debugging rclcpp::Publisher::SharedPtr debug_pub_steer_pid_; DebugValues debug_steer_; + + std::unique_ptr logger_configure_; }; } // namespace raw_vehicle_cmd_converter diff --git a/vehicle/raw_vehicle_cmd_converter/src/node.cpp b/vehicle/raw_vehicle_cmd_converter/src/node.cpp index e45439ecb4d41..0b0e14a845200 100644 --- a/vehicle/raw_vehicle_cmd_converter/src/node.cpp +++ b/vehicle/raw_vehicle_cmd_converter/src/node.cpp @@ -84,6 +84,8 @@ RawVehicleCommandConverterNode::RawVehicleCommandConverterNode( "~/input/steering", 1, std::bind(&RawVehicleCommandConverterNode::onSteering, this, _1)); debug_pub_steer_pid_ = create_publisher( "/vehicle/raw_vehicle_cmd_converter/debug/steer_pid", 1); + + logger_configure_ = std::make_unique(this); } void RawVehicleCommandConverterNode::publishActuationCmd() From 154da3dd0c8f14da051247e8ff885a18b59fc560 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 27 Sep 2023 17:39:07 +0900 Subject: [PATCH 345/547] fix(goal_planner): fix goal searcher dying in free space (#5151) Signed-off-by: kosuke55 --- .../src/scene_module/goal_planner/goal_planner_module.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 1f5a823386774..556138dcfb524 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -459,6 +459,7 @@ ModuleStatus GoalPlannerModule::updateState() bool GoalPlannerModule::planFreespacePath() { mutex_.lock(); + goal_searcher_->setPlannerData(planner_data_); goal_searcher_->update(goal_candidates_); const auto goal_candidates = goal_candidates_; debug_data_.freespace_planner.num_goal_candidates = goal_candidates.size(); From c6701df78115db83c3671be4f314cc0569f90a6b Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 27 Sep 2023 17:40:06 +0900 Subject: [PATCH 346/547] perf(goal_planner): speed up goal candidates update (#5114) * perf(goal_planner): speed up goal candidates update Signed-off-by: kosuke55 typo Signed-off-by: kosuke55 * fix(goal_planner): fix goal searcher dying in free space Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../utils/goal_planner/goal_searcher.hpp | 2 +- .../src/utils/goal_planner/goal_searcher.cpp | 30 +++++++------------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp index d564dcaa19e29..6115e3fcec31e 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp @@ -41,7 +41,7 @@ class GoalSearcher : public GoalSearcherBase private: void createAreaPolygons(std::vector original_search_poses); - bool checkCollision(const Pose & pose) const; + bool checkCollision(const Pose & pose, const PredictedObjects & dynamic_objects) const; bool checkCollisionWithLongitudinalDistance( const Pose & ego_pose, const PredictedObjects & dynamic_objects) const; BasicPolygons2d getNoParkingAreaPolygons(const lanelet::ConstLanelets & lanes) const; diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index c1d24f02ac140..5e55d712f4f25 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -183,25 +183,25 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) void GoalSearcher::update(GoalCandidates & goal_candidates) const { + const auto stop_objects = utils::path_safety_checker::filterObjectsByVelocity( + *(planner_data_->dynamic_object), parameters_.th_moving_object_velocity); + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, + parameters_.forward_goal_search_length); + const auto [pull_over_lane_stop_objects, others] = + utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_over_lanes); + // update is_safe for (auto & goal_candidate : goal_candidates) { const Pose goal_pose = goal_candidate.goal_pose; // check collision with footprint - if (checkCollision(goal_pose)) { + if (checkCollision(goal_pose, pull_over_lane_stop_objects)) { goal_candidate.is_safe = false; continue; } // check longitudinal margin with pull over lane objects - const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( - *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, - parameters_.forward_goal_search_length); - const auto [pull_over_lane_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets( - *(planner_data_->dynamic_object), pull_over_lanes); - const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( - pull_over_lane_objects, parameters_.th_moving_object_velocity); constexpr bool filter_inside = true; const auto target_objects = goal_planner_utils::filterObjectsByLateralDistance( goal_pose, planner_data_->parameters.vehicle_width, pull_over_lane_stop_objects, @@ -215,7 +215,7 @@ void GoalSearcher::update(GoalCandidates & goal_candidates) const } } -bool GoalSearcher::checkCollision(const Pose & pose) const +bool GoalSearcher::checkCollision(const Pose & pose, const PredictedObjects & dynamic_objects) const { if (parameters_.use_occupancy_grid_for_goal_search) { const Pose pose_grid_coords = global2local(occupancy_grid_map_->getMap(), pose); @@ -228,16 +228,8 @@ bool GoalSearcher::checkCollision(const Pose & pose) const } if (parameters_.use_object_recognition) { - const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( - *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, - parameters_.forward_goal_search_length); - const auto [pull_over_lane_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets( - *(planner_data_->dynamic_object), pull_over_lanes); - const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( - pull_over_lane_objects, parameters_.th_moving_object_velocity); if (utils::checkCollisionBetweenFootprintAndObjects( - vehicle_footprint_, pose, pull_over_lane_stop_objects, + vehicle_footprint_, pose, dynamic_objects, parameters_.object_recognition_collision_check_margin)) { return true; } From c7d3d0ea9baca4f5b3a6f85f2ddefff190939a05 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 27 Sep 2023 17:42:22 +0900 Subject: [PATCH 347/547] fix(goal_planner): disable freespace planner when fixed goal (#5152) Signed-off-by: kosuke55 --- .../src/scene_module/goal_planner/goal_planner_module.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 556138dcfb524..cffde017ccbe1 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -217,6 +217,10 @@ void GoalPlannerModule::onFreespaceParkingTimer() if (!planner_data_->costmap) { return; } + // fixed goal planner do not use freespace planner + if (!goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { + return; + } const bool is_new_costmap = (clock_->now() - planner_data_->costmap->header.stamp).seconds() < 1.0; From 0cd4f625d255f36d5ff55c571292625cb0272af9 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 27 Sep 2023 17:45:29 +0900 Subject: [PATCH 348/547] fix(out_of_lane): lateral distance calculation for predicted path (#5145) * fix(out_of_lane): lateral distance calculation for predicted path Signed-off-by: Takamasa Horibe * remove overlap points in the object_time_to_range function Signed-off-by: Maxime CLEMENT --------- Signed-off-by: Takamasa Horibe Signed-off-by: Maxime CLEMENT Co-authored-by: Maxime CLEMENT --- .../src/decisions.cpp | 19 ++++++++++--------- .../src/filter_predicted_objects.hpp | 10 +++++++++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp index 62114f5a95b00..da46c1355e735 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp @@ -78,17 +78,18 @@ std::optional> object_time_to_range( auto worst_exit_time = std::optional(); for (const auto & predicted_path : object.kinematics.predicted_paths) { + const auto unique_path_points = motion_utils::removeOverlapPoints(predicted_path.path); const auto time_step = rclcpp::Duration(predicted_path.time_step).seconds(); const auto enter_point = geometry_msgs::msg::Point().set__x(range.entering_point.x()).set__y(range.entering_point.y()); const auto enter_segment_idx = - motion_utils::findNearestSegmentIndex(predicted_path.path, enter_point); + motion_utils::findNearestSegmentIndex(unique_path_points, enter_point); const auto enter_offset = motion_utils::calcLongitudinalOffsetToSegment( - predicted_path.path, enter_segment_idx, enter_point); - const auto enter_lat_dist = std::abs( - motion_utils::calcLateralOffset(predicted_path.path, enter_point, enter_segment_idx)); + unique_path_points, enter_segment_idx, enter_point); + const auto enter_lat_dist = + std::abs(motion_utils::calcLateralOffset(unique_path_points, enter_point, enter_segment_idx)); const auto enter_segment_length = tier4_autoware_utils::calcDistance2d( - predicted_path.path[enter_segment_idx], predicted_path.path[enter_segment_idx + 1]); + unique_path_points[enter_segment_idx], unique_path_points[enter_segment_idx + 1]); const auto enter_offset_ratio = enter_offset / enter_segment_length; const auto enter_time = static_cast(enter_segment_idx) * time_step + enter_offset_ratio * time_step; @@ -96,13 +97,13 @@ std::optional> object_time_to_range( const auto exit_point = geometry_msgs::msg::Point().set__x(range.exiting_point.x()).set__y(range.exiting_point.y()); const auto exit_segment_idx = - motion_utils::findNearestSegmentIndex(predicted_path.path, exit_point); + motion_utils::findNearestSegmentIndex(unique_path_points, exit_point); const auto exit_offset = motion_utils::calcLongitudinalOffsetToSegment( - predicted_path.path, exit_segment_idx, exit_point); + unique_path_points, exit_segment_idx, exit_point); const auto exit_lat_dist = - std::abs(motion_utils::calcLateralOffset(predicted_path.path, exit_point, exit_segment_idx)); + std::abs(motion_utils::calcLateralOffset(unique_path_points, exit_point, exit_segment_idx)); const auto exit_segment_length = tier4_autoware_utils::calcDistance2d( - predicted_path.path[exit_segment_idx], predicted_path.path[exit_segment_idx + 1]); + unique_path_points[exit_segment_idx], unique_path_points[exit_segment_idx + 1]); const auto exit_offset_ratio = exit_offset / static_cast(exit_segment_length); const auto exit_time = static_cast(exit_segment_idx) * time_step + exit_offset_ratio * time_step; diff --git a/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp index 13bad0d047922..f2555f31a4d5e 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp @@ -23,6 +23,9 @@ namespace behavior_velocity_planner::out_of_lane { +using motion_utils::calcLateralOffset; +using tier4_autoware_utils::calcDistance2d; + /// @brief filter predicted objects and their predicted paths /// @param [in] objects predicted objects to filter /// @param [in] ego_data ego data @@ -44,8 +47,13 @@ autoware_auto_perception_msgs::msg::PredictedObjects filter_predicted_objects( auto filtered_object = object; const auto is_invalid_predicted_path = [&](const auto & predicted_path) { const auto is_low_confidence = predicted_path.confidence < params.objects_min_confidence; + const auto unique_path_points = motion_utils::removeOverlapPoints(predicted_path.path); + + // Note: lateral offset can not be calculated for path with less than 2 unique points const auto lat_offset_to_current_ego = - std::abs(motion_utils::calcLateralOffset(predicted_path.path, ego_data.pose.position)); + unique_path_points.size() > 1 + ? std::abs(calcLateralOffset(unique_path_points, ego_data.pose.position)) + : calcDistance2d(unique_path_points.front(), ego_data.pose.position); const auto is_crossing_ego = lat_offset_to_current_ego <= object.shape.dimensions.y / 2.0 + std::max( From b98abd2954fef7e6e8379d7dd128d6edcb76e909 Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Wed, 27 Sep 2023 20:34:04 +0900 Subject: [PATCH 349/547] chore(vechicle_info_util): update maintainer (#4835) Signed-off-by: yamato-ando Co-authored-by: yamato-ando --- vehicle/vehicle_info_util/package.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vehicle/vehicle_info_util/package.xml b/vehicle/vehicle_info_util/package.xml index 41cb10003a1de..139f8439d60c9 100644 --- a/vehicle/vehicle_info_util/package.xml +++ b/vehicle/vehicle_info_util/package.xml @@ -4,7 +4,6 @@ vehicle_info_util 0.1.0 The vehicle_info_util package - Yamato ANDO Taiki Tanaka @@ -12,6 +11,7 @@ Shumpei Wakabayashi Apache License 2.0 + Yamato ANDO ament_cmake_auto autoware_cmake From d27efb28be8e95c75291c64dba94825552577369 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 27 Sep 2023 22:28:12 +0900 Subject: [PATCH 350/547] docs(start_planner): add freespace pull out docs (#5161) Signed-off-by: kosuke55 --- ...avior_path_planner_start_planner_design.md | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md index 8322952a0be76..95a9697a1d471 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md @@ -8,11 +8,11 @@ This module is activated when a new route is received. Use cases are as follows - start smoothly from the current ego position to centerline. - - ![case1](../image/start_from_road_lane.drawio.svg) + ![case1](../image/start_from_road_lane.drawio.svg) - pull out from the side of the road lane to centerline. - - ![case2](../image/start_from_road_side.drawio.svg) + ![case2](../image/start_from_road_side.drawio.svg) - pull out from the shoulder lane to the road lane centerline. - - ![case3](../image/start_from_road_shoulder.drawio.svg) + ![case3](../image/start_from_road_shoulder.drawio.svg) ## Design @@ -155,3 +155,27 @@ If a safe path cannot be generated from the current position, search backwards f | backward_search_resolution | [m] | double | distance interval for searching backward pull out start point | 2.0 | | backward_path_update_duration | [s] | double | time interval for searching backward pull out start point. this prevents chattering between back driving and pull_out | 3.0 | | ignore_distance_from_lane_end | [m] | double | distance from end of pull out lanes for ignoring start candidates | 15.0 | + +### **freespace pull out** + +If the vehicle gets stuck with pull out along lanes, execute freespace pull out. +To run this feature, you need to set `parking_lot` to the map, `activate_by_scenario` of [costmap_generator](../../costmap_generator/README.md) to `false` and `enable_freespace_planner` to `true` + + + +#### Unimplemented parts / limitations for freespace pull out + +- When a short path is generated, the ego does can not drive with it. +- Complex cases take longer to generate or fail. +- The drivable area is not guaranteed to fit in the parking_lot. + +#### Parameters freespace parking + +| Name | Unit | Type | Description | Default value | +| :----------------------------- | :--- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| enable_freespace_planner | [-] | bool | this flag activates a free space pullout that is executed when a vehicle is stuck due to obstacles in the lanes where the ego is located | true | +| end_pose_search_start_distance | [m] | double | distance from ego to the start point of the search for the end point in the freespace_pull_out driving lane | 20.0 | +| end_pose_search_end_distance | [m] | double | distance from ego to the end point of the search for the end point in the freespace_pull_out driving lane | 30.0 | +| end_pose_search_interval | [m] | bool | interval to search for the end point in the freespace_pull_out driving lane | 2.0 | + +See [freespace_planner](../../freespace_planner/README.md) for other parameters. From f2ffc668e79cc09a3a637c2b0db191ed39056866 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 27 Sep 2023 22:31:21 +0900 Subject: [PATCH 351/547] docs(goal_planner): move smooth goal connection to goal planner docs (#5159) Signed-off-by: kosuke55 --- planning/behavior_path_planner/README.md | 6 ------ .../docs/behavior_path_planner_goal_planner_design.md | 7 ++++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/planning/behavior_path_planner/README.md b/planning/behavior_path_planner/README.md index 07d949aae8a39..0c0c7c806c119 100644 --- a/planning/behavior_path_planner/README.md +++ b/planning/behavior_path_planner/README.md @@ -173,12 +173,6 @@ struct ShiftLine Click [here](./docs/behavior_path_planner_path_generation_design.md) for details. -### Smooth goal connection - -If the target path contains a goal, modify the points of the path so that the path and the goal are connected smoothly. This process will change the shape of the path by the distance of `refine_goal_search_radius_range` from the goal. Note that this logic depends on the interpolation algorithm that will be executed in a later module (at the moment it uses spline interpolation), so it needs to be updated in the future. - -![path_goal_refinement](./image/path_goal_refinement.drawio.svg) - ## References / External links This module depends on the external [BehaviorTreeCpp](https://github.com/BehaviorTree/BehaviorTree.CPP) library. diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index ee337555ceb58..54a0ced19de42 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -89,6 +89,10 @@ Either one is activated when all conditions are met. - Route is set with `allow_goal_modification=false` by default. - ego-vehicle is in the same lane as the goal. +If the target path contains a goal, modify the points of the path so that the path and the goal are connected smoothly. This process will change the shape of the path by the distance of `refine_goal_search_radius_range` from the goal. Note that this logic depends on the interpolation algorithm that will be executed in a later module (at the moment it uses spline interpolation), so it needs to be updated in the future. + +![path_goal_refinement](../image/path_goal_refinement.drawio.svg) + ### rough_goal_planner @@ -264,7 +268,8 @@ If the vehicle gets stuck with `lane_parking`, run `freespace_parking`. To run this feature, you need to set `parking_lot` to the map, `activate_by_scenario` of [costmap_generator](../../costmap_generator/README.md) to `false` and `enable_freespace_parking` to `true` ![pull_over_freespace_parking_flowchart](../image/pull_over_freespace_parking_flowchart.drawio.svg) -\*Series execution with `avoidance_module` in the flowchart is under development. + +Simultaneous execution with `avoidance_module` in the flowchart is under development. From 639d5c0098c4806764a7c9e25a377a01c3f8bc5f Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Thu, 28 Sep 2023 09:14:32 +0900 Subject: [PATCH 352/547] feat(goal_planner): prioritize goals before objects to avoid (#5137) sort path priority update docs typo Signed-off-by: kosuke55 --- .../goal_planner/goal_planner.param.yaml | 4 +- ...havior_path_planner_goal_planner_design.md | 18 +-- .../goal_planner/goal_planner_module.hpp | 3 + .../goal_planner/goal_planner_parameters.hpp | 5 +- .../utils/goal_planner/goal_searcher.hpp | 8 +- .../utils/goal_planner/goal_searcher_base.hpp | 15 +- .../utils/goal_planner/util.hpp | 3 + .../goal_planner/goal_planner_module.cpp | 62 ++++++--- .../src/scene_module/goal_planner/manager.cpp | 6 +- .../src/utils/goal_planner/goal_searcher.cpp | 130 +++++++++++++++--- .../src/utils/goal_planner/util.cpp | 44 ++++-- 11 files changed, 238 insertions(+), 60 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index babbdc60d1cf9..0ca028e053fbc 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -12,7 +12,7 @@ goal_priority: "minimum_weighted_distance" # "minimum_weighted_distance" or "minimum_longitudinal_distance" minimum_weighted_distance: lateral_weight: 40.0 - path_priority: "efficient_path" # "efficient_path" or "close_goal" + prioritize_goals_before_objects: true parking_policy: "left_side" # "left_side" or "right_side" forward_goal_search_length: 20.0 backward_goal_search_length: 20.0 @@ -47,6 +47,8 @@ decide_path_distance: 10.0 maximum_deceleration: 1.0 maximum_jerk: 1.0 + path_priority: "efficient_path" # "efficient_path" or "close_goal" + efficient_path_order: ["SHIFT", "ARC_FORWARD", "ARC_BACKWARD"] # only lane based pull over(exclude freespace parking) # shift parking shift_parking: diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md index 54a0ced19de42..888d5d9ff9826 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_goal_planner_design.md @@ -168,7 +168,7 @@ searched for in certain range of the shoulder lane. | Name | Unit | Type | Description | Default value | | :------------------------------ | :--- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------- | | goal_priority | [-] | string | In case `minimum_weighted_distance`, sort with smaller longitudinal distances taking precedence over smaller lateral distances. In case `minimum_longitudinal_distance`, sort with weighted lateral distance against longitudinal distance. | `minimum_weighted_distance` | -| path_priority | [-] | string | In case `efficient_path` use a goal that can generate an efficient path( priority is `shift_parking` -> `arc_forward_parking` -> `arc_backward_parking`). In case `close_goal` use the closest goal to the original one. | efficient_path | +| prioritize_goals_before_objects | [-] | bool | If there are objects that may need to be avoided, prioritize the goal in front of them | true | | forward_goal_search_length | [m] | double | length of forward range to be explored from the original goal | 20.0 | | backward_goal_search_length | [m] | double | length of backward range to be explored from the original goal | 20.0 | | goal_search_interval | [m] | double | distance interval for goal search | 2.0 | @@ -184,13 +184,15 @@ searched for in certain range of the shoulder lane. There are three path generation methods. The path is generated with a certain margin (default: `0.5 m`) from the boundary of shoulder lane. -| Name | Unit | Type | Description | Default value | -| :------------------------------- | :----- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------- | :------------ | -| pull_over_minimum_request_length | [m] | double | when the ego-vehicle approaches the goal by this distance or a safe distance to stop, pull over is activated. | 100.0 | -| pull_over_velocity | [m/s] | double | decelerate to this speed by the goal search area | 3.0 | -| pull_over_minimum_velocity | [m/s] | double | speed of pull_over after stopping once. this prevents excessive acceleration. | 1.38 | -| decide_path_distance | [m] | double | decide path if it approaches this distance relative to the parking position. after that, no path planning and goal search are performed | 10.0 | -| maximum_deceleration | [m/s2] | double | maximum deceleration. it prevents sudden deceleration when a parking path cannot be found suddenly | 1.0 | +| Name | Unit | Type | Description | Default value | +| :------------------------------- | :----- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------- | +| pull_over_minimum_request_length | [m] | double | when the ego-vehicle approaches the goal by this distance or a safe distance to stop, pull over is activated. | 100.0 | +| pull_over_velocity | [m/s] | double | decelerate to this speed by the goal search area | 3.0 | +| pull_over_minimum_velocity | [m/s] | double | speed of pull_over after stopping once. this prevents excessive acceleration. | 1.38 | +| decide_path_distance | [m] | double | decide path if it approaches this distance relative to the parking position. after that, no path planning and goal search are performed | 10.0 | +| maximum_deceleration | [m/s2] | double | maximum deceleration. it prevents sudden deceleration when a parking path cannot be found suddenly | 1.0 | +| path_priority | [-] | string | In case `efficient_path` use a goal that can generate an efficient path which is set in `efficient_path_order`. In case `close_goal` use the closest goal to the original one. | efficient_path | +| efficient_path_order | [-] | string | efficient order of pull over planner along lanes excluding freespace pull over | ["SHIFT", "ARC_FORWARD", "ARC_BACKWARD"] | ### **shift parking** diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index f6b2fa8666f2e..eb6ea5996cc6f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -267,6 +267,9 @@ class GoalPlannerModule : public SceneModuleInterface BehaviorModuleOutput planWithGoalModification(); BehaviorModuleOutput planWaitingApprovalWithGoalModification(); void selectSafePullOverPath(); + void sortPullOverPathCandidatesByGoalPriority( + std::vector & pull_over_path_candidates, + const GoalCandidates & goal_candidates) const; // deal with pull over partial paths PathWithLaneId getCurrentPath() const; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp index 22624bd1e0a80..2b189d583b924 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp @@ -50,9 +50,8 @@ struct GoalPlannerParameters // goal search std::string goal_priority; // "minimum_weighted_distance" or "minimum_longitudinal_distance" double minimum_weighted_distance_lateral_weight{0.0}; - std::string path_priority; // "efficient_path" or "close_goal" + bool prioritize_goals_before_objects{false}; ParkingPolicy parking_policy; // "left_side" or "right_side" - double forward_goal_search_length{0.0}; double backward_goal_search_length{0.0}; double goal_search_interval{0.0}; @@ -83,6 +82,8 @@ struct GoalPlannerParameters double decide_path_distance{0.0}; double maximum_deceleration{0.0}; double maximum_jerk{0.0}; + std::string path_priority; // "efficient_path" or "close_goal" + std::vector efficient_path_order{}; // shift path bool enable_shift_parking{false}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp index 6115e3fcec31e..9d957ae9767c9 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp @@ -36,14 +36,16 @@ class GoalSearcher : public GoalSearcherBase const GoalPlannerParameters & parameters, const LinearRing2d & vehicle_footprint, const std::shared_ptr & occupancy_grid_map); - GoalCandidates search(const Pose & original_goal_pose) override; + GoalCandidates search() override; void update(GoalCandidates & goal_candidates) const override; private: + void countObjectsToAvoid( + GoalCandidates & goal_candidates, const PredictedObjects & objects) const; void createAreaPolygons(std::vector original_search_poses); - bool checkCollision(const Pose & pose, const PredictedObjects & dynamic_objects) const; + bool checkCollision(const Pose & pose, const PredictedObjects & objects) const; bool checkCollisionWithLongitudinalDistance( - const Pose & ego_pose, const PredictedObjects & dynamic_objects) const; + const Pose & ego_pose, const PredictedObjects & objects) const; BasicPolygons2d getNoParkingAreaPolygons(const lanelet::ConstLanelets & lanes) const; BasicPolygons2d getNoStoppingAreaPolygons(const lanelet::ConstLanelets & lanes) const; bool isInAreas(const LinearRing2d & footprint, const BasicPolygons2d & areas) const; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp index cac1d37e342c2..7364ea91339e4 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp @@ -37,6 +37,7 @@ struct GoalCandidate double lateral_offset{0.0}; size_t id{0}; bool is_safe{true}; + size_t num_objects_to_avoid{0}; }; using GoalCandidates = std::vector; @@ -46,19 +47,25 @@ class GoalSearcherBase explicit GoalSearcherBase(const GoalPlannerParameters & parameters) { parameters_ = parameters; } virtual ~GoalSearcherBase() = default; + void setReferenceGoal(const Pose & reference_goal_pose) + { + reference_goal_pose_ = reference_goal_pose; + } + void setPlannerData(const std::shared_ptr & planner_data) { planner_data_ = planner_data; } MultiPolygon2d getAreaPolygons() { return area_polygons_; } - virtual GoalCandidates search(const Pose & original_goal_pose) = 0; + virtual GoalCandidates search() = 0; virtual void update([[maybe_unused]] GoalCandidates & goal_candidates) const { return; } protected: - GoalPlannerParameters parameters_; - std::shared_ptr planner_data_; - MultiPolygon2d area_polygons_; + GoalPlannerParameters parameters_{}; + std::shared_ptr planner_data_{nullptr}; + Pose reference_goal_pose_{}; + MultiPolygon2d area_polygons_{}; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp index 396798ee18519..1dc6cc411a31f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp @@ -64,6 +64,9 @@ MarkerArray createTextsMarkerArray( const std::vector & poses, std::string && ns, const std_msgs::msg::ColorRGBA & color); MarkerArray createGoalCandidatesMarkerArray( GoalCandidates & goal_candidates, const std_msgs::msg::ColorRGBA & color); +MarkerArray createNumObjectsToAvoidTextsMarkerArray( + const GoalCandidates & goal_candidates, std::string && ns, + const std_msgs::msg::ColorRGBA & color); } // namespace goal_planner_utils } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index cffde017ccbe1..5d9058890f351 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -68,22 +68,17 @@ GoalPlannerModule::GoalPlannerModule( // planner when goal modification is not allowed fixed_goal_planner_ = std::make_unique(); - // set enabled planner - if (parameters_->enable_shift_parking) { - pull_over_planners_.push_back(std::make_shared( - node, *parameters, lane_departure_checker, occupancy_grid_map_)); - } - - // set geometric pull over - if (parameters_->enable_arc_forward_parking) { - constexpr bool is_forward = true; - pull_over_planners_.push_back(std::make_shared( - node, *parameters, lane_departure_checker, occupancy_grid_map_, is_forward)); - } - if (parameters_->enable_arc_backward_parking) { - constexpr bool is_forward = false; - pull_over_planners_.push_back(std::make_shared( - node, *parameters, lane_departure_checker, occupancy_grid_map_, is_forward)); + for (const std::string & planner_type : parameters_->efficient_path_order) { + if (planner_type == "SHIFT" && parameters_->enable_shift_parking) { + pull_over_planners_.push_back(std::make_shared( + node, *parameters, lane_departure_checker, occupancy_grid_map_)); + } else if (planner_type == "ARC_FORWARD" && parameters_->enable_arc_forward_parking) { + pull_over_planners_.push_back(std::make_shared( + node, *parameters, lane_departure_checker, occupancy_grid_map_, /*is_forward*/ true)); + } else if (planner_type == "ARC_BACKWARD" && parameters_->enable_arc_backward_parking) { + pull_over_planners_.push_back(std::make_shared( + node, *parameters, lane_departure_checker, occupancy_grid_map_, /*is_forward*/ false)); + } } if (pull_over_planners_.empty()) { @@ -551,7 +546,8 @@ void GoalPlannerModule::generateGoalCandidates() refined_goal_pose_ = calcRefinedGoal(goal_pose); if (goal_planner_utils::isAllowedGoalModification(route_handler)) { goal_searcher_->setPlannerData(planner_data_); - goal_candidates_ = goal_searcher_->search(refined_goal_pose_); + goal_searcher_->setReferenceGoal(refined_goal_pose_); + goal_candidates_ = goal_searcher_->search(); } else { GoalCandidate goal_candidate{}; goal_candidate.goal_pose = goal_pose; @@ -577,15 +573,47 @@ BehaviorModuleOutput GoalPlannerModule::plan() } } +void GoalPlannerModule::sortPullOverPathCandidatesByGoalPriority( + std::vector & pull_over_path_candidates, + const GoalCandidates & goal_candidates) const +{ + // Create a map of goal_id to its index in goal_candidates + std::map goal_id_to_index; + for (size_t i = 0; i < goal_candidates.size(); ++i) { + goal_id_to_index[goal_candidates[i].id] = i; + } + + // Sort pull_over_path_candidates based on the order in goal_candidates + std::stable_sort( + pull_over_path_candidates.begin(), pull_over_path_candidates.end(), + [&goal_id_to_index](const auto & a, const auto & b) { + return goal_id_to_index[a.goal_id] < goal_id_to_index[b.goal_id]; + }); + + // Sort pull_over_path_candidates based on the order in efficient_path_order + if (parameters_->path_priority == "efficient_path") { + std::stable_sort( + pull_over_path_candidates.begin(), pull_over_path_candidates.end(), + [this](const auto & a, const auto & b) { + const auto & order = parameters_->efficient_path_order; + const auto a_pos = std::find(order.begin(), order.end(), magic_enum::enum_name(a.type)); + const auto b_pos = std::find(order.begin(), order.end(), magic_enum::enum_name(b.type)); + return a_pos < b_pos; + }); + } +} + void GoalPlannerModule::selectSafePullOverPath() { // select safe lane pull over path from candidates mutex_.lock(); goal_searcher_->setPlannerData(planner_data_); goal_searcher_->update(goal_candidates_); + sortPullOverPathCandidatesByGoalPriority(pull_over_path_candidates_, goal_candidates_); const auto pull_over_path_candidates = pull_over_path_candidates_; const auto goal_candidates = goal_candidates_; mutex_.unlock(); + status_.is_safe_static_objects = false; for (const auto & pull_over_path : pull_over_path_candidates) { // check if goal is safe diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp index 79459d6efb202..41d0299bca427 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/manager.cpp @@ -48,7 +48,8 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( p.goal_priority = node->declare_parameter(ns + "goal_priority"); p.minimum_weighted_distance_lateral_weight = node->declare_parameter(ns + "minimum_weighted_distance.lateral_weight"); - p.path_priority = node->declare_parameter(ns + "path_priority"); + p.prioritize_goals_before_objects = + node->declare_parameter(ns + "prioritize_goals_before_objects"); p.forward_goal_search_length = node->declare_parameter(ns + "forward_goal_search_length"); p.backward_goal_search_length = @@ -112,6 +113,9 @@ GoalPlannerModuleManager::GoalPlannerModuleManager( p.decide_path_distance = node->declare_parameter(ns + "decide_path_distance"); p.maximum_deceleration = node->declare_parameter(ns + "maximum_deceleration"); p.maximum_jerk = node->declare_parameter(ns + "maximum_jerk"); + p.path_priority = node->declare_parameter(ns + "path_priority"); + p.efficient_path_order = + node->declare_parameter>(ns + "efficient_path_order"); } // shift parking diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index 5e55d712f4f25..11173154f6b3a 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -21,6 +21,7 @@ #include "lanelet2_extension/regulatory_elements/no_stopping_area.hpp" #include "lanelet2_extension/utility/utilities.hpp" #include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include #include @@ -41,8 +42,20 @@ using tier4_autoware_utils::inverseTransformPose; // Sort with smaller longitudinal distances taking precedence over smaller lateral distances. struct SortByLongitudinalDistance { + bool prioritize_goals_before_objects{false}; + explicit SortByLongitudinalDistance(bool prioritize_goals_before_objects) + : prioritize_goals_before_objects(prioritize_goals_before_objects) + { + } + bool operator()(const GoalCandidate & a, const GoalCandidate & b) const noexcept { + if (prioritize_goals_before_objects) { + if (a.num_objects_to_avoid != b.num_objects_to_avoid) { + return a.num_objects_to_avoid < b.num_objects_to_avoid; + } + } + const double diff = a.distance_from_original_goal - b.distance_from_original_goal; constexpr double eps = 0.01; // If the longitudinal distances are approximately equal, sort based on lateral offset. @@ -58,10 +71,21 @@ struct SortByLongitudinalDistance struct SortByWeightedDistance { double lateral_cost{0.0}; - explicit SortByWeightedDistance(double cost) : lateral_cost(cost) {} + bool prioritize_goals_before_objects{false}; + + SortByWeightedDistance(double cost, bool prioritize_goals_before_objects) + : lateral_cost(cost), prioritize_goals_before_objects(prioritize_goals_before_objects) + { + } bool operator()(const GoalCandidate & a, const GoalCandidate & b) const noexcept { + if (prioritize_goals_before_objects) { + if (a.num_objects_to_avoid != b.num_objects_to_avoid) { + return a.num_objects_to_avoid < b.num_objects_to_avoid; + } + } + return a.distance_from_original_goal + lateral_cost * a.lateral_offset < b.distance_from_original_goal + lateral_cost * b.lateral_offset; } @@ -77,7 +101,7 @@ GoalSearcher::GoalSearcher( { } -GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) +GoalCandidates GoalSearcher::search() { GoalCandidates goal_candidates{}; @@ -101,7 +125,7 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) lanes.insert(lanes.end(), pull_over_lanes.begin(), pull_over_lanes.end()); const auto goal_arc_coords = - lanelet::utils::getArcCoordinates(pull_over_lanes, original_goal_pose); + lanelet::utils::getArcCoordinates(pull_over_lanes, reference_goal_pose_); const double s_start = std::max(0.0, goal_arc_coords.length - backward_length); const double s_end = goal_arc_coords.length + forward_length; auto center_line_path = utils::resamplePathWithSpline( @@ -131,10 +155,11 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) const double sign = left_side_parking_ ? -1.0 : 1.0; const double offset_from_center_line = -distance_from_bound.value() + sign * margin_from_boundary; + // original means non lateral offset poses const Pose original_search_pose = calcOffsetPose(center_pose, 0, offset_from_center_line, 0); const double longitudinal_distance_from_original_goal = std::abs(motion_utils::calcSignedArcLength( - center_line_path.points, original_goal_pose.position, original_search_pose.position)); + center_line_path.points, reference_goal_pose_.position, original_search_pose.position)); original_search_poses.push_back(original_search_pose); // for createAreaPolygon Pose search_pose{}; // search goal_pose in lateral direction @@ -170,17 +195,75 @@ GoalCandidates GoalSearcher::search(const Pose & original_goal_pose) } createAreaPolygons(original_search_poses); - if (parameters_.goal_priority == "minimum_weighted_distance") { - std::sort( - goal_candidates.begin(), goal_candidates.end(), - SortByWeightedDistance(parameters_.minimum_weighted_distance_lateral_weight)); - } else if (parameters_.goal_priority == "minimum_longitudinal_distance") { - std::sort(goal_candidates.begin(), goal_candidates.end(), SortByLongitudinalDistance()); - } + update(goal_candidates); return goal_candidates; } +void GoalSearcher::countObjectsToAvoid( + GoalCandidates & goal_candidates, const PredictedObjects & objects) const +{ + const auto & route_handler = planner_data_->route_handler; + const double forward_length = parameters_.forward_goal_search_length; + const double backward_length = parameters_.backward_goal_search_length; + + // calculate search start/end pose in pull over lanes + const auto [search_start_pose, search_end_pose] = std::invoke([&]() -> std::pair { + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *route_handler, left_side_parking_, parameters_.backward_goal_search_length, + parameters_.forward_goal_search_length); + const auto goal_arc_coords = + lanelet::utils::getArcCoordinates(pull_over_lanes, reference_goal_pose_); + const double s_start = std::max(0.0, goal_arc_coords.length - backward_length); + const double s_end = goal_arc_coords.length + forward_length; + const auto center_line_path = utils::resamplePathWithSpline( + route_handler->getCenterLinePath(pull_over_lanes, s_start, s_end), + parameters_.goal_search_interval); + return std::make_pair( + center_line_path.points.front().point.pose, center_line_path.points.back().point.pose); + }); + + // generate current lane center line path to check collision with objects + const auto current_lanes = utils::getExtendedCurrentLanes( + planner_data_, parameters_.backward_goal_search_length, parameters_.forward_goal_search_length, + /*forward_only_in_route*/ false); + const auto current_center_line_path = std::invoke([&]() -> PathWithLaneId { + const double s_start = + lanelet::utils::getArcCoordinates(current_lanes, search_start_pose).length; + const double s_end = lanelet::utils::getArcCoordinates(current_lanes, search_end_pose).length; + return utils::resamplePathWithSpline( + route_handler->getCenterLinePath(current_lanes, s_start, s_end), 1.0); + }); + + // reset num_objects_to_avoid + for (auto & goal_candidate : goal_candidates) { + goal_candidate.num_objects_to_avoid = 0; + } + + // count number of objects to avoid + for (const auto & object : objects.objects) { + for (const auto & p : current_center_line_path.points) { + const auto transformed_vehicle_footprint = + transformVector(vehicle_footprint_, tier4_autoware_utils::pose2transform(p.point.pose)); + const auto obj_polygon = tier4_autoware_utils::toPolygon2d(object); + const double distance = boost::geometry::distance(obj_polygon, transformed_vehicle_footprint); + if (distance > parameters_.object_recognition_collision_check_margin) { + continue; + } + const Pose & object_pose = object.kinematics.initial_pose_with_covariance.pose; + const double s_object = lanelet::utils::getArcCoordinates(current_lanes, object_pose).length; + for (auto & goal_candidate : goal_candidates) { + const Pose & goal_pose = goal_candidate.goal_pose; + const double s_goal = lanelet::utils::getArcCoordinates(current_lanes, goal_pose).length; + if (s_object < s_goal) { + goal_candidate.num_objects_to_avoid++; + } + } + break; + } + } +} + void GoalSearcher::update(GoalCandidates & goal_candidates) const { const auto stop_objects = utils::path_safety_checker::filterObjectsByVelocity( @@ -191,6 +274,22 @@ void GoalSearcher::update(GoalCandidates & goal_candidates) const const auto [pull_over_lane_stop_objects, others] = utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_over_lanes); + if (parameters_.prioritize_goals_before_objects) { + countObjectsToAvoid(goal_candidates, pull_over_lane_stop_objects); + } + + if (parameters_.goal_priority == "minimum_weighted_distance") { + std::sort( + goal_candidates.begin(), goal_candidates.end(), + SortByWeightedDistance( + parameters_.minimum_weighted_distance_lateral_weight, + parameters_.prioritize_goals_before_objects)); + } else if (parameters_.goal_priority == "minimum_longitudinal_distance") { + std::sort( + goal_candidates.begin(), goal_candidates.end(), + SortByLongitudinalDistance(parameters_.prioritize_goals_before_objects)); + } + // update is_safe for (auto & goal_candidate : goal_candidates) { const Pose goal_pose = goal_candidate.goal_pose; @@ -215,7 +314,7 @@ void GoalSearcher::update(GoalCandidates & goal_candidates) const } } -bool GoalSearcher::checkCollision(const Pose & pose, const PredictedObjects & dynamic_objects) const +bool GoalSearcher::checkCollision(const Pose & pose, const PredictedObjects & objects) const { if (parameters_.use_occupancy_grid_for_goal_search) { const Pose pose_grid_coords = global2local(occupancy_grid_map_->getMap(), pose); @@ -229,7 +328,7 @@ bool GoalSearcher::checkCollision(const Pose & pose, const PredictedObjects & dy if (parameters_.use_object_recognition) { if (utils::checkCollisionBetweenFootprintAndObjects( - vehicle_footprint_, pose, dynamic_objects, + vehicle_footprint_, pose, objects, parameters_.object_recognition_collision_check_margin)) { return true; } @@ -238,7 +337,7 @@ bool GoalSearcher::checkCollision(const Pose & pose, const PredictedObjects & dy } bool GoalSearcher::checkCollisionWithLongitudinalDistance( - const Pose & ego_pose, const PredictedObjects & dynamic_objects) const + const Pose & ego_pose, const PredictedObjects & objects) const { if ( parameters_.use_occupancy_grid_for_goal_search && @@ -274,8 +373,7 @@ bool GoalSearcher::checkCollisionWithLongitudinalDistance( if ( utils::calcLongitudinalDistanceFromEgoToObjects( ego_pose, planner_data_->parameters.base_link2front, - planner_data_->parameters.base_link2rear, - dynamic_objects) < parameters_.longitudinal_margin) { + planner_data_->parameters.base_link2rear, objects) < parameters_.longitudinal_margin) { return true; } } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index 038ca76a7fffd..ff26c7a3654c3 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -131,7 +131,7 @@ MarkerArray createPosesMarkerArray( return msg; } -MarkerArray createTextsMarkerArray( +MarkerArray createGoalPriorityTextsMarkerArray( const std::vector & poses, std::string && ns, const std_msgs::msg::ColorRGBA & color) { MarkerArray msg{}; @@ -150,24 +150,52 @@ MarkerArray createTextsMarkerArray( return msg; } +MarkerArray createNumObjectsToAvoidTextsMarkerArray( + const GoalCandidates & goal_candidates, std::string && ns, const std_msgs::msg::ColorRGBA & color) +{ + MarkerArray msg{}; + int32_t i = 0; + for (const auto & goal_candidate : goal_candidates) { + const Pose & pose = goal_candidate.goal_pose; + Marker marker = createDefaultMarker( + "map", rclcpp::Clock{RCL_ROS_TIME}.now(), ns, i, Marker::TEXT_VIEW_FACING, + createMarkerScale(0.3, 0.3, 0.3), color); + marker.pose = calcOffsetPose(pose, -0.5, 0, 1.0); + marker.id = i; + marker.text = std::to_string(goal_candidate.num_objects_to_avoid); + msg.markers.push_back(marker); + i++; + } + + return msg; +} + MarkerArray createGoalCandidatesMarkerArray( GoalCandidates & goal_candidates, const std_msgs::msg::ColorRGBA & color) { - // convert to pose vector + GoalCandidates safe_goal_candidates{}; + std::copy_if( + goal_candidates.begin(), goal_candidates.end(), std::back_inserter(safe_goal_candidates), + [](const auto & goal_candidate) { return goal_candidate.is_safe; }); + std::vector pose_vector{}; - for (const auto & goal_candidate : goal_candidates) { - if (goal_candidate.is_safe) { - pose_vector.push_back(goal_candidate.goal_pose); - } - } + std::transform( + safe_goal_candidates.begin(), safe_goal_candidates.end(), std::back_inserter(pose_vector), + [](const auto & goal_candidate) { return goal_candidate.goal_pose; }); auto marker_array = createPosesMarkerArray(pose_vector, "goal_candidates", color); for (const auto & text_marker : - createTextsMarkerArray( + createGoalPriorityTextsMarkerArray( pose_vector, "goal_candidates_priority", createMarkerColor(1.0, 1.0, 1.0, 0.999)) .markers) { marker_array.markers.push_back(text_marker); } + for (const auto & text_marker : createNumObjectsToAvoidTextsMarkerArray( + safe_goal_candidates, "goal_candidates_num_objects_to_avoid", + createMarkerColor(0.5, 0.5, 0.5, 0.999)) + .markers) { + marker_array.markers.push_back(text_marker); + } return marker_array; } From 4558485999f2e0f063d0b3b5362d5246e08b6a12 Mon Sep 17 00:00:00 2001 From: Ryoma Sakata <66205605+SKT-r@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:52:27 +0900 Subject: [PATCH 353/547] refactor(localization_error_monitor): rework parameters (#5143) Signed-off-by: SKT-r Co-authored-by: SKT-r --- localization/localization_error_monitor/src/node.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/localization/localization_error_monitor/src/node.cpp b/localization/localization_error_monitor/src/node.cpp index 75858c1f4be4d..307dc9445eb28 100644 --- a/localization/localization_error_monitor/src/node.cpp +++ b/localization/localization_error_monitor/src/node.cpp @@ -33,14 +33,14 @@ LocalizationErrorMonitor::LocalizationErrorMonitor() : Node("localization_error_monitor"), updater_(this) { - scale_ = this->declare_parameter("scale", 3.0); - error_ellipse_size_ = this->declare_parameter("error_ellipse_size", 1.0); - warn_ellipse_size_ = this->declare_parameter("warn_ellipse_size", 0.8); + scale_ = this->declare_parameter("scale"); + error_ellipse_size_ = this->declare_parameter("error_ellipse_size"); + warn_ellipse_size_ = this->declare_parameter("warn_ellipse_size"); error_ellipse_size_lateral_direction_ = - this->declare_parameter("error_ellipse_size_lateral_direction", 0.3); + this->declare_parameter("error_ellipse_size_lateral_direction"); warn_ellipse_size_lateral_direction_ = - this->declare_parameter("warn_ellipse_size_lateral_direction", 0.2); + this->declare_parameter("warn_ellipse_size_lateral_direction"); odom_sub_ = this->create_subscription( "input/odom", 1, std::bind(&LocalizationErrorMonitor::onOdom, this, std::placeholders::_1)); From f2263e1c00212a01fe8608de022904bbda8ca2d7 Mon Sep 17 00:00:00 2001 From: Ryoma Sakata <66205605+SKT-r@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:03:14 +0900 Subject: [PATCH 354/547] refactor(gnss_poser): rework parameters (#5140) * refactor(gnss_poser): rework parameters Signed-off-by: SKT-r * remove unnecessary declaration from launch Signed-off-by: kminoda --------- Signed-off-by: SKT-r Signed-off-by: kminoda Co-authored-by: SKT-r Co-authored-by: kminoda --- sensing/gnss_poser/CMakeLists.txt | 1 + .../gnss_poser/config/gnss_poser.param.yaml | 9 +++++++++ .../gnss_poser/launch/gnss_poser.launch.xml | 20 +++---------------- sensing/gnss_poser/src/gnss_poser_core.cpp | 14 ++++++------- 4 files changed, 20 insertions(+), 24 deletions(-) create mode 100644 sensing/gnss_poser/config/gnss_poser.param.yaml diff --git a/sensing/gnss_poser/CMakeLists.txt b/sensing/gnss_poser/CMakeLists.txt index 167f2c51337ce..775cde514945f 100644 --- a/sensing/gnss_poser/CMakeLists.txt +++ b/sensing/gnss_poser/CMakeLists.txt @@ -34,5 +34,6 @@ rclcpp_components_register_node(gnss_poser_node ) ament_auto_package(INSTALL_TO_SHARE + config launch ) diff --git a/sensing/gnss_poser/config/gnss_poser.param.yaml b/sensing/gnss_poser/config/gnss_poser.param.yaml new file mode 100644 index 0000000000000..30be98bba8cf1 --- /dev/null +++ b/sensing/gnss_poser/config/gnss_poser.param.yaml @@ -0,0 +1,9 @@ +/**: + ros__parameters: + base_frame: base_link + gnss_base_frame: gnss_base_link + gnss_frame: gnss + map_frame: map + buff_epoch: 1 + use_gnss_ins_orientation: true + gnss_pose_pub_method: 0 diff --git a/sensing/gnss_poser/launch/gnss_poser.launch.xml b/sensing/gnss_poser/launch/gnss_poser.launch.xml index 60e952c6845f4..d9b850e6995ae 100755 --- a/sensing/gnss_poser/launch/gnss_poser.launch.xml +++ b/sensing/gnss_poser/launch/gnss_poser.launch.xml @@ -1,5 +1,7 @@ + + @@ -7,15 +9,6 @@ - - - - - - - - - @@ -23,13 +16,6 @@ - - - - - - - - + diff --git a/sensing/gnss_poser/src/gnss_poser_core.cpp b/sensing/gnss_poser/src/gnss_poser_core.cpp index 1ae8bce2ed7f2..599a017bffed7 100644 --- a/sensing/gnss_poser/src/gnss_poser_core.cpp +++ b/sensing/gnss_poser/src/gnss_poser_core.cpp @@ -30,14 +30,14 @@ GNSSPoser::GNSSPoser(const rclcpp::NodeOptions & node_options) : rclcpp::Node("gnss_poser", node_options), tf2_listener_(tf2_buffer_), tf2_broadcaster_(*this), - base_frame_(declare_parameter("base_frame", "base_link")), - gnss_frame_(declare_parameter("gnss_frame", "gnss")), - gnss_base_frame_(declare_parameter("gnss_base_frame", "gnss_base_link")), - map_frame_(declare_parameter("map_frame", "map")), - use_gnss_ins_orientation_(declare_parameter("use_gnss_ins_orientation", true)), + base_frame_(declare_parameter("base_frame")), + gnss_frame_(declare_parameter("gnss_frame")), + gnss_base_frame_(declare_parameter("gnss_base_frame")), + map_frame_(declare_parameter("map_frame")), + use_gnss_ins_orientation_(declare_parameter("use_gnss_ins_orientation")), msg_gnss_ins_orientation_stamped_( std::make_shared()), - gnss_pose_pub_method(declare_parameter("gnss_pose_pub_method", 0)) + gnss_pose_pub_method(declare_parameter("gnss_pose_pub_method")) { // Subscribe to map_projector_info topic const auto adaptor = component_interface_utils::NodeAdaptor(this); @@ -45,7 +45,7 @@ GNSSPoser::GNSSPoser(const rclcpp::NodeOptions & node_options) sub_map_projector_info_, [this](const MapProjectorInfo::Message::ConstSharedPtr msg) { callbackMapProjectorInfo(msg); }); - int buff_epoch = declare_parameter("buff_epoch", 1); + int buff_epoch = declare_parameter("buff_epoch"); position_buffer_.set_capacity(buff_epoch); nav_sat_fix_sub_ = create_subscription( From c552c3dedc82f61015a918e505375d0bd04cde6c Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Thu, 28 Sep 2023 10:07:19 +0900 Subject: [PATCH 355/547] fix(drivable_area_expansion): segment index out of range (#5120) Signed-off-by: Takayuki Murooka --- .../utils/drivable_area_expansion/drivable_area_expansion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index d0673d8bb9ea8..60fb2ba2ff2e8 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -60,7 +60,7 @@ std::vector crop_and_resample( // crop const auto crop_seg_idx = motion_utils::findNearestSegmentIndex(points, crop_pose.position); const auto cropped_points = motion_utils::cropPoints( - points, crop_pose.position, crop_seg_idx + 1, + points, crop_pose.position, crop_seg_idx, planner_data->drivable_area_expansion_parameters.max_path_arc_length, 0.0); planner_data->drivable_area_expansion_prev_crop_pose = crop_pose; // resample From 32c2e67ce4be25475120ff7599381d53dac60dd0 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:07:18 +0900 Subject: [PATCH 356/547] feat(time_synchronizer): organize twist input (#5160) * feat(time_synchronizer): organize twist input Signed-off-by: kminoda * fix Signed-off-by: kminoda * style(pre-commit): autofix --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../time_synchronizer_nodelet.hpp | 11 ++-- .../launch/preprocessor.launch.py | 5 +- .../time_synchronizer_nodelet.cpp | 54 +++++++++++++++---- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/time_synchronizer/time_synchronizer_nodelet.hpp b/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/time_synchronizer/time_synchronizer_nodelet.hpp index f30c02a97a6c0..8c3cb98ba266e 100644 --- a/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/time_synchronizer/time_synchronizer_nodelet.hpp +++ b/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/time_synchronizer/time_synchronizer_nodelet.hpp @@ -68,9 +68,10 @@ #include #include -#include #include #include +#include +#include #include #include #include @@ -127,11 +128,14 @@ class PointCloudDataSynchronizerComponent : public rclcpp::Node /** \brief A vector of subscriber. */ std::vector::SharedPtr> filters_; - rclcpp::Subscription::SharedPtr sub_twist_; + rclcpp::Subscription::SharedPtr sub_twist_; + rclcpp::Subscription::SharedPtr sub_odom_; rclcpp::TimerBase::SharedPtr timer_; diagnostic_updater::Updater updater_{this}; + const std::string input_twist_topic_type_; + /** \brief Output TF frame the concatenated points should be transformed to. */ std::string output_frame_; @@ -168,7 +172,8 @@ class PointCloudDataSynchronizerComponent : public rclcpp::Node void cloud_callback( const sensor_msgs::msg::PointCloud2::ConstSharedPtr & input_ptr, const std::string & topic_name); - void twist_callback(const autoware_auto_vehicle_msgs::msg::VelocityReport::ConstSharedPtr input); + void twist_callback(const geometry_msgs::msg::TwistWithCovarianceStamped::ConstSharedPtr input); + void odom_callback(const nav_msgs::msg::Odometry::ConstSharedPtr input); void timer_callback(); void checkSyncStatus(diagnostic_updater::DiagnosticStatusWrapper & stat); diff --git a/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py b/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py index 0d65b45b1b3de..d82c23152aee4 100644 --- a/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py +++ b/sensing/pointcloud_preprocessor/launch/preprocessor.launch.py @@ -58,7 +58,10 @@ def launch_setup(context, *args, **kwargs): package=pkg, plugin="pointcloud_preprocessor::PointCloudDataSynchronizerComponent", name="synchronizer_filter", - remappings=[("output", "points_raw/concatenated")], + remappings=[ + ("~/input/twist", "/sensing/vehicle_velocity_converter/twist_with_covariance"), + ("output", "points_raw/concatenated"), + ], parameters=[ { "input_topics": LaunchConfiguration("input_points_raw_list"), diff --git a/sensing/pointcloud_preprocessor/src/time_synchronizer/time_synchronizer_nodelet.cpp b/sensing/pointcloud_preprocessor/src/time_synchronizer/time_synchronizer_nodelet.cpp index a6bf64b835f61..5d261f42391e9 100644 --- a/sensing/pointcloud_preprocessor/src/time_synchronizer/time_synchronizer_nodelet.cpp +++ b/sensing/pointcloud_preprocessor/src/time_synchronizer/time_synchronizer_nodelet.cpp @@ -41,7 +41,8 @@ namespace pointcloud_preprocessor { PointCloudDataSynchronizerComponent::PointCloudDataSynchronizerComponent( const rclcpp::NodeOptions & node_options) -: Node("point_cloud_time_synchronizer_component", node_options) +: Node("point_cloud_time_synchronizer_component", node_options), + input_twist_topic_type_(declare_parameter("input_twist_topic_type", "twist")) { // initialize debug tool { @@ -129,10 +130,21 @@ PointCloudDataSynchronizerComponent::PointCloudDataSynchronizerComponent( } // Subscribe to the twist - auto twist_cb = - std::bind(&PointCloudDataSynchronizerComponent::twist_callback, this, std::placeholders::_1); - sub_twist_ = this->create_subscription( - "/vehicle/status/velocity_status", rclcpp::QoS{100}, twist_cb); + if (input_twist_topic_type_ == "twist") { + auto twist_cb = std::bind( + &PointCloudDataSynchronizerComponent::twist_callback, this, std::placeholders::_1); + sub_twist_ = this->create_subscription( + "~/input/twist", rclcpp::QoS{100}, twist_cb); + } else if (input_twist_topic_type_ == "odom") { + auto odom_cb = + std::bind(&PointCloudDataSynchronizerComponent::odom_callback, this, std::placeholders::_1); + sub_odom_ = this->create_subscription( + "~/input/odom", rclcpp::QoS{100}, odom_cb); + } else { + RCLCPP_ERROR_STREAM( + get_logger(), "input_twist_topic_type is invalid: " << input_twist_topic_type_); + throw std::runtime_error("input_twist_topic_type is invalid: " + input_twist_topic_type_); + } } // Transformed Raw PointCloud2 Publisher @@ -474,7 +486,33 @@ void PointCloudDataSynchronizerComponent::timer_callback() } void PointCloudDataSynchronizerComponent::twist_callback( - const autoware_auto_vehicle_msgs::msg::VelocityReport::ConstSharedPtr input) + const geometry_msgs::msg::TwistWithCovarianceStamped::ConstSharedPtr input) +{ + // if rosbag restart, clear buffer + if (!twist_ptr_queue_.empty()) { + if (rclcpp::Time(twist_ptr_queue_.front()->header.stamp) > rclcpp::Time(input->header.stamp)) { + twist_ptr_queue_.clear(); + } + } + + // pop old data + while (!twist_ptr_queue_.empty()) { + if ( + rclcpp::Time(twist_ptr_queue_.front()->header.stamp) + rclcpp::Duration::from_seconds(1.0) > + rclcpp::Time(input->header.stamp)) { + break; + } + twist_ptr_queue_.pop_front(); + } + + auto twist_ptr = std::make_shared(); + twist_ptr->header.stamp = input->header.stamp; + twist_ptr->twist = input->twist.twist; + twist_ptr_queue_.push_back(twist_ptr); +} + +void PointCloudDataSynchronizerComponent::odom_callback( + const nav_msgs::msg::Odometry::ConstSharedPtr input) { // if rosbag restart, clear buffer if (!twist_ptr_queue_.empty()) { @@ -495,9 +533,7 @@ void PointCloudDataSynchronizerComponent::twist_callback( auto twist_ptr = std::make_shared(); twist_ptr->header.stamp = input->header.stamp; - twist_ptr->twist.linear.x = input->longitudinal_velocity; - twist_ptr->twist.linear.y = input->lateral_velocity; - twist_ptr->twist.angular.z = input->heading_rate; + twist_ptr->twist = input->twist.twist; twist_ptr_queue_.push_back(twist_ptr); } From 17984b455b9f327a0942b0084c89c1bceab7a959 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 28 Sep 2023 13:24:02 +0900 Subject: [PATCH 357/547] feat(intersection): use planned velocity from upstream modules (#5156) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 2 ++ .../package.xml | 1 + .../src/manager.cpp | 4 ++++ .../src/scene_intersection.cpp | 15 +++++++++----- .../src/scene_intersection.hpp | 5 ++++- .../src/util.cpp | 20 ++++++++++++------- .../src/util.hpp | 5 +++-- 7 files changed, 37 insertions(+), 15 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index a932254140878..4042adeb3623e 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -34,6 +34,8 @@ collision_start_margin_time: 4.0 # [s] this + state_transit_margin_time should be higher to account for collision with fast/accelerating object collision_end_margin_time: 6.0 # [s] this + state_transit_margin_time should be higher to account for collision with slow/decelerating object keep_detection_vel_thr: 0.833 # == 3.0km/h. keep detection if ego is ego.vel < keep_detection_vel_thr + use_upstream_velocity: true # flag to use the planned velocity profile from the upstream module + minimum_upstream_velocity: 0.01 # [m/s] minimum velocity to avoid null division for the stop line from the upstream velocity occlusion: enable: false diff --git a/planning/behavior_velocity_intersection_module/package.xml b/planning/behavior_velocity_intersection_module/package.xml index 879b26209a05e..9443295981679 100644 --- a/planning/behavior_velocity_intersection_module/package.xml +++ b/planning/behavior_velocity_intersection_module/package.xml @@ -9,6 +9,7 @@ Takayuki Murooka Tomoya Kimura Shumpei Wakabayashi + Kyoichi Sugahara Apache License 2.0 diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 28cc7a1ad2881..e02a1e4220030 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -103,6 +103,10 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) node, ns + ".collision_detection.not_prioritized.collision_end_margin_time"); ip.collision_detection.keep_detection_vel_thr = getOrDeclareParameter(node, ns + ".collision_detection.keep_detection_vel_thr"); + ip.collision_detection.use_upstream_velocity = + getOrDeclareParameter(node, ns + ".collision_detection.use_upstream_velocity"); + ip.collision_detection.minimum_upstream_velocity = + getOrDeclareParameter(node, ns + ".collision_detection.minimum_upstream_velocity"); ip.occlusion.enable = getOrDeclareParameter(node, ns + ".occlusion.enable"); ip.occlusion.occlusion_attention_area_length = diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index fec7589f3c18f..ca1cbfdd0ccc7 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -911,7 +911,9 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( filterTargetObjects(attention_lanelets, adjacent_lanelets, intersection_area); const bool has_collision = checkCollision( - *path, target_objects, path_lanelets, closest_idx, time_delay, traffic_prioritized_level); + *path, target_objects, path_lanelets, closest_idx, + std::min(occlusion_stop_line_idx, path->points.size() - 1), time_delay, + traffic_prioritized_level); collision_state_machine_.setStateWithMarginTime( has_collision ? StateMachine::State::STOP : StateMachine::State::GO, logger_.get_child("collision state_machine"), *clock_); @@ -1097,7 +1099,8 @@ autoware_auto_perception_msgs::msg::PredictedObjects IntersectionModule::filterT bool IntersectionModule::checkCollision( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const autoware_auto_perception_msgs::msg::PredictedObjects & objects, - const util::PathLanelets & path_lanelets, const int closest_idx, const double time_delay, + const util::PathLanelets & path_lanelets, const size_t closest_idx, + const size_t last_intersection_stop_line_candidate_idx, const double time_delay, const util::TrafficPrioritizedLevel & traffic_prioritized_level) { using lanelet::utils::getArcCoordinates; @@ -1106,9 +1109,11 @@ bool IntersectionModule::checkCollision( // check collision between target_objects predicted path and ego lane // cut the predicted path at passing_time const auto time_distance_array = util::calcIntersectionPassingTime( - path, planner_data_, associative_ids_, closest_idx, time_delay, - planner_param_.common.intersection_velocity, - planner_param_.collision_detection.minimum_ego_predicted_velocity); + path, planner_data_, associative_ids_, closest_idx, last_intersection_stop_line_candidate_idx, + time_delay, planner_param_.common.intersection_velocity, + planner_param_.collision_detection.minimum_ego_predicted_velocity, + planner_param_.collision_detection.use_upstream_velocity, + planner_param_.collision_detection.minimum_upstream_velocity); const double passing_time = time_distance_array.back().first; auto target_objects = objects; util::cutPredictPathWithDuration(&target_objects, clock_, passing_time); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 89444bb71c947..550b23aa17606 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -95,6 +95,8 @@ class IntersectionModule : public SceneModuleInterface double collision_end_margin_time; //! end margin time to check collision } not_prioritized; double keep_detection_vel_thr; //! keep detection if ego is ego.vel < keep_detection_vel_thr + bool use_upstream_velocity; + double minimum_upstream_velocity; } collision_detection; struct Occlusion { @@ -260,7 +262,8 @@ class IntersectionModule : public SceneModuleInterface bool checkCollision( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const autoware_auto_perception_msgs::msg::PredictedObjects & target_objects, - const util::PathLanelets & path_lanelets, const int closest_idx, const double time_delay, + const util::PathLanelets & path_lanelets, const size_t closest_idx, + const size_t last_intersection_stop_line_candidate_idx, const double time_delay, const util::TrafficPrioritizedLevel & traffic_prioritized_level); bool isOcclusionCleared( diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index a3bebecbd2bca..fff1d08223e5f 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -1108,8 +1108,9 @@ void cutPredictPathWithDuration( TimeDistanceArray calcIntersectionPassingTime( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const std::shared_ptr & planner_data, const std::set & associative_ids, - const int closest_idx, const double time_delay, const double intersection_velocity, - const double minimum_ego_velocity) + const size_t closest_idx, const size_t last_intersection_stop_line_candidate_idx, + const double time_delay, const double intersection_velocity, const double minimum_ego_velocity, + const bool use_upstream_velocity, const double minimum_upstream_velocity) { double dist_sum = 0.0; int assigned_lane_found = false; @@ -1119,7 +1120,9 @@ TimeDistanceArray calcIntersectionPassingTime( PathWithLaneId reference_path; for (size_t i = closest_idx; i < path.points.size(); ++i) { auto reference_point = path.points.at(i); - reference_point.point.longitudinal_velocity_mps = intersection_velocity; + if (!use_upstream_velocity) { + reference_point.point.longitudinal_velocity_mps = intersection_velocity; + } reference_path.points.push_back(reference_point); bool has_objective_lane_id = hasLaneIds(path.points.at(i), associative_ids); if (assigned_lane_found && !has_objective_lane_id) { @@ -1150,10 +1153,13 @@ TimeDistanceArray calcIntersectionPassingTime( // use average velocity between p1 and p2 const double average_velocity = (p1.point.longitudinal_velocity_mps + p2.point.longitudinal_velocity_mps) / 2.0; - passing_time += - (dist / std::max( - minimum_ego_velocity, - average_velocity)); // to avoid zero-division + const double minimum_ego_velocity_division = + (use_upstream_velocity && i > last_intersection_stop_line_candidate_idx) + ? minimum_upstream_velocity /* to avoid null division */ + : minimum_ego_velocity; + const double passing_velocity = + std::max(minimum_ego_velocity_division, average_velocity); + passing_time += (dist / passing_velocity); time_distance_array.emplace_back(passing_time, dist_sum); } diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 72e96876f02c9..f02362e3f803d 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -147,8 +147,9 @@ void cutPredictPathWithDuration( TimeDistanceArray calcIntersectionPassingTime( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const std::shared_ptr & planner_data, const std::set & associative_ids, - const int closest_idx, const double time_delay, const double intersection_velocity, - const double minimum_ego_velocity); + const size_t closest_idx, const size_t last_intersection_stop_line_candidate_idx, + const double time_delay, const double intersection_velocity, const double minimum_ego_velocity, + const bool use_upstream_velocity, const double minimum_upstream_velocity); double calcDistanceUntilIntersectionLanelet( const lanelet::ConstLanelet & assigned_lanelet, From c56423ddcc6420089952ccb24bc171f9c199620c Mon Sep 17 00:00:00 2001 From: TaikiYamada4 <129915538+TaikiYamada4@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:41:41 +0900 Subject: [PATCH 358/547] refactor(ndt_scan_matcher): delete default parameters from ndt_scan_matcher_core.cpp (#5155) * Removed default paramters in the constructor in ndt_scan_matcher_core.cpp Extracted paramter map_frame to ndt_scan_matcher.param.yaml Signed-off-by: TaikiYamada4 * Updated README.md to match ndt_scan_matcher.param.yaml Fixed minor grammar mistakes. Signed-off-by: TaikiYamada4 * style(pre-commit): autofix * style(pre-commit): autofix * Fixed minor grammar mistakes Signed-off-by: TaikiYamada4 * Removed unnecessary value initialization for parameters that should be defined in ndt_scan_matcher.param.yaml Signed-off-by: TaikiYamada4 --------- Signed-off-by: TaikiYamada4 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- localization/ndt_scan_matcher/README.md | 34 +++++++++++-------- .../config/ndt_scan_matcher.param.yaml | 11 +++--- .../src/ndt_scan_matcher_core.cpp | 32 +++++++---------- 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/localization/ndt_scan_matcher/README.md b/localization/ndt_scan_matcher/README.md index c5a14d94ebed3..a1b9339b9780a 100644 --- a/localization/ndt_scan_matcher/README.md +++ b/localization/ndt_scan_matcher/README.md @@ -54,19 +54,25 @@ One optional function is regularization. Please see the regularization chapter i ### Core Parameters -| Name | Type | Description | -| --------------------------------------- | ------ | ----------------------------------------------------------------------------------------------- | -| `base_frame` | string | Vehicle reference frame | -| `ndt_base_frame` | string | NDT reference frame | -| `input_sensor_points_queue_size` | int | Subscriber queue size | -| `trans_epsilon` | double | The maximum difference between two consecutive transformations in order to consider convergence | -| `step_size` | double | The newton line search maximum step length | -| `resolution` | double | The ND voxel grid resolution [m] | -| `max_iterations` | int | The number of iterations required to calculate alignment | -| `converged_param_type` | int | The type of indicators for scan matching score (0: TP, 1: NVTL) | -| `converged_param_transform_probability` | double | Threshold for deciding whether to trust the estimation result | -| `lidar_topic_timeout_sec` | double | Tolerance of timestamp difference between current time and sensor pointcloud | -| `num_threads` | int | Number of threads used for parallel computing | +| Name | Type | Description | +| --------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------- | +| `base_frame` | string | Vehicle reference frame | +| `ndt_base_frame` | string | NDT reference frame | +| `map_frame` | string | map frame | +| `input_sensor_points_queue_size` | int | Subscriber queue size | +| `trans_epsilon` | double | The max difference between two consecutive transformations to consider convergence | +| `step_size` | double | The newton line search maximum step length | +| `resolution` | double | The ND voxel grid resolution [m] | +| `max_iterations` | int | The number of iterations required to calculate alignment | +| `converged_param_type` | int | The type of indicators for scan matching score (0: TP, 1: NVTL) | +| `converged_param_transform_probability` | double | TP threshold for deciding whether to trust the estimation result (when converged_param_type = 0) | +| `converged_param_nearest_voxel_transformation_likelihood` | double | NVTL threshold for deciding whether to trust the estimation result (when converged_param_type = 1) | +| `initial_estimate_particles_num` | int | The number of particles to estimate initial pose | +| `lidar_topic_timeout_sec` | double | Tolerance of timestamp difference between current time and sensor pointcloud | +| `initial_pose_timeout_sec` | int | Tolerance of timestamp difference between initial_pose and sensor pointcloud. [sec] | +| `initial_pose_distance_tolerance_m` | double | Tolerance of distance difference between two initial poses used for linear interpolation. [m] | +| `num_threads` | int | Number of threads used for parallel computing | +| `output_pose_covariance` | std::array | The covariance of output pose | (TP: Transform Probability, NVTL: Nearest Voxel Transform Probability) @@ -238,7 +244,7 @@ Here is a split PCD map for `sample-map-rosbag` from Autoware tutorial: [`sample ### Abstract -This is a function that using de-grounded LiDAR scan estimate scan matching score.This score can more accurately reflect the current localization performance. +This is a function that uses de-grounded LiDAR scan to estimate the scan matching score. This score can reflect the current localization performance more accurately. [related issue](https://github.com/autowarefoundation/autoware.universe/issues/2044). ### Parameters diff --git a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml index ecd6c65fa11df..718bc6ca3b3a3 100644 --- a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml +++ b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml @@ -9,6 +9,9 @@ # NDT reference frame ndt_base_frame: "ndt_base_link" + # map frame + map_frame: "map" + # Subscriber queue size input_sensor_points_queue_size: 1 @@ -53,7 +56,7 @@ num_threads: 4 # The covariance of output pose - # Do note that this covariance matrix is empirically derived + # Note that this covariance matrix is empirically derived output_pose_covariance: [ 0.0225, 0.0, 0.0, 0.0, 0.0, 0.0, @@ -67,7 +70,7 @@ # Regularization switch regularization_enabled: false - # regularization scale factor + # Regularization scale factor regularization_scale_factor: 0.01 # Dynamic map loading distance @@ -86,5 +89,5 @@ # If lidar_point.z - base_link.z <= this threshold , the point will be removed z_margin_for_ground_removal: 0.8 - # The execution time which means probably NDT cannot matches scans properly - critical_upper_bound_exe_time_ms: 100 # [ms] + # The execution time which means probably NDT cannot matches scans properly. [ms] + critical_upper_bound_exe_time_ms: 100 diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 44c763721e159..d9054d77d2256 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -84,24 +84,9 @@ NDTScanMatcher::NDTScanMatcher() tf2_broadcaster_(*this), ndt_ptr_(new NormalDistributionsTransform), state_ptr_(new std::map), - base_frame_("base_link"), - ndt_base_frame_("ndt_base_link"), - map_frame_("map"), - converged_param_type_(ConvergedParamType::TRANSFORM_PROBABILITY), - converged_param_transform_probability_(4.5), - converged_param_nearest_voxel_transformation_likelihood_(2.3), - initial_estimate_particles_num_(100), - lidar_topic_timeout_sec_(1.0), - initial_pose_timeout_sec_(1.0), - initial_pose_distance_tolerance_m_(10.0), - inversion_vector_threshold_(-0.9), - oscillation_threshold_(10), - output_pose_covariance_(), - regularization_enabled_(declare_parameter("regularization_enabled")), - estimate_scores_for_degrounded_scan_( - declare_parameter("estimate_scores_for_degrounded_scan", false)), - z_margin_for_ground_removal_(declare_parameter("z_margin_for_ground_removal", 0.8)), - critical_upper_bound_exe_time_ms_(100) + inversion_vector_threshold_(-0.9), // Not necessary to extract to ndt_scan_matcher.param.yaml + oscillation_threshold_(10), // Not necessary to extract to ndt_scan_matcher.param.yaml + regularization_enabled_(declare_parameter("regularization_enabled")) { (*state_ptr_)["state"] = "Initializing"; is_activated_ = false; @@ -116,6 +101,9 @@ NDTScanMatcher::NDTScanMatcher() ndt_base_frame_ = this->declare_parameter("ndt_base_frame"); RCLCPP_INFO(get_logger(), "ndt_base_frame_id: %s", ndt_base_frame_.c_str()); + map_frame_ = this->declare_parameter("map_frame"); + RCLCPP_INFO(get_logger(), "map_frame_id: %s", map_frame_.c_str()); + pclomp::NdtParams ndt_params{}; ndt_params.trans_epsilon = this->declare_parameter("trans_epsilon"); ndt_params.step_size = this->declare_parameter("step_size"); @@ -141,8 +129,9 @@ NDTScanMatcher::NDTScanMatcher() this->declare_parameter("converged_param_nearest_voxel_transformation_likelihood"); lidar_topic_timeout_sec_ = this->declare_parameter("lidar_topic_timeout_sec"); + critical_upper_bound_exe_time_ms_ = - this->declare_parameter("critical_upper_bound_exe_time_ms", critical_upper_bound_exe_time_ms_); + this->declare_parameter("critical_upper_bound_exe_time_ms"); initial_pose_timeout_sec_ = this->declare_parameter("initial_pose_timeout_sec"); @@ -157,6 +146,11 @@ NDTScanMatcher::NDTScanMatcher() initial_estimate_particles_num_ = this->declare_parameter("initial_estimate_particles_num"); + estimate_scores_for_degrounded_scan_ = + this->declare_parameter("estimate_scores_for_degrounded_scan"); + + z_margin_for_ground_removal_ = this->declare_parameter("z_margin_for_ground_removal"); + rclcpp::CallbackGroup::SharedPtr initial_pose_callback_group; initial_pose_callback_group = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); From 7e9b4dedd71a88472233e9f6f20d16bd7e54baf2 Mon Sep 17 00:00:00 2001 From: Shumpei Wakabayashi <42209144+shmpwk@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:51:01 +0900 Subject: [PATCH 359/547] feat(pose_twist_estimator): automatically initialize pose only with gnss (#5115) --- .../pose_twist_estimator.launch.xml | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/launch/tier4_localization_launch/launch/pose_twist_estimator/pose_twist_estimator.launch.xml b/launch/tier4_localization_launch/launch/pose_twist_estimator/pose_twist_estimator.launch.xml index abbb15c797d19..7733b4e3117a1 100644 --- a/launch/tier4_localization_launch/launch/pose_twist_estimator/pose_twist_estimator.launch.xml +++ b/launch/tier4_localization_launch/launch/pose_twist_estimator/pose_twist_estimator.launch.xml @@ -20,7 +20,9 @@ - + + + @@ -42,7 +44,9 @@ - + + + @@ -74,7 +78,9 @@ - + + + @@ -99,7 +105,9 @@ - + + + @@ -131,7 +139,9 @@ - + + + From 3eda01859adbbc4c72f859880a76374b2d023273 Mon Sep 17 00:00:00 2001 From: "awf-autoware-bot[bot]" <94889083+awf-autoware-bot[bot]@users.noreply.github.com> Date: Thu, 28 Sep 2023 08:31:21 +0000 Subject: [PATCH 360/547] chore: update CODEOWNERS (#5087) Signed-off-by: GitHub Co-authored-by: kminoda --- .github/CODEOWNERS | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 82f7197d87aed..f9983f0fccc3f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -41,6 +41,7 @@ common/tier4_datetime_rviz_plugin/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp common/tier4_debug_rviz_plugin/** takayuki.murooka@tier4.jp common/tier4_debug_tools/** ryohsuke.mitsudome@tier4.jp common/tier4_localization_rviz_plugin/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp makoto.yabuta@tier4.jp takamasa.horibe@tier4.jp +common/tier4_logging_level_configure_rviz_plugin/** kosuke.takeuchi@tier4.jp satoshi.ota@tier4.jp takamasa.horibe@tier4.jp common/tier4_perception_rviz_plugin/** yukihiro.saito@tier4.jp common/tier4_planning_rviz_plugin/** takayuki.murooka@tier4.jp yukihiro.saito@tier4.jp yutaka.shimizu@tier4.jp common/tier4_screen_capture_rviz_plugin/** taiki.tanaka@tier4.jp @@ -84,9 +85,10 @@ launch/tier4_sensing_launch/** yukihiro.saito@tier4.jp launch/tier4_simulator_launch/** keisuke.shima@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp launch/tier4_system_launch/** akihiro.sakurai@tier4.jp fumihito.ito@tier4.jp launch/tier4_vehicle_launch/** yukihiro.saito@tier4.jp -localization/ar_tag_based_localizer/** masahiro.sakamoto@tier4.jp shintaro.sakoda@tier4.jp localization/ekf_localizer/** koji.minoda@tier4.jp masahiro.sakamoto@tier4.jp takamasa.horibe@tier4.jp takeshi.ishita@tier4.jp yamato.ando@tier4.jp localization/gyro_odometer/** masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp +localization/landmark_based_localizer/ar_tag_based_localizer/** masahiro.sakamoto@tier4.jp shintaro.sakoda@tier4.jp +localization/landmark_based_localizer/landmark_tf_caster/** masahiro.sakamoto@tier4.jp shintaro.sakoda@tier4.jp localization/localization_error_monitor/** koji.minoda@tier4.jp masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp localization/ndt_scan_matcher/** kento.yabuuchi.2@tier4.jp koji.minoda@tier4.jp masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp localization/pose2twist/** masahiro.sakamoto@tier4.jp yamato.ando@tier4.jp @@ -150,7 +152,7 @@ planning/behavior_path_planner/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4 planning/behavior_velocity_blind_spot_module/** mamoru.sobue@tier4.jp shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_crosswalk_module/** satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_detection_area_module/** kyoichi.sugahara@tier4.jp shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp -planning/behavior_velocity_intersection_module/** mamoru.sobue@tier4.jp shumpei.wakabayashi@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp +planning/behavior_velocity_intersection_module/** kyoichi.sugahara@tier4.jp mamoru.sobue@tier4.jp shumpei.wakabayashi@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_no_drivable_lane_module/** ahmed.ebrahim@leodrive.ai fumiya.watanabe@tier4.jp satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_no_stopping_area_module/** kosuke.takeuchi@tier4.jp shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_occlusion_spot_module/** shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp tomoya.kimura@tier4.jp @@ -160,6 +162,7 @@ planning/behavior_velocity_planner_common/** isamu.takagi@tier4.jp shumpei.wakab planning/behavior_velocity_run_out_module/** makoto.kurihara@tier4.jp shumpei.wakabayashi@tier4.jp tomohito.ando@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_speed_bump_module/** mdogru@leodrive.ai shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_stop_line_module/** shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp yutaka.shimizu@tier4.jp +planning/behavior_velocity_template_module/** daniel.sanchez@tier4.jp planning/behavior_velocity_traffic_light_module/** satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_virtual_traffic_light_module/** kosuke.takeuchi@tier4.jp shumpei.wakabayashi@tier4.jp tomoya.kimura@tier4.jp planning/behavior_velocity_walkway_module/** satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp @@ -215,6 +218,8 @@ system/dummy_infrastructure/** ryohsuke.mitsudome@tier4.jp system/emergency_handler/** makoto.kurihara@tier4.jp system/mrm_comfortable_stop_operator/** makoto.kurihara@tier4.jp system/mrm_emergency_stop_operator/** makoto.kurihara@tier4.jp +system/system_diagnostic_graph/** isamu.takagi@tier4.jp +system/system_diagnostic_monitor/** isamu.takagi@tier4.jp system/system_error_monitor/** fumihito.ito@tier4.jp system/system_monitor/** akihiro.sakurai@tier4.jp fumihito.ito@tier4.jp system/topic_state_monitor/** ryohsuke.mitsudome@tier4.jp @@ -224,4 +229,4 @@ vehicle/accel_brake_map_calibrator/accel_brake_map_calibrator/** taiki.tanaka@ti vehicle/external_cmd_converter/** takamasa.horibe@tier4.jp vehicle/raw_vehicle_cmd_converter/** makoto.kurihara@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp vehicle/steer_offset_estimator/** taiki.tanaka@tier4.jp -vehicle/vehicle_info_util/** shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp tomoya.kimura@tier4.jp yamato.ando@tier4.jp +vehicle/vehicle_info_util/** shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp tomoya.kimura@tier4.jp From a3e16df9acaa86c05792dd7d03d593758caffa81 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 28 Sep 2023 18:32:37 +0900 Subject: [PATCH 361/547] refactor(behavior_path_planner): remove last_approved_pose_ from start_planner (#5157) remove last_approved_pose_ from start_planner Signed-off-by: kyoichi-sugahara --- .../scene_module/start_planner/start_planner_module.hpp | 1 - .../src/scene_module/start_planner/start_planner_module.cpp | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index d4013fd97b316..d22322b63b080 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -139,7 +139,6 @@ class StartPlannerModule : public SceneModuleInterface std::unique_ptr last_route_received_time_; std::unique_ptr last_pull_out_start_update_time_; - std::unique_ptr last_approved_pose_; // generate freespace pull out paths in a separate thread std::unique_ptr freespace_planner_; diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index c2df11b4bede6..a9d8278523f45 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -215,8 +215,6 @@ BehaviorModuleOutput StartPlannerModule::plan() clearWaitingApproval(); resetPathCandidate(); resetPathReference(); - // save current_pose when approved for start_point of turn_signal for backward driving - last_approved_pose_ = std::make_unique(planner_data_->self_odometry->pose.pose); } BehaviorModuleOutput output; @@ -886,7 +884,7 @@ TurnSignalInfo StartPlannerModule::calcTurnSignalInfo() const // turn on hazard light when backward driving if (!status_.back_finished) { turn_signal.hazard_signal.command = HazardLightsCommand::ENABLE; - const auto back_start_pose = isWaitingApproval() ? current_pose : *last_approved_pose_; + const auto back_start_pose = planner_data_->route_handler->getOriginalStartPose(); turn_signal.desired_start_point = back_start_pose; turn_signal.required_start_point = back_start_pose; // pull_out start_pose is same to backward driving end_pose From fe9ad68131936f96f231dbd3b3fb72746b10c51b Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Thu, 28 Sep 2023 18:41:19 +0900 Subject: [PATCH 362/547] refactor(localization_error_monitor): refactor diag (#4964) * refactor(localization_error_monitor): refactor diag Signed-off-by: yamato-ando * refactor Signed-off-by: yamato-ando * create diagnostics file Signed-off-by: yamato-ando * delete unnecessary lines Signed-off-by: yamato-ando * add test Signed-off-by: yamato-ando * add image file Signed-off-by: yamato-ando * add newline Signed-off-by: yamato-ando * fix message Signed-off-by: yamato-ando * remove unnecessary code Signed-off-by: yamato-ando * modify include file Signed-off-by: yamato-ando * style(pre-commit): autofix * Update localization/localization_error_monitor/src/diagnostics.cpp Co-authored-by: Kento Yabuuchi * add comment Signed-off-by: yamato-ando * rename diag componet Signed-off-by: yamato-ando * update diag aggregator param Signed-off-by: yamato-ando * update default param Signed-off-by: yamato-ando * refactor Signed-off-by: yamato-ando * add OK message Signed-off-by: yamato-ando * style(pre-commit): autofix * fix conflict Signed-off-by: yamato-ando --------- Signed-off-by: yamato-ando Co-authored-by: yamato-ando Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Kento Yabuuchi --- .../localization_error_monitor/CMakeLists.txt | 21 +++ .../localization_error_monitor/README.md | 18 ++- .../localization_error_monitor.param.yaml | 2 +- .../diagnostics.hpp | 31 ++++ .../localization_error_monitor/node.hpp | 7 +- .../media/diagnostics.png | Bin 0 -> 22762 bytes .../localization_error_monitor/package.xml | 1 + .../src/diagnostics.cpp | 94 ++++++++++++ .../localization_error_monitor/src/node.cpp | 73 +++------ .../test/test_diagnostics.cpp | 140 ++++++++++++++++++ .../localization.param.yaml | 2 +- 11 files changed, 323 insertions(+), 66 deletions(-) create mode 100644 localization/localization_error_monitor/include/localization_error_monitor/diagnostics.hpp create mode 100644 localization/localization_error_monitor/media/diagnostics.png create mode 100644 localization/localization_error_monitor/src/diagnostics.cpp create mode 100644 localization/localization_error_monitor/test/test_diagnostics.cpp diff --git a/localization/localization_error_monitor/CMakeLists.txt b/localization/localization_error_monitor/CMakeLists.txt index 258e942169556..3528367486350 100644 --- a/localization/localization_error_monitor/CMakeLists.txt +++ b/localization/localization_error_monitor/CMakeLists.txt @@ -4,10 +4,31 @@ project(localization_error_monitor) find_package(autoware_cmake REQUIRED) autoware_package() +ament_auto_add_library(localization_error_monitor_module SHARED + src/diagnostics.cpp +) + ament_auto_add_executable(localization_error_monitor src/main.cpp src/node.cpp ) +target_link_libraries(localization_error_monitor localization_error_monitor_module) + +if(BUILD_TESTING) + function(add_testcase filepath) + get_filename_component(filename ${filepath} NAME) + string(REGEX REPLACE ".cpp" "" test_name ${filename}) + ament_add_gtest(${test_name} ${filepath}) + target_include_directories(${test_name} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + target_link_libraries(${test_name} localization_error_monitor_module) + ament_target_dependencies(${test_name} ${${PROJECT_NAME}_FOUND_BUILD_DEPENDS}) + endfunction() + + + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + add_testcase(test/test_diagnostics.cpp) +endif() ament_auto_package(INSTALL_TO_SHARE config diff --git a/localization/localization_error_monitor/README.md b/localization/localization_error_monitor/README.md index bf005d6a00bbb..2ddf41e4eac70 100644 --- a/localization/localization_error_monitor/README.md +++ b/localization/localization_error_monitor/README.md @@ -2,6 +2,10 @@ ## Purpose +

    + +

    + localization_error_monitor is a package for diagnosing localization errors by monitoring uncertainty of the localization results. The package monitors the following two values: @@ -25,10 +29,10 @@ The package monitors the following two values: ## Parameters -| Name | Type | Description | -| -------------------------------------- | ------ | ------------------------------------------------------------------------------------------- | -| `scale` | double | scale factor for monitored values (default: 3.0) | -| `error_ellipse_size` | double | error threshold for long radius of confidence ellipse [m] (default: 1.0) | -| `warn_ellipse_size` | double | warning threshold for long radius of confidence ellipse [m] (default: 0.8) | -| `error_ellipse_size_lateral_direction` | double | error threshold for size of confidence ellipse along lateral direction [m] (default: 0.3) | -| `warn_ellipse_size_lateral_direction` | double | warning threshold for size of confidence ellipse along lateral direction [m] (default: 0.2) | +| Name | Type | Description | +| -------------------------------------- | ------ | -------------------------------------------------------------------------------------------- | +| `scale` | double | scale factor for monitored values (default: 3.0) | +| `error_ellipse_size` | double | error threshold for long radius of confidence ellipse [m] (default: 1.0) | +| `warn_ellipse_size` | double | warning threshold for long radius of confidence ellipse [m] (default: 0.8) | +| `error_ellipse_size_lateral_direction` | double | error threshold for size of confidence ellipse along lateral direction [m] (default: 0.3) | +| `warn_ellipse_size_lateral_direction` | double | warning threshold for size of confidence ellipse along lateral direction [m] (default: 0.25) | diff --git a/localization/localization_error_monitor/config/localization_error_monitor.param.yaml b/localization/localization_error_monitor/config/localization_error_monitor.param.yaml index 026daf0532d33..2aa28014ead41 100644 --- a/localization/localization_error_monitor/config/localization_error_monitor.param.yaml +++ b/localization/localization_error_monitor/config/localization_error_monitor.param.yaml @@ -4,4 +4,4 @@ error_ellipse_size: 1.0 warn_ellipse_size: 0.8 error_ellipse_size_lateral_direction: 0.3 - warn_ellipse_size_lateral_direction: 0.2 + warn_ellipse_size_lateral_direction: 0.25 diff --git a/localization/localization_error_monitor/include/localization_error_monitor/diagnostics.hpp b/localization/localization_error_monitor/include/localization_error_monitor/diagnostics.hpp new file mode 100644 index 0000000000000..4fe4e5a5aac14 --- /dev/null +++ b/localization/localization_error_monitor/include/localization_error_monitor/diagnostics.hpp @@ -0,0 +1,31 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LOCALIZATION_ERROR_MONITOR__DIAGNOSTICS_HPP_ +#define LOCALIZATION_ERROR_MONITOR__DIAGNOSTICS_HPP_ + +#include + +#include +#include + +diagnostic_msgs::msg::DiagnosticStatus checkLocalizationAccuracy( + const double ellipse_size, const double warn_ellipse_size, const double error_ellipse_size); +diagnostic_msgs::msg::DiagnosticStatus checkLocalizationAccuracyLateralDirection( + const double ellipse_size, const double warn_ellipse_size, const double error_ellipse_size); + +diagnostic_msgs::msg::DiagnosticStatus mergeDiagnosticStatus( + const std::vector & stat_array); + +#endif // LOCALIZATION_ERROR_MONITOR__DIAGNOSTICS_HPP_ diff --git a/localization/localization_error_monitor/include/localization_error_monitor/node.hpp b/localization/localization_error_monitor/include/localization_error_monitor/node.hpp index 83b2ddef9215a..7bdc87d5d47f2 100644 --- a/localization/localization_error_monitor/include/localization_error_monitor/node.hpp +++ b/localization/localization_error_monitor/include/localization_error_monitor/node.hpp @@ -16,7 +16,6 @@ #define LOCALIZATION_ERROR_MONITOR__NODE_HPP_ #include -#include #include #include @@ -37,6 +36,7 @@ class LocalizationErrorMonitor : public rclcpp::Node private: rclcpp::Subscription::SharedPtr odom_sub_; rclcpp::Publisher::SharedPtr ellipse_marker_pub_; + rclcpp::Publisher::SharedPtr diag_pub_; rclcpp::TimerBase::SharedPtr timer_; double scale_; @@ -45,16 +45,11 @@ class LocalizationErrorMonitor : public rclcpp::Node double error_ellipse_size_lateral_direction_; double warn_ellipse_size_lateral_direction_; Ellipse ellipse_; - diagnostic_updater::Updater updater_; - void checkLocalizationAccuracy(diagnostic_updater::DiagnosticStatusWrapper & stat); - void checkLocalizationAccuracyLateralDirection( - diagnostic_updater::DiagnosticStatusWrapper & stat); void onOdom(nav_msgs::msg::Odometry::ConstSharedPtr input_msg); visualization_msgs::msg::Marker createEllipseMarker( const Ellipse & ellipse, nav_msgs::msg::Odometry::ConstSharedPtr odom); double measureSizeEllipseAlongBodyFrame(const Eigen::Matrix2d & Pinv, double theta); - void onTimer(); public: LocalizationErrorMonitor(); diff --git a/localization/localization_error_monitor/media/diagnostics.png b/localization/localization_error_monitor/media/diagnostics.png new file mode 100644 index 0000000000000000000000000000000000000000..74aacb68a2b7cefcd199bc3ae8ee120e6b545e4d GIT binary patch literal 22762 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 F{|}+<01E&B literal 0 HcmV?d00001 diff --git a/localization/localization_error_monitor/package.xml b/localization/localization_error_monitor/package.xml index 4a186bdf95ed1..42f7c2de44ee9 100644 --- a/localization/localization_error_monitor/package.xml +++ b/localization/localization_error_monitor/package.xml @@ -22,6 +22,7 @@ tf2_geometry_msgs visualization_msgs + ament_cmake_ros ament_lint_auto autoware_lint_common diff --git a/localization/localization_error_monitor/src/diagnostics.cpp b/localization/localization_error_monitor/src/diagnostics.cpp new file mode 100644 index 0000000000000..0c5a4a7800639 --- /dev/null +++ b/localization/localization_error_monitor/src/diagnostics.cpp @@ -0,0 +1,94 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include + +diagnostic_msgs::msg::DiagnosticStatus checkLocalizationAccuracy( + const double ellipse_size, const double warn_ellipse_size, const double error_ellipse_size) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = "localization_accuracy"; + key_value.value = std::to_string(ellipse_size); + stat.values.push_back(key_value); + + stat.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat.message = "OK"; + if (ellipse_size >= warn_ellipse_size) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat.message = "ellipse size is too large"; + } + if (ellipse_size >= error_ellipse_size) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat.message = "ellipse size is over the expected range"; + } + + return stat; +} + +diagnostic_msgs::msg::DiagnosticStatus checkLocalizationAccuracyLateralDirection( + const double ellipse_size, const double warn_ellipse_size, const double error_ellipse_size) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + diagnostic_msgs::msg::KeyValue key_value; + key_value.key = "localization_accuracy_lateral_direction"; + key_value.value = std::to_string(ellipse_size); + stat.values.push_back(key_value); + + stat.level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat.message = "OK"; + if (ellipse_size >= warn_ellipse_size) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat.message = "ellipse size along lateral direction is too large"; + } + if (ellipse_size >= error_ellipse_size) { + stat.level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat.message = "ellipse size along lateral direction is over the expected range"; + } + + return stat; +} + +// The highest level within the stat_array will be reflected in the merged_stat. +diagnostic_msgs::msg::DiagnosticStatus mergeDiagnosticStatus( + const std::vector & stat_array) +{ + diagnostic_msgs::msg::DiagnosticStatus merged_stat; + + for (const auto & stat : stat_array) { + if ((stat.level > diagnostic_msgs::msg::DiagnosticStatus::OK)) { + if (!merged_stat.message.empty()) { + merged_stat.message += "; "; + } + merged_stat.message += stat.message; + } + if (stat.level > merged_stat.level) { + merged_stat.level = stat.level; + } + for (const auto & value : stat.values) { + merged_stat.values.push_back(value); + } + } + + if (merged_stat.level == diagnostic_msgs::msg::DiagnosticStatus::OK) { + merged_stat.message = "OK"; + } + + return merged_stat; +} diff --git a/localization/localization_error_monitor/src/node.cpp b/localization/localization_error_monitor/src/node.cpp index 307dc9445eb28..43795d4036d42 100644 --- a/localization/localization_error_monitor/src/node.cpp +++ b/localization/localization_error_monitor/src/node.cpp @@ -14,6 +14,8 @@ #include "localization_error_monitor/node.hpp" +#include "localization_error_monitor/diagnostics.hpp" + #include #include @@ -30,8 +32,7 @@ #include #include -LocalizationErrorMonitor::LocalizationErrorMonitor() -: Node("localization_error_monitor"), updater_(this) +LocalizationErrorMonitor::LocalizationErrorMonitor() : Node("localization_error_monitor") { scale_ = this->declare_parameter("scale"); error_ellipse_size_ = this->declare_parameter("error_ellipse_size"); @@ -51,55 +52,7 @@ LocalizationErrorMonitor::LocalizationErrorMonitor() ellipse_marker_pub_ = this->create_publisher("debug/ellipse_marker", durable_qos); - updater_.setHardwareID("localization_error_monitor"); - updater_.add("localization_accuracy", this, &LocalizationErrorMonitor::checkLocalizationAccuracy); - updater_.add( - "localization_accuracy_lateral_direction", this, - &LocalizationErrorMonitor::checkLocalizationAccuracyLateralDirection); - - // Set timer - using std::chrono_literals::operator""ms; - timer_ = rclcpp::create_timer( - this, get_clock(), 100ms, std::bind(&LocalizationErrorMonitor::onTimer, this)); -} - -void LocalizationErrorMonitor::onTimer() -{ - updater_.force_update(); -} - -void LocalizationErrorMonitor::checkLocalizationAccuracy( - diagnostic_updater::DiagnosticStatusWrapper & stat) -{ - stat.add("localization_accuracy", ellipse_.long_radius); - int8_t diag_level = diagnostic_msgs::msg::DiagnosticStatus::OK; - std::string diag_message = "ellipse size is within the expected range"; - if (warn_ellipse_size_ <= ellipse_.long_radius) { - diag_level = diagnostic_msgs::msg::DiagnosticStatus::WARN; - diag_message = "ellipse size is too large"; - } - if (error_ellipse_size_ <= ellipse_.long_radius) { - diag_level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; - diag_message = "ellipse size is over the expected range"; - } - stat.summary(diag_level, diag_message); -} - -void LocalizationErrorMonitor::checkLocalizationAccuracyLateralDirection( - diagnostic_updater::DiagnosticStatusWrapper & stat) -{ - stat.add("localization_accuracy_lateral_direction", ellipse_.size_lateral_direction); - int8_t diag_level = diagnostic_msgs::msg::DiagnosticStatus::OK; - std::string diag_message = "ellipse size along lateral direction is within the expected range"; - if (warn_ellipse_size_lateral_direction_ <= ellipse_.size_lateral_direction) { - diag_level = diagnostic_msgs::msg::DiagnosticStatus::WARN; - diag_message = "ellipse size along lateral direction is too large"; - } - if (error_ellipse_size_lateral_direction_ <= ellipse_.size_lateral_direction) { - diag_level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; - diag_message = "ellipse size along lateral direction is over the expected range"; - } - stat.summary(diag_level, diag_message); + diag_pub_ = this->create_publisher("/diagnostics", 10); } visualization_msgs::msg::Marker LocalizationErrorMonitor::createEllipseMarker( @@ -158,6 +111,24 @@ void LocalizationErrorMonitor::onOdom(nav_msgs::msg::Odometry::ConstSharedPtr in const auto ellipse_marker = createEllipseMarker(ellipse_, input_msg); ellipse_marker_pub_->publish(ellipse_marker); + + // diagnostics + std::vector diag_status_array; + diag_status_array.push_back( + checkLocalizationAccuracy(ellipse_.long_radius, warn_ellipse_size_, error_ellipse_size_)); + diag_status_array.push_back(checkLocalizationAccuracyLateralDirection( + ellipse_.size_lateral_direction, warn_ellipse_size_lateral_direction_, + error_ellipse_size_lateral_direction_)); + + diagnostic_msgs::msg::DiagnosticStatus diag_merged_status; + diag_merged_status = mergeDiagnosticStatus(diag_status_array); + diag_merged_status.name = "localization: " + std::string(this->get_name()); + diag_merged_status.hardware_id = this->get_name(); + + diagnostic_msgs::msg::DiagnosticArray diag_msg; + diag_msg.header.stamp = this->now(); + diag_msg.status.push_back(diag_merged_status); + diag_pub_->publish(diag_msg); } double LocalizationErrorMonitor::measureSizeEllipseAlongBodyFrame( diff --git a/localization/localization_error_monitor/test/test_diagnostics.cpp b/localization/localization_error_monitor/test/test_diagnostics.cpp new file mode 100644 index 0000000000000..7b7e8aaab4ed9 --- /dev/null +++ b/localization/localization_error_monitor/test/test_diagnostics.cpp @@ -0,0 +1,140 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "localization_error_monitor/diagnostics.hpp" + +#include + +TEST(TestLocalizationErrorMonitorDiagnostics, CheckLocalizationAccuracy) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + const double warn_ellipse_size = 0.8; + const double error_ellipse_size = 1.0; + + double ellipse_size = 0.0; + stat = checkLocalizationAccuracy(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + ellipse_size = 0.7; + stat = checkLocalizationAccuracy(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + ellipse_size = 0.8; + stat = checkLocalizationAccuracy(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + + ellipse_size = 0.9; + stat = checkLocalizationAccuracy(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + + ellipse_size = 1.0; + stat = checkLocalizationAccuracy(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); +} + +TEST(TestLocalizationErrorMonitorDiagnostics, CheckLocalizationAccuracyLateralDirection) +{ + diagnostic_msgs::msg::DiagnosticStatus stat; + + const double warn_ellipse_size = 0.25; + const double error_ellipse_size = 0.3; + + double ellipse_size = 0.0; + stat = + checkLocalizationAccuracyLateralDirection(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + ellipse_size = 0.24; + stat = + checkLocalizationAccuracyLateralDirection(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + + ellipse_size = 0.25; + stat = + checkLocalizationAccuracyLateralDirection(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + + ellipse_size = 0.29; + stat = + checkLocalizationAccuracyLateralDirection(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + + ellipse_size = 0.3; + stat = + checkLocalizationAccuracyLateralDirection(ellipse_size, warn_ellipse_size, error_ellipse_size); + EXPECT_EQ(stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); +} + +TEST(TestLocalizationErrorMonitorDiagnostics, MergeDiagnosticStatus) +{ + diagnostic_msgs::msg::DiagnosticStatus merged_stat; + std::vector stat_array(2); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(0).message = "OK"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(1).message = "OK"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::OK); + EXPECT_EQ(merged_stat.message, "OK"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(0).message = "WARN0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(1).message = "OK"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + EXPECT_EQ(merged_stat.message, "WARN0"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(0).message = "OK"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(1).message = "WARN1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + EXPECT_EQ(merged_stat.message, "WARN1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(0).message = "WARN0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(1).message = "WARN1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::WARN); + EXPECT_EQ(merged_stat.message, "WARN0; WARN1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::OK; + stat_array.at(0).message = "OK"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(1).message = "ERROR1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); + EXPECT_EQ(merged_stat.message, "ERROR1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + stat_array.at(0).message = "WARN0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(1).message = "ERROR1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); + EXPECT_EQ(merged_stat.message, "WARN0; ERROR1"); + + stat_array.at(0).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(0).message = "ERROR0"; + stat_array.at(1).level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + stat_array.at(1).message = "ERROR1"; + merged_stat = mergeDiagnosticStatus(stat_array); + EXPECT_EQ(merged_stat.level, diagnostic_msgs::msg::DiagnosticStatus::ERROR); + EXPECT_EQ(merged_stat.message, "ERROR0; ERROR1"); +} diff --git a/system/system_error_monitor/config/diagnostic_aggregator/localization.param.yaml b/system/system_error_monitor/config/diagnostic_aggregator/localization.param.yaml index 2d0b2b00af343..f82f7d732f5dc 100644 --- a/system/system_error_monitor/config/diagnostic_aggregator/localization.param.yaml +++ b/system/system_error_monitor/config/diagnostic_aggregator/localization.param.yaml @@ -27,5 +27,5 @@ localization_accuracy: type: diagnostic_aggregator/GenericAnalyzer path: localization_accuracy - contains: [": localization_accuracy"] + contains: ["localization: localization_error_monitor"] timeout: 1.0 From 32d2b8437578de4abc35f417cbead557159105d0 Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Thu, 28 Sep 2023 19:55:57 +0900 Subject: [PATCH 363/547] fix(tier4_perception_launch): add parameters for light weight radar fusion and fix launch order (#5166) * fix(tier4_perception_launch): add parameters for light weight radar fusion and fix launch order Signed-off-by: scepter914 * style(pre-commit): autofix * add far_object_merger_sync_queue_size param for package arg Signed-off-by: scepter914 --------- Signed-off-by: scepter914 Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ...ar_radar_fusion_based_detection.launch.xml | 98 ++++++++++--------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index df833296e3f66..38d141241c215 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -47,6 +47,8 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -114,6 +158,8 @@ + + @@ -142,54 +188,12 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -293,7 +297,7 @@ - + @@ -302,6 +306,9 @@ + + + @@ -328,7 +335,7 @@ - + @@ -403,5 +410,6 @@ +
    From 684a4ccc4b58975359a6ffcca1cbeb7e32e0706a Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:11:27 +0300 Subject: [PATCH 364/547] fix(traffic_light_fine_detector): add data_path arg and update default model path (#5118) * fix(traffic_light_fine_detector): add data_path arg and update default model path Signed-off-by: Alexey Panferov * fix(traffic_light_fine_detector): update README with data path arg Signed-off-by: Alexey Panferov * style(pre-commit): autofix --------- Signed-off-by: Alexey Panferov Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- perception/traffic_light_fine_detector/README.md | 13 +++++++------ .../launch/traffic_light_fine_detector.launch.xml | 5 +++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/perception/traffic_light_fine_detector/README.md b/perception/traffic_light_fine_detector/README.md index 1ed6debfeae91..dcc89c76387c6 100644 --- a/perception/traffic_light_fine_detector/README.md +++ b/perception/traffic_light_fine_detector/README.md @@ -50,12 +50,13 @@ Based on the camera image and the global ROI array detected by `map_based_detect ### Node Parameters -| Name | Type | Default Value | Description | -| -------------------------- | ------ | ------------- | ------------------------------------------------------------------ | -| `fine_detector_model_path` | string | "" | The onnx file name for yolo model | -| `fine_detector_label_path` | string | "" | The label file with label names for detected objects written on it | -| `fine_detector_precision` | string | "fp32" | The inference mode: "fp32", "fp16" | -| `approximate_sync` | bool | false | Flag for whether to ues approximate sync policy | +| Name | Type | Default Value | Description | +| -------------------------- | ------ | --------------------------- | ------------------------------------------------------------------ | +| `data_path` | string | "$(env HOME)/autoware_data" | packages data and artifacts directory path | +| `fine_detector_model_path` | string | "" | The onnx file name for yolo model | +| `fine_detector_label_path` | string | "" | The label file with label names for detected objects written on it | +| `fine_detector_precision` | string | "fp32" | The inference mode: "fp32", "fp16" | +| `approximate_sync` | bool | false | Flag for whether to ues approximate sync policy | ## Assumptions / Known limits diff --git a/perception/traffic_light_fine_detector/launch/traffic_light_fine_detector.launch.xml b/perception/traffic_light_fine_detector/launch/traffic_light_fine_detector.launch.xml index 5ce7840c28fd6..6e32d3410c260 100644 --- a/perception/traffic_light_fine_detector/launch/traffic_light_fine_detector.launch.xml +++ b/perception/traffic_light_fine_detector/launch/traffic_light_fine_detector.launch.xml @@ -1,6 +1,7 @@ - - + + + From 376ca2d76793be6e3d06c7ca6f8172274e634f99 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:13:18 +0300 Subject: [PATCH 365/547] fix(traffic_light_ssd_fine_detector): add arg data_path, update model file path (#5141) * fix(traffic_light_ssd_fine_detector): add arg data_path, update model file path Signed-off-by: Alexey Panferov * fix(traffic_light_ssd_fine_detector): update README with data_path arg Signed-off-by: Alexey Panferov * style(pre-commit): autofix --------- Signed-off-by: Alexey Panferov Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../traffic_light_ssd_fine_detector/README.md | 19 ++++++++++--------- ...traffic_light_ssd_fine_detector.launch.xml | 5 +++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/perception/traffic_light_ssd_fine_detector/README.md b/perception/traffic_light_ssd_fine_detector/README.md index 1dd05665709f5..4dbda8421d85d 100644 --- a/perception/traffic_light_ssd_fine_detector/README.md +++ b/perception/traffic_light_ssd_fine_detector/README.md @@ -122,15 +122,16 @@ Based on the camera image and the global ROI array detected by `map_based_detect ### Node Parameters -| Name | Type | Default Value | Description | -| ------------------ | ------ | ------------------------------ | -------------------------------------------------------------------- | -| `onnx_file` | string | "./data/mb2-ssd-lite-tlr.onnx" | The onnx file name for yolo model | -| `label_file` | string | "./data/voc_labels_tl.txt" | The label file with label names for detected objects written on it | -| `dnn_header_type` | string | "pytorch" | Name of DNN trained toolbox: "pytorch" or "mmdetection" | -| `mode` | string | "FP32" | The inference mode: "FP32", "FP16", "INT8" | -| `max_batch_size` | int | 8 | The size of the batch processed at one time by inference by TensorRT | -| `approximate_sync` | bool | false | Flag for whether to ues approximate sync policy | -| `build_only` | bool | false | shutdown node after TensorRT engine file is built | +| Name | Type | Default Value | Description | +| ------------------ | ------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | +| `data_path` | string | "$(env HOME)/autoware_data" | packages data and artifacts directory path | +| `onnx_file` | string | "$(var data_path)/traffic_light_ssd_fine_detector/mb2-ssd-lite-tlr.onnx" | The onnx file name for yolo model | +| `label_file` | string | "$(var data_path)/traffic_light_ssd_fine_detector/voc_labels_tl.txt" | The label file with label names for detected objects written on it | +| `dnn_header_type` | string | "pytorch" | Name of DNN trained toolbox: "pytorch" or "mmdetection" | +| `mode` | string | "FP32" | The inference mode: "FP32", "FP16", "INT8" | +| `max_batch_size` | int | 8 | The size of the batch processed at one time by inference by TensorRT | +| `approximate_sync` | bool | false | Flag for whether to ues approximate sync policy | +| `build_only` | bool | false | shutdown node after TensorRT engine file is built | ## Assumptions / Known limits diff --git a/perception/traffic_light_ssd_fine_detector/launch/traffic_light_ssd_fine_detector.launch.xml b/perception/traffic_light_ssd_fine_detector/launch/traffic_light_ssd_fine_detector.launch.xml index a4d61b774652a..714c4d288b603 100644 --- a/perception/traffic_light_ssd_fine_detector/launch/traffic_light_ssd_fine_detector.launch.xml +++ b/perception/traffic_light_ssd_fine_detector/launch/traffic_light_ssd_fine_detector.launch.xml @@ -1,6 +1,7 @@ - - + + + From d4ceaa6e5fe259c9fc4ad41a9761ffbf51decf0a Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:15:11 +0300 Subject: [PATCH 366/547] fix(traffic_light_classifier): add arg data_path, update model path (#5144) * fix(traffic_light_classfier): add arg data_path, update model path Signed-off-by: Alexey Panferov * fix(traffic_light_classifier): add arg data_path, update model path Signed-off-by: Alexey Panferov * fix(traffic_light_classifier): add arg data_path to README Signed-off-by: Alexey Panferov --------- Signed-off-by: Alexey Panferov --- perception/traffic_light_classifier/README.md | 1 + .../launch/traffic_light_classifier.launch.xml | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/perception/traffic_light_classifier/README.md b/perception/traffic_light_classifier/README.md index 3d15af0cf7805..7df0c5466695b 100644 --- a/perception/traffic_light_classifier/README.md +++ b/perception/traffic_light_classifier/README.md @@ -55,6 +55,7 @@ These colors and shapes are assigned to the message as follows: | Name | Type | Description | | ----------------- | ---- | ------------------------------------------- | | `classifier_type` | int | if the value is `1`, cnn_classifier is used | +| `data_path` | str | packages data and artifacts directory path | ### Core Parameters diff --git a/perception/traffic_light_classifier/launch/traffic_light_classifier.launch.xml b/perception/traffic_light_classifier/launch/traffic_light_classifier.launch.xml index d4794443d95d9..10aa04cc585af 100644 --- a/perception/traffic_light_classifier/launch/traffic_light_classifier.launch.xml +++ b/perception/traffic_light_classifier/launch/traffic_light_classifier.launch.xml @@ -2,8 +2,9 @@ - - + + + From dea2b074400a7af8e66f0828fd8f3b44a4bbe585 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:16:16 +0300 Subject: [PATCH 367/547] fix(tensorrt yolo): update model path (#5158) * fix(tensorrt_yolo): add arg data_path, update onnx_file Signed-off-by: Alexey Panferov * fix(tensorrt_yolo): update model downloading in README, add data_path Signed-off-by: Alexey Panferov --------- Signed-off-by: Alexey Panferov --- perception/tensorrt_yolo/README.md | 3 ++- perception/tensorrt_yolo/launch/tensorrt_yolo.launch.xml | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/perception/tensorrt_yolo/README.md b/perception/tensorrt_yolo/README.md index afa9209c43bb2..58d4af0dfa83d 100644 --- a/perception/tensorrt_yolo/README.md +++ b/perception/tensorrt_yolo/README.md @@ -55,6 +55,7 @@ Jocher, G., et al. (2021). ultralytics/yolov5: v6.0 - YOLOv5n 'Nano' models, Rob | Name | Type | Default Value | Description | | ----------------------- | ------ | ------------- | ------------------------------------------------------------------ | +| `data_path` | string | "" | Packages data and artifacts directory path | | `onnx_file` | string | "" | The onnx file name for yolo model | | `engine_file` | string | "" | The tensorrt engine file name for yolo model | | `label_file` | string | "" | The label file with label names for detected objects written on it | @@ -71,7 +72,7 @@ This package includes multiple licenses. All YOLO ONNX models are converted from the officially trained model. If you need information about training datasets and conditions, please refer to the official repositories. -All models are downloaded automatically when building. When launching the node with a model for the first time, the model is automatically converted to TensorRT, although this may take some time. +All models are downloaded during env preparation by ansible (as mention in [installation](https://autowarefoundation.github.io/autoware-documentation/main/installation/autoware/source-installation/)). It is also possible to download them manually, see [Manual downloading of artifacts](https://github.com/autowarefoundation/autoware/tree/main/ansible/roles/artifacts) . When launching the node with a model for the first time, the model is automatically converted to TensorRT, although this may take some time. ### YOLOv3 diff --git a/perception/tensorrt_yolo/launch/tensorrt_yolo.launch.xml b/perception/tensorrt_yolo/launch/tensorrt_yolo.launch.xml index a548939f2cebe..b2656de0ab72e 100755 --- a/perception/tensorrt_yolo/launch/tensorrt_yolo.launch.xml +++ b/perception/tensorrt_yolo/launch/tensorrt_yolo.launch.xml @@ -3,7 +3,8 @@ - + + @@ -11,11 +12,11 @@ - + - + - + From a48e80bc9cbaa0d04d59b263fbd8cd7cce148886 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:17:48 +0300 Subject: [PATCH 368/547] fix(image_projection_based_fusion): add arg data_path, update model_path (#5163) Signed-off-by: Alexey Panferov --- .../launch/pointpainting_fusion.launch.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/perception/image_projection_based_fusion/launch/pointpainting_fusion.launch.xml b/perception/image_projection_based_fusion/launch/pointpainting_fusion.launch.xml index e6275a2ee83c7..8e79e21db40e3 100644 --- a/perception/image_projection_based_fusion/launch/pointpainting_fusion.launch.xml +++ b/perception/image_projection_based_fusion/launch/pointpainting_fusion.launch.xml @@ -18,8 +18,9 @@ + - + From 22d595904e783b4b5fdd2f5790e3ff16791176a9 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:52:00 +0300 Subject: [PATCH 369/547] fix(tensorrt_yolox): add data_path arg and update default model path (#5119) * fix(tensorrt_yolox): add data_path arg and update default model path Signed-off-by: Alexey Panferov * fix(tensorrt_yolox): update README Signed-off-by: Alexey Panferov --------- Signed-off-by: Alexey Panferov --- perception/tensorrt_yolox/README.md | 4 ++-- perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml | 3 ++- perception/tensorrt_yolox/launch/yolox_tiny.launch.xml | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/perception/tensorrt_yolox/README.md b/perception/tensorrt_yolox/README.md index 3c253a0b68489..ca407b1ff6811 100644 --- a/perception/tensorrt_yolox/README.md +++ b/perception/tensorrt_yolox/README.md @@ -71,7 +71,7 @@ those are labeled as `UNKNOWN`, while detected rectangles are drawn in the visua ## Onnx model -A sample model (named `yolox-tiny.onnx`) is downloaded automatically during the build process. +A sample model (named `yolox-tiny.onnx`) is downloaded by ansible script on env preparation stage, if not, please, follow [Manual downloading of artifacts](https://github.com/autowarefoundation/autoware/tree/main/ansible/roles/artifacts). To accelerate Non-maximum-suppression (NMS), which is one of the common post-process after object detection inference, `EfficientNMS_TRT` module is attached after the ordinal YOLOX (tiny) network. The `EfficientNMS_TRT` module contains fixed values for `score_threshold` and `nms_threshold` in it, @@ -146,7 +146,7 @@ Please refer [the official document](https://github.com/Megvii-BaseDetection/YOL ## Label file -A sample label file (named `label.txt`)is also downloaded automatically during the build process +A sample label file (named `label.txt`)is also downloaded automatically during env preparation process (**NOTE:** This file is incompatible with models that output labels for the COCO dataset (e.g., models from the official YOLOX repository)). This file represents the correspondence between class index (integer outputted from YOLOX network) and diff --git a/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml b/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml index cb89f5829c65d..3f8d7897ab5d3 100644 --- a/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml +++ b/perception/tensorrt_yolox/launch/yolox_s_plus_opt.launch.xml @@ -4,7 +4,8 @@ - + + diff --git a/perception/tensorrt_yolox/launch/yolox_tiny.launch.xml b/perception/tensorrt_yolox/launch/yolox_tiny.launch.xml index d8c67e39e0b8a..2f08031ea159f 100644 --- a/perception/tensorrt_yolox/launch/yolox_tiny.launch.xml +++ b/perception/tensorrt_yolox/launch/yolox_tiny.launch.xml @@ -3,7 +3,8 @@ - + + From aa90375c9ff67081c7968036b12c747e51ebb3f1 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:11:12 +0300 Subject: [PATCH 370/547] fix(lidar_centerpoint): add arg data_path, update model path (#5162) Signed-off-by: Alexey Panferov --- .../centerpoint_vs_centerpoint-tiny.launch.xml | 5 +++-- .../lidar_centerpoint/launch/lidar_centerpoint.launch.xml | 3 ++- .../launch/single_inference_lidar_centerpoint.launch.xml | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/centerpoint_vs_centerpoint-tiny.launch.xml b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/centerpoint_vs_centerpoint-tiny.launch.xml index 13fd386238eda..b9c056cfb5686 100644 --- a/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/centerpoint_vs_centerpoint-tiny.launch.xml +++ b/perception/lidar_centerpoint/launch/centerpoint_vs_centerpoint-tiny/centerpoint_vs_centerpoint-tiny.launch.xml @@ -5,6 +5,7 @@ + @@ -21,7 +22,7 @@ - + @@ -35,7 +36,7 @@ - + diff --git a/perception/lidar_centerpoint/launch/lidar_centerpoint.launch.xml b/perception/lidar_centerpoint/launch/lidar_centerpoint.launch.xml index d552cb702b980..a7f181ab78ac6 100644 --- a/perception/lidar_centerpoint/launch/lidar_centerpoint.launch.xml +++ b/perception/lidar_centerpoint/launch/lidar_centerpoint.launch.xml @@ -2,8 +2,9 @@ + - + diff --git a/perception/lidar_centerpoint/launch/single_inference_lidar_centerpoint.launch.xml b/perception/lidar_centerpoint/launch/single_inference_lidar_centerpoint.launch.xml index 0f6923d5e6414..491abfbad7764 100644 --- a/perception/lidar_centerpoint/launch/single_inference_lidar_centerpoint.launch.xml +++ b/perception/lidar_centerpoint/launch/single_inference_lidar_centerpoint.launch.xml @@ -1,7 +1,8 @@ + - + From 952753f86b1408e011c178193604b7aa8d88f90c Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:30:47 +0300 Subject: [PATCH 371/547] feat(perception_launch): add data_path arg to perception launch (#5069) * feat(perception_launch): add var data_path to perception.launch Signed-off-by: Alexey Panferov * feat(perception_launch): update default center_point_model_path Signed-off-by: Alexey Panferov --------- Signed-off-by: Alexey Panferov --- .../detection/lidar_based_detection.launch.xml | 2 +- .../tier4_perception_launch/launch/perception.launch.xml | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml index 23e0297dc5e44..3b6b9ba652a24 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml @@ -10,7 +10,7 @@ - + diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index 36d43bab74894..b0219376e9625 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -25,11 +25,12 @@ - + + @@ -78,11 +79,11 @@ - + Date: Thu, 28 Sep 2023 18:27:43 +0200 Subject: [PATCH 372/547] build(behavior_velocity_intersection_module): add missing grid_map_core dependency (#5176) Signed-off-by: Esteve Fernandez --- planning/behavior_velocity_intersection_module/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_velocity_intersection_module/package.xml b/planning/behavior_velocity_intersection_module/package.xml index 9443295981679..2fd733f167546 100644 --- a/planning/behavior_velocity_intersection_module/package.xml +++ b/planning/behavior_velocity_intersection_module/package.xml @@ -23,6 +23,7 @@ autoware_perception_msgs behavior_velocity_planner_common geometry_msgs + grid_map_core interpolation lanelet2_extension libopencv-dev From 1caa1b3eeff0190255edb6331753b9f7f752e151 Mon Sep 17 00:00:00 2001 From: Yusuke Mizoguchi <33048729+swiftfile@users.noreply.github.com> Date: Fri, 29 Sep 2023 01:46:49 +0900 Subject: [PATCH 373/547] feat(blockage_diag): dust detection (#3212) * implement about dust detection Signed-off-by: Yusuke Mizoguchi * chore: fix spell Signed-off-by: badai-nguyen * chore: refactor Signed-off-by: badai-nguyen * bug fix Signed-off-by: Shunsuke Miura * fix: avoid counter overflow Signed-off-by: badai-nguyen --------- Signed-off-by: Yusuke Mizoguchi Signed-off-by: badai-nguyen Signed-off-by: Shunsuke Miura Co-authored-by: badai nguyen <94814556+badai-nguyen@users.noreply.github.com> Co-authored-by: badai-nguyen Co-authored-by: Shunsuke Miura --- .../docs/blockage_diag.md | 63 ++- .../blockage_diag/blockage_diag_nodelet.hpp | 31 +- .../launch/blockage_diag.launch.xml | 28 ++ .../blockage_diag/blockage_diag_nodelet.cpp | 389 ++++++++++++------ 4 files changed, 362 insertions(+), 149 deletions(-) diff --git a/sensing/pointcloud_preprocessor/docs/blockage_diag.md b/sensing/pointcloud_preprocessor/docs/blockage_diag.md index 519bd831fd39c..32e5a5869109d 100644 --- a/sensing/pointcloud_preprocessor/docs/blockage_diag.md +++ b/sensing/pointcloud_preprocessor/docs/blockage_diag.md @@ -2,11 +2,13 @@ ## Purpose -To ensure the performance of LiDAR and safety for autonomous driving, the abnormal condition diagnostics feature is needed. -LiDAR blockage is abnormal condition of LiDAR when some unwanted objects stitch to and block the light pulses and return signal. +To ensure the performance of LiDAR and safety for autonomous driving, the abnormal condition diagnostics feature is +needed. +LiDAR blockage is abnormal condition of LiDAR when some unwanted objects stitch to and block the light pulses and return +signal. This node's purpose is to detect the existing of blockage on LiDAR and its related size and location. -## Inner-workings / Algorithms +## Inner-workings / Algorithms(Blockage detection) This node bases on the no-return region and its location to decide if it is a blockage. @@ -16,6 +18,14 @@ The logic is showed as below ![blockage_diag_flowchart](./image/blockage_diag_flowchart.drawio.svg) +## Inner-workings /Algorithms(Dust detection) + +About dust detection, morphological processing is implemented. +If the lidar's ray cannot be acquired due to dust in the lidar area where the point cloud is considered to return from +the ground, +black pixels appear as noise in the depth image. +The area of noise is found by erosion and dilation these black pixels. + ## Inputs / Outputs This implementation inherits `pointcloud_preprocessor::Filter` class, please refer [README](../README.md). @@ -28,30 +38,41 @@ This implementation inherits `pointcloud_preprocessor::Filter` class, please ref ### Output -| Name | Type | Description | -| ---------------------------------------------------- | --------------------------------------- | -------------------------------------------------- | -| `~/output/blockage_diag/debug/blockage_mask_image` | `sensor_msgs::msg::Image` | The mask image of detected blockage | -| `~/output/blockage_diag/debug/ground_blockage_ratio` | `tier4_debug_msgs::msg::Float32Stamped` | The area ratio of blockage region in ground region | -| `~/output/blockage_diag/debug/sky_blockage_ratio` | `tier4_debug_msgs::msg::Float32Stamped` | The area ratio of blockage region in sky region | -| `~/output/blockage_diag/debug/lidar_depth_map` | `sensor_msgs::msg::Image` | The depth map image of input point cloud | +| Name | Type | Description | +| :-------------------------------------------------------- | :-------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `~/output/blockage_diag/debug/blockage_mask_image` | `sensor_msgs::msg::Image` | The mask image of detected blockage | +| `~/output/blockage_diag/debug/ground_blockage_ratio` | `tier4_debug_msgs::msg::Float32Stamped` | The area ratio of blockage region in ground region | +| `~/output/blockage_diag/debug/sky_blockage_ratio` | `tier4_debug_msgs::msg::Float32Stamped` | The area ratio of blockage region in sky region | +| `~/output/blockage_diag/debug/lidar_depth_map` | `sensor_msgs::msg::Image` | The depth map image of input point cloud | +| `~/output/blockage_diag/debug/single_frame_dust_mask` | `sensor_msgs::msg::Image` | The mask image of detected dusty area in latest single frame | +| `~/output/blockage_diag/debug/multi_frame_dust_mask` | `sensor_msgs::msg::Image` | The mask image of continuous detected dusty area | +| `~/output/blockage_diag/debug/blockage_dust_merged_image` | `sensor_msgs::msg::Image` | The merged image of blockage detection(red) and multi frame dusty area detection(yellow) results | +| `~/output/blockage_diag/debug/ground_dust_ratio` | `tier4_debug_msgs::msg::Float32Stamped` | The ratio of dusty area divided by area where ray usually returns from the ground. | ## Parameters -| Name | Type | Description | -| -------------------------- | ------ | -------------------------------------------------- | -| `blockage_ratio_threshold` | float | The threshold of blockage area ratio | -| `blockage_count_threshold` | float | The threshold of number continuous blockage frames | -| `horizontal_ring_id` | int | The id of horizontal ring of the LiDAR | -| `angle_range` | vector | The effective range of LiDAR | -| `vertical_bins` | int | The LiDAR channel number | -| `model` | string | The LiDAR model | -| `buffering_frames` | uint | The number of buffering [range:1-200] | -| `buffering_interval` | uint | The interval of buffering | +| Name | Type | Description | +| ----------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- | +| `blockage_ratio_threshold` | float | The threshold of blockage area ratio.If the blockage value exceeds this threshold, the diagnostic state will be set to ERROR. | +| `blockage_count_threshold` | float | The threshold of number continuous blockage frames | +| `horizontal_ring_id` | int | The id of horizontal ring of the LiDAR | +| `angle_range` | vector | The effective range of LiDAR | +| `vertical_bins` | int | The LiDAR channel number | +| `model` | string | The LiDAR model | +| `blockage_buffering_frames` | int | The number of buffering about blockage detection [range:1-200] | +| `blockage_buffering_interval` | int | The interval of buffering about blockage detection | +| `dust_ratio_threshold` | float | The threshold of dusty area ratio | +| `dust_count_threshold` | int | The threshold of number continuous frames include dusty area | +| `dust_kernel_size` | int | The kernel size of morphology processing in dusty area detection | +| `dust_buffering_frames` | int | The number of buffering about dusty area detection [range:1-200] | +| `dust_buffering_interval` | int | The interval of buffering about dusty area detection | ## Assumptions / Known limits -1. Only Hesai Pandar40P and Hesai PandarQT were tested. For a new LiDAR, it is necessary to check order of channel id in vertical distribution manually and modify the code. -2. The current method is still limited for dust type of blockage when dust particles are sparsely distributed. +1. Only Hesai Pandar40P and Hesai PandarQT were tested. For a new LiDAR, it is necessary to check order of channel id in + vertical distribution manually and modify the code. +2. About dusty area detection, False positives occur when there are water puddles on the road surface due to rain, etc. + Also, the area of the ray to the sky is currently undetectable. ## (Optional) Error detection and handling diff --git a/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/blockage_diag/blockage_diag_nodelet.hpp b/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/blockage_diag/blockage_diag_nodelet.hpp index 116dd2f832b93..66e14c418b52c 100644 --- a/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/blockage_diag/blockage_diag_nodelet.hpp +++ b/sensing/pointcloud_preprocessor/include/pointcloud_preprocessor/blockage_diag/blockage_diag_nodelet.hpp @@ -53,27 +53,42 @@ class BlockageDiagComponent : public pointcloud_preprocessor::Filter rcl_interfaces::msg::SetParametersResult paramCallback(const std::vector & p); image_transport::Publisher lidar_depth_map_pub_; image_transport::Publisher blockage_mask_pub_; + image_transport::Publisher single_frame_dust_mask_pub; + image_transport::Publisher multi_frame_dust_mask_pub; + image_transport::Publisher blockage_dust_merged_pub; rclcpp::Publisher::SharedPtr ground_blockage_ratio_pub_; rclcpp::Publisher::SharedPtr sky_blockage_ratio_pub_; + rclcpp::Publisher::SharedPtr ground_dust_ratio_pub_; + rclcpp::Publisher::SharedPtr blockage_type_pub_; private: void onBlockageChecker(DiagnosticStatusWrapper & stat); + void dustChecker(DiagnosticStatusWrapper & stat); Updater updater_{this}; - uint vertical_bins_; + int vertical_bins_; std::vector angle_range_deg_; - uint horizontal_ring_id_ = 12; + int horizontal_ring_id_; float blockage_ratio_threshold_; + float dust_ratio_threshold_; float ground_blockage_ratio_ = -1.0f; float sky_blockage_ratio_ = -1.0f; + float ground_dust_ratio_ = -1.0f; std::vector ground_blockage_range_deg_ = {0.0f, 0.0f}; std::vector sky_blockage_range_deg_ = {0.0f, 0.0f}; - uint erode_kernel_ = 10; - uint ground_blockage_count_ = 0; - uint sky_blockage_count_ = 0; - uint blockage_count_threshold_; + int blockage_kernel_ = 10; + int blockage_frame_count_ = 0; + int ground_blockage_count_ = 0; + int sky_blockage_count_ = 0; + int blockage_count_threshold_; std::string lidar_model_; - uint buffering_frames_ = 100; - uint buffering_interval_ = 5; + int blockage_buffering_frames_; + int blockage_buffering_interval_; + int dust_kernel_size_; + int dust_buffering_frames_; + int dust_buffering_interval_; + int dust_buffering_frame_counter_ = 0; + int dust_count_threshold_; + int dust_frame_count_ = 0; public: PCL_MAKE_ALIGNED_OPERATOR_NEW diff --git a/sensing/pointcloud_preprocessor/launch/blockage_diag.launch.xml b/sensing/pointcloud_preprocessor/launch/blockage_diag.launch.xml index 47dc1e73eef12..8fa5796e2802d 100644 --- a/sensing/pointcloud_preprocessor/launch/blockage_diag.launch.xml +++ b/sensing/pointcloud_preprocessor/launch/blockage_diag.launch.xml @@ -2,8 +2,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sensing/pointcloud_preprocessor/src/blockage_diag/blockage_diag_nodelet.cpp b/sensing/pointcloud_preprocessor/src/blockage_diag/blockage_diag_nodelet.cpp index 3563bf2b6c408..06da91e61b3bd 100644 --- a/sensing/pointcloud_preprocessor/src/blockage_diag/blockage_diag_nodelet.cpp +++ b/sensing/pointcloud_preprocessor/src/blockage_diag/blockage_diag_nodelet.cpp @@ -19,6 +19,7 @@ #include #include +#include namespace pointcloud_preprocessor { @@ -30,34 +31,45 @@ BlockageDiagComponent::BlockageDiagComponent(const rclcpp::NodeOptions & options { { // initialize params: - horizontal_ring_id_ = static_cast(declare_parameter("horizontal_ring_id", 12)); - blockage_ratio_threshold_ = - static_cast(declare_parameter("blockage_ratio_threshold", 0.1)); - vertical_bins_ = static_cast(declare_parameter("vertical_bins", 40)); - angle_range_deg_ = declare_parameter("angle_range", std::vector{0.0, 360.0}); - lidar_model_ = static_cast(declare_parameter("model", "Pandar40P")); - blockage_count_threshold_ = - static_cast(declare_parameter("blockage_count_threshold", 50)); - buffering_frames_ = static_cast(declare_parameter("buffering_frames", 100)); - buffering_interval_ = static_cast(declare_parameter("buffering_interval", 5)); + horizontal_ring_id_ = declare_parameter("horizontal_ring_id"); + blockage_ratio_threshold_ = declare_parameter("blockage_ratio_threshold"); + vertical_bins_ = declare_parameter("vertical_bins"); + angle_range_deg_ = declare_parameter>("angle_range"); + lidar_model_ = declare_parameter("model"); + blockage_count_threshold_ = declare_parameter("blockage_count_threshold"); + blockage_buffering_frames_ = declare_parameter("blockage_buffering_frames"); + blockage_buffering_interval_ = declare_parameter("blockage_buffering_interval"); + dust_ratio_threshold_ = declare_parameter("dust_ratio_threshold"); + dust_count_threshold_ = declare_parameter("dust_count_threshold"); + dust_kernel_size_ = declare_parameter("dust_kernel_size"); + dust_buffering_frames_ = declare_parameter("dust_buffering_frames"); + dust_buffering_interval_ = declare_parameter("dust_buffering_interval"); } updater_.setHardwareID("blockage_diag"); updater_.add( std::string(this->get_namespace()) + ": blockage_validation", this, &BlockageDiagComponent::onBlockageChecker); + updater_.add( + std::string(this->get_namespace()) + ": dust_validation", this, + &BlockageDiagComponent::dustChecker); updater_.setPeriod(0.1); - + single_frame_dust_mask_pub = + image_transport::create_publisher(this, "blockage_diag/debug/single_frame_dust_mask_image"); + multi_frame_dust_mask_pub = + image_transport::create_publisher(this, "blockage_diag/debug/multi_frame_dust_mask_image"); lidar_depth_map_pub_ = image_transport::create_publisher(this, "blockage_diag/debug/lidar_depth_map"); blockage_mask_pub_ = image_transport::create_publisher(this, "blockage_diag/debug/blockage_mask_image"); - + blockage_dust_merged_pub = + image_transport::create_publisher(this, "blockage_diag/debug/blockage_dust_merged_image"); ground_blockage_ratio_pub_ = create_publisher( "blockage_diag/debug/ground_blockage_ratio", rclcpp::SensorDataQoS()); sky_blockage_ratio_pub_ = create_publisher( "blockage_diag/debug/sky_blockage_ratio", rclcpp::SensorDataQoS()); - + ground_dust_ratio_pub_ = create_publisher( + "blockage_diag/debug/ground_dust_ratio", rclcpp::SensorDataQoS()); using std::placeholders::_1; set_param_res_ = this->add_on_set_parameters_callback( std::bind(&BlockageDiagComponent::paramCallback, this, _1)); @@ -105,19 +117,59 @@ void BlockageDiagComponent::onBlockageChecker(DiagnosticStatusWrapper & stat) stat.summary(level, msg); } +void BlockageDiagComponent::dustChecker(diagnostic_updater::DiagnosticStatusWrapper & stat) +{ + stat.add("ground_dust_ratio", std::to_string(ground_dust_ratio_)); + auto level = DiagnosticStatus::OK; + std::string msg; + if (ground_dust_ratio_ < 0.0f) { + level = DiagnosticStatus::STALE; + msg = "STALE"; + } else if ( + (ground_dust_ratio_ > dust_ratio_threshold_) && (dust_frame_count_ > dust_count_threshold_)) { + level = DiagnosticStatus::ERROR; + msg = "ERROR"; + } else if (ground_dust_ratio_ > 0.0f) { + level = DiagnosticStatus::WARN; + msg = "WARN"; + } else { + level = DiagnosticStatus::OK; + msg = "OK"; + } + + if (ground_dust_ratio_ > 0.0f) { + msg = msg + ": LIDAR ground dust"; + } + stat.summary(level, msg); +} + void BlockageDiagComponent::filter( - const PointCloud2ConstPtr & input, const IndicesPtr & indices, PointCloud2 & output) + const PointCloud2ConstPtr & input, [[maybe_unused]] const IndicesPtr & indices, + PointCloud2 & output) { std::scoped_lock lock(mutex_); - if (indices) { - RCLCPP_WARN(get_logger(), "Indices are not supported and will be ignored"); + int vertical_bins = vertical_bins_; + int ideal_horizontal_bins; + float distance_coefficient = 327.67f; + float horizontal_resolution_ = 0.4f; + if (lidar_model_ == "Pandar40P") { + distance_coefficient = 327.67f; + horizontal_resolution_ = 0.4f; + } else if (lidar_model_ == "PandarQT") { + distance_coefficient = 3276.75f; + horizontal_resolution_ = 0.6f; } - uint horizontal_bins = static_cast((angle_range_deg_[1] - angle_range_deg_[0])); - uint vertical_bins = vertical_bins_; + ideal_horizontal_bins = + static_cast((angle_range_deg_[1] - angle_range_deg_[0]) / horizontal_resolution_); pcl::PointCloud::Ptr pcl_input(new pcl::PointCloud); pcl::fromROSMsg(*input, *pcl_input); - cv::Mat lidar_depth_map(cv::Size(horizontal_bins, vertical_bins), CV_16UC1, cv::Scalar(0)); - cv::Mat lidar_depth_map_8u(cv::Size(horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + std::vector horizontal_bin_reference(ideal_horizontal_bins); + std::vector> each_ring_pointcloud(vertical_bins); + cv::Mat full_size_depth_map( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_16UC1, cv::Scalar(0)); + cv::Mat lidar_depth_map(cv::Size(ideal_horizontal_bins, vertical_bins), CV_16UC1, cv::Scalar(0)); + cv::Mat lidar_depth_map_8u( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); if (pcl_input->points.empty()) { ground_blockage_ratio_ = 1.0f; sky_blockage_ratio_ = 1.0f; @@ -132,114 +184,186 @@ void BlockageDiagComponent::filter( sky_blockage_range_deg_[0] = angle_range_deg_[0]; sky_blockage_range_deg_[1] = angle_range_deg_[1]; } else { - for (const auto & p : pcl_input->points) { - if ((p.azimuth / 100.0 > angle_range_deg_[0]) && (p.azimuth / 100.0 < angle_range_deg_[1])) { - if (lidar_model_ == "Pandar40P") { - lidar_depth_map.at( - p.ring, static_cast((p.azimuth / 100.0 - angle_range_deg_[0]))) += - static_cast(6250.0 / p.distance); // make image clearly - lidar_depth_map_8u.at( - p.ring, static_cast((p.azimuth / 100.0 - angle_range_deg_[0]))) = 255; - } else if (lidar_model_ == "PandarQT") { - lidar_depth_map.at( - vertical_bins - p.ring - 1, - static_cast((p.azimuth / 100.0 - angle_range_deg_[0]))) += - static_cast(6250.0 / p.distance); - lidar_depth_map_8u.at( - vertical_bins - p.ring - 1, - static_cast((p.azimuth / 100.0 - angle_range_deg_[0]))) = 255; + for (int i = 0; i < ideal_horizontal_bins; ++i) { + horizontal_bin_reference.at(i) = angle_range_deg_[0] + i * horizontal_resolution_; + } + for (const auto p : pcl_input->points) { + for (int horizontal_bin = 0; + horizontal_bin < static_cast(horizontal_bin_reference.size()); horizontal_bin++) { + if ( + (p.azimuth / 100 > + (horizontal_bin_reference.at(horizontal_bin) - horizontal_resolution_ / 2)) && + (p.azimuth / 100 <= + (horizontal_bin_reference.at(horizontal_bin) + horizontal_resolution_ / 2))) { + if (lidar_model_ == "Pandar40P") { + full_size_depth_map.at(p.ring, horizontal_bin) = + UINT16_MAX - distance_coefficient * p.distance; + } else if (lidar_model_ == "PandarQT") { + full_size_depth_map.at(vertical_bins - p.ring - 1, horizontal_bin) = + UINT16_MAX - distance_coefficient * p.distance; + } } } } - lidar_depth_map.convertTo(lidar_depth_map, CV_8UC1, 1.0 / 100.0); - cv::Mat no_return_mask; - cv::inRange(lidar_depth_map_8u, 0, 1, no_return_mask); - - cv::Mat erosion_dst; - cv::Mat element = cv::getStructuringElement( - cv::MORPH_RECT, cv::Size(2 * erode_kernel_ + 1, 2 * erode_kernel_ + 1), - cv::Point(erode_kernel_, erode_kernel_)); - cv::erode(no_return_mask, erosion_dst, element); - cv::dilate(erosion_dst, no_return_mask, element); - - static boost::circular_buffer no_return_mask_buffer(buffering_frames_); - - cv::Mat no_return_mask_result(cv::Size(horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); - cv::Mat time_series_blockage_mask( - cv::Size(horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); - cv::Mat no_return_mask_binarized( - cv::Size(horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + } + full_size_depth_map.convertTo(lidar_depth_map_8u, CV_8UC1, 1.0 / 300); + cv::Mat no_return_mask(cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + cv::inRange(lidar_depth_map_8u, 0, 1, no_return_mask); + cv::Mat erosion_dst; + cv::Mat blockage_element = cv::getStructuringElement( + cv::MORPH_RECT, cv::Size(2 * blockage_kernel_ + 1, 2 * blockage_kernel_ + 1), + cv::Point(blockage_kernel_, blockage_kernel_)); + cv::erode(no_return_mask, erosion_dst, blockage_element); + cv::dilate(erosion_dst, no_return_mask, blockage_element); + static boost::circular_buffer no_return_mask_buffer(blockage_buffering_frames_); + cv::Mat time_series_blockage_result( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + cv::Mat time_series_blockage_mask( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + cv::Mat no_return_mask_binarized( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); - static uint frame_count; - frame_count++; - if (buffering_interval_ != 0) { - no_return_mask_binarized = no_return_mask / 255; - if (frame_count == buffering_interval_) { - no_return_mask_buffer.push_back(no_return_mask_binarized); - frame_count = 0; - } - for (const auto & binary_mask : no_return_mask_buffer) { - time_series_blockage_mask += binary_mask; - } - cv::inRange( - time_series_blockage_mask, no_return_mask_buffer.size() - 1, no_return_mask_buffer.size(), - no_return_mask_result); + if (blockage_buffering_interval_ == 0) { + no_return_mask.copyTo(time_series_blockage_result); + } else { + no_return_mask_binarized = no_return_mask / 255; + if (blockage_frame_count_ >= blockage_buffering_interval_) { + no_return_mask_buffer.push_back(no_return_mask_binarized); + blockage_frame_count_ = 0; } else { - no_return_mask.copyTo(no_return_mask_result); + blockage_frame_count_++; } - cv::Mat ground_no_return_mask; - cv::Mat sky_no_return_mask; - no_return_mask_result(cv::Rect(0, 0, horizontal_bins, horizontal_ring_id_)) - .copyTo(sky_no_return_mask); - no_return_mask_result( - cv::Rect(0, horizontal_ring_id_, horizontal_bins, vertical_bins - horizontal_ring_id_)) - .copyTo(ground_no_return_mask); - ground_blockage_ratio_ = - static_cast(cv::countNonZero(ground_no_return_mask)) / - static_cast(horizontal_bins * (vertical_bins - horizontal_ring_id_)); - sky_blockage_ratio_ = static_cast(cv::countNonZero(sky_no_return_mask)) / - static_cast(horizontal_bins * horizontal_ring_id_); - - if (ground_blockage_ratio_ > blockage_ratio_threshold_) { - cv::Rect ground_blockage_bb = cv::boundingRect(ground_no_return_mask); - ground_blockage_range_deg_[0] = - static_cast(ground_blockage_bb.x) + angle_range_deg_[0]; - ground_blockage_range_deg_[1] = - static_cast(ground_blockage_bb.x + ground_blockage_bb.width) + angle_range_deg_[0]; + for (const auto & binary_mask : no_return_mask_buffer) { + time_series_blockage_mask += binary_mask; + } + cv::inRange( + time_series_blockage_mask, no_return_mask_buffer.size() - 1, no_return_mask_buffer.size(), + time_series_blockage_result); + } + cv::Mat ground_no_return_mask; + cv::Mat sky_no_return_mask; + no_return_mask(cv::Rect(0, 0, ideal_horizontal_bins, horizontal_ring_id_)) + .copyTo(sky_no_return_mask); + no_return_mask( + cv::Rect(0, horizontal_ring_id_, ideal_horizontal_bins, vertical_bins - horizontal_ring_id_)) + .copyTo(ground_no_return_mask); + ground_blockage_ratio_ = + static_cast(cv::countNonZero(ground_no_return_mask)) / + static_cast(ideal_horizontal_bins * (vertical_bins - horizontal_ring_id_)); + sky_blockage_ratio_ = static_cast(cv::countNonZero(sky_no_return_mask)) / + static_cast(ideal_horizontal_bins * horizontal_ring_id_); - if (ground_blockage_count_ <= 2 * blockage_count_threshold_) { - ground_blockage_count_ += 1; - } - } else { - ground_blockage_count_ = 0; + if (ground_blockage_ratio_ > blockage_ratio_threshold_) { + cv::Rect ground_blockage_bb = cv::boundingRect(ground_no_return_mask); + ground_blockage_range_deg_[0] = static_cast(ground_blockage_bb.x) + angle_range_deg_[0]; + ground_blockage_range_deg_[1] = + static_cast(ground_blockage_bb.x + ground_blockage_bb.width) + angle_range_deg_[0]; + if (ground_blockage_count_ <= 2 * blockage_count_threshold_) { + ground_blockage_count_ += 1; } + } else { + ground_blockage_count_ = 0; + } + if (sky_blockage_ratio_ > blockage_ratio_threshold_) { + cv::Rect sky_blockage_bx = cv::boundingRect(sky_no_return_mask); + sky_blockage_range_deg_[0] = static_cast(sky_blockage_bx.x) + angle_range_deg_[0]; + sky_blockage_range_deg_[1] = + static_cast(sky_blockage_bx.x + sky_blockage_bx.width) + angle_range_deg_[0]; + if (sky_blockage_count_ <= 2 * blockage_count_threshold_) { + sky_blockage_count_ += 1; + } + } else { + sky_blockage_count_ = 0; + } + // dust + cv::Mat ground_depth_map = lidar_depth_map_8u( + cv::Rect(0, horizontal_ring_id_, ideal_horizontal_bins, vertical_bins - horizontal_ring_id_)); + cv::Mat sky_blank(horizontal_ring_id_, ideal_horizontal_bins, CV_8UC1, cv::Scalar(0)); + cv::Mat ground_blank( + vertical_bins - horizontal_ring_id_, ideal_horizontal_bins, CV_8UC1, cv::Scalar(0)); + cv::Mat single_dust_img( + cv::Size(lidar_depth_map_8u.rows, lidar_depth_map_8u.cols), CV_8UC1, cv::Scalar(0)); + cv::Mat single_dust_ground_img = ground_depth_map.clone(); + cv::inRange(single_dust_ground_img, 0, 1, single_dust_ground_img); + cv::Mat dust_element = getStructuringElement( + cv::MORPH_RECT, cv::Size(2 * dust_kernel_size_ + 1, 2 * dust_kernel_size_ + 1), + cv::Point(-1, -1)); + cv::dilate(single_dust_ground_img, single_dust_ground_img, dust_element); + cv::erode(single_dust_ground_img, single_dust_ground_img, dust_element); + cv::inRange(single_dust_ground_img, 254, 255, single_dust_ground_img); + cv::Mat ground_mask(cv::Size(ideal_horizontal_bins, horizontal_ring_id_), CV_8UC1); + cv::vconcat(sky_blank, single_dust_ground_img, single_dust_img); + static boost::circular_buffer dust_mask_buffer(dust_buffering_frames_); + cv::Mat binarized_dust_mask_( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + cv::Mat multi_frame_dust_mask( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + cv::Mat multi_frame_ground_dust_result( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); - if (sky_blockage_ratio_ > blockage_ratio_threshold_) { - cv::Rect sky_blockage_bx = cv::boundingRect(sky_no_return_mask); - sky_blockage_range_deg_[0] = static_cast(sky_blockage_bx.x) + angle_range_deg_[0]; - sky_blockage_range_deg_[1] = - static_cast(sky_blockage_bx.x + sky_blockage_bx.width) + angle_range_deg_[0]; - if (sky_blockage_count_ <= 2 * blockage_count_threshold_) { - sky_blockage_count_ += 1; - } + if (dust_buffering_interval_ == 0) { + single_dust_img.copyTo(multi_frame_ground_dust_result); + dust_buffering_frame_counter_ = 0; + } else { + binarized_dust_mask_ = single_dust_img / 255; + if (dust_buffering_frame_counter_ >= dust_buffering_interval_) { + dust_mask_buffer.push_back(binarized_dust_mask_); + dust_buffering_frame_counter_ = 0; } else { - sky_blockage_count_ = 0; + dust_buffering_frame_counter_++; } - - cv::Mat lidar_depth_colorized; - cv::applyColorMap(lidar_depth_map, lidar_depth_colorized, cv::COLORMAP_JET); - sensor_msgs::msg::Image::SharedPtr lidar_depth_msg = - cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", lidar_depth_colorized).toImageMsg(); - lidar_depth_msg->header = input->header; - lidar_depth_map_pub_.publish(lidar_depth_msg); - - cv::Mat blockage_mask_colorized; - cv::applyColorMap(no_return_mask_result, blockage_mask_colorized, cv::COLORMAP_JET); - sensor_msgs::msg::Image::SharedPtr blockage_mask_msg = - cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", blockage_mask_colorized).toImageMsg(); - blockage_mask_msg->header = input->header; - blockage_mask_pub_.publish(blockage_mask_msg); + for (const auto & binarized_dust_mask : dust_mask_buffer) { + multi_frame_dust_mask += binarized_dust_mask; + } + cv::inRange( + multi_frame_dust_mask, dust_mask_buffer.size() - 1, dust_mask_buffer.size(), + multi_frame_ground_dust_result); + } + cv::Mat single_frame_ground_dust_colorized( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC3, cv::Scalar(0, 0, 0)); + cv::applyColorMap(single_dust_img, single_frame_ground_dust_colorized, cv::COLORMAP_JET); + cv::Mat multi_frame_ground_dust_colorized; + cv::Mat blockage_dust_merged_img( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC3, cv::Scalar(0, 0, 0)); + cv::Mat blockage_dust_merged_mask( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC1, cv::Scalar(0)); + blockage_dust_merged_img.setTo( + cv::Vec3b(0, 0, 255), time_series_blockage_result); // red:blockage + blockage_dust_merged_img.setTo( + cv::Vec3b(0, 255, 255), multi_frame_ground_dust_result); // yellow:dust + sensor_msgs::msg::Image::SharedPtr single_frame_dust_mask_msg = + cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", single_frame_ground_dust_colorized) + .toImageMsg(); + single_frame_dust_mask_pub.publish(single_frame_dust_mask_msg); + sensor_msgs::msg::Image::SharedPtr multi_frame_dust_mask_msg = + cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", multi_frame_ground_dust_colorized) + .toImageMsg(); + multi_frame_dust_mask_pub.publish(multi_frame_dust_mask_msg); + sensor_msgs::msg::Image::SharedPtr lidar_depth_map_msg = + cv_bridge::CvImage(std_msgs::msg::Header(), "mono16", full_size_depth_map).toImageMsg(); + lidar_depth_map_msg->header = input->header; + lidar_depth_map_pub_.publish(lidar_depth_map_msg); + cv::Mat blockage_mask_colorized; + cv::applyColorMap(time_series_blockage_result, blockage_mask_colorized, cv::COLORMAP_JET); + sensor_msgs::msg::Image::SharedPtr blockage_mask_msg = + cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", blockage_mask_colorized).toImageMsg(); + blockage_mask_msg->header = input->header; + blockage_mask_pub_.publish(blockage_mask_msg); + cv::Mat blockage_dust_merged_colorized( + cv::Size(ideal_horizontal_bins, vertical_bins), CV_8UC3, cv::Scalar(0, 0, 0)); + blockage_dust_merged_img.copyTo(blockage_dust_merged_colorized); + sensor_msgs::msg::Image::SharedPtr blockage_dust_merged_msg = + cv_bridge::CvImage(std_msgs::msg::Header(), "bgr8", blockage_dust_merged_colorized) + .toImageMsg(); + if (ground_dust_ratio_ > dust_ratio_threshold_) { + if (dust_frame_count_ < 2 * dust_count_threshold_) { + dust_frame_count_++; + } + } else { + dust_frame_count_ = 0; } + blockage_dust_merged_msg->header = input->header; + blockage_dust_merged_pub.publish(blockage_dust_merged_msg); tier4_debug_msgs::msg::Float32Stamped ground_blockage_ratio_msg; ground_blockage_ratio_msg.data = ground_blockage_ratio_; @@ -250,7 +374,14 @@ void BlockageDiagComponent::filter( sky_blockage_ratio_msg.data = sky_blockage_ratio_; sky_blockage_ratio_msg.stamp = now(); sky_blockage_ratio_pub_->publish(sky_blockage_ratio_msg); + tier4_debug_msgs::msg::Float32Stamped blockage_ratio_msg; + tier4_debug_msgs::msg::Float32Stamped ground_dust_ratio_msg; + ground_dust_ratio_ = static_cast(cv::countNonZero(single_dust_ground_img)) / + (single_dust_ground_img.cols * single_dust_ground_img.rows); + ground_dust_ratio_msg.data = ground_dust_ratio_; + ground_dust_ratio_msg.stamp = now(); + ground_dust_ratio_pub_->publish(ground_dust_ratio_msg); pcl::toROSMsg(*pcl_input, output); output.header = input->header; } @@ -280,11 +411,29 @@ rcl_interfaces::msg::SetParametersResult BlockageDiagComponent::paramCallback( get_logger(), " Setting new angle_range to: [%f , %f].", angle_range_deg_[0], angle_range_deg_[1]); } - if (get_param(p, "buffering_frames", buffering_frames_)) { - RCLCPP_DEBUG(get_logger(), "Setting new buffering_frames to: %d.", buffering_frames_); + if (get_param(p, "blockage_buffering_frames", blockage_buffering_frames_)) { + RCLCPP_DEBUG( + get_logger(), "Setting new blockage_buffering_frames_ to: %d.", blockage_buffering_frames_); + } + if (get_param(p, "blockage_buffering_interval", blockage_buffering_interval_)) { + RCLCPP_DEBUG( + get_logger(), "Setting new blockage_buffering_interval_ to: %d.", + blockage_buffering_interval_); + } + if (get_param(p, "dust_kernel_size", dust_kernel_size_)) { + RCLCPP_DEBUG(get_logger(), "Setting new dust_kernel_size_ to: %d.", dust_kernel_size_); } - if (get_param(p, "buffering_interval", buffering_interval_)) { - RCLCPP_DEBUG(get_logger(), "Setting new buffering_interval to: %d.", buffering_interval_); + if (get_param(p, "dust_buffering_frames", dust_buffering_frames_)) { + RCLCPP_DEBUG( + get_logger(), "Setting new dust_buffering_frames_ to: %d.", dust_buffering_frames_); + // note:NOT affects to actual variable. + // if you want change this param/variable, change the parameter called at launch this + // node(aip_launcher). + } + if (get_param(p, "dust_buffering_interval", dust_buffering_interval_)) { + RCLCPP_DEBUG( + get_logger(), "Setting new dust_buffering_interval_ to: %d.", dust_buffering_interval_); + dust_buffering_frame_counter_ = 0; } rcl_interfaces::msg::SetParametersResult result; result.successful = true; From e32ed7252a4a8aa4ed171d8714ffa73fdbcab2fc Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Fri, 29 Sep 2023 13:01:10 +0900 Subject: [PATCH 374/547] refactor(localization_packages): remove unused in packages.xml files (#5171) Signed-off-by: yamato-ando Co-authored-by: yamato-ando --- localization/ekf_localizer/package.xml | 2 -- localization/gyro_odometer/package.xml | 2 -- .../ar_tag_based_localizer/package.xml | 2 -- .../landmark_based_localizer/landmark_tf_caster/package.xml | 2 -- localization/localization_error_monitor/package.xml | 2 +- localization/yabloc/yabloc_common/package.xml | 6 ------ localization/yabloc/yabloc_image_processing/package.xml | 4 ---- localization/yabloc/yabloc_particle_filter/package.xml | 2 -- localization/yabloc/yabloc_pose_initializer/package.xml | 4 ---- 9 files changed, 1 insertion(+), 25 deletions(-) diff --git a/localization/ekf_localizer/package.xml b/localization/ekf_localizer/package.xml index 005bddd3eb22b..4101bae88b6e2 100644 --- a/localization/ekf_localizer/package.xml +++ b/localization/ekf_localizer/package.xml @@ -24,8 +24,6 @@ kalman_filter nav_msgs rclcpp - sensor_msgs - std_msgs std_srvs tf2 tf2_ros diff --git a/localization/gyro_odometer/package.xml b/localization/gyro_odometer/package.xml index cbcaadebddc2e..ad4a865a2014a 100644 --- a/localization/gyro_odometer/package.xml +++ b/localization/gyro_odometer/package.xml @@ -15,10 +15,8 @@ fmt geometry_msgs sensor_msgs - std_msgs tf2 tf2_geometry_msgs - tf2_ros tier4_autoware_utils rclcpp diff --git a/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml b/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml index 2c35be181d2f0..216fa21bc951a 100644 --- a/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml +++ b/localization/landmark_based_localizer/ar_tag_based_localizer/package.xml @@ -14,10 +14,8 @@ aruco cv_bridge diagnostic_msgs - geometry_msgs image_transport rclcpp - sensor_msgs tf2_eigen tf2_geometry_msgs tf2_ros diff --git a/localization/landmark_based_localizer/landmark_tf_caster/package.xml b/localization/landmark_based_localizer/landmark_tf_caster/package.xml index 64d81f744ce47..75f42b91f502a 100644 --- a/localization/landmark_based_localizer/landmark_tf_caster/package.xml +++ b/localization/landmark_based_localizer/landmark_tf_caster/package.xml @@ -12,9 +12,7 @@ autoware_cmake autoware_auto_mapping_msgs - geometry_msgs lanelet2_extension - libopencv-dev rclcpp tf2_eigen tf2_geometry_msgs diff --git a/localization/localization_error_monitor/package.xml b/localization/localization_error_monitor/package.xml index 42f7c2de44ee9..4cc5599fd81a9 100644 --- a/localization/localization_error_monitor/package.xml +++ b/localization/localization_error_monitor/package.xml @@ -13,10 +13,10 @@ ament_cmake_auto autoware_cmake + eigen diagnostic_msgs diagnostic_updater - eigen nav_msgs tf2 tf2_geometry_msgs diff --git a/localization/yabloc/yabloc_common/package.xml b/localization/yabloc/yabloc_common/package.xml index 1c3ba4de307f6..f3f46952dec13 100644 --- a/localization/yabloc/yabloc_common/package.xml +++ b/localization/yabloc/yabloc_common/package.xml @@ -13,22 +13,16 @@ autoware_auto_mapping_msgs cv_bridge - geographiclib geometry_msgs lanelet2_core lanelet2_extension - lanelet2_io - libgoogle-glog-dev pcl_conversions - rcl_interfaces rclcpp - rclcpp_components sensor_msgs signal_processing sophus std_msgs tf2_ros - ublox_msgs visualization_msgs ament_lint_auto diff --git a/localization/yabloc/yabloc_image_processing/package.xml b/localization/yabloc/yabloc_image_processing/package.xml index 961ce574f7c7a..ffdcea25b4b6d 100644 --- a/localization/yabloc/yabloc_image_processing/package.xml +++ b/localization/yabloc/yabloc_image_processing/package.xml @@ -12,14 +12,10 @@ autoware_cmake cv_bridge - nav_msgs pcl_conversions rclcpp sensor_msgs - sophus std_msgs - tf2 - tf2_ros tier4_autoware_utils visualization_msgs yabloc_common diff --git a/localization/yabloc/yabloc_particle_filter/package.xml b/localization/yabloc/yabloc_particle_filter/package.xml index 6d11b12d9c922..aabb6009c2148 100644 --- a/localization/yabloc/yabloc_particle_filter/package.xml +++ b/localization/yabloc/yabloc_particle_filter/package.xml @@ -13,7 +13,6 @@ rosidl_default_generators geometry_msgs - libgoogle-glog-dev rclcpp sensor_msgs sophus @@ -22,7 +21,6 @@ tf2 tf2_geometry_msgs tf2_ros - tf2_sensor_msgs tier4_autoware_utils visualization_msgs yabloc_common diff --git a/localization/yabloc/yabloc_pose_initializer/package.xml b/localization/yabloc/yabloc_pose_initializer/package.xml index f6e8aa83bafc7..4e3ca1c1e61d1 100644 --- a/localization/yabloc/yabloc_pose_initializer/package.xml +++ b/localization/yabloc/yabloc_pose_initializer/package.xml @@ -15,11 +15,7 @@ geometry_msgs lanelet2_extension rclcpp - rclpy sensor_msgs - std_msgs - std_srvs - tf2 tier4_localization_msgs visualization_msgs yabloc_common From ec305031e8cfa50c4da58ebe6b4bde1e99bc5b3c Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Fri, 29 Sep 2023 15:55:21 +0900 Subject: [PATCH 375/547] refactor(behavior_path_planner): use updateData() (#5165) use updateData Signed-off-by: kyoichi-sugahara --- .../start_planner/start_planner_module.hpp | 6 ++- .../start_planner/start_planner_module.cpp | 47 ++++++++++++++----- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index d22322b63b080..9bbd1203ee6b0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -63,6 +63,9 @@ struct PullOutStatus bool is_safe_dynamic_objects{false}; // current path is safe against dynamic objects bool back_finished{false}; // if backward driving is not required, this is also set to true // todo: rename to clear variable name. + bool backward_driving_complete{ + false}; // after backward driving is complete, this is set to true (warning: this is set to + // false at next cycle after backward driving is complete) Pose pull_out_start_pose{}; PullOutStatus() {} @@ -87,12 +90,13 @@ class StartPlannerModule : public SceneModuleInterface bool isExecutionRequested() const override; bool isExecutionReady() const override; // TODO(someone): remove this, and use base class function - [[deprecated]] ModuleStatus updateState() override; + [[deprecated]] void updateCurrentState() override; BehaviorModuleOutput plan() override; BehaviorModuleOutput planWaitingApproval() override; CandidateOutput planCandidate() const override; void processOnEntry() override; void processOnExit() override; + void updateData() override; void setParameters(const std::shared_ptr & parameters) { diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index a9d8278523f45..819686c20ff54 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -96,6 +96,7 @@ void StartPlannerModule::onFreespacePlannerTimer() BehaviorModuleOutput StartPlannerModule::run() { + updateData(); if (!isActivated()) { return planWaitingApproval(); } @@ -120,6 +121,23 @@ void StartPlannerModule::processOnExit() debug_marker_.markers.clear(); } +void StartPlannerModule::updateData() +{ + if (isBackwardDrivingComplete()) { + updateStatusAfterBackwardDriving(); + } else { + status_.backward_driving_complete = false; + } + + const bool has_received_new_route = + !planner_data_->prev_route_id || + *planner_data_->prev_route_id != planner_data_->route_handler->getRouteUuid(); + + if (has_received_new_route) { + status_ = PullOutStatus(); + } +} + bool StartPlannerModule::isExecutionRequested() const { const Pose & current_pose = planner_data_->self_odometry->pose.pose; @@ -178,9 +196,16 @@ bool StartPlannerModule::isExecutionReady() const return true; } -ModuleStatus StartPlannerModule::updateState() +void StartPlannerModule::updateCurrentState() { - RCLCPP_DEBUG(getLogger(), "START_PLANNER updateState"); + RCLCPP_DEBUG(getLogger(), "START_PLANNER updateCurrentState"); + + const auto print = [this](const auto & from, const auto & to) { + RCLCPP_DEBUG(getLogger(), "[start_planner] Transit from %s to %s.", from.data(), to.data()); + }; + + const auto & from = current_state_; + // current_state_ = updateState(); if (isActivated() && !isWaitingApproval()) { current_state_ = ModuleStatus::RUNNING; @@ -188,16 +213,15 @@ ModuleStatus StartPlannerModule::updateState() current_state_ = ModuleStatus::IDLE; } + // TODO(someone): move to canTransitSuccessState if (hasFinishedPullOut()) { - return ModuleStatus::SUCCESS; + current_state_ = ModuleStatus::SUCCESS; } - - if (isBackwardDrivingComplete()) { - updateStatusAfterBackwardDriving(); - return ModuleStatus::SUCCESS; // for breaking loop + // TODO(someone): move to canTransitSuccessState + if (status_.backward_driving_complete) { + current_state_ = ModuleStatus::SUCCESS; // for breaking loop } - - return current_state_; + print(magic_enum::enum_name(from), magic_enum::enum_name(current_state_)); } BehaviorModuleOutput StartPlannerModule::plan() @@ -614,10 +638,6 @@ void StartPlannerModule::updatePullOutStatus() !planner_data_->prev_route_id || *planner_data_->prev_route_id != planner_data_->route_handler->getRouteUuid(); - if (has_received_new_route) { - status_ = PullOutStatus(); - } - // save pull out lanes which is generated using current pose before starting pull out // (before approval) status_.pull_out_lanes = start_planner_utils::getPullOutLanes( @@ -672,6 +692,7 @@ void StartPlannerModule::updatePullOutStatus() void StartPlannerModule::updateStatusAfterBackwardDriving() { status_.back_finished = true; + status_.backward_driving_complete = true; // request start_planner approval waitApproval(); // To enable approval of the forward path, the RTC status is removed. From ad69c2851b7b84e12c9f0c3b177fb6a9032bf284 Mon Sep 17 00:00:00 2001 From: danielsanchezaran Date: Fri, 29 Sep 2023 16:35:27 +0900 Subject: [PATCH 376/547] refactor(behavior path planner): simplify code by using lambda (#5124) simplify code by using lambda Signed-off-by: Daniel Sanchez --- .../src/behavior_path_planner_node.cpp | 61 +++++++------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index f52e018f2eef7..6e37bd0ec4b2c 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -134,43 +134,34 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod const auto & p = planner_data_->parameters; planner_manager_ = std::make_shared(*this, p.verbose); - const auto register_and_create_publisher = [&](const auto & manager) { - const auto & module_name = manager->name(); - planner_manager_->registerSceneModuleManager(manager); - path_candidate_publishers_.emplace( - module_name, create_publisher(path_candidate_name_space + module_name, 1)); - path_reference_publishers_.emplace( - module_name, create_publisher(path_reference_name_space + module_name, 1)); - }; + const auto register_and_create_publisher = + [&](const auto & manager, const bool create_publishers) { + const auto & module_name = manager->name(); + planner_manager_->registerSceneModuleManager(manager); + if (create_publishers) { + path_candidate_publishers_.emplace( + module_name, create_publisher(path_candidate_name_space + module_name, 1)); + path_reference_publishers_.emplace( + module_name, create_publisher(path_reference_name_space + module_name, 1)); + } + }; if (p.config_start_planner.enable_module) { auto manager = std::make_shared(this, "start_planner", p.config_start_planner); - planner_manager_->registerSceneModuleManager(manager); - path_candidate_publishers_.emplace( - "start_planner", create_publisher(path_candidate_name_space + "start_planner", 1)); - path_reference_publishers_.emplace( - "start_planner", create_publisher(path_reference_name_space + "start_planner", 1)); + register_and_create_publisher(manager, true); } if (p.config_goal_planner.enable_module) { auto manager = std::make_shared(this, "goal_planner", p.config_goal_planner); - planner_manager_->registerSceneModuleManager(manager); - path_candidate_publishers_.emplace( - "goal_planner", create_publisher(path_candidate_name_space + "goal_planner", 1)); - path_reference_publishers_.emplace( - "goal_planner", create_publisher(path_reference_name_space + "goal_planner", 1)); + register_and_create_publisher(manager, true); } if (p.config_side_shift.enable_module) { auto manager = std::make_shared(this, "side_shift", p.config_side_shift); - planner_manager_->registerSceneModuleManager(manager); - path_candidate_publishers_.emplace( - "side_shift", create_publisher(path_candidate_name_space + "side_shift", 1)); - path_reference_publishers_.emplace( - "side_shift", create_publisher(path_reference_name_space + "side_shift", 1)); + register_and_create_publisher(manager, true); } if (p.config_lane_change_left.enable_module) { @@ -178,7 +169,7 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod auto manager = std::make_shared( this, module_topic, p.config_lane_change_left, route_handler::Direction::LEFT, LaneChangeModuleType::NORMAL); - register_and_create_publisher(manager); + register_and_create_publisher(manager, true); } if (p.config_lane_change_right.enable_module) { @@ -186,7 +177,7 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod auto manager = std::make_shared( this, module_topic, p.config_lane_change_right, route_handler::Direction::RIGHT, LaneChangeModuleType::NORMAL); - register_and_create_publisher(manager); + register_and_create_publisher(manager, true); } if (p.config_ext_request_lane_change_right.enable_module) { @@ -194,7 +185,7 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod auto manager = std::make_shared( this, module_topic, p.config_ext_request_lane_change_right, route_handler::Direction::RIGHT, LaneChangeModuleType::EXTERNAL_REQUEST); - register_and_create_publisher(manager); + register_and_create_publisher(manager, true); } if (p.config_ext_request_lane_change_left.enable_module) { @@ -202,35 +193,25 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod auto manager = std::make_shared( this, module_topic, p.config_ext_request_lane_change_left, route_handler::Direction::LEFT, LaneChangeModuleType::EXTERNAL_REQUEST); - register_and_create_publisher(manager); + register_and_create_publisher(manager, true); } if (p.config_avoidance.enable_module) { auto manager = std::make_shared(this, "avoidance", p.config_avoidance); - planner_manager_->registerSceneModuleManager(manager); - path_candidate_publishers_.emplace( - "avoidance", create_publisher(path_candidate_name_space + "avoidance", 1)); - path_reference_publishers_.emplace( - "avoidance", create_publisher(path_reference_name_space + "avoidance", 1)); + register_and_create_publisher(manager, true); } if (p.config_avoidance_by_lc.enable_module) { auto manager = std::make_shared( this, "avoidance_by_lane_change", p.config_avoidance_by_lc); - planner_manager_->registerSceneModuleManager(manager); - path_candidate_publishers_.emplace( - "avoidance_by_lane_change", - create_publisher(path_candidate_name_space + "avoidance_by_lane_change", 1)); - path_reference_publishers_.emplace( - "avoidance_by_lane_change", - create_publisher(path_reference_name_space + "avoidance_by_lane_change", 1)); + register_and_create_publisher(manager, true); } if (p.config_dynamic_avoidance.enable_module) { auto manager = std::make_shared( this, "dynamic_avoidance", p.config_dynamic_avoidance); - planner_manager_->registerSceneModuleManager(manager); + register_and_create_publisher(manager, false); } } From e337576f8a22a015df9a21f21e4beb12bbdae2b7 Mon Sep 17 00:00:00 2001 From: Berkay Karaman Date: Fri, 29 Sep 2023 15:55:55 +0300 Subject: [PATCH 377/547] feat(predicted_path_checker): check predicted trajectory to avoid collisions planning can not handle (#2528) * feat(predicted_path_checker): check predicted trajectory to avoid collisions planning can not handle (#2528) Signed-off-by: Berkay Karaman * Added pkg to control.launch.py Signed-off-by: Berkay Karaman --------- Signed-off-by: Berkay Karaman --- control/predicted_path_checker/CMakeLists.txt | 37 ++ control/predicted_path_checker/README.md | 103 +++ .../config/predicted_path_checker.param.yaml | 22 + .../images/FlowChart.png | Bin 0 -> 64755 bytes .../images/Z_axis_filtering.png | Bin 0 -> 22632 bytes .../images/general-structure.png | Bin 0 -> 84656 bytes .../collision_checker.hpp | 128 ++++ .../predicted_path_checker/debug_marker.hpp | 92 +++ .../predicted_path_checker_node.hpp | 178 ++++++ .../include/predicted_path_checker/utils.hpp | 104 ++++ .../launch/predicted_path_checker.launch.xml | 21 + control/predicted_path_checker/package.xml | 47 ++ .../collision_checker.cpp | 231 +++++++ .../debug_marker.cpp | 329 ++++++++++ .../predicted_path_checker_node.cpp | 585 ++++++++++++++++++ .../src/predicted_path_checker_node/utils.cpp | 429 +++++++++++++ .../launch/control.launch.py | 32 + 17 files changed, 2338 insertions(+) create mode 100644 control/predicted_path_checker/CMakeLists.txt create mode 100644 control/predicted_path_checker/README.md create mode 100644 control/predicted_path_checker/config/predicted_path_checker.param.yaml create mode 100644 control/predicted_path_checker/images/FlowChart.png create mode 100644 control/predicted_path_checker/images/Z_axis_filtering.png create mode 100644 control/predicted_path_checker/images/general-structure.png create mode 100644 control/predicted_path_checker/include/predicted_path_checker/collision_checker.hpp create mode 100644 control/predicted_path_checker/include/predicted_path_checker/debug_marker.hpp create mode 100644 control/predicted_path_checker/include/predicted_path_checker/predicted_path_checker_node.hpp create mode 100644 control/predicted_path_checker/include/predicted_path_checker/utils.hpp create mode 100755 control/predicted_path_checker/launch/predicted_path_checker.launch.xml create mode 100644 control/predicted_path_checker/package.xml create mode 100644 control/predicted_path_checker/src/predicted_path_checker_node/collision_checker.cpp create mode 100644 control/predicted_path_checker/src/predicted_path_checker_node/debug_marker.cpp create mode 100644 control/predicted_path_checker/src/predicted_path_checker_node/predicted_path_checker_node.cpp create mode 100644 control/predicted_path_checker/src/predicted_path_checker_node/utils.cpp diff --git a/control/predicted_path_checker/CMakeLists.txt b/control/predicted_path_checker/CMakeLists.txt new file mode 100644 index 0000000000000..cfbe95df99e74 --- /dev/null +++ b/control/predicted_path_checker/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.14) +project(predicted_path_checker) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +find_package(Eigen3 REQUIRED) + +include_directories( + include + SYSTEM + ${Eigen3_INCLUDE_DIRS} +) + +ament_auto_add_library(predicted_path_checker SHARED + src/predicted_path_checker_node/predicted_path_checker_node.cpp + src/predicted_path_checker_node/collision_checker.cpp + src/predicted_path_checker_node/utils.cpp + src/predicted_path_checker_node/debug_marker.cpp + +) + +rclcpp_components_register_node(predicted_path_checker + PLUGIN "autoware::motion::control::predicted_path_checker::PredictedPathCheckerNode" + EXECUTABLE predicted_path_checker_node +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package( + INSTALL_TO_SHARE + launch + config +) diff --git a/control/predicted_path_checker/README.md b/control/predicted_path_checker/README.md new file mode 100644 index 0000000000000..24e4b91ef441b --- /dev/null +++ b/control/predicted_path_checker/README.md @@ -0,0 +1,103 @@ +# Predicted Path Checker + +## Purpose + +The Predicted Path Checker package is designed for autonomous vehicles to check the predicted path generated by control +modules. It handles potential collisions that the planning module might not be able to handle and that in the brake +distance. In case of collision in brake distance, the package will send a diagnostic message labeled "ERROR" to alert +the system to send emergency and in the case of collisions in outside reference trajectory, it sends pause request to +pause interface to make the vehicle stop. + +![general-structure.png](images%2Fgeneral-structure.png) + +## Algorithm + +The package algorithm evaluates the predicted trajectory against the reference trajectory and the predicted objects in +the environment. It checks for potential collisions and, if necessary, generates an appropriate response to avoid them ( +emergency or pause request). + +### Inner Algorithm + +![FlowChart.png](images%2FFlowChart.png) + +**cutTrajectory() ->** It cuts the predicted trajectory with input length. Length is calculated by multiplying the +velocity +of ego vehicle with "trajectory_check_time" parameter and "min_trajectory_length". + +**filterObstacles() ->** It filters the predicted objects in the environment. It filters the objects which are not in +front of the vehicle and far away from predicted trajectory. + +**checkTrajectoryForCollision() ->** It checks the predicted trajectory for collision with the predicted objects. It +calculates both polygon of trajectory points and predicted objects and checks intersection of both polygons. If there is +an intersection, it calculates the nearest collision point. It returns the nearest collision point of polygon and the +predicted object. It also checks predicted objects history which are intersect with the footprint before to avoid +unexpected behaviors. Predicted objects history stores the objects if it was detected below the "chattering_threshold" +seconds ago. + +If the "enable_z_axis_obstacle_filtering" parameter is set to true, it filters the predicted objects in the Z-axis by +using "z_axis_filtering_buffer". If the object does not intersect with the Z-axis, it is filtered out. + +![Z_axis_filtering.png](images%2FZ_axis_filtering.png) + +**calculateProjectedVelAndAcc() ->** It calculates the projected velocity and acceleration of the predicted object on +predicted trajectory's collision point's axes. + +**isInBrakeDistance() ->** It checks if the stop point is in brake distance. It gets relative velocity and +acceleration of ego vehicle with respect to the predicted object. It calculates the brake distance, if the point in +brake distance, it returns true. + +**isItDiscretePoint() ->** It checks if the stop point on predicted trajectory is discrete point or not. If it is not +discrete point, planning should handle the stop. + +**isThereStopPointOnRefTrajectory() ->** It checks if there is a stop point on reference trajectory. If there is a stop +point before the stop index, it returns true. Otherwise, it returns false, and node is going to call pause interface to +make the vehicle stop. + +## Inputs + +| Name | Type | Description | +| ------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- | +| `~/input/reference_trajectory` | `autoware_auto_planning_msgs::msg::Trajectory` | Reference trajectory | +| `~/input/predicted_trajectory` | `autoware_auto_planning_msgs::msg::Trajectory` | Predicted trajectory | +| `~/input/objects` | `autoware_auto_perception_msgs::msg::PredictedObject` | Dynamic objects in the environment | +| `~/input/odometry` | `nav_msgs::msg::Odometry` | Odometry message of vehicle to get current velocity | +| `~/input/current_accel` | `geometry_msgs::msg::AccelWithCovarianceStamped` | Current acceleration | +| `/control/vehicle_cmd_gate/is_paused` | `tier4_control_msgs::msg::IsPaused` | Current pause state of the vehicle | + +## Outputs + +| Name | Type | Description | +| ------------------------------------- | ---------------------------------------- | -------------------------------------- | +| `~/debug/marker` | `visualization_msgs::msg::MarkerArray` | Marker for visualization | +| `~/debug/virtual_wall` | `visualization_msgs::msg::MarkerArray` | Virtual wall marker for visualization | +| `/control/vehicle_cmd_gate/set_pause` | `tier4_control_msgs::srv::SetPause` | Pause service to make the vehicle stop | +| `/diagnostics` | `diagnostic_msgs::msg::DiagnosticStatus` | Diagnostic status of vehicle | + +## Parameters + +### Node Parameters + +| Name | Type | Description | Default value | +| :---------------------------------- | :------- | :-------------------------------------------------------------------- | :------------ | +| `update_rate` | `double` | The update rate [Hz] | 10.0 | +| `delay_time` | `double` | he time delay considered for the emergency response [s] | 0.17 | +| `max_deceleration` | `double` | Max deceleration for ego vehicle to stop [m/s^2] | 1.5 | +| `resample_interval` | `double` | Interval for resampling trajectory [m] | 0.5 | +| `stop_margin` | `double` | The stopping margin [m] | 0.5 | +| `ego_nearest_dist_threshold` | `double` | The nearest distance threshold for ego vehicle [m] | 3.0 | +| `ego_nearest_yaw_threshold` | `double` | The nearest yaw threshold for ego vehicle [rad] | 1.046 | +| `min_trajectory_check_length` | `double` | The minimum trajectory check length in meters [m] | 1.5 | +| `trajectory_check_time` | `double` | The trajectory check time in seconds. [s] | 3.0 | +| `distinct_point_distance_threshold` | `double` | The distinct point distance threshold [m] | 0.3 | +| `distinct_point_yaw_threshold` | `double` | The distinct point yaw threshold [deg] | 5.0 | +| `filtering_distance_threshold` | `double` | It ignores the objects if distance is higher than this [m] | 1.5 | +| `use_object_prediction` | `bool` | If true, node predicts current pose of the objects wrt delta time [-] | true | + +### Collision Checker Parameters + +| Name | Type | Description | Default value | +| :--------------------------------- | :------- | :---------------------------------------------------------------- | :------------ | +| `width_margin` | `double` | The width margin for collision checking [Hz] | 0.2 | +| `chattering_threshold` | `double` | The chattering threshold for collision detection [s] | 0.2 | +| `z_axis_filtering_buffer` | `double` | The Z-axis filtering buffer [m] | 0.3 | +| `enable_z_axis_obstacle_filtering` | `bool` | A boolean flag indicating if Z-axis obstacle filtering is enabled | false | diff --git a/control/predicted_path_checker/config/predicted_path_checker.param.yaml b/control/predicted_path_checker/config/predicted_path_checker.param.yaml new file mode 100644 index 0000000000000..780d86b5e9804 --- /dev/null +++ b/control/predicted_path_checker/config/predicted_path_checker.param.yaml @@ -0,0 +1,22 @@ +/**: + ros__parameters: + # Node + update_rate: 10.0 + delay_time: 0.17 + max_deceleration: 1.5 + resample_interval: 0.5 + stop_margin: 0.5 # [m] + ego_nearest_dist_threshold: 3.0 # [m] + ego_nearest_yaw_threshold: 1.046 # [rad] = 60 [deg] + min_trajectory_check_length: 1.5 # [m] + trajectory_check_time: 3.0 + distinct_point_distance_threshold: 0.3 + distinct_point_yaw_threshold: 5.0 # [deg] + filtering_distance_threshold: 1.5 # [m] + use_object_prediction: true + + collision_checker_params: + width_margin: 0.2 + chattering_threshold: 0.2 + z_axis_filtering_buffer: 0.3 + enable_z_axis_obstacle_filtering: false diff --git a/control/predicted_path_checker/images/FlowChart.png b/control/predicted_path_checker/images/FlowChart.png new file mode 100644 index 0000000000000000000000000000000000000000..15b32417d2a73a0783c0c36e5e244f51459b2ca1 GIT binary patch literal 64755 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

    $>#N+PgcG$AAFOIsWPt+ZhX2D?( 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 z zSG?Ee+K3CfX-n|drCRRuI-U1>iWerJx~?Y0;oBMqQb%6yiSoFmr}w3dZ|##_(www$ z@_pAn_(0*LBEMKg%TSFxgOqf0m=1Pwho#Wkh?~PgH(zid zo)Og#Xj7&bn2SWd5!sk?YU)!6+ZLJ>TSjD*+*T%54~d<4MUiIeHHlanQ0cTl?2b|; z91|JFCK>-2th#|5kocY%eP6-`+vim+SYKz*r7W~3$NlA8P5}Q|UvfkvKT8PPMu~uoaOc>^Mn13%O z`70I_&#jtlB#%Hy{btjbDos&t2Oa^wYjEnS?@(V*@o}_4Moqu~-2b(lGq@Y*NOs=s z?mectdf`vb0-?nl?Ǧ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¶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 literal 0 HcmV?d00001 diff --git a/control/predicted_path_checker/images/Z_axis_filtering.png b/control/predicted_path_checker/images/Z_axis_filtering.png new file mode 100644 index 0000000000000000000000000000000000000000..0c064aecae42e81f470ea19efb7422c412173beb GIT binary patch literal 22632 zcmeHu2|ShU);B^MVVg@bY-4QmEb|mH53xzYHqY}sWXP~HR%Q}XnQbJQGG!}7C{#ou z86q-d`mS9(PtS9nbIv!M_x;}Uy0oK+Y3bs{=W62(n!tM(H>88DgAMX$A0YuDVO{|dUO{m~@K#tsL=^lH6z7u^7B~Ib z-_p*;^>9Khw?GGHXG?Y=RZ%_xFcp`Hpdh=jB6u>g@^SU{`T4Br;OgTK+WaNNEW|{9 zwt4zurxlRo6F3XroVT%das#sn2?z>+|AL@N)xp!o)(s3(6%-W0PLAEGjkl%U&uywI zxtzVHrkxB1zMwDEPY{?T1n zgP5O#ToLmFA?2&idZ@D9A-=xgDur>UdhX{v+m z@#}0ybGrDS_q24k*K$MJID;vW{zqgMKRgpM;Ae}VprG*2W$ZkU7CKxP+hE`jc(^0M z!+~}_4oDlX!@-9~baQj|c5we=qqUo>tBv&`#|~$-^z?M|`(rm-H|N9S9rketC;!9j z*aib@OXt6BHFiLH+yCquvi?$X8bsPkcim%Sy@&F|;Hq{9n@3oBo~md-wh#QoXe%2l+P){ryZoj`@!Xd&C1E(l%}`Hr}2AV9$QP zv;K(l_P=obsLj*H+0xs=_m84uc_@K)zfJd7cj#B%{`!pNv#y&1&_zJC{1hHR;X~FO z-YGF*$)AKgTFJ}J$J5&8a1@p%|6C;g$&w;}T2e&pPfL1RdfM4||8FcQtOPdq3$6b# za}Im|5d44E^Kb5l;9m*lp*H$QLitCz^RsvGwlQ$G1gce7*$-&FKMSch5P=_>nHLZZ zYbJgOy^W{0%@O~8Ho05=pUdTMdiIYJAtG`3`xm(t#IhK>m0tx{?6B=fa(esK=~gu)K_<)ZviYaRLwf1%whogM73-m*1tZ)||p zQv?pQw*zqV6b@gwI3ST&FXS&V#UByip;`P7MRdQpkw=0kdT5P*(T7K}`YWJ20v|`h z^M4=Efmq4kx_y5o1(qNT^8cmar@wjRDf|h*h#dX?_mhJE8C?In^Z$R=MThE2wD!TJx-LVsTJ{~4TibdXdFd+{+E|>iFS<*_7BLpHJ;}2qHCdNDbL=yX;YATy;hA)4?T2Jm4hZl zOaq~$0lU=qI(LYmT8shXi)_^X&F-&kogp*<5Sb^x6!v_|jb%vo5)r<7F}T zcIWYzk4=-=yOVBKY>M&_2rdgVJ~4Au97b_R>F2L_7G^Zpwd7DbydUpb(E}XNACFfH zAhGax#UjGPap(cYf7uHz^zk%)3s=)x`|6gF*A`(e-`D4@T^l~85mz=XJ$%#K6tLEN z?n{&P#mdVrYE9iP>?dO3~Y)-ycY>4=D9HRW{rDzVk6&G6fWflK%wGc1ga)s*8B2 z*1ZL4EC)=*R+@6YI>aA?ucYHVz=yZ(2XK8%I`^&_;oS@}~jmN7Wy0ZnXpJq6Ylw_t`gpN5s zo-Zb|E4BT>v@i-NLZWl+Mc95#z(RR_S&m74m(N9F`J|v#Y%65I+lhyJ=ey-k!f+^)g_jk98FQoI$ z_K8o{@_(_Kz6tj%5(r$kui2i>&aQbO`{p}`=Yg#K0P|Wq3BQR8vL}`ORXPg=R zhGPmKpPgQnRkuVEQE_B|CHe>HCD~Ehxl);Xo0F55{NDN4&cD2SaqY>SkTwFk40746 zCt0Nvp*t>Kt6g;2@BAj;$uovN&yn;ht5k;JM}{YN{?jZ5jFeOaQf>!m;6 zFYERC{zW z*hR~ty{6ggOI``zVn9#vmh}hPU=1~XJQ!-d!ll8FQ2l)v%Xh~cT4I9M-HCPDK5Ra>nANm zNyv!OM&Mg#CLy62&u;PTL}2o}Ux)^7g8f#v+%q zdOK{tVNbjOEy24S^J!}HSl8nEojuBfI$A_$SOQ)qOVL%gyMf<6UmV)nY&~#!5jc}w z(sZIMW|AautnuOw`@Rz&I`)8`K&s<%b71CiM*k}^h!YGF19VG@G(;oNNSxmPU z!BAV3dila{tx&vhy_rt_zj%8^n9YJF3m;iMa?xz8^I9^&J*LsX3bD2nM;QDs2SLS0)fG=#vG6oyt? zWP~EveCKy_A7R=pq(`#P;TItub@0T$f}yx{lcq9YsFQpe*NWKy&8$#h0@XXcx=0G* zUV3HQ`<=_UDJoX!{gZJh_->a$bAu9)oO4YD_Ppp=yl*EH&!S_e3Xt|1V1xV*F|d6n z>SrfZXzx5JQ$SwR_`3^NGown3O0%$6*BL0%3PG&um>;+kDLG$s!&ylBd&7Kjc5e^o zZRgU~I=9IQj;@85cTo-l`By*eTLgTh8LG69TdQx~>suAoKAq>)FIns`R6M&_^TIbU z&P5!FfqLyM)xVJxlzZv@*~@$)Bo@dOQaRIcB|SkvCd8{2!izG(b_r>E>P63?8IqR+y=YWVa+5~iI3=|~L& zR}$LB=W#y*If%6eA>X~Er zYwL2r>O;qoXYk8Sqt2~+8?!)9)sATo;tfD9mYdq#FCVCPvslBhCXMdDIQK<*ExmR3 zZhQ6Yi`#D6=axjyiGMG>Se~`SX;EWRYoF4v`t-C`0=;N2&|YpEqt@gd&%b|rZ6Qv2 zT1-SKhD=l|OQf3LvSm11+|4yF^t*#_=&t|JJsqYU8l8E)?*Tx?@Uq;YS#c9}dR4m_ zNvQPd;^m3q>iurHeZAzZlVNXM>P6RP`)VZ4c=Za}wnq^Gv-7Z8wCcgdbXM)!3Z}5# z!S%(lA?dC6k;Sg>9v8d3dFUIu-WHZ5x9=}(+k4W??lkEggL~T7)<5j#iUH-CE_txK z_L?dIQ_y)&C7s|}I0hzQAF?o7ZQF*rIXu#If~4ni(2~u9q#v*|nwdgfm%{eGrEx)9 zmRonfUbq0si1Wv_4t$o@rN*`o z9$+52{n7SS8RFIOdjGjE86Tggn-%1z9ou~N_G=HGP~f)_W39dK-Fj&nRHs+I-iyEKmQqLDCW6H?mV(0oux3C!0`98*lX1PsM_ zJNRX2Lo47NfrUh`j4|ESN^24P%g=%xSP#CxZY`U{Nr5XXIDQ7%6V2~i1a#uXovHdXUNh6%$3GMV%-rZIQoqsL9!;_&3M_=^ zm*&m9`N6`N5pL$wGm+%7Rh_d@@P}_)&!KSfDw+o5_ST0|F?b1tFCTv{DRmAjqMOSD z3=q9Qo3TwV>S*Zh6bDxf{VMX7@P{^HiBBcqzt~d16tb@8HEJ0{(_9F!KzFFaT68Ze zgU&e-{^+EZ_1i}tvo~~P5jupxkYA6o_1JvpmklIrREk7=GPs64J-(9^G0;uKnR8_W z5RUZmX=(qe2S%l7J`wx~bm#uXG`N8H)FoAcJ$Y!kS^dM#)n4HjqhSYgat98g?3_qk z2TOXzfl+}QQ*LKV5m&Oz8hy)047p#znI#$awzE218<%YR9Fn^LE_18oWrLv#%*;sf zl;x2W%#UP!%#-0<*CJ(_R8X|9q6{^e2`+aw*>Apale++si1k9Q0$`B=2sOF_i-ZW| zGE1W0`&~|x@Dw!94gEg9;j}$$pz|#nzH(=U;Kg%c&Mg+l*A*_gGrdImTuE@|<7#}j z;mqk1%|Vr|ga^k$Bw0vy#ge%rTJd8<`Gc34UQV@UJQY~@_`Nh1&SljZBFHoYfX~D6 zq5YjNZasKIG#}#N@k>|s7-@idxu5JO>#Bf$UmEiC`l(mfr`Yu4=(s!EFHpVN1`xtA z_gPaDR8fV6ilItbZq;g*AcK|U^3&1DRn!#@DWD6=b#T3ORHhAU9xbAuJAV}4WnLdE zHRj*>NU9+3*gM9H5D*)u>bDW0#lssA$w?;IPbDCs9n~*xS`4WE;5>IXaTZV2_;SGA zQ)130dgu%(pOO*G%65L-0F$kB@Oc=?9C@UvY7Dr+Qj58Xmy+SJ4DBy;n*+an+oL#R zgxODUJGLX6Xm&YI!bR}Ld-sn7qzYFqKAw)}DGS57VUP$9-TP*|)6St0n?7;&UXGQL z6^~KLOdLEm@$ttrv)&GJIp?1Fw_Y#m$ecxT^a@qSxOiKZ-=z|;T^-sftLYb#+g*ud zKfg>8rp40MmSBO8elqn!l1PnEm|>qDf;ahM@H~^tjzxo~je;;WJu?!wr|R+*t+4lS zwEK6i`{p-+I2R~Sj*Y5>(Dpy>vE}KvdXL{-8KD|(aeT>sV{t6*n6fOYlNV9K0SA`U z^DzgBqO73bO->1y>ZzTxTYOvJ`E}em)(R5!1v00G4*>hh)#LaBTb4ga&wX8?L?}0) zM6f0_=!qzz^B_&6LmzwpBeE)1NpOMPS0j9#gn>yoQ;pPMV0zDQJ?@DIw)!WCVHWPJ zZ13^VkAk|Sv7+LrII~dvEu(oYob=^}gB=#!o?}hg02VF$ghgUXuzrXK2Ry4rpm7n6 zA{}BMA?^?2FTmobsmsr|IfRIn&yMr!6?}1aPKg%I(I1Ofx|-9)Ou<@&tL}a>Imfuj zXA>5<)tpb)Uzdy@<1)x0`KYH`(Xe2TlIClFHw{9foBI~8E0xy_bikbQ6yQ~v3>u(@(w%qOl}en+C9;T+bp$rCCjOzdYr(NfIk`c7@>cQ zvCM8B%ZZ|6LQ*$uj}NhC#;PajFp3HBKqhC+$(jKX%P5nCubSkQ9^x=;t2 z@cbCL%Vt;K+98d%7Ay`N3CS4jhvw(*t*{KW14HfL^iY`v94~*_RWk)(8T~Q(CA>G^ z@)aV32e|=u8sjLs({`NTRC3xiQN9Gmeo<52R5uFNKc6(2&y}o8Zp9wN87? zBcPB{8SfYp{j#mG!9!-(b>M3&uPlU{ZGT=T41Fq}g(TI8mXcd1^+~F8_sm%mgIbGr z<;L1-W!k$_X&I$kC7p}Rz|UZog{)}m-x5{_VC|)&inOr^RFit9qy5xnwe`Cv_pS6C zQhK`$A1W8VcV`df5?8!tEkePqkm@4^9}FcTnSa?$fw-AyK3maQ-_~%V_LdceLEu)$ z8%FBqRxWBK>URY6-x>r|8%|yCdwKVQ3+=}$Ky-M~1*Y>zJt^PJ##E}woJC(s(YrBn zIZLbWBgw~Mh+*3{egz_ACEoU8!(5*2c&)=jHN;84U&3b-PoW6^4t`)^T1Vvy2K(?9BvPGNpJaO4+U?s&WL05G*wx) zMg15vMvHYr*&Qflrf)w%QX9@tesf@EBMv6u9p_hIU2G0ZiS|2TFnLU}Xn<{io9pU@ zMOoz4Vm&>wvogE;t|Sp8T%^+eD_6XBSG!MZ5WFridOoS}Y<=SyPvz+NSDuSyRSS$o z_O1AF6>BZG{ilh5FaE}pANYjbf;GG>gp{P7gpuR#4e{B|E!9tknpygqf@xjSPYK(a z@Zv2cN-gwdi*Hu0!DM^@w0Kn>b3cN5DV=$ccS4+rFlS}16^qq@FFo-ImZIXpApFj% zKS$E{!*vG9m);AnTteQVfa~$(lJ}sBuXUZnkS*)=^_8GWv76LS@z=3#J5b{j>z_xS zW8L71bH0Pe9xN=3l-f=Be;_qz^mU;#e(Y4TH%-RhYWiUfCLb}E5t##F8T!(Cu&-+Q z8b;Ep`$A1lo9FZV6n}_PvQ!S#M*9kIoVH)#pR7G~f515g#2~yhEDInSwH6VI4|hY? zI?A_$5N(~8HUug$u(zTbyY?{D`?Ta;O#o(UTJnpvvQ1qofJ@m%`l-q;*vMz$Rb3q6 z^C8$7UxTVlOglL>YI7BAn!de!RkXm!kS!x`knbzGZ9F{wLn&b=KJ#(HNI365=sOVWbral_ zd}GUEvc7WejYtt0C+~6f-2txe-xln+fsS9CPBuH^Gptt(@X1E>hkEs8U zv__ZWg2@8;u*kS^>ET1+6cu-;o6qv@BDQMEfE?CE7po>Qx&*F&vOF_-Zm}v-F8BB_ zN#X`D7H@6u_BG4z(rX~6=u!_BVxWHdFKfC5*rjN8t&PonZp}}&9uV63E7NLI7&ab+ znyb~U)KU#RTW*+R+!3HOSLSlA9-hi0T;H~0SYE`{M{S*jksJ)LmF3E)CeV|4NYTKS z0yh>NAog7R^%D*Zn3#>~8GmdOkbL7wcE84P`lxba)jm%P_Q8HB^ zCY3#tG|T{yB|3!5Z_I{{PNuWwEQJ{eOH_^|f=Jev=WHspciS+T40x=rt$V)NMr{vt zP16?!ymMbkQy?T%jF*ttJgbj027Y2G)k@|*)*8k1pu_wGlmF0Ijollnk4&LkmV=5W z+8nHC_lTDVd_Im?@ec#%=-pU93w3S05}9OY@Jie9i;-VpFx2#?RhITsboDJT3~p*T zyeSsbP7-*k=_0avs^ErH4j1M0m|4B6OHB=7u9UR)n?!?mQ1*Z_(5=8~9TLVqoaJuv z(no@(-9oZeNurGDxQq&|c8c-2%sbB(uOez}!TLq6b8m5{Vh~iR150(UFVAM$kAv?YFfwbV)G}lcNJMKX ztB*eD_)vRhd%A1vI|#<5OY^359pry_KMNpxJjV_XxpTpCtUfE-D>n+nR)YPro#*IN z@g3epWVZz@BiJD@4K8AK!E2OdTh3~-#__T6eUxb^1x#GRGlR^_K6$(x)7}?<--Y7^ z;tD56;+cXc!;18)5=CT5L%uWY-Ns93o97JOOLH&e7LaVc=tdnuhuVRGD{p7`pOOh%)^9SX+5#OF1BNsu@ z@_L5o%QzvYY9V@KM$_BpfCu7Yi&Gmfpn{~}7(aWGX9=nB*}a{#`rzx`>+wvRK=q7Y zjDqr;KOWXCe3yU(#+sJp*_1zUKgL3O!U~}&D%AHm{d;Ru2`IRLr*S($o+UL(cU19| z1XWYiRc#sL2%pcx4=OG@BA7|EFIh`v$%Z^G6~%0`Z~4awod(f^5%dEO`X0PFcxW7d z>+VaAy`2uG{ru>S@F!Rl`hu_tgccKn)P)gS=1~~I`?BHypq#KDZ@fT-xMF?p?TmGj zjIGzwiXDpgq1q1RHP5gp_q>pevDxdw{q7={>3d-JVaka(r1>cr7>@4HTN`=|o&ga( ze%!uVbstMHP>@MOErs|_*|H=zMyl&Pk%lW>59Txh6rz zhv8O@z@6t`9LE_hv9&Sd9_wP`MN+6z6j+%Merv?QxR3_pZu&h|Ands@{Ovqb2y$uO zE-%2gsK5QDYZ9p|in0t?0#OjueLM#CO+4ZGvIo`+Q`TELlcHjX;q-o6ltOSyPn}M? zh-yApQXp*uOMg?=^NF?J9uIyZR@d)=#e?qU!02$#P)WJ!WANr+r_QOb`>hY2e=|I# zz-D~mlh8G>rAtH?^)r|PW>RPpvz5`YB@jJ(l(8-I;VNv&UBE?j;A@znbh3rrGf3_k zp?rM(j&?R>8Ff_PT&^Ft$R2wfyns)U`t$ThQVw)1@pe?=X*)FY*V(4E{+iW2d+K5O z3Fn$V%6RX`#ai0DuowN3b2*-Fa3Ul{I`%*(4wsThC5XwxOe%X*RqrWJa{2f-iJfFO z8C;2_9d|4(7t6Yo(*tu=i7hZi91Tbz=owvp^nuI^LwvS>r8=9n?`Y!sL&ylqk|Q@6G#wHOC>J&AXgEY+v^H*MT)-u+Cdx^FUt9m3iF`I z(@CchoedB@!*i-g3YxD(XT%-R%svu6mA6AS5T$y_s+S&g+^ET1Zd%j+kyzlgm{z1! z=Tf8|aYVrj6lL#xP*tHFO_0<)4}ov*s-L-BsG1N}knzsJdR!7jUtCI-c6G<^_rTP_ zLB?fZBikan0q@5N zP467Dzn2$S(wuIfEooC&p*EuR;3**~z4MCMD6;K&^f+YLZvw;~%BjqNU2q-G5bf^t zdWEnXgQ+*z*WJg!;;=N-PKmxSL(<$d8Mwe})?A`nol}LG-k!T%Z{ZrPc%CGMK>fYI z+D(~4CjNsk=!MP z+U0kRZx)DjF4Z~S8i8lb85x_~kUTEr?adZ{(X=&!cN9E3$CjG!ehq5cKr-qPjHWCfIIv2qDIh%1aVCe%35TB3Ek|^5`7+u>w!me z{G?031AT$H#|srTua%@hgrYPD-*NP{pw{5=C}lZGH1in(3YOhBAbaC#5qq-`zOWSu z=cXseW2O;7u7HROqWumrU8bQIvaJKtEUyWj66|KMCSp5Jn}K&ZaGpJ~Ax2UTq^^pA zrVwlP9?eqWLldDR+=;4BH1=23bgPzYD1^)C5VT)CcHFcauT_PQ;KIo)71Ma=t%zo( z*T^h5P8EpX`F4Co%YtxUA2y=rvUU87ck-T4_eRoisqu?z_FvDnrr4lC-2c^byl(_K z>v`JUa~9k7>=2jj_|K!&9|F5HJX6SS5y6J4rD2J8WEa+7@wr8*_gmM|{qBb3 zR;JxuGuYf>jr){hsb1BqkK2P0Fq8-_*j*(u4=xb0(*E?OQWgXgaT9-wZAE-0Dp)Ba z0od3E1rfslWHm5UW>o#a$93lKRtg`vL?#Hr%T*q8_Q!HGLIH>u8zqUx;MJU3 zoyy2!j-ur?HF?PzarQiI@@cPd{J58j*5SIISt=~(@VANQmLHMMei&)Xb*r~+u{Y5; z!#MK6BKT4;!aitGxONm+rLje@y^A|GFDy&gpWyW+gO|H+f#%y#!uc8wSL@Q(+=Zdw z)GXZBY;;>u@K7b%Mmc2oWcYF1wz2EvI9@NX0mxhrWoeqgfVuNZ6c_rR=TwFSliAbO zIgX4o(|-neiF!RbaL zvID%LACdTU)-+OBi1RWAsC}vXS{0!el)}BF5>AUhq{q8OfFd&Gxwo**Z1qJRuHD6h zKt6z^7-;Uf0ypLKV8*wY4`*UFsw_Lo_`@ANwgk4m@~CLAJmi>b#=)Wah^+-6(WHyo zqOCDY7h*D}I)Miz*oOySa&1oow#-TGeBz6W<0=XDGE8$?lU+h;T@ zwIgXXSWsnH(I$`oY2nfoX_N6~sd__Fw^+lK=v&jNh745vBGqXO;CIHgLChH03`wDq zjx zR&$I$djQRiR>dZYo{LGC9y^?5I9LRMGjuThIv&+JO zpwz*SC1yiD1`*3V&Mi;~Ft#R>bN4K$qKJ-%(kSsYO{&mNS?0#4G$pH4OZ7g$;hGX} z!_|2?JYz?}^(_se(!HoATW6)81~-hlf6{8|fDa8}T2oH?6b*m0s_2tJMXIU6@#3of z%Qrjh+B?s&vu;N>9qh%zce_PKWCGbyppyaVB|O)}p%+L-=0;Dx=H< zEN7Lediqd%YPnSUpLvw3(?_^N%P|EKs*Wi!;l}8Ep7yiqUhPD_>g$mA0~|KJz~6D| z(_xrqBSd(aDt> zS$i_9!qTTXW_a+ChQ(8n7D|ranMuTr&}dfy0e`-UIHo>+G!BH4PJiYTNXcqQ^QkxB z>U-piCO>XDpTsEU<3EH3zXt2sJ1-cXkib#cKTJ2k2CzG0N zQ|&ioAGaJAdo7Y#nck{=jSGT>$GV}g6rCAFO|&(ho_yT?10q<*gdj#Ry( z7*cRs+%|9j{>x}^%=MiNJKpHJA>O9oLS%P zW5WqL!q$x39LlOG{dx4Ou8?B|`MxrB@9W2GPdmM8e@lR%PDLZz;+!;1v}a8hFRwSs zfq2bso1k#>mKJU2h{5Ze(S2;Vg|J(|^(NF%AF1n2!YOQb33Fq{kom;tvit-INuQ*L zo*ks(CTmm#oh#8R6ye(sGJtifoxOCtp&`?$djDIaAHFi}gkY~BdFTy-_Y@`SQw%UK zwh0Q8de>YTKY3gpPuA7w%b(+%HT9c=HXoW25#i1!wQxb6N)i+MrPRlH&lVMQBk>)Q z&4|$++{s;ZFAS?+H#~@(stv1KO9YPBxoer9Ss-W}`4Xb91gIf1eJMO7N+tV!SKo!x zx09I^ad}EO6p0O`qa?P(H8%8JZ=I;Q$-&1yHVG7{Th_OC9iU9XU#_@B7L-6BDWFvj zOJZE;Cl&o9$L;ES$(Q}eqr<;%EIH*q%ZPaF_l0r%6uw+MR8n1UnUThPiW3yY$PM<& zTPUOu45X~qf=t@Oj z$wAU$N_3Z9+k$9X?Cs@bE(ggK3U}h{6@6{-7#0OO7D_}^b^Kv0*UR|07A=m3vJT&6 zIWL@aua2nl)Jg}CyG>(;@V^5Ibfv2tWiVwJN;hq2h#5wrvtsjQS^$G*2>ee+i|aG^ z&q~~@5|mt^1UB00tlAjIla#(cPEq>#w^NuP6?*qQy3g2wyklC=i9!ZlHfq}QV*Lxi zzqp2e&CCTWZRb;DLqlu=WI)lv3cke#9~lZRpH_Acsr*sI^CGeGc)b&WveBeNf3hm(gyivUlHWGPQiL+3X2=UbY znWycGO2tNsmK_@1=GYL($>TrP!r!)k0}?`Aavp^-TC}5!gWSLo>mR;#g`I(>6`!73 z{Xor+@)|X_qM`lnA$J0P<4y+$2~M7%^);=^+%(Fv97B+)e%^?ZfLvTf^FOS)5}q|1iphi|$YL~+$ic3_u^ zJ#bRMRs4LX+i6o&Mq?Y9bdXbwHJTO_>~>MZOzH)=>WnurkI$pq!1Kg2#QN1g^HmzW*7CIQc2Fm$Re8A??`&H<>lr(dZ+F!j z6h6voH0wKM4b1?!Dg^ZWBw^6ihalXLhpmMuSH0R7R?{a&65NWf7sa)rZY%)On@1_F zTc%H=;3g2t1&T91BSyyCXg-Pl7g7swrH4i$=|`|pto9qIyb=dB8t6ILM9>$RY{p#x zIR)hQw+j0vqgXSgFBUC8SbhAHRbqp2YofH3Y1zYRzp}$n$+$Y--Sx8Xcy5J&!h%_l zx)B3)Zr_40;@Wv*>l5&ygBo2k6^+YiwrKUm@MC0E9QUen+QIGT_Wh!;2XF#0;@~?C zVk^dmBQTx>fg`q$t8WAKFf5U$E*g6>`F@sKH0}T-f2Wqw?8-8c>)#uL5#8 zzZ2Z%dao{=Fm45u_1WYFetwWj)?nGsLQS{SNvby^h8H!MuW&5r8XhI7BDmqyoiY;h z2FNM=yT9F^ZuJ@*X-)a96YhMCG$kt()ia&zhI*515}hJZqo$qThG$^=S0y>*{6n0k0;gFB;|keqSRtY?S|1r6vb7+wG& zY#7~y4-BR{dy-0*?2Eb7s8{xyLnV$M3r+$B*V9x|2KCM&(q z-(V>H6p}AeDJuRZ_pFLQKov^f-%*hp|i|Aue192<+cAS;Gu|-H7?}(JsD|is`+fs>D1lwImiZNWHS+-#FkV@KG4!tpWXNa_pEh}N31m9$T; z9@7HWQFCd$PV3v}5tnJXN=547i)Jo1eOGMQ9S%98x16p&ms7W(Oe<*O@zUjnZv_9e z1hHtLdd4QWM`ka>*|?1>oIA9i2Pt@x>&fmiq@J6KqFU^6)Tm08Nh_;Lepg;U;V6u; zBAdPW853vmYhZ*4$z9DF;ThNl-~WMi})7y0_v7g5BD2Rl}en!XzOg-e)^v!voyPH`lB0YAmly zaQ1km0xfrU`u(@Z_cu|mx9z3^Q=&VJ@}vXMGVD22FE3L03{{vn5(hn56m*-AUc;6j zjg%T^Z!EpZ231~TroB1RjIzOxwCei8)UPJkR)bR1@eXrClMm6?Etn^BrrjSWX7^XCXL7f^L(Q#EvE#o$Vb0c*;}#Z>ljw%P6D-~x3U+LM49WUrykzDDZK|O z5wtq4k&?#DuY4@J1JN5yfa*rx-@G7Sf2+h*g_a%8=|k#&hf1r1&QApov7*Rcr+ioA z*5Y##DhQjf>syJ_b;mMqvXeaXP}C-&Ri4uFyz9}J%e8s9#B`+OQZ*$(^jt#b{2DbZ9n zzm7vW#){ObvR^(PowvYX>yK+Gl+-|ktIYQ$C_Ua0C8mIzJ#=P8XNWkw{0=Du_i~hf z5~%5kkDAZj$keZtc89)ry)s&Dt+l!Q?##CYUj;S_i_g)u@sEO4oE3st<6`xsv5;tZY*-h>!B7G-TD8;Pc zJw>^>9IwPokfeD4!ar@#Q#V$uQ$N3JtoNBPkiC^IIr;@ayNxI@+j0<)A1)T|HdK2$ zPQu?bV4LH^no$lij5MYpuHs%Ow9Ctl3pY(vL?2?>3BDp}yPVg7AWM~4=yD_E$!@N% zqu~D4cizpMyFUn5E26tuyZCanR=U|k_g(2EHO##)?t_89@q7b2kj1(g63Vr~({4>u z61R0Pff6sAY^hn!V9vXav}AWQY--A=Su{RQ&ld+PfD9${sc^qUhm^1%NUD4@z7+8B zRCbhhRq=)s)e&XpBtH+Xe$CYl3jA5RX(Y0VmwC@y7xQNwoNH2IqdXybk;gX=&#*v8qH6_@XUqS(%mtULYu&sAa- z2}~o|i9um%LE42Iq?EWcEX0)N-*UZy@1bs?&^IZJ$E`SXO3^177qpx*LB6xa>%QZl z${WUfWyEC*@;)6nhASSgW5>X-lms8`Z|3@_2vaK1^RvyXK9%dB=QP2<$^?|zrzsv9 z(4lKgg3AeZk&|B}R;~1YNJmbzDE8h($*gFD2Z);DQ&QgdLCHCaMWs<0RO{PedrlQy z6jYE`iM2vQvs7mR36ZWnHv&4q61_e{WkZo*2^pu%@pF0<1;c#hu5bk~h_coMkzEoG zwQ=e4Y4uPAN17Yy`Scc?sF0-cO`<3$C^O;LdB|}OraWfq_Ra+jre$+#)<0(k$guSC z^Hco@FfBVt=I|SEz@mzr*<4XTafI1w#@otxXcv-2gp}ny6jU!M-sU{KZ7x&2l7@(L7%Ur8a`~l_36d-5qhmvLyM=4{jfkqW9gX+AWyAe zl)M<0I@Y-%trx(kGx|PY<^(YFtLId$Jz&)CD`jCyJWFO~H00RFo7eBal+Pm6-d%%o z;euTg<=ZS#f?bctFN8)vc?yVF%fqdVomdi7-DAM_DFc^BAz(W%i$RrrJ{)+AOL3%7 zT?&{B1boc#Oi3PjC&ZEh`x_tpCI$mATl?VD*WGa5wLsltfqb&)Iye=>lpW0U-X%dH zv|!b$t*2-|5ODs8w%1o=eg#&wYL?#D)&{u%P?Jq`6^@N=@)hej0j+TE6&Rpaqv-A!~G%=k~JsUm@+h-MTO9n{XcEbJdRCtOwfO%)fjsuxV zjUIM^T+IN~jh%0o$V~%2FrXkf;R@h5$d*%FE`=#mL{#31mL~+{Wpf`iisHJ8Dn3hc zHHwuPtu}xMl6Mib7Yu0d&VnPSgcBH16yU>Ci@EUifg2BdN&e|!Wp2UgFq{`lY{fI@u$B-ZOOfzLgJ)6IP| z8@@i{Aq`9a@m16`5EEFDkwU3_qx^Guu`wi-*Z28**+6v<<{PPjqeAh1T<7V}*n()v zPRp1xwtJMj#y>tkQNsiKPBxy8&r(*fy76@AV^i$ml8Ny5nS|a| VQxBWK18~4Ubro&p3dJiC{{yym>&pND literal 0 HcmV?d00001 diff --git a/control/predicted_path_checker/images/general-structure.png b/control/predicted_path_checker/images/general-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..86f3016db5c2c492a0f1c57dfad87823919ea2e0 GIT binary patch literal 84656 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 z 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 xd-?Ys{ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace autoware::motion::control::predicted_path_checker +{ +using autoware_auto_planning_msgs::msg::Trajectory; +using autoware_auto_planning_msgs::msg::TrajectoryPoint; +using TrajectoryPoints = std::vector; +using autoware_auto_perception_msgs::msg::PredictedObject; +using autoware_auto_perception_msgs::msg::PredictedObjects; +using geometry_msgs::msg::Pose; +using geometry_msgs::msg::TransformStamped; +using tier4_autoware_utils::Point2d; +using tier4_autoware_utils::Polygon2d; +using PointArray = std::vector; + +namespace bg = boost::geometry; + +struct CollisionCheckerParam +{ + double width_margin; + double z_axis_filtering_buffer; + bool enable_z_axis_obstacle_filtering; + double chattering_threshold; +}; + +struct PredictedObjectWithDetectionTime +{ + explicit PredictedObjectWithDetectionTime( + const rclcpp::Time & t, geometry_msgs::msg::Point & p, PredictedObject obj) + : detection_time(t), point(p), object(std::move(obj)) + { + } + + rclcpp::Time detection_time; + geometry_msgs::msg::Point point; + PredictedObject object; +}; + +class CollisionChecker +{ +public: + explicit CollisionChecker( + rclcpp::Node * node, std::shared_ptr debug_ptr); + + boost::optional> + checkTrajectoryForCollision( + TrajectoryPoints & predicted_trajectory_array, + PredictedObjects::ConstSharedPtr dynamic_objects); + + void setParam(const CollisionCheckerParam & param); + +private: + // Functions + + boost::optional> checkObstacleHistory( + const Pose & base_pose, const Polygon2d & one_step_move_vehicle_polygon2d, const double z_min, + const double z_max); + + boost::optional> checkDynamicObjects( + const Pose & base_pose, PredictedObjects::ConstSharedPtr dynamic_objects, + const Polygon2d & one_step_move_vehicle_polygon2d, const double z_min, const double z_max); + + void updatePredictedObjectHistory(const rclcpp::Time & now) + { + for (auto itr = predicted_object_history_.begin(); itr != predicted_object_history_.end();) { + const auto expired = (now - itr->detection_time).seconds() > param_.chattering_threshold; + + if (expired) { + itr = predicted_object_history_.erase(itr); + continue; + } + + itr++; + } + } + + // Parameter + CollisionCheckerParam param_; + + // Variables + std::shared_ptr debug_ptr_; + rclcpp::Node * node_; + vehicle_info_util::VehicleInfo vehicle_info_; + std::vector predicted_object_history_{}; +}; +} // namespace autoware::motion::control::predicted_path_checker + +#endif // PREDICTED_PATH_CHECKER__COLLISION_CHECKER_HPP_ diff --git a/control/predicted_path_checker/include/predicted_path_checker/debug_marker.hpp b/control/predicted_path_checker/include/predicted_path_checker/debug_marker.hpp new file mode 100644 index 0000000000000..02ade624d630c --- /dev/null +++ b/control/predicted_path_checker/include/predicted_path_checker/debug_marker.hpp @@ -0,0 +1,92 @@ +// Copyright 2023 LeoDrive A.Ş. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef PREDICTED_PATH_CHECKER__DEBUG_MARKER_HPP_ +#define PREDICTED_PATH_CHECKER__DEBUG_MARKER_HPP_ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#define EIGEN_MPL2_ONLY + +#include +#include +#include + +namespace autoware::motion::control::predicted_path_checker +{ + +enum class PolygonType : int8_t { Vehicle = 0, Collision }; + +enum class PointType : int8_t { Stop = 0 }; + +enum class PoseType : int8_t { Stop = 0, Collision }; + +class PredictedPathCheckerDebugNode +{ +public: + explicit PredictedPathCheckerDebugNode(rclcpp::Node * node, const double base_link2front); + + ~PredictedPathCheckerDebugNode() {} + + bool pushPolygon( + const tier4_autoware_utils::Polygon2d & polygon, const double z, const PolygonType & type); + + bool pushPolygon(const std::vector & polygon, const PolygonType & type); + + bool pushPolyhedron( + const tier4_autoware_utils::Polygon2d & polyhedron, const double z_min, const double z_max, + const PolygonType & type); + + bool pushPolyhedron(const std::vector & polyhedron, const PolygonType & type); + + bool pushPose(const geometry_msgs::msg::Pose & pose, const PoseType & type); + + bool pushObstaclePoint(const geometry_msgs::msg::Point & obstacle_point, const PointType & type); + + visualization_msgs::msg::MarkerArray makeVirtualWallMarker(); + + visualization_msgs::msg::MarkerArray makeVisualizationMarker(); + + void publish(); + +private: + rclcpp::Publisher::SharedPtr virtual_wall_pub_; + rclcpp::Publisher::SharedPtr debug_viz_pub_; + rclcpp::Node * node_; + double base_link2front_; + + std::shared_ptr stop_pose_ptr_; + std::shared_ptr collision_pose_ptr_; + std::shared_ptr stop_obstacle_point_ptr_; + std::vector> vehicle_polygons_; + std::vector> collision_polygons_; + std::vector> vehicle_polyhedrons_; + std::vector> collision_polyhedrons_; +}; + +} // namespace autoware::motion::control::predicted_path_checker + +#endif // PREDICTED_PATH_CHECKER__DEBUG_MARKER_HPP_ diff --git a/control/predicted_path_checker/include/predicted_path_checker/predicted_path_checker_node.hpp b/control/predicted_path_checker/include/predicted_path_checker/predicted_path_checker_node.hpp new file mode 100644 index 0000000000000..e3c3b5cccfb8f --- /dev/null +++ b/control/predicted_path_checker/include/predicted_path_checker/predicted_path_checker_node.hpp @@ -0,0 +1,178 @@ +// Copyright 2023 LeoDrive A.Ş. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PREDICTED_PATH_CHECKER__PREDICTED_PATH_CHECKER_NODE_HPP_ +#define PREDICTED_PATH_CHECKER__PREDICTED_PATH_CHECKER_NODE_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace autoware::motion::control::predicted_path_checker +{ +using autoware_auto_planning_msgs::msg::Trajectory; +using autoware_auto_planning_msgs::msg::TrajectoryPoint; +using TrajectoryPoints = std::vector; +using autoware_auto_perception_msgs::msg::PredictedObjects; +using geometry_msgs::msg::Pose; +using geometry_msgs::msg::TransformStamped; +using tier4_autoware_utils::Point2d; +using tier4_autoware_utils::Polygon2d; + +struct NodeParam +{ + double update_rate; + double delay_time; + double max_deceleration; + double resample_interval; + double ego_nearest_dist_threshold; + double ego_nearest_yaw_threshold; + double stop_margin; + double min_trajectory_check_length; + double trajectory_check_time; + double distinct_point_distance_threshold; + double distinct_point_yaw_threshold; + double filtering_distance_threshold; + bool use_object_prediction; +}; + +enum class State { + DRIVE = 0, + EMERGENCY = 1, + STOP = 2, +}; + +class PredictedPathCheckerNode : public rclcpp::Node +{ +public: + explicit PredictedPathCheckerNode(const rclcpp::NodeOptions & node_options); + +private: + rclcpp::CallbackGroup::SharedPtr group_cli_; + + // Subscriber + std::shared_ptr self_pose_listener_; + rclcpp::Subscription::SharedPtr sub_dynamic_objects_; + rclcpp::Subscription::SharedPtr + sub_reference_trajectory_; + rclcpp::Subscription::SharedPtr + sub_predicted_trajectory_; + rclcpp::Subscription::SharedPtr sub_odom_; + rclcpp::Subscription::SharedPtr sub_accel_; + component_interface_utils::Subscription::SharedPtr sub_stop_state_; + + // Client + component_interface_utils::Client::SharedPtr cli_set_stop_; + + // Data Buffer + geometry_msgs::msg::PoseStamped::ConstSharedPtr current_pose_; + geometry_msgs::msg::Twist::ConstSharedPtr current_twist_; + geometry_msgs::msg::AccelWithCovarianceStamped::SharedPtr current_accel_; + PredictedObjects::ConstSharedPtr object_ptr_{nullptr}; + autoware_auto_planning_msgs::msg::Trajectory::ConstSharedPtr reference_trajectory_; + autoware_auto_planning_msgs::msg::Trajectory::ConstSharedPtr predicted_trajectory_; + control_interface::IsStopped::Message::ConstSharedPtr is_stopped_ptr_{nullptr}; + + // Core + std::unique_ptr collision_checker_; + std::shared_ptr debug_ptr_; + + // Variables + State current_state_{State::DRIVE}; + vehicle_info_util::VehicleInfo vehicle_info_; + bool is_calling_set_stop_{false}; + bool is_stopped_by_node_{false}; + + // Callback + void onDynamicObjects(PredictedObjects::ConstSharedPtr msg); + void onReferenceTrajectory(const autoware_auto_planning_msgs::msg::Trajectory::SharedPtr msg); + void onPredictedTrajectory(const autoware_auto_planning_msgs::msg::Trajectory::SharedPtr msg); + void onOdom(const nav_msgs::msg::Odometry::SharedPtr msg); + void onAccel(const geometry_msgs::msg::AccelWithCovarianceStamped::SharedPtr msg); + void onIsStopped(const control_interface::IsStopped::Message::ConstSharedPtr msg); + + // Timer + rclcpp::TimerBase::SharedPtr timer_; + void initTimer(double period_s); + + // Functions + bool isDataReady(); + + bool isDataTimeout(); + + bool isThereStopPointOnReferenceTrajectory( + const geometry_msgs::msg::Pose & pose, const TrajectoryPoints & reference_trajectory_array); + + void onTimer(); + + void checkVehicleState(diagnostic_updater::DiagnosticStatusWrapper & stat); + + TrajectoryPoints trimTrajectoryFromSelfPose( + const TrajectoryPoints & input, const Pose & self_pose) const; + + void sendRequest(bool make_stop_vehicle); + + bool isItDiscretePoint( + const TrajectoryPoints & reference_trajectory, const TrajectoryPoint & collision_point) const; + + static Trajectory cutTrajectory(const Trajectory & trajectory, const double length); + + size_t insertStopPoint( + TrajectoryPoints & trajectory, const geometry_msgs::msg::Point collision_point); + + void extendTrajectoryPointsArray(TrajectoryPoints & trajectory); + + static std::pair calculateProjectedVelAndAcc( + const PredictedObject & object, const TrajectoryPoint & trajectory_point); + + void filterObstacles( + const Pose & ego_pose, const TrajectoryPoints & traj, const double dist_threshold, + const bool use_prediction, PredictedObjects & filtered_objects); + + // Parameters + CollisionCheckerParam collision_checker_param_; + NodeParam node_param_; + + // Diagnostic Updater + diagnostic_updater::Updater updater_; +}; +} // namespace autoware::motion::control::predicted_path_checker + +#endif // PREDICTED_PATH_CHECKER__PREDICTED_PATH_CHECKER_NODE_HPP_ diff --git a/control/predicted_path_checker/include/predicted_path_checker/utils.hpp b/control/predicted_path_checker/include/predicted_path_checker/utils.hpp new file mode 100644 index 0000000000000..75e90e624a17e --- /dev/null +++ b/control/predicted_path_checker/include/predicted_path_checker/utils.hpp @@ -0,0 +1,104 @@ +// Copyright 2023 LeoDrive A.Ş. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PREDICTED_PATH_CHECKER__UTILS_HPP_ +#define PREDICTED_PATH_CHECKER__UTILS_HPP_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace utils +{ + +using autoware_auto_perception_msgs::msg::PredictedObject; +using autoware_auto_perception_msgs::msg::PredictedObjects; +using autoware_auto_planning_msgs::msg::TrajectoryPoint; +using geometry_msgs::msg::Point; +using geometry_msgs::msg::Pose; +using geometry_msgs::msg::TransformStamped; +using std_msgs::msg::Header; +using tier4_autoware_utils::Point2d; +using tier4_autoware_utils::Polygon2d; +using vehicle_info_util::VehicleInfo; +using PointArray = std::vector; + +using TrajectoryPoints = std::vector; + +void appendPointToPolygon(Polygon2d & polygon, const geometry_msgs::msg::Point & geom_point); + +Polygon2d createOneStepPolygon( + const geometry_msgs::msg::Pose & base_step_pose, const geometry_msgs::msg::Pose & next_step_pose, + const vehicle_info_util::VehicleInfo & vehicle_info, const double expand_width); + +TrajectoryPoint calcInterpolatedPoint( + const TrajectoryPoints & trajectory, const geometry_msgs::msg::Point & target_point, + const size_t segment_idx, const bool use_zero_order_hold_for_twist); + +std::pair findStopPoint( + TrajectoryPoints & predicted_trajectory_array, const size_t collision_idx, + const double stop_margin, vehicle_info_util::VehicleInfo & vehicle_info); + +bool isInBrakeDistance( + const TrajectoryPoints & trajectory, const size_t stop_idx, const double relative_velocity, + const double relative_acceleration, const double max_deceleration, const double delay_time_sec); + +TrajectoryPoint getExtendTrajectoryPoint( + const double extend_distance, const TrajectoryPoint & goal_point); + +bool intersectsInZAxis(const PredictedObject & object, const double z_min, const double z_max); + +double getNearestPointAndDistanceForPredictedObject( + const PointArray & points, const Pose & base_pose, + geometry_msgs::msg::Point * nearest_collision_point); + +Polygon2d convertBoundingBoxObjectToGeometryPolygon( + const Pose & current_pose, const double & base_to_front, const double & base_to_rear, + const double & base_to_width); + +Polygon2d convertCylindricalObjectToGeometryPolygon( + const Pose & current_pose, const autoware_auto_perception_msgs::msg::Shape & obj_shape); + +Polygon2d convertPolygonObjectToGeometryPolygon( + const Pose & current_pose, const autoware_auto_perception_msgs::msg::Shape & obj_shape); + +Polygon2d convertObjToPolygon(const PredictedObject & obj); + +double calcObstacleMaxLength(const autoware_auto_perception_msgs::msg::Shape & shape); + +void getCurrentObjectPose( + PredictedObject & predicted_object, const rclcpp::Time & obj_base_time, + const rclcpp::Time & current_time); + +bool isFrontObstacle(const Pose & ego_pose, const geometry_msgs::msg::Point & obstacle_pos); +} // namespace utils + +#endif // PREDICTED_PATH_CHECKER__UTILS_HPP_ diff --git a/control/predicted_path_checker/launch/predicted_path_checker.launch.xml b/control/predicted_path_checker/launch/predicted_path_checker.launch.xml new file mode 100755 index 0000000000000..a35c11b80c1f7 --- /dev/null +++ b/control/predicted_path_checker/launch/predicted_path_checker.launch.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/control/predicted_path_checker/package.xml b/control/predicted_path_checker/package.xml new file mode 100644 index 0000000000000..35f971907696a --- /dev/null +++ b/control/predicted_path_checker/package.xml @@ -0,0 +1,47 @@ + + + + predicted_path_checker + 1.0.0 + The predicted_path_checker package + + Berkay Karaman + Apache 2.0 + Berkay Karaman + + ament_cmake + autoware_cmake + + autoware_auto_perception_msgs + autoware_auto_planning_msgs + boost + component_interface_specs + component_interface_utils + diagnostic_updater + eigen + geometry_msgs + motion_utils + nav_msgs + rclcpp + rclcpp_components + sensor_msgs + std_msgs + tf2 + tf2_eigen + tf2_geometry_msgs + tf2_ros + tier4_autoware_utils + tier4_control_msgs + tier4_external_api_msgs + tier4_planning_msgs + vehicle_info_util + + ament_cmake_ros + ament_lint_auto + autoware_lint_common + eigen + + + ament_cmake + + diff --git a/control/predicted_path_checker/src/predicted_path_checker_node/collision_checker.cpp b/control/predicted_path_checker/src/predicted_path_checker_node/collision_checker.cpp new file mode 100644 index 0000000000000..5e8d96805b832 --- /dev/null +++ b/control/predicted_path_checker/src/predicted_path_checker_node/collision_checker.cpp @@ -0,0 +1,231 @@ +// Copyright 2022 LeoDrive A.Ş. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "predicted_path_checker/collision_checker.hpp" + +#include +#include + +#include +#include +#include + +namespace autoware::motion::control::predicted_path_checker +{ +CollisionChecker::CollisionChecker( + rclcpp::Node * node, std::shared_ptr debug_ptr) +: debug_ptr_(std::move(debug_ptr)), + node_(node), + vehicle_info_(vehicle_info_util::VehicleInfoUtil(*node).getVehicleInfo()) +{ +} + +void CollisionChecker::setParam(const CollisionCheckerParam & param) +{ + param_ = param; +} + +boost::optional> +CollisionChecker::checkTrajectoryForCollision( + TrajectoryPoints & predicted_trajectory_array, PredictedObjects::ConstSharedPtr dynamic_objects) +{ + // It checks the collision, if there is a collision, it updates the predicted_trajectory_array and + // returns the index of the stop point. + // If there is no collision, it returns boost::none. + const auto now = node_->now(); + + updatePredictedObjectHistory(now); + if (dynamic_objects->objects.empty() && predicted_object_history_.empty()) { + return boost::none; + } + + for (size_t i = 0; i < predicted_trajectory_array.size() - 1; i++) { + // create one step circle center for vehicle + const auto & p_front = predicted_trajectory_array.at(i).pose; + const auto & p_back = predicted_trajectory_array.at(i + 1).pose; + const auto z_min = p_front.position.z; + const auto z_max = + p_front.position.z + vehicle_info_.max_height_offset_m + param_.z_axis_filtering_buffer; + + // create one-step polygon for vehicle + Polygon2d one_step_move_vehicle_polygon2d = + utils::createOneStepPolygon(p_front, p_back, vehicle_info_, param_.width_margin); + if (param_.enable_z_axis_obstacle_filtering) { + debug_ptr_->pushPolyhedron( + one_step_move_vehicle_polygon2d, z_min, z_max, PolygonType::Vehicle); + } else { + debug_ptr_->pushPolygon( + one_step_move_vehicle_polygon2d, p_front.position.z, PolygonType::Vehicle); + } + + // check obstacle history + auto found_collision_at_history = + checkObstacleHistory(p_front, one_step_move_vehicle_polygon2d, z_min, z_max); + + auto found_collision_at_dynamic_objects = + checkDynamicObjects(p_front, dynamic_objects, one_step_move_vehicle_polygon2d, z_min, z_max); + + if (found_collision_at_dynamic_objects || found_collision_at_history) { + double distance_to_current = std::numeric_limits::max(); + double distance_to_history = std::numeric_limits::max(); + if (found_collision_at_dynamic_objects) { + distance_to_current = tier4_autoware_utils::calcDistance2d( + p_front, found_collision_at_dynamic_objects.get().first); + } + if (found_collision_at_history) { + distance_to_history = + tier4_autoware_utils::calcDistance2d(p_front, found_collision_at_history.get().first); + } else { + predicted_object_history_.clear(); + } + + if (param_.enable_z_axis_obstacle_filtering) { + debug_ptr_->pushPolyhedron( + one_step_move_vehicle_polygon2d, z_min, z_max, PolygonType::Collision); + } else { + debug_ptr_->pushPolygon( + one_step_move_vehicle_polygon2d, p_front.position.z, PolygonType::Collision); + } + + if (distance_to_current > distance_to_history) { + debug_ptr_->pushObstaclePoint(found_collision_at_history->first, PointType::Stop); + return found_collision_at_history; + } + + predicted_object_history_.emplace_back( + now, found_collision_at_dynamic_objects.get().first, + found_collision_at_dynamic_objects.get().second); + debug_ptr_->pushObstaclePoint(found_collision_at_dynamic_objects->first, PointType::Stop); + return found_collision_at_dynamic_objects; + } + } + return boost::none; +} + +boost::optional> +CollisionChecker::checkObstacleHistory( + const Pose & base_pose, const Polygon2d & one_step_move_vehicle_polygon2d, const double z_min, + const double z_max) +{ + if (predicted_object_history_.empty()) { + return boost::none; + } + + std::vector> collision_points_in_history; + for (const auto & obj_history : predicted_object_history_) { + if (param_.enable_z_axis_obstacle_filtering) { + if (!utils::intersectsInZAxis(obj_history.object, z_min, z_max)) { + continue; + } + } + const auto & point = obj_history.point; + const Point2d point2d(point.x, point.y); + if (bg::within(point2d, one_step_move_vehicle_polygon2d)) { + collision_points_in_history.emplace_back(point, obj_history.object); + } + } + if (!collision_points_in_history.empty()) { + double min_norm = 0.0; + bool is_init = false; + std::pair nearest_collision_object_with_point; + for (const auto & p : collision_points_in_history) { + double norm = tier4_autoware_utils::calcDistance2d(p.first, base_pose); + if (norm < min_norm || !is_init) { + min_norm = norm; + nearest_collision_object_with_point = p; + is_init = true; + } + } + return boost::make_optional(nearest_collision_object_with_point); + } + return boost::none; +} + +boost::optional> +CollisionChecker::checkDynamicObjects( + const Pose & base_pose, PredictedObjects::ConstSharedPtr dynamic_objects, + const Polygon2d & one_step_move_vehicle_polygon2d, const double z_min, const double z_max) +{ + if (dynamic_objects->objects.empty()) { + return boost::none; + } + double min_norm_collision_norm = 0.0; + bool is_init = false; + size_t nearest_collision_object_index = 0; + geometry_msgs::msg::Point nearest_collision_point; + + for (size_t i = 0; i < dynamic_objects->objects.size(); ++i) { + const auto & obj = dynamic_objects->objects.at(i); + if (param_.enable_z_axis_obstacle_filtering) { + if (!utils::intersectsInZAxis(obj, z_min, z_max)) { + continue; + } + } + const auto object_polygon = utils::convertObjToPolygon(obj); + if (object_polygon.outer().empty()) { + // unsupported type + continue; + } + + const auto found_collision_points = + bg::intersects(one_step_move_vehicle_polygon2d, object_polygon); + + if (found_collision_points) { + std::vector collision_points; + PointArray collision_point_array; + bg::intersection(one_step_move_vehicle_polygon2d, object_polygon, collision_points); + for (const auto & point : collision_points) { + geometry_msgs::msg::Point p; + p.x = point.x(); + p.y = point.y(); + collision_point_array.push_back(p); + } + + // Also check the corner points + + for (const auto & point : object_polygon.outer()) { + if (bg::within(point, one_step_move_vehicle_polygon2d)) { + geometry_msgs::msg::Point p; + p.x = point.x(); + p.y = point.y(); + collision_point_array.push_back(p); + } + } + geometry_msgs::msg::Point nearest_collision_point_tmp; + + double norm = utils::getNearestPointAndDistanceForPredictedObject( + collision_point_array, base_pose, &nearest_collision_point_tmp); + if (norm < min_norm_collision_norm || !is_init) { + min_norm_collision_norm = norm; + nearest_collision_point = nearest_collision_point_tmp; + is_init = true; + nearest_collision_object_index = i; + } + } + } + if (is_init) { + const auto & obj = dynamic_objects->objects.at(nearest_collision_object_index); + const auto obstacle_polygon = utils::convertObjToPolygon(obj); + if (param_.enable_z_axis_obstacle_filtering) { + debug_ptr_->pushPolyhedron(obstacle_polygon, z_min, z_max, PolygonType::Collision); + } else { + debug_ptr_->pushPolygon( + obstacle_polygon, obj.kinematics.initial_pose_with_covariance.pose.position.z, + PolygonType::Collision); + } + return boost::make_optional(std::make_pair(nearest_collision_point, obj)); + } + return boost::none; +} +} // namespace autoware::motion::control::predicted_path_checker diff --git a/control/predicted_path_checker/src/predicted_path_checker_node/debug_marker.cpp b/control/predicted_path_checker/src/predicted_path_checker_node/debug_marker.cpp new file mode 100644 index 0000000000000..3fb7b69d531c0 --- /dev/null +++ b/control/predicted_path_checker/src/predicted_path_checker_node/debug_marker.cpp @@ -0,0 +1,329 @@ +// Copyright 2023 LeoDrive A.Ş. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "predicted_path_checker/debug_marker.hpp" + +#include +#include +#include +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif + +#include + +#include +#include + +using motion_utils::createDeletedStopVirtualWallMarker; +using motion_utils::createStopVirtualWallMarker; +using tier4_autoware_utils::appendMarkerArray; +using tier4_autoware_utils::calcOffsetPose; +using tier4_autoware_utils::createDefaultMarker; +using tier4_autoware_utils::createMarkerColor; +using tier4_autoware_utils::createMarkerOrientation; +using tier4_autoware_utils::createMarkerScale; +using tier4_autoware_utils::createPoint; + +namespace autoware::motion::control::predicted_path_checker +{ +PredictedPathCheckerDebugNode::PredictedPathCheckerDebugNode( + rclcpp::Node * node, const double base_link2front) +: node_(node), base_link2front_(base_link2front) +{ + virtual_wall_pub_ = + node->create_publisher("~/debug/virtual_wall", 1); + debug_viz_pub_ = + node_->create_publisher("~/debug/marker", 1); +} + +bool PredictedPathCheckerDebugNode::pushPolygon( + const tier4_autoware_utils::Polygon2d & polygon, const double z, const PolygonType & type) +{ + std::vector eigen_polygon; + for (const auto & point : polygon.outer()) { + Eigen::Vector3d eigen_point; + eigen_point << point.x(), point.y(), z; + eigen_polygon.push_back(eigen_point); + } + return pushPolygon(eigen_polygon, type); +} + +bool PredictedPathCheckerDebugNode::pushPolygon( + const std::vector & polygon, const PolygonType & type) +{ + switch (type) { + case PolygonType::Vehicle: + if (!polygon.empty()) { + vehicle_polygons_.push_back(polygon); + } + return true; + case PolygonType::Collision: + if (!polygon.empty()) { + collision_polygons_.push_back(polygon); + } + return true; + default: + return false; + } +} + +bool PredictedPathCheckerDebugNode::pushPolyhedron( + const tier4_autoware_utils::Polygon2d & polyhedron, const double z_min, const double z_max, + const PolygonType & type) +{ + std::vector eigen_polyhedron; + for (const auto & point : polyhedron.outer()) { + eigen_polyhedron.emplace_back(point.x(), point.y(), z_min); + eigen_polyhedron.emplace_back(point.x(), point.y(), z_max); + } + + return pushPolyhedron(eigen_polyhedron, type); +} + +bool PredictedPathCheckerDebugNode::pushPolyhedron( + const std::vector & polyhedron, const PolygonType & type) +{ + switch (type) { + case PolygonType::Vehicle: + if (!polyhedron.empty()) { + vehicle_polyhedrons_.push_back(polyhedron); + } + return true; + case PolygonType::Collision: + if (!polyhedron.empty()) { + collision_polyhedrons_.push_back(polyhedron); + } + return true; + default: + return false; + } +} + +bool PredictedPathCheckerDebugNode::pushPose( + const geometry_msgs::msg::Pose & pose, const PoseType & type) +{ + switch (type) { + case PoseType::Stop: + stop_pose_ptr_ = std::make_shared(pose); + return true; + case PoseType::Collision: + collision_pose_ptr_ = std::make_shared(pose); + return true; + default: + return false; + } +} + +bool PredictedPathCheckerDebugNode::pushObstaclePoint( + const geometry_msgs::msg::Point & obstacle_point, const PointType & type) +{ + switch (type) { + case PointType::Stop: + stop_obstacle_point_ptr_ = std::make_shared(obstacle_point); + return true; + default: + return false; + } +} + +void PredictedPathCheckerDebugNode::publish() +{ + /* publish debug marker for rviz */ + const auto virtual_wall_msg = makeVirtualWallMarker(); + virtual_wall_pub_->publish(virtual_wall_msg); + + /* publish debug marker for rviz */ + const auto visualization_msg = makeVisualizationMarker(); + debug_viz_pub_->publish(visualization_msg); + + /* reset variables */ + vehicle_polygons_.clear(); + collision_polygons_.clear(); + vehicle_polyhedrons_.clear(); + collision_polyhedrons_.clear(); + collision_pose_ptr_ = nullptr; + stop_pose_ptr_ = nullptr; + stop_obstacle_point_ptr_ = nullptr; +} + +visualization_msgs::msg::MarkerArray PredictedPathCheckerDebugNode::makeVirtualWallMarker() +{ + visualization_msgs::msg::MarkerArray msg; + rclcpp::Time current_time = node_->now(); + + if (stop_pose_ptr_ != nullptr) { + const auto p = calcOffsetPose(*stop_pose_ptr_, base_link2front_, 0.0, 0.0); + const auto markers = + createStopVirtualWallMarker(p, "obstacle on predicted path", current_time, 0); + appendMarkerArray(markers, &msg); + } else { + const auto markers = createDeletedStopVirtualWallMarker(current_time, 0); + appendMarkerArray(markers, &msg); + } + + if (collision_pose_ptr_ != nullptr) { + const auto markers = + createStopVirtualWallMarker(*collision_pose_ptr_, "collision_point", current_time, 1); + appendMarkerArray(markers, &msg); + } else { + const auto markers = createDeletedStopVirtualWallMarker(current_time, 1); + appendMarkerArray(markers, &msg); + } + + return msg; +} + +visualization_msgs::msg::MarkerArray PredictedPathCheckerDebugNode::makeVisualizationMarker() +{ + visualization_msgs::msg::MarkerArray msg; + rclcpp::Time current_time = node_->now(); + + // cube + if (!vehicle_polyhedrons_.empty()) { + auto marker = createDefaultMarker( + "map", current_time, "detection_cubes", 0, visualization_msgs::msg::Marker::LINE_LIST, + createMarkerScale(0.01, 0.0, 0.0), createMarkerColor(0.0, 1.0, 0.0, 0.999)); + + for (size_t i = 0; i < vehicle_polyhedrons_.size(); ++i) { + for (size_t j = 0; j < vehicle_polyhedrons_.at(i).size(); ++j) { + const auto & p = vehicle_polyhedrons_.at(i).at(j); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } + } + + for (size_t i = 0; i < vehicle_polyhedrons_.size(); ++i) { + for (size_t j = 0; j + 2 < vehicle_polyhedrons_.at(i).size(); ++j) { + const auto & p = vehicle_polyhedrons_.at(i).at(j); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + const auto & p1 = vehicle_polyhedrons_.at(i).at(j + 2); + marker.points.push_back(createPoint(p1.x(), p1.y(), p1.z())); + } + const auto & p = vehicle_polyhedrons_.at(i).at(1); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + const auto & p1 = vehicle_polyhedrons_.at(i).at(vehicle_polyhedrons_.at(i).size() - 1); + marker.points.push_back(createPoint(p1.x(), p1.y(), p1.z())); + const auto & p2 = vehicle_polyhedrons_.at(i).at(0); + marker.points.push_back(createPoint(p2.x(), p2.y(), p2.z())); + const auto & p3 = vehicle_polyhedrons_.at(i).at(vehicle_polyhedrons_.at(i).size() - 2); + marker.points.push_back(createPoint(p3.x(), p3.y(), p3.z())); + } + + msg.markers.push_back(marker); + } + + if (!collision_polyhedrons_.empty()) { + auto marker = createDefaultMarker( + "map", current_time, "collision_cubes", 0, visualization_msgs::msg::Marker::LINE_LIST, + createMarkerScale(0.05, 0.0, 0.0), createMarkerColor(1.0, 0.0, 0.0, 0.999)); + + for (size_t i = 0; i < collision_polyhedrons_.size(); ++i) { + for (size_t j = 0; j < collision_polyhedrons_.at(i).size(); ++j) { + const auto & p = collision_polyhedrons_.at(i).at(j); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } + } + + for (size_t i = 0; i < collision_polyhedrons_.size(); ++i) { + for (size_t j = 0; j + 2 < collision_polyhedrons_.at(i).size(); ++j) { + const auto & p = collision_polyhedrons_.at(i).at(j); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + const auto & p1 = collision_polyhedrons_.at(i).at(j + 2); + marker.points.push_back(createPoint(p1.x(), p1.y(), p1.z())); + } + const auto & p = collision_polyhedrons_.at(i).at(1); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + const auto & p1 = collision_polyhedrons_.at(i).at(collision_polyhedrons_.at(i).size() - 1); + marker.points.push_back(createPoint(p1.x(), p1.y(), p1.z())); + const auto & p2 = collision_polyhedrons_.at(i).at(0); + marker.points.push_back(createPoint(p2.x(), p2.y(), p2.z())); + const auto & p3 = collision_polyhedrons_.at(i).at(collision_polyhedrons_.at(i).size() - 2); + marker.points.push_back(createPoint(p3.x(), p3.y(), p3.z())); + } + + msg.markers.push_back(marker); + } + + // polygon + if (!vehicle_polygons_.empty()) { + auto marker = createDefaultMarker( + "map", current_time, "detection_polygons", 0, visualization_msgs::msg::Marker::LINE_LIST, + createMarkerScale(0.01, 0.0, 0.0), createMarkerColor(0.0, 1.0, 0.0, 0.999)); + + for (size_t i = 0; i < vehicle_polygons_.size(); ++i) { + for (size_t j = 0; j < vehicle_polygons_.at(i).size(); ++j) { + { + const auto & p = vehicle_polygons_.at(i).at(j); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } + if (j + 1 == vehicle_polygons_.at(i).size()) { + const auto & p = vehicle_polygons_.at(i).at(0); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } else { + const auto & p = vehicle_polygons_.at(i).at(j + 1); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } + } + } + msg.markers.push_back(marker); + } + + if (!collision_polygons_.empty()) { + auto marker = createDefaultMarker( + "map", current_time, "collision_polygons", 0, visualization_msgs::msg::Marker::LINE_LIST, + createMarkerScale(0.05, 0.0, 0.0), createMarkerColor(1.0, 0.0, 0.0, 0.999)); + + for (size_t i = 0; i < collision_polygons_.size(); ++i) { + for (size_t j = 0; j < collision_polygons_.at(i).size(); ++j) { + { + const auto & p = collision_polygons_.at(i).at(j); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } + if (j + 1 == collision_polygons_.at(i).size()) { + const auto & p = collision_polygons_.at(i).at(0); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } else { + const auto & p = collision_polygons_.at(i).at(j + 1); + marker.points.push_back(createPoint(p.x(), p.y(), p.z())); + } + } + } + msg.markers.push_back(marker); + } + + if (stop_obstacle_point_ptr_ != nullptr) { + auto marker = createDefaultMarker( + "map", current_time, "stop_obstacle_point", 0, visualization_msgs::msg::Marker::SPHERE, + createMarkerScale(0.25, 0.25, 0.25), createMarkerColor(1.0, 0.0, 0.0, 0.999)); + marker.pose.position = *stop_obstacle_point_ptr_; + msg.markers.push_back(marker); + } + + if (stop_obstacle_point_ptr_ != nullptr) { + auto marker = createDefaultMarker( + "map", current_time, "stop_obstacle_text", 0, + visualization_msgs::msg::Marker::TEXT_VIEW_FACING, createMarkerScale(0.0, 0.0, 1.0), + createMarkerColor(1.0, 1.0, 1.0, 0.999)); + marker.pose.position = *stop_obstacle_point_ptr_; + marker.pose.position.z += 2.0; + marker.text = "!"; + msg.markers.push_back(marker); + } + + return msg; +} + +} // namespace autoware::motion::control::predicted_path_checker diff --git a/control/predicted_path_checker/src/predicted_path_checker_node/predicted_path_checker_node.cpp b/control/predicted_path_checker/src/predicted_path_checker_node/predicted_path_checker_node.cpp new file mode 100644 index 0000000000000..0cf2548d69746 --- /dev/null +++ b/control/predicted_path_checker/src/predicted_path_checker_node/predicted_path_checker_node.cpp @@ -0,0 +1,585 @@ +// Copyright 2023 LeoDrive A.Ş. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "predicted_path_checker/predicted_path_checker_node.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace autoware::motion::control::predicted_path_checker +{ + +PredictedPathCheckerNode::PredictedPathCheckerNode(const rclcpp::NodeOptions & node_options) +: Node("predicted_path_checker_node", node_options), updater_(this) +{ + using std::placeholders::_1; + + const auto adaptor = component_interface_utils::NodeAdaptor(this); + group_cli_ = create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); + adaptor.init_cli(cli_set_stop_, group_cli_); + adaptor.init_sub(sub_stop_state_, this, &PredictedPathCheckerNode::onIsStopped); + vehicle_info_ = vehicle_info_util::VehicleInfoUtil(*this).getVehicleInfo(); + + // Node Parameter + node_param_.update_rate = declare_parameter("update_rate", 10.0); + node_param_.ego_nearest_dist_threshold = declare_parameter("ego_nearest_dist_threshold", 3.0); + node_param_.ego_nearest_yaw_threshold = declare_parameter("ego_nearest_yaw_threshold", 1.046); + node_param_.max_deceleration = declare_parameter("max_deceleration", 1.5); + node_param_.delay_time = declare_parameter("delay_time", 0.17); + node_param_.stop_margin = declare_parameter("stop_margin", 0.5); + node_param_.min_trajectory_check_length = declare_parameter("min_trajectory_check_length", 1.5); + node_param_.trajectory_check_time = declare_parameter("trajectory_check_time", 3.0); + node_param_.resample_interval = declare_parameter("resample_interval", 0.5); + node_param_.distinct_point_distance_threshold = + declare_parameter("distinct_point_distance_threshold", 0.3); + node_param_.distinct_point_yaw_threshold = declare_parameter("distinct_point_yaw_threshold", 5.0); + node_param_.filtering_distance_threshold = declare_parameter("filtering_distance_threshold", 1.5); + node_param_.use_object_prediction = declare_parameter("use_object_prediction", true); + + // Collision Checker Parameter + collision_checker_param_.width_margin = + declare_parameter("collision_checker_params.width_margin", 0.2); + collision_checker_param_.enable_z_axis_obstacle_filtering = + declare_parameter("collision_checker_params.enable_z_axis_obstacle_filtering", false); + collision_checker_param_.z_axis_filtering_buffer = + declare_parameter("collision_checker_params.z_axis_filtering_buffer", 0.3); + collision_checker_param_.chattering_threshold = + declare_parameter("collision_checker_params.chattering_threshold", 0.2); + + // Subscriber + self_pose_listener_ = std::make_shared(this); + + sub_dynamic_objects_ = create_subscription( + "~/input/objects", rclcpp::SensorDataQoS(), + std::bind(&PredictedPathCheckerNode::onDynamicObjects, this, _1)); + sub_reference_trajectory_ = create_subscription( + "~/input/reference_trajectory", 1, + std::bind(&PredictedPathCheckerNode::onReferenceTrajectory, this, _1)); + sub_predicted_trajectory_ = create_subscription( + "~/input/predicted_trajectory", 1, + std::bind(&PredictedPathCheckerNode::onPredictedTrajectory, this, _1)); + sub_odom_ = create_subscription( + "~/input/odometry", 1, std::bind(&PredictedPathCheckerNode::onOdom, this, _1)); + sub_accel_ = create_subscription( + "~/input/current_accel", rclcpp::QoS{1}, + std::bind(&PredictedPathCheckerNode::onAccel, this, _1)); + + debug_ptr_ = + std::make_shared(this, vehicle_info_.max_longitudinal_offset_m); + + // Core + + collision_checker_ = std::make_unique(this, debug_ptr_); + collision_checker_->setParam(collision_checker_param_); + + // Diagnostic Updater + updater_.setHardwareID("predicted_path_checker"); + updater_.add("predicted_path_checker", this, &PredictedPathCheckerNode::checkVehicleState); + + // Wait for first self pose + self_pose_listener_->waitForFirstPose(); + + // Timer + initTimer(1.0 / node_param_.update_rate); +} + +void PredictedPathCheckerNode::onDynamicObjects(const PredictedObjects::ConstSharedPtr msg) +{ + object_ptr_ = msg; +} + +void PredictedPathCheckerNode::onReferenceTrajectory( + const autoware_auto_planning_msgs::msg::Trajectory::SharedPtr msg) +{ + reference_trajectory_ = msg; +} + +void PredictedPathCheckerNode::onPredictedTrajectory( + const autoware_auto_planning_msgs::msg::Trajectory::SharedPtr msg) +{ + predicted_trajectory_ = msg; +} + +void PredictedPathCheckerNode::onOdom(const nav_msgs::msg::Odometry::SharedPtr msg) +{ + current_twist_ = std::make_shared(msg->twist.twist); +} + +void PredictedPathCheckerNode::onAccel( + const geometry_msgs::msg::AccelWithCovarianceStamped::SharedPtr msg) +{ + current_accel_ = msg; +} + +void PredictedPathCheckerNode::onIsStopped( + const control_interface::IsStopped::Message::ConstSharedPtr msg) +{ + is_stopped_ptr_ = msg; + + is_stopped_by_node_ = + is_stopped_ptr_->data && + std::find( + is_stopped_ptr_->requested_sources.begin(), is_stopped_ptr_->requested_sources.end(), + "predicted_path_checker") != is_stopped_ptr_->requested_sources.end(); +} + +void PredictedPathCheckerNode::initTimer(double period_s) +{ + const auto period_ns = + std::chrono::duration_cast(std::chrono::duration(period_s)); + timer_ = rclcpp::create_timer( + this, get_clock(), period_ns, std::bind(&PredictedPathCheckerNode::onTimer, this)); +} + +bool PredictedPathCheckerNode::isDataReady() +{ + if (!current_pose_) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for current_pose..."); + return false; + } + + if (!object_ptr_) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for dynamic objects msg..."); + return false; + } + + if (!reference_trajectory_) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, + "waiting for reference_trajectory msg..."); + return false; + } + + if (!predicted_trajectory_) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, + "waiting for predicted_trajectory msg..."); + return false; + } + + if (!current_twist_) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for current_twist msg..."); + return false; + } + + if (!current_accel_) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for current_accel msg..."); + return false; + } + + if (!is_stopped_ptr_) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for is_stopped msg..."); + return false; + } + + if (!cli_set_stop_->service_is_ready()) { + RCLCPP_INFO_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, "waiting for stop service..."); + return false; + } + + return true; +} + +bool PredictedPathCheckerNode::isDataTimeout() +{ + const auto now = this->now(); + + constexpr double th_pose_timeout = 1.0; + const auto pose_time_diff = rclcpp::Time(current_pose_->header.stamp).seconds() - now.seconds(); + if (pose_time_diff > th_pose_timeout) { + RCLCPP_WARN_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, "pose is timeout..."); + return true; + } + + return false; +} + +void PredictedPathCheckerNode::onTimer() +{ + current_pose_ = self_pose_listener_->getCurrentPose(); + + if (!isDataReady()) { + return; + } + + if (isDataTimeout()) { + return; + } + + // Cut trajectory + const auto cut_trajectory = cutTrajectory( + *predicted_trajectory_, std::max( + node_param_.min_trajectory_check_length, + current_twist_->linear.x * node_param_.trajectory_check_time)); + + // Convert to trajectory array + + TrajectoryPoints predicted_trajectory_array = motion_utils::convertToTrajectoryPointArray( + motion_utils::resampleTrajectory(cut_trajectory, node_param_.resample_interval)); + + // Filter the objects + + PredictedObjects filtered_objects; + filterObstacles( + current_pose_.get()->pose, predicted_trajectory_array, node_param_.filtering_distance_threshold, + node_param_.use_object_prediction, filtered_objects); + + PredictedObjects::ConstSharedPtr filtered_obj_ptr = + std::make_shared(filtered_objects); + + // Check collision + + const auto collision_checker_output = + collision_checker_->checkTrajectoryForCollision(predicted_trajectory_array, filtered_obj_ptr); + + if (!collision_checker_output) { + // There is no need to stop + if (is_stopped_by_node_) { + sendRequest(false); + } + current_state_ = State::DRIVE; + updater_.force_update(); + debug_ptr_->publish(); + + return; + } + + // Extend trajectory + + extendTrajectoryPointsArray(predicted_trajectory_array); + + // Insert collision and stop points + + const auto stop_idx = + insertStopPoint(predicted_trajectory_array, collision_checker_output.get().first); + + // Check ego vehicle is stopped or not + constexpr double th_stopped_velocity = 0.001; + const bool is_ego_vehicle_stopped = current_twist_->linear.x < th_stopped_velocity; + + // If ego vehicle is not stopped, check obstacle is in the brake distance + if (!is_ego_vehicle_stopped) { + // Calculate projected velocity and acceleration of the object on the trajectory point + + const auto projected_obj_vel_acc = calculateProjectedVelAndAcc( + collision_checker_output->second, predicted_trajectory_array.at(stop_idx)); + + // Calculate relative velocity and acceleration wrt the object + + const double relative_velocity = current_twist_->linear.x - projected_obj_vel_acc.first; + const double relative_acceleration = + current_accel_->accel.accel.linear.x - projected_obj_vel_acc.second; + + // Check if the vehicle is in the brake distance + + const bool is_in_brake_distance = utils::isInBrakeDistance( + predicted_trajectory_array, stop_idx, relative_velocity, relative_acceleration, + node_param_.max_deceleration, node_param_.delay_time); + + if (is_in_brake_distance) { + // Send emergency and stop request + current_state_ = State::EMERGENCY; + updater_.force_update(); + if (!is_stopped_by_node_) { + sendRequest(true); + } + debug_ptr_->publish(); + RCLCPP_ERROR_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, + "There is an obstacle in the brake distance. Sending emergency and stop request..."); + return; + } + } + + // If it is not in the brake distance, check if the collision point is discrete from the reference + // trajectory or not + + const auto reference_trajectory_array = + motion_utils::convertToTrajectoryPointArray(*reference_trajectory_); + + const auto is_discrete_point = + isItDiscretePoint(reference_trajectory_array, predicted_trajectory_array.at(stop_idx)); + + if (is_discrete_point) { + // Check reference trajectory has stop point or not + + const auto is_there_stop_in_ref_trajectory = isThereStopPointOnReferenceTrajectory( + predicted_trajectory_array.at(stop_idx).pose, reference_trajectory_array); + + if (!is_there_stop_in_ref_trajectory) { + // Send stop + if (!is_stopped_by_node_) { + sendRequest(true); + } + current_state_ = State::STOP; + updater_.force_update(); + + RCLCPP_WARN_THROTTLE( + this->get_logger(), *this->get_clock(), 5000 /* ms */, + "There is an obstacle on predicted path. Sending stop request..."); + + debug_ptr_->publish(); + return; + } + } + + // If it is not discrete point, planning should handle it. Send drive. + current_state_ = State::DRIVE; + updater_.force_update(); + + if (is_stopped_by_node_) { + sendRequest(false); + } + + debug_ptr_->publish(); +} + +TrajectoryPoints PredictedPathCheckerNode::trimTrajectoryFromSelfPose( + const TrajectoryPoints & input, const Pose & self_pose) const +{ + TrajectoryPoints output{}; + + const size_t min_distance_index = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( + input, self_pose, node_param_.ego_nearest_dist_threshold, + node_param_.ego_nearest_yaw_threshold) + + 1; + + for (size_t i = min_distance_index; i < input.size(); ++i) { + output.push_back(input.at(i)); + } + + return output; +} + +bool PredictedPathCheckerNode::isThereStopPointOnReferenceTrajectory( + const geometry_msgs::msg::Pose & pose, const TrajectoryPoints & reference_trajectory_array) +{ + const auto trimmed_reference_trajectory_array = + trimTrajectoryFromSelfPose(reference_trajectory_array, current_pose_.get()->pose); + + const auto nearest_stop_point_on_ref_trajectory = + motion_utils::findNearestIndex(trimmed_reference_trajectory_array, pose); + + const auto stop_point_on_trajectory = motion_utils::searchZeroVelocityIndex( + trimmed_reference_trajectory_array, 0, *nearest_stop_point_on_ref_trajectory); + + return !!stop_point_on_trajectory; +} + +void PredictedPathCheckerNode::checkVehicleState(diagnostic_updater::DiagnosticStatusWrapper & stat) +{ + int8_t level = diagnostic_msgs::msg::DiagnosticStatus::OK; + std::string msg = "OK"; + + if (current_state_ == State::EMERGENCY) { + level = diagnostic_msgs::msg::DiagnosticStatus::ERROR; + msg = "vehicle will collide with obstacles"; + } + if (current_state_ == State::STOP) { + level = diagnostic_msgs::msg::DiagnosticStatus::WARN; + msg = "vehicle will stop due to obstacle"; + } + + stat.summary(level, msg); +} + +void PredictedPathCheckerNode::sendRequest(bool make_stop_vehicle) +{ + if (!is_calling_set_stop_ && cli_set_stop_->service_is_ready()) { + const auto req = std::make_shared(); + req->stop = make_stop_vehicle; + req->request_source = "predicted_path_checker"; + is_calling_set_stop_ = true; + cli_set_stop_->async_send_request(req, [this](auto) { is_calling_set_stop_ = false; }); + } +} + +bool PredictedPathCheckerNode::isItDiscretePoint( + const TrajectoryPoints & reference_trajectory, const TrajectoryPoint & collision_point) const +{ + const auto nearest_segment = + motion_utils::findNearestSegmentIndex(reference_trajectory, collision_point.pose); + + const auto nearest_point = utils::calcInterpolatedPoint( + reference_trajectory, collision_point.pose.position, *nearest_segment, false); + + const auto distance = tier4_autoware_utils::calcDistance2d( + nearest_point.pose.position, collision_point.pose.position); + + const auto yaw_diff = + std::abs(tier4_autoware_utils::calcYawDeviation(nearest_point.pose, collision_point.pose)); + return distance >= node_param_.distinct_point_distance_threshold || + yaw_diff >= tier4_autoware_utils::deg2rad(node_param_.distinct_point_yaw_threshold); +} + +Trajectory PredictedPathCheckerNode::cutTrajectory( + const Trajectory & trajectory, const double length) +{ + Trajectory cut; + cut.header = trajectory.header; + if (trajectory.points.empty()) { + return cut; + } + double total_length = 0.0; + cut.points.push_back(trajectory.points.front()); + for (size_t i = 1; i < trajectory.points.size(); ++i) { + const auto & point = trajectory.points.at(i); + + const auto p1 = tier4_autoware_utils::fromMsg(cut.points.back().pose.position); + const auto p2 = tier4_autoware_utils::fromMsg(point.pose.position); + const auto points_distance = boost::geometry::distance(p1.to_2d(), p2.to_2d()); + + const auto remain_distance = length - total_length; + + // Over length + if (remain_distance <= 0.001) { + break; + } + + // Require interpolation + if (remain_distance <= points_distance) { + const Eigen::Vector3d p_interpolated = p1 + remain_distance * (p2 - p1).normalized(); + + TrajectoryPoint p; + p.pose.position.x = p_interpolated.x(); + p.pose.position.y = p_interpolated.y(); + p.pose.position.z = p_interpolated.z(); + p.pose.orientation = point.pose.orientation; + + cut.points.push_back(p); + break; + } + + cut.points.push_back(point); + total_length += points_distance; + } + motion_utils::removeOverlapPoints(cut.points); + + return cut; +} + +void PredictedPathCheckerNode::extendTrajectoryPointsArray(TrajectoryPoints & trajectory) +{ + // It extends the trajectory to the end of the footprint of the vehicle to get better distance to + // collision_point. + const double extend_distance = vehicle_info_.max_longitudinal_offset_m + node_param_.stop_margin; + const auto & goal_point = trajectory.back(); + const auto trajectory_point_extend = utils::getExtendTrajectoryPoint(extend_distance, goal_point); + trajectory.push_back(trajectory_point_extend); +} + +size_t PredictedPathCheckerNode::insertStopPoint( + TrajectoryPoints & trajectory, const geometry_msgs::msg::Point collision_point) +{ + const auto nearest_collision_segment = + motion_utils::findNearestSegmentIndex(trajectory, collision_point); + + const auto nearest_collision_point = + utils::calcInterpolatedPoint(trajectory, collision_point, nearest_collision_segment, true); + + const size_t collision_idx = nearest_collision_segment + 1; + + trajectory.insert(trajectory.begin() + static_cast(collision_idx), nearest_collision_point); + + const auto stop_point = + utils::findStopPoint(trajectory, collision_idx, node_param_.stop_margin, vehicle_info_); + + const size_t stop_idx = stop_point.first + 1; + trajectory.insert(trajectory.begin() + static_cast(stop_idx), stop_point.second); + + debug_ptr_->pushPose(stop_point.second.pose, PoseType::Stop); + return stop_idx; +} + +std::pair PredictedPathCheckerNode::calculateProjectedVelAndAcc( + const PredictedObject & object, const TrajectoryPoint & trajectory_point) +{ + const auto & orientation_obj = object.kinematics.initial_pose_with_covariance.pose.orientation; + const auto & orientation_stop_point = trajectory_point.pose.orientation; + const auto velocity_obj = object.kinematics.initial_twist_with_covariance.twist.linear.x; + const auto acceleration_obj = + object.kinematics.initial_acceleration_with_covariance.accel.linear.x; + const auto k = std::cos(tier4_autoware_utils::normalizeRadian( + tf2::getYaw(orientation_obj) - tf2::getYaw(orientation_stop_point))); + const auto projected_velocity = velocity_obj * k; + const auto projected_acceleration = acceleration_obj * k; + return std::make_pair(projected_velocity, projected_acceleration); +} + +void PredictedPathCheckerNode::filterObstacles( + const Pose & ego_pose, const TrajectoryPoints & traj, const double dist_threshold, + const bool use_prediction, PredictedObjects & filtered_objects) +{ + filtered_objects.header.frame_id = object_ptr_.get()->header.frame_id; + filtered_objects.header.stamp = this->now(); + + for (auto & object : object_ptr_.get()->objects) { + // Check is it in front of ego vehicle + if (!utils::isFrontObstacle( + ego_pose, object.kinematics.initial_pose_with_covariance.pose.position)) { + continue; + } + + // Check is it near to trajectory + const double max_length = utils::calcObstacleMaxLength(object.shape); + const size_t seg_idx = motion_utils::findNearestSegmentIndex( + traj, object.kinematics.initial_pose_with_covariance.pose.position); + const auto p_front = tier4_autoware_utils::getPoint(traj.at(seg_idx)); + const auto p_back = tier4_autoware_utils::getPoint(traj.at(seg_idx + 1)); + const auto & p_target = object.kinematics.initial_pose_with_covariance.pose.position; + const Eigen::Vector3d segment_vec{p_back.x - p_front.x, p_back.y - p_front.y, 0.0}; + const Eigen::Vector3d target_vec{p_target.x - p_front.x, p_target.y - p_front.y, 0.0}; + + if (seg_idx == traj.size() - 2) { + // Calculate longitudinal offset + const auto longitudinal_dist = std::abs(segment_vec.dot(target_vec) / segment_vec.norm()); + if ( + longitudinal_dist - max_length - vehicle_info_.max_longitudinal_offset_m - dist_threshold > + 0.0) { + continue; + } + } + const auto lateral_dist = std::abs(segment_vec.cross(target_vec)(2) / segment_vec.norm()); + if (lateral_dist - max_length - vehicle_info_.max_lateral_offset_m - dist_threshold > 0.0) { + continue; + } + PredictedObject filtered_object = object; + if (use_prediction) { + utils::getCurrentObjectPose(filtered_object, object_ptr_.get()->header.stamp, this->now()); + } + filtered_objects.objects.push_back(filtered_object); + } +} + +} // namespace autoware::motion::control::predicted_path_checker + +#include +RCLCPP_COMPONENTS_REGISTER_NODE( + autoware::motion::control::predicted_path_checker::PredictedPathCheckerNode) diff --git a/control/predicted_path_checker/src/predicted_path_checker_node/utils.cpp b/control/predicted_path_checker/src/predicted_path_checker_node/utils.cpp new file mode 100644 index 0000000000000..9cb095e908d41 --- /dev/null +++ b/control/predicted_path_checker/src/predicted_path_checker_node/utils.cpp @@ -0,0 +1,429 @@ +// Copyright 2023 LeoDrive A.Ş. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "predicted_path_checker/utils.hpp" + +#include +#include +#include + +namespace utils +{ + +using motion_utils::findFirstNearestIndexWithSoftConstraints; +using motion_utils::findFirstNearestSegmentIndexWithSoftConstraints; +using tier4_autoware_utils::calcDistance2d; +using tier4_autoware_utils::getRPY; + +// Utils Functions +Polygon2d createOneStepPolygon( + const geometry_msgs::msg::Pose & base_step_pose, const geometry_msgs::msg::Pose & next_step_pose, + const vehicle_info_util::VehicleInfo & vehicle_info, const double expand_width) +{ + Polygon2d polygon; + + const double longitudinal_offset = vehicle_info.max_longitudinal_offset_m; + const double width = vehicle_info.vehicle_width_m / 2.0 + expand_width; + const double rear_overhang = vehicle_info.rear_overhang_m; + + { // base step + appendPointToPolygon( + polygon, tier4_autoware_utils::calcOffsetPose(base_step_pose, longitudinal_offset, width, 0.0) + .position); + appendPointToPolygon( + polygon, + tier4_autoware_utils::calcOffsetPose(base_step_pose, longitudinal_offset, -width, 0.0) + .position); + appendPointToPolygon( + polygon, + tier4_autoware_utils::calcOffsetPose(base_step_pose, -rear_overhang, -width, 0.0).position); + appendPointToPolygon( + polygon, + tier4_autoware_utils::calcOffsetPose(base_step_pose, -rear_overhang, width, 0.0).position); + } + + { // next step + appendPointToPolygon( + polygon, tier4_autoware_utils::calcOffsetPose(next_step_pose, longitudinal_offset, width, 0.0) + .position); + appendPointToPolygon( + polygon, + tier4_autoware_utils::calcOffsetPose(next_step_pose, longitudinal_offset, -width, 0.0) + .position); + appendPointToPolygon( + polygon, + tier4_autoware_utils::calcOffsetPose(next_step_pose, -rear_overhang, -width, 0.0).position); + appendPointToPolygon( + polygon, + tier4_autoware_utils::calcOffsetPose(next_step_pose, -rear_overhang, width, 0.0).position); + } + + polygon = tier4_autoware_utils::isClockwise(polygon) + ? polygon + : tier4_autoware_utils::inverseClockwise(polygon); + + Polygon2d hull_polygon; + boost::geometry::convex_hull(polygon, hull_polygon); + boost::geometry::correct(hull_polygon); + + return hull_polygon; +} + +TrajectoryPoint calcInterpolatedPoint( + const TrajectoryPoints & trajectory, const geometry_msgs::msg::Point & target_point, + const size_t segment_idx, const bool use_zero_order_hold_for_twist) +{ + if (trajectory.empty()) { + TrajectoryPoint interpolated_point{}; + interpolated_point.pose.position = target_point; + return interpolated_point; + } else if (trajectory.size() == 1) { + return trajectory.front(); + } + + // Calculate interpolation ratio + const auto & curr_pt = trajectory.at(segment_idx); + const auto & next_pt = trajectory.at(segment_idx + 1); + const auto v1 = tier4_autoware_utils::point2tfVector(curr_pt, next_pt); + const auto v2 = tier4_autoware_utils::point2tfVector(curr_pt, target_point); + if (v1.length2() < 1e-3) { + return curr_pt; + } + + const double ratio = v1.dot(v2) / v1.length2(); + const double clamped_ratio = std::clamp(ratio, 0.0, 1.0); + + // Interpolate + TrajectoryPoint interpolated_point{}; + + // pose interpolation + interpolated_point.pose = + tier4_autoware_utils::calcInterpolatedPose(curr_pt, next_pt, clamped_ratio); + + // twist interpolation + if (use_zero_order_hold_for_twist) { + interpolated_point.longitudinal_velocity_mps = curr_pt.longitudinal_velocity_mps; + interpolated_point.lateral_velocity_mps = curr_pt.lateral_velocity_mps; + interpolated_point.acceleration_mps2 = curr_pt.acceleration_mps2; + } else { + interpolated_point.longitudinal_velocity_mps = interpolation::lerp( + curr_pt.longitudinal_velocity_mps, next_pt.longitudinal_velocity_mps, clamped_ratio); + interpolated_point.lateral_velocity_mps = interpolation::lerp( + curr_pt.lateral_velocity_mps, next_pt.lateral_velocity_mps, clamped_ratio); + interpolated_point.acceleration_mps2 = + interpolation::lerp(curr_pt.acceleration_mps2, next_pt.acceleration_mps2, clamped_ratio); + } + + // heading rate interpolation + interpolated_point.heading_rate_rps = + interpolation::lerp(curr_pt.heading_rate_rps, next_pt.heading_rate_rps, clamped_ratio); + + // wheel interpolation + interpolated_point.front_wheel_angle_rad = interpolation::lerp( + curr_pt.front_wheel_angle_rad, next_pt.front_wheel_angle_rad, clamped_ratio); + interpolated_point.rear_wheel_angle_rad = + interpolation::lerp(curr_pt.rear_wheel_angle_rad, next_pt.rear_wheel_angle_rad, clamped_ratio); + + // time interpolation + const double interpolated_time = interpolation::lerp( + rclcpp::Duration(curr_pt.time_from_start).seconds(), + rclcpp::Duration(next_pt.time_from_start).seconds(), clamped_ratio); + interpolated_point.time_from_start = rclcpp::Duration::from_seconds(interpolated_time); + + return interpolated_point; +} + +std::pair findStopPoint( + TrajectoryPoints & trajectory_array, const size_t collision_idx, const double stop_margin, + vehicle_info_util::VehicleInfo & vehicle_info) +{ + // It returns the stop point and segment of the point on trajectory. + + double desired_distance_base_link_to_collision = + vehicle_info.max_longitudinal_offset_m + stop_margin; + size_t stop_segment_idx = 0; + bool found_stop_point = false; + double distance_point_to_collision = 0.0; + + for (size_t i = collision_idx; i > 0; i--) { + distance_point_to_collision = + motion_utils::calcSignedArcLength(trajectory_array, i - 1, collision_idx); + if (distance_point_to_collision >= desired_distance_base_link_to_collision) { + stop_segment_idx = i - 1; + found_stop_point = true; + break; + } + } + if (found_stop_point) { + const auto & base_point = trajectory_array.at(stop_segment_idx); + const auto & next_point = trajectory_array.at(stop_segment_idx + 1); + + double ratio = (distance_point_to_collision - desired_distance_base_link_to_collision) / + (std::hypot( + base_point.pose.position.x - next_point.pose.position.x, + base_point.pose.position.y - next_point.pose.position.y)); + + geometry_msgs::msg::Pose interpolated_pose = + tier4_autoware_utils::calcInterpolatedPose(base_point.pose, next_point.pose, ratio, false); + TrajectoryPoint output; + output.set__pose(interpolated_pose); + return std::make_pair(stop_segment_idx, output); + } else { + // It means that there is no enough distance between vehicle and collision point. + // So, we insert a stop at the first point of the trajectory. + return std::make_pair(0, trajectory_array.front()); + } +} + +bool isInBrakeDistance( + const TrajectoryPoints & trajectory, const size_t stop_idx, const double relative_velocity, + const double relative_acceleration, const double max_deceleration, const double delay_time_sec) +{ + if (relative_velocity < 0.0) { + return false; + } + + const auto distance_to_obstacle = motion_utils::calcSignedArcLength( + trajectory, trajectory.front().pose.position, trajectory.at(stop_idx).pose.position); + + const double distance_in_delay = relative_velocity * delay_time_sec + + relative_acceleration * delay_time_sec * delay_time_sec * 0.5; + + const double velocity_after_delay = relative_velocity + relative_acceleration * delay_time_sec; + + const double time_to_stop = velocity_after_delay / std::abs(max_deceleration); + const double distance_after_delay = + velocity_after_delay * time_to_stop - 0.5 * abs(max_deceleration) * time_to_stop * time_to_stop; + const double brake_distance = distance_in_delay + distance_after_delay; + + return brake_distance > distance_to_obstacle; +} + +TrajectoryPoint getExtendTrajectoryPoint( + const double extend_distance, const TrajectoryPoint & goal_point) +{ + tf2::Transform map2goal; + tf2::fromMsg(goal_point.pose, map2goal); + tf2::Transform local_extend_point; + local_extend_point.setOrigin(tf2::Vector3(extend_distance, 0.0, 0.0)); + tf2::Quaternion q; + q.setRPY(0, 0, 0); + local_extend_point.setRotation(q); + const auto map2extend_point = map2goal * local_extend_point; + Pose extend_pose; + tf2::toMsg(map2extend_point, extend_pose); + TrajectoryPoint extend_trajectory_point; + extend_trajectory_point.pose = extend_pose; + extend_trajectory_point.longitudinal_velocity_mps = goal_point.longitudinal_velocity_mps; + extend_trajectory_point.lateral_velocity_mps = goal_point.lateral_velocity_mps; + extend_trajectory_point.acceleration_mps2 = goal_point.acceleration_mps2; + return extend_trajectory_point; +} + +bool intersectsInZAxis(const PredictedObject & object, const double z_min, const double z_max) +{ + const auto & obj_pose = object.kinematics.initial_pose_with_covariance; + const auto & obj_height = object.shape.dimensions.z; + return obj_pose.pose.position.z - obj_height / 2.0 <= z_max && + obj_pose.pose.position.z + obj_height / 2.0 >= z_min; +} + +double getNearestPointAndDistanceForPredictedObject( + const PointArray & points, const Pose & base_pose, + geometry_msgs::msg::Point * nearest_collision_point) +{ + double min_norm = 0.0; + bool is_init = false; + + for (const auto & p : points) { + double norm = tier4_autoware_utils::calcDistance2d(p, base_pose); + if (norm < min_norm || !is_init) { + min_norm = norm; + *nearest_collision_point = p; + is_init = true; + } + } + return min_norm; +} + +Polygon2d convertBoundingBoxObjectToGeometryPolygon( + const Pose & current_pose, const double & base_to_front, const double & base_to_rear, + const double & base_to_width) +{ + const auto mapped_point = [](const double & length_scalar, const double & width_scalar) { + tf2::Vector3 map; + map.setX(length_scalar); + map.setY(width_scalar); + map.setZ(0.0); + map.setW(1.0); + return map; + }; + + // set vertices at map coordinate + const tf2::Vector3 p1_map = std::invoke(mapped_point, base_to_front, -base_to_width); + const tf2::Vector3 p2_map = std::invoke(mapped_point, base_to_front, base_to_width); + const tf2::Vector3 p3_map = std::invoke(mapped_point, -base_to_rear, base_to_width); + const tf2::Vector3 p4_map = std::invoke(mapped_point, -base_to_rear, -base_to_width); + + // transform vertices from map coordinate to object coordinate + tf2::Transform tf_map2obj; + tf2::fromMsg(current_pose, tf_map2obj); + const tf2::Vector3 p1_obj = tf_map2obj * p1_map; + const tf2::Vector3 p2_obj = tf_map2obj * p2_map; + const tf2::Vector3 p3_obj = tf_map2obj * p3_map; + const tf2::Vector3 p4_obj = tf_map2obj * p4_map; + + Polygon2d object_polygon; + object_polygon.outer().reserve(5); + object_polygon.outer().emplace_back(p1_obj.x(), p1_obj.y()); + object_polygon.outer().emplace_back(p2_obj.x(), p2_obj.y()); + object_polygon.outer().emplace_back(p3_obj.x(), p3_obj.y()); + object_polygon.outer().emplace_back(p4_obj.x(), p4_obj.y()); + + object_polygon.outer().push_back(object_polygon.outer().front()); + boost::geometry::correct(object_polygon); + + return object_polygon; +} + +Polygon2d convertCylindricalObjectToGeometryPolygon( + const Pose & current_pose, const autoware_auto_perception_msgs::msg::Shape & obj_shape) +{ + Polygon2d object_polygon; + + const double obj_x = current_pose.position.x; + const double obj_y = current_pose.position.y; + + constexpr int N = 20; + const double r = obj_shape.dimensions.x / 2; + object_polygon.outer().reserve(N + 1); + for (int i = 0; i < N; ++i) { + object_polygon.outer().emplace_back( + obj_x + r * std::cos(2.0 * M_PI / N * i), obj_y + r * std::sin(2.0 * M_PI / N * i)); + } + + object_polygon.outer().push_back(object_polygon.outer().front()); + boost::geometry::correct(object_polygon); + + return object_polygon; +} + +Polygon2d convertPolygonObjectToGeometryPolygon( + const Pose & current_pose, const autoware_auto_perception_msgs::msg::Shape & obj_shape) +{ + Polygon2d object_polygon; + tf2::Transform tf_map2obj; + fromMsg(current_pose, tf_map2obj); + const auto obj_points = obj_shape.footprint.points; + object_polygon.outer().reserve(obj_points.size() + 1); + for (const auto & obj_point : obj_points) { + tf2::Vector3 obj(obj_point.x, obj_point.y, obj_point.z); + tf2::Vector3 tf_obj = tf_map2obj * obj; + object_polygon.outer().emplace_back(tf_obj.x(), tf_obj.y()); + } + object_polygon.outer().push_back(object_polygon.outer().front()); + boost::geometry::correct(object_polygon); + + return object_polygon; +} + +void appendPointToPolygon(Polygon2d & polygon, const geometry_msgs::msg::Point & geom_point) +{ + Point2d point; + point.x() = geom_point.x; + point.y() = geom_point.y; + + boost::geometry::append(polygon.outer(), point); +} + +Polygon2d convertObjToPolygon(const PredictedObject & obj) +{ + Polygon2d object_polygon{}; + if (obj.shape.type == autoware_auto_perception_msgs::msg::Shape::CYLINDER) { + object_polygon = utils::convertCylindricalObjectToGeometryPolygon( + obj.kinematics.initial_pose_with_covariance.pose, obj.shape); + } else if (obj.shape.type == autoware_auto_perception_msgs::msg::Shape::BOUNDING_BOX) { + const double & length_m = obj.shape.dimensions.x / 2; + const double & width_m = obj.shape.dimensions.y / 2; + object_polygon = utils::convertBoundingBoxObjectToGeometryPolygon( + obj.kinematics.initial_pose_with_covariance.pose, length_m, length_m, width_m); + } else if (obj.shape.type == autoware_auto_perception_msgs::msg::Shape::POLYGON) { + object_polygon = utils::convertPolygonObjectToGeometryPolygon( + obj.kinematics.initial_pose_with_covariance.pose, obj.shape); + } else { + throw std::runtime_error("Unsupported shape type"); + } + return object_polygon; +} + +bool isFrontObstacle(const Pose & ego_pose, const geometry_msgs::msg::Point & obstacle_pos) +{ + const auto yaw = tier4_autoware_utils::getRPY(ego_pose).z; + const Eigen::Vector2d base_pose_vec(std::cos(yaw), std::sin(yaw)); + const Eigen::Vector2d obstacle_vec( + obstacle_pos.x - ego_pose.position.x, obstacle_pos.y - ego_pose.position.y); + + return base_pose_vec.dot(obstacle_vec) >= 0; +} + +double calcObstacleMaxLength(const autoware_auto_perception_msgs::msg::Shape & shape) +{ + if (shape.type == autoware_auto_perception_msgs::msg::Shape::BOUNDING_BOX) { + return std::hypot(shape.dimensions.x / 2.0, shape.dimensions.y / 2.0); + } else if (shape.type == autoware_auto_perception_msgs::msg::Shape::CYLINDER) { + return shape.dimensions.x / 2.0; + } else if (shape.type == autoware_auto_perception_msgs::msg::Shape::POLYGON) { + double max_length_to_point = 0.0; + for (const auto rel_point : shape.footprint.points) { + const double length_to_point = std::hypot(rel_point.x, rel_point.y); + if (max_length_to_point < length_to_point) { + max_length_to_point = length_to_point; + } + } + return max_length_to_point; + } + + throw std::logic_error("The shape type is not supported in obstacle_cruise_planner."); +} + +void getCurrentObjectPose( + PredictedObject & predicted_object, const rclcpp::Time & obj_base_time, + const rclcpp::Time & current_time) +{ + const double yaw = tier4_autoware_utils::getRPY( + predicted_object.kinematics.initial_pose_with_covariance.pose.orientation) + .z; + const double vx = predicted_object.kinematics.initial_twist_with_covariance.twist.linear.x; + const double ax = predicted_object.kinematics.initial_acceleration_with_covariance.accel.linear.x; + + const double dt = (current_time - obj_base_time).seconds(); + const double ds = vx * dt + 0.5 * ax * dt * dt; + const double delta_yaw = + predicted_object.kinematics.initial_twist_with_covariance.twist.angular.z * dt; + geometry_msgs::msg::Transform transform; + transform.translation = tier4_autoware_utils::createTranslation(ds, 0.0, 0.0); + transform.rotation = tier4_autoware_utils::createQuaternionFromRPY(0.0, 0.0, yaw); + + tf2::Transform tf_pose; + tf2::Transform tf_offset; + tf2::fromMsg(transform, tf_offset); + tf2::fromMsg(predicted_object.kinematics.initial_pose_with_covariance.pose, tf_pose); + tf2::toMsg(tf_pose * tf_offset, predicted_object.kinematics.initial_pose_with_covariance.pose); + predicted_object.kinematics.initial_twist_with_covariance.twist.linear.x += ax * dt; + predicted_object.kinematics.initial_pose_with_covariance.pose.orientation = + tier4_autoware_utils::createQuaternionFromRPY( + 0.0, 0.0, tier4_autoware_utils::normalizeRadian(yaw + delta_yaw)); +} + +} // namespace utils diff --git a/launch/tier4_control_launch/launch/control.launch.py b/launch/tier4_control_launch/launch/control.launch.py index 375819f8e7579..af5b82349f180 100644 --- a/launch/tier4_control_launch/launch/control.launch.py +++ b/launch/tier4_control_launch/launch/control.launch.py @@ -63,6 +63,8 @@ def launch_setup(context, *args, **kwargs): obstacle_collision_checker_param = yaml.safe_load(f)["/**"]["ros__parameters"] with open(LaunchConfiguration("aeb_param_path").perform(context), "r") as f: aeb_param = yaml.safe_load(f)["/**"]["ros__parameters"] + with open(LaunchConfiguration("predicted_path_checker_param_path").perform(context), "r") as f: + predicted_path_checker_param = yaml.safe_load(f)["/**"]["ros__parameters"] controller_component = ComposableNode( package="trajectory_follower_node", @@ -176,6 +178,34 @@ def launch_setup(context, *args, **kwargs): target_container="/control/control_container", ) + # autonomous emergency braking + predicted_path_checker = ComposableNode( + package="predicted_path_checker", + plugin="autoware::motion::control::predicted_path_checker::PredictedPathCheckerNode", + name="predicted_path_checker", + remappings=[ + ("~/input/objects", "/perception/object_recognition/objects"), + ("~/input/reference_trajectory", "/planning/scenario_planning/trajectory"), + ("~/input/current_accel", "/localization/acceleration"), + ("~/input/odometry", "/localization/kinematic_state"), + ( + "~/input/predicted_trajectory", + "/control/trajectory_follower/lateral/predicted_trajectory", + ), + ], + parameters=[ + vehicle_info_param, + predicted_path_checker_param, + ], + extra_arguments=[{"use_intra_process_comms": LaunchConfiguration("use_intra_process")}], + ) + + predicted_path_checker_loader = LoadComposableNodes( + condition=IfCondition(LaunchConfiguration("enable_predicted_path_checker")), + composable_node_descriptions=[predicted_path_checker], + target_container="/control/control_container", + ) + # vehicle cmd gate vehicle_cmd_gate_component = ComposableNode( package="vehicle_cmd_gate", @@ -338,6 +368,7 @@ def launch_setup(context, *args, **kwargs): external_cmd_converter_loader, obstacle_collision_checker_loader, autonomous_emergency_braking_loader, + predicted_path_checker_loader, ] ) @@ -372,6 +403,7 @@ def add_launch_arg(name: str, default_value=None, description=None): add_launch_arg("obstacle_collision_checker_param_path") add_launch_arg("external_cmd_selector_param_path") add_launch_arg("aeb_param_path") + add_launch_arg("predicted_path_checker_param_path") add_launch_arg("enable_autonomous_emergency_braking") add_launch_arg("check_external_emergency_heartbeat") From b32f8ef5f1791c268aab7265937e87f8eb23ad69 Mon Sep 17 00:00:00 2001 From: Berkay Karaman Date: Sun, 1 Oct 2023 17:06:10 +0300 Subject: [PATCH 378/547] feat(tier4_control_launch): add launch argument for predicted path checker (#5186) Signed-off-by: Berkay Karaman --- launch/tier4_control_launch/launch/control.launch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/launch/tier4_control_launch/launch/control.launch.py b/launch/tier4_control_launch/launch/control.launch.py index af5b82349f180..06718a2cf0ffa 100644 --- a/launch/tier4_control_launch/launch/control.launch.py +++ b/launch/tier4_control_launch/launch/control.launch.py @@ -404,6 +404,7 @@ def add_launch_arg(name: str, default_value=None, description=None): add_launch_arg("external_cmd_selector_param_path") add_launch_arg("aeb_param_path") add_launch_arg("predicted_path_checker_param_path") + add_launch_arg("enable_predicted_path_checker") add_launch_arg("enable_autonomous_emergency_braking") add_launch_arg("check_external_emergency_heartbeat") From c99c6b2fc7062e6e21edb80a1b96797f59d04efb Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:55:00 +0900 Subject: [PATCH 379/547] feat(lane_change): expand target lanes for object filtering (#5167) * feat(lane_change): expand target lanes for object filtering Signed-off-by: Muhammad Zulfaqar Azmi * use expanded target lanes in collision check Signed-off-by: Muhammad Zulfaqar Azmi --------- Signed-off-by: Muhammad Zulfaqar Azmi --- .../config/lane_change/lane_change.param.yaml | 5 +++++ .../lane_change/lane_change_module_data.hpp | 2 ++ .../utils/lane_change/utils.hpp | 19 +++++++++++++++++++ .../src/scene_module/lane_change/manager.cpp | 5 ++++- .../src/scene_module/lane_change/normal.cpp | 16 ++++++++++++---- .../src/utils/lane_change/utils.cpp | 9 +++++++++ 6 files changed, 51 insertions(+), 5 deletions(-) diff --git a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml index 583bdeaaf8dc5..fda7390abdb9e 100644 --- a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml +++ b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml @@ -38,6 +38,11 @@ longitudinal_distance_min_threshold: 3.0 longitudinal_velocity_delta_time: 0.8 + # lane expansion for object filtering + lane_expansion: + left_offset: 0.0 # [m] + right_offset: 0.0 # [m] + # lateral acceleration map lateral_acceleration: velocity: [0.0, 4.0, 10.0] diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index 27735777c27cc..0ba35635a9143 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -67,6 +67,8 @@ struct LaneChangeParameters bool check_objects_on_current_lanes{true}; bool check_objects_on_other_lanes{true}; bool use_all_predicted_path{false}; + double lane_expansion_left_offset{0.0}; + double lane_expansion_right_offset{0.0}; // regulatory elements bool regulate_on_crosswalk{false}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index 8a78d2665fa8f..b2592bc1b899b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -186,5 +186,24 @@ ExtendedPredictedObject transform( bool isCollidedPolygonsInLanelet( const std::vector & collided_polygons, const lanelet::ConstLanelets & lanes); + +/** + * @brief Generates expanded lanelets based on the given direction and offsets. + * + * Expands the provided lanelets in either the left or right direction based on + * the specified direction. If the direction is 'LEFT', the lanelets are expanded + * using the left_offset; if 'RIGHT', they are expanded using the right_offset. + * Otherwise, no expansion occurs. + * + * @param lanes The lanelets to be expanded. + * @param direction The direction of expansion: either LEFT or RIGHT. + * @param left_offset The offset value for left expansion. + * @param right_offset The offset value for right expansion. + * @return lanelet::ConstLanelets A collection of expanded lanelets. + */ +lanelet::ConstLanelets generateExpandedLanelets( + const lanelet::ConstLanelets & lanes, const Direction direction, const double left_offset, + const double right_offset); + } // namespace behavior_path_planner::utils::lane_change #endif // BEHAVIOR_PATH_PLANNER__UTILS__LANE_CHANGE__UTILS_HPP_ diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index a502c9d8e43ec..9c45f79d00263 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -77,7 +77,10 @@ LaneChangeModuleManager::LaneChangeModuleManager( getOrDeclareParameter(*node, parameter("check_objects_on_other_lanes")); p.use_all_predicted_path = getOrDeclareParameter(*node, parameter("use_all_predicted_path")); - + p.lane_expansion_left_offset = + getOrDeclareParameter(*node, parameter("safety_check.lane_expansion.left_offset")); + p.lane_expansion_right_offset = + getOrDeclareParameter(*node, parameter("safety_check.lane_expansion.right_offset")); // lane change regulations p.regulate_on_crosswalk = getOrDeclareParameter(*node, parameter("regulation.crosswalk")); p.regulate_on_intersection = diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 1bcd3e6b0cdcd..d8fa9cb5630e9 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -674,8 +674,11 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( const auto current_polygon = utils::lane_change::createPolygon(current_lanes, 0.0, std::numeric_limits::max()); - const auto target_polygon = - utils::lane_change::createPolygon(target_lanes, 0.0, std::numeric_limits::max()); + const auto expanded_target_lanes = utils::lane_change::generateExpandedLanelets( + target_lanes, direction_, lane_change_parameters_->lane_expansion_left_offset, + lane_change_parameters_->lane_expansion_right_offset); + const auto target_polygon = utils::lane_change::createPolygon( + expanded_target_lanes, 0.0, std::numeric_limits::max()); const auto dist_ego_to_current_lanes_center = lanelet::utils::getLateralDistanceToClosestLanelet(current_lanes, current_pose); std::vector> target_backward_polygons; @@ -1449,6 +1452,11 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( target_objects.other_lane.end()); } + const auto expanded_target_lanes = utils::lane_change::generateExpandedLanelets( + lane_change_path.info.target_lanes, direction_, + lane_change_parameters_->lane_expansion_left_offset, + lane_change_parameters_->lane_expansion_right_offset); + for (const auto & obj : collision_check_objects) { auto current_debug_data = marker_utils::createObjectDebug(obj); const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( @@ -1464,8 +1472,8 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( const auto collision_in_current_lanes = utils::lane_change::isCollidedPolygonsInLanelet( collided_polygons, lane_change_path.info.current_lanes); - const auto collision_in_target_lanes = utils::lane_change::isCollidedPolygonsInLanelet( - collided_polygons, lane_change_path.info.target_lanes); + const auto collision_in_target_lanes = + utils::lane_change::isCollidedPolygonsInLanelet(collided_polygons, expanded_target_lanes); if (!collision_in_current_lanes && !collision_in_target_lanes) { continue; diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index 0ae599a74e439..df609dce48512 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -1104,4 +1104,13 @@ bool isCollidedPolygonsInLanelet( return std::any_of(collided_polygons.begin(), collided_polygons.end(), is_in_lanes); } + +lanelet::ConstLanelets generateExpandedLanelets( + const lanelet::ConstLanelets & lanes, const Direction direction, const double left_offset, + const double right_offset) +{ + const auto left_extend_offset = (direction == Direction::LEFT) ? left_offset : 0.0; + const auto right_extend_offset = (direction == Direction::RIGHT) ? -right_offset : 0.0; + return lanelet::utils::getExpandedLanelets(lanes, left_extend_offset, right_extend_offset); +} } // namespace behavior_path_planner::utils::lane_change From de83f251d3e9bc9399530107df5ab4e938c3413f Mon Sep 17 00:00:00 2001 From: Tomohito ANDO Date: Mon, 2 Oct 2023 11:37:47 +0900 Subject: [PATCH 380/547] fix(crosswalk_traffic_light_estimator): change the shape of pedestrian light to CIRCLE (#5168) Signed-off-by: Tomohito Ando --- perception/crosswalk_traffic_light_estimator/src/node.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/perception/crosswalk_traffic_light_estimator/src/node.cpp b/perception/crosswalk_traffic_light_estimator/src/node.cpp index e45e3494dadce..870b8bc7c13f5 100644 --- a/perception/crosswalk_traffic_light_estimator/src/node.cpp +++ b/perception/crosswalk_traffic_light_estimator/src/node.cpp @@ -231,6 +231,7 @@ void CrosswalkTrafficLightEstimatorNode::setCrosswalkTrafficSignal( TrafficSignal output_traffic_signal; TrafficSignalElement output_traffic_signal_element; output_traffic_signal_element.color = color; + output_traffic_signal_element.shape = TrafficSignalElement::CIRCLE; output_traffic_signal_element.confidence = 1.0; output_traffic_signal.elements.push_back(output_traffic_signal_element); output_traffic_signal.traffic_signal_id = tl_reg_elem->id(); From 3c5a0b84d195a60f045c5e829024d085c77735a2 Mon Sep 17 00:00:00 2001 From: Tomohito ANDO Date: Mon, 2 Oct 2023 11:38:20 +0900 Subject: [PATCH 381/547] fix(crosswalk): check all elements in traffic signal (#5169) Signed-off-by: Tomohito Ando --- .../src/scene_crosswalk.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index 9ea03996c88dd..b99bd522b81f8 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -962,8 +962,10 @@ bool CrosswalkModule::isRedSignalForPedestrians() const continue; } - if (lights.front().color == TrafficSignalElement::RED) { - return true; + for (const auto & element : lights) { + if ( + element.color == TrafficSignalElement::RED && element.shape == TrafficSignalElement::CIRCLE) + return true; } } From a44830788f70b4e84ccdd874d56eea2e84d7ca1b Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Mon, 2 Oct 2023 12:06:24 +0900 Subject: [PATCH 382/547] feat(behavior_path_planner): safety check against dynamic objects after approval for start_planner (#5056) * add stop judgement after approval Signed-off-by: kyoichi-sugahara * update param Signed-off-by: kyoichi-sugahara * add status of has_stop_point Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../goal_planner/goal_planner.param.yaml | 10 ++-- .../start_planner/start_planner.param.yaml | 10 ++-- .../start_planner/start_planner_module.hpp | 3 ++ .../goal_planner/goal_planner_module.cpp | 7 +-- .../start_planner/start_planner_module.cpp | 51 ++++++++++++++++--- 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml index 0ca028e053fbc..5ffac49847ce6 100644 --- a/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml +++ b/planning/behavior_path_planner/config/goal_planner/goal_planner.param.yaml @@ -132,7 +132,7 @@ # detection range object_check_forward_distance: 10.0 object_check_backward_distance: 100.0 - ignore_object_velocity_threshold: 0.0 + ignore_object_velocity_threshold: 1.0 # ObjectTypesToCheck object_types_to_check: check_car: true @@ -164,11 +164,11 @@ check_all_predicted_path: true publish_debug_marker: false rss_params: - rear_vehicle_reaction_time: 1.0 + rear_vehicle_reaction_time: 2.0 rear_vehicle_safety_time_margin: 1.0 - lateral_distance_max_threshold: 1.0 - longitudinal_distance_min_threshold: 1.0 - longitudinal_velocity_delta_time: 1.0 + lateral_distance_max_threshold: 2.0 + longitudinal_distance_min_threshold: 3.0 + longitudinal_velocity_delta_time: 0.8 # hysteresis factor to expand/shrink polygon with the value hysteresis_factor_expand_rate: 1.0 # temporary diff --git a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml index 6760ec787bb03..4a42da9bc5ac3 100644 --- a/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml +++ b/planning/behavior_path_planner/config/start_planner/start_planner.param.yaml @@ -95,7 +95,7 @@ # detection range object_check_forward_distance: 10.0 object_check_backward_distance: 100.0 - ignore_object_velocity_threshold: 0.0 + ignore_object_velocity_threshold: 1.0 # ObjectTypesToCheck object_types_to_check: check_car: true @@ -127,11 +127,11 @@ check_all_predicted_path: true publish_debug_marker: false rss_params: - rear_vehicle_reaction_time: 1.0 + rear_vehicle_reaction_time: 2.0 rear_vehicle_safety_time_margin: 1.0 - lateral_distance_max_threshold: 1.0 - longitudinal_distance_min_threshold: 1.0 - longitudinal_velocity_delta_time: 1.0 + lateral_distance_max_threshold: 2.0 + longitudinal_distance_min_threshold: 3.0 + longitudinal_velocity_delta_time: 0.8 # hysteresis factor to expand/shrink polygon hysteresis_factor_expand_rate: 1.0 # temporary diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index 9bbd1203ee6b0..7ab404d99bb12 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -67,6 +67,9 @@ struct PullOutStatus false}; // after backward driving is complete, this is set to true (warning: this is set to // false at next cycle after backward driving is complete) Pose pull_out_start_pose{}; + bool prev_is_safe_dynamic_objects{false}; + std::shared_ptr prev_stop_path_after_approval{nullptr}; + bool has_stop_point{false}; PullOutStatus() {} }; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 5d9058890f351..574a1a38fc66a 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -365,12 +365,7 @@ bool GoalPlannerModule::isExecutionRequested() const bool GoalPlannerModule::isExecutionReady() const { - // TODO(Sugahara): should check safe or not but in the current flow, it is not possible. - if (status_.pull_over_path == nullptr) { - return true; - } - - if (status_.is_safe_static_objects && parameters_->safety_check_params.enable_safety_check) { + if (parameters_->safety_check_params.enable_safety_check && isWaitingApproval()) { if (!isSafePath()) { RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against dynamic objects"); return false; diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 819686c20ff54..092b91499252c 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -136,6 +136,12 @@ void StartPlannerModule::updateData() if (has_received_new_route) { status_ = PullOutStatus(); } + // check safety status after back finished + if (parameters_->safety_check_params.enable_safety_check && status_.back_finished) { + status_.is_safe_dynamic_objects = isSafePath(); + } else { + status_.is_safe_dynamic_objects = true; + } } bool StartPlannerModule::isExecutionRequested() const @@ -179,17 +185,19 @@ bool StartPlannerModule::isExecutionRequested() const bool StartPlannerModule::isExecutionReady() const { + // when is_safe_static_objects is false,the path is not generated and approval shouldn't be + // allowed if (!status_.is_safe_static_objects) { + RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against static objects"); return false; } - if (status_.pull_out_path.partial_paths.empty()) { - return true; - } - - if (status_.is_safe_static_objects && parameters_->safety_check_params.enable_safety_check) { + if ( + parameters_->safety_check_params.enable_safety_check && status_.back_finished && + isWaitingApproval()) { if (!isSafePath()) { RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against dynamic objects"); + stop_pose_ = planner_data_->self_odometry->pose.pose; return false; } } @@ -221,6 +229,7 @@ void StartPlannerModule::updateCurrentState() if (status_.backward_driving_complete) { current_state_ = ModuleStatus::SUCCESS; // for breaking loop } + print(magic_enum::enum_name(from), magic_enum::enum_name(current_state_)); } @@ -252,12 +261,42 @@ BehaviorModuleOutput StartPlannerModule::plan() } PathWithLaneId path; + + // Check if backward motion is finished if (status_.back_finished) { + // Increment path index if the current path is finished if (hasFinishedCurrentPath()) { RCLCPP_INFO(getLogger(), "Increment path index"); incrementPathIndex(); } - path = getCurrentPath(); + + if (!status_.is_safe_dynamic_objects && !isWaitingApproval() && !status_.has_stop_point) { + auto current_path = getCurrentPath(); + const auto stop_path = + behavior_path_planner::utils::start_goal_planner_common::generateFeasibleStopPath( + current_path, planner_data_, *stop_pose_, parameters_->maximum_deceleration_for_stop, + parameters_->maximum_jerk_for_stop); + + // Insert stop point in the path if needed + if (stop_path) { + RCLCPP_ERROR_THROTTLE( + getLogger(), *clock_, 5000, "Insert stop point in the path because of dynamic objects"); + path = *stop_path; + status_.prev_stop_path_after_approval = std::make_shared(path); + status_.has_stop_point = true; + } else { + path = current_path; + } + } else if (!isWaitingApproval() && status_.has_stop_point) { + // Delete stop point if conditions are met + if (status_.is_safe_dynamic_objects && isStopped()) { + status_.has_stop_point = false; + path = getCurrentPath(); + } + path = *status_.prev_stop_path_after_approval; + } else { + path = getCurrentPath(); + } } else { path = status_.backward_path; } From 14ef695b33325ac946438063278a16d1835c18f0 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 2 Oct 2023 12:11:33 +0900 Subject: [PATCH 383/547] fix(trajectory_visualizer): fix plotter error (#5181) fix(trajectory_visualizer): fix plotter Signed-off-by: Mamoru Sobue --- .../planning_debug_tools/scripts/trajectory_visualizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planning/planning_debug_tools/scripts/trajectory_visualizer.py b/planning/planning_debug_tools/scripts/trajectory_visualizer.py index 73e41b052cd90..10bd41c5c9c93 100755 --- a/planning/planning_debug_tools/scripts/trajectory_visualizer.py +++ b/planning/planning_debug_tools/scripts/trajectory_visualizer.py @@ -178,15 +178,15 @@ def __init__(self): # main process if PLOT_TYPE == "VEL_ACC_JERK": + self.setPlotTrajectory() self.ani = animation.FuncAnimation( self.fig, self.plotTrajectory, interval=100, blit=True ) - self.setPlotTrajectory() else: + self.setPlotTrajectoryVelocity() self.ani = animation.FuncAnimation( self.fig, self.plotTrajectoryVelocity, interval=100, blit=True ) - self.setPlotTrajectoryVelocity() plt.show() From 93b61184520d93ab8bcdb653f51d94a2de5bfbf9 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 2 Oct 2023 15:00:43 +0900 Subject: [PATCH 384/547] feat(intersection): ignore occlusion behind blocking vehicle (#5122) Signed-off-by: Mamoru Sobue --- .../README.md | 10 + .../docs/occlusion_grid.drawio.svg | 2616 +++++++++++++++++ .../src/debug.cpp | 6 + .../src/scene_intersection.cpp | 87 +- .../src/util.cpp | 19 +- .../src/util.hpp | 7 +- .../src/util_type.hpp | 1 + 7 files changed, 2714 insertions(+), 32 deletions(-) create mode 100644 planning/behavior_velocity_intersection_module/docs/occlusion_grid.drawio.svg diff --git a/planning/behavior_velocity_intersection_module/README.md b/planning/behavior_velocity_intersection_module/README.md index 5f26ef34c1186..e8a823a7e790e 100644 --- a/planning/behavior_velocity_intersection_module/README.md +++ b/planning/behavior_velocity_intersection_module/README.md @@ -94,6 +94,16 @@ If a stopline is associated with the intersection lane, that line is used as the To avoid a rapid braking, if deceleration and jerk more than a threshold (`behavior_velocity_planner.max_accel` and `behavior_velocity_planner.max_jerk`) are needed to stop just in front of the attention area, this module does not insert stopline after passing the default stopline position. +### Occlusion detection + +If the flag `occlusion.enable` is true this module checks if there is sufficient field of view (FOV) on the attention area up to `occlusion.occlusion_attention_area_length`. If FOV is not clear enough the ego vehicle will once stop at the _default stop line_ for `occlusion.before_creep_stop_time`, and then slowly creep toward _occlusion peeking stop line_ at the speed of `occlusion.occlusion_creep_velocity` if `occlusion.enable_creeping` is true. During the creeping if collision is detected this module inserts a stop line instantly, and if the FOV gets sufficiently clear the _intersection occlusion_ wall will disappear. If occlusion is cleared and no collision is detected the ego vehicle will pass the intersection. + +The occlusion is detected as the common area of occlusion attention area(which is partially the same as the normal attention area) and the unknown cells of the occupancy grid map. The occupancy grid map is denoised using morphology with the window size of `occlusion.denoise_kernel`. The occlusion attention area lanes are discretized to line strings and they are used to generate a grid whose each cell represents the distance from the ego path along the lane as shown below. + +![occlusion_detection](./docs/occlusion_grid.drawio.svg) + +If the nearest occlusion cell value is below the threshold, occlusion is detected. It is expected that the occlusion gets cleared as the vehicle approaches the occlusion peeking stop line. + ### Module Parameters | Parameter | Type | Description | diff --git a/planning/behavior_velocity_intersection_module/docs/occlusion_grid.drawio.svg b/planning/behavior_velocity_intersection_module/docs/occlusion_grid.drawio.svg new file mode 100644 index 0000000000000..fc09a4212070a --- /dev/null +++ b/planning/behavior_velocity_intersection_module/docs/occlusion_grid.drawio.svg @@ -0,0 +1,2616 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + default stop line + +
    +
    +
    +
    + default stop line +
    +
    + + + + + + +
    +
    +
    + + occlusion peeking line + +
    +
    +
    +
    + occlusion peeking line +
    +
    + + + + + + + + + + + + + + + +
    +
    +
    + + ego + +
    +
    +
    +
    + ego +
    +
    + + + + + + +
    +
    +
    + occlusion attention area +
    +
    +
    +
    + occlusion attent... +
    +
    + + + + + + + + + +
    +
    +
    + + + stopped vehicle on attention area +
    + (blocking vehicle) +
    +
    +
    +
    +
    +
    + stopped vehicle on attentio... +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + The cells behind blocking vehicles +
    + are not marked as occluded +
    +
    +
    +
    +
    +
    +
    + The cells behind blocking vehicles... +
    +
    + + + + + + +
    +
    +
    + + + mark the cells which is unknown and +
    + belong to attention area are +
    + marked as occluded +
    +
    +
    +
    +
    +
    +
    + mark the cells which is unknown and... +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 13 +
    +
    +
    +
    + 13 +
    +
    + + + + + +
    +
    +
    + 12 +
    +
    +
    +
    + 12 +
    +
    + + + + + +
    +
    +
    + 12 +
    +
    +
    +
    + 12 +
    +
    + + + + + +
    +
    +
    + 11 +
    +
    +
    +
    + 11 +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 12 +
    +
    +
    +
    + 12 +
    +
    + + + + + +
    +
    +
    + 11 +
    +
    +
    +
    + 11 +
    +
    + + + + + +
    +
    +
    + 11 +
    +
    +
    +
    + 11 +
    +
    + + + + + +
    +
    +
    + 10 +
    +
    +
    +
    + 10 +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 11 +
    +
    +
    +
    + 11 +
    +
    + + + + + +
    +
    +
    + 10 +
    +
    +
    +
    + 10 +
    +
    + + + + + +
    +
    +
    + 10 +
    +
    +
    +
    + 10 +
    +
    + + + + + +
    +
    +
    + 9 +
    +
    +
    +
    + 9 +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 10 +
    +
    +
    +
    + 10 +
    +
    + + + + + +
    +
    +
    + 9 +
    +
    +
    +
    + 9 +
    +
    + + + + + +
    +
    +
    + 9 +
    +
    +
    +
    + 9 +
    +
    + + + + +
    +
    +
    + 8 +
    +
    +
    +
    + 8 +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 9 +
    +
    +
    +
    + 9 +
    +
    + + + + + +
    +
    +
    + 8 +
    +
    +
    +
    + 8 +
    +
    + + + + +
    +
    +
    + 8 +
    +
    +
    +
    + 8 +
    +
    + + + + +
    +
    +
    + 7 +
    +
    +
    +
    + 7 +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 8 +
    +
    +
    +
    + 8 +
    +
    + + + + + +
    +
    +
    + 7 +
    +
    +
    +
    + 7 +
    +
    + + + + +
    +
    +
    + 7 +
    +
    +
    +
    + 7 +
    +
    + + + + +
    +
    +
    + 6 +
    +
    +
    +
    + 6 +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 7 +
    +
    +
    +
    + 7 +
    +
    + + + + +
    +
    +
    + 6 +
    +
    +
    +
    + 6 +
    +
    + + + + +
    +
    +
    + 6 +
    +
    +
    +
    + 6 +
    +
    + + + + +
    +
    +
    + 5 +
    +
    +
    +
    + 5 +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 6 +
    +
    +
    +
    + 6 +
    +
    + + + + +
    +
    +
    + 5 +
    +
    +
    +
    + 5 +
    +
    + + + + +
    +
    +
    + 5 +
    +
    +
    +
    + 5 +
    +
    + + + + +
    +
    +
    + 4 +
    +
    +
    +
    + 4 +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 5 +
    +
    +
    +
    + 5 +
    +
    + + + + +
    +
    +
    + 4 +
    +
    +
    +
    + 4 +
    +
    + + + + +
    +
    +
    + 4 +
    +
    +
    +
    + 4 +
    +
    + + + + +
    +
    +
    + 3 +
    +
    +
    +
    + 3 +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + 4 +
    +
    +
    +
    +
    +
    +
    + 4... +
    +
    + + + + +
    +
    +
    + 3 +
    +
    +
    +
    + 3 +
    +
    + + + + +
    +
    +
    + 3 +
    +
    +
    +
    + 3 +
    +
    + + + + +
    +
    +
    + 2 +
    +
    +
    +
    + 2 +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 3 +
    +
    +
    +
    + 3 +
    +
    + + + + +
    +
    +
    + 2 +
    +
    +
    +
    + 2 +
    +
    + + + + +
    +
    +
    + 2 +
    +
    +
    +
    + 2 +
    +
    + + + + +
    +
    +
    + 1 +
    +
    +
    +
    + 1 +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 2 +
    +
    +
    +
    + 2 +
    +
    + + + + +
    +
    +
    + 1 +
    +
    +
    +
    + 1 +
    +
    + + + + +
    +
    +
    + 1 +
    +
    +
    +
    + 1 +
    +
    + + + + +
    +
    +
    + 0 +
    +
    +
    +
    + 0 +
    +
    + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 1 +
    +
    +
    +
    + 1 +
    +
    + + + + +
    +
    +
    + 0 +
    +
    +
    +
    + 0 +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + 0 +
    +
    +
    +
    + 0 +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + nearest occlusion cell + +
    +
    +
    +
    + nearest occlusion... +
    +
    +
    + + + + Text is not SVG - cannot display + + +
    diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index af8972424f54a..fbb82d6ace175 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -230,6 +230,12 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( debug_data_.stuck_targets, "stuck_targets", module_id_, now, 0.99, 0.99, 0.2), &debug_marker_array, now); + appendMarkerArray( + debug::createObjectsMarkerArray( + debug_data_.blocking_attention_objects, "blocking_attention_objects", module_id_, now, 0.99, + 0.99, 0.6), + &debug_marker_array, now); + /* appendMarkerArray( createPoseMarkerArray( diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index ca1cbfdd0ccc7..89bb65f403095 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -929,28 +929,29 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( if (!occlusion_attention_divisions_) { occlusion_attention_divisions_ = util::generateDetectionLaneDivisions( occlusion_attention_lanelets, routing_graph_ptr, - planner_data_->occupancy_grid->info.resolution / std::sqrt(2.0)); + planner_data_->occupancy_grid->info.resolution); } const auto & occlusion_attention_divisions = occlusion_attention_divisions_.value(); const double occlusion_dist_thr = std::fabs( std::pow(planner_param_.occlusion.max_vehicle_velocity_for_rss, 2) / (2 * planner_param_.occlusion.min_vehicle_brake_for_rss)); - std::vector parked_attention_objects; + std::vector blocking_attention_objects; std::copy_if( target_objects.objects.begin(), target_objects.objects.end(), - std::back_inserter(parked_attention_objects), + std::back_inserter(blocking_attention_objects), [thresh = planner_param_.occlusion.ignore_parked_vehicle_speed_threshold](const auto & object) { return std::hypot( object.kinematics.initial_twist_with_covariance.twist.linear.x, object.kinematics.initial_twist_with_covariance.twist.linear.y) <= thresh; }); + debug_data_.blocking_attention_objects.objects = blocking_attention_objects; const bool is_occlusion_cleared = (enable_occlusion_detection_ && !occlusion_attention_lanelets.empty() && !is_prioritized) ? isOcclusionCleared( *planner_data_->occupancy_grid, occlusion_attention_area, adjacent_lanelets, first_attention_area, interpolated_path_info, occlusion_attention_divisions, - parked_attention_objects, occlusion_dist_thr) + blocking_attention_objects, occlusion_dist_thr) : true; occlusion_stop_state_machine_.setStateWithMarginTime( is_occlusion_cleared ? StateMachine::State::GO : StateMachine::STOP, @@ -1064,9 +1065,11 @@ autoware_auto_perception_msgs::msg::PredictedObjects IntersectionModule::filterT // check direction of objects const auto object_direction = util::getObjectPoseWithVelocityDirection(object.kinematics); const auto is_in_adjacent_lanelets = util::checkAngleForTargetLanelets( - object_direction, adjacent_lanelets, planner_param_.common.attention_area_angle_thr, + object_direction, object.kinematics.initial_twist_with_covariance.twist.linear.x, + adjacent_lanelets, planner_param_.common.attention_area_angle_thr, planner_param_.common.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin); + planner_param_.common.attention_area_margin, + planner_param_.occlusion.ignore_parked_vehicle_speed_threshold); if (is_in_adjacent_lanelets) { continue; } @@ -1078,17 +1081,19 @@ autoware_auto_perception_msgs::msg::PredictedObjects IntersectionModule::filterT if (is_in_intersection_area) { target_objects.objects.push_back(object); } else if (util::checkAngleForTargetLanelets( - object_direction, attention_area_lanelets, - planner_param_.common.attention_area_angle_thr, + object_direction, object.kinematics.initial_twist_with_covariance.twist.linear.x, + attention_area_lanelets, planner_param_.common.attention_area_angle_thr, planner_param_.common.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin)) { + planner_param_.common.attention_area_margin, + planner_param_.occlusion.ignore_parked_vehicle_speed_threshold)) { target_objects.objects.push_back(object); } } else if (util::checkAngleForTargetLanelets( - object_direction, attention_area_lanelets, - planner_param_.common.attention_area_angle_thr, + object_direction, object.kinematics.initial_twist_with_covariance.twist.linear.x, + attention_area_lanelets, planner_param_.common.attention_area_angle_thr, planner_param_.common.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin)) { + planner_param_.common.attention_area_margin, + planner_param_.occlusion.ignore_parked_vehicle_speed_threshold)) { // intersection_area is not available, use detection_area_with_margin as before target_objects.objects.push_back(object); } @@ -1240,7 +1245,7 @@ bool IntersectionModule::isOcclusionCleared( const lanelet::CompoundPolygon3d & first_attention_area, const util::InterpolatedPathInfo & interpolated_path_info, const std::vector & lane_divisions, - [[maybe_unused]] const std::vector & + const std::vector & blocking_attention_objects, const double occlusion_dist_thr) { @@ -1265,6 +1270,13 @@ bool IntersectionModule::isOcclusionCleared( const int height = occ_grid.info.height; const double resolution = occ_grid.info.resolution; const auto & origin = occ_grid.info.origin.position; + auto coord2index = [&](const double x, const double y) { + const int idx_x = (x - origin.x) / resolution; + const int idx_y = (y - origin.y) / resolution; + if (idx_x < 0 || idx_x >= width) return std::make_tuple(false, -1, -1); + if (idx_y < 0 || idx_y >= height) return std::make_tuple(false, -1, -1); + return std::make_tuple(true, idx_x, idx_y); + }; Polygon2d grid_poly; grid_poly.outer().emplace_back(origin.x, origin.y); @@ -1351,8 +1363,39 @@ bool IntersectionModule::isOcclusionCleared( cv::getStructuringElement(cv::MORPH_RECT, cv::Size(morph_size, morph_size))); // (3) occlusion mask + static constexpr unsigned char OCCLUDED = 255; + static constexpr unsigned char BLOCKED = 127; cv::Mat occlusion_mask(width, height, CV_8UC1, cv::Scalar(0)); cv::bitwise_and(attention_mask, unknown_mask, occlusion_mask); + // re-use attention_mask + attention_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); + // (3.1) draw all cells on attention_mask behind blocking vehicles as not occluded + std::vector> blocking_polygons; + for (const auto & blocking_attention_object : blocking_attention_objects) { + const Polygon2d obj_poly = tier4_autoware_utils::toPolygon2d(blocking_attention_object); + findCommonCvPolygons(obj_poly.outer(), blocking_polygons); + } + for (const auto & blocking_polygon : blocking_polygons) { + cv::fillPoly(attention_mask, blocking_polygon, cv::Scalar(BLOCKED), cv::LINE_AA); + } + for (const auto & lane_division : lane_divisions) { + const auto & divisions = lane_division.divisions; + for (const auto & division : divisions) { + bool blocking_vehicle_found = false; + for (const auto & point_it : division) { + const auto [valid, idx_x, idx_y] = coord2index(point_it.x(), point_it.y()); + if (!valid) continue; + if (blocking_vehicle_found) { + occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; + continue; + } + if (attention_mask.at(height - 1 - idx_y, idx_x) == BLOCKED) { + blocking_vehicle_found = true; + occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; + } + } + } + } // (4) extract occlusion polygons const auto & possible_object_bbox = planner_param_.occlusion.possible_object_bbox; @@ -1397,19 +1440,11 @@ bool IntersectionModule::isOcclusionCleared( } // (4.1) re-draw occluded cells using valid_contours occlusion_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); - for (unsigned i = 0; i < valid_contours.size(); ++i) { + for (const auto & valid_contour : valid_contours) { // NOTE: drawContour does not work well - cv::fillPoly(occlusion_mask, valid_contours[i], cv::Scalar(255), cv::LINE_AA); + cv::fillPoly(occlusion_mask, valid_contour, cv::Scalar(OCCLUDED), cv::LINE_AA); } - auto coord2index = [&](const double x, const double y) { - const int idx_x = (x - origin.x) / resolution; - const int idx_y = (y - origin.y) / resolution; - if (idx_x < 0 || idx_x >= width) return std::make_tuple(false, -1, -1); - if (idx_y < 0 || idx_y >= height) return std::make_tuple(false, -1, -1); - return std::make_tuple(true, idx_x, idx_y); - }; - // (5) find distance // (5.1) discretize path_ip with resolution for computational cost LineString2d path_linestring; @@ -1496,7 +1531,11 @@ bool IntersectionModule::isOcclusionCleared( const auto [valid, idx_x, idx_y] = coord2index(point_it->x(), point_it->y()); // TODO(Mamoru Sobue): add handling for blocking vehicles if (!valid) continue; - if (occlusion_mask.at(height - 1 - idx_y, idx_x) == 255) { + const auto pixel = occlusion_mask.at(height - 1 - idx_y, idx_x); + if (pixel == BLOCKED) { + break; + } + if (pixel == OCCLUDED) { if (acc_dist < min_dist) { min_dist = acc_dist; nearest_occlusion_point = { diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index fff1d08223e5f..37dc83cbdf761 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -1058,17 +1058,18 @@ Polygon2d generateStuckVehicleDetectAreaPolygon( } bool checkAngleForTargetLanelets( - const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, - const double detection_area_angle_thr, const bool consider_wrong_direction_vehicle, - const double margin) + const geometry_msgs::msg::Pose & pose, const double longitudinal_velocity, + const lanelet::ConstLanelets & target_lanelets, const double detection_area_angle_thr, + const bool consider_wrong_direction_vehicle, const double dist_margin, + const double parked_vehicle_speed_threshold) { for (const auto & ll : target_lanelets) { - if (!lanelet::utils::isInLanelet(pose, ll, margin)) { + if (!lanelet::utils::isInLanelet(pose, ll, dist_margin)) { continue; } const double ll_angle = lanelet::utils::getLaneletAngle(ll, pose.position); const double pose_angle = tf2::getYaw(pose.orientation); - const double angle_diff = tier4_autoware_utils::normalizeRadian(ll_angle - pose_angle); + const double angle_diff = tier4_autoware_utils::normalizeRadian(ll_angle - pose_angle, -M_PI); if (consider_wrong_direction_vehicle) { if (std::fabs(angle_diff) > 1.57 || std::fabs(angle_diff) < detection_area_angle_thr) { return true; @@ -1077,6 +1078,14 @@ bool checkAngleForTargetLanelets( if (std::fabs(angle_diff) < detection_area_angle_thr) { return true; } + // NOTE: sometimes parked vehicle direction is reversed even if its longitudinal velocity is + // positive + if ( + std::fabs(longitudinal_velocity) < parked_vehicle_speed_threshold && + (std::fabs(angle_diff) < detection_area_angle_thr || + (std::fabs(angle_diff + M_PI) < detection_area_angle_thr))) { + return true; + } } } return false; diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index f02362e3f803d..7ba894bd9d32a 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -136,9 +136,10 @@ Polygon2d generateStuckVehicleDetectAreaPolygon( const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist); bool checkAngleForTargetLanelets( - const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, - const double detection_area_angle_thr, const bool consider_wrong_direction_vehicle, - const double margin = 0.0); + const geometry_msgs::msg::Pose & pose, const double longitudinal_velocity, + const lanelet::ConstLanelets & target_lanelets, const double detection_area_angle_thr, + const bool consider_wrong_direction_vehicle, const double dist_margin, + const double parked_vehicle_speed_threshold); void cutPredictPathWithDuration( autoware_auto_perception_msgs::msg::PredictedObjects * objects_ptr, diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 4135884f03c74..7e5bff89485ed 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -50,6 +50,7 @@ struct DebugData std::vector occlusion_polygons; std::optional> nearest_occlusion_projection{std::nullopt}; + autoware_auto_perception_msgs::msg::PredictedObjects blocking_attention_objects; }; struct InterpolatedPathInfo From 89404e6070a43526f86a0bc88b231865ce2344af Mon Sep 17 00:00:00 2001 From: Yuxuan Liu <619684051@qq.com> Date: Mon, 2 Oct 2023 14:24:46 +0800 Subject: [PATCH 385/547] perf(system_monitor): fix program command line reading (#5191) * Fix program command line reading Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * fix spelling commandline->command_line Signed-off-by: Owen-Liuyuxuan --------- Signed-off-by: Owen-Liuyuxuan Co-authored-by: Owen-Liuyuxuan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../process_monitor/process_monitor.hpp | 8 +++++++ .../src/process_monitor/process_monitor.cpp | 24 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/system/system_monitor/include/system_monitor/process_monitor/process_monitor.hpp b/system/system_monitor/include/system_monitor/process_monitor/process_monitor.hpp index e114d58770883..42bd2a3ea9236 100644 --- a/system/system_monitor/include/system_monitor/process_monitor/process_monitor.hpp +++ b/system/system_monitor/include/system_monitor/process_monitor/process_monitor.hpp @@ -88,6 +88,14 @@ class ProcessMonitor : public rclcpp::Node */ void getHighMemoryProcesses(const std::string & output); + /** + * @brief get command line from process id + * @param [in] pid process id + * @param [out] command output command line + * @return true if success to get command line name + */ + bool getCommandLineFromPiD(const std::string & pid, std::string * command); + /** * @brief get top-rated processes * @param [in] tasks list of diagnostics tasks for high load procs diff --git a/system/system_monitor/src/process_monitor/process_monitor.cpp b/system/system_monitor/src/process_monitor/process_monitor.cpp index f8ee314e5a0ef..6f58339f2ff4b 100644 --- a/system/system_monitor/src/process_monitor/process_monitor.cpp +++ b/system/system_monitor/src/process_monitor/process_monitor.cpp @@ -414,6 +414,19 @@ void ProcessMonitor::getHighMemoryProcesses(const std::string & output) getTopratedProcesses(&memory_tasks_, &p2); } +bool ProcessMonitor::getCommandLineFromPiD(const std::string & pid, std::string * command) +{ + std::string commandLineFilePath = "/proc/" + pid + "/cmdline"; + std::ifstream commandFile(commandLineFilePath); + if (commandFile.is_open()) { + std::getline(commandFile, *command); + commandFile.close(); + return true; + } else { + return false; + } +} + void ProcessMonitor::getTopratedProcesses( std::vector> * tasks, bp::pipe * p) { @@ -462,7 +475,14 @@ void ProcessMonitor::getTopratedProcesses( info.virtualImage >> info.residentSize >> info.sharedMemSize >> info.processStatus >> info.cpuUsage >> info.memoryUsage >> info.cpuTime; - std::getline(stream, info.commandName); + std::string program_name; + std::getline(stream, program_name); + + bool flag_find_command_line = getCommandLineFromPiD(info.processId, &info.commandName); + + if (!flag_find_command_line) { + info.commandName = program_name; // if command line is not found, use program name instead + } tasks->at(index)->setDiagnosticsStatus(DiagStatus::OK, "OK"); tasks->at(index)->setProcessInformation(info); @@ -515,7 +535,7 @@ void ProcessMonitor::onTimer() std::ostringstream os; // Get processes - bp::child c("top -bcn1 -o %CPU -w 256", bp::std_out > is_out, bp::std_err > is_err); + bp::child c("top -bn1 -o %CPU -w 128", bp::std_out > is_out, bp::std_err > is_err); c.wait(); if (c.exit_code() != 0) { From c6b802fc2908592cb4f27ae12feb6ca42015a51e Mon Sep 17 00:00:00 2001 From: beyzanurkaya <32412808+beyzanurkaya@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:29:52 +0300 Subject: [PATCH 386/547] fix(behavior_path_planner): if ego leaves the current lane turn the signal on (#4348) * if ego leaves its lane due the avoidance, turn the signal on Signed-off-by: beyza * add right_bound check Signed-off-by: beyza * style(pre-commit): autofix --------- Signed-off-by: beyza Co-authored-by: beyza Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../avoidance/avoidance_module.cpp | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 83789818c456b..9117efcc447da 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -17,6 +17,7 @@ #include "behavior_path_planner/marker_utils/avoidance/debug.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/avoidance/utils.hpp" +#include "behavior_path_planner/utils/create_vehicle_footprint.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" @@ -2543,9 +2544,31 @@ TurnSignalInfo AvoidanceModule::calcTurnSignalInfo(const ShiftedPath & path) con turn_signal_info.turn_signal.command = TurnIndicatorsCommand::ENABLE_RIGHT; } } else { - turn_signal_info.turn_signal.command = TurnIndicatorsCommand::DISABLE; + const lanelet::ConstLanelets current_lanes = utils::getCurrentLanes(planner_data_); + const auto local_vehicle_footprint = + createVehicleFootprint(planner_data_->parameters.vehicle_info); + boost::geometry::model::ring shifted_vehicle_footprint; + for (const auto & cl : current_lanes) { + // get left and right bounds of current lane + const auto lane_left_bound = cl.leftBound2d().basicLineString(); + const auto lane_right_bound = cl.rightBound2d().basicLineString(); + for (size_t i = start_idx; i < end_idx; ++i) { + // transform vehicle footprint onto path points + shifted_vehicle_footprint = transformVector( + local_vehicle_footprint, + tier4_autoware_utils::pose2transform(path.path.points.at(i).point.pose)); + if ( + boost::geometry::intersects(lane_left_bound, shifted_vehicle_footprint) || + boost::geometry::intersects(lane_right_bound, shifted_vehicle_footprint)) { + if (segment_shift_length > 0.0) { + turn_signal_info.turn_signal.command = TurnIndicatorsCommand::ENABLE_LEFT; + } else { + turn_signal_info.turn_signal.command = TurnIndicatorsCommand::ENABLE_RIGHT; + } + } + } + } } - if (ego_front_to_shift_start > 0.0) { turn_signal_info.desired_start_point = planner_data_->self_odometry->pose.pose; } else { From de07fb8011cb9330b435e13dadac74c875f9bae2 Mon Sep 17 00:00:00 2001 From: Alexey Panferov <37497658+lexavtanke@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:04:32 +0300 Subject: [PATCH 387/547] fix(lidar_apollo_instance_segmentation): add arg data_path, update model path (#5139) Signed-off-by: Alexey Panferov --- .../launch/lidar_apollo_instance_segmentation.launch.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/perception/lidar_apollo_instance_segmentation/launch/lidar_apollo_instance_segmentation.launch.xml b/perception/lidar_apollo_instance_segmentation/launch/lidar_apollo_instance_segmentation.launch.xml index bceb928dd6692..7f5024929b531 100644 --- a/perception/lidar_apollo_instance_segmentation/launch/lidar_apollo_instance_segmentation.launch.xml +++ b/perception/lidar_apollo_instance_segmentation/launch/lidar_apollo_instance_segmentation.launch.xml @@ -7,7 +7,8 @@ - + + From bab14534a60e5741bd57b70979035bdc874c65cb Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 2 Oct 2023 21:01:09 +0900 Subject: [PATCH 388/547] feat(logger_level_reconfigure_plugin): use node interface and some cosmetic updates (#5204) * use node service Signed-off-by: Takamasa Horibe * enable yaml configuration Signed-off-by: Takamasa Horibe * update yaml loading Signed-off-by: Takamasa Horibe * make it scrollable Signed-off-by: Takamasa Horibe * change function order Signed-off-by: Takamasa Horibe * change color for level Signed-off-by: Takamasa Horibe * fix depend Signed-off-by: Takamasa Horibe * Update common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp Co-authored-by: Kosuke Takeuchi * Update common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp --------- Signed-off-by: Takamasa Horibe Co-authored-by: Kosuke Takeuchi --- .../CMakeLists.txt | 1 + .../config/logger_config.yaml | 59 +++++ .../logging_level_configure.hpp | 14 +- .../package.xml | 1 + .../src/logging_level_configure.cpp | 230 ++++++++---------- 5 files changed, 164 insertions(+), 141 deletions(-) create mode 100644 common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml diff --git a/common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt b/common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt index f86b6d4d2efdc..cc7da7e322d19 100644 --- a/common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt +++ b/common/tier4_logging_level_configure_rviz_plugin/CMakeLists.txt @@ -24,4 +24,5 @@ pluginlib_export_plugin_description_file(rviz_common plugins/plugin_description. ament_auto_package( INSTALL_TO_SHARE plugins + config ) diff --git a/common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml b/common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml new file mode 100644 index 0000000000000..da5cc757682c5 --- /dev/null +++ b/common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml @@ -0,0 +1,59 @@ +# logger_config.yaml + +# ============================================================ +# planning +# ============================================================ + +behavior_path_planner: + - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner + logger_name: planning.scenario_planning.lane_driving.behavior_planning.behavior_path_planner + - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner + logger_name: tier4_autoware_utils + +behavior_path_planner_avoidance: + - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner + logger_name: planning.scenario_planning.lane_driving.behavior_planning.behavior_path_planner.avoidance + +behavior_velocity_planner: + - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_velocity_planner + logger_name: planning.scenario_planning.lane_driving.behavior_planning.behavior_velocity_planner + - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_velocity_planner + logger_name: tier4_autoware_utils + +behavior_velocity_planner_intersection: + - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_velocity_planner + logger_name: planning.scenario_planning.lane_driving.behavior_planning.behavior_velocity_planner.intersection + +motion_obstacle_avoidance: + - node_name: /planning/scenario_planning/lane_driving/motion_planning/obstacle_avoidance_planner + logger_name: planning.scenario_planning.lane_driving.motion_planning.obstacle_avoidance_planner + - node_name: /planning/scenario_planning/lane_driving/motion_planning/obstacle_avoidance_planner + logger_name: tier4_autoware_utils + +motion_velocity_smoother: + - node_name: /planning/scenario_planning/motion_velocity_smoother + logger_name: planning.scenario_planning.motion_velocity_smoother + - node_name: /planning/scenario_planning/motion_velocity_smoother + logger_name: tier4_autoware_utils + +# ============================================================ +# control +# ============================================================ + +lateral_controller: + - node_name: /control/trajectory_follower/controller_node_exe + logger_name: control.trajectory_follower.controller_node_exe.lateral_controller + - node_name: /control/trajectory_follower/controller_node_exe + logger_name: tier4_autoware_utils + +longitudinal_controller: + - node_name: /control/trajectory_follower/controller_node_exe + logger_name: control.trajectory_follower.controller_node_exe.longitudinal_controller + - node_name: /control/trajectory_follower/controller_node_exe + logger_name: tier4_autoware_utils + +vehicle_cmd_gate: + - node_name: /control/vehicle_cmd_gate + logger_name: control.vehicle_cmd_gate + - node_name: /control/vehicle_cmd_gate + logger_name: tier4_autoware_utils diff --git a/common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp b/common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp index f513a7e04a248..4d9b81ffb86bf 100644 --- a/common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp +++ b/common/tier4_logging_level_configure_rviz_plugin/include/tier4_logging_level_configure_rviz_plugin/logging_level_configure.hpp @@ -48,24 +48,24 @@ class LoggingLevelConfigureRvizPlugin : public rviz_common::Panel QMap buttonGroups_; rclcpp::Node::SharedPtr raw_node_; - // logger_node_map_[target_name] = {container_name, logger_name} - std::map>> logger_node_map_; + // node_logger_map_[button_name] = {node_name, logger_name} + std::map>> node_logger_map_; - // client_map_[container_name] = service_client + // client_map_[node_name] = service_client std::unordered_map::SharedPtr> client_map_; - // button_map_[target_name][logging_level] = Q_button_pointer + // button_map_[button_name][logging_level] = Q_button_pointer std::unordered_map> button_map_; - QStringList getContainerList(); + QStringList getNodeList(); int getMaxModuleNameWidth(QLabel * containerLabel); void setLoggerNodeMap(); - void attachLoggingComponent(); private Q_SLOTS: void onButtonClick(QPushButton * button, const QString & name, const QString & level); - void updateButtonColors(const QString & target_module_name, QPushButton * active_button); + void updateButtonColors( + const QString & target_module_name, QPushButton * active_button, const QString & level); void changeLogLevel(const QString & container, const QString & level); }; diff --git a/common/tier4_logging_level_configure_rviz_plugin/package.xml b/common/tier4_logging_level_configure_rviz_plugin/package.xml index 4ba5bb1579e13..7849e6049a077 100644 --- a/common/tier4_logging_level_configure_rviz_plugin/package.xml +++ b/common/tier4_logging_level_configure_rviz_plugin/package.xml @@ -21,6 +21,7 @@ rviz_common rviz_default_plugins rviz_rendering + yaml-cpp ament_lint_auto autoware_lint_common diff --git a/common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp b/common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp index e645ff682d50e..b15c0f0f735fa 100644 --- a/common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp +++ b/common/tier4_logging_level_configure_rviz_plugin/src/logging_level_configure.cpp @@ -12,7 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "yaml-cpp/yaml.h" + #include +#include +#include #include #include @@ -26,59 +30,28 @@ LoggingLevelConfigureRvizPlugin::LoggingLevelConfigureRvizPlugin(QWidget * paren { } -// Calculate the maximum width among all target_module_name. -int LoggingLevelConfigureRvizPlugin::getMaxModuleNameWidth(QLabel * containerLabel) -{ - int max_width = 0; - QFontMetrics metrics(containerLabel->font()); - for (const auto & item : logger_node_map_) { - const auto & target_module_name = item.first; - int width = metrics.horizontalAdvance(target_module_name); - if (width > max_width) { - max_width = width; - } - } - return max_width; -} - -// create container list in logger_node_map_ without -QStringList LoggingLevelConfigureRvizPlugin::getContainerList() -{ - QStringList containers; - for (const auto & item : logger_node_map_) { - const auto & container_logger_vec = item.second; - for (const auto & container_logger_pair : container_logger_vec) { - if (!containers.contains(container_logger_pair.first)) { - containers.append(container_logger_pair.first); - } - } - } - return containers; -} - void LoggingLevelConfigureRvizPlugin::onInitialize() { - setLoggerNodeMap(); + raw_node_ = this->getDisplayContext()->getRosNodeAbstraction().lock()->get_raw_node(); - attachLoggingComponent(); + setLoggerNodeMap(); QVBoxLayout * layout = new QVBoxLayout; QStringList levels = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}; - QStringList loaded_container; constexpr int height = 20; - for (const auto & item : logger_node_map_) { - const auto & target_module_name = item.first; + for (const auto & item : node_logger_map_) { + const auto & target_node_name = item.first; QHBoxLayout * hLayout = new QHBoxLayout; - // Create a QLabel to display the container name. - QLabel * containerLabel = new QLabel(target_module_name); - containerLabel->setFixedHeight(height); // Set fixed height for the button - containerLabel->setFixedWidth(getMaxModuleNameWidth(containerLabel)); + // Create a QLabel to display the node name. + QLabel * label = new QLabel(target_node_name); + label->setFixedHeight(height); // Set fixed height for the button + label->setFixedWidth(getMaxModuleNameWidth(label)); - hLayout->addWidget(containerLabel); // Add the QLabel to the hLayout. + hLayout->addWidget(label); // Add the QLabel to the hLayout. QButtonGroup * group = new QButtonGroup(this); for (const QString & level : levels) { @@ -86,42 +59,66 @@ void LoggingLevelConfigureRvizPlugin::onInitialize() btn->setFixedHeight(height); // Set fixed height for the button hLayout->addWidget(btn); // Add each QPushButton to the hLayout. group->addButton(btn); - button_map_[target_module_name][level] = btn; - connect(btn, &QPushButton::clicked, this, [this, btn, target_module_name, level]() { - this->onButtonClick(btn, target_module_name, level); + button_map_[target_node_name][level] = btn; + connect(btn, &QPushButton::clicked, this, [this, btn, target_node_name, level]() { + this->onButtonClick(btn, target_node_name, level); }); } // Set the "INFO" button as checked by default and change its color. - updateButtonColors(target_module_name, button_map_[target_module_name]["INFO"]); + updateButtonColors(target_node_name, button_map_[target_node_name]["INFO"], "INFO"); - buttonGroups_[target_module_name] = group; + buttonGroups_[target_node_name] = group; layout->addLayout(hLayout); } - setLayout(layout); + // Create a QWidget to hold the layout. + QWidget * containerWidget = new QWidget; + containerWidget->setLayout(layout); + + // Create a QScrollArea to make the layout scrollable. + QScrollArea * scrollArea = new QScrollArea; + scrollArea->setWidget(containerWidget); + scrollArea->setWidgetResizable(true); + + // Set the QScrollArea as the layout of the main widget. + QVBoxLayout * mainLayout = new QVBoxLayout; + mainLayout->addWidget(scrollArea); + setLayout(mainLayout); // set up service clients - raw_node_ = this->getDisplayContext()->getRosNodeAbstraction().lock()->get_raw_node(); - const auto & containers = getContainerList(); - for (const QString & container : containers) { + const auto & nodes = getNodeList(); + for (const QString & node : nodes) { const auto client = raw_node_->create_client( - container.toStdString() + "/config_logger"); - client_map_[container] = client; + node.toStdString() + "/config_logger"); + client_map_[node] = client; } } -void LoggingLevelConfigureRvizPlugin::attachLoggingComponent() +// Calculate the maximum width among all target_module_name. +int LoggingLevelConfigureRvizPlugin::getMaxModuleNameWidth(QLabel * label) { - const auto & containers = getContainerList(); - for (const auto & container_name : containers) { - // Load the component for each container - QString command = "ros2 component load --node-namespace " + container_name + - " --node-name logging_configure " + container_name + - " logging_demo logging_demo::LoggerConfig"; - int result = system(qPrintable(command)); - std::cerr << "load logger in " << container_name.toStdString() << ": result = " << result - << std::endl; + int max_width = 0; + QFontMetrics metrics(label->font()); + for (const auto & item : node_logger_map_) { + const auto & target_module_name = item.first; + max_width = std::max(metrics.horizontalAdvance(target_module_name), max_width); } + return max_width; +} + +// create node list in node_logger_map_ without +QStringList LoggingLevelConfigureRvizPlugin::getNodeList() +{ + QStringList nodes; + for (const auto & item : node_logger_map_) { + const auto & node_logger_vec = item.second; + for (const auto & node_logger_pair : node_logger_vec) { + if (!nodes.contains(node_logger_pair.first)) { + nodes.append(node_logger_pair.first); + } + } + } + return nodes; } // Modify the signature of the onButtonClick function: @@ -135,40 +132,48 @@ void LoggingLevelConfigureRvizPlugin::onButtonClick( << std::string(future.get()->success ? "success!" : "failed...") << std::endl; }; - for (const auto & container_logger_map : logger_node_map_[target_module_name]) { - const auto container_node = container_logger_map.first; - const auto logger_name = container_logger_map.second; + for (const auto & node_logger_map : node_logger_map_[target_module_name]) { + const auto node_name = node_logger_map.first; + const auto logger_name = node_logger_map.second; const auto req = std::make_shared(); req->logger_name = logger_name.toStdString(); req->level = level.toStdString(); std::cerr << "logger level of " << req->logger_name << " is set to " << req->level << std::endl; - client_map_[container_node]->async_send_request(req, callback); + client_map_[node_name]->async_send_request(req, callback); } updateButtonColors( - target_module_name, button); // Modify updateButtonColors to accept QPushButton only. + target_module_name, button, level); // Modify updateButtonColors to accept QPushButton only. } } void LoggingLevelConfigureRvizPlugin::updateButtonColors( - const QString & target_module_name, QPushButton * active_button) + const QString & target_module_name, QPushButton * active_button, const QString & level) { - const QString LIGHT_GREEN = "rgb(181, 255, 20)"; - const QString LIGHT_GRAY = "rgb(211, 211, 211)"; + std::unordered_map colorMap = { + {"DEBUG", "rgb(181, 255, 20)"}, /* green */ + {"INFO", "rgb(200, 255, 255)"}, /* light blue */ + {"WARN", "rgb(255, 255, 0)"}, /* yellow */ + {"ERROR", "rgb(255, 0, 0)"}, /* red */ + {"FATAL", "rgb(139, 0, 0)"}, /* dark red */ + {"OFF", "rgb(211, 211, 211)"} /* gray */ + }; + const QString LIGHT_GRAY_TEXT = "rgb(180, 180, 180)"; + const QString color = colorMap.count(level) ? colorMap[level] : colorMap["OFF"]; + for (const auto & button : button_map_[target_module_name]) { if (button.second == active_button) { - button.second->setStyleSheet("background-color: " + LIGHT_GREEN + "; color: black"); + button.second->setStyleSheet("background-color: " + color + "; color: black"); } else { button.second->setStyleSheet( - "background-color: " + LIGHT_GRAY + "; color: " + LIGHT_GRAY_TEXT); + "background-color: " + colorMap["OFF"] + "; color: " + LIGHT_GRAY_TEXT); } } } - void LoggingLevelConfigureRvizPlugin::save(rviz_common::Config config) const { Panel::save(config); @@ -181,68 +186,25 @@ void LoggingLevelConfigureRvizPlugin::load(const rviz_common::Config & config) void LoggingLevelConfigureRvizPlugin::setLoggerNodeMap() { - // =============================================================================================== - // ====================================== Planning =============================================== - // =============================================================================================== - - QString behavior_planning_container = - "/planning/scenario_planning/lane_driving/behavior_planning/behavior_planning_container"; - QString motion_planning_container = - "/planning/scenario_planning/lane_driving/motion_planning/motion_planning_container"; - - // behavior_path_planner (all) - logger_node_map_["behavior_path_planner"] = { - {behavior_planning_container, - "planning.scenario_planning.lane_driving.behavior_planning.behavior_path_planner"}, - {behavior_planning_container, "tier4_autoware_utils"}}; - - // behavior_path_planner: avoidance - logger_node_map_["behavior_path_planner: avoidance"] = { - {behavior_planning_container, - "planning.scenario_planning.lane_driving.behavior_planning.behavior_path_planner." - "avoidance"}}; - - // behavior_velocity_planner (all) - logger_node_map_["behavior_velocity_planner"] = { - {behavior_planning_container, - "planning.scenario_planning.lane_driving.behavior_planning.behavior_velocity_planner"}, - {behavior_planning_container, "tier4_autoware_utils"}}; - - // behavior_velocity_planner: intersection - logger_node_map_["behavior_velocity_planner: intersection"] = { - {behavior_planning_container, - "planning.scenario_planning.lane_driving.behavior_planning.behavior_velocity_planner." - "intersection"}}; - - // obstacle_avoidance_planner - logger_node_map_["motion: obstacle_avoidance"] = { - {motion_planning_container, - "planning.scenario_planning.lane_driving.motion_planning.obstacle_avoidance_planner"}, - {motion_planning_container, "tier4_autoware_utils"}}; - - // motion_velocity_smoother - QString container = "/planning/scenario_planning/motion_velocity_smoother_container"; - logger_node_map_["motion: velocity_smoother"] = { - {container, "planning.scenario_planning.motion_velocity_smoother"}, - {container, "tier4_autoware_utils"}}; - - // =============================================================================================== - // ======================================= Control =============================================== - // =============================================================================================== - - QString control_container = "/control/control_container"; - - // lateral_controller - logger_node_map_["lateral_controller"] = { - {control_container, "control.trajectory_follower.controller_node_exe.lateral_controller"}, - {control_container, "tier4_autoware_utils"}, - }; - - // longitudinal_controller - logger_node_map_["longitudinal_controller"] = { - {control_container, "control.trajectory_follower.controller_node_exe.longitudinal_controller"}, - {control_container, "tier4_autoware_utils"}, - }; + const std::string package_share_directory = + ament_index_cpp::get_package_share_directory("tier4_logging_level_configure_rviz_plugin"); + const std::string default_config_path = package_share_directory + "/config/logger_config.yaml"; + + const auto filename = + raw_node_->declare_parameter("config_filename", default_config_path); + RCLCPP_INFO(raw_node_->get_logger(), "load config file: %s", filename.c_str()); + + YAML::Node config = YAML::LoadFile(filename); + + for (YAML::const_iterator it = config.begin(); it != config.end(); ++it) { + const auto key = QString::fromStdString(it->first.as()); + const YAML::Node values = it->second; + for (size_t i = 0; i < values.size(); i++) { + const auto node_name = QString::fromStdString(values[i]["node_name"].as()); + const auto logger_name = QString::fromStdString(values[i]["logger_name"].as()); + node_logger_map_[key].push_back({node_name, logger_name}); + } + } } } // namespace rviz_plugin From e7bc187a9f4f86cbb7712e5fa9086a70d5b4652f Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 3 Oct 2023 06:57:08 +0900 Subject: [PATCH 389/547] fix(map_based_prediction): filter by distance for opposite lanes as well (#5195) * fix(map_based_prediction): filter by distance for opposite lanes as well Signed-off-by: kminoda * update Signed-off-by: kminoda --------- Signed-off-by: kminoda --- .../map_based_prediction/map_based_prediction_node.hpp | 3 +-- .../map_based_prediction/src/map_based_prediction_node.cpp | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp index 8ef6d628aa2f6..3d01ab96e9b62 100644 --- a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp @@ -176,8 +176,7 @@ class MapBasedPredictionNode : public rclcpp::Node LaneletsData getCurrentLanelets(const TrackedObject & object); bool checkCloseLaneletCondition( - const std::pair & lanelet, const TrackedObject & object, - const bool check_distance = true); + const std::pair & lanelet, const TrackedObject & object); float calculateLocalLikelihood( const lanelet::Lanelet & current_lanelet, const TrackedObject & object) const; void updateObjectData(TrackedObject & object); diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 6a414c26b8806..58356f4f96ed6 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -1251,7 +1251,7 @@ LaneletsData MapBasedPredictionNode::getCurrentLanelets(const TrackedObject & ob for (const auto & lanelet : surrounding_opposite_lanelets) { // Check if the close lanelets meet the necessary condition for start lanelets // except for distance checking - if (!checkCloseLaneletCondition(lanelet, object, false)) { + if (!checkCloseLaneletCondition(lanelet, object)) { continue; } @@ -1271,8 +1271,7 @@ LaneletsData MapBasedPredictionNode::getCurrentLanelets(const TrackedObject & ob } bool MapBasedPredictionNode::checkCloseLaneletCondition( - const std::pair & lanelet, const TrackedObject & object, - const bool check_distance) + const std::pair & lanelet, const TrackedObject & object) { // Step1. If we only have one point in the centerline, we will ignore the lanelet if (lanelet.second.centerline().size() <= 1) { @@ -1307,7 +1306,7 @@ bool MapBasedPredictionNode::checkCloseLaneletCondition( const double object_vel = object.kinematics.twist_with_covariance.twist.linear.x; const bool is_yaw_reversed = M_PI - delta_yaw_threshold_for_searching_lanelet_ < abs_norm_delta && object_vel < 0.0; - if (check_distance && dist_threshold_for_searching_lanelet_ < lanelet.first) { + if (dist_threshold_for_searching_lanelet_ < lanelet.first) { return false; } if (!is_yaw_reversed && delta_yaw_threshold_for_searching_lanelet_ < abs_norm_delta) { From a6af44eaf534a3c96e4bdb3b483f3eb99548027a Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 3 Oct 2023 06:57:21 +0900 Subject: [PATCH 390/547] feat(localization_error_monitor): update parameter (#5201) Signed-off-by: kminoda --- .../config/localization_error_monitor.param.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/localization/localization_error_monitor/config/localization_error_monitor.param.yaml b/localization/localization_error_monitor/config/localization_error_monitor.param.yaml index 2aa28014ead41..def5c80164642 100644 --- a/localization/localization_error_monitor/config/localization_error_monitor.param.yaml +++ b/localization/localization_error_monitor/config/localization_error_monitor.param.yaml @@ -1,7 +1,7 @@ /**: ros__parameters: scale: 3.0 - error_ellipse_size: 1.0 - warn_ellipse_size: 0.8 + error_ellipse_size: 1.5 + warn_ellipse_size: 1.2 error_ellipse_size_lateral_direction: 0.3 warn_ellipse_size_lateral_direction: 0.25 From ccf9235e7a5537ed83aea451216ac963487cbd36 Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Tue, 3 Oct 2023 08:35:39 +0900 Subject: [PATCH 391/547] fix(avoidance): add debug info (#5205) Signed-off-by: Fumiya Watanabe --- .../src/scene_module/avoidance/avoidance_module.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 9117efcc447da..36908bd87de23 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -2662,6 +2662,7 @@ void AvoidanceModule::updateDebugMarker( addObjects(data.other_objects, std::string("OutOfTargetArea")); addObjects(data.other_objects, std::string("NotNeedAvoidance")); addObjects(data.other_objects, std::string("LessThanExecutionThreshold")); + addObjects(data.other_objects, std::string("TooNearToGoal")); } // shift line pre-process From 6e49e8297c915a0c68aaae7663d1ad89d4863221 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Tue, 3 Oct 2023 11:05:22 +0900 Subject: [PATCH 392/547] fix(simulator, controller): fix inverse pitch calculation (#5199) Signed-off-by: Mamoru Sobue Signed-off-by: Takamasa Horibe Co-authored-by: Takamasa Horibe --- control/pid_longitudinal_controller/README.md | 4 + .../media/slope_definition.drawio.svg | 165 +++++++++ .../src/pid_longitudinal_controller.cpp | 4 +- simulator/simple_planning_simulator/README.md | 6 + .../media/pitch-calculation.drawio.svg | 329 ++++++++++++++++++ .../simple_planning_simulator_core.cpp | 2 +- 6 files changed, 508 insertions(+), 2 deletions(-) create mode 100644 control/pid_longitudinal_controller/media/slope_definition.drawio.svg create mode 100644 simulator/simple_planning_simulator/media/pitch-calculation.drawio.svg diff --git a/control/pid_longitudinal_controller/README.md b/control/pid_longitudinal_controller/README.md index 418b3f7d27220..9d25322c793e0 100644 --- a/control/pid_longitudinal_controller/README.md +++ b/control/pid_longitudinal_controller/README.md @@ -75,6 +75,10 @@ For instance, if the vehicle is attempting to start with an acceleration of `1.0 A suitable example of a vehicle system for the slope compensation function is one in which the output acceleration from the longitudinal_controller is converted into target accel/brake pedal input without any feedbacks. In this case, the output acceleration is just used as a feedforward term to calculate the target pedal, and hence the issue mentioned above does not arise. +Note: The angle of the slope is defined as positive for an uphill slope, while the pitch angle of the ego pose is defined as negative when facing upward. They have an opposite definition. + +![slope_definition](./media/slope_definition.drawio.svg) + #### PID control For deviations that cannot be handled by FeedForward control, such as model errors, PID control is used to construct a feedback system. diff --git a/control/pid_longitudinal_controller/media/slope_definition.drawio.svg b/control/pid_longitudinal_controller/media/slope_definition.drawio.svg new file mode 100644 index 0000000000000..7f0aa677629a3 --- /dev/null +++ b/control/pid_longitudinal_controller/media/slope_definition.drawio.svg @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + Slope is defined as positive for an uphill +
    +
    +
    +
    + Slope is defined as positive for an uphi... +
    +
    + + + + + + + + + + + + + +
    +
    +
    + (Pitch of ego is defined as negative when facing upward) +
    +
    +
    +
    + (Pitch of ego is defined as negative when facing upwar... +
    +
    + + + + +
    +
    +
    + x +
    +
    +
    +
    + x +
    +
    + + + + +
    +
    +
    + y +
    +
    +
    +
    + z +
    +
    + + + + +
    +
    +
    + z +
    +
    +
    +
    + y +
    +
    + + + + +
    +
    +
    + d_z +
    +
    +
    +
    + d_z +
    +
    + + + + +
    +
    +
    + d_xy +
    +
    +
    +
    + d_xy +
    +
    +
    + + + + Text is not SVG - cannot display + + +
    diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index 644effce50824..b80874a8a2e57 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -457,7 +457,9 @@ PidLongitudinalController::ControlData PidLongitudinalController::getControlData current_pose, m_trajectory, m_ego_nearest_dist_threshold, m_ego_nearest_yaw_threshold); // pitch - const double raw_pitch = longitudinal_utils::getPitchByPose(current_pose.orientation); + // NOTE: getPitchByTraj() calculates the pitch angle as defined in + // ../media/slope_definition.drawio.svg while getPitchByPose() is not, so `raw_pitch` is reversed + const double raw_pitch = (-1.0) * longitudinal_utils::getPitchByPose(current_pose.orientation); const double traj_pitch = longitudinal_utils::getPitchByTraj(m_trajectory, control_data.nearest_idx, m_wheel_base); control_data.slope_angle = m_use_traj_for_pitch ? traj_pitch : m_lpf_pitch->filter(raw_pitch); diff --git a/simulator/simple_planning_simulator/README.md b/simulator/simple_planning_simulator/README.md index d70aca794dc07..ceecacfe8cd8c 100644 --- a/simulator/simple_planning_simulator/README.md +++ b/simulator/simple_planning_simulator/README.md @@ -89,6 +89,12 @@ _Note_: The steering/velocity/acceleration dynamics is modeled by a first order Since the vehicle outputs `odom`->`base_link` tf, this simulator outputs the tf with the same frame_id configuration. In the simple_planning_simulator.launch.py, the node that outputs the `map`->`odom` tf, that usually estimated by the localization module (e.g. NDT), will be launched as well. Since the tf output by this simulator module is an ideal value, `odom`->`map` will always be 0. +### (Caveat) Pitch calculation + +Ego vehicle pitch angle is calculated in the following manner. + +![pitch calculation](./media/pitch-calculation.drawio.svg) + ## Error detection and handling The only validation on inputs being done is testing for a valid vehicle model type. diff --git a/simulator/simple_planning_simulator/media/pitch-calculation.drawio.svg b/simulator/simple_planning_simulator/media/pitch-calculation.drawio.svg new file mode 100644 index 0000000000000..d022b29d2a730 --- /dev/null +++ b/simulator/simple_planning_simulator/media/pitch-calculation.drawio.svg @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + forward
    $diff_xy > 0
    +
    +
    +
    +
    +
    + forward... +
    +
    + + + +
    +
    +
    + + + ego +
    + direction +
    +
    +
    +
    +
    +
    +
    + ego... +
    +
    + + + +
    +
    +
    + + reverse
    $diff_xy < 0
    +
    +
    +
    +
    +
    + reverse... +
    +
    + + + +
    +
    +
    + + + lane +
    + direction +
    +
    +
    +
    +
    +
    +
    + lane... +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + $diff_z > 0
    +
    +
    +
    +
    +
    + $diff_z > 0 +
    +
    + + + +
    +
    +
    + + $diff_z < 0
    +
    +
    +
    +
    +
    + $diff_z < 0 +
    +
    + + + +
    +
    +
    + + pitch < 0
    +
    +
    +
    +
    +
    + pitch < 0 +
    +
    + + + +
    +
    +
    + + pitch > 0
    +
    +
    +
    +
    +
    + pitch > 0 +
    +
    + + + +
    +
    +
    + + pitch < 0
    +
    +
    +
    +
    +
    + pitch < 0 +
    +
    + + + +
    +
    +
    + + pitch > 0
    +
    +
    +
    +
    +
    + pitch > 0 +
    +
    + +
    + + + + Text is not SVG - cannot display + + +
    diff --git a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp index 8c2688928b4c9..208fd80d57a08 100644 --- a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp +++ b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp @@ -313,7 +313,7 @@ double SimplePlanningSimulator::calculate_ego_pitch() const std::cos(ego_yaw_against_lanelet); const bool reverse_sign = std::cos(ego_yaw_against_lanelet) < 0.0; const double ego_pitch_angle = - reverse_sign ? std::atan2(-diff_z, -diff_xy) : std::atan2(diff_z, diff_xy); + reverse_sign ? -std::atan2(-diff_z, -diff_xy) : -std::atan2(diff_z, diff_xy); return ego_pitch_angle; } From 8ab910ba8dd3ff4fd09d149d6857b629b3cf9b0b Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 3 Oct 2023 12:11:48 +0900 Subject: [PATCH 393/547] feat(crosswalk): dyanmic timeout for no intention to walk decision (#5188) * feat(crosswalk): dyanmic timeout for no intention to walk decision Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/crosswalk.param.yaml | 4 +- .../src/manager.cpp | 6 ++- .../src/scene_crosswalk.cpp | 2 +- .../src/scene_crosswalk.hpp | 54 ++++++++++++++++--- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml b/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml index b1e5bcb32bd18..e7accc14096e0 100644 --- a/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml +++ b/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml @@ -54,7 +54,9 @@ ## param for yielding disable_stop_for_yield_cancel: true # for the crosswalk where there is a traffic signal disable_yield_for_new_stopped_object: true # for the crosswalk where there is a traffic signal - timeout_no_intention_to_walk: 3.0 # [s] if the pedestrian does not move for X seconds after stopping before the crosswalk, the module judge that ego is able to pass first. + # if the pedestrian does not move for X seconds after stopping before the crosswalk, the module judge that ego is able to pass first. + distance_map_for_no_intention_to_walk: [1.0, 5.0] # [m] ascending order + timeout_map_for_no_intention_to_walk: [3.0, 0.0] # [s] timeout_ego_stop_for_yield: 3.0 # [s] the amount of time which ego should be stopping to query whether it yields or not # param for target object filtering diff --git a/planning/behavior_velocity_crosswalk_module/src/manager.cpp b/planning/behavior_velocity_crosswalk_module/src/manager.cpp index 304b4bdf6f5e7..94e87d0174193 100644 --- a/planning/behavior_velocity_crosswalk_module/src/manager.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/manager.cpp @@ -104,8 +104,10 @@ CrosswalkModuleManager::CrosswalkModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".pass_judge.disable_stop_for_yield_cancel"); cp.disable_yield_for_new_stopped_object = getOrDeclareParameter(node, ns + ".pass_judge.disable_yield_for_new_stopped_object"); - cp.timeout_no_intention_to_walk = - getOrDeclareParameter(node, ns + ".pass_judge.timeout_no_intention_to_walk"); + cp.distance_map_for_no_intention_to_walk = getOrDeclareParameter>( + node, ns + ".pass_judge.distance_map_for_no_intention_to_walk"); + cp.timeout_map_for_no_intention_to_walk = getOrDeclareParameter>( + node, ns + ".pass_judge.timeout_map_for_no_intention_to_walk"); cp.timeout_ego_stop_for_yield = getOrDeclareParameter(node, ns + ".pass_judge.timeout_ego_stop_for_yield"); diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index b99bd522b81f8..62acd191d8697 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -928,7 +928,7 @@ void CrosswalkModule::updateObjectState( getCollisionPoint(sparse_resample_path, object, crosswalk_attention_range, attention_area); object_info_manager_.update( obj_uuid, obj_pos, std::hypot(obj_vel.x, obj_vel.y), clock_->now(), is_ego_yielding, - has_traffic_light, collision_point, planner_param_); + has_traffic_light, collision_point, planner_param_, crosswalk_.polygon2d().basicPolygon()); if (collision_point) { const auto collision_state = object_info_manager_.getCollisionState(obj_uuid); diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp index 667298cbb4c58..063ea4f83cd45 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp @@ -30,12 +30,15 @@ #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -45,6 +48,7 @@ namespace behavior_velocity_planner { +namespace bg = boost::geometry; using autoware_auto_perception_msgs::msg::ObjectClassification; using autoware_auto_perception_msgs::msg::PredictedObject; using autoware_auto_perception_msgs::msg::PredictedObjects; @@ -71,6 +75,33 @@ double interpolateEgoPassMargin( } return y_vec.back(); } + +double InterpolateMap( + const std::vector & key_map, const std::vector & value_map, const double query) +{ + // If the speed is out of range of the key_map, apply zero-order hold. + if (query <= key_map.front()) { + return value_map.front(); + } + if (query >= key_map.back()) { + return value_map.back(); + } + + // Apply linear interpolation + for (size_t i = 0; i < key_map.size() - 1; ++i) { + if (key_map.at(i) <= query && query <= key_map.at(i + 1)) { + auto ratio = (query - key_map.at(i)) / std::max(key_map.at(i + 1) - key_map.at(i), 1.0e-5); + ratio = std::clamp(ratio, 0.0, 1.0); + const auto interp = value_map.at(i) + ratio * (value_map.at(i + 1) - value_map.at(i)); + return interp; + } + } + + std::cerr << "Crosswalk's InterpolateMap interpolation logic is broken." + "Please check the code." + << std::endl; + return value_map.back(); +} } // namespace class CrosswalkModule : public SceneModuleInterface @@ -111,7 +142,8 @@ class CrosswalkModule : public SceneModuleInterface double min_object_velocity; bool disable_stop_for_yield_cancel; bool disable_yield_for_new_stopped_object; - double timeout_no_intention_to_walk; + std::vector distance_map_for_no_intention_to_walk; + std::vector timeout_map_for_no_intention_to_walk; double timeout_ego_stop_for_yield; // param for input data double traffic_light_state_timeout; @@ -132,9 +164,10 @@ class CrosswalkModule : public SceneModuleInterface std::optional collision_point{}; void transitState( - const rclcpp::Time & now, const double vel, const bool is_ego_yielding, - const bool has_traffic_light, const std::optional & collision_point, - const PlannerParam & planner_param) + const rclcpp::Time & now, const geometry_msgs::msg::Point & position, const double vel, + const bool is_ego_yielding, const bool has_traffic_light, + const std::optional & collision_point, const PlannerParam & planner_param, + const lanelet::BasicPolygon2d & crosswalk_polygon) { const bool is_stopped = vel < planner_param.stop_object_velocity; @@ -147,8 +180,13 @@ class CrosswalkModule : public SceneModuleInterface if (!time_to_start_stopped) { time_to_start_stopped = now; } + const double distance_to_crosswalk = + bg::distance(crosswalk_polygon, lanelet::BasicPoint2d(position.x, position.y)); + const double timeout_no_intention_to_walk = InterpolateMap( + planner_param.distance_map_for_no_intention_to_walk, + planner_param.timeout_map_for_no_intention_to_walk, distance_to_crosswalk); const bool intent_to_cross = - (now - *time_to_start_stopped).seconds() < planner_param.timeout_no_intention_to_walk; + (now - *time_to_start_stopped).seconds() < timeout_no_intention_to_walk; if ( (is_ego_yielding || (has_traffic_light && planner_param.disable_stop_for_yield_cancel)) && !intent_to_cross) { @@ -203,7 +241,8 @@ class CrosswalkModule : public SceneModuleInterface void update( const std::string & uuid, const geometry_msgs::msg::Point & position, const double vel, const rclcpp::Time & now, const bool is_ego_yielding, const bool has_traffic_light, - const std::optional & collision_point, const PlannerParam & planner_param) + const std::optional & collision_point, const PlannerParam & planner_param, + const lanelet::BasicPolygon2d & crosswalk_polygon) { // update current uuids current_uuids_.push_back(uuid); @@ -221,7 +260,8 @@ class CrosswalkModule : public SceneModuleInterface // update object state objects.at(uuid).transitState( - now, vel, is_ego_yielding, has_traffic_light, collision_point, planner_param); + now, position, vel, is_ego_yielding, has_traffic_light, collision_point, planner_param, + crosswalk_polygon); objects.at(uuid).collision_point = collision_point; objects.at(uuid).position = position; } From 8c3812603173b68ec8fc3c5b7b300e170fa2bea6 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:24:25 +0900 Subject: [PATCH 394/547] fix(out_of_lane): improve stop decision (#5207) Signed-off-by: Takayuki Murooka --- .../CMakeLists.txt | 8 +- .../README.md | 15 ++-- .../config/out_of_lane.param.yaml | 3 +- .../src/calculate_slowdown_points.hpp | 87 ++++++++++++++++++ .../src/decisions.cpp | 20 ++--- .../src/filter_predicted_objects.hpp | 10 +-- .../src/footprint.cpp | 6 +- .../src/lanelets_selection.cpp | 3 + .../src/manager.cpp | 2 +- .../src/scene_out_of_lane.cpp | 90 +++++-------------- .../src/scene_out_of_lane.hpp | 9 -- .../src/types.hpp | 4 +- 12 files changed, 136 insertions(+), 121 deletions(-) create mode 100644 planning/behavior_velocity_out_of_lane_module/src/calculate_slowdown_points.hpp diff --git a/planning/behavior_velocity_out_of_lane_module/CMakeLists.txt b/planning/behavior_velocity_out_of_lane_module/CMakeLists.txt index 633d5fdd613af..8ce1a334c4b35 100644 --- a/planning/behavior_velocity_out_of_lane_module/CMakeLists.txt +++ b/planning/behavior_velocity_out_of_lane_module/CMakeLists.txt @@ -6,13 +6,7 @@ autoware_package() pluginlib_export_plugin_description_file(behavior_velocity_planner plugins.xml) ament_auto_add_library(${PROJECT_NAME} SHARED - src/debug.cpp - src/decisions.cpp - src/footprint.cpp - src/lanelets_selection.cpp - src/manager.cpp - src/overlapping_range.cpp - src/scene_out_of_lane.cpp + DIRECTORY src ) ament_auto_package(INSTALL_TO_SHARE config) diff --git a/planning/behavior_velocity_out_of_lane_module/README.md b/planning/behavior_velocity_out_of_lane_module/README.md index 885c0dc2859ab..99396eb0edf22 100644 --- a/planning/behavior_velocity_out_of_lane_module/README.md +++ b/planning/behavior_velocity_out_of_lane_module/README.md @@ -149,14 +149,13 @@ Moreover, parameter `action.distance_buffer` adds an extra distance between the | `minimum_distance` | double | [m] minimum distance inside a lanelet for an overlap to be considered | | `extra_length` | double | [m] extra arc length to add to the front and back of an overlap (used to calculate enter/exit times) | -| Parameter /action | Type | Description | -| ----------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `skip_if_over_max_decel` | bool | [-] if true, do not take an action that would cause more deceleration than the maximum allowed | -| `strict` | bool | [-] if true, when a decision is taken to avoid entering a lane, the stop point will make sure no lane at all is entered by ego; if false, ego stops just before entering a lane but may then be overlapping another lane | -| `distance_buffer` | double | [m] buffer distance to try to keep between the ego footprint and lane | -| `slowdown.distance_threshold` | double | [m] insert a slow down when closer than this distance from an overlap | -| `slowdown.velocity` | double | [m/s] slow down velocity | -| `stop.distance_threshold` | double | [m] insert a stop when closer than this distance from an overlap | +| Parameter /action | Type | Description | +| ----------------------------- | ------ | ---------------------------------------------------------------------------------------------- | +| `skip_if_over_max_decel` | bool | [-] if true, do not take an action that would cause more deceleration than the maximum allowed | +| `distance_buffer` | double | [m] buffer distance to try to keep between the ego footprint and lane | +| `slowdown.distance_threshold` | double | [m] insert a slow down when closer than this distance from an overlap | +| `slowdown.velocity` | double | [m] slow down velocity | +| `stop.distance_threshold` | double | [m] insert a stop when closer than this distance from an overlap | | Parameter /ego | Type | Description | | -------------------- | ------ | ---------------------------------------------------- | diff --git a/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml b/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml index 510dc86ef651d..c74c5b3d87c1d 100644 --- a/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml +++ b/planning/behavior_velocity_out_of_lane_module/config/out_of_lane.param.yaml @@ -24,8 +24,7 @@ action: # action to insert in the path if an object causes a conflict at an overlap skip_if_over_max_decel: true # if true, do not take an action that would cause more deceleration than the maximum allowed - strict: true # if true, when a decision is taken to avoid entering a lane, the stop point will make sure no lane at all is entered by ego - # if false, ego stops just before entering a lane but may then be overlapping another lane. + precision: 0.1 # [m] precision when inserting a stop pose in the path distance_buffer: 1.5 # [m] buffer distance to try to keep between the ego footprint and lane slowdown: distance_threshold: 30.0 # [m] insert a slowdown when closer than this distance from an overlap diff --git a/planning/behavior_velocity_out_of_lane_module/src/calculate_slowdown_points.hpp b/planning/behavior_velocity_out_of_lane_module/src/calculate_slowdown_points.hpp new file mode 100644 index 0000000000000..5d22f01222919 --- /dev/null +++ b/planning/behavior_velocity_out_of_lane_module/src/calculate_slowdown_points.hpp @@ -0,0 +1,87 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CALCULATE_SLOWDOWN_POINTS_HPP_ +#define CALCULATE_SLOWDOWN_POINTS_HPP_ + +#include "footprint.hpp" +#include "types.hpp" + +#include +#include + +#include + +#include +#include + +namespace behavior_velocity_planner::out_of_lane +{ + +bool can_decelerate( + const EgoData & ego_data, const PathPointWithLaneId & point, const double target_vel) +{ + if (ego_data.velocity < 0.5) return true; + const auto dist_ahead_of_ego = motion_utils::calcSignedArcLength( + ego_data.path.points, ego_data.pose.position, point.point.pose.position); + const auto acc_to_target_vel = + (ego_data.velocity * ego_data.velocity - target_vel * target_vel) / (2 * dist_ahead_of_ego); + return acc_to_target_vel < std::abs(ego_data.max_decel); +} + +std::optional calculate_last_in_lane_pose( + const EgoData & ego_data, const Slowdown & decision, const Polygon2d & footprint, + const PlannerParam & params) +{ + const auto from_arc_length = + motion_utils::calcSignedArcLength(ego_data.path.points, 0, ego_data.first_path_idx); + const auto to_arc_length = + motion_utils::calcSignedArcLength(ego_data.path.points, 0, decision.target_path_idx); + PathPointWithLaneId interpolated_point; + for (auto l = to_arc_length - params.precision; l > from_arc_length; l -= params.precision) { + // TODO(Maxime): binary search + interpolated_point.point.pose = motion_utils::calcInterpolatedPose(ego_data.path.points, l); + const auto respect_decel_limit = + !params.skip_if_over_max_decel || + can_decelerate(ego_data, interpolated_point, decision.velocity); + if ( + respect_decel_limit && !boost::geometry::overlaps( + project_to_pose(footprint, interpolated_point.point.pose), + decision.lane_to_avoid.polygon2d().basicPolygon())) + return interpolated_point; + } + return std::nullopt; +} + +/// @brief calculate the slowdown point to insert in the path +/// @param ego_data ego data (path, velocity, etc) +/// @param decisions decision (before which point to stop, what lane to avoid entering, etc) +/// @param params parameters +/// @return optional slowdown point to insert in the path +std::optional calculate_slowdown_point( + const EgoData & ego_data, const std::vector & decisions, PlannerParam params) +{ + params.extra_front_offset += params.dist_buffer; + const auto base_footprint = make_base_footprint(params); + + // search for the first slowdown decision for which a stop point can be inserted + for (const auto & decision : decisions) { + const auto last_in_lane_pose = + calculate_last_in_lane_pose(ego_data, decision, base_footprint, params); + if (last_in_lane_pose) return SlowdownToInsert{decision, *last_in_lane_pose}; + } + return std::nullopt; +} +} // namespace behavior_velocity_planner::out_of_lane +#endif // CALCULATE_SLOWDOWN_POINTS_HPP_ diff --git a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp index da46c1355e735..78d9b73c86a17 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/decisions.cpp @@ -35,7 +35,7 @@ namespace behavior_velocity_planner::out_of_lane double distance_along_path(const EgoData & ego_data, const size_t target_idx) { return motion_utils::calcSignedArcLength( - ego_data.path->points, ego_data.pose.position, ego_data.first_path_idx + target_idx); + ego_data.path.points, ego_data.pose.position, ego_data.first_path_idx + target_idx); } double time_along_path(const EgoData & ego_data, const size_t target_idx, const double min_velocity) @@ -43,7 +43,7 @@ double time_along_path(const EgoData & ego_data, const size_t target_idx, const const auto dist = distance_along_path(ego_data, target_idx); const auto v = std::max( std::max(ego_data.velocity, min_velocity), - ego_data.path->points[ego_data.first_path_idx + target_idx].point.longitudinal_velocity_mps * + ego_data.path.points[ego_data.first_path_idx + target_idx].point.longitudinal_velocity_mps * 0.5); return dist / v; } @@ -126,11 +126,9 @@ std::optional> object_time_to_range( const auto same_driving_direction_as_ego = enter_time < exit_time; if (same_driving_direction_as_ego) { - RCLCPP_DEBUG(logger, " / SAME DIR \\\n"); worst_enter_time = worst_enter_time ? std::min(*worst_enter_time, enter_time) : enter_time; worst_exit_time = worst_exit_time ? std::max(*worst_exit_time, exit_time) : exit_time; } else { - RCLCPP_DEBUG(logger, " / OPPOSITE DIR \\\n"); worst_enter_time = worst_enter_time ? std::max(*worst_enter_time, enter_time) : enter_time; worst_exit_time = worst_exit_time ? std::min(*worst_exit_time, exit_time) : exit_time; } @@ -212,8 +210,11 @@ std::optional> object_time_to_range( bool threshold_condition(const RangeTimes & range_times, const PlannerParam & params) { - return std::min(range_times.object.enter_time, range_times.object.exit_time) < - params.time_threshold; + const auto enter_within_threshold = + range_times.object.enter_time > 0.0 && range_times.object.enter_time < params.time_threshold; + const auto exit_within_threshold = + range_times.object.exit_time > 0.0 && range_times.object.exit_time < params.time_threshold; + return enter_within_threshold || exit_within_threshold; } bool intervals_condition( @@ -357,11 +358,10 @@ std::optional calculate_decision( { std::optional decision; if (should_not_enter(range, inputs, params, logger)) { - const auto stop_before_range = params.strict ? find_most_preceding_range(range, inputs) : range; decision.emplace(); - decision->target_path_idx = inputs.ego_data.first_path_idx + - stop_before_range.entering_path_idx; // add offset from curr pose - decision->lane_to_avoid = stop_before_range.lane; + decision->target_path_idx = + inputs.ego_data.first_path_idx + range.entering_path_idx; // add offset from curr pose + decision->lane_to_avoid = range.lane; const auto ego_dist_to_range = distance_along_path(inputs.ego_data, range.entering_path_idx); set_decision_velocity(decision, ego_dist_to_range, params); } diff --git a/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp index f2555f31a4d5e..13bad0d047922 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/filter_predicted_objects.hpp @@ -23,9 +23,6 @@ namespace behavior_velocity_planner::out_of_lane { -using motion_utils::calcLateralOffset; -using tier4_autoware_utils::calcDistance2d; - /// @brief filter predicted objects and their predicted paths /// @param [in] objects predicted objects to filter /// @param [in] ego_data ego data @@ -47,13 +44,8 @@ autoware_auto_perception_msgs::msg::PredictedObjects filter_predicted_objects( auto filtered_object = object; const auto is_invalid_predicted_path = [&](const auto & predicted_path) { const auto is_low_confidence = predicted_path.confidence < params.objects_min_confidence; - const auto unique_path_points = motion_utils::removeOverlapPoints(predicted_path.path); - - // Note: lateral offset can not be calculated for path with less than 2 unique points const auto lat_offset_to_current_ego = - unique_path_points.size() > 1 - ? std::abs(calcLateralOffset(unique_path_points, ego_data.pose.position)) - : calcDistance2d(unique_path_points.front(), ego_data.pose.position); + std::abs(motion_utils::calcLateralOffset(predicted_path.path, ego_data.pose.position)); const auto is_crossing_ego = lat_offset_to_current_ego <= object.shape.dimensions.y / 2.0 + std::max( diff --git a/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp b/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp index 9ad7437901309..31cc035703a7b 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/footprint.cpp @@ -57,9 +57,9 @@ std::vector calculate_path_footprints( { const auto base_footprint = make_base_footprint(params); std::vector path_footprints; - path_footprints.reserve(ego_data.path->points.size()); - for (auto i = ego_data.first_path_idx; i < ego_data.path->points.size(); ++i) { - const auto & path_pose = ego_data.path->points[i].point.pose; + path_footprints.reserve(ego_data.path.points.size()); + for (auto i = ego_data.first_path_idx; i < ego_data.path.points.size(); ++i) { + const auto & path_pose = ego_data.path.points[i].point.pose; const auto angle = tf2::getYaw(path_pose.orientation); const auto rotated_footprint = tier4_autoware_utils::rotatePolygon(base_footprint, angle); lanelet::BasicPolygon2d footprint; diff --git a/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp b/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp index 7f3b69f40840b..c5dedf23785ab 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/lanelets_selection.cpp @@ -20,6 +20,7 @@ #include #include +#include namespace behavior_velocity_planner::out_of_lane { @@ -68,6 +69,8 @@ lanelet::ConstLanelets calculate_other_lanelets( return false; }; for (const auto & ll : lanelets_within_range) { + if (std::string(ll.second.attributeOr(lanelet::AttributeName::Subtype, "none")) != "road") + continue; const auto is_path_lanelet = contains_lanelet(path_lanelets, ll.second.id()); const auto is_ignored_lanelet = contains_lanelet(ignored_lanelets, ll.second.id()); if (!is_path_lanelet && !is_ignored_lanelet && !is_overlapped_by_path_lanelets(ll.second)) diff --git a/planning/behavior_velocity_out_of_lane_module/src/manager.cpp b/planning/behavior_velocity_out_of_lane_module/src/manager.cpp index 5765363024aed..2422c6fe84e56 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/manager.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/manager.cpp @@ -56,7 +56,7 @@ OutOfLaneModuleManager::OutOfLaneModuleManager(rclcpp::Node & node) pp.skip_if_over_max_decel = getOrDeclareParameter(node, ns + ".action.skip_if_over_max_decel"); - pp.strict = getOrDeclareParameter(node, ns + ".action.strict"); + pp.precision = getOrDeclareParameter(node, ns + ".action.precision"); pp.dist_buffer = getOrDeclareParameter(node, ns + ".action.distance_buffer"); pp.slow_velocity = getOrDeclareParameter(node, ns + ".action.slowdown.velocity"); pp.slow_dist_threshold = diff --git a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp index 013643b6bd6d4..1487d9e63c8b0 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp +++ b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.cpp @@ -14,6 +14,7 @@ #include "scene_out_of_lane.hpp" +#include "calculate_slowdown_points.hpp" #include "debug.hpp" #include "decisions.hpp" #include "filter_predicted_objects.hpp" @@ -31,6 +32,8 @@ #include #include +#include + #include #include @@ -62,9 +65,9 @@ bool OutOfLaneModule::modifyPathVelocity( stopwatch.tic(); EgoData ego_data; ego_data.pose = planner_data_->current_odometry->pose; - ego_data.path = path; + ego_data.path.points = path->points; ego_data.first_path_idx = - motion_utils::findNearestSegmentIndex(path->points, ego_data.pose.position); + motion_utils::findNearestSegmentIndex(ego_data.path.points, ego_data.pose.position); ego_data.velocity = planner_data_->current_velocity->twist.linear.x; ego_data.max_decel = -planner_data_->max_stop_acceleration_threshold; stopwatch.tic("calculate_path_footprints"); @@ -73,7 +76,7 @@ bool OutOfLaneModule::modifyPathVelocity( const auto calculate_path_footprints_us = stopwatch.toc("calculate_path_footprints"); // Calculate lanelets to ignore and consider const auto path_lanelets = planning_utils::getLaneletsOnPath( - *path, planner_data_->route_handler_->getLaneletMapPtr(), + ego_data.path, planner_data_->route_handler_->getLaneletMapPtr(), planner_data_->current_odometry->pose); const auto ignored_lanelets = calculate_ignored_lanelets(ego_data, path_lanelets, *planner_data_->route_handler_, params_); @@ -113,22 +116,27 @@ bool OutOfLaneModule::modifyPathVelocity( auto decisions = calculate_decisions(inputs, params_, logger_); const auto calculate_decisions_us = stopwatch.toc("calculate_decisions"); stopwatch.tic("calc_slowdown_points"); - const auto points_to_insert = calculate_slowdown_points(ego_data, decisions, params_); - debug_data_.slowdowns = points_to_insert; + const auto point_to_insert = calculate_slowdown_point(ego_data, decisions, params_); const auto calc_slowdown_points_us = stopwatch.toc("calc_slowdown_points"); stopwatch.tic("insert_slowdown_points"); - for (const auto & point : points_to_insert) { - auto path_idx = point.slowdown.target_path_idx; - planning_utils::insertVelocity(*ego_data.path, point.point, point.slowdown.velocity, path_idx); - if (point.slowdown.velocity == 0.0) { + debug_data_.slowdowns.clear(); + if (point_to_insert) { + debug_data_.slowdowns = {*point_to_insert}; + auto path_idx = motion_utils::findNearestSegmentIndex( + path->points, point_to_insert->point.point.pose.position) + + 1; + planning_utils::insertVelocity( + *path, point_to_insert->point, point_to_insert->slowdown.velocity, path_idx, + params_.precision); + if (point_to_insert->slowdown.velocity == 0.0) { tier4_planning_msgs::msg::StopFactor stop_factor; - stop_factor.stop_pose = point.point.point.pose; + stop_factor.stop_pose = point_to_insert->point.point.pose; stop_factor.dist_to_stop_pose = motion_utils::calcSignedArcLength( - ego_data.path->points, ego_data.pose.position, point.point.point.pose.position); + ego_data.path.points, ego_data.pose.position, point_to_insert->point.point.pose.position); planning_utils::appendStopReason(stop_factor, stop_reason); } velocity_factor_.set( - path->points, planner_data_->current_odometry->pose, point.point.point.pose, + path->points, planner_data_->current_odometry->pose, point_to_insert->point.point.pose, VelocityFactor::UNKNOWN); } const auto insert_slowdown_points_us = stopwatch.toc("insert_slowdown_points"); @@ -181,62 +189,4 @@ motion_utils::VirtualWalls OutOfLaneModule::createVirtualWalls() return virtual_walls; } -std::vector calculate_slowdown_points( - const EgoData & ego_data, const std::vector & decisions, PlannerParam params) -{ - std::vector to_insert; - params.extra_front_offset += params.dist_buffer; - const auto base_footprint = make_base_footprint(params); - - const auto can_decel = [&](const auto dist_ahead_of_ego, const auto target_vel) { - const auto acc_to_target_vel = - (ego_data.velocity * ego_data.velocity - target_vel * target_vel) / (2 * dist_ahead_of_ego); - return acc_to_target_vel < std::abs(ego_data.max_decel); - }; - const auto insert_decision = [&](const auto & path_point, const auto & decision) -> bool { - const auto dist_ahead_of_ego = motion_utils::calcSignedArcLength( - ego_data.path->points, ego_data.pose.position, path_point.point.pose.position); - if (!params.skip_if_over_max_decel || can_decel(dist_ahead_of_ego, decision.velocity)) { - to_insert.push_back({decision, path_point}); - return true; - } - return false; - }; - const auto insert_interpolated_decision = - [&](const auto & path_point, const auto & decision) -> bool { - auto interpolated_point = path_point; - const auto & path_pose = path_point.point.pose; - const auto & prev_path_pose = ego_data.path->points[decision.target_path_idx - 1].point.pose; - constexpr auto precision = 0.1; - for (auto ratio = precision; ratio <= 1.0; ratio += precision) { - interpolated_point.point.pose = - tier4_autoware_utils::calcInterpolatedPose(path_pose, prev_path_pose, ratio, false); - const auto is_overlap = boost::geometry::overlaps( - project_to_pose(base_footprint, interpolated_point.point.pose), - decision.lane_to_avoid.polygon2d().basicPolygon()); - if (!is_overlap) { - return insert_decision(path_point, decision); - } - } - return false; - }; - for (const auto & decision : decisions) { - const auto & path_point = ego_data.path->points[decision.target_path_idx]; - const auto decision_is_at_beginning_of_path = - decision.target_path_idx == ego_data.first_path_idx; - bool inserted = false; - if (decision_is_at_beginning_of_path) { - inserted = insert_decision(path_point, decision); - } else { - inserted = insert_interpolated_decision(path_point, decision); - // if no valid point found, fallback to using the previous index (known to not overlap) - if (!inserted) - inserted = insert_decision(ego_data.path->points[decision.target_path_idx], decision); - } - // only insert the first (i.e., lowest arc length) decision - if (inserted) break; - } - return to_insert; -} - } // namespace behavior_velocity_planner::out_of_lane diff --git a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp index 1a64c11a3c921..cdf8eef8f277b 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/scene_out_of_lane.hpp @@ -57,15 +57,6 @@ class OutOfLaneModule : public SceneModuleInterface // Debug mutable DebugData debug_data_; }; - -/// @brief calculate points along the path where we want ego to slowdown/stop -/// @param ego_data ego data (path, velocity, etc) -/// @param decisions decisions (before which point to stop, what lane to avoid entering, etc) -/// @param params parameters -/// @return precise points to insert in the path -std::vector calculate_slowdown_points( - const EgoData & ego_data, const std::vector & decisions, PlannerParam params); - } // namespace behavior_velocity_planner::out_of_lane #endif // SCENE_OUT_OF_LANE_HPP_ diff --git a/planning/behavior_velocity_out_of_lane_module/src/types.hpp b/planning/behavior_velocity_out_of_lane_module/src/types.hpp index b81fa09884819..64d88f15e6966 100644 --- a/planning/behavior_velocity_out_of_lane_module/src/types.hpp +++ b/planning/behavior_velocity_out_of_lane_module/src/types.hpp @@ -52,11 +52,11 @@ struct PlannerParam double overlap_min_dist; // [m] min distance inside another lane to consider an overlap // action to insert in the path if an object causes a conflict at an overlap bool skip_if_over_max_decel; // if true, skip the action if it causes more than the max decel - bool strict; // if true stop before entering *any* other lane, not only the lane to avoid double dist_buffer; double slow_velocity; double slow_dist_threshold; double stop_dist_threshold; + double precision; // [m] precision when inserting a stop pose in the path // ego dimensions used to create its polygon footprint double front_offset; // [m] front offset (from vehicle info) double rear_offset; // [m] rear offset (from vehicle info) @@ -135,7 +135,7 @@ struct OtherLane /// @brief data related to the ego vehicle struct EgoData { - autoware_auto_planning_msgs::msg::PathWithLaneId * path{}; + autoware_auto_planning_msgs::msg::PathWithLaneId path{}; size_t first_path_idx{}; double velocity{}; // [m/s] double max_decel{}; // [m/s²] From 58a513efabcce9b93d1bc2ee4639d7b15b852038 Mon Sep 17 00:00:00 2001 From: shulanbushangshu <102840938+shulanbushangshu@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:33:33 +0800 Subject: [PATCH 395/547] feat: add common interface specs test (#5034) * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * add interface specs test Signed-off-by: jack.song * style(pre-commit): autofix * fix copyright Signed-off-by: Takagi, Isamu --------- Signed-off-by: jack.song Signed-off-by: Takagi, Isamu Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Takagi, Isamu <43976882+isamu-takagi@users.noreply.github.com> Co-authored-by: Takagi, Isamu --- .../component_interface_specs/CMakeLists.txt | 34 ++++++++++ common/component_interface_specs/package.xml | 17 ++++- .../test/gtest_main.cpp | 21 ++++++ .../test/test_control.cpp | 46 +++++++++++++ .../test/test_localization.cpp | 46 +++++++++++++ .../test/test_map.cpp | 28 ++++++++ .../test/test_perception.cpp | 28 ++++++++ .../test/test_planning.cpp | 64 +++++++++++++++++++ .../test/test_system.cpp | 37 +++++++++++ .../test/test_vehicle.cpp | 64 +++++++++++++++++++ 10 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 common/component_interface_specs/test/gtest_main.cpp create mode 100644 common/component_interface_specs/test/test_control.cpp create mode 100644 common/component_interface_specs/test/test_localization.cpp create mode 100644 common/component_interface_specs/test/test_map.cpp create mode 100644 common/component_interface_specs/test/test_perception.cpp create mode 100644 common/component_interface_specs/test/test_planning.cpp create mode 100644 common/component_interface_specs/test/test_system.cpp create mode 100644 common/component_interface_specs/test/test_vehicle.cpp diff --git a/common/component_interface_specs/CMakeLists.txt b/common/component_interface_specs/CMakeLists.txt index 31c723600183f..b519a0ddce86a 100644 --- a/common/component_interface_specs/CMakeLists.txt +++ b/common/component_interface_specs/CMakeLists.txt @@ -4,4 +4,38 @@ project(component_interface_specs) find_package(autoware_cmake REQUIRED) autoware_package() +include_directories( + include + SYSTEM + ${rclcpp_INCLUDE_DIRS} + ${rosidl_runtime_cpp_INCLUDE_DIRS} + ${rcl_INCLUDE_DIRS} + ${autoware_adapi_v1_msgs_INCLUDE_DIRS} + ${autoware_auto_planning_msgs_INCLUDE_DIRS} + ${autoware_planning_msgs_INCLUDE_DIRS} + ${autoware_auto_vehicle_msgs_INCLUDE_DIRS} + ${tier4_control_msgs_INCLUDE_DIRS} + ${nav_msgs_INCLUDE_DIRS} + ${tier4_system_msgs_INCLUDE_DIRS} + ${tier4_vehicle_msgs_INCLUDE_DIRS} + ${autoware_auto_perception_msgs_INCLUDE_DIRS} + ${tier4_map_msgs_INCLUDE_DIRS} +) + +if(BUILD_TESTING) + ament_add_ros_isolated_gtest(test_component_interface_specs + test/gtest_main.cpp + test/test_planning.cpp + test/test_control.cpp + test/test_localization.cpp + test/test_system.cpp + test/test_map.cpp + test/test_perception.cpp + test/test_vehicle.cpp + ) + target_include_directories(test_component_interface_specs + PRIVATE include + ) +endif() + ament_auto_package() diff --git a/common/component_interface_specs/package.xml b/common/component_interface_specs/package.xml index 7ebc3f0d1bea9..93f77c651869d 100644 --- a/common/component_interface_specs/package.xml +++ b/common/component_interface_specs/package.xml @@ -11,13 +11,28 @@ Apache License 2.0 ament_cmake_auto + ament_cmake_core + ament_cmake_export_dependencies + ament_cmake_test autoware_cmake + ament_cmake_core + ament_cmake_test + autoware_adapi_v1_msgs + autoware_auto_perception_msgs + autoware_auto_planning_msgs autoware_auto_vehicle_msgs + autoware_planning_msgs + nav_msgs + rcl + rclcpp + rosidl_runtime_cpp + tier4_control_msgs tier4_map_msgs + tier4_system_msgs tier4_vehicle_msgs - + ament_cmake_ros ament_lint_auto autoware_lint_common diff --git a/common/component_interface_specs/test/gtest_main.cpp b/common/component_interface_specs/test/gtest_main.cpp new file mode 100644 index 0000000000000..81d9d5345270d --- /dev/null +++ b/common/component_interface_specs/test/gtest_main.cpp @@ -0,0 +1,21 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gtest/gtest.h" + +int main(int argc, char * argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/common/component_interface_specs/test/test_control.cpp b/common/component_interface_specs/test/test_control.cpp new file mode 100644 index 0000000000000..366641eacbd23 --- /dev/null +++ b/common/component_interface_specs/test/test_control.cpp @@ -0,0 +1,46 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_specs/control.hpp" +#include "gtest/gtest.h" + +TEST(control, interface) +{ + { + using control_interface::IsPaused; + IsPaused is_paused; + size_t depth = 1; + EXPECT_EQ(is_paused.depth, depth); + EXPECT_EQ(is_paused.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(is_paused.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } + + { + using control_interface::IsStartRequested; + IsStartRequested is_start_requested; + size_t depth = 1; + EXPECT_EQ(is_start_requested.depth, depth); + EXPECT_EQ(is_start_requested.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(is_start_requested.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } + + { + using control_interface::IsStopped; + IsStopped is_stopped; + size_t depth = 1; + EXPECT_EQ(is_stopped.depth, depth); + EXPECT_EQ(is_stopped.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(is_stopped.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } +} diff --git a/common/component_interface_specs/test/test_localization.cpp b/common/component_interface_specs/test/test_localization.cpp new file mode 100644 index 0000000000000..31d8e139bd69a --- /dev/null +++ b/common/component_interface_specs/test/test_localization.cpp @@ -0,0 +1,46 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_specs/localization.hpp" +#include "gtest/gtest.h" + +TEST(localization, interface) +{ + { + using localization_interface::InitializationState; + InitializationState initialization_state; + size_t depth = 1; + EXPECT_EQ(initialization_state.depth, depth); + EXPECT_EQ(initialization_state.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(initialization_state.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } + + { + using localization_interface::KinematicState; + KinematicState kinematic_state; + size_t depth = 1; + EXPECT_EQ(kinematic_state.depth, depth); + EXPECT_EQ(kinematic_state.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(kinematic_state.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } + + { + using localization_interface::Acceleration; + Acceleration acceleration; + size_t depth = 1; + EXPECT_EQ(acceleration.depth, depth); + EXPECT_EQ(acceleration.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(acceleration.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } +} diff --git a/common/component_interface_specs/test/test_map.cpp b/common/component_interface_specs/test/test_map.cpp new file mode 100644 index 0000000000000..a65f2cb7120d1 --- /dev/null +++ b/common/component_interface_specs/test/test_map.cpp @@ -0,0 +1,28 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_specs/map.hpp" +#include "gtest/gtest.h" + +TEST(map, interface) +{ + { + using map_interface::MapProjectorInfo; + MapProjectorInfo map_projector; + size_t depth = 1; + EXPECT_EQ(map_projector.depth, depth); + EXPECT_EQ(map_projector.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(map_projector.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } +} diff --git a/common/component_interface_specs/test/test_perception.cpp b/common/component_interface_specs/test/test_perception.cpp new file mode 100644 index 0000000000000..ec80c06bb119a --- /dev/null +++ b/common/component_interface_specs/test/test_perception.cpp @@ -0,0 +1,28 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_specs/perception.hpp" +#include "gtest/gtest.h" + +TEST(perception, interface) +{ + { + using perception_interface::ObjectRecognition; + ObjectRecognition object_recognition; + size_t depth = 1; + EXPECT_EQ(object_recognition.depth, depth); + EXPECT_EQ(object_recognition.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(object_recognition.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } +} diff --git a/common/component_interface_specs/test/test_planning.cpp b/common/component_interface_specs/test/test_planning.cpp new file mode 100644 index 0000000000000..8c504cb119854 --- /dev/null +++ b/common/component_interface_specs/test/test_planning.cpp @@ -0,0 +1,64 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_specs/planning.hpp" +#include "gtest/gtest.h" + +TEST(planning, interface) +{ + { + using planning_interface::RouteState; + RouteState state; + size_t depth = 1; + EXPECT_EQ(state.depth, depth); + EXPECT_EQ(state.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(state.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } + + { + using planning_interface::Route; + Route route; + size_t depth = 1; + EXPECT_EQ(route.depth, depth); + EXPECT_EQ(route.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(route.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } + + { + using planning_interface::NormalRoute; + NormalRoute route; + size_t depth = 1; + EXPECT_EQ(route.depth, depth); + EXPECT_EQ(route.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(route.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } + + { + using planning_interface::MrmRoute; + MrmRoute route; + size_t depth = 1; + EXPECT_EQ(route.depth, depth); + EXPECT_EQ(route.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(route.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } + + { + using planning_interface::Trajectory; + Trajectory trajectory; + size_t depth = 1; + EXPECT_EQ(trajectory.depth, depth); + EXPECT_EQ(trajectory.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(trajectory.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } +} diff --git a/common/component_interface_specs/test/test_system.cpp b/common/component_interface_specs/test/test_system.cpp new file mode 100644 index 0000000000000..416d2effad766 --- /dev/null +++ b/common/component_interface_specs/test/test_system.cpp @@ -0,0 +1,37 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_specs/system.hpp" +#include "gtest/gtest.h" + +TEST(system, interface) +{ + { + using system_interface::MrmState; + MrmState state; + size_t depth = 1; + EXPECT_EQ(state.depth, depth); + EXPECT_EQ(state.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(state.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } + + { + using system_interface::OperationModeState; + OperationModeState state; + size_t depth = 1; + EXPECT_EQ(state.depth, depth); + EXPECT_EQ(state.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(state.durability, RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL); + } +} diff --git a/common/component_interface_specs/test/test_vehicle.cpp b/common/component_interface_specs/test/test_vehicle.cpp new file mode 100644 index 0000000000000..1f2041b6cb79e --- /dev/null +++ b/common/component_interface_specs/test/test_vehicle.cpp @@ -0,0 +1,64 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_specs/vehicle.hpp" +#include "gtest/gtest.h" + +TEST(vehicle, interface) +{ + { + using vehicle_interface::SteeringStatus; + SteeringStatus status; + size_t depth = 1; + EXPECT_EQ(status.depth, depth); + EXPECT_EQ(status.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(status.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } + + { + using vehicle_interface::GearStatus; + GearStatus status; + size_t depth = 1; + EXPECT_EQ(status.depth, depth); + EXPECT_EQ(status.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(status.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } + + { + using vehicle_interface::TurnIndicatorStatus; + TurnIndicatorStatus status; + size_t depth = 1; + EXPECT_EQ(status.depth, depth); + EXPECT_EQ(status.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(status.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } + + { + using vehicle_interface::HazardLightStatus; + HazardLightStatus status; + size_t depth = 1; + EXPECT_EQ(status.depth, depth); + EXPECT_EQ(status.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(status.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } + + { + using vehicle_interface::EnergyStatus; + EnergyStatus status; + size_t depth = 1; + EXPECT_EQ(status.depth, depth); + EXPECT_EQ(status.reliability, RMW_QOS_POLICY_RELIABILITY_RELIABLE); + EXPECT_EQ(status.durability, RMW_QOS_POLICY_DURABILITY_VOLATILE); + } +} From 81ede03f749d46fb3eed3c9e96ce9d993acaf8c3 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:52:18 +0900 Subject: [PATCH 396/547] fix(utils): fix bug in drivable area expansion (#5198) * fix(avoidance): fix invalid drivable area bound Signed-off-by: satoshi-ota * refactor(avoidance): split huge process to three functions Signed-off-by: satoshi-ota * fix(utils): improve sharp bound remove logic Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../behavior_path_planner/utils/utils.hpp | 9 + .../behavior_path_planner/src/utils/utils.cpp | 347 +++++++++++------- 2 files changed, 230 insertions(+), 126 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp index 3d81c584b7c01..c5e5aa3e50191 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp @@ -231,6 +231,15 @@ std::vector calcBound( const bool enable_expanding_hatched_road_markings, const bool enable_expanding_intersection_areas, const bool is_left); +std::vector getBoundWithHatchedRoadMarkings( + const std::vector & original_bound, + const std::shared_ptr & route_handler); + +std::vector getBoundWithIntersectionAreas( + const std::vector & original_bound, + const std::shared_ptr & route_handler, + const std::vector & drivable_lanes, const bool is_left); + boost::optional getOverlappedLaneletId(const std::vector & lanes); std::vector cutOverlappedLanes( PathWithLaneId & path, const std::vector & lanes); diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 1b14741db4a56..7305405b50410 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1552,31 +1552,10 @@ void generateDrivableArea( } } -// calculate bounds from drivable lanes and hatched road markings -std::vector calcBound( - const std::shared_ptr route_handler, - const std::vector & drivable_lanes, - const bool enable_expanding_hatched_road_markings, const bool enable_expanding_intersection_areas, - const bool is_left) +std::vector getBoundWithHatchedRoadMarkings( + const std::vector & original_bound, + const std::shared_ptr & route_handler) { - // a function to convert drivable lanes to points without duplicated points - const auto convert_to_points = [&](const std::vector & drivable_lanes) { - constexpr double overlap_threshold = 0.01; - - std::vector points; - for (const auto & drivable_lane : drivable_lanes) { - const auto bound = - is_left ? drivable_lane.left_lane.leftBound3d() : drivable_lane.right_lane.rightBound3d(); - for (const auto & point : bound) { - if ( - points.empty() || - overlap_threshold < (points.back().basicPoint2d() - point.basicPoint2d()).norm()) { - points.push_back(point); - } - } - } - return points; - }; // a function to get polygon with a designated point id const auto get_corresponding_polygon_index = [&](const auto & polygon, const auto & target_point_id) { @@ -1589,18 +1568,89 @@ std::vector calcBound( // This means calculation has some errors. return polygon.size() - 1; }; + const auto mod = [&](const int a, const int b) { return (a + b) % b; // NOTE: consider negative value }; + + std::vector expanded_bound{}; + + std::optional current_polygon{std::nullopt}; + std::vector current_polygon_border_indices; + // expand drivable area by hatched road markings. + for (size_t bound_point_idx = 0; bound_point_idx < original_bound.size(); ++bound_point_idx) { + const auto & bound_point = original_bound[bound_point_idx]; + const auto polygon = getPolygonByPoint(route_handler, bound_point, "hatched_road_markings"); + + bool will_close_polygon{false}; + if (!current_polygon) { + if (!polygon) { + expanded_bound.push_back(bound_point); + } else { + // There is a new additional polygon to expand + current_polygon = polygon; + current_polygon_border_indices.push_back( + get_corresponding_polygon_index(*current_polygon, bound_point.id())); + } + } else { + if (!polygon) { + will_close_polygon = true; + } else { + current_polygon_border_indices.push_back( + get_corresponding_polygon_index(*current_polygon, bound_point.id())); + } + } + + if (bound_point_idx == original_bound.size() - 1 && current_polygon) { + // If drivable lanes ends earlier than polygon, close the polygon + will_close_polygon = true; + } + + if (will_close_polygon) { + // The current additional polygon ends to expand + if (current_polygon_border_indices.size() == 1) { + expanded_bound.push_back((*current_polygon)[current_polygon_border_indices.front()]); + } else { + const size_t current_polygon_points_num = current_polygon->size(); + const bool is_polygon_opposite_direction = [&]() { + const size_t modulo_diff = mod( + static_cast(current_polygon_border_indices[1]) - + static_cast(current_polygon_border_indices[0]), + current_polygon_points_num); + return modulo_diff == 1; + }(); + + const int target_points_num = + current_polygon_points_num - current_polygon_border_indices.size() + 1; + for (int poly_idx = 0; poly_idx <= target_points_num; ++poly_idx) { + const int target_poly_idx = current_polygon_border_indices.front() + + poly_idx * (is_polygon_opposite_direction ? -1 : 1); + expanded_bound.push_back( + (*current_polygon)[mod(target_poly_idx, current_polygon_points_num)]); + } + } + current_polygon = std::nullopt; + current_polygon_border_indices.clear(); + } + } + + return expanded_bound; +} + +std::vector getBoundWithIntersectionAreas( + const std::vector & original_bound, + const std::shared_ptr & route_handler, + const std::vector & drivable_lanes, const bool is_left) +{ const auto extract_bound_from_polygon = [](const auto & polygon, const auto start_idx, const auto end_idx, const auto clockwise) { - std::vector ret; + std::vector ret{}; for (size_t i = start_idx; i != end_idx; i = clockwise ? i + 1 : i - 1) { - ret.push_back(lanelet::utils::conversion::toGeomMsgPt(polygon[i])); + ret.push_back(polygon[i]); if (i + 1 == polygon.size() && clockwise) { if (end_idx == 0) { - ret.push_back(lanelet::utils::conversion::toGeomMsgPt(polygon[end_idx])); + ret.push_back(polygon[end_idx]); return ret; } i = 0; @@ -1609,7 +1659,7 @@ std::vector calcBound( if (i == 0 && !clockwise) { if (end_idx == polygon.size() - 1) { - ret.push_back(lanelet::utils::conversion::toGeomMsgPt(polygon[end_idx])); + ret.push_back(polygon[end_idx]); return ret; } i = polygon.size() - 1; @@ -1617,34 +1667,30 @@ std::vector calcBound( } } + ret.push_back(polygon[end_idx]); + return ret; }; - // If no need to expand with polygons, return here. - std::vector output_points; - const auto bound_points = convert_to_points(drivable_lanes); - if (!enable_expanding_hatched_road_markings && !enable_expanding_intersection_areas) { - for (const auto & point : bound_points) { - output_points.push_back(lanelet::utils::conversion::toGeomMsgPt(point)); - } - return motion_utils::removeOverlapPoints(output_points); - } + std::vector expanded_bound = original_bound; - std::optional current_polygon{std::nullopt}; - std::vector current_polygon_border_indices; + // expand drivable area by using intersection area. for (const auto & drivable_lane : drivable_lanes) { - // extract target lane and bound. - const auto bound_lane = is_left ? drivable_lane.left_lane : drivable_lane.right_lane; - const auto bound = is_left ? bound_lane.leftBound3d() : bound_lane.rightBound3d(); + const auto edge_lanelet = is_left ? drivable_lane.left_lane : drivable_lane.right_lane; + const auto lanelet_bound = is_left ? edge_lanelet.leftBound3d() : edge_lanelet.rightBound3d(); - if (bound.size() < 2) { + if (lanelet_bound.size() < 2) { continue; } - // expand drivable area by intersection areas. - const std::string id = bound_lane.attributeOr("intersection_area", "else"); - const auto use_intersection_area = enable_expanding_intersection_areas && id != "else"; - if (use_intersection_area) { + const std::string id = edge_lanelet.attributeOr("intersection_area", "else"); + if (id == "else") { + continue; + } + + // Step1. extract intersection partial bound. + std::vector intersection_bound{}; + { const auto polygon = route_handler->getLaneletMapPtr()->polygonLayer.get(std::atoi(id.c_str())); @@ -1652,96 +1698,137 @@ std::vector calcBound( boost::geometry::is_valid(lanelet::utils::to2D(polygon.basicPolygon())); const auto is_clockwise_iteration = is_clockwise_polygon ? is_left : !is_left; - const auto start_itr = std::find_if(polygon.begin(), polygon.end(), [&bound](const auto & p) { - return p.id() == bound.front().id(); - }); + const auto intersection_bound_itr_init = std::find_if( + polygon.begin(), polygon.end(), + [&lanelet_bound](const auto & p) { return p.id() == lanelet_bound.front().id(); }); - const auto end_itr = std::find_if(polygon.begin(), polygon.end(), [&bound](const auto & p) { - return p.id() == bound.back().id(); - }); + const auto intersection_bound_itr_last = std::find_if( + polygon.begin(), polygon.end(), + [&lanelet_bound](const auto & p) { return p.id() == lanelet_bound.back().id(); }); - if (start_itr == polygon.end() || end_itr == polygon.end()) { + if ( + intersection_bound_itr_init == polygon.end() || + intersection_bound_itr_last == polygon.end()) { continue; } // extract line strings between start_idx and end_idx. - const size_t start_idx = std::distance(polygon.begin(), start_itr); - const size_t end_idx = std::distance(polygon.begin(), end_itr); - for (const auto & point : - extract_bound_from_polygon(polygon, start_idx, end_idx, is_clockwise_iteration)) { - output_points.push_back(point); - } + const size_t start_idx = std::distance(polygon.begin(), intersection_bound_itr_init); + const size_t end_idx = std::distance(polygon.begin(), intersection_bound_itr_last); - continue; + intersection_bound = + extract_bound_from_polygon(polygon, start_idx, end_idx, is_clockwise_iteration); } - if (!enable_expanding_hatched_road_markings) { - for (const auto & point : bound) { - output_points.push_back(lanelet::utils::conversion::toGeomMsgPt(point)); - } + // Step2. check shared bound point. + const auto shared_point_itr_init = + std::find_if(expanded_bound.begin(), expanded_bound.end(), [&](const auto & p) { + return std::any_of( + intersection_bound.begin(), intersection_bound.end(), + [&](const auto & point) { return point.id() == p.id(); }); + }); + + const auto shared_point_itr_last = + std::find_if(expanded_bound.rbegin(), expanded_bound.rend(), [&](const auto & p) { + return std::any_of( + intersection_bound.begin(), intersection_bound.end(), + [&](const auto & point) { return point.id() == p.id(); }); + }); + + if ( + shared_point_itr_init == expanded_bound.end() || + shared_point_itr_last == expanded_bound.rend()) { continue; } - // expand drivable area by hatched road markings. - for (size_t bound_point_idx = 0; bound_point_idx < bound.size(); ++bound_point_idx) { - const auto & bound_point = bound[bound_point_idx]; - const auto polygon = getPolygonByPoint(route_handler, bound_point, "hatched_road_markings"); - - bool will_close_polygon{false}; - if (!current_polygon) { - if (!polygon) { - output_points.push_back(lanelet::utils::conversion::toGeomMsgPt(bound_point)); - } else { - // There is a new additional polygon to expand - current_polygon = polygon; - current_polygon_border_indices.push_back( - get_corresponding_polygon_index(*current_polygon, bound_point.id())); - } - } else { - if (!polygon) { - will_close_polygon = true; - } else { - current_polygon_border_indices.push_back( - get_corresponding_polygon_index(*current_polygon, bound_point.id())); - } - } + // Step3. overwrite duplicate drivable bound by intersection bound. + { + const auto trim_point_itr_init = std::find_if( + intersection_bound.begin(), intersection_bound.end(), + [&](const auto & p) { return p.id() == shared_point_itr_init->id(); }); - if (bound_point_idx == bound_points.size() - 1 && current_polygon) { - // If drivable lanes ends earlier than polygon, close the polygon - will_close_polygon = true; + const auto trim_point_itr_last = std::find_if( + intersection_bound.begin(), intersection_bound.end(), + [&](const auto & p) { return p.id() == shared_point_itr_last->id(); }); + + if ( + trim_point_itr_init == intersection_bound.end() || + trim_point_itr_last == intersection_bound.end()) { + continue; } - if (will_close_polygon) { - // The current additional polygon ends to expand - if (current_polygon_border_indices.size() == 1) { - output_points.push_back(lanelet::utils::conversion::toGeomMsgPt( - (*current_polygon)[current_polygon_border_indices.front()])); - } else { - const size_t current_polygon_points_num = current_polygon->size(); - const bool is_polygon_opposite_direction = [&]() { - const size_t modulo_diff = mod( - static_cast(current_polygon_border_indices[1]) - - static_cast(current_polygon_border_indices[0]), - current_polygon_points_num); - return modulo_diff == 1; - }(); + std::vector tmp_bound{}; - const int target_points_num = - current_polygon_points_num - current_polygon_border_indices.size() + 1; - for (int poly_idx = 0; poly_idx <= target_points_num; ++poly_idx) { - const int target_poly_idx = current_polygon_border_indices.front() + - poly_idx * (is_polygon_opposite_direction ? -1 : 1); - output_points.push_back(lanelet::utils::conversion::toGeomMsgPt( - (*current_polygon)[mod(target_poly_idx, current_polygon_points_num)])); - } + tmp_bound.insert(tmp_bound.end(), expanded_bound.begin(), shared_point_itr_init); + tmp_bound.insert(tmp_bound.end(), trim_point_itr_init, trim_point_itr_last); + tmp_bound.insert( + tmp_bound.end(), std::next(shared_point_itr_last).base(), expanded_bound.end()); + + expanded_bound = tmp_bound; + } + } + + return expanded_bound; +} + +// calculate bounds from drivable lanes and hatched road markings +std::vector calcBound( + const std::shared_ptr route_handler, + const std::vector & drivable_lanes, + const bool enable_expanding_hatched_road_markings, const bool enable_expanding_intersection_areas, + const bool is_left) +{ + // a function to convert drivable lanes to points without duplicated points + const auto convert_to_points = [&](const std::vector & drivable_lanes) { + constexpr double overlap_threshold = 0.01; + + std::vector points; + for (const auto & drivable_lane : drivable_lanes) { + const auto bound = + is_left ? drivable_lane.left_lane.leftBound3d() : drivable_lane.right_lane.rightBound3d(); + for (const auto & point : bound) { + if ( + points.empty() || + overlap_threshold < (points.back().basicPoint2d() - point.basicPoint2d()).norm()) { + points.push_back(point); } - current_polygon = std::nullopt; - current_polygon_border_indices.clear(); } } + return points; + }; + + const auto to_ros_point = [](const std::vector & bound) { + std::vector ret{}; + std::for_each(bound.begin(), bound.end(), [&](const auto & p) { + ret.push_back(lanelet::utils::conversion::toGeomMsgPt(p)); + }); + return ret; + }; + + // Step1. create drivable bound from drivable lanes. + std::vector bound_points = convert_to_points(drivable_lanes); + + // Step2. if there is no drivable area defined by polygon, return original drivable bound. + if (!enable_expanding_hatched_road_markings && !enable_expanding_intersection_areas) { + return motion_utils::removeOverlapPoints(to_ros_point(bound_points)); + } + + // Step3. if there are hatched road markings, expand drivable bound with the polygon. + if (enable_expanding_hatched_road_markings) { + bound_points = getBoundWithHatchedRoadMarkings(bound_points, route_handler); + } + + if (!enable_expanding_intersection_areas) { + return motion_utils::removeOverlapPoints(to_ros_point(bound_points)); } - return motion_utils::removeOverlapPoints(output_points); + // Step4. if there are intersection areas, expand drivable bound with the polygon. + { + bound_points = + getBoundWithIntersectionAreas(bound_points, route_handler, drivable_lanes, is_left); + } + + return motion_utils::removeOverlapPoints(to_ros_point(bound_points)); } void makeBoundLongitudinallyMonotonic( @@ -1915,19 +2002,27 @@ void makeBoundLongitudinallyMonotonic( return bound; } - std::vector ret; + std::vector ret = bound; + auto itr = std::next(ret.begin()); + while (std::next(itr) != ret.end()) { + const auto p1 = *std::prev(itr); + const auto p2 = *itr; + const auto p3 = *std::next(itr); - for (size_t i = 0; i < bound.size(); i++) { - const auto & p_new = bound.at(i); - const auto duplicated_points_itr = std::find_if( - ret.begin(), ret.end(), - [&p_new](const auto & p) { return calcDistance2d(p, p_new) < 0.1; }); + const std::vector vec_1to2 = {p2.x - p1.x, p2.y - p1.y, p2.z - p1.z}; + const std::vector vec_3to2 = {p2.x - p3.x, p2.y - p3.y, p2.z - p3.z}; + const auto product = + std::inner_product(vec_1to2.begin(), vec_1to2.end(), vec_3to2.begin(), 0.0); - if (duplicated_points_itr != ret.end() && std::next(duplicated_points_itr, 2) == ret.end()) { - ret.erase(duplicated_points_itr, ret.end()); - } + const auto dist_1to2 = tier4_autoware_utils::calcDistance3d(p1, p2); + const auto dist_3to2 = tier4_autoware_utils::calcDistance3d(p3, p2); - ret.push_back(p_new); + constexpr double epsilon = 1e-3; + if (std::cos(M_PI_4) < product / dist_1to2 / dist_3to2 + epsilon) { + itr = ret.erase(itr); + } else { + itr++; + } } return ret; From 1c2c59d0ab81400b692aa8d9c3b595be0222b9ab Mon Sep 17 00:00:00 2001 From: shulanbushangshu <102840938+shulanbushangshu@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:16:21 +0800 Subject: [PATCH 397/547] feat: add common interface utils test (#5173) * add interface utils test Signed-off-by: jack.song * style(pre-commit): autofix --------- Signed-off-by: jack.song Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../component_interface_utils/CMakeLists.txt | 20 +++++ common/component_interface_utils/package.xml | 1 + .../test/test_component_interface_utils.cpp | 76 +++++++++++++++++++ 3 files changed, 97 insertions(+) mode change 100644 => 100755 common/component_interface_utils/package.xml create mode 100644 common/component_interface_utils/test/test_component_interface_utils.cpp diff --git a/common/component_interface_utils/CMakeLists.txt b/common/component_interface_utils/CMakeLists.txt index d4f6343e381c7..435d5535e0a37 100644 --- a/common/component_interface_utils/CMakeLists.txt +++ b/common/component_interface_utils/CMakeLists.txt @@ -3,4 +3,24 @@ project(component_interface_utils) find_package(autoware_cmake REQUIRED) autoware_package() + +include_directories( + include + SYSTEM + ${rclcpp_INCLUDE_DIRS} + ${rosidl_runtime_cpp_INCLUDE_DIRS} + ${rcl_INCLUDE_DIRS} + ${autoware_adapi_v1_msgs_INCLUDE_DIRS} +) + +if(BUILD_TESTING) + ament_add_ros_isolated_gtest(test_component_interface_utils + test/test_component_interface_utils.cpp + ) + + target_include_directories(test_component_interface_utils + PRIVATE include + ) +endif() + ament_auto_package() diff --git a/common/component_interface_utils/package.xml b/common/component_interface_utils/package.xml old mode 100644 new mode 100755 index a1803a35cc1c9..dc82fda3f5c64 --- a/common/component_interface_utils/package.xml +++ b/common/component_interface_utils/package.xml @@ -19,6 +19,7 @@ rclcpp rclcpp_components + ament_cmake_ros ament_lint_auto autoware_lint_common diff --git a/common/component_interface_utils/test/test_component_interface_utils.cpp b/common/component_interface_utils/test/test_component_interface_utils.cpp new file mode 100644 index 0000000000000..3c41f4a85b4f9 --- /dev/null +++ b/common/component_interface_utils/test/test_component_interface_utils.cpp @@ -0,0 +1,76 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "component_interface_utils/rclcpp/exceptions.hpp" +#include "component_interface_utils/specs.hpp" +#include "component_interface_utils/status.hpp" +#include "gtest/gtest.h" + +TEST(interface, utils) +{ + { + using component_interface_utils::ServiceException; + using ResponseStatus = autoware_adapi_v1_msgs::msg::ResponseStatus; + using ResponseStatusCode = ResponseStatus::_code_type; + + ResponseStatusCode code = 10; + const std::string message = "test_exception"; + ServiceException service(code, message); + ResponseStatus code_back; + code_back = service.status(); + EXPECT_EQ(code_back.code, code); + EXPECT_EQ(code_back.message, message); + } + + { + using component_interface_utils::ServiceException; + using ResponseStatus = autoware_adapi_v1_msgs::msg::ResponseStatus; + using ResponseStatusCode = ResponseStatus::_code_type; + + ResponseStatusCode code = 10; + const std::string message = "test_exception"; + ServiceException service(code, message); + ResponseStatus code_set; + service.set(code_set); + EXPECT_EQ(code_set.code, code); + EXPECT_EQ(code_set.message, message); + } + + { + using component_interface_utils::ServiceException; + using ResponseStatus = autoware_adapi_v1_msgs::msg::ResponseStatus; + using ResponseStatusCode = ResponseStatus::_code_type; + using component_interface_utils::status::copy; + + class status_test + { + public: + status_test(ResponseStatusCode code, const std::string & message, bool success = false) + { + status.code = code; + status.message = message; + status.success = success; + } + ResponseStatus status; + }; + + const status_test status_in(10, "test_exception", true); + auto status_copy = std::make_shared(100, "test_exception_copy", false); + copy(&status_in, status_copy); + + EXPECT_EQ(status_in.status.code, status_copy->status.code); + EXPECT_EQ(status_in.status.message, status_copy->status.message); + EXPECT_EQ(status_in.status.success, status_copy->status.success); + } +} From 9aa2e5a8fdabe62c86dfec5d56de128f5e6ab6aa Mon Sep 17 00:00:00 2001 From: Yuki TAKAGI <141538661+yuki-takagi-66@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:56:46 +0900 Subject: [PATCH 398/547] feat(goal_planner): secure lateral distance for path planning with corresponding to the centrifugal force (#5196) * add feature to plan the path with considering latral overshoot Signed-off-by: Yuki Takagi * separate a function in "safety_check" in order to that "goal_planner_module" can handle the ego_polygons Signed-off-by: Yuki Takagi * add visualization codes fix the handling miss of sign of value Signed-off-by: Yuki Takagi * refactor codes based on comments in the PR Signed-off-by: Yuki Takagi --------- Signed-off-by: Yuki Takagi --- .../goal_planner/goal_planner_module.hpp | 1 + .../path_safety_checker/safety_check.hpp | 14 +++-- .../goal_planner/goal_planner_module.cpp | 63 ++++++++++++------- .../path_safety_checker/safety_check.cpp | 40 +++++++++--- 4 files changed, 82 insertions(+), 36 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index eb6ea5996cc6f..a82cff16756f8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -106,6 +106,7 @@ struct FreespacePlannerDebugData struct GoalPlannerDebugData { FreespacePlannerDebugData freespace_planner{}; + std::vector ego_polygons_expanded{}; }; class GoalPlannerModule : public SceneModuleInterface diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 9cc4afe22bdce..4983060aa1c0a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -110,6 +110,10 @@ bool checkCollision( const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, const double hysteresis_factor, CollisionCheckDebug & debug); +std::vector generatePolygonsWithStoppingAndInertialMargin( + const PathWithLaneId & ego_path, const double base_to_front, const double base_to_rear, + const double width, const double maximum_deceleration, const double max_extra_stopping_margin); + /** * @brief Iterate the points in the ego and target's predicted path and * perform safety check for each of the iterated points. @@ -133,14 +137,12 @@ std::vector getCollidedPolygons( const double hysteresis_factor, CollisionCheckDebug & debug); /** - * @brief Check collision between ego path footprints with extra longitudinal stopping margin and - * objects. + * @brief Check collision between ego polygons with margin and objects. * @return Has collision or not */ -bool checkCollisionWithExtraStoppingMargin( - const PathWithLaneId & ego_path, const PredictedObjects & dynamic_objects, - const double base_to_front, const double base_to_rear, const double width, - const double maximum_deceleration, const double margin, const double max_stopping_margin); +bool checkCollisionWithMargin( + const std::vector & ego_polygons, const PredictedObjects & dynamic_objects, + const double collision_check_margin); } // namespace behavior_path_planner::utils::path_safety_checker #endif // BEHAVIOR_PATH_PLANNER__UTILS__PATH_SAFETY_CHECKER__SAFETY_CHECK_HPP_ diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 574a1a38fc66a..ce88760212d90 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -1296,28 +1296,32 @@ bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const } } - if (parameters_->use_object_recognition) { - const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( - *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, - parameters_->forward_goal_search_length); - const auto [pull_over_lane_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets( - *(planner_data_->dynamic_object), pull_over_lanes); - const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( - pull_over_lane_objects, parameters_->th_moving_object_velocity); - const auto common_parameters = planner_data_->parameters; - const double base_link2front = common_parameters.base_link2front; - const double base_link2rear = common_parameters.base_link2rear; - const double vehicle_width = common_parameters.vehicle_width; - if (utils::path_safety_checker::checkCollisionWithExtraStoppingMargin( - path, pull_over_lane_stop_objects, base_link2front, base_link2rear, vehicle_width, - parameters_->maximum_deceleration, parameters_->object_recognition_collision_check_margin, - parameters_->object_recognition_collision_check_max_extra_stopping_margin)) { - return true; - } + if (!parameters_->use_object_recognition) { + return false; } - return false; + const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( + *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, + parameters_->forward_goal_search_length); + const auto [pull_over_lane_objects, others] = + utils::path_safety_checker::separateObjectsByLanelets( + *(planner_data_->dynamic_object), pull_over_lanes); + const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( + pull_over_lane_objects, parameters_->th_moving_object_velocity); + const auto common_parameters = planner_data_->parameters; + const double base_link2front = common_parameters.base_link2front; + const double base_link2rear = common_parameters.base_link2rear; + const double vehicle_width = common_parameters.vehicle_width; + + const auto ego_polygons_expanded = + utils::path_safety_checker::generatePolygonsWithStoppingAndInertialMargin( + path, base_link2front, base_link2rear, vehicle_width, parameters_->maximum_deceleration, + parameters_->object_recognition_collision_check_max_extra_stopping_margin); + debug_data_.ego_polygons_expanded = ego_polygons_expanded; + + return utils::path_safety_checker::checkCollisionWithMargin( + ego_polygons_expanded, pull_over_lane_stop_objects, + parameters_->object_recognition_collision_check_margin); } bool GoalPlannerModule::hasEnoughDistance(const PullOverPath & pull_over_path) const @@ -1686,6 +1690,24 @@ void GoalPlannerModule::setDebugData() add( createPathMarkerArray(partial_path, "partial_path_" + std::to_string(i), 0, 0.9, 0.5, 0.9)); } + + auto marker = tier4_autoware_utils::createDefaultMarker( + "map", rclcpp::Clock{RCL_ROS_TIME}.now(), "detection_polygons", 0, Marker::LINE_LIST, + tier4_autoware_utils::createMarkerScale(0.01, 0.0, 0.0), + tier4_autoware_utils::createMarkerColor(0.0, 0.0, 1.0, 0.999)); + + for (const auto & ego_polygon : debug_data_.ego_polygons_expanded) { + for (size_t ep_idx = 0; ep_idx < ego_polygon.outer().size(); ++ep_idx) { + const auto & current_point = ego_polygon.outer().at(ep_idx); + const auto & next_point = ego_polygon.outer().at((ep_idx + 1) % ego_polygon.outer().size()); + + marker.points.push_back( + tier4_autoware_utils::createPoint(current_point.x(), current_point.y(), 0.0)); + marker.points.push_back( + tier4_autoware_utils::createPoint(next_point.x(), next_point.y(), 0.0)); + } + } + debug_marker_.markers.push_back(marker); } // safety check if (parameters_->safety_check_params.enable_safety_check) { @@ -1695,7 +1717,6 @@ void GoalPlannerModule::setDebugData() add(createPredictedPathMarkerArray( ego_predicted_path, vehicle_info_, "ego_predicted_path_goal_planner", 0, 0.0, 0.5, 0.9)); } - if (goal_planner_data_.filtered_objects.objects.size() > 0) { add(createObjectsMarkerArray( goal_planner_data_.filtered_objects, "filtered_objects", 0, 0.0, 0.5, 0.9)); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 4e671f9f4b599..b44a3f841035d 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -364,27 +364,49 @@ std::vector getCollidedPolygons( return collided_polygons; } -bool checkCollisionWithExtraStoppingMargin( - const PathWithLaneId & ego_path, const PredictedObjects & dynamic_objects, - const double base_to_front, const double base_to_rear, const double width, - const double maximum_deceleration, const double collision_check_margin, - const double max_extra_stopping_margin) + +std::vector generatePolygonsWithStoppingAndInertialMargin( + const PathWithLaneId & ego_path, const double base_to_front, const double base_to_rear, + const double width, const double maximum_deceleration, const double max_extra_stopping_margin) { - for (const auto & p : ego_path.points) { + std::vector polygons; + const auto curvatures = motion_utils::calcCurvature(ego_path.points); + + for (size_t i = 0; i < ego_path.points.size(); ++i) { + const auto p = ego_path.points.at(i); + const double extra_stopping_margin = std::min( std::pow(p.point.longitudinal_velocity_mps, 2) * 0.5 / maximum_deceleration, max_extra_stopping_margin); + double extra_lateral_margin = (-1) * curvatures[i] * p.point.longitudinal_velocity_mps * + std::abs(p.point.longitudinal_velocity_mps); + extra_lateral_margin = + std::clamp(extra_lateral_margin, -extra_stopping_margin, extra_stopping_margin); + + const auto lateral_offset_pose = + tier4_autoware_utils::calcOffsetPose(p.point.pose, 0.0, extra_lateral_margin / 2.0, 0.0); const auto ego_polygon = tier4_autoware_utils::toFootprint( - p.point.pose, base_to_front + extra_stopping_margin, base_to_rear, width); + lateral_offset_pose, base_to_front + extra_stopping_margin, base_to_rear, + width + std::abs(extra_lateral_margin)); + polygons.push_back(ego_polygon); + } + return polygons; +} +bool checkCollisionWithMargin( + const std::vector & ego_polygons, const PredictedObjects & dynamic_objects, + const double collision_check_margin) +{ + for (const auto & ego_polygon : ego_polygons) { for (const auto & object : dynamic_objects.objects) { const auto obj_polygon = tier4_autoware_utils::toPolygon2d(object); const double distance = boost::geometry::distance(obj_polygon, ego_polygon); - if (distance < collision_check_margin) return true; + if (distance < collision_check_margin) { + return true; + } } } - return false; } } // namespace behavior_path_planner::utils::path_safety_checker From 68544c73bffca2b5acbaab299303f64653b6be8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2E=20Fatih=20C=C4=B1r=C4=B1t?= Date: Tue, 3 Oct 2023 12:13:21 +0300 Subject: [PATCH 399/547] ci(build-and-test-differential): remove sync for the build-and-test-differential workflows (#5200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: M. Fatih Cırıt --- .github/sync-files.yaml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/sync-files.yaml b/.github/sync-files.yaml index 4a1f20cada359..f9cb3065f12a7 100644 --- a/.github/sync-files.yaml +++ b/.github/sync-files.yaml @@ -42,20 +42,6 @@ - \"\" - -cuda include:" {source} - - source: .github/workflows/build-and-test-differential.yaml - pre-commands: | - sd "container: ros:(\w+)" "container: ghcr.io/autowarefoundation/autoware-universe:\$1-latest" {source} - - sd -s 'container: ${{ matrix.container }}' 'container: ${{ matrix.container }}${{ matrix.container-suffix }}' {source} - sd -- \ - " include:" \ - " container-suffix: - - \"\" - - -cuda - include:" {source} - - sd "^ container: ghcr.io/autowarefoundation/autoware-universe:(\w+)-latest\$" \ - " container: ghcr.io/autowarefoundation/autoware-universe:\$1-latest-cuda" {source} - source: .github/workflows/build-and-test-differential-self-hosted.yaml pre-commands: | sd "container: ros:(\w+)" "container: ghcr.io/autowarefoundation/autoware-universe:\$1-latest" {source} From ac06a5c2e05bc166478d1ff5e0d730471089a41c Mon Sep 17 00:00:00 2001 From: "awf-autoware-bot[bot]" <94889083+awf-autoware-bot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 09:24:59 +0000 Subject: [PATCH 400/547] chore: sync files (#4372) Signed-off-by: GitHub Co-authored-by: xmfcx --- mkdocs.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mkdocs.yaml b/mkdocs.yaml index 2ca8b3c86ad43..a72529fbe959a 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -55,8 +55,7 @@ plugins: regex: - ^(?!(.*/)?assets/).*\.(?!(.*\.)?md|(.*\.)?svg|(.*\.)?png|(.*\.)?gif|(.*\.)?jpg).*$ - ^(.*/)?[^.]*$ - - macros: - module_name: mkdocs_macros + - macros - mkdocs-video - same-dir - search From 8a0ff1cfaa2c5a8afd47a280bd67db435f799146 Mon Sep 17 00:00:00 2001 From: Ryohsuke Mitsudome <43976834+mitsudome-r@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:01:42 +0900 Subject: [PATCH 401/547] revert: "chore: sync files (#4372)" (#5213) Signed-off-by: Ryohsuke Mitsudome --- mkdocs.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mkdocs.yaml b/mkdocs.yaml index a72529fbe959a..2ca8b3c86ad43 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -55,7 +55,8 @@ plugins: regex: - ^(?!(.*/)?assets/).*\.(?!(.*\.)?md|(.*\.)?svg|(.*\.)?png|(.*\.)?gif|(.*\.)?jpg).*$ - ^(.*/)?[^.]*$ - - macros + - macros: + module_name: mkdocs_macros - mkdocs-video - same-dir - search From 6155f1ff63b95492f5d62250bda03334efe416c7 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 4 Oct 2023 09:49:56 +0900 Subject: [PATCH 402/547] feat(intersection): aggressively peek into attention area if traffic light does not exist (#5192) Signed-off-by: Mamoru Sobue --- .../README.md | 93 +- .../config/intersection.param.yaml | 3 + .../docs/data-structure.drawio.svg | 771 ++++++++++++++++ .../intersection-attention-ll-rr.drawio.svg | 134 ++- .../intersection-attention-lr-rl.drawio.svg | 832 ++++++++++++++---- ...intersection-attention-straight.drawio.svg | 152 +++- .../docs/intersection-attention.drawio.svg | 348 ++++++-- .../docs/occlusion-without-tl.drawio.svg | 518 +++++++++++ .../docs/stuck-vehicle.drawio.svg | 42 +- .../package.xml | 4 - .../src/debug.cpp | 12 +- .../src/manager.cpp | 20 +- .../src/scene_intersection.cpp | 337 ++++--- .../src/scene_intersection.hpp | 45 +- .../src/util.cpp | 2 +- .../src/util.hpp | 8 - .../src/util_type.hpp | 1 + 17 files changed, 2803 insertions(+), 519 deletions(-) create mode 100644 planning/behavior_velocity_intersection_module/docs/data-structure.drawio.svg create mode 100644 planning/behavior_velocity_intersection_module/docs/occlusion-without-tl.drawio.svg diff --git a/planning/behavior_velocity_intersection_module/README.md b/planning/behavior_velocity_intersection_module/README.md index e8a823a7e790e..16459bb64074b 100644 --- a/planning/behavior_velocity_intersection_module/README.md +++ b/planning/behavior_velocity_intersection_module/README.md @@ -24,7 +24,7 @@ This module is activated when the path contains the lanes with `turn_direction` ### Attention area -The `Attention Area` in the intersection are defined as the set of lanes that are conflicting with ego vehicle's path and their preceding lanes up to `attention_area_length` meters. `RightOfWay` tag is used to rule out the lanes that each lane has priority given the traffic light relation and `turn_direction` priority. +The `attention area` in the intersection are defined as the set of lanes that are conflicting with ego vehicle's path and their preceding lanes up to `common.attention_area_length` meters. `RightOfWay` tag is used to rule out the lanes that each lane has priority given the traffic light relation and `turn_direction` priority(`yield lane`). `Intersection Area`, which is supposed to be defined on the HDMap, is an area converting the entire intersection. @@ -54,10 +54,10 @@ For [stuck vehicle detection](#stuck-vehicle-detection) and [collision detection Objects that satisfy all of the following conditions are considered as target objects (possible collision objects): -- The center of mass of the object is **within a certain distance** from the attention lane (threshold = `attention_area_margin`) . +- The center of mass of the object is **within a certain distance** from the attention lane (threshold = `common.attention_area_margin`) . - (Optional condition) The center of gravity is in the **intersection area**. - To deal with objects that is in the area not covered by the lanelets in the intersection. -- The posture of object is **the same direction as the attention lane** (threshold = `attention_area_angle_threshold`). +- The posture of object is **the same direction as the attention lane** (threshold = `common.attention_area_angle_threshold`). - Not being **in the adjacent lanes of the ego vehicle**. #### Stuck Vehicle Detection @@ -68,31 +68,31 @@ If there is any object on the path in inside the intersection and at the exit of #### Collision detection -The following process is performed for the targets objects to determine whether the ego vehicle can pass the intersection safely. If it is judged that the ego vehicle cannot pass through the intersection with enough _margin_, it will insert a stopline on the _path_. +The following process is performed for the targets objects to determine whether the ego vehicle can pass the intersection safely. If it is judged that the ego vehicle cannot pass through the intersection with enough margin, this module inserts a stopline on the path. 1. calculate the time interval that the ego vehicle is in the intersection. This time is set as $t_s$ ~ $t_e$ -2. extract the predicted path of the target object whose confidence is greater than `min_predicted_path_confidence`. +2. extract the predicted path of the target object whose confidence is greater than `collision_detection.min_predicted_path_confidence`. 3. detect collision between the extracted predicted path and ego's predicted path in the following process. 1. obtain the passing area of the ego vehicle $A_{ego}$ in $t_s$ ~ $t_e$. - 2. calculate the passing area of the target object $A_{target}$ at $t_s$ - `collision_start_margin_time` ~ $t_e$ + `collision_end_margin_time` for each predicted path (\*1). + 2. calculate the passing area of the target object $A_{target}$ at $t_s$ - `collision_detection.collision_start_margin_time` ~ $t_e$ + `collision_detection.collision_end_margin_time` for each predicted path (\*1). 3. check if $A_{ego}$ and $A_{target}$ polygons are overlapped (has collision). 4. when a collision is detected, the module inserts a stopline. 5. If ego is over the `pass_judge_line`, collision checking is not processed to avoid sudden braking and/or unnecessary stop in the middle of the intersection. -The parameters `collision_start_margin_time` and `collision_end_margin_time` can be interpreted as follows: +The parameters `collision_detection.collision_start_margin_time` and `collision_detection.collision_end_margin_time` can be interpreted as follows: -- If the ego vehicle was to pass through the intersection earlier than the target object, collision would be detected if the time difference between the two was less than `collision_start_margin_time`. -- If the ego vehicle was to pass through the intersection later than the target object, collision would be detected if the time difference between the two was less than `collision_end_margin_time`. +- If the ego vehicle was to pass through the intersection earlier than the target object, collision would be detected if the time difference between the two was less than `collision_detection.collision_start_margin_time`. +- If the ego vehicle was to pass through the intersection later than the target object, collision would be detected if the time difference between the two was less than `collision_detection.collision_end_margin_time`. -If collision is detected, the state transits to "STOP" immediately. On the other hand, the state does not transit to "GO" unless _safe_ judgement continues for a certain period to prevent the chattering of decisions. +If collision is detected, the state transits to "STOP" immediately. On the other hand, the state does not transit to "GO" unless safe judgement continues for a certain period `collision_detection.state_transit_margin` to prevent the chattering of decisions. #### Stop Line Automatic Generation -If a stopline is associated with the intersection lane, that line is used as the stopline for collision detection. Otherwise the path is interpolated at a certain intervals (=`path_interpolation_ds`), and the point which is `stop_line_margin` meters behind the attention lane is defined as the position of the stop line for the vehicle front. +If a stopline is associated with the intersection lane on the map, that line is used as the stopline for collision detection. Otherwise the path is interpolated at a certain intervals (=`common.path_interpolation_ds`), and the point which is `stop_line_margin` meters behind the attention area is defined as the position of the stop line for the vehicle front. #### Pass Judge Line -To avoid a rapid braking, if deceleration and jerk more than a threshold (`behavior_velocity_planner.max_accel` and `behavior_velocity_planner.max_jerk`) are needed to stop just in front of the attention area, this module does not insert stopline after passing the default stopline position. +To avoid sudden braking, if deceleration and jerk more than a threshold (`behavior_velocity_planner.max_accel` and `behavior_velocity_planner.max_jerk`) is required to stop just in front of the attention area, this module does not insert stopline after passing the default stopline position. ### Occlusion detection @@ -104,6 +104,66 @@ The occlusion is detected as the common area of occlusion attention area(which i If the nearest occlusion cell value is below the threshold, occlusion is detected. It is expected that the occlusion gets cleared as the vehicle approaches the occlusion peeking stop line. +In there are no traffic lights associated with the lane, the ego vehicle will make a brief stop at the _default stop line_ and the position where the vehicle heading touches the attention area for the first time(which is denoted as _first attention stop line_). After stopping at the _first attention area stop line_ this module inserts `occlusion.absence_traffic_light.creep_velocity` velocity between ego and the position which is `occlusion.absence_traffic_light.maximum_peeking_distance` ahead of _first attention area stop line_ while occlusion is not cleared. If collision is detected, ego will instantly stop. Once the occlusion is cleared or ego passed `occlusion.absence_traffic_light.maximum_peeking_distance` this module does not detect collision and occlusion because ego vehicle is already inside the intersection. + +![occlusion_detection](./docs/occlusion-without-tl.drawio.svg) + +### Data Structure + +#### `IntersectionLanelets` + +```plantuml +@startuml +entity IntersectionLanelets { + * conflicting lanes/area + -- + * first conflicting area + The conflicting lane area which the path intersects first + -- + * attention lanes/area + -- + * first attention lane area + The attention lane area which the path intersects first + -- + * occlusion attention lanes/area + Part of attention lanes/area for occlusion detection + -- + * is_priortized: bool + If ego vehicle has priority in current traffic light context +} +@enduml +``` + +#### `IntersectionStopLines` + +Each stop lines are generated from interpolated path points to obtain precise positions. + +```plantuml +@startuml +entity IntersectionStopLines { + * closest_idx: size_t + closest path point index for ego vehicle + -- + * stuck_stop_line: size_t + stop line index on stuck vehicle detection + -- + * default_stop_line: size_t + If defined on the map, its index on the path. Otherwise generated before first_attention_stop_line + -- + * first_attention_stop_line + The index of the first path point which is inside the attention area + -- + * occlusion_peeking_stop_line + The index of the path point for the peeking limit position + -- + * pass_judge_line + The index of the path point which is before first_attention_stop_line/occlusion_peeking_stop_line by braking distance +} +@enduml +``` + +![data structure](./docs/data-structure.drawio.svg) + ### Module Parameters | Parameter | Type | Description | @@ -115,7 +175,6 @@ If the nearest occlusion cell value is below the threshold, occlusion is detecte | `common.intersection_velocity` | double | [m/s] velocity profile for pass judge calculation | | `common.intersection_max_accel` | double | [m/s^2] acceleration profile for pass judge calculation | | `common.stop_overshoot_margin` | double | [m] margin for the overshoot from stopline | -| `common.path_interpolation_ds` | double | [m] path interpolation interval | | `stuck_vehicle.stuck_vehicle_detect_dist` | double | [m] length toward from the exit of intersection for stuck vehicle detection | | `stuck_vehicle.stuck_vehicle_vel_thr` | double | [m/s] velocity threshold for stuck vehicle detection | | `collision_detection.state_transit_margin_time` | double | [m] time margin to change state | @@ -128,7 +187,13 @@ If the nearest occlusion cell value is below the threshold, occlusion is detecte | `occlusion.peeking_offset` | double | [m] the offset of the front of the vehicle into the attention area for peeking to occlusion | | `occlusion.min_vehicle_brake_for_rss` | double | [m/s] assumed minimum brake of the vehicle running from behind the occlusion | | `occlusion.max_vehicle_velocity_for_rss` | double | [m/s] assumed maximum velocity of the vehicle running from behind the occlusion | -| `occlusion.denoise_kernel` | double | [m] the window size of morphology process for clearing noisy occlusion | + +#### For developers only + +| Parameter | Type | Description | +| ------------------------------ | ------ | ---------------------------------------------------------------------- | +| `common.path_interpolation_ds` | double | [m] path interpolation interval | +| `occlusion.denoise_kernel` | double | [m] the window size of morphology process for clearing noisy occlusion | ### How to turn parameters diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 4042adeb3623e..5e74e837bf186 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -54,6 +54,9 @@ ignore_parked_vehicle_speed_threshold: 0.8333 # == 3.0km/h stop_release_margin_time: 1.5 # [s] temporal_stop_before_attention_area: false + absence_traffic_light: + creep_velocity: 1.388 # [m/s] + maximum_peeking_distance: 6.0 # [m] enable_rtc: intersection: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/behavior_velocity_intersection_module/docs/data-structure.drawio.svg b/planning/behavior_velocity_intersection_module/docs/data-structure.drawio.svg new file mode 100644 index 0000000000000..fb512902ef856 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/docs/data-structure.drawio.svg @@ -0,0 +1,771 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + closest_idx + +
    +
    +
    +
    + closest_idx +
    +
    + + + + + + +
    +
    +
    + + stuck_stop_line + +
    +
    +
    +
    + stuck_stop_line +
    +
    + + + + + + + + + +
    +
    +
    + + default_stop_line + +
    +
    +
    +
    + default_stop_line +
    +
    + + + + + + +
    +
    +
    + + first_attention_stop_line + +
    +
    +
    +
    + first_attention_stop_line +
    +
    + + + + + + +
    +
    +
    + + + occlusion_peeking +
    + _stop_line +
    +
    +
    +
    +
    +
    + occlusion_peeking... +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + next + + +
    +
    +
    +
    + next +
    +
    + + + + + + +
    +
    +
    + + + prev + + +
    +
    +
    +
    + prev +
    +
    + + + + + + +
    +
    +
    + + + ego_or_entry2exit + + +
    +
    +
    +
    + ego_or_entry2exit +
    +
    + + + + + + +
    +
    +
    + + + entry2ego + + +
    +
    +
    +
    + entry2ego +
    +
    + + + + + +
    +
    +
    + + IntersectionStopLines + +
    +
    +
    +
    + IntersectionStopLines +
    +
    + + + +
    +
    +
    + + PathLanelets + +
    +
    +
    +
    + PathLanelets +
    +
    +
    + + + + Text is not SVG - cannot display + + +
    diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg index d4d709eee6b99..51e926fe266d6 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention-ll-rr.drawio.svg @@ -8,12 +8,48 @@ width="3723px" height="2401px" viewBox="-0.5 -0.5 3723 2401" - content="<mxfile host="Electron" modified="2023-06-06T10:28:31.375Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="M_P_fA_OZlKNZHzEOQxb" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + content="<mxfile host="Electron" modified="2023-10-03T02:44:21.616Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="wkqIuFIE5UyEHdVd8sjR" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > + + + + + + + + + - + @@ -51,7 +87,7 @@ stroke-miterlimit="10" pointer-events="none" /> - + - + - + yield lane - - + + @@ -749,10 +785,10 @@ - - + + - + - attention area + attention lane

i-A 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

- attention area + attention lane @@ -1141,12 +1177,12 @@
- attention area + attention lane
- attention area + attention lane @@ -1226,9 +1262,9 @@ stroke-miterlimit="10" pointer-events="none" /> - + + + + + + + + + + + + + + + + diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg index 2d590b9fd51ca..25a7c6b519f96 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention-lr-rl.drawio.svg @@ -5,10 +5,10 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - width="3729px" + width="3727px" height="2651px" - viewBox="-0.5 -0.5 3729 2651" - content="<mxfile host="Electron" modified="2023-06-06T10:27:24.636Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="EZYouzMJcjefpLDW8VVp" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + viewBox="-0.5 -0.5 3727 2651" + content="<mxfile host="Electron" modified="2023-10-03T03:32:56.314Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="aYRnTHIqVcSkLQHKGcXP" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > @@ -25,13 +25,13 @@ transform="rotate(-130,954.7,670.3)" pointer-events="none" /> - - - + + + - + - - - + + + @@ -50,8 +50,7 @@ - - + @@ -88,9 +87,9 @@ stroke-miterlimit="10" pointer-events="none" /> - + - + - + - + - + - + - - - + + + - + - + - + - - - - - + + + + + - + - + - - - + + + - + - + - + - - + + - + - + - + - + @@ -436,8 +435,8 @@ ego lane - - + + @@ -474,16 +473,16 @@
- attention area + attention lane
- attention area + attention lane
- - + + @@ -548,12 +547,12 @@ transform="rotate(225,2890.14,695.64)" pointer-events="none" /> - + - - + + - attention area + attention lane - attention area + attention lane - - + + - - - + + + - - + + - + @@ -753,13 +752,13 @@ transform="rotate(-130,954.7,1832.3)" pointer-events="none" /> - - - + + + - + - - - + + + @@ -779,7 +778,7 @@ - + - + - + - + - + - + - + - - - + + + - - - - - - - - + - - + + - - + + - + - + - - - + + + - + - + - + - - + + - + - + - + - + - + @@ -1150,8 +1118,8 @@ ego lane - - + + @@ -1186,16 +1154,16 @@
- attention area + attention lane
- attention area + attention lane
- - + + @@ -1224,12 +1192,12 @@ transform="rotate(225,2890.14,1857.64)" pointer-events="none" /> - + - - + + - attention area + attention lane - attention area + attention lane - - - - - + + + + + - - + + - + @@ -1376,6 +1344,494 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg index d33674a257d9b..640eba618fa49 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention-straight.drawio.svg @@ -5,41 +5,42 @@ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" - width="3389px" - height="1411px" - viewBox="-0.5 -0.5 3389 1411" - content="<mxfile host="Electron" modified="2023-06-06T10:31:30.247Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="U1JUqxOsfTr30viw8iHe" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + width="3390px" + height="1412px" + viewBox="-0.5 -0.5 3390 1412" + content="<mxfile host="Electron" modified="2023-10-03T04:48:10.725Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="02kGXKyoYgRU_RdR3ghD" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > + - + + - - + - + - + @@ -390,7 +391,7 @@ - + - + - + - + - + - + - - - + + + - + attention area - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg b/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg index 3e9826fd84ad6..57552f586e63b 100644 --- a/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/intersection-attention.drawio.svg @@ -6,9 +6,9 @@ xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="3707px" - height="2193px" - viewBox="-0.5 -0.5 3707 2193" - content="<mxfile host="Electron" modified="2023-06-06T08:17:01.045Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="0BpzzxhIQKLXKpqq79kr" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" + height="2195px" + viewBox="-0.5 -0.5 3707 2195" + content="<mxfile host="Electron" modified="2023-10-03T05:15:42.124Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="WBrLRHcQvF5FOJ4jcZAE" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > @@ -23,7 +23,7 @@ pointer-events="none" /> - + - + - - + + - + - + - - - + + + @@ -139,7 +139,7 @@ - + @@ -176,9 +176,9 @@ stroke-miterlimit="10" pointer-events="none" /> - + - + - + - + - + - + - - - + + + - + - + - + - - + + - - + + - + - + - - - + + + - + - + - + + + + + + + @@ -589,16 +613,16 @@
- attention area + attention lane
- attention area + attention lane
- - + + @@ -654,7 +678,7 @@ stroke-miterlimit="10" pointer-events="none" /> - + - + - - + + - attention area + attention lane - attention area + attention lane - - + + @@ -875,7 +899,7 @@ stroke-miterlimit="10" pointer-events="none" /> - + @@ -990,10 +1014,10 @@ stroke-miterlimit="10" pointer-events="none" /> - - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ attention area +
+
+
+
+ attention area +
+
+ + + + + + +
+
+
+ attention area +
+
+
+
+ attention area +
+
+ + diff --git a/planning/behavior_velocity_intersection_module/docs/occlusion-without-tl.drawio.svg b/planning/behavior_velocity_intersection_module/docs/occlusion-without-tl.drawio.svg new file mode 100644 index 0000000000000..2fc22c8a4a401 --- /dev/null +++ b/planning/behavior_velocity_intersection_module/docs/occlusion-without-tl.drawio.svg @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + default stop +
+ line +
+
+
+
+
+
+
+
+ default stop... +
+
+ + + + + + +
+
+
+ + occlusion + +
+
+
+
+ occlusion +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + first attention +
+ stop line +
+
+
+
+
+
+
+
+ first attentio... +
+
+ + + + + + + +
+ + + + Text is not SVG - cannot display + + +
diff --git a/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg b/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg index 8802dc786b617..c29cb7ade21cd 100644 --- a/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg +++ b/planning/behavior_velocity_intersection_module/docs/stuck-vehicle.drawio.svg @@ -8,7 +8,7 @@ width="2842px" height="1201px" viewBox="-0.5 -0.5 2842 1201" - content="<mxfile host="Electron" modified="2023-08-28T08:00:31.600Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="CbgId97oWJwBUVWni98L" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">7V1rc9s21v41nul+MAd3gB8TJ267230nu912237JyLJsq1EsV5Jz2V//AiRB4sareJOjtJ1GIAVSOM85OHdc4KuPX77fLZ4e/rm9XW0uELj9coHfXCCEIEY4QvJvavBrOhgTQtKR+936Nh2DxcDP6/+tskGQjT6vb1d768bDdrs5rJ/sweX28XG1PFhji91u+9m+7W67sZ/6tLhfeQM/LxcbPap/gRr/7/r28JCOC8SL8R9W6/sH/WzI4vTKx4W+OZt6/7C43X5Oh5Kfh99e4KvddntI//bxy9Vqo5ZQr8yvV9+Tr3/9cvufV7dP19dXb37/44fry3Rhrtt8Jf9pu9XjofPUV89vf3n6/PbygV79+f7dl5/f/veP15c0nfrTYvOcrVj2Ww9f9RLuts+Ptys1CbjArz8/rA+rn58WS3X1swSOHHs4fNzIT1D+9W692VxtN9td8l38OvlHju+2h8VhvX2Uw5dQqInuto8H4870jxzfH3bbD6vQHFv51PVBIZGqCW7XO4mYdM7Pq71cmNcN1ylbz0+r3WH1xSBxtm7fr7YfV4fdV3lLdvUScowzRGeccEmZyNbqcwEriGDGHw8GplgcxRrTGZ7v82cUFJN/yYjWgoCinoCrx9tXipXkp8ft48omWLrcmjNw60Vc3VoM6C+hsToU+Gujx3arjQTIJ5uZQ6uVPeHddi1fr45CsT3Pfvu8W66yr5qs4s3GYuTMxgAR9myHxe5+dfBmk2u9+Grc9qRu2Fe9uv8w+erCgUY6bQGUfIm7Yyc+YyfHDnQIgBk4AjvubADz4bDjvTrlw2NHb/Bn8DAIUY+ChyJntiEFD/XAM4rggXoH7g88t4v9Q6KkwKAOca3+OWGMcSpckUJg5AiVFijz56PQEXglKOsNA+gsQLQA8bZ/LcK7CRB3NoDZcALEf3UWUTKCDMGt8LPcLPb79dKGkCU0FB5+zr673R0etvfbx8XmbTH6erW5SeZShsNa2piepMlAaltMLweVFNKuqKRCeLMBZ7b+UOm/OiK0/Rs6XxoIx6Qex32b4PEpGuDa3C4AJDIAmQY4AHFEsc8XHEWMlHPBUTY4nMCLcpIkBHi2JGS9ayMvXCNl1BPo7jwtlBYY+7ONrI/y8Zn4RF2hKHZoRXCAjRHgPv6GdIWiBhSs0whtCpcTwGZ34pNk9WV9+E1rlvLvv6spI5p9evMle0Ly4av+8CiX4Tfzg/Et9bH4WvJJf68drVOubKCRpAzX4Mb5CCUhmANNcIQlJbA7GwTN/Hh9CSXUzr1/hnSNhlYPaTo3SPOYRHFMXVgTFBFOYv2nM8aD0zNAcQQa2md9YR23cwqesV4d0q2F+tyQLs0DLwoGHc93c2BzzwfAgBgZ0O08nOMAGnQBNJxWePOmwpvPDNIMcdfixcIxa1rIaoTc2Qge10iiDTwdZzPZMpOFSzPmxoJbIMCbTe7UZFwEnB0lrXMLuIMAijr70Zng/mwjI+DsKKkEVpWjhIFsHttRwnz8DekooQ2syjMFwxSk2ukxqauLtkvdOquWNZxQbyyJmW0rIdVSh1d6US0xdWYbeFthZ+9tP5Buai3RuVlLUtoiV9oyWninuoSVkKcthWYcGNqigbS+l9h9uvBDvEdubnllxeJGPwmEqWwYGZhEcmtG0lLBlHM3UYwwFJGYUYiT5FPsbYeEYOv72qdqggYLHglaTOKKrt52StLAq6jzeX5a3Kw277b7dUaAm+3hsP0oiaBveLVZ36sLh62jAO0fFk9qso9f7lUtTXSzkLIpklSTys7j8u1ms37arxLBI4dePd4nTwaRkFJcQMwJgDiW/yWy4ba4DiHAjFPAY84ZJbG8vtgttdwCkZSZjZQvSHx5mFlmXdSrapA31q80aTJkYR9JUEAfOpDHQ2GlgcOub714joSBuJYyiAUo46bM90eYBrl+vRsscVPSSJa/Xa8Kuya7a0yKXTJbfUN63zNJxgOb92Ak4z2kZ9okVaZjVmCIC9KYjqTGRmRXVU2+8PV6o18vt3siDoSlKKLEpq1QFeWHd6vdWi71aleqBlZio1YP1NK1Vg/UUm8meiByokAw7uguxdyeyJ1nYKWPN8jrXD7vPhX5x1Ve026WDQ6wTSkrRBwimx3UDl3HEPKTi+SCLxCmNl+w9nxR8Ny0XDIvvzKzsc078giEDrOhYerUHFZkIxSo8WNDVcOxnMEfzOEP1IE/+vKkjcVJeM6cBElfrDTyfqMTaOudDON7FEQcR3EsAJf/QobcZKaYqssUZH/8EIn8UsRE8f1AvIQjyT40T4gSQ/kTWAN/wnSmSHebI4dPc5sDS6pJmKt/BeZuaBoSEomCILEmmkFWLOnOA3YkH8ooYQ0M/OmcQZx6/h8Ja46J4DjGlMfU9f8g0QxAhM4QPxDQiAACcAwwFRBD29EIgYgApzEDMSNQIsm3aKmcIStXdnYGHjE+FIam8EWobgTzo6DEpKRgLpaJHSulUgAAI0VVePQLeiQGY37ewAs/tQ3W0h2RK5ICEEuRhHE8lANCw6RWIdTCtlYhZPMyrSCENqptZ6hQqM51FcA7xlwhgxW8g6sfMnS8qoEqOSfXRUvLx2Qbx/4axzsxAAvNy6aCtArchDro7sZBmFQ9hLos1IiD2vo1YId3GNTrIRqYJ8NkUbRwfIyRVWG76ntnTHyajInjKrwy2AtjkmqmYF0YswN/8H+Jzzc//sn+/uHd0+bD7pfFX29+u+zKHqmt1pxrqnOa018eZKEgXBpDdzJciSpLAPF+BL5glhcJ2NWZ2HIigWY9s3rzvjWwCueT4sMRINbaXdKYI7l8FVk9OGZGVo+01wMdGhQMpGarJ3GDCb2ZcrhBbO3FJ/VcjpDUk8O6uVeH297d/PN0aT14gvYsabb73IhjMz2KfdKMm9eDGxQTDdI4Z5K0ng7M5PgeoM6NmCytRzdhmqETbdKU7UqC15oTmg9qzQl940zMCQQcYe963ZuqdojaEyE4bvBUtEsWOAWz2cpUmwvOm/YmwPNqTuDiHHc1YZgzz8g5AqJBjc0cHLvHFJSpPDZqwxwRVAP0cB5bJ99u/zJ/Xl2WdFPMQovsKvOdrjPYzbzpyUeLofMcNIIfNp6gq+flKfaEhMTtsUVFoMJ2ip6QcQ9KQZe9e4LE9nYErpVtGv21si2el2wLtrWEbkZCm34dXksjNHJby3gCE/skBRHgLqmoCFSkTSKIJiuNHiF5pY1k7FtMNVXB4nmZ3arvvtcvNybdxRQl3mzxyGKqQUuSibKXOfaKyKWwiCiIdeqi70blhERI5LmNoQYvTk4zc0rS+6udbJD2NpmP9QhnqiihcLnzm8IIs9hpmalzUjiJOMopSvxkRQxoBANtXgbLV9TCYp5BrlCycuf85EuIR8hQbo2YPMaZu9+JyfaEziQlmU5RkzBKhKs5yXISsToSYTEqFzdwBLzItjbdErBEQ9VNQ76+y828kosFdUQKi4N4bavExc7RGhBWzzt08fOxXSLHqHeWipcNaRh3chN38Fb3zQ5ayatnBzArdmDAVq0ZtRRr4pxb1jjM4mQ6MIKrph2aGRrYNnOKukjGsNtZQNG+mqRzcn6noEvjbQOc5raR+8TzWAnuZdvAzrwqn73DttE2JENj57Es+HP6itBcf/n3r+ubP8mnffyPq4+72+c///fusoF15dpGe2lD7aPDbvG4f9ruUlU4Wi5275HNosnPX+3eflqpVQiq1ABQsLoLMTAA8M3rK8N0a2nbLbJPy9Vjwo2W+i1xodDq+mbvkj+D+lelZHcDPSjgXw1lleZZT8co5yv2+h+/XsW/3PzxdRF/+vHX9eKnyzYYOCT/qzOc9ql4BRFAQZe4DYLl7epG3NRbUayHLOEq0sRc1c471GEMRIHcTQFjn0JMSo2BSIR6ZFPcmk1XFBIQ3mdRlsLfF5s65J4N18aRiwwe52NWfngU6PPI2WDI6LsO2yFAHfPeieVquQxB40ZQQgdmWYFJpLOoC5blJGJ+mwVKRAQDclU3ke6dMiGXB9sov8+NRR/21/P2kOH6MhWdr+QNiD59SVZPX5d/u8/+v9H3HzXR/vC8/JAs+cN6KedA4Faqrim7yfXYrRbqJ0oFBtyvHle7xUFiCIHk4uFhldBULq66UwEsH1V6sLrvLh9JBMw+nTnKF2Hn/qBi5Mf0Gcmzl4v9Kp/p/95d6Xf681kqpbcp4fRlCULrJ1U8TJI0XUBv+KYYKxGxUoxtpDCqZ6YnwxzIvmRYCC6vPWViE6V7Jyl2UmxeTo0RZEjPzeou+ZFqpaQZE3A3Xyd/pmJTqdTaPMog9ZUeggJ7am469M6eE5RDnGT3fyqA2/uY6fx3q/u/PhJggO7/QQL2fwRLu0WciVFcQiFnV2txRjmOndkYIM3KCNufUe4/TL66cKBxnNkbxE67VJKXjR33bA/sls20wo47G3DPeeoTO96r9+wyCWKn3WGkLxg7xE+/OUbuADdLcEC5Q9zMh5HkzrHNl761Y8Mo8g4fBgRGnY+OC8xH4WA5XkEIwAbutm9GfribP+5+TjWJvdkAbhbi6iQ//FdnUZbmMKgIgROkuZxgJjMVzN2cqAgU4Q+cyRwmITqTsAkJuefbmw8J2x3scN7JKUSuxKTuPC2EPWP+bCPv41PUtp2oA8nNHCc4wMZDHh8ZpuBk5e4TJyW2o7WZTlKpkZjpJJU3zkcoYb1BFDpjdw2UYujOBodr7BZe4XZO0TOkazS0ekijuUEaJS3MSLjAQ5nPKCLcvdzFkq58DgMURwA4Ue+h0X8+PLUX9Af6k1TdNx/s+8d0Mui4EFsg3HNPMCBGBnQ7X/P5gOsaXb1enM+tEp9w6trAWHSOzxAO3NkIHtlsOrvA2xrOzKUZc2NqLYSaN5vcqcmoCED9O8BfOAIEdJMPKaKdEYCpP9vICJiuUvPkXScscDAYRKHS+iFdJ5pgZwq2pyDVSsakzi/U+8nE36ZqqZetVrXUN85mWwmplrp+thfVElNntqG3lW+1yLxvSDf156L5aUpepzHKqDoQoTOsGfC0pdCMQ0O7QahiPgcrUAiRcUqC55QhDEWk+SkLPA6U1KhDFgQt5nAlV38bZcilnhYL7J8WjxehCoxlKjFU9cXu/mbxnVwwVbfg/O9vCXFAUq5xt/i43nxNv/Jx+7jdp5pSfr0o5wBZNQeQZDpcZiUAr5LXWOxSZQbcLJYf7hNRdmm/yndIpdSppyenkyR/odmL+HUmNCGwqib75/Z2tVEDb5PR3VbemX/S60iTlZQjb9TfFUqoWisql7/uXpjfq4HaaRpUTJOSK79SXEiplV/QAl8NqEdZmqoaTHRVNZ7sEmoEJh8LjVXPpXcHdTVnQnUx0VyT7+S6a/6dTH9VV83dJjSn3mHUNZpOWOiyyasqbVYNG8uQsnOThc4v5Wtc8DZV3J3fqbqB4YIu4KtxKS0O0FdSni4ekRQJ6IsZO+dXM3VXX1Z1PfrSvfEmLhaSjzkgqgGC2wMk1xjUSOKMCIDBVAfUKHbIkOyQPRDBWU9qEMFdTfNavrGGXqFqnfO3yHa2VjiIw8+wNt8Qa5c8S4r5kmelicKhZ1nbsv+sLPHP+ZJO/2u2BOWvlaYUlwBWP9oGsUHsDug2eOuM7n7QbcxkkTYpkugZ3WXPSkoopkN36RJQHnrqYOimZ3T3im4GobFyA8tui0rzkd20FN1jy242KrqzgIl7m6t7poGTb4QdODWAaAvgtOSkV4Yof1pSkNKBIXqBofEzz0K2FyFbqh2a+2dfQrbsWUklzHRCtnwJ0pqZEeWswXVzchHEL85BYLoBbPkmDE3OdRDoWo4yps1qOpqwZlcfgaH6nBEyJEKAQeeTQoi5QZ+VtUm2VUbL9hRa9ozO2yqMy581oaoGjd85J0H1Er3d5qZgISCpZCoTVUlSRxmfjODtNu37TnJKh8gDgPCRVEdQV7iRMjKnofPiUWn4PHmQaqNH8xB68ewsjG4MpKF0Z8CZJQupOzdZ8xTrVCpKs3h6fsnQMLMIeujSqQtgIQyLwrY1erdrBC57FgTdXKMXvQjgY6NJZ/bqwF6GJHbZy7h06uzFY5ImmYRZzCvi6pXfKh9eVHZNxnfHxrnOfNeB74y1dfnOuHTifCeN0tJgLiyJU3RlM17qGkzrzCZjr2MDbVOzV/H5CPaCI7OXac25/AVfkOGOeJnXJ6lr63Ufs/J9HLNtSsP9HOubGoVMlCGDlSVbdEZh6bPSyrvJUDhuqO+MQg8ZHJtYcxyLJQpAdxualz9rShTONB73TTkxk5pCfcV3Yhrb1RROzJnG474phFDT5p6bm9s0o84Gy2gGS4Ujzrx24pt0lcFiBrAHNlgwLXnWGJu0uZYnyWAn6XAzVSOPwV5OJEnuOgaK7F1HV1r2ymQQlWrCVc8bhdGMlWjKaGnZJ3LVk9jFXYXSYBZ45tcN2h+jVDCGiVMLGlx7vya0RN0I1IaW4TdUIzqoItIhIBg+OE9dzo7OU1S0D89TF9XxeQFpmp5QpT7rYwBvFlLwRpKCUkd9XL7dbNZP+9RsT+pFXz3eZ28DIiH3OgExJwDiGMBU2io5btwDIcCMU8BjzhlVNaTynsVuWYhjEDGejLbQpSEJi/7cxTCwllzQpxbRJFwwgMsBC4XxFReikJcE73rDZIdg2Rjm08kQHJbUgFVQHLEKipcVjvVG8A7hm1Hs5bg9yaUIu12vTPM6v3siNFyysIZuVU64cOAVOtbgcDg22NBKd88PV1dD2CS46w/u4BfpSxfXh6sXP8QwxSOuDlNzrAWUOXMa2AvWIfDFeLHWjb3ZFoHcYLyBQi8an0v8UzAAUEnAHca9xmEwDz+m7CmjaPsdIjDL590nK4zSKCRzvJGNS9m7hlEjnmShOsyaqlFN2VWOhHjK5FqEC4s+fwg7lmtNSTE+H+fXToGPWZi/eK9cDGGJsEDd2Pj48rYSscLGLY3vUtU2S8FhcTTLxwqORvlYV44ezWHemxzIFfoTlgOQjCMIJt3PO0QqtfduYiedYDAiQAAu/4UMlaf+kghwdVJn8qc8aiwniZgo5qsIIXMk+ZrmucxmsswgHaA6BAvnah73agPn4K3HC46jWLKa+ldgXpbfBAmJREHY2ASB69OlsdQTKzAysJFsunhO0XHL9cZl+2olb3FMBMcxpqmj3PPVItEWqISeCk4hoFKoEYBjgKmA2AyamTgFQgm1mIGYESgRW+7JoXJGoy9MYH/lESvZgXrDqgHJWQmwtCXWSSBDsoO53ZFw2gslarcrBJhB+DYevsGF17FlaHNR/vty7xlGhAAkH9MzwTjOx8Zz5hkQc5V/c/dxlf9CXTkF5R9CaPNVOHQiLC0S8BKVr6NpwGAD3sbNXmEMswF3iNCdmhvwKFvbZuiAV+BIdu7Hy9eVwXMinwSD0ybMRajDXX3yNyZNXoG6DN6Jv4/3D8Ij33YM7yGedcFfRwfi9Cl/fpixWM1+xIpBntMWKzhuwiUMDihWSDNGZX2IlX64tkNgf+a5ghwBQxey0gPj5LTZ1umB0uqvaBWm8gOlvqgnLQsi9WaodYj3zsnLNFl64OXE6YEFq9X7nHjYd26NzypBEM+0Ak+XV50A0cNCC8XlJJ80QxB3iFON2OFyhgmCbQRAic8DmnlOc0oQJB0CYrP0NsxI9S8WrrGib6pznqJvMKyn6Nd4veel6CNQsj2WRU26KfOIhh9jNT0YW2E3KwfOZnYoC3em3FdRh1dcO2Xuw/2a0qzkKVPm5hCDx16Wk73fKnKVr2twWpGvixqzX3m+bh9+9s77ZA6+k+BUUrKB9ZsVj0qaPOKybL3B/eUYlrwRGtcnTjoE1cdME3s5TUcgKWvuS0VF05EZHANAOgRumyp0fWhYsyu8KlalcRMEkw9dkW5eO/EmCBXnGcCy/KHuDelKe5xanvzRVbSZF66+HJELSqpWlMitqGOfg8gdtZr1xNLduu8Uxdo1F8wGt3qC2bh26oKZlh7+EpO+BTMlpc+KpxTMHeKW86hr4bi0148UghEFsU4OLw/VcEIiJPIs8qpuiE7Vi+kHGUQYzjRyN3ElC6mx9M3AHYURZnHgHAUzm42TiKMcKeaqu7kHgEamh3nsTHAzaeUUEwzKylh6qly5hHja2pUWyLTyVqwwIjHFFqHzLlYx9+g5iajLqbML6qBgkZw1JTkWE0ofOuv4Fimj6pzixr2kjJrqmqu1m/zoau3FtVNQzQUtEZAsLuWW45X0uOTMUgibPXUMdZ0a7HbSCR1mVEoA7jMWjFtwVn0Tmc7s3zOTmhqmZ1qfVLoHA2Hzi1HL+CIlh5N3DD+X5OMxgps8dBQW7RBdOrVotGRZv5EaFHFDnhq6gUsvwehKLq4IRp/WVmvF4awoMR5wq8UlT1UVVT1stceHqmlc8oKsdFnGiFybZwo1lS2+2b6Xhv4+OuwWj/un7S61nKLlYvc+WRdXyiTLttq9/bRKV6/CIgOAgtXdRakkAgC+eX2VXD/SNbHIR5arx0yIODacRGbKZ6Hgzl3yxxENA4Vk5IZYFgVHFSGZquIWK+F4EHuvQ7yuANoh+0tT436vNx8QgRSD4SCdj7jl7epG3AQRF7T2mbsdDEXzmKvORyVkZwxEFTUjwjxiw0sZh9HArmfaIQ7XQsjgEAJaCJkVhQRUqTtIFe9dDCVkAmiar9yJozII8ti65hXamRc9ZxMbHIIdolKdXYsBijYVP3diuVouy7F4IyihY4kcJknKjPWx6C1AHIlygiPOLYnkaaKGRBqE4NXhLrZJ3bE3yYf7wjerLyhWC4CC/fW8LW641PvMq+RWudxfionyO81H7A/Pyw8Z0R7Wy2RmBG6l2aL5XS7HbrVIf0+iW4L71eNqtzgkSEQgu+nwsMoInKy6+paCq3VNWUXpd+6s8UQy7osn6t+crK/+4aFFSW7wlqxGfktJuJESrA0DPVmmXDaBZd+FeOwpk8foItv5Uy/u3jFEn0yxrQJGF7Zo3qzuMmws0tjTG1oaPLpO/syEXaUZUcKrsdkA0wtDVugGsOzk29ZsmgzutilL2AaM2t3/ub1d6bsV2tR6Xe+fFpIkCmrqo3xMBjL1DiWhyHwjdrfhdBPOkKZwpsOPdcFHK/QYrGw2YpDBqmYjGJlVNJtYUj9Ge1SMfSI95sSGVaaCFuBPPCmmupAFmvRyZXBK1mvFXv/j16v4l5s/vi7iTz/+ul78dHmpFEp1YwbJ5EaohiQdbLSB7CqB6Te+ph+VTaE+fs5+3kVWoKzGMjClg6owWQ1KECUDBYDk6HX6vIscNtnjUwgpuV7xE3j2ExIhn9znYUXv5Goft4RQJoIsWDQgzRwJk5w8Uk2ZpI7YpYwSnsMQRoxPGKVZNSONXQuc3zUmxZLTQQyKIQ1lk2QcjEmyuJ5kuSO5CMFaVLJJmnuCk+3X9hcrb7FLLJOuDsUyX246b+bHTb24hQ/XuPjma/4p9d2mr5f6nHF2dIf2N+Ps2I7c15x9NfUzFx8KHSQda4mNzOtbcSNLb8xcwPVSL3E+V2HNwBINQEmP5c7gYq4wvrInZD5ODeXkXA4DyklnbXMKy6tboNSbCHN7Incey0/roD3/1d0ZAIN6BshCKxmcCm7IJEgpK5QD3WcKl21KWSE5MMNiB7VD1zGEFd0s0P1bxgfU5gvWni8KnpuWS9isuITZ2OYdeSRpZ28yG2rGJFn8IL9Nhy/KXtdhRcYdjkvn65f/YKsNaEyWM/iDOfyBOvBHxsDwotVeNh0niTlzUnJeRC+sNPZ+g+rxniXHu4T/9ep78vWvX27/8+r26fr66s3vf/xwfanZx9VB7cT5ZByE6ad1U0gxjp1THoyFCp3u4KiugVMdXDQETnMYRK/VttA8TZHuNkcOn+Y2R+A0BouR/FMYHLLq0xc8Wg5llKiOx3XEm84ZxKnn/3EPUnD8P0g0AxChM8RP6JQEEz+B0xEc/OhEc39nSBPMh8EQrcdQ7wJAHXEwPwoGTjMwCBg4xaCJR2I45mezVQi7uiNyRVIdN2AqkuqogYEcEBomtQqhFra1CqHeUmeiEIbOEjBgHThDoJO66J8WYHpcqx8ytCrZwA8+J9dFS8vHZBvH/hrHOzEAC9F5sZDfrd8Ad6BLfyf/nt8429x9/M72A/g1Al32a99hWK9Hg0hJO7f7AI4PaDEdrWa6Xlz1vTMmPU3GDPS7N91yfp/7LowZ6GhvMoXfyX7Mra1BWCr1klgKeByW7oM7TphA1uKF+tG7RnWgD72LpkD/+UFUbtIgBvLiky8uR0i+yGHd3Prmthcu/zxd+gVp4LHv27JW5djzIw6xSJM0S3RN51HzLwganzLTpV90YCbHRoQ6hj1Z+gVp4KaeNvo1hhLYkuC1ap/mg1q1T984E7Uv6b9s4tP1jjZV7ZLaG1M0wXHVN9LAgX9i5o2VUTQXnMOmOIezxjnuasIwZ56RY7mkQYxhDg447UjrJMqTht9OvhGqAXo436iTD65/mQ/mxQvEEdVd8+hy9TP3TQ/jS0s6cJvPQSP4y0iDoNAw4fyiAC+5Mevo2iKJtZGC28pDUemJcIySrEu2azHqVq0uqLMWrcPoug3CFXVKQZe9e4IE5HYErpdtTXO2yLyyHyUeIRIOHqEbOW4q4ORsgLuzZc658bb8yUocTkwQUeKSiopA5dAkgqj3mocZJRm0kYx9iynRVEzNK7VUCRbmojUmR4gp6M0WjyumaAOP/lRZpsKx/kK9lh0xEeix7AkMv7fyIMKDTuD9HiGvNAdMc+e31xvZoGmgJ7JDUt0L2aPjUK5WiuoJN6+k0s55pEkj4/khJo9x5u53v1exg5JJUkfpFLnjY0S4WpAsJ5HfW9hlZDEqF0/mWSbTxkg6JcpogteqbhrytaobnVcSqKCOSAl09+2ixCV9fM15A/17x1TnGjiap86JTlrwWpCGcSc3cQdvde/s0DSwopWKmbAD04EUnSnm99Htwg2uUzHQKXdMZmjgC55T1CVpdGtl/Yv2Wf+dk6i7BF2a8wk6zW0j94nnsRK/JWsXRsHOvKFetA04pW1IJuk0a+2CwZ8zaISGNvDuu8ZRRRNZi0ftzo5BnTprGxvg4KxhbFknx1rjbpF9yvo32vp32qjRc85mLRoHdbAC6kZ6UMDBGkorzdOe+tfOG3hWG4MAtwZB1tYzJMbThp79gaAQ9sy1yVaLxCYrg8TdZv30Q6ka3xc8SAyT/psWQEgxZjrXmB60co/zW3tHCQu50NIWcDdF87d0IO1NaOAnbbJoNmNUrRgBok9f0q9l1/VEWfvFovmi2Xoxa7yYtF00my6Cu53q7Jq2T8waLl4lG6K6+TG5knxnmTQMzO77v3dXeq5H1fUO/Pkst7vbdAn1Td775L3v0p9q//yLpPliST88zUm63WKtH+LJUCq8FosepovmijhtrZg1VszUnaeCeZKWigWTJO0Us2aKSStFj1GzJooBRs3aJwa9HKNxjss3WOezGVxDQk5oqOOTvfOM6Lcq1tBdUeEqNF0dHmWur7OAY7uln4nGp481tukKnHmax3iY3GrqZhvYDhJd8ylS3/MRHrK0IsiDDQoA61TBQp1OfVK70iGOLmDxZsNugGposLQrONTJ+Yv9Q25GG/hQ4+8WB7ltJMUZchrfREYtcPRSUMO0CCincysR44aRx0ZNHAojl2hq60Lj2j6936wfV+8/yrdT2lKuy6xb6Tdyyz7YuPPUcFdb/7i+vVVff71bSV0lC0oDbT0ki0ZfX9A3aq7nwzbTZ6AHzgz/ptqih46CahtFhFEfTTD2FBGEA5B2kxd600N4g9jYUFFUpRCaREEFVWzRAwtVFSnir6UQSqhDHQICXzW9evP29Wvha8PN+jwNhwYKffUFBRKqND0sNAxVCsgaxCWmjKnHQlDEMeBIoPjCCbADwkEsQAwFhOq/xIQxw+2U+vC4W9zyxdLBA0J+uF05HbQtU0CJj5+IB5irqBIiqmuTRy4o5SF/wGxQRJi06hAQlFICMXdRJDjkgDMgBOaQuRDCAS9VEEKQhv1Ws4CQagHGAiDS7n/T34jiSDvBLXcSQBEayqHEA0kBumTzW9qXKnJIhkMHEU7nCImNONCJPuSJJj24S56v/vWDAO9u3j+//1US7/fLV+jrJZqzRIEwjgUhVKrzgEAxP4mSebCHlCixq9pyML4wCUJnQgX3BUBnBH0GeluR8K2iSaAz77alAAsBAaEYMIC9NiqxCiNLm5PGHENOyUsFD/E1GRFPocnwf4nPNz/+yf7+4d3T5sPul8Vfb367bJC239xL56esdPXHuXS/W7HlMqeU439rSrpZ+emY6NNP583Wn58uCJuQujNUQDXg73tefnifxTPfp9HV97fr/aHG8dc63jlLf2CJEOrOBAWuSOziSmhfn5XhMZD/L4i0kHbUDli4DljpGXCBiZbpyqtJdvc3i+9AGpR3/ve3ZCqQPPVu8XG9+Zp+5eP2cbtPQ+P59eKtQOlLpWfwuSfWyVH7aLuSQylB6Li8knsrTgVsMw0yzh0sPwazy2mXCemqT7s0H2EdG/lZvzwCh93i7m69TP6+yU4fRMnZgGCnP1afP5k/Ydfhinnhu59Wd4fLh4U+RjN9s781f4OLugM0UzS3XOf6U0VLf1GzRWjwo/SL1577mYjlwJGegfM1/XOSMwGtLhYiGuXH7+rMx/RZVL0EfZPOrsV18cDS07vtYzzzYYNPhjmfk+Oy47s5Ms/gdM/npMYRnN75nGQ+53M6KoZc/0X6sqHNPLt512BED3z3bzVdxp4Gc5ZPX6JWlG4p7t5Uvg2UqFP+25e8VuXJpuWaTZZL9ZL0Go6pE8liUJpgIFAjSgKqDRxMtwmZ72fd5qzbnHWbs25z1m2+bd0mJTM4PO8eqzb6s3rzzas3VCDuqjcxGFS9uciwbvgUC5Tjt/8P</diagram></mxfile>" + content="<mxfile host="Electron" modified="2023-10-03T03:33:38.127Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="TqIhwSEqRvc68Imy-9XT" version="20.3.0" type="device"><diagram name="intersection" id="0L5whF3ImEvTl2DSWTjR">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</diagram></mxfile>" > @@ -18,7 +18,7 @@ - + @@ -57,9 +57,9 @@ stroke-miterlimit="10" pointer-events="all" /> - + - + - - - - + + + + - - - + + + - + - +
- +
autoware_perception_msgs behavior_velocity_planner_common geometry_msgs - grid_map_core interpolation lanelet2_extension libopencv-dev - magic_enum motion_utils nav_msgs pluginlib @@ -41,8 +39,6 @@ vehicle_info_util visualization_msgs - grid_map_rviz_plugin - ament_lint_auto autoware_lint_common diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index fbb82d6ace175..c875611b16003 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -271,29 +271,37 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( motion_utils::VirtualWalls IntersectionModule::createVirtualWalls() { - // TODO(Mamoru Sobue): collision stop pose depends on before/after occlusion clearance motion_utils::VirtualWalls virtual_walls; motion_utils::VirtualWall wall; - wall.style = motion_utils::VirtualWallType::stop; if (debug_data_.collision_stop_wall_pose) { + wall.style = motion_utils::VirtualWallType::stop; wall.text = "intersection"; wall.ns = "intersection" + std::to_string(module_id_) + "_"; wall.pose = debug_data_.collision_stop_wall_pose.value(); virtual_walls.push_back(wall); } if (debug_data_.occlusion_first_stop_wall_pose) { + wall.style = motion_utils::VirtualWallType::stop; wall.text = "intersection"; wall.ns = "intersection_occlusion_first_stop" + std::to_string(module_id_) + "_"; wall.pose = debug_data_.occlusion_first_stop_wall_pose.value(); virtual_walls.push_back(wall); } if (debug_data_.occlusion_stop_wall_pose) { + wall.style = motion_utils::VirtualWallType::stop; wall.text = "intersection_occlusion"; wall.ns = "intersection_occlusion" + std::to_string(module_id_) + "_"; wall.pose = debug_data_.occlusion_stop_wall_pose.value(); virtual_walls.push_back(wall); } + if (debug_data_.absence_traffic_light_creep_wall) { + wall.style = motion_utils::VirtualWallType::slowdown; + wall.text = "intersection_occlusion"; + wall.ns = "intersection_occlusion" + std::to_string(module_id_) + "_"; + wall.pose = debug_data_.absence_traffic_light_creep_wall.value(); + virtual_walls.push_back(wall); + } return virtual_walls; } diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index e02a1e4220030..d42c9b3ca2aa2 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -136,6 +136,10 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".occlusion.stop_release_margin_time"); ip.occlusion.temporal_stop_before_attention_area = getOrDeclareParameter(node, ns + ".occlusion.temporal_stop_before_attention_area"); + ip.occlusion.absence_traffic_light.creep_velocity = + getOrDeclareParameter(node, ns + ".occlusion.absence_traffic_light.creep_velocity"); + ip.occlusion.absence_traffic_light.maximum_peeking_distance = getOrDeclareParameter( + node, ns + ".occlusion.absence_traffic_light.maximum_peeking_distance"); } void IntersectionModuleManager::launchNewModules( @@ -165,13 +169,21 @@ void IntersectionModuleManager::launchNewModules( continue; } - const auto associative_ids = - planning_utils::getAssociativeIntersectionLanelets(ll, lanelet_map, routing_graph); const std::string location = ll.attributeOr("location", "else"); const bool is_private_area = (location.compare("private") == 0); + const auto associative_ids = + planning_utils::getAssociativeIntersectionLanelets(ll, lanelet_map, routing_graph); + bool has_traffic_light = false; + if (const auto tl_reg_elems = ll.regulatoryElementsAs(); + tl_reg_elems.size() != 0) { + const auto tl_reg_elem = tl_reg_elems.front(); + const auto stop_line_opt = tl_reg_elem->stopLine(); + if (!!stop_line_opt) has_traffic_light = true; + } const auto new_module = std::make_shared( - module_id, lane_id, planner_data_, intersection_param_, associative_ids, is_private_area, - enable_occlusion_detection, node_, logger_.get_child("intersection_module"), clock_); + module_id, lane_id, planner_data_, intersection_param_, associative_ids, turn_direction, + has_traffic_light, enable_occlusion_detection, is_private_area, node_, + logger_.get_child("intersection_module"), clock_); generateUUID(module_id); /* set RTC status as non_occluded status initially */ const UUID uuid = getUUID(new_module->getModuleId()); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 89bb65f403095..89cdeaae56723 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -17,7 +17,6 @@ #include "util.hpp" #include -#include #include #include #include @@ -65,14 +64,18 @@ static bool isTargetCollisionVehicleType( } IntersectionModule::IntersectionModule( - const int64_t module_id, const int64_t lane_id, std::shared_ptr planner_data, + const int64_t module_id, const int64_t lane_id, + [[maybe_unused]] std::shared_ptr planner_data, const PlannerParam & planner_param, const std::set & associative_ids, - const bool is_private_area, const bool enable_occlusion_detection, rclcpp::Node & node, + const std::string & turn_direction, const bool has_traffic_light, + const bool enable_occlusion_detection, const bool is_private_area, rclcpp::Node & node, const rclcpp::Logger logger, const rclcpp::Clock::SharedPtr clock) : SceneModuleInterface(module_id, logger, clock), node_(node), lane_id_(lane_id), associative_ids_(associative_ids), + turn_direction_(turn_direction), + has_traffic_light_(has_traffic_light), enable_occlusion_detection_(enable_occlusion_detection), occlusion_attention_divisions_(std::nullopt), is_private_area_(is_private_area), @@ -81,11 +84,10 @@ IntersectionModule::IntersectionModule( velocity_factor_.init(VelocityFactor::INTERSECTION); planner_param_ = planner_param; - const auto & assigned_lanelet = - planner_data->route_handler_->getLaneletMapPtr()->laneletLayer.get(lane_id); - turn_direction_ = assigned_lanelet.attributeOr("turn_direction", "else"); - collision_state_machine_.setMarginTime( - planner_param_.collision_detection.state_transit_margin_time); + { + collision_state_machine_.setMarginTime( + planner_param_.collision_detection.state_transit_margin_time); + } { before_creep_state_machine_.setMarginTime(planner_param_.occlusion.before_creep_stop_time); before_creep_state_machine_.setState(StateMachine::State::STOP); @@ -226,6 +228,23 @@ void prepareRTCByDecisionResult( return; } +template <> +void prepareRTCByDecisionResult( + const IntersectionModule::OccludedAbsenceTrafficLight & result, + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, + double * default_distance, bool * occlusion_safety, double * occlusion_distance) +{ + RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "OccludedAbsenceTrafficLight"); + const auto closest_idx = result.closest_idx; + const auto collision_stop_line_idx = result.closest_idx; + *default_safety = !result.collision_detected; + *default_distance = + motion_utils::calcSignedArcLength(path.points, closest_idx, collision_stop_line_idx); + *occlusion_safety = result.is_actually_occlusion_cleared; + *occlusion_distance = 0; + return; +} + template <> void prepareRTCByDecisionResult( const IntersectionModule::OccludedCollisionStop & result, @@ -570,6 +589,59 @@ void reactRTCApprovalByDecisionResult( return; } +template <> +void reactRTCApprovalByDecisionResult( + const bool rtc_default_approved, const bool rtc_occlusion_approved, + const IntersectionModule::OccludedAbsenceTrafficLight & decision_result, + [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, + const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) +{ + RCLCPP_DEBUG( + rclcpp::get_logger("reactRTCApprovalByDecisionResult"), + "OccludedAbsenceTrafficLight, approval = (default: %d, occlusion: %d)", rtc_default_approved, + rtc_occlusion_approved); + if (!rtc_default_approved) { + const auto stop_line_idx = decision_result.closest_idx; + planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); + debug_data->collision_stop_wall_pose = + planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); + { + tier4_planning_msgs::msg::StopFactor stop_factor; + stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; + planning_utils::appendStopReason(stop_factor, stop_reason); + velocity_factor->set( + path->points, path->points.at(decision_result.closest_idx).point.pose, + path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); + } + } + if (!rtc_occlusion_approved && decision_result.temporal_stop_before_attention_required) { + const auto stop_line_idx = decision_result.first_attention_area_stop_line_idx; + planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); + debug_data->occlusion_stop_wall_pose = + planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); + { + tier4_planning_msgs::msg::StopFactor stop_factor; + stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; + planning_utils::appendStopReason(stop_factor, stop_reason); + velocity_factor->set( + path->points, path->points.at(decision_result.closest_idx).point.pose, + path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); + } + } + if (!rtc_occlusion_approved && !decision_result.temporal_stop_before_attention_required) { + const auto closest_idx = decision_result.closest_idx; + const auto peeking_limit_line = decision_result.peeking_limit_line_idx; + for (auto i = closest_idx; i <= peeking_limit_line; ++i) { + planning_utils::setVelocityFromIndex( + i, planner_param.occlusion.absence_traffic_light.creep_velocity, path); + } + debug_data->absence_traffic_light_creep_wall = + planning_utils::getAheadPose(closest_idx, baselink2front, *path); + } + return; +} + template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, @@ -675,7 +747,8 @@ void reactRTCApproval( static std::string formatDecisionResult(const IntersectionModule::DecisionResult & decision_result) { if (std::holds_alternative(decision_result)) { - return "Indecisive"; + const auto indecisive = std::get(decision_result); + return "Indecisive because " + indecisive.error; } if (std::holds_alternative(decision_result)) { return "Safe"; @@ -695,6 +768,9 @@ static std::string formatDecisionResult(const IntersectionModule::DecisionResult if (std::holds_alternative(decision_result)) { return "OccludedCollisionStop"; } + if (std::holds_alternative(decision_result)) { + return "OccludedAbsenceTrafficLight"; + } if (std::holds_alternative(decision_result)) { return "TrafficLightArrowSolidOn"; } @@ -747,15 +823,15 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const auto interpolated_path_info_opt = util::generateInterpolatedPath( lane_id_, associative_ids_, *path, planner_param_.common.path_interpolation_ds, logger_); if (!interpolated_path_info_opt) { - RCLCPP_DEBUG(logger_, "splineInterpolate failed"); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"splineInterpolate failed"}; } const auto & interpolated_path_info = interpolated_path_info_opt.value(); if (!interpolated_path_info.lane_id_interval) { - RCLCPP_WARN(logger_, "Path has no interval on intersection lane %ld", lane_id_); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{ + "Path has no interval on intersection lane " + std::to_string(lane_id_)}; } + // cache intersection lane information because it is invariant const auto & current_pose = planner_data_->current_odometry->pose; const auto lanelets_on_path = planning_utils::getLaneletsOnPath(*path, lanelet_map_ptr, current_pose); @@ -766,30 +842,40 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( planner_param_.occlusion.occlusion_attention_area_length, planner_param_.common.consider_wrong_direction_vehicle); } + auto & intersection_lanelets = intersection_lanelets_.value(); + + // at the very first time of registration of this module, the path may not be conflicting with the + // attention area, so update() is called to update the internal data as well as traffic light info const auto traffic_prioritized_level = util::getTrafficPrioritizedLevel(assigned_lanelet, planner_data_->traffic_light_id_map); const bool is_prioritized = traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED; - intersection_lanelets_.value().update(is_prioritized, interpolated_path_info); + intersection_lanelets.update(is_prioritized, interpolated_path_info); - const auto & conflicting_lanelets = intersection_lanelets_.value().conflicting(); - const auto & first_conflicting_area_opt = intersection_lanelets_.value().first_conflicting_area(); + // this is abnormal + const auto & conflicting_lanelets = intersection_lanelets.conflicting(); + const auto & first_conflicting_area_opt = intersection_lanelets.first_conflicting_area(); if (conflicting_lanelets.empty() || !first_conflicting_area_opt) { - RCLCPP_DEBUG(logger_, "conflicting area is empty"); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"conflicting area is empty"}; } const auto first_conflicting_area = first_conflicting_area_opt.value(); - const auto & first_attention_area_opt = intersection_lanelets_.value().first_attention_area(); + // generate all stop line candidates + // see the doc for struct IntersectionStopLines + const auto & first_attention_area_opt = intersection_lanelets.first_attention_area(); + /// even if the attention area is null, stuck vehicle stop line needs to be generated from + /// conflicting lanes const auto & dummy_first_attention_area = first_attention_area_opt ? first_attention_area_opt.value() : first_conflicting_area; + const double peeking_offset = + has_traffic_light_ ? planner_param_.occlusion.peeking_offset + : planner_param_.occlusion.absence_traffic_light.maximum_peeking_distance; const auto intersection_stop_lines_opt = util::generateIntersectionStopLines( first_conflicting_area, dummy_first_attention_area, planner_data_, interpolated_path_info, planner_param_.stuck_vehicle.use_stuck_stopline, planner_param_.common.stop_line_margin, - planner_param_.occlusion.peeking_offset, path); + peeking_offset, path); if (!intersection_stop_lines_opt) { - RCLCPP_DEBUG(logger_, "failed to generate intersection_stop_lines"); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"failed to generate intersection_stop_lines"}; } const auto & intersection_stop_lines = intersection_stop_lines_opt.value(); const auto @@ -797,38 +883,49 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( first_attention_stop_line_idx_opt, occlusion_peeking_stop_line_idx_opt, pass_judge_line_idx] = intersection_stop_lines; - const auto & conflicting_area = intersection_lanelets_.value().conflicting_area(); + // see the doc for struct PathLanelets + const auto & conflicting_area = intersection_lanelets.conflicting_area(); const auto path_lanelets_opt = util::generatePathLanelets( lanelets_on_path, interpolated_path_info, associative_ids_, first_conflicting_area, - conflicting_area, first_attention_area_opt, intersection_lanelets_.value().attention_area(), - closest_idx, planner_data_->vehicle_info_.vehicle_width_m); + conflicting_area, first_attention_area_opt, intersection_lanelets.attention_area(), closest_idx, + planner_data_->vehicle_info_.vehicle_width_m); if (!path_lanelets_opt.has_value()) { - RCLCPP_DEBUG(logger_, "failed to generate PathLanelets"); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"failed to generate PathLanelets"}; } const auto path_lanelets = path_lanelets_opt.value(); - const bool stuck_detected = checkStuckVehicle(planner_data_, path_lanelets); + // utility functions + auto fromEgoDist = [&](const size_t index) { + return motion_utils::calcSignedArcLength(path->points, closest_idx, index); + }; + auto stoppedForDuration = + [&](const size_t pos, const double duration, StateMachine & state_machine) { + const double dist_stopline = fromEgoDist(pos); + const bool approached_dist_stopline = + (std::fabs(dist_stopline) < planner_param_.common.stop_overshoot_margin); + const bool over_stopline = (dist_stopline < 0.0); + const bool is_stopped_duration = planner_data_->isVehicleStopped(duration); + if (over_stopline) { + state_machine.setState(StateMachine::State::GO); + } else if (is_stopped_duration && approached_dist_stopline) { + state_machine.setState(StateMachine::State::GO); + } + return state_machine.getState() == StateMachine::State::GO; + }; + // stuck vehicle detection is viable even if attention area is empty + // so this needs to be checked before attention area validation + const bool stuck_detected = checkStuckVehicle(planner_data_, path_lanelets); if (stuck_detected && stuck_stop_line_idx_opt) { auto stuck_stop_line_idx = stuck_stop_line_idx_opt.value(); - const double dist_stopline = motion_utils::calcSignedArcLength( - path->points, path->points.at(closest_idx).point.pose.position, - path->points.at(stuck_stop_line_idx).point.pose.position); - const bool approached_stop_line = - (std::fabs(dist_stopline) < planner_param_.common.stop_overshoot_margin); - const bool is_stopped = planner_data_->isVehicleStopped(); - if (is_stopped && approached_stop_line) { - stuck_private_area_timeout_.setStateWithMarginTime( - StateMachine::State::GO, logger_.get_child("stuck_private_area_timeout"), *clock_); - } - const bool timeout = - (is_private_area_ && stuck_private_area_timeout_.getState() == StateMachine::State::GO); + const bool stopped_at_stuck_line = stoppedForDuration( + stuck_stop_line_idx, planner_param_.stuck_vehicle.timeout_private_area, + stuck_private_area_timeout_); + const bool timeout = (is_private_area_ && stopped_at_stuck_line); if (!timeout) { if ( default_stop_line_idx_opt && - motion_utils::calcSignedArcLength(path->points, stuck_stop_line_idx, closest_idx) > - planner_param_.common.stop_overshoot_margin) { + fromEgoDist(stuck_stop_line_idx) < -planner_param_.common.stop_overshoot_margin) { stuck_stop_line_idx = default_stop_line_idx_opt.value(); } return IntersectionModule::StuckStop{ @@ -836,15 +933,16 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } } + // if attention area is empty, collision/occlusion detection is impossible if (!first_attention_area_opt) { - RCLCPP_DEBUG(logger_, "attention area is empty"); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"attention area is empty"}; } const auto first_attention_area = first_attention_area_opt.value(); + // if attention area is not null but default stop line is not available, ego/backward-path has + // already passed the stop line if (!default_stop_line_idx_opt) { - RCLCPP_DEBUG(logger_, "default stop line is null"); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"default stop line is null"}; } const auto default_stop_line_idx = default_stop_line_idx_opt.value(); @@ -873,25 +971,25 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( // is_go_out_: previous RTC approval // activated_: current RTC approval is_permanent_go_ = true; - RCLCPP_DEBUG(logger_, "over the pass judge line. no plan needed."); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"over the pass judge line. no plan needed"}; } + // occlusion stop line is generated from the intersection of ego footprint along the path with the + // attention area, so if this is null, eog has already passed the intersection if (!first_attention_stop_line_idx_opt || !occlusion_peeking_stop_line_idx_opt) { - RCLCPP_DEBUG(logger_, "occlusion stop line is null"); - return IntersectionModule::Indecisive{}; + return IntersectionModule::Indecisive{"occlusion stop line is null"}; } const auto collision_stop_line_idx = is_over_default_stop_line ? closest_idx : default_stop_line_idx; const auto first_attention_stop_line_idx = first_attention_stop_line_idx_opt.value(); const auto occlusion_stop_line_idx = occlusion_peeking_stop_line_idx_opt.value(); - const auto & attention_lanelets = intersection_lanelets_.value().attention(); - const auto & adjacent_lanelets = intersection_lanelets_.value().adjacent(); - const auto & occlusion_attention_lanelets = intersection_lanelets_.value().occlusion_attention(); - const auto & occlusion_attention_area = intersection_lanelets_.value().occlusion_attention_area(); - debug_data_.attention_area = intersection_lanelets_.value().attention_area(); - debug_data_.adjacent_area = intersection_lanelets_.value().adjacent_area(); + const auto & attention_lanelets = intersection_lanelets.attention(); + const auto & adjacent_lanelets = intersection_lanelets.adjacent(); + const auto & occlusion_attention_lanelets = intersection_lanelets.occlusion_attention(); + const auto & occlusion_attention_area = intersection_lanelets.occlusion_attention_area(); + debug_data_.attention_area = intersection_lanelets.attention_area(); + debug_data_.adjacent_area = intersection_lanelets.adjacent_area(); // get intersection area const auto intersection_area = planner_param_.common.use_intersection_area @@ -902,17 +1000,17 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( debug_data_.intersection_area = toGeomPoly(intersection_area_2d); } - // calculate dynamic collision around detection area - const double time_delay = (is_go_out_ || is_prioritized) - ? 0.0 - : (planner_param_.collision_detection.state_transit_margin_time - - collision_state_machine_.getDuration()); + // calculate dynamic collision around attention area + const double time_to_restart = (is_go_out_ || is_prioritized) + ? 0.0 + : (planner_param_.collision_detection.state_transit_margin_time - + collision_state_machine_.getDuration()); const auto target_objects = filterTargetObjects(attention_lanelets, adjacent_lanelets, intersection_area); const bool has_collision = checkCollision( *path, target_objects, path_lanelets, closest_idx, - std::min(occlusion_stop_line_idx, path->points.size() - 1), time_delay, + std::min(occlusion_stop_line_idx, path->points.size() - 1), time_to_restart, traffic_prioritized_level); collision_state_machine_.setStateWithMarginTime( has_collision ? StateMachine::State::STOP : StateMachine::State::GO, @@ -959,74 +1057,63 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const bool is_occlusion_cleared_with_margin = (occlusion_stop_state_machine_.getState() == StateMachine::State::GO); - // check safety + // distinguish if ego detected occlusion or RTC detects occlusion const bool ext_occlusion_requested = (is_occlusion_cleared_with_margin && !occlusion_activated_); - if ( - occlusion_stop_state_machine_.getState() == StateMachine::State::STOP || - ext_occlusion_requested) { - const double dist_default_stopline = motion_utils::calcSignedArcLength( - path->points, path->points.at(closest_idx).point.pose.position, - path->points.at(default_stop_line_idx).point.pose.position); - const bool approached_default_stop_line = - (std::fabs(dist_default_stopline) < planner_param_.common.stop_overshoot_margin); - const bool over_default_stop_line = (dist_default_stopline < 0.0); - const bool is_stopped_at_default = - planner_data_->isVehicleStopped(planner_param_.occlusion.before_creep_stop_time); - if (over_default_stop_line) { - before_creep_state_machine_.setState(StateMachine::State::GO); - } - if (before_creep_state_machine_.getState() == StateMachine::State::GO) { - const double dist_first_attention_stopline = motion_utils::calcSignedArcLength( - path->points, path->points.at(closest_idx).point.pose.position, - path->points.at(first_attention_stop_line_idx).point.pose.position); - const bool approached_first_attention_stop_line = - (std::fabs(dist_first_attention_stopline) < planner_param_.common.stop_overshoot_margin); - const bool over_first_attention_stop_line = (dist_first_attention_stopline < 0.0); - const bool is_stopped_at_first_attention = - planner_data_->isVehicleStopped(planner_param_.occlusion.before_creep_stop_time); - if (planner_param_.occlusion.temporal_stop_before_attention_area) { - if (over_first_attention_stop_line) { - temporal_stop_before_attention_state_machine_.setState(StateMachine::State::GO); - } - if (is_stopped_at_first_attention && approached_first_attention_stop_line) { - temporal_stop_before_attention_state_machine_.setState(StateMachine::State::GO); - } - } - const bool temporal_stop_before_attention_required = - planner_param_.occlusion.temporal_stop_before_attention_area && - temporal_stop_before_attention_state_machine_.getState() == StateMachine::State::STOP; - if (has_collision_with_margin) { - return IntersectionModule::OccludedCollisionStop{is_occlusion_cleared_with_margin, - temporal_stop_before_attention_required, - closest_idx, - collision_stop_line_idx, - first_attention_stop_line_idx, - occlusion_stop_line_idx}; - } else { - return IntersectionModule::PeekingTowardOcclusion{is_occlusion_cleared_with_margin, - temporal_stop_before_attention_required, - closest_idx, - collision_stop_line_idx, - first_attention_stop_line_idx, - occlusion_stop_line_idx}; + const bool is_occlusion_state = (!is_occlusion_cleared_with_margin || ext_occlusion_requested); + // Safe + if (!is_occlusion_state && !has_collision_with_margin) { + return IntersectionModule::Safe{closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; + } + // Only collision + if (!is_occlusion_state && has_collision_with_margin) { + return IntersectionModule::NonOccludedCollisionStop{ + closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; + } + // Occluded + const bool stopped_at_default_line = stoppedForDuration( + default_stop_line_idx, planner_param_.occlusion.before_creep_stop_time, + before_creep_state_machine_); + if (stopped_at_default_line) { + // in this case ego will temporarily stop before entering attention area + const bool temporal_stop_before_attention_required = + (planner_param_.occlusion.temporal_stop_before_attention_area || !has_traffic_light_) + ? !stoppedForDuration( + first_attention_stop_line_idx, planner_param_.occlusion.before_creep_stop_time, + temporal_stop_before_attention_state_machine_) + : false; + if (!has_traffic_light_) { + if (fromEgoDist(first_attention_stop_line_idx) <= -peeking_offset) { + return IntersectionModule::Indecisive{ + "already passed maximum peeking line in the absence of traffic light"}; } + return IntersectionModule::OccludedAbsenceTrafficLight{ + is_occlusion_cleared_with_margin, has_collision_with_margin, + temporal_stop_before_attention_required, closest_idx, + first_attention_stop_line_idx, occlusion_stop_line_idx}; + } + if (has_collision_with_margin) { + return IntersectionModule::OccludedCollisionStop{is_occlusion_cleared_with_margin, + temporal_stop_before_attention_required, + closest_idx, + collision_stop_line_idx, + first_attention_stop_line_idx, + occlusion_stop_line_idx}; } else { - if (is_stopped_at_default && approached_default_stop_line) { - // start waiting at the first stop line - before_creep_state_machine_.setState(StateMachine::State::GO); - } - const auto occlusion_stop_line = planner_param_.occlusion.temporal_stop_before_attention_area - ? first_attention_stop_line_idx - : occlusion_stop_line_idx; - return IntersectionModule::FirstWaitBeforeOcclusion{ - is_occlusion_cleared_with_margin, closest_idx, default_stop_line_idx, occlusion_stop_line}; + return IntersectionModule::PeekingTowardOcclusion{is_occlusion_cleared_with_margin, + temporal_stop_before_attention_required, + closest_idx, + collision_stop_line_idx, + first_attention_stop_line_idx, + occlusion_stop_line_idx}; } - } else if (has_collision_with_margin) { - return IntersectionModule::NonOccludedCollisionStop{ - closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; + } else { + const auto occlusion_stop_line = + (planner_param_.occlusion.temporal_stop_before_attention_area || !has_traffic_light_) + ? first_attention_stop_line_idx + : occlusion_stop_line_idx; + return IntersectionModule::FirstWaitBeforeOcclusion{ + is_occlusion_cleared_with_margin, closest_idx, default_stop_line_idx, occlusion_stop_line}; } - - return IntersectionModule::Safe{closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; } bool IntersectionModule::checkStuckVehicle( diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 550b23aa17606..7bd923ed6198c 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -18,9 +18,7 @@ #include "util_type.hpp" #include -#include #include -#include #include #include @@ -116,10 +114,18 @@ class IntersectionModule : public SceneModuleInterface double ignore_parked_vehicle_speed_threshold; double stop_release_margin_time; bool temporal_stop_before_attention_area; + struct AbsenceTrafficLight + { + double creep_velocity; + double maximum_peeking_distance; + } absence_traffic_light; } occlusion; }; - using Indecisive = std::monostate; + struct Indecisive + { + std::string error; + }; struct StuckStop { size_t closest_idx{0}; @@ -159,6 +165,15 @@ class IntersectionModule : public SceneModuleInterface size_t first_attention_stop_line_idx{0}; size_t occlusion_stop_line_idx{0}; }; + struct OccludedAbsenceTrafficLight + { + bool is_actually_occlusion_cleared{false}; + bool collision_detected{false}; + bool temporal_stop_before_attention_required{false}; + size_t closest_idx{0}; + size_t first_attention_area_stop_line_idx{0}; + size_t peeking_limit_line_idx{0}; + }; struct Safe { // NOTE: if RTC is disapproved status, default stop lines are still needed. @@ -174,20 +189,22 @@ class IntersectionModule : public SceneModuleInterface size_t occlusion_stop_line_idx{0}; }; using DecisionResult = std::variant< - Indecisive, // internal process error, or over the pass judge line - StuckStop, // detected stuck vehicle - NonOccludedCollisionStop, // detected collision while FOV is clear - FirstWaitBeforeOcclusion, // stop for a while before peeking to occlusion - PeekingTowardOcclusion, // peeking into occlusion while collision is not detected - OccludedCollisionStop, // occlusion and collision are both detected - Safe, // judge as safe - TrafficLightArrowSolidOn // only detect vehicles violating traffic rules + Indecisive, // internal process error, or over the pass judge line + StuckStop, // detected stuck vehicle + NonOccludedCollisionStop, // detected collision while FOV is clear + FirstWaitBeforeOcclusion, // stop for a while before peeking to occlusion + PeekingTowardOcclusion, // peeking into occlusion while collision is not detected + OccludedCollisionStop, // occlusion and collision are both detected + OccludedAbsenceTrafficLight, // occlusion is detected in the absence of traffic light + Safe, // judge as safe + TrafficLightArrowSolidOn // only detect vehicles violating traffic rules >; IntersectionModule( const int64_t module_id, const int64_t lane_id, std::shared_ptr planner_data, const PlannerParam & planner_param, const std::set & associative_ids, - const bool is_private_area, const bool enable_occlusion_detection, rclcpp::Node & node, + const std::string & turn_direction, const bool has_traffic_light, + const bool enable_occlusion_detection, const bool is_private_area, rclcpp::Node & node, const rclcpp::Logger logger, const rclcpp::Clock::SharedPtr clock); /** @@ -211,7 +228,8 @@ class IntersectionModule : public SceneModuleInterface rclcpp::Node & node_; const int64_t lane_id_; const std::set associative_ids_; - std::string turn_direction_; + const std::string turn_direction_; + const bool has_traffic_light_; bool is_go_out_ = false; bool is_permanent_go_ = false; @@ -230,6 +248,7 @@ class IntersectionModule : public SceneModuleInterface StateMachine before_creep_state_machine_; //! for two phase stop StateMachine occlusion_stop_state_machine_; StateMachine temporal_stop_before_attention_state_machine_; + // NOTE: uuid_ is base member // for stuck vehicle detection diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 37dc83cbdf761..f381925b13208 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -16,10 +16,10 @@ #include "util_type.hpp" +#include #include #include #include -#include #include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 7ba894bd9d32a..6acc1d60443bf 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -20,14 +20,6 @@ #include -#include - -#ifdef ROS_DISTRO_GALACTIC -#include -#else -#include -#endif - #include #include #include diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 7e5bff89485ed..5c188f4aebebf 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -51,6 +51,7 @@ struct DebugData std::optional> nearest_occlusion_projection{std::nullopt}; autoware_auto_perception_msgs::msg::PredictedObjects blocking_attention_objects; + std::optional absence_traffic_light_creep_wall{std::nullopt}; }; struct InterpolatedPathInfo From a9bb25adda3f33adefaf5cccbc3402307a0f2e52 Mon Sep 17 00:00:00 2001 From: Felix F Xu <84662027+felixf4xu@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:18:37 +0800 Subject: [PATCH 403/547] build(lidar_apollo_segmentation_tvm): fix compiling error tier4_autoware_utils (#4516) * fix compiling error tier4_autoware_utils:: Signed-off-by: Felix F Xu * style(pre-commit): autofix --------- Signed-off-by: Felix F Xu Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> From 26e3be8569945733f2e76ba59a7077ccb0c29fab Mon Sep 17 00:00:00 2001 From: Yuki TAKAGI <141538661+yuki-takagi-66@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:14:34 +0900 Subject: [PATCH 404/547] fix(obstacle_cruise_planner): fix coordinate transformation bag (#5180) * fix coordinate transformation. linear.x denotes longtidional velocity, not the x velocity in the world coordinate Signed-off-by: Yuki Takagi * declare Eigen type Signed-off-by: Yuki Takagi --------- Signed-off-by: Yuki Takagi --- planning/obstacle_cruise_planner/src/node.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index b5e0718f20a49..7de6bb2c966f6 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -100,14 +100,15 @@ std::pair projectObstacleVelocityToTrajectory( const std::vector & traj_points, const Obstacle & obstacle) { const size_t object_idx = motion_utils::findNearestIndex(traj_points, obstacle.pose.position); - - const double object_vel_norm = std::hypot(obstacle.twist.linear.x, obstacle.twist.linear.y); - const double object_vel_yaw = std::atan2(obstacle.twist.linear.y, obstacle.twist.linear.x); const double traj_yaw = tf2::getYaw(traj_points.at(object_idx).pose.orientation); - return std::make_pair( - object_vel_norm * std::cos(object_vel_yaw - traj_yaw), - object_vel_norm * std::sin(object_vel_yaw - traj_yaw)); + const double obstacle_yaw = tf2::getYaw(obstacle.pose.orientation); + + const Eigen::Rotation2Dd R_ego_to_obstacle(obstacle_yaw - traj_yaw); + const Eigen::Vector2d obstacle_velocity(obstacle.twist.linear.x, obstacle.twist.linear.y); + const Eigen::Vector2d projected_velocity = R_ego_to_obstacle * obstacle_velocity; + + return std::make_pair(projected_velocity[0], projected_velocity[1]); } double calcObstacleMaxLength(const Shape & shape) From 9cf1f7cc4f8255dce34273eb8aba4ec59a1797a7 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Wed, 4 Oct 2023 15:14:03 +0900 Subject: [PATCH 405/547] perf(ndt_scan_matcher): changed default `initial_estimate_particles_num` to 200 (#5220) Changed initial_estimate_particles_num to 200 Signed-off-by: Shintaro Sakoda --- .../ndt_scan_matcher/config/ndt_scan_matcher.param.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml index 718bc6ca3b3a3..cf41a4cf55d0b 100644 --- a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml +++ b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml @@ -41,7 +41,7 @@ converged_param_nearest_voxel_transformation_likelihood: 2.3 # The number of particles to estimate initial pose - initial_estimate_particles_num: 100 + initial_estimate_particles_num: 200 # Tolerance of timestamp difference between current time and sensor pointcloud. [sec] lidar_topic_timeout_sec: 1.0 From bc6806390ffbe07b91d23e2a7ceea641280aa03a Mon Sep 17 00:00:00 2001 From: Kento Yabuuchi Date: Wed, 4 Oct 2023 15:53:57 +0900 Subject: [PATCH 406/547] feat(yabloc_image_processing): support both of raw and compressed image input (#5209) * add raw image subscriber Signed-off-by: Kento Yabuuchi * update README Signed-off-by: Kento Yabuuchi * improve format and variable names Signed-off-by: Kento Yabuuchi --------- Signed-off-by: Kento Yabuuchi --- .../pose_twist_estimator/yabloc.launch.xml | 2 +- .../yabloc/yabloc_image_processing/README.md | 13 +++-- .../launch/image_processing.launch.xml | 3 +- .../src/undistort/undistort_node.cpp | 53 ++++++++++++++----- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/launch/tier4_localization_launch/launch/pose_twist_estimator/yabloc.launch.xml b/launch/tier4_localization_launch/launch/pose_twist_estimator/yabloc.launch.xml index a9b0b93947054..8f0191d6156fe 100644 --- a/launch/tier4_localization_launch/launch/pose_twist_estimator/yabloc.launch.xml +++ b/launch/tier4_localization_launch/launch/pose_twist_estimator/yabloc.launch.xml @@ -1,7 +1,7 @@ - + diff --git a/localization/yabloc/yabloc_image_processing/README.md b/localization/yabloc/yabloc_image_processing/README.md index eb9ae658c39a7..85e1d408a1b4b 100644 --- a/localization/yabloc/yabloc_image_processing/README.md +++ b/localization/yabloc/yabloc_image_processing/README.md @@ -108,10 +108,15 @@ This node performs image resizing and undistortion at the same time. #### Input -| Name | Type | Description | -| ---------------------------- | ----------------------------------- | -------------------- | -| `input/camera_info` | `sensor_msgs::msg::CameraInfo` | raw compressed image | -| `input/image_raw/compressed` | `sensor_msgs::msg::CompressedImage` | raw sensor info | +| Name | Type | Description | +| ---------------------------- | ----------------------------------- | ----------------------- | +| `input/camera_info` | `sensor_msgs::msg::CameraInfo` | camera info | +| `input/image_raw` | `sensor_msgs::msg::Image` | raw camera image | +| `input/image_raw/compressed` | `sensor_msgs::msg::CompressedImage` | compressed camera image | + +This node subscribes to both compressed image and raw image topics. +If raw image is subscribed to even once, compressed image will no longer be subscribed to. +This is to avoid redundant decompression within Autoware. #### Output diff --git a/localization/yabloc/yabloc_image_processing/launch/image_processing.launch.xml b/localization/yabloc/yabloc_image_processing/launch/image_processing.launch.xml index fd55a8163123a..aa6fc10ee6667 100644 --- a/localization/yabloc/yabloc_image_processing/launch/image_processing.launch.xml +++ b/localization/yabloc/yabloc_image_processing/launch/image_processing.launch.xml @@ -1,5 +1,5 @@ - + @@ -9,6 +9,7 @@ + diff --git a/localization/yabloc/yabloc_image_processing/src/undistort/undistort_node.cpp b/localization/yabloc/yabloc_image_processing/src/undistort/undistort_node.cpp index 7412db28e1550..7fc9ad785dbe2 100644 --- a/localization/yabloc/yabloc_image_processing/src/undistort/undistort_node.cpp +++ b/localization/yabloc/yabloc_image_processing/src/undistort/undistort_node.cpp @@ -50,9 +50,12 @@ class UndistortNode : public rclcpp::Node } auto on_image = std::bind(&UndistortNode::on_image, this, _1); + auto on_compressed_image = std::bind(&UndistortNode::on_compressed_image, this, _1); auto on_info = std::bind(&UndistortNode::on_info, this, _1); - sub_image_ = - create_subscription("~/input/image_raw", qos, std::move(on_image)); + sub_image_ = create_subscription("~/input/image_raw", qos, std::move(on_image)); + sub_compressed_image_ = create_subscription( + "~/input/image_raw/compressed", qos, std::move(on_compressed_image)); + sub_info_ = create_subscription("~/input/camera_info", qos, std::move(on_info)); pub_info_ = create_publisher("~/output/resized_info", 10); @@ -63,7 +66,8 @@ class UndistortNode : public rclcpp::Node const int OUTPUT_WIDTH; const std::string OVERRIDE_FRAME_ID; - rclcpp::Subscription::SharedPtr sub_image_; + rclcpp::Subscription::SharedPtr sub_image_; + rclcpp::Subscription::SharedPtr sub_compressed_image_; rclcpp::Subscription::SharedPtr sub_info_; rclcpp::Publisher::SharedPtr pub_image_; rclcpp::Publisher::SharedPtr pub_info_; @@ -99,14 +103,8 @@ class UndistortNode : public rclcpp::Node scaled_info_->height = new_size.height; } - void on_image(const CompressedImage & msg) + void remap_and_publish(const cv::Mat & image, const std_msgs::msg::Header & header) { - if (!info_.has_value()) return; - if (undistort_map_x.empty()) make_remap_lut(); - - tier4_autoware_utils::StopWatch stop_watch; - cv::Mat image = common::decompress_to_cv_mat(msg); - cv::Mat undistorted_image; cv::remap(image, undistorted_image, undistort_map_x, undistort_map_y, cv::INTER_LINEAR); @@ -120,16 +118,47 @@ class UndistortNode : public rclcpp::Node // Publish Image { cv_bridge::CvImage bridge; - bridge.header.stamp = msg.header.stamp; + bridge.header.stamp = header.stamp; if (OVERRIDE_FRAME_ID != "") bridge.header.frame_id = OVERRIDE_FRAME_ID; else - bridge.header.frame_id = msg.header.frame_id; + bridge.header.frame_id = header.frame_id; bridge.encoding = "bgr8"; bridge.image = undistorted_image; pub_image_->publish(*bridge.toImageMsg()); } + } + void on_image(const Image & msg) + { + if (!info_.has_value()) { + return; + } + if (undistort_map_x.empty()) { + make_remap_lut(); + } + + // To remove redundant decompression, deactivate compressed image subscriber + if (sub_compressed_image_) { + sub_compressed_image_.reset(); + } + + tier4_autoware_utils::StopWatch stop_watch; + remap_and_publish(common::decompress_to_cv_mat(msg), msg.header); + RCLCPP_INFO_STREAM(get_logger(), "image undistort: " << stop_watch.toc() << "[ms]"); + } + + void on_compressed_image(const CompressedImage & msg) + { + if (!info_.has_value()) { + return; + } + if (undistort_map_x.empty()) { + make_remap_lut(); + } + + tier4_autoware_utils::StopWatch stop_watch; + remap_and_publish(common::decompress_to_cv_mat(msg), msg.header); RCLCPP_INFO_STREAM(get_logger(), "image undistort: " << stop_watch.toc() << "[ms]"); } From 5fed746365fd647c61eae400fbddc304088b9d4c Mon Sep 17 00:00:00 2001 From: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:47:58 +0900 Subject: [PATCH 407/547] chore: fix spell-check errors in perception packages (#5215) fix: cspell error Signed-off-by: Shunsuke Miura --- .../src/lidar_apollo_segmentation_tvm.cpp | 1 + perception/multi_object_tracker/models.md | 2 +- perception/traffic_light_classifier/README.md | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp b/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp index 905d70235e33e..e6c95f202106d 100644 --- a/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp +++ b/perception/lidar_apollo_segmentation_tvm/src/lidar_apollo_segmentation_tvm.cpp @@ -22,6 +22,7 @@ #include #include +// cspell: ignore bcnn using model_zoo::perception::lidar_obstacle_detection::baidu_cnn::onnx_bcnn::config; namespace autoware diff --git a/perception/multi_object_tracker/models.md b/perception/multi_object_tracker/models.md index c83038ffa7b59..f74967943be32 100644 --- a/perception/multi_object_tracker/models.md +++ b/perception/multi_object_tracker/models.md @@ -2,7 +2,7 @@ ## Tracking model - + ### CTRV model [1] diff --git a/perception/traffic_light_classifier/README.md b/perception/traffic_light_classifier/README.md index 7df0c5466695b..2aecf66f8fb7b 100644 --- a/perception/traffic_light_classifier/README.md +++ b/perception/traffic_light_classifier/README.md @@ -148,6 +148,8 @@ DATASET_ROOT #### Prerequisites + + **Step 1.** Download and install Miniconda from the [official website](https://mmpretrain.readthedocs.io/en/latest/get_started.html). **Step 2.** Create a conda virtual environment and activate it @@ -295,7 +297,7 @@ python tools/train.py configs/mobilenet_v2/mobilenet-v2_8xb32_custom.py Training logs and weights will be saved in the `work_dirs/mobilenet-v2_8xb32_custom` folder. -### Convert PyTorh model to ONNX model +### Convert PyTorch model to ONNX model #### Install mmdeploy From 36c17b338e390a7f40e1f0ee216c95e9354675f3 Mon Sep 17 00:00:00 2001 From: Kotaro Uetake <60615504+ktro2828@users.noreply.github.com> Date: Wed, 4 Oct 2023 20:33:34 +0900 Subject: [PATCH 408/547] refactor(heatmap_visualizer): add JSON Schema and remove default value definition (#4901) * refactor: add JSON Schema and remove default value defined in `declare_parameter()` Signed-off-by: ktro2828 * refactor: add configuration file and update launcher to load this Signed-off-by: ktro2828 * docs: update the docuemnt of core parameters Signed-off-by: ktro2828 * feat: add config into install to share Signed-off-by: ktro2828 --------- Signed-off-by: ktro2828 --- perception/heatmap_visualizer/CMakeLists.txt | 1 + perception/heatmap_visualizer/README.md | 17 ++-- .../config/heatmap_visualizer.param.yaml | 33 ++++++++ .../launch/heatmap_visualizer.launch.xml | 14 +--- .../schema/heatmap_visualizer.schema.json | 81 +++++++++++++++++++ .../src/heatmap_visualizer_node.cpp | 14 ++-- 6 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 perception/heatmap_visualizer/config/heatmap_visualizer.param.yaml create mode 100644 perception/heatmap_visualizer/schema/heatmap_visualizer.schema.json diff --git a/perception/heatmap_visualizer/CMakeLists.txt b/perception/heatmap_visualizer/CMakeLists.txt index ed995491f2af6..75fa02a1cdcf7 100644 --- a/perception/heatmap_visualizer/CMakeLists.txt +++ b/perception/heatmap_visualizer/CMakeLists.txt @@ -16,4 +16,5 @@ rclcpp_components_register_node(heatmap_visualizer_component ament_auto_package(INSTALL_TO_SHARE launch + config ) diff --git a/perception/heatmap_visualizer/README.md b/perception/heatmap_visualizer/README.md index dd8b6f2a7f2b2..747a158b3d487 100644 --- a/perception/heatmap_visualizer/README.md +++ b/perception/heatmap_visualizer/README.md @@ -44,14 +44,15 @@ When publishing, firstly these values are normalized to [0, 1] using maximum and ### Core Parameters -| Name | Type | Default Value | Description | -| ----------------------------- | ------ | ------------- | ------------------------------------------------- | -| `frame_count` | int | `50` | The number of frames to publish heatmap | -| `map_frame` | string | `base_link` | the frame of heatmap to be respected | -| `map_length` | float | `200.0` | the length of map in meter | -| `map_resolution` | float | `0.8` | the resolution of map | -| `use_confidence` | bool | `false` | the flag if use confidence score as heatmap value | -| `rename_car_to_truck_and_bus` | bool | `true` | the flag if rename car to truck or bus | +| Name | Type | Default Value | Description | +| --------------------- | ------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------- | +| `publish_frame_count` | int | `50` | The number of frames to publish heatmap | +| `heatmap_frame_id` | string | `base_link` | The frame ID of heatmap to be respected | +| `heatmap_length` | float | `200.0` | A length of map in meter | +| `heatmap_resolution` | float | `0.8` | A resolution of map | +| `use_confidence` | bool | `false` | A flag if use confidence score as heatmap value | +| `class_names` | array | `["UNKNOWN", "CAR", "TRUCK", "BUS", "TRAILER", "BICYCLE", "MOTORBIKE", "PEDESTRIAN"]` | An array of class names to be published | +| `rename_to_car` | bool | `true` | A flag if rename car like vehicle to car | ## Assumptions / Known limits diff --git a/perception/heatmap_visualizer/config/heatmap_visualizer.param.yaml b/perception/heatmap_visualizer/config/heatmap_visualizer.param.yaml new file mode 100644 index 0000000000000..2da863ea5e37a --- /dev/null +++ b/perception/heatmap_visualizer/config/heatmap_visualizer.param.yaml @@ -0,0 +1,33 @@ +# Copyright 2023 TIER IV, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +/**: + ros__parameters: + publish_frame_count: 50 + heatmap_frame_id: "base_link" + heatmap_length: 200.0 + heatmap_resolution: 0.8 + use_confidence: false + class_names: + [ + "UNKNOWN", + "CAR", + "TRUCK", + "BUS", + "TRAILER", + "BICYCLE", + "MOTORBIKE", + "PEDESTRIAN", + ] + rename_to_car: false diff --git a/perception/heatmap_visualizer/launch/heatmap_visualizer.launch.xml b/perception/heatmap_visualizer/launch/heatmap_visualizer.launch.xml index e87ae4e63a4d1..7251d776b6110 100644 --- a/perception/heatmap_visualizer/launch/heatmap_visualizer.launch.xml +++ b/perception/heatmap_visualizer/launch/heatmap_visualizer.launch.xml @@ -2,20 +2,10 @@ - - - - - - + - - - - - - + diff --git a/perception/heatmap_visualizer/schema/heatmap_visualizer.schema.json b/perception/heatmap_visualizer/schema/heatmap_visualizer.schema.json new file mode 100644 index 0000000000000..cb67ae383c35a --- /dev/null +++ b/perception/heatmap_visualizer/schema/heatmap_visualizer.schema.json @@ -0,0 +1,81 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Heatmap Visualizer Node", + "type": "object", + "definitions": { + "heatmap_visualizer": { + "type": "object", + "properties": { + "publish_frame_count": { + "type": "integer", + "description": "The number of frames to be published at once.", + "default": 50, + "minimum": 1 + }, + "heatmap_frame_id": { + "type": "string", + "description": "A frame ID in which visualized heatmap is with respect to.", + "default": "base_link" + }, + "heatmap_length": { + "type": "number", + "description": "A size length of heatmap [m].", + "default": 200.0, + "exclusiveMinimum": 0.0 + }, + "heatmap_resolution": { + "type": "number", + "description": "A cell resolution of heatmap [m].", + "default": 0.8, + "exclusiveMinimum": 0.0 + }, + "use_confidence": { + "type": "boolean", + "description": "Indicates whether to use object existence probability as score. It this parameter is false, 1.0 is set to score.", + "default": false + }, + "class_names": { + "type": "array", + "description": "An array of object class names.", + "default": [ + "UNKNOWN", + "CAR", + "TRUCK", + "BUS", + "TRAILER", + "BICYCLE", + "MOTORBIKE", + "PEDESTRIAN" + ], + "uniqueItems": true + }, + "rename_to_car": { + "type": "boolean", + "description": "Indicates whether to rename car like vehicle into car.", + "default": false + } + }, + "required": [ + "publish_frame_count", + "heatmap_frame_id", + "heatmap_length", + "heatmap_resolution", + "use_confidence", + "class_names", + "rename_to_car" + ] + } + }, + "properties": { + "/**": { + "type": "object", + "properties": { + "ros__parameters": { + "$ref": "#/definitions/heatmap_visualizer" + } + }, + "required": ["ros__parameters"] + } + }, + "required": ["/**"] +} diff --git a/perception/heatmap_visualizer/src/heatmap_visualizer_node.cpp b/perception/heatmap_visualizer/src/heatmap_visualizer_node.cpp index ad57d8638065f..d9337e0c9f61d 100644 --- a/perception/heatmap_visualizer/src/heatmap_visualizer_node.cpp +++ b/perception/heatmap_visualizer/src/heatmap_visualizer_node.cpp @@ -27,13 +27,13 @@ namespace heatmap_visualizer HeatmapVisualizerNode::HeatmapVisualizerNode(const rclcpp::NodeOptions & node_options) : Node("heatmap_visualizer", node_options), frame_count_(0) { - total_frame_ = declare_parameter("frame_count", 50); - map_frame_ = declare_parameter("map_frame", "base_link"); - map_length_ = declare_parameter("map_length", 200.0); - map_resolution_ = declare_parameter("map_resolution", 0.8); - use_confidence_ = declare_parameter("use_confidence", false); - class_names_ = declare_parameter("class_names", class_names_); - rename_car_to_truck_and_bus_ = declare_parameter("rename_car_to_truck_and_bus", false); + total_frame_ = static_cast(declare_parameter("publish_frame_count")); + map_frame_ = declare_parameter("heatmap_frame_id"); + map_length_ = static_cast(declare_parameter("heatmap_length")); + map_resolution_ = static_cast(declare_parameter("heatmap_resolution")); + use_confidence_ = declare_parameter("use_confidence"); + class_names_ = declare_parameter>("class_names"); + rename_car_to_truck_and_bus_ = declare_parameter("rename_to_car"); width_ = static_cast(map_length_ / map_resolution_); height_ = static_cast(map_length_ / map_resolution_); From 8770e1e7ff25d951b539b7d48f4e30024aa15cd8 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 4 Oct 2023 21:17:07 +0900 Subject: [PATCH 409/547] feat(intersection): ignore high curvature lane occlusion (#5223) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 2 + .../src/debug.cpp | 8 ++++ .../src/manager.cpp | 4 ++ .../src/scene_intersection.cpp | 5 ++- .../src/scene_intersection.hpp | 2 + .../src/util.cpp | 43 ++++++++++++++++++- .../src/util.hpp | 3 +- .../src/util_type.hpp | 1 + 8 files changed, 64 insertions(+), 4 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 5e74e837bf186..ccd3612203af5 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -57,6 +57,8 @@ absence_traffic_light: creep_velocity: 1.388 # [m/s] maximum_peeking_distance: 6.0 # [m] + attention_lane_crop_curvature_threshold: 0.25 + attention_lane_curvature_calculation_ds: 0.5 enable_rtc: intersection: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index c875611b16003..9ff638cb61876 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -173,6 +173,14 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( &debug_marker_array); } + if (debug_data_.occlusion_attention_area) { + appendMarkerArray( + createLaneletPolygonsMarkerArray( + debug_data_.occlusion_attention_area.value(), "occlusion_attention_area", lane_id_, 0.917, + 0.568, 0.596), + &debug_marker_array); + } + if (debug_data_.adjacent_area) { appendMarkerArray( createLaneletPolygonsMarkerArray( diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index d42c9b3ca2aa2..6f47dfbbe31fb 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -140,6 +140,10 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".occlusion.absence_traffic_light.creep_velocity"); ip.occlusion.absence_traffic_light.maximum_peeking_distance = getOrDeclareParameter( node, ns + ".occlusion.absence_traffic_light.maximum_peeking_distance"); + ip.occlusion.attention_lane_crop_curvature_threshold = + getOrDeclareParameter(node, ns + ".occlusion.attention_lane_crop_curvature_threshold"); + ip.occlusion.attention_lane_curvature_calculation_ds = + getOrDeclareParameter(node, ns + ".occlusion.attention_lane_curvature_calculation_ds"); } void IntersectionModuleManager::launchNewModules( diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 89cdeaae56723..10f1ed73bf435 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -989,6 +989,7 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const auto & occlusion_attention_lanelets = intersection_lanelets.occlusion_attention(); const auto & occlusion_attention_area = intersection_lanelets.occlusion_attention_area(); debug_data_.attention_area = intersection_lanelets.attention_area(); + debug_data_.occlusion_attention_area = occlusion_attention_area; debug_data_.adjacent_area = intersection_lanelets.adjacent_area(); // get intersection area @@ -1027,7 +1028,9 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( if (!occlusion_attention_divisions_) { occlusion_attention_divisions_ = util::generateDetectionLaneDivisions( occlusion_attention_lanelets, routing_graph_ptr, - planner_data_->occupancy_grid->info.resolution); + planner_data_->occupancy_grid->info.resolution, + planner_param_.occlusion.attention_lane_crop_curvature_threshold, + planner_param_.occlusion.attention_lane_curvature_calculation_ds); } const auto & occlusion_attention_divisions = occlusion_attention_divisions_.value(); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 7bd923ed6198c..e75c57ffb3fc7 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -119,6 +119,8 @@ class IntersectionModule : public SceneModuleInterface double creep_velocity; double maximum_peeking_distance; } absence_traffic_light; + double attention_lane_crop_curvature_threshold; + double attention_lane_curvature_calculation_ds; } occlusion; }; diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index f381925b13208..2f3fcc6f6fde0 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,22 @@ #include #include #include + +namespace tier4_autoware_utils +{ + +template <> +inline geometry_msgs::msg::Point getPoint(const lanelet::ConstPoint3d & p) +{ + geometry_msgs::msg::Point point; + point.x = p.x(); + point.y = p.y(); + point.z = p.z(); + return point; +} + +} // namespace tier4_autoware_utils + namespace behavior_velocity_planner { namespace bg = boost::geometry; @@ -809,10 +826,26 @@ TrafficPrioritizedLevel getTrafficPrioritizedLevel( return TrafficPrioritizedLevel::NOT_PRIORITIZED; } +double getHighestCurvature(const lanelet::ConstLineString3d & centerline) +{ + std::vector points; + for (auto point = centerline.begin(); point != centerline.end(); point++) { + points.push_back(*point); + } + + SplineInterpolationPoints2d interpolation(points); + const std::vector curvatures = interpolation.getSplineInterpolatedCurvatures(); + std::vector curvatures_positive; + for (const auto & curvature : curvatures) { + curvatures_positive.push_back(std::fabs(curvature)); + } + return *std::max_element(curvatures_positive.begin(), curvatures_positive.end()); +} + std::vector generateDetectionLaneDivisions( lanelet::ConstLanelets detection_lanelets_all, - [[maybe_unused]] const lanelet::routing::RoutingGraphPtr routing_graph_ptr, - const double resolution) + const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution, + const double curvature_threshold, const double curvature_calculation_ds) { using lanelet::utils::getCenterlineWithOffset; using lanelet::utils::to2D; @@ -824,6 +857,12 @@ std::vector generateDetectionLaneDivisions( if (turn_direction.compare("left") == 0 || turn_direction.compare("right") == 0) { continue; } + const auto fine_centerline = + lanelet::utils::generateFineCenterline(detection_lanelet, curvature_calculation_ds); + const double highest_curvature = getHighestCurvature(fine_centerline); + if (highest_curvature > curvature_threshold) { + continue; + } detection_lanelets.push_back(detection_lanelet); } diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 6acc1d60443bf..a75545353fb7a 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -109,7 +109,8 @@ TrafficPrioritizedLevel getTrafficPrioritizedLevel( std::vector generateDetectionLaneDivisions( lanelet::ConstLanelets detection_lanelets, - const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution); + const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution, + const double curvature_threshold, const double curvature_calculation_ds); std::optional generateInterpolatedPath( const int lane_id, const std::set & associative_lane_ids, diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 5c188f4aebebf..aed81d771e480 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -39,6 +39,7 @@ struct DebugData std::optional occlusion_first_stop_wall_pose{std::nullopt}; std::optional pass_judge_wall_pose{std::nullopt}; std::optional> attention_area{std::nullopt}; + std::optional> occlusion_attention_area{std::nullopt}; std::optional intersection_area{std::nullopt}; std::optional ego_lane{std::nullopt}; std::optional> adjacent_area{std::nullopt}; From 98004cedb5059b863f6c964cbacf6812736bd52b Mon Sep 17 00:00:00 2001 From: Esteve Fernandez <33620+esteve@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:06:57 +0200 Subject: [PATCH 410/547] build(yabloc_common): add missing libgoogle-glog-dev dependency (#5225) * build(yabloc_common): add missing libgoogle-glog-dev dependency Signed-off-by: Esteve Fernandez * style(pre-commit): autofix --------- Signed-off-by: Esteve Fernandez Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- localization/yabloc/yabloc_common/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/localization/yabloc/yabloc_common/package.xml b/localization/yabloc/yabloc_common/package.xml index f3f46952dec13..d7e94ebefa24b 100644 --- a/localization/yabloc/yabloc_common/package.xml +++ b/localization/yabloc/yabloc_common/package.xml @@ -16,6 +16,7 @@ geometry_msgs lanelet2_core lanelet2_extension + libgoogle-glog-dev pcl_conversions rclcpp sensor_msgs From 8649d930353151e4fc656e5f2b8a67f2de35f535 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 5 Oct 2023 09:14:48 +0900 Subject: [PATCH 411/547] refactor(safety_check): support lambda function in target filtering logic (#5210) * feat(safety_check): make it possible to set various conditions Signed-off-by: satoshi-ota * feat(utils): add util func Signed-off-by: satoshi-ota * refactor(start_planner): use common function Signed-off-by: satoshi-ota * refactor(avoidance): use common function Signed-off-by: satoshi-ota * refactor(goal_planner): use common function Signed-off-by: satoshi-ota * refactor(ABLC): use common function Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../path_safety_checker/objects_filtering.hpp | 6 +- .../behavior_path_planner/utils/utils.hpp | 2 + .../goal_planner/goal_planner_module.cpp | 8 ++- .../lane_change/avoidance_by_lane_change.cpp | 8 ++- .../start_planner/start_planner_module.cpp | 8 ++- .../src/utils/avoidance/utils.cpp | 65 ++++++++++--------- .../src/utils/goal_planner/goal_searcher.cpp | 7 +- .../path_safety_checker/objects_filtering.cpp | 26 ++------ .../start_planner/geometric_pull_out.cpp | 8 ++- .../utils/start_planner/shift_pull_out.cpp | 9 ++- .../behavior_path_planner/src/utils/utils.cpp | 13 ++++ 11 files changed, 102 insertions(+), 58 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp index e1256fd774027..4d5b9052a4681 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp @@ -117,14 +117,16 @@ void filterObjectsByClass( * lanelet. */ std::pair, std::vector> separateObjectIndicesByLanelets( - const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets); + const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets, + const std::function & condition); /** * @brief Separate the objects into two part based on whether the object is within lanelet. * @return Objects pair. first objects are in the lanelet, and second others are out of lanelet. */ std::pair separateObjectsByLanelets( - const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets); + const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets, + const std::function & condition); /** * @brief Get the predicted path from an object. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp index c5e5aa3e50191..3e03a09d3adf8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp @@ -336,6 +336,8 @@ std::optional getSignedDistanceFromBoundary( // misc +Polygon2d toPolygon2d(const lanelet::ConstLanelet & lanelet); + std::vector getTargetLaneletPolygons( const lanelet::ConstLanelets & lanelets, const Pose & pose, const double check_length, const std::string & target_type); diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index ce88760212d90..4de8720a4a52d 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -21,6 +21,7 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" #include @@ -1300,12 +1301,17 @@ bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const return false; } + const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); + const auto lanelet_polygon = utils::toPolygon2d(lanelet); + return !boost::geometry::disjoint(lanelet_polygon, object_polygon); + }; const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, parameters_->forward_goal_search_length); const auto [pull_over_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( - *(planner_data_->dynamic_object), pull_over_lanes); + *(planner_data_->dynamic_object), pull_over_lanes, condition); const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_over_lane_objects, parameters_->th_moving_object_velocity); const auto common_parameters = planner_data_->parameters; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp index 6d06b26cd1e17..ce49501b78d08 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp @@ -17,6 +17,7 @@ #include "behavior_path_planner/utils/avoidance/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include #include @@ -140,9 +141,14 @@ void AvoidanceByLaneChange::fillAvoidanceTargetObjects( { const auto p = std::dynamic_pointer_cast(avoidance_parameters_); + const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); + const auto lanelet_polygon = utils::toPolygon2d(lanelet); + return !boost::geometry::disjoint(lanelet_polygon, object_polygon); + }; const auto [object_within_target_lane, object_outside_target_lane] = utils::path_safety_checker::separateObjectsByLanelets( - *planner_data_->dynamic_object, data.current_lanelets); + *planner_data_->dynamic_object, data.current_lanelets, condition); // Assume that the maximum allocation for data.other object is the sum of // objects_within_target_lane and object_outside_target_lane. The maximum allocation for diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 092b91499252c..f19bcf30e7771 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -20,6 +20,7 @@ #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "motion_utils/trajectory/trajectory.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include #include @@ -770,9 +771,14 @@ std::vector StartPlannerModule::searchPullOutStartPoses( std::vector pull_out_start_pose{}; // filter pull out lanes stop objects + const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); + const auto lanelet_polygon = utils::toPolygon2d(lanelet); + return !boost::geometry::disjoint(lanelet_polygon, object_polygon); + }; const auto [pull_out_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( - *planner_data_->dynamic_object, status_.pull_out_lanes); + *planner_data_->dynamic_object, status_.pull_out_lanes, condition); const auto pull_out_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_out_lane_objects, parameters_->th_moving_object_velocity); diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index b2b4ecd2ea42a..86886e4414838 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -564,25 +564,6 @@ double getLongitudinalVelocity(const Pose & p_ref, const Pose & p_target, const return v * std::cos(calcYawDeviation(p_ref, p_target)); } -bool isCentroidWithinLanelets( - const PredictedObject & object, const lanelet::ConstLanelets & target_lanelets) -{ - if (target_lanelets.empty()) { - return false; - } - - const auto & object_pos = object.kinematics.initial_pose_with_covariance.pose.position; - lanelet::BasicPoint2d object_centroid(object_pos.x, object_pos.y); - - for (const auto & llt : target_lanelets) { - if (boost::geometry::within(object_centroid, llt.polygon2d().basicPolygon())) { - return true; - } - } - - return false; -} - lanelet::ConstLanelets getTargetLanelets( const std::shared_ptr & planner_data, lanelet::ConstLanelets & route_lanelets, const double left_offset, const double right_offset) @@ -1591,14 +1572,26 @@ std::vector getSafetyCheckTargetObjects( std::vector target_objects; - const auto append_target_objects = [&](const auto & check_lanes, const auto & objects) { - std::for_each(objects.begin(), objects.end(), [&](const auto & object) { - if (isCentroidWithinLanelets(object.object, check_lanes)) { - target_objects.push_back(utils::avoidance::transform(object.object, p)); - } + const auto append = [&](const auto & objects) { + std::for_each(objects.objects.begin(), objects.objects.end(), [&](const auto & object) { + target_objects.push_back(utils::avoidance::transform(object, p)); }); }; + const auto to_predicted_objects = [&p](const auto & objects) { + PredictedObjects ret{}; + std::for_each(objects.begin(), objects.end(), [&p, &ret](const auto & object) { + ret.objects.push_back(object.object); + }); + return ret; + }; + + const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { + const auto & object_pos = object.kinematics.initial_pose_with_covariance.pose.position; + lanelet::BasicPoint2d object_centroid(object_pos.x, object_pos.y); + return boost::geometry::within(object_centroid, lanelet.polygon2d().basicPolygon()); + }; + const auto unavoidable_objects = [&data]() { ObjectDataArray ret; std::for_each(data.target_objects.begin(), data.target_objects.end(), [&](const auto & object) { @@ -1614,11 +1607,15 @@ std::vector getSafetyCheckTargetObjects( const auto check_lanes = getAdjacentLane(planner_data, p, true); if (p->check_other_object) { - append_target_objects(check_lanes, data.other_objects); + const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( + to_predicted_objects(data.other_objects), check_lanes, condition); + append(targets); } if (p->check_unavoidable_object) { - append_target_objects(check_lanes, unavoidable_objects); + const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( + to_predicted_objects(unavoidable_objects), check_lanes, condition); + append(targets); } } @@ -1627,11 +1624,15 @@ std::vector getSafetyCheckTargetObjects( const auto check_lanes = getAdjacentLane(planner_data, p, false); if (p->check_other_object) { - append_target_objects(check_lanes, data.other_objects); + const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( + to_predicted_objects(data.other_objects), check_lanes, condition); + append(targets); } if (p->check_unavoidable_object) { - append_target_objects(check_lanes, unavoidable_objects); + const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( + to_predicted_objects(unavoidable_objects), check_lanes, condition); + append(targets); } } @@ -1640,11 +1641,15 @@ std::vector getSafetyCheckTargetObjects( const auto check_lanes = data.current_lanelets; if (p->check_other_object) { - append_target_objects(check_lanes, data.other_objects); + const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( + to_predicted_objects(data.other_objects), check_lanes, condition); + append(targets); } if (p->check_unavoidable_object) { - append_target_objects(check_lanes, unavoidable_objects); + const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( + to_predicted_objects(unavoidable_objects), check_lanes, condition); + append(targets); } } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index 11173154f6b3a..bd374c12427bc 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -266,13 +266,18 @@ void GoalSearcher::countObjectsToAvoid( void GoalSearcher::update(GoalCandidates & goal_candidates) const { + const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); + const auto lanelet_polygon = utils::toPolygon2d(lanelet); + return !boost::geometry::disjoint(lanelet_polygon, object_polygon); + }; const auto stop_objects = utils::path_safety_checker::filterObjectsByVelocity( *(planner_data_->dynamic_object), parameters_.th_moving_object_velocity); const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, parameters_.forward_goal_search_length); const auto [pull_over_lane_stop_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_over_lanes); + utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_over_lanes, condition); if (parameters_.prioritize_goals_before_objects) { countObjectsToAvoid(goal_candidates, pull_over_lane_stop_objects); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index ff54ce2d2dcc4..e63d35f7bc925 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -128,7 +128,8 @@ void filterObjectsByClass( } std::pair, std::vector> separateObjectIndicesByLanelets( - const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets) + const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets, + const std::function & condition) { if (target_lanelets.empty()) { return {}; @@ -138,25 +139,9 @@ std::pair, std::vector> separateObjectIndicesByLanel std::vector other_indices; for (size_t i = 0; i < objects.objects.size(); i++) { - // create object polygon - const auto & obj = objects.objects.at(i); - // create object polygon - const auto obj_polygon = tier4_autoware_utils::toPolygon2d(obj); bool is_filtered_object = false; for (const auto & llt : target_lanelets) { - // create lanelet polygon - const auto polygon2d = llt.polygon2d().basicPolygon(); - if (polygon2d.empty()) { - // no lanelet polygon - continue; - } - Polygon2d lanelet_polygon; - for (const auto & lanelet_point : polygon2d) { - lanelet_polygon.outer().emplace_back(lanelet_point.x(), lanelet_point.y()); - } - lanelet_polygon.outer().push_back(lanelet_polygon.outer().front()); - // check the object does not intersect the lanelet - if (!boost::geometry::disjoint(lanelet_polygon, obj_polygon)) { + if (condition(objects.objects.at(i), llt)) { target_indices.push_back(i); is_filtered_object = true; break; @@ -172,13 +157,14 @@ std::pair, std::vector> separateObjectIndicesByLanel } std::pair separateObjectsByLanelets( - const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets) + const PredictedObjects & objects, const lanelet::ConstLanelets & target_lanelets, + const std::function & condition) { PredictedObjects target_objects; PredictedObjects other_objects; const auto [target_indices, other_indices] = - separateObjectIndicesByLanelets(objects, target_lanelets); + separateObjectIndicesByLanelets(objects, target_lanelets, condition); target_objects.objects.reserve(target_indices.size()); other_objects.objects.reserve(other_indices.size()); diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index bc071e2a2a288..f4bfb4d681440 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -18,6 +18,7 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include @@ -62,10 +63,15 @@ boost::optional GeometricPullOut::plan(const Pose & start_pose, con // collision check with stop objects in pull out lanes const auto arc_path = planner_.getArcPath(); + const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); + const auto lanelet_polygon = utils::toPolygon2d(lanelet); + return !boost::geometry::disjoint(lanelet_polygon, object_polygon); + }; const auto & stop_objects = utils::path_safety_checker::filterObjectsByVelocity( *(planner_data_->dynamic_object), parameters_.th_moving_object_velocity); const auto [pull_out_lane_stop_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_out_lanes); + utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_out_lanes, condition); if (utils::checkCollisionBetweenPathFootprintsAndObjects( vehicle_footprint_, arc_path, pull_out_lane_stop_objects, diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index b82157cc17e41..02c6d41bdbf08 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -20,6 +20,7 @@ #include "behavior_path_planner/utils/start_planner/util.hpp" #include "behavior_path_planner/utils/utils.hpp" #include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include @@ -64,8 +65,14 @@ boost::optional ShiftPullOut::plan(const Pose & start_pose, const P } // extract stop objects in pull out lane for collision check + const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); + const auto lanelet_polygon = utils::toPolygon2d(lanelet); + return !boost::geometry::disjoint(lanelet_polygon, object_polygon); + }; const auto [pull_out_lane_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets(*dynamic_objects, pull_out_lanes); + utils::path_safety_checker::separateObjectsByLanelets( + *dynamic_objects, pull_out_lanes, condition); const auto pull_out_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_out_lane_objects, parameters_.th_moving_object_velocity); diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 7305405b50410..03063b5a9e2fe 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -2675,6 +2675,19 @@ double getArcLengthToTargetLanelet( std::min(arc_front.length - arc_pose.length, arc_back.length - arc_pose.length), 0.0); } +Polygon2d toPolygon2d(const lanelet::ConstLanelet & lanelet) +{ + Polygon2d polygon; + for (const auto & p : lanelet.polygon2d().basicPolygon()) { + polygon.outer().emplace_back(p.x(), p.y()); + } + polygon.outer().push_back(polygon.outer().front()); + + return tier4_autoware_utils::isClockwise(polygon) + ? polygon + : tier4_autoware_utils::inverseClockwise(polygon); +} + std::vector getTargetLaneletPolygons( const lanelet::PolygonLayer & map_polygons, lanelet::ConstLanelets & lanelets, const Pose & pose, const double check_length, const std::string & target_type) From c763eda68ede1ccc5b50290cb61f5775061aa212 Mon Sep 17 00:00:00 2001 From: badai nguyen <94814556+badai-nguyen@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:32:43 +0900 Subject: [PATCH 412/547] fix(detected_object_validation): change the points_num of the validator to be set class by class (#5177) * fix: add param for each object class Signed-off-by: badai-nguyen * fix: add missing classes param Signed-off-by: badai-nguyen * fix: launch file Signed-off-by: badai-nguyen * fix: typo Signed-off-by: badai-nguyen * chore: refactor Signed-off-by: badai-nguyen --------- Signed-off-by: badai-nguyen --- ...mera_lidar_fusion_based_detection.launch.xml | 1 + ...idar_radar_fusion_based_detection.launch.xml | 1 + .../detection/lidar_based_detection.launch.xml | 1 + .../launch/perception.launch.xml | 1 + ...stacle_pointcloud_based_validator.param.yaml | 13 +++++++++++++ .../obstacle_pointcloud_based_validator.hpp | 8 ++++---- ...stacle_pointcloud_based_validator.launch.xml | 2 ++ .../src/obstacle_pointcloud_based_validator.cpp | 17 ++++++++++++----- 8 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 perception/detected_object_validation/config/obstacle_pointcloud_based_validator.param.yaml diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index 178e193dc2d99..51359d8d8f0e2 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -314,6 +314,7 @@ + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index 38d141241c215..45cfb91bf25c1 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -348,6 +348,7 @@ + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml index 3b6b9ba652a24..c844db39f4e9d 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml @@ -115,6 +115,7 @@ + diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index b0219376e9625..3d4ce1035e72c 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -17,6 +17,7 @@ + diff --git a/perception/detected_object_validation/config/obstacle_pointcloud_based_validator.param.yaml b/perception/detected_object_validation/config/obstacle_pointcloud_based_validator.param.yaml new file mode 100644 index 0000000000000..745a0fd6591a9 --- /dev/null +++ b/perception/detected_object_validation/config/obstacle_pointcloud_based_validator.param.yaml @@ -0,0 +1,13 @@ +/**: + ros__parameters: + min_points_num: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [10, 10, 10, 10, 10, 10, 10, 10] + + max_points_num: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [10, 10, 10, 10, 10, 10, 10, 10] + + min_points_and_distance_ratio: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [800.0, 800.0, 800.0, 800.0, 800.0, 800.0, 800.0, 800.0] diff --git a/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/obstacle_pointcloud_based_validator.hpp b/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/obstacle_pointcloud_based_validator.hpp index 270e5c7bdb7ff..5819302664907 100644 --- a/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/obstacle_pointcloud_based_validator.hpp +++ b/perception/detected_object_validation/include/obstacle_pointcloud_based_validator/obstacle_pointcloud_based_validator.hpp @@ -32,14 +32,14 @@ #include #include - +#include namespace obstacle_pointcloud_based_validator { struct PointsNumThresholdParam { - size_t min_points_num; - size_t max_points_num; - float min_points_and_distance_ratio; + std::vector min_points_num; + std::vector max_points_num; + std::vector min_points_and_distance_ratio; }; class ObstaclePointCloudBasedValidator : public rclcpp::Node { diff --git a/perception/detected_object_validation/launch/obstacle_pointcloud_based_validator.launch.xml b/perception/detected_object_validation/launch/obstacle_pointcloud_based_validator.launch.xml index 8196e95f37957..799b605658345 100644 --- a/perception/detected_object_validation/launch/obstacle_pointcloud_based_validator.launch.xml +++ b/perception/detected_object_validation/launch/obstacle_pointcloud_based_validator.launch.xml @@ -3,11 +3,13 @@ + + diff --git a/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp b/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp index 00e53e9de9a9c..47640c9332e4d 100644 --- a/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp +++ b/perception/detected_object_validation/src/obstacle_pointcloud_based_validator.cpp @@ -92,10 +92,12 @@ ObstaclePointCloudBasedValidator::ObstaclePointCloudBasedValidator( objects_pub_ = create_publisher( "~/output/objects", rclcpp::QoS{1}); - points_num_threshold_param_.min_points_num = declare_parameter("min_points_num", 10); - points_num_threshold_param_.max_points_num = declare_parameter("max_points_num", 10); + points_num_threshold_param_.min_points_num = + declare_parameter>("min_points_num"); + points_num_threshold_param_.max_points_num = + declare_parameter>("max_points_num"); points_num_threshold_param_.min_points_and_distance_ratio = - declare_parameter("min_points_and_distance_ratio", 800.0); + declare_parameter>("min_points_and_distance_ratio"); const bool enable_debugger = declare_parameter("enable_debugger", false); if (enable_debugger) debugger_ = std::make_shared(this); @@ -134,6 +136,7 @@ void ObstaclePointCloudBasedValidator::onObjectsAndObstaclePointCloud( for (size_t i = 0; i < transformed_objects.objects.size(); ++i) { const auto & transformed_object = transformed_objects.objects.at(i); + const auto object_label_id = transformed_object.classification.front().label; const auto & object = input_objects->objects.at(i); const auto & transformed_object_position = transformed_object.kinematics.pose_with_covariance.pose.position; @@ -155,13 +158,17 @@ void ObstaclePointCloudBasedValidator::onObjectsAndObstaclePointCloud( if (debugger_) debugger_->addNeighborPointcloud(neighbor_pointcloud); // Filter object that have few pointcloud in them. + // TODO(badai-nguyen) add 3d validator option const auto num = getPointCloudNumWithinPolygon(transformed_object, neighbor_pointcloud); const auto object_distance = std::hypot(transformed_object_position.x, transformed_object_position.y); size_t min_pointcloud_num = std::clamp( static_cast( - points_num_threshold_param_.min_points_and_distance_ratio / object_distance + 0.5f), - points_num_threshold_param_.min_points_num, points_num_threshold_param_.max_points_num); + points_num_threshold_param_.min_points_and_distance_ratio.at(object_label_id) / + object_distance + + 0.5f), + static_cast(points_num_threshold_param_.min_points_num.at(object_label_id)), + static_cast(points_num_threshold_param_.max_points_num.at(object_label_id))); if (num) { (min_pointcloud_num <= num.value()) ? output.objects.push_back(object) : removed_objects.objects.push_back(object); From 02fda2f44217bdd57690302460f16ff3b9c4dcbe Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:16:32 +0900 Subject: [PATCH 413/547] refactor(safety_check): use common function in safety check library (#5217) refactor(avoidance): use common function Signed-off-by: satoshi-ota --- .../config/avoidance/avoidance.param.yaml | 10 ++- .../utils/avoidance/avoidance_module_data.hpp | 6 +- .../utils/avoidance/utils.hpp | 8 --- .../avoidance/avoidance_module.cpp | 14 ++-- .../src/scene_module/avoidance/manager.cpp | 22 ++++-- .../src/utils/avoidance/utils.cpp | 69 ++----------------- 6 files changed, 43 insertions(+), 86 deletions(-) diff --git a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml index 589233cf5a441..df2dd806fe2c7 100644 --- a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml +++ b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml @@ -139,12 +139,16 @@ check_other_object: true # [-] # collision check parameters check_all_predicted_path: false # [-] - time_resolution: 0.5 # [s] - time_horizon_for_front_object: 10.0 # [s] - time_horizon_for_rear_object: 10.0 # [s] safety_check_backward_distance: 100.0 # [m] hysteresis_factor_expand_rate: 2.0 # [-] hysteresis_factor_safe_count: 10 # [-] + # predicted path parameters + min_velocity: 1.38 # [m/s] + max_velocity: 50.0 # [m/s] + time_resolution: 0.5 # [s] + time_horizon_for_front_object: 10.0 # [s] + time_horizon_for_rear_object: 10.0 # [s] + delay_until_departure: 0.0 # [s] # rss parameters expected_front_deceleration: -1.0 # [m/ss] expected_rear_deceleration: -1.0 # [m/ss] diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 634ac69bae12c..6173ecb15a5c2 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -183,9 +183,6 @@ struct AvoidanceParameters // parameters for collision check. bool check_all_predicted_path{false}; - double time_horizon_for_front_object{0.0}; - double time_horizon_for_rear_object{0.0}; - double safety_check_time_resolution{0.0}; // find adjacent lane vehicles double safety_check_backward_distance{0.0}; @@ -295,6 +292,9 @@ struct AvoidanceParameters // parameters depend on object class std::unordered_map object_parameters; + // ego predicted path params. + utils::path_safety_checker::EgoPredictedPathParams ego_predicted_path_params{}; + // rss parameters utils::path_safety_checker::RSSparams rss_params{}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index 44d5d6f6b4e56..c0bd621cc86b4 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -87,11 +87,6 @@ std::vector generateObstaclePolygonsForDrivableArea( const ObjectDataArray & objects, const std::shared_ptr & parameters, const double vehicle_width); -std::vector convertToPredictedPath( - const PathWithLaneId & path, const std::shared_ptr & planner_data, - const bool is_object_front, const bool limit_to_max_velocity, - const std::shared_ptr & parameters); - double getLongitudinalVelocity(const Pose & p_ref, const Pose & p_target, const double v); bool isCentroidWithinLanelets( @@ -162,9 +157,6 @@ AvoidLine fillAdditionalInfo(const AvoidancePlanningData & data, const AvoidLine AvoidLineArray combineRawShiftLinesWithUniqueCheck( const AvoidLineArray & base_lines, const AvoidLineArray & added_lines); -ExtendedPredictedObject transform( - const PredictedObject & object, const std::shared_ptr & parameters); - std::vector getSafetyCheckTargetObjects( const AvoidancePlanningData & data, const std::shared_ptr & planner_data, const std::shared_ptr & parameters, const bool is_right_shift); diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 36908bd87de23..a8dd0b794245e 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -1839,10 +1839,16 @@ bool AvoidanceModule::isSafePath( } const bool limit_to_max_velocity = false; - const auto ego_predicted_path_for_front_object = utils::avoidance::convertToPredictedPath( - shifted_path.path, planner_data_, true, limit_to_max_velocity, parameters_); - const auto ego_predicted_path_for_rear_object = utils::avoidance::convertToPredictedPath( - shifted_path.path, planner_data_, false, limit_to_max_velocity, parameters_); + const auto ego_predicted_path_params = + std::make_shared( + parameters_->ego_predicted_path_params); + const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(shifted_path.path.points); + const auto ego_predicted_path_for_front_object = utils::path_safety_checker::createPredictedPath( + ego_predicted_path_params, shifted_path.path.points, getEgoPose(), getEgoSpeed(), ego_seg_idx, + true, limit_to_max_velocity); + const auto ego_predicted_path_for_rear_object = utils::path_safety_checker::createPredictedPath( + ego_predicted_path_params, shifted_path.path.points, getEgoPose(), getEgoSpeed(), ego_seg_idx, + false, limit_to_max_velocity); const auto ego_idx = planner_data_->findEgoIndex(shifted_path.path.points); const auto is_right_shift = [&]() -> std::optional { diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp index 912f867fd5701..8c65323e5a123 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp @@ -142,11 +142,6 @@ AvoidanceModuleManager::AvoidanceModuleManager( p.check_other_object = getOrDeclareParameter(*node, ns + "check_other_object"); p.check_all_predicted_path = getOrDeclareParameter(*node, ns + "check_all_predicted_path"); - p.time_horizon_for_front_object = - getOrDeclareParameter(*node, ns + "time_horizon_for_front_object"); - p.time_horizon_for_rear_object = - getOrDeclareParameter(*node, ns + "time_horizon_for_rear_object"); - p.safety_check_time_resolution = getOrDeclareParameter(*node, ns + "time_resolution"); p.safety_check_backward_distance = getOrDeclareParameter(*node, ns + "safety_check_backward_distance"); p.hysteresis_factor_expand_rate = @@ -155,6 +150,23 @@ AvoidanceModuleManager::AvoidanceModuleManager( getOrDeclareParameter(*node, ns + "hysteresis_factor_safe_count"); } + // safety check predicted path params + { + std::string ns = "avoidance.safety_check."; + p.ego_predicted_path_params.min_velocity = + getOrDeclareParameter(*node, ns + "min_velocity"); + p.ego_predicted_path_params.max_velocity = + getOrDeclareParameter(*node, ns + "max_velocity"); + p.ego_predicted_path_params.acceleration = + getOrDeclareParameter(*node, "avoidance.constraints.longitudinal.max_acceleration"); + p.ego_predicted_path_params.time_horizon_for_rear_object = + getOrDeclareParameter(*node, ns + "time_horizon_for_rear_object"); + p.ego_predicted_path_params.time_resolution = + getOrDeclareParameter(*node, ns + "time_resolution"); + p.ego_predicted_path_params.delay_until_departure = + getOrDeclareParameter(*node, ns + "delay_until_departure"); + } + // safety check rss params { std::string ns = "avoidance.safety_check."; diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 86886e4414838..d7da362402197 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -1457,68 +1457,6 @@ AvoidLineArray combineRawShiftLinesWithUniqueCheck( return combined; } -std::vector convertToPredictedPath( - const PathWithLaneId & path, const std::shared_ptr & planner_data, - const bool is_object_front, const bool limit_to_max_velocity, - const std::shared_ptr & parameters) -{ - const auto & vehicle_pose = planner_data->self_odometry->pose.pose; - const auto & initial_velocity = std::abs(planner_data->self_odometry->twist.twist.linear.x); - const size_t ego_seg_idx = planner_data->findEgoSegmentIndex(path.points); - - auto ego_predicted_path_params = - std::make_shared(); - - ego_predicted_path_params->min_velocity = parameters->min_slow_down_speed; - ego_predicted_path_params->acceleration = parameters->max_acceleration; - ego_predicted_path_params->max_velocity = std::numeric_limits::infinity(); - ego_predicted_path_params->time_horizon_for_front_object = - parameters->time_horizon_for_front_object; - ego_predicted_path_params->time_horizon_for_rear_object = - parameters->time_horizon_for_rear_object; - ego_predicted_path_params->time_resolution = parameters->safety_check_time_resolution; - ego_predicted_path_params->delay_until_departure = 0.0; - - return behavior_path_planner::utils::path_safety_checker::createPredictedPath( - ego_predicted_path_params, path.points, vehicle_pose, initial_velocity, ego_seg_idx, - is_object_front, limit_to_max_velocity); -} - -ExtendedPredictedObject transform( - const PredictedObject & object, const std::shared_ptr & parameters) -{ - ExtendedPredictedObject extended_object; - extended_object.uuid = object.object_id; - extended_object.initial_pose = object.kinematics.initial_pose_with_covariance; - extended_object.initial_twist = object.kinematics.initial_twist_with_covariance; - extended_object.initial_acceleration = object.kinematics.initial_acceleration_with_covariance; - extended_object.shape = object.shape; - - const auto & obj_velocity_norm = std::hypot( - extended_object.initial_twist.twist.linear.x, extended_object.initial_twist.twist.linear.y); - const auto & time_horizon = - std::max(parameters->time_horizon_for_front_object, parameters->time_horizon_for_rear_object); - const auto & time_resolution = parameters->safety_check_time_resolution; - - extended_object.predicted_paths.resize(object.kinematics.predicted_paths.size()); - for (size_t i = 0; i < object.kinematics.predicted_paths.size(); ++i) { - const auto & path = object.kinematics.predicted_paths.at(i); - extended_object.predicted_paths.at(i).confidence = path.confidence; - - // create path - for (double t = 0.0; t < time_horizon + 1e-3; t += time_resolution) { - const auto obj_pose = object_recognition_utils::calcInterpolatedPose(path, t); - if (obj_pose) { - const auto obj_polygon = tier4_autoware_utils::toPolygon2d(*obj_pose, object.shape); - extended_object.predicted_paths.at(i).path.emplace_back( - t, *obj_pose, obj_velocity_norm, obj_polygon); - } - } - } - - return extended_object; -} - lanelet::ConstLanelets getAdjacentLane( const std::shared_ptr & planner_data, const std::shared_ptr & parameters, const bool is_right_shift) @@ -1572,9 +1510,14 @@ std::vector getSafetyCheckTargetObjects( std::vector target_objects; + const auto time_horizon = std::max( + parameters->ego_predicted_path_params.time_horizon_for_front_object, + parameters->ego_predicted_path_params.time_horizon_for_rear_object); + const auto append = [&](const auto & objects) { std::for_each(objects.objects.begin(), objects.objects.end(), [&](const auto & object) { - target_objects.push_back(utils::avoidance::transform(object, p)); + target_objects.push_back(utils::path_safety_checker::transform( + object, time_horizon, parameters->ego_predicted_path_params.time_resolution)); }); }; From dd275bf94e78e5c18d43a2228759812c267d2184 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Thu, 5 Oct 2023 14:43:08 +0900 Subject: [PATCH 414/547] fix(lane_change): current lane obj treated as target lane obj (#5214) Signed-off-by: Muhammad Zulfaqar Azmi --- .../src/scene_module/lane_change/normal.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index d8fa9cb5630e9..c75970e6fcc2d 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -683,6 +683,16 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( lanelet::utils::getLateralDistanceToClosestLanelet(current_lanes, current_pose); std::vector> target_backward_polygons; for (const auto & target_backward_lane : target_backward_lanes) { + // Check to see is target_backward_lane is in current_lanes + // Without this check, current lane object might be treated as target lane object + const auto is_current_lane = [&](const lanelet::ConstLanelet & current_lane) { + return current_lane.id() == target_backward_lane.id(); + }; + + if (std::any_of(current_lanes.begin(), current_lanes.end(), is_current_lane)) { + continue; + } + lanelet::ConstLanelets lanelet{target_backward_lane}; auto lane_polygon = utils::lane_change::createPolygon(lanelet, 0.0, std::numeric_limits::max()); From 877b04b133175fc39444a9400cd83ae8f22de5b4 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 5 Oct 2023 15:51:15 +0900 Subject: [PATCH 415/547] fix(blind_spot): fix runtime crash (#5227) Signed-off-by: Mamoru Sobue --- planning/behavior_velocity_blind_spot_module/src/debug.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/planning/behavior_velocity_blind_spot_module/src/debug.cpp b/planning/behavior_velocity_blind_spot_module/src/debug.cpp index f95ed73c2a184..7da494bfd5231 100644 --- a/planning/behavior_velocity_blind_spot_module/src/debug.cpp +++ b/planning/behavior_velocity_blind_spot_module/src/debug.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include #include From 8e971bcc8ef480e2791e436d7eb979f82d324e54 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Thu, 5 Oct 2023 17:22:20 +0900 Subject: [PATCH 416/547] fix(behavior_velocity_crosswalk_module): stop at stop line associated with crosswalk (#5231) don't consider margin when stop line is found Signed-off-by: kyoichi-sugahara --- .../behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index 62acd191d8697..fe96985743b94 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -299,8 +299,7 @@ std::optional> CrosswalkModule::get const auto p_stop_lines = getLinestringIntersects( ego_path, lanelet::utils::to2D(stop_line).basicLineString(), ego_pos, 2); if (!p_stop_lines.empty()) { - return std::make_pair( - p_stop_lines.front(), -planner_param_.stop_distance_from_object - base_link2front); + return std::make_pair(p_stop_lines.front(), -base_link2front); } } From b0310e769c9184e70a3b2eecd616cd1eb066fd18 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 5 Oct 2023 18:49:30 +0900 Subject: [PATCH 417/547] refactor(safety_check): define filtering function in safety check library (#5228) Signed-off-by: satoshi-ota --- .../path_safety_checker/objects_filtering.hpp | 18 ++++++++++++++ .../goal_planner/goal_planner_module.cpp | 8 ++----- .../lane_change/avoidance_by_lane_change.cpp | 8 ++----- .../start_planner/start_planner_module.cpp | 8 ++----- .../src/utils/avoidance/utils.cpp | 24 +++++++++---------- .../src/utils/goal_planner/goal_searcher.cpp | 8 ++----- .../path_safety_checker/objects_filtering.cpp | 14 +++++++++++ .../start_planner/geometric_pull_out.cpp | 8 ++----- .../utils/start_planner/shift_pull_out.cpp | 7 +----- 9 files changed, 55 insertions(+), 48 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp index 4d5b9052a4681..15b15bac40b51 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp @@ -38,6 +38,24 @@ using autoware_auto_perception_msgs::msg::PredictedObject; using autoware_auto_perception_msgs::msg::PredictedObjects; using autoware_auto_planning_msgs::msg::PathPointWithLaneId; +/** + * @brief Filters objects based on object centroid position. + * + * @param objects The predicted objects to filter. + * @param lanelet + * @return result. + */ +bool isCentroidWithinLanelet(const PredictedObject & object, const lanelet::ConstLanelet & lanelet); + +/** + * @brief Filters objects based on object polygon overlapping with lanelet. + * + * @param objects The predicted objects to filter. + * @param lanelet + * @return result. + */ +bool isPolygonOverlapLanelet(const PredictedObject & object, const lanelet::ConstLanelet & lanelet); + /** * @brief Filters objects based on various criteria. * diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 4de8720a4a52d..9c2544cf60cec 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -1301,17 +1301,13 @@ bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const return false; } - const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); - const auto lanelet_polygon = utils::toPolygon2d(lanelet); - return !boost::geometry::disjoint(lanelet_polygon, object_polygon); - }; const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, parameters_->forward_goal_search_length); const auto [pull_over_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( - *(planner_data_->dynamic_object), pull_over_lanes, condition); + *(planner_data_->dynamic_object), pull_over_lanes, + utils::path_safety_checker::isPolygonOverlapLanelet); const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_over_lane_objects, parameters_->th_moving_object_velocity); const auto common_parameters = planner_data_->parameters; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp index ce49501b78d08..1f305e3988828 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp @@ -141,14 +141,10 @@ void AvoidanceByLaneChange::fillAvoidanceTargetObjects( { const auto p = std::dynamic_pointer_cast(avoidance_parameters_); - const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); - const auto lanelet_polygon = utils::toPolygon2d(lanelet); - return !boost::geometry::disjoint(lanelet_polygon, object_polygon); - }; const auto [object_within_target_lane, object_outside_target_lane] = utils::path_safety_checker::separateObjectsByLanelets( - *planner_data_->dynamic_object, data.current_lanelets, condition); + *planner_data_->dynamic_object, data.current_lanelets, + utils::path_safety_checker::isPolygonOverlapLanelet); // Assume that the maximum allocation for data.other object is the sum of // objects_within_target_lane and object_outside_target_lane. The maximum allocation for diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index f19bcf30e7771..c828a747856d8 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -771,14 +771,10 @@ std::vector StartPlannerModule::searchPullOutStartPoses( std::vector pull_out_start_pose{}; // filter pull out lanes stop objects - const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); - const auto lanelet_polygon = utils::toPolygon2d(lanelet); - return !boost::geometry::disjoint(lanelet_polygon, object_polygon); - }; const auto [pull_out_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( - *planner_data_->dynamic_object, status_.pull_out_lanes, condition); + *planner_data_->dynamic_object, status_.pull_out_lanes, + utils::path_safety_checker::isPolygonOverlapLanelet); const auto pull_out_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_out_lane_objects, parameters_->th_moving_object_velocity); diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index d7da362402197..0e7eb87feed9b 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -1529,12 +1529,6 @@ std::vector getSafetyCheckTargetObjects( return ret; }; - const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { - const auto & object_pos = object.kinematics.initial_pose_with_covariance.pose.position; - lanelet::BasicPoint2d object_centroid(object_pos.x, object_pos.y); - return boost::geometry::within(object_centroid, lanelet.polygon2d().basicPolygon()); - }; - const auto unavoidable_objects = [&data]() { ObjectDataArray ret; std::for_each(data.target_objects.begin(), data.target_objects.end(), [&](const auto & object) { @@ -1551,13 +1545,15 @@ std::vector getSafetyCheckTargetObjects( if (p->check_other_object) { const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( - to_predicted_objects(data.other_objects), check_lanes, condition); + to_predicted_objects(data.other_objects), check_lanes, + utils::path_safety_checker::isCentroidWithinLanelet); append(targets); } if (p->check_unavoidable_object) { const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( - to_predicted_objects(unavoidable_objects), check_lanes, condition); + to_predicted_objects(unavoidable_objects), check_lanes, + utils::path_safety_checker::isCentroidWithinLanelet); append(targets); } } @@ -1568,13 +1564,15 @@ std::vector getSafetyCheckTargetObjects( if (p->check_other_object) { const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( - to_predicted_objects(data.other_objects), check_lanes, condition); + to_predicted_objects(data.other_objects), check_lanes, + utils::path_safety_checker::isCentroidWithinLanelet); append(targets); } if (p->check_unavoidable_object) { const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( - to_predicted_objects(unavoidable_objects), check_lanes, condition); + to_predicted_objects(unavoidable_objects), check_lanes, + utils::path_safety_checker::isCentroidWithinLanelet); append(targets); } } @@ -1585,13 +1583,15 @@ std::vector getSafetyCheckTargetObjects( if (p->check_other_object) { const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( - to_predicted_objects(data.other_objects), check_lanes, condition); + to_predicted_objects(data.other_objects), check_lanes, + utils::path_safety_checker::isCentroidWithinLanelet); append(targets); } if (p->check_unavoidable_object) { const auto [targets, others] = utils::path_safety_checker::separateObjectsByLanelets( - to_predicted_objects(unavoidable_objects), check_lanes, condition); + to_predicted_objects(unavoidable_objects), check_lanes, + utils::path_safety_checker::isCentroidWithinLanelet); append(targets); } } diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index bd374c12427bc..12044980ebd81 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -266,18 +266,14 @@ void GoalSearcher::countObjectsToAvoid( void GoalSearcher::update(GoalCandidates & goal_candidates) const { - const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); - const auto lanelet_polygon = utils::toPolygon2d(lanelet); - return !boost::geometry::disjoint(lanelet_polygon, object_polygon); - }; const auto stop_objects = utils::path_safety_checker::filterObjectsByVelocity( *(planner_data_->dynamic_object), parameters_.th_moving_object_velocity); const auto pull_over_lanes = goal_planner_utils::getPullOverLanes( *(planner_data_->route_handler), left_side_parking_, parameters_.backward_goal_search_length, parameters_.forward_goal_search_length); const auto [pull_over_lane_stop_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_over_lanes, condition); + utils::path_safety_checker::separateObjectsByLanelets( + stop_objects, pull_over_lanes, utils::path_safety_checker::isPolygonOverlapLanelet); if (parameters_.prioritize_goals_before_objects) { countObjectsToAvoid(goal_candidates, pull_over_lane_stop_objects); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index e63d35f7bc925..dfc9f76b25e33 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -24,6 +24,20 @@ namespace behavior_path_planner::utils::path_safety_checker { +bool isCentroidWithinLanelet(const PredictedObject & object, const lanelet::ConstLanelet & lanelet) +{ + const auto & object_pos = object.kinematics.initial_pose_with_covariance.pose.position; + lanelet::BasicPoint2d object_centroid(object_pos.x, object_pos.y); + return boost::geometry::within(object_centroid, lanelet.polygon2d().basicPolygon()); +} + +bool isPolygonOverlapLanelet(const PredictedObject & object, const lanelet::ConstLanelet & lanelet) +{ + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); + const auto lanelet_polygon = utils::toPolygon2d(lanelet); + return !boost::geometry::disjoint(lanelet_polygon, object_polygon); +} + PredictedObjects filterObjects( const std::shared_ptr & objects, const std::shared_ptr & route_handler, const lanelet::ConstLanelets & current_lanes, diff --git a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp index f4bfb4d681440..3cf42dbb5da26 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/geometric_pull_out.cpp @@ -63,15 +63,11 @@ boost::optional GeometricPullOut::plan(const Pose & start_pose, con // collision check with stop objects in pull out lanes const auto arc_path = planner_.getArcPath(); - const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); - const auto lanelet_polygon = utils::toPolygon2d(lanelet); - return !boost::geometry::disjoint(lanelet_polygon, object_polygon); - }; const auto & stop_objects = utils::path_safety_checker::filterObjectsByVelocity( *(planner_data_->dynamic_object), parameters_.th_moving_object_velocity); const auto [pull_out_lane_stop_objects, others] = - utils::path_safety_checker::separateObjectsByLanelets(stop_objects, pull_out_lanes, condition); + utils::path_safety_checker::separateObjectsByLanelets( + stop_objects, pull_out_lanes, utils::path_safety_checker::isPolygonOverlapLanelet); if (utils::checkCollisionBetweenPathFootprintsAndObjects( vehicle_footprint_, arc_path, pull_out_lane_stop_objects, diff --git a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp index 02c6d41bdbf08..a430e18c330e1 100644 --- a/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp +++ b/planning/behavior_path_planner/src/utils/start_planner/shift_pull_out.cpp @@ -65,14 +65,9 @@ boost::optional ShiftPullOut::plan(const Pose & start_pose, const P } // extract stop objects in pull out lane for collision check - const auto condition = [](const PredictedObject & object, const lanelet::ConstLanelet & lanelet) { - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object); - const auto lanelet_polygon = utils::toPolygon2d(lanelet); - return !boost::geometry::disjoint(lanelet_polygon, object_polygon); - }; const auto [pull_out_lane_objects, others] = utils::path_safety_checker::separateObjectsByLanelets( - *dynamic_objects, pull_out_lanes, condition); + *dynamic_objects, pull_out_lanes, utils::path_safety_checker::isPolygonOverlapLanelet); const auto pull_out_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_out_lane_objects, parameters_.th_moving_object_velocity); From e12817df60b2579b1be513950d463b6652c4e717 Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka <16330533+scepter914@users.noreply.github.com> Date: Fri, 6 Oct 2023 09:03:28 +0900 Subject: [PATCH 418/547] fix(tier4_perception_launch): fix faraway detection to reduce calculation cost (#5233) * fix(tier4_perception_launch): fix node order in radar_based_detection.launch Signed-off-by: scepter914 * fix comment out unused node Signed-off-by: scepter914 --------- Signed-off-by: scepter914 --- .../detection/radar_based_detection.launch.xml | 16 ++++++++-------- .../tracking/tracking.launch.xml | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml index 52c8aedff8ef9..5b5646a061ac7 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/radar_based_detection.launch.xml @@ -20,14 +20,8 @@ - - - - - - - + @@ -40,8 +34,14 @@ + + + + + + - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml index 1ba1e8de9d42e..77de1e5995ee0 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml @@ -15,6 +15,7 @@ + From 6dfb197f95771421a7fc9acbdafe2028856ce789 Mon Sep 17 00:00:00 2001 From: danielsanchezaran Date: Fri, 6 Oct 2023 13:53:46 +0900 Subject: [PATCH 419/547] feat(obstacle_cruise_planner): obstacle type dependent slow down for obstacle cruise planner (#5208) * Add slow down parameters dependant on obstacle type Signed-off-by: Daniel Sanchez * divide obstacle parameters based on obstacle type Signed-off-by: Daniel Sanchez * preserve obstacle type for velocity calculation Signed-off-by: Daniel Sanchez * added pre-commit changes Signed-off-by: Daniel Sanchez * change sample config params Signed-off-by: Daniel Sanchez * Update Readme Signed-off-by: Daniel Sanchez * Update README Signed-off-by: Daniel Sanchez * small refactor for cleaner code Signed-off-by: Daniel Sanchez --------- Signed-off-by: Daniel Sanchez Signed-off-by: Daniel Sanchez --- planning/obstacle_cruise_planner/README.md | 21 +++-- .../config/obstacle_cruise_planner.param.yaml | 17 +++- .../common_structs.hpp | 8 +- .../planner_interface.hpp | 80 +++++++++++++++---- planning/obstacle_cruise_planner/src/node.cpp | 6 +- .../src/planner_interface.cpp | 7 +- 6 files changed, 101 insertions(+), 38 deletions(-) diff --git a/planning/obstacle_cruise_planner/README.md b/planning/obstacle_cruise_planner/README.md index 314f9dcc29393..260f302791079 100644 --- a/planning/obstacle_cruise_planner/README.md +++ b/planning/obstacle_cruise_planner/README.md @@ -230,14 +230,19 @@ $$ ### Slow down planning -| Parameter | Type | Description | -| ---------------------------- | ------ | ------------------------------------------------------------------- | -| `slow_down.min_lat_velocity` | double | minimum velocity to linearly calculate slow down velocity [m] | -| `slow_down.max_lat_velocity` | double | maximum velocity to linearly calculate slow down velocity [m] | -| `slow_down.min_lat_margin` | double | minimum lateral margin to linearly calculate slow down velocity [m] | -| `slow_down.max_lat_margin` | double | maximum lateral margin to linearly calculate slow down velocity [m] | - -The role of the slow down planning is inserting slow down velocity in the trajectory where the trajectory points are close to the obstacles. +| Parameter | Type | Description | +| ----------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `slow_down.labels` | vector(string) | A vector of labels for customizing obstacle-label-based slow down behavior. Each label represents an obstacle type that will be treated differently when applying slow down. The possible labels are ("default" (Mandatory), "unknown","car","truck","bus","trailer","motorcycle","bicycle" or "pedestrian") | +| `slow_down.default.min_lat_velocity` | double | minimum velocity to linearly calculate slow down velocity [m]. Note: This default value will be used when the detected obstacle label does not match any of the slow_down.labels | +| `slow_down.default.max_lat_velocity` | double | maximum velocity to linearly calculate slow down velocity [m]. Note: This default value will be used when the detected obstacle label does not match any of the slow_down.labels | +| `slow_down.default.min_lat_margin` | double | minimum lateral margin to linearly calculate slow down velocity [m]. Note: This default value will be used when the detected obstacle label does not match any of the slow_down.labels | +| `slow_down.default.max_lat_margin` | double | maximum lateral margin to linearly calculate slow down velocity [m]. Note: This default value will be used when the detected obstacle label does not match any of the slow_down.labels | +| `(optional) slow_down."label".min_lat_velocity` | double | minimum velocity to linearly calculate slow down velocity [m]. Note: only for obstacles specified in `slow_down.labels` | +| `(optional) slow_down."label".max_lat_velocity` | double | maximum velocity to linearly calculate slow down velocity [m]. Note: only for obstacles specified in `slow_down.labels` | +| `(optional) slow_down."label".min_lat_margin` | double | minimum lateral margin to linearly calculate slow down velocity [m]. Note: only for obstacles specified in `slow_down.labels` | +| `(optional) slow_down."label".max_lat_margin` | double | maximum lateral margin to linearly calculate slow down velocity [m]. Note: only for obstacles specified in `slow_down.labels` | + +The role of the slow down planning is inserting slow down velocity in the trajectory where the trajectory points are close to the obstacles. The parameters can be customized depending on the obstacle type (see `slow_down.labels`), making it possible to adjust the slow down behavior depending if the obstacle is a pedestrian, bicycle, car, etc. The closest point on the obstacle to the ego's trajectory is calculated. Then, the slow down velocity is calculated by linear interpolation with the distance between the point and trajectory as follows. diff --git a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml index 73bc4a83249a4..123d0dda93b7a 100644 --- a/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml +++ b/planning/obstacle_cruise_planner/config/obstacle_cruise_planner.param.yaml @@ -179,10 +179,19 @@ slow_down: # parameters to calculate slow down velocity by linear interpolation - min_lat_margin: 0.2 - max_lat_margin: 1.0 - min_ego_velocity: 2.0 - max_ego_velocity: 8.0 + labels: + - "default" + - "pedestrian" + default: + min_lat_margin: 0.2 + max_lat_margin: 1.0 + min_ego_velocity: 2.0 + max_ego_velocity: 8.0 + pedestrian: + min_lat_margin: 0.5 + max_lat_margin: 1.5 + min_ego_velocity: 1.0 + max_ego_velocity: 2.0 time_margin_on_target_velocity: 1.5 # [s] diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp index e958074971f2a..c6cb73a680e5a 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/common_structs.hpp @@ -137,19 +137,21 @@ struct SlowDownObstacle : public TargetObstacleInterface { SlowDownObstacle( const std::string & arg_uuid, const rclcpp::Time & arg_stamp, - const geometry_msgs::msg::Pose & arg_pose, const double arg_lon_velocity, - const double arg_lat_velocity, const double arg_precise_lat_dist, + const ObjectClassification & object_classification, const geometry_msgs::msg::Pose & arg_pose, + const double arg_lon_velocity, const double arg_lat_velocity, const double arg_precise_lat_dist, const geometry_msgs::msg::Point & arg_front_collision_point, const geometry_msgs::msg::Point & arg_back_collision_point) : TargetObstacleInterface(arg_uuid, arg_stamp, arg_pose, arg_lon_velocity, arg_lat_velocity), precise_lat_dist(arg_precise_lat_dist), front_collision_point(arg_front_collision_point), - back_collision_point(arg_back_collision_point) + back_collision_point(arg_back_collision_point), + classification(object_classification) { } double precise_lat_dist; // for efficient calculation geometry_msgs::msg::Point front_collision_point; geometry_msgs::msg::Point back_collision_point; + ObjectClassification classification; }; struct LongitudinalInfo diff --git a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp index 9a5a6521a6494..d8c31fb35df9b 100644 --- a/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp +++ b/planning/obstacle_cruise_planner/include/obstacle_cruise_planner/planner_interface.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -221,12 +222,42 @@ class PlannerInterface struct SlowDownParam { + std::vector obstacle_labels{"default"}; + std::unordered_map types_map; + struct ObstacleSpecificParams + { + double max_lat_margin; + double min_lat_margin; + double max_ego_velocity; + double min_ego_velocity; + }; explicit SlowDownParam(rclcpp::Node & node) { - max_lat_margin = node.declare_parameter("slow_down.max_lat_margin"); - min_lat_margin = node.declare_parameter("slow_down.min_lat_margin"); - max_ego_velocity = node.declare_parameter("slow_down.max_ego_velocity"); - min_ego_velocity = node.declare_parameter("slow_down.min_ego_velocity"); + types_map = {{ObjectClassification::UNKNOWN, "unknown"}, + {ObjectClassification::CAR, "car"}, + {ObjectClassification::TRUCK, "truck"}, + {ObjectClassification::BUS, "bus"}, + {ObjectClassification::TRAILER, "trailer"}, + {ObjectClassification::MOTORCYCLE, "motorcycle"}, + {ObjectClassification::BICYCLE, "bicycle"}, + {ObjectClassification::PEDESTRIAN, "pedestrian"}}; + obstacle_labels = + node.declare_parameter>("slow_down.labels", obstacle_labels); + // obstacle label dependant parameters + for (const auto & label : obstacle_labels) { + ObstacleSpecificParams params; + params.max_lat_margin = + node.declare_parameter("slow_down." + label + ".max_lat_margin"); + params.min_lat_margin = + node.declare_parameter("slow_down." + label + ".min_lat_margin"); + params.max_ego_velocity = + node.declare_parameter("slow_down." + label + ".max_ego_velocity"); + params.min_ego_velocity = + node.declare_parameter("slow_down." + label + ".min_ego_velocity"); + obstacle_to_param_struct_map.emplace(std::make_pair(label, params)); + } + + // common parameters time_margin_on_target_velocity = node.declare_parameter("slow_down.time_margin_on_target_velocity"); lpf_gain_slow_down_vel = node.declare_parameter("slow_down.lpf_gain_slow_down_vel"); @@ -235,16 +266,35 @@ class PlannerInterface node.declare_parameter("slow_down.lpf_gain_dist_to_slow_down"); } + ObstacleSpecificParams getObstacleParamByLabel(const ObjectClassification & label_id) const + { + const std::string label = types_map.at(label_id.label); + if (obstacle_to_param_struct_map.count(label) > 0) { + return obstacle_to_param_struct_map.at(label); + } + return obstacle_to_param_struct_map.at("default"); + } + void onParam(const std::vector & parameters) { - tier4_autoware_utils::updateParam( - parameters, "slow_down.max_lat_margin", max_lat_margin); - tier4_autoware_utils::updateParam( - parameters, "slow_down.min_lat_margin", min_lat_margin); - tier4_autoware_utils::updateParam( - parameters, "slow_down.max_ego_velocity", max_ego_velocity); - tier4_autoware_utils::updateParam( - parameters, "slow_down.min_ego_velocity", min_ego_velocity); + // obstacle type dependant parameters + for (const auto & label : obstacle_labels) { + auto & param_by_obstacle_label = obstacle_to_param_struct_map[label]; + tier4_autoware_utils::updateParam( + parameters, "slow_down." + label + ".max_lat_margin", + param_by_obstacle_label.max_lat_margin); + tier4_autoware_utils::updateParam( + parameters, "slow_down." + label + ".min_lat_margin", + param_by_obstacle_label.min_lat_margin); + tier4_autoware_utils::updateParam( + parameters, "slow_down." + label + ".max_ego_velocity", + param_by_obstacle_label.max_ego_velocity); + tier4_autoware_utils::updateParam( + parameters, "slow_down." + label + ".min_ego_velocity", + param_by_obstacle_label.min_ego_velocity); + } + + // common parameters tier4_autoware_utils::updateParam( parameters, "slow_down.time_margin_on_target_velocity", time_margin_on_target_velocity); tier4_autoware_utils::updateParam( @@ -255,10 +305,8 @@ class PlannerInterface parameters, "slow_down.lpf_gain_dist_to_slow_down", lpf_gain_dist_to_slow_down); } - double max_lat_margin; - double min_lat_margin; - double max_ego_velocity; - double min_ego_velocity; + std::unordered_map obstacle_to_param_struct_map; + double time_margin_on_target_velocity; double lpf_gain_slow_down_vel; double lpf_gain_lat_dist; diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index 7de6bb2c966f6..d50054da46237 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -1175,9 +1175,9 @@ std::optional ObstacleCruisePlannerNode::createSlowDownObstacl } const auto [tangent_vel, normal_vel] = projectObstacleVelocityToTrajectory(traj_points, obstacle); - return SlowDownObstacle{obstacle.uuid, obstacle.stamp, obstacle.pose, - tangent_vel, normal_vel, precise_lat_dist, - front_collision_point, back_collision_point}; + return SlowDownObstacle{obstacle.uuid, obstacle.stamp, obstacle.classification, + obstacle.pose, tangent_vel, normal_vel, + precise_lat_dist, front_collision_point, back_collision_point}; } void ObstacleCruisePlannerNode::checkConsistency( diff --git a/planning/obstacle_cruise_planner/src/planner_interface.cpp b/planning/obstacle_cruise_planner/src/planner_interface.cpp index 287df10dcebb5..76469364cfb39 100644 --- a/planning/obstacle_cruise_planner/src/planner_interface.cpp +++ b/planning/obstacle_cruise_planner/src/planner_interface.cpp @@ -665,7 +665,7 @@ std::vector PlannerInterface::generateSlowDownTrajectory( double PlannerInterface::calculateSlowDownVelocity( const SlowDownObstacle & obstacle, const std::optional & prev_output) const { - const auto & p = slow_down_param_; + const auto & p = slow_down_param_.getObstacleParamByLabel(obstacle.classification); const double stable_precise_lat_dist = [&]() { if (prev_output) { @@ -691,7 +691,6 @@ PlannerInterface::calculateDistanceToSlowDownWithConstraints( const SlowDownObstacle & obstacle, const std::optional & prev_output, const double dist_to_ego) const { - const auto & p = slow_down_param_; const double abs_ego_offset = planner_data.is_driving_forward ? std::abs(vehicle_info_.max_longitudinal_offset_m) : std::abs(vehicle_info_.min_longitudinal_offset_m); @@ -728,8 +727,8 @@ PlannerInterface::calculateDistanceToSlowDownWithConstraints( // calculate distance during deceleration, slow down preparation, and slow down const double min_slow_down_prepare_dist = 3.0; - const double slow_down_prepare_dist = - std::max(min_slow_down_prepare_dist, slow_down_vel * p.time_margin_on_target_velocity); + const double slow_down_prepare_dist = std::max( + min_slow_down_prepare_dist, slow_down_vel * slow_down_param_.time_margin_on_target_velocity); const double deceleration_dist = offset_dist_to_collision + dist_to_front_collision - abs_ego_offset - dist_to_ego - slow_down_prepare_dist; const double slow_down_dist = From 950805fda122ac31f9784e841ac5106b201bc1ca Mon Sep 17 00:00:00 2001 From: Yuki TAKAGI <141538661+yuki-takagi-66@users.noreply.github.com> Date: Fri, 6 Oct 2023 13:54:15 +0900 Subject: [PATCH 420/547] feat(intersection)!: disable the exception behavior in the private areas (#5229) * feat: make configurable to disable the exception treat of stuck obstacle in the private areas feat: change behavior in the private areas Signed-off-by: Yuki Takagi * delete the coment outed lines Signed-off-by: Yuki Takagi * change "enabled" to "enable" Signed-off-by: Yuki Takagi * fix setting Signed-off-by: Yuki Takagi * delete unused variables Signed-off-by: Yuki Takagi --------- Signed-off-by: Yuki Takagi --- .../config/intersection.param.yaml | 1 + .../src/manager.cpp | 2 ++ .../src/scene_intersection.cpp | 11 ++--------- .../src/scene_intersection.hpp | 2 +- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index ccd3612203af5..e210d2fb21b88 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -19,6 +19,7 @@ # enable_front_car_decel_prediction: false # By default this feature is disabled # assumed_front_car_decel: 1.0 # [m/ss] the expected deceleration of front car when front car as well as ego are turning timeout_private_area: 3.0 # [s] cancel stuck vehicle stop in private area + enable_private_area_stuck_disregard: false #In the intersections which labeled as "private area", the obstacle vehicles judged as "stuck" are neglected if this param is set as true. collision_detection: state_transit_margin_time: 1.0 diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 6f47dfbbe31fb..3c96a171516bd 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -77,6 +77,8 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) */ ip.stuck_vehicle.timeout_private_area = getOrDeclareParameter(node, ns + ".stuck_vehicle.timeout_private_area"); + ip.stuck_vehicle.enable_private_area_stuck_disregard = + getOrDeclareParameter(node, ns + ".stuck_vehicle.enable_private_area_stuck_disregard"); ip.collision_detection.min_predicted_path_confidence = getOrDeclareParameter(node, ns + ".collision_detection.min_predicted_path_confidence"); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 10f1ed73bf435..b30a57f9e8dad 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -96,10 +96,6 @@ IntersectionModule::IntersectionModule( occlusion_stop_state_machine_.setMarginTime(planner_param_.occlusion.stop_release_margin_time); occlusion_stop_state_machine_.setState(StateMachine::State::GO); } - { - stuck_private_area_timeout_.setMarginTime(planner_param_.stuck_vehicle.timeout_private_area); - stuck_private_area_timeout_.setState(StateMachine::State::STOP); - } { temporal_stop_before_attention_state_machine_.setMarginTime( planner_param_.occlusion.before_creep_stop_time); @@ -918,16 +914,13 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const bool stuck_detected = checkStuckVehicle(planner_data_, path_lanelets); if (stuck_detected && stuck_stop_line_idx_opt) { auto stuck_stop_line_idx = stuck_stop_line_idx_opt.value(); - const bool stopped_at_stuck_line = stoppedForDuration( - stuck_stop_line_idx, planner_param_.stuck_vehicle.timeout_private_area, - stuck_private_area_timeout_); - const bool timeout = (is_private_area_ && stopped_at_stuck_line); - if (!timeout) { + if (is_private_area_ && planner_param_.stuck_vehicle.enable_private_area_stuck_disregard) { if ( default_stop_line_idx_opt && fromEgoDist(stuck_stop_line_idx) < -planner_param_.common.stop_overshoot_margin) { stuck_stop_line_idx = default_stop_line_idx_opt.value(); } + } else { return IntersectionModule::StuckStop{ closest_idx, stuck_stop_line_idx, occlusion_peeking_stop_line_idx_opt}; } diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index e75c57ffb3fc7..b5cf3ec04cdc3 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -70,6 +70,7 @@ class IntersectionModule : public SceneModuleInterface bool enable_front_car_decel_prediction; //! flag for using above feature */ double timeout_private_area; + bool enable_private_area_stuck_disregard; } stuck_vehicle; struct CollisionDetection { @@ -255,7 +256,6 @@ class IntersectionModule : public SceneModuleInterface // for stuck vehicle detection const bool is_private_area_; - StateMachine stuck_private_area_timeout_; // for RTC const UUID occlusion_uuid_; From 057d3d4259f69e0c83e7897a954ddac3f4818fcc Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Fri, 6 Oct 2023 20:16:42 +0900 Subject: [PATCH 421/547] feat(lane_change): separate execution and cancel safety check param (#5246) Signed-off-by: Zulfaqar Azmi --- .../config/lane_change/lane_change.param.yaml | 26 ++++--- .../lane_change/lane_change_module_data.hpp | 1 + .../src/scene_module/lane_change/manager.cpp | 70 +++++++++++++------ 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml index fda7390abdb9e..e7f3b51bd2d26 100644 --- a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml +++ b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml @@ -28,15 +28,23 @@ # safety check safety_check: - expected_front_deceleration: -1.0 - expected_rear_deceleration: -1.0 - expected_front_deceleration_for_abort: -1.0 - expected_rear_deceleration_for_abort: -2.0 - rear_vehicle_reaction_time: 2.0 - rear_vehicle_safety_time_margin: 1.0 - lateral_distance_max_threshold: 2.0 - longitudinal_distance_min_threshold: 3.0 - longitudinal_velocity_delta_time: 0.8 + allow_loose_check_for_cancel: true + execution: + expected_front_deceleration: -1.0 + expected_rear_deceleration: -1.0 + rear_vehicle_reaction_time: 2.0 + rear_vehicle_safety_time_margin: 1.0 + lateral_distance_max_threshold: 2.0 + longitudinal_distance_min_threshold: 3.0 + longitudinal_velocity_delta_time: 0.8 + cancel: + expected_front_deceleration: -1.0 + expected_rear_deceleration: -2.0 + rear_vehicle_reaction_time: 1.5 + rear_vehicle_safety_time_margin: 0.8 + lateral_distance_max_threshold: 1.0 + longitudinal_distance_min_threshold: 2.5 + longitudinal_velocity_delta_time: 0.6 # lane expansion for object filtering lane_expansion: diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index 0ba35635a9143..24a507d1f8695 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -82,6 +82,7 @@ struct LaneChangeParameters utils::path_safety_checker::ObjectTypesToCheck object_types_to_check; // safety check + bool allow_loose_check_for_cancel{true}; utils::path_safety_checker::RSSparams rss_params{}; utils::path_safety_checker::RSSparams rss_params_for_abort{}; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index 9c45f79d00263..e33a6c4b05ee3 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -92,35 +92,41 @@ LaneChangeModuleManager::LaneChangeModuleManager( p.stop_time_threshold = getOrDeclareParameter(*node, parameter("stuck_detection.stop_time")); + // safety check + p.allow_loose_check_for_cancel = + getOrDeclareParameter(*node, parameter("safety_check.allow_loose_check_for_cancel")); + p.rss_params.longitudinal_distance_min_threshold = getOrDeclareParameter( - *node, parameter("safety_check.longitudinal_distance_min_threshold")); + *node, parameter("safety_check.execution.longitudinal_distance_min_threshold")); + p.rss_params.longitudinal_distance_min_threshold = getOrDeclareParameter( + *node, parameter("safety_check.execution.longitudinal_distance_min_threshold")); p.rss_params.longitudinal_velocity_delta_time = getOrDeclareParameter( - *node, parameter("safety_check.longitudinal_velocity_delta_time")); - p.rss_params.front_vehicle_deceleration = - getOrDeclareParameter(*node, parameter("safety_check.expected_front_deceleration")); - p.rss_params.rear_vehicle_deceleration = - getOrDeclareParameter(*node, parameter("safety_check.expected_rear_deceleration")); - p.rss_params.rear_vehicle_reaction_time = - getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_reaction_time")); - p.rss_params.rear_vehicle_safety_time_margin = - getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_safety_time_margin")); - p.rss_params.lateral_distance_max_threshold = - getOrDeclareParameter(*node, parameter("safety_check.lateral_distance_max_threshold")); + *node, parameter("safety_check.execution.longitudinal_velocity_delta_time")); + p.rss_params.front_vehicle_deceleration = getOrDeclareParameter( + *node, parameter("safety_check.execution.expected_front_deceleration")); + p.rss_params.rear_vehicle_deceleration = getOrDeclareParameter( + *node, parameter("safety_check.execution.expected_rear_deceleration")); + p.rss_params.rear_vehicle_reaction_time = getOrDeclareParameter( + *node, parameter("safety_check.execution.rear_vehicle_reaction_time")); + p.rss_params.rear_vehicle_safety_time_margin = getOrDeclareParameter( + *node, parameter("safety_check.execution.rear_vehicle_safety_time_margin")); + p.rss_params.lateral_distance_max_threshold = getOrDeclareParameter( + *node, parameter("safety_check.execution.lateral_distance_max_threshold")); p.rss_params_for_abort.longitudinal_distance_min_threshold = getOrDeclareParameter( - *node, parameter("safety_check.longitudinal_distance_min_threshold")); + *node, parameter("safety_check.cancel.longitudinal_distance_min_threshold")); p.rss_params_for_abort.longitudinal_velocity_delta_time = getOrDeclareParameter( - *node, parameter("safety_check.longitudinal_velocity_delta_time")); + *node, parameter("safety_check.cancel.longitudinal_velocity_delta_time")); p.rss_params_for_abort.front_vehicle_deceleration = getOrDeclareParameter( - *node, parameter("safety_check.expected_front_deceleration_for_abort")); + *node, parameter("safety_check.cancel.expected_front_deceleration")); p.rss_params_for_abort.rear_vehicle_deceleration = getOrDeclareParameter( - *node, parameter("safety_check.expected_rear_deceleration_for_abort")); - p.rss_params_for_abort.rear_vehicle_reaction_time = - getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_reaction_time")); - p.rss_params_for_abort.rear_vehicle_safety_time_margin = - getOrDeclareParameter(*node, parameter("safety_check.rear_vehicle_safety_time_margin")); - p.rss_params_for_abort.lateral_distance_max_threshold = - getOrDeclareParameter(*node, parameter("safety_check.lateral_distance_max_threshold")); + *node, parameter("safety_check.cancel.expected_rear_deceleration")); + p.rss_params_for_abort.rear_vehicle_reaction_time = getOrDeclareParameter( + *node, parameter("safety_check.cancel.rear_vehicle_reaction_time")); + p.rss_params_for_abort.rear_vehicle_safety_time_margin = getOrDeclareParameter( + *node, parameter("safety_check.cancel.rear_vehicle_safety_time_margin")); + p.rss_params_for_abort.lateral_distance_max_threshold = getOrDeclareParameter( + *node, parameter("safety_check.cancel.lateral_distance_max_threshold")); // target object { @@ -165,6 +171,26 @@ LaneChangeModuleManager::LaneChangeModuleManager( exit(EXIT_FAILURE); } + // validation of safety check parameters + // if loosely check is not allowed, lane change module will keep on chattering and canceling, and + // false positive situation might occur + if (!p.allow_loose_check_for_cancel) { + if ( + p.rss_params.front_vehicle_deceleration > p.rss_params_for_abort.front_vehicle_deceleration || + p.rss_params.rear_vehicle_deceleration > p.rss_params_for_abort.rear_vehicle_deceleration || + p.rss_params.rear_vehicle_reaction_time > p.rss_params_for_abort.rear_vehicle_reaction_time || + p.rss_params.rear_vehicle_safety_time_margin > + p.rss_params_for_abort.rear_vehicle_safety_time_margin || + p.rss_params.lateral_distance_max_threshold > + p.rss_params_for_abort.lateral_distance_max_threshold || + p.rss_params.longitudinal_distance_min_threshold > + p.rss_params_for_abort.longitudinal_distance_min_threshold || + p.rss_params.longitudinal_velocity_delta_time > + p.rss_params_for_abort.longitudinal_velocity_delta_time) { + RCLCPP_FATAL_STREAM(logger_, "abort parameter might be loose... Terminating the program..."); + exit(EXIT_FAILURE); + } + } if (p.cancel.delta_time < 1.0) { RCLCPP_WARN_STREAM( logger_, "cancel.delta_time: " << p.cancel.delta_time From 0c48944c343cd74a2acb26cf428f113eb536a7f6 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Sat, 7 Oct 2023 01:26:59 +0900 Subject: [PATCH 422/547] feat(intersection): yield initially on green light (#5234) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 4 + .../src/manager.cpp | 8 ++ .../src/scene_intersection.cpp | 76 ++++++++++++++++++- .../src/scene_intersection.hpp | 29 ++++--- 4 files changed, 103 insertions(+), 14 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index e210d2fb21b88..972f572890d16 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -37,6 +37,10 @@ keep_detection_vel_thr: 0.833 # == 3.0km/h. keep detection if ego is ego.vel < keep_detection_vel_thr use_upstream_velocity: true # flag to use the planned velocity profile from the upstream module minimum_upstream_velocity: 0.01 # [m/s] minimum velocity to avoid null division for the stop line from the upstream velocity + yield_on_green_traffic_light: + distance_to_assigned_lanelet_start: 5.0 + duration: 2.0 + range: 30.0 # [m] occlusion: enable: false diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 3c96a171516bd..4137090a5b6ae 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -109,6 +109,14 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".collision_detection.use_upstream_velocity"); ip.collision_detection.minimum_upstream_velocity = getOrDeclareParameter(node, ns + ".collision_detection.minimum_upstream_velocity"); + ip.collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start = + getOrDeclareParameter( + node, + ns + ".collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start"); + ip.collision_detection.yield_on_green_traffic_light.duration = getOrDeclareParameter( + node, ns + ".collision_detection.yield_on_green_traffic_light.duration"); + ip.collision_detection.yield_on_green_traffic_light.range = getOrDeclareParameter( + node, ns + ".collision_detection.yield_on_green_traffic_light.range"); ip.occlusion.enable = getOrDeclareParameter(node, ns + ".occlusion.enable"); ip.occlusion.occlusion_attention_area_length = diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index b30a57f9e8dad..75bb4e861a60f 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -30,14 +30,15 @@ #include #include +#include #include #include #include +#include #include #include #include - namespace behavior_velocity_planner { namespace bg = boost::geometry; @@ -807,6 +808,36 @@ bool IntersectionModule::modifyPathVelocity(PathWithLaneId * path, StopReason * return true; } +static bool isGreenSolidOn( + lanelet::ConstLanelet lane, const std::map & tl_infos) +{ + using TrafficSignalElement = autoware_perception_msgs::msg::TrafficSignalElement; + + std::optional tl_id = std::nullopt; + for (auto && tl_reg_elem : lane.regulatoryElementsAs()) { + tl_id = tl_reg_elem->id(); + break; + } + if (!tl_id) { + // this lane has no traffic light + return false; + } + const auto tl_info_it = tl_infos.find(tl_id.value()); + if (tl_info_it == tl_infos.end()) { + // the info of this traffic light is not available + return false; + } + const auto & tl_info = tl_info_it->second; + for (auto && tl_light : tl_info.signal.elements) { + if ( + tl_light.color == TrafficSignalElement::GREEN && + tl_light.shape == TrafficSignalElement::CIRCLE) { + return true; + } + } + return false; +} + IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( PathWithLaneId * path, [[maybe_unused]] StopReason * stop_reason) { @@ -994,13 +1025,52 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( debug_data_.intersection_area = toGeomPoly(intersection_area_2d); } + const auto target_objects = + filterTargetObjects(attention_lanelets, adjacent_lanelets, intersection_area); + + // If there are any vehicles on the attention area when ego entered the intersection on green + // light, do pseudo collision detection because the vehicles are very slow and no collisions may + // be detected. check if ego vehicle entered assigned lanelet + const bool is_green_solid_on = + isGreenSolidOn(assigned_lanelet, planner_data_->traffic_light_id_map); + if (is_green_solid_on) { + if (!initial_green_light_observed_time_) { + const auto assigned_lane_begin_point = assigned_lanelet.centerline().front(); + const bool approached_assigned_lane = + motion_utils::calcSignedArcLength( + path->points, closest_idx, + tier4_autoware_utils::createPoint( + assigned_lane_begin_point.x(), assigned_lane_begin_point.y(), + assigned_lane_begin_point.z())) < + planner_param_.collision_detection.yield_on_green_traffic_light + .distance_to_assigned_lanelet_start; + if (approached_assigned_lane) { + initial_green_light_observed_time_ = clock_->now(); + } + } + if (initial_green_light_observed_time_) { + const auto now = clock_->now(); + const bool exist_close_vehicles = std::any_of( + target_objects.objects.begin(), target_objects.objects.end(), [&](const auto & object) { + return tier4_autoware_utils::calcDistance3d( + object.kinematics.initial_pose_with_covariance.pose, current_pose) < + planner_param_.collision_detection.yield_on_green_traffic_light.range; + }); + if ( + exist_close_vehicles && + rclcpp::Duration((now - initial_green_light_observed_time_.value())).seconds() < + planner_param_.collision_detection.yield_on_green_traffic_light.duration) { + return IntersectionModule::NonOccludedCollisionStop{ + closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; + } + } + } + // calculate dynamic collision around attention area const double time_to_restart = (is_go_out_ || is_prioritized) ? 0.0 : (planner_param_.collision_detection.state_transit_margin_time - collision_state_machine_.getDuration()); - const auto target_objects = - filterTargetObjects(attention_lanelets, adjacent_lanelets, intersection_area); const bool has_collision = checkCollision( *path, target_objects, path_lanelets, closest_idx, diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index b5cf3ec04cdc3..de7d97a50c133 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -96,6 +96,12 @@ class IntersectionModule : public SceneModuleInterface double keep_detection_vel_thr; //! keep detection if ego is ego.vel < keep_detection_vel_thr bool use_upstream_velocity; double minimum_upstream_velocity; + struct YieldOnGreeTrafficLight + { + double distance_to_assigned_lanelet_start; + double duration; + double range; + } yield_on_green_traffic_light; } collision_detection; struct Occlusion { @@ -234,36 +240,37 @@ class IntersectionModule : public SceneModuleInterface const std::string turn_direction_; const bool has_traffic_light_; - bool is_go_out_ = false; - bool is_permanent_go_ = false; - DecisionResult prev_decision_result_; + bool is_go_out_{false}; + bool is_permanent_go_{false}; + DecisionResult prev_decision_result_{Indecisive{""}}; // Parameter PlannerParam planner_param_; - std::optional intersection_lanelets_; + std::optional intersection_lanelets_{std::nullopt}; // for occlusion detection const bool enable_occlusion_detection_; - std::optional> occlusion_attention_divisions_; - // OcclusionState prev_occlusion_state_ = OcclusionState::NONE; + std::optional> occlusion_attention_divisions_{std::nullopt}; StateMachine collision_state_machine_; //! for stable collision checking StateMachine before_creep_state_machine_; //! for two phase stop StateMachine occlusion_stop_state_machine_; StateMachine temporal_stop_before_attention_state_machine_; - // NOTE: uuid_ is base member + // for pseudo-collision detection when ego just entered intersection on green light and upcoming + // vehicles are very slow + std::optional initial_green_light_observed_time_{std::nullopt}; // for stuck vehicle detection const bool is_private_area_; // for RTC const UUID occlusion_uuid_; - bool occlusion_safety_ = true; - double occlusion_stop_distance_; - bool occlusion_activated_ = true; + bool occlusion_safety_{true}; + double occlusion_stop_distance_{0.0}; + bool occlusion_activated_{true}; // for first stop in two-phase stop - bool occlusion_first_stop_required_ = false; + bool occlusion_first_stop_required_{false}; void initializeRTCStatus(); void prepareRTCStatus( From 83821d4818cf83ea3bcb14511fc64482859ed7f1 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Sat, 7 Oct 2023 17:20:47 +0900 Subject: [PATCH 423/547] feat(rtc_auto_mode_manager): eliminate rtc auto mode manager (#5235) * change namespace of auto_mode Signed-off-by: kyoichi-sugahara * delete RTC auto mode manager package Signed-off-by: kyoichi-sugahara * delete rtc_replayer.param Signed-off-by: kyoichi-sugahara * style(pre-commit): autofix * fix typo Signed-off-by: kyoichi-sugahara * fix typo Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../launch/planning.launch.xml | 2 - .../scenario_planning/lane_driving.launch.xml | 5 - launch/tier4_planning_launch/package.xml | 1 - .../config/scene_module_manager.param.yaml | 20 ++-- .../config/blind_spot.param.yaml | 2 +- .../config/crosswalk.param.yaml | 2 +- .../config/detection_area.param.yaml | 2 +- .../config/intersection.param.yaml | 4 +- .../config/no_stopping_area.param.yaml | 2 +- .../config/traffic_light.param.yaml | 2 +- planning/rtc_auto_mode_manager/CMakeLists.txt | 29 ----- planning/rtc_auto_mode_manager/README.md | 47 -------- .../config/rtc_auto_mode_manager.param.yaml | 31 ----- .../include/rtc_auto_mode_manager/node.hpp | 44 ------- .../rtc_auto_mode_manager_interface.hpp | 53 --------- .../launch/rtc_auto_mode_manager.launch.xml | 7 -- planning/rtc_auto_mode_manager/package.xml | 28 ----- planning/rtc_auto_mode_manager/src/node.cpp | 51 -------- .../src/rtc_auto_mode_manager_interface.cpp | 109 ------------------ .../include/rtc_interface/rtc_interface.hpp | 2 +- .../launch/rtc_replayer.launch.xml | 6 +- 21 files changed, 19 insertions(+), 430 deletions(-) delete mode 100644 planning/rtc_auto_mode_manager/CMakeLists.txt delete mode 100644 planning/rtc_auto_mode_manager/README.md delete mode 100644 planning/rtc_auto_mode_manager/config/rtc_auto_mode_manager.param.yaml delete mode 100644 planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/node.hpp delete mode 100644 planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/rtc_auto_mode_manager_interface.hpp delete mode 100644 planning/rtc_auto_mode_manager/launch/rtc_auto_mode_manager.launch.xml delete mode 100644 planning/rtc_auto_mode_manager/package.xml delete mode 100644 planning/rtc_auto_mode_manager/src/node.cpp delete mode 100644 planning/rtc_auto_mode_manager/src/rtc_auto_mode_manager_interface.cpp diff --git a/launch/tier4_planning_launch/launch/planning.launch.xml b/launch/tier4_planning_launch/launch/planning.launch.xml index 7e78c8842f7df..6ace3f423d1bc 100644 --- a/launch/tier4_planning_launch/launch/planning.launch.xml +++ b/launch/tier4_planning_launch/launch/planning.launch.xml @@ -15,8 +15,6 @@ - - diff --git a/launch/tier4_planning_launch/launch/scenario_planning/lane_driving.launch.xml b/launch/tier4_planning_launch/launch/scenario_planning/lane_driving.launch.xml index 24a33b9d3f3df..5f17256df6b5c 100644 --- a/launch/tier4_planning_launch/launch/scenario_planning/lane_driving.launch.xml +++ b/launch/tier4_planning_launch/launch/scenario_planning/lane_driving.launch.xml @@ -14,11 +14,6 @@ - - - - - diff --git a/launch/tier4_planning_launch/package.xml b/launch/tier4_planning_launch/package.xml index 39b95286bb6cc..5988d34cded88 100644 --- a/launch/tier4_planning_launch/package.xml +++ b/launch/tier4_planning_launch/package.xml @@ -68,7 +68,6 @@ obstacle_stop_planner planning_evaluator planning_validator - rtc_auto_mode_manager scenario_selector surround_obstacle_checker diff --git a/planning/behavior_path_planner/config/scene_module_manager.param.yaml b/planning/behavior_path_planner/config/scene_module_manager.param.yaml index 9895d9b5e473f..5797704e8a0ca 100644 --- a/planning/behavior_path_planner/config/scene_module_manager.param.yaml +++ b/planning/behavior_path_planner/config/scene_module_manager.param.yaml @@ -5,7 +5,7 @@ ros__parameters: external_request_lane_change_left: enable_module: false - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: true keep_last: false @@ -14,7 +14,7 @@ external_request_lane_change_right: enable_module: false - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: true keep_last: false @@ -23,7 +23,7 @@ lane_change_left: enable_module: true - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: true keep_last: false @@ -32,7 +32,7 @@ lane_change_right: enable_module: true - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: true keep_last: false @@ -41,7 +41,7 @@ start_planner: enable_module: true - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false keep_last: false @@ -50,7 +50,7 @@ side_shift: enable_module: true - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false keep_last: false @@ -59,7 +59,7 @@ goal_planner: enable_module: true - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false keep_last: true @@ -68,7 +68,7 @@ avoidance: enable_module: true - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: false keep_last: false @@ -77,7 +77,7 @@ avoidance_by_lc: enable_module: false - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: false enable_simultaneous_execution_as_candidate_module: false keep_last: false @@ -86,7 +86,7 @@ dynamic_avoidance: enable_module: false - enable_rtc: true + enable_rtc: false enable_simultaneous_execution_as_approved_module: true enable_simultaneous_execution_as_candidate_module: true keep_last: false diff --git a/planning/behavior_velocity_blind_spot_module/config/blind_spot.param.yaml b/planning/behavior_velocity_blind_spot_module/config/blind_spot.param.yaml index 3aa8c82a5e556..f9ddd3ce61099 100644 --- a/planning/behavior_velocity_blind_spot_module/config/blind_spot.param.yaml +++ b/planning/behavior_velocity_blind_spot_module/config/blind_spot.param.yaml @@ -8,4 +8,4 @@ max_future_movement_time: 10.0 # [second] threshold_yaw_diff: 0.523 # [rad] adjacent_extend_width: 1.5 # [m] - enable_rtc: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval + enable_rtc: false # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml b/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml index e7accc14096e0..a4f9e9d7ce23d 100644 --- a/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml +++ b/planning/behavior_velocity_crosswalk_module/config/crosswalk.param.yaml @@ -5,7 +5,7 @@ show_processing_time: false # [-] whether to show processing time # param for input data traffic_light_state_timeout: 3.0 # [s] timeout threshold for traffic light signal - enable_rtc: true # if true, the scene modules should be approved by (request to cooperate)rtc function. if false, the module can be run without approval from rtc. + enable_rtc: false # if true, the scene modules should be approved by (request to cooperate)rtc function. if false, the module can be run without approval from rtc. # param for stop position stop_position: diff --git a/planning/behavior_velocity_detection_area_module/config/detection_area.param.yaml b/planning/behavior_velocity_detection_area_module/config/detection_area.param.yaml index b5d075a4d6493..62b5f2458336f 100644 --- a/planning/behavior_velocity_detection_area_module/config/detection_area.param.yaml +++ b/planning/behavior_velocity_detection_area_module/config/detection_area.param.yaml @@ -8,4 +8,4 @@ state_clear_time: 2.0 hold_stop_margin_distance: 0.0 distance_to_judge_over_stop_line: 0.5 - enable_rtc: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval + enable_rtc: false # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 972f572890d16..30fefcaeee035 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -66,8 +66,8 @@ attention_lane_curvature_calculation_ds: 0.5 enable_rtc: - intersection: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval - intersection_to_occlusion: true + intersection: false # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval + intersection_to_occlusion: false merge_from_private: stop_line_margin: 3.0 diff --git a/planning/behavior_velocity_no_stopping_area_module/config/no_stopping_area.param.yaml b/planning/behavior_velocity_no_stopping_area_module/config/no_stopping_area.param.yaml index f550188d4f2c1..c84848f8cc31d 100644 --- a/planning/behavior_velocity_no_stopping_area_module/config/no_stopping_area.param.yaml +++ b/planning/behavior_velocity_no_stopping_area_module/config/no_stopping_area.param.yaml @@ -8,4 +8,4 @@ stop_line_margin: 1.0 # [m] margin to auto-gen stop line at no stopping area detection_area_length: 200.0 # [m] used to create detection area polygon stuck_vehicle_front_margin: 6.0 # [m] obstacle stop max distance(5.0m) - enable_rtc: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval + enable_rtc: false # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/behavior_velocity_traffic_light_module/config/traffic_light.param.yaml b/planning/behavior_velocity_traffic_light_module/config/traffic_light.param.yaml index e8e0357daa4a1..a837ae1b46b9b 100644 --- a/planning/behavior_velocity_traffic_light_module/config/traffic_light.param.yaml +++ b/planning/behavior_velocity_traffic_light_module/config/traffic_light.param.yaml @@ -5,4 +5,4 @@ tl_state_timeout: 1.0 yellow_lamp_period: 2.75 enable_pass_judge: true - enable_rtc: true # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval + enable_rtc: false # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/rtc_auto_mode_manager/CMakeLists.txt b/planning/rtc_auto_mode_manager/CMakeLists.txt deleted file mode 100644 index 52c0c68384a57..0000000000000 --- a/planning/rtc_auto_mode_manager/CMakeLists.txt +++ /dev/null @@ -1,29 +0,0 @@ -cmake_minimum_required(VERSION 3.5) -project(rtc_auto_mode_manager) - -### Compile options -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) -endif() -if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wall -Wextra -Wpedantic -Werror) -endif() - -find_package(autoware_cmake REQUIRED) -autoware_package() - -ament_auto_add_library(${PROJECT_NAME} SHARED - src/rtc_auto_mode_manager_interface.cpp - src/node.cpp -) - -rclcpp_components_register_node(${PROJECT_NAME} - PLUGIN "rtc_auto_mode_manager::RTCAutoModeManagerNode" - EXECUTABLE ${PROJECT_NAME}_node -) - -ament_auto_package( - INSTALL_TO_SHARE - config - launch -) diff --git a/planning/rtc_auto_mode_manager/README.md b/planning/rtc_auto_mode_manager/README.md deleted file mode 100644 index 0e2614baaf1c8..0000000000000 --- a/planning/rtc_auto_mode_manager/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# RTC Auto Mode Manager - -## Purpose - -RTC Auto Mode Manager is a node to approve request to cooperate from behavior planning modules automatically. - -## Inputs / Outputs - -### Input - -| Name | Type | Description | -| ------------------------------- | --------------------------- | ------------------------------------------ | -| `/planning/enable_auto_mode/**` | tier4_rtc_msgs/srv/AutoMode | Service to enable auto mode for the module | - -### Output - -| Name | Type | Description | -| ---------------------------------------- | --------------------------- | ------------------------------------------ | -| `/planning/enable_auto_mode/internal/**` | tier4_rtc_msgs/srv/AutoMode | Service to enable auto mode for the module | - -## Parameters - -| Name | Type | Description | -| :-------------------- | :--------------- | :----------------------------------------------- | -| `module_list` | List of `string` | Module names managing in `rtc_auto_mode_manager` | -| `default_enable_list` | List of `string` | Module names enabled auto mode at initialization | - -## Inner-workings / Algorithms - -```plantuml - -start -:Read parameters; -:Send enable auto mode service to the module listed in `default_enable_list`; -repeat - if (Enable auto mode command received?) then (yes) - :Send enable auto mode command to rtc_interface; - else (no) - endif -repeat while (Is node running?) is (yes) not (no) -end - -``` - -## Assumptions / Known limits - -## Future extensions / Unimplemented parts diff --git a/planning/rtc_auto_mode_manager/config/rtc_auto_mode_manager.param.yaml b/planning/rtc_auto_mode_manager/config/rtc_auto_mode_manager.param.yaml deleted file mode 100644 index 0aa3cbd49e8e9..0000000000000 --- a/planning/rtc_auto_mode_manager/config/rtc_auto_mode_manager.param.yaml +++ /dev/null @@ -1,31 +0,0 @@ -/**: - ros__parameters: - module_list: - - "blind_spot" - - "crosswalk" - - "detection_area" - - "intersection" - - "no_stopping_area" - - "traffic_light" - - "lane_change_left" - - "lane_change_right" - - "avoidance_left" - - "avoidance_right" - - "goal_planner" - - "start_planner" - - "intersection_occlusion" - - default_enable_list: - - "blind_spot" - - "crosswalk" - - "detection_area" - - "intersection" - - "no_stopping_area" - - "traffic_light" - - "lane_change_left" - - "lane_change_right" - - "avoidance_left" - - "avoidance_right" - - "goal_planner" - - "start_planner" - - "intersection_occlusion" diff --git a/planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/node.hpp b/planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/node.hpp deleted file mode 100644 index d36974508d2df..0000000000000 --- a/planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/node.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2022 TIER IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef RTC_AUTO_MODE_MANAGER__NODE_HPP_ -#define RTC_AUTO_MODE_MANAGER__NODE_HPP_ - -#include "rclcpp/rclcpp.hpp" -#include "rtc_auto_mode_manager/rtc_auto_mode_manager_interface.hpp" - -#include "tier4_rtc_msgs/msg/auto_mode_status_array.hpp" - -#include -#include -#include - -namespace rtc_auto_mode_manager -{ -using tier4_rtc_msgs::msg::AutoModeStatusArray; -class RTCAutoModeManagerNode : public rclcpp::Node -{ -public: - explicit RTCAutoModeManagerNode(const rclcpp::NodeOptions & node_options); - -private: - rclcpp::TimerBase::SharedPtr timer_; - AutoModeStatusArray auto_mode_statuses_; - rclcpp::Publisher::SharedPtr statuses_pub_; - std::vector> managers_; -}; - -} // namespace rtc_auto_mode_manager - -#endif // RTC_AUTO_MODE_MANAGER__NODE_HPP_ diff --git a/planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/rtc_auto_mode_manager_interface.hpp b/planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/rtc_auto_mode_manager_interface.hpp deleted file mode 100644 index 098852c397161..0000000000000 --- a/planning/rtc_auto_mode_manager/include/rtc_auto_mode_manager/rtc_auto_mode_manager_interface.hpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2022 TIER IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef RTC_AUTO_MODE_MANAGER__RTC_AUTO_MODE_MANAGER_INTERFACE_HPP_ -#define RTC_AUTO_MODE_MANAGER__RTC_AUTO_MODE_MANAGER_INTERFACE_HPP_ - -#include "rclcpp/rclcpp.hpp" - -#include "tier4_rtc_msgs/msg/auto_mode_status.hpp" -#include "tier4_rtc_msgs/msg/module.hpp" -#include "tier4_rtc_msgs/srv/auto_mode.hpp" - -#include -#include -#include - -namespace rtc_auto_mode_manager -{ -using tier4_rtc_msgs::msg::AutoModeStatus; -using tier4_rtc_msgs::msg::Module; -using tier4_rtc_msgs::srv::AutoMode; - -class RTCAutoModeManagerInterface -{ -public: - RTCAutoModeManagerInterface( - rclcpp::Node * node, const std::string & module_name, const bool default_enable); - AutoModeStatus getAutoModeStatus() const { return auto_mode_status_; } - -private: - void onEnableService( - const AutoMode::Request::SharedPtr request, const AutoMode::Response::SharedPtr response); - AutoMode::Request createRequest(const AutoMode::Request::SharedPtr request) const; - AutoModeStatus auto_mode_status_; - rclcpp::Client::SharedPtr enable_cli_; - rclcpp::Service::SharedPtr enable_srv_; - - std::string enable_auto_mode_namespace_ = "/planning/enable_auto_mode"; -}; -} // namespace rtc_auto_mode_manager - -#endif // RTC_AUTO_MODE_MANAGER__RTC_AUTO_MODE_MANAGER_INTERFACE_HPP_ diff --git a/planning/rtc_auto_mode_manager/launch/rtc_auto_mode_manager.launch.xml b/planning/rtc_auto_mode_manager/launch/rtc_auto_mode_manager.launch.xml deleted file mode 100644 index 85d4b3446fb89..0000000000000 --- a/planning/rtc_auto_mode_manager/launch/rtc_auto_mode_manager.launch.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/planning/rtc_auto_mode_manager/package.xml b/planning/rtc_auto_mode_manager/package.xml deleted file mode 100644 index e9c16aac1bb91..0000000000000 --- a/planning/rtc_auto_mode_manager/package.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - rtc_auto_mode_manager - 0.1.0 - The rtc_auto_mode_manager package - - Fumiya Watanabe - Taiki Tanaka - - Apache License 2.0 - - Fumiya Watanabe - - ament_cmake_auto - autoware_cmake - - rclcpp - rclcpp_components - tier4_rtc_msgs - - ament_lint_auto - autoware_lint_common - - - ament_cmake - - diff --git a/planning/rtc_auto_mode_manager/src/node.cpp b/planning/rtc_auto_mode_manager/src/node.cpp deleted file mode 100644 index 6092d2741d909..0000000000000 --- a/planning/rtc_auto_mode_manager/src/node.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2022 TIER IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "rtc_auto_mode_manager/node.hpp" - -#include - -namespace rtc_auto_mode_manager -{ - -RTCAutoModeManagerNode::RTCAutoModeManagerNode(const rclcpp::NodeOptions & node_options) -: Node("rtc_auto_mode_manager_node", node_options) -{ - const std::vector module_list = - declare_parameter>("module_list"); - const std::vector default_enable_list = - declare_parameter>("default_enable_list"); - - for (const auto & module_name : module_list) { - const bool enabled = - std::count(default_enable_list.begin(), default_enable_list.end(), module_name) != 0; - managers_.push_back(std::make_shared(this, module_name, enabled)); - } - statuses_pub_ = create_publisher("output/auto_mode_statuses", 1); - using std::chrono_literals::operator""ms; - timer_ = rclcpp::create_timer(this, get_clock(), 100ms, [this] { - AutoModeStatusArray auto_mode_statuses; - for (const auto & m : managers_) { - auto_mode_statuses.stamp = get_clock()->now(); - auto_mode_statuses.statuses.emplace_back(m->getAutoModeStatus()); - } - statuses_pub_->publish(auto_mode_statuses); - auto_mode_statuses.statuses.clear(); - }); -} - -} // namespace rtc_auto_mode_manager - -#include -RCLCPP_COMPONENTS_REGISTER_NODE(rtc_auto_mode_manager::RTCAutoModeManagerNode) diff --git a/planning/rtc_auto_mode_manager/src/rtc_auto_mode_manager_interface.cpp b/planning/rtc_auto_mode_manager/src/rtc_auto_mode_manager_interface.cpp deleted file mode 100644 index e28957cee161b..0000000000000 --- a/planning/rtc_auto_mode_manager/src/rtc_auto_mode_manager_interface.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2022 TIER IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "rtc_auto_mode_manager/rtc_auto_mode_manager_interface.hpp" - -namespace rtc_auto_mode_manager -{ -Module getModuleType(const std::string & module_name) -{ - Module module; - if (module_name == "blind_spot") { - module.type = Module::BLIND_SPOT; - } else if (module_name == "crosswalk") { - module.type = Module::CROSSWALK; - } else if (module_name == "detection_area") { - module.type = Module::DETECTION_AREA; - } else if (module_name == "intersection") { - module.type = Module::INTERSECTION; - } else if (module_name == "no_stopping_area") { - module.type = Module::NO_STOPPING_AREA; - } else if (module_name == "occlusion_spot") { - module.type = Module::OCCLUSION_SPOT; - } else if (module_name == "traffic_light") { - module.type = Module::TRAFFIC_LIGHT; - } else if (module_name == "virtual_traffic_light") { - module.type = Module::TRAFFIC_LIGHT; - } else if (module_name == "external_request_lane_change_left") { - module.type = Module::EXT_REQUEST_LANE_CHANGE_LEFT; - } else if (module_name == "external_request_lane_change_right") { - module.type = Module::EXT_REQUEST_LANE_CHANGE_RIGHT; - } else if (module_name == "lane_change_left") { - module.type = Module::LANE_CHANGE_LEFT; - } else if (module_name == "lane_change_right") { - module.type = Module::LANE_CHANGE_RIGHT; - } else if (module_name == "avoidance_by_lane_change_left") { - module.type = Module::AVOIDANCE_BY_LC_LEFT; - } else if (module_name == "avoidance_by_lane_change_right") { - module.type = Module::AVOIDANCE_BY_LC_RIGHT; - } else if (module_name == "avoidance_left") { - module.type = Module::AVOIDANCE_LEFT; - } else if (module_name == "avoidance_right") { - module.type = Module::AVOIDANCE_RIGHT; - } else if (module_name == "goal_planner") { - module.type = Module::GOAL_PLANNER; - } else if (module_name == "start_planner") { - module.type = Module::START_PLANNER; - } else if (module_name == "intersection_occlusion") { - module.type = Module::INTERSECTION_OCCLUSION; - } else { - module.type = Module::NONE; - } - return module; -} - -RTCAutoModeManagerInterface::RTCAutoModeManagerInterface( - rclcpp::Node * node, const std::string & module_name, const bool default_enable) -{ - using std::chrono_literals::operator""s; - using std::placeholders::_1; - using std::placeholders::_2; - - // Service client - enable_cli_ = - node->create_client(enable_auto_mode_namespace_ + "/internal/" + module_name); - - while (!enable_cli_->wait_for_service(1s)) { - if (!rclcpp::ok()) { - RCLCPP_ERROR(node->get_logger(), "Interrupted while waiting for service."); - rclcpp::shutdown(); - return; - } - RCLCPP_INFO_STREAM(node->get_logger(), "Waiting for service... [" << module_name << "]"); - } - - // Service - enable_srv_ = node->create_service( - enable_auto_mode_namespace_ + "/" + module_name, - std::bind(&RTCAutoModeManagerInterface::onEnableService, this, _1, _2)); - - auto_mode_status_.module = getModuleType(module_name); - // Send enable auto mode request - if (default_enable) { - auto_mode_status_.is_auto_mode = true; - AutoMode::Request::SharedPtr request = std::make_shared(); - request->enable = true; - enable_cli_->async_send_request(request); - } -} - -void RTCAutoModeManagerInterface::onEnableService( - const AutoMode::Request::SharedPtr request, const AutoMode::Response::SharedPtr response) -{ - auto_mode_status_.is_auto_mode = request->enable; - enable_cli_->async_send_request(request); - response->success = true; -} - -} // namespace rtc_auto_mode_manager diff --git a/planning/rtc_interface/include/rtc_interface/rtc_interface.hpp b/planning/rtc_interface/include/rtc_interface/rtc_interface.hpp index 4929d0f8e27f3..d6e083421c0b7 100644 --- a/planning/rtc_interface/include/rtc_interface/rtc_interface.hpp +++ b/planning/rtc_interface/include/rtc_interface/rtc_interface.hpp @@ -85,7 +85,7 @@ class RTCInterface std::string cooperate_status_namespace_ = "/planning/cooperate_status"; std::string cooperate_commands_namespace_ = "/planning/cooperate_commands"; - std::string enable_auto_mode_namespace_ = "/planning/enable_auto_mode/internal"; + std::string enable_auto_mode_namespace_ = "/planning/enable_auto_mode"; mutable std::mutex mutex_; }; diff --git a/planning/rtc_replayer/launch/rtc_replayer.launch.xml b/planning/rtc_replayer/launch/rtc_replayer.launch.xml index 9b832b369c77c..70d7baf2dce4a 100644 --- a/planning/rtc_replayer/launch/rtc_replayer.launch.xml +++ b/planning/rtc_replayer/launch/rtc_replayer.launch.xml @@ -1,7 +1,3 @@ - - - - - + From c508aa71c19d09f4198e24cd93c39191c9dacca6 Mon Sep 17 00:00:00 2001 From: badai nguyen <94814556+badai-nguyen@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:29:00 +0900 Subject: [PATCH 424/547] fix(image_projection_based_fusion): add iou_x use in long range for roi_cluster_fusion (#5148) * fix: add iou_x for long range obj Signed-off-by: badai-nguyen * fix: add launch file param Signed-off-by: badai-nguyen * chore: fix unexpect calc iou in long range Signed-off-by: badai-nguyen * fix: multi iou usable Signed-off-by: badai-nguyen * chore: typo Signed-off-by: badai-nguyen * docs: update readme Signed-off-by: badai-nguyen * chore: refactor Signed-off-by: badai-nguyen --------- Signed-off-by: badai-nguyen --- ...ra_lidar_fusion_based_detection.launch.xml | 6 +- ...ar_radar_fusion_based_detection.launch.xml | 6 +- .../detection/detection.launch.xml | 6 +- .../launch/perception.launch.xml | 6 +- .../docs/roi-cluster-fusion.md | 26 ++++---- .../roi_cluster_fusion/node.hpp | 18 +++--- .../launch/roi_cluster_fusion.launch.xml | 13 ++-- .../src/roi_cluster_fusion/node.cpp | 59 ++++++++++++------- 8 files changed, 86 insertions(+), 54 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index 51359d8d8f0e2..67e123ea5b018 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -15,7 +15,8 @@ - + + @@ -169,7 +170,8 @@ - + + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index 45cfb91bf25c1..fbe5f21c041b6 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -15,7 +15,8 @@ - + + @@ -190,7 +191,8 @@ - + + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml index 77a3e345ec5e8..1fcf29606891b 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/detection.launch.xml @@ -35,7 +35,8 @@ - + + @@ -64,7 +65,8 @@ - + + diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index 3d4ce1035e72c..bbb7ebb2d8d25 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -93,7 +93,8 @@ - + + @@ -169,7 +170,8 @@ - + + diff --git a/perception/image_projection_based_fusion/docs/roi-cluster-fusion.md b/perception/image_projection_based_fusion/docs/roi-cluster-fusion.md index 01cdc3756866b..03eaab2a3c6ca 100644 --- a/perception/image_projection_based_fusion/docs/roi-cluster-fusion.md +++ b/perception/image_projection_based_fusion/docs/roi-cluster-fusion.md @@ -32,18 +32,20 @@ The clusters are projected onto image planes, and then if the ROIs of clusters a ### Core Parameters -| Name | Type | Description | -| --------------------------- | ----- | -------------------------------------------------------------------------------- | -| `use_iou_x` | bool | calculate IoU only along x-axis | -| `use_iou_y` | bool | calculate IoU only along y-axis | -| `use_iou` | bool | calculate IoU both along x-axis and y-axis | -| `use_cluster_semantic_type` | bool | if `false`, the labels of clusters are overwritten by `UNKNOWN` before fusion | -| `only_allow_inside_cluster` | bool | if `true`, the only clusters contained inside RoIs by a detector | -| `roi_scale_factor` | float | the scale factor for offset of detector RoIs if `only_allow_inside_cluster=true` | -| `iou_threshold` | float | the IoU threshold to overwrite a label of clusters with a label of roi | -| `unknown_iou_threshold` | float | the IoU threshold to fuse cluster with unknown label of roi | -| `rois_number` | int | the number of input rois | -| `debug_mode` | bool | If `true`, subscribe and publish images for visualization. | +| Name | Type | Description | +| --------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fusion_distance` | double | If the detected object's distance to frame_id is less than the threshold, the fusion will be processed | +| `trust_object_distance` | double | if the detected object's distance is less than the `trust_object_distance`, `trust_object_iou_mode` will be used, otherwise `non_trust_object_iou_mode` will be used | +| `trust_object_iou_mode` | string | select mode from 3 options {`iou`, `iou_x`, `iou_y`} to calculate IoU in range of [`0`, `trust_distance`].
 `iou`: IoU along x-axis and y-axis
 `iou_x`: IoU along x-axis
 `iou_y`: IoU along y-axis | +| `non_trust_object_iou_mode` | string | the IOU mode using in range of [`trust_distance`, `fusion_distance`] if `trust_distance` < `fusion_distance` | +| `use_cluster_semantic_type` | bool | if `false`, the labels of clusters are overwritten by `UNKNOWN` before fusion | +| `only_allow_inside_cluster` | bool | if `true`, the only clusters contained inside RoIs by a detector | +| `roi_scale_factor` | double | the scale factor for offset of detector RoIs if `only_allow_inside_cluster=true` | +| `iou_threshold` | double | the IoU threshold to overwrite a label of clusters with a label of roi | +| `unknown_iou_threshold` | double | the IoU threshold to fuse cluster with unknown label of roi | +| `remove_unknown` | bool | if `true`, remove all `UNKNOWN` labeled objects from output | +| `rois_number` | int | the number of input rois | +| `debug_mode` | bool | If `true`, subscribe and publish images for visualization. | ## Assumptions / Known limits diff --git a/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp b/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp index 70a7866e79b87..e54710ad477da 100644 --- a/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp +++ b/perception/image_projection_based_fusion/include/image_projection_based_fusion/roi_cluster_fusion/node.hpp @@ -17,10 +17,12 @@ #include "image_projection_based_fusion/fusion_node.hpp" +#include #include - +#include namespace image_projection_based_fusion { +const std::map IOU_MODE_MAP{{"iou", 0}, {"iou_x", 1}, {"iou_y", 2}}; class RoiClusterFusionNode : public FusionNode @@ -38,9 +40,7 @@ class RoiClusterFusionNode const sensor_msgs::msg::CameraInfo & camera_info, DetectedObjectsWithFeature & output_cluster_msg) override; - bool use_iou_x_{false}; - bool use_iou_y_{false}; - bool use_iou_{false}; + std::string trust_object_iou_mode_{"iou"}; bool use_cluster_semantic_type_{false}; bool only_allow_inside_cluster_{false}; double roi_scale_factor_{1.1}; @@ -49,10 +49,14 @@ class RoiClusterFusionNode const float min_roi_existence_prob_ = 0.1; // keep small value to lessen affect on merger object stage bool remove_unknown_; - double trust_distance_; - - bool filter_by_distance(const DetectedObjectWithFeature & obj); + double fusion_distance_; + double trust_object_distance_; + std::string non_trust_object_iou_mode_{"iou_x"}; + bool is_far_enough(const DetectedObjectWithFeature & obj, const double distance_threshold); bool out_of_scope(const DetectedObjectWithFeature & obj); + double cal_iou_by_mode( + const sensor_msgs::msg::RegionOfInterest & roi_1, + const sensor_msgs::msg::RegionOfInterest & roi_2, const std::string iou_mode); // bool CheckUnknown(const DetectedObjectsWithFeature & obj); }; diff --git a/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml b/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml index 2d99c25bb68f7..60f6f943b8cda 100644 --- a/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml +++ b/perception/image_projection_based_fusion/launch/roi_cluster_fusion.launch.xml @@ -1,5 +1,5 @@ - + @@ -20,7 +20,8 @@ - + + @@ -45,9 +46,8 @@ - - - + + @@ -55,7 +55,8 @@ - + + diff --git a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp index 316d29dae1301..4a46c8aace696 100644 --- a/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp +++ b/perception/image_projection_based_fusion/src/roi_cluster_fusion/node.cpp @@ -36,16 +36,16 @@ namespace image_projection_based_fusion RoiClusterFusionNode::RoiClusterFusionNode(const rclcpp::NodeOptions & options) : FusionNode("roi_cluster_fusion", options) { - use_iou_x_ = declare_parameter("use_iou_x"); - use_iou_y_ = declare_parameter("use_iou_y"); - use_iou_ = declare_parameter("use_iou"); + trust_object_iou_mode_ = declare_parameter("trust_object_iou_mode"); + non_trust_object_iou_mode_ = declare_parameter("non_trust_object_iou_mode"); use_cluster_semantic_type_ = declare_parameter("use_cluster_semantic_type"); only_allow_inside_cluster_ = declare_parameter("only_allow_inside_cluster"); roi_scale_factor_ = declare_parameter("roi_scale_factor"); iou_threshold_ = declare_parameter("iou_threshold"); unknown_iou_threshold_ = declare_parameter("unknown_iou_threshold"); remove_unknown_ = declare_parameter("remove_unknown"); - trust_distance_ = declare_parameter("trust_distance"); + fusion_distance_ = declare_parameter("fusion_distance"); + trust_object_distance_ = declare_parameter("trust_object_distance"); } void RoiClusterFusionNode::preprocess(DetectedObjectsWithFeature & output_cluster_msg) @@ -111,7 +111,7 @@ void RoiClusterFusionNode::fuseOnSingleImage( continue; } - if (filter_by_distance(input_cluster_msg.feature_objects.at(i))) { + if (is_far_enough(input_cluster_msg.feature_objects.at(i), fusion_distance_)) { continue; } @@ -175,25 +175,23 @@ void RoiClusterFusionNode::fuseOnSingleImage( bool is_roi_label_known = feature_obj.object.classification.front().label != ObjectClassification::UNKNOWN; for (const auto & cluster_map : m_cluster_roi) { - double iou(0.0), iou_x(0.0), iou_y(0.0); - if (use_iou_) { - iou = calcIoU(cluster_map.second, feature_obj.feature.roi); - } - // use for unknown roi to improve small objects like traffic cone detect - // TODO(badai-nguyen): add option to disable roi_cluster mode - if (use_iou_x_ || !is_roi_label_known) { - iou_x = calcIoUX(cluster_map.second, feature_obj.feature.roi); - } - if (use_iou_y_) { - iou_y = calcIoUY(cluster_map.second, feature_obj.feature.roi); + double iou(0.0); + bool is_use_non_trust_object_iou_mode = is_far_enough( + input_cluster_msg.feature_objects.at(cluster_map.first), trust_object_distance_); + if (is_use_non_trust_object_iou_mode || is_roi_label_known) { + iou = + cal_iou_by_mode(cluster_map.second, feature_obj.feature.roi, non_trust_object_iou_mode_); + } else { + iou = cal_iou_by_mode(cluster_map.second, feature_obj.feature.roi, trust_object_iou_mode_); } + const bool passed_inside_cluster_gate = only_allow_inside_cluster_ ? is_inside(feature_obj.feature.roi, cluster_map.second, roi_scale_factor_) : true; - if (max_iou < iou + iou_x + iou_y && passed_inside_cluster_gate) { + if (max_iou < iou && passed_inside_cluster_gate) { index = cluster_map.first; - max_iou = iou + iou_x + iou_y; + max_iou = iou; associated = true; } } @@ -274,11 +272,30 @@ bool RoiClusterFusionNode::out_of_scope(const DetectedObjectWithFeature & obj) return is_out; } -bool RoiClusterFusionNode::filter_by_distance(const DetectedObjectWithFeature & obj) +bool RoiClusterFusionNode::is_far_enough( + const DetectedObjectWithFeature & obj, const double distance_threshold) { const auto & position = obj.object.kinematics.pose_with_covariance.pose.position; - const auto square_distance = position.x * position.x + position.y + position.y; - return square_distance > trust_distance_ * trust_distance_; + return position.x * position.x + position.y * position.y > + distance_threshold * distance_threshold; +} + +double RoiClusterFusionNode::cal_iou_by_mode( + const sensor_msgs::msg::RegionOfInterest & roi_1, + const sensor_msgs::msg::RegionOfInterest & roi_2, const std::string iou_mode) +{ + switch (IOU_MODE_MAP.at(iou_mode)) { + case 0 /* use iou mode */: + return calcIoU(roi_1, roi_2); + + case 1 /* use iou_x mode */: + return calcIoUX(roi_1, roi_2); + + case 2 /* use iou_y mode */: + return calcIoUY(roi_1, roi_2); + default: + return 0.0; + } } } // namespace image_projection_based_fusion From 9ddb07ae06c269d8cf007bab8fe402c24de54223 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:06:17 +0900 Subject: [PATCH 425/547] fix(vehicle_cmd_gate): fix filtering condition (#5250) Signed-off-by: kminoda --- control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp index e2ee0ec65eec0..e681bc22cd24b 100644 --- a/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp +++ b/control/vehicle_cmd_gate/src/vehicle_cmd_gate.cpp @@ -555,9 +555,7 @@ AckermannControlCommand VehicleCmdGate::filterControlCommand(const AckermannCont // set prev value for both to keep consistency over switching: // Actual steer, vel, acc should be considered in manual mode to prevent sudden motion when // switching from manual to autonomous - const auto in_autonomous = - (mode.mode == OperationModeState::AUTONOMOUS && mode.is_autoware_control_enabled); - auto prev_values = in_autonomous ? out : current_status_cmd; + auto prev_values = mode.is_autoware_control_enabled ? out : current_status_cmd; if (ego_is_stopped) { prev_values.longitudinal = out.longitudinal; From adb4872de9bedac1ff7440b135e741d5da2336ae Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 10 Oct 2023 18:01:13 +0900 Subject: [PATCH 426/547] fix(lane_change): add guard to neigibor lanelets (#5245) Signed-off-by: kosuke55 --- .../src/scene_module/lane_change/normal.cpp | 3 +++ planning/behavior_path_planner/src/utils/lane_change/utils.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index c75970e6fcc2d..71ba8c51ec6ad 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -946,6 +946,9 @@ bool NormalLaneChange::getLaneChangePaths( const auto target_neighbor_preferred_lane_poly_2d = utils::lane_change::getTargetNeighborLanesPolygon(route_handler, current_lanes, type_); + if (target_neighbor_preferred_lane_poly_2d.empty()) { + return false; + } const auto target_objects = getTargetObjects(current_lanes, target_lanes); debug_filtered_objects_ = target_objects; diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index df609dce48512..038cf9488345f 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -236,6 +236,9 @@ lanelet::BasicPolygon2d getTargetNeighborLanesPolygon( { const auto target_neighbor_lanelets = utils::lane_change::getTargetNeighborLanes(route_handler, current_lanes, type); + if (target_neighbor_lanelets.empty()) { + return {}; + } const auto target_neighbor_preferred_lane_poly = lanelet::utils::getPolygonFromArcLength( target_neighbor_lanelets, 0, std::numeric_limits::max()); From b36bc09b982f3f7b95c7c5164fa436b5b313ca00 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:31:52 +0900 Subject: [PATCH 427/547] chore(default_ad_api_helpers): update readme topic (#5258) * chore(default_ad_api_helpers): update readme topic Signed-off-by: kminoda * style(pre-commit): autofix * update readme Signed-off-by: kminoda --------- Signed-off-by: kminoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- system/default_ad_api_helpers/ad_api_adaptors/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/default_ad_api_helpers/ad_api_adaptors/README.md b/system/default_ad_api_helpers/ad_api_adaptors/README.md index 121befb68c88a..2e299fd7a2e51 100644 --- a/system/default_ad_api_helpers/ad_api_adaptors/README.md +++ b/system/default_ad_api_helpers/ad_api_adaptors/README.md @@ -21,9 +21,11 @@ The clear API is called automatically before setting the route. | Interface | Local Name | Global Name | Description | | ------------ | ------------------ | ------------------------------------- | -------------------------------------------------- | +| Subscription | - | /api/routing/state | The state of the routing API. | | Subscription | ~/input/fixed_goal | /planning/mission_planning/goal | The goal pose of route. Disable goal modification. | | Subscription | ~/input/rough_goal | /rviz/routing/rough_goal | The goal pose of route. Enable goal modification. | | Subscription | ~/input/reroute | /rviz/routing/reroute | The goal pose of reroute. | | Subscription | ~/input/waypoint | /planning/mission_planning/checkpoint | The waypoint pose of route. | | Client | - | /api/routing/clear_route | The route clear API. | | Client | - | /api/routing/set_route_points | The route points set API. | +| Client | - | /api/routing/change_route_points | The route points change API. | From 45339f63a05434cceaabbb2fa2d162b96e11d275 Mon Sep 17 00:00:00 2001 From: cyn-liu <104069308+cyn-liu@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:16:58 +0800 Subject: [PATCH 428/547] feat(autoware_auto_msgs_adapter): add map adapter (#4700) * feat(autoware_auto_msgs_adapter): add map adapter Signed-off-by: Cynthia Liu * feat(autoware_auto_msgs_adapter): add map adapter Signed-off-by: Cynthia Liu * feat(autoware_auto_msgs_adapter): add map adapter Signed-off-by: Cynthia Liu * feat(autoware_auto_msgs_adapter): add map adapter Signed-off-by: Cynthia Liu * feat(autoware_auto_msgs_adapter): add map adapter Signed-off-by: Cynthia Liu * style(pre-commit): autofix --------- Signed-off-by: Cynthia Liu Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../autoware_auto_msgs_adapter/CMakeLists.txt | 1 + .../config/adapter_map.param.yaml | 5 + .../autoware_auto_msgs_adapter.launch.xml | 4 + system/autoware_auto_msgs_adapter/package.xml | 4 +- .../autoware_auto_msgs_adapter.schema.json | 1 + .../src/autoware_auto_msgs_adapter_core.cpp | 5 + .../src/include/adapter_map.hpp | 59 ++++++++++ .../autoware_auto_msgs_adapter_core.hpp | 1 + .../test/test_msg_had_map_bin.cpp | 104 ++++++++++++++++++ 9 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 system/autoware_auto_msgs_adapter/config/adapter_map.param.yaml create mode 100644 system/autoware_auto_msgs_adapter/src/include/adapter_map.hpp create mode 100644 system/autoware_auto_msgs_adapter/test/test_msg_had_map_bin.cpp diff --git a/system/autoware_auto_msgs_adapter/CMakeLists.txt b/system/autoware_auto_msgs_adapter/CMakeLists.txt index ff8dcf6cedbb3..8b1d31ff2d01b 100644 --- a/system/autoware_auto_msgs_adapter/CMakeLists.txt +++ b/system/autoware_auto_msgs_adapter/CMakeLists.txt @@ -12,6 +12,7 @@ ament_auto_add_library(${NODE_NAME} src/include/adapter_base.hpp src/include/adapter_base_interface.hpp src/include/adapter_control.hpp + src/include/adapter_map.hpp src/include/adapter_perception.hpp src/include/autoware_auto_msgs_adapter_core.hpp src/autoware_auto_msgs_adapter_core.cpp) diff --git a/system/autoware_auto_msgs_adapter/config/adapter_map.param.yaml b/system/autoware_auto_msgs_adapter/config/adapter_map.param.yaml new file mode 100644 index 0000000000000..dcaa49e089b44 --- /dev/null +++ b/system/autoware_auto_msgs_adapter/config/adapter_map.param.yaml @@ -0,0 +1,5 @@ +/**: + ros__parameters: + msg_type_target: "autoware_auto_mapping_msgs/msg/HADMapBin" + topic_name_source: "/map/vector_map" + topic_name_target: "/map/vector_map_auto" diff --git a/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml b/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml index f914fe6e824c4..d80c1bd8cbedf 100755 --- a/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml +++ b/system/autoware_auto_msgs_adapter/launch/autoware_auto_msgs_adapter.launch.xml @@ -1,11 +1,15 @@ + + + + diff --git a/system/autoware_auto_msgs_adapter/package.xml b/system/autoware_auto_msgs_adapter/package.xml index de19032e1aee9..99a94fa043565 100644 --- a/system/autoware_auto_msgs_adapter/package.xml +++ b/system/autoware_auto_msgs_adapter/package.xml @@ -10,8 +10,6 @@ Apache License 2.0 - M. Fatih Cırıt - ament_cmake_auto autoware_cmake @@ -22,8 +20,10 @@ autoware_lint_common autoware_auto_control_msgs + autoware_auto_mapping_msgs autoware_auto_perception_msgs autoware_control_msgs + autoware_map_msgs autoware_perception_msgs rclcpp rclcpp_components diff --git a/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json b/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json index 35e3aef1511cc..f6aa87f774528 100644 --- a/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json +++ b/system/autoware_auto_msgs_adapter/schema/autoware_auto_msgs_adapter.schema.json @@ -11,6 +11,7 @@ "description": "Target message type", "enum": [ "autoware_auto_control_msgs/msg/AckermannControlCommand", + "autoware_auto_mapping_msgs/msg/HADMapBin", "autoware_auto_perception_msgs/msg/PredictedObjects" ], "default": "autoware_auto_control_msgs/msg/AckermannControlCommand" diff --git a/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp b/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp index 0f65571aba07b..15e3c90d227dd 100644 --- a/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp +++ b/system/autoware_auto_msgs_adapter/src/autoware_auto_msgs_adapter_core.cpp @@ -57,6 +57,11 @@ MapStringAdapter AutowareAutoMsgsAdapterNode::create_adapter_map( return std::static_pointer_cast( std::make_shared(*this, topic_name_source, topic_name_target)); }}, + {"autoware_auto_mapping_msgs/msg/HADMapBin", + [&] { + return std::static_pointer_cast( + std::make_shared(*this, topic_name_source, topic_name_target)); + }}, {"autoware_auto_perception_msgs/msg/PredictedObjects", [&] { return std::static_pointer_cast( diff --git a/system/autoware_auto_msgs_adapter/src/include/adapter_map.hpp b/system/autoware_auto_msgs_adapter/src/include/adapter_map.hpp new file mode 100644 index 0000000000000..8150b7d7dba08 --- /dev/null +++ b/system/autoware_auto_msgs_adapter/src/include/adapter_map.hpp @@ -0,0 +1,59 @@ +// Copyright 2023 The Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef ADAPTER_MAP_HPP_ +#define ADAPTER_MAP_HPP_ + +#include "adapter_base.hpp" + +#include + +#include +#include + +#include + +namespace autoware_auto_msgs_adapter +{ +using autoware_auto_mapping_msgs::msg::HADMapBin; +using autoware_map_msgs::msg::LaneletMapBin; + +class AdapterMap : public autoware_auto_msgs_adapter::AdapterBase +{ +public: + AdapterMap( + rclcpp::Node & node, const std::string & topic_name_source, + const std::string & topic_name_target, const rclcpp::QoS & qos = rclcpp::QoS{1}) + : AdapterBase(node, topic_name_source, topic_name_target, qos) + { + RCLCPP_DEBUG( + node.get_logger(), "AdapterMap is initialized to convert: %s -> %s", + topic_name_source.c_str(), topic_name_target.c_str()); + } + +protected: + HADMapBin convert(const LaneletMapBin & msg_source) override + { + autoware_auto_mapping_msgs::msg::HADMapBin msg_auto; + msg_auto.header = msg_source.header; + msg_auto.format_version = msg_source.version_map_format; + msg_auto.map_version = msg_source.version_map; + msg_auto.map_format = 0; + msg_auto.data = msg_source.data; + + return msg_auto; + } +}; +} // namespace autoware_auto_msgs_adapter + +#endif // ADAPTER_MAP_HPP_ diff --git a/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp b/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp index f595359dcc1e7..f0e28f48aacd8 100644 --- a/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp +++ b/system/autoware_auto_msgs_adapter/src/include/autoware_auto_msgs_adapter_core.hpp @@ -15,6 +15,7 @@ #define AUTOWARE_AUTO_MSGS_ADAPTER_CORE_HPP_ #include "adapter_control.hpp" +#include "adapter_map.hpp" #include "adapter_perception.hpp" #include diff --git a/system/autoware_auto_msgs_adapter/test/test_msg_had_map_bin.cpp b/system/autoware_auto_msgs_adapter/test/test_msg_had_map_bin.cpp new file mode 100644 index 0000000000000..b312bd144f6f3 --- /dev/null +++ b/system/autoware_auto_msgs_adapter/test/test_msg_had_map_bin.cpp @@ -0,0 +1,104 @@ +// Copyright 2023 The Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include + +autoware_map_msgs::msg::LaneletMapBin generate_map_msg() +{ + // generate deterministic random int + std::mt19937 gen(0); + std::uniform_int_distribution<> dis_int(0, 1000000); + auto rand_int = [&dis_int, &gen]() { return dis_int(gen); }; + + autoware_map_msgs::msg::LaneletMapBin msg_map; + msg_map.header.stamp = rclcpp::Time(rand_int()); + msg_map.header.frame_id = "test_frame"; + + msg_map.version_map_format = "1.1.1"; + msg_map.version_map = "1.0.0"; + msg_map.name_map = "florence-prato-city-center"; + msg_map.data.push_back(rand_int()); + + return msg_map; +} + +TEST(AutowareAutoMsgsAdapter, TestHADMapBin) // NOLINT for gtest +{ + const std::string msg_type_target = "autoware_auto_mapping_msgs/msg/HADMapBin"; + const std::string topic_name_source = "topic_name_source"; + const std::string topic_name_target = "topic_name_target"; + + std::cout << "Creating the adapter node..." << std::endl; + + rclcpp::NodeOptions node_options; + node_options.append_parameter_override("msg_type_target", msg_type_target); + node_options.append_parameter_override("topic_name_source", topic_name_source); + node_options.append_parameter_override("topic_name_target", topic_name_target); + + using autoware_auto_msgs_adapter::AutowareAutoMsgsAdapterNode; + AutowareAutoMsgsAdapterNode::SharedPtr node_adapter; + node_adapter = std::make_shared(node_options); + + std::cout << "Creating the subscriber node..." << std::endl; + + auto node_subscriber = std::make_shared("node_subscriber", rclcpp::NodeOptions{}); + + bool test_completed = false; + + const auto msg_map = generate_map_msg(); + auto sub = node_subscriber->create_subscription( + topic_name_target, rclcpp::QoS{1}, + [&msg_map, &test_completed](const autoware_auto_mapping_msgs::msg::HADMapBin::SharedPtr msg) { + EXPECT_EQ(msg->header.stamp, msg_map.header.stamp); + EXPECT_EQ(msg->header.frame_id, msg_map.header.frame_id); + + EXPECT_EQ(msg->map_format, 0); + EXPECT_EQ(msg->format_version, msg_map.version_map_format); + EXPECT_EQ(msg->map_version, msg_map.version_map); + EXPECT_EQ(msg->data, msg_map.data); + + test_completed = true; + }); + + std::cout << "Creating the publisher node..." << std::endl; + + auto node_publisher = std::make_shared("node_publisher", rclcpp::NodeOptions{}); + auto pub = node_publisher->create_publisher( + topic_name_source, rclcpp::QoS{1}); + pub->publish(msg_map); + + auto start_time = std::chrono::system_clock::now(); + auto max_test_dur = std::chrono::seconds(5); + auto timed_out = false; + + while (rclcpp::ok() && !test_completed) { + rclcpp::spin_some(node_subscriber); + rclcpp::spin_some(node_adapter); + rclcpp::spin_some(node_publisher); + rclcpp::sleep_for(std::chrono::milliseconds(50)); + if (std::chrono::system_clock::now() - start_time > max_test_dur) { + timed_out = true; + break; + } + } + + EXPECT_TRUE(test_completed); + EXPECT_FALSE(timed_out); + + // rclcpp::shutdown(); +} From 82d9840bc16dd380e2509e4fdac6d0be041f1796 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 11 Oct 2023 09:05:36 +0900 Subject: [PATCH 429/547] refactor(behavior_path_planner): separate processing time function (#5256) Signed-off-by: Takamasa Horibe --- .../include/behavior_path_planner/planner_manager.hpp | 5 +++++ .../src/behavior_path_planner_node.cpp | 1 + planning/behavior_path_planner/src/planner_manager.cpp | 10 ++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index ee5771c98217e..72c32039da627 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -242,6 +242,11 @@ class PlannerManager */ void print() const; + /** + * @brief publish processing time of each module. + */ + void publishProcessingTime() const; + /** * @brief visit each module and get debug information. */ diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index 6e37bd0ec4b2c..755817aa22ae7 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -579,6 +579,7 @@ void BehaviorPathPlannerNode::run() lk_pd.unlock(); // release planner_data_ planner_manager_->print(); + planner_manager_->publishProcessingTime(); planner_manager_->publishMarker(); planner_manager_->publishVirtualWall(); lk_manager.unlock(); // release planner_manager_ diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index 5963ddb778874..24967608775f3 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -886,13 +886,19 @@ void PlannerManager::print() const string_stream << std::right << "[" << std::setw(max_string_num + 1) << std::left << t.first << ":" << std::setw(4) << std::right << t.second << "ms]\n" << std::setw(21); - std::string name = std::string("processing_time/") + t.first; - debug_publisher_ptr_->publish(name, t.second); } RCLCPP_INFO_STREAM(logger_, string_stream.str()); } +void PlannerManager::publishProcessingTime() const +{ + for (const auto & t : processing_time_) { + std::string name = std::string("processing_time/") + t.first; + debug_publisher_ptr_->publish(name, t.second); + } +} + std::shared_ptr PlannerManager::getDebugMsg() { debug_msg_ptr_ = std::make_shared(); From 2aa9d1cd68b81df7b154195e7243f3e872256b8e Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Wed, 11 Oct 2023 09:05:54 +0900 Subject: [PATCH 430/547] chore(obstacle_velocity_limiter): publish processing time as float (#5257) Signed-off-by: Takamasa Horibe --- planning/obstacle_velocity_limiter/README.md | 2 +- .../obstacle_velocity_limiter_node.hpp | 4 ++-- .../src/obstacle_velocity_limiter_node.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/planning/obstacle_velocity_limiter/README.md b/planning/obstacle_velocity_limiter/README.md index d670f07d26e9d..36aa35bb31156 100644 --- a/planning/obstacle_velocity_limiter/README.md +++ b/planning/obstacle_velocity_limiter/README.md @@ -161,7 +161,7 @@ For example a value of `1` means all trajectory points will be evaluated while a | ------------------------------- | ---------------------------------------- | -------------------------------------------------------- | | `~/output/trajectory` | `autoware_auto_planning_msgs/Trajectory` | Trajectory with adjusted velocities | | `~/output/debug_markers` | `visualization_msgs/MarkerArray` | Debug markers (envelopes, obstacle polygons) | -| `~/output/runtime_microseconds` | `std_msgs/Int64` | Time taken to calculate the trajectory (in microseconds) | +| `~/output/runtime_microseconds` | `tier4_debug_msgs/Float64` | Time taken to calculate the trajectory (in microseconds) | ## Parameters diff --git a/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp b/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp index a2ff72d0da562..cbdd390183d61 100644 --- a/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp +++ b/planning/obstacle_velocity_limiter/include/obstacle_velocity_limiter/obstacle_velocity_limiter_node.hpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include @@ -56,7 +56,7 @@ class ObstacleVelocityLimiterNode : public rclcpp::Node pub_trajectory_; //!< @brief publisher for output trajectory rclcpp::Publisher::SharedPtr pub_debug_markers_; //!< @brief publisher for debug markers - rclcpp::Publisher::SharedPtr + rclcpp::Publisher::SharedPtr pub_runtime_; //!< @brief publisher for callback runtime rclcpp::Subscription::SharedPtr sub_trajectory_; //!< @brief subscriber for reference trajectory diff --git a/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp b/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp index f4601979b9b8a..086d76e9ef0fe 100644 --- a/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp +++ b/planning/obstacle_velocity_limiter/src/obstacle_velocity_limiter_node.cpp @@ -68,7 +68,8 @@ ObstacleVelocityLimiterNode::ObstacleVelocityLimiterNode(const rclcpp::NodeOptio pub_trajectory_ = create_publisher("~/output/trajectory", 1); pub_debug_markers_ = create_publisher("~/output/debug_markers", 1); - pub_runtime_ = create_publisher("~/output/runtime_microseconds", 1); + pub_runtime_ = + create_publisher("~/output/runtime_microseconds", 1); const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(*this).getVehicleInfo(); vehicle_lateral_offset_ = static_cast(vehicle_info.max_lateral_offset_m); @@ -223,7 +224,8 @@ void ObstacleVelocityLimiterNode::onTrajectory(const Trajectory::ConstSharedPtr const auto t_end = std::chrono::system_clock::now(); const auto runtime = std::chrono::duration_cast(t_end - t_start); - pub_runtime_->publish(std_msgs::msg::Int64().set__data(runtime.count())); + pub_runtime_->publish(tier4_debug_msgs::msg::Float64Stamped().set__stamp(now()).set__data( + static_cast(runtime.count()))); if (pub_debug_markers_->get_subscription_count() > 0) { const auto safe_projected_linestrings = From 3a8ff49eb0da121c6c0a28546f0853d5853f57d4 Mon Sep 17 00:00:00 2001 From: Kento Yabuuchi Date: Wed, 11 Oct 2023 10:09:48 +0900 Subject: [PATCH 431/547] fix(map_projection_loader): fix sample config parameter in readme (#5262) fix readme Signed-off-by: Kento Yabuuchi --- map/map_projection_loader/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map/map_projection_loader/README.md b/map/map_projection_loader/README.md index 0b0c41821d036..94b142da3dcf9 100644 --- a/map/map_projection_loader/README.md +++ b/map/map_projection_loader/README.md @@ -25,7 +25,7 @@ sample-map-rosbag ```yaml # map_projector_info.yaml -type: "local" +projector_type: local ``` ### Using MGRS From 671cc9658f8ccf52de03089f7b1ddc9e6c59a2a5 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 11 Oct 2023 15:58:07 +0900 Subject: [PATCH 432/547] feat(intersection): find stop line from footprint intersection with areas (#5264) Signed-off-by: Mamoru Sobue --- .../src/scene_intersection.cpp | 3 +- .../src/scene_merge_from_private_road.cpp | 6 +- .../src/util.cpp | 106 +++++++++++++----- .../src/util_type.hpp | 6 +- 4 files changed, 87 insertions(+), 34 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 75bb4e861a60f..f5eda0d0fe4d7 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -877,7 +877,8 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( util::getTrafficPrioritizedLevel(assigned_lanelet, planner_data_->traffic_light_id_map); const bool is_prioritized = traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED; - intersection_lanelets.update(is_prioritized, interpolated_path_info); + const auto footprint = planner_data_->vehicle_info_.createFootprint(0.0, 0.0); + intersection_lanelets.update(is_prioritized, interpolated_path_info, footprint); // this is abnormal const auto & conflicting_lanelets = intersection_lanelets.conflicting(); diff --git a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp index bb54d829cc477..698242da67528 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_merge_from_private_road.cpp @@ -94,8 +94,10 @@ bool MergeFromPrivateRoadModule::modifyPathVelocity(PathWithLaneId * path, StopR planner_param_.attention_area_length, planner_param_.occlusion_attention_area_length, planner_param_.consider_wrong_direction_vehicle); } - intersection_lanelets_.value().update(false, interpolated_path_info); - const auto & first_conflicting_area = intersection_lanelets_.value().first_conflicting_area(); + auto & intersection_lanelets = intersection_lanelets_.value(); + const auto local_footprint = planner_data_->vehicle_info_.createFootprint(0.0, 0.0); + intersection_lanelets.update(false, interpolated_path_info, local_footprint); + const auto & first_conflicting_area = intersection_lanelets.first_conflicting_area(); if (!first_conflicting_area) { return false; } diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 2f3fcc6f6fde0..7862a533f34eb 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -220,6 +220,50 @@ static std::optional getStopLineIndexFromMap( return stop_idx_ip_opt.get(); } +static std::optional getFirstPointInsidePolygonByFootprint( + const lanelet::CompoundPolygon3d & polygon, const InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint) +{ + const auto & path_ip = interpolated_path_info.path; + const auto [lane_start, lane_end] = interpolated_path_info.lane_id_interval.value(); + + const auto area_2d = lanelet::utils::to2D(polygon).basicPolygon(); + for (auto i = lane_start; i <= lane_end; ++i) { + const auto & base_pose = path_ip.points.at(i).point.pose; + const auto path_footprint = tier4_autoware_utils::transformVector( + footprint, tier4_autoware_utils::pose2transform(base_pose)); + if (bg::intersects(path_footprint, area_2d)) { + return std::make_optional(i); + } + } + return std::nullopt; +} + +static std::optional> +getFirstPointInsidePolygonsByFootprint( + const std::vector & polygons, + const InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint) +{ + const auto & path_ip = interpolated_path_info.path; + const auto [lane_start, lane_end] = interpolated_path_info.lane_id_interval.value(); + + for (size_t i = lane_start; i <= lane_end; ++i) { + const auto & pose = path_ip.points.at(i).point.pose; + const auto path_footprint = + tier4_autoware_utils::transformVector(footprint, tier4_autoware_utils::pose2transform(pose)); + for (const auto & polygon : polygons) { + const auto area_2d = lanelet::utils::to2D(polygon).basicPolygon(); + const bool is_in_polygon = bg::intersects(area_2d, path_footprint); + if (is_in_polygon) { + return std::make_optional>( + i, polygon); + } + } + } + return std::nullopt; +} + std::optional generateIntersectionStopLines( const lanelet::CompoundPolygon3d & first_conflicting_area, const lanelet::CompoundPolygon3d & first_detection_area, @@ -236,6 +280,7 @@ std::optional generateIntersectionStopLines( const int base2front_idx_dist = std::ceil(planner_data->vehicle_info_.max_longitudinal_offset_m / ds); + /* // find the index of the first point that intersects with detection_areas const auto first_inside_detection_idx_ip_opt = getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_detection_area); @@ -244,6 +289,16 @@ std::optional generateIntersectionStopLines( return std::nullopt; } const auto first_inside_detection_ip = first_inside_detection_idx_ip_opt.value(); + */ + // find the index of the first point whose vehicle footprint on it intersects with detection_area + const auto local_footprint = planner_data->vehicle_info_.createFootprint(0.0, 0.0); + std::optional first_footprint_inside_detection_ip_opt = + getFirstPointInsidePolygonByFootprint( + first_detection_area, interpolated_path_info, local_footprint); + if (!first_footprint_inside_detection_ip_opt) { + return std::nullopt; + } + const auto first_footprint_inside_detection_ip = first_footprint_inside_detection_ip_opt.value(); // (1) default stop line position on interpolated path bool default_stop_line_valid = true; @@ -254,8 +309,7 @@ std::optional generateIntersectionStopLines( stop_idx_ip_int = static_cast(map_stop_idx_ip.value()) - base2front_idx_dist; } if (stop_idx_ip_int < 0) { - stop_idx_ip_int = static_cast(first_inside_detection_ip) - stop_line_margin_idx_dist - - base2front_idx_dist; + stop_idx_ip_int = first_footprint_inside_detection_ip - stop_line_margin_idx_dist; } if (stop_idx_ip_int < 0) { default_stop_line_valid = false; @@ -272,8 +326,6 @@ std::optional generateIntersectionStopLines( const auto closest_idx_ip = closest_idx_ip_opt.value(); // (3) occlusion peeking stop line position on interpolated path - const auto local_footprint = planner_data->vehicle_info_.createFootprint(0.0, 0.0); - const auto area_2d = lanelet::utils::to2D(first_detection_area).basicPolygon(); int occlusion_peeking_line_ip_int = static_cast(default_stop_line_ip); bool occlusion_peeking_line_valid = true; { @@ -281,20 +333,13 @@ std::optional generateIntersectionStopLines( const auto & base_pose0 = path_ip.points.at(default_stop_line_ip).point.pose; const auto path_footprint0 = tier4_autoware_utils::transformVector( local_footprint, tier4_autoware_utils::pose2transform(base_pose0)); - if (bg::intersects(path_footprint0, area_2d)) { + if (bg::intersects( + path_footprint0, lanelet::utils::to2D(first_detection_area).basicPolygon())) { occlusion_peeking_line_valid = false; } } if (occlusion_peeking_line_valid) { - for (size_t i = default_stop_line_ip + 1; i <= std::get<1>(lane_interval_ip); ++i) { - const auto & base_pose = path_ip.points.at(i).point.pose; - const auto path_footprint = tier4_autoware_utils::transformVector( - local_footprint, tier4_autoware_utils::pose2transform(base_pose)); - if (bg::intersects(path_footprint, area_2d)) { - occlusion_peeking_line_ip_int = i; - break; - } - } + occlusion_peeking_line_ip_int = first_footprint_inside_detection_ip; } const auto first_attention_stop_line_ip = static_cast(occlusion_peeking_line_ip_int); const bool first_attention_stop_line_valid = occlusion_peeking_line_valid; @@ -310,8 +355,8 @@ std::optional generateIntersectionStopLines( const double delay_response_time = planner_data->delay_response_time; const double braking_dist = planning_utils::calcJudgeLineDistWithJerkLimit( velocity, acceleration, max_stop_acceleration, max_stop_jerk, delay_response_time); - int pass_judge_ip_int = static_cast(first_inside_detection_ip) - base2front_idx_dist - - std::ceil(braking_dist / ds); + int pass_judge_ip_int = + static_cast(first_footprint_inside_detection_ip) - std::ceil(braking_dist / ds); const auto pass_judge_line_ip = static_cast( std::clamp(pass_judge_ip_int, 0, static_cast(path_ip.points.size()) - 1)); @@ -321,18 +366,18 @@ std::optional generateIntersectionStopLines( if (use_stuck_stopline) { // NOTE: when ego vehicle is approaching detection area and already passed // first_conflicting_area, this could be null. - const auto stuck_stop_line_idx_ip_opt = - getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_conflicting_area); + const auto stuck_stop_line_idx_ip_opt = getFirstPointInsidePolygonByFootprint( + first_conflicting_area, interpolated_path_info, local_footprint); if (!stuck_stop_line_idx_ip_opt) { stuck_stop_line_valid = false; stuck_stop_line_ip_int = 0; } else { - stuck_stop_line_ip_int = stuck_stop_line_idx_ip_opt.value(); + stuck_stop_line_ip_int = stuck_stop_line_idx_ip_opt.value() - stop_line_margin_idx_dist; } } else { - stuck_stop_line_ip_int = std::get<0>(lane_interval_ip); + stuck_stop_line_ip_int = + std::get<0>(lane_interval_ip) - (stop_line_margin_idx_dist + base2front_idx_dist); } - stuck_stop_line_ip_int -= (stop_line_margin_idx_dist + base2front_idx_dist); if (stuck_stop_line_ip_int < 0) { stuck_stop_line_valid = false; } @@ -1243,21 +1288,22 @@ double calcDistanceUntilIntersectionLanelet( } void IntersectionLanelets::update( - const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info) + const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint) { is_prioritized_ = is_prioritized; // find the first conflicting/detection area polygon intersecting the path - const auto & path = interpolated_path_info.path; - const auto & lane_interval = interpolated_path_info.lane_id_interval.value(); - { - auto first = getFirstPointInsidePolygons(path, lane_interval, conflicting_area_); - if (first && !first_conflicting_area_) { + if (!first_conflicting_area_) { + auto first = + getFirstPointInsidePolygonsByFootprint(conflicting_area_, interpolated_path_info, footprint); + if (first) { first_conflicting_area_ = first.value().second; } } - { - auto first = getFirstPointInsidePolygons(path, lane_interval, attention_area_); - if (first && !first_attention_area_) { + if (!first_attention_area_) { + auto first = + getFirstPointInsidePolygonsByFootprint(attention_area_, interpolated_path_info, footprint); + if (first) { first_attention_area_ = first.value().second; } } diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index aed81d771e480..581f22904fd2b 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -15,6 +15,8 @@ #ifndef UTIL_TYPE_HPP_ #define UTIL_TYPE_HPP_ +#include + #include #include #include @@ -67,7 +69,9 @@ struct InterpolatedPathInfo struct IntersectionLanelets { public: - void update(const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info); + void update( + const bool is_prioritized, const InterpolatedPathInfo & interpolated_path_info, + const tier4_autoware_utils::LinearRing2d & footprint); const lanelet::ConstLanelets & attention() const { return is_prioritized_ ? attention_non_preceding_ : attention_; From 9fe7626889668b80298c7aeeba3068b6b622fc73 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 11 Oct 2023 15:58:22 +0900 Subject: [PATCH 433/547] fix(intersection): collision check considering object width (#5265) Signed-off-by: Takayuki Murooka --- .../src/scene_intersection.cpp | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index f5eda0d0fe4d7..fb35834ab39a5 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -43,6 +44,32 @@ namespace behavior_velocity_planner { namespace bg = boost::geometry; +namespace +{ +Polygon2d createOneStepPolygon( + const geometry_msgs::msg::Pose & prev_pose, const geometry_msgs::msg::Pose & next_pose, + const autoware_auto_perception_msgs::msg::Shape & shape) +{ + const auto prev_poly = tier4_autoware_utils::toPolygon2d(prev_pose, shape); + const auto next_poly = tier4_autoware_utils::toPolygon2d(next_pose, shape); + + Polygon2d one_step_poly; + for (const auto & point : prev_poly.outer()) { + one_step_poly.outer().push_back(point); + } + for (const auto & point : next_poly.outer()) { + one_step_poly.outer().push_back(point); + } + + bg::correct(one_step_poly); + + Polygon2d convex_one_step_poly; + bg::convex_hull(one_step_poly, convex_one_step_poly); + + return convex_one_step_poly; +} +} // namespace + static bool isTargetCollisionVehicleType( const autoware_auto_perception_msgs::msg::PredictedObject & object) { @@ -1313,14 +1340,14 @@ bool IntersectionModule::checkCollision( // collision point const auto first_itr = std::adjacent_find( predicted_path.path.cbegin(), predicted_path.path.cend(), - [&ego_poly](const auto & a, const auto & b) { - return bg::intersects(ego_poly, LineString2d{to_bg2d(a), to_bg2d(b)}); + [&ego_poly, &object](const auto & a, const auto & b) { + return bg::intersects(ego_poly, createOneStepPolygon(a, b, object.shape)); }); if (first_itr == predicted_path.path.cend()) continue; const auto last_itr = std::adjacent_find( predicted_path.path.crbegin(), predicted_path.path.crend(), - [&ego_poly](const auto & a, const auto & b) { - return bg::intersects(ego_poly, LineString2d{to_bg2d(a), to_bg2d(b)}); + [&ego_poly, &object](const auto & a, const auto & b) { + return bg::intersects(ego_poly, createOneStepPolygon(a, b, object.shape)); }); if (last_itr == predicted_path.path.crend()) continue; From dc8cc07bfebeed7aef328891a720bfc17899726d Mon Sep 17 00:00:00 2001 From: cyn-liu <104069308+cyn-liu@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:48:44 +0800 Subject: [PATCH 434/547] feat(kalman_filter): add gtest (#4799) * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * feat(kalman_filter): add gtest Signed-off-by: Cynthia Liu * style(pre-commit): autofix --------- Signed-off-by: Cynthia Liu Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- common/kalman_filter/CMakeLists.txt | 7 + common/kalman_filter/package.xml | 2 + .../kalman_filter/test/test_kalman_filter.cpp | 94 ++++++++++++++ .../test/test_time_delay_kalman_filter.cpp | 121 ++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 common/kalman_filter/test/test_kalman_filter.cpp create mode 100644 common/kalman_filter/test/test_time_delay_kalman_filter.cpp diff --git a/common/kalman_filter/CMakeLists.txt b/common/kalman_filter/CMakeLists.txt index dd59da727605d..6cbdf67a0affa 100644 --- a/common/kalman_filter/CMakeLists.txt +++ b/common/kalman_filter/CMakeLists.txt @@ -19,4 +19,11 @@ ament_auto_add_library(kalman_filter SHARED include/kalman_filter/time_delay_kalman_filter.hpp ) +if(BUILD_TESTING) + file(GLOB_RECURSE test_files test/*.cpp) + ament_add_ros_isolated_gtest(test_kalman_filter ${test_files}) + + target_link_libraries(test_kalman_filter kalman_filter) +endif() + ament_auto_package() diff --git a/common/kalman_filter/package.xml b/common/kalman_filter/package.xml index ea061f3f23bb8..200440b5774c7 100644 --- a/common/kalman_filter/package.xml +++ b/common/kalman_filter/package.xml @@ -18,7 +18,9 @@ eigen3_cmake_module ament_cmake_cppcheck + ament_cmake_ros ament_lint_auto + autoware_lint_common ament_cmake diff --git a/common/kalman_filter/test/test_kalman_filter.cpp b/common/kalman_filter/test/test_kalman_filter.cpp new file mode 100644 index 0000000000000..4f4e9ce44669b --- /dev/null +++ b/common/kalman_filter/test/test_kalman_filter.cpp @@ -0,0 +1,94 @@ +// Copyright 2023 The Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "kalman_filter/kalman_filter.hpp" + +#include + +TEST(kalman_filter, kf) +{ + KalmanFilter kf_; + + Eigen::MatrixXd x_t(2, 1); + x_t << 1, 2; + + Eigen::MatrixXd P_t(2, 2); + P_t << 1, 0, 0, 1; + + Eigen::MatrixXd Q_t(2, 2); + Q_t << 0.01, 0, 0, 0.01; + + Eigen::MatrixXd R_t(2, 2); + R_t << 0.09, 0, 0, 0.09; + + Eigen::MatrixXd C_t(2, 2); + C_t << 1, 0, 0, 1; + + Eigen::MatrixXd A_t(2, 2); + A_t << 1, 0, 0, 1; + + Eigen::MatrixXd B_t(2, 2); + B_t << 1, 0, 0, 1; + + // Initialize the filter and check if initialization was successful + EXPECT_TRUE(kf_.init(x_t, A_t, B_t, C_t, Q_t, R_t, P_t)); + + // Perform prediction + Eigen::MatrixXd u_t(2, 1); + u_t << 0.1, 0.1; + EXPECT_TRUE(kf_.predict(u_t)); + + // Check the updated state and covariance matrix + Eigen::MatrixXd x_predict_expected = A_t * x_t + B_t * u_t; + Eigen::MatrixXd P_predict_expected = A_t * P_t * A_t.transpose() + Q_t; + + Eigen::MatrixXd x_predict; + kf_.getX(x_predict); + Eigen::MatrixXd P_predict; + kf_.getP(P_predict); + + EXPECT_NEAR(x_predict(0, 0), x_predict_expected(0, 0), 1e-5); + EXPECT_NEAR(x_predict(1, 0), x_predict_expected(1, 0), 1e-5); + EXPECT_NEAR(P_predict(0, 0), P_predict_expected(0, 0), 1e-5); + EXPECT_NEAR(P_predict(1, 1), P_predict_expected(1, 1), 1e-5); + + // Perform update + Eigen::MatrixXd y_t(2, 1); + y_t << 1.05, 2.05; + EXPECT_TRUE(kf_.update(y_t)); + + // Check the updated state and covariance matrix + const Eigen::MatrixXd PCT_t = P_predict_expected * C_t.transpose(); + const Eigen::MatrixXd K_t = PCT_t * ((R_t + C_t * PCT_t).inverse()); + const Eigen::MatrixXd y_pred = C_t * x_predict_expected; + Eigen::MatrixXd x_update_expected = x_predict_expected + K_t * (y_t - y_pred); + Eigen::MatrixXd P_update_expected = P_predict_expected - K_t * (C_t * P_predict_expected); + + Eigen::MatrixXd x_update; + kf_.getX(x_update); + Eigen::MatrixXd P_update; + kf_.getP(P_update); + + EXPECT_NEAR(x_update(0, 0), x_update_expected(0, 0), 1e-5); + EXPECT_NEAR(x_update(1, 0), x_update_expected(1, 0), 1e-5); + EXPECT_NEAR(P_update(0, 0), P_update_expected(0, 0), 1e-5); + EXPECT_NEAR(P_update(1, 1), P_update_expected(1, 1), 1e-5); +} + +int main(int argc, char * argv[]) +{ + testing::InitGoogleTest(&argc, argv); + bool result = RUN_ALL_TESTS(); + return result; +} diff --git a/common/kalman_filter/test/test_time_delay_kalman_filter.cpp b/common/kalman_filter/test/test_time_delay_kalman_filter.cpp new file mode 100644 index 0000000000000..32fefd8ceff70 --- /dev/null +++ b/common/kalman_filter/test/test_time_delay_kalman_filter.cpp @@ -0,0 +1,121 @@ +// Copyright 2023 The Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "kalman_filter/time_delay_kalman_filter.hpp" + +#include + +TEST(time_delay_kalman_filter, td_kf) +{ + TimeDelayKalmanFilter td_kf_; + + Eigen::MatrixXd x_t(3, 1); + x_t << 1.0, 2.0, 3.0; + Eigen::MatrixXd P_t(3, 3); + P_t << 0.1, 0.0, 0.0, 0.0, 0.2, 0.0, 0.0, 0.0, 0.3; + const int max_delay_step = 5; + const int dim_x = x_t.rows(); + const int dim_x_ex = dim_x * max_delay_step; + // Initialize the filter + td_kf_.init(x_t, P_t, max_delay_step); + + // Check if initialization was successful + Eigen::MatrixXd x_init = td_kf_.getLatestX(); + Eigen::MatrixXd P_init = td_kf_.getLatestP(); + Eigen::MatrixXd x_ex_t = Eigen::MatrixXd::Zero(dim_x_ex, 1); + Eigen::MatrixXd P_ex_t = Eigen::MatrixXd::Zero(dim_x_ex, dim_x_ex); + for (int i = 0; i < max_delay_step; ++i) { + x_ex_t.block(i * dim_x, 0, dim_x, 1) = x_t; + P_ex_t.block(i * dim_x, i * dim_x, dim_x, dim_x) = P_t; + } + + EXPECT_EQ(x_init.rows(), 3); + EXPECT_EQ(x_init.cols(), 1); + EXPECT_EQ(P_init.rows(), 3); + EXPECT_EQ(P_init.cols(), 3); + EXPECT_NEAR(x_init(0, 0), x_t(0, 0), 1e-5); + EXPECT_NEAR(x_init(1, 0), x_t(1, 0), 1e-5); + EXPECT_NEAR(x_init(2, 0), x_t(2, 0), 1e-5); + EXPECT_NEAR(P_init(0, 0), P_t(0, 0), 1e-5); + EXPECT_NEAR(P_init(1, 1), P_t(1, 1), 1e-5); + EXPECT_NEAR(P_init(2, 2), P_t(2, 2), 1e-5); + + // Define prediction parameters + Eigen::MatrixXd A_t(3, 3); + A_t << 2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0; + Eigen::MatrixXd Q_t(3, 3); + Q_t << 0.01, 0.0, 0.0, 0.0, 0.02, 0.0, 0.0, 0.0, 0.03; + Eigen::MatrixXd x_next(3, 1); + x_next << 2.0, 4.0, 6.0; + + // Perform prediction + EXPECT_TRUE(td_kf_.predictWithDelay(x_next, A_t, Q_t)); + + // Check the prediction state and covariance matrix + Eigen::MatrixXd x_predict = td_kf_.getLatestX(); + Eigen::MatrixXd P_predict = td_kf_.getLatestP(); + Eigen::MatrixXd x_tmp = Eigen::MatrixXd::Zero(dim_x_ex, 1); + x_tmp.block(0, 0, dim_x, 1) = A_t * x_t; + x_tmp.block(dim_x, 0, dim_x_ex - dim_x, 1) = x_ex_t.block(0, 0, dim_x_ex - dim_x, 1); + x_ex_t = x_tmp; + Eigen::MatrixXd x_predict_expected = x_ex_t.block(0, 0, dim_x, 1); + Eigen::MatrixXd P_tmp = Eigen::MatrixXd::Zero(dim_x_ex, dim_x_ex); + P_tmp.block(0, 0, dim_x, dim_x) = A_t * P_ex_t.block(0, 0, dim_x, dim_x) * A_t.transpose() + Q_t; + P_tmp.block(0, dim_x, dim_x, dim_x_ex - dim_x) = + A_t * P_ex_t.block(0, 0, dim_x, dim_x_ex - dim_x); + P_tmp.block(dim_x, 0, dim_x_ex - dim_x, dim_x) = + P_ex_t.block(0, 0, dim_x_ex - dim_x, dim_x) * A_t.transpose(); + P_tmp.block(dim_x, dim_x, dim_x_ex - dim_x, dim_x_ex - dim_x) = + P_ex_t.block(0, 0, dim_x_ex - dim_x, dim_x_ex - dim_x); + P_ex_t = P_tmp; + Eigen::MatrixXd P_predict_expected = P_ex_t.block(0, 0, dim_x, dim_x); + EXPECT_NEAR(x_predict(0, 0), x_predict_expected(0, 0), 1e-5); + EXPECT_NEAR(x_predict(1, 0), x_predict_expected(1, 0), 1e-5); + EXPECT_NEAR(x_predict(2, 0), x_predict_expected(2, 0), 1e-5); + EXPECT_NEAR(P_predict(0, 0), P_predict_expected(0, 0), 1e-5); + EXPECT_NEAR(P_predict(1, 1), P_predict_expected(1, 1), 1e-5); + EXPECT_NEAR(P_predict(2, 2), P_predict_expected(2, 2), 1e-5); + + // Define update parameters + Eigen::MatrixXd C_t(3, 3); + C_t << 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5; + Eigen::MatrixXd R_t(3, 3); + R_t << 0.001, 0.0, 0.0, 0.0, 0.002, 0.0, 0.0, 0.0, 0.003; + Eigen::MatrixXd y_t(3, 1); + y_t << 1.05, 2.05, 3.05; + const int delay_step = 2; // Choose an appropriate delay step + const int dim_y = y_t.rows(); + + // Perform update + EXPECT_TRUE(td_kf_.updateWithDelay(y_t, C_t, R_t, delay_step)); + + // Check the updated state and covariance matrix + Eigen::MatrixXd x_update = td_kf_.getLatestX(); + Eigen::MatrixXd P_update = td_kf_.getLatestP(); + + Eigen::MatrixXd C_ex_t = Eigen::MatrixXd::Zero(dim_y, dim_x_ex); + const Eigen::MatrixXd PCT_t = P_ex_t * C_ex_t.transpose(); + const Eigen::MatrixXd K_t = PCT_t * ((R_t + C_ex_t * PCT_t).inverse()); + const Eigen::MatrixXd y_pred = C_ex_t * x_ex_t; + x_ex_t = x_ex_t + K_t * (y_t - y_pred); + P_ex_t = P_ex_t - K_t * (C_ex_t * P_ex_t); + Eigen::MatrixXd x_update_expected = x_ex_t.block(0, 0, dim_x, 1); + Eigen::MatrixXd P_update_expected = P_ex_t.block(0, 0, dim_x, dim_x); + EXPECT_NEAR(x_update(0, 0), x_update_expected(0, 0), 1e-5); + EXPECT_NEAR(x_update(1, 0), x_update_expected(1, 0), 1e-5); + EXPECT_NEAR(x_update(2, 0), x_update_expected(2, 0), 1e-5); + EXPECT_NEAR(P_update(0, 0), P_update_expected(0, 0), 1e-5); + EXPECT_NEAR(P_update(1, 1), P_update_expected(1, 1), 1e-5); + EXPECT_NEAR(P_update(2, 2), P_update_expected(2, 2), 1e-5); +} From 84995ec3ca4e201ed1d800c0eeb967de25298f84 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 11 Oct 2023 18:37:33 +0900 Subject: [PATCH 435/547] feat(intersection): not detect stuck vehicle when turning left (#5268) Signed-off-by: Takayuki Murooka --- .../src/scene_intersection.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index fb35834ab39a5..26bc8210b3bad 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -1213,6 +1213,11 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( bool IntersectionModule::checkStuckVehicle( const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets) { + // NOTE: No need to stop for stuck vehicle when the ego will turn left. + if (turn_direction_ == std::string("left")) { + return false; + } + const auto & objects_ptr = planner_data->predicted_objects; // considering lane change in the intersection, these lanelets are generated from the path From 6b58314939716775b7253c07f1140928af4e28ac Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:06:01 +0900 Subject: [PATCH 436/547] refactor(motion_velocity_smoother): align processing time topic name (#5273) Signed-off-by: satoshi-ota --- .../src/motion_velocity_smoother_node.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp index 75c84d3c482f6..6b5da01b9cb09 100644 --- a/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp +++ b/planning/motion_velocity_smoother/src/motion_velocity_smoother_node.cpp @@ -79,7 +79,7 @@ MotionVelocitySmootherNode::MotionVelocitySmootherNode(const rclcpp::NodeOptions debug_closest_acc_ = create_publisher("~/closest_acceleration", 1); debug_closest_jerk_ = create_publisher("~/closest_jerk", 1); debug_closest_max_velocity_ = create_publisher("~/closest_max_velocity", 1); - debug_calculation_time_ = create_publisher("~/calculation_time", 1); + debug_calculation_time_ = create_publisher("~/debug/processing_time_ms", 1); pub_trajectory_raw_ = create_publisher("~/debug/trajectory_raw", 1); pub_trajectory_vel_lim_ = create_publisher("~/debug/trajectory_external_velocity_limited", 1); From 7e0ee1eba48862ec0a6579e3db578c128aa4d8cb Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Thu, 12 Oct 2023 11:49:11 +0900 Subject: [PATCH 437/547] feat(lane_change): keep distance against avoidable stationary objects (#5236) * feat(lane_change): keep distance against avoidable stationary objects Signed-off-by: kosuke55 consider rss distance Signed-off-by: kosuke55 tmp Signed-off-by: kosuke55 * fix dangling Signed-off-by: Takamasa Horibe * use parameter for velocity_treshold Signed-off-by: Takamasa Horibe * check object only on the ego path Signed-off-by: Takamasa Horibe * fix Signed-off-by: Takamasa Horibe --------- Signed-off-by: kosuke55 Signed-off-by: Takamasa Horibe Co-authored-by: Takamasa Horibe --- .../src/scene_module/lane_change/normal.cpp | 83 ++++++++++++++++--- 1 file changed, 73 insertions(+), 10 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 71ba8c51ec6ad..46812d9c487a9 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -37,6 +37,8 @@ namespace behavior_path_planner { +using motion_utils::calcSignedArcLength; + NormalLaneChange::NormalLaneChange( const std::shared_ptr & parameters, LaneChangeModuleType type, Direction direction) @@ -139,7 +141,7 @@ BehaviorModuleOutput NormalLaneChange::generateOutput() if (isStopState()) { const auto current_velocity = getEgoVelocity(); - const auto current_dist = motion_utils::calcSignedArcLength( + const auto current_dist = calcSignedArcLength( output.path->points, output.path->points.front().point.pose.position, getEgoPosition()); const auto stop_dist = -(current_velocity * current_velocity / (2.0 * planner_data_->parameters.min_acc)); @@ -202,7 +204,70 @@ void NormalLaneChange::insertStopPoint( } const double stop_point_buffer = getCommonParam().backward_length_buffer_for_end_of_lane; - const double stopping_distance = distance_to_terminal - lane_change_buffer - stop_point_buffer; + const auto target_objects = getTargetObjects(status_.current_lanes, status_.target_lanes); + const bool is_in_terminal_section = lanelet::utils::isInLanelet(getEgoPose(), lanelets.back()) || + distance_to_terminal < lane_change_buffer; + const bool has_blocking_target_lane_obj = std::any_of( + target_objects.target_lane.begin(), target_objects.target_lane.end(), [&](const auto & o) { + if (o.initial_twist.twist.linear.x > lane_change_parameters_->stop_velocity_threshold) { + return false; + } + const double distance_to_target_lane_obj = utils::getSignedDistance( + path.points.front().point.pose, o.initial_pose.pose, status_.target_lanes); + return distance_to_target_lane_obj < distance_to_terminal; + }); + + // auto stopping_distance = raw_stopping_distance; + double stopping_distance = distance_to_terminal - lane_change_buffer - stop_point_buffer; + if (is_in_terminal_section || !has_blocking_target_lane_obj) { + // calculate minimum distance from path front to the stationary object on the ego lane. + const auto distance_to_ego_lane_obj = [&]() -> double { + double distance_to_obj = distance_to_terminal; + const double distance_to_ego = + utils::getSignedDistance(path.points.front().point.pose, getEgoPose(), lanelets); + + for (const auto & object : target_objects.current_lane) { + // check if stationary + const auto obj_v = std::abs(object.initial_twist.twist.linear.x); + if (obj_v > lane_change_parameters_->stop_velocity_threshold) { + continue; + } + + // calculate distance from path front to the stationary object polygon on the ego lane. + const auto polygon = + tier4_autoware_utils::toPolygon2d(object.initial_pose.pose, object.shape).outer(); + for (const auto & polygon_p : polygon) { + const auto p_fp = tier4_autoware_utils::toMsg(polygon_p.to_3d()); + const auto lateral_fp = motion_utils::calcLateralOffset(path.points, p_fp); + + // ignore if the point is around the ego path + if (std::abs(lateral_fp) > planner_data_->parameters.vehicle_width) { + continue; + } + + const double current_distance_to_obj = calcSignedArcLength(path.points, 0, p_fp); + + // ignore backward object + if (current_distance_to_obj < distance_to_ego) { + continue; + } + distance_to_obj = std::min(distance_to_obj, current_distance_to_obj); + } + } + return distance_to_obj; + }(); + + // If the lane change space is occupied by a stationary obstacle, move the stop line closer. + // TODO(Horibe): We need to loop this process because the new space could be occupied as well. + stopping_distance = std::min( + distance_to_ego_lane_obj - lane_change_buffer - stop_point_buffer - + getCommonParam().base_link2front - + calcRssDistance( + 0.0, planner_data_->parameters.minimum_lane_changing_velocity, + lane_change_parameters_->rss_params), + stopping_distance); + } + if (stopping_distance > 0.0) { const auto stop_point = utils::insertStopPoint(stopping_distance, path); setStopPose(stop_point.point.pose); @@ -220,8 +285,7 @@ std::optional NormalLaneChange::extendPath() const auto path = status_.lane_change_path.path; const auto lc_start_point = status_.lane_change_path.info.lane_changing_start.position; - const auto dist = - motion_utils::calcSignedArcLength(path.points, lc_start_point, getEgoPosition()); + const auto dist = calcSignedArcLength(path.points, lc_start_point, getEgoPosition()); if (dist < 0.0) { return std::nullopt; @@ -423,7 +487,7 @@ bool NormalLaneChange::isAbleToReturnCurrentLane() const double dist = 0.0; for (size_t idx = nearest_idx; idx < status_.lane_change_path.path.points.size() - 1; ++idx) { - dist += motion_utils::calcSignedArcLength(status_.lane_change_path.path.points, idx, idx + 1); + dist += calcSignedArcLength(status_.lane_change_path.path.points, idx, idx + 1); if (dist > estimated_travel_dist) { const auto & estimated_pose = status_.lane_change_path.path.points.at(idx + 1).point.pose; return utils::isEgoWithinOriginalLane( @@ -439,7 +503,7 @@ bool NormalLaneChange::isEgoOnPreparePhase() const { const auto & start_position = status_.lane_change_path.info.shift_line.start.position; const auto & path_points = status_.lane_change_path.path.points; - return motion_utils::calcSignedArcLength(path_points, start_position, getEgoPosition()) < 0.0; + return calcSignedArcLength(path_points, start_position, getEgoPosition()) < 0.0; } bool NormalLaneChange::isAbleToStopSafely() const @@ -459,7 +523,7 @@ bool NormalLaneChange::isAbleToStopSafely() const double dist = 0.0; for (size_t idx = nearest_idx; idx < status_.lane_change_path.path.points.size() - 1; ++idx) { - dist += motion_utils::calcSignedArcLength(status_.lane_change_path.path.points, idx, idx + 1); + dist += calcSignedArcLength(status_.lane_change_path.path.points, idx, idx + 1); if (dist > stop_dist) { const auto & estimated_pose = status_.lane_change_path.path.points.at(idx + 1).point.pose; return utils::isEgoWithinOriginalLane( @@ -475,7 +539,7 @@ bool NormalLaneChange::hasFinishedAbort() const return true; } - const auto distance_to_finish = motion_utils::calcSignedArcLength( + const auto distance_to_finish = calcSignedArcLength( abort_path_->path.points, getEgoPosition(), abort_path_->info.shift_line.end.position); if (distance_to_finish < 0.0) { @@ -720,8 +784,7 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( double min_dist_ego_to_obj = std::numeric_limits::max(); for (const auto & polygon_p : obj_polygon.outer()) { const auto obj_p = tier4_autoware_utils::createPoint(polygon_p.x(), polygon_p.y(), 0.0); - const double dist_ego_to_obj = - motion_utils::calcSignedArcLength(path.points, current_pose.position, obj_p); + const double dist_ego_to_obj = calcSignedArcLength(path.points, current_pose.position, obj_p); max_dist_ego_to_obj = std::max(dist_ego_to_obj, max_dist_ego_to_obj); min_dist_ego_to_obj = std::min(dist_ego_to_obj, min_dist_ego_to_obj); } From e58eb3262d81a1f1bdf6730c0e42c5b8fad148f6 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Thu, 12 Oct 2023 12:18:48 +0900 Subject: [PATCH 438/547] feat(lane_change): sampling all possible longitudinal acceleration when the ego is in stuck (#5249) * [lane_change] sampling all possible longitudinal acceleration when the ego is in stuck Signed-off-by: Takamasa Horibe * add clock Signed-off-by: Takamasa Horibe * update Signed-off-by: Takamasa Horibe * Update planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp Co-authored-by: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> * Update planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp Co-authored-by: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> * Update planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp Co-authored-by: Fumiya Watanabe * Update planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp Co-authored-by: Fumiya Watanabe * fix style Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe Co-authored-by: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Co-authored-by: Fumiya Watanabe --- .../scene_module/lane_change/normal.hpp | 13 ++ .../src/scene_module/lane_change/normal.cpp | 120 ++++++++++++++---- 2 files changed, 108 insertions(+), 25 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index f1e4483c8bf51..4f1eb7340fdff 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -153,6 +153,19 @@ class NormalLaneChange : public LaneChangeBase const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, const lanelet::ConstLanelets & target_backward_lanes) const; + //! @brief Check if the ego vehicle is in stuck by a stationary obstacle. + //! @param obstacle_check_distance Distance to check ahead for any objects that might be + //! obstructing ego path. It makes sense to use values like the maximum lane change distance. + bool isVehicleStuckByObstacle( + const lanelet::ConstLanelets & current_lanes, const double obstacle_check_distance) const; + + bool isVehicleStuckByObstacle(const lanelet::ConstLanelets & current_lanes) const; + + double calcMaximumLaneChangeLength( + const lanelet::ConstLanelet & current_terminal_lanelet, const double max_acc) const; + + std::pair calcCurrentMinMaxAcceleration() const; + void setStopPose(const Pose & stop_pose); void updateStopTime(); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 46812d9c487a9..4075202367a65 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -572,6 +572,37 @@ int NormalLaneChange::getNumToPreferredLane(const lanelet::ConstLanelet & lane) return std::abs(getRouteHandler()->getNumLaneToPreferredLane(lane, get_opposite_direction)); } +std::pair NormalLaneChange::calcCurrentMinMaxAcceleration() const +{ + const auto & p = getCommonParam(); + + const auto vehicle_min_acc = std::max(p.min_acc, lane_change_parameters_->min_longitudinal_acc); + const auto vehicle_max_acc = std::min(p.max_acc, lane_change_parameters_->max_longitudinal_acc); + + const auto ego_seg_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( + prev_module_path_.points, getEgoPose(), p.ego_nearest_dist_threshold, + p.ego_nearest_yaw_threshold); + const auto max_path_velocity = + prev_module_path_.points.at(ego_seg_idx).point.longitudinal_velocity_mps; + + // calculate minimum and maximum acceleration + const auto min_acc = + utils::lane_change::calcMinimumAcceleration(getEgoVelocity(), vehicle_min_acc, p); + const auto max_acc = utils::lane_change::calcMaximumAcceleration( + getEgoVelocity(), max_path_velocity, vehicle_max_acc, p); + + return {min_acc, max_acc}; +} + +double NormalLaneChange::calcMaximumLaneChangeLength( + const lanelet::ConstLanelet & current_terminal_lanelet, const double max_acc) const +{ + const auto shift_intervals = + getRouteHandler()->getLateralIntervalsToPreferredLane(current_terminal_lanelet); + return utils::lane_change::calcMaximumLaneChangeLength( + getEgoVelocity(), getCommonParam(), shift_intervals, max_acc); +} + std::vector NormalLaneChange::sampleLongitudinalAccValues( const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes) const { @@ -579,29 +610,11 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( return {}; } - const auto & common_parameters = planner_data_->parameters; const auto & route_handler = *getRouteHandler(); const auto current_pose = getEgoPose(); - const auto current_velocity = getEgoVelocity(); - const auto longitudinal_acc_sampling_num = lane_change_parameters_->longitudinal_acc_sampling_num; - const auto vehicle_min_acc = - std::max(common_parameters.min_acc, lane_change_parameters_->min_longitudinal_acc); - const auto vehicle_max_acc = - std::min(common_parameters.max_acc, lane_change_parameters_->max_longitudinal_acc); - const double nearest_dist_threshold = common_parameters.ego_nearest_dist_threshold; - const double nearest_yaw_threshold = common_parameters.ego_nearest_yaw_threshold; - const size_t current_seg_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( - prev_module_path_.points, current_pose, nearest_dist_threshold, nearest_yaw_threshold); - const double & max_path_velocity = - prev_module_path_.points.at(current_seg_idx).point.longitudinal_velocity_mps; - - // calculate minimum and maximum acceleration - const auto min_acc = utils::lane_change::calcMinimumAcceleration( - current_velocity, vehicle_min_acc, common_parameters); - const auto max_acc = utils::lane_change::calcMaximumAcceleration( - current_velocity, max_path_velocity, vehicle_max_acc, common_parameters); + const auto [min_acc, max_acc] = calcCurrentMinMaxAcceleration(); // if max acc is not positive, then we do the normal sampling if (max_acc <= 0.0) { @@ -610,15 +623,22 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( } // calculate maximum lane change length - const double max_lane_change_length = utils::lane_change::calcMaximumLaneChangeLength( - current_velocity, common_parameters, - route_handler.getLateralIntervalsToPreferredLane(current_lanes.back()), max_acc); + const double max_lane_change_length = calcMaximumLaneChangeLength(current_lanes.back(), max_acc); if (max_lane_change_length > utils::getDistanceToEndOfLane(current_pose, current_lanes)) { return utils::lane_change::getAccelerationValues( min_acc, max_acc, longitudinal_acc_sampling_num); } + // If the ego is in stuck, sampling all possible accelerations to find avoiding path. + if (isVehicleStuckByObstacle(current_lanes, max_lane_change_length)) { + auto clock = rclcpp::Clock(RCL_ROS_TIME); + RCLCPP_INFO_THROTTLE( + logger_, clock, 1000, "Vehicle is stuck. sample all longitudinal acceleration."); + return utils::lane_change::getAccelerationValues( + min_acc, max_acc, longitudinal_acc_sampling_num); + } + // if maximum lane change length is less than length to goal or the end of target lanes, only // sample max acc if (route_handler.isInGoalRouteSection(target_lanes.back())) { @@ -1185,8 +1205,9 @@ bool NormalLaneChange::getLaneChangePaths( if (getStopTime() < lane_change_parameters_->stop_time_threshold) { continue; } - RCLCPP_WARN_STREAM( - logger_, "Stop time is over threshold. Allow lane change in crosswalk."); + auto clock{rclcpp::Clock{RCL_ROS_TIME}}; + RCLCPP_WARN_THROTTLE( + logger_, clock, 1000, "Stop time is over threshold. Allow lane change in crosswalk."); } if ( @@ -1515,8 +1536,10 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( utils::path_safety_checker::convertToPredictedPath(ego_predicted_path, time_resolution); auto collision_check_objects = target_objects.target_lane; + const auto current_lanes = getCurrentLanes(); + const auto is_stuck = isVehicleStuckByObstacle(current_lanes); - if (lane_change_parameters_->check_objects_on_current_lanes) { + if (lane_change_parameters_->check_objects_on_current_lanes || is_stuck) { collision_check_objects.insert( collision_check_objects.end(), target_objects.current_lane.begin(), target_objects.current_lane.end()); @@ -1571,6 +1594,53 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( return path_safety_status; } +// Check if the ego vehicle is in stuck by a stationary obstacle. +bool NormalLaneChange::isVehicleStuckByObstacle( + const lanelet::ConstLanelets & current_lanes, const double obstacle_check_distance) const +{ + // Ego is still moving, not in stuck + if (std::abs(getEgoVelocity()) > lane_change_parameters_->stop_velocity_threshold) { + return false; + } + + // Ego is just stopped, not sure it is in stuck yet. + if (getStopTime() < lane_change_parameters_->stop_time_threshold) { + return false; + } + + // Check if any stationary object exist in obstacle_check_distance + using lanelet::utils::getArcCoordinates; + const auto base_distance = getArcCoordinates(current_lanes, getEgoPose()).length; + + for (const auto & object : debug_filtered_objects_.current_lane) { + const auto & p = object.initial_pose.pose; // TODO(Horibe): consider footprint point + + // Note: it needs chattering prevention. + if (std::abs(object.initial_twist.twist.linear.x) > 0.3) { // check if stationary + continue; + } + + const auto ego_to_obj_dist = getArcCoordinates(current_lanes, p).length - base_distance; + if (0 < ego_to_obj_dist && ego_to_obj_dist < obstacle_check_distance) { + return true; // Stationary object is in front of ego. + } + } + + // No stationary objects found in obstacle_check_distance + return false; +} + +bool NormalLaneChange::isVehicleStuckByObstacle(const lanelet::ConstLanelets & current_lanes) const +{ + if (current_lanes.empty()) { + return false; // can not check + } + + const auto [min_acc, max_acc] = calcCurrentMinMaxAcceleration(); + const auto max_lane_change_length = calcMaximumLaneChangeLength(current_lanes.back(), max_acc); + return isVehicleStuckByObstacle(current_lanes, max_lane_change_length); +} + void NormalLaneChange::setStopPose(const Pose & stop_pose) { lane_change_stop_pose_ = stop_pose; From 29279a0f736c7c6c02cccd2c782a5a1b792fb0b2 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:41:28 +0900 Subject: [PATCH 439/547] feat(planning_validator): output processing time (#5276) Signed-off-by: satoshi-ota --- .../planning_validator/planning_validator.hpp | 8 ++++++++ .../planning_validator/src/planning_validator.cpp | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/planning/planning_validator/include/planning_validator/planning_validator.hpp b/planning/planning_validator/include/planning_validator/planning_validator.hpp index 3b09ebe51ff6b..ef570b57773bb 100644 --- a/planning/planning_validator/include/planning_validator/planning_validator.hpp +++ b/planning/planning_validator/include/planning_validator/planning_validator.hpp @@ -18,6 +18,7 @@ #include "planning_validator/debug_marker.hpp" #include "planning_validator/msg/planning_validator_status.hpp" #include "tier4_autoware_utils/ros/logger_level_configure.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" #include "vehicle_info_util/vehicle_info_util.hpp" #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include @@ -38,6 +40,8 @@ using diagnostic_updater::DiagnosticStatusWrapper; using diagnostic_updater::Updater; using nav_msgs::msg::Odometry; using planning_validator::msg::PlanningValidatorStatus; +using tier4_autoware_utils::StopWatch; +using tier4_debug_msgs::msg::Float64Stamped; struct ValidationParams { @@ -81,6 +85,7 @@ class PlanningValidator : public rclcpp::Node void validate(const Trajectory & trajectory); + void publishProcessingTime(const double processing_time_ms); void publishTrajectory(); void publishDebugInfo(); void displayStatus(); @@ -91,6 +96,7 @@ class PlanningValidator : public rclcpp::Node rclcpp::Subscription::SharedPtr sub_traj_; rclcpp::Publisher::SharedPtr pub_traj_; rclcpp::Publisher::SharedPtr pub_status_; + rclcpp::Publisher::SharedPtr pub_processing_time_ms_; rclcpp::Publisher::SharedPtr pub_markers_; // system parameters @@ -120,6 +126,8 @@ class PlanningValidator : public rclcpp::Node std::shared_ptr debug_pose_publisher_; std::unique_ptr logger_configure_; + + StopWatch stop_watch_; }; } // namespace planning_validator diff --git a/planning/planning_validator/src/planning_validator.cpp b/planning/planning_validator/src/planning_validator.cpp index 13a63a7e7d4ed..875781d47b25d 100644 --- a/planning/planning_validator/src/planning_validator.cpp +++ b/planning/planning_validator/src/planning_validator.cpp @@ -41,6 +41,7 @@ PlanningValidator::PlanningValidator(const rclcpp::NodeOptions & options) pub_traj_ = create_publisher("~/output/trajectory", 1); pub_status_ = create_publisher("~/output/validation_status", 1); pub_markers_ = create_publisher("~/output/markers", 1); + pub_processing_time_ms_ = create_publisher("~/debug/processing_time_ms", 1); debug_pose_publisher_ = std::make_shared(this); @@ -167,6 +168,8 @@ bool PlanningValidator::isDataReady() void PlanningValidator::onTrajectory(const Trajectory::ConstSharedPtr msg) { + stop_watch_.tic(__func__); + current_trajectory_ = msg; if (!isDataReady()) return; @@ -180,6 +183,7 @@ void PlanningValidator::onTrajectory(const Trajectory::ConstSharedPtr msg) publishTrajectory(); // for debug + publishProcessingTime(stop_watch_.toc(__func__)); publishDebugInfo(); displayStatus(); } @@ -222,6 +226,14 @@ void PlanningValidator::publishTrajectory() return; } +void PlanningValidator::publishProcessingTime(const double processing_time_ms) +{ + Float64Stamped msg{}; + msg.stamp = this->now(); + msg.data = processing_time_ms; + pub_processing_time_ms_->publish(msg); +} + void PlanningValidator::publishDebugInfo() { validation_status_.stamp = get_clock()->now(); From 79e64d832c979e0cceae48e4eab2d1459db85113 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:52:29 +0900 Subject: [PATCH 440/547] feat(obstacle_stop_planner): output processing time (#5279) Signed-off-by: satoshi-ota --- .../include/obstacle_stop_planner/node.hpp | 8 ++++++++ planning/obstacle_stop_planner/src/node.cpp | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp b/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp index 93a9736bcdb36..368ad34b9c4e2 100644 --- a/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp +++ b/planning/obstacle_stop_planner/include/obstacle_stop_planner/node.hpp @@ -19,6 +19,7 @@ #include "obstacle_stop_planner/debug_marker.hpp" #include "obstacle_stop_planner/planner_data.hpp" #include "tier4_autoware_utils/ros/logger_level_configure.hpp" +#include "tier4_autoware_utils/system/stop_watch.hpp" #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -78,9 +80,11 @@ using autoware_auto_planning_msgs::msg::Trajectory; using autoware_auto_planning_msgs::msg::TrajectoryPoint; using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; +using tier4_autoware_utils::StopWatch; using tier4_debug_msgs::msg::BoolStamped; using tier4_debug_msgs::msg::Float32MultiArrayStamped; using tier4_debug_msgs::msg::Float32Stamped; +using tier4_debug_msgs::msg::Float64Stamped; using tier4_planning_msgs::msg::ExpandStopRange; using tier4_planning_msgs::msg::VelocityLimit; using tier4_planning_msgs::msg::VelocityLimitClearCommand; @@ -143,6 +147,8 @@ class ObstacleStopPlannerNode : public rclcpp::Node rclcpp::Publisher::SharedPtr pub_collision_pointcloud_debug_; + rclcpp::Publisher::SharedPtr pub_processing_time_ms_; + std::unique_ptr acc_controller_; std::shared_ptr debug_ptr_; boost::optional latest_slow_down_section_{boost::none}; @@ -166,6 +172,8 @@ class ObstacleStopPlannerNode : public rclcpp::Node StopParam stop_param_; SlowDownParam slow_down_param_; + StopWatch stop_watch_; + // mutex for vehicle_info_, stop_param_, current_acc_, obstacle_ros_pointcloud_ptr_ // NOTE: shared_ptr itself is thread safe so we do not have to care if *ptr is not used // (current_velocity_ptr_) diff --git a/planning/obstacle_stop_planner/src/node.cpp b/planning/obstacle_stop_planner/src/node.cpp index 4e4b5d2f91ef3..9a9712c5a2503 100644 --- a/planning/obstacle_stop_planner/src/node.cpp +++ b/planning/obstacle_stop_planner/src/node.cpp @@ -209,6 +209,8 @@ ObstacleStopPlannerNode::ObstacleStopPlannerNode(const rclcpp::NodeOptions & nod pub_collision_pointcloud_debug_ = this->create_publisher("~/debug/collision_pointcloud", 1); + pub_processing_time_ms_ = this->create_publisher("~/debug/processing_time_ms", 1); + // Subscribers if (!node_param_.use_predicted_objects) { // No need to point cloud while using predicted objects @@ -274,6 +276,8 @@ void ObstacleStopPlannerNode::onPointCloud(const PointCloud2::ConstSharedPtr inp void ObstacleStopPlannerNode::onTrigger(const Trajectory::ConstSharedPtr input_msg) { + stop_watch_.tic(__func__); + mutex_.lock(); // NOTE: these variables must not be referenced for multithreading const auto vehicle_info = vehicle_info_; @@ -376,6 +380,11 @@ void ObstacleStopPlannerNode::onTrigger(const Trajectory::ConstSharedPtr input_m trajectory.header = input_msg->header; pub_trajectory_->publish(trajectory); + + Float64Stamped processing_time_ms; + processing_time_ms.stamp = now(); + processing_time_ms.data = stop_watch_.toc(__func__); + pub_processing_time_ms_->publish(processing_time_ms); } void ObstacleStopPlannerNode::searchObstacle( From 10bb43cab859d90802ffbfe6114885726974fdb4 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:54:03 +0900 Subject: [PATCH 441/547] refactor(path_smoother, obstacle_avoidance_planner): output processing time topic (#5274) * refactor(path_smoother): output processing time topic Signed-off-by: satoshi-ota * refactor(obstacle_avoidance_planner): output processing time topic Signed-off-by: satoshi-ota * Update planning/path_smoother/src/elastic_band_smoother.cpp Co-authored-by: Takamasa Horibe * Update planning/path_smoother/src/elastic_band_smoother.cpp Co-authored-by: Takamasa Horibe * Update planning/obstacle_avoidance_planner/src/node.cpp Co-authored-by: Takamasa Horibe * Update planning/obstacle_avoidance_planner/src/node.cpp Co-authored-by: Takamasa Horibe --------- Signed-off-by: satoshi-ota Co-authored-by: Takamasa Horibe --- .../common_structs.hpp | 11 ++++++++++- .../include/obstacle_avoidance_planner/node.hpp | 3 ++- .../obstacle_avoidance_planner/type_alias.hpp | 2 ++ planning/obstacle_avoidance_planner/src/node.cpp | 16 ++++++++++++++-- .../include/path_smoother/common_structs.hpp | 11 ++++++++++- .../path_smoother/elastic_band_smoother.hpp | 3 ++- .../include/path_smoother/type_alias.hpp | 2 ++ .../path_smoother/src/elastic_band_smoother.cpp | 16 ++++++++++++++-- 8 files changed, 56 insertions(+), 8 deletions(-) diff --git a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp index d0fc995da0ef4..4f4f01bf1ec9f 100644 --- a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp +++ b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/common_structs.hpp @@ -45,7 +45,11 @@ struct PlannerData struct TimeKeeper { - void init() { accumulated_msg = "\n"; } + void init() + { + accumulated_msg = "\n"; + accumulated_time = 0.0; + } template TimeKeeper & operator<<(const T & msg) @@ -71,6 +75,7 @@ struct TimeKeeper { const double elapsed_time = stop_watch_.toc(func_name); *this << white_spaces << func_name << ":= " << elapsed_time << " [ms]"; + accumulated_time = elapsed_time; endLine(); } @@ -80,6 +85,10 @@ struct TimeKeeper std::string accumulated_msg = "\n"; std::stringstream latest_stream; + double getAccumulatedTime() const { return accumulated_time; } + + double accumulated_time{0.0}; + tier4_autoware_utils::StopWatch< std::chrono::milliseconds, std::chrono::microseconds, std::chrono::steady_clock> stop_watch_; diff --git a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp index d6f49afad4391..9207277d0bc98 100644 --- a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp +++ b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/node.hpp @@ -90,7 +90,8 @@ class ObstacleAvoidancePlanner : public rclcpp::Node // debug publisher rclcpp::Publisher::SharedPtr debug_extended_traj_pub_; rclcpp::Publisher::SharedPtr debug_markers_pub_; - rclcpp::Publisher::SharedPtr debug_calculation_time_pub_; + rclcpp::Publisher::SharedPtr debug_calculation_time_str_pub_; + rclcpp::Publisher::SharedPtr debug_calculation_time_float_pub_; // parameter callback rcl_interfaces::msg::SetParametersResult onParam( diff --git a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/type_alias.hpp b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/type_alias.hpp index 0f45e1223551f..b02d2232a4aa1 100644 --- a/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/type_alias.hpp +++ b/planning/obstacle_avoidance_planner/include/obstacle_avoidance_planner/type_alias.hpp @@ -24,6 +24,7 @@ #include "geometry_msgs/msg/twist.hpp" #include "nav_msgs/msg/odometry.hpp" #include "std_msgs/msg/header.hpp" +#include "tier4_debug_msgs/msg/float64_stamped.hpp" #include "tier4_debug_msgs/msg/string_stamped.hpp" #include "visualization_msgs/msg/marker_array.hpp" @@ -42,6 +43,7 @@ using nav_msgs::msg::Odometry; using visualization_msgs::msg::Marker; using visualization_msgs::msg::MarkerArray; // debug +using tier4_debug_msgs::msg::Float64Stamped; using tier4_debug_msgs::msg::StringStamped; } // namespace obstacle_avoidance_planner diff --git a/planning/obstacle_avoidance_planner/src/node.cpp b/planning/obstacle_avoidance_planner/src/node.cpp index 9cee933a1c9a7..da31c7f469555 100644 --- a/planning/obstacle_avoidance_planner/src/node.cpp +++ b/planning/obstacle_avoidance_planner/src/node.cpp @@ -45,6 +45,14 @@ StringStamped createStringStamped(const rclcpp::Time & now, const std::string & return msg; } +Float64Stamped createFloat64Stamped(const rclcpp::Time & now, const float & data) +{ + Float64Stamped msg; + msg.stamp = now; + msg.data = data; + return msg; +} + void setZeroVelocityAfterStopPoint(std::vector & traj_points) { const auto opt_zero_vel_idx = motion_utils::searchZeroVelocityIndex(traj_points); @@ -92,7 +100,9 @@ ObstacleAvoidancePlanner::ObstacleAvoidancePlanner(const rclcpp::NodeOptions & n // debug publisher debug_extended_traj_pub_ = create_publisher("~/debug/extended_traj", 1); debug_markers_pub_ = create_publisher("~/debug/marker", 1); - debug_calculation_time_pub_ = create_publisher("~/debug/calculation_time", 1); + debug_calculation_time_str_pub_ = create_publisher("~/debug/calculation_time", 1); + debug_calculation_time_float_pub_ = + create_publisher("~/debug/processing_time_ms", 1); { // parameters // parameter for option @@ -253,7 +263,9 @@ void ObstacleAvoidancePlanner::onPath(const Path::ConstSharedPtr path_ptr) // publish calculation_time // NOTE: This function must be called after measuring onPath calculation time const auto calculation_time_msg = createStringStamped(now(), time_keeper_ptr_->getLog()); - debug_calculation_time_pub_->publish(calculation_time_msg); + debug_calculation_time_str_pub_->publish(calculation_time_msg); + debug_calculation_time_float_pub_->publish( + createFloat64Stamped(now(), time_keeper_ptr_->getAccumulatedTime())); const auto output_traj_msg = trajectory_utils::createTrajectory(path_ptr->header, full_traj_points); diff --git a/planning/path_smoother/include/path_smoother/common_structs.hpp b/planning/path_smoother/include/path_smoother/common_structs.hpp index a14c42ed056af..d44c964cf634c 100644 --- a/planning/path_smoother/include/path_smoother/common_structs.hpp +++ b/planning/path_smoother/include/path_smoother/common_structs.hpp @@ -44,7 +44,11 @@ struct PlannerData struct TimeKeeper { - void init() { accumulated_msg = "\n"; } + void init() + { + accumulated_msg = "\n"; + accumulated_time = 0.0; + } template TimeKeeper & operator<<(const T & msg) @@ -66,6 +70,7 @@ struct TimeKeeper { const double elapsed_time = stop_watch_.toc(func_name); *this << white_spaces << func_name << ":= " << elapsed_time << " [ms]"; + accumulated_time = elapsed_time; endLine(); } @@ -74,6 +79,10 @@ struct TimeKeeper std::string accumulated_msg = "\n"; std::stringstream latest_stream; + double getAccumulatedTime() const { return accumulated_time; } + + double accumulated_time{0.0}; + tier4_autoware_utils::StopWatch< std::chrono::milliseconds, std::chrono::microseconds, std::chrono::steady_clock> stop_watch_; diff --git a/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp b/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp index a67983393681f..217a138084310 100644 --- a/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp +++ b/planning/path_smoother/include/path_smoother/elastic_band_smoother.hpp @@ -82,7 +82,8 @@ class ElasticBandSmoother : public rclcpp::Node // debug publisher rclcpp::Publisher::SharedPtr debug_extended_traj_pub_; - rclcpp::Publisher::SharedPtr debug_calculation_time_pub_; + rclcpp::Publisher::SharedPtr debug_calculation_time_str_pub_; + rclcpp::Publisher::SharedPtr debug_calculation_time_float_pub_; // parameter callback rcl_interfaces::msg::SetParametersResult onParam( diff --git a/planning/path_smoother/include/path_smoother/type_alias.hpp b/planning/path_smoother/include/path_smoother/type_alias.hpp index 75bf1cff20857..c8f6d6da98dcd 100644 --- a/planning/path_smoother/include/path_smoother/type_alias.hpp +++ b/planning/path_smoother/include/path_smoother/type_alias.hpp @@ -22,6 +22,7 @@ #include "geometry_msgs/msg/point.hpp" #include "nav_msgs/msg/odometry.hpp" #include "std_msgs/msg/header.hpp" +#include "tier4_debug_msgs/msg/float64_stamped.hpp" #include "tier4_debug_msgs/msg/string_stamped.hpp" namespace path_smoother @@ -36,6 +37,7 @@ using autoware_auto_planning_msgs::msg::TrajectoryPoint; // navigation using nav_msgs::msg::Odometry; // debug +using tier4_debug_msgs::msg::Float64Stamped; using tier4_debug_msgs::msg::StringStamped; } // namespace path_smoother diff --git a/planning/path_smoother/src/elastic_band_smoother.cpp b/planning/path_smoother/src/elastic_band_smoother.cpp index cd314b7b141bf..923b753e83ac6 100644 --- a/planning/path_smoother/src/elastic_band_smoother.cpp +++ b/planning/path_smoother/src/elastic_band_smoother.cpp @@ -43,6 +43,14 @@ StringStamped createStringStamped(const rclcpp::Time & now, const std::string & return msg; } +Float64Stamped createFloat64Stamped(const rclcpp::Time & now, const float & data) +{ + Float64Stamped msg; + msg.stamp = now; + msg.data = data; + return msg; +} + void setZeroVelocityAfterStopPoint(std::vector & traj_points) { const auto opt_zero_vel_idx = motion_utils::searchZeroVelocityIndex(traj_points); @@ -75,7 +83,9 @@ ElasticBandSmoother::ElasticBandSmoother(const rclcpp::NodeOptions & node_option // debug publisher debug_extended_traj_pub_ = create_publisher("~/debug/extended_traj", 1); - debug_calculation_time_pub_ = create_publisher("~/debug/calculation_time", 1); + debug_calculation_time_str_pub_ = create_publisher("~/debug/calculation_time", 1); + debug_calculation_time_float_pub_ = + create_publisher("~/debug/processing_time_ms", 1); { // parameters // parameters for ego nearest search @@ -205,7 +215,9 @@ void ElasticBandSmoother::onPath(const Path::ConstSharedPtr path_ptr) // publish calculation_time // NOTE: This function must be called after measuring onPath calculation time const auto calculation_time_msg = createStringStamped(now(), time_keeper_ptr_->getLog()); - debug_calculation_time_pub_->publish(calculation_time_msg); + debug_calculation_time_str_pub_->publish(calculation_time_msg); + debug_calculation_time_float_pub_->publish( + createFloat64Stamped(now(), time_keeper_ptr_->getAccumulatedTime())); const auto output_traj_msg = trajectory_utils::createTrajectory(path_ptr->header, full_traj_points); From 021d54057b4d9163248885e06ecd8a89a4f943cb Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:08:38 +0900 Subject: [PATCH 442/547] fix(avoidance): align execution request condition (#5266) Signed-off-by: satoshi-ota --- .../src/scene_module/avoidance/avoidance_module.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index a8dd0b794245e..632aad8873906 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -171,7 +171,10 @@ bool AvoidanceModule::isExecutionRequested() const return false; } - return !avoid_data_.target_objects.empty(); + return std::any_of( + avoid_data_.target_objects.begin(), avoid_data_.target_objects.end(), [](const auto & o) { + return o.is_avoidable || o.reason == AvoidanceDebugFactor::TOO_LARGE_JERK; + }); } bool AvoidanceModule::isExecutionReady() const From bc1d6c74afe7ac2af57d43b10320a1635113bb89 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:19:39 +0900 Subject: [PATCH 443/547] refactor(behavior_path_planner): align processing time topic name (#5281) Signed-off-by: satoshi-ota --- planning/behavior_path_planner/src/planner_manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index 24967608775f3..d0e55ffa574fd 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -35,7 +35,7 @@ PlannerManager::PlannerManager(rclcpp::Node & node, const bool verbose) verbose_{verbose} { processing_time_.emplace("total_time", 0.0); - debug_publisher_ptr_ = std::make_unique(&node, "behavior_planner_manager/debug"); + debug_publisher_ptr_ = std::make_unique(&node, "~/debug"); } BehaviorModuleOutput PlannerManager::run(const std::shared_ptr & data) @@ -894,7 +894,7 @@ void PlannerManager::print() const void PlannerManager::publishProcessingTime() const { for (const auto & t : processing_time_) { - std::string name = std::string("processing_time/") + t.first; + std::string name = t.first + std::string("/processing_time_ms"); debug_publisher_ptr_->publish(name, t.second); } } From a220a96169e8c6b7b214493451f10447070a59dc Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:01:36 +0900 Subject: [PATCH 444/547] fix(avoidance): expand detection area to prevent chattering (#5267) Signed-off-by: satoshi-ota --- .../utils/avoidance/utils.hpp | 2 +- .../avoidance/avoidance_module.cpp | 6 ++++- .../src/utils/avoidance/utils.cpp | 24 ++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index c0bd621cc86b4..d012fd5f612fb 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -164,7 +164,7 @@ std::vector getSafetyCheckTargetObjects( std::pair separateObjectsByPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, const AvoidancePlanningData & data, const std::shared_ptr & parameters, - DebugData & debug); + const bool is_running, DebugData & debug); DrivableLanes generateExpandDrivableLanes( const lanelet::ConstLanelet & lanelet, const std::shared_ptr & planner_data, diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 632aad8873906..8edc291fd560d 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -292,12 +292,16 @@ void AvoidanceModule::fillAvoidanceTargetObjects( using utils::avoidance::filterTargetObjects; using utils::avoidance::getTargetLanelets; + // Add margin in order to prevent avoidance request chattering only when the module is running. + const auto is_running = getCurrentStatus() == ModuleStatus::RUNNING || + getCurrentStatus() == ModuleStatus::WAITING_APPROVAL; + // Separate dynamic objects based on whether they are inside or outside of the expanded lanelets. const auto [object_within_target_lane, object_outside_target_lane] = utils::avoidance::separateObjectsByPath( utils::resamplePathWithSpline( helper_.getPreviousSplineShiftPath().path, parameters_->resample_interval_for_output), - planner_data_, data, parameters_, debug); + planner_data_, data, parameters_, is_running, debug); for (const auto & object : object_outside_target_lane.objects) { ObjectData other_object; diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 0e7eb87feed9b..ed2e42bf0c5ff 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -29,9 +29,12 @@ #include +#include #include #include #include +#include +#include #include #include @@ -1602,7 +1605,7 @@ std::vector getSafetyCheckTargetObjects( std::pair separateObjectsByPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, const AvoidancePlanningData & data, const std::shared_ptr & parameters, - DebugData & debug) + const bool is_running, DebugData & debug) { PredictedObjects target_objects; PredictedObjects other_objects; @@ -1639,6 +1642,25 @@ std::pair separateObjectsByPath( } } + // expand detection area width only when the module is running. + if (is_running) { + constexpr int PER_CIRCLE = 36; + constexpr double MARGIN = 1.0; // [m] + boost::geometry::strategy::buffer::distance_symmetric distance_strategy(MARGIN); + boost::geometry::strategy::buffer::join_round join_strategy(PER_CIRCLE); + boost::geometry::strategy::buffer::end_round end_strategy(PER_CIRCLE); + boost::geometry::strategy::buffer::point_circle circle_strategy(PER_CIRCLE); + boost::geometry::strategy::buffer::side_straight side_strategy; + boost::geometry::model::multi_polygon result; + // Create the buffer of a multi polygon + boost::geometry::buffer( + attention_area, result, distance_strategy, side_strategy, join_strategy, end_strategy, + circle_strategy); + if (!result.empty()) { + attention_area = result.front(); + } + } + debug.detection_area = toMsg(attention_area, data.reference_pose.position.z); const auto objects = planner_data->dynamic_object->objects; From bc60bbfb659be580f6e8aa894d2f16b9afbb4c0d Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:03:20 +0900 Subject: [PATCH 445/547] refactor(obstacle_cruise_planner): align processing time topic name (#5278) Signed-off-by: satoshi-ota --- planning/obstacle_cruise_planner/src/node.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/obstacle_cruise_planner/src/node.cpp b/planning/obstacle_cruise_planner/src/node.cpp index d50054da46237..c9fe9f2210423 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -374,7 +374,7 @@ ObstacleCruisePlannerNode::ObstacleCruisePlannerNode(const rclcpp::NodeOptions & "~/output/clear_velocity_limit", rclcpp::QoS{1}.transient_local()); // debug publisher - debug_calculation_time_pub_ = create_publisher("~/debug/calculation_time", 1); + debug_calculation_time_pub_ = create_publisher("~/debug/processing_time_ms", 1); debug_cruise_wall_marker_pub_ = create_publisher("~/debug/cruise/virtual_wall", 1); debug_stop_wall_marker_pub_ = create_publisher("~/virtual_wall", 1); debug_slow_down_wall_marker_pub_ = From f872838baeeb43e4d8bf10140676ce75151d09a1 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 12 Oct 2023 14:31:22 +0900 Subject: [PATCH 446/547] chore(intersection): parameterize stuck vehicle detection turn_direction (#5277) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 4 ++++ .../behavior_velocity_intersection_module/src/manager.cpp | 6 ++++++ .../src/scene_intersection.cpp | 8 ++++++-- .../src/scene_intersection.hpp | 6 ++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 30fefcaeee035..bb80c140b95fb 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -13,6 +13,10 @@ path_interpolation_ds: 0.1 # [m] consider_wrong_direction_vehicle: false stuck_vehicle: + turn_direction: + left: true + right: true + straight: true use_stuck_stopline: true # stopline generated before the first conflicting area stuck_vehicle_detect_dist: 5.0 # this should be the length between cars when they are stopped. The actual stuck vehicle detection length will be this value + vehicle_length. stuck_vehicle_vel_thr: 0.833 # 0.833m/s = 3.0km/h diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 4137090a5b6ae..3cca1f42115d1 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -63,6 +63,12 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) ip.common.consider_wrong_direction_vehicle = getOrDeclareParameter(node, ns + ".common.consider_wrong_direction_vehicle"); + ip.stuck_vehicle.turn_direction.left = + getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.left"); + ip.stuck_vehicle.turn_direction.right = + getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.right"); + ip.stuck_vehicle.turn_direction.straight = + getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.straight"); ip.stuck_vehicle.use_stuck_stopline = getOrDeclareParameter(node, ns + ".stuck_vehicle.use_stuck_stopline"); ip.stuck_vehicle.stuck_vehicle_detect_dist = diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 26bc8210b3bad..ee541a061ce0e 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -1213,8 +1213,12 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( bool IntersectionModule::checkStuckVehicle( const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets) { - // NOTE: No need to stop for stuck vehicle when the ego will turn left. - if (turn_direction_ == std::string("left")) { + const bool stuck_detection_direction = [&]() { + return (turn_direction_ == "left" && planner_param_.stuck_vehicle.turn_direction.left) || + (turn_direction_ == "right" && planner_param_.stuck_vehicle.turn_direction.right) || + (turn_direction_ == "straight" && planner_param_.stuck_vehicle.turn_direction.straight); + }(); + if (!stuck_detection_direction) { return false; } diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index de7d97a50c133..6ae734bd6e05b 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -61,6 +61,12 @@ class IntersectionModule : public SceneModuleInterface } common; struct StuckVehicle { + struct TurnDirection + { + bool left; + bool right; + bool straight; + } turn_direction; bool use_stuck_stopline; //! stopline generate before the intersection lanelet when is_stuck. double stuck_vehicle_detect_dist; //! distance from end point to finish stuck vehicle check double stuck_vehicle_vel_thr; //! Threshold of the speed to be recognized as stopped From 02eb8434ed634b93c334bab26825fd97cbc94ab3 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:22:10 +0900 Subject: [PATCH 447/547] fix(lane_change): fixed debug visualization bug (#5284) Signed-off-by: Muhammad Zulfaqar Azmi --- .../src/scene_module/lane_change/normal.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 4075202367a65..69c39b41efb06 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1560,12 +1560,14 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( auto current_debug_data = marker_utils::createObjectDebug(obj); const auto obj_predicted_paths = utils::path_safety_checker::getPredictedPathFromObj( obj, lane_change_parameters_->use_all_predicted_path); + auto is_safe = true; for (const auto & obj_path : obj_predicted_paths) { const auto collided_polygons = utils::path_safety_checker::getCollidedPolygons( path, ego_predicted_path, obj, obj_path, common_parameters, rss_params, 1.0, current_debug_data.second); if (collided_polygons.empty()) { + marker_utils::updateCollisionCheckDebugMap(debug_data, current_debug_data, is_safe); continue; } @@ -1575,20 +1577,20 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( utils::lane_change::isCollidedPolygonsInLanelet(collided_polygons, expanded_target_lanes); if (!collision_in_current_lanes && !collision_in_target_lanes) { + marker_utils::updateCollisionCheckDebugMap(debug_data, current_debug_data, is_safe); continue; } + is_safe = false; path_safety_status.is_safe = false; - marker_utils::updateCollisionCheckDebugMap( - debug_data, current_debug_data, path_safety_status.is_safe); + marker_utils::updateCollisionCheckDebugMap(debug_data, current_debug_data, is_safe); const auto & obj_pose = obj.initial_pose.pose; const auto obj_polygon = tier4_autoware_utils::toPolygon2d(obj_pose, obj.shape); path_safety_status.is_object_coming_from_rear |= !utils::path_safety_checker::isTargetObjectFront( path, current_pose, common_parameters.vehicle_info, obj_polygon); } - marker_utils::updateCollisionCheckDebugMap( - debug_data, current_debug_data, path_safety_status.is_safe); + marker_utils::updateCollisionCheckDebugMap(debug_data, current_debug_data, is_safe); } return path_safety_status; From 75debf01597726df68399519777fd0f78f15dc84 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 12 Oct 2023 18:02:48 +0900 Subject: [PATCH 448/547] feat(avoidance): check if the avoidance path is in drivable area (#5032) * feat(avoidance): check if the avoidance path is in drivable area Signed-off-by: satoshi-ota * refactor(avoidance): remove redundant function Signed-off-by: satoshi-ota * fix(utils): add guard Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../config/avoidance/avoidance.param.yaml | 1 + .../avoidance/avoidance_module.hpp | 10 +- .../utils/avoidance/avoidance_module_data.hpp | 11 ++ .../avoidance/avoidance_module.cpp | 142 ++++++++++++------ .../src/scene_module/avoidance/manager.cpp | 2 + .../behavior_path_planner/src/utils/utils.cpp | 9 +- 6 files changed, 120 insertions(+), 55 deletions(-) diff --git a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml index df2dd806fe2c7..2b38536341d7c 100644 --- a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml +++ b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml @@ -169,6 +169,7 @@ hard_road_shoulder_margin: 0.3 # [m] max_right_shift_length: 5.0 max_left_shift_length: 5.0 + max_deviation_from_lane: 0.5 # [m] # avoidance distance parameters longitudinal: prepare_time: 2.0 # [s] diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp index d362a5e758a3e..cbf40e59535be 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/avoidance/avoidance_module.hpp @@ -334,11 +334,10 @@ class AvoidanceModule : public SceneModuleInterface /* * @brief generate candidate shift lines. * @param one-shot shift lines. - * @param path shifter. * @param debug data. */ AvoidLineArray generateCandidateShiftLine( - const AvoidLineArray & shift_lines, const PathShifter & path_shifter, DebugData & debug) const; + const AvoidLineArray & shift_lines, DebugData & debug) const; /** * @brief clean up raw shift lines. @@ -478,13 +477,6 @@ class AvoidanceModule : public SceneModuleInterface */ TurnSignalInfo calcTurnSignalInfo(const ShiftedPath & path) const; - // TODO(murooka) judge when and which way to extend drivable area. current implementation is keep - // extending during avoidance module - // TODO(murooka) freespace during turning in intersection where there is no neighbor lanes - // NOTE: Assume that there is no situation where there is an object in the middle lane of more - // than two lanes since which way to avoid is not obvious - void generateExpandDrivableLanes(BehaviorModuleOutput & output) const; - /** * @brief fill debug markers. */ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 6173ecb15a5c2..5ba0085ded5b7 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -232,6 +232,9 @@ struct AvoidanceParameters // Even if the obstacle is very large, it will not avoid more than this length for left direction double max_left_shift_length{0.0}; + // Validate vehicle departure from driving lane. + double max_deviation_from_lane{0.0}; + // To prevent large acceleration while avoidance. double max_lateral_acceleration{0.0}; @@ -490,8 +493,16 @@ struct AvoidancePlanningData // safe shift point AvoidLineArray safe_shift_line{}; + std::vector drivable_lanes{}; + + lanelet::BasicLineString3d right_bound{}; + + lanelet::BasicLineString3d left_bound{}; + bool safe{false}; + bool valid{false}; + bool comfortable{false}; bool avoid_required{false}; diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index 8edc291fd560d..c94406e466219 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -143,6 +143,14 @@ AvoidLineArray toArray(const AvoidOutlines & outlines) } return ret; } + +lanelet::BasicLineString3d toLineString3d(const std::vector & bound) +{ + lanelet::BasicLineString3d ret{}; + std::for_each( + bound.begin(), bound.end(), [&](const auto & p) { ret.emplace_back(p.x, p.y, p.z); }); + return ret; +} } // namespace AvoidanceModule::AvoidanceModule( @@ -248,6 +256,23 @@ void AvoidanceModule::fillFundamentalData(AvoidancePlanningData & data, DebugDat data.current_lanelets = utils::avoidance::getCurrentLanesFromPath( *getPreviousModuleOutput().reference_path, planner_data_); + // expand drivable lanes + std::for_each( + data.current_lanelets.begin(), data.current_lanelets.end(), [&](const auto & lanelet) { + data.drivable_lanes.push_back( + utils::avoidance::generateExpandDrivableLanes(lanelet, planner_data_, parameters_)); + }); + + // calc drivable bound + const auto shorten_lanes = + utils::cutOverlappedLanes(*getPreviousModuleOutput().path, data.drivable_lanes); + data.left_bound = toLineString3d(utils::calcBound( + planner_data_->route_handler, shorten_lanes, parameters_->use_hatched_road_markings, + parameters_->use_intersection_areas, true)); + data.right_bound = toLineString3d(utils::calcBound( + planner_data_->route_handler, shorten_lanes, parameters_->use_hatched_road_markings, + parameters_->use_intersection_areas, false)); + // reference path if (isDrivingSameLane(helper_.getPreviousDrivingLanes(), data.current_lanelets)) { data.reference_path_rough = extendBackwardLength(*getPreviousModuleOutput().path); @@ -447,13 +472,21 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de * STEP3: Create candidate shift lines. * Merge rough shift lines, and extract new shift lines. */ - data.new_shift_line = generateCandidateShiftLine(data.raw_shift_line, path_shifter, debug); + const auto processed_shift_lines = generateCandidateShiftLine(data.raw_shift_line, debug); + + /** + * Step4: Validate new shift lines. + * Output new shift lines only when the avoidance path which is generated from them doesn't have + * huge offset from ego. + */ + data.valid = isValidShiftLine(processed_shift_lines, path_shifter); + data.new_shift_line = data.valid ? processed_shift_lines : AvoidLineArray{}; const auto found_new_sl = data.new_shift_line.size() > 0; const auto registered = path_shifter.getShiftLines().size() > 0; data.found_avoidance_path = found_new_sl || registered; /** - * STEP4: Set new shift lines. + * STEP5: Set new shift lines. * If there are new shift points, these shift points are registered in path_shifter in order to * generate candidate avoidance path. */ @@ -462,7 +495,7 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de } /** - * STEP5: Generate avoidance path. + * STEP6: Generate avoidance path. */ ShiftedPath spline_shift_path = utils::avoidance::toShiftedPath(data.reference_path); const auto success_spline_path_generation = @@ -472,7 +505,7 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de : utils::avoidance::toShiftedPath(data.reference_path); /** - * STEP6: Check avoidance path safety. + * STEP7: Check avoidance path safety. * For each target objects and the objects in adjacent lanes, * check that there is a certain amount of margin in the lateral and longitudinal direction. */ @@ -483,6 +516,17 @@ void AvoidanceModule::fillShiftLine(AvoidancePlanningData & data, DebugData & de void AvoidanceModule::fillEgoStatus( AvoidancePlanningData & data, [[maybe_unused]] DebugData & debug) const { + /** + * TODO(someone): prevent meaningless stop point insertion in other way. + * If the candidate shift line is invalid, manage all objects as unavoidable. + */ + if (!data.valid) { + std::for_each(data.target_objects.begin(), data.target_objects.end(), [](auto & o) { + o.is_avoidable = false; + o.reason = "InvalidShiftLine"; + }); + } + /** * Find the nearest object that should be avoid. When the ego follows reference path, * if the both of following two conditions are satisfied, the module surely avoid the object. @@ -769,7 +813,7 @@ AvoidLineArray AvoidanceModule::applyPreProcess( } AvoidLineArray AvoidanceModule::generateCandidateShiftLine( - const AvoidLineArray & shift_lines, const PathShifter & path_shifter, DebugData & debug) const + const AvoidLineArray & shift_lines, DebugData & debug) const { AvoidLineArray processed_shift_lines = shift_lines; @@ -789,15 +833,7 @@ AvoidLineArray AvoidanceModule::generateCandidateShiftLine( * Step3: Extract new shift lines. * Compare processed shift lines and registered shift lines in order to find new shift lines. */ - processed_shift_lines = findNewShiftLine(processed_shift_lines, debug); - - /** - * Step4: Validate new shift lines. - * Output new shift lines only when the avoidance path which is generated from them doesn't have - * huge offset from ego. - */ - return isValidShiftLine(processed_shift_lines, path_shifter) ? processed_shift_lines - : AvoidLineArray{}; + return findNewShiftLine(processed_shift_lines, debug); } void AvoidanceModule::registerRawShiftLines(const AvoidLineArray & future) @@ -1921,34 +1957,6 @@ bool AvoidanceModule::isSafePath( return safe_ || safe_count_ > parameters_->hysteresis_factor_safe_count; } -void AvoidanceModule::generateExpandDrivableLanes(BehaviorModuleOutput & output) const -{ - std::vector drivable_lanes; - for (const auto & lanelet : avoid_data_.current_lanelets) { - drivable_lanes.push_back( - utils::avoidance::generateExpandDrivableLanes(lanelet, planner_data_, parameters_)); - } - - { // for new architecture - DrivableAreaInfo current_drivable_area_info; - // generate drivable lanes - current_drivable_area_info.drivable_lanes = drivable_lanes; - // generate obstacle polygons - current_drivable_area_info.obstacles = - utils::avoidance::generateObstaclePolygonsForDrivableArea( - avoid_data_.target_objects, parameters_, planner_data_->parameters.vehicle_width / 2.0); - // expand hatched road markings - current_drivable_area_info.enable_expanding_hatched_road_markings = - parameters_->use_hatched_road_markings; - // expand intersection areas - current_drivable_area_info.enable_expanding_intersection_areas = - parameters_->use_intersection_areas; - - output.drivable_area_info = utils::combineDrivableAreaInfo( - current_drivable_area_info, getPreviousModuleOutput().drivable_area_info); - } -} - PathWithLaneId AvoidanceModule::extendBackwardLength(const PathWithLaneId & original_path) const { const auto previous_path = helper_.getPreviousReferencePath(); @@ -2082,8 +2090,26 @@ BehaviorModuleOutput AvoidanceModule::plan() utils::clipPathLength(*output.path, ego_idx, planner_data_->parameters); // Drivable area generation. - generateExpandDrivableLanes(output); - setDrivableLanes(output.drivable_area_info.drivable_lanes); + { + DrivableAreaInfo current_drivable_area_info; + // generate drivable lanes + current_drivable_area_info.drivable_lanes = avoid_data_.drivable_lanes; + // generate obstacle polygons + current_drivable_area_info.obstacles = + utils::avoidance::generateObstaclePolygonsForDrivableArea( + avoid_data_.target_objects, parameters_, planner_data_->parameters.vehicle_width / 2.0); + // expand hatched road markings + current_drivable_area_info.enable_expanding_hatched_road_markings = + parameters_->use_hatched_road_markings; + // expand intersection areas + current_drivable_area_info.enable_expanding_intersection_areas = + parameters_->use_intersection_areas; + + output.drivable_area_info = utils::combineDrivableAreaInfo( + current_drivable_area_info, getPreviousModuleOutput().drivable_area_info); + + setDrivableLanes(output.drivable_area_info.drivable_lanes); + } return output; } @@ -2338,7 +2364,7 @@ bool AvoidanceModule::isValidShiftLine( const AvoidLineArray & shift_lines, const PathShifter & shifter) const { if (shift_lines.empty()) { - return false; + return true; } auto shifter_for_validate = shifter; @@ -2364,6 +2390,32 @@ bool AvoidanceModule::isValidShiftLine( } } + // check if the vehicle is in road. (yaw angle is not considered) + { + const auto minimum_distance = 0.5 * planner_data_->parameters.vehicle_width + + parameters_->hard_road_shoulder_margin - + parameters_->max_deviation_from_lane; + + const size_t start_idx = shift_lines.front().start_idx; + const size_t end_idx = shift_lines.back().end_idx; + + for (size_t i = start_idx; i <= end_idx; ++i) { + const auto p = getPoint(shifter_for_validate.getReferencePath().points.at(i)); + lanelet::BasicPoint2d basic_point{p.x, p.y}; + + const auto shift_length = proposed_shift_path.shift_length.at(i); + const auto bound = shift_length > 0.0 ? avoid_data_.left_bound : avoid_data_.right_bound; + const auto THRESHOLD = minimum_distance + std::abs(shift_length); + + if (boost::geometry::distance(basic_point, lanelet::utils::to2D(bound)) < THRESHOLD) { + RCLCPP_DEBUG_THROTTLE( + getLogger(), *clock_, 1000, + "following latest new shift line may cause deviation from drivable area."); + return false; + } + } + } + return true; // valid shift line. } diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp index 8c65323e5a123..500ebe873a2d8 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp @@ -201,6 +201,8 @@ AvoidanceModuleManager::AvoidanceModuleManager( getOrDeclareParameter(*node, ns + "lateral_avoid_check_threshold"); p.max_right_shift_length = getOrDeclareParameter(*node, ns + "max_right_shift_length"); p.max_left_shift_length = getOrDeclareParameter(*node, ns + "max_left_shift_length"); + p.max_deviation_from_lane = + getOrDeclareParameter(*node, ns + "max_deviation_from_lane"); } // avoidance maneuver (longitudinal) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 03063b5a9e2fe..bf6fffda41863 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1731,7 +1731,7 @@ std::vector getBoundWithIntersectionAreas( const auto shared_point_itr_last = std::find_if(expanded_bound.rbegin(), expanded_bound.rend(), [&](const auto & p) { return std::any_of( - intersection_bound.begin(), intersection_bound.end(), + intersection_bound.rbegin(), intersection_bound.rend(), [&](const auto & point) { return point.id() == p.id(); }); }); @@ -1757,6 +1757,13 @@ std::vector getBoundWithIntersectionAreas( continue; } + // TODO(Satoshi OTA): remove this guard. + if ( + std::distance(intersection_bound.begin(), trim_point_itr_last) < + std::distance(intersection_bound.begin(), trim_point_itr_init)) { + continue; + } + std::vector tmp_bound{}; tmp_bound.insert(tmp_bound.end(), expanded_bound.begin(), shared_point_itr_init); From 3cf0481b9b796d1a2dd325601467f5f98cd509ab Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Thu, 12 Oct 2023 18:35:24 +0900 Subject: [PATCH 449/547] fix(intersection): fix unsigned subtraction (#5280) Signed-off-by: Mamoru Sobue --- planning/behavior_velocity_intersection_module/src/util.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 7862a533f34eb..ffc0b61880188 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -475,6 +475,9 @@ std::optional getFirstPointInsidePolygon( if (is_in_lanelet) { return std::make_optional(i); } + if (i == 0) { + break; + } } } return std::nullopt; @@ -517,6 +520,9 @@ getFirstPointInsidePolygons( if (is_in_lanelet) { break; } + if (i == 0) { + break; + } } } return std::nullopt; From d5709ab4ffc99478042913a411ee57a4b50e0aa7 Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Thu, 12 Oct 2023 19:09:30 +0900 Subject: [PATCH 450/547] fix(lane_change): update target lane if valid path not found (#5287) Signed-off-by: Fumiya Watanabe --- .../src/scene_module/lane_change/normal.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 69c39b41efb06..913ddcacf08bc 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -89,6 +89,7 @@ std::pair NormalLaneChange::getSafePath(LaneChangePath & safe_path) if (valid_paths.empty()) { safe_path.info.current_lanes = current_lanes; + safe_path.info.target_lanes = target_lanes; return {false, false}; } From 4ba8923dd064b21749814f14bd30a5a97bd81e2e Mon Sep 17 00:00:00 2001 From: Yuxuan Liu <619684051@qq.com> Date: Thu, 12 Oct 2023 20:30:18 +0800 Subject: [PATCH 451/547] feat(tier4_system_rviz_plugin): improve visualization results and logics (#5222) * Add Init Localization and Init Planning Check; Add error list check Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * int casting problem updated Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * improve if statement writing styles Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * FIX ci Signed-off-by: Owen-Liuyuxuan * subscribe to ADAPI for more stable status checking Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * try using level attribute only Signed-off-by: Owen-Liuyuxuan * fix hpp so that it just look like the original hpp Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * set default value to NO_FAULT Signed-off-by: Owen-Liuyuxuan --------- Signed-off-by: Owen-Liuyuxuan Co-authored-by: Owen-Liuyuxuan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- common/tier4_system_rviz_plugin/README.md | 10 +++++ .../src/mrm_summary_overlay_display.cpp | 40 ++++++++++++++----- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/common/tier4_system_rviz_plugin/README.md b/common/tier4_system_rviz_plugin/README.md index 098844c8b4091..61260ecfda723 100644 --- a/common/tier4_system_rviz_plugin/README.md +++ b/common/tier4_system_rviz_plugin/README.md @@ -1 +1,11 @@ # tier4_system_rviz_plugin + +## Purpose + +This plugin display the Hazard information from Autoware; and output notices when emergencies are from initial localization and route setting. + +## Input + +| Name | Type | Description | +| --------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------ | +| `/system/emergency/hazard_status` | `autoware_auto_system_msgs::msg::HazardStatusStamped` | The topic represents the emergency information from Autoware | diff --git a/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp index b80f06f645632..61c2bd378dab1 100644 --- a/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp +++ b/common/tier4_system_rviz_plugin/src/mrm_summary_overlay_display.cpp @@ -50,6 +50,7 @@ #include #include +#include #include #include @@ -126,6 +127,7 @@ MrmSummaryOverlayDisplay::~MrmSummaryOverlayDisplay() void MrmSummaryOverlayDisplay::onInitialize() { RTDClass::onInitialize(); + static int count = 0; rviz_common::UniformStringStream ss; ss << "MrmSummaryOverlayDisplayObject" << count++; @@ -160,9 +162,11 @@ void MrmSummaryOverlayDisplay::update(float wall_dt, float ros_dt) // MRM summary std::vector mrm_comfortable_stop_summary_list = {}; std::vector mrm_emergency_stop_summary_list = {}; + int hazard_level = autoware_auto_system_msgs::msg::HazardStatus::NO_FAULT; { std::lock_guard message_lock(mutex_); if (last_msg_ptr_) { + hazard_level = last_msg_ptr_->status.level; for (const auto & diag_status : last_msg_ptr_->status.diag_latent_fault) { const std::optional msg = generateMrmMessage(diag_status); if (msg != std::nullopt) { @@ -201,16 +205,32 @@ void MrmSummaryOverlayDisplay::update(float wall_dt, float ros_dt) painter.setFont(font); std::ostringstream output_text; - output_text << std::fixed - << "Comfortable Stop MRM Summary: " << int(mrm_comfortable_stop_summary_list.size()) - << std::endl; - for (const auto & mrm_element : mrm_comfortable_stop_summary_list) { - output_text << mrm_element << std::endl; - } - output_text << "Emergency Stop MRM Summary: " << int(mrm_emergency_stop_summary_list.size()) - << std::endl; - for (const auto & mrm_element : mrm_emergency_stop_summary_list) { - output_text << mrm_element << std::endl; + + // Display the MRM Summary only when there is a fault + if (hazard_level != autoware_auto_system_msgs::msg::HazardStatus::NO_FAULT) { + // Broadcasting the Basic Error Infos + int number_of_comfortable_stop_messages = + static_cast(mrm_comfortable_stop_summary_list.size()); + if (number_of_comfortable_stop_messages > 0) // Only Display when there are errors + { + output_text << std::fixed + << "Comfortable Stop MRM Summary: " << number_of_comfortable_stop_messages + << std::endl; + for (const auto & mrm_element : mrm_comfortable_stop_summary_list) { + output_text << mrm_element << std::endl; + } + } + + int number_of_emergency_stop_messages = + static_cast(mrm_emergency_stop_summary_list.size()); + if (number_of_emergency_stop_messages > 0) // Only Display when there are some errors + { + output_text << "Emergency Stop MRM Summary: " << int(mrm_emergency_stop_summary_list.size()) + << std::endl; + for (const auto & mrm_element : mrm_emergency_stop_summary_list) { + output_text << mrm_element << std::endl; + } + } } // same as above, but align on right side From 744d48d0e3aaf9693062b44eb4c1edabc9659f6b Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Thu, 12 Oct 2023 23:05:31 +0900 Subject: [PATCH 452/547] refactor(behavior_path_planner): remove planner data unused variable (#5194) refactor(behavior_path_planner): remove planner data used variable Signed-off-by: Zulfaqar Azmi --- .../include/behavior_path_planner/data_manager.hpp | 2 -- .../behavior_path_planner/test/test_drivable_area_expansion.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp index 0a7b59d52ff5a..7f1648c463097 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp @@ -141,11 +141,9 @@ struct PlannerData OccupancyGrid::ConstSharedPtr costmap{}; LateralOffset::ConstSharedPtr lateral_offset{}; OperationModeState::ConstSharedPtr operation_mode{}; - PathWithLaneId::SharedPtr reference_path{std::make_shared()}; PathWithLaneId::SharedPtr prev_output_path{std::make_shared()}; std::optional prev_modified_goal{}; std::optional prev_route_id{}; - lanelet::ConstLanelets current_lanes{}; std::shared_ptr route_handler{std::make_shared()}; BehaviorPathPlannerParameters parameters{}; drivable_area_expansion::DrivableAreaExpansionParameters drivable_area_expansion_parameters{}; diff --git a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp index e0a16089b8d84..163221951d726 100644 --- a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp @@ -270,7 +270,6 @@ TEST(DrivableAreaExpansionProjection, expandDrivableArea) } behavior_path_planner::PlannerData planner_data; planner_data.drivable_area_expansion_parameters = params; - planner_data.reference_path = std::make_shared(path); planner_data.dynamic_object = std::make_shared(dynamic_objects); planner_data.route_handler = std::make_shared(route_handler); From d8f285bf81ce33f55b604745cbdf91f8cc005af6 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Fri, 13 Oct 2023 04:52:39 +0900 Subject: [PATCH 453/547] fix(goal_planner): disable freespace pull over after arriving modified goal (#5290) fix(goal_planner): disable freespace pull over after arriving modified goal Signed-off-by: kosuke55 --- .../src/scene_module/goal_planner/goal_planner_module.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 9c2544cf60cec..c6e68f8e81af7 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -218,6 +218,10 @@ void GoalPlannerModule::onFreespaceParkingTimer() return; } + if (isOnModifiedGoal()) { + return; + } + const bool is_new_costmap = (clock_->now() - planner_data_->costmap->header.stamp).seconds() < 1.0; constexpr double path_update_duration = 1.0; @@ -1210,6 +1214,10 @@ bool GoalPlannerModule::isStopped() bool GoalPlannerModule::isStuck() { + if (isOnModifiedGoal()) { + return false; + } + constexpr double stuck_time = 5.0; if (!isStopped(odometry_buffer_stuck_, stuck_time)) { return false; From e56698836e9c6491dd904c5178946639d37c89aa Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Fri, 13 Oct 2023 10:22:06 +0900 Subject: [PATCH 454/547] feat(tracking_object_merger): merge tracked object. especially for far radar object and conventional lidar object (#4340) * init package: migrate from object merger Signed-off-by: yoshiri * add node outline and check build passed Signed-off-by: yoshiri * add util functions to interpolate tracked objs Signed-off-by: yoshiri * add object merger function using interpolation Signed-off-by: yoshiri * create object merger utils Signed-off-by: yoshiri * add kinematics velocity merger Signed-off-by: yoshiri * add association and merger Signed-off-by: yoshiri * rename perception_utils to object_recognition_utils Signed-off-by: yoshiri * add comment and complete main subscriber Signed-off-by: yoshiri * add parameter control and rename some executable files Signed-off-by: yoshiri * refactoring: fix apparent bugs Signed-off-by: yoshiri * add debug tools to check association Signed-off-by: yoshiri * temporary fix: radar tracker node name to anon Signed-off-by: yoshiri * debug: data association tuning Signed-off-by: yoshiri * rename copyright and add merger util function Signed-off-by: yoshiri * add tracker_state and update association Signed-off-by: yoshiri * update decorative tracker by using tracker_state Signed-off-by: yoshiri * update system around measurement state function Signed-off-by: yoshiri * fix radar object not merged problem Signed-off-by: yoshiri * add existence probability control Signed-off-by: yoshiri * create const function Signed-off-by: yoshiri * change association settings depend on measurement and tracker state Signed-off-by: yoshiri * fix association matrix Signed-off-by: yoshiri * put hardcoded node parameter to yaml file Signed-off-by: yoshiri * move tracker state parameter to yaml file Signed-off-by: yoshiri * remove prediction failed objects Signed-off-by: yoshiri * fix bug when none closest time sub objects found Signed-off-by: yoshiri * add velocity diff gate in association Signed-off-by: yoshiri * fix object interpolation problem Signed-off-by: yoshiri * use fixed object interpolation Signed-off-by: yoshiri * add README Signed-off-by: yoshiri * add interpolated sub object publisher for debug Signed-off-by: yoshiri * add debug message and fix interpolation Signed-off-by: yoshiri * update README Signed-off-by: yoshiri * fix unintended changes in radar tracking launch Signed-off-by: yoshiri * add CmakeLists version Signed-off-by: yoshiri * remove unnecessary debug description and type cast causes build warning Signed-off-by: yoshiri * fix spell-check error Signed-off-by: yoshiri * replace autoware_utils.hpp Signed-off-by: yoshiri --------- Signed-off-by: yoshiri --- .../tracking_object_merger/CMakeLists.txt | 50 +++ perception/tracking_object_merger/README.md | 114 +++++ .../config/data_association_matrix.param.yaml | 168 ++++++++ .../decorative_tracker_merger.param.yaml | 26 ++ ...ecorative_tracker_merger_policy.param.yaml | 16 + .../decorative_tracker_merger.drawio.svg | 345 +++++++++++++++ .../image/time_sync.drawio.svg | 223 ++++++++++ .../image/tracklet_management.drawio.svg | 216 ++++++++++ .../data_association/data_association.hpp | 73 ++++ .../data_association/solver/gnn_solver.hpp | 22 + .../solver/gnn_solver_interface.hpp | 35 ++ .../solver/mu_successive_shortest_path.hpp | 37 ++ .../solver/successive_shortest_path.hpp | 37 ++ .../decorative_tracker_merger.hpp | 134 ++++++ .../utils/tracker_state.hpp | 148 +++++++ .../tracking_object_merger/utils/utils.hpp | 129 ++++++ .../decorative_tracker_merger.launch.xml | 21 + perception/tracking_object_merger/package.xml | 32 ++ .../src/data_association/data_association.cpp | 238 +++++++++++ .../mu_successive_shortest_path_wrapper.cpp | 41 ++ .../successive_shortest_path.cpp | 370 ++++++++++++++++ .../src/decorative_tracker_merger.cpp | 403 ++++++++++++++++++ .../src/utils/tracker_state.cpp | 321 ++++++++++++++ .../src/utils/utils.cpp | 403 ++++++++++++++++++ 24 files changed, 3602 insertions(+) create mode 100644 perception/tracking_object_merger/CMakeLists.txt create mode 100644 perception/tracking_object_merger/README.md create mode 100644 perception/tracking_object_merger/config/data_association_matrix.param.yaml create mode 100644 perception/tracking_object_merger/config/decorative_tracker_merger.param.yaml create mode 100644 perception/tracking_object_merger/config/decorative_tracker_merger_policy.param.yaml create mode 100644 perception/tracking_object_merger/image/decorative_tracker_merger.drawio.svg create mode 100644 perception/tracking_object_merger/image/time_sync.drawio.svg create mode 100644 perception/tracking_object_merger/image/tracklet_management.drawio.svg create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/data_association/data_association.hpp create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver.hpp create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver_interface.hpp create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/mu_successive_shortest_path.hpp create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/successive_shortest_path.hpp create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/decorative_tracker_merger.hpp create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/utils/tracker_state.hpp create mode 100644 perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp create mode 100644 perception/tracking_object_merger/launch/decorative_tracker_merger.launch.xml create mode 100644 perception/tracking_object_merger/package.xml create mode 100644 perception/tracking_object_merger/src/data_association/data_association.cpp create mode 100644 perception/tracking_object_merger/src/data_association/mu_successive_shortest_path/mu_successive_shortest_path_wrapper.cpp create mode 100644 perception/tracking_object_merger/src/data_association/successive_shortest_path/successive_shortest_path.cpp create mode 100644 perception/tracking_object_merger/src/decorative_tracker_merger.cpp create mode 100644 perception/tracking_object_merger/src/utils/tracker_state.cpp create mode 100644 perception/tracking_object_merger/src/utils/utils.cpp diff --git a/perception/tracking_object_merger/CMakeLists.txt b/perception/tracking_object_merger/CMakeLists.txt new file mode 100644 index 0000000000000..108749c5f91a7 --- /dev/null +++ b/perception/tracking_object_merger/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.8) +project(tracking_object_merger VERSION 1.0.0) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -Werror -Wconversion) +endif() + +find_package(autoware_cmake REQUIRED) +autoware_package() +find_package(nlohmann_json REQUIRED) # for debug + + +# find dependencies +find_package(eigen3_cmake_module REQUIRED) +find_package(Eigen3 REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +include_directories( + SYSTEM + ${EIGEN3_INCLUDE_DIR} +) + +ament_auto_add_library(mu_successive_shortest_path SHARED + src/data_association/mu_successive_shortest_path/mu_successive_shortest_path_wrapper.cpp +) + +ament_auto_add_library(decorative_tracker_merger_node SHARED + src/data_association/data_association.cpp + src/decorative_tracker_merger.cpp + src/utils/utils.cpp + src/utils/tracker_state.cpp +) + +target_link_libraries(decorative_tracker_merger_node + mu_successive_shortest_path + Eigen3::Eigen + nlohmann_json::nlohmann_json # for debug +) + +rclcpp_components_register_node(decorative_tracker_merger_node + PLUGIN "tracking_object_merger::DecorativeTrackerMergerNode" + EXECUTABLE decorative_tracker_merger +) + +ament_auto_package(INSTALL_TO_SHARE + launch + config +) diff --git a/perception/tracking_object_merger/README.md b/perception/tracking_object_merger/README.md new file mode 100644 index 0000000000000..9964534372037 --- /dev/null +++ b/perception/tracking_object_merger/README.md @@ -0,0 +1,114 @@ +# Tracking Object Merger + +## Purpose + +This package try to merge two tracking objects from different sensor. + +## Inner-workings / Algorithms + +Merging tracking objects from different sensor is a combination of data association and state fusion algorithms. + +Detailed process depends on the merger policy. + +### decorative_tracker_merger + +In decorative_tracker_merger, we assume there are dominant tracking objects and sub tracking objects. +The name `decorative` means that sub tracking objects are used to complement the main objects. + +Usually the dominant tracking objects are from LiDAR and sub tracking objects are from Radar or Camera. + +Here show the processing pipeline. + +![decorative_tracker_merger](./image/decorative_tracker_merger.drawio.svg) + +#### time sync + +Sub object(Radar or Camera) often has higher frequency than dominant object(LiDAR). So we need to sync the time of sub object to dominant object. + +![time sync](image/time_sync.drawio.svg) + +#### data association + +In the data association, we use the following rules to determine whether two tracking objects are the same object. + +- gating + - `distance gate`: distance between two tracking objects + - `angle gate`: angle between two tracking objects + - `mahalanobis_distance_gate`: Mahalanobis distance between two tracking objects + - `min_iou_gate`: minimum IoU between two tracking objects + - `max_velocity_gate`: maximum velocity difference between two tracking objects +- score + - score used in matching is equivalent to the distance between two tracking objects + +#### tracklet update + +Sub tracking objects are merged into dominant tracking objects. + +Depends on the tracklet input sensor state, we update the tracklet state with different rules. + +| state\priority | 1st | 2nd | 3rd | +| -------------------------- | ------ | ----- | ------ | +| Kinematics except velocity | LiDAR | Radar | Camera | +| Forward velocity | Radar | LiDAR | Camera | +| Object classification | Camera | LiDAR | Radar | + +#### tracklet management + +We use the `existence_probability` to manage tracklet. + +- When we create a new tracklet, we set the `existence_probability` to $p_{sensor}$ value. +- In each update with specific sensor, we set the `existence_probability` to $p_{sensor}$ value. +- When tracklet does not have update with specific sensor, we reduce the `existence_probability` by `decay_rate` +- Object can be published if `existence_probability` is larger than `publish_probability_threshold` +- Object will be removed if `existence_probability` is smaller than `remove_probability_threshold` + +![tracklet_management](./image/tracklet_management.drawio.svg) + +These parameter can be set in `config/decorative_tracker_merger.param.yaml`. + +```yaml +tracker_state_parameter: + remove_probability_threshold: 0.3 + publish_probability_threshold: 0.6 + default_lidar_existence_probability: 0.7 + default_radar_existence_probability: 0.6 + default_camera_existence_probability: 0.6 + decay_rate: 0.1 + max_dt: 1.0 +``` + +#### input/parameters + +| topic name | message type | description | +| ------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------- | +| `~input/main_object` | `autoware_auto_perception_msgs::TrackedObjects` | Dominant tracking objects. Output will be published with this dominant object stamps. | +| `~input/sub_object` | `autoware_auto_perception_msgs::TrackedObjects` | Sub tracking objects. | +| `output/object` | `autoware_auto_perception_msgs::TrackedObjects` | Merged tracking objects. | +| `debug/interpolated_sub_object` | `autoware_auto_perception_msgs::TrackedObjects` | Interpolated sub tracking objects. | + +Default parameters are set in [config/decorative_tracker_merger.param.yaml](./config/decorative_tracker_merger.param.yaml). + +| parameter name | description | default value | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `base_link_frame_id` | base link frame id. This is used to transform the tracking object. | "base_link" | +| `time_sync_threshold` | time sync threshold. If the time difference between two tracking objects is smaller than this value, we consider these two tracking objects are the same object. | 0.05 | +| `sub_object_timeout_sec` | sub object timeout. If the sub object is not updated for this time, we consider this object is not exist. | 0.5 | +| `main_sensor_type` | main sensor type. This is used to determine the dominant tracking object. | "lidar" | +| `sub_sensor_type` | sub sensor type. This is used to determine the sub tracking object. | "radar" | +| `tracker_state_parameter` | tracker state parameter. This is used to manage the tracklet. | | + +- the detail of `tracker_state_parameter` is described in [tracklet management](#tracklet-management) + +#### tuning + +As explained in [tracklet management](#tracklet-management), this tracker merger tend to maintain the both input tracking objects. + +If there are many false positive tracking objects, + +- decrease `default__existence_probability` of that sensor +- increase `decay_rate` +- increase `publish_probability_threshold` to publish only reliable tracking objects + +### equivalent_tracker_merger + +This is future work. diff --git a/perception/tracking_object_merger/config/data_association_matrix.param.yaml b/perception/tracking_object_merger/config/data_association_matrix.param.yaml new file mode 100644 index 0000000000000..702809b3cede1 --- /dev/null +++ b/perception/tracking_object_merger/config/data_association_matrix.param.yaml @@ -0,0 +1,168 @@ +/**: + ros__parameters: + lidar-lidar: + can_assign_matrix: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE,PEDESTRIAN + [0, 0, 0, 0, 0, 0, 0, 0, #UNKNOWN + 0, 1, 1, 1, 1, 0, 0, 0, #CAR + 0, 1, 1, 1, 1, 0, 0, 0, #TRUCK + 0, 1, 1, 1, 1, 0, 0, 0, #BUS + 0, 1, 1, 1, 1, 0, 0, 0, #TRAILER + 0, 0, 0, 0, 0, 1, 1, 1, #MOTORBIKE + 0, 0, 0, 0, 0, 1, 1, 1, #BICYCLE + 0, 0, 0, 0, 0, 1, 1, 1] #PEDESTRIAN + + max_dist_matrix: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [4.0, 4.0, 5.0, 5.0, 5.0, 2.0, 2.0, 2.0, #UNKNOWN + 4.0, 2.0, 5.0, 5.0, 5.0, 1.0, 1.0, 1.0, #CAR + 5.0, 5.0, 5.0, 5.0, 5.0, 1.0, 1.0, 1.0, #TRUCK + 5.0, 5.0, 5.0, 5.0, 5.0, 1.0, 1.0, 1.0, #BUS + 5.0, 5.0, 5.0, 5.0, 5.0, 1.0, 1.0, 1.0, #TRAILER + 2.0, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.0, #MOTORBIKE + 2.0, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.0, #BICYCLE + 2.0, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 2.0] #PEDESTRIAN + + max_rad_matrix: # If value is greater than pi, it will be ignored. + #UNKNOWN, CAR, TRUCK, BUS, TRAILER MOTORBIKE, BICYCLE, PEDESTRIAN + [3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #UNKNOWN + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #CAR + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #TRUCK + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #BUS + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #TRAILER + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #MOTORBIKE + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #BICYCLE + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150] #PEDESTRIAN + + max_velocity_diff_matrix: # Ignored when value is larger than 100.0 + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #UNKNOWN + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #CAR + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #TRUCK + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #BUS + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #TRAILER + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #MOTORBIKE + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #BICYCLE + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0] #PEDESTRIAN + + min_iou_matrix: # If value is negative, it will be ignored. + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, #UNKNOWN + 0.1, 0.3, 0.2, 0.2, 0.2, 0.1, 0.1, 0.1, #CAR + 0.1, 0.2, 0.3, 0.3, 0.3, 0.1, 0.1, 0.1, #TRUCK + 0.1, 0.2, 0.3, 0.3, 0.3, 0.1, 0.1, 0.1, #BUS + 0.1, 0.2, 0.3, 0.3, 0.3, 0.1, 0.1, 0.1, #TRAILER + 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, #MOTORBIKE + 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, #BICYCLE + 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1] #PEDESTRIAN + + lidar-radar: + can_assign_matrix: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE,PEDESTRIAN + [0, 0, 0, 0, 0, 0, 0, 0, #UNKNOWN + 0, 1, 1, 1, 1, 0, 0, 0, #CAR + 0, 1, 1, 1, 1, 0, 0, 0, #TRUCK + 0, 1, 1, 1, 1, 0, 0, 0, #BUS + 0, 1, 1, 1, 1, 0, 0, 0, #TRAILER + 0, 0, 0, 0, 0, 1, 1, 1, #MOTORBIKE + 0, 0, 0, 0, 0, 1, 1, 1, #BICYCLE + 0, 0, 0, 0, 0, 1, 1, 1] #PEDESTRIAN + + max_dist_matrix: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [4.0, 4.0, 5.0, 5.0, 5.0, 3.5, 3.5, 3.5, #UNKNOWN + 4.0, 5.5, 6.0, 6.0, 6.0, 1.0, 1.0, 1.0, #CAR + 5.0, 6.0, 6.5, 6.5, 6.5, 1.0, 1.0, 1.0, #TRUCK + 5.0, 6.0, 6.5, 6.5, 6.5, 1.0, 1.0, 1.0, #BUS + 5.0, 6.0, 6.5, 6.5, 6.5, 1.0, 1.0, 1.0, #TRAILER + 3.5, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.0, #MOTORBIKE + 3.5, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.0, #BICYCLE + 3.5, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.5] #PEDESTRIAN + + max_rad_matrix: # If value is greater than pi, it will be ignored. + #UNKNOWN, CAR, TRUCK, BUS, TRAILER MOTORBIKE, BICYCLE, PEDESTRIAN + [3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #UNKNOWN + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #CAR + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #TRUCK + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #BUS + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #TRAILER + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #MOTORBIKE + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #BICYCLE + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150] #PEDESTRIAN + + max_velocity_diff_matrix: # Ignored when value is larger than 100.0 + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #UNKNOWN + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #CAR + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #TRUCK + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #BUS + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #TRAILER + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #MOTORBIKE + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #BICYCLE + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0] #PEDESTRIAN + + min_iou_matrix: # set all value to 0.0 to disable this constraint + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #UNKNOWN + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #CAR + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #TRUCK + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #BUS + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #TRAILER + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #MOTORBIKE + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #BICYCLE + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] #PEDESTRIAN + + radar-radar: + can_assign_matrix: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE,PEDESTRIAN + [0, 0, 0, 0, 0, 0, 0, 0, #UNKNOWN + 0, 1, 1, 1, 1, 0, 0, 0, #CAR + 0, 1, 1, 1, 1, 0, 0, 0, #TRUCK + 0, 1, 1, 1, 1, 0, 0, 0, #BUS + 0, 1, 1, 1, 1, 0, 0, 0, #TRAILER + 0, 0, 0, 0, 0, 1, 1, 1, #MOTORBIKE + 0, 0, 0, 0, 0, 1, 1, 1, #BICYCLE + 0, 0, 0, 0, 0, 1, 1, 1] #PEDESTRIAN + + max_dist_matrix: + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [4.0, 4.0, 5.0, 5.0, 5.0, 3.5, 3.5, 3.5, #UNKNOWN + 4.0, 7.0, 7.5, 7.5, 7.5, 1.0, 1.0, 1.0, #CAR + 5.0, 7.5, 7.5, 7.5, 7.5, 1.0, 1.0, 1.0, #TRUCK + 5.0, 7.5, 7.5, 7.5, 7.5, 1.0, 1.0, 1.0, #BUS + 5.0, 7.5, 7.5, 7.5, 7.5, 1.0, 1.0, 1.0, #TRAILER + 3.5, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.0, #MOTORBIKE + 3.5, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.0, #BICYCLE + 3.5, 1.0, 1.0, 1.0, 1.0, 3.0, 3.0, 3.5] #PEDESTRIAN + max_rad_matrix: # If value is greater than pi, it will be ignored. + #UNKNOWN, CAR, TRUCK, BUS, TRAILER MOTORBIKE, BICYCLE, PEDESTRIAN + [3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #UNKNOWN + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #CAR + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #TRUCK + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #BUS + 3.150, 1.047, 1.047, 1.047, 1.047, 3.150, 3.150, 3.150, #TRAILER + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #MOTORBIKE + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, #BICYCLE + 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150, 3.150] #PEDESTRIAN + + max_velocity_diff_matrix: # Ignored when value is larger than 100.0 + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #UNKNOWN + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #CAR + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #TRUCK + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #BUS + 100.0, 8.0, 8.0, 8.0, 8.0, 100.0, 100.0, 100.0, #TRAILER + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #MOTORBIKE + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0, #BICYCLE + 100.0, 100.0,100.0,100.0,100.0, 100.0, 100.0, 100.0] #PEDESTRIAN + + min_iou_matrix: # set all value to 0.0 to disable this constraint + #UNKNOWN, CAR, TRUCK, BUS, TRAILER, MOTORBIKE, BICYCLE, PEDESTRIAN + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #UNKNOWN + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #CAR + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #TRUCK + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #BUS + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #TRAILER + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #MOTORBIKE + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, #BICYCLE + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] #PEDESTRIAN diff --git a/perception/tracking_object_merger/config/decorative_tracker_merger.param.yaml b/perception/tracking_object_merger/config/decorative_tracker_merger.param.yaml new file mode 100644 index 0000000000000..4a108be657a27 --- /dev/null +++ b/perception/tracking_object_merger/config/decorative_tracker_merger.param.yaml @@ -0,0 +1,26 @@ +# Node parameters +/**: + ros__parameters: + base_link_frame_id: "base_link" + time_sync_threshold: 0.999 + sub_object_timeout_sec: 0.8 + publish_interpolated_sub_objects: true #for debug + + # choose the input sensor type for each object type + # "lidar", "radar", "camera" are available + main_sensor_type: "lidar" + sub_sensor_type: "radar" + + # tracker settings + tracker_state_parameter: + remove_probability_threshold: 0.3 + publish_probability_threshold: 0.5 + default_lidar_existence_probability: 0.7 + default_radar_existence_probability: 0.6 + default_camera_existence_probability: 0.5 + decay_rate: 0.1 + max_dt: 1.0 + + # logging settings + enable_logging: false + log_file_path: "/tmp/decorative_tracker_merger.log" diff --git a/perception/tracking_object_merger/config/decorative_tracker_merger_policy.param.yaml b/perception/tracking_object_merger/config/decorative_tracker_merger_policy.param.yaml new file mode 100644 index 0000000000000..0b98e1b202957 --- /dev/null +++ b/perception/tracking_object_merger/config/decorative_tracker_merger_policy.param.yaml @@ -0,0 +1,16 @@ +# Merger policy for decorative tracker merger node +# decorative tracker merger works by merging the sub-object trackers into a main object tracker result +# There are 3 merger policy: +# 1. "skip": skip the sub-object tracker result +# 2. "overwrite": overwrite the main object tracker result with sub-object tracker result +# 3. "fusion": merge the main object tracker result with sub-object tracker result by using covariance based fusion +/**: + ros__parameters: + kinematics_to_be_merged: "velocity" # currently only support "velocity" + + # choose the merger policy for each object type + # : "skip", "overwrite", "fusion" + kinematics_merge_policy: "overwrite" + classification_merge_policy: "skip" + existence_prob_merge_policy: "skip" + shape_merge_policy: "skip" diff --git a/perception/tracking_object_merger/image/decorative_tracker_merger.drawio.svg b/perception/tracking_object_merger/image/decorative_tracker_merger.drawio.svg new file mode 100644 index 0000000000000..3e35fa4d1c338 --- /dev/null +++ b/perception/tracking_object_merger/image/decorative_tracker_merger.drawio.svg @@ -0,0 +1,345 @@ + + + + + + + + + + + + +
+
+
+ main_object +
+
+
+
+ main_object +
+
+ + + + +
+
+
+ decorative_tracker_merger_node +
+
+
+
+ decorative_tracker_merger_node +
+
+ + + + + + +
+
+
+ sub_object +
+
+
+
+ sub_object +
+
+ + + + + + +
+
+
+ time-sync +
+ (delay compensation) +
+
+
+
+ time-sync... +
+
+ + + + + + +
+
+
+ buffer +
+
+
+
+ buffer +
+
+ + + + +
+
+
+ main obj +
+ timestamp +
+
+
+
+ main obj... +
+
+ + + + + + + + +
+
+
+ inner +
+ tracklet +
+
+
+
+ inner... +
+
+ + + + + +
+
+
+ not +
+ found +
+
+
+
+ not... +
+
+ + + + + +
+
+
+ found +
+
+
+
+ found +
+
+ + + + +
+
+
+ association +
+
+
+
+ association +
+
+ + + + + + +
+
+
+ update +
+ tracklet +
+
+
+
+ update... +
+
+ + + + + + +
+
+
+ add new +
+ tracklet +
+
+
+
+ add new... +
+
+ + + + + +
+
+
+ converted +
+ tracked object +
+
+
+
+ converted... +
+
+ + + + + + +
+
+
+ tracklet management +
+ (publisher) +
+
+
+
+ tracklet management... +
+
+ + + + +
+
+
+ merged object +
+
+
+
+ merged object +
+
+ + + + +
+
+
msg: TrackedObjects
+
+
+
+ msg: TrackedObjects +
+
+ + + + +
+
+
+ remove when low existence +
+ probability objects +
+
+
+
+ remove when low existence... +
+
+
+ + + + Text is not SVG - cannot display + + +
diff --git a/perception/tracking_object_merger/image/time_sync.drawio.svg b/perception/tracking_object_merger/image/time_sync.drawio.svg new file mode 100644 index 0000000000000..dad2eb1a7fd27 --- /dev/null +++ b/perception/tracking_object_merger/image/time_sync.drawio.svg @@ -0,0 +1,223 @@ + + + + + + + + + + + +
+
+
time
+
+
+
+ time +
+
+ + + + + +
+
+
main object topic
+
+
+
+ main object topic +
+
+ + + + +
+
+
sub object topic
+
+
+
+ sub object topic +
+
+ + + + + + + + + + + + + + + + + + +
+
+
publish timing
+
+
+
+ publish timing +
+
+ + + + +
+
+
+ interpolated sub object +
+
+
+
+ interpolated sub object +
+
+ + + + + + +
+
+
raw main/sub object
+
+
+
+ raw main/sub object +
+
+ + + + + + + +
+
+
+ time sync threshold +
+
+
+
+ time sync threshold +
+
+ + + + +
+
+
+ time synchronize prediction pattern +
+
+
+
+ time synchronize prediction pattern +
+
+ + + + + + + +
+
+
+ 1. forward +
+
+
+ 2. backward +
+
+
+ 3. interpolation +
+
+
+
+
+ 1. forward... +
+
+ + + + + + + + + + + + + + + + +
+
+
legend
+
+
+
+ legend +
+
+ + + + +
+
+
+ disabled +
+ now +
+
+
+
+ disabled... +
+
+
+ + + + Text is not SVG - cannot display + + +
diff --git a/perception/tracking_object_merger/image/tracklet_management.drawio.svg b/perception/tracking_object_merger/image/tracklet_management.drawio.svg new file mode 100644 index 0000000000000..4f2a00dfd4dfd --- /dev/null +++ b/perception/tracking_object_merger/image/tracklet_management.drawio.svg @@ -0,0 +1,216 @@ + + + + + + + + +
+
+
0.0
+
+
+
+ 0.0 +
+
+ + + + +
+
+
1.0
+
+
+
+ 1.0 +
+
+ + + + +
+
+
Existence Probability
+
+
+
+ Existence Probability +
+
+ + + + + +
+
+
remove threshold
+
+
+
+ remove threshold +
+
+ + + + +
+
+
can publish threshold
+
+
+
+ can publish threshold +
+
+ + + + + + + +
+
+
+ decay when +
+ not observed +
+
+
+
+ decay when... +
+
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+ + + + +
+
+
+ init or update probability +
+ from sensor +
+
+
+
+ init or update probabilit... +
+
+
+ + + + Text is not SVG - cannot display + + +
diff --git a/perception/tracking_object_merger/include/tracking_object_merger/data_association/data_association.hpp b/perception/tracking_object_merger/include/tracking_object_merger/data_association/data_association.hpp new file mode 100644 index 0000000000000..05fc9f6caccb5 --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/data_association/data_association.hpp @@ -0,0 +1,73 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// Author: v1.0 Yukihiro Saito +// + +#ifndef TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__DATA_ASSOCIATION_HPP_ +#define TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__DATA_ASSOCIATION_HPP_ + +// #include // for debug json library + +#include +#include +#include +#include + +#define EIGEN_MPL2_ONLY +#include "tracking_object_merger/data_association/solver/gnn_solver.hpp" +#include "tracking_object_merger/utils/tracker_state.hpp" + +#include +#include + +#include +#include + +#include + +class DataAssociation +{ +private: + Eigen::MatrixXi can_assign_matrix_; + Eigen::MatrixXd max_dist_matrix_; + Eigen::MatrixXd max_rad_matrix_; + Eigen::MatrixXd min_iou_matrix_; + Eigen::MatrixXd max_velocity_diff_matrix_; + const double score_threshold_; + std::unique_ptr gnn_solver_ptr_; + +public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + DataAssociation( + std::vector can_assign_vector, std::vector max_dist_vector, + std::vector max_rad_vector, std::vector min_iou_vector, + std::vector max_velocity_diff_vector); + void assign( + const Eigen::MatrixXd & src, std::unordered_map & direct_assignment, + std::unordered_map & reverse_assignment); + Eigen::MatrixXd calcScoreMatrix( + const autoware_auto_perception_msgs::msg::TrackedObjects & objects0, + const autoware_auto_perception_msgs::msg::TrackedObjects & objects1); + Eigen::MatrixXd calcScoreMatrix( + const autoware_auto_perception_msgs::msg::TrackedObjects & objects0, + const std::vector & trackers); + double calcScoreBetweenObjects( + const autoware_auto_perception_msgs::msg::TrackedObject & object0, + const autoware_auto_perception_msgs::msg::TrackedObject & object1) const; + virtual ~DataAssociation() {} +}; + +#endif // TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__DATA_ASSOCIATION_HPP_ diff --git a/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver.hpp b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver.hpp new file mode 100644 index 0000000000000..31240848f1977 --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver.hpp @@ -0,0 +1,22 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__GNN_SOLVER_HPP_ +#define TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__GNN_SOLVER_HPP_ + +#include "tracking_object_merger/data_association/solver/gnn_solver_interface.hpp" +#include "tracking_object_merger/data_association/solver/mu_successive_shortest_path.hpp" +#include "tracking_object_merger/data_association/solver/successive_shortest_path.hpp" + +#endif // TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__GNN_SOLVER_HPP_ diff --git a/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver_interface.hpp b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver_interface.hpp new file mode 100644 index 0000000000000..e915b5d2a9e7b --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/gnn_solver_interface.hpp @@ -0,0 +1,35 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__GNN_SOLVER_INTERFACE_HPP_ +#define TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__GNN_SOLVER_INTERFACE_HPP_ + +#include +#include + +namespace gnn_solver +{ +class GnnSolverInterface +{ +public: + GnnSolverInterface() = default; + virtual ~GnnSolverInterface() = default; + + virtual void maximizeLinearAssignment( + const std::vector> & cost, std::unordered_map * direct_assignment, + std::unordered_map * reverse_assignment) = 0; +}; +} // namespace gnn_solver + +#endif // TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__GNN_SOLVER_INTERFACE_HPP_ diff --git a/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/mu_successive_shortest_path.hpp b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/mu_successive_shortest_path.hpp new file mode 100644 index 0000000000000..5c0ebc04fdde3 --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/mu_successive_shortest_path.hpp @@ -0,0 +1,37 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__MU_SUCCESSIVE_SHORTEST_PATH_HPP_ +#define TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__MU_SUCCESSIVE_SHORTEST_PATH_HPP_ + +#include "tracking_object_merger/data_association/solver/gnn_solver_interface.hpp" + +#include +#include + +namespace gnn_solver +{ +class MuSSP : public GnnSolverInterface +{ +public: + MuSSP() = default; + ~MuSSP() = default; + + void maximizeLinearAssignment( + const std::vector> & cost, std::unordered_map * direct_assignment, + std::unordered_map * reverse_assignment) override; +}; +} // namespace gnn_solver + +#endif // TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__MU_SUCCESSIVE_SHORTEST_PATH_HPP_ diff --git a/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/successive_shortest_path.hpp b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/successive_shortest_path.hpp new file mode 100644 index 0000000000000..47a737cf58298 --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/data_association/solver/successive_shortest_path.hpp @@ -0,0 +1,37 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__SUCCESSIVE_SHORTEST_PATH_HPP_ +#define TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__SUCCESSIVE_SHORTEST_PATH_HPP_ + +#include "tracking_object_merger/data_association/solver/gnn_solver_interface.hpp" + +#include +#include + +namespace gnn_solver +{ +class SSP : public GnnSolverInterface +{ +public: + SSP() = default; + ~SSP() = default; + + void maximizeLinearAssignment( + const std::vector> & cost, std::unordered_map * direct_assignment, + std::unordered_map * reverse_assignment) override; +}; +} // namespace gnn_solver + +#endif // TRACKING_OBJECT_MERGER__DATA_ASSOCIATION__SOLVER__SUCCESSIVE_SHORTEST_PATH_HPP_ diff --git a/perception/tracking_object_merger/include/tracking_object_merger/decorative_tracker_merger.hpp b/perception/tracking_object_merger/include/tracking_object_merger/decorative_tracker_merger.hpp new file mode 100644 index 0000000000000..0509fb2a07dc5 --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/decorative_tracker_merger.hpp @@ -0,0 +1,134 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACKING_OBJECT_MERGER__DECORATIVE_TRACKER_MERGER_HPP_ +#define TRACKING_OBJECT_MERGER__DECORATIVE_TRACKER_MERGER_HPP_ + +#include "tracking_object_merger/data_association/data_association.hpp" +#include "tracking_object_merger/utils/tracker_state.hpp" +#include "tracking_object_merger/utils/utils.hpp" + +#include + +#include +#include + +#ifdef ROS_DISTRO_GALACTIC +#include +#else +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace tracking_object_merger +{ + +class DecorativeTrackerMergerNode : public rclcpp::Node +{ +public: + explicit DecorativeTrackerMergerNode(const rclcpp::NodeOptions & node_options); + enum class PriorityMode : int { Object0 = 0, Object1 = 1, Confidence = 2 }; + +private: + void set3dDataAssociation( + const std::string & prefix, + std::unordered_map> & data_association_map); + + void mainObjectsCallback( + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr & input_object_msg); + void subObjectsCallback( + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr & input_object_msg); + + bool decorativeMerger( + const MEASUREMENT_STATE input_index, + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr & input_object_msg); + autoware_auto_perception_msgs::msg::TrackedObjects predictFutureState( + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr & input_object_msg, + const std_msgs::msg::Header & header); + std::optional interpolateObjectState( + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr & input_object_msg1, + const autoware_auto_perception_msgs::msg::TrackedObjects::ConstSharedPtr & input_object_msg2, + const std_msgs::msg::Header & header); + TrackedObjects getTrackedObjects(const std_msgs::msg::Header & header); + TrackerState createNewTracker( + const MEASUREMENT_STATE input_index, rclcpp::Time current_time, + const autoware_auto_perception_msgs::msg::TrackedObject & input_object); + +private: + tf2_ros::Buffer tf_buffer_; + tf2_ros::TransformListener tf_listener_; + rclcpp::Publisher::SharedPtr + merged_object_pub_; + rclcpp::Subscription::SharedPtr + sub_main_objects_; + rclcpp::Subscription::SharedPtr + sub_sub_objects_; + // debug object publisher + rclcpp::Publisher::SharedPtr + debug_object_pub_; + bool publish_interpolated_sub_objects_; + + /* handle objects */ + std::unordered_map> + input_merger_map_; + MEASUREMENT_STATE main_sensor_type_; + MEASUREMENT_STATE sub_sensor_type_; + std::vector inner_tracker_objects_; + std::unordered_map> data_association_map_; + std::string target_frame_; + std::string base_link_frame_id_; + // buffer to save the sub objects + std::vector + sub_objects_buffer_; + double sub_object_timeout_sec_; + double time_sync_threshold_; + + // tracker default settings + TrackerStateParameter tracker_state_parameter_; + + // merge policy (currently not used) + struct + { + std::string kinematics_to_be_merged; + merger_utils::MergePolicy kinematics_merge_policy; + merger_utils::MergePolicy classification_merge_policy; + merger_utils::MergePolicy existence_prob_merge_policy; + merger_utils::MergePolicy shape_merge_policy; + } merger_policy_params_; + + std::map merger_policy_map_ = { + {"skip", merger_utils::MergePolicy::SKIP}, + {"overwrite", merger_utils::MergePolicy::OVERWRITE}, + {"fusion", merger_utils::MergePolicy::FUSION}}; + + // debug parameters + struct logging + { + bool enable = false; + std::string path; + } logging_; +}; + +} // namespace tracking_object_merger + +#endif // TRACKING_OBJECT_MERGER__DECORATIVE_TRACKER_MERGER_HPP_ diff --git a/perception/tracking_object_merger/include/tracking_object_merger/utils/tracker_state.hpp b/perception/tracking_object_merger/include/tracking_object_merger/utils/tracker_state.hpp new file mode 100644 index 0000000000000..6bedf7db8d727 --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/utils/tracker_state.hpp @@ -0,0 +1,148 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACKING_OBJECT_MERGER__UTILS__TRACKER_STATE_HPP_ +#define TRACKING_OBJECT_MERGER__UTILS__TRACKER_STATE_HPP_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +using autoware_auto_perception_msgs::msg::TrackedObject; +using autoware_auto_perception_msgs::msg::TrackedObjects; + +// Enum classes to distinguish input sources +enum class MEASUREMENT_STATE : int { + NONE = 0, // 0000 + LIDAR = 1, // 0001 + RADAR = 2, // 0010 + CAMERA = 4, // 0100 + LIDAR_RADAR = 3, // 0011 + LIDAR_CAMERA = 5, // 0101 + RADAR_CAMERA = 6, // 0110 + LIDAR_RADAR_CAMERA = 7, // 0111 +}; + +// Operator overloading for MEASUREMENT_STATE +// 1. operator| for MEASUREMENT_STATE +// e.g. MEASUREMENT_STATE::LIDAR | MEASUREMENT_STATE::RADAR == MEASUREMENT_STATE::LIDAR_RADAR +// 2. operator& for MEASUREMENT_STATE +// e.g. MEASUREMENT_STATE::LIDAR_RADAR & MEASUREMENT_STATE::LIDAR == true +// e.g. MEASUREMENT_STATE::LIDAR_RADAR & MEASUREMENT_STATE::CAMERA == false +inline MEASUREMENT_STATE operator|(MEASUREMENT_STATE lhs, MEASUREMENT_STATE rhs) +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} +inline bool operator&(MEASUREMENT_STATE combined, MEASUREMENT_STATE target) +{ + return (static_cast(combined) & static_cast(target)) != 0; +} + +// Struct to handle tracker state public parameter +struct TrackerStateParameter +{ + double publish_probability_threshold = 0.5; + double remove_probability_threshold = 0.3; + double default_lidar_existence_probability = 0.8; + double default_radar_existence_probability = 0.7; + double default_camera_existence_probability = 0.6; + double decay_rate = 0.1; + double max_dt = 2.0; +}; + +// Class to handle tracker state +class TrackerState +{ +private: + /* data */ + TrackedObject tracked_object_; + rclcpp::Time last_update_time_; + // Eigen::MatrixXf state_matrix_; + // Eigen::MatrixXf covariance_matrix_; + + // timer handle + std::unordered_map last_updated_time_map_; + double max_dt_ = 2.0; + +public: + TrackerState( + const MEASUREMENT_STATE input, const rclcpp::Time & last_update_time, + const TrackedObject & tracked_object); + TrackerState( + const MEASUREMENT_STATE input_source, const rclcpp::Time & last_update_time, + const autoware_auto_perception_msgs::msg::TrackedObject & tracked_object, + const unique_identifier_msgs::msg::UUID & uuid); + + ~TrackerState(); + +public: + void setParameter(const TrackerStateParameter & parameter); + bool predict(const rclcpp::Time & time); + bool predict(const double dt, std::function func); + MEASUREMENT_STATE getCurrentMeasurementState(const rclcpp::Time & current_time) const; + bool updateState( + const MEASUREMENT_STATE input, const rclcpp::Time & current_time, + const TrackedObject & tracked_object); + void updateWithLIDAR(const rclcpp::Time & current_time, const TrackedObject & tracked_object); + void updateWithRADAR(const rclcpp::Time & current_time, const TrackedObject & tracked_object); + void updateWithCAMERA(const rclcpp::Time & current_time, const TrackedObject & tracked_object); + void updateWithoutSensor(const rclcpp::Time & current_time); + bool update(const MEASUREMENT_STATE input, const TrackedObject & tracked_object); + bool updateWithFunction( + const MEASUREMENT_STATE input, const rclcpp::Time & current_time, + const TrackedObject & tracked_object, + std::function update_func); + // const functions + bool hasUUID(const MEASUREMENT_STATE input, const unique_identifier_msgs::msg::UUID & uuid) const; + bool isValid() const; + bool canPublish() const; + TrackedObject getObject() const; + +public: + // handle uuid + unique_identifier_msgs::msg::UUID const_uuid_; + // each sensor input to uuid map + std::unordered_map> + input_uuid_map_; + MEASUREMENT_STATE measurement_state_; + + // following lifecycle control parameter is overwritten by TrackerStateParameter + std::unordered_map default_existence_probability_map_ = { + {MEASUREMENT_STATE::LIDAR, 0.8}, + {MEASUREMENT_STATE::RADAR, 0.7}, + {MEASUREMENT_STATE::CAMERA, 0.6}, + }; + double existence_probability_ = 0.0; + double publish_probability_threshold_ = 0.5; + double remove_probability_threshold_ = 0.3; + double decay_rate_ = 0.1; +}; + +TrackedObjects getTrackedObjectsFromTrackerStates( + std::vector & tracker_states, const rclcpp::Time & time); + +#endif // TRACKING_OBJECT_MERGER__UTILS__TRACKER_STATE_HPP_ diff --git a/perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp b/perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp new file mode 100644 index 0000000000000..013040d15bded --- /dev/null +++ b/perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp @@ -0,0 +1,129 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// + +#ifndef TRACKING_OBJECT_MERGER__UTILS__UTILS_HPP_ +#define TRACKING_OBJECT_MERGER__UTILS__UTILS_HPP_ + +// #include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +using autoware_auto_perception_msgs::msg::TrackedObject; +using autoware_auto_perception_msgs::msg::TrackedObjects; +namespace utils +{ +enum MSG_COV_IDX { + X_X = 0, + X_Y = 1, + X_Z = 2, + X_ROLL = 3, + X_PITCH = 4, + X_YAW = 5, + Y_X = 6, + Y_Y = 7, + Y_Z = 8, + Y_ROLL = 9, + Y_PITCH = 10, + Y_YAW = 11, + Z_X = 12, + Z_Y = 13, + Z_Z = 14, + Z_ROLL = 15, + Z_PITCH = 16, + Z_YAW = 17, + ROLL_X = 18, + ROLL_Y = 19, + ROLL_Z = 20, + ROLL_ROLL = 21, + ROLL_PITCH = 22, + ROLL_YAW = 23, + PITCH_X = 24, + PITCH_Y = 25, + PITCH_Z = 26, + PITCH_ROLL = 27, + PITCH_PITCH = 28, + PITCH_YAW = 29, + YAW_X = 30, + YAW_Y = 31, + YAW_Z = 32, + YAW_ROLL = 33, + YAW_PITCH = 34, + YAW_YAW = 35 +}; + +// linear interpolation for tracked objects +TrackedObject linearInterpolationForTrackedObject( + const TrackedObject & obj1, const TrackedObject & obj2); + +// predict tracked objects +TrackedObject predictPastOrFutureTrackedObject(const TrackedObject & obj, const double dt); + +TrackedObjects predictPastOrFutureTrackedObjects( + const TrackedObjects & obj, const std_msgs::msg::Header & header); + +// predict tracked objects +TrackedObjects interpolateTrackedObjects( + const TrackedObjects & objects1, const TrackedObjects & objects2, std_msgs::msg::Header header); + +} // namespace utils + +namespace merger_utils +{ +// merge policy +enum MergePolicy : int { SKIP = 0, OVERWRITE = 1, FUSION = 2 }; + +// object kinematics velocity merger +autoware_auto_perception_msgs::msg::TrackedObjectKinematics objectKinematicsVXMerger( + const TrackedObject & main_obj, const TrackedObject & sub_obj, const MergePolicy policy); + +// object classification merger +TrackedObject objectClassificationMerger( + const TrackedObject & main_obj, const TrackedObject & sub_obj, const MergePolicy policy); + +// probability merger +float probabilityMerger(const float main_prob, const float sub_prob, const MergePolicy policy); + +// shape merger +autoware_auto_perception_msgs::msg::Shape shapeMerger( + const TrackedObject & main_obj, const TrackedObject & sub_obj, const MergePolicy policy); + +// update tracked object +void updateExceptVelocity(TrackedObject & main_obj, const TrackedObject & sub_obj); + +void updateOnlyObjectVelocity(TrackedObject & main_obj, const TrackedObject & sub_obj); + +void updateOnlyClassification(TrackedObject & main_obj, const TrackedObject & sub_obj); + +void updateWholeTrackedObject(TrackedObject & main_obj, const TrackedObject & sub_obj); + +} // namespace merger_utils + +#endif // TRACKING_OBJECT_MERGER__UTILS__UTILS_HPP_ diff --git a/perception/tracking_object_merger/launch/decorative_tracker_merger.launch.xml b/perception/tracking_object_merger/launch/decorative_tracker_merger.launch.xml new file mode 100644 index 0000000000000..5affbe474e8ae --- /dev/null +++ b/perception/tracking_object_merger/launch/decorative_tracker_merger.launch.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/perception/tracking_object_merger/package.xml b/perception/tracking_object_merger/package.xml new file mode 100644 index 0000000000000..d74a8449b20e6 --- /dev/null +++ b/perception/tracking_object_merger/package.xml @@ -0,0 +1,32 @@ + + + + tracking_object_merger + 0.0.0 + merge tracking object + Yukihiro Saito + Yoshi Ri + Apache License 2.0 + + ament_cmake_auto + autoware_cmake + eigen3_cmake_module + + autoware_auto_perception_msgs + eigen + mussp + object_recognition_utils + rclcpp + rclcpp_components + tf2 + tf2_ros + tier4_autoware_utils + tier4_perception_msgs + + ament_lint_auto + autoware_lint_common + + + ament_cmake + + diff --git a/perception/tracking_object_merger/src/data_association/data_association.cpp b/perception/tracking_object_merger/src/data_association/data_association.cpp new file mode 100644 index 0000000000000..e7ab6077250f8 --- /dev/null +++ b/perception/tracking_object_merger/src/data_association/data_association.cpp @@ -0,0 +1,238 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracking_object_merger/data_association/data_association.hpp" + +#include "object_recognition_utils/object_recognition_utils.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" +#include "tracking_object_merger/data_association/solver/gnn_solver.hpp" +#include "tracking_object_merger/utils/utils.hpp" + +#include +#include +#include +#include +#include +#include +namespace +{ +double getFormedYawAngle( + const geometry_msgs::msg::Quaternion & quat0, const geometry_msgs::msg::Quaternion & quat1, + const bool distinguish_front_or_back = true) +{ + const double yaw0 = tier4_autoware_utils::normalizeRadian(tf2::getYaw(quat0)); + const double yaw1 = tier4_autoware_utils::normalizeRadian(tf2::getYaw(quat1)); + const double angle_range = distinguish_front_or_back ? M_PI : M_PI_2; + const double angle_step = distinguish_front_or_back ? 2.0 * M_PI : M_PI; + // Fixed yaw0 to be in the range of +-90 or 180 degrees of yaw1 + double fixed_yaw0 = yaw0; + while (angle_range <= yaw1 - fixed_yaw0) { + fixed_yaw0 = fixed_yaw0 + angle_step; + } + while (angle_range <= fixed_yaw0 - yaw1) { + fixed_yaw0 = fixed_yaw0 - angle_step; + } + return std::fabs(fixed_yaw0 - yaw1); +} +} // namespace + +DataAssociation::DataAssociation( + std::vector can_assign_vector, std::vector max_dist_vector, + std::vector max_rad_vector, std::vector min_iou_vector, + std::vector max_velocity_diff_vector) +: score_threshold_(0.01) +{ + { + const int assign_label_num = static_cast(std::sqrt(can_assign_vector.size())); + Eigen::Map can_assign_matrix_tmp( + can_assign_vector.data(), assign_label_num, assign_label_num); + can_assign_matrix_ = can_assign_matrix_tmp.transpose(); + } + { + const int max_dist_label_num = static_cast(std::sqrt(max_dist_vector.size())); + Eigen::Map max_dist_matrix_tmp( + max_dist_vector.data(), max_dist_label_num, max_dist_label_num); + max_dist_matrix_ = max_dist_matrix_tmp.transpose(); + } + { + const int max_rad_label_num = static_cast(std::sqrt(max_rad_vector.size())); + Eigen::Map max_rad_matrix_tmp( + max_rad_vector.data(), max_rad_label_num, max_rad_label_num); + max_rad_matrix_ = max_rad_matrix_tmp.transpose(); + } + { + const int min_iou_label_num = static_cast(std::sqrt(min_iou_vector.size())); + Eigen::Map min_iou_matrix_tmp( + min_iou_vector.data(), min_iou_label_num, min_iou_label_num); + min_iou_matrix_ = min_iou_matrix_tmp.transpose(); + } + { + const int max_velocity_diff_label_num = + static_cast(std::sqrt(max_velocity_diff_vector.size())); + Eigen::Map max_velocity_diff_matrix_tmp( + max_velocity_diff_vector.data(), max_velocity_diff_label_num, max_velocity_diff_label_num); + max_velocity_diff_matrix_ = max_velocity_diff_matrix_tmp.transpose(); + } + + gnn_solver_ptr_ = std::make_unique(); +} + +void DataAssociation::assign( + const Eigen::MatrixXd & src, std::unordered_map & direct_assignment, + std::unordered_map & reverse_assignment) +{ + std::vector> score(src.rows()); + for (int row = 0; row < src.rows(); ++row) { + score.at(row).resize(src.cols()); + for (int col = 0; col < src.cols(); ++col) { + score.at(row).at(col) = src(row, col); + } + } + // Solve + gnn_solver_ptr_->maximizeLinearAssignment(score, &direct_assignment, &reverse_assignment); + + for (auto itr = direct_assignment.begin(); itr != direct_assignment.end();) { + if (src(itr->first, itr->second) < score_threshold_) { + itr = direct_assignment.erase(itr); + continue; + } else { + ++itr; + } + } + for (auto itr = reverse_assignment.begin(); itr != reverse_assignment.end();) { + if (src(itr->second, itr->first) < score_threshold_) { + itr = reverse_assignment.erase(itr); + continue; + } else { + ++itr; + } + } +} + +/** + * @brief calc score matrix between two tracked objects + * This is used to associate input measurements + * + * @param objects0 : measurements + * @param objects1 : base objects(tracker objects) + * @return Eigen::MatrixXd + */ +Eigen::MatrixXd DataAssociation::calcScoreMatrix( + const autoware_auto_perception_msgs::msg::TrackedObjects & objects0, + const autoware_auto_perception_msgs::msg::TrackedObjects & objects1) +{ + Eigen::MatrixXd score_matrix = + Eigen::MatrixXd::Zero(objects1.objects.size(), objects0.objects.size()); + for (size_t objects1_idx = 0; objects1_idx < objects1.objects.size(); ++objects1_idx) { + const auto & object1 = objects1.objects.at(objects1_idx); + for (size_t objects0_idx = 0; objects0_idx < objects0.objects.size(); ++objects0_idx) { + const auto & object0 = objects0.objects.at(objects0_idx); + const double score = calcScoreBetweenObjects(object0, object1); + + score_matrix(objects1_idx, objects0_idx) = score; + } + } + return score_matrix; +} + +/** + * @brief calc score matrix between two tracked object and Tracker State + * + * @param objects0 : measurements + * @param objects1 : tracker inner objects + * @return Eigen::MatrixXd + */ +Eigen::MatrixXd DataAssociation::calcScoreMatrix( + const autoware_auto_perception_msgs::msg::TrackedObjects & objects0, + const std::vector & trackers) +{ + Eigen::MatrixXd score_matrix = Eigen::MatrixXd::Zero(trackers.size(), objects0.objects.size()); + for (size_t trackers_idx = 0; trackers_idx < trackers.size(); ++trackers_idx) { + const auto & object1 = trackers.at(trackers_idx).getObject(); + + for (size_t objects0_idx = 0; objects0_idx < objects0.objects.size(); ++objects0_idx) { + const auto & object0 = objects0.objects.at(objects0_idx); + const double score = calcScoreBetweenObjects(object0, object1); + + score_matrix(trackers_idx, objects0_idx) = score; + } + } + return score_matrix; +} + +double DataAssociation::calcScoreBetweenObjects( + const autoware_auto_perception_msgs::msg::TrackedObject & object0, + const autoware_auto_perception_msgs::msg::TrackedObject & object1) const +{ + const std::uint8_t object1_label = + object_recognition_utils::getHighestProbLabel(object1.classification); + const std::uint8_t object0_label = + object_recognition_utils::getHighestProbLabel(object0.classification); + + std::vector tracker_pose = { + object1.kinematics.pose_with_covariance.pose.position.x, + object1.kinematics.pose_with_covariance.pose.position.y}; + std::vector measurement_pose = { + object0.kinematics.pose_with_covariance.pose.position.x, + object0.kinematics.pose_with_covariance.pose.position.y}; + + double score = 0.0; + if (can_assign_matrix_(object1_label, object0_label)) { + const double max_dist = max_dist_matrix_(object1_label, object0_label); + const double dist = tier4_autoware_utils::calcDistance2d( + object0.kinematics.pose_with_covariance.pose.position, + object1.kinematics.pose_with_covariance.pose.position); + + bool passed_gate = true; + // dist gate + if (passed_gate) { + if (max_dist < dist) passed_gate = false; + } + // angle gate + if (passed_gate) { + const double max_rad = max_rad_matrix_(object1_label, object0_label); + const double angle = getFormedYawAngle( + object0.kinematics.pose_with_covariance.pose.orientation, + object1.kinematics.pose_with_covariance.pose.orientation, false); + if (std::fabs(max_rad) < M_PI && std::fabs(max_rad) < std::fabs(angle)) passed_gate = false; + } + // 2d iou gate + if (passed_gate) { + const double min_iou = min_iou_matrix_(object1_label, object0_label); + const double min_union_iou_area = 1e-2; + const double iou = object_recognition_utils::get2dIoU(object0, object1, min_union_iou_area); + if (iou < min_iou) passed_gate = false; + } + // max velocity diff gate + if (passed_gate) { + const double max_velocity_diff = max_velocity_diff_matrix_(object1_label, object0_label); + // calc absolute velocity diff (only thinking about speed) + const auto speed0 = std::hypot( + object0.kinematics.twist_with_covariance.twist.linear.x, + object0.kinematics.twist_with_covariance.twist.linear.y); + const auto speed1 = std::hypot( + object1.kinematics.twist_with_covariance.twist.linear.x, + object1.kinematics.twist_with_covariance.twist.linear.y); + const double velocity_diff = std::fabs(speed0 - speed1); + if (max_velocity_diff < velocity_diff) passed_gate = false; + } + + // all gate is passed + if (passed_gate) { + score = (max_dist - std::min(dist, max_dist)) / max_dist; + if (score < score_threshold_) score = 0.0; + } + } + return score; +} diff --git a/perception/tracking_object_merger/src/data_association/mu_successive_shortest_path/mu_successive_shortest_path_wrapper.cpp b/perception/tracking_object_merger/src/data_association/mu_successive_shortest_path/mu_successive_shortest_path_wrapper.cpp new file mode 100644 index 0000000000000..fcc79c0a3cbd7 --- /dev/null +++ b/perception/tracking_object_merger/src/data_association/mu_successive_shortest_path/mu_successive_shortest_path_wrapper.cpp @@ -0,0 +1,41 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracking_object_merger/data_association/solver/mu_successive_shortest_path.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace gnn_solver +{ +void MuSSP::maximizeLinearAssignment( + const std::vector> & cost, std::unordered_map * direct_assignment, + std::unordered_map * reverse_assignment) +{ + // Terminate if the graph is empty + if (cost.size() == 0 || cost.at(0).size() == 0) { + return; + } + + // Solve DA by muSSP + solve_muSSP(cost, direct_assignment, reverse_assignment); +} +} // namespace gnn_solver diff --git a/perception/tracking_object_merger/src/data_association/successive_shortest_path/successive_shortest_path.cpp b/perception/tracking_object_merger/src/data_association/successive_shortest_path/successive_shortest_path.cpp new file mode 100644 index 0000000000000..133f0d377373f --- /dev/null +++ b/perception/tracking_object_merger/src/data_association/successive_shortest_path/successive_shortest_path.cpp @@ -0,0 +1,370 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracking_object_merger/data_association/solver/successive_shortest_path.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace gnn_solver +{ +struct ResidualEdge +{ + // Destination node + const int dst; + int capacity; + const double cost; + int flow; + // Access to the reverse edge by adjacency_list.at(dst).at(reverse) + const int reverse; + + // ResidualEdge() + // : dst(0), capacity(0), cost(0), flow(0), reverse(0) {} + + ResidualEdge(int dst, int capacity, double cost, int flow, int reverse) + : dst(dst), capacity(capacity), cost(cost), flow(flow), reverse(reverse) + { + } +}; + +void SSP::maximizeLinearAssignment( + const std::vector> & cost, std::unordered_map * direct_assignment, + std::unordered_map * reverse_assignment) +{ + // NOTE: Need to set as default arguments + bool sparse_cost = true; + // bool sparse_cost = false; + + // Hyperparameters + // double MAX_COST = 6; + const double MAX_COST = 10; + const double INF_DIST = 10000000; + const double EPS = 1e-5; + + // When there is no agents or no tasks, terminate + if (cost.size() == 0 || cost.at(0).size() == 0) { + return; + } + + // Construct a bipartite graph from the cost matrix + int n_agents = cost.size(); + int n_tasks = cost.at(0).size(); + + int n_dummies; + if (sparse_cost) { + n_dummies = n_agents; + } else { + n_dummies = 0; + } + + int source = 0; + int sink = n_agents + n_tasks + 1; + int n_nodes = n_agents + n_tasks + n_dummies + 2; + + // // Print cost matrix + // std::cout << std::endl; + // for (int agent = 0; agent < n_agents; agent++) + // { + // for (int task = 0; task < n_tasks; task++) + // { + // std::cout << cost.at(agent).at(task) << " "; + // } + // std::cout << std::endl; + // } + + // std::chrono::system_clock::time_point start_time, end_time; + // start_time = std::chrono::system_clock::now(); + + // Adjacency list of residual graph (index: nodes) + // - 0: source node + // - {1, ..., n_agents}: agent nodes + // - {n_agents+1, ..., n_agents+n_tasks}: task nodes + // - n_agents+n_tasks+1: sink node + // - {n_agents+n_tasks+2, ..., n_agents+n_tasks+1+n_agents}: + // dummy node (when sparse_cost is true) + std::vector> adjacency_list(n_nodes); + + // Reserve memory + for (int v = 0; v < n_nodes; ++v) { + if (v == source) { + // Source + adjacency_list.at(v).reserve(n_agents); + } else if (v <= n_agents) { + // Agents + adjacency_list.at(v).reserve(n_tasks + 1 + 1); + } else if (v <= n_agents + n_tasks) { + // Tasks + adjacency_list.at(v).reserve(n_agents + 1); + } else if (v == sink) { + // Sink + adjacency_list.at(v).reserve(n_tasks + n_dummies); + } else { + // Dummies + adjacency_list.at(v).reserve(2); + } + } + + // Add edges form source + for (int agent = 0; agent < n_agents; ++agent) { + // From source to agent + adjacency_list.at(source).emplace_back(agent + 1, 1, 0, 0, adjacency_list.at(agent + 1).size()); + // From agent to source + adjacency_list.at(agent + 1).emplace_back( + source, 0, 0, 0, adjacency_list.at(source).size() - 1); + } + + // Add edges from agents + for (int agent = 0; agent < n_agents; ++agent) { + for (int task = 0; task < n_tasks; ++task) { + if (!sparse_cost || cost.at(agent).at(task) > EPS) { + // From agent to task + adjacency_list.at(agent + 1).emplace_back( + task + n_agents + 1, 1, MAX_COST - cost.at(agent).at(task), 0, + adjacency_list.at(task + n_agents + 1).size()); + + // From task to agent + adjacency_list.at(task + n_agents + 1) + .emplace_back( + agent + 1, 0, cost.at(agent).at(task) - MAX_COST, 0, + adjacency_list.at(agent + 1).size() - 1); + } + } + } + + // Add edges form tasks + for (int task = 0; task < n_tasks; ++task) { + // From task to sink + adjacency_list.at(task + n_agents + 1) + .emplace_back(sink, 1, 0, 0, adjacency_list.at(sink).size()); + + // From sink to task + adjacency_list.at(sink).emplace_back( + task + n_agents + 1, 0, 0, 0, adjacency_list.at(task + n_agents + 1).size() - 1); + } + + // Add edges from dummy + if (sparse_cost) { + for (int agent = 0; agent < n_agents; ++agent) { + // From agent to dummy + adjacency_list.at(agent + 1).emplace_back( + agent + n_agents + n_tasks + 2, 1, MAX_COST, 0, + adjacency_list.at(agent + n_agents + n_tasks + 2).size()); + + // From dummy to agent + adjacency_list.at(agent + n_agents + n_tasks + 2) + .emplace_back(agent + 1, 0, -MAX_COST, 0, adjacency_list.at(agent + 1).size() - 1); + + // From dummy to sink + adjacency_list.at(agent + n_agents + n_tasks + 2) + .emplace_back(sink, 1, 0, 0, adjacency_list.at(sink).size()); + + // From sink to dummy + adjacency_list.at(sink).emplace_back( + agent + n_agents + n_tasks + 2, 0, 0, 0, + adjacency_list.at(agent + n_agents + n_tasks + 2).size() - 1); + } + } + + // Maximum flow value + const int max_flow = std::min(n_agents, n_tasks); + + // Feasible potentials + std::vector potentials(n_nodes, 0); + + // Shortest path lengths + std::vector distances(n_nodes, INF_DIST); + + // Whether previously visited the node or not + std::vector is_visited(n_nodes, false); + + // Parent node () + std::vector> prev_values(n_nodes); + + for (int i = 0; i < max_flow; ++i) { + // Initialize priority queue () + std::priority_queue< + std::pair, std::vector>, + std::greater>> + p_queue; + + // Reset all trajectory states + if (i > 0) { + std::fill(distances.begin(), distances.end(), INF_DIST); + std::fill(is_visited.begin(), is_visited.end(), false); + } + + // Start trajectory from the source node + p_queue.push(std::make_pair(0, source)); + distances.at(source) = 0; + + while (!p_queue.empty()) { + // Get the next element + std::pair cur_elem = p_queue.top(); + // std::cout << "[pop]: (" << cur_elem.first << ", " << cur_elem.second << ")" << std::endl; + p_queue.pop(); + + double cur_node_dist = cur_elem.first; + int cur_node = cur_elem.second; + + // If already visited node, skip and continue + if (is_visited.at(cur_node)) { + continue; + } + assert(cur_node_dist == distances.at(cur_node)); + + // Mark as visited + is_visited.at(cur_node) = true; + // Update potential + potentials.at(cur_node) += cur_node_dist; + + // When reached to the sink node, terminate. + if (cur_node == sink) { + break; + } + + // Loop over the incident nodes(/edges) + for (auto it_incident_edge = adjacency_list.at(cur_node).cbegin(); + it_incident_edge != adjacency_list.at(cur_node).cend(); it_incident_edge++) { + // If the node is not visited and have capacity to increase flow, visit. + if (!is_visited.at(it_incident_edge->dst) && it_incident_edge->capacity > 0) { + // Calculate reduced cost + double reduced_cost = + it_incident_edge->cost + potentials.at(cur_node) - potentials.at(it_incident_edge->dst); + assert(reduced_cost >= 0); + if (distances.at(it_incident_edge->dst) > reduced_cost) { + distances.at(it_incident_edge->dst) = reduced_cost; + prev_values.at(it_incident_edge->dst) = + std::make_pair(cur_node, it_incident_edge - adjacency_list.at(cur_node).cbegin()); + // std::cout << "[push]: (" << reduced_cost << ", " << next_v << ")" << std::endl; + p_queue.push(std::make_pair(reduced_cost, it_incident_edge->dst)); + } + } + } + } + + // Shortest path length to sink is greater than MAX_COST, + // which means no non-dummy routes left, terminate + if (potentials.at(sink) >= MAX_COST) { + break; + } + + // Update potentials of unvisited nodes + for (int v = 0; v < n_nodes; ++v) { + if (!is_visited.at(v)) { + potentials.at(v) += distances.at(sink); + } + } + // //Print potentials + // for (int v = 0; v < n_nodes; ++v) + // { + // std::cout << potentials.at(v) << ", "; + // } + // std::cout << std::endl; + + // Increase/decrease flow and capacity along the shortest path from the source to the sink + int v = sink; + int prev_v; + while (v != source) { + ResidualEdge & e_forward = + adjacency_list.at(prev_values.at(v).first).at(prev_values.at(v).second); + assert(e_forward.dst == v); + ResidualEdge & e_backward = adjacency_list.at(v).at(e_forward.reverse); + prev_v = e_backward.dst; + + if (e_backward.flow == 0) { + // Increase flow + // State A + assert(e_forward.capacity == 1); + assert(e_forward.flow == 0); + assert(e_backward.capacity == 0); + assert(e_backward.flow == 0); + + e_forward.capacity -= 1; + e_forward.flow += 1; + e_backward.capacity += 1; + + // State B + assert(e_forward.capacity == 0); + assert(e_forward.flow == 1); + assert(e_backward.capacity == 1); + assert(e_backward.flow == 0); + } else { + // Decrease flow + // State B + assert(e_forward.capacity == 1); + assert(e_forward.flow == 0); + assert(e_backward.capacity == 0); + assert(e_backward.flow == 1); + + e_forward.capacity -= 1; + e_backward.capacity += 1; + e_backward.flow -= 1; + + // State A + assert(e_forward.capacity == 0); + assert(e_forward.flow == 0); + assert(e_backward.capacity == 1); + assert(e_backward.flow == 0); + } + + v = prev_v; + } + +#ifndef NDEBUG + // Check if the potentials are feasible potentials + for (int v = 0; v < n_nodes; ++v) { + for (auto it_incident_edge = adjacency_list.at(v).cbegin(); + it_incident_edge != adjacency_list.at(v).cend(); ++it_incident_edge) { + if (it_incident_edge->capacity > 0) { + double reduced_cost = + it_incident_edge->cost + potentials.at(v) - potentials.at(it_incident_edge->dst); + assert(reduced_cost >= 0); + } + } + } +#endif + } + + // Output + for (int agent = 0; agent < n_agents; ++agent) { + for (auto it_incident_edge = adjacency_list.at(agent + 1).cbegin(); + it_incident_edge != adjacency_list.at(agent + 1).cend(); ++it_incident_edge) { + int task = it_incident_edge->dst - (n_agents + 1); + + // If the flow value is 1 and task is not dummy, assign the task to the agent. + if (it_incident_edge->flow == 1 && 0 <= task && task < n_tasks) { + (*direct_assignment)[agent] = task; + (*reverse_assignment)[task] = agent; + break; + } + } + } + +#ifndef NDEBUG + // Check if the result is valid assignment + for (int agent = 0; agent < n_agents; ++agent) { + if (direct_assignment->find(agent) != direct_assignment->cend()) { + int task = (*direct_assignment).at(agent); + assert(direct_assignment->at(agent) == task); + assert(reverse_assignment->at(task) == agent); + } + } +#endif +} +} // namespace gnn_solver diff --git a/perception/tracking_object_merger/src/decorative_tracker_merger.cpp b/perception/tracking_object_merger/src/decorative_tracker_merger.cpp new file mode 100644 index 0000000000000..47a333691eabf --- /dev/null +++ b/perception/tracking_object_merger/src/decorative_tracker_merger.cpp @@ -0,0 +1,403 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracking_object_merger/decorative_tracker_merger.hpp" + +#include "object_recognition_utils/object_recognition_utils.hpp" +#include "tracking_object_merger/data_association/solver/successive_shortest_path.hpp" +#include "tracking_object_merger/utils/utils.hpp" + +#include + +#include +#include + +#define EIGEN_MPL2_ONLY +#include +#include + +using Label = autoware_auto_perception_msgs::msg::ObjectClassification; + +namespace tracking_object_merger +{ + +using autoware_auto_perception_msgs::msg::TrackedObject; +using autoware_auto_perception_msgs::msg::TrackedObjects; + +// get unix time from header +double getUnixTime(const std_msgs::msg::Header & header) +{ + return header.stamp.sec + header.stamp.nanosec * 1e-9; +} + +// calc association score matrix +Eigen::MatrixXd calcScoreMatrixForAssociation( + const MEASUREMENT_STATE measurement_state, + const autoware_auto_perception_msgs::msg::TrackedObjects & objects0, + const std::vector & trackers, + const std::unordered_map> & data_association_map + // const bool debug_log, const std::string & file_name // do not logging for now +) +{ + // get current time + const rclcpp::Time current_time = rclcpp::Time(objects0.header.stamp); + + // calc score matrix + Eigen::MatrixXd score_matrix = Eigen::MatrixXd::Zero(trackers.size(), objects0.objects.size()); + for (size_t trackers_idx = 0; trackers_idx < trackers.size(); ++trackers_idx) { + const auto & tracker_obj = trackers.at(trackers_idx); + const auto & object1 = tracker_obj.getObject(); + const auto & tracker_state = tracker_obj.getCurrentMeasurementState(current_time); + + for (size_t objects0_idx = 0; objects0_idx < objects0.objects.size(); ++objects0_idx) { + const auto & object0 = objects0.objects.at(objects0_idx); + // switch calc score function by input and trackers measurement state + // we assume that lidar and radar are exclusive + double score; + const auto input_has_lidar = measurement_state & MEASUREMENT_STATE::LIDAR; + const auto tracker_has_lidar = tracker_state & MEASUREMENT_STATE::LIDAR; + if (input_has_lidar && tracker_has_lidar) { + score = data_association_map.at("lidar-lidar")->calcScoreBetweenObjects(object0, object1); + } else if (!input_has_lidar && !tracker_has_lidar) { + score = data_association_map.at("radar-radar")->calcScoreBetweenObjects(object0, object1); + } else { + score = data_association_map.at("lidar-radar")->calcScoreBetweenObjects(object0, object1); + } + score_matrix(trackers_idx, objects0_idx) = score; + } + } + return score_matrix; +} + +DecorativeTrackerMergerNode::DecorativeTrackerMergerNode(const rclcpp::NodeOptions & node_options) +: rclcpp::Node("decorative_object_merger_node", node_options), + tf_buffer_(get_clock()), + tf_listener_(tf_buffer_) +{ + // Subscriber + sub_main_objects_ = create_subscription( + "input/main_object", rclcpp::QoS{1}, + std::bind(&DecorativeTrackerMergerNode::mainObjectsCallback, this, std::placeholders::_1)); + sub_sub_objects_ = create_subscription( + "input/sub_object", rclcpp::QoS{1}, + std::bind(&DecorativeTrackerMergerNode::subObjectsCallback, this, std::placeholders::_1)); + + // merged object publisher + merged_object_pub_ = create_publisher("output/object", rclcpp::QoS{1}); + // debug object publisher + debug_object_pub_ = + create_publisher("debug/interpolated_sub_object", rclcpp::QoS{1}); + + // logging + logging_.enable = declare_parameter("enable_logging"); + logging_.path = declare_parameter("logging_file_path"); + + // Parameters + publish_interpolated_sub_objects_ = declare_parameter("publish_interpolated_sub_objects"); + base_link_frame_id_ = declare_parameter("base_link_frame_id"); + time_sync_threshold_ = declare_parameter("time_sync_threshold"); + sub_object_timeout_sec_ = declare_parameter("sub_object_timeout_sec"); + // default setting parameter for tracker + tracker_state_parameter_.remove_probability_threshold = + declare_parameter("tracker_state_parameter.remove_probability_threshold"); + tracker_state_parameter_.publish_probability_threshold = + declare_parameter("tracker_state_parameter.publish_probability_threshold"); + tracker_state_parameter_.default_lidar_existence_probability = + declare_parameter("tracker_state_parameter.default_lidar_existence_probability"); + tracker_state_parameter_.default_radar_existence_probability = + declare_parameter("tracker_state_parameter.default_radar_existence_probability"); + tracker_state_parameter_.default_camera_existence_probability = + declare_parameter("tracker_state_parameter.default_camera_existence_probability"); + tracker_state_parameter_.decay_rate = + declare_parameter("tracker_state_parameter.decay_rate"); + tracker_state_parameter_.max_dt = declare_parameter("tracker_state_parameter.max_dt"); + + const std::string main_sensor_type = declare_parameter("main_sensor_type"); + const std::string sub_sensor_type = declare_parameter("sub_sensor_type"); + // str to MEASUREMENT_STATE + auto str2measurement_state = [](const std::string & str) { + if (str == "lidar") { + return MEASUREMENT_STATE::LIDAR; + } else if (str == "radar") { + return MEASUREMENT_STATE::RADAR; + } else { + throw std::runtime_error("invalid sensor type"); + } + }; + main_sensor_type_ = str2measurement_state(main_sensor_type); + sub_sensor_type_ = str2measurement_state(sub_sensor_type); + + /* init association **/ + // lidar-lidar association matrix + set3dDataAssociation("lidar-lidar", data_association_map_); + // lidar-radar association matrix + set3dDataAssociation("lidar-radar", data_association_map_); + // radar-radar association matrix + set3dDataAssociation("radar-radar", data_association_map_); +} + +void DecorativeTrackerMergerNode::set3dDataAssociation( + const std::string & prefix, + std::unordered_map> & data_association_map) +{ + const auto tmp = this->declare_parameter>(prefix + ".can_assign_matrix"); + const std::vector can_assign_matrix(tmp.begin(), tmp.end()); + const auto max_dist_matrix = + this->declare_parameter>(prefix + ".max_dist_matrix"); + const auto max_rad_matrix = + this->declare_parameter>(prefix + ".max_rad_matrix"); + const auto min_iou_matrix = + this->declare_parameter>(prefix + ".min_iou_matrix"); + const auto max_velocity_diff_matrix = + this->declare_parameter>(prefix + ".max_velocity_diff_matrix"); + + data_association_map[prefix] = std::make_unique( + can_assign_matrix, max_dist_matrix, max_rad_matrix, min_iou_matrix, max_velocity_diff_matrix); +} + +/** + * @brief callback function for main objects + * + * @param main_objects + * @note if there are no sub objects, publish main objects as it is + * else, merge main objects and sub objects + */ +void DecorativeTrackerMergerNode::mainObjectsCallback( + const TrackedObjects::ConstSharedPtr & main_objects) +{ + // try to merge sub object + if (!sub_objects_buffer_.empty()) { + // get interpolated sub objects + // get newest sub objects which timestamp is earlier to main objects + TrackedObjects::ConstSharedPtr closest_time_sub_objects; + TrackedObjects::ConstSharedPtr closest_time_sub_objects_later; + for (const auto & sub_object : sub_objects_buffer_) { + if (getUnixTime(sub_object->header) < getUnixTime(main_objects->header)) { + closest_time_sub_objects = sub_object; + } else { + closest_time_sub_objects_later = sub_object; + break; + } + } + // get delay compensated sub objects + const auto interpolated_sub_objects = interpolateObjectState( + closest_time_sub_objects, closest_time_sub_objects_later, main_objects->header); + if (interpolated_sub_objects.has_value()) { + // Merge sub objects + const auto interp_sub_objs = interpolated_sub_objects.value(); + debug_object_pub_->publish(interp_sub_objs); + this->decorativeMerger( + sub_sensor_type_, std::make_shared(interpolated_sub_objects.value())); + } else { + RCLCPP_DEBUG(this->get_logger(), "interpolated_sub_objects is null"); + } + } + + // try to merge main object + this->decorativeMerger(main_sensor_type_, main_objects); + + merged_object_pub_->publish(getTrackedObjects(main_objects->header)); +} + +/** + * @brief callback function for sub objects + * + * @param msg + * @note push back sub objects to buffer and remove old sub objects + */ +void DecorativeTrackerMergerNode::subObjectsCallback(const TrackedObjects::ConstSharedPtr & msg) +{ + sub_objects_buffer_.push_back(msg); + // remove old sub objects + // const auto now = get_clock()->now(); + const auto now = rclcpp::Time(msg->header.stamp); + const auto remove_itr = std::remove_if( + sub_objects_buffer_.begin(), sub_objects_buffer_.end(), [now, this](const auto & sub_object) { + return (now - rclcpp::Time(sub_object->header.stamp)).seconds() > sub_object_timeout_sec_; + }); + sub_objects_buffer_.erase(remove_itr, sub_objects_buffer_.end()); +} + +/** + * @brief merge objects into inner_tracker_objects_ + * + * @param main_objects + * @return TrackedObjects + * + * @note 1. interpolate sub objects to sync main objects + * 2. do association + * 3. merge objects + */ +bool DecorativeTrackerMergerNode::decorativeMerger( + const MEASUREMENT_STATE input_sensor, const TrackedObjects::ConstSharedPtr & input_objects_msg) +{ + // get current time + const auto current_time = rclcpp::Time(input_objects_msg->header.stamp); + if (input_objects_msg->objects.empty()) { + return false; + } + if (inner_tracker_objects_.empty()) { + for (const auto & object : input_objects_msg->objects) { + inner_tracker_objects_.push_back(createNewTracker(input_sensor, current_time, object)); + } + } + + // do prediction for inner objects + for (auto & object : inner_tracker_objects_) { + object.predict(current_time); + } + + // TODO(yoshiri): pre-association + + // associate inner objects and input objects + /* global nearest neighbor */ + std::unordered_map direct_assignment, reverse_assignment; + const auto & objects1 = input_objects_msg->objects; + Eigen::MatrixXd score_matrix = calcScoreMatrixForAssociation( + input_sensor, *input_objects_msg, inner_tracker_objects_, data_association_map_); + data_association_map_.at("lidar-lidar") + ->assign(score_matrix, direct_assignment, reverse_assignment); + + // look for tracker + for (int tracker_idx = 0; tracker_idx < static_cast(inner_tracker_objects_.size()); + ++tracker_idx) { + auto & object0_state = inner_tracker_objects_.at(tracker_idx); + if (direct_assignment.find(tracker_idx) != direct_assignment.end()) { // found and merge + const auto & object1 = objects1.at(direct_assignment.at(tracker_idx)); + // merge object1 into object0_state + object0_state.updateState(input_sensor, current_time, object1); + } else { // not found + // decrease existence probability + object0_state.updateWithoutSensor(current_time); + } + } + // look for new object + for (int object1_idx = 0; object1_idx < static_cast(objects1.size()); ++object1_idx) { + const auto & object1 = objects1.at(object1_idx); + if (reverse_assignment.find(object1_idx) != reverse_assignment.end()) { // found + } else { // not found + inner_tracker_objects_.push_back(createNewTracker(input_sensor, current_time, object1)); + } + } + return true; +} + +/** + * @brief interpolate sub objects to sync main objects + * + * @param former_msg + * @param latter_msg + * @param output_header + * @return std::optional + * + * @note 1. if both msg is nullptr, return null optional + * 2. if former_msg is nullptr, return latter_msg + * 3. if latter_msg is nullptr, return former_msg + * 4. if both msg is not nullptr, do the interpolation + */ +std::optional DecorativeTrackerMergerNode::interpolateObjectState( + const TrackedObjects::ConstSharedPtr & former_msg, + const TrackedObjects::ConstSharedPtr & latter_msg, const std_msgs::msg::Header & output_header) +{ + // Assumption: output_header must be newer than former_msg and older than latter_msg + // There three possible patterns + // 1. both msg is nullptr + // 2. former_msg is nullptr + // 3. latter_msg is nullptr + // 4. both msg is not nullptr + + // 1. both msg is nullptr + if (former_msg == nullptr && latter_msg == nullptr) { + // return null optional + RCLCPP_DEBUG(this->get_logger(), "interpolation err: both msg is nullptr"); + return std::nullopt; + } + + // 2. former_msg is nullptr + if (former_msg == nullptr) { + // std::cout << "interpolation: 2. former_msg is nullptr" << std::endl; + // depends on header stamp difference + if ( + (rclcpp::Time(latter_msg->header.stamp) - rclcpp::Time(output_header.stamp)).seconds() > + time_sync_threshold_) { + // do nothing + RCLCPP_DEBUG( + this->get_logger(), "interpolation err: latter msg is too different from output msg"); + return std::nullopt; + } else { // else, return latter_msg + return *latter_msg; + } + + // 3. latter_msg is nullptr + } else if (latter_msg == nullptr) { + // std::cout << "interpolation: 3. latter_msg is nullptr" << std::endl; + // depends on header stamp difference + const auto dt = + (rclcpp::Time(output_header.stamp) - rclcpp::Time(former_msg->header.stamp)).seconds(); + if (dt > time_sync_threshold_) { + // do nothing + RCLCPP_DEBUG(this->get_logger(), "interpolation err: former msg is too old"); + return std::nullopt; + } else { + // else, return former_msg + return utils::predictPastOrFutureTrackedObjects(*former_msg, output_header); + } + + // 4. both msg is not nullptr + } else { + // do the interpolation + // std::cout << "interpolation: 4. both msg is not nullptr" << std::endl; + TrackedObjects interpolated_msg = + utils::interpolateTrackedObjects(*former_msg, *latter_msg, output_header); + return interpolated_msg; + } +} + +// get merged objects +TrackedObjects DecorativeTrackerMergerNode::getTrackedObjects(const std_msgs::msg::Header & header) +{ + // get main objects + rclcpp::Time current_time = rclcpp::Time(header.stamp); + return getTrackedObjectsFromTrackerStates(inner_tracker_objects_, current_time); +} + +// create new tracker +TrackerState DecorativeTrackerMergerNode::createNewTracker( + const MEASUREMENT_STATE input_index, rclcpp::Time current_time, + const autoware_auto_perception_msgs::msg::TrackedObject & input_object) +{ + // check if object id is not included in innner_tracker_objects_ + for (const auto & object : inner_tracker_objects_) { + if (object.const_uuid_ == input_object.object_id) { + // create new uuid + unique_identifier_msgs::msg::UUID uuid; + // Generate random number + std::mt19937 gen(std::random_device{}()); + std::independent_bits_engine bit_eng(gen); + std::generate(uuid.uuid.begin(), uuid.uuid.end(), bit_eng); + auto new_tracker = TrackerState(input_index, current_time, input_object, uuid); + new_tracker.setParameter(tracker_state_parameter_); + return new_tracker; + } + } + // if not found, just create new tracker + auto new_tracker = TrackerState(input_index, current_time, input_object); + new_tracker.setParameter(tracker_state_parameter_); + return new_tracker; +} + +} // namespace tracking_object_merger + +#include +RCLCPP_COMPONENTS_REGISTER_NODE(tracking_object_merger::DecorativeTrackerMergerNode) diff --git a/perception/tracking_object_merger/src/utils/tracker_state.cpp b/perception/tracking_object_merger/src/utils/tracker_state.cpp new file mode 100644 index 0000000000000..f0dc657145a24 --- /dev/null +++ b/perception/tracking_object_merger/src/utils/tracker_state.cpp @@ -0,0 +1,321 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracking_object_merger/utils/tracker_state.hpp" + +#include "tracking_object_merger/utils/utils.hpp" + +using autoware_auto_perception_msgs::msg::TrackedObject; +using autoware_auto_perception_msgs::msg::TrackedObjects; + +/** + * @brief Construct a new Tracker State:: Tracker State object + * + * @param input_source : input source distinguished + * @param tracked_object : input source tracked object + * @param last_update_time : last update time + */ +TrackerState::TrackerState( + const MEASUREMENT_STATE input_source, const rclcpp::Time & last_update_time, + const autoware_auto_perception_msgs::msg::TrackedObject & tracked_object) +: tracked_object_(tracked_object), + last_update_time_(last_update_time), + const_uuid_(tracked_object.object_id), + measurement_state_(input_source) +{ + input_uuid_map_[input_source] = tracked_object_.object_id; + last_updated_time_map_[input_source] = last_update_time; + existence_probability_ = default_existence_probability_map_[input_source]; +} + +TrackerState::TrackerState( + const MEASUREMENT_STATE input_source, const rclcpp::Time & last_update_time, + const autoware_auto_perception_msgs::msg::TrackedObject & tracked_object, + const unique_identifier_msgs::msg::UUID & uuid) +: tracked_object_(tracked_object), + last_update_time_(last_update_time), + const_uuid_(uuid), + measurement_state_(input_source) +{ + input_uuid_map_[input_source] = tracked_object_.object_id; + last_updated_time_map_[input_source] = last_update_time; + existence_probability_ = default_existence_probability_map_[input_source]; +} + +void TrackerState::setParameter(const TrackerStateParameter & parameter) +{ + max_dt_ = parameter.max_dt; + publish_probability_threshold_ = parameter.publish_probability_threshold; + remove_probability_threshold_ = parameter.remove_probability_threshold; + default_existence_probability_map_.at(MEASUREMENT_STATE::LIDAR) = + parameter.default_lidar_existence_probability; + default_existence_probability_map_.at(MEASUREMENT_STATE::RADAR) = + parameter.default_radar_existence_probability; + default_existence_probability_map_.at(MEASUREMENT_STATE::CAMERA) = + parameter.default_camera_existence_probability; + decay_rate_ = parameter.decay_rate; +} + +/** + * @brief Predict state to current time + * + * @param current_time + * @return true + * @return false + */ +bool TrackerState::predict(const rclcpp::Time & current_time) +{ + // get dt and give warning if dt is too large + double dt = (current_time - last_update_time_).seconds(); + if (std::abs(dt) > max_dt_) { + std::cerr << "[tracking_object_merger] dt is too large: " << dt << std::endl; + return false; + } + + // do prediction + last_update_time_ = current_time; + return this->predict(dt, utils::predictPastOrFutureTrackedObject); +} + +/** + * @brief Predict state to current time with given update function + * + * @param dt : time to predict + * @param func : update function (e.g. PredictPastOrFutureTrackedObject) + * @return true: prediction succeeded + * @return false: prediction failed + */ +bool TrackerState::predict( + const double dt, std::function func) +{ + const auto predicted_object = func(tracked_object_, dt); + tracked_object_ = predicted_object; + return true; +} + +// get current measurement state +MEASUREMENT_STATE TrackerState::getCurrentMeasurementState(const rclcpp::Time & current_time) const +{ + MEASUREMENT_STATE measurement_state = MEASUREMENT_STATE::NONE; + // check LIDAR + if (last_updated_time_map_.find(MEASUREMENT_STATE::LIDAR) != last_updated_time_map_.end()) { + if ((current_time - last_updated_time_map_.at(MEASUREMENT_STATE::LIDAR)).seconds() < max_dt_) { + measurement_state = measurement_state | MEASUREMENT_STATE::LIDAR; + } + } + // check RADAR + if (last_updated_time_map_.find(MEASUREMENT_STATE::RADAR) != last_updated_time_map_.end()) { + if ((current_time - last_updated_time_map_.at(MEASUREMENT_STATE::RADAR)).seconds() < max_dt_) { + measurement_state = measurement_state | MEASUREMENT_STATE::RADAR; + } + } + // check CAMERA + if (last_updated_time_map_.find(MEASUREMENT_STATE::CAMERA) != last_updated_time_map_.end()) { + if ((current_time - last_updated_time_map_.at(MEASUREMENT_STATE::CAMERA)).seconds() < max_dt_) { + measurement_state = measurement_state | MEASUREMENT_STATE::CAMERA; + } + } + return measurement_state; +} + +bool TrackerState::updateState( + const MEASUREMENT_STATE input, const rclcpp::Time & current_time, + const TrackedObject & tracked_object) +{ + // calc dt + double dt = (current_time - last_update_time_).seconds(); + if (dt < 0.0) { + std::cerr << "[tracking_object_merger] dt is negative: " << dt << std::endl; + return false; + } + + // predict + if (dt > 0.0) { + this->predict(dt, utils::predictPastOrFutureTrackedObject); + } + + // get current measurement state + measurement_state_ = getCurrentMeasurementState(current_time); + + // update with input + if (input & MEASUREMENT_STATE::LIDAR) { + updateWithLIDAR(current_time, tracked_object); + } + if (input & MEASUREMENT_STATE::RADAR) { + updateWithRADAR(current_time, tracked_object); + } + if (input & MEASUREMENT_STATE::CAMERA) { + updateWithCAMERA(current_time, tracked_object); + } + return true; +} + +void TrackerState::updateWithCAMERA( + const rclcpp::Time & current_time, const TrackedObject & tracked_object) +{ + // update tracked object + updateWithFunction( + MEASUREMENT_STATE::CAMERA, current_time, tracked_object, + merger_utils::updateOnlyClassification); +} + +void TrackerState::updateWithLIDAR( + const rclcpp::Time & current_time, const TrackedObject & tracked_object) +{ + // if radar is available, do not update velocity + if (measurement_state_ & MEASUREMENT_STATE::RADAR) { + // update tracked object + updateWithFunction( + MEASUREMENT_STATE::LIDAR, current_time, tracked_object, merger_utils::updateExceptVelocity); + } else { + // else just update tracked object + updateWithFunction( + MEASUREMENT_STATE::LIDAR, current_time, tracked_object, + merger_utils::updateWholeTrackedObject); + } +} + +void TrackerState::updateWithRADAR( + const rclcpp::Time & current_time, const TrackedObject & tracked_object) +{ + // if lidar is available, update only velocity + if (measurement_state_ & MEASUREMENT_STATE::LIDAR) { + // update tracked object + updateWithFunction( + MEASUREMENT_STATE::RADAR, current_time, tracked_object, + merger_utils::updateOnlyObjectVelocity); + } else { + // else just update tracked object + updateWithFunction( + MEASUREMENT_STATE::RADAR, current_time, tracked_object, + merger_utils::updateWholeTrackedObject); + } +} + +bool TrackerState::updateWithFunction( + const MEASUREMENT_STATE input, const rclcpp::Time & current_time, + const TrackedObject & input_tracked_object, + std::function update_func) +{ + // put input uuid and last update time + if (current_time > last_update_time_) { + const auto predict_successful = predict(current_time); + if (!predict_successful) { + return false; + } + } + + // update with measurement state + last_updated_time_map_[input] = current_time; + input_uuid_map_[input] = input_tracked_object.object_id; + measurement_state_ = measurement_state_ | input; + existence_probability_ = + std::max(existence_probability_, default_existence_probability_map_[input]); + + // update tracked object + update_func(tracked_object_, input_tracked_object); + tracked_object_.object_id = const_uuid_; // overwrite uuid to stay same + return true; +} + +void TrackerState::updateWithoutSensor(const rclcpp::Time & current_time) +{ + // calc dt + double dt = (current_time - last_update_time_).seconds(); + if (dt < 0) { + std::cerr << "[tracking_object_merger] dt is negative: " << dt << std::endl; + return; + } + + // predict + if (dt > 0.0) { + const auto predict_successful = this->predict(dt, utils::predictPastOrFutureTrackedObject); + if (!predict_successful) { + existence_probability_ = 0.0; // remove object + } + } + + // reduce probability + existence_probability_ -= decay_rate_; + if (existence_probability_ < 0.0) { + existence_probability_ = 0.0; + } +} + +TrackedObject TrackerState::getObject() const +{ + TrackedObject tracked_object = tracked_object_; + tracked_object.object_id = const_uuid_; + tracked_object.existence_probability = + static_cast(existence_probability_); // double -> float + return tracked_object; +} + +bool TrackerState::hasUUID( + const MEASUREMENT_STATE input, const unique_identifier_msgs::msg::UUID & uuid) const +{ + if (input_uuid_map_.find(input) == input_uuid_map_.end()) { + return false; + } + return input_uuid_map_.at(input) == uuid; +} + +bool TrackerState::isValid() const +{ + // check if tracker state is valid + if (existence_probability_ < remove_probability_threshold_) { + return false; + } + return true; +} + +bool TrackerState::canPublish() const +{ + // check if tracker state is valid + if (existence_probability_ < publish_probability_threshold_) { + return false; + } + return true; +} + +TrackerState::~TrackerState() +{ + // destructor +} + +TrackedObjects getTrackedObjectsFromTrackerStates( + std::vector & tracker_states, const rclcpp::Time & current_time) +{ + TrackedObjects tracked_objects; + + // sanitize and get tracked objects + for (auto it = tracker_states.begin(); it != tracker_states.end();) { + // check if tracker state is valid + if (it->isValid()) { + if (it->canPublish()) { + // if valid, get tracked object + tracked_objects.objects.push_back(it->getObject()); + } + ++it; + } else { + // if not valid, delete tracker state + it = tracker_states.erase(it); + } + } + + // update header + tracked_objects.header.stamp = current_time; + tracked_objects.header.frame_id = "map"; // TODO(yoshiri): get frame_id from input + return tracked_objects; +} diff --git a/perception/tracking_object_merger/src/utils/utils.cpp b/perception/tracking_object_merger/src/utils/utils.cpp new file mode 100644 index 0000000000000..5987bdc2d1bef --- /dev/null +++ b/perception/tracking_object_merger/src/utils/utils.cpp @@ -0,0 +1,403 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracking_object_merger/utils/utils.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +using autoware_auto_perception_msgs::msg::TrackedObject; +using autoware_auto_perception_msgs::msg::TrackedObjects; +namespace utils +{ + +/** + * @brief linear interpolation for tracked object + * + * @param obj1 : obj1 and obj2 must have same shape type + * @param obj2 : obj1 and obj2 must have same shape type + * @param weight : 0 <= weight <= 1, weight = 0: obj1, weight = 1: obj2 + * @return TrackedObject : interpolated object + */ +TrackedObject linearInterpolationForTrackedObject( + const TrackedObject & obj1, const TrackedObject & obj2, const double weight) +{ + // interpolate obj1 and obj2 with weight: obj1 * (1 - weight) + obj2 * weight + // weight = 0: obj1, weight = 1: obj2 + + // weight check (0 <= weight <= 1) + if (weight < 0 || weight > 1) { + std::cerr << "weight must be 0 <= weight <= 1 in linearInterpolationForTrackedObject function." + << std::endl; + return obj1; + } + + // output object + TrackedObject output; + // copy input object at first + output = obj1; + + // get current twist and pose + const auto twist1 = obj1.kinematics.twist_with_covariance.twist; + const auto pose1 = obj1.kinematics.pose_with_covariance.pose; + const auto twist2 = obj2.kinematics.twist_with_covariance.twist; + const auto pose2 = obj2.kinematics.pose_with_covariance.pose; + + // interpolate twist + auto & output_twist = output.kinematics.twist_with_covariance.twist; + output_twist.linear.x = twist1.linear.x * (1 - weight) + twist2.linear.x * weight; + output_twist.linear.y = twist1.linear.y * (1 - weight) + twist2.linear.y * weight; + output_twist.linear.z = twist1.linear.z * (1 - weight) + twist2.linear.z * weight; + output_twist.angular.x = twist1.angular.x * (1 - weight) + twist2.angular.x * weight; + output_twist.angular.y = twist1.angular.y * (1 - weight) + twist2.angular.y * weight; + output_twist.angular.z = twist1.angular.z * (1 - weight) + twist2.angular.z * weight; + + // interpolate pose + auto & output_pose = output.kinematics.pose_with_covariance.pose; + output_pose.position.x = pose1.position.x * (1 - weight) + pose2.position.x * weight; + output_pose.position.y = pose1.position.y * (1 - weight) + pose2.position.y * weight; + output_pose.position.z = pose1.position.z * (1 - weight) + pose2.position.z * weight; + // interpolate orientation with slerp + // https://en.wikipedia.org/wiki/Slerp + const auto q1 = tf2::Quaternion( + pose1.orientation.x, pose1.orientation.y, pose1.orientation.z, pose1.orientation.w); + const auto q2 = tf2::Quaternion( + pose2.orientation.x, pose2.orientation.y, pose2.orientation.z, pose2.orientation.w); + const auto q = q1.slerp(q2, weight); + output_pose.orientation.x = q.x(); + output_pose.orientation.y = q.y(); + output_pose.orientation.z = q.z(); + output_pose.orientation.w = q.w(); + + // interpolate shape(mostly for bounding box) + const auto shape1 = obj1.shape; + const auto shape2 = obj2.shape; + if (shape1.type != shape2.type) { + // if shape type is different, return obj1 + } else { + if (shape1.type == autoware_auto_perception_msgs::msg::Shape::BOUNDING_BOX) { + auto & output_shape = output.shape; + output_shape.dimensions.x = shape1.dimensions.x * (1 - weight) + shape2.dimensions.x * weight; + output_shape.dimensions.y = shape1.dimensions.y * (1 - weight) + shape2.dimensions.y * weight; + output_shape.dimensions.z = shape1.dimensions.z * (1 - weight) + shape2.dimensions.z * weight; + } else if (shape1.type == autoware_auto_perception_msgs::msg::Shape::CYLINDER) { + // (TODO) implement + } else if (shape1.type == autoware_auto_perception_msgs::msg::Shape::POLYGON) { + // (TODO) implement + } else { + // when type is unknown, print warning and do nothing + std::cerr << "unknown shape type in linearInterpolationForTrackedObject function." + << std::endl; + return output; + } + } + + return output; +} + +/** + * @brief predict past or future tracked object + * + * @param obj + * @param dt + * @return TrackedObject + */ +TrackedObject predictPastOrFutureTrackedObject(const TrackedObject & obj, const double dt) +{ + // output object + TrackedObject output; + // copy input object at first + output = obj; + if (dt == 0) { + return output; + } + + // get current twist and pose + const auto twist = obj.kinematics.twist_with_covariance.twist; + const auto pose = obj.kinematics.pose_with_covariance.pose; + + // get yaw + const auto yaw = tf2::getYaw(pose.orientation); + const auto vx = twist.linear.x; + const auto vy = twist.linear.y; + + // linear prediction equation: + // x = x0 + vx * cos(yaw) * dt - vy * sin(yaw) * dt + // y = y0 + vx * sin(yaw) * dt + vy * cos(yaw) * dt + auto & output_pose = output.kinematics.pose_with_covariance.pose; + output_pose.position.x += vx * std::cos(yaw) * dt - vy * std::sin(yaw) * dt; + output_pose.position.y += vx * std::sin(yaw) * dt + vy * std::cos(yaw) * dt; + + // (TODO) covariance prediction + + return output; +} + +/** + * @brief predict past or future tracked objects + * + * @param obj + * @param header + * @return TrackedObjects + */ +TrackedObjects predictPastOrFutureTrackedObjects( + const TrackedObjects & obj, const std_msgs::msg::Header & header) +{ + // for each object, predict past or future + TrackedObjects output_objects; + output_objects.header = obj.header; + output_objects.header.stamp = header.stamp; + + const auto dt = (rclcpp::Time(header.stamp) - rclcpp::Time(obj.header.stamp)).seconds(); + for (const auto & obj : obj.objects) { + output_objects.objects.push_back(predictPastOrFutureTrackedObject(obj, dt)); + } + return output_objects; +} + +/** + * @brief interpolate tracked objects + * + * @param objects1 + * @param objects2 + * @param header : header of output object + * @return TrackedObjects + */ +TrackedObjects interpolateTrackedObjects( + const TrackedObjects & objects1, const TrackedObjects & objects2, std_msgs::msg::Header header) +{ + // Assumption: time of output header is between objects1 and objects2 + // time of objects1 < header < objects2 + + // define output objects + TrackedObjects output_objects; + output_objects.header = header; + + // calc weight + const auto dt = + (rclcpp::Time(objects1.header.stamp) - rclcpp::Time(objects2.header.stamp)).seconds(); + const auto dt1 = (rclcpp::Time(objects1.header.stamp) - rclcpp::Time(header.stamp)).seconds(); + const auto weight = dt1 / dt; + + // check if object number is not zero + if (objects1.objects.size() == 0 && objects2.objects.size() == 0) { + // if objects1 and objects2 are empty, return empty objects + return output_objects; + } else if (objects1.objects.size() == 0) { + // if objects1 is empty return objects2 + for (const auto & obj2 : objects2.objects) { + output_objects.objects.push_back(predictPastOrFutureTrackedObject(obj2, dt1 - dt)); + } + return output_objects; + } else if (objects2.objects.size() == 0) { + // if objects2 is empty return objects1 + for (const auto & obj1 : objects1.objects) { + output_objects.objects.push_back(predictPastOrFutureTrackedObject(obj1, dt1)); + } + } else { + // if both objects1 and objects2 are not empty do nothing + } + + // map to handle selected objects + std::unordered_map selected_objects1; + // iterate with int + for (std::size_t i = 0; i < objects1.objects.size(); i++) { + selected_objects1[i] = false; + } + // search for objects with int iterator + for (std::size_t i = 0; i < objects1.objects.size(); i++) { + for (std::size_t j = 0; j < objects2.objects.size(); j++) { + if (objects1.objects[i].object_id == objects2.objects[j].object_id) { + // if id is same, interpolate + const auto interp_objects = + linearInterpolationForTrackedObject(objects1.objects[i], objects2.objects[j], weight); + output_objects.objects.push_back(interp_objects); + selected_objects1[i] = true; + break; + } + } + if (selected_objects1[i] == false) { + // if obj1 is not selected, predict future + const auto pred_objects = predictPastOrFutureTrackedObject(objects1.objects[i], dt1); + output_objects.objects.push_back(pred_objects); + } + } + + return output_objects; +} + +} // namespace utils + +namespace merger_utils +{ + +double mean(const double a, const double b) +{ + return (a + b) / 2.0; +} + +// object kinematics merger +// currently only support velocity fusion +autoware_auto_perception_msgs::msg::TrackedObjectKinematics objectKinematicsVXMerger( + const TrackedObject & main_obj, const TrackedObject & sub_obj, const MergePolicy policy) +{ + autoware_auto_perception_msgs::msg::TrackedObjectKinematics output_kinematics; + // copy main object at first + output_kinematics = main_obj.kinematics; + if (policy == MergePolicy::SKIP) { + return output_kinematics; + } else if (policy == MergePolicy::OVERWRITE) { + output_kinematics.twist_with_covariance.twist.linear.x = + sub_obj.kinematics.twist_with_covariance.twist.linear.x; + return output_kinematics; + } else if (policy == MergePolicy::FUSION) { + const auto main_vx = main_obj.kinematics.twist_with_covariance.twist.linear.x; + const auto sub_vx = sub_obj.kinematics.twist_with_covariance.twist.linear.x; + // inverse weight + const auto main_vx_cov = main_obj.kinematics.twist_with_covariance.covariance[0]; + const auto sub_vx_cov = sub_obj.kinematics.twist_with_covariance.covariance[0]; + double main_vx_weight, sub_vx_weight; + if (main_vx_cov == 0.0) { + main_vx_weight = 1.0 * 1e6; + } else { + main_vx_weight = 1.0 / main_vx_cov; + } + if (sub_vx_cov == 0.0) { + sub_vx_weight = 1.0 * 1e6; + } else { + sub_vx_weight = 1.0 / sub_vx_cov; + } + // merge with covariance + output_kinematics.twist_with_covariance.twist.linear.x = + (main_vx * main_vx_weight + sub_vx * sub_vx_weight) / (main_vx_weight + sub_vx_weight); + output_kinematics.twist_with_covariance.covariance[0] = 1.0 / (main_vx_weight + sub_vx_weight); + return output_kinematics; + } else { + std::cerr << "unknown merge policy in objectKinematicsMerger function." << std::endl; + return output_kinematics; + } + return output_kinematics; +} + +// object classification merger +TrackedObject objectClassificationMerger( + const TrackedObject & main_obj, const TrackedObject & sub_obj, const MergePolicy policy) +{ + if (policy == MergePolicy::SKIP) { + return main_obj; + } else if (policy == MergePolicy::OVERWRITE) { + return sub_obj; + } else if (policy == MergePolicy::FUSION) { + // merge two std::vector into one + TrackedObject dummy_obj; + dummy_obj.classification = main_obj.classification; + for (const auto & sub_class : sub_obj.classification) { + dummy_obj.classification.push_back(sub_class); + } + return dummy_obj; + } else { + std::cerr << "unknown merge policy in objectClassificationMerger function." << std::endl; + return main_obj; + } +} + +// probability merger +float probabilityMerger(const float main_prob, const float sub_prob, const MergePolicy policy) +{ + if (policy == MergePolicy::SKIP) { + return main_prob; + } else if (policy == MergePolicy::OVERWRITE) { + return sub_prob; + } else if (policy == MergePolicy::FUSION) { + return static_cast(mean(main_prob, sub_prob)); + } else { + std::cerr << "unknown merge policy in probabilityMerger function." << std::endl; + return main_prob; + } +} + +// shape merger +autoware_auto_perception_msgs::msg::Shape shapeMerger( + const autoware_auto_perception_msgs::msg::Shape & main_obj_shape, + const autoware_auto_perception_msgs::msg::Shape & sub_obj_shape, const MergePolicy policy) +{ + autoware_auto_perception_msgs::msg::Shape output_shape; + // copy main object at first + output_shape = main_obj_shape; + + if (main_obj_shape.type != sub_obj_shape.type) { + // if shape type is different, return main object + return output_shape; + } + + if (policy == MergePolicy::SKIP) { + return output_shape; + } else if (policy == MergePolicy::OVERWRITE) { + return sub_obj_shape; + } else if (policy == MergePolicy::FUSION) { + // write down fusion method for each shape type + if (main_obj_shape.type == autoware_auto_perception_msgs::msg::Shape::BOUNDING_BOX) { + // if shape type is bounding box, merge bounding box + output_shape.dimensions.x = mean(main_obj_shape.dimensions.x, sub_obj_shape.dimensions.x); + output_shape.dimensions.y = mean(main_obj_shape.dimensions.y, sub_obj_shape.dimensions.y); + output_shape.dimensions.z = mean(main_obj_shape.dimensions.z, sub_obj_shape.dimensions.z); + return output_shape; + } else if (main_obj_shape.type == autoware_auto_perception_msgs::msg::Shape::CYLINDER) { + // if shape type is cylinder, merge cylinder + // (TODO) implement + return output_shape; + } else if (main_obj_shape.type == autoware_auto_perception_msgs::msg::Shape::POLYGON) { + // if shape type is polygon, merge polygon + // (TODO) + return output_shape; + } else { + // when type is unknown, print warning and do nothing + std::cerr << "unknown shape type in shapeMerger function." << std::endl; + return output_shape; + } + } else { + std::cerr << "unknown merge policy in shapeMerger function." << std::endl; + return output_shape; + } +} + +void updateExceptVelocity(TrackedObject & main_obj, const TrackedObject & sub_obj) +{ + const auto vx_temp = main_obj.kinematics.twist_with_covariance.twist.linear.x; + main_obj = sub_obj; + main_obj.kinematics.twist_with_covariance.twist.linear.x = vx_temp; +} + +void updateOnlyObjectVelocity(TrackedObject & main_obj, const TrackedObject & sub_obj) +{ + main_obj.kinematics = objectKinematicsVXMerger(main_obj, sub_obj, MergePolicy::OVERWRITE); +} + +void updateOnlyClassification(TrackedObject & main_obj, const TrackedObject & sub_obj) +{ + main_obj = objectClassificationMerger(main_obj, sub_obj, MergePolicy::OVERWRITE); +} + +void updateWholeTrackedObject(TrackedObject & main_obj, const TrackedObject & sub_obj) +{ + main_obj = sub_obj; +} + +} // namespace merger_utils From 7e0031e988e41ad1124f241d202802dc3b7f671a Mon Sep 17 00:00:00 2001 From: "Md. Muhaimin Rahman" Date: Fri, 13 Oct 2023 11:48:25 +0900 Subject: [PATCH 455/547] fix(euclidean cluster): update the broken link (#5292) Update the broken link The given link was broken. I have updated with the correct link. --- perception/euclidean_cluster/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perception/euclidean_cluster/README.md b/perception/euclidean_cluster/README.md index 6c8995a44c56c..b57204c84cc3b 100644 --- a/perception/euclidean_cluster/README.md +++ b/perception/euclidean_cluster/README.md @@ -10,7 +10,7 @@ This package has two clustering methods: `euclidean_cluster` and `voxel_grid_bas ### euclidean_cluster -`pcl::EuclideanClusterExtraction` is applied to points. See [official document](https://pcl.readthedocs.io/en/latest/cluster_extraction.html) for details. +`pcl::EuclideanClusterExtraction` is applied to points. See [official document](https://pcl.readthedocs.io/projects/tutorials/en/master/cluster_extraction.html) for details. ### voxel_grid_based_euclidean_cluster From bcfae36caf2fd0948202c0c4992ae356239265d0 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Fri, 13 Oct 2023 13:07:24 +0900 Subject: [PATCH 456/547] docs(behavior_path_planner): update doc of safety check feature fot start planner (#5240) * update doc Signed-off-by: kyoichi-sugahara * rotate foot print Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ...avior_path_planner_start_planner_design.md | 122 ++++++++++++++++-- .../collision_check_range.drawio.svg | 119 +++++++++++++++++ 2 files changed, 229 insertions(+), 12 deletions(-) create mode 100644 planning/behavior_path_planner/image/start_planner/collision_check_range.drawio.svg diff --git a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md index 95a9697a1d471..131202038c270 100644 --- a/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md +++ b/planning/behavior_path_planner/docs/behavior_path_planner_start_planner_design.md @@ -62,7 +62,7 @@ PullOutPath --o PullOutPlannerBase | Name | Unit | Type | Description | Default value | | :---------------------------------------------------------- | :---- | :----- | :-------------------------------------------------------------------------- | :------------ | | th_arrived_distance_m | [m] | double | distance threshold for arrival of path termination | 1.0 | -| th_distance_to_middle_of_the_road | [m] | double | distance threshold to determine if the vehicle is on the middle of the road | +| th_distance_to_middle_of_the_road | [m] | double | distance threshold to determine if the vehicle is on the middle of the road | 0.1 | | th_stopped_velocity_mps | [m/s] | double | velocity threshold for arrival of path termination | 0.01 | | th_stopped_time_sec | [s] | double | time threshold for arrival of path termination | 1.0 | | th_turn_signal_on_lateral_offset | [m] | double | lateral distance threshold for turning on blinker | 1.0 | @@ -82,8 +82,108 @@ PullOutPath --o PullOutPlannerBase ## Safety check with dynamic obstacles +### **Basic concept of safety check against dynamic obstacles** + +This is based on the concept of RSS. For the logic used, refer to the link below. +See [safety check feature explanation](../docs/behavior_path_planner_safety_check.md) + +### **Collision check performed range** + +A collision check with dynamic objects is primarily performed between the shift start point and end point. The range for safety check varies depending on the type of path generated, so it will be explained for each pattern. + +#### **Shift pull out** + +For the "shift pull out", safety verification starts at the beginning of the shift and ends at the shift's conclusion. + +#### **Geometric pull out** + +Since there's a stop at the midpoint during the shift, this becomes the endpoint for safety verification. After stopping, safety verification resumes. + +#### **Backward pull out start point search** + +During backward movement, no safety check is performed. Safety check begins at the point where the backward movement ends. + +![collision_check_range](../image/start_planner/collision_check_range.drawio.svg) + +### **Ego vehicle's velocity planning** + +WIP + +### **Safety check in free space area** + WIP +## Parameters for safety check + +### Stop Condition Parameters + +Parameters under `stop_condition` define the criteria for stopping conditions. + +| Name | Unit | Type | Description | Default value | +| :---------------------------- | :------ | :----- | :-------------------------------------- | :------------ | +| maximum_deceleration_for_stop | [m/s^2] | double | Maximum deceleration allowed for a stop | 1.0 | +| maximum_jerk_for_stop | [m/s^3] | double | Maximum jerk allowed for a stop | 1.0 | + +### Ego Predicted Path Parameters + +Parameters under `path_safety_check.ego_predicted_path` specify the ego vehicle's predicted path characteristics. + +| Name | Unit | Type | Description | Default value | +| :---------------------------- | :------ | :----- | :--------------------------------------------------- | :------------ | +| min_velocity | [m/s] | double | Minimum velocity of the ego vehicle's predicted path | 0.0 | +| acceleration | [m/s^2] | double | Acceleration for the ego vehicle's predicted path | 1.0 | +| max_velocity | [m/s] | double | Maximum velocity of the ego vehicle's predicted path | 1.0 | +| time_horizon_for_front_object | [s] | double | Time horizon for predicting front objects | 10.0 | +| time_horizon_for_rear_object | [s] | double | Time horizon for predicting rear objects | 10.0 | +| time_resolution | [s] | double | Time resolution for the ego vehicle's predicted path | 0.5 | +| delay_until_departure | [s] | double | Delay until the ego vehicle departs | 1.0 | + +### Target Object Filtering Parameters + +Parameters under `target_filtering` are related to filtering target objects for safety check. + +| Name | Unit | Type | Description | Default value | +| :---------------------------------------------- | :---- | :----- | :------------------------------------------------- | :------------ | +| safety_check_time_horizon | [s] | double | Time horizon for safety check | 5.0 | +| safety_check_time_resolution | [s] | double | Time resolution for safety check | 1.0 | +| object_check_forward_distance | [m] | double | Forward distance for object detection | 10.0 | +| object_check_backward_distance | [m] | double | Backward distance for object detection | 100.0 | +| ignore_object_velocity_threshold | [m/s] | double | Velocity threshold below which objects are ignored | 1.0 | +| object_types_to_check.check_car | - | bool | Flag to check cars | true | +| object_types_to_check.check_truck | - | bool | Flag to check trucks | true | +| object_types_to_check.check_bus | - | bool | Flag to check buses | true | +| object_types_to_check.check_trailer | - | bool | Flag to check trailers | true | +| object_types_to_check.check_bicycle | - | bool | Flag to check bicycles | true | +| object_types_to_check.check_motorcycle | - | bool | Flag to check motorcycles | true | +| object_types_to_check.check_pedestrian | - | bool | Flag to check pedestrians | true | +| object_types_to_check.check_unknown | - | bool | Flag to check unknown object types | false | +| object_lane_configuration.check_current_lane | - | bool | Flag to check the current lane | true | +| object_lane_configuration.check_right_side_lane | - | bool | Flag to check the right side lane | true | +| object_lane_configuration.check_left_side_lane | - | bool | Flag to check the left side lane | true | +| object_lane_configuration.check_shoulder_lane | - | bool | Flag to check the shoulder lane | true | +| object_lane_configuration.check_other_lane | - | bool | Flag to check other lanes | false | +| include_opposite_lane | - | bool | Flag to include the opposite lane in check | false | +| invert_opposite_lane | - | bool | Flag to invert the opposite lane check | false | +| check_all_predicted_path | - | bool | Flag to check all predicted paths | true | +| use_all_predicted_path | - | bool | Flag to use all predicted paths | true | +| use_predicted_path_outside_lanelet | - | bool | Flag to use predicted paths outside of lanelets | false | + +### Safety Check Parameters + +Parameters under `safety_check_params` define the configuration for safety check. + +| Name | Unit | Type | Description | Default value | +| :--------------------------------------------- | :--- | :----- | :------------------------------------------ | :------------ | +| enable_safety_check | - | bool | Flag to enable safety check | true | +| check_all_predicted_path | - | bool | Flag to check all predicted paths | true | +| publish_debug_marker | - | bool | Flag to publish debug markers | false | +| rss_params.rear_vehicle_reaction_time | [s] | double | Reaction time for rear vehicles | 2.0 | +| rss_params.rear_vehicle_safety_time_margin | [s] | double | Safety time margin for rear vehicles | 1.0 | +| rss_params.lateral_distance_max_threshold | [m] | double | Maximum lateral distance threshold | 2.0 | +| rss_params.longitudinal_distance_min_threshold | [m] | double | Minimum longitudinal distance threshold | 3.0 | +| rss_params.longitudinal_velocity_delta_time | [s] | double | Delta time for longitudinal velocity | 0.8 | +| hysteresis_factor_expand_rate | - | double | Rate to expand/shrink the hysteresis factor | 1.0 | + ## **Path Generation** There are two path generation methods. @@ -113,9 +213,7 @@ Pull out distance is calculated by the speed, lateral deviation, and the lateral | maximum_lateral_jerk | [m/s3] | double | maximum lateral jerk | 2.0 | | minimum_lateral_jerk | [m/s3] | double | minimum lateral jerk | 0.1 | | minimum_shift_pull_out_distance | [m] | double | minimum shift pull out distance. if calculated pull out distance is shorter than this, use this for path generation. | 0.0 | -| maximum_curvature | [m] | double | maximum curvature. The pull out distance is calculated so that the curvature is smaller than this value. | - -| 0.07 | +| maximum_curvature | [m] | double | maximum curvature. The pull out distance is calculated so that the curvature is smaller than this value. | 0.07 | ### **geometric pull out** @@ -128,14 +226,14 @@ See also [[1]](https://www.sciencedirect.com/science/article/pii/S14746670153474 #### parameters for geometric pull out -| Name | Unit | Type | Description | Default value | -| :-------------------------- | :---- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | -| enable_geometric_pull_out | [-] | bool | flag whether to enable geometric pull out | true | -| divide_pull_out_path | [-] | bool | flag whether to divide arc paths. The path is assumed to be divided because the curvature is not continuous. But it requires a stop during the departure. | false | -| geometric_pull_out_velocity | [m/s] | double | velocity of geometric pull out | 1.0 | -| arc_path_interval | [m] | double | path points interval of arc paths of geometric pull out | 1.0 | -| lane_departure_margin | [m] | double | margin of deviation to lane right | 0.2 | -| pull_out_max_steer_angle | [rad] | double | maximum steer angle for path generation | 0.26 | +| Name | Unit | Type | Description | Default value | +| :-------------------------- | :---- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| enable_geometric_pull_out | [-] | bool | flag whether to enable geometric pull out | true | +| divide_pull_out_path | [-] | bool | flag whether to divide arc paths. The path is assumed to be divided because the curvature is not continuous. But it requires a stop during the departure. | false | +| geometric_pull_out_velocity | [m/s] | double | velocity of geometric pull out | 1.0 | +| arc_path_interval | [m] | double | path points interval of arc paths of geometric pull out | 1.0 | +| lane_departure_margin | [m] | double | margin of deviation to lane right | 0.2 | +| pull_out_max_steer_angle | [rad] | double | maximum steer angle for path generation | 0.26 | ## **backward pull out start point search** diff --git a/planning/behavior_path_planner/image/start_planner/collision_check_range.drawio.svg b/planning/behavior_path_planner/image/start_planner/collision_check_range.drawio.svg new file mode 100644 index 0000000000000..064f3602d6b6a --- /dev/null +++ b/planning/behavior_path_planner/image/start_planner/collision_check_range.drawio.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
safety check range
+
+
+ +
+
no-safety check range
+
+
+ +
+
safety check start point with vehicle frame
+
+
+ +
+
safety check end point with vehicle frame
+
+
+
From 21acc282c523ce05c36fb59c70cb5b4a076e5ae8 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Fri, 13 Oct 2023 13:27:22 +0900 Subject: [PATCH 457/547] feat(ndt_scan_matcher): implement tpe sampler in ndt_align_srv (#5078) * Added output_pose_with_cov_to_log Signed-off-by: Shintaro Sakoda * Added logging particles Signed-off-by: Shintaro Sakoda * Added TreeStructuredParzenEstimator Signed-off-by: Shintaro Sakoda * FIxed to run TPE Signed-off-by: Shintaro Sakoda * Fixed initial pose Signed-off-by: Shintaro Sakoda * Fixed Input type Signed-off-by: Shintaro Sakoda * Fixed good condition Signed-off-by: Shintaro Sakoda * Fixed operation if good_num_ == 0 Signed-off-by: Shintaro Sakoda * Fixed cov Signed-off-by: Shintaro Sakoda * Fixed parameter Signed-off-by: Shintaro Sakoda * Fixed initial Signed-off-by: Shintaro Sakoda * Fixed to use good_threshold_ Signed-off-by: Shintaro Sakoda * Fixed to count good_num_ Signed-off-by: Shintaro Sakoda * Fixed stddev Signed-off-by: Shintaro Sakoda * Fixed parameter Signed-off-by: Shintaro Sakoda * Fixed parameters Signed-off-by: Shintaro Sakoda * Removed unused functions Signed-off-by: Shintaro Sakoda * Fixed to abs Signed-off-by: Shintaro Sakoda * Fixed parameters Signed-off-by: Shintaro Sakoda * FIxed parameter Signed-off-by: Shintaro Sakoda * add_trial by result Signed-off-by: Shintaro Sakoda * FIxed constructor Signed-off-by: Shintaro Sakoda * FIxed to use n_startup_trials_ Signed-off-by: Shintaro Sakoda * Fixed parameters Signed-off-by: Shintaro Sakoda * Added kTargetScore Signed-off-by: Shintaro Sakoda * Fixed constants Signed-off-by: Shintaro Sakoda * Removed output_pose_with_cov_to_log Signed-off-by: Shintaro Sakoda * Fixed a comment Signed-off-by: Shintaro Sakoda * Added a parameter "n_startup_trials" Signed-off-by: Shintaro Sakoda * style(pre-commit): autofix * Fixed include guard Signed-off-by: Shintaro Sakoda * style(pre-commit): autofix * Fixed hyphen to underscore Signed-off-by: Shintaro Sakoda * Added sample_from_prior Signed-off-by: Shintaro Sakoda * Fixed to use log_sum_exp Signed-off-by: Shintaro Sakoda * Fixed acquisition_function Signed-off-by: Shintaro Sakoda * Fixed n_startup_trials_ Signed-off-by: Shintaro Sakoda * Fixed to 99% Signed-off-by: Shintaro Sakoda * Fixed n_startup_trials_ Signed-off-by: Shintaro Sakoda * Fixed value of n_startup_trials Signed-off-by: Shintaro Sakoda * Removed log output Signed-off-by: Shintaro Sakoda * Fixed default value Signed-off-by: Shintaro Sakoda * Removed static distributions Signed-off-by: Shintaro Sakoda * Fixed PRIOR_WEIGHT Signed-off-by: Shintaro Sakoda * Fixed order Signed-off-by: Shintaro Sakoda * Added operator* to Input Signed-off-by: Shintaro Sakoda * Fixed parameters Signed-off-by: Shintaro Sakoda * Implemented normal tpe Signed-off-by: Shintaro Sakoda * Added is_loop_variable Signed-off-by: Shintaro Sakoda * Organized PRIOR_WEIGHT Signed-off-by: Shintaro Sakoda * Fixed PRIOR_WEIGHT 0.0 Signed-off-by: Shintaro Sakoda * Added CONVERT_COEFF Signed-off-by: Shintaro Sakoda * Fixed n_startup_trials Signed-off-by: Shintaro Sakoda * Renamed acquisition_function to compute_log_likelihood_ratio Signed-off-by: Shintaro Sakoda * Changed random number generator to static variables Signed-off-by: Shintaro Sakoda * Added reference Signed-off-by: Shintaro Sakoda * Fixed base_stddev_ to a const variable Signed-off-by: Shintaro Sakoda * Fixed to use VALUE_WIDTH Signed-off-by: Shintaro Sakoda * Removed a comment Signed-off-by: Shintaro Sakoda * Added `n_startup_trials` to README.md Signed-off-by: Shintaro Sakoda * Added a test about tpe Signed-off-by: Shintaro Sakoda * Added the license statement Signed-off-by: Shintaro Sakoda * Changed default `n_startup_trials` to 20 Signed-off-by: Shintaro Sakoda * Fixed sqrt(2) and added comments Signed-off-by: Shintaro SAKODA * Added comments Signed-off-by: Shintaro SAKODA * Added comments Signed-off-by: Shintaro SAKODA --------- Signed-off-by: Shintaro Sakoda Signed-off-by: Shintaro SAKODA Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- localization/ndt_scan_matcher/CMakeLists.txt | 11 + localization/ndt_scan_matcher/README.md | 1 + .../config/ndt_scan_matcher.param.yaml | 5 + .../ndt_scan_matcher_core.hpp | 1 + .../tree_structured_parzen_estimator.hpp | 80 +++++++ .../src/ndt_scan_matcher_core.cpp | 84 ++++++- .../src/tree_structured_parzen_estimator.cpp | 217 ++++++++++++++++++ .../ndt_scan_matcher/test/test_tpe.cpp | 65 ++++++ 8 files changed, 459 insertions(+), 5 deletions(-) create mode 100644 localization/ndt_scan_matcher/include/ndt_scan_matcher/tree_structured_parzen_estimator.hpp create mode 100644 localization/ndt_scan_matcher/src/tree_structured_parzen_estimator.cpp create mode 100644 localization/ndt_scan_matcher/test/test_tpe.cpp diff --git a/localization/ndt_scan_matcher/CMakeLists.txt b/localization/ndt_scan_matcher/CMakeLists.txt index 1d5a9d5ac5320..6f51b5210d853 100644 --- a/localization/ndt_scan_matcher/CMakeLists.txt +++ b/localization/ndt_scan_matcher/CMakeLists.txt @@ -34,11 +34,22 @@ ament_auto_add_executable(ndt_scan_matcher src/tf2_listener_module.cpp src/map_module.cpp src/map_update_module.cpp + src/tree_structured_parzen_estimator.cpp ) link_directories(${PCL_LIBRARY_DIRS}) target_link_libraries(ndt_scan_matcher ${PCL_LIBRARIES}) +if(BUILD_TESTING) + find_package(ament_cmake_gtest REQUIRED) + ament_add_gtest(test_tpe + test/test_tpe.cpp + src/tree_structured_parzen_estimator.cpp + ) + target_include_directories(test_tpe PRIVATE include) + target_link_libraries(test_tpe) +endif() + ament_auto_package( INSTALL_TO_SHARE launch diff --git a/localization/ndt_scan_matcher/README.md b/localization/ndt_scan_matcher/README.md index a1b9339b9780a..1f7779890f89a 100644 --- a/localization/ndt_scan_matcher/README.md +++ b/localization/ndt_scan_matcher/README.md @@ -68,6 +68,7 @@ One optional function is regularization. Please see the regularization chapter i | `converged_param_transform_probability` | double | TP threshold for deciding whether to trust the estimation result (when converged_param_type = 0) | | `converged_param_nearest_voxel_transformation_likelihood` | double | NVTL threshold for deciding whether to trust the estimation result (when converged_param_type = 1) | | `initial_estimate_particles_num` | int | The number of particles to estimate initial pose | +| `n_startup_trials` | int | The number of initial random trials in the TPE (Tree-Structured Parzen Estimator). | | `lidar_topic_timeout_sec` | double | Tolerance of timestamp difference between current time and sensor pointcloud | | `initial_pose_timeout_sec` | int | Tolerance of timestamp difference between initial_pose and sensor pointcloud. [sec] | | `initial_pose_distance_tolerance_m` | double | Tolerance of distance difference between two initial poses used for linear interpolation. [m] | diff --git a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml index cf41a4cf55d0b..0ba1b1a2e15f4 100644 --- a/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml +++ b/localization/ndt_scan_matcher/config/ndt_scan_matcher.param.yaml @@ -43,6 +43,11 @@ # The number of particles to estimate initial pose initial_estimate_particles_num: 200 + # The number of initial random trials in the TPE (Tree-Structured Parzen Estimator). + # This value should be equal to or less than 'initial_estimate_particles_num' and more than 0. + # If it is equal to 'initial_estimate_particles_num', the search will be the same as a full random search. + n_startup_trials: 20 + # Tolerance of timestamp difference between current time and sensor pointcloud. [sec] lidar_topic_timeout_sec: 1.0 diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp index 011cda6ba3356..fc4015aede059 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp @@ -179,6 +179,7 @@ class NDTScanMatcher : public rclcpp::Node double converged_param_nearest_voxel_transformation_likelihood_; int initial_estimate_particles_num_; + int n_startup_trials_; double lidar_topic_timeout_sec_; double initial_pose_timeout_sec_; double initial_pose_distance_tolerance_m_; diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/tree_structured_parzen_estimator.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/tree_structured_parzen_estimator.hpp new file mode 100644 index 0000000000000..3c79ab75dba48 --- /dev/null +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/tree_structured_parzen_estimator.hpp @@ -0,0 +1,80 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NDT_SCAN_MATCHER__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ +#define NDT_SCAN_MATCHER__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ + +/* +A implementation of tree-structured parzen estimator (TPE) +See below pdf for the TPE algorithm detail. +https://papers.nips.cc/paper_files/paper/2011/file/86e8f7ab32cfd12577bc2619bc635690-Paper.pdf + +Optuna is also used as a reference for implementation. +https://github.com/optuna/optuna +*/ + +#include +#include +#include + +class TreeStructuredParzenEstimator +{ +public: + using Input = std::vector; + using Score = double; + struct Trial + { + Input input; + Score score; + }; + + enum Direction { + MINIMIZE = 0, + MAXIMIZE = 1, + }; + + TreeStructuredParzenEstimator() = delete; + TreeStructuredParzenEstimator( + const Direction direction, const int64_t n_startup_trials, std::vector is_loop_variable); + void add_trial(const Trial & trial); + Input get_next_input() const; + +private: + static constexpr double BASE_STDDEV_COEFF = 0.2; + static constexpr double MAX_GOOD_RATE = 0.10; + static constexpr double MAX_VALUE = 1.0; + static constexpr double MIN_VALUE = -1.0; + static constexpr double VALUE_WIDTH = MAX_VALUE - MIN_VALUE; + static constexpr int64_t N_EI_CANDIDATES = 100; + static constexpr double PRIOR_WEIGHT = 0.0; + + static std::mt19937_64 engine; + static std::uniform_real_distribution dist_uniform; + static std::normal_distribution dist_normal; + + double compute_log_likelihood_ratio(const Input & input) const; + double log_gaussian_pdf(const Input & input, const Input & mu, const Input & sigma) const; + static std::vector get_weights(const int64_t n); + static double normalize_loop_variable(const double value); + + std::vector trials_; + int64_t above_num_; + const Direction direction_; + const int64_t n_startup_trials_; + const int64_t input_dimension_; + const std::vector is_loop_variable_; + const Input base_stddev_; +}; + +#endif // NDT_SCAN_MATCHER__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index d9054d77d2256..238e5c6be89bc 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -17,11 +17,14 @@ #include "ndt_scan_matcher/matrix_type.hpp" #include "ndt_scan_matcher/particle.hpp" #include "ndt_scan_matcher/pose_array_interpolator.hpp" +#include "ndt_scan_matcher/tree_structured_parzen_estimator.hpp" #include "ndt_scan_matcher/util_func.hpp" #include #include +#include + #include #ifdef ROS_DISTRO_GALACTIC @@ -145,6 +148,7 @@ NDTScanMatcher::NDTScanMatcher() } initial_estimate_particles_num_ = this->declare_parameter("initial_estimate_particles_num"); + n_startup_trials_ = this->declare_parameter("n_startup_trials"); estimate_scores_for_degrounded_scan_ = this->declare_parameter("estimate_scores_for_degrounded_scan"); @@ -784,15 +788,64 @@ geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_ output_pose_with_cov_to_log(get_logger(), "align_using_monte_carlo_input", initial_pose_with_cov); - // generateParticle - const auto initial_poses = - create_random_pose_array(initial_pose_with_cov, initial_estimate_particles_num_); + const auto base_rpy = get_rpy(initial_pose_with_cov); + const Eigen::Map covariance = { + initial_pose_with_cov.pose.covariance.data(), 6, 6}; + const double stddev_x = std::sqrt(covariance(0, 0)); + const double stddev_y = std::sqrt(covariance(1, 1)); + const double stddev_z = std::sqrt(covariance(2, 2)); + const double stddev_roll = std::sqrt(covariance(3, 3)); + const double stddev_pitch = std::sqrt(covariance(4, 4)); + + // Let phi be the cumulative distribution function of the standard normal distribution. + // It has the following relationship with the error function (erf). + // phi(x) = 1/2 (1 + erf(x / sqrt(2))) + // so, 2 * phi(x) - 1 = erf(x / sqrt(2)). + // The range taken by 2 * phi(x) - 1 is [-1, 1], so it can be used as a uniform distribution in + // TPE. Let u = 2 * phi(x) - 1, then x = sqrt(2) * erf_inv(u). Computationally, it is not a good + // to give erf_inv -1 and 1, so it is rounded off at (-1 + eps, 1 - eps). + const double SQRT2 = std::sqrt(2); + auto uniform_to_normal = [&SQRT2](const double uniform) { + assert(-1.0 <= uniform && uniform <= 1.0); + constexpr double epsilon = 1.0e-6; + const double clamped = std::clamp(uniform, -1.0 + epsilon, 1.0 - epsilon); + return boost::math::erf_inv(clamped) * SQRT2; + }; + + auto normal_to_uniform = [&SQRT2](const double normal) { + return boost::math::erf(normal / SQRT2); + }; + + // Optimizing (x, y, z, roll, pitch, yaw) 6 dimensions. + // The last dimension (yaw) is a loop variable. + // Although roll and pitch are also angles, they are considered non-looping variables that follow + // a normal distribution with a small standard deviation. This assumes that the initial pose of + // the ego vehicle is aligned with the ground to some extent about roll and pitch. + const std::vector is_loop_variable = {false, false, false, false, false, true}; + TreeStructuredParzenEstimator tpe( + TreeStructuredParzenEstimator::Direction::MAXIMIZE, n_startup_trials_, is_loop_variable); std::vector particle_array; auto output_cloud = std::make_shared>(); - for (unsigned int i = 0; i < initial_poses.size(); i++) { - const auto & initial_pose = initial_poses[i]; + for (int i = 0; i < initial_estimate_particles_num_; i++) { + const TreeStructuredParzenEstimator::Input input = tpe.get_next_input(); + + geometry_msgs::msg::Pose initial_pose; + initial_pose.position.x = + initial_pose_with_cov.pose.pose.position.x + uniform_to_normal(input[0]) * stddev_x; + initial_pose.position.y = + initial_pose_with_cov.pose.pose.position.y + uniform_to_normal(input[1]) * stddev_y; + initial_pose.position.z = + initial_pose_with_cov.pose.pose.position.z + uniform_to_normal(input[2]) * stddev_z; + geometry_msgs::msg::Vector3 init_rpy; + init_rpy.x = base_rpy.x + uniform_to_normal(input[3]) * stddev_roll; + init_rpy.y = base_rpy.y + uniform_to_normal(input[4]) * stddev_pitch; + init_rpy.z = base_rpy.z + input[5] * M_PI; + tf2::Quaternion tf_quaternion; + tf_quaternion.setRPY(init_rpy.x, init_rpy.y, init_rpy.z); + initial_pose.orientation = tf2::toMsg(tf_quaternion); + const Eigen::Matrix4f initial_pose_matrix = pose_to_matrix4f(initial_pose); ndt_ptr->align(*output_cloud, initial_pose_matrix); const pclomp::NdtResult ndt_result = ndt_ptr->getResult(); @@ -806,6 +859,27 @@ geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_ particle, i); ndt_monte_carlo_initial_pose_marker_pub_->publish(marker_array); + const geometry_msgs::msg::Pose pose = matrix4f_to_pose(ndt_result.pose); + const geometry_msgs::msg::Vector3 rpy = get_rpy(pose); + + const double diff_x = pose.position.x - initial_pose_with_cov.pose.pose.position.x; + const double diff_y = pose.position.y - initial_pose_with_cov.pose.pose.position.y; + const double diff_z = pose.position.z - initial_pose_with_cov.pose.pose.position.z; + const double diff_roll = rpy.x - base_rpy.x; + const double diff_pitch = rpy.y - base_rpy.y; + const double diff_yaw = rpy.z - base_rpy.z; + + // Only yaw is a loop_variable, so only simple normalization is performed. + // All other variables are converted from normal distribution to uniform distribution. + TreeStructuredParzenEstimator::Input result(is_loop_variable.size()); + result[0] = normal_to_uniform(diff_x / stddev_x); + result[1] = normal_to_uniform(diff_y / stddev_y); + result[2] = normal_to_uniform(diff_z / stddev_z); + result[3] = normal_to_uniform(diff_roll / stddev_roll); + result[4] = normal_to_uniform(diff_pitch / stddev_pitch); + result[5] = diff_yaw / M_PI; + tpe.add_trial(TreeStructuredParzenEstimator::Trial{result, ndt_result.transform_probability}); + auto sensor_points_in_map_ptr = std::make_shared>(); tier4_autoware_utils::transformPointCloud( *ndt_ptr->getInputSource(), *sensor_points_in_map_ptr, ndt_result.pose); diff --git a/localization/ndt_scan_matcher/src/tree_structured_parzen_estimator.cpp b/localization/ndt_scan_matcher/src/tree_structured_parzen_estimator.cpp new file mode 100644 index 0000000000000..b92c8a075252c --- /dev/null +++ b/localization/ndt_scan_matcher/src/tree_structured_parzen_estimator.cpp @@ -0,0 +1,217 @@ +// Copyright 2023 Autoware Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ndt_scan_matcher/tree_structured_parzen_estimator.hpp" + +#include +#include +#include +#include + +// random number generator +std::mt19937_64 TreeStructuredParzenEstimator::engine(std::random_device{}()); +std::uniform_real_distribution TreeStructuredParzenEstimator::dist_uniform( + TreeStructuredParzenEstimator::MIN_VALUE, TreeStructuredParzenEstimator::MAX_VALUE); +std::normal_distribution TreeStructuredParzenEstimator::dist_normal(0.0, 1.0); + +TreeStructuredParzenEstimator::TreeStructuredParzenEstimator( + const Direction direction, const int64_t n_startup_trials, std::vector is_loop_variable) +: above_num_(0), + direction_(direction), + n_startup_trials_(n_startup_trials), + input_dimension_(is_loop_variable.size()), + is_loop_variable_(is_loop_variable), + base_stddev_(input_dimension_, VALUE_WIDTH) +{ +} + +void TreeStructuredParzenEstimator::add_trial(const Trial & trial) +{ + trials_.push_back(trial); + std::sort(trials_.begin(), trials_.end(), [this](const Trial & lhs, const Trial & rhs) { + return (direction_ == Direction::MAXIMIZE ? lhs.score > rhs.score : lhs.score < rhs.score); + }); + above_num_ = + std::min(static_cast(25), static_cast(trials_.size() * MAX_GOOD_RATE)); +} + +TreeStructuredParzenEstimator::Input TreeStructuredParzenEstimator::get_next_input() const +{ + if (static_cast(trials_.size()) < n_startup_trials_ || above_num_ == 0) { + // Random sampling based on prior until the number of trials reaches `n_startup_trials_`. + Input input(input_dimension_); + for (int64_t j = 0; j < input_dimension_; j++) { + input[j] = dist_uniform(engine); + } + return input; + } + + Input best_input; + double best_log_likelihood_ratio = std::numeric_limits::lowest(); + const double coeff = BASE_STDDEV_COEFF * std::pow(above_num_, -1.0 / (4 + input_dimension_)); + std::vector weights = get_weights(above_num_); + weights.push_back(PRIOR_WEIGHT); + std::discrete_distribution dist(weights.begin(), weights.end()); + for (int64_t i = 0; i < N_EI_CANDIDATES; i++) { + Input mu, sigma; + const int64_t index = dist(engine); + if (index == above_num_) { + mu = Input(input_dimension_, 0.0); + sigma = base_stddev_; + } else { + mu = trials_[index].input; + sigma = base_stddev_; + for (int64_t j = 0; j < input_dimension_; j++) { + sigma[j] *= coeff; + } + } + // sample from the normal distribution + Input input(input_dimension_); + for (int64_t j = 0; j < input_dimension_; j++) { + input[j] = mu[j] + dist_normal(engine) * sigma[j]; + input[j] = + (is_loop_variable_[j] ? normalize_loop_variable(input[j]) + : std::clamp(input[j], MIN_VALUE, MAX_VALUE)); + } + const double log_likelihood_ratio = compute_log_likelihood_ratio(input); + if (log_likelihood_ratio > best_log_likelihood_ratio) { + best_log_likelihood_ratio = log_likelihood_ratio; + best_input = input; + } + } + return best_input; +} + +double TreeStructuredParzenEstimator::compute_log_likelihood_ratio(const Input & input) const +{ + const int64_t n = trials_.size(); + + // The above KDE and the below KDE are calculated respectively, and the ratio is the criteria to + // select best sample. + std::vector above_logs; + std::vector below_logs; + + // Scott's rule + const double coeff_above = + BASE_STDDEV_COEFF * std::pow(above_num_, -1.0 / (4 + input_dimension_)); + const double coeff_below = + BASE_STDDEV_COEFF * std::pow(n - above_num_, -1.0 / (4 + input_dimension_)); + Input sigma_above = base_stddev_; + Input sigma_below = base_stddev_; + for (int64_t j = 0; j < input_dimension_; j++) { + sigma_above[j] *= coeff_above; + sigma_below[j] *= coeff_below; + } + + std::vector above_weights = get_weights(above_num_); + std::vector below_weights = get_weights(n - above_num_); + std::reverse(below_weights.begin(), below_weights.end()); // below_weights is ascending order + + // calculate the sum of weights to normalize + double above_sum = std::accumulate(above_weights.begin(), above_weights.end(), 0.0); + double below_sum = std::accumulate(below_weights.begin(), below_weights.end(), 0.0); + + // above includes prior + above_sum += PRIOR_WEIGHT; + + for (int64_t i = 0; i < n; i++) { + if (i < above_num_) { + const double log_p = log_gaussian_pdf(input, trials_[i].input, sigma_above); + const double w = above_weights[i] / above_sum; + const double log_w = std::log(w); + above_logs.push_back(log_p + log_w); + } else { + const double log_p = log_gaussian_pdf(input, trials_[i].input, sigma_below); + const double w = below_weights[i - above_num_] / below_sum; + const double log_w = std::log(w); + below_logs.push_back(log_p + log_w); + } + } + + // prior + if (PRIOR_WEIGHT > 0.0) { + const double log_p = log_gaussian_pdf(input, Input(input_dimension_, 0.0), base_stddev_); + const double log_w = std::log(PRIOR_WEIGHT / above_sum); + above_logs.push_back(log_p + log_w); + } + + auto log_sum_exp = [](const std::vector & log_vec) { + const double max = *std::max_element(log_vec.begin(), log_vec.end()); + double sum = 0.0; + for (const double log_v : log_vec) { + sum += std::exp(log_v - max); + } + return max + std::log(sum); + }; + + const double above = log_sum_exp(above_logs); + const double below = log_sum_exp(below_logs); + const double r = above - below; + return r; +} + +double TreeStructuredParzenEstimator::log_gaussian_pdf( + const Input & input, const Input & mu, const Input & sigma) const +{ + const double log_2pi = std::log(2.0 * M_PI); + auto log_gaussian_pdf_1d = [&](const double diff, const double sigma) { + return -0.5 * log_2pi - std::log(sigma) - (diff * diff) / (2.0 * sigma * sigma); + }; + + const int64_t n = input.size(); + + double result = 0.0; + for (int64_t i = 0; i < n; i++) { + double diff = input[i] - mu[i]; + if (is_loop_variable_[i]) { + diff = normalize_loop_variable(diff); + } + result += log_gaussian_pdf_1d(diff, sigma[i]); + } + return result; +} + +std::vector TreeStructuredParzenEstimator::get_weights(const int64_t n) +{ + // See optuna + // https://github.com/optuna/optuna/blob/4bfab78e98bf786f6a2ce6e593a26e3f8403e08d/optuna/samplers/_tpe/sampler.py#L50-L58 + std::vector weights; + constexpr int64_t WEIGHT_ALPHA = 25; + if (n == 0) { + return weights; + } else if (n < WEIGHT_ALPHA) { + weights.resize(n, 1.0); + } else { + weights.resize(n); + const double unit = (1.0 - 1.0 / n) / (n - WEIGHT_ALPHA); + for (int64_t i = 0; i < n; i++) { + weights[i] = (i < WEIGHT_ALPHA ? 1.0 : 1.0 - unit * (i - WEIGHT_ALPHA)); + } + } + + return weights; +} + +double TreeStructuredParzenEstimator::normalize_loop_variable(const double value) +{ + // Normalize the loop variable to [-1, 1) + double result = value; + while (result >= MAX_VALUE) { + result -= VALUE_WIDTH; + } + while (result < MIN_VALUE) { + result += VALUE_WIDTH; + } + return result; +} diff --git a/localization/ndt_scan_matcher/test/test_tpe.cpp b/localization/ndt_scan_matcher/test/test_tpe.cpp new file mode 100644 index 0000000000000..fb7190f4a1c74 --- /dev/null +++ b/localization/ndt_scan_matcher/test/test_tpe.cpp @@ -0,0 +1,65 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ndt_scan_matcher/tree_structured_parzen_estimator.hpp" + +#include + +TEST(TreeStructuredParzenEstimatorTest, TPE_is_better_than_random_search_on_sphere_function) +{ + auto sphere_function = [](const TreeStructuredParzenEstimator::Input & input) { + double value = 0.0; + const int64_t n = input.size(); + for (int64_t i = 0; i < n; i++) { + const double v = input[i] * 10; + value += v * v; + } + return value; + }; + + constexpr int64_t kOuterTrialsNum = 10; + constexpr int64_t kInnerTrialsNum = 100; + std::cout << std::fixed; + std::vector mean_scores; + for (const int64_t n_startup_trials : {kInnerTrialsNum, kInnerTrialsNum / 10}) { + const std::string method = ((n_startup_trials == kInnerTrialsNum) ? "Random" : "TPE"); + + std::vector scores; + for (int64_t i = 0; i < kOuterTrialsNum; i++) { + double best_score = std::numeric_limits::lowest(); + const std::vector is_loop_variable(6, false); + TreeStructuredParzenEstimator estimator( + TreeStructuredParzenEstimator::Direction::MAXIMIZE, n_startup_trials, is_loop_variable); + for (int64_t trial = 0; trial < kInnerTrialsNum; trial++) { + const TreeStructuredParzenEstimator::Input input = estimator.get_next_input(); + const double score = -sphere_function(input); + estimator.add_trial({input, score}); + best_score = std::max(best_score, score); + } + scores.push_back(best_score); + } + + const double sum = std::accumulate(scores.begin(), scores.end(), 0.0); + const double mean = sum / scores.size(); + mean_scores.push_back(mean); + double sq_sum = 0.0; + for (const double score : scores) { + sq_sum += (score - mean) * (score - mean); + } + const double stddev = std::sqrt(sq_sum / scores.size()); + + std::cout << method << ", mean = " << mean << ", stddev = " << stddev << std::endl; + } + ASSERT_LT(mean_scores[0], mean_scores[1]); +} From 69560ad3f1ccf7576329c75efdc5ec3d11d94d88 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Fri, 13 Oct 2023 13:39:32 +0900 Subject: [PATCH 458/547] fix(lane_change): fix force path not appear (#5288) Signed-off-by: Muhammad Zulfaqar Azmi --- .../src/scene_module/lane_change/normal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 913ddcacf08bc..21fa1495fba75 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1222,12 +1222,12 @@ bool NormalLaneChange::getLaneChangePaths( logger_, "Stop time is over threshold. Allow lane change in intersection."); } + candidate_paths->push_back(*candidate_path); if (utils::lane_change::passParkedObject( route_handler, *candidate_path, target_objects.target_lane, lane_change_buffer, is_goal_in_route, *lane_change_parameters_)) { return false; } - candidate_paths->push_back(*candidate_path); if (!check_safety) { return false; From df836214c7443f4ad92e5abc9ee601c1cae4bf5b Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Fri, 13 Oct 2023 13:41:39 +0900 Subject: [PATCH 459/547] fix(lane_change): prevent node from dying for trimmed shifted path (#5271) Signed-off-by: Zulfaqar Azmi --- .../src/utils/lane_change/utils.cpp | 9 +++++++++ planning/behavior_path_planner/src/utils/path_utils.cpp | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index 038cf9488345f..a76b3cbbaf1e0 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -301,6 +301,15 @@ std::optional constructCandidatePath( "failed to generate shifted path."); } + // TODO(Zulfaqar Azmi): have to think of a more feasible solution for points being remove by path + // shifter. + if (shifted_path.path.points.size() < shift_line.end_idx + 1) { + RCLCPP_DEBUG( + rclcpp::get_logger("behavior_path_planner").get_child("utils").get_child(__func__), + "path points are removed by PathShifter."); + return std::nullopt; + } + const auto & prepare_length = lane_change_length.prepare; const auto & lane_changing_length = lane_change_length.lane_changing; diff --git a/planning/behavior_path_planner/src/utils/path_utils.cpp b/planning/behavior_path_planner/src/utils/path_utils.cpp index 509b31ad3da2b..1ac63f4a4be87 100644 --- a/planning/behavior_path_planner/src/utils/path_utils.cpp +++ b/planning/behavior_path_planner/src/utils/path_utils.cpp @@ -226,7 +226,7 @@ std::pair getPathTurnSignal( turn_signal.command = TurnIndicatorsCommand::NO_COMMAND; const double max_time = std::numeric_limits::max(); const double max_distance = std::numeric_limits::max(); - if (path.shift_length.empty()) { + if (path.shift_length.size() < shift_line.end_idx + 1) { return std::make_pair(turn_signal, max_distance); } const auto base_link2front = common_parameter.base_link2front; From f1cf22b1d44b20a33e7df1533bef5a8854e29562 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Fri, 13 Oct 2023 13:45:12 +0900 Subject: [PATCH 460/547] fix(lane_change): add visualization for passParkedObject (#5291) Signed-off-by: Muhammad Zulfaqar Azmi --- .../behavior_path_planner/utils/lane_change/utils.hpp | 4 +++- .../src/scene_module/lane_change/normal.cpp | 2 +- .../behavior_path_planner/src/utils/lane_change/utils.cpp | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index b2592bc1b899b..cd8f293b2e610 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -52,6 +52,7 @@ using data::lane_change::PathSafetyStatus; using geometry_msgs::msg::Point; using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; +using path_safety_checker::CollisionCheckDebugMap; using route_handler::Direction; using tier4_autoware_utils::Polygon2d; @@ -170,7 +171,8 @@ bool isParkedObject( bool passParkedObject( const RouteHandler & route_handler, const LaneChangePath & lane_change_path, const std::vector & objects, const double minimum_lane_change_length, - const bool is_goal_in_route, const LaneChangeParameters & lane_change_parameters); + const bool is_goal_in_route, const LaneChangeParameters & lane_change_parameters, + CollisionCheckDebugMap & object_debug); boost::optional getLeadingStaticObjectIdx( const RouteHandler & route_handler, const LaneChangePath & lane_change_path, diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 21fa1495fba75..375bd1e803d58 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1225,7 +1225,7 @@ bool NormalLaneChange::getLaneChangePaths( candidate_paths->push_back(*candidate_path); if (utils::lane_change::passParkedObject( route_handler, *candidate_path, target_objects.target_lane, lane_change_buffer, - is_goal_in_route, *lane_change_parameters_)) { + is_goal_in_route, *lane_change_parameters_, object_debug_)) { return false; } diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index a76b3cbbaf1e0..6d25cb8751a8e 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -939,7 +939,8 @@ bool isParkedObject( bool passParkedObject( const RouteHandler & route_handler, const LaneChangePath & lane_change_path, const std::vector & objects, const double minimum_lane_change_length, - const bool is_goal_in_route, const LaneChangeParameters & lane_change_parameters) + const bool is_goal_in_route, const LaneChangeParameters & lane_change_parameters, + CollisionCheckDebugMap & object_debug) { const auto & object_check_min_road_shoulder_width = lane_change_parameters.object_check_min_road_shoulder_width; @@ -962,6 +963,7 @@ bool passParkedObject( } const auto & leading_obj = objects.at(*leading_obj_idx); + auto debug = marker_utils::createObjectDebug(leading_obj); const auto leading_obj_poly = tier4_autoware_utils::toPolygon2d(leading_obj.initial_pose.pose, leading_obj.shape); if (leading_obj_poly.outer().empty()) { @@ -985,6 +987,8 @@ bool passParkedObject( // If there are still enough length after the target object, we delay the lane change if (min_dist_to_end_of_current_lane > minimum_lane_change_length) { + debug.second.unsafe_reason = "delay lane change"; + marker_utils::updateCollisionCheckDebugMap(object_debug, debug, false); return true; } From 61c7e3205466fd6bd80a8fa6432f268d65a788d2 Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Fri, 13 Oct 2023 14:11:55 +0900 Subject: [PATCH 461/547] refactor(map_packages): remove unused depend in pakcages.xml files (#5172) Signed-off-by: yamato-ando Co-authored-by: yamato-ando --- map/map_height_fitter/package.xml | 1 - map/map_loader/CMakeLists.txt | 2 +- map/map_loader/package.xml | 5 ----- map/map_projection_loader/package.xml | 1 - map/util/lanelet2_map_preprocessor/CMakeLists.txt | 6 ++++++ map/util/lanelet2_map_preprocessor/package.xml | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/map/map_height_fitter/package.xml b/map/map_height_fitter/package.xml index 8f6ccf969c9a4..f50eba9218d67 100644 --- a/map/map_height_fitter/package.xml +++ b/map/map_height_fitter/package.xml @@ -19,7 +19,6 @@ pcl_conversions rclcpp sensor_msgs - tf2 tf2_geometry_msgs tf2_ros tier4_localization_msgs diff --git a/map/map_loader/CMakeLists.txt b/map/map_loader/CMakeLists.txt index 745edd0daca99..b94eaac7ee34d 100644 --- a/map/map_loader/CMakeLists.txt +++ b/map/map_loader/CMakeLists.txt @@ -4,7 +4,7 @@ project(map_loader) find_package(autoware_cmake REQUIRED) autoware_package() -find_package(PCL REQUIRED COMPONENTS io) +find_package(PCL REQUIRED COMPONENTS common io filters) ament_auto_add_library(pointcloud_map_loader_node SHARED src/pointcloud_map_loader/pointcloud_map_loader_node.cpp diff --git a/map/map_loader/package.xml b/map/map_loader/package.xml index e663d19d516ac..5230d4bd03214 100644 --- a/map/map_loader/package.xml +++ b/map/map_loader/package.xml @@ -24,13 +24,8 @@ lanelet2_extension libpcl-all-dev pcl_conversions - pcl_ros rclcpp rclcpp_components - std_msgs - tf2_geometry_msgs - tf2_ros - tier4_autoware_utils tier4_map_msgs visualization_msgs yaml-cpp diff --git a/map/map_projection_loader/package.xml b/map/map_projection_loader/package.xml index 03053533d3ead..b128c2cf15e15 100644 --- a/map/map_projection_loader/package.xml +++ b/map/map_projection_loader/package.xml @@ -16,7 +16,6 @@ component_interface_utils lanelet2_extension rclcpp - rclcpp_components tier4_map_msgs yaml-cpp diff --git a/map/util/lanelet2_map_preprocessor/CMakeLists.txt b/map/util/lanelet2_map_preprocessor/CMakeLists.txt index caa1d27b3e292..8a6b16c05011b 100644 --- a/map/util/lanelet2_map_preprocessor/CMakeLists.txt +++ b/map/util/lanelet2_map_preprocessor/CMakeLists.txt @@ -4,12 +4,18 @@ project(lanelet2_map_preprocessor) find_package(autoware_cmake REQUIRED) autoware_package() +find_package(PCL REQUIRED COMPONENTS common io kdtree) + include_directories( include SYSTEM ${PCL_INCLUDE_DIRS} ) +link_libraries( + ${PCL_LIBRARIES} +) + ament_auto_add_executable(fix_z_value_by_pcd src/fix_z_value_by_pcd.cpp) ament_auto_add_executable(transform_maps src/transform_maps.cpp) ament_auto_add_executable(merge_close_lines src/merge_close_lines.cpp) diff --git a/map/util/lanelet2_map_preprocessor/package.xml b/map/util/lanelet2_map_preprocessor/package.xml index 15f061369e649..79fce94f87a95 100644 --- a/map/util/lanelet2_map_preprocessor/package.xml +++ b/map/util/lanelet2_map_preprocessor/package.xml @@ -11,7 +11,7 @@ autoware_cmake lanelet2_extension - pcl_ros + libpcl-all-dev rclcpp ament_lint_auto From 910f30bcd776b48b9c7dadcc3b638236aa4006ab Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Fri, 13 Oct 2023 15:43:51 +0900 Subject: [PATCH 462/547] fix(behavior_path_planner): fix calcMinimumLongitudinalLength args (#5299) Signed-off-by: kosuke55 --- .../utils/path_safety_checker/safety_check.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 4983060aa1c0a..6e5dd2cda8920 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -80,7 +80,7 @@ double calcRssDistance( double calcMinimumLongitudinalLength( const double front_object_velocity, const double rear_object_velocity, - const BehaviorPathPlannerParameters & params); + const RSSparams & rss_params); boost::optional calcInterpolatedPoseWithVelocity( const std::vector & path, const double relative_time); From d9abd595eecd73796ba4fd481c9f395760c187be Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Fri, 13 Oct 2023 19:28:50 +0900 Subject: [PATCH 463/547] fix(lane_change): update target lane obj block condition (#5296) fix(lane_change): target lane blocking condition remove in_terminal condition fix Signed-off-by: Takamasa Horibe --- .../src/scene_module/lane_change/normal.cpp | 122 ++++++++++-------- 1 file changed, 68 insertions(+), 54 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 375bd1e803d58..c332438dd31a5 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -194,79 +194,93 @@ void NormalLaneChange::insertStopPoint( const double lane_change_buffer = utils::calcMinimumLaneChangeLength(getCommonParam(), shift_intervals, 0.0); + const auto getDistanceAlongLanelet = [&](const geometry_msgs::msg::Pose & target) { + return utils::getSignedDistance(path.points.front().point.pose, target, lanelets); + }; + // If lanelets.back() is in goal route section, get distance to goal. // Otherwise, get distance to end of lane. double distance_to_terminal = 0.0; if (route_handler->isInGoalRouteSection(lanelets.back())) { const auto goal = route_handler->getGoalPose(); - distance_to_terminal = utils::getSignedDistance(path.points.front().point.pose, goal, lanelets); + distance_to_terminal = getDistanceAlongLanelet(goal); } else { distance_to_terminal = utils::getDistanceToEndOfLane(path.points.front().point.pose, lanelets); } const double stop_point_buffer = getCommonParam().backward_length_buffer_for_end_of_lane; const auto target_objects = getTargetObjects(status_.current_lanes, status_.target_lanes); - const bool is_in_terminal_section = lanelet::utils::isInLanelet(getEgoPose(), lanelets.back()) || - distance_to_terminal < lane_change_buffer; - const bool has_blocking_target_lane_obj = std::any_of( - target_objects.target_lane.begin(), target_objects.target_lane.end(), [&](const auto & o) { - if (o.initial_twist.twist.linear.x > lane_change_parameters_->stop_velocity_threshold) { - return false; + double stopping_distance = distance_to_terminal - lane_change_buffer - stop_point_buffer; + + // calculate minimum distance from path front to the stationary object on the ego lane. + const auto distance_to_ego_lane_obj = [&]() -> double { + double distance_to_obj = distance_to_terminal; + const double distance_to_ego = getDistanceAlongLanelet(getEgoPose()); + + for (const auto & object : target_objects.current_lane) { + // check if stationary + const auto obj_v = std::abs(object.initial_twist.twist.linear.x); + if (obj_v > lane_change_parameters_->stop_velocity_threshold) { + continue; } - const double distance_to_target_lane_obj = utils::getSignedDistance( - path.points.front().point.pose, o.initial_pose.pose, status_.target_lanes); - return distance_to_target_lane_obj < distance_to_terminal; - }); - // auto stopping_distance = raw_stopping_distance; - double stopping_distance = distance_to_terminal - lane_change_buffer - stop_point_buffer; - if (is_in_terminal_section || !has_blocking_target_lane_obj) { - // calculate minimum distance from path front to the stationary object on the ego lane. - const auto distance_to_ego_lane_obj = [&]() -> double { - double distance_to_obj = distance_to_terminal; - const double distance_to_ego = - utils::getSignedDistance(path.points.front().point.pose, getEgoPose(), lanelets); - - for (const auto & object : target_objects.current_lane) { - // check if stationary - const auto obj_v = std::abs(object.initial_twist.twist.linear.x); - if (obj_v > lane_change_parameters_->stop_velocity_threshold) { + // calculate distance from path front to the stationary object polygon on the ego lane. + const auto polygon = + tier4_autoware_utils::toPolygon2d(object.initial_pose.pose, object.shape).outer(); + for (const auto & polygon_p : polygon) { + const auto p_fp = tier4_autoware_utils::toMsg(polygon_p.to_3d()); + const auto lateral_fp = motion_utils::calcLateralOffset(path.points, p_fp); + + // ignore if the point is around the ego path + if (std::abs(lateral_fp) > planner_data_->parameters.vehicle_width) { continue; } - // calculate distance from path front to the stationary object polygon on the ego lane. - const auto polygon = - tier4_autoware_utils::toPolygon2d(object.initial_pose.pose, object.shape).outer(); - for (const auto & polygon_p : polygon) { - const auto p_fp = tier4_autoware_utils::toMsg(polygon_p.to_3d()); - const auto lateral_fp = motion_utils::calcLateralOffset(path.points, p_fp); + const double current_distance_to_obj = calcSignedArcLength(path.points, 0, p_fp); - // ignore if the point is around the ego path - if (std::abs(lateral_fp) > planner_data_->parameters.vehicle_width) { - continue; - } - - const double current_distance_to_obj = calcSignedArcLength(path.points, 0, p_fp); - - // ignore backward object - if (current_distance_to_obj < distance_to_ego) { - continue; - } - distance_to_obj = std::min(distance_to_obj, current_distance_to_obj); + // ignore backward object + if (current_distance_to_obj < distance_to_ego) { + continue; } + distance_to_obj = std::min(distance_to_obj, current_distance_to_obj); } - return distance_to_obj; - }(); - - // If the lane change space is occupied by a stationary obstacle, move the stop line closer. - // TODO(Horibe): We need to loop this process because the new space could be occupied as well. - stopping_distance = std::min( - distance_to_ego_lane_obj - lane_change_buffer - stop_point_buffer - - getCommonParam().base_link2front - - calcRssDistance( - 0.0, planner_data_->parameters.minimum_lane_changing_velocity, - lane_change_parameters_->rss_params), - stopping_distance); + } + return distance_to_obj; + }(); + + // Need to stop before blocking obstacle + if (distance_to_ego_lane_obj < distance_to_terminal) { + // consider rss distance when the LC need to avoid obstacles + const auto rss_dist = calcRssDistance( + 0.0, planner_data_->parameters.minimum_lane_changing_velocity, + lane_change_parameters_->rss_params); + + const auto stopping_distance_for_obj = distance_to_ego_lane_obj - lane_change_buffer - + stop_point_buffer - rss_dist - + getCommonParam().base_link2front; + + // If the target lane in the lane change section is blocked by a stationary obstacle, there + // is no reason for stopping with a lane change margin. Instead, stop right behind the + // obstacle. + // ---------------------------------------------------------- + // [obj]> + // ---------------------------------------------------------- + // [ego]> | <--- lane change margin ---> [obj]> + // ---------------------------------------------------------- + const bool has_blocking_target_lane_obj = std::any_of( + target_objects.target_lane.begin(), target_objects.target_lane.end(), [&](const auto & o) { + const auto v = std::abs(o.initial_twist.twist.linear.x); + if (v > lane_change_parameters_->stop_velocity_threshold) { + return false; + } + const double distance_to_target_lane_obj = getDistanceAlongLanelet(o.initial_pose.pose); + return stopping_distance_for_obj < distance_to_target_lane_obj && + distance_to_target_lane_obj < distance_to_ego_lane_obj; + }); + + if (!has_blocking_target_lane_obj) { + stopping_distance = stopping_distance_for_obj; + } } if (stopping_distance > 0.0) { From c03b5ac3abde3e4240e6c36d77c8bc939df90ea8 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:47:44 +0900 Subject: [PATCH 464/547] fix(tier4_autoware_utils): fix `-Werror=float-conversion` (#5301) fix(tier4_autoware_utils): fix Signed-off-by: satoshi-ota --- .../include/tier4_autoware_utils/geometry/geometry.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp index 17ce7e1f4de5a..6106e3a15f4c7 100644 --- a/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp +++ b/common/tier4_autoware_utils/include/tier4_autoware_utils/geometry/geometry.hpp @@ -261,7 +261,7 @@ inline void setOrientation(const geometry_msgs::msg::Quaternion & orientation, T } template -void setLongitudinalVelocity([[maybe_unused]] const double velocity, [[maybe_unused]] T & p) +void setLongitudinalVelocity([[maybe_unused]] const float velocity, [[maybe_unused]] T & p) { static_assert(sizeof(T) == 0, "Only specializations of getLongitudinalVelocity can be used."); throw std::logic_error("Only specializations of getLongitudinalVelocity can be used."); @@ -269,21 +269,21 @@ void setLongitudinalVelocity([[maybe_unused]] const double velocity, [[maybe_unu template <> inline void setLongitudinalVelocity( - const double velocity, autoware_auto_planning_msgs::msg::TrajectoryPoint & p) + const float velocity, autoware_auto_planning_msgs::msg::TrajectoryPoint & p) { p.longitudinal_velocity_mps = velocity; } template <> inline void setLongitudinalVelocity( - const double velocity, autoware_auto_planning_msgs::msg::PathPoint & p) + const float velocity, autoware_auto_planning_msgs::msg::PathPoint & p) { p.longitudinal_velocity_mps = velocity; } template <> inline void setLongitudinalVelocity( - const double velocity, autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) + const float velocity, autoware_auto_planning_msgs::msg::PathPointWithLaneId & p) { p.point.longitudinal_velocity_mps = velocity; } From def9ffc14a91a89e417f82d5ee6aafbc17944058 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Fri, 13 Oct 2023 20:24:30 +0900 Subject: [PATCH 465/547] feat(simple_planning_sim): publish lateral acceleration (#5307) Signed-off-by: Takamasa Horibe --- .../simple_planning_simulator_core.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp index 208fd80d57a08..fb6a5457e8f05 100644 --- a/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp +++ b/simulator/simple_planning_simulator/src/simple_planning_simulator/simple_planning_simulator_core.cpp @@ -638,6 +638,7 @@ void SimplePlanningSimulator::publish_acceleration() msg.header.frame_id = "/base_link"; msg.header.stamp = get_clock()->now(); msg.accel.accel.linear.x = vehicle_model_ptr_->getAx(); + msg.accel.accel.linear.y = vehicle_model_ptr_->getWz() * vehicle_model_ptr_->getVx(); using COV_IDX = tier4_autoware_utils::xyzrpy_covariance_index::XYZRPY_COV_IDX; constexpr auto COV = 0.001; @@ -658,6 +659,7 @@ void SimplePlanningSimulator::publish_imu() imu.header.frame_id = "base_link"; imu.header.stamp = now(); imu.linear_acceleration.x = vehicle_model_ptr_->getAx(); + imu.linear_acceleration.y = vehicle_model_ptr_->getWz() * vehicle_model_ptr_->getVx(); constexpr auto COV = 0.001; imu.linear_acceleration_covariance.at(COV_IDX::X_X) = COV; imu.linear_acceleration_covariance.at(COV_IDX::Y_Y) = COV; From 8b1f3c0c58e78d6946a27773e51d1f36c9334581 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Fri, 13 Oct 2023 21:31:46 +0900 Subject: [PATCH 466/547] feat(intersection): new feature of yield stuck detection (#5270) * feat(intersection): new feature of yield stuck detection Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka * use turn_direction Signed-off-by: Takayuki Murooka * update config Signed-off-by: Takayuki Murooka * update Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka --- .../config/intersection.param.yaml | 12 ++- .../src/debug.cpp | 5 + .../src/manager.cpp | 8 ++ .../src/scene_intersection.cpp | 98 +++++++++++++++++++ .../src/scene_intersection.hpp | 16 ++- .../src/util.cpp | 39 ++++++++ .../src/util.hpp | 6 ++ .../src/util_type.hpp | 1 + 8 files changed, 181 insertions(+), 4 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index bb80c140b95fb..30f4bc7b42a25 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -24,6 +24,12 @@ # assumed_front_car_decel: 1.0 # [m/ss] the expected deceleration of front car when front car as well as ego are turning timeout_private_area: 3.0 # [s] cancel stuck vehicle stop in private area enable_private_area_stuck_disregard: false #In the intersections which labeled as "private area", the obstacle vehicles judged as "stuck" are neglected if this param is set as true. + yield_stuck: + turn_direction: + left: true + right: false + straight: false + distance_thr: 1.0 # [m] collision_detection: state_transit_margin_time: 1.0 @@ -42,9 +48,9 @@ use_upstream_velocity: true # flag to use the planned velocity profile from the upstream module minimum_upstream_velocity: 0.01 # [m/s] minimum velocity to avoid null division for the stop line from the upstream velocity yield_on_green_traffic_light: - distance_to_assigned_lanelet_start: 5.0 - duration: 2.0 - range: 30.0 # [m] + distance_to_assigned_lanelet_start: 10.0 + duration: 3.0 + range: 50.0 # [m] occlusion: enable: false diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index 9ff638cb61876..82f5b12966c12 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -238,6 +238,11 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( debug_data_.stuck_targets, "stuck_targets", module_id_, now, 0.99, 0.99, 0.2), &debug_marker_array, now); + appendMarkerArray( + debug::createObjectsMarkerArray( + debug_data_.yield_stuck_targets, "stuck_targets", module_id_, now, 0.4, 0.99, 0.2), + &debug_marker_array, now); + appendMarkerArray( debug::createObjectsMarkerArray( debug_data_.blocking_attention_objects, "blocking_attention_objects", module_id_, now, 0.99, diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 3cca1f42115d1..93d67e786cff5 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -85,6 +85,14 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".stuck_vehicle.timeout_private_area"); ip.stuck_vehicle.enable_private_area_stuck_disregard = getOrDeclareParameter(node, ns + ".stuck_vehicle.enable_private_area_stuck_disregard"); + ip.stuck_vehicle.yield_stuck_turn_direction.left = + getOrDeclareParameter(node, ns + ".stuck_vehicle.yield_stuck.turn_direction.left"); + ip.stuck_vehicle.yield_stuck_turn_direction.right = + getOrDeclareParameter(node, ns + ".stuck_vehicle.yield_stuck.turn_direction.right"); + ip.stuck_vehicle.yield_stuck_turn_direction.straight = + getOrDeclareParameter(node, ns + ".stuck_vehicle.yield_stuck.turn_direction.straight"); + ip.stuck_vehicle.yield_stuck_distance_thr = + getOrDeclareParameter(node, ns + ".stuck_vehicle.yield_stuck.distance_thr"); ip.collision_detection.min_predicted_path_confidence = getOrDeclareParameter(node, ns + ".collision_detection.min_predicted_path_confidence"); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index ee541a061ce0e..76c2dc072dda9 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -195,6 +195,21 @@ void prepareRTCByDecisionResult( return; } +template <> +void prepareRTCByDecisionResult( + const IntersectionModule::YieldStuckStop & result, + const autoware_auto_planning_msgs::msg::PathWithLaneId & path, bool * default_safety, + double * default_distance, bool * occlusion_safety, [[maybe_unused]] double * occlusion_distance) +{ + RCLCPP_DEBUG(rclcpp::get_logger("prepareRTCByDecisionResult"), "YieldStuckStop"); + const auto closest_idx = result.closest_idx; + const auto stop_line_idx = result.stuck_stop_line_idx; + *default_safety = false; + *default_distance = motion_utils::calcSignedArcLength(path.points, closest_idx, stop_line_idx); + *occlusion_safety = true; + return; +} + template <> void prepareRTCByDecisionResult( const IntersectionModule::NonOccludedCollisionStop & result, @@ -419,6 +434,38 @@ void reactRTCApprovalByDecisionResult( return; } +template <> +void reactRTCApprovalByDecisionResult( + const bool rtc_default_approved, const bool rtc_occlusion_approved, + const IntersectionModule::YieldStuckStop & decision_result, + [[maybe_unused]] const IntersectionModule::PlannerParam & planner_param, + const double baselink2front, autoware_auto_planning_msgs::msg::PathWithLaneId * path, + StopReason * stop_reason, VelocityFactorInterface * velocity_factor, util::DebugData * debug_data) +{ + RCLCPP_DEBUG( + rclcpp::get_logger("reactRTCApprovalByDecisionResult"), + "YieldStuckStop, approval = (default: %d, occlusion: %d)", rtc_default_approved, + rtc_occlusion_approved); + const auto closest_idx = decision_result.closest_idx; + if (!rtc_default_approved) { + // use default_rtc uuid for stuck vehicle detection + const auto stop_line_idx = decision_result.stuck_stop_line_idx; + planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); + debug_data->collision_stop_wall_pose = + planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); + { + tier4_planning_msgs::msg::StopFactor stop_factor; + stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; + stop_factor.stop_factor_points = planning_utils::toRosPoints(debug_data->conflicting_targets); + planning_utils::appendStopReason(stop_factor, stop_reason); + velocity_factor->set( + path->points, path->points.at(closest_idx).point.pose, + path->points.at(stop_line_idx).point.pose, VelocityFactor::INTERSECTION); + } + } + return; +} + template <> void reactRTCApprovalByDecisionResult( const bool rtc_default_approved, const bool rtc_occlusion_approved, @@ -780,6 +827,9 @@ static std::string formatDecisionResult(const IntersectionModule::DecisionResult if (std::holds_alternative(decision_result)) { return "StuckStop"; } + if (std::holds_alternative(decision_result)) { + return "YieldStuckStop"; + } if (std::holds_alternative(decision_result)) { return "NonOccludedCollisionStop"; } @@ -985,6 +1035,23 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } } + // yield stuck vehicle detection is viable even if attention area is empty + // so this needs to be checked before attention area validation + const bool yield_stuck_detected = checkYieldStuckVehicle( + planner_data_, path_lanelets, intersection_lanelets.first_attention_area()); + if (yield_stuck_detected && stuck_stop_line_idx_opt) { + auto stuck_stop_line_idx = stuck_stop_line_idx_opt.value(); + if (is_private_area_ && planner_param_.stuck_vehicle.enable_private_area_stuck_disregard) { + if ( + default_stop_line_idx_opt && + fromEgoDist(stuck_stop_line_idx) < -planner_param_.common.stop_overshoot_margin) { + stuck_stop_line_idx = default_stop_line_idx_opt.value(); + } + } else { + return IntersectionModule::YieldStuckStop{closest_idx, stuck_stop_line_idx}; + } + } + // if attention area is empty, collision/occlusion detection is impossible if (!first_attention_area_opt) { return IntersectionModule::Indecisive{"attention area is empty"}; @@ -1234,6 +1301,37 @@ bool IntersectionModule::checkStuckVehicle( &debug_data_); } +bool IntersectionModule::checkYieldStuckVehicle( + const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets, + const std::optional & first_attention_area) +{ + if (!first_attention_area) { + return false; + } + + const bool yield_stuck_detection_direction = [&]() { + return (turn_direction_ == "left" && + planner_param_.stuck_vehicle.yield_stuck_turn_direction.left) || + (turn_direction_ == "right" && + planner_param_.stuck_vehicle.yield_stuck_turn_direction.right) || + (turn_direction_ == "straight" && + planner_param_.stuck_vehicle.yield_stuck_turn_direction.straight); + }(); + if (!yield_stuck_detection_direction) { + return false; + } + + const auto & objects_ptr = planner_data->predicted_objects; + + const auto & ego_lane = path_lanelets.ego_or_entry2exit; + const auto ego_poly = ego_lane.polygon2d().basicPolygon(); + + return util::checkYieldStuckVehicleInIntersection( + objects_ptr, ego_poly, first_attention_area.value(), + planner_param_.stuck_vehicle.stuck_vehicle_vel_thr, + planner_param_.stuck_vehicle.yield_stuck_distance_thr, &debug_data_); +} + autoware_auto_perception_msgs::msg::PredictedObjects IntersectionModule::filterTargetObjects( const lanelet::ConstLanelets & attention_area_lanelets, const lanelet::ConstLanelets & adjacent_lanelets, diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 6ae734bd6e05b..a4804018d194b 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -66,7 +66,8 @@ class IntersectionModule : public SceneModuleInterface bool left; bool right; bool straight; - } turn_direction; + }; + TurnDirection turn_direction; bool use_stuck_stopline; //! stopline generate before the intersection lanelet when is_stuck. double stuck_vehicle_detect_dist; //! distance from end point to finish stuck vehicle check double stuck_vehicle_vel_thr; //! Threshold of the speed to be recognized as stopped @@ -77,6 +78,8 @@ class IntersectionModule : public SceneModuleInterface */ double timeout_private_area; bool enable_private_area_stuck_disregard; + double yield_stuck_distance_thr; + TurnDirection yield_stuck_turn_direction; } stuck_vehicle; struct CollisionDetection { @@ -147,6 +150,11 @@ class IntersectionModule : public SceneModuleInterface size_t stuck_stop_line_idx{0}; std::optional occlusion_stop_line_idx{std::nullopt}; }; + struct YieldStuckStop + { + size_t closest_idx{0}; + size_t stuck_stop_line_idx{0}; + }; struct NonOccludedCollisionStop { size_t closest_idx{0}; @@ -206,6 +214,7 @@ class IntersectionModule : public SceneModuleInterface using DecisionResult = std::variant< Indecisive, // internal process error, or over the pass judge line StuckStop, // detected stuck vehicle + YieldStuckStop, // detected yield stuck vehicle NonOccludedCollisionStop, // detected collision while FOV is clear FirstWaitBeforeOcclusion, // stop for a while before peeking to occlusion PeekingTowardOcclusion, // peeking into occlusion while collision is not detected @@ -288,6 +297,11 @@ class IntersectionModule : public SceneModuleInterface const std::shared_ptr & planner_data, const util::PathLanelets & path_lanelets); + bool checkYieldStuckVehicle( + const std::shared_ptr & planner_data, + const util::PathLanelets & path_lanelets, + const std::optional & first_attention_area); + autoware_auto_perception_msgs::msg::PredictedObjects filterTargetObjects( const lanelet::ConstLanelets & attention_area_lanelets, const lanelet::ConstLanelets & adjacent_lanelets, diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index ffc0b61880188..17df31ba3ffc9 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -1108,6 +1108,45 @@ bool checkStuckVehicleInIntersection( return false; } +bool checkYieldStuckVehicleInIntersection( + const autoware_auto_perception_msgs::msg::PredictedObjects::ConstSharedPtr objects_ptr, + const lanelet::BasicPolygon2d & ego_poly, const lanelet::CompoundPolygon3d & first_attention_area, + const double stuck_vehicle_vel_thr, const double yield_stuck_distance_thr, DebugData * debug_data) +{ + const auto first_attention_area_2d = lanelet::utils::to2D(first_attention_area); + Polygon2d first_attention_area_poly; + for (const auto & p : first_attention_area_2d) { + first_attention_area_poly.outer().emplace_back(p.x(), p.y()); + } + + for (const auto & object : objects_ptr->objects) { + if (!isTargetStuckVehicleType(object)) { + continue; // not target vehicle type + } + const auto obj_v_norm = std::hypot( + object.kinematics.initial_twist_with_covariance.twist.linear.x, + object.kinematics.initial_twist_with_covariance.twist.linear.y); + if (obj_v_norm > stuck_vehicle_vel_thr) { + continue; // not stop vehicle + } + + const auto obj_footprint = tier4_autoware_utils::toPolygon2d(object); + + // check if the object is too close to the ego path + if (yield_stuck_distance_thr < bg::distance(ego_poly, obj_footprint)) { + continue; + } + + // check if the footprint is in the stuck detect area + const bool is_in_stuck_area = bg::within(obj_footprint, first_attention_area_poly); + if (is_in_stuck_area && debug_data) { + debug_data->yield_stuck_targets.objects.push_back(object); + return true; + } + } + return false; +} + Polygon2d generateStuckVehicleDetectAreaPolygon( const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist) { diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index a75545353fb7a..001aa63c7fe12 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -125,6 +125,12 @@ bool checkStuckVehicleInIntersection( const Polygon2d & stuck_vehicle_detect_area, const double stuck_vehicle_vel_thr, DebugData * debug_data); +bool checkYieldStuckVehicleInIntersection( + const autoware_auto_perception_msgs::msg::PredictedObjects::ConstSharedPtr objects_ptr, + const lanelet::BasicPolygon2d & ego_poly, const lanelet::CompoundPolygon3d & first_attention_area, + const double stuck_vehicle_vel_thr, const double yield_stuck_distance_thr, + DebugData * debug_data); + Polygon2d generateStuckVehicleDetectAreaPolygon( const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist); diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 581f22904fd2b..11c8d7d8407b7 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -50,6 +50,7 @@ struct DebugData std::vector candidate_collision_object_polygons; autoware_auto_perception_msgs::msg::PredictedObjects conflicting_targets; autoware_auto_perception_msgs::msg::PredictedObjects stuck_targets; + autoware_auto_perception_msgs::msg::PredictedObjects yield_stuck_targets; std::vector occlusion_polygons; std::optional> nearest_occlusion_projection{std::nullopt}; From 4383d6bcfcd5df6ec4f0b15e28ea501c2024b7ba Mon Sep 17 00:00:00 2001 From: Yuxuan Liu <619684051@qq.com> Date: Sat, 14 Oct 2023 06:32:59 +0800 Subject: [PATCH 467/547] feat(duplicated_node_checker): add packages to check duplication of node names in ros2 (#5286) * add implementation for duplicated node checking Signed-off-by: Owen-Liuyuxuan * update the default parameters of system_error_monitor to include results from duplication check Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * fix typo in readme Signed-off-by: Owen-Liuyuxuan * update license Signed-off-by: Owen-Liuyuxuan * change module to the system module Signed-off-by: Owen-Liuyuxuan * follow json schema: 1. update code to start without default 2. add schema/config/readme/launch accordingly Signed-off-by: Owen-Liuyuxuan * add duplicated node checker to launch Signed-off-by: Owen-Liuyuxuan * style(pre-commit): autofix * fix var name to config for uniform launch Signed-off-by: Owen-Liuyuxuan * Update system/duplicated_node_checker/README.md * Update system/duplicated_node_checker/README.md --------- Signed-off-by: Owen-Liuyuxuan Co-authored-by: Owen-Liuyuxuan Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Shumpei Wakabayashi <42209144+shmpwk@users.noreply.github.com> --- .../launch/system.launch.xml | 7 ++ system/duplicated_node_checker/CMakeLists.txt | 20 +++++ system/duplicated_node_checker/README.md | 37 +++++++++ .../config/duplicated_node_checker.param.yaml | 3 + .../duplicated_node_checker_core.hpp | 54 +++++++++++++ .../launch/duplicated_node_checker.launch.xml | 7 ++ system/duplicated_node_checker/package.xml | 24 ++++++ .../duplicated_node_checker.schema.json | 31 +++++++ .../src/duplicated_node_checker_core.cpp | 81 +++++++++++++++++++ .../diagnostic_aggregator/system.param.yaml | 6 ++ .../config/system_error_monitor.param.yaml | 1 + ...ror_monitor.planning_simulation.param.yaml | 1 + 12 files changed, 272 insertions(+) create mode 100644 system/duplicated_node_checker/CMakeLists.txt create mode 100644 system/duplicated_node_checker/README.md create mode 100644 system/duplicated_node_checker/config/duplicated_node_checker.param.yaml create mode 100644 system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp create mode 100644 system/duplicated_node_checker/launch/duplicated_node_checker.launch.xml create mode 100644 system/duplicated_node_checker/package.xml create mode 100644 system/duplicated_node_checker/schema/duplicated_node_checker.schema.json create mode 100644 system/duplicated_node_checker/src/duplicated_node_checker_core.cpp diff --git a/launch/tier4_system_launch/launch/system.launch.xml b/launch/tier4_system_launch/launch/system.launch.xml index 016d613cfa72d..2de6a61547498 100644 --- a/launch/tier4_system_launch/launch/system.launch.xml +++ b/launch/tier4_system_launch/launch/system.launch.xml @@ -3,6 +3,7 @@ + @@ -84,6 +85,12 @@
+ + + + + + diff --git a/system/duplicated_node_checker/CMakeLists.txt b/system/duplicated_node_checker/CMakeLists.txt new file mode 100644 index 0000000000000..6a8089fa85c96 --- /dev/null +++ b/system/duplicated_node_checker/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.14) +project(duplicated_node_checker) + +find_package(autoware_cmake REQUIRED) +autoware_package() +find_package(rclcpp REQUIRED) + +ament_auto_add_library(${PROJECT_NAME} SHARED + src/duplicated_node_checker_core.cpp +) + +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "duplicated_node_checker::DuplicatedNodeChecker" + EXECUTABLE duplicated_node_checker_node +) + +ament_auto_package(INSTALL_TO_SHARE + launch + config +) diff --git a/system/duplicated_node_checker/README.md b/system/duplicated_node_checker/README.md new file mode 100644 index 0000000000000..81b53e36c9f6d --- /dev/null +++ b/system/duplicated_node_checker/README.md @@ -0,0 +1,37 @@ +# Duplicated Node Checker + +## Purpose + +This node monitors the ROS 2 environments and detect duplication of node names in the environment. +The result is published as diagnostics. + +### Standalone Startup + +```bash +ros2 launch duplicated_node_checker duplicated_node_checker.launch.xml +``` + +## Inner-workings / Algorithms + +The types of topic status and corresponding diagnostic status are following. + +| Duplication status | Diagnostic status | Description | +| --------------------- | ----------------- | -------------------------- | +| `OK` | OK | No duplication is detected | +| `Duplicated Detected` | ERROR | Duplication is detected | + +## Inputs / Outputs + +### Output + +| Name | Type | Description | +| -------------- | --------------------------------- | ------------------- | +| `/diagnostics` | `diagnostic_msgs/DiagnosticArray` | Diagnostics outputs | + +## Parameters + +{{ json_to_markdown("system/duplicated_node_checker/schema/duplicated_node_checker.schema.json") }} + +## Assumptions / Known limits + +TBD. diff --git a/system/duplicated_node_checker/config/duplicated_node_checker.param.yaml b/system/duplicated_node_checker/config/duplicated_node_checker.param.yaml new file mode 100644 index 0000000000000..5b8c019de5a20 --- /dev/null +++ b/system/duplicated_node_checker/config/duplicated_node_checker.param.yaml @@ -0,0 +1,3 @@ +/**: + ros__parameters: + update_rate: 10.0 diff --git a/system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp b/system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp new file mode 100644 index 0000000000000..ec086058e9041 --- /dev/null +++ b/system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp @@ -0,0 +1,54 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef DUPLICATED_NODE_CHECKER__DUPLICATED_NODE_CHECKER_CORE_HPP_ +#define DUPLICATED_NODE_CHECKER__DUPLICATED_NODE_CHECKER_CORE_HPP_ + +#include +#include + +#include +#include +#include + +namespace duplicated_node_checker +{ +class DuplicatedNodeChecker : public rclcpp::Node +{ +public: + explicit DuplicatedNodeChecker(const rclcpp::NodeOptions & node_options); + std::vector findIdenticalNames(const std::vector input_name_lists) + { + std::unordered_set unique_names; + std::vector identical_names; + for (auto name : input_name_lists) { + if (unique_names.find(name) != unique_names.end()) { + identical_names.push_back(name); + } else { + unique_names.insert(name); + } + } + return identical_names; + } + +private: + void onTimer(); + void produceDiagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat); + + diagnostic_updater::Updater updater_{this}; + rclcpp::TimerBase::SharedPtr timer_; +}; +} // namespace duplicated_node_checker + +#endif // DUPLICATED_NODE_CHECKER__DUPLICATED_NODE_CHECKER_CORE_HPP_ diff --git a/system/duplicated_node_checker/launch/duplicated_node_checker.launch.xml b/system/duplicated_node_checker/launch/duplicated_node_checker.launch.xml new file mode 100644 index 0000000000000..87f41f045545d --- /dev/null +++ b/system/duplicated_node_checker/launch/duplicated_node_checker.launch.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/system/duplicated_node_checker/package.xml b/system/duplicated_node_checker/package.xml new file mode 100644 index 0000000000000..e2ba225b16330 --- /dev/null +++ b/system/duplicated_node_checker/package.xml @@ -0,0 +1,24 @@ + + + + duplicated_node_checker + 1.0.0 + A package to find out nodes with common names + Shumpei Wakabayashi + yliuhb + Apache 2.0 + + ament_cmake + autoware_cmake + + diagnostic_updater + rclcpp + rclcpp_components + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/system/duplicated_node_checker/schema/duplicated_node_checker.schema.json b/system/duplicated_node_checker/schema/duplicated_node_checker.schema.json new file mode 100644 index 0000000000000..6ee933ecf1a77 --- /dev/null +++ b/system/duplicated_node_checker/schema/duplicated_node_checker.schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Parameters for Duplicated Node Checker", + "type": "object", + "definitions": { + "duplicated_node_checker": { + "type": "object", + "properties": { + "update_rate": { + "type": "number", + "default": 10, + "exclusiveMinimum": 2, + "description": "The scanning and update frequency of the checker." + } + }, + "required": ["update_rate"] + } + }, + "properties": { + "/**": { + "type": "object", + "properties": { + "ros__parameters": { + "$ref": "#/definitions/duplicated_node_checker" + } + }, + "required": ["ros__parameters"] + } + }, + "required": ["/**"] +} diff --git a/system/duplicated_node_checker/src/duplicated_node_checker_core.cpp b/system/duplicated_node_checker/src/duplicated_node_checker_core.cpp new file mode 100644 index 0000000000000..c9aa483724532 --- /dev/null +++ b/system/duplicated_node_checker/src/duplicated_node_checker_core.cpp @@ -0,0 +1,81 @@ +// Copyright 2023 TIER IV, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "duplicated_node_checker/duplicated_node_checker_core.hpp" + +#include +#include + +#include +#include +#include + +namespace duplicated_node_checker +{ + +DuplicatedNodeChecker::DuplicatedNodeChecker(const rclcpp::NodeOptions & node_options) +: Node("duplicated_node_checker", node_options) +{ + double update_rate = declare_parameter("update_rate"); + updater_.setHardwareID("duplicated_node_checker"); + updater_.add("duplicated_node_checker", this, &DuplicatedNodeChecker::produceDiagnostics); + + const auto period_ns = rclcpp::Rate(update_rate).period(); + timer_ = rclcpp::create_timer( + this, get_clock(), period_ns, std::bind(&DuplicatedNodeChecker::onTimer, this)); +} + +std::string get_fullname_from_name_ns_pair(std::pair name_and_ns_pair) +{ + std::string full_name; + const std::string & name = name_and_ns_pair.first; + const std::string & ns = name_and_ns_pair.second; + if (ns.back() == '/') { + full_name = ns + name; + } else { + full_name = ns + "/" + name; + } + return full_name; +} + +void DuplicatedNodeChecker::onTimer() +{ + updater_.force_update(); +} + +void DuplicatedNodeChecker::produceDiagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat) +{ + using diagnostic_msgs::msg::DiagnosticStatus; + + std::vector node_names = this->get_node_names(); + std::vector identical_names = findIdenticalNames(node_names); + std::string msg; + int level; + if (identical_names.size() > 0) { + msg = "Error"; + level = DiagnosticStatus::ERROR; + for (auto name : identical_names) { + stat.add("Duplicated Node Name", name); + } + } else { + msg = "OK"; + level = DiagnosticStatus::OK; + } + stat.summary(level, msg); +} + +} // namespace duplicated_node_checker + +#include +RCLCPP_COMPONENTS_REGISTER_NODE(duplicated_node_checker::DuplicatedNodeChecker) diff --git a/system/system_error_monitor/config/diagnostic_aggregator/system.param.yaml b/system/system_error_monitor/config/diagnostic_aggregator/system.param.yaml index 87cf767accc06..f6e13012957aa 100644 --- a/system/system_error_monitor/config/diagnostic_aggregator/system.param.yaml +++ b/system/system_error_monitor/config/diagnostic_aggregator/system.param.yaml @@ -26,6 +26,12 @@ contains: ["service_log_checker"] timeout: 5.0 + duplicated_node_checker: + type: diagnostic_aggregator/GenericAnalyzer + path: duplicated_node_checker + contains: ["duplicated_node_checker"] + timeout: 5.0 + resource_monitoring: type: diagnostic_aggregator/AnalyzerGroup path: resource_monitoring diff --git a/system/system_error_monitor/config/system_error_monitor.param.yaml b/system/system_error_monitor/config/system_error_monitor.param.yaml index 312a2e6186c42..1778f6594f0c3 100644 --- a/system/system_error_monitor/config/system_error_monitor.param.yaml +++ b/system/system_error_monitor/config/system_error_monitor.param.yaml @@ -39,6 +39,7 @@ /autoware/system/emergency_stop_operation: default /autoware/system/service_log_checker: { sf_at: "warn", lf_at: "none", spf_at: "none" } /autoware/system/resource_monitoring: { sf_at: "warn", lf_at: "none", spf_at: "none" } + /autoware/system/duplicated_node_checker: default /autoware/vehicle/node_alive_monitoring: default diff --git a/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml b/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml index 88431dc406fe3..c396e2e9f5ed8 100644 --- a/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml +++ b/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml @@ -38,6 +38,7 @@ /autoware/system/node_alive_monitoring: default /autoware/system/emergency_stop_operation: default /autoware/system/service_log_checker: { sf_at: "warn", lf_at: "none", spf_at: "none" } + /autoware/system/duplicated_node_checker: default # /autoware/system/resource_monitoring: { sf_at: "warn", lf_at: "error", spf_at: "none" } /autoware/vehicle/node_alive_monitoring: default From d1a77d59fc6b676e94bd9ef62a644c310b676c3f Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Sun, 15 Oct 2023 13:50:03 +0900 Subject: [PATCH 468/547] feat(intersection): ignore decelerating vehicle on amber traffic light (#5304) * merge attention area lanelets topologically Signed-off-by: Mamoru Sobue * query stop line for each merged detection lanelet Signed-off-by: Mamoru Sobue * first conflicting/attention area Signed-off-by: Mamoru Sobue * ignore expectedToStopBeforeStopLine vehicle Signed-off-by: Mamoru Sobue * ignore expectedToStopBeforeStopLine vehicle Signed-off-by: Mamoru Sobue * fix Signed-off-by: Mamoru Sobue --------- Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 8 +- .../src/debug.cpp | 5 + .../src/manager.cpp | 8 +- .../src/scene_intersection.cpp | 314 ++++++++-------- .../src/scene_intersection.hpp | 26 +- .../src/util.cpp | 334 +++++++++++------- .../src/util.hpp | 15 +- .../src/util_type.hpp | 59 +++- 8 files changed, 455 insertions(+), 314 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 30f4bc7b42a25..1a5143678ddce 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -48,9 +48,11 @@ use_upstream_velocity: true # flag to use the planned velocity profile from the upstream module minimum_upstream_velocity: 0.01 # [m/s] minimum velocity to avoid null division for the stop line from the upstream velocity yield_on_green_traffic_light: - distance_to_assigned_lanelet_start: 10.0 - duration: 3.0 - range: 50.0 # [m] + distance_to_assigned_lanelet_start: 5.0 + duration: 2.0 + object_dist_to_stopline: 10.0 # [m] + ignore_on_amber_traffic_light: + object_expected_deceleration: 2.0 # [m/ss] occlusion: enable: false diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index 82f5b12966c12..3d5874c3c89e8 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -233,6 +233,11 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( debug_data_.conflicting_targets, "conflicting_targets", module_id_, now, 0.99, 0.4, 0.0), &debug_marker_array, now); + appendMarkerArray( + debug::createObjectsMarkerArray( + debug_data_.amber_ignore_targets, "amber_ignore_targets", module_id_, now, 0.0, 1.0, 0.0), + &debug_marker_array, now); + appendMarkerArray( debug::createObjectsMarkerArray( debug_data_.stuck_targets, "stuck_targets", module_id_, now, 0.99, 0.99, 0.2), diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 93d67e786cff5..22fa57f20e79f 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -129,8 +129,12 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) ns + ".collision_detection.yield_on_green_traffic_light.distance_to_assigned_lanelet_start"); ip.collision_detection.yield_on_green_traffic_light.duration = getOrDeclareParameter( node, ns + ".collision_detection.yield_on_green_traffic_light.duration"); - ip.collision_detection.yield_on_green_traffic_light.range = getOrDeclareParameter( - node, ns + ".collision_detection.yield_on_green_traffic_light.range"); + ip.collision_detection.yield_on_green_traffic_light.object_dist_to_stopline = + getOrDeclareParameter( + node, ns + ".collision_detection.yield_on_green_traffic_light.object_dist_to_stopline"); + ip.collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration = + getOrDeclareParameter( + node, ns + ".collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration"); ip.occlusion.enable = getOrDeclareParameter(node, ns + ".occlusion.enable"); ip.occlusion.occlusion_attention_area_length = diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 76c2dc072dda9..b37b70f290ff6 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -1103,7 +1103,6 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const auto first_attention_stop_line_idx = first_attention_stop_line_idx_opt.value(); const auto occlusion_stop_line_idx = occlusion_peeking_stop_line_idx_opt.value(); - const auto & attention_lanelets = intersection_lanelets.attention(); const auto & adjacent_lanelets = intersection_lanelets.adjacent(); const auto & occlusion_attention_lanelets = intersection_lanelets.occlusion_attention(); const auto & occlusion_attention_area = intersection_lanelets.occlusion_attention_area(); @@ -1120,8 +1119,7 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( debug_data_.intersection_area = toGeomPoly(intersection_area_2d); } - const auto target_objects = - filterTargetObjects(attention_lanelets, adjacent_lanelets, intersection_area); + auto target_objects = generateTargetObjects(intersection_lanelets, intersection_area); // If there are any vehicles on the attention area when ego entered the intersection on green // light, do pseudo collision detection because the vehicles are very slow and no collisions may @@ -1145,11 +1143,12 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } if (initial_green_light_observed_time_) { const auto now = clock_->now(); - const bool exist_close_vehicles = std::any_of( - target_objects.objects.begin(), target_objects.objects.end(), [&](const auto & object) { - return tier4_autoware_utils::calcDistance3d( - object.kinematics.initial_pose_with_covariance.pose, current_pose) < - planner_param_.collision_detection.yield_on_green_traffic_light.range; + const bool exist_close_vehicles = + std::any_of(target_objects.all.begin(), target_objects.all.end(), [&](const auto & object) { + return object.dist_to_stop_line.has_value() && + object.dist_to_stop_line.value() < + planner_param_.collision_detection.yield_on_green_traffic_light + .object_dist_to_stopline; }); if ( exist_close_vehicles && @@ -1168,7 +1167,7 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( collision_state_machine_.getDuration()); const bool has_collision = checkCollision( - *path, target_objects, path_lanelets, closest_idx, + *path, &target_objects, path_lanelets, closest_idx, std::min(occlusion_stop_line_idx, path->points.size() - 1), time_to_restart, traffic_prioritized_level); collision_state_machine_.setStateWithMarginTime( @@ -1195,16 +1194,11 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const double occlusion_dist_thr = std::fabs( std::pow(planner_param_.occlusion.max_vehicle_velocity_for_rss, 2) / (2 * planner_param_.occlusion.min_vehicle_brake_for_rss)); - std::vector blocking_attention_objects; - std::copy_if( - target_objects.objects.begin(), target_objects.objects.end(), - std::back_inserter(blocking_attention_objects), - [thresh = planner_param_.occlusion.ignore_parked_vehicle_speed_threshold](const auto & object) { - return std::hypot( - object.kinematics.initial_twist_with_covariance.twist.linear.x, - object.kinematics.initial_twist_with_covariance.twist.linear.y) <= thresh; - }); - debug_data_.blocking_attention_objects.objects = blocking_attention_objects; + const auto blocking_attention_objects = target_objects.parked_attention_objects; + for (const auto & blocking_attention_object : blocking_attention_objects) { + debug_data_.blocking_attention_objects.objects.push_back(blocking_attention_object.object); + } + // debug_data_.blocking_attention_objects.objects = blocking_attention_objects; const bool is_occlusion_cleared = (enable_occlusion_detection_ && !occlusion_attention_lanelets.empty() && !is_prioritized) ? isOcclusionCleared( @@ -1332,18 +1326,17 @@ bool IntersectionModule::checkYieldStuckVehicle( planner_param_.stuck_vehicle.yield_stuck_distance_thr, &debug_data_); } -autoware_auto_perception_msgs::msg::PredictedObjects IntersectionModule::filterTargetObjects( - const lanelet::ConstLanelets & attention_area_lanelets, - const lanelet::ConstLanelets & adjacent_lanelets, +util::TargetObjects IntersectionModule::generateTargetObjects( + const util::IntersectionLanelets & intersection_lanelets, const std::optional & intersection_area) const { - using lanelet::utils::getArcCoordinates; - using lanelet::utils::getPolygonFromArcLength; - const auto & objects_ptr = planner_data_->predicted_objects; // extract target objects - autoware_auto_perception_msgs::msg::PredictedObjects target_objects; + util::TargetObjects target_objects; target_objects.header = objects_ptr->header; + const auto & attention_lanelets = intersection_lanelets.attention(); + const auto & attention_lanelet_stoplines = intersection_lanelets.attention_stop_lines(); + const auto & adjacent_lanelets = intersection_lanelets.adjacent(); for (const auto & object : objects_ptr->objects) { // ignore non-vehicle type objects, such as pedestrian. if (!isTargetCollisionVehicleType(object)) { @@ -1352,49 +1345,72 @@ autoware_auto_perception_msgs::msg::PredictedObjects IntersectionModule::filterT // check direction of objects const auto object_direction = util::getObjectPoseWithVelocityDirection(object.kinematics); - const auto is_in_adjacent_lanelets = util::checkAngleForTargetLanelets( - object_direction, object.kinematics.initial_twist_with_covariance.twist.linear.x, - adjacent_lanelets, planner_param_.common.attention_area_angle_thr, + const auto belong_adjacent_lanelet_id = util::checkAngleForTargetLanelets( + object_direction, adjacent_lanelets, planner_param_.common.attention_area_angle_thr, planner_param_.common.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin, - planner_param_.occlusion.ignore_parked_vehicle_speed_threshold); - if (is_in_adjacent_lanelets) { + planner_param_.common.attention_area_margin, false); + if (belong_adjacent_lanelet_id) { continue; } + const auto is_parked_vehicle = + std::fabs(object.kinematics.initial_twist_with_covariance.twist.linear.x) < + planner_param_.occlusion.ignore_parked_vehicle_speed_threshold; + auto & container = is_parked_vehicle ? target_objects.parked_attention_objects + : target_objects.attention_objects; if (intersection_area) { const auto obj_poly = tier4_autoware_utils::toPolygon2d(object); const auto intersection_area_2d = intersection_area.value(); - const auto is_in_intersection_area = bg::within(obj_poly, intersection_area_2d); - if (is_in_intersection_area) { - target_objects.objects.push_back(object); - } else if (util::checkAngleForTargetLanelets( - object_direction, object.kinematics.initial_twist_with_covariance.twist.linear.x, - attention_area_lanelets, planner_param_.common.attention_area_angle_thr, - planner_param_.common.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin, - planner_param_.occlusion.ignore_parked_vehicle_speed_threshold)) { - target_objects.objects.push_back(object); + const auto belong_attention_lanelet_id = util::checkAngleForTargetLanelets( + object_direction, attention_lanelets, planner_param_.common.attention_area_angle_thr, + planner_param_.common.consider_wrong_direction_vehicle, + planner_param_.common.attention_area_margin, is_parked_vehicle); + if (belong_attention_lanelet_id) { + const auto id = belong_adjacent_lanelet_id.value(); + util::TargetObject target_object; + target_object.object = object; + target_object.attention_lanelet = attention_lanelets.at(id); + target_object.stop_line = attention_lanelet_stoplines.at(id); + container.push_back(target_object); + } else if (bg::within(obj_poly, intersection_area_2d)) { + util::TargetObject target_object; + target_object.object = object; + target_object.attention_lanelet = std::nullopt; + target_object.stop_line = std::nullopt; + container.push_back(target_object); } - } else if (util::checkAngleForTargetLanelets( - object_direction, object.kinematics.initial_twist_with_covariance.twist.linear.x, - attention_area_lanelets, planner_param_.common.attention_area_angle_thr, + } else if (const auto belong_attention_lanelet_id = util::checkAngleForTargetLanelets( + object_direction, attention_lanelets, + planner_param_.common.attention_area_angle_thr, planner_param_.common.consider_wrong_direction_vehicle, - planner_param_.common.attention_area_margin, - planner_param_.occlusion.ignore_parked_vehicle_speed_threshold)) { + planner_param_.common.attention_area_margin, is_parked_vehicle); + belong_attention_lanelet_id.has_value()) { // intersection_area is not available, use detection_area_with_margin as before - target_objects.objects.push_back(object); + const auto id = belong_attention_lanelet_id.value(); + util::TargetObject target_object; + target_object.object = object; + target_object.attention_lanelet = attention_lanelets.at(id); + target_object.stop_line = attention_lanelet_stoplines.at(id); + container.push_back(target_object); } } + for (const auto & object : target_objects.attention_objects) { + target_objects.all.push_back(object); + } + for (const auto & object : target_objects.parked_attention_objects) { + target_objects.all.push_back(object); + } + for (auto & object : target_objects.all) { + object.calc_dist_to_stop_line(); + } return target_objects; } bool IntersectionModule::checkCollision( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const autoware_auto_perception_msgs::msg::PredictedObjects & objects, - const util::PathLanelets & path_lanelets, const size_t closest_idx, - const size_t last_intersection_stop_line_candidate_idx, const double time_delay, - const util::TrafficPrioritizedLevel & traffic_prioritized_level) + util::TargetObjects * target_objects, const util::PathLanelets & path_lanelets, + const size_t closest_idx, const size_t last_intersection_stop_line_candidate_idx, + const double time_delay, const util::TrafficPrioritizedLevel & traffic_prioritized_level) { using lanelet::utils::getArcCoordinates; using lanelet::utils::getPolygonFromArcLength; @@ -1408,17 +1424,16 @@ bool IntersectionModule::checkCollision( planner_param_.collision_detection.use_upstream_velocity, planner_param_.collision_detection.minimum_upstream_velocity); const double passing_time = time_distance_array.back().first; - auto target_objects = objects; - util::cutPredictPathWithDuration(&target_objects, clock_, passing_time); + util::cutPredictPathWithDuration(target_objects, clock_, passing_time); const auto & concat_lanelets = path_lanelets.all; const auto closest_arc_coords = getArcCoordinates( concat_lanelets, tier4_autoware_utils::getPose(path.points.at(closest_idx).point)); const auto & ego_lane = path_lanelets.ego_or_entry2exit; debug_data_.ego_lane = ego_lane.polygon3d(); - const auto ego_poly = ego_lane.polygon2d().basicPolygon(); - // check collision between predicted_path and ego_area + + // change TTC margin based on ego traffic light color const auto [collision_start_margin_time, collision_end_margin_time] = [&]() { if (traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED) { return std::make_pair( @@ -1434,8 +1449,33 @@ bool IntersectionModule::checkCollision( planner_param_.collision_detection.not_prioritized.collision_start_margin_time, planner_param_.collision_detection.not_prioritized.collision_end_margin_time); }(); + const auto expectedToStopBeforeStopLine = [&](const util::TargetObject & target_object) { + if (!target_object.dist_to_stop_line) { + return false; + } + const double dist_to_stop_line = target_object.dist_to_stop_line.value(); + if (dist_to_stop_line < 0) { + return false; + } + const double v = target_object.object.kinematics.initial_twist_with_covariance.twist.linear.x; + const double braking_distance = + v * v / + (2.0 * std::fabs(planner_param_.collision_detection.ignore_on_amber_traffic_light + .object_expected_deceleration)); + return dist_to_stop_line > braking_distance; + }; + + // check collision between predicted_path and ego_area bool collision_detected = false; - for (const auto & object : target_objects.objects) { + for (const auto & target_object : target_objects->all) { + const auto & object = target_object.object; + // If the vehicle is expected to stop before their stopline, ignore + if ( + traffic_prioritized_level == util::TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED && + expectedToStopBeforeStopLine(target_object)) { + debug_data_.amber_ignore_targets.objects.push_back(object); + continue; + } for (const auto & predicted_path : object.kinematics.predicted_paths) { if ( predicted_path.confidence < @@ -1532,9 +1572,8 @@ bool IntersectionModule::isOcclusionCleared( const lanelet::ConstLanelets & adjacent_lanelets, const lanelet::CompoundPolygon3d & first_attention_area, const util::InterpolatedPathInfo & interpolated_path_info, - const std::vector & lane_divisions, - const std::vector & - blocking_attention_objects, + const std::vector & lane_divisions, + const std::vector & blocking_attention_objects, const double occlusion_dist_thr) { const auto & path_ip = interpolated_path_info.path; @@ -1660,27 +1699,24 @@ bool IntersectionModule::isOcclusionCleared( // (3.1) draw all cells on attention_mask behind blocking vehicles as not occluded std::vector> blocking_polygons; for (const auto & blocking_attention_object : blocking_attention_objects) { - const Polygon2d obj_poly = tier4_autoware_utils::toPolygon2d(blocking_attention_object); + const Polygon2d obj_poly = tier4_autoware_utils::toPolygon2d(blocking_attention_object.object); findCommonCvPolygons(obj_poly.outer(), blocking_polygons); } for (const auto & blocking_polygon : blocking_polygons) { cv::fillPoly(attention_mask, blocking_polygon, cv::Scalar(BLOCKED), cv::LINE_AA); } - for (const auto & lane_division : lane_divisions) { - const auto & divisions = lane_division.divisions; - for (const auto & division : divisions) { - bool blocking_vehicle_found = false; - for (const auto & point_it : division) { - const auto [valid, idx_x, idx_y] = coord2index(point_it.x(), point_it.y()); - if (!valid) continue; - if (blocking_vehicle_found) { - occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; - continue; - } - if (attention_mask.at(height - 1 - idx_y, idx_x) == BLOCKED) { - blocking_vehicle_found = true; - occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; - } + for (const auto & division : lane_divisions) { + bool blocking_vehicle_found = false; + for (const auto & point_it : division) { + const auto [valid, idx_x, idx_y] = coord2index(point_it.x(), point_it.y()); + if (!valid) continue; + if (blocking_vehicle_found) { + occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; + continue; + } + if (attention_mask.at(height - 1 - idx_y, idx_x) == BLOCKED) { + blocking_vehicle_found = true; + occlusion_mask.at(height - 1 - idx_y, idx_x) = 0; } } } @@ -1753,84 +1789,78 @@ bool IntersectionModule::isOcclusionCleared( } } - auto findNearestPointToProjection = - [](lanelet::ConstLineString2d division, const Point2d & projection, const double dist_thresh) { - double min_dist = std::numeric_limits::infinity(); - auto nearest = division.end(); - for (auto it = division.begin(); it != division.end(); it++) { - const double dist = std::hypot(it->x() - projection.x(), it->y() - projection.y()); - if (dist < min_dist) { - min_dist = dist; - nearest = it; - } - if (dist < dist_thresh) { - break; - } + auto findNearestPointToProjection = []( + const lanelet::ConstLineString3d & division, + const Point2d & projection, const double dist_thresh) { + double min_dist = std::numeric_limits::infinity(); + auto nearest = division.end(); + for (auto it = division.begin(); it != division.end(); it++) { + const double dist = std::hypot(it->x() - projection.x(), it->y() - projection.y()); + if (dist < min_dist) { + min_dist = dist; + nearest = it; } - return nearest; - }; + if (dist < dist_thresh) { + break; + } + } + return nearest; + }; struct NearestOcclusionPoint { - int lane_id; int64 division_index; double dist; geometry_msgs::msg::Point point; geometry_msgs::msg::Point projection; } nearest_occlusion_point; double min_dist = std::numeric_limits::infinity(); - for (const auto & lane_division : lane_divisions) { - const auto & divisions = lane_division.divisions; - const auto lane_id = lane_division.lane_id; - for (unsigned division_index = 0; division_index < divisions.size(); ++division_index) { - const auto & division = divisions.at(division_index); - LineString2d division_linestring; - auto division_point_it = division.begin(); - division_linestring.emplace_back(division_point_it->x(), division_point_it->y()); - for (auto point_it = division.begin(); point_it != division.end(); point_it++) { - if ( - std::hypot( - point_it->x() - division_point_it->x(), point_it->y() - division_point_it->y()) < - 3.0 /* rough tick for computational cost */) { - continue; - } - division_linestring.emplace_back(point_it->x(), point_it->y()); - division_point_it = point_it; - } - - // find the intersection point of lane_line and path - std::vector intersection_points; - boost::geometry::intersection(division_linestring, path_linestring, intersection_points); - if (intersection_points.empty()) { + for (unsigned division_index = 0; division_index < lane_divisions.size(); ++division_index) { + const auto & division = lane_divisions.at(division_index); + LineString2d division_linestring; + auto division_point_it = division.begin(); + division_linestring.emplace_back(division_point_it->x(), division_point_it->y()); + for (auto point_it = division.begin(); point_it != division.end(); point_it++) { + if ( + std::hypot(point_it->x() - division_point_it->x(), point_it->y() - division_point_it->y()) < + 3.0 /* rough tick for computational cost */) { continue; } - const auto & projection_point = intersection_points.at(0); - const auto projection_it = - findNearestPointToProjection(division, projection_point, resolution); - if (projection_it == division.end()) { - continue; + division_linestring.emplace_back(point_it->x(), point_it->y()); + division_point_it = point_it; + } + + // find the intersection point of lane_line and path + std::vector intersection_points; + boost::geometry::intersection(division_linestring, path_linestring, intersection_points); + if (intersection_points.empty()) { + continue; + } + const auto & projection_point = intersection_points.at(0); + const auto projection_it = findNearestPointToProjection(division, projection_point, resolution); + if (projection_it == division.end()) { + continue; + } + double acc_dist = 0.0; + auto acc_dist_it = projection_it; + for (auto point_it = projection_it; point_it != division.end(); point_it++) { + const double dist = + std::hypot(point_it->x() - acc_dist_it->x(), point_it->y() - acc_dist_it->y()); + acc_dist += dist; + acc_dist_it = point_it; + const auto [valid, idx_x, idx_y] = coord2index(point_it->x(), point_it->y()); + // TODO(Mamoru Sobue): add handling for blocking vehicles + if (!valid) continue; + const auto pixel = occlusion_mask.at(height - 1 - idx_y, idx_x); + if (pixel == BLOCKED) { + break; } - double acc_dist = 0.0; - auto acc_dist_it = projection_it; - for (auto point_it = projection_it; point_it != division.end(); point_it++) { - const double dist = - std::hypot(point_it->x() - acc_dist_it->x(), point_it->y() - acc_dist_it->y()); - acc_dist += dist; - acc_dist_it = point_it; - const auto [valid, idx_x, idx_y] = coord2index(point_it->x(), point_it->y()); - // TODO(Mamoru Sobue): add handling for blocking vehicles - if (!valid) continue; - const auto pixel = occlusion_mask.at(height - 1 - idx_y, idx_x); - if (pixel == BLOCKED) { - break; - } - if (pixel == OCCLUDED) { - if (acc_dist < min_dist) { - min_dist = acc_dist; - nearest_occlusion_point = { - lane_id, std::distance(division.begin(), point_it), acc_dist, - tier4_autoware_utils::createPoint(point_it->x(), point_it->y(), origin.z), - tier4_autoware_utils::createPoint(projection_it->x(), projection_it->y(), origin.z)}; - } + if (pixel == OCCLUDED) { + if (acc_dist < min_dist) { + min_dist = acc_dist; + nearest_occlusion_point = { + std::distance(division.begin(), point_it), acc_dist, + tier4_autoware_utils::createPoint(point_it->x(), point_it->y(), origin.z), + tier4_autoware_utils::createPoint(projection_it->x(), projection_it->y(), origin.z)}; } } } diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index a4804018d194b..beb13ef7bef7a 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -109,8 +109,12 @@ class IntersectionModule : public SceneModuleInterface { double distance_to_assigned_lanelet_start; double duration; - double range; + double object_dist_to_stopline; } yield_on_green_traffic_light; + struct IgnoreOnAmberTrafficLight + { + double object_expected_deceleration; + } ignore_on_amber_traffic_light; } collision_detection; struct Occlusion { @@ -266,7 +270,8 @@ class IntersectionModule : public SceneModuleInterface // for occlusion detection const bool enable_occlusion_detection_; - std::optional> occlusion_attention_divisions_{std::nullopt}; + std::optional> occlusion_attention_divisions_{ + std::nullopt}; StateMachine collision_state_machine_; //! for stable collision checking StateMachine before_creep_state_machine_; //! for two phase stop StateMachine occlusion_stop_state_machine_; @@ -302,17 +307,15 @@ class IntersectionModule : public SceneModuleInterface const util::PathLanelets & path_lanelets, const std::optional & first_attention_area); - autoware_auto_perception_msgs::msg::PredictedObjects filterTargetObjects( - const lanelet::ConstLanelets & attention_area_lanelets, - const lanelet::ConstLanelets & adjacent_lanelets, + util::TargetObjects generateTargetObjects( + const util::IntersectionLanelets & intersection_lanelets, const std::optional & intersection_area) const; bool checkCollision( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, - const autoware_auto_perception_msgs::msg::PredictedObjects & target_objects, - const util::PathLanelets & path_lanelets, const size_t closest_idx, - const size_t last_intersection_stop_line_candidate_idx, const double time_delay, - const util::TrafficPrioritizedLevel & traffic_prioritized_level); + util::TargetObjects * target_objects, const util::PathLanelets & path_lanelets, + const size_t closest_idx, const size_t last_intersection_stop_line_candidate_idx, + const double time_delay, const util::TrafficPrioritizedLevel & traffic_prioritized_level); bool isOcclusionCleared( const nav_msgs::msg::OccupancyGrid & occ_grid, @@ -320,9 +323,8 @@ class IntersectionModule : public SceneModuleInterface const lanelet::ConstLanelets & adjacent_lanelets, const lanelet::CompoundPolygon3d & first_attention_area, const util::InterpolatedPathInfo & interpolated_path_info, - const std::vector & lane_divisions, - const std::vector & - parked_attention_objects, + const std::vector & lane_divisions, + const std::vector & blocking_attention_objects, const double occlusion_dist_thr); /* diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 17df31ba3ffc9..08c818c2e49ce 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -239,7 +239,8 @@ static std::optional getFirstPointInsidePolygonByFootprint( return std::nullopt; } -static std::optional> +static std::optional> getFirstPointInsidePolygonsByFootprint( const std::vector & polygons, const InterpolatedPathInfo & interpolated_path_info, @@ -252,12 +253,11 @@ getFirstPointInsidePolygonsByFootprint( const auto & pose = path_ip.points.at(i).point.pose; const auto path_footprint = tier4_autoware_utils::transformVector(footprint, tier4_autoware_utils::pose2transform(pose)); - for (const auto & polygon : polygons) { - const auto area_2d = lanelet::utils::to2D(polygon).basicPolygon(); + for (size_t j = 0; j < polygons.size(); ++j) { + const auto area_2d = lanelet::utils::to2D(polygons.at(j)).basicPolygon(); const bool is_in_polygon = bg::intersects(area_2d, path_footprint); if (is_in_polygon) { - return std::make_optional>( - i, polygon); + return std::make_optional>(i, j); } } } @@ -570,6 +570,101 @@ static std::vector getPolygon3dFromLanelets( return polys; } +static std::string getTurnDirection(lanelet::ConstLanelet lane) +{ + return lane.attributeOr("turn_direction", "else"); +} + +/** + * @param pair lanelets and the vector of original lanelets in topological order (not reversed as + *in generateDetectionLaneDivisions()) + **/ +static std::pair> +mergeLaneletsByTopologicalSort( + const lanelet::ConstLanelets & lanelets, + const lanelet::routing::RoutingGraphPtr routing_graph_ptr) +{ + const int n_node = lanelets.size(); + std::vector> adjacency(n_node); + for (int dst = 0; dst < n_node; ++dst) { + adjacency[dst].resize(n_node); + for (int src = 0; src < n_node; ++src) { + adjacency[dst][src] = false; + } + } + std::set lanelet_ids; + std::unordered_map id2ind; + std::unordered_map ind2id; + std::unordered_map id2lanelet; + int ind = 0; + for (const auto & lanelet : lanelets) { + lanelet_ids.insert(lanelet.id()); + const auto id = lanelet.id(); + id2ind[id] = ind; + ind2id[ind] = id; + id2lanelet[id] = lanelet; + ind++; + } + for (const auto & lanelet : lanelets) { + const auto & followings = routing_graph_ptr->following(lanelet); + const int dst = lanelet.id(); + for (const auto & following : followings) { + if (const int src = following.id(); lanelet_ids.find(src) != lanelet_ids.end()) { + adjacency[(id2ind[src])][(id2ind[dst])] = true; + } + } + } + // terminal node + std::map> branches; + auto has_no_previous = [&](const int node) { + for (int dst = 0; dst < n_node; dst++) { + if (adjacency[dst][node]) { + return false; + } + } + return true; + }; + for (int src = 0; src < n_node; src++) { + if (!has_no_previous(src)) { + continue; + } + branches[(ind2id[src])] = std::vector{}; + auto & branch = branches[(ind2id[src])]; + lanelet::Id node_iter = ind2id[src]; + while (true) { + const auto & destinations = adjacency[(id2ind[node_iter])]; + // NOTE: assuming detection lanelets have only one previous lanelet + const auto next = std::find(destinations.begin(), destinations.end(), true); + if (next == destinations.end()) { + branch.push_back(node_iter); + break; + } + branch.push_back(node_iter); + node_iter = ind2id[std::distance(destinations.begin(), next)]; + } + } + for (decltype(branches)::iterator it = branches.begin(); it != branches.end(); it++) { + auto & branch = it->second; + std::reverse(branch.begin(), branch.end()); + } + lanelet::ConstLanelets merged; + std::vector originals; + for (const auto & [id, sub_ids] : branches) { + if (sub_ids.size() == 0) { + continue; + } + lanelet::ConstLanelets merge; + originals.push_back(lanelet::ConstLanelets({})); + auto & original = originals.back(); + for (const auto sub_id : sub_ids) { + merge.push_back(id2lanelet[sub_id]); + original.push_back(id2lanelet[sub_id]); + } + merged.push_back(lanelet::utils::combineLaneletsShape(merge)); + } + return {merged, originals}; +} + IntersectionLanelets getObjectiveLanelets( lanelet::LaneletMapConstPtr lanelet_map_ptr, lanelet::routing::RoutingGraphPtr routing_graph_ptr, const lanelet::ConstLanelet assigned_lanelet, const lanelet::ConstLanelets & lanelets_on_path, @@ -707,14 +802,52 @@ IntersectionLanelets getObjectiveLanelets( } } } + lanelet::ConstLanelets occlusion_detection_and_preceding_lanelets_wo_turn_direction; + for (const auto & ll : occlusion_detection_and_preceding_lanelets) { + const auto turn_direction = getTurnDirection(ll); + if (turn_direction == "left" || turn_direction == "right") { + continue; + } + occlusion_detection_and_preceding_lanelets_wo_turn_direction.push_back(ll); + } + + auto [attention_lanelets, original_attention_lanelet_sequences] = + mergeLaneletsByTopologicalSort(detection_and_preceding_lanelets, routing_graph_ptr); IntersectionLanelets result; - result.attention_ = std::move(detection_and_preceding_lanelets); + result.attention_ = std::move(attention_lanelets); + for (const auto & original_attention_lanelet_seq : original_attention_lanelet_sequences) { + // NOTE: in mergeLaneletsByTopologicalSort(), sub_ids are empty checked, so it is ensured that + // back() exists. + std::optional stop_line{std::nullopt}; + for (auto it = original_attention_lanelet_seq.rbegin(); + it != original_attention_lanelet_seq.rend(); ++it) { + const auto traffic_lights = it->regulatoryElementsAs(); + for (const auto & traffic_light : traffic_lights) { + const auto stop_line_opt = traffic_light->stopLine(); + if (!stop_line_opt) continue; + stop_line = stop_line_opt.get(); + break; + } + if (stop_line) break; + } + result.attention_stop_lines_.push_back(stop_line); + } result.attention_non_preceding_ = std::move(detection_lanelets); + // TODO(Mamoru Sobue): find stop lines for attention_non_preceding_ if needed + for (unsigned i = 0; i < result.attention_non_preceding_.size(); ++i) { + result.attention_non_preceding_stop_lines_.push_back(std::nullopt); + } result.conflicting_ = std::move(conflicting_ex_ego_lanelets); result.adjacent_ = planning_utils::getConstLaneletsFromIds(lanelet_map_ptr, associative_ids); - result.occlusion_attention_ = std::move(occlusion_detection_and_preceding_lanelets); - // compoundPolygon3d + // NOTE: occlusion_attention is not inverted here + // TODO(Mamoru Sobue): apply mergeLaneletsByTopologicalSort for occlusion lanelets as well and + // then trim part of them based on curvature threshold + result.occlusion_attention_ = + std::move(occlusion_detection_and_preceding_lanelets_wo_turn_direction); + + // NOTE: to properly update(), each element in conflicting_/conflicting_area_, + // attention_non_preceding_/attention_non_preceding_area_ need to be matched result.attention_area_ = getPolygon3dFromLanelets(result.attention_); result.attention_non_preceding_area_ = getPolygon3dFromLanelets(result.attention_non_preceding_); result.conflicting_area_ = getPolygon3dFromLanelets(result.conflicting_); @@ -723,11 +856,6 @@ IntersectionLanelets getObjectiveLanelets( return result; } -static std::string getTurnDirection(lanelet::ConstLanelet lane) -{ - return lane.attributeOr("turn_direction", "else"); -} - bool isOverTargetIndex( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, const int closest_idx, const geometry_msgs::msg::Pose & current_pose, const int target_idx) @@ -893,21 +1021,17 @@ double getHighestCurvature(const lanelet::ConstLineString3d & centerline) return *std::max_element(curvatures_positive.begin(), curvatures_positive.end()); } -std::vector generateDetectionLaneDivisions( +std::vector generateDetectionLaneDivisions( lanelet::ConstLanelets detection_lanelets_all, const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution, const double curvature_threshold, const double curvature_calculation_ds) { using lanelet::utils::getCenterlineWithOffset; - using lanelet::utils::to2D; // (0) remove left/right lanelet lanelet::ConstLanelets detection_lanelets; for (const auto & detection_lanelet : detection_lanelets_all) { - const auto turn_direction = getTurnDirection(detection_lanelet); - if (turn_direction.compare("left") == 0 || turn_direction.compare("right") == 0) { - continue; - } + // TODO(Mamoru Sobue): instead of ignoring, only trim straight part of lanelet const auto fine_centerline = lanelet::utils::generateFineCenterline(detection_lanelet, curvature_calculation_ds); const double highest_curvature = getHighestCurvature(fine_centerline); @@ -918,111 +1042,34 @@ std::vector generateDetectionLaneDivisions( } // (1) tsort detection_lanelets - // generate adjacency matrix - // if lanelet1 => lanelet2; then adjacency[lanelet2][lanelet1] = true - const int n_node = detection_lanelets.size(); - std::vector> adjacency(n_node); - for (int dst = 0; dst < n_node; ++dst) { - adjacency[dst].resize(n_node); - for (int src = 0; src < n_node; ++src) { - adjacency[dst][src] = false; - } - } - std::set detection_lanelet_ids; - std::unordered_map id2ind, ind2id; - std::unordered_map id2lanelet; - int ind = 0; - for (const auto & detection_lanelet : detection_lanelets) { - detection_lanelet_ids.insert(detection_lanelet.id()); - const int id = detection_lanelet.id(); - id2ind[id] = ind; - ind2id[ind] = id; - id2lanelet[id] = detection_lanelet; - ind++; - } - for (const auto & detection_lanelet : detection_lanelets) { - const auto & followings = routing_graph_ptr->following(detection_lanelet); - const int dst = detection_lanelet.id(); - for (const auto & following : followings) { - if (const int src = following.id(); - detection_lanelet_ids.find(src) != detection_lanelet_ids.end()) { - adjacency[(id2ind[src])][(id2ind[dst])] = true; - } - } - } - // terminal node - std::map> branches; - auto has_no_previous = [&](const int node) { - for (int dst = 0; dst < n_node; dst++) { - if (adjacency[dst][node]) { - return false; - } - } - return true; - }; - for (int src = 0; src < n_node; src++) { - if (!has_no_previous(src)) { - continue; - } - branches[(ind2id[src])] = std::vector{}; - auto & branch = branches[(ind2id[src])]; - int node_iter = ind2id[src]; - while (true) { - const auto & destinations = adjacency[(id2ind[node_iter])]; - // NOTE: assuming detection lanelets have only one previous lanelet - const auto next = std::find(destinations.begin(), destinations.end(), true); - if (next == destinations.end()) { - branch.push_back(node_iter); - break; - } - branch.push_back(node_iter); - node_iter = ind2id[std::distance(destinations.begin(), next)]; - } - } - for (decltype(branches)::iterator it = branches.begin(); it != branches.end(); it++) { - auto & branch = it->second; - std::reverse(branch.begin(), branch.end()); - } + const auto [merged_detection_lanelets, originals] = + mergeLaneletsByTopologicalSort(detection_lanelets, routing_graph_ptr); // (2) merge each branch to one lanelet // NOTE: somehow bg::area() for merged lanelet does not work, so calculate it here - std::unordered_map> merged_branches; - for (const auto & [src, branch] : branches) { - lanelet::Points3d lefts; - lanelet::Points3d rights; + std::vector> merged_lanelet_with_area; + for (unsigned i = 0; i < merged_detection_lanelets.size(); ++i) { + const auto & merged_detection_lanelet = merged_detection_lanelets.at(i); + const auto & original = originals.at(i); double area = 0; - for (const auto & lane_id : branch) { - const auto lane = id2lanelet[lane_id]; - for (const auto & left_point : lane.leftBound()) { - lefts.push_back(lanelet::Point3d(left_point)); - } - for (const auto & right_point : lane.rightBound()) { - rights.push_back(lanelet::Point3d(right_point)); - } - area += bg::area(lane.polygon2d().basicPolygon()); + for (const auto & partition : original) { + area += bg::area(partition.polygon2d().basicPolygon()); } - lanelet::LineString3d right = lanelet::LineString3d(lanelet::InvalId, lefts).invert(); - lanelet::LineString3d left = lanelet::LineString3d(lanelet::InvalId, rights).invert(); - lanelet::Lanelet merged = lanelet::Lanelet(lanelet::InvalId, left, right); - merged_branches[src] = std::make_pair(merged, area); + merged_lanelet_with_area.emplace_back(merged_detection_lanelet, area); } // (3) discretize each merged lanelet - std::vector detection_divisions; - for (const auto & [last_lane_id, branch] : merged_branches) { - DiscretizedLane detection_division; - detection_division.lane_id = last_lane_id; - const auto detection_lanelet = branch.first; - const double area = branch.second; - const double length = bg::length(detection_lanelet.centerline()); + std::vector detection_divisions; + for (const auto & [merged_lanelet, area] : merged_lanelet_with_area) { + const double length = bg::length(merged_lanelet.centerline()); const double width = area / length; - auto & divisions = detection_division.divisions; for (int i = 0; i < static_cast(width / resolution); ++i) { const double offset = resolution * i - width / 2; - divisions.push_back(to2D(getCenterlineWithOffset(detection_lanelet, offset, resolution))); + detection_divisions.push_back( + getCenterlineWithOffset(merged_lanelet, offset, resolution).invert()); } - divisions.push_back(to2D(getCenterlineWithOffset(detection_lanelet, width / 2, resolution))); - detection_divisions.push_back(detection_division); + detection_divisions.push_back( + getCenterlineWithOffset(merged_lanelet, width / 2, resolution).invert()); } return detection_divisions; } @@ -1186,13 +1233,13 @@ Polygon2d generateStuckVehicleDetectAreaPolygon( return polygon; } -bool checkAngleForTargetLanelets( - const geometry_msgs::msg::Pose & pose, const double longitudinal_velocity, - const lanelet::ConstLanelets & target_lanelets, const double detection_area_angle_thr, - const bool consider_wrong_direction_vehicle, const double dist_margin, - const double parked_vehicle_speed_threshold) +std::optional checkAngleForTargetLanelets( + const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, + const double detection_area_angle_thr, const bool consider_wrong_direction_vehicle, + const double dist_margin, const bool is_parked_vehicle) { - for (const auto & ll : target_lanelets) { + for (unsigned i = 0; i < target_lanelets.size(); ++i) { + const auto & ll = target_lanelets.at(i); if (!lanelet::utils::isInLanelet(pose, ll, dist_margin)) { continue; } @@ -1201,39 +1248,38 @@ bool checkAngleForTargetLanelets( const double angle_diff = tier4_autoware_utils::normalizeRadian(ll_angle - pose_angle, -M_PI); if (consider_wrong_direction_vehicle) { if (std::fabs(angle_diff) > 1.57 || std::fabs(angle_diff) < detection_area_angle_thr) { - return true; + return std::make_optional(i); } } else { if (std::fabs(angle_diff) < detection_area_angle_thr) { - return true; + return std::make_optional(i); } // NOTE: sometimes parked vehicle direction is reversed even if its longitudinal velocity is // positive if ( - std::fabs(longitudinal_velocity) < parked_vehicle_speed_threshold && - (std::fabs(angle_diff) < detection_area_angle_thr || - (std::fabs(angle_diff + M_PI) < detection_area_angle_thr))) { - return true; + is_parked_vehicle && (std::fabs(angle_diff) < detection_area_angle_thr || + (std::fabs(angle_diff + M_PI) < detection_area_angle_thr))) { + return std::make_optional(i); } } } - return false; + return std::nullopt; } void cutPredictPathWithDuration( - autoware_auto_perception_msgs::msg::PredictedObjects * objects_ptr, - const rclcpp::Clock::SharedPtr clock, const double time_thr) + util::TargetObjects * target_objects, const rclcpp::Clock::SharedPtr clock, const double time_thr) { const rclcpp::Time current_time = clock->now(); - for (auto & object : objects_ptr->objects) { // each objects - for (auto & predicted_path : object.kinematics.predicted_paths) { // each predicted paths + for (auto & target_object : target_objects->all) { // each objects + for (auto & predicted_path : + target_object.object.kinematics.predicted_paths) { // each predicted paths const auto origin_path = predicted_path; predicted_path.path.clear(); for (size_t k = 0; k < origin_path.path.size(); ++k) { // each path points const auto & predicted_pose = origin_path.path.at(k); const auto predicted_time = - rclcpp::Time(objects_ptr->header.stamp) + + rclcpp::Time(target_objects->header.stamp) + rclcpp::Duration(origin_path.time_step) * static_cast(k); if ((predicted_time - current_time).seconds() < time_thr) { predicted_path.path.push_back(predicted_pose); @@ -1342,14 +1388,16 @@ void IntersectionLanelets::update( auto first = getFirstPointInsidePolygonsByFootprint(conflicting_area_, interpolated_path_info, footprint); if (first) { - first_conflicting_area_ = first.value().second; + first_conflicting_lane_ = conflicting_.at(first.value().second); + first_conflicting_area_ = conflicting_area_.at(first.value().second); } } if (!first_attention_area_) { - auto first = - getFirstPointInsidePolygonsByFootprint(attention_area_, interpolated_path_info, footprint); + auto first = getFirstPointInsidePolygonsByFootprint( + attention_non_preceding_area_, interpolated_path_info, footprint); if (first) { - first_attention_area_ = first.value().second; + first_attention_lane_ = attention_non_preceding_.at(first.value().second); + first_attention_area_ = attention_non_preceding_area_.at(first.value().second); } } } @@ -1435,5 +1483,23 @@ std::optional generatePathLanelets( return path_lanelets; } +void TargetObject::calc_dist_to_stop_line() +{ + if (!attention_lanelet || !stop_line) { + return; + } + const auto attention_lanelet_val = attention_lanelet.value(); + const auto object_arc_coords = lanelet::utils::getArcCoordinates( + {attention_lanelet_val}, object.kinematics.initial_pose_with_covariance.pose); + const auto stop_line_val = stop_line.value(); + geometry_msgs::msg::Pose stopline_center; + stopline_center.position.x = (stop_line_val.front().x() + stop_line_val.back().x()) / 2.0; + stopline_center.position.y = (stop_line_val.front().y() + stop_line_val.back().y()) / 2.0; + stopline_center.position.z = (stop_line_val.front().z() + stop_line_val.back().z()) / 2.0; + const auto stopline_arc_coords = + lanelet::utils::getArcCoordinates({attention_lanelet_val}, stopline_center); + dist_to_stop_line = (stopline_arc_coords.length - object_arc_coords.length); +} + } // namespace util } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 001aa63c7fe12..ef658a25fce55 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -107,7 +107,7 @@ bool hasAssociatedTrafficLight(lanelet::ConstLanelet lane); TrafficPrioritizedLevel getTrafficPrioritizedLevel( lanelet::ConstLanelet lane, const std::map & tl_infos); -std::vector generateDetectionLaneDivisions( +std::vector generateDetectionLaneDivisions( lanelet::ConstLanelets detection_lanelets, const lanelet::routing::RoutingGraphPtr routing_graph_ptr, const double resolution, const double curvature_threshold, const double curvature_calculation_ds); @@ -134,15 +134,14 @@ bool checkYieldStuckVehicleInIntersection( Polygon2d generateStuckVehicleDetectAreaPolygon( const util::PathLanelets & path_lanelets, const double stuck_vehicle_detect_dist); -bool checkAngleForTargetLanelets( - const geometry_msgs::msg::Pose & pose, const double longitudinal_velocity, - const lanelet::ConstLanelets & target_lanelets, const double detection_area_angle_thr, - const bool consider_wrong_direction_vehicle, const double dist_margin, - const double parked_vehicle_speed_threshold); +std::optional checkAngleForTargetLanelets( + const geometry_msgs::msg::Pose & pose, const lanelet::ConstLanelets & target_lanelets, + const double detection_area_angle_thr, const bool consider_wrong_direction_vehicle, + const double dist_margin, const bool is_parked_vehicle); void cutPredictPathWithDuration( - autoware_auto_perception_msgs::msg::PredictedObjects * objects_ptr, - const rclcpp::Clock::SharedPtr clock, const double time_thr); + util::TargetObjects * target_objects, const rclcpp::Clock::SharedPtr clock, + const double time_thr); TimeDistanceArray calcIntersectionPassingTime( const autoware_auto_planning_msgs::msg::PathWithLaneId & path, diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 11c8d7d8407b7..3f09b54f88be4 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -49,6 +49,7 @@ struct DebugData std::optional candidate_collision_ego_lane_polygon{std::nullopt}; std::vector candidate_collision_object_polygons; autoware_auto_perception_msgs::msg::PredictedObjects conflicting_targets; + autoware_auto_perception_msgs::msg::PredictedObjects amber_ignore_targets; autoware_auto_perception_msgs::msg::PredictedObjects stuck_targets; autoware_auto_perception_msgs::msg::PredictedObjects yield_stuck_targets; std::vector occlusion_polygons; @@ -77,6 +78,10 @@ struct IntersectionLanelets { return is_prioritized_ ? attention_non_preceding_ : attention_; } + const std::vector> & attention_stop_lines() const + { + return is_prioritized_ ? attention_non_preceding_stop_lines_ : attention_stop_lines_; + } const lanelet::ConstLanelets & conflicting() const { return conflicting_; } const lanelet::ConstLanelets & adjacent() const { return adjacent_; } const lanelet::ConstLanelets & occlusion_attention() const @@ -96,38 +101,48 @@ struct IntersectionLanelets { return occlusion_attention_area_; } + const std::optional & first_conflicting_lane() const + { + return first_conflicting_lane_; + } const std::optional & first_conflicting_area() const { return first_conflicting_area_; } + const std::optional & first_attention_lane() const + { + return first_attention_lane_; + } const std::optional & first_attention_area() const { return first_attention_area_; } - lanelet::ConstLanelets attention_; + lanelet::ConstLanelets attention_; // topologically merged lanelets + std::vector> + attention_stop_lines_; // the stop lines for each attention_ lanelets lanelet::ConstLanelets attention_non_preceding_; + std::vector> + attention_non_preceding_stop_lines_; // the stop lines for each attention_non_preceding_ + // lanelets lanelet::ConstLanelets conflicting_; lanelet::ConstLanelets adjacent_; - lanelet::ConstLanelets occlusion_attention_; // for occlusion detection - std::vector attention_area_; + lanelet::ConstLanelets occlusion_attention_; // topologically merged lanelets + std::vector occlusion_attention_size_; // the area() of each occlusion attention lanelets + std::vector attention_area_; // topologically merged lanelets std::vector attention_non_preceding_area_; std::vector conflicting_area_; std::vector adjacent_area_; - std::vector occlusion_attention_area_; + std::vector + occlusion_attention_area_; // topologically merged lanelets // the first area intersecting with the path // even if lane change/re-routing happened on the intersection, these areas area are supposed to // be invariant under the 'associative' lanes. + std::optional first_conflicting_lane_{std::nullopt}; + std::optional first_conflicting_area_{std::nullopt}; + std::optional first_attention_lane_{std::nullopt}; + std::optional first_attention_area_{std::nullopt}; bool is_prioritized_ = false; - std::optional first_conflicting_area_ = std::nullopt; - std::optional first_attention_area_ = std::nullopt; -}; - -struct DiscretizedLane -{ - int lane_id{0}; - // discrete fine lines from left to right - std::vector divisions{}; }; struct IntersectionStopLines @@ -161,6 +176,24 @@ struct PathLanelets // conflicting lanelets plus the next lane part of the path }; +struct TargetObject +{ + autoware_auto_perception_msgs::msg::PredictedObject object; + std::optional attention_lanelet{std::nullopt}; + std::optional stop_line{std::nullopt}; + std::optional dist_to_stop_line{std::nullopt}; + void calc_dist_to_stop_line(); +}; + +struct TargetObjects +{ + std_msgs::msg::Header header; + std::vector attention_objects; + std::vector parked_attention_objects; + std::vector intersection_area_objects; + std::vector all; // TODO(Mamoru Sobue): avoid copy +}; + enum class TrafficPrioritizedLevel { // The target lane's traffic signal is red or the ego's traffic signal has an arrow. FULLY_PRIORITIZED = 0, From 1c5ca20218a1968888e42fd30cf535aa24dcc79f Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 15 Oct 2023 19:32:06 +0900 Subject: [PATCH 469/547] feat(lane_change): add rss paramas for stuck (#5300) Signed-off-by: kosuke55 --- .../config/lane_change/lane_change.param.yaml | 8 +++++ .../scene_module/lane_change/base_class.hpp | 3 +- .../scene_module/lane_change/normal.hpp | 3 +- .../lane_change/lane_change_module_data.hpp | 1 + .../src/scene_module/lane_change/manager.cpp | 15 +++++++++ .../src/scene_module/lane_change/normal.cpp | 33 +++++++++++++------ 6 files changed, 51 insertions(+), 12 deletions(-) diff --git a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml index e7f3b51bd2d26..d3c0a22e867f9 100644 --- a/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml +++ b/planning/behavior_path_planner/config/lane_change/lane_change.param.yaml @@ -45,6 +45,14 @@ lateral_distance_max_threshold: 1.0 longitudinal_distance_min_threshold: 2.5 longitudinal_velocity_delta_time: 0.6 + stuck: + expected_front_deceleration: -1.0 + expected_rear_deceleration: -1.0 + rear_vehicle_reaction_time: 2.0 + rear_vehicle_safety_time_margin: 1.0 + lateral_distance_max_threshold: 2.0 + longitudinal_distance_min_threshold: 3.0 + longitudinal_velocity_delta_time: 0.8 # lane expansion for object filtering lane_expansion: diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp index a3469dee2909b..26f68e69b3728 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp @@ -230,7 +230,8 @@ class LaneChangeBase virtual bool getLaneChangePaths( const lanelet::ConstLanelets & original_lanelets, const lanelet::ConstLanelets & target_lanelets, Direction direction, - LaneChangePaths * candidate_paths, const bool check_safety) const = 0; + LaneChangePaths * candidate_paths, const utils::path_safety_checker::RSSparams rss_params, + const bool is_stuck, const bool check_safety) const = 0; virtual TurnSignalInfo calcTurnSignalInfo() = 0; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index 4f1eb7340fdff..e9e361e1c39b8 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -138,6 +138,7 @@ class NormalLaneChange : public LaneChangeBase bool getLaneChangePaths( const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, Direction direction, LaneChangePaths * candidate_paths, + const utils::path_safety_checker::RSSparams rss_params, const bool is_stuck, const bool check_safety = true) const override; TurnSignalInfo calcTurnSignalInfo() override; @@ -146,7 +147,7 @@ class NormalLaneChange : public LaneChangeBase PathSafetyStatus isLaneChangePathSafe( const LaneChangePath & lane_change_path, const LaneChangeTargetObjects & target_objects, - const utils::path_safety_checker::RSSparams & rss_params, + const utils::path_safety_checker::RSSparams & rss_params, const bool is_stuck, CollisionCheckDebugMap & debug_data) const; LaneChangeTargetObjectIndices filterObject( diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index 24a507d1f8695..8b9ef52394cdd 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -85,6 +85,7 @@ struct LaneChangeParameters bool allow_loose_check_for_cancel{true}; utils::path_safety_checker::RSSparams rss_params{}; utils::path_safety_checker::RSSparams rss_params_for_abort{}; + utils::path_safety_checker::RSSparams rss_params_for_stuck{}; // abort LaneChangeCancelParameters cancel{}; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index e33a6c4b05ee3..3d7cf07308a79 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -128,6 +128,21 @@ LaneChangeModuleManager::LaneChangeModuleManager( p.rss_params_for_abort.lateral_distance_max_threshold = getOrDeclareParameter( *node, parameter("safety_check.cancel.lateral_distance_max_threshold")); + p.rss_params_for_stuck.longitudinal_distance_min_threshold = getOrDeclareParameter( + *node, parameter("safety_check.stuck.longitudinal_distance_min_threshold")); + p.rss_params_for_stuck.longitudinal_velocity_delta_time = getOrDeclareParameter( + *node, parameter("safety_check.stuck.longitudinal_velocity_delta_time")); + p.rss_params_for_stuck.front_vehicle_deceleration = getOrDeclareParameter( + *node, parameter("safety_check.stuck.expected_front_deceleration")); + p.rss_params_for_stuck.rear_vehicle_deceleration = getOrDeclareParameter( + *node, parameter("safety_check.stuck.expected_rear_deceleration")); + p.rss_params_for_stuck.rear_vehicle_reaction_time = getOrDeclareParameter( + *node, parameter("safety_check.stuck.rear_vehicle_reaction_time")); + p.rss_params_for_stuck.rear_vehicle_safety_time_margin = getOrDeclareParameter( + *node, parameter("safety_check.stuck.rear_vehicle_safety_time_margin")); + p.rss_params_for_stuck.lateral_distance_max_threshold = getOrDeclareParameter( + *node, parameter("safety_check.stuck.lateral_distance_max_threshold")); + // target object { std::string ns = "lane_change.target_object."; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index c332438dd31a5..d155b204f62d6 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -83,8 +83,17 @@ std::pair NormalLaneChange::getSafePath(LaneChangePath & safe_path) // find candidate paths LaneChangePaths valid_paths{}; - const auto found_safe_path = - getLaneChangePaths(current_lanes, target_lanes, direction_, &valid_paths); + const bool is_stuck = isVehicleStuckByObstacle(current_lanes); + bool found_safe_path = getLaneChangePaths( + current_lanes, target_lanes, direction_, &valid_paths, lane_change_parameters_->rss_params, + is_stuck); + // if no safe path is found and ego is stuck, try to find a path with a small margin + if (!found_safe_path && is_stuck) { + found_safe_path = getLaneChangePaths( + current_lanes, target_lanes, direction_, &valid_paths, + lane_change_parameters_->rss_params_for_stuck, is_stuck); + } + debug_valid_path_ = valid_paths; if (valid_paths.empty()) { @@ -1006,7 +1015,9 @@ bool NormalLaneChange::hasEnoughLengthToIntersection( bool NormalLaneChange::getLaneChangePaths( const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, - Direction direction, LaneChangePaths * candidate_paths, const bool check_safety) const + Direction direction, LaneChangePaths * candidate_paths, + const utils::path_safety_checker::RSSparams rss_params, const bool is_stuck, + const bool check_safety) const { object_debug_.clear(); if (current_lanes.empty() || target_lanes.empty()) { @@ -1237,9 +1248,11 @@ bool NormalLaneChange::getLaneChangePaths( } candidate_paths->push_back(*candidate_path); - if (utils::lane_change::passParkedObject( - route_handler, *candidate_path, target_objects.target_lane, lane_change_buffer, - is_goal_in_route, *lane_change_parameters_, object_debug_)) { + if ( + !is_stuck && + utils::lane_change::passParkedObject( + route_handler, *candidate_path, target_objects.target_lane, lane_change_buffer, + is_goal_in_route, *lane_change_parameters_, object_debug_)) { return false; } @@ -1248,7 +1261,7 @@ bool NormalLaneChange::getLaneChangePaths( } const auto [is_safe, is_object_coming_from_rear] = isLaneChangePathSafe( - *candidate_path, target_objects, lane_change_parameters_->rss_params, object_debug_); + *candidate_path, target_objects, rss_params, is_stuck, object_debug_); if (is_safe) { return true; @@ -1270,8 +1283,9 @@ PathSafetyStatus NormalLaneChange::isApprovedPathSafe() const debug_filtered_objects_ = target_objects; CollisionCheckDebugMap debug_data; + const bool is_stuck = isVehicleStuckByObstacle(current_lanes); const auto safety_status = isLaneChangePathSafe( - path, target_objects, lane_change_parameters_->rss_params_for_abort, debug_data); + path, target_objects, lane_change_parameters_->rss_params_for_abort, is_stuck, debug_data); { // only for debug purpose object_debug_.clear(); @@ -1528,7 +1542,7 @@ bool NormalLaneChange::getAbortPath() PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( const LaneChangePath & lane_change_path, const LaneChangeTargetObjects & target_objects, - const utils::path_safety_checker::RSSparams & rss_params, + const utils::path_safety_checker::RSSparams & rss_params, const bool is_stuck, CollisionCheckDebugMap & debug_data) const { PathSafetyStatus path_safety_status; @@ -1552,7 +1566,6 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( auto collision_check_objects = target_objects.target_lane; const auto current_lanes = getCurrentLanes(); - const auto is_stuck = isVehicleStuckByObstacle(current_lanes); if (lane_change_parameters_->check_objects_on_current_lanes || is_stuck) { collision_check_objects.insert( From b266f47aeaf85088e02ff851e544757634ceb076 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Sun, 15 Oct 2023 19:32:21 +0900 Subject: [PATCH 470/547] fix(lane_change): fix chattering for stopped objects (#5302) Update planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp fix(path_safety_checker): remove redundant parameter name Signed-off-by: Fumiya Watanabe --- .../path_safety_checker_parameters.hpp | 25 ++++---- .../path_safety_checker/safety_check.hpp | 5 +- .../src/marker_utils/utils.cpp | 5 +- .../path_safety_checker/safety_check.cpp | 60 ++++++++++++------- .../test/test_safety_check.cpp | 19 +++--- 5 files changed, 69 insertions(+), 45 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp index 889f016e7182f..dc113b0b0be18 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp @@ -182,18 +182,19 @@ struct SafetyCheckParams struct CollisionCheckDebug { - std::string unsafe_reason; ///< Reason indicating unsafe situation. - Twist current_twist{}; ///< Ego vehicle's current velocity and rotation. - Pose expected_ego_pose{}; ///< Predicted future pose of ego vehicle. - Pose current_obj_pose{}; ///< Detected object's current pose. - Twist object_twist{}; ///< Detected object's velocity and rotation. - Pose expected_obj_pose{}; ///< Predicted future pose of object. - double rss_longitudinal{0.0}; ///< Longitudinal RSS measure. - double inter_vehicle_distance{0.0}; ///< Distance between ego vehicle and object. - double extended_polygon_lon_offset{0.0}; ///< Longitudinal offset for extended polygon. - double extended_polygon_lat_offset{0.0}; ///< Lateral offset for extended polygon. - bool is_front{false}; ///< True if object is in front of ego vehicle. - bool is_safe{false}; ///< True if situation is deemed safe. + std::string unsafe_reason; ///< Reason indicating unsafe situation. + Twist current_twist{}; ///< Ego vehicle's current velocity and rotation. + Pose expected_ego_pose{}; ///< Predicted future pose of ego vehicle. + Pose current_obj_pose{}; ///< Detected object's current pose. + Twist object_twist{}; ///< Detected object's velocity and rotation. + Pose expected_obj_pose{}; ///< Predicted future pose of object. + double rss_longitudinal{0.0}; ///< Longitudinal RSS measure. + double inter_vehicle_distance{0.0}; ///< Distance between ego vehicle and object. + double forward_lon_offset{0.0}; ///< Forward longitudinal offset for extended polygon. + double backward_lon_offset{0.0}; ///< Backward longitudinal offset for extended polygon. + double lat_offset{0.0}; ///< Lateral offset for extended polygon. + bool is_front{false}; ///< True if object is in front of ego vehicle. + bool is_safe{false}; ///< True if situation is deemed safe. std::vector ego_predicted_path; ///< ego vehicle's predicted path. std::vector obj_predicted_path; ///< object's predicted path. Polygon2d extended_ego_polygon{}; ///< Ego vehicle's extended collision polygon. diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 6e5dd2cda8920..64f5c9e5fc3e0 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -66,10 +66,11 @@ bool isTargetObjectFront( Polygon2d createExtendedPolygon( const Pose & base_link_pose, const vehicle_info_util::VehicleInfo & vehicle_info, - const double lon_length, const double lat_margin, CollisionCheckDebug & debug); + const double lon_length, const double lat_margin, const double is_stopped_obj, + CollisionCheckDebug & debug); Polygon2d createExtendedPolygon( const Pose & obj_pose, const Shape & shape, const double lon_length, const double lat_margin, - CollisionCheckDebug & debug); + const double is_stopped_obj, CollisionCheckDebug & debug); PredictedPath convertToPredictedPath( const std::vector & path, const double time_resolution); diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index a2883403e1ccd..03ac135ec4bc1 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -641,8 +641,9 @@ MarkerArray showSafetyCheckInfo(const CollisionCheckDebugMap & obj_debug_vec, st << "\nRSS dist: " << std::setprecision(4) << info.rss_longitudinal << "\nEgo to obj: " << info.inter_vehicle_distance << "\nExtended polygon: " << (info.is_front ? "ego" : "object") - << "\nExtended polygon lateral offset: " << info.extended_polygon_lat_offset - << "\nExtended polygon longitudinal offset: " << info.extended_polygon_lon_offset + << "\nExtended polygon lateral offset: " << info.lat_offset + << "\nExtended polygon forward longitudinal offset: " << info.forward_lon_offset + << "\nExtended polygon backward longitudinal offset: " << info.backward_lon_offset << "\nLast checked position: " << (info.is_front ? "obj in front ego" : "obj at back ego") << "\nSafe: " << (info.is_safe ? "Yes" : "No"); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index b44a3f841035d..e4698e98d46eb 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -82,28 +82,33 @@ bool isTargetObjectFront( Polygon2d createExtendedPolygon( const Pose & base_link_pose, const vehicle_info_util::VehicleInfo & vehicle_info, - const double lon_length, const double lat_margin, CollisionCheckDebug & debug) + const double lon_length, const double lat_margin, const double is_stopped_obj, + CollisionCheckDebug & debug) { const double & base_to_front = vehicle_info.max_longitudinal_offset_m; const double & width = vehicle_info.vehicle_width_m; const double & base_to_rear = vehicle_info.rear_overhang_m; - const double lon_offset = std::max(lon_length + base_to_front, base_to_front); - + // if stationary object, extend forward and backward by the half of lon length + const double forward_lon_offset = base_to_front + (is_stopped_obj ? lon_length / 2 : lon_length); + const double backward_lon_offset = + -base_to_rear - (is_stopped_obj ? lon_length / 2 : 0); // minus value const double lat_offset = width / 2.0 + lat_margin; { - debug.extended_polygon_lon_offset = lon_offset; - debug.extended_polygon_lat_offset = lat_offset; + debug.forward_lon_offset = forward_lon_offset; + debug.backward_lon_offset = backward_lon_offset; + debug.lat_offset = lat_offset; } - const auto p1 = tier4_autoware_utils::calcOffsetPose(base_link_pose, lon_offset, lat_offset, 0.0); + const auto p1 = + tier4_autoware_utils::calcOffsetPose(base_link_pose, forward_lon_offset, lat_offset, 0.0); const auto p2 = - tier4_autoware_utils::calcOffsetPose(base_link_pose, lon_offset, -lat_offset, 0.0); + tier4_autoware_utils::calcOffsetPose(base_link_pose, forward_lon_offset, -lat_offset, 0.0); const auto p3 = - tier4_autoware_utils::calcOffsetPose(base_link_pose, -base_to_rear, -lat_offset, 0.0); + tier4_autoware_utils::calcOffsetPose(base_link_pose, backward_lon_offset, -lat_offset, 0.0); const auto p4 = - tier4_autoware_utils::calcOffsetPose(base_link_pose, -base_to_rear, lat_offset, 0.0); + tier4_autoware_utils::calcOffsetPose(base_link_pose, backward_lon_offset, lat_offset, 0.0); Polygon2d polygon; appendPointToPolygon(polygon, p1.position); @@ -118,7 +123,7 @@ Polygon2d createExtendedPolygon( Polygon2d createExtendedPolygon( const Pose & obj_pose, const Shape & shape, const double lon_length, const double lat_margin, - CollisionCheckDebug & debug) + const double is_stopped_obj, CollisionCheckDebug & debug) { const auto obj_polygon = tier4_autoware_utils::toPolygon2d(obj_pose, shape); if (obj_polygon.outer().empty()) { @@ -139,19 +144,27 @@ Polygon2d createExtendedPolygon( min_y = std::min(transformed_p.y, min_y); } - const double lon_offset = max_x + lon_length; + // if stationary object, extend forward and backward by the half of lon length + const double forward_lon_offset = max_x + (is_stopped_obj ? lon_length / 2 : lon_length); + const double backward_lon_offset = min_x - (is_stopped_obj ? lon_length / 2 : 0); // minus value + const double left_lat_offset = max_y + lat_margin; const double right_lat_offset = min_y - lat_margin; { - debug.extended_polygon_lon_offset = lon_offset; - debug.extended_polygon_lat_offset = (left_lat_offset + right_lat_offset) / 2; + debug.forward_lon_offset = forward_lon_offset; + debug.backward_lon_offset = backward_lon_offset; + debug.lat_offset = (left_lat_offset + right_lat_offset) / 2; } - const auto p1 = tier4_autoware_utils::calcOffsetPose(obj_pose, lon_offset, left_lat_offset, 0.0); - const auto p2 = tier4_autoware_utils::calcOffsetPose(obj_pose, lon_offset, right_lat_offset, 0.0); - const auto p3 = tier4_autoware_utils::calcOffsetPose(obj_pose, min_x, right_lat_offset, 0.0); - const auto p4 = tier4_autoware_utils::calcOffsetPose(obj_pose, min_x, left_lat_offset, 0.0); + const auto p1 = + tier4_autoware_utils::calcOffsetPose(obj_pose, forward_lon_offset, left_lat_offset, 0.0); + const auto p2 = + tier4_autoware_utils::calcOffsetPose(obj_pose, forward_lon_offset, right_lat_offset, 0.0); + const auto p3 = + tier4_autoware_utils::calcOffsetPose(obj_pose, backward_lon_offset, right_lat_offset, 0.0); + const auto p4 = + tier4_autoware_utils::calcOffsetPose(obj_pose, backward_lon_offset, left_lat_offset, 0.0); Polygon2d polygon; appendPointToPolygon(polygon, p1.position); @@ -338,14 +351,17 @@ std::vector getCollidedPolygons( const auto & lon_offset = std::max(rss_dist, min_lon_length) * hysteresis_factor; const auto & lat_margin = rss_parameters.lateral_distance_max_threshold * hysteresis_factor; - const auto & extended_ego_polygon = - is_object_front - ? createExtendedPolygon(ego_pose, ego_vehicle_info, lon_offset, lat_margin, debug) - : ego_polygon; + // TODO(watanabe) fix hard coding value + const bool is_stopped_object = object_velocity < 0.3; + const auto & extended_ego_polygon = is_object_front ? createExtendedPolygon( + ego_pose, ego_vehicle_info, lon_offset, + lat_margin, is_stopped_object, debug) + : ego_polygon; const auto & extended_obj_polygon = is_object_front ? obj_polygon - : createExtendedPolygon(obj_pose, target_object.shape, lon_offset, lat_margin, debug); + : createExtendedPolygon( + obj_pose, target_object.shape, lon_offset, lat_margin, is_stopped_object, debug); { debug.rss_longitudinal = rss_dist; diff --git a/planning/behavior_path_planner/test/test_safety_check.cpp b/planning/behavior_path_planner/test/test_safety_check.cpp index 39e831bd5565b..a5fc19d1ecbbe 100644 --- a/planning/behavior_path_planner/test/test_safety_check.cpp +++ b/planning/behavior_path_planner/test/test_safety_check.cpp @@ -52,9 +52,10 @@ TEST(BehaviorPathPlanningSafetyUtilsTest, createExtendedEgoPolygon) const double lon_length = 10.0; const double lat_margin = 2.0; + const bool is_stopped_object = false; - const auto polygon = - createExtendedPolygon(ego_pose, vehicle_info, lon_length, lat_margin, debug); + const auto polygon = createExtendedPolygon( + ego_pose, vehicle_info, lon_length, lat_margin, is_stopped_object, debug); EXPECT_EQ(polygon.outer().size(), static_cast(5)); @@ -79,9 +80,10 @@ TEST(BehaviorPathPlanningSafetyUtilsTest, createExtendedEgoPolygon) const double lon_length = 10.0; const double lat_margin = 2.0; + const bool is_stopped_object = false; - const auto polygon = - createExtendedPolygon(ego_pose, vehicle_info, lon_length, lat_margin, debug); + const auto polygon = createExtendedPolygon( + ego_pose, vehicle_info, lon_length, lat_margin, is_stopped_object, debug); EXPECT_EQ(polygon.outer().size(), static_cast(5)); @@ -107,9 +109,10 @@ TEST(BehaviorPathPlanningSafetyUtilsTest, createExtendedEgoPolygon) const double lon_length = 10.0; const double lat_margin = 2.0; + const bool is_stopped_object = false; - const auto polygon = - createExtendedPolygon(ego_pose, vehicle_info, lon_length, lat_margin, debug); + const auto polygon = createExtendedPolygon( + ego_pose, vehicle_info, lon_length, lat_margin, is_stopped_object, debug); EXPECT_EQ(polygon.outer().size(), static_cast(5)); @@ -155,9 +158,11 @@ TEST(BehaviorPathPlanningSafetyUtilsTest, createExtendedObjPolygon) const double lon_length = 10.0; const double lat_margin = 2.0; + const bool is_stopped_object = false; CollisionCheckDebug debug; - const auto polygon = createExtendedPolygon(obj_pose, shape, lon_length, lat_margin, debug); + const auto polygon = + createExtendedPolygon(obj_pose, shape, lon_length, lat_margin, is_stopped_object, debug); EXPECT_EQ(polygon.outer().size(), static_cast(5)); From 5d6449defe3309b5fc7a438eaa511e5d1985c0cf Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Sun, 15 Oct 2023 19:32:46 +0900 Subject: [PATCH 471/547] fix(lane_change): add margin in stuck detection distance (#5306) * fix(lane_change): add margin in stuck detection distance Signed-off-by: Takamasa Horibe * use margin Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- .../src/scene_module/lane_change/normal.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index d155b204f62d6..e691a66d118d7 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -655,7 +655,7 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( } // If the ego is in stuck, sampling all possible accelerations to find avoiding path. - if (isVehicleStuckByObstacle(current_lanes, max_lane_change_length)) { + if (isVehicleStuckByObstacle(current_lanes)) { auto clock = rclcpp::Clock(RCL_ROS_TIME); RCLCPP_INFO_THROTTLE( logger_, clock, 1000, "Vehicle is stuck. sample all longitudinal acceleration."); @@ -1668,7 +1668,21 @@ bool NormalLaneChange::isVehicleStuckByObstacle(const lanelet::ConstLanelets & c const auto [min_acc, max_acc] = calcCurrentMinMaxAcceleration(); const auto max_lane_change_length = calcMaximumLaneChangeLength(current_lanes.back(), max_acc); - return isVehicleStuckByObstacle(current_lanes, max_lane_change_length); + const auto rss_dist = calcRssDistance( + 0.0, planner_data_->parameters.minimum_lane_changing_velocity, + lane_change_parameters_->rss_params); + + // It is difficult to define the detection range. If it is too short, the stuck will not be + // determined, even though you are stuck by an obstacle. If it is too long, + // the ego will be judged to be stuck by a distant vehicle, even though the ego is only + // stopped at a traffic light. Essentially, the calculation should be based on the information of + // the stop reason, but this is outside the scope of one module. I keep it as a TODO. + constexpr double DETECTION_DISTANCE_MARGIN = 10.0; + const auto detection_distance = max_lane_change_length + rss_dist + + getCommonParam().base_link2front + DETECTION_DISTANCE_MARGIN; + RCLCPP_INFO(logger_, "max_lane_change_length: %f, max_acc: %f", max_lane_change_length, max_acc); + + return isVehicleStuckByObstacle(current_lanes, detection_distance); } void NormalLaneChange::setStopPose(const Pose & stop_pose) From 95e8f212a6a8b5eb92941ae0d4d8dc997970de25 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 16 Oct 2023 12:46:17 +0900 Subject: [PATCH 472/547] refactor(lane_change): add debug log (#5308) Signed-off-by: Takamasa Horibe --- .../config/logger_config.yaml | 4 + .../scene_module/lane_change/base_class.hpp | 3 + .../scene_module/lane_change/normal.hpp | 1 - .../src/scene_module/lane_change/normal.cpp | 85 ++++++++++++++----- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml b/common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml index da5cc757682c5..97c3104242026 100644 --- a/common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml +++ b/common/tier4_logging_level_configure_rviz_plugin/config/logger_config.yaml @@ -14,6 +14,10 @@ behavior_path_planner_avoidance: - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner logger_name: planning.scenario_planning.lane_driving.behavior_planning.behavior_path_planner.avoidance +behavior_path_planner_lane_change: + - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_path_planner + logger_name: lane_change + behavior_velocity_planner: - node_name: /planning/scenario_planning/lane_driving/behavior_planning/behavior_velocity_planner logger_name: planning.scenario_planning.lane_driving.behavior_planning.behavior_velocity_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp index 26f68e69b3728..472564a061f04 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/base_class.hpp @@ -271,6 +271,9 @@ class LaneChangeBase mutable LaneChangeTargetObjects debug_filtered_objects_{}; mutable double object_debug_lifetime_{0.0}; mutable StopWatch stop_watch_; + + rclcpp::Logger logger_ = rclcpp::get_logger("lane_change"); + mutable rclcpp::Clock clock_{RCL_ROS_TIME}; }; } // namespace behavior_path_planner #endif // BEHAVIOR_PATH_PLANNER__SCENE_MODULE__LANE_CHANGE__BASE_CLASS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index e9e361e1c39b8..ccadcd7144a9b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -173,7 +173,6 @@ class NormalLaneChange : public LaneChangeBase double getStopTime() const { return stop_time_; } - rclcpp::Logger logger_ = rclcpp::get_logger("lane_change").get_child(getModuleTypeStr()); double stop_time_{0.0}; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index e691a66d118d7..fadc63dd8834e 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -642,6 +642,8 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( // if max acc is not positive, then we do the normal sampling if (max_acc <= 0.0) { + RCLCPP_DEBUG( + logger_, "Available max acc <= 0. Normal sampling for acc: [%f ~ %f]", min_acc, max_acc); return utils::lane_change::getAccelerationValues( min_acc, max_acc, longitudinal_acc_sampling_num); } @@ -650,6 +652,9 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( const double max_lane_change_length = calcMaximumLaneChangeLength(current_lanes.back(), max_acc); if (max_lane_change_length > utils::getDistanceToEndOfLane(current_pose, current_lanes)) { + RCLCPP_DEBUG( + logger_, "No enough distance to the end of lane. Normal sampling for acc: [%f ~ %f]", min_acc, + max_acc); return utils::lane_change::getAccelerationValues( min_acc, max_acc, longitudinal_acc_sampling_num); } @@ -658,7 +663,8 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( if (isVehicleStuckByObstacle(current_lanes)) { auto clock = rclcpp::Clock(RCL_ROS_TIME); RCLCPP_INFO_THROTTLE( - logger_, clock, 1000, "Vehicle is stuck. sample all longitudinal acceleration."); + logger_, clock, 1000, "Vehicle is in stuck. Sample all possible acc: [%f ~ %f]", min_acc, + max_acc); return utils::lane_change::getAccelerationValues( min_acc, max_acc, longitudinal_acc_sampling_num); } @@ -668,12 +674,17 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( if (route_handler.isInGoalRouteSection(target_lanes.back())) { const auto goal_pose = route_handler.getGoalPose(); if (max_lane_change_length < utils::getSignedDistance(current_pose, goal_pose, target_lanes)) { + RCLCPP_DEBUG( + logger_, "Distance to goal has enough distance. Sample only max_acc: %f", max_acc); return {max_acc}; } } else if (max_lane_change_length < utils::getDistanceToEndOfLane(current_pose, target_lanes)) { + RCLCPP_DEBUG( + logger_, "Distance to end of lane has enough distance. Sample only max_acc: %f", max_acc); return {max_acc}; } + RCLCPP_DEBUG(logger_, "Normal sampling for acc: [%f ~ %f]", min_acc, max_acc); return utils::lane_change::getAccelerationValues(min_acc, max_acc, longitudinal_acc_sampling_num); } @@ -1021,6 +1032,7 @@ bool NormalLaneChange::getLaneChangePaths( { object_debug_.clear(); if (current_lanes.empty() || target_lanes.empty()) { + RCLCPP_WARN(logger_, "target_neighbor_preferred_lane_poly_2d is empty. Not expected."); return false; } const auto & route_handler = *getRouteHandler(); @@ -1056,6 +1068,7 @@ bool NormalLaneChange::getLaneChangePaths( const auto target_neighbor_preferred_lane_poly_2d = utils::lane_change::getTargetNeighborLanesPolygon(route_handler, current_lanes, type_); if (target_neighbor_preferred_lane_poly_2d.empty()) { + RCLCPP_WARN(logger_, "target_neighbor_preferred_lane_poly_2d is empty. Not expected."); return false; } @@ -1067,8 +1080,18 @@ bool NormalLaneChange::getLaneChangePaths( candidate_paths->reserve( longitudinal_acc_sampling_values.size() * lateral_acc_sampling_num * prepare_durations.size()); + RCLCPP_DEBUG( + logger_, "lane change sampling start. Sampling num for prep_time: %lu, acc: %lu", + prepare_durations.size(), longitudinal_acc_sampling_values.size()); + for (const auto & prepare_duration : prepare_durations) { for (const auto & sampled_longitudinal_acc : longitudinal_acc_sampling_values) { + const auto debug_print = [&](const auto & s) { + RCLCPP_DEBUG_STREAM( + logger_, " - " << s << " : prep_time = " << prepare_duration + << ", lon_acc = " << sampled_longitudinal_acc); + }; + // get path on original lanes const auto prepare_velocity = std::max( current_velocity + sampled_longitudinal_acc * prepare_duration, @@ -1086,7 +1109,7 @@ bool NormalLaneChange::getLaneChangePaths( auto prepare_segment = getPrepareSegment(current_lanes, backward_path_length, prepare_length); if (prepare_segment.points.empty()) { - RCLCPP_DEBUG(logger_, "prepare segment is empty!!"); + debug_print("prepare segment is empty...? Unexpected."); continue; } @@ -1097,8 +1120,7 @@ bool NormalLaneChange::getLaneChangePaths( // Check if the lane changing start point is not on the lanes next to target lanes, if (target_length_from_lane_change_start_pose > 0.0) { - RCLCPP_DEBUG( - logger_, "[only new arch] lane change start getEgoPose() is behind target lanelet!!"); + debug_print("lane change start getEgoPose() is behind target lanelet!"); break; } @@ -1113,11 +1135,21 @@ bool NormalLaneChange::getLaneChangePaths( common_parameters.lane_change_lat_acc_map.find(initial_lane_changing_velocity); const auto lateral_acc_resolution = std::abs(max_lateral_acc - min_lateral_acc) / lateral_acc_sampling_num; - constexpr double lateral_acc_epsilon = 0.01; - for (double lateral_acc = min_lateral_acc; - lateral_acc < max_lateral_acc + lateral_acc_epsilon; - lateral_acc += lateral_acc_resolution) { + std::vector sample_lat_acc; + constexpr double eps = 0.01; + for (double a = min_lateral_acc; a < max_lateral_acc + eps; a += lateral_acc_resolution) { + sample_lat_acc.push_back(a); + } + RCLCPP_DEBUG(logger_, " - sampling num for lat_acc: %lu", sample_lat_acc.size()); + + for (const auto & lateral_acc : sample_lat_acc) { + const auto debug_print = [&](const auto & s) { + RCLCPP_DEBUG_STREAM( + logger_, " - " << s << " : prep_time = " << prepare_duration << ", lon_acc = " + << sampled_longitudinal_acc << ", lat_acc = " << lateral_acc); + }; + const auto lane_changing_time = PathShifter::calcShiftTimeFromJerk( shift_length, common_parameters.lane_changing_lateral_jerk, lateral_acc); const double longitudinal_acc_on_lane_changing = @@ -1133,7 +1165,7 @@ bool NormalLaneChange::getLaneChangePaths( prepare_segment, current_velocity, terminal_lane_changing_velocity); if (lane_changing_length + prepare_length > dist_to_end_of_current_lanes) { - RCLCPP_DEBUG(logger_, "length of lane changing path is longer than length to goal!!"); + debug_print("Reject: length of lane changing path is longer than length to goal!!"); continue; } @@ -1151,7 +1183,7 @@ bool NormalLaneChange::getLaneChangePaths( s_start + lane_changing_length + finish_judge_buffer + backward_buffer + next_lane_change_buffer > s_goal) { - RCLCPP_DEBUG(logger_, "length of lane changing path is longer than length to goal!!"); + debug_print("Reject: length of lane changing path is longer than length to goal!!"); continue; } } @@ -1161,7 +1193,7 @@ bool NormalLaneChange::getLaneChangePaths( initial_lane_changing_velocity, next_lane_change_buffer); if (target_segment.points.empty()) { - RCLCPP_DEBUG(logger_, "target segment is empty!! something wrong..."); + debug_print("Reject: target segment is empty!! something wrong..."); continue; } @@ -1178,7 +1210,9 @@ bool NormalLaneChange::getLaneChangePaths( boost::geometry::covered_by(lc_start_point, target_lane_poly_2d); if (!is_valid_start_point) { - // lane changing points are not inside of the target preferred lanes or its neighbors + debug_print( + "Reject: lane changing points are not inside of the target preferred lanes or its " + "neighbors"); continue; } @@ -1190,7 +1224,7 @@ bool NormalLaneChange::getLaneChangePaths( next_lane_change_buffer); if (target_lane_reference_path.points.empty()) { - RCLCPP_DEBUG(logger_, "target_lane_reference_path is empty!!"); + debug_print("Reject: target_lane_reference_path is empty!!"); continue; } @@ -1215,32 +1249,31 @@ bool NormalLaneChange::getLaneChangePaths( sorted_lane_ids); if (!candidate_path) { - RCLCPP_DEBUG(logger_, "no candidate path!!"); + debug_print("Reject: failed to generate candidate path!!"); continue; } if (!hasEnoughLength(*candidate_path, current_lanes, target_lanes, direction)) { - RCLCPP_DEBUG(logger_, "invalid candidate path!!"); + debug_print("Reject: invalid candidate path!!"); continue; } if ( lane_change_parameters_->regulate_on_crosswalk && !hasEnoughLengthToCrosswalk(*candidate_path, current_lanes)) { - RCLCPP_DEBUG(logger_, "Including crosswalk!!"); if (getStopTime() < lane_change_parameters_->stop_time_threshold) { + debug_print("Reject: including crosswalk!!"); continue; } - auto clock{rclcpp::Clock{RCL_ROS_TIME}}; - RCLCPP_WARN_THROTTLE( - logger_, clock, 1000, "Stop time is over threshold. Allow lane change in crosswalk."); + RCLCPP_INFO_THROTTLE( + logger_, clock_, 1000, "Stop time is over threshold. Allow lane change in crosswalk."); } if ( lane_change_parameters_->regulate_on_intersection && !hasEnoughLengthToIntersection(*candidate_path, current_lanes)) { - RCLCPP_DEBUG(logger_, "Including intersection!!"); if (getStopTime() < lane_change_parameters_->stop_time_threshold) { + debug_print("Reject: including intersection!!"); continue; } RCLCPP_WARN_STREAM( @@ -1253,10 +1286,14 @@ bool NormalLaneChange::getLaneChangePaths( utils::lane_change::passParkedObject( route_handler, *candidate_path, target_objects.target_lane, lane_change_buffer, is_goal_in_route, *lane_change_parameters_, object_debug_)) { + debug_print( + "Reject: parking vehicle exists in the target lane, and the ego is not in stuck. Skip " + "lane change."); return false; } if (!check_safety) { + debug_print("ACCEPT!!!: it is valid (and safety check is skipped)."); return false; } @@ -1264,12 +1301,16 @@ bool NormalLaneChange::getLaneChangePaths( *candidate_path, target_objects, rss_params, is_stuck, object_debug_); if (is_safe) { + debug_print("ACCEPT!!!: it is valid and safe!"); return true; } + + debug_print("Reject: sampled path is not safe."); } } } + RCLCPP_DEBUG(logger_, "No safety path found."); return false; } @@ -1630,11 +1671,13 @@ bool NormalLaneChange::isVehicleStuckByObstacle( { // Ego is still moving, not in stuck if (std::abs(getEgoVelocity()) > lane_change_parameters_->stop_velocity_threshold) { + RCLCPP_DEBUG(logger_, "Ego is still moving, not in stuck"); return false; } // Ego is just stopped, not sure it is in stuck yet. if (getStopTime() < lane_change_parameters_->stop_time_threshold) { + RCLCPP_DEBUG(logger_, "Ego is just stopped, counting for stuck judge... (%f)", getStopTime()); return false; } @@ -1652,11 +1695,13 @@ bool NormalLaneChange::isVehicleStuckByObstacle( const auto ego_to_obj_dist = getArcCoordinates(current_lanes, p).length - base_distance; if (0 < ego_to_obj_dist && ego_to_obj_dist < obstacle_check_distance) { + RCLCPP_DEBUG(logger_, "Stationary object is in front of ego."); return true; // Stationary object is in front of ego. } } // No stationary objects found in obstacle_check_distance + RCLCPP_DEBUG(logger_, "No stationary objects found in obstacle_check_distance"); return false; } From 5725c2daf9b6d00a5f52abe028641af7c71dc192 Mon Sep 17 00:00:00 2001 From: badai nguyen <94814556+badai-nguyen@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:59:34 +0900 Subject: [PATCH 473/547] chore(ground_segmentation): update docs (#4806) * chore(ground_segmentation): update docs Signed-off-by: badai-nguyen * chore: update figure Signed-off-by: badai-nguyen * style(pre-commit): autofix --------- Signed-off-by: badai-nguyen Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../scan_ground_filter_parameters.drawio.svg | 225 ++++++++++++++++++ .../docs/scan-ground-filter.md | 39 +-- 2 files changed, 245 insertions(+), 19 deletions(-) create mode 100644 perception/ground_segmentation/docs/image/scan_ground_filter_parameters.drawio.svg diff --git a/perception/ground_segmentation/docs/image/scan_ground_filter_parameters.drawio.svg b/perception/ground_segmentation/docs/image/scan_ground_filter_parameters.drawio.svg new file mode 100644 index 0000000000000..985b99d82ba9b --- /dev/null +++ b/perception/ground_segmentation/docs/image/scan_ground_filter_parameters.drawio.svg @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ detection_range_z_max +
+
+
+
+
+ detection_range_z_max +
+
+ + + + + + + + + +
+
+
+
+ global_slope +
+
+
+
+
+ global_slope +
+
+ + + + + + + + + + +
+
+
+
+ local_slope +
+
+
+
+
+ local_slope +
+
+ + + + + + + + + +
+
+
+
+
split_height_distance
+
+
+
+
+
+ split_height_distance +
+
+ + + + + + + +
+
+
+
+
+
grid_size_m
+
+
+
+
+
+
+ grid_size_m +
+
+ + + + + + + + + + +
+
+
+
+ z +
+
+
+
+
+ z +
+
+
+ + + + Text is not SVG - cannot display + + +
diff --git a/perception/ground_segmentation/docs/scan-ground-filter.md b/perception/ground_segmentation/docs/scan-ground-filter.md index 0c07ce600f625..bc44544fa298c 100644 --- a/perception/ground_segmentation/docs/scan-ground-filter.md +++ b/perception/ground_segmentation/docs/scan-ground-filter.md @@ -29,25 +29,26 @@ This implementation inherits `pointcloud_preprocessor::Filter` class, please ref #### Core Parameters -| Name | Type | Default Value | Description | -| --------------------------------- | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `input_frame` | string | "base_link" | frame id of input pointcloud | -| `output_frame` | string | "base_link" | frame id of output pointcloud | -| `global_slope_max_angle_deg` | double | 8.0 | The global angle to classify as the ground or object [deg] | -| `local_slope_max_angle_deg` | double | 10.0 | The local angle to classify as the ground or object [deg] | -| `radial_divider_angle_deg` | double | 1.0 | The angle which divide the whole pointcloud to sliced group [deg] | -| `split_points_distance_tolerance` | double | 0.2 | The xy-distance threshold to to distinguishing far and near [m] | -| `split_height_distance` | double | 0.2 | The height threshold to distinguishing far and near [m] | -| `use_virtual_ground_point` | bool | true | whether to use the ground center of front wheels as the virtual ground point. | -| `detection_range_z_max` | float | 2.5 | Maximum height of detection range [m], applied only for elevation_grid_mode | -| `center_pcl_shift` | float | 0.0 | The x-axis offset of addition LiDARs from vehicle center of mass [m],
recommended to use only for additional LiDARs in elevation_grid_mode | -| `non_ground_height_threshold` | float | 0.2 | Height threshold of non ground objects [m], applied only for elevation_grid_mode | -| `grid_mode_switch_radius` | float | 20.0 | The distance where grid division mode change from by distance to by vertical angle [m],
applied only for elevation_grid_mode | -| `grid_size_m` | float | 0.5 | The first grid size [m], applied only for elevation_grid_mode | -| `gnd_grid_buffer_size` | uint16 | 4 | Number of grids using to estimate local ground slope,
applied only for elevation_grid_mode | -| `low_priority_region_x` | float | -20.0 | The non-zero x threshold in back side from which small objects detection is low priority [m] | -| `elevation_grid_mode` | bool | true | Elevation grid scan mode option | -| `use_recheck_ground_cluster` | bool | true | Enable recheck ground cluster | +![scan_ground_parameter](./image/scan_ground_filter_parameters.drawio.svg) +| Name | Type | Default Value | Description | +| --------------------------------- | ------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `input_frame` | string | "base_link" | frame id of input pointcloud | +| `output_frame` | string | "base_link" | frame id of output pointcloud | +| `global_slope_max_angle_deg` | double | 8.0 | The global angle to classify as the ground or object [deg].
A large threshold may reduce false positive of high slope road classification but it may lead to increase false negative of non-ground classification, particularly for small objects. | +| `local_slope_max_angle_deg` | double | 10.0 | The local angle to classify as the ground or object [deg] when comparing with adjacent point.
A small value enhance accuracy classification of object with inclined surface. This should be considered together with `split_points_distance_tolerance` value. | +| `radial_divider_angle_deg` | double | 1.0 | The angle which divide the whole pointcloud to sliced group [deg] | +| `split_points_distance_tolerance` | double | 0.2 | The xy-distance threshold to distinguish far and near [m] | +| `split_height_distance` | double | 0.2 | The height threshold to distinguish ground and non-ground pointcloud when comparing with adjacent points [m].
A small threshold improves classification of non-ground point, especially for high elevation resolution pointcloud lidar. However, it might cause false positive for small step-like road surface or misaligned multiple lidar configuration. | +| `use_virtual_ground_point` | bool | true | whether to use the ground center of front wheels as the virtual ground point. | +| `detection_range_z_max` | float | 2.5 | Maximum height of detection range [m], applied only for elevation_grid_mode | +| `center_pcl_shift` | float | 0.0 | The x-axis offset of addition LiDARs from vehicle center of mass [m],
recommended to use only for additional LiDARs in elevation_grid_mode | +| `non_ground_height_threshold` | float | 0.2 | Height threshold of non ground objects [m] as `split_height_distance` and applied only for elevation_grid_mode | +| `grid_mode_switch_radius` | float | 20.0 | The distance where grid division mode change from by distance to by vertical angle [m],
applied only for elevation_grid_mode | +| `grid_size_m` | float | 0.5 | The first grid size [m], applied only for elevation_grid_mode.
A large value enhances the prediction stability for ground surface. suitable for rough surface or multiple lidar configuration. | +| `gnd_grid_buffer_size` | uint16 | 4 | Number of grids using to estimate local ground slope,
applied only for elevation_grid_mode | +| `low_priority_region_x` | float | -20.0 | The non-zero x threshold in back side from which small objects detection is low priority [m] | +| `elevation_grid_mode` | bool | true | Elevation grid scan mode option | +| `use_recheck_ground_cluster` | bool | true | Enable recheck ground cluster | ## Assumptions / Known limits From 35cf82eefa96326b5d6f9754405387299ea7a752 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:54:43 +0900 Subject: [PATCH 474/547] fix(lane_change): change logger level in isVehicleStuckByObstacle (#5315) Signed-off-by: Zulfaqar Azmi --- .../src/scene_module/lane_change/normal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index fadc63dd8834e..12b6d53a83220 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -1725,7 +1725,7 @@ bool NormalLaneChange::isVehicleStuckByObstacle(const lanelet::ConstLanelets & c constexpr double DETECTION_DISTANCE_MARGIN = 10.0; const auto detection_distance = max_lane_change_length + rss_dist + getCommonParam().base_link2front + DETECTION_DISTANCE_MARGIN; - RCLCPP_INFO(logger_, "max_lane_change_length: %f, max_acc: %f", max_lane_change_length, max_acc); + RCLCPP_DEBUG(logger_, "max_lane_change_length: %f, max_acc: %f", max_lane_change_length, max_acc); return isVehicleStuckByObstacle(current_lanes, detection_distance); } From dbc6de6f15507f6132247e4a875a1404db814bc3 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Mon, 16 Oct 2023 17:23:15 +0900 Subject: [PATCH 475/547] feat(behavior_path_planner): curvature based drivable area expansion (#5294) * [DEBUG] Launch planner with konsole + gdb Signed-off-by: Maxime CLEMENT * Switch to new curvature based dynamic drivable area expansion Signed-off-by: Maxime CLEMENT * Cleanup + remove the old code Signed-off-by: Maxime CLEMENT * Handle uncrossable lines/polygons (may not be accurate enough) Signed-off-by: Maxime CLEMENT * Add runtime measurements Signed-off-by: Maxime CLEMENT * [WIP] Reuse previously calculated raw curvatures Signed-off-by: Maxime CLEMENT * Fix bug with lateral offset distance and repeating path points Signed-off-by: Maxime CLEMENT * Remove self crossings in the expanded bounds Signed-off-by: Maxime CLEMENT * Big cleanup + update parameters Signed-off-by: Maxime CLEMENT * Revert "[DEBUG] Launch planner with konsole + gdb" This reverts commit e8f04def14e566a7f5e53d5562bef5fc85628649. Signed-off-by: Maxime CLEMENT * Remove svg debug output Signed-off-by: Maxime CLEMENT * Update parameter file Signed-off-by: Maxime CLEMENT * Add parameter to enable/disable printing the runtime Signed-off-by: Maxime CLEMENT * Fix append of new path points to satisfy the resampling interval Signed-off-by: Maxime CLEMENT * Add smoothing.extra_arc_length param Signed-off-by: Maxime CLEMENT * Code cleanup + add docstrings Signed-off-by: Maxime CLEMENT * Fix spellcheck Signed-off-by: Maxime CLEMENT * Fix initial path poses (no longer cropped) and fix test Signed-off-by: Maxime CLEMENT --------- Signed-off-by: Maxime CLEMENT --- planning/behavior_path_planner/CMakeLists.txt | 1 - .../config/drivable_area_expansion.param.yaml | 28 +- .../behavior_path_planner/data_manager.hpp | 3 +- .../drivable_area_expansion.hpp | 84 ++- .../drivable_area_expansion/expansion.hpp | 85 --- .../drivable_area_expansion/footprints.hpp | 13 +- .../drivable_area_expansion/map_utils.hpp | 13 +- .../drivable_area_expansion/parameters.hpp | 74 ++- .../path_projection.hpp | 36 +- .../utils/drivable_area_expansion/types.hpp | 22 +- .../src/behavior_path_planner_node.cpp | 36 +- .../drivable_area_expansion.cpp | 534 +++++++++--------- .../drivable_area_expansion/expansion.cpp | 237 -------- .../drivable_area_expansion/footprints.cpp | 46 +- .../drivable_area_expansion/map_utils.cpp | 25 +- .../behavior_path_planner/src/utils/utils.cpp | 2 +- .../test/test_drivable_area_expansion.cpp | 221 ++------ 17 files changed, 525 insertions(+), 935 deletions(-) delete mode 100644 planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/expansion.hpp delete mode 100644 planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp diff --git a/planning/behavior_path_planner/CMakeLists.txt b/planning/behavior_path_planner/CMakeLists.txt index f62e418371401..ee6e50f5a9553 100644 --- a/planning/behavior_path_planner/CMakeLists.txt +++ b/planning/behavior_path_planner/CMakeLists.txt @@ -50,7 +50,6 @@ ament_auto_add_library(behavior_path_planner_node SHARED src/utils/drivable_area_expansion/drivable_area_expansion.cpp src/utils/drivable_area_expansion/map_utils.cpp src/utils/drivable_area_expansion/footprints.cpp - src/utils/drivable_area_expansion/expansion.cpp src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp src/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.cpp src/marker_utils/utils.cpp diff --git a/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml b/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml index 160ebdc180020..c0b6f259c7726 100644 --- a/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml +++ b/planning/behavior_path_planner/config/drivable_area_expansion.param.yaml @@ -5,15 +5,19 @@ drivable_area_left_bound_offset: 0.0 drivable_area_types_to_skip: [road_border] - # Dynamic expansion by projecting the ego footprint along the path + # Dynamic expansion by using the path curvature dynamic_expansion: - enabled: false + enabled: true + print_runtime: false + max_expansion_distance: 0.0 # [m] maximum distance by which the drivable area can be expended (0.0 means no limit) + smoothing: + curvature_average_window: 3 # window size used for smoothing the curvatures using a moving window average + max_bound_rate: 1.0 # [m/m] maximum rate of change of the bound lateral distance over its arc length + extra_arc_length: 2.0 # [m] extra arc length where an expansion distance is initially applied ego: - extra_footprint_offset: - front: 0.5 # [m] extra length to add to the front of the ego footprint - rear: 0.5 # [m] extra length to add to the rear of the ego footprint - left: 0.5 # [m] extra length to add to the left of the ego footprint - right: 0.5 # [m] extra length to add to the rear of the ego footprint + extra_wheelbase: 0.0 # [m] extra length to add to the wheelbase + extra_front_overhang: 0.5 # [m] extra length to add to the front overhang + extra_width: 1.0 # [m] extra length to add to the width dynamic_objects: avoid: true # if true, the drivable area is not expanded in the predicted path of dynamic objects extra_footprint_offset: @@ -24,16 +28,8 @@ path_preprocessing: max_arc_length: 50.0 # [m] maximum arc length along the path where the ego footprint is projected (0.0 means no limit) resample_interval: 2.0 # [m] fixed interval between resampled path points (0.0 means path points are directly used) - expansion: - method: polygon # method used to expand the drivable area. Either 'lanelet' or 'polygon'. - # 'lanelet': add lanelets overlapped by the ego footprints - # 'polygon': add polygons built around sections of the ego footprint that go out of the drivable area - max_distance: 0.0 # [m] maximum distance by which the drivable area can be expended (0.0 means no limit) - extra_arc_length: 0.5 # [m] extra expansion arc length around an ego footprint + reuse_max_deviation: 0.5 # [m] if the path changes by more than this value, the curvatures are recalculated. Otherwise they are reused. avoid_linestring: types: # linestring types in the lanelet maps that will not be crossed when expanding the drivable area - road_border distance: 0.0 # [m] distance to keep between the drivable area and the linestrings to avoid - compensate: - enable: true # if true, when the drivable area cannot be expanded in one direction to completely include the ego footprint, it is expanded in the opposite direction - extra_distance: 3.0 # [m] extra distance to add to the compensation diff --git a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp index 7f1648c463097..9280a81e643ca 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/data_manager.hpp @@ -148,7 +148,8 @@ struct PlannerData BehaviorPathPlannerParameters parameters{}; drivable_area_expansion::DrivableAreaExpansionParameters drivable_area_expansion_parameters{}; - mutable std::optional drivable_area_expansion_prev_crop_pose; + mutable std::vector drivable_area_expansion_prev_path_poses{}; + mutable std::vector drivable_area_expansion_prev_curvatures{}; mutable TurnSignalDecider turn_signal_decider; TurnIndicatorsCommand getTurnSignal( diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp index 8293e0a1d10a9..19ea89a3ce3c7 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp @@ -24,30 +24,76 @@ #include #include +#include namespace drivable_area_expansion { -/// @brief Expand the drivable area based on the projected ego footprint along the path +/// @brief Expand the drivable area based on the path curvature and the vehicle dimensions /// @param[inout] path path whose drivable area will be expanded -/// @param[inout] planner_data planning data (params, dynamic objects, route handler, ...) -/// @param[in] path_lanes lanelets of the path -void expandDrivableArea( +/// @param[inout] planner_data planning data (params, dynamic objects, vehicle info, ...) +void expand_drivable_area( PathWithLaneId & path, - const std::shared_ptr planner_data, - const lanelet::ConstLanelets & path_lanes); - -/// @brief Create a polygon combining the drivable area of a path and some expansion polygons -/// @param[in] path path and its drivable area -/// @param[in] expansion_polygons polygons to add to the drivable area -/// @return expanded drivable area polygon -polygon_t createExpandedDrivableAreaPolygon( - const PathWithLaneId & path, const multi_polygon_t & expansion_polygons); - -/// @brief Update the drivable area of the given path with the given polygon -/// @details this function splits the polygon into a left and right bound and sets it in the path -/// @param[in] path path whose drivable area will be expanded -/// @param[in] expanded_drivable_area polygon of the new drivable area -void updateDrivableAreaBounds(PathWithLaneId & path, const polygon_t & expanded_drivable_area); + const std::shared_ptr planner_data); + +/// @brief prepare path poses and try to reuse their previously calculated curvatures +/// @details poses are reused if they do not deviate too much from the current path +/// @param [in] path input path +/// @param [inout] prev_poses previous poses to reuse +/// @param [inout] prev_curvatures previous curvatures to reuse +/// @param [in] ego_point current ego point +/// @param [in] params parameters for reuse criteria and resampling interval +void update_path_poses_and_previous_curvatures( + const PathWithLaneId & path, std::vector & prev_poses, + std::vector & prev_curvatures, const Point & ego_point, + const DrivableAreaExpansionParameters & params); + +/// @brief calculate the minimum lane width based on the path curvature and the vehicle dimensions +/// @cite Lim, H., Kim, C., and Jo, A., "Model Predictive Control-Based Lateral Control of +/// Autonomous Large-Size Bus on Road with Large Curvature," SAE Technical Paper 2021-01-0099, 2021, +/// https://doi.org/10.4271/2021-01-0099 +/// @param [in] curvature curvature +/// @param [in] params parameters containing the vehicle dimensions +/// @return minimum lane width +double calculate_minimum_lane_width( + const double curvature_radius, const DrivableAreaExpansionParameters & params); + +/// @brief smooth the bound by applying a limit on its rate of change +/// @details rate of change is the lateral distance from the path over the arc length along the path +/// @param [inout] bound_distances bound distances (lateral distance from the path) +/// @param [in] bound_points bound points +/// @param [in] max_rate [m/m] maximum rate of lateral deviation over arc length +void apply_bound_change_rate_limit( + std::vector & distances, const std::vector & bound, const double max_rate); + +/// @brief calculate the maximum distance by which a bound can be expanded +/// @param [in] path_poses input path +/// @param [in] bound bound points +/// @param [in] uncrossable_lines lines that limit the bound expansion +/// @param [in] uncrossable_polygons polygons that limit the bound expansion +/// @param [in] params parameters with the buffer distance to keep with lines, +/// and the static maximum expansion distance +std::vector calculate_maximum_distance( + const std::vector & path_poses, const std::vector bound, + const std::vector & uncrossable_lines, + const std::vector & uncrossable_polygons, + const DrivableAreaExpansionParameters & params); + +/// @brief expand a bound by the given lateral distances away from the path +/// @param [inout] bound bound points to expand +/// @param [in] path_poses input path +/// @param [in] distances new lateral distances of the bound points away from the path +void expand_bound( + std::vector & bound, const std::vector & path_poses, + const std::vector & distances); + +/// @brief calculate smoothed curvatures +/// @details smoothing is done using a moving average +/// @param [in] poses input poses used to calculate the curvatures +/// @param [in] smoothing_window_size size of the window used for the moving average +/// @return smoothed curvatures of the input poses +std::vector calculate_smoothed_curvatures( + const std::vector & poses, const size_t smoothing_window_size); + } // namespace drivable_area_expansion #endif // BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__DRIVABLE_AREA_EXPANSION_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/expansion.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/expansion.hpp deleted file mode 100644 index 70cc8a8bc5925..0000000000000 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/expansion.hpp +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2023 TIER IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__EXPANSION_HPP_ -#define BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__EXPANSION_HPP_ - -#include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" -#include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" - -#include - -#include - -#include -#include - -namespace drivable_area_expansion -{ -/// @brief Calculate the distance limit required for the polygon to not cross the limit lines -/// @details Calculate the minimum distance from base_ls to an intersection of limit_lines and -/// expansion_polygon -/// @param[in] base_ls base linestring from which the distance is calculated -/// @param[in] expansion_polygon polygon to consider -/// @param[in] limit_lines lines we do not want to cross -/// @return distance limit -double calculateDistanceLimit( - const linestring_t & base_ls, const polygon_t & expansion_polygon, - const multi_linestring_t & limit_lines); - -/// @brief Calculate the distance limit required for the polygon to not cross the limit polygons. -/// @details Calculate the minimum distance from base_ls to an intersection of limit_polygons and -/// expansion_polygon -/// @param[in] base_ls base linestring from which the distance is calculated -/// @param[in] expansion_polygon polygon to consider -/// @param[in] limit_polygons polygons we do not want to cross -/// @return distance limit -double calculateDistanceLimit( - const linestring_t & base_ls, const polygon_t & expansion_polygon, - const multi_polygon_t & limit_polygons); - -/// @brief Create a polygon from a base line with a given expansion distance -/// @param[in] base_ls base linestring from which the polygon is created -/// @param[in] dist desired expansion distance from the base line -/// @param[in] is_left_side desired side of the expansion from the base line -/// @return expansion polygon -polygon_t createExpansionPolygon( - const linestring_t & base_ls, const double dist, const bool is_left_side); - -/// @brief Create polygons for the area where the drivable area should be expanded -/// @param[in] path path and its drivable area -/// @param[in] path_footprints polygons of the ego footprint projected along the path -/// @param[in] predicted_paths polygons of the dynamic objects' predicted paths -/// @param[in] uncrossable_lines lines that should not be crossed by the expanded drivable area -/// @param[in] params expansion parameters -/// @return expansion polygons -multi_polygon_t createExpansionPolygons( - const PathWithLaneId & path, const multi_polygon_t & path_footprints, - const multi_polygon_t & predicted_paths, const multi_linestring_t & uncrossable_lines, - const DrivableAreaExpansionParameters & params); - -/// @brief Create polygons for the area where the drivable area should be expanded -/// @param[in] path_lanes lanelets of the current path -/// @param[in] route_handler route handler -/// @param[in] path_footprints polygons of the ego footprint projected along the path -/// @param[in] predicted_paths polygons of the dynamic objects' predicted paths -/// @param[in] params expansion parameters -/// @return expansion polygons -multi_polygon_t createExpansionLaneletPolygons( - const lanelet::ConstLanelets & path_lanes, const route_handler::RouteHandler & route_handler, - const multi_polygon_t & path_footprints, const multi_polygon_t & predicted_paths, - const DrivableAreaExpansionParameters & params); -} // namespace drivable_area_expansion - -#endif // BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__EXPANSION_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp index 8fc0157710dc8..418a9a5a68572 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp @@ -43,27 +43,20 @@ namespace drivable_area_expansion /// @param[in] x translation distance on the x axis /// @param[in] y translation distance on the y axis /// @return translated polygon -polygon_t translatePolygon(const polygon_t & polygon, const double x, const double y); +Polygon2d translate_polygon(const Polygon2d & polygon, const double x, const double y); /// @brief create the footprint of a pose and its base footprint /// @param[in] pose the origin pose of the footprint /// @param[in] base_footprint the base axis-aligned footprint /// @return footprint polygon -polygon_t createFootprint(const geometry_msgs::msg::Pose & pose, const polygon_t base_footprint); +Polygon2d create_footprint(const geometry_msgs::msg::Pose & pose, const Polygon2d base_footprint); /// @brief create footprints of the predicted paths of an object /// @param [in] objects objects from which to create polygons /// @param[in] params expansion parameters containing extra offsets to add to the dynamic objects /// @return footprint polygons of the object's predicted paths -multi_polygon_t createObjectFootprints( +MultiPolygon2d create_object_footprints( const autoware_auto_perception_msgs::msg::PredictedObjects & objects, const DrivableAreaExpansionParameters & params); - -/// @brief create the footprint polygon from a path -/// @param[in] path the path for which to create a footprint -/// @param[in] params expansion parameters defining how to create the footprint -/// @return footprint polygons of the path -multi_polygon_t createPathFootprints( - const std::vector & path, const DrivableAreaExpansionParameters & params); } // namespace drivable_area_expansion #endif // BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__FOOTPRINTS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp index 4524bd2be2299..6f96b83237310 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp @@ -15,6 +15,7 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__MAP_UTILS_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__MAP_UTILS_HPP_ +#include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" #include @@ -24,18 +25,20 @@ namespace drivable_area_expansion { -/// @brief Extract uncrossable linestrings from the lanelet map +/// @brief Extract uncrossable linestrings from the lanelet map that are in range of ego /// @param[in] lanelet_map lanelet map -/// @param[in] uncrossable_types types that cannot be crossed +/// @param[in] ego_point point of the current ego position +/// @param[in] params parameters with linestring types that cannot be crossed and maximum range /// @return the uncrossable linestrings -multi_linestring_t extractUncrossableLines( - const lanelet::LaneletMap & lanelet_map, const std::vector & uncrossable_types); +MultiLineString2d extract_uncrossable_lines( + const lanelet::LaneletMap & lanelet_map, const Point & ego_point, + const DrivableAreaExpansionParameters & params); /// @brief Determine if the given linestring has one of the given types /// @param[in] ls linestring to check /// @param[in] types type strings to check /// @return true if the linestring has one of the given types -bool hasTypes(const lanelet::ConstLineString3d & ls, const std::vector & types); +bool has_types(const lanelet::ConstLineString3d & ls, const std::vector & types); } // namespace drivable_area_expansion #endif // BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__MAP_UTILS_HPP_ diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp index 81eab9560b736..e7275960b0888 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp @@ -32,12 +32,9 @@ struct DrivableAreaExpansionParameters static constexpr auto DRIVABLE_AREA_LEFT_BOUND_OFFSET_PARAM = "drivable_area_left_bound_offset"; static constexpr auto DRIVABLE_AREA_TYPES_TO_SKIP_PARAM = "drivable_area_types_to_skip"; static constexpr auto ENABLED_PARAM = "dynamic_expansion.enabled"; - static constexpr auto EGO_EXTRA_OFFSET_FRONT = - "dynamic_expansion.ego.extra_footprint_offset.front"; - static constexpr auto EGO_EXTRA_OFFSET_REAR = "dynamic_expansion.ego.extra_footprint_offset.rear"; - static constexpr auto EGO_EXTRA_OFFSET_LEFT = "dynamic_expansion.ego.extra_footprint_offset.left"; - static constexpr auto EGO_EXTRA_OFFSET_RIGHT = - "dynamic_expansion.ego.extra_footprint_offset.right"; + static constexpr auto EGO_EXTRA_FRONT_OVERHANG = "dynamic_expansion.ego.extra_front_overhang"; + static constexpr auto EGO_EXTRA_WHEELBASE = "dynamic_expansion.ego.extra_wheelbase"; + static constexpr auto EGO_EXTRA_WIDTH = "dynamic_expansion.ego.extra_width"; static constexpr auto DYN_OBJECTS_EXTRA_OFFSET_FRONT = "dynamic_expansion.dynamic_objects.extra_footprint_offset.front"; static constexpr auto DYN_OBJECTS_EXTRA_OFFSET_REAR = @@ -46,34 +43,36 @@ struct DrivableAreaExpansionParameters "dynamic_expansion.dynamic_objects.extra_footprint_offset.left"; static constexpr auto DYN_OBJECTS_EXTRA_OFFSET_RIGHT = "dynamic_expansion.dynamic_objects.extra_footprint_offset.right"; - static constexpr auto EXPANSION_METHOD_PARAM = "dynamic_expansion.expansion.method"; - static constexpr auto MAX_EXP_DIST_PARAM = "dynamic_expansion.expansion.max_distance"; + static constexpr auto MAX_EXP_DIST_PARAM = "dynamic_expansion.max_expansion_distance"; static constexpr auto RESAMPLE_INTERVAL_PARAM = "dynamic_expansion.path_preprocessing.resample_interval"; static constexpr auto MAX_PATH_ARC_LENGTH_PARAM = "dynamic_expansion.path_preprocessing.max_arc_length"; - static constexpr auto EXTRA_ARC_LENGTH_PARAM = "dynamic_expansion.expansion.extra_arc_length"; + static constexpr auto MAX_REUSE_DEVIATION_PARAM = + "dynamic_expansion.path_preprocessing.reuse_max_deviation"; static constexpr auto AVOID_DYN_OBJECTS_PARAM = "dynamic_expansion.dynamic_objects.avoid"; static constexpr auto AVOID_LINESTRING_TYPES_PARAM = "dynamic_expansion.avoid_linestring.types"; static constexpr auto AVOID_LINESTRING_DIST_PARAM = "dynamic_expansion.avoid_linestring.distance"; - static constexpr auto COMPENSATE_PARAM = "dynamic_expansion.avoid_linestring.compensate.enable"; - static constexpr auto EXTRA_COMPENSATE_PARAM = - "dynamic_expansion.avoid_linestring.compensate.extra_distance"; + static constexpr auto SMOOTHING_CURVATURE_WINDOW_PARAM = + "dynamic_expansion.smoothing.curvature_average_window"; + static constexpr auto SMOOTHING_MAX_BOUND_RATE_PARAM = + "dynamic_expansion.smoothing.max_bound_rate"; + static constexpr auto SMOOTHING_EXTRA_ARC_LENGTH_PARAM = + "dynamic_expansion.smoothing.extra_arc_length"; + static constexpr auto PRINT_RUNTIME_PARAM = "dynamic_expansion.print_runtime"; - double drivable_area_right_bound_offset; - double drivable_area_left_bound_offset; - std::vector drivable_area_types_to_skip; + // static expansion + double drivable_area_right_bound_offset{}; + double drivable_area_left_bound_offset{}; + std::vector drivable_area_types_to_skip{}; + // dynamic expansion bool enabled = false; - std::string expansion_method{}; double avoid_linestring_dist{}; - double ego_left_offset{}; - double ego_right_offset{}; - double ego_rear_offset{}; - double ego_front_offset{}; - double ego_extra_left_offset{}; - double ego_extra_right_offset{}; - double ego_extra_rear_offset{}; - double ego_extra_front_offset{}; + double extra_front_overhang{}; + double extra_wheelbase{}; + double extra_width{}; + int curvature_average_window{}; + double max_bound_rate{}; double dynamic_objects_extra_left_offset{}; double dynamic_objects_extra_right_offset{}; double dynamic_objects_extra_rear_offset{}; @@ -82,10 +81,11 @@ struct DrivableAreaExpansionParameters double max_path_arc_length{}; double resample_interval{}; double extra_arc_length{}; + double max_reuse_deviation{}; bool avoid_dynamic_objects{}; + bool print_runtime{}; std::vector avoid_linestring_types{}; - bool compensate_uncrossable_lines = false; - double compensate_extra_dist{}; + vehicle_info_util::VehicleInfo vehicle_info; DrivableAreaExpansionParameters() = default; explicit DrivableAreaExpansionParameters(rclcpp::Node & node) { init(node); } @@ -100,12 +100,15 @@ struct DrivableAreaExpansionParameters node.declare_parameter>(DRIVABLE_AREA_TYPES_TO_SKIP_PARAM); enabled = node.declare_parameter(ENABLED_PARAM); max_expansion_distance = node.declare_parameter(MAX_EXP_DIST_PARAM); + extra_front_overhang = node.declare_parameter(EGO_EXTRA_FRONT_OVERHANG); + extra_wheelbase = node.declare_parameter(EGO_EXTRA_WHEELBASE); + extra_width = node.declare_parameter(EGO_EXTRA_WIDTH); + curvature_average_window = node.declare_parameter(SMOOTHING_CURVATURE_WINDOW_PARAM); + max_bound_rate = node.declare_parameter(SMOOTHING_MAX_BOUND_RATE_PARAM); + extra_arc_length = node.declare_parameter(SMOOTHING_EXTRA_ARC_LENGTH_PARAM); max_path_arc_length = node.declare_parameter(MAX_PATH_ARC_LENGTH_PARAM); resample_interval = node.declare_parameter(RESAMPLE_INTERVAL_PARAM); - ego_extra_front_offset = node.declare_parameter(EGO_EXTRA_OFFSET_FRONT); - ego_extra_rear_offset = node.declare_parameter(EGO_EXTRA_OFFSET_REAR); - ego_extra_left_offset = node.declare_parameter(EGO_EXTRA_OFFSET_LEFT); - ego_extra_right_offset = node.declare_parameter(EGO_EXTRA_OFFSET_RIGHT); + max_reuse_deviation = node.declare_parameter(MAX_REUSE_DEVIATION_PARAM); dynamic_objects_extra_front_offset = node.declare_parameter(DYN_OBJECTS_EXTRA_OFFSET_FRONT); dynamic_objects_extra_rear_offset = @@ -118,16 +121,9 @@ struct DrivableAreaExpansionParameters node.declare_parameter>(AVOID_LINESTRING_TYPES_PARAM); avoid_dynamic_objects = node.declare_parameter(AVOID_DYN_OBJECTS_PARAM); avoid_linestring_dist = node.declare_parameter(AVOID_LINESTRING_DIST_PARAM); - extra_arc_length = node.declare_parameter(EXTRA_ARC_LENGTH_PARAM); - compensate_uncrossable_lines = node.declare_parameter(COMPENSATE_PARAM); - compensate_extra_dist = node.declare_parameter(EXTRA_COMPENSATE_PARAM); - expansion_method = node.declare_parameter(EXPANSION_METHOD_PARAM); + print_runtime = node.declare_parameter(PRINT_RUNTIME_PARAM); - const auto vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); - ego_left_offset = vehicle_info.max_lateral_offset_m; - ego_right_offset = vehicle_info.min_lateral_offset_m; - ego_rear_offset = vehicle_info.min_longitudinal_offset_m; - ego_front_offset = vehicle_info.max_longitudinal_offset_m; + vehicle_info = vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo(); } }; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp index 3e2b177f59167..93afaad825582 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp @@ -33,10 +33,10 @@ namespace drivable_area_expansion /// @param p2 second segment point /// @return projected point and corresponding distance inline PointDistance point_to_segment_projection( - const point_t & p, const point_t & p1, const point_t & p2) + const Point2d & p, const Point2d & p1, const Point2d & p2) { - const point_t p2_vec = {p2.x() - p1.x(), p2.y() - p1.y()}; - const point_t p_vec = {p.x() - p1.x(), p.y() - p1.y()}; + const Point2d p2_vec = {p2.x() - p1.x(), p2.y() - p1.y()}; + const Point2d p_vec = {p.x() - p1.x(), p.y() - p1.y()}; const auto cross = p2_vec.x() * p_vec.y() - p2_vec.y() * p_vec.x(); const auto dist_sign = cross < 0.0 ? -1.0 : 1.0; @@ -48,7 +48,7 @@ inline PointDistance point_to_segment_projection( if (c2 <= c1) return {p2, boost::geometry::distance(p, p2) * dist_sign}; const auto projection = p1 + (p2_vec * c1 / c2); - const auto projection_point = point_t{projection.x(), projection.y()}; + const auto projection_point = Point2d{projection.x(), projection.y()}; return {projection_point, boost::geometry::distance(p, projection_point) * dist_sign}; } @@ -59,10 +59,10 @@ inline PointDistance point_to_segment_projection( /// @param p2 second line point /// @return projected point and corresponding distance inline PointDistance point_to_line_projection( - const point_t & p, const point_t & p1, const point_t & p2) + const Point2d & p, const Point2d & p1, const Point2d & p2) { - const point_t p2_vec = {p2.x() - p1.x(), p2.y() - p1.y()}; - const point_t p_vec = {p.x() - p1.x(), p.y() - p1.y()}; + const Point2d p2_vec = {p2.x() - p1.x(), p2.y() - p1.y()}; + const Point2d p_vec = {p.x() - p1.x(), p.y() - p1.y()}; const auto cross = p2_vec.x() * p_vec.y() - p2_vec.y() * p_vec.x(); const auto dist_sign = cross < 0.0 ? -1.0 : 1.0; @@ -70,7 +70,7 @@ inline PointDistance point_to_line_projection( const auto c1 = boost::geometry::dot_product(p_vec, p2_vec); const auto c2 = boost::geometry::dot_product(p2_vec, p2_vec); const auto projection = p1 + (p2_vec * c1 / c2); - const auto projection_point = point_t{projection.x(), projection.y()}; + const auto projection_point = Point2d{projection.x(), projection.y()}; return {projection_point, boost::geometry::distance(p, projection_point) * dist_sign}; } @@ -78,7 +78,7 @@ inline PointDistance point_to_line_projection( /// @param p point to project /// @param ls linestring /// @return projected point, corresponding distance, and arc length along the linestring -inline Projection point_to_linestring_projection(const point_t & p, const linestring_t & ls) +inline Projection point_to_linestring_projection(const Point2d & p, const LineString2d & ls) { Projection closest; closest.distance = std::numeric_limits::max(); @@ -100,14 +100,14 @@ inline Projection point_to_linestring_projection(const point_t & p, const linest /// @param p2 second vector point /// @param dist distance /// @return point p such that (p1,p) is orthogonal to (p1,p2) at the given distance -inline point_t normal_at_distance(const point_t & p1, const point_t & p2, const double dist) +inline Point2d normal_at_distance(const Point2d & p1, const Point2d & p2, const double dist) { auto base = p1; auto normal_vector = p2; boost::geometry::subtract_point(normal_vector, base); boost::geometry::detail::vec_normalize(normal_vector); boost::geometry::multiply_value(normal_vector, dist); - return point_t{base.x() - normal_vector.y(), base.y() + normal_vector.x()}; + return Point2d{base.x() - normal_vector.y(), base.y() + normal_vector.x()}; } /// @brief interpolate between two points @@ -115,7 +115,7 @@ inline point_t normal_at_distance(const point_t & p1, const point_t & p2, const /// @param b second point /// @param ratio interpolation ratio such that 0 yields a, and 1 yields b /// @return point interpolated between a and b as per the given ratio -inline point_t lerp_point(const point_t & a, const point_t & b, const double ratio) +inline Point2d lerp_point(const Point2d & a, const Point2d & b, const double ratio) { return {interpolation::lerp(a.x(), b.x(), ratio), interpolation::lerp(a.y(), b.y(), ratio)}; } @@ -125,10 +125,10 @@ inline point_t lerp_point(const point_t & a, const point_t & b, const double rat /// @param arc_length arc length along the reference linestring of the resulting point /// @param distance distance from the reference linestring of the resulting point /// @return point at the distance and arc length relative to the reference linestring -inline segment_t linestring_to_point_projection( - const linestring_t & ls, const double arc_length, const double distance) +inline Segment2d linestring_to_point_projection( + const LineString2d & ls, const double arc_length, const double distance) { - if (ls.empty()) return segment_t{}; + if (ls.empty()) return Segment2d{}; if (ls.size() == 1lu) return {ls.front(), ls.front()}; auto ls_iterator = ls.begin(); auto prev_arc_length = 0.0; @@ -156,10 +156,10 @@ inline segment_t linestring_to_point_projection( /// @param from_arc_length arc length of the first point of the sub linestring /// @param to_arc_length arc length of the last point of the sub linestring /// @return sub linestring -inline linestring_t sub_linestring( - const linestring_t & ls, const double from_arc_length, const double to_arc_length) +inline LineString2d sub_linestring( + const LineString2d & ls, const double from_arc_length, const double to_arc_length) { - linestring_t sub_ls; + LineString2d sub_ls; if (from_arc_length >= to_arc_length || ls.empty()) throw(std::runtime_error("sub_linestring: bad inputs")); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp index e300a347e47a8..7db92c163f567 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp @@ -29,26 +29,28 @@ using autoware_auto_planning_msgs::msg::PathPoint; using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using autoware_auto_planning_msgs::msg::PathWithLaneId; using geometry_msgs::msg::Point; +using geometry_msgs::msg::Pose; -using point_t = tier4_autoware_utils::Point2d; -using multi_point_t = tier4_autoware_utils::MultiPoint2d; -using polygon_t = tier4_autoware_utils::Polygon2d; -using ring_t = tier4_autoware_utils::LinearRing2d; -using multi_polygon_t = tier4_autoware_utils::MultiPolygon2d; -using segment_t = tier4_autoware_utils::Segment2d; -using linestring_t = tier4_autoware_utils::LineString2d; -using multi_linestring_t = tier4_autoware_utils::MultiLineString2d; +using tier4_autoware_utils::LineString2d; +using tier4_autoware_utils::MultiLineString2d; +using tier4_autoware_utils::MultiPoint2d; +using tier4_autoware_utils::MultiPolygon2d; +using tier4_autoware_utils::Point2d; +using tier4_autoware_utils::Polygon2d; +using tier4_autoware_utils::Segment2d; struct PointDistance { - point_t point; + Point2d point; double distance; }; struct Projection { - point_t projected_point; + Point2d projected_point; double distance; double arc_length; }; +enum Side { LEFT, RIGHT }; + } // namespace drivable_area_expansion #endif // BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__TYPES_HPP_ diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index 755817aa22ae7..3fb9060835698 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -1003,9 +1003,6 @@ SetParametersResult BehaviorPathPlannerNode::onSetParam( updateParam( parameters, DrivableAreaExpansionParameters::AVOID_DYN_OBJECTS_PARAM, planner_data_->drivable_area_expansion_parameters.avoid_dynamic_objects); - updateParam( - parameters, DrivableAreaExpansionParameters::EXPANSION_METHOD_PARAM, - planner_data_->drivable_area_expansion_parameters.expansion_method); updateParam( parameters, DrivableAreaExpansionParameters::AVOID_LINESTRING_TYPES_PARAM, planner_data_->drivable_area_expansion_parameters.avoid_linestring_types); @@ -1013,17 +1010,14 @@ SetParametersResult BehaviorPathPlannerNode::onSetParam( parameters, DrivableAreaExpansionParameters::AVOID_LINESTRING_DIST_PARAM, planner_data_->drivable_area_expansion_parameters.avoid_linestring_dist); updateParam( - parameters, DrivableAreaExpansionParameters::EGO_EXTRA_OFFSET_FRONT, - planner_data_->drivable_area_expansion_parameters.ego_extra_front_offset); - updateParam( - parameters, DrivableAreaExpansionParameters::EGO_EXTRA_OFFSET_REAR, - planner_data_->drivable_area_expansion_parameters.ego_extra_rear_offset); + parameters, DrivableAreaExpansionParameters::EGO_EXTRA_FRONT_OVERHANG, + planner_data_->drivable_area_expansion_parameters.extra_front_overhang); updateParam( - parameters, DrivableAreaExpansionParameters::EGO_EXTRA_OFFSET_LEFT, - planner_data_->drivable_area_expansion_parameters.ego_extra_left_offset); + parameters, DrivableAreaExpansionParameters::EGO_EXTRA_WHEELBASE, + planner_data_->drivable_area_expansion_parameters.extra_wheelbase); updateParam( - parameters, DrivableAreaExpansionParameters::EGO_EXTRA_OFFSET_RIGHT, - planner_data_->drivable_area_expansion_parameters.ego_extra_right_offset); + parameters, DrivableAreaExpansionParameters::EGO_EXTRA_WIDTH, + planner_data_->drivable_area_expansion_parameters.extra_width); updateParam( parameters, DrivableAreaExpansionParameters::DYN_OBJECTS_EXTRA_OFFSET_FRONT, planner_data_->drivable_area_expansion_parameters.dynamic_objects_extra_front_offset); @@ -1046,14 +1040,20 @@ SetParametersResult BehaviorPathPlannerNode::onSetParam( parameters, DrivableAreaExpansionParameters::RESAMPLE_INTERVAL_PARAM, planner_data_->drivable_area_expansion_parameters.resample_interval); updateParam( - parameters, DrivableAreaExpansionParameters::EXTRA_ARC_LENGTH_PARAM, - planner_data_->drivable_area_expansion_parameters.extra_arc_length); + parameters, DrivableAreaExpansionParameters::MAX_REUSE_DEVIATION_PARAM, + planner_data_->drivable_area_expansion_parameters.max_reuse_deviation); updateParam( - parameters, DrivableAreaExpansionParameters::COMPENSATE_PARAM, - planner_data_->drivable_area_expansion_parameters.compensate_uncrossable_lines); + parameters, DrivableAreaExpansionParameters::SMOOTHING_CURVATURE_WINDOW_PARAM, + planner_data_->drivable_area_expansion_parameters.curvature_average_window); + updateParam( + parameters, DrivableAreaExpansionParameters::SMOOTHING_MAX_BOUND_RATE_PARAM, + planner_data_->drivable_area_expansion_parameters.max_bound_rate); + updateParam( + parameters, DrivableAreaExpansionParameters::SMOOTHING_EXTRA_ARC_LENGTH_PARAM, + planner_data_->drivable_area_expansion_parameters.extra_arc_length); updateParam( - parameters, DrivableAreaExpansionParameters::EXTRA_COMPENSATE_PARAM, - planner_data_->drivable_area_expansion_parameters.compensate_extra_dist); + parameters, DrivableAreaExpansionParameters::PRINT_RUNTIME_PARAM, + planner_data_->drivable_area_expansion_parameters.print_runtime); } catch (const rclcpp::exceptions::InvalidParameterTypeException & e) { result.successful = false; result.reason = e.what(); diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index 60fb2ba2ff2e8..76d1f280651bf 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -14,10 +14,10 @@ #include "behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp" -#include "behavior_path_planner/utils/drivable_area_expansion/expansion.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/footprints.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/parameters.hpp" +#include "behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" #include @@ -25,304 +25,306 @@ #include #include #include +#include #include +#include + namespace drivable_area_expansion { -std::vector crop_and_resample( - const std::vector & points, - const std::shared_ptr planner_data, - const double resample_interval) +namespace { - auto lon_offset = 0.0; - auto crop_pose = *planner_data->drivable_area_expansion_prev_crop_pose; - // reuse or update the previous crop point - if (planner_data->drivable_area_expansion_prev_crop_pose) { - const auto lon_offset = motion_utils::calcSignedArcLength( - points, points.front().point.pose.position, crop_pose.position); - if (lon_offset < 0.0) { - planner_data->drivable_area_expansion_prev_crop_pose.reset(); - } else { - const auto is_behind_ego = - motion_utils::calcSignedArcLength( - points, crop_pose.position, planner_data->self_odometry->pose.pose.position) > 0.0; - const auto is_too_far = motion_utils::calcLateralOffset(points, crop_pose.position) > 0.1; - if (!is_behind_ego || is_too_far) - planner_data->drivable_area_expansion_prev_crop_pose.reset(); - } - } - if (!planner_data->drivable_area_expansion_prev_crop_pose) { - crop_pose = planner_data->drivable_area_expansion_prev_crop_pose.value_or( - motion_utils::calcInterpolatedPose(points, resample_interval - lon_offset)); - } - // crop - const auto crop_seg_idx = motion_utils::findNearestSegmentIndex(points, crop_pose.position); - const auto cropped_points = motion_utils::cropPoints( - points, crop_pose.position, crop_seg_idx, - planner_data->drivable_area_expansion_parameters.max_path_arc_length, 0.0); - planner_data->drivable_area_expansion_prev_crop_pose = crop_pose; - // resample - PathWithLaneId cropped_path; - if (tier4_autoware_utils::calcDistance2d(crop_pose, cropped_points.front()) > 1e-3) { - PathPointWithLaneId crop_path_point; - crop_path_point.point.pose = crop_pose; - cropped_path.points.push_back(crop_path_point); - } - cropped_path.points.insert( - cropped_path.points.end(), cropped_points.begin(), cropped_points.end()); - const auto resampled_path = - motion_utils::resamplePath(cropped_path, resample_interval, true, true, false); - return resampled_path.points; +Point2d convert_point(const Point & p) +{ + return Point2d{p.x, p.y}; } -void expandDrivableArea( - PathWithLaneId & path, - const std::shared_ptr planner_data, - const lanelet::ConstLanelets & path_lanes) +} // namespace + +void reuse_previous_poses( + const PathWithLaneId & path, std::vector & prev_poses, + std::vector & prev_curvatures, const Point & ego_point, + const DrivableAreaExpansionParameters & params) { - const auto & params = planner_data->drivable_area_expansion_parameters; - const auto & dynamic_objects = *planner_data->dynamic_object; - const auto & route_handler = *planner_data->route_handler; - const auto uncrossable_lines = - extractUncrossableLines(*route_handler.getLaneletMapPtr(), params.avoid_linestring_types); - multi_linestring_t uncrossable_lines_in_range; - const auto & p = path.points.front().point.pose.position; - for (const auto & line : uncrossable_lines) - if (boost::geometry::distance(line, point_t{p.x, p.y}) < params.max_path_arc_length) - uncrossable_lines_in_range.push_back(line); - const auto points = crop_and_resample(path.points, planner_data, params.resample_interval); - const auto path_footprints = createPathFootprints(points, params); - const auto predicted_paths = createObjectFootprints(dynamic_objects, params); - const auto expansion_polygons = - params.expansion_method == "lanelet" - ? createExpansionLaneletPolygons( - path_lanes, route_handler, path_footprints, predicted_paths, params) - : createExpansionPolygons( - path, path_footprints, predicted_paths, uncrossable_lines_in_range, params); - const auto expanded_drivable_area = createExpandedDrivableAreaPolygon(path, expansion_polygons); - updateDrivableAreaBounds(path, expanded_drivable_area); + std::vector cropped_poses; + std::vector cropped_curvatures; + const auto ego_is_behind = prev_poses.size() > 1 && motion_utils::calcLongitudinalOffsetToSegment( + prev_poses, 0, ego_point) < 0.0; + const auto ego_is_far = !prev_poses.empty() && + tier4_autoware_utils::calcDistance2d(ego_point, prev_poses.front()) < 0.0; + if (!ego_is_behind && !ego_is_far && prev_poses.size() > 1) { + const auto first_idx = + motion_utils::findNearestSegmentIndex(prev_poses, path.points.front().point.pose); + const auto deviation = + motion_utils::calcLateralOffset(prev_poses, path.points.front().point.pose.position); + if (first_idx && deviation < params.max_reuse_deviation) { + for (auto idx = *first_idx; idx < prev_poses.size(); ++idx) { + if ( + motion_utils::calcLateralOffset(path.points, prev_poses[idx].position) > + params.max_reuse_deviation) + break; + cropped_poses.push_back(prev_poses[idx]); + cropped_curvatures.push_back(prev_curvatures[idx]); + } + } + } + if (cropped_poses.empty()) { + const auto resampled_path_points = + motion_utils::resamplePath(path, params.resample_interval, true, true, false).points; + for (const auto & p : resampled_path_points) cropped_poses.push_back(p.point.pose); + } else if (!path.points.empty()) { + const auto initial_arc_length = motion_utils::calcArcLength(cropped_poses); + const auto max_path_arc_length = motion_utils::calcArcLength(path.points); + const auto first_arc_length = motion_utils::calcSignedArcLength( + path.points, path.points.front().point.pose.position, cropped_poses.back().position); + for (auto arc_length = first_arc_length + params.resample_interval; + initial_arc_length + (arc_length - first_arc_length) <= params.max_path_arc_length && + arc_length <= max_path_arc_length; + arc_length += params.resample_interval) + cropped_poses.push_back(motion_utils::calcInterpolatedPose(path.points, arc_length)); + } + prev_poses = motion_utils::removeOverlapPoints(cropped_poses); + prev_curvatures = cropped_curvatures; } -point_t convert_point(const Point & p) +double calculate_minimum_lane_width( + const double curvature_radius, const DrivableAreaExpansionParameters & params) { - return point_t{p.x, p.y}; + const auto k = curvature_radius; + const auto a = params.vehicle_info.front_overhang_m + params.extra_front_overhang; + const auto w = params.vehicle_info.vehicle_width_m + params.extra_width; + const auto l = params.vehicle_info.wheel_base_m + params.extra_wheelbase; + return (a * a + 2.0 * a * l + 2.0 * k * w + l * l + w * w) / (2.0 * k + w); } -Point convert_point(const point_t & p) +std::vector calculate_minimum_expansions( + const std::vector & path_poses, const std::vector bound, + const std::vector curvatures, const Side side, + const DrivableAreaExpansionParameters & params) { - return Point().set__x(p.x()).set__y(p.y()); + std::vector minimum_expansions(bound.size()); + size_t lb_idx = 0; + for (auto path_idx = 0UL; path_idx < path_poses.size(); ++path_idx) { + const auto & path_pose = path_poses[path_idx]; + if (curvatures[path_idx] == 0.0) continue; + const auto curvature_radius = 1 / curvatures[path_idx]; + const auto min_lane_width = calculate_minimum_lane_width(curvature_radius, params); + const auto side_distance = min_lane_width / 2.0 * (side == LEFT ? 1.0 : -1.0); + const auto offset_point = + tier4_autoware_utils::calcOffsetPose(path_pose, 0.0, side_distance, 0.0).position; + for (auto bound_idx = lb_idx; bound_idx + 1 < bound.size(); ++bound_idx) { + const auto & prev_p = bound[bound_idx]; + const auto & next_p = bound[bound_idx + 1]; + const auto intersection_point = + tier4_autoware_utils::intersect(offset_point, path_pose.position, prev_p, next_p); + if (intersection_point) { + lb_idx = bound_idx; + const auto dist = tier4_autoware_utils::calcDistance2d(*intersection_point, offset_point); + minimum_expansions[bound_idx] = std::max(minimum_expansions[bound_idx], dist); + minimum_expansions[bound_idx + 1] = std::max(minimum_expansions[bound_idx + 1], dist); + // apply the expansion to all bound points within the extra arc length + if (bound_idx + 2 < bound.size()) { + auto up_arc_length = + tier4_autoware_utils::calcDistance2d(*intersection_point, bound[bound_idx + 1]) + + tier4_autoware_utils::calcDistance2d(bound[bound_idx + 1], bound[bound_idx + 2]); + for (auto up_bound_idx = bound_idx + 2; + bound_idx < bound.size() && up_arc_length <= params.extra_arc_length; + ++up_bound_idx) { + minimum_expansions[up_bound_idx] = std::max(minimum_expansions[up_bound_idx], dist); + if (up_bound_idx + 1 < bound.size()) + up_arc_length += + tier4_autoware_utils::calcDistance2d(bound[up_bound_idx], bound[up_bound_idx + 1]); + } + } + if (bound_idx > 0) { + auto down_arc_length = + tier4_autoware_utils::calcDistance2d(*intersection_point, bound[bound_idx]) + + tier4_autoware_utils::calcDistance2d(bound[bound_idx - 1], bound[bound_idx]); + for (auto down_bound_idx = bound_idx - 1; + down_bound_idx > 0 && down_arc_length <= params.extra_arc_length; --down_bound_idx) { + minimum_expansions[down_bound_idx] = std::max(minimum_expansions[down_bound_idx], dist); + if (down_bound_idx > 1) + down_arc_length += tier4_autoware_utils::calcDistance2d( + bound[down_bound_idx], bound[down_bound_idx - 1]); + } + } + break; + } + } + } + return minimum_expansions; } -polygon_t createExpandedDrivableAreaPolygon( - const PathWithLaneId & path, const multi_polygon_t & expansion_polygons) +void apply_bound_change_rate_limit( + std::vector & distances, const std::vector & bound, const double max_rate) { - polygon_t original_da_poly; - original_da_poly.outer().reserve(path.left_bound.size() + path.right_bound.size() + 1); - for (const auto & p : path.left_bound) original_da_poly.outer().push_back(convert_point(p)); - for (auto it = path.right_bound.rbegin(); it != path.right_bound.rend(); ++it) - original_da_poly.outer().push_back(convert_point(*it)); - original_da_poly.outer().push_back(original_da_poly.outer().front()); - - multi_polygon_t unions; - auto expanded_da_poly = original_da_poly; - for (const auto & p : expansion_polygons) { - unions.clear(); - boost::geometry::union_(expanded_da_poly, p, unions); - if (unions.size() == 1) // union of overlapping polygons should produce a single polygon - expanded_da_poly = unions[0]; - } - return expanded_da_poly; + if (distances.empty()) return; + const auto apply_max_vel = [&](auto & exp, const auto from, const auto to) { + if (exp[from] > exp[to]) { + const auto arc_length = tier4_autoware_utils::calcDistance2d(bound[from], bound[to]); + const auto smoothed_dist = exp[from] - arc_length * max_rate; + exp[to] = std::max(exp[to], smoothed_dist); + } + }; + for (auto idx = 0LU; idx + 1 < distances.size(); ++idx) apply_max_vel(distances, idx, idx + 1); + for (auto idx = distances.size() - 1; idx > 0; --idx) apply_max_vel(distances, idx, idx - 1); } -void copy_z_over_arc_length( - const std::vector & from, std::vector & to) +std::vector calculate_maximum_distance( + const std::vector & path_poses, const std::vector bound, + const std::vector & uncrossable_lines, + const std::vector & uncrossable_polygons, + const DrivableAreaExpansionParameters & params) { - if (from.empty() || to.empty()) return; - to.front().z = from.front().z; - if (from.size() < 2 || to.size() < 2) return; - to.back().z = from.back().z; - auto i_from = 1lu; - auto s_from = tier4_autoware_utils::calcDistance2d(from[0], from[1]); - auto s_to = 0.0; - auto s_from_prev = 0.0; - for (auto i_to = 1lu; i_to + 1 < to.size(); ++i_to) { - s_to += tier4_autoware_utils::calcDistance2d(to[i_to - 1], to[i_to]); - for (; s_from < s_to && i_from + 1 < from.size(); ++i_from) { - s_from_prev = s_from; - s_from += tier4_autoware_utils::calcDistance2d(from[i_from], from[i_from + 1]); + // TODO(Maxime): improve performances (dont use bg::distance ? use rtree ?) + std::vector maximum_distances(bound.size(), std::numeric_limits::max()); + LineString2d path_ls; + LineString2d bound_ls; + for (const auto & p : bound) bound_ls.push_back(convert_point(p)); + for (const auto & p : path_poses) path_ls.push_back(convert_point(p.position)); + for (auto i = 0UL; i + 1 < bound_ls.size(); ++i) { + const LineString2d segment_ls = {bound_ls[i], bound_ls[i + 1]}; + for (const auto & uncrossable_line : uncrossable_lines) { + const auto bound_to_line_dist = boost::geometry::distance(segment_ls, uncrossable_line); + const auto dist_limit = std::max(0.0, bound_to_line_dist - params.avoid_linestring_dist); + maximum_distances[i] = std::min(maximum_distances[i], dist_limit); + maximum_distances[i + 1] = std::min(maximum_distances[i + 1], dist_limit); } - if (s_from - s_from_prev != 0.0) { - const auto ratio = (s_to - s_from_prev) / (s_from - s_from_prev); - to[i_to].z = interpolation::lerp(from[i_from - 1].z, from[i_from].z, ratio); - } else { - to[i_to].z = to[i_to - 1].z; + for (const auto & uncrossable_poly : uncrossable_polygons) { + const auto bound_to_poly_dist = boost::geometry::distance(segment_ls, uncrossable_poly); + maximum_distances[i] = std::min(maximum_distances[i], bound_to_poly_dist); + maximum_distances[i + 1] = std::min(maximum_distances[i + 1], bound_to_poly_dist); } } + if (params.max_expansion_distance > 0.0) + for (auto & d : maximum_distances) d = std::min(params.max_expansion_distance, d); + return maximum_distances; } -void updateDrivableAreaBounds(PathWithLaneId & path, const polygon_t & expanded_drivable_area) +void expand_bound( + std::vector & bound, const std::vector & path_poses, + const std::vector & expansions) { - const auto original_left_bound = path.left_bound; - const auto original_right_bound = path.right_bound; - const auto is_left_of_path = [&](const point_t & p) { - return motion_utils::calcLateralOffset(path.points, convert_point(p)) > 0.0; - }; - // prepare delimiting lines: start and end of the original expanded drivable area - const auto start_segment = - segment_t{convert_point(path.left_bound.front()), convert_point(path.right_bound.front())}; - const auto end_segment = - segment_t{convert_point(path.left_bound.back()), convert_point(path.right_bound.back())}; - point_t start_segment_center; - boost::geometry::centroid(start_segment, start_segment_center); - const auto path_start_segment = - segment_t{start_segment_center, convert_point(path.points[1].point.pose.position)}; - point_t end_segment_center; - boost::geometry::centroid(end_segment, end_segment_center); - const auto path_end_segment = - segment_t{convert_point(path.points.back().point.pose.position), end_segment_center}; - const auto segment_to_line_intersection = - [](const auto p1, const auto p2, const auto q1, const auto q2) -> std::optional { - const auto line = Eigen::Hyperplane::Through(q1, q2); - const auto segment = Eigen::Hyperplane::Through(p1, p2); - const auto intersection = line.intersection(segment); - std::optional result; - const auto is_on_segment = - (p1.x() <= p2.x() ? intersection.x() >= p1.x() && intersection.x() <= p2.x() - : intersection.x() <= p1.x() && intersection.x() >= p2.x()) && - (p1.y() <= p2.y() ? intersection.y() >= p1.y() && intersection.y() <= p2.y() - : intersection.y() <= p1.y() && intersection.y() >= p2.y()); - if (is_on_segment) result = point_t{intersection.x(), intersection.y()}; - return result; - }; - // find intersection between the expanded drivable area and the delimiting lines - const auto & da = expanded_drivable_area.outer(); - struct Intersection - { - point_t intersection_point; - ring_t::const_iterator segment_it; - double distance = std::numeric_limits::max(); - explicit Intersection(ring_t::const_iterator it) : segment_it(it) {} - void update(const point_t & p, const ring_t::const_iterator & it, const double dist) - { - intersection_point = p; - segment_it = it; - distance = dist; - } - }; - Intersection start_left(da.end()); - Intersection end_left(da.end()); - Intersection start_right(da.end()); - Intersection end_right(da.end()); - for (auto it = da.begin(); it != da.end(); ++it) { - if (boost::geometry::distance(*it, start_segment.first) < 1e-3) - start_left.update(*it, it, 0.0); - else if (boost::geometry::distance(*it, start_segment.second) < 1e-3) - start_right.update(*it, it, 0.0); - else if (boost::geometry::distance(*it, end_segment.first) < 1e-3) - end_left.update(*it, it, 0.0); - else if (boost::geometry::distance(*it, end_segment.second) < 1e-3) - end_right.update(*it, it, 0.0); - const auto inter_start = - std::next(it) == da.end() - ? segment_to_line_intersection(*it, da.front(), start_segment.first, start_segment.second) - : segment_to_line_intersection( - *it, *std::next(it), start_segment.first, start_segment.second); - if (inter_start) { - const auto dist = boost::geometry::distance(*inter_start, path_start_segment); - const auto is_left = is_left_of_path(*inter_start); - if (is_left && dist < start_left.distance) - start_left.update(*inter_start, it, dist); - else if (!is_left && dist < start_right.distance) - start_right.update(*inter_start, it, dist); + LineString2d path_ls; + for (const auto & p : path_poses) path_ls.push_back(convert_point(p.position)); + for (auto idx = 0LU; idx < bound.size(); ++idx) { + const auto bound_p = convert_point(bound[idx]); + const auto projection = point_to_linestring_projection(bound_p, path_ls); + const auto expansion_ratio = + (expansions[idx] + std::abs(projection.distance)) / std::abs(projection.distance); + if (expansion_ratio > 1.0) { + const auto & path_p = projection.projected_point; + const auto expanded_p = lerp_point(path_p, bound_p, expansion_ratio); + bound[idx].x = expanded_p.x(); + bound[idx].y = expanded_p.y(); } - const auto inter_end = - std::next(it) == da.end() - ? segment_to_line_intersection(*it, da.front(), end_segment.first, end_segment.second) - : segment_to_line_intersection(*it, *std::next(it), end_segment.first, end_segment.second); - if (inter_end) { - const auto dist = boost::geometry::distance(*inter_end, path_end_segment); - const auto is_left = is_left_of_path(*inter_end); - if (is_left && dist < end_left.distance) - end_left.update(*inter_end, it, dist); - else if (!is_left && dist < end_right.distance) - end_right.update(*inter_end, it, dist); - } - } - if (start_left.segment_it == da.end()) { - const auto closest_it = - std::min_element(da.begin(), da.end(), [&](const auto & a, const auto & b) { - return boost::geometry::distance(a, start_segment.first) < - boost::geometry::distance(b, start_segment.first); - }); - start_left.update(*closest_it, closest_it, 0.0); - } - if (start_right.segment_it == da.end()) { - const auto closest_it = - std::min_element(da.begin(), da.end(), [&](const auto & a, const auto & b) { - return boost::geometry::distance(a, start_segment.second) < - boost::geometry::distance(b, start_segment.second); - }); - start_right.update(*closest_it, closest_it, 0.0); - } - if (end_left.segment_it == da.end()) { - const auto closest_it = - std::min_element(da.begin(), da.end(), [&](const auto & a, const auto & b) { - return boost::geometry::distance(a, end_segment.first) < - boost::geometry::distance(b, end_segment.first); - }); - end_left.update(*closest_it, closest_it, 0.0); - } - if (end_right.segment_it == da.end()) { - const auto closest_it = - std::min_element(da.begin(), da.end(), [&](const auto & a, const auto & b) { - return boost::geometry::distance(a, end_segment.second) < - boost::geometry::distance(b, end_segment.second); - }); - end_right.update(*closest_it, closest_it, 0.0); } - // extract the expanded left and right bound from the expanded drivable area - path.left_bound.clear(); - path.right_bound.clear(); - path.left_bound.push_back(convert_point(start_left.intersection_point)); - path.right_bound.push_back(convert_point(start_right.intersection_point)); - if (!boost::geometry::equals(start_right.intersection_point, *start_right.segment_it)) - path.right_bound.push_back(convert_point(*start_right.segment_it)); - if (start_left.segment_it < end_left.segment_it) { - for (auto it = std::next(start_left.segment_it); it <= end_left.segment_it; ++it) - path.left_bound.push_back(convert_point(*it)); - } else { - for (auto it = std::next(start_left.segment_it); it < da.end(); ++it) - path.left_bound.push_back(convert_point(*it)); - for (auto it = da.begin(); it <= end_left.segment_it; ++it) - path.left_bound.push_back(convert_point(*it)); + // remove any self intersection by skipping the points inside of the loop + std::vector no_loop_bound = {bound.front()}; + for (auto idx = 1LU; idx < bound.size(); ++idx) { + bool is_intersecting = false; + for (auto succ_idx = idx + 1; succ_idx < bound.size(); ++succ_idx) { + const auto intersection = tier4_autoware_utils::intersect( + bound[idx - 1], bound[idx], bound[succ_idx - 1], bound[succ_idx]); + if ( + intersection && + tier4_autoware_utils::calcDistance2d(*intersection, bound[idx - 1]) < 1e-3 && + tier4_autoware_utils::calcDistance2d(*intersection, bound[idx]) < 1e-3) { + idx = succ_idx; + is_intersecting = true; + } + } + if (!is_intersecting) no_loop_bound.push_back(bound[idx]); } - if (!boost::geometry::equals(end_left.intersection_point, *end_left.segment_it)) - path.left_bound.push_back(convert_point(end_left.intersection_point)); - if (start_right.segment_it < end_right.segment_it) { - for (auto it = std::prev(start_right.segment_it); it >= da.begin(); --it) - path.right_bound.push_back(convert_point(*it)); - for (auto it = std::prev(da.end()); it > end_right.segment_it; --it) - path.right_bound.push_back(convert_point(*it)); - } else { - for (auto it = std::prev(start_right.segment_it); it > end_right.segment_it; --it) - path.right_bound.push_back(convert_point(*it)); + bound = no_loop_bound; +} + +std::vector calculate_smoothed_curvatures( + const std::vector & poses, const size_t smoothing_window_size) +{ + const auto curvatures = motion_utils::calcCurvature(poses); + std::vector smoothed_curvatures(curvatures.size()); + for (auto i = 0UL; i < curvatures.size(); ++i) { + auto sum = 0.0; + const auto from_idx = (i >= smoothing_window_size ? i - smoothing_window_size : 0); + const auto to_idx = std::min(i + smoothing_window_size, curvatures.size() - 1); + for (auto j = from_idx; j <= to_idx; ++j) sum += std::abs(curvatures[j]); + smoothed_curvatures[i] = sum / static_cast(to_idx - from_idx + 1); } - if (!boost::geometry::equals(end_right.intersection_point, *std::next(end_right.segment_it))) - path.right_bound.push_back(convert_point(end_right.intersection_point)); - // remove possible duplicated points - const auto point_cmp = [](const auto & p1, const auto & p2) { - return p1.x == p2.x && p1.y == p2.y; - }; - path.left_bound.erase( - std::unique(path.left_bound.begin(), path.left_bound.end(), point_cmp), path.left_bound.end()); - path.right_bound.erase( - std::unique(path.right_bound.begin(), path.right_bound.end(), point_cmp), - path.right_bound.end()); - copy_z_over_arc_length(original_left_bound, path.left_bound); - copy_z_over_arc_length(original_right_bound, path.right_bound); + return smoothed_curvatures; +} + +void expand_drivable_area( + PathWithLaneId & path, + const std::shared_ptr planner_data) +{ + // skip if no bounds or not enough points to calculate path curvature + if (path.points.size() < 3 || path.left_bound.empty() || path.right_bound.empty()) return; + tier4_autoware_utils::StopWatch stop_watch; + stop_watch.tic("overall"); + stop_watch.tic("preprocessing"); + // crop first/last non deviating path_poses + const auto & params = planner_data->drivable_area_expansion_parameters; + const auto & route_handler = *planner_data->route_handler; + const auto uncrossable_lines = extract_uncrossable_lines( + *route_handler.getLaneletMapPtr(), planner_data->self_odometry->pose.pose.position, params); + const auto uncrossable_polygons = create_object_footprints(*planner_data->dynamic_object, params); + const auto preprocessing_ms = stop_watch.toc("preprocessing"); + + stop_watch.tic("crop"); + std::vector path_poses = planner_data->drivable_area_expansion_prev_path_poses; + std::vector curvatures = planner_data->drivable_area_expansion_prev_curvatures; + reuse_previous_poses( + path, path_poses, curvatures, planner_data->self_odometry->pose.pose.position, params); + const auto crop_ms = stop_watch.toc("crop"); + + stop_watch.tic("curvatures_expansion"); + // Only add curvatures for the new points. Curvatures of reused path points are not updated. + const auto new_curvatures = + calculate_smoothed_curvatures(path_poses, params.curvature_average_window); + const auto first_new_point_idx = curvatures.size(); + curvatures.insert( + curvatures.end(), new_curvatures.begin() + first_new_point_idx, new_curvatures.end()); + auto left_expansions = + calculate_minimum_expansions(path_poses, path.left_bound, curvatures, LEFT, params); + auto right_expansions = + calculate_minimum_expansions(path_poses, path.right_bound, curvatures, RIGHT, params); + const auto curvature_expansion_ms = stop_watch.toc("curvatures_expansion"); + + stop_watch.tic("max_dist"); + const auto max_left_expansions = calculate_maximum_distance( + path_poses, path.left_bound, uncrossable_lines, uncrossable_polygons, params); + const auto max_right_expansions = calculate_maximum_distance( + path_poses, path.right_bound, uncrossable_lines, uncrossable_polygons, params); + for (auto i = 0LU; i < left_expansions.size(); ++i) + left_expansions[i] = std::min(left_expansions[i], max_left_expansions[i]); + for (auto i = 0LU; i < right_expansions.size(); ++i) + right_expansions[i] = std::min(right_expansions[i], max_right_expansions[i]); + const auto max_dist_ms = stop_watch.toc("max_dist"); + + stop_watch.tic("smooth"); + apply_bound_change_rate_limit(left_expansions, path.left_bound, params.max_bound_rate); + apply_bound_change_rate_limit(right_expansions, path.right_bound, params.max_bound_rate); + const auto smooth_ms = stop_watch.toc("smooth"); + // TODO(Maxime): limit the distances based on the total width (left + right < min_lane_width) + stop_watch.tic("expand"); + expand_bound(path.left_bound, path_poses, left_expansions); + expand_bound(path.right_bound, path_poses, right_expansions); + const auto expand_ms = stop_watch.toc("expand"); + + const auto total_ms = stop_watch.toc("overall"); + if (params.print_runtime) + std::printf( + "Total runtime(ms): %2.2f\n\tPreprocessing: %2.2f\n\tCrop: %2.2f\n\tCurvature expansion: " + "%2.2f\n\tMaximum expansion: %2.2f\n\tSmoothing: %2.2f\n\tExpansion: %2.2f\n\n", + total_ms, preprocessing_ms, crop_ms, curvature_expansion_ms, max_dist_ms, smooth_ms, + expand_ms); + + planner_data->drivable_area_expansion_prev_path_poses = path_poses; + planner_data->drivable_area_expansion_prev_curvatures = curvatures; } } // namespace drivable_area_expansion diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp deleted file mode 100644 index 828fdc2f17a51..0000000000000 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/expansion.cpp +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2023 TIER IV, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "behavior_path_planner/utils/drivable_area_expansion/expansion.hpp" - -#include "behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp" -#include "behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp" - -namespace drivable_area_expansion -{ - -double calculateDistanceLimit( - const linestring_t & base_ls, const polygon_t & expansion_polygon, - const multi_linestring_t & limit_lines) -{ - auto dist_limit = std::numeric_limits::max(); - for (const auto & line : limit_lines) { - multi_point_t intersections; - boost::geometry::intersection(expansion_polygon, limit_lines, intersections); - for (const auto & p : intersections) - dist_limit = std::min(dist_limit, boost::geometry::distance(p, base_ls)); - for (const auto & p : line) - if (boost::geometry::within(p, expansion_polygon)) - dist_limit = std::min(dist_limit, boost::geometry::distance(p, base_ls)); - } - return dist_limit; -} - -double calculateDistanceLimit( - const linestring_t & base_ls, const polygon_t & expansion_polygon, - const multi_polygon_t & limit_polygons) -{ - auto dist_limit = std::numeric_limits::max(); - for (const auto & polygon : limit_polygons) { - multi_point_t intersections; - boost::geometry::intersection(expansion_polygon, polygon, intersections); - for (const auto & p : intersections) - dist_limit = std::min(dist_limit, boost::geometry::distance(p, base_ls)); - } - return dist_limit; -} - -polygon_t createExpansionPolygon( - const linestring_t & base_ls, const double dist, const bool is_left_side) -{ - namespace strategy = boost::geometry::strategy::buffer; - multi_polygon_t polygons; - // set a non 0 value for the buffer as it sometimes causes no polygon to be returned by bg:buffer - constexpr auto zero = 0.1; - const auto left_dist = is_left_side ? dist : zero; - const auto right_dist = !is_left_side ? dist : zero; - const auto distance_strategy = strategy::distance_asymmetric(left_dist, right_dist); - boost::geometry::buffer( - base_ls, polygons, distance_strategy, strategy::side_straight(), strategy::join_miter(), - strategy::end_flat(), strategy::point_square()); - return polygons.front(); -} - -std::array calculate_arc_length_range_and_distance( - const linestring_t & path_ls, const polygon_t & footprint, const linestring_t & bound, - const bool is_left, const double path_length) -{ - multi_point_t intersections; - double expansion_dist = 0.0; - double from_arc_length = std::numeric_limits::max(); - double to_arc_length = std::numeric_limits::min(); - boost::geometry::intersection(footprint, bound, intersections); - if (!intersections.empty()) { - for (const auto & intersection : intersections) { - const auto projection = point_to_linestring_projection(intersection, path_ls); - if (projection.arc_length <= 0.0 || projection.arc_length >= path_length) continue; - from_arc_length = std::min(from_arc_length, projection.arc_length); - to_arc_length = std::max(to_arc_length, projection.arc_length); - } - for (const auto & p : footprint.outer()) { - const auto projection = point_to_linestring_projection(p, path_ls); - if (projection.arc_length <= 0.0 || projection.arc_length >= path_length - 1e-3) continue; - if (is_left == (projection.distance > 0) && std::abs(projection.distance) > expansion_dist) { - expansion_dist = std::abs(projection.distance); - from_arc_length = std::min(from_arc_length, projection.arc_length); - to_arc_length = std::max(to_arc_length, projection.arc_length); - } - } - } - return std::array({from_arc_length, to_arc_length, expansion_dist}); -} - -polygon_t create_compensation_polygon( - const linestring_t & base_ls, const double compensation_dist, const bool is_left, - const multi_linestring_t uncrossable_lines, const multi_polygon_t & predicted_paths) -{ - polygon_t compensation_polygon = createExpansionPolygon(base_ls, compensation_dist, !is_left); - double dist_limit = std::min( - compensation_dist, calculateDistanceLimit(base_ls, compensation_polygon, uncrossable_lines)); - if (!predicted_paths.empty()) - dist_limit = - std::min(dist_limit, calculateDistanceLimit(base_ls, compensation_polygon, predicted_paths)); - if (dist_limit < compensation_dist) - compensation_polygon = createExpansionPolygon(base_ls, dist_limit, !is_left); - return compensation_polygon; -} - -multi_polygon_t createExpansionPolygons( - const PathWithLaneId & path, const multi_polygon_t & path_footprints, - const multi_polygon_t & predicted_paths, const multi_linestring_t & uncrossable_lines, - const DrivableAreaExpansionParameters & params) -{ - linestring_t path_ls; - linestring_t left_ls; - linestring_t right_ls; - for (const auto & p : path.points) - path_ls.emplace_back(p.point.pose.position.x, p.point.pose.position.y); - for (const auto & p : path.left_bound) left_ls.emplace_back(p.x, p.y); - for (const auto & p : path.right_bound) right_ls.emplace_back(p.x, p.y); - // extend the path linestring to the beginning and end of the drivable area - if (!right_ls.empty() && !left_ls.empty() && path_ls.size() > 2) { - const auto left_proj_begin = point_to_line_projection(left_ls.front(), path_ls[0], path_ls[1]); - const auto right_proj_begin = - point_to_line_projection(right_ls.front(), path_ls[0], path_ls[1]); - const auto left_ls_proj_begin = point_to_linestring_projection(left_proj_begin.point, path_ls); - const auto right_ls_proj_begin = - point_to_linestring_projection(right_proj_begin.point, path_ls); - if (left_ls_proj_begin.arc_length < right_ls_proj_begin.arc_length) - path_ls.insert(path_ls.begin(), left_proj_begin.point); - else - path_ls.insert(path_ls.begin(), right_proj_begin.point); - const auto left_proj_end = - point_to_line_projection(left_ls.back(), path_ls[path_ls.size() - 2], path_ls.back()); - const auto right_proj_end = - point_to_line_projection(right_ls.back(), path_ls[path_ls.size() - 2], path_ls.back()); - const auto left_ls_proj_end = point_to_linestring_projection(left_proj_end.point, path_ls); - const auto right_ls_proj_end = point_to_linestring_projection(right_proj_end.point, path_ls); - if (left_ls_proj_end.arc_length > right_ls_proj_end.arc_length) - path_ls.push_back(left_proj_end.point); - else - path_ls.push_back(right_proj_end.point); - } - const auto path_length = static_cast(boost::geometry::length(path_ls)); - - multi_polygon_t expansion_polygons; - for (const auto & footprint : path_footprints) { - bool is_left = true; - for (const auto & bound : {left_ls, right_ls}) { - auto [from_arc_length, to_arc_length, footprint_dist] = - calculate_arc_length_range_and_distance(path_ls, footprint, bound, is_left, path_length); - if (footprint_dist > 0.0) { - from_arc_length -= params.extra_arc_length; - to_arc_length += params.extra_arc_length; - from_arc_length = std::max(0.0, from_arc_length); - to_arc_length = std::min(path_length, to_arc_length); - const auto base_ls = sub_linestring(path_ls, from_arc_length, to_arc_length); - const auto expansion_dist = params.max_expansion_distance != 0.0 - ? std::min(params.max_expansion_distance, footprint_dist) - : footprint_dist; - auto expansion_polygon = createExpansionPolygon(base_ls, expansion_dist, is_left); - auto limited_dist = expansion_dist; - const auto uncrossable_dist_limit = std::max( - 0.0, calculateDistanceLimit(base_ls, expansion_polygon, uncrossable_lines) - - params.avoid_linestring_dist); - if (uncrossable_dist_limit < limited_dist) { - limited_dist = uncrossable_dist_limit; - if (params.compensate_uncrossable_lines) { - const auto compensation_dist = - footprint_dist - limited_dist + params.compensate_extra_dist; - expansion_polygons.push_back(create_compensation_polygon( - base_ls, compensation_dist, is_left, uncrossable_lines, predicted_paths)); - } - } - limited_dist = std::min( - limited_dist, calculateDistanceLimit(base_ls, expansion_polygon, predicted_paths)); - if (limited_dist < expansion_dist) - expansion_polygon = createExpansionPolygon(base_ls, limited_dist, is_left); - expansion_polygons.push_back(expansion_polygon); - } - is_left = false; - } - } - return expansion_polygons; -} - -multi_polygon_t createExpansionLaneletPolygons( - const lanelet::ConstLanelets & path_lanes, const route_handler::RouteHandler & route_handler, - const multi_polygon_t & path_footprints, const multi_polygon_t & predicted_paths, - const DrivableAreaExpansionParameters & params) -{ - multi_polygon_t expansion_polygons; - lanelet::ConstLanelets candidates; - const auto already_added = [&](const auto & ll) { - return std::find_if(candidates.begin(), candidates.end(), [&](const auto & l) { - return ll.id() == l.id(); - }) != candidates.end(); - }; - const auto add_if_valid = [&](const auto & ll, const auto is_left) { - const auto bound_to_check = is_left ? ll.rightBound() : ll.leftBound(); - if (std::find_if(path_lanes.begin(), path_lanes.end(), [&](const auto & l) { - return ll.id() == l.id(); - }) == path_lanes.end()) - if (!already_added(ll) && !hasTypes(bound_to_check, params.avoid_linestring_types)) - candidates.push_back(ll); - }; - for (const auto & current_ll : path_lanes) { - for (const auto & left_ll : - route_handler.getLaneletsFromPoint(current_ll.leftBound3d().front())) - add_if_valid(left_ll, true); - for (const auto & left_ll : route_handler.getLaneletsFromPoint(current_ll.leftBound3d().back())) - add_if_valid(left_ll, true); - for (const auto & right_ll : - route_handler.getLaneletsFromPoint(current_ll.rightBound3d().front())) - add_if_valid(right_ll, false); - for (const auto & right_ll : - route_handler.getLaneletsFromPoint(current_ll.rightBound3d().back())) - add_if_valid(right_ll, false); - } - for (const auto & candidate : candidates) { - polygon_t candidate_poly; - for (const auto & p : candidate.polygon2d()) candidate_poly.outer().emplace_back(p.x(), p.y()); - boost::geometry::correct(candidate_poly); - if ( - !boost::geometry::overlaps(candidate_poly, predicted_paths) && - boost::geometry::overlaps(path_footprints, candidate_poly)) - expansion_polygons.push_back(candidate_poly); - } - return expansion_polygons; -} - -} // namespace drivable_area_expansion diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp index 4a45dce6a0bcc..2e6d4929fdf02 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/footprints.cpp @@ -28,63 +28,39 @@ namespace drivable_area_expansion { -polygon_t translatePolygon(const polygon_t & polygon, const double x, const double y) +Polygon2d translate_polygon(const Polygon2d & polygon, const double x, const double y) { - polygon_t translated_polygon; + Polygon2d translated_polygon; const boost::geometry::strategy::transform::translate_transformer translation(x, y); boost::geometry::transform(polygon, translated_polygon, translation); return translated_polygon; } -polygon_t createFootprint(const geometry_msgs::msg::Pose & pose, const polygon_t base_footprint) +Polygon2d create_footprint(const geometry_msgs::msg::Pose & pose, const Polygon2d base_footprint) { const auto angle = tf2::getYaw(pose.orientation); - return translatePolygon(rotatePolygon(base_footprint, angle), pose.position.x, pose.position.y); + return translate_polygon( + tier4_autoware_utils::rotatePolygon(base_footprint, angle), pose.position.x, pose.position.y); } -multi_polygon_t createObjectFootprints( +MultiPolygon2d create_object_footprints( const autoware_auto_perception_msgs::msg::PredictedObjects & objects, const DrivableAreaExpansionParameters & params) { - multi_polygon_t footprints; + MultiPolygon2d footprints; if (params.avoid_dynamic_objects) { for (const auto & object : objects.objects) { const auto front = object.shape.dimensions.x / 2 + params.dynamic_objects_extra_front_offset; const auto rear = -object.shape.dimensions.x / 2 - params.dynamic_objects_extra_rear_offset; const auto left = object.shape.dimensions.y / 2 + params.dynamic_objects_extra_left_offset; const auto right = -object.shape.dimensions.y / 2 - params.dynamic_objects_extra_right_offset; - polygon_t base_footprint; + Polygon2d base_footprint; base_footprint.outer() = { - point_t{front, left}, point_t{front, right}, point_t{rear, right}, point_t{rear, left}, - point_t{front, left}}; + Point2d{front, left}, Point2d{front, right}, Point2d{rear, right}, Point2d{rear, left}, + Point2d{front, left}}; for (const auto & path : object.kinematics.predicted_paths) for (const auto & pose : path.path) - footprints.push_back(createFootprint(pose, base_footprint)); - } - } - return footprints; -} - -multi_polygon_t createPathFootprints( - const std::vector & points, const DrivableAreaExpansionParameters & params) -{ - const auto left = params.ego_left_offset + params.ego_extra_left_offset; - const auto right = params.ego_right_offset - params.ego_extra_right_offset; - const auto rear = params.ego_rear_offset - params.ego_extra_rear_offset; - const auto front = params.ego_front_offset + params.ego_extra_front_offset; - polygon_t base_footprint; - base_footprint.outer() = { - point_t{front, left}, point_t{front, right}, point_t{rear, right}, point_t{rear, left}, - point_t{front, left}}; - multi_polygon_t footprints; - // skip the last footprint as its orientation is usually wrong - footprints.reserve(points.size() - 1); - double arc_length = 0.0; - for (auto it = points.begin(); std::next(it) != points.end(); ++it) { - footprints.push_back(createFootprint(it->point.pose, base_footprint)); - if (params.max_path_arc_length > 0.0) { - arc_length += tier4_autoware_utils::calcDistance2d(it->point.pose, std::next(it)->point.pose); - if (arc_length > params.max_path_arc_length) break; + footprints.push_back(create_footprint(pose, base_footprint)); } } return footprints; diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp index ded67c251f7ae..deeb787cf39f6 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp @@ -15,33 +15,34 @@ #include "behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp" -#include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" -#include "lanelet2_core/primitives/LineString.h" - #include #include +#include #include namespace drivable_area_expansion { -multi_linestring_t extractUncrossableLines( - const lanelet::LaneletMap & lanelet_map, const std::vector & uncrossable_types) +MultiLineString2d extract_uncrossable_lines( + const lanelet::LaneletMap & lanelet_map, const Point & ego_point, + const DrivableAreaExpansionParameters & params) { - multi_linestring_t lines; - linestring_t line; + MultiLineString2d uncrossable_lines_in_range; + LineString2d line; + const auto ego_p = Point2d{ego_point.x, ego_point.y}; for (const auto & ls : lanelet_map.lineStringLayer) { - if (hasTypes(ls, uncrossable_types)) { + if (has_types(ls, params.avoid_linestring_types)) { line.clear(); - for (const auto & p : ls) line.push_back(point_t{p.x(), p.y()}); - lines.push_back(line); + for (const auto & p : ls) line.push_back(Point2d{p.x(), p.y()}); + if (boost::geometry::distance(line, ego_p) < params.max_path_arc_length) + uncrossable_lines_in_range.push_back(line); } } - return lines; + return uncrossable_lines_in_range; } -bool hasTypes(const lanelet::ConstLineString3d & ls, const std::vector & types) +bool has_types(const lanelet::ConstLineString3d & ls, const std::vector & types) { constexpr auto no_type = ""; const auto type = ls.attributeOr(lanelet::AttributeName::Type, no_type); diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index bf6fffda41863..cb0486b6cd01b 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -1539,7 +1539,7 @@ void generateDrivableArea( } const auto & expansion_params = planner_data->drivable_area_expansion_parameters; if (expansion_params.enabled) { - drivable_area_expansion::expandDrivableArea(path, planner_data, transformed_lanes); + drivable_area_expansion::expand_drivable_area(path, planner_data); } // make bound longitudinally monotonic diff --git a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp index 163221951d726..99cd5cac2b9d9 100644 --- a/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/test/test_drivable_area_expansion.cpp @@ -14,7 +14,6 @@ #include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp" -#include "behavior_path_planner/utils/drivable_area_expansion/expansion.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" #include "lanelet2_extension/utility/message_conversion.hpp" @@ -22,9 +21,9 @@ #include #include -using drivable_area_expansion::linestring_t; -using drivable_area_expansion::point_t; -using drivable_area_expansion::segment_t; +using drivable_area_expansion::LineString2d; +using drivable_area_expansion::Point2d; +using drivable_area_expansion::Segment2d; constexpr auto eps = 1e-9; TEST(DrivableAreaExpansionProjection, PointToSegment) @@ -32,56 +31,56 @@ TEST(DrivableAreaExpansionProjection, PointToSegment) using drivable_area_expansion::point_to_segment_projection; { - point_t query(1.0, 1.0); - segment_t segment(point_t(0.0, 0.0), point_t(10.0, 0.0)); + Point2d query(1.0, 1.0); + Segment2d segment(Point2d(0.0, 0.0), Point2d(10.0, 0.0)); const auto projection = point_to_segment_projection(query, segment.first, segment.second); EXPECT_NEAR(projection.distance, 1.0, eps); EXPECT_NEAR(projection.point.x(), 1.0, eps); EXPECT_NEAR(projection.point.y(), 0.0, eps); } { - point_t query(-1.0, 1.0); - segment_t segment(point_t(0.0, 0.0), point_t(10.0, 0.0)); + Point2d query(-1.0, 1.0); + Segment2d segment(Point2d(0.0, 0.0), Point2d(10.0, 0.0)); const auto projection = point_to_segment_projection(query, segment.first, segment.second); EXPECT_NEAR(projection.distance, std::sqrt(2), eps); EXPECT_NEAR(projection.point.x(), 0.0, eps); EXPECT_NEAR(projection.point.y(), 0.0, eps); } { - point_t query(11.0, 1.0); - segment_t segment(point_t(0.0, 0.0), point_t(10.0, 0.0)); + Point2d query(11.0, 1.0); + Segment2d segment(Point2d(0.0, 0.0), Point2d(10.0, 0.0)); const auto projection = point_to_segment_projection(query, segment.first, segment.second); EXPECT_NEAR(projection.distance, std::sqrt(2), eps); EXPECT_NEAR(projection.point.x(), 10.0, eps); EXPECT_NEAR(projection.point.y(), 0.0, eps); } { - point_t query(5.0, -5.0); - segment_t segment(point_t(0.0, 0.0), point_t(10.0, 0.0)); + Point2d query(5.0, -5.0); + Segment2d segment(Point2d(0.0, 0.0), Point2d(10.0, 0.0)); const auto projection = point_to_segment_projection(query, segment.first, segment.second); EXPECT_NEAR(projection.distance, -5.0, eps); EXPECT_NEAR(projection.point.x(), 5.0, eps); EXPECT_NEAR(projection.point.y(), 0.0, eps); } { - point_t query(5.0, -5.0); - segment_t segment(point_t(0.0, 0.0), point_t(0.0, -10.0)); + Point2d query(5.0, -5.0); + Segment2d segment(Point2d(0.0, 0.0), Point2d(0.0, -10.0)); const auto projection = point_to_segment_projection(query, segment.first, segment.second); EXPECT_NEAR(projection.distance, 5.0, eps); EXPECT_NEAR(projection.point.x(), 0.0, eps); EXPECT_NEAR(projection.point.y(), -5.0, eps); } { - point_t query(5.0, 5.0); - segment_t segment(point_t(2.5, 7.5), point_t(7.5, 2.5)); + Point2d query(5.0, 5.0); + Segment2d segment(Point2d(2.5, 7.5), Point2d(7.5, 2.5)); const auto projection = point_to_segment_projection(query, segment.first, segment.second); EXPECT_NEAR(projection.distance, 0.0, eps); EXPECT_NEAR(projection.point.x(), 5.0, eps); EXPECT_NEAR(projection.point.y(), 5.0, eps); } { - point_t query(0.0, 0.0); - segment_t segment(point_t(2.5, 7.5), point_t(7.5, 2.5)); + Point2d query(0.0, 0.0); + Segment2d segment(Point2d(2.5, 7.5), Point2d(7.5, 2.5)); const auto projection = point_to_segment_projection(query, segment.first, segment.second); EXPECT_NEAR(projection.distance, -std::sqrt(50), eps); EXPECT_NEAR(projection.point.x(), 5.0, eps); @@ -93,11 +92,11 @@ TEST(DrivableAreaExpansionProjection, PointToLinestring) { using drivable_area_expansion::point_to_linestring_projection; - linestring_t ls = { - point_t(0.0, 0.0), point_t(10.0, 0.0), point_t(10.0, 10.0), point_t(0.0, 10.0), - point_t(5.0, 5.0)}; + LineString2d ls = { + Point2d(0.0, 0.0), Point2d(10.0, 0.0), Point2d(10.0, 10.0), Point2d(0.0, 10.0), + Point2d(5.0, 5.0)}; { - point_t query(0.0, 0.0); + Point2d query(0.0, 0.0); const auto projection = point_to_linestring_projection(query, ls); EXPECT_NEAR(projection.arc_length, 0.0, eps); EXPECT_NEAR(projection.distance, 0.0, eps); @@ -105,7 +104,7 @@ TEST(DrivableAreaExpansionProjection, PointToLinestring) EXPECT_NEAR(projection.projected_point.y(), 0.0, eps); } { - point_t query(2.0, 1.0); + Point2d query(2.0, 1.0); const auto projection = point_to_linestring_projection(query, ls); EXPECT_NEAR(projection.arc_length, 2.0, eps); EXPECT_NEAR(projection.distance, 1.0, eps); @@ -113,7 +112,7 @@ TEST(DrivableAreaExpansionProjection, PointToLinestring) EXPECT_NEAR(projection.projected_point.y(), 0.0, eps); } { - point_t query(0.0, 5.0); + Point2d query(0.0, 5.0); const auto projection = point_to_linestring_projection(query, ls); EXPECT_NEAR(projection.arc_length, 30.0 + std::sqrt(2.5 * 2.5 * 2), eps); EXPECT_NEAR(projection.distance, -std::sqrt(2.5 * 2.5 * 2), eps); @@ -126,9 +125,9 @@ TEST(DrivableAreaExpansionProjection, LinestringToPoint) { using drivable_area_expansion::linestring_to_point_projection; - linestring_t ls = { - point_t(0.0, 0.0), point_t(10.0, 0.0), point_t(10.0, 10.0), point_t(0.0, 10.0), - point_t(5.0, 5.0)}; + LineString2d ls = { + Point2d(0.0, 0.0), Point2d(10.0, 0.0), Point2d(10.0, 10.0), Point2d(0.0, 10.0), + Point2d(5.0, 5.0)}; for (auto arc_length = 0.0; arc_length <= 10.0; arc_length += 1.0) { const auto projection = linestring_to_point_projection(ls, arc_length, 0.0); EXPECT_NEAR(projection.first.x(), arc_length, eps); @@ -152,58 +151,18 @@ TEST(DrivableAreaExpansionProjection, LinestringToPoint) } } -TEST(DrivableAreaExpansionProjection, SubLinestring) -{ - using drivable_area_expansion::sub_linestring; - - const linestring_t ls = { - point_t{0.0, 0.0}, point_t{1.0, 0.0}, point_t{2.0, 0.0}, point_t{3.0, 0.0}, - point_t{4.0, 0.0}, point_t{5.0, 0.0}, point_t{6.0, 0.0}, - }; - { - // arc lengths equal to the original range: same linestring is returned - const auto sub = sub_linestring(ls, 0.0, 6.0); - ASSERT_EQ(ls.size(), sub.size()); - for (auto i = 0lu; i < ls.size(); ++i) EXPECT_TRUE(boost::geometry::equals(ls[i], sub[i])); - } - { - // arc lengths equal to existing point: sub-linestring with same points - const auto sub = sub_linestring(ls, 1.0, 5.0); - ASSERT_EQ(ls.size() - 2lu, sub.size()); - for (auto i = 0lu; i < sub.size(); ++i) EXPECT_TRUE(boost::geometry::equals(ls[i + 1], sub[i])); - } - { - // arc lengths inside the original: sub-linestring with some interpolated points - const auto sub = sub_linestring(ls, 1.5, 2.5); - ASSERT_EQ(sub.size(), 3lu); - EXPECT_NEAR(sub[0].x(), 1.5, eps); - EXPECT_NEAR(sub[1].x(), 2.0, eps); - EXPECT_NEAR(sub[2].x(), 2.5, eps); - for (const auto & p : sub) EXPECT_NEAR(p.y(), 0.0, eps); - } - { - // arc length outside of the original range: first & last point are replaced by interpolations - const auto sub = sub_linestring(ls, -0.5, 8.5); - ASSERT_EQ(sub.size(), ls.size()); - EXPECT_NEAR(sub.front().x(), -0.5, eps); - for (auto i = 1lu; i + 1 < ls.size(); ++i) EXPECT_TRUE(boost::geometry::equals(ls[i], sub[i])); - EXPECT_NEAR(sub.back().x(), 8.5, eps); - for (const auto & p : sub) EXPECT_NEAR(p.y(), 0.0, eps); - } -} - TEST(DrivableAreaExpansionProjection, InverseProjection) { using drivable_area_expansion::linestring_to_point_projection; using drivable_area_expansion::point_to_linestring_projection; - linestring_t ls = { - point_t(0.0, 0.0), point_t(10.0, 0.0), point_t(10.0, 10.0), point_t(0.0, 10.0), - point_t(5.0, 5.0)}; + LineString2d ls = { + Point2d(0.0, 0.0), Point2d(10.0, 0.0), Point2d(10.0, 10.0), Point2d(0.0, 10.0), + Point2d(5.0, 5.0)}; for (auto x = 0.0; x < 10.0; x += 0.1) { for (auto y = 0.0; x < 10.0; x += 0.1) { - point_t p(x, y); + Point2d p(x, y); const auto projection = point_to_linestring_projection(p, ls); const auto inverse = linestring_to_point_projection(ls, projection.arc_length, projection.distance); @@ -213,7 +172,7 @@ TEST(DrivableAreaExpansionProjection, InverseProjection) } } -TEST(DrivableAreaExpansionProjection, expandDrivableArea) +TEST(DrivableAreaExpansionProjection, expand_drivable_area) { drivable_area_expansion::DrivableAreaExpansionParameters params; drivable_area_expansion::PredictedObjects dynamic_objects; @@ -256,121 +215,59 @@ TEST(DrivableAreaExpansionProjection, expandDrivableArea) params.avoid_dynamic_objects = false; params.avoid_linestring_dist = 0.0; params.avoid_linestring_types = {}; - params.compensate_extra_dist = false; params.max_expansion_distance = 0.0; // means no limit params.max_path_arc_length = 0.0; // means no limit params.resample_interval = 1.0; - params.extra_arc_length = 1.0; - params.expansion_method = "polygon"; // 2m x 4m ego footprint - params.ego_front_offset = 1.0; - params.ego_rear_offset = -1.0; - params.ego_left_offset = 2.0; - params.ego_right_offset = -2.0; + params.vehicle_info.front_overhang_m = 0.0; + params.vehicle_info.wheel_base_m = 2.0; + params.vehicle_info.vehicle_width_m = 2.0; } behavior_path_planner::PlannerData planner_data; planner_data.drivable_area_expansion_parameters = params; planner_data.dynamic_object = std::make_shared(dynamic_objects); + planner_data.self_odometry = std::make_shared(); planner_data.route_handler = std::make_shared(route_handler); - // we expect the drivable area to be expanded by 1m on each side - drivable_area_expansion::expandDrivableArea( - path, std::make_shared(planner_data), path_lanes); + drivable_area_expansion::expand_drivable_area( + path, std::make_shared(planner_data)); // unchanged path points ASSERT_EQ(path.points.size(), 3ul); for (auto i = 0.0; i < path.points.size(); ++i) { EXPECT_NEAR(path.points[i].point.pose.position.x, i, eps); EXPECT_NEAR(path.points[i].point.pose.position.y, 0.0, eps); } - + // straight path: no expansion // expanded left bound - ASSERT_EQ(path.left_bound.size(), 4ul); + ASSERT_EQ(path.left_bound.size(), 3ul); EXPECT_NEAR(path.left_bound[0].x, 0.0, eps); EXPECT_NEAR(path.left_bound[0].y, 1.0, eps); - EXPECT_NEAR(path.left_bound[1].x, 0.0, eps); - EXPECT_NEAR(path.left_bound[1].y, 2.0, eps); + EXPECT_NEAR(path.left_bound[1].x, 1.0, eps); + EXPECT_NEAR(path.left_bound[1].y, 1.0, eps); EXPECT_NEAR(path.left_bound[2].x, 2.0, eps); - EXPECT_NEAR(path.left_bound[2].y, 2.0, eps); - EXPECT_NEAR(path.left_bound[3].x, 2.0, eps); - EXPECT_NEAR(path.left_bound[3].y, 1.0, eps); + EXPECT_NEAR(path.left_bound[2].y, 1.0, eps); // expanded right bound ASSERT_EQ(path.right_bound.size(), 3ul); EXPECT_NEAR(path.right_bound[0].x, 0.0, eps); - EXPECT_NEAR(path.right_bound[0].y, -2.0, eps); - EXPECT_NEAR(path.right_bound[1].x, 2.0, eps); - EXPECT_NEAR(path.right_bound[1].y, -2.0, eps); + EXPECT_NEAR(path.right_bound[0].y, -1.0, eps); + EXPECT_NEAR(path.right_bound[1].x, 1.0, eps); + EXPECT_NEAR(path.right_bound[1].y, -1.0, eps); EXPECT_NEAR(path.right_bound[2].x, 2.0, eps); EXPECT_NEAR(path.right_bound[2].y, -1.0, eps); -} - -TEST(DrivableAreaExpansion, calculateDistanceLimit) -{ - using drivable_area_expansion::calculateDistanceLimit; - using drivable_area_expansion::linestring_t; - using drivable_area_expansion::multi_linestring_t; - using drivable_area_expansion::polygon_t; - { - const linestring_t base_ls = {{0.0, 0.0}, {10.0, 0.0}}; - const multi_linestring_t uncrossable_lines = {}; - const polygon_t expansion_polygon = { - {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; - const auto limit_distance = - calculateDistanceLimit(base_ls, expansion_polygon, uncrossable_lines); - EXPECT_NEAR(limit_distance, std::numeric_limits::max(), 1e-9); - } - { - const linestring_t base_ls = {{0.0, 0.0}, {10.0, 0.0}}; - const linestring_t uncrossable_line = {{0.0, 2.0}, {10.0, 2.0}}; - const polygon_t expansion_polygon = { - {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; - const auto limit_distance = - calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_line}); - EXPECT_NEAR(limit_distance, 2.0, 1e-9); - } - { - const linestring_t base_ls = {{0.0, 0.0}, {10.0, 0.0}}; - const multi_linestring_t uncrossable_lines = { - {{0.0, 2.0}, {10.0, 2.0}}, {{0.0, 1.5}, {10.0, 1.0}}}; - const polygon_t expansion_polygon = { - {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; - const auto limit_distance = - calculateDistanceLimit(base_ls, expansion_polygon, uncrossable_lines); - EXPECT_NEAR(limit_distance, 1.0, 1e-9); - } -} - -TEST(DrivableAreaExpansion, calculateDistanceLimitEdgeCases) -{ - using drivable_area_expansion::calculateDistanceLimit; - using drivable_area_expansion::linestring_t; - using drivable_area_expansion::polygon_t; + // add some curvature + path.points[1].point.pose.position.y = 0.5; - const linestring_t base_ls = {{0.0, 0.0}, {10.0, 0.0}}; - const polygon_t expansion_polygon = { - {{0.0, -4.0}, {0.0, 4.0}, {10.0, 4.0}, {10.0, -4.0}, {0.0, -4.0}}, {}}; - { // intersection points further than the line point inside the expansion polygon - const linestring_t uncrossable_lines = {{4.0, 5.0}, {6.0, 3.0}}; - const auto limit_distance = - calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); - EXPECT_NEAR(limit_distance, 3.0, 1e-9); - } - { // intersection points further than the line point inside the expansion polygon - const linestring_t uncrossable_lines = {{4.0, 5.0}, {5.0, 2.0}, {6.0, 4.5}}; - const auto limit_distance = - calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); - EXPECT_NEAR(limit_distance, 2.0, 1e-9); - } - { // line completely inside the expansion polygon - const linestring_t uncrossable_lines = {{4.0, 2.0}, {6.0, 3.0}}; - const auto limit_distance = - calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); - EXPECT_NEAR(limit_distance, 2.0, 1e-9); - } - { // line completely inside the expansion polygon - const linestring_t uncrossable_lines = {{4.0, 3.5}, {6.0, 3.0}}; - const auto limit_distance = - calculateDistanceLimit(base_ls, expansion_polygon, {uncrossable_lines}); - EXPECT_NEAR(limit_distance, 3.0, 1e-9); - } + drivable_area_expansion::expand_drivable_area( + path, std::make_shared(planner_data)); + // expanded left bound + ASSERT_EQ(path.left_bound.size(), 3ul); + EXPECT_GT(path.left_bound[0].y, 1.0); + EXPECT_GT(path.left_bound[1].y, 1.0); + EXPECT_GT(path.left_bound[2].y, 1.0); + // expanded right bound + ASSERT_EQ(path.right_bound.size(), 3ul); + EXPECT_LT(path.right_bound[0].y, -1.0); + EXPECT_LT(path.right_bound[1].y, -1.0); + EXPECT_LT(path.right_bound[2].y, -1.0); } From 78d4d8247fda3192f8b7032286b56c81fce74c4e Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 16 Oct 2023 17:59:36 +0900 Subject: [PATCH 476/547] fix(lane_change): fix filtering objects (#5317) Signed-off-by: kosuke55 --- .../scene_module/lane_change/normal.hpp | 3 ++- .../src/scene_module/lane_change/normal.cpp | 22 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index ccadcd7144a9b..8400cf8c40afd 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -151,7 +151,8 @@ class NormalLaneChange : public LaneChangeBase CollisionCheckDebugMap & debug_data) const; LaneChangeTargetObjectIndices filterObject( - const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, + const PredictedObjects & objects, const lanelet::ConstLanelets & current_lanes, + const lanelet::ConstLanelets & target_lanes, const lanelet::ConstLanelets & target_backward_lanes) const; //! @brief Check if the ego vehicle is in stuck by a stationary obstacle. diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 12b6d53a83220..1012c228e8607 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -732,7 +732,9 @@ LaneChangeTargetObjects NormalLaneChange::getTargetObjects( const auto current_pose = getEgoPose(); const auto & route_handler = *getRouteHandler(); const auto & common_parameters = planner_data_->parameters; - const auto & objects = *planner_data_->dynamic_object; + auto objects = *planner_data_->dynamic_object; + utils::path_safety_checker::filterObjectsByClass( + objects, lane_change_parameters_->object_types_to_check); // get backward lanes const auto backward_length = lane_change_parameters_->backward_lane_length; @@ -740,7 +742,8 @@ LaneChangeTargetObjects NormalLaneChange::getTargetObjects( route_handler, target_lanes, current_pose, backward_length); // filter objects to get target object index - const auto target_obj_index = filterObject(current_lanes, target_lanes, target_backward_lanes); + const auto target_obj_index = + filterObject(objects, current_lanes, target_lanes, target_backward_lanes); LaneChangeTargetObjects target_objects; target_objects.current_lane.reserve(target_obj_index.current_lane.size()); @@ -772,13 +775,13 @@ LaneChangeTargetObjects NormalLaneChange::getTargetObjects( } LaneChangeTargetObjectIndices NormalLaneChange::filterObject( - const lanelet::ConstLanelets & current_lanes, const lanelet::ConstLanelets & target_lanes, + const PredictedObjects & objects, const lanelet::ConstLanelets & current_lanes, + const lanelet::ConstLanelets & target_lanes, const lanelet::ConstLanelets & target_backward_lanes) const { const auto current_pose = getEgoPose(); const auto & route_handler = *getRouteHandler(); const auto & common_parameters = planner_data_->parameters; - const auto & objects = *planner_data_->dynamic_object; // Guard if (objects.objects.empty()) { @@ -818,15 +821,10 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( target_backward_polygons.push_back(lane_polygon); } - auto filtered_objects = objects; - - utils::path_safety_checker::filterObjectsByClass( - filtered_objects, lane_change_parameters_->object_types_to_check); - LaneChangeTargetObjectIndices filtered_obj_indices; - for (size_t i = 0; i < filtered_objects.objects.size(); ++i) { - const auto & object = filtered_objects.objects.at(i); - const auto & obj_velocity_norm = std::hypot( + for (size_t i = 0; i < objects.objects.size(); ++i) { + const auto & object = objects.objects.at(i); + const auto obj_velocity_norm = std::hypot( object.kinematics.initial_twist_with_covariance.twist.linear.x, object.kinematics.initial_twist_with_covariance.twist.linear.y); const auto extended_object = From dad4722a309cca23304bf449cd7a266db942efb9 Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Mon, 16 Oct 2023 18:11:07 +0900 Subject: [PATCH 477/547] fix(lane_change): do not use reference values of polygon outer (#5318) * fix(lane_change): do not use refelence values of polygon outer Signed-off-by: Fumiya Watanabe * fix(lane_change): fix in lane change Signed-off-by: Fumiya Watanabe --------- Signed-off-by: Fumiya Watanabe --- .../src/scene_module/lane_change/normal.cpp | 3 ++- .../src/utils/path_safety_checker/safety_check.cpp | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 1012c228e8607..769b0d681d2ac 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -835,7 +835,8 @@ LaneChangeTargetObjectIndices NormalLaneChange::filterObject( // calc distance from the current ego position double max_dist_ego_to_obj = std::numeric_limits::lowest(); double min_dist_ego_to_obj = std::numeric_limits::max(); - for (const auto & polygon_p : obj_polygon.outer()) { + const auto obj_polygon_outer = obj_polygon.outer(); + for (const auto & polygon_p : obj_polygon_outer) { const auto obj_p = tier4_autoware_utils::createPoint(polygon_p.x(), polygon_p.y(), 0.0); const double dist_ego_to_obj = calcSignedArcLength(path.points, current_pose.position, obj_p); max_dist_ego_to_obj = std::max(dist_ego_to_obj, max_dist_ego_to_obj); diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index e4698e98d46eb..e36115c62e148 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -51,7 +51,8 @@ bool isTargetObjectFront( tier4_autoware_utils::calcOffsetPose(ego_pose, base_to_front, 0.0, 0.0); // check all edges in the polygon - for (const auto & obj_edge : obj_polygon.outer()) { + const auto obj_polygon_outer = obj_polygon.outer(); + for (const auto & obj_edge : obj_polygon_outer) { const auto obj_point = tier4_autoware_utils::createPoint(obj_edge.x(), obj_edge.y(), 0.0); if (tier4_autoware_utils::calcLongitudinalDeviation(ego_offset_pose, obj_point) > 0.0) { return true; @@ -70,7 +71,8 @@ bool isTargetObjectFront( tier4_autoware_utils::calcOffsetPose(ego_pose, base_to_front, 0.0, 0.0).position; // check all edges in the polygon - for (const auto & obj_edge : obj_polygon.outer()) { + const auto obj_polygon_outer = obj_polygon.outer(); + for (const auto & obj_edge : obj_polygon_outer) { const auto obj_point = tier4_autoware_utils::createPoint(obj_edge.x(), obj_edge.y(), 0.0); if (motion_utils::isTargetPointFront(path.points, ego_point, obj_point)) { return true; @@ -134,7 +136,8 @@ Polygon2d createExtendedPolygon( double min_x = std::numeric_limits::max(); double max_y = std::numeric_limits::lowest(); double min_y = std::numeric_limits::max(); - for (const auto & polygon_p : obj_polygon.outer()) { + const auto obj_polygon_outer = obj_polygon.outer(); + for (const auto & polygon_p : obj_polygon_outer) { const auto obj_p = tier4_autoware_utils::createPoint(polygon_p.x(), polygon_p.y(), 0.0); const auto transformed_p = tier4_autoware_utils::inverseTransformPoint(obj_p, obj_pose); From fb35f6216ae57ec72ff30f173198d2d70ba99f31 Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Tue, 17 Oct 2023 00:13:17 +0900 Subject: [PATCH 478/547] feat(tier4_perception_launch): add radar far object integration in tracking stage (#5269) * update tracking/perception launch Signed-off-by: yoshiri * switch tracker launcher mode with argument Signed-off-by: yoshiri * update prediction to switch by radar_long_range_integration paramter Signed-off-by: yoshiri * make radar far object integration switchable between detection/tracking Signed-off-by: yoshiri * fix camera lidar radar fusion flow when 'tracking' is used. Signed-off-by: yoshiri * fix spelling and appearance Signed-off-by: yoshiri * reconstruct topic flow when use tracking to merge far object detection and near object detection Signed-off-by: yoshiri * fix input topic miss in tracking.launch Signed-off-by: yoshiri * fix comment in camera_lidar_radar fusion Signed-off-by: yoshiri * refactor: rename and remove paramters in prediction.launch Signed-off-by: yoshiri * refactor: rename merger control variable from string to bool Signed-off-by: yoshiri --------- Signed-off-by: yoshiri --- ...ar_radar_fusion_based_detection.launch.xml | 31 ++++++----- .../prediction/prediction.launch.xml | 3 +- .../tracking/tracking.launch.xml | 53 ++++++++++++++----- .../launch/perception.launch.xml | 15 ++++++ 4 files changed, 75 insertions(+), 27 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index fbe5f21c041b6..7d0988635fb10 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -51,6 +51,10 @@ + + + + - - - - - - - - - + + + + + + + + + + + +
diff --git a/launch/tier4_perception_launch/launch/object_recognition/prediction/prediction.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/prediction/prediction.launch.xml index 711e6374f5786..fa343f49840ad 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/prediction/prediction.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/prediction/prediction.launch.xml @@ -1,12 +1,13 @@ - + + diff --git a/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml index 77de1e5995ee0..07b53fb671732 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/tracking/tracking.launch.xml @@ -3,29 +3,54 @@ + + + + + + + + - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index bbb7ebb2d8d25..63ea0cc7f0f3d 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -96,6 +96,13 @@ + + + @@ -181,6 +188,14 @@ + + + + + From 738c3763c28c16f537c0f0305f9cc66531b710ea Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 17 Oct 2023 09:57:42 +0900 Subject: [PATCH 479/547] fix(lane_change): filter out objects out of lane to fix stopping margin chattering (#5321) * fix(lane_change): filter out objects out of lane to fix stopping margin chattering Signed-off-by: kosuke55 * use boost intersects Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../src/scene_module/lane_change/normal.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 769b0d681d2ac..3a2903cb01e9e 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -282,6 +282,16 @@ void NormalLaneChange::insertStopPoint( if (v > lane_change_parameters_->stop_velocity_threshold) { return false; } + + // target_objects includes objects out of target lanes, so filter them out + if (!boost::geometry::intersects( + tier4_autoware_utils::toPolygon2d(o.initial_pose.pose, o.shape).outer(), + lanelet::utils::combineLaneletsShape(status_.target_lanes) + .polygon2d() + .basicPolygon())) { + return false; + } + const double distance_to_target_lane_obj = getDistanceAlongLanelet(o.initial_pose.pose); return stopping_distance_for_obj < distance_to_target_lane_obj && distance_to_target_lane_obj < distance_to_ego_lane_obj; From 99cb32208cd079989f91f69d245d8099c8309eb9 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:06:21 +0900 Subject: [PATCH 480/547] feat(avoidance): add another envelope polygon update logic (#5323) feat(avoidance): add other envelope polygon update logic Signed-off-by: satoshi-ota --- .../utils/avoidance/utils.hpp | 3 ++ .../src/utils/avoidance/utils.cpp | 34 +++++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index d012fd5f612fb..f14e523d0619b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -80,6 +80,9 @@ void setStartData( AvoidLine & al, const double start_shift_length, const geometry_msgs::msg::Pose & start, const size_t start_idx, const double start_dist); +Polygon2d createEnvelopePolygon( + const Polygon2d & object_polygon, const Pose & closest_pose, const double envelope_buffer); + Polygon2d createEnvelopePolygon( const ObjectData & object_data, const Pose & closest_pose, const double envelope_buffer); diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index ed2e42bf0c5ff..232ab5769f0c7 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -475,15 +475,14 @@ void setStartData( } Polygon2d createEnvelopePolygon( - const ObjectData & object_data, const Pose & closest_pose, const double envelope_buffer) + const Polygon2d & object_polygon, const Pose & closest_pose, const double envelope_buffer) { namespace bg = boost::geometry; + using tier4_autoware_utils::expandPolygon; using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; using Box = bg::model::box; - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object_data.object); - const auto toPolygon2d = [](const geometry_msgs::msg::Polygon & polygon) { Polygon2d ret{}; @@ -518,11 +517,17 @@ Polygon2d createEnvelopePolygon( tf2::doTransform( toMsg(envelope_poly, closest_pose.position.z), envelope_ros_polygon, geometry_tf); - const auto expanded_polygon = - tier4_autoware_utils::expandPolygon(toPolygon2d(envelope_ros_polygon), envelope_buffer); + const auto expanded_polygon = expandPolygon(toPolygon2d(envelope_ros_polygon), envelope_buffer); return expanded_polygon; } +Polygon2d createEnvelopePolygon( + const ObjectData & object_data, const Pose & closest_pose, const double envelope_buffer) +{ + const auto object_polygon = tier4_autoware_utils::toPolygon2d(object_data.object); + return createEnvelopePolygon(object_polygon, closest_pose, envelope_buffer); +} + std::vector generateObstaclePolygonsForDrivableArea( const ObjectDataArray & objects, const std::shared_ptr & parameters, const double vehicle_width) @@ -658,8 +663,6 @@ void fillObjectEnvelopePolygon( ObjectData & object_data, const ObjectDataArray & registered_objects, const Pose & closest_pose, const std::shared_ptr & parameters) { - using boost::geometry::within; - const auto object_type = utils::getHighestProbLabel(object_data.object.classification); const auto object_parameter = parameters->object_parameters.at(object_type); @@ -677,15 +680,26 @@ void fillObjectEnvelopePolygon( return; } - const auto object_polygon = tier4_autoware_utils::toPolygon2d(object_data.object); + const auto envelope_poly = + createEnvelopePolygon(object_data, closest_pose, envelope_buffer_margin); + + if (boost::geometry::within(envelope_poly, same_id_obj->envelope_poly)) { + object_data.envelope_poly = same_id_obj->envelope_poly; + return; + } - if (!within(object_polygon, same_id_obj->envelope_poly)) { + std::vector unions; + boost::geometry::union_(envelope_poly, same_id_obj->envelope_poly, unions); + + if (unions.empty()) { object_data.envelope_poly = createEnvelopePolygon(object_data, closest_pose, envelope_buffer_margin); return; } - object_data.envelope_poly = same_id_obj->envelope_poly; + boost::geometry::correct(unions.front()); + + object_data.envelope_poly = createEnvelopePolygon(unions.front(), closest_pose, 0.0); } void fillObjectMovingTime( From 3f2230f0c734c15f148e3b68a349ca0325be09ee Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:12:39 +0900 Subject: [PATCH 481/547] fix(tier4_simulator_launch): add lacked param path (#5326) Signed-off-by: satoshi-ota --- .../launch/simulator.launch.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/launch/tier4_simulator_launch/launch/simulator.launch.xml b/launch/tier4_simulator_launch/launch/simulator.launch.xml index aef1e45f517b1..4da6720b0af47 100644 --- a/launch/tier4_simulator_launch/launch/simulator.launch.xml +++ b/launch/tier4_simulator_launch/launch/simulator.launch.xml @@ -65,7 +65,16 @@ - + + + + + + + From 68252008c60103d36b9f1e9d6ee36782968767bc Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:19:56 +0900 Subject: [PATCH 482/547] fix(drivable_area_expansion): correct bound limit check (#5325) Signed-off-by: Maxime CLEMENT --- .../drivable_area_expansion.cpp | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index 76d1f280651bf..44dddb57e726e 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -126,30 +126,19 @@ std::vector calculate_minimum_expansions( minimum_expansions[bound_idx] = std::max(minimum_expansions[bound_idx], dist); minimum_expansions[bound_idx + 1] = std::max(minimum_expansions[bound_idx + 1], dist); // apply the expansion to all bound points within the extra arc length - if (bound_idx + 2 < bound.size()) { - auto up_arc_length = - tier4_autoware_utils::calcDistance2d(*intersection_point, bound[bound_idx + 1]) + - tier4_autoware_utils::calcDistance2d(bound[bound_idx + 1], bound[bound_idx + 2]); - for (auto up_bound_idx = bound_idx + 2; - bound_idx < bound.size() && up_arc_length <= params.extra_arc_length; - ++up_bound_idx) { - minimum_expansions[up_bound_idx] = std::max(minimum_expansions[up_bound_idx], dist); - if (up_bound_idx + 1 < bound.size()) - up_arc_length += - tier4_autoware_utils::calcDistance2d(bound[up_bound_idx], bound[up_bound_idx + 1]); - } + auto arc_length = + tier4_autoware_utils::calcDistance2d(*intersection_point, bound[bound_idx + 1]); + for (auto up_bound_idx = bound_idx + 2; up_bound_idx < bound.size(); ++up_bound_idx) { + tier4_autoware_utils::calcDistance2d(bound[bound_idx - 1], bound[bound_idx]); + if (arc_length > params.extra_arc_length) break; + minimum_expansions[up_bound_idx] = std::max(minimum_expansions[up_bound_idx], dist); } - if (bound_idx > 0) { - auto down_arc_length = - tier4_autoware_utils::calcDistance2d(*intersection_point, bound[bound_idx]) + - tier4_autoware_utils::calcDistance2d(bound[bound_idx - 1], bound[bound_idx]); - for (auto down_bound_idx = bound_idx - 1; - down_bound_idx > 0 && down_arc_length <= params.extra_arc_length; --down_bound_idx) { - minimum_expansions[down_bound_idx] = std::max(minimum_expansions[down_bound_idx], dist); - if (down_bound_idx > 1) - down_arc_length += tier4_autoware_utils::calcDistance2d( - bound[down_bound_idx], bound[down_bound_idx - 1]); - } + arc_length = tier4_autoware_utils::calcDistance2d(*intersection_point, bound[bound_idx]); + for (auto down_offset_idx = 1LU; down_offset_idx < bound_idx; ++down_offset_idx) { + const auto idx = bound_idx - down_offset_idx; + arc_length += tier4_autoware_utils::calcDistance2d(bound[idx - 1], bound[idx]); + if (arc_length > params.extra_arc_length) break; + minimum_expansions[idx] = std::max(minimum_expansions[idx], dist); } break; } From 3a1d48076ed7e42fa9e6a47df285979e372f761a Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Tue, 17 Oct 2023 13:36:33 +0900 Subject: [PATCH 483/547] feat(imu_corrector): changed GyroBiasEstimator to use ndt pose instead of twist (#5259) * Implemented ndt pose based GyroBiasEstimator Signed-off-by: Shintaro Sakoda * style(pre-commit): autofix * Added missing includes Signed-off-by: Shintaro Sakoda * FIxed default gyro_bias_threshold Signed-off-by: Shintaro Sakoda * Restored gyro_bias_pub_, which had been deleted due to a mistake Signed-off-by: Shintaro Sakoda * removed get_bias_std and anything else that is no longer needed as a result of remove Signed-off-by: Shintaro Sakoda * Updated README.md Signed-off-by: Shintaro Sakoda * Revert "Updated README.md" This reverts commit f669c20d54eeb471f69edc1e54fabe14ba8a9e16. * Updated README.md Signed-off-by: Shintaro Sakoda * Added notes to README.md Signed-off-by: Shintaro Sakoda --------- Signed-off-by: Shintaro Sakoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sensing/imu_corrector/README.md | 31 ++-- .../config/gyro_bias_estimator.param.yaml | 5 +- .../launch/gyro_bias_estimator.launch.xml | 4 +- .../src/gyro_bias_estimation_module.cpp | 124 ++++++++++----- .../src/gyro_bias_estimation_module.hpp | 25 ++- .../imu_corrector/src/gyro_bias_estimator.cpp | 142 +++++++++++++++--- .../imu_corrector/src/gyro_bias_estimator.hpp | 28 +++- .../test/test_gyro_bias_estimation_module.cpp | 76 ++++++---- 8 files changed, 310 insertions(+), 125 deletions(-) diff --git a/sensing/imu_corrector/README.md b/sensing/imu_corrector/README.md index b47ae1500f43b..7af82c1129aff 100644 --- a/sensing/imu_corrector/README.md +++ b/sensing/imu_corrector/README.md @@ -51,10 +51,16 @@ Note that the node calculates bias from the gyroscope data by averaging the data ### Input -| Name | Type | Description | -| ----------------- | ------------------------------------------------ | ---------------- | -| `~/input/imu_raw` | `sensor_msgs::msg::Imu` | **raw** imu data | -| `~/input/twist` | `geometry_msgs::msg::TwistWithCovarianceStamped` | vehicle velocity | +| Name | Type | Description | +| ----------------- | ----------------------------------------------- | ---------------- | +| `~/input/imu_raw` | `sensor_msgs::msg::Imu` | **raw** imu data | +| `~/input/pose` | `geometry_msgs::msg::PoseWithCovarianceStamped` | ndt pose | + +Note that the input pose is assumed to be accurate enough. For example when using NDT, we assume that the NDT is appropriately converged. + +Currently, it is possible to use methods other than NDT as a `pose_source` for Autoware, but less accurate methods are not suitable for IMU bias estimation. + +In the future, with careful implementation for pose errors, the IMU bias estimated by NDT could potentially be used not only for validation but also for online calibration. ### Output @@ -64,12 +70,11 @@ Note that the node calculates bias from the gyroscope data by averaging the data ### Parameters -| Name | Type | Description | -| --------------------------- | ------ | ----------------------------------------------------------------------------- | -| `angular_velocity_offset_x` | double | roll rate offset in imu_link [rad/s] | -| `angular_velocity_offset_y` | double | pitch rate offset imu_link [rad/s] | -| `angular_velocity_offset_z` | double | yaw rate offset imu_link [rad/s] | -| `gyro_bias_threshold` | double | threshold of the bias of the gyroscope [rad/s] | -| `velocity_threshold` | double | threshold of the vehicle velocity to determine if the vehicle is stopped[m/s] | -| `timestamp_threshold` | double | threshold of the timestamp diff between IMU and twist [s] | -| `data_num_threshold` | int | number of data used to calculate bias | +| Name | Type | Description | +| ------------------------------------- | ------ | ------------------------------------------------------------------------------------------- | +| `angular_velocity_offset_x` | double | roll rate offset in imu_link [rad/s] | +| `angular_velocity_offset_y` | double | pitch rate offset imu_link [rad/s] | +| `angular_velocity_offset_z` | double | yaw rate offset imu_link [rad/s] | +| `gyro_bias_threshold` | double | threshold of the bias of the gyroscope [rad/s] | +| `timer_callback_interval_sec` | double | seconds about the timer callback function [sec] | +| `straight_motion_ang_vel_upper_limit` | double | upper limit of yaw angular velocity, beyond which motion is not considered straight [rad/s] | diff --git a/sensing/imu_corrector/config/gyro_bias_estimator.param.yaml b/sensing/imu_corrector/config/gyro_bias_estimator.param.yaml index d5868e1df416a..e10329c920137 100644 --- a/sensing/imu_corrector/config/gyro_bias_estimator.param.yaml +++ b/sensing/imu_corrector/config/gyro_bias_estimator.param.yaml @@ -1,6 +1,5 @@ /**: ros__parameters: gyro_bias_threshold: 0.0015 # [rad/s] - velocity_threshold: 0.0 # [m/s] - timestamp_threshold: 0.1 # [s] - data_num_threshold: 200 # [num] + timer_callback_interval_sec: 0.5 # [sec] + straight_motion_ang_vel_upper_limit: 0.015 # [rad/s] diff --git a/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml b/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml index a25ce5f90ed27..0d7cba9faa3a6 100644 --- a/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml +++ b/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml @@ -1,14 +1,14 @@ - + - + diff --git a/sensing/imu_corrector/src/gyro_bias_estimation_module.cpp b/sensing/imu_corrector/src/gyro_bias_estimation_module.cpp index 2deb6088f6542..9b5719de69524 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimation_module.cpp +++ b/sensing/imu_corrector/src/gyro_bias_estimation_module.cpp @@ -14,58 +14,106 @@ #include "gyro_bias_estimation_module.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" + +#include + namespace imu_corrector { -GyroBiasEstimationModule::GyroBiasEstimationModule( - const double velocity_threshold, const double timestamp_threshold, - const size_t data_num_threshold) -: velocity_threshold_(velocity_threshold), - timestamp_threshold_(timestamp_threshold), - data_num_threshold_(data_num_threshold), - is_stopped_(false) -{ -} -void GyroBiasEstimationModule::update_gyro( - const double time, const geometry_msgs::msg::Vector3 & gyro) +/** + * @brief perform dead reckoning based on "gyro_list" and return a relative pose (in RPY) + */ +geometry_msgs::msg::Vector3 integrate_orientation( + const std::vector & gyro_list, + const geometry_msgs::msg::Vector3 & gyro_bias) { - if (time - last_velocity_time_ > timestamp_threshold_) { - return; - } - if (!is_stopped_) { - return; - } - gyro_buffer_.push_back(gyro); - if (gyro_buffer_.size() > data_num_threshold_) { - gyro_buffer_.pop_front(); + geometry_msgs::msg::Vector3 d_rpy{}; + double t_prev = rclcpp::Time(gyro_list.front().header.stamp).seconds(); + for (std::size_t i = 0; i < gyro_list.size() - 1; ++i) { + double t_cur = rclcpp::Time(gyro_list[i + 1].header.stamp).seconds(); + + d_rpy.x += (t_cur - t_prev) * (gyro_list[i].vector.x - gyro_bias.x); + d_rpy.y += (t_cur - t_prev) * (gyro_list[i].vector.y - gyro_bias.y); + d_rpy.z += (t_cur - t_prev) * (gyro_list[i].vector.z - gyro_bias.z); + + t_prev = t_cur; } + return d_rpy; } -void GyroBiasEstimationModule::update_velocity(const double time, const double velocity) +/** + * @brief calculate RPY error on dead-reckoning (calculated from "gyro_list") compared to the + * ground-truth pose from "pose_list". + */ +geometry_msgs::msg::Vector3 calculate_error_rpy( + const std::vector & pose_list, + const std::vector & gyro_list, + const geometry_msgs::msg::Vector3 & gyro_bias) { - is_stopped_ = velocity <= velocity_threshold_; - last_velocity_time_ = time; + const geometry_msgs::msg::Vector3 rpy_0 = + tier4_autoware_utils::getRPY(pose_list.front().pose.orientation); + const geometry_msgs::msg::Vector3 rpy_1 = + tier4_autoware_utils::getRPY(pose_list.back().pose.orientation); + const geometry_msgs::msg::Vector3 d_rpy = integrate_orientation(gyro_list, gyro_bias); + + geometry_msgs::msg::Vector3 error_rpy; + error_rpy.x = tier4_autoware_utils::normalizeRadian(-rpy_1.x + rpy_0.x + d_rpy.x); + error_rpy.y = tier4_autoware_utils::normalizeRadian(-rpy_1.y + rpy_0.y + d_rpy.y); + error_rpy.z = tier4_autoware_utils::normalizeRadian(-rpy_1.z + rpy_0.z + d_rpy.z); + return error_rpy; } -geometry_msgs::msg::Vector3 GyroBiasEstimationModule::get_bias() const +/** + * @brief update gyroscope bias based on a given trajectory data + */ +void GyroBiasEstimationModule::update_bias( + const std::vector & pose_list, + const std::vector & gyro_list) { - if (gyro_buffer_.size() < data_num_threshold_) { - throw std::runtime_error("Bias estimation is not yet ready because of insufficient data."); + const double dt_pose = + (rclcpp::Time(pose_list.back().header.stamp) - rclcpp::Time(pose_list.front().header.stamp)) + .seconds(); + const double dt_gyro = + (rclcpp::Time(gyro_list.back().header.stamp) - rclcpp::Time(gyro_list.front().header.stamp)) + .seconds(); + if (dt_pose == 0 || dt_gyro == 0) { + throw std::runtime_error("dt_pose or dt_gyro is zero"); } - geometry_msgs::msg::Vector3 bias; - bias.x = 0.0; - bias.y = 0.0; - bias.z = 0.0; - for (const auto & gyro : gyro_buffer_) { - bias.x += gyro.x; - bias.y += gyro.y; - bias.z += gyro.z; + auto error_rpy = calculate_error_rpy(pose_list, gyro_list, geometry_msgs::msg::Vector3{}); + error_rpy.x *= dt_pose / dt_gyro; + error_rpy.y *= dt_pose / dt_gyro; + error_rpy.z *= dt_pose / dt_gyro; + + gyro_bias_pair_.first.x += dt_pose * error_rpy.x; + gyro_bias_pair_.first.y += dt_pose * error_rpy.y; + gyro_bias_pair_.first.z += dt_pose * error_rpy.z; + gyro_bias_pair_.second.x += dt_pose * dt_pose; + gyro_bias_pair_.second.y += dt_pose * dt_pose; + gyro_bias_pair_.second.z += dt_pose * dt_pose; + + geometry_msgs::msg::Vector3 gyro_bias; + gyro_bias.x = error_rpy.x / dt_pose; + gyro_bias.y = error_rpy.y / dt_pose; + gyro_bias.z = error_rpy.z / dt_pose; +} + +/** + * @brief getter function for current estimated bias + */ +geometry_msgs::msg::Vector3 GyroBiasEstimationModule::get_bias_base_link() const +{ + geometry_msgs::msg::Vector3 gyro_bias_base; + if ( + gyro_bias_pair_.second.x == 0 || gyro_bias_pair_.second.y == 0 || + gyro_bias_pair_.second.z == 0) { + throw std::runtime_error("gyro_bias_pair_.second is zero"); } - bias.x /= gyro_buffer_.size(); - bias.y /= gyro_buffer_.size(); - bias.z /= gyro_buffer_.size(); - return bias; + gyro_bias_base.x = gyro_bias_pair_.first.x / gyro_bias_pair_.second.x; + gyro_bias_base.y = gyro_bias_pair_.first.y / gyro_bias_pair_.second.y; + gyro_bias_base.z = gyro_bias_pair_.first.z / gyro_bias_pair_.second.z; + return gyro_bias_base; } } // namespace imu_corrector diff --git a/sensing/imu_corrector/src/gyro_bias_estimation_module.hpp b/sensing/imu_corrector/src/gyro_bias_estimation_module.hpp index 6ebae942fff5d..dfa152d32c0d9 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimation_module.hpp +++ b/sensing/imu_corrector/src/gyro_bias_estimation_module.hpp @@ -11,33 +11,30 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + #ifndef GYRO_BIAS_ESTIMATION_MODULE_HPP_ #define GYRO_BIAS_ESTIMATION_MODULE_HPP_ -#include +#include +#include #include +#include +#include namespace imu_corrector { class GyroBiasEstimationModule { public: - GyroBiasEstimationModule( - const double velocity_threshold, const double timestamp_threshold, - const size_t data_num_threshold); - geometry_msgs::msg::Vector3 get_bias() const; - void update_gyro(const double time, const geometry_msgs::msg::Vector3 & gyro); - void update_velocity(const double time, const double velocity); + GyroBiasEstimationModule() = default; + void update_bias( + const std::vector & pose_list, + const std::vector & gyro_list); + geometry_msgs::msg::Vector3 get_bias_base_link() const; private: - const double velocity_threshold_; - const double timestamp_threshold_; - const size_t data_num_threshold_; - bool is_stopped_; - std::deque gyro_buffer_; - - double last_velocity_time_; + std::pair gyro_bias_pair_; }; } // namespace imu_corrector diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.cpp b/sensing/imu_corrector/src/gyro_bias_estimator.cpp index 5f41a9089b6ee..21bb51dc5f1f1 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimator.cpp +++ b/sensing/imu_corrector/src/gyro_bias_estimator.cpp @@ -14,6 +14,13 @@ #include "gyro_bias_estimator.hpp" +#include "tier4_autoware_utils/geometry/geometry.hpp" + +#include +#include + +#include + namespace imu_corrector { GyroBiasEstimator::GyroBiasEstimator() @@ -22,59 +29,146 @@ GyroBiasEstimator::GyroBiasEstimator() angular_velocity_offset_x_(declare_parameter("angular_velocity_offset_x")), angular_velocity_offset_y_(declare_parameter("angular_velocity_offset_y")), angular_velocity_offset_z_(declare_parameter("angular_velocity_offset_z")), + timer_callback_interval_sec_(declare_parameter("timer_callback_interval_sec")), + straight_motion_ang_vel_upper_limit_( + declare_parameter("straight_motion_ang_vel_upper_limit")), updater_(this), gyro_bias_(std::nullopt) { updater_.setHardwareID(get_name()); updater_.add("gyro_bias_validator", this, &GyroBiasEstimator::update_diagnostics); - const double velocity_threshold = declare_parameter("velocity_threshold"); - const double timestamp_threshold = declare_parameter("timestamp_threshold"); - const size_t data_num_threshold = - static_cast(declare_parameter("data_num_threshold")); - gyro_bias_estimation_module_ = std::make_unique( - velocity_threshold, timestamp_threshold, data_num_threshold); + gyro_bias_estimation_module_ = std::make_unique(); imu_sub_ = create_subscription( "~/input/imu_raw", rclcpp::SensorDataQoS(), [this](const Imu::ConstSharedPtr msg) { callback_imu(msg); }); - twist_sub_ = create_subscription( - "~/input/twist", rclcpp::SensorDataQoS(), - [this](const TwistWithCovarianceStamped::ConstSharedPtr msg) { callback_twist(msg); }); - + pose_sub_ = create_subscription( + "~/input/pose", rclcpp::SensorDataQoS(), + [this](const PoseWithCovarianceStamped::ConstSharedPtr msg) { callback_pose(msg); }); gyro_bias_pub_ = create_publisher("~/output/gyro_bias", rclcpp::SensorDataQoS()); + + auto timer_callback = std::bind(&GyroBiasEstimator::timer_callback, this); + auto period_control = std::chrono::duration_cast( + std::chrono::duration(timer_callback_interval_sec_)); + timer_ = std::make_shared>( + this->get_clock(), period_control, std::move(timer_callback), + this->get_node_base_interface()->get_context()); + this->get_node_timers_interface()->add_timer(timer_, nullptr); + + transform_listener_ = std::make_shared(this); } void GyroBiasEstimator::callback_imu(const Imu::ConstSharedPtr imu_msg_ptr) { - // Update gyro data - gyro_bias_estimation_module_->update_gyro( - rclcpp::Time(imu_msg_ptr->header.stamp).seconds(), imu_msg_ptr->angular_velocity); - - // Estimate gyro bias - try { - gyro_bias_ = gyro_bias_estimation_module_->get_bias(); - } catch (const std::runtime_error & e) { - RCLCPP_WARN_STREAM_THROTTLE(this->get_logger(), *(this->get_clock()), 1000, e.what()); + imu_frame_ = imu_msg_ptr->header.frame_id; + geometry_msgs::msg::TransformStamped::ConstSharedPtr tf_imu2base_ptr = + transform_listener_->getLatestTransform(imu_frame_, output_frame_); + if (!tf_imu2base_ptr) { + RCLCPP_ERROR( + this->get_logger(), "Please publish TF %s to %s", output_frame_.c_str(), + (imu_frame_).c_str()); + return; } + geometry_msgs::msg::Vector3Stamped gyro; + gyro.header.stamp = imu_msg_ptr->header.stamp; + gyro.vector = transform_vector3(imu_msg_ptr->angular_velocity, *tf_imu2base_ptr); + + gyro_all_.push_back(gyro); + // Publish results for debugging if (gyro_bias_ != std::nullopt) { Vector3Stamped gyro_bias_msg; + gyro_bias_msg.header.stamp = this->now(); gyro_bias_msg.vector = gyro_bias_.value(); + gyro_bias_pub_->publish(gyro_bias_msg); } +} + +void GyroBiasEstimator::callback_pose(const PoseWithCovarianceStamped::ConstSharedPtr pose_msg_ptr) +{ + // push pose_msg to queue + geometry_msgs::msg::PoseStamped pose; + pose.header = pose_msg_ptr->header; + pose.pose = pose_msg_ptr->pose.pose; + pose_buf_.push_back(pose); +} + +void GyroBiasEstimator::timer_callback() +{ + if (pose_buf_.empty()) { + return; + } + + // Copy data + const std::vector pose_buf = pose_buf_; + const std::vector gyro_all = gyro_all_; + pose_buf_.clear(); + gyro_all_.clear(); + + // Check time + const rclcpp::Time t0_rclcpp_time = rclcpp::Time(pose_buf.front().header.stamp); + const rclcpp::Time t1_rclcpp_time = rclcpp::Time(pose_buf.back().header.stamp); + if (t1_rclcpp_time <= t0_rclcpp_time) { + return; + } + + // Filter gyro data + std::vector gyro_filtered; + for (const auto & gyro : gyro_all) { + const rclcpp::Time t = rclcpp::Time(gyro.header.stamp); + if (t0_rclcpp_time <= t && t < t1_rclcpp_time) { + gyro_filtered.push_back(gyro); + } + } + + // Check gyro data size + // Data size must be greater than or equal to 2 since the time difference will be taken later + if (gyro_filtered.size() <= 1) { + return; + } + + // Check if the vehicle is moving straight + const geometry_msgs::msg::Vector3 rpy_0 = + tier4_autoware_utils::getRPY(pose_buf.front().pose.orientation); + const geometry_msgs::msg::Vector3 rpy_1 = + tier4_autoware_utils::getRPY(pose_buf.back().pose.orientation); + const double yaw_diff = std::abs(tier4_autoware_utils::normalizeRadian(rpy_1.z - rpy_0.z)); + const double time_diff = (t1_rclcpp_time - t0_rclcpp_time).seconds(); + const double yaw_vel = yaw_diff / time_diff; + const bool is_straight = (yaw_vel < straight_motion_ang_vel_upper_limit_); + if (!is_straight) { + return; + } + + // Calculate gyro bias + gyro_bias_estimation_module_->update_bias(pose_buf, gyro_filtered); + + geometry_msgs::msg::TransformStamped::ConstSharedPtr tf_base2imu_ptr = + transform_listener_->getLatestTransform(output_frame_, imu_frame_); + if (!tf_base2imu_ptr) { + RCLCPP_ERROR( + this->get_logger(), "Please publish TF %s to %s", imu_frame_.c_str(), output_frame_.c_str()); + return; + } + gyro_bias_ = + transform_vector3(gyro_bias_estimation_module_->get_bias_base_link(), *tf_base2imu_ptr); - // Update diagnostics updater_.force_update(); } -void GyroBiasEstimator::callback_twist( - const TwistWithCovarianceStamped::ConstSharedPtr twist_msg_ptr) +geometry_msgs::msg::Vector3 GyroBiasEstimator::transform_vector3( + const geometry_msgs::msg::Vector3 & vec, const geometry_msgs::msg::TransformStamped & transform) { - gyro_bias_estimation_module_->update_velocity( - rclcpp::Time(twist_msg_ptr->header.stamp).seconds(), twist_msg_ptr->twist.twist.linear.x); + geometry_msgs::msg::Vector3Stamped vec_stamped; + vec_stamped.vector = vec; + + geometry_msgs::msg::Vector3Stamped vec_stamped_transformed; + tf2::doTransform(vec_stamped, vec_stamped_transformed, transform); + return vec_stamped_transformed.vector; } void GyroBiasEstimator::update_diagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat) diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.hpp b/sensing/imu_corrector/src/gyro_bias_estimator.hpp index e74a0390af3aa..7eb06bcdcb365 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimator.hpp +++ b/sensing/imu_corrector/src/gyro_bias_estimator.hpp @@ -15,17 +15,19 @@ #define GYRO_BIAS_ESTIMATOR_HPP_ #include "gyro_bias_estimation_module.hpp" +#include "tier4_autoware_utils/ros/transform_listener.hpp" #include #include -#include +#include #include #include #include #include #include +#include namespace imu_corrector { @@ -33,7 +35,7 @@ class GyroBiasEstimator : public rclcpp::Node { private: using Imu = sensor_msgs::msg::Imu; - using TwistWithCovarianceStamped = geometry_msgs::msg::TwistWithCovarianceStamped; + using PoseWithCovarianceStamped = geometry_msgs::msg::PoseWithCovarianceStamped; using Vector3Stamped = geometry_msgs::msg::Vector3Stamped; using Vector3 = geometry_msgs::msg::Vector3; @@ -43,12 +45,19 @@ class GyroBiasEstimator : public rclcpp::Node private: void update_diagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat); void callback_imu(const Imu::ConstSharedPtr imu_msg_ptr); - void callback_twist(const TwistWithCovarianceStamped::ConstSharedPtr twist_msg_ptr); + void callback_pose(const PoseWithCovarianceStamped::ConstSharedPtr pose_msg_ptr); + void timer_callback(); - rclcpp::Subscription::SharedPtr imu_sub_; - rclcpp::Subscription::SharedPtr twist_sub_; + static geometry_msgs::msg::Vector3 transform_vector3( + const geometry_msgs::msg::Vector3 & vec, + const geometry_msgs::msg::TransformStamped & transform); + + const std::string output_frame_ = "base_link"; + rclcpp::Subscription::SharedPtr imu_sub_; + rclcpp::Subscription::SharedPtr pose_sub_; rclcpp::Publisher::SharedPtr gyro_bias_pub_; + rclcpp::TimerBase::SharedPtr timer_; std::unique_ptr gyro_bias_estimation_module_; @@ -56,10 +65,19 @@ class GyroBiasEstimator : public rclcpp::Node const double angular_velocity_offset_x_; const double angular_velocity_offset_y_; const double angular_velocity_offset_z_; + const double timer_callback_interval_sec_; + const double straight_motion_ang_vel_upper_limit_; diagnostic_updater::Updater updater_; std::optional gyro_bias_; + + std::shared_ptr transform_listener_; + + std::string imu_frame_; + + std::vector gyro_all_; + std::vector pose_buf_; }; } // namespace imu_corrector diff --git a/sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp b/sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp index a0da4d0e24e17..78558feeb7936 100644 --- a/sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp +++ b/sensing/imu_corrector/test/test_gyro_bias_estimation_module.cpp @@ -14,6 +14,8 @@ #include "../src/gyro_bias_estimation_module.hpp" +#include + #include namespace imu_corrector @@ -21,45 +23,67 @@ namespace imu_corrector class GyroBiasEstimationModuleTest : public ::testing::Test { protected: - double velocity_threshold = 1.0; - double timestamp_threshold = 5.0; - size_t data_num_threshold = 10; - GyroBiasEstimationModule module = - GyroBiasEstimationModule(velocity_threshold, timestamp_threshold, data_num_threshold); + GyroBiasEstimationModule module; }; TEST_F(GyroBiasEstimationModuleTest, GetBiasEstimationWhenVehicleStopped) { - geometry_msgs::msg::Vector3 gyro; - gyro.x = 0.1; - gyro.y = 0.2; - gyro.z = 0.3; - for (size_t i = 0; i < data_num_threshold + 1; ++i) { - module.update_velocity( - i * 0.1 * timestamp_threshold, 0.0); // velocity = 0.0 < 1.0 = velocity_threshold - module.update_gyro(i * 0.1 * timestamp_threshold, gyro); + std::vector pose_list; + std::vector gyro_list; + geometry_msgs::msg::PoseStamped pose1; + pose1.header.stamp = rclcpp::Time(0); + pose1.pose.orientation.w = 1.0; + pose_list.push_back(pose1); + geometry_msgs::msg::PoseStamped pose2; + pose2.header.stamp = rclcpp::Time(1e9); + pose2.pose.orientation.w = 1.0; + pose_list.push_back(pose2); + + geometry_msgs::msg::Vector3Stamped gyro1; + gyro1.header.stamp = rclcpp::Time(0.25 * 1e9); + gyro1.vector.x = 0.1; + gyro1.vector.y = 0.2; + gyro1.vector.z = 0.3; + gyro_list.push_back(gyro1); + geometry_msgs::msg::Vector3Stamped gyro2; + gyro2.header.stamp = rclcpp::Time(0.5 * 1e9); + gyro2.vector.x = 0.1; + gyro2.vector.y = 0.2; + gyro2.vector.z = 0.3; + gyro_list.push_back(gyro2); + + for (size_t i = 0; i < 10; ++i) { + module.update_bias(pose_list, gyro_list); } - ASSERT_NEAR(module.get_bias().x, gyro.x, 0.0001); - ASSERT_NEAR(module.get_bias().y, gyro.y, 0.0001); - ASSERT_NEAR(module.get_bias().z, gyro.z, 0.0001); + const geometry_msgs::msg::Vector3 result = module.get_bias_base_link(); + ASSERT_DOUBLE_EQ(result.x, 0.1); + ASSERT_DOUBLE_EQ(result.y, 0.2); + ASSERT_DOUBLE_EQ(result.z, 0.3); } TEST_F(GyroBiasEstimationModuleTest, GetInsufficientDataException) { - ASSERT_THROW(module.get_bias(), std::runtime_error); + ASSERT_THROW(module.get_bias_base_link(), std::runtime_error); } TEST_F(GyroBiasEstimationModuleTest, GetInsufficientDataExceptionWhenVehicleMoving) { - geometry_msgs::msg::Vector3 gyro; - gyro.x = 0.1; - gyro.y = 0.2; - gyro.z = 0.3; - for (size_t i = 0; i < data_num_threshold + 1; ++i) { - module.update_velocity( - i * 0.1 * timestamp_threshold, 5.0); // velocity = 5.0 > 1.0 = velocity_threshold - module.update_gyro(i * 0.1 * timestamp_threshold, gyro); + std::vector pose_list; + std::vector gyro_list; + geometry_msgs::msg::PoseStamped pose1; + pose1.header.stamp = rclcpp::Time(0); + pose1.pose.orientation.w = 1.0; + pose_list.push_back(pose1); + + geometry_msgs::msg::Vector3Stamped gyro1; + gyro1.header.stamp = rclcpp::Time(0); + gyro1.vector.x = 0.1; + gyro1.vector.y = 0.2; + gyro1.vector.z = 0.3; + gyro_list.push_back(gyro1); + + for (size_t i = 0; i < 10; ++i) { + ASSERT_THROW(module.update_bias(pose_list, gyro_list), std::runtime_error); } - ASSERT_THROW(module.get_bias(), std::runtime_error); } } // namespace imu_corrector From 0c7ef9863fc95918731abee39375fc7779fd4976 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Tue, 17 Oct 2023 15:02:11 +0900 Subject: [PATCH 484/547] feat(ndt_scan_matcher): add initial_to_result_relative_pose (#5319) Added initial_to_result_relative_pose Signed-off-by: Shintaro Sakoda --- localization/ndt_scan_matcher/README.md | 1 + .../ndt_scan_matcher/ndt_scan_matcher_core.hpp | 4 +++- .../ndt_scan_matcher/src/ndt_scan_matcher_core.cpp | 13 +++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/localization/ndt_scan_matcher/README.md b/localization/ndt_scan_matcher/README.md index 1f7779890f89a..e210d3a28796f 100644 --- a/localization/ndt_scan_matcher/README.md +++ b/localization/ndt_scan_matcher/README.md @@ -38,6 +38,7 @@ One optional function is regularization. Please see the regularization chapter i | `transform_probability` | `tier4_debug_msgs::msg::Float32Stamped` | [debug topic] score of scan matching | | `no_ground_transform_probability` | `tier4_debug_msgs::msg::Float32Stamped` | [debug topic] score of scan matching based on de-grounded LiDAR scan | | `iteration_num` | `tier4_debug_msgs::msg::Int32Stamped` | [debug topic] number of scan matching iterations | +| `initial_to_result_relative_pose` | `geometry_msgs::msg::PoseStamped` | [debug topic] relative pose between the initial point and the convergence point | | `initial_to_result_distance` | `tier4_debug_msgs::msg::Float32Stamped` | [debug topic] distance difference between the initial point and the convergence point [m] | | `initial_to_result_distance_old` | `tier4_debug_msgs::msg::Float32Stamped` | [debug topic] distance difference between the older of the two initial points used in linear interpolation and the convergence point [m] | | `initial_to_result_distance_new` | `tier4_debug_msgs::msg::Float32Stamped` | [debug topic] distance difference between the newer of the two initial points used in linear interpolation and the convergence point [m] | diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp index fc4015aede059..a07eb9cd5c8d0 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp @@ -111,7 +111,7 @@ class NDTScanMatcher : public rclcpp::Node const pcl::shared_ptr> & sensor_points_in_map_ptr); void publish_marker( const rclcpp::Time & sensor_ros_time, const std::vector & pose_array); - void publish_initial_to_result_distances( + void publish_initial_to_result( const rclcpp::Time & sensor_ros_time, const geometry_msgs::msg::Pose & result_pose_msg, const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_cov_msg, const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_old_msg, @@ -150,6 +150,8 @@ class NDTScanMatcher : public rclcpp::Node rclcpp::Publisher::SharedPtr no_ground_nearest_voxel_transformation_likelihood_pub_; rclcpp::Publisher::SharedPtr iteration_num_pub_; + rclcpp::Publisher::SharedPtr + initial_to_result_relative_pose_pub_; rclcpp::Publisher::SharedPtr initial_to_result_distance_pub_; rclcpp::Publisher::SharedPtr diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 238e5c6be89bc..25033c8c83c01 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -205,6 +205,8 @@ NDTScanMatcher::NDTScanMatcher() "no_ground_nearest_voxel_transformation_likelihood", 10); iteration_num_pub_ = this->create_publisher("iteration_num", 10); + initial_to_result_relative_pose_pub_ = + this->create_publisher("initial_to_result_relative_pose", 10); initial_to_result_distance_pub_ = this->create_publisher("initial_to_result_distance", 10); initial_to_result_distance_old_pub_ = @@ -487,7 +489,7 @@ void NDTScanMatcher::callback_sensor_points( publish_tf(sensor_ros_time, result_pose_msg); publish_pose(sensor_ros_time, result_pose_msg, is_converged); publish_marker(sensor_ros_time, transformation_msg_array); - publish_initial_to_result_distances( + publish_initial_to_result( sensor_ros_time, result_pose_msg, interpolator.get_current_pose(), interpolator.get_old_pose(), interpolator.get_new_pose()); @@ -628,12 +630,19 @@ void NDTScanMatcher::publish_marker( ndt_marker_pub_->publish(marker_array); } -void NDTScanMatcher::publish_initial_to_result_distances( +void NDTScanMatcher::publish_initial_to_result( const rclcpp::Time & sensor_ros_time, const geometry_msgs::msg::Pose & result_pose_msg, const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_cov_msg, const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_old_msg, const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_new_msg) { + geometry_msgs::msg::PoseStamped initial_to_result_relative_pose_stamped; + initial_to_result_relative_pose_stamped.pose = + tier4_autoware_utils::inverseTransformPose(result_pose_msg, initial_pose_cov_msg.pose.pose); + initial_to_result_relative_pose_stamped.header.stamp = sensor_ros_time; + initial_to_result_relative_pose_stamped.header.frame_id = map_frame_; + initial_to_result_relative_pose_pub_->publish(initial_to_result_relative_pose_stamped); + const auto initial_to_result_distance = static_cast(norm(initial_pose_cov_msg.pose.pose.position, result_pose_msg.position)); initial_to_result_distance_pub_->publish( From d38beae3097fe614806f0b900b7b6417291c5c10 Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Tue, 17 Oct 2023 17:27:32 +0900 Subject: [PATCH 485/547] fix(tracking_object_merger): fix unintended error in radar tracking merger (#5328) * fix: fix tracking merger node Signed-off-by: yoshiri * fix: unintended condition setting Signed-off-by: yoshiri --------- Signed-off-by: yoshiri --- .../camera_lidar_radar_fusion_based_detection.launch.xml | 2 +- .../include/tracking_object_merger/utils/utils.hpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index 7d0988635fb10..07640495a5e19 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -411,7 +411,7 @@ - + diff --git a/perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp b/perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp index 013040d15bded..bc6dfef9b80bf 100644 --- a/perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp +++ b/perception/tracking_object_merger/include/tracking_object_merger/utils/utils.hpp @@ -27,8 +27,12 @@ #include #include #include +#include #include +#include +#include +#include #include #include From 08f6b8de901ca70df1705d420df0b596958e0794 Mon Sep 17 00:00:00 2001 From: Tomoya Kimura Date: Tue, 17 Oct 2023 17:54:34 +0900 Subject: [PATCH 486/547] chore(autonmous_emergency_braking): add maintainer (#5331) Signed-off-by: tomoya.kimura --- control/autonomous_emergency_braking/package.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/control/autonomous_emergency_braking/package.xml b/control/autonomous_emergency_braking/package.xml index 4f2ad6c50a9b3..5dc65cb243bfc 100644 --- a/control/autonomous_emergency_braking/package.xml +++ b/control/autonomous_emergency_braking/package.xml @@ -6,6 +6,8 @@ Autonomous Emergency Braking package as a ROS 2 node Yutaka Shimizu Takamasa Horibe + Tomoya Kimura + Apache License 2.0 ament_cmake From 076319bef0e92f23497b05be1e7f9e8de9bdebef Mon Sep 17 00:00:00 2001 From: "awf-autoware-bot[bot]" <94889083+awf-autoware-bot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:08:14 +0000 Subject: [PATCH 487/547] chore: update CODEOWNERS (#5184) Signed-off-by: GitHub Co-authored-by: github-actions --- .github/CODEOWNERS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f9983f0fccc3f..9de27f82ffcef 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -56,7 +56,7 @@ common/traffic_light_recognition_marker_publisher/** shumpei.wakabayashi@tier4.j common/traffic_light_utils/** mingyu.li@tier4.jp shunsuke.miura@tier4.jp common/trtexec_vendor/** daisuke.nishimatsu@tier4.jp yusuke.muramatsu@tier4.jp common/tvm_utility/** ambroise.vincent@arm.com xinyu.wang@tier4.jp -control/autonomous_emergency_braking/** takamasa.horibe@tier4.jp yutaka.shimizu@tier4.jp +control/autonomous_emergency_braking/** takamasa.horibe@tier4.jp tomoya.kimura@tier4.jp yutaka.shimizu@tier4.jp control/control_performance_analysis/** berkay@leodrive.ai fumiya.watanabe@tier4.jp satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp control/control_validator/** kyoichi.sugahara@tier4.jp makoto.kurihara@tier4.jp takamasa.horibe@tier4.jp control/external_cmd_selector/** fumiya.watanabe@tier4.jp satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp @@ -66,6 +66,7 @@ control/mpc_lateral_controller/** takamasa.horibe@tier4.jp takayuki.murooka@tier control/obstacle_collision_checker/** fumiya.watanabe@tier4.jp satoshi.ota@tier4.jp shumpei.wakabayashi@tier4.jp taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp tomoya.kimura@tier4.jp control/operation_mode_transition_manager/** takamasa.horibe@tier4.jp tomoya.kimura@tier4.jp control/pid_longitudinal_controller/** takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp +control/predicted_path_checker/** berkay@leodrive.ai control/pure_pursuit/** takamasa.horibe@tier4.jp control/shift_decider/** takamasa.horibe@tier4.jp control/trajectory_follower_base/** takamasa.horibe@tier4.jp takayuki.murooka@tier4.jp @@ -140,6 +141,7 @@ perception/simple_object_merger/** satoshi.tanaka@tier4.jp shunsuke.miura@tier4. perception/tensorrt_classifier/** mingyu.li@tier4.jp perception/tensorrt_yolo/** daisuke.nishimatsu@tier4.jp perception/tensorrt_yolox/** daisuke.nishimatsu@tier4.jp dan.umeda@tier4.jp manato.hirabayashi@tier4.jp +perception/tracking_object_merger/** yoshi.ri@tier4.jp yukihiro.saito@tier4.jp perception/traffic_light_arbiter/** kenzo.lobos@tier4.jp shunsuke.miura@tier4.jp perception/traffic_light_classifier/** shunsuke.miura@tier4.jp yukihiro.saito@tier4.jp perception/traffic_light_fine_detector/** shunsuke.miura@tier4.jp tao.zhong@tier4.jp @@ -181,7 +183,6 @@ planning/planning_debug_tools/** taiki.tanaka@tier4.jp takamasa.horibe@tier4.jp planning/planning_test_utils/** kyoichi.sugahara@tier4.jp takamasa.horibe@tier4.jp planning/planning_validator/** kosuke.takeuchi@tier4.jp takamasa.horibe@tier4.jp planning/route_handler/** fumiya.watanabe@tier4.jp kosuke.takeuchi@tier4.jp mamoru.sobue@tier4.jp takayuki.murooka@tier4.jp yutaka.shimizu@tier4.jp zulfaqar.azmi@tier4.jp -planning/rtc_auto_mode_manager/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp planning/rtc_interface/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp planning/rtc_replayer/** fumiya.watanabe@tier4.jp taiki.tanaka@tier4.jp planning/sampling_based_planner/bezier_sampler/** maxime.clement@tier4.jp @@ -215,6 +216,7 @@ system/default_ad_api_helpers/ad_api_visualizers/** isamu.takagi@tier4.jp kahhoo system/default_ad_api_helpers/automatic_pose_initializer/** isamu.takagi@tier4.jp kahhooi.tan@tier4.jp makoto.yabuta@tier4.jp ryohsuke.mitsudome@tier4.jp yukihiro.saito@tier4.jp system/dummy_diag_publisher/** akihiro.sakurai@tier4.jp fumihito.ito@tier4.jp system/dummy_infrastructure/** ryohsuke.mitsudome@tier4.jp +system/duplicated_node_checker/** shumpei.wakabayashi@tier4.jp uken.ryu@tier4.jp system/emergency_handler/** makoto.kurihara@tier4.jp system/mrm_comfortable_stop_operator/** makoto.kurihara@tier4.jp system/mrm_emergency_stop_operator/** makoto.kurihara@tier4.jp From 8453525fcc9dace38b0780d791e890ce2a2461b1 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 17 Oct 2023 18:36:20 +0900 Subject: [PATCH 488/547] fix(AEB): failure due to an empty path (#5329) Signed-off-by: Takamasa Horibe Co-authored-by: Tomoya Kimura --- control/autonomous_emergency_braking/src/node.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/control/autonomous_emergency_braking/src/node.cpp b/control/autonomous_emergency_braking/src/node.cpp index e68dff6b0ec1d..0ccc9bc4a3dae 100644 --- a/control/autonomous_emergency_braking/src/node.cpp +++ b/control/autonomous_emergency_braking/src/node.cpp @@ -432,6 +432,10 @@ void AEB::generateEgoPath( const Trajectory & predicted_traj, Path & path, std::vector & polygons) { + if (predicted_traj.points.empty()) { + return; + } + geometry_msgs::msg::TransformStamped transform_stamped{}; try { transform_stamped = tf_buffer_.lookupTransform( From b71b04e52e62eb23b2affb6fdcf1bab59b1de298 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 17 Oct 2023 19:08:42 +0900 Subject: [PATCH 489/547] feat(avoidance): make detection area dynamically (#5303) Signed-off-by: satoshi-ota --- .../config/avoidance/avoidance.param.yaml | 9 +++++++-- .../utils/avoidance/avoidance_module_data.hpp | 6 +++--- .../utils/avoidance/helper.hpp | 16 ++++++++++++++++ .../utils/avoidance/utils.hpp | 2 +- .../scene_module/avoidance/avoidance_module.cpp | 7 ++++--- .../src/scene_module/avoidance/manager.cpp | 15 +++++++++++---- .../src/scene_module/lane_change/manager.cpp | 15 +++++++++++---- .../src/utils/avoidance/utils.cpp | 8 ++++---- 8 files changed, 57 insertions(+), 21 deletions(-) diff --git a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml index 2b38536341d7c..25e42d20d144a 100644 --- a/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml +++ b/planning/behavior_path_planner/config/avoidance/avoidance.param.yaml @@ -118,8 +118,6 @@ object_ignore_section_crosswalk_in_front_distance: 30.0 # [m] object_ignore_section_crosswalk_behind_distance: 30.0 # [m] # detection range - object_check_forward_distance: 150.0 # [m] - object_check_backward_distance: 2.0 # [m] object_check_goal_distance: 20.0 # [m] # filtering parking objects threshold_distance_object_is_on_center: 1.0 # [m] @@ -128,6 +126,13 @@ # lost object compensation object_last_seen_threshold: 2.0 + # detection area generation parameters + detection_area: + static: true # [-] + min_forward_distance: 50.0 # [m] + max_forward_distance: 150.0 # [m] + backward_distance: 10.0 # [m] + # For safety check safety_check: # safety check configuration diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index 5ba0085ded5b7..d04b35a3b185b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -145,9 +145,9 @@ struct AvoidanceParameters double object_ignore_section_crosswalk_behind_distance{0.0}; // distance to avoid object detection - double object_check_forward_distance{0.0}; - - // continue to detect backward vehicles as avoidance targets until they are this distance away + bool use_static_detection_area{true}; + double object_check_min_forward_distance{0.0}; + double object_check_max_forward_distance{0.0}; double object_check_backward_distance{0.0}; // if the distance between object and goal position is less than this parameter, the module ignore diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/helper.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/helper.hpp index 77fe2e29c4978..11388dd6bb74a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/helper.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/helper.hpp @@ -173,6 +173,22 @@ class AvoidanceHelper : std::max(shift_length, getRightShiftBound()); } + double getForwardDetectionRange() const + { + if (parameters_->use_static_detection_area) { + return parameters_->object_check_max_forward_distance; + } + + const auto max_shift_length = std::max( + std::abs(parameters_->max_right_shift_length), std::abs(parameters_->max_left_shift_length)); + const auto dynamic_distance = PathShifter::calcLongitudinalDistFromJerk( + max_shift_length, getLateralMinJerkLimit(), getEgoSpeed()); + + return std::clamp( + 1.5 * dynamic_distance, parameters_->object_check_min_forward_distance, + parameters_->object_check_max_forward_distance); + } + void alignShiftLinesOrder(AvoidLineArray & lines, const bool align_shift_length = true) const { if (lines.empty()) { diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index f14e523d0619b..8cb09cd47b444 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -167,7 +167,7 @@ std::vector getSafetyCheckTargetObjects( std::pair separateObjectsByPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, const AvoidancePlanningData & data, const std::shared_ptr & parameters, - const bool is_running, DebugData & debug); + const double object_check_forward_distance, const bool is_running, DebugData & debug); DrivableLanes generateExpandDrivableLanes( const lanelet::ConstLanelet & lanelet, const std::shared_ptr & planner_data, diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index c94406e466219..b4b0a871eec62 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -322,11 +322,12 @@ void AvoidanceModule::fillAvoidanceTargetObjects( getCurrentStatus() == ModuleStatus::WAITING_APPROVAL; // Separate dynamic objects based on whether they are inside or outside of the expanded lanelets. + const auto sparse_resample_path = utils::resamplePathWithSpline( + helper_.getPreviousSplineShiftPath().path, parameters_->resample_interval_for_output); const auto [object_within_target_lane, object_outside_target_lane] = utils::avoidance::separateObjectsByPath( - utils::resamplePathWithSpline( - helper_.getPreviousSplineShiftPath().path, parameters_->resample_interval_for_output), - planner_data_, data, parameters_, is_running, debug); + sparse_resample_path, planner_data_, data, parameters_, helper_.getForwardDetectionRange(), + is_running, debug); for (const auto & object : object_outside_target_lane.objects) { ObjectData other_object; diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp index 500ebe873a2d8..763cf17890721 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/manager.cpp @@ -114,10 +114,6 @@ AvoidanceModuleManager::AvoidanceModuleManager( *node, ns + "object_ignore_section_crosswalk_in_front_distance"); p.object_ignore_section_crosswalk_behind_distance = getOrDeclareParameter(*node, ns + "object_ignore_section_crosswalk_behind_distance"); - p.object_check_forward_distance = - getOrDeclareParameter(*node, ns + "object_check_forward_distance"); - p.object_check_backward_distance = - getOrDeclareParameter(*node, ns + "object_check_backward_distance"); p.object_check_goal_distance = getOrDeclareParameter(*node, ns + "object_check_goal_distance"); p.threshold_distance_object_is_on_center = @@ -130,6 +126,17 @@ AvoidanceModuleManager::AvoidanceModuleManager( getOrDeclareParameter(*node, ns + "object_last_seen_threshold"); } + { + std::string ns = "avoidance.target_filtering.detection_area."; + p.use_static_detection_area = getOrDeclareParameter(*node, ns + "static"); + p.object_check_min_forward_distance = + getOrDeclareParameter(*node, ns + "min_forward_distance"); + p.object_check_max_forward_distance = + getOrDeclareParameter(*node, ns + "max_forward_distance"); + p.object_check_backward_distance = + getOrDeclareParameter(*node, ns + "backward_distance"); + } + // safety check general params { std::string ns = "avoidance.safety_check."; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index 3d7cf07308a79..c6995ada1bfa6 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -329,10 +329,6 @@ AvoidanceByLaneChangeModuleManager::AvoidanceByLaneChangeModuleManager( *node, ns + "object_ignore_section_crosswalk_in_front_distance"); p.object_ignore_section_crosswalk_behind_distance = getOrDeclareParameter(*node, ns + "object_ignore_section_crosswalk_behind_distance"); - p.object_check_forward_distance = - getOrDeclareParameter(*node, ns + "object_check_forward_distance"); - p.object_check_backward_distance = - getOrDeclareParameter(*node, ns + "object_check_backward_distance"); p.object_check_goal_distance = getOrDeclareParameter(*node, ns + "object_check_goal_distance"); p.threshold_distance_object_is_on_center = @@ -345,6 +341,17 @@ AvoidanceByLaneChangeModuleManager::AvoidanceByLaneChangeModuleManager( getOrDeclareParameter(*node, ns + "object_last_seen_threshold"); } + { + std::string ns = "avoidance.target_filtering.detection_area."; + p.use_static_detection_area = getOrDeclareParameter(*node, ns + "static"); + p.object_check_min_forward_distance = + getOrDeclareParameter(*node, ns + "min_forward_distance"); + p.object_check_max_forward_distance = + getOrDeclareParameter(*node, ns + "max_forward_distance"); + p.object_check_backward_distance = + getOrDeclareParameter(*node, ns + "backward_distance"); + } + // safety check { std::string ns = "avoidance.safety_check."; diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 232ab5769f0c7..8fb30a28f1272 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -991,7 +991,7 @@ void filterTargetObjects( data.other_objects.push_back(o); continue; } - if (o.longitudinal > parameters->object_check_forward_distance) { + if (o.longitudinal > parameters->object_check_max_forward_distance) { o.reason = AvoidanceDebugFactor::OBJECT_IS_IN_FRONT_THRESHOLD; data.other_objects.push_back(o); continue; @@ -1479,7 +1479,7 @@ lanelet::ConstLanelets getAdjacentLane( const std::shared_ptr & parameters, const bool is_right_shift) { const auto & rh = planner_data->route_handler; - const auto & forward_distance = parameters->object_check_forward_distance; + const auto & forward_distance = parameters->object_check_max_forward_distance; const auto & backward_distance = parameters->safety_check_backward_distance; const auto & vehicle_pose = planner_data->self_odometry->pose.pose; @@ -1619,7 +1619,7 @@ std::vector getSafetyCheckTargetObjects( std::pair separateObjectsByPath( const PathWithLaneId & path, const std::shared_ptr & planner_data, const AvoidancePlanningData & data, const std::shared_ptr & parameters, - const bool is_running, DebugData & debug) + const double object_check_forward_distance, const bool is_running, DebugData & debug) { PredictedObjects target_objects; PredictedObjects other_objects; @@ -1642,7 +1642,7 @@ std::pair separateObjectsByPath( const auto & p_ego_back = path.points.at(i + 1).point.pose; const auto distance_from_ego = calcSignedArcLength(path.points, ego_idx, i); - if (distance_from_ego > parameters->object_check_forward_distance) { + if (distance_from_ego > object_check_forward_distance) { break; } From 86a59542d1ef6ec24e168832f9c35bfa2af66e73 Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:55:26 +0900 Subject: [PATCH 490/547] fix(drivable_area_expansion): fix max_arc_length parameter and extra_arc_length parameters (#5333) Signed-off-by: Maxime CLEMENT --- .../drivable_area_expansion.cpp | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index 44dddb57e726e..25cf917d27135 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -74,14 +74,21 @@ void reuse_previous_poses( if (cropped_poses.empty()) { const auto resampled_path_points = motion_utils::resamplePath(path, params.resample_interval, true, true, false).points; - for (const auto & p : resampled_path_points) cropped_poses.push_back(p.point.pose); - } else if (!path.points.empty()) { + const auto cropped_path = + params.max_path_arc_length <= 0.0 + ? resampled_path_points + : motion_utils::cropForwardPoints( + resampled_path_points, resampled_path_points.front().point.pose.position, 0, + params.max_path_arc_length); + for (const auto & p : cropped_path) cropped_poses.push_back(p.point.pose); + } else { const auto initial_arc_length = motion_utils::calcArcLength(cropped_poses); const auto max_path_arc_length = motion_utils::calcArcLength(path.points); const auto first_arc_length = motion_utils::calcSignedArcLength( path.points, path.points.front().point.pose.position, cropped_poses.back().position); for (auto arc_length = first_arc_length + params.resample_interval; - initial_arc_length + (arc_length - first_arc_length) <= params.max_path_arc_length && + (params.max_path_arc_length <= 0.0 || + initial_arc_length + (arc_length - first_arc_length) <= params.max_path_arc_length) && arc_length <= max_path_arc_length; arc_length += params.resample_interval) cropped_poses.push_back(motion_utils::calcInterpolatedPose(path.points, arc_length)); @@ -129,7 +136,8 @@ std::vector calculate_minimum_expansions( auto arc_length = tier4_autoware_utils::calcDistance2d(*intersection_point, bound[bound_idx + 1]); for (auto up_bound_idx = bound_idx + 2; up_bound_idx < bound.size(); ++up_bound_idx) { - tier4_autoware_utils::calcDistance2d(bound[bound_idx - 1], bound[bound_idx]); + arc_length += + tier4_autoware_utils::calcDistance2d(bound[up_bound_idx - 1], bound[up_bound_idx]); if (arc_length > params.extra_arc_length) break; minimum_expansions[up_bound_idx] = std::max(minimum_expansions[up_bound_idx], dist); } @@ -200,11 +208,11 @@ void expand_bound( LineString2d path_ls; for (const auto & p : path_poses) path_ls.push_back(convert_point(p.position)); for (auto idx = 0LU; idx < bound.size(); ++idx) { - const auto bound_p = convert_point(bound[idx]); - const auto projection = point_to_linestring_projection(bound_p, path_ls); - const auto expansion_ratio = - (expansions[idx] + std::abs(projection.distance)) / std::abs(projection.distance); - if (expansion_ratio > 1.0) { + if (expansions[idx] > 0.0) { + const auto bound_p = convert_point(bound[idx]); + const auto projection = point_to_linestring_projection(bound_p, path_ls); + const auto expansion_ratio = + (expansions[idx] + std::abs(projection.distance)) / std::abs(projection.distance); const auto & path_p = projection.projected_point; const auto expanded_p = lerp_point(path_p, bound_p, expansion_ratio); bound[idx].x = expanded_p.x(); @@ -267,6 +275,7 @@ void expand_drivable_area( stop_watch.tic("crop"); std::vector path_poses = planner_data->drivable_area_expansion_prev_path_poses; std::vector curvatures = planner_data->drivable_area_expansion_prev_curvatures; + reuse_previous_poses( path, path_poses, curvatures, planner_data->self_odometry->pose.pose.position, params); const auto crop_ms = stop_watch.toc("crop"); @@ -299,6 +308,7 @@ void expand_drivable_area( apply_bound_change_rate_limit(left_expansions, path.left_bound, params.max_bound_rate); apply_bound_change_rate_limit(right_expansions, path.right_bound, params.max_bound_rate); const auto smooth_ms = stop_watch.toc("smooth"); + // TODO(Maxime): limit the distances based on the total width (left + right < min_lane_width) stop_watch.tic("expand"); expand_bound(path.left_bound, path_poses, left_expansions); @@ -312,7 +322,6 @@ void expand_drivable_area( "%2.2f\n\tMaximum expansion: %2.2f\n\tSmoothing: %2.2f\n\tExpansion: %2.2f\n\n", total_ms, preprocessing_ms, crop_ms, curvature_expansion_ms, max_dist_ms, smooth_ms, expand_ms); - planner_data->drivable_area_expansion_prev_path_poses = path_poses; planner_data->drivable_area_expansion_prev_curvatures = curvatures; } From c65bccd981cae83eac8f4b85d688d297db49ad1b Mon Sep 17 00:00:00 2001 From: danielsanchezaran Date: Tue, 17 Oct 2023 22:04:11 +0900 Subject: [PATCH 491/547] refactor(map_based_prediction): to improve readability. Possibly (unlikely) performance improvements (#5261) * refactor to improve readability. Possibly (unlikely) performance Signed-off-by: Daniel Sanchez * Use ternary operator to make the code more readable Signed-off-by: Daniel Sanchez Co-authored-by: Takamasa Horibe * Add const to curr lanelets and pred. object crosswalk Signed-off-by: Daniel Sanchez * remove redundant breaks in switch table Signed-off-by: Daniel Sanchez * change magic numbers to autoware function kph -> mps Signed-off-by: Daniel Sanchez * simplify code by returning on false condition, change ifelse to switch Signed-off-by: Daniel Sanchez --------- Signed-off-by: Daniel Sanchez Co-authored-by: Takamasa Horibe --- .../src/map_based_prediction_node.cpp | 322 +++++++++--------- 1 file changed, 156 insertions(+), 166 deletions(-) diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 58356f4f96ed6..745bccf6dbabe 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -369,17 +370,13 @@ bool withinLanelet( boost::geometry::correct(polygon); bool with_in_polygon = boost::geometry::within(p_object, polygon); - if (!use_yaw_information) { - return with_in_polygon; - } else { - // use yaw angle to compare - const double abs_yaw_diff = calcAbsYawDiffBetweenLaneletAndObject(object, lanelet); - if (abs_yaw_diff < yaw_threshold) { - return with_in_polygon; - } else { - return false; - } - } + if (!use_yaw_information) return with_in_polygon; + + // use yaw angle to compare + const double abs_yaw_diff = calcAbsYawDiffBetweenLaneletAndObject(object, lanelet); + if (abs_yaw_diff < yaw_threshold) return with_in_polygon; + + return false; } bool withinRoadLanelet( @@ -594,53 +591,53 @@ ObjectClassification::_label_type changeLabelForPrediction( const lanelet::LaneletMapPtr & lanelet_map_ptr_) { // for car like vehicle do not change labels - if ( - label == ObjectClassification::CAR || label == ObjectClassification::BUS || - label == ObjectClassification::TRUCK || label == ObjectClassification::TRAILER || - label == ObjectClassification::UNKNOWN) { - return label; - } else if ( // for bicycle and motorcycle - label == ObjectClassification::MOTORCYCLE || label == ObjectClassification::BICYCLE) { - // if object is within road lanelet and satisfies yaw constraints - const bool within_road_lanelet = withinRoadLanelet(object, lanelet_map_ptr_, true); - const float high_speed_threshold = 25.0 / 18.0 * 5.0; // High speed bicycle 25 km/h - // calc abs speed from x and y velocity - const double abs_speed = std::hypot( - object.kinematics.twist_with_covariance.twist.linear.x, - object.kinematics.twist_with_covariance.twist.linear.y); - const bool high_speed_object = abs_speed > high_speed_threshold; - - // if the object is within lanelet, do the same estimation with vehicle - if (within_road_lanelet) { - return ObjectClassification::MOTORCYCLE; - } else if (high_speed_object) { + switch (label) { + case ObjectClassification::CAR: + case ObjectClassification::BUS: + case ObjectClassification::TRUCK: + case ObjectClassification::TRAILER: + case ObjectClassification::UNKNOWN: + return label; + + case ObjectClassification::MOTORCYCLE: + case ObjectClassification::BICYCLE: { // if object is within road lanelet and satisfies yaw + // constraints + const bool within_road_lanelet = withinRoadLanelet(object, lanelet_map_ptr_, true); + const float high_speed_threshold = + tier4_autoware_utils::kmph2mps(25.0); // High speed bicycle 25 km/h + // calc abs speed from x and y velocity + const double abs_speed = std::hypot( + object.kinematics.twist_with_covariance.twist.linear.x, + object.kinematics.twist_with_covariance.twist.linear.y); + const bool high_speed_object = abs_speed > high_speed_threshold; + + // if the object is within lanelet, do the same estimation with vehicle + if (within_road_lanelet) return ObjectClassification::MOTORCYCLE; // high speed object outside road lanelet will move like unknown object // return ObjectClassification::UNKNOWN; // temporary disabled - return label; - } else { + if (high_speed_object) return label; // Do nothing for now return ObjectClassification::BICYCLE; } - } else if (label == ObjectClassification::PEDESTRIAN) { - const bool within_road_lanelet = withinRoadLanelet(object, lanelet_map_ptr_, true); - const float max_velocity_for_human_mps = - 25.0 / 18.0 * 5.0; // Max human being motion speed is 25km/h - const double abs_speed = std::hypot( - object.kinematics.twist_with_covariance.twist.linear.x, - object.kinematics.twist_with_covariance.twist.linear.y); - const bool high_speed_object = abs_speed > max_velocity_for_human_mps; - // fast, human-like object: like segway - if (within_road_lanelet && high_speed_object) { - return label; // currently do nothing + + case ObjectClassification::PEDESTRIAN: { + const bool within_road_lanelet = withinRoadLanelet(object, lanelet_map_ptr_, true); + const float max_velocity_for_human_mps = + tier4_autoware_utils::kmph2mps(25.0); // Max human being motion speed is 25km/h + const double abs_speed = std::hypot( + object.kinematics.twist_with_covariance.twist.linear.x, + object.kinematics.twist_with_covariance.twist.linear.y); + const bool high_speed_object = abs_speed > max_velocity_for_human_mps; + // fast, human-like object: like segway + if (within_road_lanelet && high_speed_object) return label; // currently do nothing // return ObjectClassification::MOTORCYCLE; - } else if (high_speed_object) { - return label; // currently do nothing + if (high_speed_object) return label; // currently do nothing // fast human outside road lanelet will move like unknown object // return ObjectClassification::UNKNOWN; - } else { return label; } - } else { - return label; + + default: + return label; } } @@ -850,102 +847,99 @@ void MapBasedPredictionNode::objectsCallback(const TrackedObjects::ConstSharedPt const auto & label_ = transformed_object.classification.front().label; const auto label = changeLabelForPrediction(label_, object, lanelet_map_ptr_); - // For crosswalk user - if (label == ObjectClassification::PEDESTRIAN || label == ObjectClassification::BICYCLE) { - const auto predicted_object = getPredictedObjectAsCrosswalkUser(transformed_object); - output.objects.push_back(predicted_object); - // For road user - } else if ( - label == ObjectClassification::CAR || label == ObjectClassification::BUS || - label == ObjectClassification::TRAILER || label == ObjectClassification::MOTORCYCLE || - label == ObjectClassification::TRUCK) { - // Update object yaw and velocity - updateObjectData(transformed_object); - - // Get Closest Lanelet - const auto current_lanelets = getCurrentLanelets(transformed_object); - - // Update Objects History - updateObjectsHistory(output.header, transformed_object, current_lanelets); - - // For off lane obstacles - if (current_lanelets.empty()) { - PredictedPath predicted_path = - path_generator_->generatePathForOffLaneVehicle(transformed_object); - predicted_path.confidence = 1.0; - if (predicted_path.path.empty()) { - continue; - } - - auto predicted_object = convertToPredictedObject(transformed_object); - predicted_object.kinematics.predicted_paths.push_back(predicted_path); - output.objects.push_back(predicted_object); - continue; + switch (label) { + case ObjectClassification::PEDESTRIAN: + case ObjectClassification::BICYCLE: { + const auto predicted_object_crosswalk = + getPredictedObjectAsCrosswalkUser(transformed_object); + output.objects.push_back(predicted_object_crosswalk); + break; } - - // For too-slow vehicle - const double abs_obj_speed = std::hypot( - transformed_object.kinematics.twist_with_covariance.twist.linear.x, - transformed_object.kinematics.twist_with_covariance.twist.linear.y); - if (std::fabs(abs_obj_speed) < min_velocity_for_map_based_prediction_) { - PredictedPath predicted_path = - path_generator_->generatePathForLowSpeedVehicle(transformed_object); - predicted_path.confidence = 1.0; - if (predicted_path.path.empty()) { - continue; + case ObjectClassification::CAR: + case ObjectClassification::BUS: + case ObjectClassification::TRAILER: + case ObjectClassification::MOTORCYCLE: + case ObjectClassification::TRUCK: { + // Update object yaw and velocity + updateObjectData(transformed_object); + + // Get Closest Lanelet + const auto current_lanelets = getCurrentLanelets(transformed_object); + + // Update Objects History + updateObjectsHistory(output.header, transformed_object, current_lanelets); + + // For off lane obstacles + if (current_lanelets.empty()) { + PredictedPath predicted_path = + path_generator_->generatePathForOffLaneVehicle(transformed_object); + predicted_path.confidence = 1.0; + if (predicted_path.path.empty()) break; + + auto predicted_object_vehicle = convertToPredictedObject(transformed_object); + predicted_object_vehicle.kinematics.predicted_paths.push_back(predicted_path); + output.objects.push_back(predicted_object_vehicle); + break; } - auto predicted_object = convertToPredictedObject(transformed_object); - predicted_object.kinematics.predicted_paths.push_back(predicted_path); - output.objects.push_back(predicted_object); - continue; - } - - // Get Predicted Reference Path for Each Maneuver and current lanelets - // return: - const auto ref_paths = - getPredictedReferencePath(transformed_object, current_lanelets, objects_detected_time); - - // If predicted reference path is empty, assume this object is out of the lane - if (ref_paths.empty()) { - PredictedPath predicted_path = - path_generator_->generatePathForLowSpeedVehicle(transformed_object); - predicted_path.confidence = 1.0; - if (predicted_path.path.empty()) { - continue; + // For too-slow vehicle + const double abs_obj_speed = std::hypot( + transformed_object.kinematics.twist_with_covariance.twist.linear.x, + transformed_object.kinematics.twist_with_covariance.twist.linear.y); + if (std::fabs(abs_obj_speed) < min_velocity_for_map_based_prediction_) { + PredictedPath predicted_path = + path_generator_->generatePathForLowSpeedVehicle(transformed_object); + predicted_path.confidence = 1.0; + if (predicted_path.path.empty()) break; + + auto predicted_slow_object = convertToPredictedObject(transformed_object); + predicted_slow_object.kinematics.predicted_paths.push_back(predicted_path); + output.objects.push_back(predicted_slow_object); + break; } - auto predicted_object = convertToPredictedObject(transformed_object); - predicted_object.kinematics.predicted_paths.push_back(predicted_path); - output.objects.push_back(predicted_object); - continue; - } + // Get Predicted Reference Path for Each Maneuver and current lanelets + // return: + const auto ref_paths = + getPredictedReferencePath(transformed_object, current_lanelets, objects_detected_time); + + // If predicted reference path is empty, assume this object is out of the lane + if (ref_paths.empty()) { + PredictedPath predicted_path = + path_generator_->generatePathForLowSpeedVehicle(transformed_object); + predicted_path.confidence = 1.0; + if (predicted_path.path.empty()) break; + + auto predicted_object_out_of_lane = convertToPredictedObject(transformed_object); + predicted_object_out_of_lane.kinematics.predicted_paths.push_back(predicted_path); + output.objects.push_back(predicted_object_out_of_lane); + break; + } - // Get Debug Marker for On Lane Vehicles - const auto max_prob_path = std::max_element( - ref_paths.begin(), ref_paths.end(), - [](const PredictedRefPath & a, const PredictedRefPath & b) { - return a.probability < b.probability; - }); - const auto debug_marker = - getDebugMarker(object, max_prob_path->maneuver, debug_markers.markers.size()); - debug_markers.markers.push_back(debug_marker); - - // Generate Predicted Path - std::vector predicted_paths; - for (const auto & ref_path : ref_paths) { - PredictedPath predicted_path = - path_generator_->generatePathForOnLaneVehicle(transformed_object, ref_path.path); - if (predicted_path.path.empty()) { - continue; + // Get Debug Marker for On Lane Vehicles + const auto max_prob_path = std::max_element( + ref_paths.begin(), ref_paths.end(), + [](const PredictedRefPath & a, const PredictedRefPath & b) { + return a.probability < b.probability; + }); + const auto debug_marker = + getDebugMarker(object, max_prob_path->maneuver, debug_markers.markers.size()); + debug_markers.markers.push_back(debug_marker); + + // Generate Predicted Path + std::vector predicted_paths; + for (const auto & ref_path : ref_paths) { + PredictedPath predicted_path = + path_generator_->generatePathForOnLaneVehicle(transformed_object, ref_path.path); + if (predicted_path.path.empty()) { + continue; + } + predicted_path.confidence = ref_path.probability; + predicted_paths.push_back(predicted_path); } - predicted_path.confidence = ref_path.probability; - predicted_paths.push_back(predicted_path); - } + // Normalize Path Confidence and output the predicted object - // Normalize Path Confidence and output the predicted object - { float sum_confidence = 0.0; for (const auto & predicted_path : predicted_paths) { sum_confidence += predicted_path.confidence; @@ -953,25 +947,25 @@ void MapBasedPredictionNode::objectsCallback(const TrackedObjects::ConstSharedPt const float min_sum_confidence_value = 1e-3; sum_confidence = std::max(sum_confidence, min_sum_confidence_value); + auto predicted_object = convertToPredictedObject(transformed_object); + for (auto & predicted_path : predicted_paths) { predicted_path.confidence = predicted_path.confidence / sum_confidence; - } - - auto predicted_object = convertToPredictedObject(transformed_object); - for (const auto & predicted_path : predicted_paths) { predicted_object.kinematics.predicted_paths.push_back(predicted_path); } output.objects.push_back(predicted_object); + break; } - // For unknown object - } else { - auto predicted_object = convertToPredictedObject(transformed_object); - PredictedPath predicted_path = - path_generator_->generatePathForNonVehicleObject(transformed_object); - predicted_path.confidence = 1.0; + default: { + auto predicted_unknown_object = convertToPredictedObject(transformed_object); + PredictedPath predicted_path = + path_generator_->generatePathForNonVehicleObject(transformed_object); + predicted_path.confidence = 1.0; - predicted_object.kinematics.predicted_paths.push_back(predicted_path); - output.objects.push_back(predicted_object); + predicted_unknown_object.kinematics.predicted_paths.push_back(predicted_path); + output.objects.push_back(predicted_unknown_object); + break; + } } } @@ -1114,26 +1108,28 @@ void MapBasedPredictionNode::updateObjectData(TrackedObject & object) object_pose, object_twist.linear.x * 0.1, object_twist.linear.y * 0.1, 0.0); // assumption: the object vx is much larger than vy - if (object.kinematics.twist_with_covariance.twist.linear.x < 0.0) { - if ( - object.kinematics.orientation_availability == - autoware_auto_perception_msgs::msg::DetectedObjectKinematics::SIGN_UNKNOWN) { + if (object.kinematics.twist_with_covariance.twist.linear.x >= 0.0) return; + + switch (object.kinematics.orientation_availability) { + case autoware_auto_perception_msgs::msg::DetectedObjectKinematics::SIGN_UNKNOWN: { const auto original_yaw = tf2::getYaw(object.kinematics.pose_with_covariance.pose.orientation); // flip the angle object.kinematics.pose_with_covariance.pose.orientation = tier4_autoware_utils::createQuaternionFromYaw(tier4_autoware_utils::pi + original_yaw); - } else { + break; + } + default: { const auto updated_object_yaw = tier4_autoware_utils::calcAzimuthAngle(object_pose.position, future_object_pose.position); object.kinematics.pose_with_covariance.pose.orientation = tier4_autoware_utils::createQuaternionFromYaw(updated_object_yaw); + break; } - - object.kinematics.twist_with_covariance.twist.linear.x *= -1.0; - object.kinematics.twist_with_covariance.twist.linear.y *= -1.0; } + object.kinematics.twist_with_covariance.twist.linear.x *= -1.0; + object.kinematics.twist_with_covariance.twist.linear.y *= -1.0; return; } @@ -1864,16 +1860,10 @@ ManeuverProbability MapBasedPredictionNode::calculateManeuverProbability( constexpr float LC_PROB = 1.0; // probability for left lane change constexpr float RC_PROB = 1.0; // probability for right lane change lane_follow_probability = 0.0; - if (!left_paths.empty() && right_paths.empty()) { - left_lane_change_probability = LC_PROB; - right_lane_change_probability = 0.0; - } else if (left_paths.empty() && !right_paths.empty()) { - left_lane_change_probability = 0.0; - right_lane_change_probability = RC_PROB; - } else { - left_lane_change_probability = LC_PROB; - right_lane_change_probability = RC_PROB; - } + + // If the given lane is empty, the probability goes to 0 + left_lane_change_probability = left_paths.empty() ? 0.0 : LC_PROB; + right_lane_change_probability = right_paths.empty() ? 0.0 : RC_PROB; } const float MIN_PROBABILITY = 1e-3; From b3bdedc584682fe6ab6310af4792312c36521e74 Mon Sep 17 00:00:00 2001 From: beyzanurkaya <32412808+beyzanurkaya@users.noreply.github.com> Date: Wed, 18 Oct 2023 00:40:26 +0300 Subject: [PATCH 492/547] fix(obstacle_avoidance_planner): fix lateral calculations (#4292) * use right_overhang and left_overhang for lateral calculation Signed-off-by: beyza * style(pre-commit): autofix * update debug_marker.cpp * style(pre-commit): autofix --------- Signed-off-by: beyza Co-authored-by: beyza Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../src/debug_marker.cpp | 56 +++++++++++-------- .../src/mpt_optimizer.cpp | 17 ++++-- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/planning/obstacle_avoidance_planner/src/debug_marker.cpp b/planning/obstacle_avoidance_planner/src/debug_marker.cpp index 8bf46c697104a..2f8babf103877 100644 --- a/planning/obstacle_avoidance_planner/src/debug_marker.cpp +++ b/planning/obstacle_avoidance_planner/src/debug_marker.cpp @@ -47,21 +47,22 @@ MarkerArray getFootprintsMarkerArray( const auto & traj_point = mpt_traj.at(i); - const double half_width = vehicle_info.vehicle_width_m / 2.0; + const double base_to_right = (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.right_overhang_m; + const double base_to_left = (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.left_overhang_m; const double base_to_front = vehicle_info.vehicle_length_m - vehicle_info.rear_overhang_m; const double base_to_rear = vehicle_info.rear_overhang_m; marker.points.push_back( - tier4_autoware_utils::calcOffsetPose(traj_point.pose, base_to_front, -half_width, 0.0) + tier4_autoware_utils::calcOffsetPose(traj_point.pose, base_to_front, base_to_left, 0.0) .position); marker.points.push_back( - tier4_autoware_utils::calcOffsetPose(traj_point.pose, base_to_front, half_width, 0.0) + tier4_autoware_utils::calcOffsetPose(traj_point.pose, base_to_front, -base_to_right, 0.0) .position); marker.points.push_back( - tier4_autoware_utils::calcOffsetPose(traj_point.pose, -base_to_rear, half_width, 0.0) + tier4_autoware_utils::calcOffsetPose(traj_point.pose, -base_to_rear, -base_to_right, 0.0) .position); marker.points.push_back( - tier4_autoware_utils::calcOffsetPose(traj_point.pose, -base_to_rear, -half_width, 0.0) + tier4_autoware_utils::calcOffsetPose(traj_point.pose, -base_to_rear, base_to_left, 0.0) .position); marker.points.push_back(marker.points.front()); @@ -71,8 +72,8 @@ MarkerArray getFootprintsMarkerArray( } MarkerArray getBoundsWidthMarkerArray( - const std::vector & ref_points, const double vehicle_width, - const size_t sampling_num) + const std::vector & ref_points, + const vehicle_info_util::VehicleInfo & vehicle_info, const size_t sampling_num) { const auto current_time = rclcpp::Clock().now(); MarkerArray marker_array; @@ -105,8 +106,10 @@ MarkerArray getBoundsWidthMarkerArray( } const geometry_msgs::msg::Pose & pose = ref_points.at(i).pose_on_constraints.at(bound_idx); + const double base_to_right = + (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.right_overhang_m; const double lb_y = - ref_points.at(i).bounds_on_constraints.at(bound_idx).lower_bound - vehicle_width / 2.0; + ref_points.at(i).bounds_on_constraints.at(bound_idx).lower_bound - base_to_right; const auto lb = tier4_autoware_utils::calcOffsetPose(pose, 0.0, lb_y, 0.0).position; lb_marker.points.push_back(pose.position); @@ -125,8 +128,10 @@ MarkerArray getBoundsWidthMarkerArray( } const geometry_msgs::msg::Pose & pose = ref_points.at(i).pose_on_constraints.at(bound_idx); + const double base_to_left = + (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.left_overhang_m; const double ub_y = - ref_points.at(i).bounds_on_constraints.at(bound_idx).upper_bound + vehicle_width / 2.0; + ref_points.at(i).bounds_on_constraints.at(bound_idx).upper_bound + base_to_left; const auto ub = tier4_autoware_utils::calcOffsetPose(pose, 0.0, ub_y, 0.0).position; ub_marker.points.push_back(pose.position); @@ -140,7 +145,8 @@ MarkerArray getBoundsWidthMarkerArray( } MarkerArray getBoundsLineMarkerArray( - const std::vector & ref_points, const double vehicle_width) + const std::vector & ref_points, + const vehicle_info_util::VehicleInfo & vehicle_info) { MarkerArray marker_array; @@ -158,12 +164,13 @@ MarkerArray getBoundsLineMarkerArray( for (size_t i = 0; i < ref_points.size(); i++) { const geometry_msgs::msg::Pose & pose = ref_points.at(i).pose; - - const double ub_y = ref_points.at(i).bounds.upper_bound + vehicle_width / 2.0; + const double base_to_right = (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.right_overhang_m; + const double base_to_left = (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.left_overhang_m; + const double ub_y = ref_points.at(i).bounds.upper_bound + base_to_left; const auto ub = tier4_autoware_utils::calcOffsetPose(pose, 0.0, ub_y, 0.0).position; ub_marker.points.push_back(ub); - const double lb_y = ref_points.at(i).bounds.lower_bound - vehicle_width / 2.0; + const double lb_y = ref_points.at(i).bounds.lower_bound - base_to_right; const auto lb = tier4_autoware_utils::calcOffsetPose(pose, 0.0, lb_y, 0.0).position; lb_marker.points.push_back(lb); } @@ -175,8 +182,9 @@ MarkerArray getBoundsLineMarkerArray( MarkerArray getVehicleCircleLinesMarkerArray( const std::vector & ref_points, - const std::vector & vehicle_circle_longitudinal_offsets, const double vehicle_width, - const size_t sampling_num, const std::string & ns) + const std::vector & vehicle_circle_longitudinal_offsets, + const vehicle_info_util::VehicleInfo & vehicle_info, const size_t sampling_num, + const std::string & ns) { const auto current_time = rclcpp::Clock().now(); MarkerArray msg; @@ -206,11 +214,13 @@ MarkerArray getVehicleCircleLinesMarkerArray( auto base_pose = tier4_autoware_utils::calcOffsetPose(pose_with_deviation, d, 0.0, 0.0); base_pose.orientation = tier4_autoware_utils::createQuaternionFromYaw(ref_point.getYaw() + ref_point.alpha); - + const double base_to_right = + (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.right_overhang_m; + const double base_to_left = (vehicle_info.wheel_tread_m / 2.0) + vehicle_info.left_overhang_m; const auto ub = - tier4_autoware_utils::calcOffsetPose(base_pose, 0.0, vehicle_width / 2.0, 0.0).position; + tier4_autoware_utils::calcOffsetPose(base_pose, 0.0, base_to_left, 0.0).position; const auto lb = - tier4_autoware_utils::calcOffsetPose(base_pose, 0.0, -vehicle_width / 2.0, 0.0).position; + tier4_autoware_utils::calcOffsetPose(base_pose, 0.0, -base_to_right, 0.0).position; marker.points.push_back(ub); marker.points.push_back(lb); @@ -368,13 +378,12 @@ MarkerArray getDebugMarker( MarkerArray marker_array; // bounds line - appendMarkerArray( - getBoundsLineMarkerArray(debug_data.ref_points, vehicle_info.vehicle_width_m), &marker_array); + appendMarkerArray(getBoundsLineMarkerArray(debug_data.ref_points, vehicle_info), &marker_array); // bounds width appendMarkerArray( getBoundsWidthMarkerArray( - debug_data.ref_points, vehicle_info.vehicle_width_m, debug_data.mpt_visualize_sampling_num), + debug_data.ref_points, vehicle_info, debug_data.mpt_visualize_sampling_num), &marker_array); // current vehicle circles @@ -404,9 +413,8 @@ MarkerArray getDebugMarker( // vehicle circle line appendMarkerArray( getVehicleCircleLinesMarkerArray( - debug_data.ref_points, debug_data.vehicle_circle_longitudinal_offsets, - vehicle_info.vehicle_width_m, debug_data.mpt_visualize_sampling_num, - "vehicle_circle_lines"), + debug_data.ref_points, debug_data.vehicle_circle_longitudinal_offsets, vehicle_info, + debug_data.mpt_visualize_sampling_num, "vehicle_circle_lines"), &marker_array); // footprint by drivable area diff --git a/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp b/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp index 359485fadcfff..765136b3cf6f6 100644 --- a/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp +++ b/planning/obstacle_avoidance_planner/src/mpt_optimizer.cpp @@ -36,9 +36,11 @@ std::tuple, std::vector> calcVehicleCirclesByUniform const vehicle_info_util::VehicleInfo & vehicle_info, const size_t circle_num, const double radius_ratio) { + const double lateral_offset = + abs(vehicle_info.right_overhang_m - vehicle_info.left_overhang_m) / 2.0; const double radius = std::hypot( vehicle_info.vehicle_length_m / static_cast(circle_num) / 2.0, - vehicle_info.vehicle_width_m / 2.0) * + vehicle_info.vehicle_width_m / 2.0 + lateral_offset) * radius_ratio; const std::vector radiuses(circle_num, radius); @@ -59,16 +61,18 @@ std::tuple, std::vector> calcVehicleCirclesByBicycle if (circle_num < 2) { throw std::invalid_argument("circle_num is less than 2."); } - + const double lateral_offset = + abs(vehicle_info.right_overhang_m - vehicle_info.left_overhang_m) / 2.0; // 1st circle (rear wheel) - const double rear_radius = vehicle_info.vehicle_width_m / 2.0 * rear_radius_ratio; + const double rear_radius = + vehicle_info.vehicle_width_m / 2.0 + lateral_offset * rear_radius_ratio; const double rear_lon_offset = 0.0; // 2nd circle (front wheel) const double front_radius = std::hypot( vehicle_info.vehicle_length_m / static_cast(circle_num) / 2.0, - vehicle_info.vehicle_width_m / 2.0) * + vehicle_info.vehicle_width_m / 2.0 + lateral_offset) * front_radius_ratio; const double unit_lon_length = vehicle_info.vehicle_length_m / static_cast(circle_num); @@ -84,8 +88,9 @@ std::tuple, std::vector> calcVehicleCirclesByFitting if (circle_num < 2) { throw std::invalid_argument("circle_num is less than 2."); } - - const double radius = vehicle_info.vehicle_width_m / 2.0; + const double lateral_offset = + abs(vehicle_info.right_overhang_m - vehicle_info.left_overhang_m) / 2.0; + const double radius = vehicle_info.vehicle_width_m / 2.0 + lateral_offset; std::vector radiuses(circle_num, radius); const double unit_lon_length = From 5c220226e06f94416ee7794ae40a165a76680799 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 18 Oct 2023 09:54:51 +0900 Subject: [PATCH 493/547] fix(lane_change): detect stuck in current lane termianl (#5337) * feat(lane_change): detect stuck in current lane termianl Signed-off-by: kosuke55 * rename func Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../scene_module/lane_change/normal.hpp | 4 +- .../src/scene_module/lane_change/normal.cpp | 37 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp index 8400cf8c40afd..54883489f2fe6 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/lane_change/normal.hpp @@ -158,10 +158,10 @@ class NormalLaneChange : public LaneChangeBase //! @brief Check if the ego vehicle is in stuck by a stationary obstacle. //! @param obstacle_check_distance Distance to check ahead for any objects that might be //! obstructing ego path. It makes sense to use values like the maximum lane change distance. - bool isVehicleStuckByObstacle( + bool isVehicleStuck( const lanelet::ConstLanelets & current_lanes, const double obstacle_check_distance) const; - bool isVehicleStuckByObstacle(const lanelet::ConstLanelets & current_lanes) const; + bool isVehicleStuck(const lanelet::ConstLanelets & current_lanes) const; double calcMaximumLaneChangeLength( const lanelet::ConstLanelet & current_terminal_lanelet, const double max_acc) const; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index 3a2903cb01e9e..ce07cda882692 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -83,7 +83,7 @@ std::pair NormalLaneChange::getSafePath(LaneChangePath & safe_path) // find candidate paths LaneChangePaths valid_paths{}; - const bool is_stuck = isVehicleStuckByObstacle(current_lanes); + const bool is_stuck = isVehicleStuck(current_lanes); bool found_safe_path = getLaneChangePaths( current_lanes, target_lanes, direction_, &valid_paths, lane_change_parameters_->rss_params, is_stuck); @@ -670,7 +670,7 @@ std::vector NormalLaneChange::sampleLongitudinalAccValues( } // If the ego is in stuck, sampling all possible accelerations to find avoiding path. - if (isVehicleStuckByObstacle(current_lanes)) { + if (isVehicleStuck(current_lanes)) { auto clock = rclcpp::Clock(RCL_ROS_TIME); RCLCPP_INFO_THROTTLE( logger_, clock, 1000, "Vehicle is in stuck. Sample all possible acc: [%f ~ %f]", min_acc, @@ -1333,7 +1333,7 @@ PathSafetyStatus NormalLaneChange::isApprovedPathSafe() const debug_filtered_objects_ = target_objects; CollisionCheckDebugMap debug_data; - const bool is_stuck = isVehicleStuckByObstacle(current_lanes); + const bool is_stuck = isVehicleStuck(current_lanes); const auto safety_status = isLaneChangePathSafe( path, target_objects, lane_change_parameters_->rss_params_for_abort, is_stuck, debug_data); { @@ -1674,8 +1674,8 @@ PathSafetyStatus NormalLaneChange::isLaneChangePathSafe( return path_safety_status; } -// Check if the ego vehicle is in stuck by a stationary obstacle. -bool NormalLaneChange::isVehicleStuckByObstacle( +// Check if the ego vehicle is in stuck by a stationary obstacle or by the terminal of current lanes +bool NormalLaneChange::isVehicleStuck( const lanelet::ConstLanelets & current_lanes, const double obstacle_check_distance) const { // Ego is still moving, not in stuck @@ -1709,12 +1709,31 @@ bool NormalLaneChange::isVehicleStuckByObstacle( } } - // No stationary objects found in obstacle_check_distance - RCLCPP_DEBUG(logger_, "No stationary objects found in obstacle_check_distance"); + // Check if Ego is in terminal of current lanes + const auto & route_handler = getRouteHandler(); + const double distance_to_terminal = + route_handler->isInGoalRouteSection(current_lanes.back()) + ? utils::getSignedDistance(getEgoPose(), route_handler->getGoalPose(), current_lanes) + : utils::getDistanceToEndOfLane(getEgoPose(), current_lanes); + const auto shift_intervals = + route_handler->getLateralIntervalsToPreferredLane(current_lanes.back()); + const double lane_change_buffer = + utils::calcMinimumLaneChangeLength(getCommonParam(), shift_intervals, 0.0); + const double stop_point_buffer = getCommonParam().backward_length_buffer_for_end_of_lane; + const double terminal_judge_buffer = lane_change_buffer + stop_point_buffer + 1.0; + if (distance_to_terminal < terminal_judge_buffer) { + return true; + } + + // No stationary objects found in obstacle_check_distance and Ego is not in terminal of current + RCLCPP_DEBUG( + logger_, + "No stationary objects found in obstacle_check_distance and Ego is not in " + "terminal of current lanes"); return false; } -bool NormalLaneChange::isVehicleStuckByObstacle(const lanelet::ConstLanelets & current_lanes) const +bool NormalLaneChange::isVehicleStuck(const lanelet::ConstLanelets & current_lanes) const { if (current_lanes.empty()) { return false; // can not check @@ -1736,7 +1755,7 @@ bool NormalLaneChange::isVehicleStuckByObstacle(const lanelet::ConstLanelets & c getCommonParam().base_link2front + DETECTION_DISTANCE_MARGIN; RCLCPP_DEBUG(logger_, "max_lane_change_length: %f, max_acc: %f", max_lane_change_length, max_acc); - return isVehicleStuckByObstacle(current_lanes, detection_distance); + return isVehicleStuck(current_lanes, detection_distance); } void NormalLaneChange::setStopPose(const Pose & stop_pose) From 16217e849b65047525c2c080cfca9ab77149f788 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 18 Oct 2023 10:27:17 +0900 Subject: [PATCH 494/547] fix(intersection): fix misuse of original path index to interpolated path (#5334) Signed-off-by: Mamoru Sobue --- .../src/util.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 08c818c2e49ce..bd31f758f6d0d 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -1327,6 +1327,19 @@ TimeDistanceArray calcIntersectionPassingTime( dist_sum = 0.0; double passing_time = time_delay; time_distance_array.emplace_back(passing_time, dist_sum); + + // NOTE: `reference_path` is resampled in `reference_smoothed_path`, so + // `last_intersection_stop_line_candidate_idx` makes no sense + const auto last_intersection_stop_line_candidate_point_orig = + path.points.at(last_intersection_stop_line_candidate_idx).point.pose; + const auto last_intersection_stop_line_candidate_nearest_ind_opt = motion_utils::findNearestIndex( + smoothed_reference_path.points, last_intersection_stop_line_candidate_point_orig, 3.0, M_PI_4); + if (!last_intersection_stop_line_candidate_nearest_ind_opt) { + return time_distance_array; + } + const auto last_intersection_stop_line_candidate_nearest_ind = + last_intersection_stop_line_candidate_nearest_ind_opt.value(); + for (size_t i = 1; i < smoothed_reference_path.points.size(); ++i) { const auto & p1 = smoothed_reference_path.points.at(i - 1); const auto & p2 = smoothed_reference_path.points.at(i); @@ -1338,7 +1351,7 @@ TimeDistanceArray calcIntersectionPassingTime( const double average_velocity = (p1.point.longitudinal_velocity_mps + p2.point.longitudinal_velocity_mps) / 2.0; const double minimum_ego_velocity_division = - (use_upstream_velocity && i > last_intersection_stop_line_candidate_idx) + (use_upstream_velocity && i > last_intersection_stop_line_candidate_nearest_ind) ? minimum_upstream_velocity /* to avoid null division */ : minimum_ego_velocity; const double passing_velocity = From f0ea398bd5fe71e51eb80116f0a29c035e2843a4 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 18 Oct 2023 10:27:43 +0900 Subject: [PATCH 495/547] fix(intersection): fix infinite loop in tsort (#5332) Signed-off-by: Mamoru Sobue --- .../src/util.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index bd31f758f6d0d..403509a926022 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -605,6 +605,8 @@ mergeLaneletsByTopologicalSort( id2lanelet[id] = lanelet; ind++; } + // NOTE: this function aims to traverse the detection lanelet backward from ego side to farthest + // side, so if lane B follows lane A on the routing_graph, adj[B][A] = true for (const auto & lanelet : lanelets) { const auto & followings = routing_graph_ptr->following(lanelet); const int dst = lanelet.id(); @@ -628,18 +630,25 @@ mergeLaneletsByTopologicalSort( if (!has_no_previous(src)) { continue; } + // So `src` has no previous lanelets branches[(ind2id[src])] = std::vector{}; auto & branch = branches[(ind2id[src])]; lanelet::Id node_iter = ind2id[src]; + std::set visited_ids; while (true) { const auto & destinations = adjacency[(id2ind[node_iter])]; - // NOTE: assuming detection lanelets have only one previous lanelet + // NOTE: assuming detection lanelets have only one "previous"(on the routing_graph) lanelet const auto next = std::find(destinations.begin(), destinations.end(), true); if (next == destinations.end()) { branch.push_back(node_iter); break; } + if (visited_ids.find(node_iter) != visited_ids.end()) { + // loop detected + break; + } branch.push_back(node_iter); + visited_ids.insert(node_iter); node_iter = ind2id[std::distance(destinations.begin(), next)]; } } From 22a5ced2c7a4d4b9421b508cf90e4d9018a073c9 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Wed, 18 Oct 2023 10:48:05 +0900 Subject: [PATCH 496/547] feat(behavior_path_planner): add postProcess() in scene module interface (#5336) * add postProcess Signed-off-by: kyoichi-sugahara * modify comment Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../include/behavior_path_planner/planner_manager.hpp | 2 ++ .../scene_module/scene_module_interface.hpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index 72c32039da627..4b9b0bff4be63 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -272,6 +272,8 @@ class PlannerManager const auto result = module_ptr->run(); module_ptr->unlockRTCCommand(); + module_ptr->postProcess(); + module_ptr->updateCurrentState(); module_ptr->publishRTCStatus(); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp index 077f7a3a763d5..68faa3806f614 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp @@ -131,6 +131,12 @@ class SceneModuleInterface */ virtual void updateData() {} + /** + * @brief After executing run(), update the module-specific status and/or data used for internal + * processing that are not defined in ModuleStatus. + */ + virtual void postProcess() {} + /** * @brief Execute module. Once this function is executed, * it will continue to run as long as it is in the RUNNING state. From a39b20d95200527726284101a0030dad08aa5a59 Mon Sep 17 00:00:00 2001 From: Shumpei Wakabayashi <42209144+shmpwk@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:00:15 +0900 Subject: [PATCH 497/547] fix(detected_object_validation): consider shoulder lanelet in object lanelet filter (#5324) * fix(detected_object_validation): consider shoulder lanelet in object lanelet filter Signed-off-by: Shumpei Wakabayashi * style(pre-commit): autofix --------- Signed-off-by: Shumpei Wakabayashi Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../detected_object_filter/object_lanelet_filter.hpp | 1 + .../src/object_lanelet_filter.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp b/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp index 600da31df5868..ae6162542a1c3 100644 --- a/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp +++ b/perception/detected_object_validation/include/detected_object_filter/object_lanelet_filter.hpp @@ -51,6 +51,7 @@ class ObjectLaneletFilterNode : public rclcpp::Node lanelet::LaneletMapPtr lanelet_map_ptr_; lanelet::ConstLanelets road_lanelets_; + lanelet::ConstLanelets shoulder_lanelets_; std::string lanelet_frame_id_; tf2_ros::Buffer tf_buffer_; diff --git a/perception/detected_object_validation/src/object_lanelet_filter.cpp b/perception/detected_object_validation/src/object_lanelet_filter.cpp index 3b78ae449e2da..f9b208cca02bc 100644 --- a/perception/detected_object_validation/src/object_lanelet_filter.cpp +++ b/perception/detected_object_validation/src/object_lanelet_filter.cpp @@ -62,6 +62,7 @@ void ObjectLaneletFilterNode::mapCallback( lanelet::utils::conversion::fromBinMsg(*map_msg, lanelet_map_ptr_); const lanelet::ConstLanelets all_lanelets = lanelet::utils::query::laneletLayer(lanelet_map_ptr_); road_lanelets_ = lanelet::utils::query::roadLanelets(all_lanelets); + shoulder_lanelets_ = lanelet::utils::query::shoulderLanelets(all_lanelets); } void ObjectLaneletFilterNode::objectCallback( @@ -87,7 +88,10 @@ void ObjectLaneletFilterNode::objectCallback( // calculate convex hull const auto convex_hull = getConvexHull(transformed_objects); // get intersected lanelets - lanelet::ConstLanelets intersected_lanelets = getIntersectedLanelets(convex_hull, road_lanelets_); + lanelet::ConstLanelets intersected_road_lanelets = + getIntersectedLanelets(convex_hull, road_lanelets_); + lanelet::ConstLanelets intersected_shoulder_lanelets = + getIntersectedLanelets(convex_hull, shoulder_lanelets_); int index = 0; for (const auto & object : transformed_objects.objects) { @@ -101,7 +105,9 @@ void ObjectLaneletFilterNode::objectCallback( polygon.outer().emplace_back(point_transformed.x, point_transformed.y); } polygon.outer().push_back(polygon.outer().front()); - if (isPolygonOverlapLanelets(polygon, intersected_lanelets)) { + if (isPolygonOverlapLanelets(polygon, intersected_road_lanelets)) { + output_object_msg.objects.emplace_back(input_msg->objects.at(index)); + } else if (isPolygonOverlapLanelets(polygon, intersected_shoulder_lanelets)) { output_object_msg.objects.emplace_back(input_msg->objects.at(index)); } } else { From 264accb7086239e6c128478bf0f548138db59068 Mon Sep 17 00:00:00 2001 From: melike tanrikulu <41450930+meliketanrikulu@users.noreply.github.com> Date: Wed, 18 Oct 2023 11:04:28 +0300 Subject: [PATCH 498/547] fix(gnss_poser): fix_transform_direction_problem (#5033) * fix(gnss_poser)fix_transform_direction_problem Signed-off-by: meliketanrikulu * style(pre-commit): autofix * fix(gnss_poser)update comment line Signed-off-by: meliketanrikulu --------- Signed-off-by: meliketanrikulu Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- sensing/gnss_poser/src/gnss_poser_core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensing/gnss_poser/src/gnss_poser_core.cpp b/sensing/gnss_poser/src/gnss_poser_core.cpp index 599a017bffed7..3a18dca815f12 100644 --- a/sensing/gnss_poser/src/gnss_poser_core.cpp +++ b/sensing/gnss_poser/src/gnss_poser_core.cpp @@ -139,11 +139,11 @@ void GNSSPoser::callbackNavSatFix( tf2::Transform tf_map2gnss_antenna{}; tf2::fromMsg(gnss_antenna_pose, tf_map2gnss_antenna); - // get TF from base_link to gnss_antenna + // get TF from gnss_antenna to base_link auto tf_gnss_antenna2base_link_msg_ptr = std::make_shared(); getStaticTransform( - gnss_frame_, base_frame_, tf_gnss_antenna2base_link_msg_ptr, nav_sat_fix_msg_ptr->header.stamp); + base_frame_, gnss_frame_, tf_gnss_antenna2base_link_msg_ptr, nav_sat_fix_msg_ptr->header.stamp); tf2::Transform tf_gnss_antenna2base_link{}; tf2::fromMsg(tf_gnss_antenna2base_link_msg_ptr->transform, tf_gnss_antenna2base_link); From 5d175014c4972fead53a39ffb46d20fb8034f5e0 Mon Sep 17 00:00:00 2001 From: "Takagi, Isamu" <43976882+isamu-takagi@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:22:45 +0900 Subject: [PATCH 499/547] feat(system_diagnostic_graph): change config file format (#5340) Signed-off-by: Takagi, Isamu --- system/system_diagnostic_graph/CMakeLists.txt | 8 +- system/system_diagnostic_graph/README.md | 38 ++- .../config/default.param.yaml | 2 + .../doc/overview.drawio.svg | 217 ++++++------- .../example/example.launch.xml | 4 +- .../example/example1.yaml | 41 --- .../example/example2.yaml | 32 -- .../example/example_0.yaml | 32 ++ .../example/example_1.yaml | 23 ++ .../example/example_2.yaml | 8 + .../example/example_diags.py | 64 ++++ .../example/publisher.py | 83 ----- .../src/core/config.cpp | 249 +++++++++++---- .../src/core/config.hpp | 90 ++++-- .../src/core/debug.cpp | 28 +- .../system_diagnostic_graph/src/core/expr.cpp | 284 ------------------ .../src/core/exprs.cpp | 216 +++++++++++++ .../src/core/{expr.hpp => exprs.hpp} | 46 ++- .../src/core/graph.cpp | 168 ++++++++--- .../src/core/graph.hpp | 27 +- .../src/core/modes.cpp | 75 +++++ .../src/core/{update.hpp => modes.hpp} | 45 ++- .../system_diagnostic_graph/src/core/node.cpp | 94 ------ .../system_diagnostic_graph/src/core/node.hpp | 84 ------ .../src/core/nodes.cpp | 157 ++++++++++ .../src/core/nodes.hpp | 114 +++++++ .../src/core/types.hpp | 7 +- .../src/core/update.cpp | 116 ------- system/system_diagnostic_graph/src/main.cpp | 35 ++- system/system_diagnostic_graph/src/main.hpp | 10 +- system/system_diagnostic_graph/src/tool.hpp | 1 - .../system_diagnostic_monitor/CMakeLists.txt | 7 - system/system_diagnostic_monitor/README.md | 1 - .../config/autoware.yaml | 74 ----- .../config/control.yaml | 10 - .../config/localization.yaml | 5 - .../system_diagnostic_monitor/config/map.yaml | 7 - .../config/perception.yaml | 7 - .../config/planning.yaml | 15 - .../config/system.yaml | 7 - .../config/vehicle.yaml | 10 - .../system_diagnostic_monitor.launch.xml | 6 - system/system_diagnostic_monitor/package.xml | 23 -- .../script/component_state_diagnostics.py | 79 ----- 44 files changed, 1299 insertions(+), 1350 deletions(-) delete mode 100644 system/system_diagnostic_graph/example/example1.yaml delete mode 100644 system/system_diagnostic_graph/example/example2.yaml create mode 100644 system/system_diagnostic_graph/example/example_0.yaml create mode 100644 system/system_diagnostic_graph/example/example_1.yaml create mode 100644 system/system_diagnostic_graph/example/example_2.yaml create mode 100755 system/system_diagnostic_graph/example/example_diags.py delete mode 100755 system/system_diagnostic_graph/example/publisher.py delete mode 100644 system/system_diagnostic_graph/src/core/expr.cpp create mode 100644 system/system_diagnostic_graph/src/core/exprs.cpp rename system/system_diagnostic_graph/src/core/{expr.hpp => exprs.hpp} (68%) create mode 100644 system/system_diagnostic_graph/src/core/modes.cpp rename system/system_diagnostic_graph/src/core/{update.hpp => modes.hpp} (53%) delete mode 100644 system/system_diagnostic_graph/src/core/node.cpp delete mode 100644 system/system_diagnostic_graph/src/core/node.hpp create mode 100644 system/system_diagnostic_graph/src/core/nodes.cpp create mode 100644 system/system_diagnostic_graph/src/core/nodes.hpp delete mode 100644 system/system_diagnostic_graph/src/core/update.cpp delete mode 100644 system/system_diagnostic_monitor/CMakeLists.txt delete mode 100644 system/system_diagnostic_monitor/README.md delete mode 100644 system/system_diagnostic_monitor/config/autoware.yaml delete mode 100644 system/system_diagnostic_monitor/config/control.yaml delete mode 100644 system/system_diagnostic_monitor/config/localization.yaml delete mode 100644 system/system_diagnostic_monitor/config/map.yaml delete mode 100644 system/system_diagnostic_monitor/config/perception.yaml delete mode 100644 system/system_diagnostic_monitor/config/planning.yaml delete mode 100644 system/system_diagnostic_monitor/config/system.yaml delete mode 100644 system/system_diagnostic_monitor/config/vehicle.yaml delete mode 100644 system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml delete mode 100644 system/system_diagnostic_monitor/package.xml delete mode 100755 system/system_diagnostic_monitor/script/component_state_diagnostics.py diff --git a/system/system_diagnostic_graph/CMakeLists.txt b/system/system_diagnostic_graph/CMakeLists.txt index 54aebe6f37f73..81d8ba39b3d1b 100644 --- a/system/system_diagnostic_graph/CMakeLists.txt +++ b/system/system_diagnostic_graph/CMakeLists.txt @@ -5,13 +5,13 @@ find_package(autoware_cmake REQUIRED) autoware_package() ament_auto_add_executable(aggregator - src/main.cpp src/core/config.cpp src/core/debug.cpp src/core/graph.cpp - src/core/node.cpp - src/core/expr.cpp - src/core/update.cpp + src/core/nodes.cpp + src/core/exprs.cpp + src/core/modes.cpp + src/main.cpp ) ament_auto_add_executable(converter diff --git a/system/system_diagnostic_graph/README.md b/system/system_diagnostic_graph/README.md index c2c99938348e2..df22121a778fb 100644 --- a/system/system_diagnostic_graph/README.md +++ b/system/system_diagnostic_graph/README.md @@ -2,28 +2,42 @@ ## Overview -The system diagnostic graph node subscribes to diagnostic status and publishes aggregated diagnostic status. +The system diagnostic graph node subscribes to diagnostic array and publishes aggregated diagnostic graph. As shown in the diagram below, this node introduces extra diagnostic status for intermediate functional unit. Diagnostic status dependencies will be directed acyclic graph (DAG). ![overview](./doc/overview.drawio.svg) +## Operation mode availability + +For MRM, this node publishes the status of the top-level functional units in the dedicated message. +Therefore, the diagnostic graph must contain functional units with the following names. +This feature breaks the generality of the graph and may be changed to a plugin or another node in the future. + +- /autoware/operation/stop +- /autoware/operation/autonomous +- /autoware/operation/local +- /autoware/operation/remote +- /autoware/operation/emergency-stop +- /autoware/operation/comfortable-stop +- /autoware/operation/pull-over + ## Interface -| Interface Type | Interface Name | Data Type | Description | -| -------------- | --------------------------- | --------------------------------------- | ------------------ | -| subscription | `/diagnostics` | `diagnostic_msgs/msg/DiagnosticArray` | Input diagnostics. | -| publisher | `/diagnostics_graph/status` | `diagnostic_msgs/msg/DiagnosticArray` | Graph status. | -| publisher | `/diagnostics_graph/struct` | `tier4_system_msgs/msg/DiagnosticGraph` | Graph structure. | +| Interface Type | Interface Name | Data Type | Description | +| -------------- | ------------------------------------- | ------------------------------------------------- | ------------------ | +| subscription | `/diagnostics` | `diagnostic_msgs/msg/DiagnosticArray` | Diagnostics input. | +| publisher | `/diagnostics_graph` | `tier4_system_msgs/msg/DiagnosticGraph` | Diagnostics graph. | +| publisher | `/system/operation_mode/availability` | `tier4_system_msgs/msg/OperationModeAvailability` | mode availability. | ## Parameters -| Parameter Name | Data Type | Description | -| ------------------ | --------- | ------------------------------------------ | -| `graph_file` | `string` | Path of the config file. | -| `rate` | `double` | Rate of aggregation and topic publication. | -| `status_qos_depth` | `uint` | QoS depth of status topic. | -| `source_qos_depth` | `uint` | QoS depth of source topic. | +| Parameter Name | Data Type | Description | +| ----------------- | --------- | ------------------------------------------ | +| `graph_file` | `string` | Path of the config file. | +| `rate` | `double` | Rate of aggregation and topic publication. | +| `input_qos_depth` | `uint` | QoS depth of input array topic. | +| `graph_qos_depth` | `uint` | QoS depth of output graph topic. | ## Graph file format diff --git a/system/system_diagnostic_graph/config/default.param.yaml b/system/system_diagnostic_graph/config/default.param.yaml index f02247374b14e..9fd572c7926fa 100644 --- a/system/system_diagnostic_graph/config/default.param.yaml +++ b/system/system_diagnostic_graph/config/default.param.yaml @@ -1,5 +1,7 @@ /**: ros__parameters: + mode_availability: true + mode: psim rate: 1.0 input_qos_depth: 1000 graph_qos_depth: 1 diff --git a/system/system_diagnostic_graph/doc/overview.drawio.svg b/system/system_diagnostic_graph/doc/overview.drawio.svg index aa6be5a48b08e..e971c052dedc8 100644 --- a/system/system_diagnostic_graph/doc/overview.drawio.svg +++ b/system/system_diagnostic_graph/doc/overview.drawio.svg @@ -4,13 +4,13 @@ xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="921px" - height="561px" - viewBox="-0.5 -0.5 921 561" - content="<mxfile><diagram id="pC5EcGJeU-t8W7RoQB87" name="Page-1">3VtNc6M4EP01vpsPYfvoOJ7Z2sruTiVbtccpBRTQDtAuIcfx/vqVQBiQcGKXGdDkZGgakF4/tfpJeOZtsrevDO+SPyAi6cydR28z737mug5yFuJHWo6VBS1RZYgZjZRTY3ii/xFlnCvrnkak6DhygJTTXdcYQp6TkHdsmDE4dN1eIO2+dYdjYhieQpya1n9oxJPKunQXjf03QuOkfrMTrKorGa6dVU+KBEdwaJm87czbMABeHWVvG5JK8Gpcqvu+nLl6ahgjOb/kBtev7njF6V51TjhHFMc5FJyGxfdYhvB7wTHfF6rN/FgDwWCfR0Q+az7z7g4J5eRph0N59SDuE7aEZ6k4c8QhTmmci+OUvIi23b0SJl6A07Uyc5D+hbid5vFD6XOPhCXCRVK+Qj7D7J/qsnwaeWuZVH+/EsgIZ0fhoq76Cvpj9/TQBHJVRydpBREFyogVeeLTkxt8xYGC+Azc6AO4PwXCSw3hHoiXfg/EjjsExIGBIYnE6FWnwHgCMeQ43TbWuy7Kjc8DSMhKXP4lnB9VKsJ7Dl3kz6JWwJ6Fqh11fuCYxUS5eYoRso3vYstIijl97WagPqDKW9eM4WPLYQc050Xryd+koQmZp48KT8sb1/mLg6oFTchOXbksigtjoPwtQuHOH+j9+vG2UTIAww2KL02KO31ZxB+C4UtrGa5Y0GW4ZwXDkaPn/fcZHgTv+t/O8NUvFUW0tCKK/uq6KH7gf3MUa6haeeoLg7IFdmQqX0/UY2YqzzH6bw3HvZ5MtbCC4wHqhqyebC6dizX/2znu/lpRXNkRxcV1UfzA//YoemczFTwLeReKsAlFTrgQzBTyyRNXMO/i4bljJi7fXsojaxOXkYiCKxNXMDDlTbX9DQpJcyLEdoat4LkuJcblub1i2esRy2hwKXExUKYeXYt+5ZCBXBabZxAR66jkzsekksWqdGUVlVYGlTaQvYjO4+dyDi7kOt3UXNKn31G5VD/DQi759X6DFVyqW9Pi0jYjLM7Doy1EOpFkEiLZK118ewWoo88jEwtQ35Quj7Dn00+4OrdHXVzx7dUofo9GmTBJmjLgdzjK7bYftlRui2DKJGmvCPB7RIA/+I7ZxUCZIqAhkmUMGjcVLQ1g1n/eG5iIrvBuxwvO4AfZQApMWHLIJbVeaJpqpnpjOxQAEdaztZ3RKCp52Yd0NxYDgG1MeMgEu2+De4j9bX+SfaPeQJ1FcoSa5sMixHE0sKu8ou5q8L51kcvxryuONP+bi6Oaeu11XZrKUeLOn2XH9uWKV4jLH5xHkjsiaZGsJBHuKaM+zThFmoh1PHOcnj4qG3qgIns3v5BrjtPp6rO6NS0GP5XK1Y7aTN9BHbU2Q6b02TIGrIzDTvBjcnh0DdRTd5xqk8HRsVcCob5tGju+hNG/bPEW130Jo/nfPoOZ+kztTAY4k4TNn4tyesARZpPzXV8YHbXQRoEB1V+Pn3f+Pse8MepstLA3tyx7cosdS4d6xTX10iEylWnPVw87RiJqx2cPerXxM7eDZxKL+p8GFbjN/zW87f8=</diagram></mxfile>" + height="521px" + viewBox="-0.5 -0.5 921 521" + content="<mxfile><diagram id="pC5EcGJeU-t8W7RoQB87" name="Page-1">3VpNb6MwEP01ufMdckyTtntod1ftSnusXHDBrWEi43ywv37tYELAtE0UCqiXyDzGxn7zPB6PMrEXye6WoVV8DyGmE8sIdxN7ObEs3zXErwTyAvBMtwAiRsICMivgkfzDClT9ojUJcVYz5ACUk1UdDCBNccBrGGIMtnWzF6D1r65QhDXgMUBUR/+SkMdqWda0wn9gEsXll01vVrxJUGmsVpLFKITtEWRfT+wFA+BFK9ktMJXclbwU/W7eeXuYGMMpP6WD5RQ9Noiu1eKEcUhQlELGSZA9RdKDarI8LxlgsE5DLAcxJvbVNiYcP65QIN9uRQeBxTyh4skUTURJlIo2xS9iUlcbzMTIiM4VzEHaZ6I7SaO7vc3SFUiIsnj/CTmGvjC1Vjka3h1BaqG3GBLMWS5M1FtHcZ7XH7eVB2elW+Ij77kliJRqosPIFbGiobht59mdtfGc5RnHiWjACjPECaRPidgrAkAbRCh6JpTw/FuQ79fJ93XyfaeFfLML8i33E5F/R4Ydo1eKPY1DHIqYqR6B8RgiSBG9rtCrOsuVzR1Iyva8vGLOc3UAoDWHOvPvspbBmgVqHmVU5ohFWJnZShFyjh9yyzAV+3JTj/ttRO27zhlD+ZHBCkjKs6ORf0ugcplr1H1m+41ofZ69aBQzqFx2WMppXpxqG+WPcIVl3JHl/OGyXdJFAG9G8JYQbraFcKcLhfujVbhttCjcHoXCvWlDsd7HCp86H9pfrPCSqiOF3zDYz2AcGveMATVum9r6R6Nxu0Xj01FovKnZ0hWnRvGG/eUatzWNw3PGUSDcJe5cmIsrkUg1h5d6I2NpSwq/TurOeKXujlbqmnSNM6VudCx1PbNfQSZljkVin6BR6LyZtvSrcz2lk7JMIYH1hfeeL6DmQEMf1DimRg3DCXA8OC3Nu1yvtHjWxZHx3IjntBzupXe6i3gn60I/P0tdGAEkCUrD0Smk1zTR0aMuhQDRwVk5rHiQcDLeEojTUgJxOi+BnEyUfiS9Qi4LcW8j2mFNLfW7w3yNovnPpcaJWAqvLzzjDN7wAigwgaSQSpG9EEobUFmzDARBmLVULRMShnuFtjFd90UHZGt5oqmT3Va77KJ06cyG2LetjnqXyR6uAp/m7tMG10WAUZ0qui+9UphnXinMjq8UrqVtvUyW8IeOR83CUK9nmzve27LbdlseR/HTbxwhQxc/XT1vU8VPDyVSselztg/vKERscMFPvQEPYNfTqPr18H3PX015dn/nr6dTff9wPzr5fWW8nch9WP7lo9jY1f9m7Ov/</diagram></mxfile>" > - + @@ -20,243 +20,231 @@ >
- /diagnostics_graph_status + /diagnostics_graph
- /diagnostics_graph_status + /diagnostics_graph
- +
- /diagnostics + /system/operation_mode/availability
- /diagnostics + /system/operation_mode/availability
- - - +
-
+
- Top LiDAR + /diagnostics
- Top LiDAR + /diagnostics - - - - - + + +
- Front LiDAR + Top LiDAR
- Front LiDAR + Top LiDAR
- - - - - + + +
- Front obstacle detection + Front LiDAR
- Front obstacle detec... + Front LiDAR
- - - + + +
- Pose estimation + obstacle detection
- Pose estimation + obstacle detection
- - - + + +
- Autonomous mode + pose estimation
- Autonomous mode + pose estimation
- - - +
- Comfortable stop + autonomous
- Comfortable stop + autonomous
- - - +
- Emergncy stop + remote
- Emergncy stop + remote
- - - + + +
- Route + remote command
- Route + remote command
- - - +
- Joystick mode + local
- Joystick mode + local
- - - + + +
- Joystick + joystick command
- Joystick + joystick command
- +
@@ -265,73 +253,38 @@
- AND + AND - - - + + +
- Filter by use case and system state -
-
-
-
- Filter by use case and system state -
-
- - - - - - -
-
-
- Stop mode -
-
-
-
- Stop mode -
-
- - - - -
-
-
- Error report + stop
- Error report + stop
- - - + + +
@@ -340,16 +293,16 @@
- Front radar + Front radar - +
@@ -358,27 +311,25 @@
- OR + OR - - - +
- Front obstacle prediction + MRM
- Front obstacle predi... + MRM
diff --git a/system/system_diagnostic_graph/example/example.launch.xml b/system/system_diagnostic_graph/example/example.launch.xml index 461842f020ded..1456bfd5c6593 100644 --- a/system/system_diagnostic_graph/example/example.launch.xml +++ b/system/system_diagnostic_graph/example/example.launch.xml @@ -1,6 +1,6 @@ - + - + diff --git a/system/system_diagnostic_graph/example/example1.yaml b/system/system_diagnostic_graph/example/example1.yaml deleted file mode 100644 index 25053b5f7e12c..0000000000000 --- a/system/system_diagnostic_graph/example/example1.yaml +++ /dev/null @@ -1,41 +0,0 @@ -files: - - { package: system_diagnostic_graph, path: example/example2.yaml } - -nodes: - - name: /autoware/diagnostics - type: and - list: - - { type: unit, name: /autoware/operation/stop } - - { type: unit, name: /autoware/operation/autonomous } - - { type: unit, name: /autoware/operation/local } - - { type: unit, name: /autoware/operation/remote } - - { type: unit, name: /autoware/operation/emergency-stop } - - { type: unit, name: /autoware/operation/comfortable-stop } - - - name: /autoware/operation/stop - type: debug-ok - - - name: /autoware/operation/autonomous - type: and - list: - - { type: unit, name: /autoware/localization } - - { type: unit, name: /autoware/planning } - - { type: unit, name: /autoware/perception } - - - name: /autoware/operation/local - type: debug-ok - - - name: /autoware/operation/remote - type: and - list: - - { type: diag, hardware: test, name: /external/remote_command } - - - name: /autoware/operation/emergency-stop - type: debug-ok - - - name: /autoware/operation/comfortable-stop - type: and - list: - - { type: unit, name: /autoware/localization } - - { type: unit, name: /autoware/planning } - - { type: unit, name: /autoware/perception/front-obstacle-detection } diff --git a/system/system_diagnostic_graph/example/example2.yaml b/system/system_diagnostic_graph/example/example2.yaml deleted file mode 100644 index 2af09c0cb69a2..0000000000000 --- a/system/system_diagnostic_graph/example/example2.yaml +++ /dev/null @@ -1,32 +0,0 @@ -nodes: - - name: /autoware/localization - type: and - list: - - { type: diag, hardware: test, name: /sensing/lidars/top } - - - name: /autoware/planning - type: and - list: - - { type: unit, name: /autoware/planning/route } - - - name: /autoware/planning/route - type: and - list: - - { type: diag, hardware: test, name: /planning/route } - - - name: /autoware/perception - type: and - list: - - { type: unit, name: /autoware/perception/front-obstacle-detection } - - { type: unit, name: /autoware/perception/front-obstacle-prediction } - - - name: /autoware/perception/front-obstacle-detection - type: or - list: - - { type: diag, hardware: test, name: /sensing/lidars/front } - - { type: diag, hardware: test, name: /sensing/radars/front } - - - name: /autoware/perception/front-obstacle-prediction - type: and - list: - - { type: diag, hardware: test, name: /sensing/lidars/front } diff --git a/system/system_diagnostic_graph/example/example_0.yaml b/system/system_diagnostic_graph/example/example_0.yaml new file mode 100644 index 0000000000000..0ee6e59c8e121 --- /dev/null +++ b/system/system_diagnostic_graph/example/example_0.yaml @@ -0,0 +1,32 @@ +files: + - { package: system_diagnostic_graph, path: example/example_1.yaml } + - { package: system_diagnostic_graph, path: example/example_2.yaml } + +nodes: + - path: /autoware/modes/stop + type: debug-ok + + - path: /autoware/modes/autonomous + type: and + list: + - { type: link, path: /functions/pose_estimation } + - { type: link, path: /functions/obstacle_detection } + + - path: /autoware/modes/local + type: and + list: + - { type: link, path: /external/joystick_command } + + - path: /autoware/modes/remote + type: and + list: + - { type: link, path: /external/remote_command } + + - path: /autoware/modes/emergency-stop + type: debug-ok + + - path: /autoware/modes/comfortable-stop + type: debug-ok + + - path: /autoware/modes/pull-over + type: debug-ok diff --git a/system/system_diagnostic_graph/example/example_1.yaml b/system/system_diagnostic_graph/example/example_1.yaml new file mode 100644 index 0000000000000..5ba93ec3059e4 --- /dev/null +++ b/system/system_diagnostic_graph/example/example_1.yaml @@ -0,0 +1,23 @@ +nodes: + - path: /functions/pose_estimation + type: and + list: + - { type: link, path: /sensing/lidars/top } + + - path: /functions/obstacle_detection + type: or + list: + - { type: link, path: /sensing/lidars/front } + - { type: link, path: /sensing/radars/front } + + - path: /sensing/lidars/top + type: diag + name: "lidar_driver/top: status" + + - path: /sensing/lidars/front + type: diag + name: "lidar_driver/front: status" + + - path: /sensing/radars/front + type: diag + name: "radar_driver/front: status" diff --git a/system/system_diagnostic_graph/example/example_2.yaml b/system/system_diagnostic_graph/example/example_2.yaml new file mode 100644 index 0000000000000..f61f4accbfec8 --- /dev/null +++ b/system/system_diagnostic_graph/example/example_2.yaml @@ -0,0 +1,8 @@ +nodes: + - path: /external/joystick_command + type: diag + name: "external_command_checker: joystick_command" + + - path: /external/remote_command + type: diag + name: "external_command_checker: remote_command" diff --git a/system/system_diagnostic_graph/example/example_diags.py b/system/system_diagnostic_graph/example/example_diags.py new file mode 100755 index 0000000000000..52d6189a63f30 --- /dev/null +++ b/system/system_diagnostic_graph/example/example_diags.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 + +# Copyright 2023 The Autoware Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from diagnostic_msgs.msg import DiagnosticArray +from diagnostic_msgs.msg import DiagnosticStatus +import rclpy +import rclpy.node +import rclpy.qos + + +class DummyDiagnostics(rclpy.node.Node): + def __init__(self, names): + super().__init__("dummy_diagnostics") + qos = rclpy.qos.qos_profile_system_default + self.diags = self.create_publisher(DiagnosticArray, "/diagnostics", qos) + self.timer = self.create_timer(0.5, self.on_timer) + self.count = 0 + self.array = [self.create_status(name) for name in names] + + def on_timer(self): + error_index = int(self.count / 10) + for index, status in enumerate(self.array, 1): + if index == error_index: + status.level = DiagnosticStatus.ERROR + status.message = "ERROR" + else: + status.level = DiagnosticStatus.OK + status.message = "OK" + + diagnostics = DiagnosticArray() + diagnostics.header.stamp = self.get_clock().now().to_msg() + diagnostics.status = self.array + self.count = (self.count + 1) % 60 + self.diags.publish(diagnostics) + + @staticmethod + def create_status(name: str): + return DiagnosticStatus(name=name, hardware_id="example") + + +if __name__ == "__main__": + diags = [ + "lidar_driver/top: status", + "lidar_driver/front: status", + "radar_driver/front: status", + "external_command_checker: joystick_command", + "external_command_checker: remote_command", + ] + rclpy.init() + rclpy.spin(DummyDiagnostics(diags)) + rclpy.shutdown() diff --git a/system/system_diagnostic_graph/example/publisher.py b/system/system_diagnostic_graph/example/publisher.py deleted file mode 100755 index 2e73237284508..0000000000000 --- a/system/system_diagnostic_graph/example/publisher.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2023 The Autoware Contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse - -from diagnostic_msgs.msg import DiagnosticArray -from diagnostic_msgs.msg import DiagnosticStatus -import rclpy -import rclpy.node -import rclpy.qos - - -class DummyDiagnostics(rclpy.node.Node): - def __init__(self, array): - super().__init__("dummy_diagnostics") - qos = rclpy.qos.qos_profile_system_default - self.diags = self.create_publisher(DiagnosticArray, "/diagnostics", qos) - self.timer = self.create_timer(0.5, self.on_timer) - self.array = [self.create_status(*data) for data in array] - - def on_timer(self): - diagnostics = DiagnosticArray() - diagnostics.header.stamp = self.get_clock().now().to_msg() - diagnostics.status = self.array - self.diags.publish(diagnostics) - - @staticmethod - def create_status(name: str, level: int): - return DiagnosticStatus(level=level, name=name, message="OK", hardware_id="test") - - -if __name__ == "__main__": - data = { - "ok": [ - ("/sensing/lidars/top", DiagnosticStatus.OK), - ("/sensing/lidars/front", DiagnosticStatus.OK), - ("/sensing/radars/front", DiagnosticStatus.OK), - ("/planning/route", DiagnosticStatus.OK), - ("/external/remote_command", DiagnosticStatus.OK), - ], - "front-lidar": [ - ("/sensing/lidars/top", DiagnosticStatus.OK), - ("/sensing/lidars/front", DiagnosticStatus.ERROR), - ("/sensing/radars/front", DiagnosticStatus.OK), - ("/planning/route", DiagnosticStatus.OK), - ("/external/remote_command", DiagnosticStatus.OK), - ], - "front-radar": [ - ("/sensing/lidars/top", DiagnosticStatus.OK), - ("/sensing/lidars/front", DiagnosticStatus.OK), - ("/sensing/radars/front", DiagnosticStatus.ERROR), - ("/planning/route", DiagnosticStatus.OK), - ("/external/remote_command", DiagnosticStatus.OK), - ], - "front": [ - ("/sensing/lidars/top", DiagnosticStatus.OK), - ("/sensing/lidars/front", DiagnosticStatus.ERROR), - ("/sensing/radars/front", DiagnosticStatus.ERROR), - ("/planning/route", DiagnosticStatus.OK), - ("/external/remote_command", DiagnosticStatus.OK), - ], - } - - parser = argparse.ArgumentParser() - parser.add_argument("--data", default="ok") - args = parser.parse_args() - - rclpy.init() - rclpy.spin(DummyDiagnostics(data[args.data])) - rclpy.shutdown() diff --git a/system/system_diagnostic_graph/src/core/config.cpp b/system/system_diagnostic_graph/src/core/config.cpp index b33a7deb66fb6..305860af32840 100644 --- a/system/system_diagnostic_graph/src/core/config.cpp +++ b/system/system_diagnostic_graph/src/core/config.cpp @@ -17,106 +17,245 @@ #include #include -#include #include #include +#include + +// DEBUG +#include namespace system_diagnostic_graph { -ConfigError create_error(const FileConfig & config, const std::string & message) +ErrorMarker::ErrorMarker(const std::string & file) +{ + file_ = file; +} + +std::string ErrorMarker::str() const +{ + if (type_.empty()) { + return file_; + } + + std::string result = type_; + for (const auto & index : indices_) { + result += "-" + std::to_string(index); + } + return result + " in " + file_; +} + +ErrorMarker ErrorMarker::type(const std::string & type) const { - const std::string marker = config ? "File:" + config->path : "Parameter"; - return ConfigError(message + " (" + marker + ")"); + ErrorMarker mark = *this; + mark.type_ = type; + return mark; } -ConfigError create_error(const NodeConfig & config, const std::string & message) +ErrorMarker ErrorMarker::index(size_t index) const { - const std::string marker = "File:" + config->path + ", Node:" + config->name; - return ConfigError(message + " (" + marker + ")"); + ErrorMarker mark = *this; + mark.indices_.push_back(index); + return mark; } -NodeConfig parse_config_node(YAML::Node yaml, const FileConfig & scope) +ConfigObject::ConfigObject(const ErrorMarker & mark, YAML::Node yaml, const std::string & type) { if (!yaml.IsMap()) { - throw create_error(scope, "node object is not a dict"); + throw create_error(mark, type + " is not a dict type"); } - if (!yaml["name"]) { - throw create_error(scope, "node object has no 'name' field"); + for (const auto & kv : yaml) { + dict_[kv.first.as()] = kv.second; } + mark_ = mark; + type_ = type; +} - const auto config = std::make_shared(); - config->path = scope->path; - config->name = take(yaml, "name"); - config->yaml = yaml; - return config; +ErrorMarker ConfigObject::mark() const +{ + return mark_; } -FileConfig parse_config_path(YAML::Node yaml, const FileConfig & scope) +std::optional ConfigObject::take_yaml(const std::string & name) { - if (!yaml.IsMap()) { - throw create_error(scope, "file object is not a dict"); + if (!dict_.count(name)) { + return std::nullopt; } - if (!yaml["package"]) { - throw create_error(scope, "file object has no 'package' field"); + const auto yaml = dict_.at(name); + dict_.erase(name); + return yaml; +} + +std::string ConfigObject::take_text(const std::string & name) +{ + if (!dict_.count(name)) { + throw create_error(mark_, "object has no '" + name + "' field"); } - if (!yaml["path"]) { - throw create_error(scope, "file object has no 'path' field"); + + const auto yaml = dict_.at(name); + dict_.erase(name); + return yaml.as(); +} + +std::string ConfigObject::take_text(const std::string & name, const std::string & fail) +{ + if (!dict_.count(name)) { + return fail; } - const auto package_name = yaml["package"].as(); - const auto package_path = ament_index_cpp::get_package_share_directory(package_name); - return parse_config_path(package_path + "/" + yaml["path"].as(), scope); + const auto yaml = dict_.at(name); + dict_.erase(name); + return yaml.as(); } -FileConfig parse_config_path(const std::string & path, const FileConfig & scope) +std::vector ConfigObject::take_list(const std::string & name) { - if (!std::filesystem::exists(path)) { - throw create_error(scope, "config file '" + path + "' does not exist"); + if (!dict_.count(name)) { + return std::vector(); } - return parse_config_file(path); + + const auto yaml = dict_.at(name); + dict_.erase(name); + + if (!yaml.IsSequence()) { + throw ConfigError("the '" + name + "' field is not a list type"); + } + return std::vector(yaml.begin(), yaml.end()); +} + +bool ConfigFilter::check(const std::string & mode) const +{ + if (!excludes.empty() && excludes.count(mode) != 0) return false; + if (!includes.empty() && includes.count(mode) == 0) return false; + return true; } -FileConfig parse_config_file(const std::string & path) +ConfigError create_error(const ErrorMarker & mark, const std::string & message) { - const auto config = std::make_shared(); - config->path = path; + (void)mark; + return ConfigError(message); +} - const auto yaml = YAML::LoadFile(path); - if (!yaml.IsMap()) { - throw create_error(config, "config file is not a dict"); +ConfigFilter parse_mode_filter(const ErrorMarker & mark, std::optional yaml) +{ + std::unordered_set excludes; + std::unordered_set includes; + if (yaml) { + ConfigObject dict(mark, yaml.value(), "mode filter"); + + for (const auto & mode : dict.take_list("except")) { + excludes.emplace(mode.as()); + } + for (const auto & mode : dict.take_list("only")) { + includes.emplace(mode.as()); + } } + return ConfigFilter{excludes, includes}; +} + +FileConfig parse_file_config(const ErrorMarker & mark, YAML::Node yaml) +{ + ConfigObject dict(mark, yaml, "file object"); + const auto relative_path = dict.take_text("path"); + const auto package_name = dict.take_text("package"); + const auto package_path = ament_index_cpp::get_package_share_directory(package_name); + return FileConfig{mark, package_path + "/" + relative_path}; +} + +NodeConfig parse_node_config(const ErrorMarker & mark, YAML::Node yaml) +{ + ConfigObject dict(mark, yaml, "node object"); + const auto path = dict.take_text("path"); + const auto mode = parse_mode_filter(dict.mark(), dict.take_yaml("mode")); + return NodeConfig{path, mode, dict}; +} + +ExprConfig parse_expr_config(const ErrorMarker & mark, YAML::Node yaml) +{ + ConfigObject dict(mark, yaml, "expr object"); + return parse_expr_config(dict); +} + +ExprConfig parse_expr_config(ConfigObject & dict) +{ + const auto type = dict.take_text("type"); + const auto mode = parse_mode_filter(dict.mark(), dict.take_yaml("mode")); + return ExprConfig{type, mode, dict}; +} - const auto files = yaml["files"] ? yaml["files"] : YAML::Node(YAML::NodeType::Sequence); - if (!files.IsSequence()) { - throw create_error(config, "files section is not a list"); +void dump(const ConfigFile & config) +{ + std::cout << "=================================================================" << std::endl; + std::cout << config.mark.str() << std::endl; + for (const auto & file : config.files) { + std::cout << " - f: " << file.path << " (" << file.mark.str() << ")" << std::endl; + } + for (const auto & unit : config.units) { + std::cout << " - u: " << unit.path << " (" << unit.dict.mark().str() << ")" << std::endl; } - for (const auto file : files) { - config->files.push_back(parse_config_path(file, config)); + for (const auto & diag : config.diags) { + std::cout << " - d: " << diag.path << " (" << diag.dict.mark().str() << ")" << std::endl; } +} - const auto nodes = yaml["nodes"] ? yaml["nodes"] : YAML::Node(YAML::NodeType::Sequence); - if (!nodes.IsSequence()) { - throw create_error(config, "nodes section is not a list"); +template +auto apply(const ErrorMarker & mark, F & func, const std::vector & list) +{ + std::vector result; + for (size_t i = 0; i < list.size(); ++i) { + result.push_back(func(mark.index(i), list[i])); } - for (const auto node : nodes) { - config->nodes.push_back(parse_config_node(node, config)); + return result; +} + +ConfigFile load_config_file(const FileConfig & file) +{ + if (!std::filesystem::exists(file.path)) { + throw create_error(file.mark, "config file '" + file.path + "' does not exist"); } + const auto yaml = YAML::LoadFile(file.path); + const auto mark = ErrorMarker(file.path); + auto dict = ConfigObject(mark, yaml, "config file"); + + std::vector units; + std::vector diags; + for (const auto & node : dict.take_list("nodes")) { + const auto type = node["type"].as(); + if (type == "diag") { + diags.push_back(node); + } else { + units.push_back(node); + } + } + + ConfigFile config(mark); + config.files = apply(mark.type("file"), parse_file_config, dict.take_list("files")); + config.units = apply(mark.type("unit"), parse_node_config, units); + config.diags = apply(mark.type("diag"), parse_node_config, diags); return config; } -void walk_config_tree(const FileConfig & config, std::vector & nodes) +ConfigFile load_config_root(const std::string & path) { - nodes.insert(nodes.end(), config->nodes.begin(), config->nodes.end()); - for (const auto & file : config->files) walk_config_tree(file, nodes); -} + const auto mark = ErrorMarker("root file"); + std::vector configs; + configs.push_back(load_config_file(FileConfig{mark, path})); -std::vector load_config_file(const std::string & path) -{ - std::vector nodes; - walk_config_tree(parse_config_path(path, nullptr), nodes); - return nodes; + // Use an index because updating the vector invalidates the iterator. + for (size_t i = 0; i < configs.size(); ++i) { + for (const auto & file : configs[i].files) { + configs.push_back(load_config_file(file)); + } + } + + ConfigFile result(mark); + for (const auto & config : configs) { + result.files.insert(result.files.end(), config.files.begin(), config.files.end()); + result.units.insert(result.units.end(), config.units.begin(), config.units.end()); + result.diags.insert(result.diags.end(), config.diags.begin(), config.diags.end()); + } + return result; } } // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/config.hpp b/system/system_diagnostic_graph/src/core/config.hpp index 6393cb906b119..4d679ef575944 100644 --- a/system/system_diagnostic_graph/src/core/config.hpp +++ b/system/system_diagnostic_graph/src/core/config.hpp @@ -18,8 +18,11 @@ #include #include +#include #include #include +#include +#include #include namespace system_diagnostic_graph @@ -30,38 +33,79 @@ struct ConfigError : public std::runtime_error using runtime_error::runtime_error; }; -struct NodeConfig_ +class ErrorMarker +{ +public: + explicit ErrorMarker(const std::string & file = ""); + std::string str() const; + ErrorMarker type(const std::string & type) const; + ErrorMarker index(size_t index) const; + +private: + std::string file_; + std::string type_; + std::vector indices_; +}; + +class ConfigObject +{ +public: + ConfigObject(const ErrorMarker & mark, YAML::Node yaml, const std::string & type); + ErrorMarker mark() const; + std::optional take_yaml(const std::string & name); + std::string take_text(const std::string & name); + std::string take_text(const std::string & name, const std::string & fail); + std::vector take_list(const std::string & name); + +private: + ErrorMarker mark_; + std::string type_; + std::unordered_map dict_; +}; + +struct ConfigFilter +{ + bool check(const std::string & mode) const; + std::unordered_set excludes; + std::unordered_set includes; +}; + +struct ExprConfig +{ + std::string type; + ConfigFilter mode; + ConfigObject dict; +}; + +struct NodeConfig { std::string path; - std::string name; - YAML::Node yaml; + ConfigFilter mode; + ConfigObject dict; }; -struct FileConfig_ +struct FileConfig { + ErrorMarker mark; std::string path; - std::vector> files; - std::vector> nodes; }; -template -T take(YAML::Node yaml, const std::string & field) +struct ConfigFile { - const auto result = yaml[field].as(); - yaml.remove(field); - return result; -} - -using NodeConfig = std::shared_ptr; -using FileConfig = std::shared_ptr; -ConfigError create_error(const FileConfig & config, const std::string & message); -ConfigError create_error(const NodeConfig & config, const std::string & message); -std::vector load_config_file(const std::string & path); - -NodeConfig parse_config_node(YAML::Node yaml, const FileConfig & scope); -FileConfig parse_config_path(YAML::Node yaml, const FileConfig & scope); -FileConfig parse_config_path(const std::string & path, const FileConfig & scope); -FileConfig parse_config_file(const std::string & path); + explicit ConfigFile(const ErrorMarker & mark) : mark(mark) {} + ErrorMarker mark; + std::vector files; + std::vector units; + std::vector diags; +}; + +using ConfigDict = std::unordered_map; + +ConfigError create_error(const ErrorMarker & mark, const std::string & message); +ConfigFile load_config_root(const std::string & path); + +ExprConfig parse_expr_config(const ErrorMarker & mark, YAML::Node yaml); +ExprConfig parse_expr_config(ConfigObject & dict); } // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/debug.cpp b/system/system_diagnostic_graph/src/core/debug.cpp index 337be8c74aded..ed696573521a7 100644 --- a/system/system_diagnostic_graph/src/core/debug.cpp +++ b/system/system_diagnostic_graph/src/core/debug.cpp @@ -14,9 +14,9 @@ #include "debug.hpp" -#include "node.hpp" +#include "graph.hpp" +#include "nodes.hpp" #include "types.hpp" -#include "update.hpp" #include #include @@ -32,10 +32,10 @@ const std::unordered_map level_names = { {DiagnosticStatus::ERROR, "ERROR"}, {DiagnosticStatus::STALE, "STALE"}}; -void DiagGraph::debug() +void Graph::debug() { std::vector lines; - for (const auto & node : graph_.nodes()) { + for (const auto & node : nodes_) { lines.push_back(node->debug()); } @@ -59,17 +59,23 @@ void DiagGraph::debug() DiagDebugData UnitNode::debug() const { - const auto & level = node_.status.level; - const auto & name = node_.status.name; - return DiagDebugData{std::to_string(index()), "unit", name, "-----", level_names.at(level)}; + const auto level_name = level_names.at(level()); + const auto index_name = std::to_string(index()); + return {"unit", index_name, level_name, path_, "-----"}; } DiagDebugData DiagNode::debug() const { - const auto & level = node_.status.level; - const auto & name = node_.status.name; - const auto & hardware = node_.status.hardware_id; - return DiagDebugData{std::to_string(index()), "diag", name, hardware, level_names.at(level)}; + const auto level_name = level_names.at(level()); + const auto index_name = std::to_string(index()); + return {"diag", index_name, level_name, path_, name_}; +} + +DiagDebugData UnknownNode::debug() const +{ + const auto level_name = level_names.at(level()); + const auto index_name = std::to_string(index()); + return {"test", index_name, level_name, path_, "-----"}; } } // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/expr.cpp b/system/system_diagnostic_graph/src/core/expr.cpp deleted file mode 100644 index dc7ebcf8dd859..0000000000000 --- a/system/system_diagnostic_graph/src/core/expr.cpp +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2023 The Autoware Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "expr.hpp" - -#include "config.hpp" -#include "graph.hpp" -#include "node.hpp" - -#include -#include -#include -#include - -// -#include - -namespace system_diagnostic_graph -{ - -using LinkStatus = std::vector>; - -void extend(LinkStatus & a, const LinkStatus & b) -{ - a.insert(a.end(), b.begin(), b.end()); -} - -void extend_false(LinkStatus & a, const LinkStatus & b) -{ - for (const auto & p : b) { - a.push_back(std::make_pair(p.first, false)); - } -} - -std::unique_ptr BaseExpr::create(Graph & graph, YAML::Node yaml) -{ - if (!yaml.IsMap()) { - throw ConfigError("expr object is not a dict"); - } - if (!yaml["type"]) { - throw ConfigError("expr object has no 'type' field"); - } - - const auto type = take(yaml, "type"); - - if (type == "unit") { - return std::make_unique(graph, yaml); - } - if (type == "diag") { - return std::make_unique(graph, yaml); - } - if (type == "and") { - return std::make_unique(graph, yaml); - } - if (type == "or") { - return std::make_unique(graph, yaml); - } - if (type == "if") { - return std::make_unique(graph, yaml); - } - if (type == "debug-ok") { - return std::make_unique(DiagnosticStatus::OK); - } - if (type == "debug-warn") { - return std::make_unique(DiagnosticStatus::WARN); - } - if (type == "debug-error") { - return std::make_unique(DiagnosticStatus::ERROR); - } - if (type == "debug-stale") { - return std::make_unique(DiagnosticStatus::STALE); - } - throw ConfigError("unknown expr type: " + type); -} - -ConstExpr::ConstExpr(const DiagnosticLevel level) -{ - level_ = level; -} - -ExprStatus ConstExpr::eval() const -{ - ExprStatus status; - status.level = level_; - return status; -} - -std::vector ConstExpr::get_dependency() const -{ - return {}; -} - -UnitExpr::UnitExpr(Graph & graph, YAML::Node yaml) -{ - if (!yaml["name"]) { - throw ConfigError("unit object has no 'name' field"); - } - const auto name = take(yaml, "name"); - node_ = graph.find_unit(name); - if (!node_) { - throw ConfigError("unit node '" + name + "' does not exist"); - } -} - -ExprStatus UnitExpr::eval() const -{ - ExprStatus status; - status.level = node_->level(); - status.links.push_back(std::make_pair(node_, true)); - return status; -} - -std::vector UnitExpr::get_dependency() const -{ - return {node_}; -} - -DiagExpr::DiagExpr(Graph & graph, YAML::Node yaml) -{ - if (!yaml["name"]) { - throw ConfigError("diag object has no 'name' field"); - } - const auto name = yaml["name"].as(); - const auto hardware = yaml["hardware"].as(""); - node_ = graph.find_diag(name, hardware); - if (!node_) { - node_ = graph.make_diag(name, hardware); - } -} - -ExprStatus DiagExpr::eval() const -{ - ExprStatus status; - status.level = node_->level(); - status.links.push_back(std::make_pair(node_, true)); - return status; -} - -std::vector DiagExpr::get_dependency() const -{ - return {node_}; -} - -AndExpr::AndExpr(Graph & graph, YAML::Node yaml) -{ - if (!yaml["list"]) { - throw ConfigError("expr object has no 'list' field"); - } - if (!yaml["list"].IsSequence()) { - throw ConfigError("list field is not a list"); - } - - for (const auto & node : yaml["list"]) { - list_.push_back(BaseExpr::create(graph, node)); - } -} - -ExprStatus AndExpr::eval() const -{ - std::vector results; - for (const auto & expr : list_) { - results.push_back(expr->eval()); - } - std::vector levels; - for (const auto & result : results) { - levels.push_back(result.level); - } - ExprStatus status; - for (const auto & result : results) { - extend(status.links, result.links); - } - const auto level = *std::max_element(levels.begin(), levels.end()); - status.level = std::min(level, DiagnosticStatus::ERROR); - return status; -} - -std::vector AndExpr::get_dependency() const -{ - std::vector depends; - for (const auto & expr : list_) { - const auto nodes = expr->get_dependency(); - depends.insert(depends.end(), nodes.begin(), nodes.end()); - } - return depends; -} - -OrExpr::OrExpr(Graph & graph, YAML::Node yaml) -{ - if (!yaml["list"]) { - throw ConfigError("expr object has no 'list' field"); - } - if (!yaml["list"].IsSequence()) { - throw ConfigError("list field is not a list"); - } - - for (const auto & node : yaml["list"]) { - list_.push_back(BaseExpr::create(graph, node)); - } -} - -ExprStatus OrExpr::eval() const -{ - std::vector results; - for (const auto & expr : list_) { - results.push_back(expr->eval()); - } - std::vector levels; - for (const auto & result : results) { - levels.push_back(result.level); - } - ExprStatus status; - for (const auto & result : results) { - extend(status.links, result.links); - } - const auto level = *std::min_element(levels.begin(), levels.end()); - status.level = std::min(level, DiagnosticStatus::ERROR); - return status; -} - -std::vector OrExpr::get_dependency() const -{ - std::vector depends; - for (const auto & expr : list_) { - const auto nodes = expr->get_dependency(); - depends.insert(depends.end(), nodes.begin(), nodes.end()); - } - return depends; -} - -IfExpr::IfExpr(Graph & graph, YAML::Node yaml) -{ - if (!yaml["cond"]) { - throw ConfigError("expr object has no 'cond' field"); - } - if (!yaml["then"]) { - throw ConfigError("expr object has no 'then' field"); - } - cond_ = BaseExpr::create(graph, yaml["cond"]); - then_ = BaseExpr::create(graph, yaml["then"]); -} - -ExprStatus IfExpr::eval() const -{ - const auto cond = cond_->eval(); - const auto then = then_->eval(); - ExprStatus status; - if (cond.level == DiagnosticStatus::OK) { - status.level = std::min(then.level, DiagnosticStatus::ERROR); - extend(status.links, cond.links); - extend(status.links, then.links); - } else { - status.level = std::min(cond.level, DiagnosticStatus::ERROR); - extend(status.links, cond.links); - extend_false(status.links, then.links); - } - return status; -} - -std::vector IfExpr::get_dependency() const -{ - std::vector depends; - { - const auto nodes = cond_->get_dependency(); - depends.insert(depends.end(), nodes.begin(), nodes.end()); - } - { - const auto nodes = then_->get_dependency(); - depends.insert(depends.end(), nodes.begin(), nodes.end()); - } - return depends; -} - -} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/exprs.cpp b/system/system_diagnostic_graph/src/core/exprs.cpp new file mode 100644 index 0000000000000..22281f939cad2 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/exprs.cpp @@ -0,0 +1,216 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "exprs.hpp" + +#include "nodes.hpp" + +#include +#include +#include +#include +#include + +// DEBUG +#include + +namespace system_diagnostic_graph +{ + +using LinkStatus = std::vector>; + +void extend(LinkStatus & a, const LinkStatus & b) +{ + a.insert(a.end(), b.begin(), b.end()); +} + +void extend_false(LinkStatus & a, const LinkStatus & b) +{ + for (const auto & p : b) { + a.push_back(std::make_pair(p.first, false)); + } +} + +auto create_expr_list(ExprInit & exprs, ConfigObject & config) +{ + std::vector> result; + const auto list = config.take_list("list"); + for (size_t i = 0; i < list.size(); ++i) { + auto dict = parse_expr_config(config.mark().index(i), list[i]); + auto expr = exprs.create(dict); + if (expr) { + result.push_back(std::move(expr)); + } + } + return result; +} + +ConstExpr::ConstExpr(const DiagnosticLevel level) +{ + level_ = level; +} + +ExprStatus ConstExpr::eval() const +{ + ExprStatus status; + status.level = level_; + return status; +} + +std::vector ConstExpr::get_dependency() const +{ + return {}; +} + +LinkExpr::LinkExpr(ExprInit & exprs, ConfigObject & config) +{ + // TODO(Takagi, Isamu): remove + (void)config; + (void)exprs; +} + +void LinkExpr::init(ConfigObject & config, std::unordered_map nodes) +{ + const auto path = config.take_text("path"); + if (!nodes.count(path)) { + throw ConfigError("node path '" + path + "' does not exist"); + } + node_ = nodes.at(path); +} + +ExprStatus LinkExpr::eval() const +{ + ExprStatus status; + status.level = node_->level(); + status.links.push_back(std::make_pair(node_, true)); + return status; +} + +std::vector LinkExpr::get_dependency() const +{ + return {node_}; +} + +AndExpr::AndExpr(ExprInit & exprs, ConfigObject & config, bool short_circuit) +{ + list_ = create_expr_list(exprs, config); + short_circuit_ = short_circuit; +} + +ExprStatus AndExpr::eval() const +{ + if (list_.empty()) { + ExprStatus status; + status.level = DiagnosticStatus::OK; + return status; + } + + ExprStatus status; + status.level = DiagnosticStatus::OK; + for (const auto & expr : list_) { + const auto result = expr->eval(); + status.level = std::max(status.level, result.level); + extend(status.links, result.links); + if (short_circuit_ && status.level != DiagnosticStatus::OK) { + break; + } + } + status.level = std::min(status.level, DiagnosticStatus::ERROR); + return status; +} + +std::vector AndExpr::get_dependency() const +{ + std::vector depends; + for (const auto & expr : list_) { + const auto nodes = expr->get_dependency(); + depends.insert(depends.end(), nodes.begin(), nodes.end()); + } + return depends; +} + +OrExpr::OrExpr(ExprInit & exprs, ConfigObject & config) +{ + list_ = create_expr_list(exprs, config); +} + +ExprStatus OrExpr::eval() const +{ + if (list_.empty()) { + ExprStatus status; + status.level = DiagnosticStatus::OK; + return status; + } + + ExprStatus status; + status.level = DiagnosticStatus::ERROR; + for (const auto & expr : list_) { + const auto result = expr->eval(); + status.level = std::min(status.level, result.level); + extend(status.links, result.links); + } + status.level = std::min(status.level, DiagnosticStatus::ERROR); + return status; +} + +std::vector OrExpr::get_dependency() const +{ + std::vector depends; + for (const auto & expr : list_) { + const auto nodes = expr->get_dependency(); + depends.insert(depends.end(), nodes.begin(), nodes.end()); + } + return depends; +} + +ExprInit::ExprInit(const std::string & mode) +{ + mode_ = mode; +} + +std::unique_ptr ExprInit::create(ExprConfig config) +{ + if (!config.mode.check(mode_)) { + return nullptr; + } + if (config.type == "link") { + auto expr = std::make_unique(*this, config.dict); + links_.push_back(std::make_pair(expr.get(), config.dict)); + return expr; + } + if (config.type == "and") { + return std::make_unique(*this, config.dict, false); + } + if (config.type == "short-circuit-and") { + return std::make_unique(*this, config.dict, true); + } + if (config.type == "or") { + return std::make_unique(*this, config.dict); + } + if (config.type == "debug-ok") { + return std::make_unique(DiagnosticStatus::OK); + } + if (config.type == "debug-warn") { + return std::make_unique(DiagnosticStatus::WARN); + } + if (config.type == "debug-error") { + return std::make_unique(DiagnosticStatus::ERROR); + } + if (config.type == "debug-stale") { + return std::make_unique(DiagnosticStatus::STALE); + } + throw ConfigError("unknown expr type: " + config.type + " " + config.dict.mark().str()); +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/expr.hpp b/system/system_diagnostic_graph/src/core/exprs.hpp similarity index 68% rename from system/system_diagnostic_graph/src/core/expr.hpp rename to system/system_diagnostic_graph/src/core/exprs.hpp index 541841582180a..f582e019d5691 100644 --- a/system/system_diagnostic_graph/src/core/expr.hpp +++ b/system/system_diagnostic_graph/src/core/exprs.hpp @@ -12,15 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CORE__EXPR_HPP_ -#define CORE__EXPR_HPP_ +#ifndef CORE__EXPRS_HPP_ +#define CORE__EXPRS_HPP_ +#include "config.hpp" #include "types.hpp" -#include - #include #include +#include #include #include @@ -36,7 +36,6 @@ struct ExprStatus class BaseExpr { public: - static std::unique_ptr create(Graph & graph, YAML::Node yaml); virtual ~BaseExpr() = default; virtual ExprStatus eval() const = 0; virtual std::vector get_dependency() const = 0; @@ -53,43 +52,34 @@ class ConstExpr : public BaseExpr DiagnosticLevel level_; }; -class UnitExpr : public BaseExpr +class LinkExpr : public BaseExpr { public: - UnitExpr(Graph & graph, YAML::Node yaml); + LinkExpr(ExprInit & exprs, ConfigObject & config); + void init(ConfigObject & config, std::unordered_map nodes); ExprStatus eval() const override; std::vector get_dependency() const override; private: - UnitNode * node_; -}; - -class DiagExpr : public BaseExpr -{ -public: - DiagExpr(Graph & graph, YAML::Node yaml); - ExprStatus eval() const override; - std::vector get_dependency() const override; - -private: - DiagNode * node_; + BaseNode * node_; }; class AndExpr : public BaseExpr { public: - AndExpr(Graph & graph, YAML::Node yaml); + AndExpr(ExprInit & exprs, ConfigObject & config, bool short_circuit); ExprStatus eval() const override; std::vector get_dependency() const override; private: + bool short_circuit_; std::vector> list_; }; class OrExpr : public BaseExpr { public: - OrExpr(Graph & graph, YAML::Node yaml); + OrExpr(ExprInit & exprs, ConfigObject & config); ExprStatus eval() const override; std::vector get_dependency() const override; @@ -97,18 +87,18 @@ class OrExpr : public BaseExpr std::vector> list_; }; -class IfExpr : public BaseExpr +class ExprInit { public: - IfExpr(Graph & graph, YAML::Node yaml); - ExprStatus eval() const override; - std::vector get_dependency() const override; + explicit ExprInit(const std::string & mode); + std::unique_ptr create(ExprConfig config); + auto get() const { return links_; } private: - std::unique_ptr cond_; - std::unique_ptr then_; + std::string mode_; + std::vector> links_; }; } // namespace system_diagnostic_graph -#endif // CORE__EXPR_HPP_ +#endif // CORE__EXPRS_HPP_ diff --git a/system/system_diagnostic_graph/src/core/graph.cpp b/system/system_diagnostic_graph/src/core/graph.cpp index ba0e3cfd2e016..b4fd1d15827f3 100644 --- a/system/system_diagnostic_graph/src/core/graph.cpp +++ b/system/system_diagnostic_graph/src/core/graph.cpp @@ -14,58 +14,32 @@ #include "graph.hpp" -#include "node.hpp" +#include "config.hpp" +#include "exprs.hpp" +#include "nodes.hpp" #include -#include #include +#include +#include #include +#include -// +// DEBUG #include namespace system_diagnostic_graph { -UnitNode * Graph::make_unit(const std::string & name) +void topological_sort(std::vector> & input) { - const auto key = name; - auto unit = std::make_unique(key); - units_[key] = unit.get(); - nodes_.emplace_back(std::move(unit)); - return units_[key]; -} - -UnitNode * Graph::find_unit(const std::string & name) -{ - const auto key = name; - return units_.count(key) ? units_.at(key) : nullptr; -} - -DiagNode * Graph::make_diag(const std::string & name, const std::string & hardware) -{ - const auto key = std::make_pair(name, hardware); - auto diag = std::make_unique(name, hardware); - diags_[key] = diag.get(); - nodes_.emplace_back(std::move(diag)); - return diags_[key]; -} - -DiagNode * Graph::find_diag(const std::string & name, const std::string & hardware) -{ - const auto key = std::make_pair(name, hardware); - return diags_.count(key) ? diags_.at(key) : nullptr; -} - -void Graph::topological_sort() -{ - std::map degrees; + std::unordered_map degrees; std::deque nodes; std::deque result; std::deque buffer; // Create a list of raw pointer nodes. - for (const auto & node : nodes_) { + for (const auto & node : input) { nodes.push_back(node.get()); } @@ -104,15 +78,127 @@ void Graph::topological_sort() result = std::deque(result.rbegin(), result.rend()); // Replace node vector. - std::map indices; + std::unordered_map indices; for (size_t i = 0; i < result.size(); ++i) { indices[result[i]] = i; } - std::vector> temp(nodes_.size()); - for (auto & node : nodes_) { - temp[indices[node.get()]] = std::move(node); + std::vector> sorted(input.size()); + for (auto & node : input) { + sorted[indices[node.get()]] = std::move(node); + } + input = std::move(sorted); +} + +Graph::Graph() +{ + // for unique_ptr +} + +Graph::~Graph() +{ + // for unique_ptr +} + +void Graph::init(const std::string & file, const std::string & mode) +{ + ConfigFile root = load_config_root(file); + + std::vector> nodes; + std::unordered_map diags; + std::unordered_map paths; + ExprInit exprs(mode); + + for (auto & config : root.units) { + if (config.mode.check(mode)) { + auto node = std::make_unique(config.path); + nodes.push_back(std::move(node)); + } + } + for (auto & config : root.diags) { + if (config.mode.check(mode)) { + auto node = std::make_unique(config.path, config.dict); + diags[node->name()] = node.get(); + nodes.push_back(std::move(node)); + } + } + + // TODO(Takagi, Isamu): unknown diag names + { + auto node = std::make_unique("/unknown"); + unknown_ = node.get(); + nodes.push_back(std::move(node)); + } + + for (const auto & node : nodes) { + paths[node->path()] = node.get(); + } + + for (auto & config : root.units) { + if (paths.count(config.path)) { + paths.at(config.path)->create(config.dict, exprs); + } + } + for (auto & config : root.diags) { + if (paths.count(config.path)) { + paths.at(config.path)->create(config.dict, exprs); + } + } + + for (auto & [link, config] : exprs.get()) { + link->init(config, paths); + } + + // Sort unit nodes in topological order for update dependencies. + topological_sort(nodes); + + // Set the link index for the ros message. + for (size_t i = 0; i < nodes.size(); ++i) { + nodes[i]->set_index(i); + } + + nodes_ = std::move(nodes); + diags_ = diags; +} + +void Graph::callback(const DiagnosticArray & array, const rclcpp::Time & stamp) +{ + for (const auto & status : array.status) { + const auto iter = diags_.find(status.name); + if (iter != diags_.end()) { + iter->second->callback(status, stamp); + } else { + unknown_->callback(status, stamp); + } + } +} + +void Graph::update(const rclcpp::Time & stamp) +{ + for (const auto & node : nodes_) { + node->update(stamp); + } + stamp_ = stamp; +} + +DiagnosticGraph Graph::message() const +{ + DiagnosticGraph result; + result.stamp = stamp_; + result.nodes.reserve(nodes_.size()); + for (const auto & node : nodes_) { + result.nodes.push_back(node->report()); + } + return result; +} + +std::vector Graph::nodes() const +{ + std::vector result; + result.reserve(nodes_.size()); + for (const auto & node : nodes_) { + result.push_back(node.get()); } - nodes_ = std::move(temp); + return result; } } // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/graph.hpp b/system/system_diagnostic_graph/src/core/graph.hpp index a3f46760388f7..e0060c9111592 100644 --- a/system/system_diagnostic_graph/src/core/graph.hpp +++ b/system/system_diagnostic_graph/src/core/graph.hpp @@ -17,29 +17,36 @@ #include "types.hpp" -#include +#include + #include #include +#include #include #include namespace system_diagnostic_graph { -class Graph +class Graph final { public: - UnitNode * make_unit(const std::string & name); - UnitNode * find_unit(const std::string & name); - DiagNode * make_diag(const std::string & name, const std::string & hardware); - DiagNode * find_diag(const std::string & name, const std::string & hardware); - void topological_sort(); - const std::vector> & nodes() { return nodes_; } + Graph(); + ~Graph(); + + void init(const std::string & file, const std::string & mode); + void callback(const DiagnosticArray & array, const rclcpp::Time & stamp); + void update(const rclcpp::Time & stamp); + DiagnosticGraph message() const; + std::vector nodes() const; + + void debug(); private: std::vector> nodes_; - std::map units_; - std::map, DiagNode *> diags_; + std::unordered_map diags_; + UnknownNode * unknown_; + rclcpp::Time stamp_; }; } // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/modes.cpp b/system/system_diagnostic_graph/src/core/modes.cpp new file mode 100644 index 0000000000000..0ca18f84b0407 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/modes.cpp @@ -0,0 +1,75 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "modes.hpp" + +#include "config.hpp" +#include "nodes.hpp" + +#include +#include +#include + +namespace system_diagnostic_graph +{ + +OperationModes::OperationModes(rclcpp::Node & node, const std::vector & graph) +{ + pub_ = node.create_publisher("/system/operation_mode/availability", rclcpp::QoS(1)); + + using PathNodes = std::unordered_map; + PathNodes paths; + for (const auto & node : graph) { + paths[node->path()] = node; + } + + const auto find_node = [](const PathNodes & paths, const std::string & name) { + const auto iter = paths.find(name); + if (iter != paths.end()) { + return iter->second; + } + throw ConfigError("summary node '" + name + "' does node exist"); + }; + + // clang-format off + stop_mode_ = find_node(paths, "/autoware/modes/stop"); + autonomous_mode_ = find_node(paths, "/autoware/modes/autonomous"); + local_mode_ = find_node(paths, "/autoware/modes/local"); + remote_mode_ = find_node(paths, "/autoware/modes/remote"); + emergency_stop_mrm_ = find_node(paths, "/autoware/modes/emergency-stop"); + comfortable_stop_mrm_ = find_node(paths, "/autoware/modes/comfortable-stop"); + pull_over_mrm_ = find_node(paths, "/autoware/modes/pull-over"); + // clang-format on +} + +void OperationModes::update(const rclcpp::Time & stamp) const +{ + const auto is_ok = [](const BaseNode * node) { return node->level() == DiagnosticStatus::OK; }; + + // clang-format off + Availability message; + message.stamp = stamp; + message.stop = is_ok(stop_mode_); + message.autonomous = is_ok(autonomous_mode_); + message.local = is_ok(local_mode_); + message.remote = is_ok(remote_mode_); + message.emergency_stop = is_ok(emergency_stop_mrm_); + message.comfortable_stop = is_ok(comfortable_stop_mrm_); + message.pull_over = is_ok(pull_over_mrm_); + // clang-format on + + pub_->publish(message); +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/update.hpp b/system/system_diagnostic_graph/src/core/modes.hpp similarity index 53% rename from system/system_diagnostic_graph/src/core/update.hpp rename to system/system_diagnostic_graph/src/core/modes.hpp index 3cba96ad8203a..ead1ec10dce93 100644 --- a/system/system_diagnostic_graph/src/core/update.hpp +++ b/system/system_diagnostic_graph/src/core/modes.hpp @@ -12,47 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CORE__UPDATE_HPP_ -#define CORE__UPDATE_HPP_ +#ifndef CORE__MODES_HPP_ +#define CORE__MODES_HPP_ -#include "graph.hpp" -#include "node.hpp" #include "types.hpp" #include -#include +#include + #include namespace system_diagnostic_graph { -struct Summary -{ - UnitNode * stop_mode; - UnitNode * autonomous_mode; - UnitNode * local_mode; - UnitNode * remote_mode; - UnitNode * emergency_stop_mrm; - UnitNode * comfortable_stop_mrm; - UnitNode * pull_over_mrm; -}; - -class DiagGraph +class OperationModes { public: - void create(const std::string & file); - void callback(const DiagnosticArray & array); - DiagnosticGraph report(const rclcpp::Time & stamp); - OperationModeAvailability summary(const rclcpp::Time & stamp); - - void debug(); + explicit OperationModes(rclcpp::Node & node, const std::vector & graph); + void update(const rclcpp::Time & stamp) const; private: - Graph graph_; - Summary summary_; + using Availability = tier4_system_msgs::msg::OperationModeAvailability; + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr pub_; + + BaseNode * stop_mode_; + BaseNode * autonomous_mode_; + BaseNode * local_mode_; + BaseNode * remote_mode_; + BaseNode * emergency_stop_mrm_; + BaseNode * comfortable_stop_mrm_; + BaseNode * pull_over_mrm_; }; } // namespace system_diagnostic_graph -#endif // CORE__UPDATE_HPP_ +#endif // CORE__MODES_HPP_ diff --git a/system/system_diagnostic_graph/src/core/node.cpp b/system/system_diagnostic_graph/src/core/node.cpp deleted file mode 100644 index 1439188d93e18..0000000000000 --- a/system/system_diagnostic_graph/src/core/node.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2023 The Autoware Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "node.hpp" - -#include "expr.hpp" - -#include - -#include -#include - -namespace system_diagnostic_graph -{ - -UnitNode::UnitNode(const std::string & name) -{ - node_.status.level = DiagnosticStatus::STALE; - node_.status.name = name; - node_.status.hardware_id = ""; -} - -UnitNode::~UnitNode() -{ - // for unique_ptr -} - -DiagnosticNode UnitNode::report() const -{ - return node_; -} - -void UnitNode::update() -{ - const auto result = expr_->eval(); - node_.status.level = result.level; - node_.links.clear(); - for (const auto & [node, used] : result.links) { - DiagnosticLink link; - link.index = node->index(); - link.used = used; - node_.links.push_back(link); - } -} - -void UnitNode::create(Graph & graph, const NodeConfig & config) -{ - try { - expr_ = BaseExpr::create(graph, config->yaml); - } catch (const ConfigError & error) { - throw create_error(config, error.what()); - } -} - -std::vector UnitNode::links() const -{ - return expr_->get_dependency(); -} - -DiagNode::DiagNode(const std::string & name, const std::string & hardware) -{ - node_.status.level = DiagnosticStatus::STALE; - node_.status.name = name; - node_.status.hardware_id = hardware; -} - -DiagnosticNode DiagNode::report() const -{ - return node_; -} - -void DiagNode::update() -{ - // TODO(Takagi, Isamu): timeout, error hold - // constexpr double timeout = 1.0; // TODO(Takagi, Isamu): parameterize -} - -void DiagNode::callback(const DiagnosticStatus & status) -{ - node_.status = status; -} - -} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/node.hpp b/system/system_diagnostic_graph/src/core/node.hpp deleted file mode 100644 index 359153fc2824a..0000000000000 --- a/system/system_diagnostic_graph/src/core/node.hpp +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2023 The Autoware Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef CORE__NODE_HPP_ -#define CORE__NODE_HPP_ - -#include "config.hpp" -#include "debug.hpp" -#include "types.hpp" - -#include -#include -#include -#include - -namespace system_diagnostic_graph -{ - -class BaseNode -{ -public: - virtual ~BaseNode() = default; - virtual void update() = 0; - virtual DiagnosticNode report() const = 0; - virtual DiagDebugData debug() const = 0; - virtual std::vector links() const = 0; - - DiagnosticLevel level() const { return node_.status.level; } - std::string name() const { return node_.status.name; } - - size_t index() const { return index_; } - void set_index(const size_t index) { index_ = index; } - -protected: - size_t index_ = 0; - DiagnosticNode node_; -}; - -class UnitNode : public BaseNode -{ -public: - explicit UnitNode(const std::string & name); - ~UnitNode() override; - - DiagnosticNode report() const override; - DiagDebugData debug() const override; - void update() override; - void create(Graph & graph, const NodeConfig & config); - - std::vector links() const override; - -private: - std::unique_ptr expr_; -}; - -class DiagNode : public BaseNode -{ -public: - explicit DiagNode(const std::string & name, const std::string & hardware); - - DiagnosticNode report() const override; - DiagDebugData debug() const override; - void update() override; - void callback(const DiagnosticStatus & status); - - std::vector links() const override { return {}; } - -private: -}; - -} // namespace system_diagnostic_graph - -#endif // CORE__NODE_HPP_ diff --git a/system/system_diagnostic_graph/src/core/nodes.cpp b/system/system_diagnostic_graph/src/core/nodes.cpp new file mode 100644 index 0000000000000..bbc4bb4d42561 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/nodes.cpp @@ -0,0 +1,157 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "nodes.hpp" + +#include "exprs.hpp" + +#include + +#include +#include + +namespace system_diagnostic_graph +{ + +BaseNode::BaseNode(const std::string & path) : path_(path) +{ + index_ = 0; +} + +UnitNode::UnitNode(const std::string & path) : BaseNode(path) +{ + level_ = DiagnosticStatus::STALE; +} + +UnitNode::~UnitNode() +{ + // for unique_ptr +} + +void UnitNode::create(ConfigObject & config, ExprInit & exprs) +{ + expr_ = exprs.create(parse_expr_config(config)); +} + +void UnitNode::update(const rclcpp::Time &) +{ + const auto result = expr_->eval(); + level_ = result.level; + links_.clear(); + for (const auto & [node, used] : result.links) { + DiagnosticLink link; + link.index = node->index(); + link.used = used; + links_.push_back(link); + } +} + +DiagnosticNode UnitNode::report() const +{ + DiagnosticNode message; + message.status.level = level_; + message.status.name = path_; + message.links = links_; + return message; +} + +DiagnosticLevel UnitNode::level() const +{ + return level_; +} + +std::vector UnitNode::links() const +{ + return expr_->get_dependency(); +} + +DiagNode::DiagNode(const std::string & path, ConfigObject & config) : BaseNode(path) +{ + timeout_ = 3.0; // TODO(Takagi, Isamu): parameterize + name_ = config.take_text("name"); + + status_.level = DiagnosticStatus::STALE; +} + +void DiagNode::create(ConfigObject &, ExprInit &) +{ +} + +void DiagNode::update(const rclcpp::Time & stamp) +{ + if (time_) { + const auto elapsed = (stamp - time_.value()).seconds(); + if (timeout_ < elapsed) { + status_ = DiagnosticStatus(); + status_.level = DiagnosticStatus::STALE; + time_ = std::nullopt; + } + } +} + +DiagnosticNode DiagNode::report() const +{ + DiagnosticNode message; + message.status = status_; + message.status.name = path_; + return message; +} + +DiagnosticLevel DiagNode::level() const +{ + return status_.level; +} + +void DiagNode::callback(const DiagnosticStatus & status, const rclcpp::Time & stamp) +{ + status_ = status; + time_ = stamp; +} + +UnknownNode::UnknownNode(const std::string & path) : BaseNode(path) +{ +} + +void UnknownNode::create(ConfigObject &, ExprInit &) +{ +} + +void UnknownNode::update(const rclcpp::Time & stamp) +{ + (void)stamp; +} + +DiagnosticNode UnknownNode::report() const +{ + DiagnosticNode message; + message.status.name = path_; + for (const auto & diag : diagnostics_) { + diagnostic_msgs::msg::KeyValue kv; + kv.key = diag.first; + message.status.values.push_back(kv); + } + return message; +} + +DiagnosticLevel UnknownNode::level() const +{ + return DiagnosticStatus::WARN; +} + +void UnknownNode::callback(const DiagnosticStatus & status, const rclcpp::Time & stamp) +{ + diagnostics_[status.name] = stamp; +} + +} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/nodes.hpp b/system/system_diagnostic_graph/src/core/nodes.hpp new file mode 100644 index 0000000000000..1a849cf58b268 --- /dev/null +++ b/system/system_diagnostic_graph/src/core/nodes.hpp @@ -0,0 +1,114 @@ +// Copyright 2023 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE__NODES_HPP_ +#define CORE__NODES_HPP_ + +#include "config.hpp" +#include "debug.hpp" +#include "types.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +namespace system_diagnostic_graph +{ + +class BaseNode +{ +public: + explicit BaseNode(const std::string & path); + virtual ~BaseNode() = default; + virtual void create(ConfigObject & config, ExprInit & exprs) = 0; + virtual void update(const rclcpp::Time & stamp) = 0; + virtual DiagnosticNode report() const = 0; + virtual DiagnosticLevel level() const = 0; + virtual DiagDebugData debug() const = 0; + virtual std::vector links() const = 0; + + std::string path() const { return path_; } + + size_t index() const { return index_; } + void set_index(const size_t index) { index_ = index; } + +protected: + const std::string path_; + size_t index_; +}; + +class UnitNode : public BaseNode +{ +public: + explicit UnitNode(const std::string & path); + ~UnitNode() override; + void create(ConfigObject & config, ExprInit & exprs) override; + void update(const rclcpp::Time & stamp) override; + DiagnosticNode report() const override; + DiagnosticLevel level() const override; + DiagDebugData debug() const override; + std::vector links() const override; + +private: + std::unique_ptr expr_; + std::vector links_; + DiagnosticLevel level_; +}; + +class DiagNode : public BaseNode +{ +public: + DiagNode(const std::string & path, ConfigObject & config); + void create(ConfigObject & config, ExprInit & exprs) override; + void update(const rclcpp::Time & stamp) override; + DiagnosticNode report() const override; + DiagnosticLevel level() const override; + DiagDebugData debug() const override; + std::vector links() const override { return {}; } + std::string name() const { return name_; } + + void callback(const DiagnosticStatus & status, const rclcpp::Time & stamp); + +private: + double timeout_; + std::optional time_; + std::string name_; + DiagnosticStatus status_; +}; + +class UnknownNode : public BaseNode +{ +public: + explicit UnknownNode(const std::string & path); + void create(ConfigObject & config, ExprInit & exprs) override; + void update(const rclcpp::Time & stamp) override; + DiagnosticNode report() const override; + DiagnosticLevel level() const override; + DiagDebugData debug() const override; + std::vector links() const override { return {}; } + + void callback(const DiagnosticStatus & status, const rclcpp::Time & stamp); + +private: + std::map diagnostics_; +}; + +} // namespace system_diagnostic_graph + +#endif // CORE__NODES_HPP_ diff --git a/system/system_diagnostic_graph/src/core/types.hpp b/system/system_diagnostic_graph/src/core/types.hpp index 75167958e49bc..2adfbb3f9d4ef 100644 --- a/system/system_diagnostic_graph/src/core/types.hpp +++ b/system/system_diagnostic_graph/src/core/types.hpp @@ -20,7 +20,6 @@ #include #include #include -#include namespace system_diagnostic_graph { @@ -30,15 +29,15 @@ using diagnostic_msgs::msg::DiagnosticStatus; using tier4_system_msgs::msg::DiagnosticGraph; using tier4_system_msgs::msg::DiagnosticLink; using tier4_system_msgs::msg::DiagnosticNode; -using tier4_system_msgs::msg::OperationModeAvailability; - using DiagnosticLevel = DiagnosticStatus::_level_type; -class Graph; class BaseNode; class UnitNode; class DiagNode; +class UnknownNode; + class BaseExpr; +class ExprInit; } // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/core/update.cpp b/system/system_diagnostic_graph/src/core/update.cpp deleted file mode 100644 index bb42dcba12192..0000000000000 --- a/system/system_diagnostic_graph/src/core/update.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2023 The Autoware Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "update.hpp" - -#include "config.hpp" - -#include -#include -#include -#include -#include -#include - -namespace system_diagnostic_graph -{ - -UnitNode * find_node(Graph & graph, const std::string & name) -{ - const auto node = graph.find_unit(name); - if (!node) { - throw ConfigError("summary node '" + name + "' does node exist"); - } - return node; -}; - -void DiagGraph::create(const std::string & file) -{ - const auto configs = load_config_file(file); - - // Create unit nodes first because it is necessary for the link. - std::vector> units; - for (const auto & config : configs) { - UnitNode * unit = graph_.make_unit(config->name); - units.push_back(std::make_pair(config, unit)); - } - - // Reflect the config after creating all the unit nodes, - for (auto & [config, unit] : units) { - unit->create(graph_, config); - } - - // Sort unit nodes in topological order for update dependencies. - graph_.topological_sort(); - - // Set the link index for the ros message. - const auto & nodes = graph_.nodes(); - for (size_t i = 0; i < nodes.size(); ++i) { - nodes[i]->set_index(i); - } - - // Get reserved unit node for summary. - summary_.stop_mode = find_node(graph_, "/autoware/operation/stop"); - summary_.autonomous_mode = find_node(graph_, "/autoware/operation/autonomous"); - summary_.local_mode = find_node(graph_, "/autoware/operation/local"); - summary_.remote_mode = find_node(graph_, "/autoware/operation/remote"); - summary_.emergency_stop_mrm = find_node(graph_, "/autoware/operation/emergency-stop"); - summary_.comfortable_stop_mrm = find_node(graph_, "/autoware/operation/comfortable-stop"); - summary_.pull_over_mrm = find_node(graph_, "/autoware/operation/pull-over"); -} - -DiagnosticGraph DiagGraph::report(const rclcpp::Time & stamp) -{ - DiagnosticGraph message; - message.stamp = stamp; - message.nodes.reserve(graph_.nodes().size()); - - for (const auto & node : graph_.nodes()) { - node->update(); - } - for (const auto & node : graph_.nodes()) { - message.nodes.push_back(node->report()); - } - return message; -} - -OperationModeAvailability DiagGraph::summary(const rclcpp::Time & stamp) -{ - const auto is_ok = [](const UnitNode * node) { return node->level() == DiagnosticStatus::OK; }; - - OperationModeAvailability message; - message.stamp = stamp; - message.stop = is_ok(summary_.stop_mode); - message.autonomous = is_ok(summary_.autonomous_mode); - message.local = is_ok(summary_.local_mode); - message.remote = is_ok(summary_.remote_mode); - message.emergency_stop = is_ok(summary_.emergency_stop_mrm); - message.comfortable_stop = is_ok(summary_.comfortable_stop_mrm); - message.pull_over = is_ok(summary_.pull_over_mrm); - return message; -} - -void DiagGraph::callback(const DiagnosticArray & array) -{ - for (const auto & status : array.status) { - auto diag = graph_.find_diag(status.name, status.hardware_id); - if (diag) { - diag->callback(status); - } else { - // TODO(Takagi, Isamu): handle unknown diagnostics - } - } -} - -} // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/main.cpp b/system/system_diagnostic_graph/src/main.cpp index 38b9fa5bacb3b..722ddcf833577 100644 --- a/system/system_diagnostic_graph/src/main.cpp +++ b/system/system_diagnostic_graph/src/main.cpp @@ -22,6 +22,19 @@ namespace system_diagnostic_graph MainNode::MainNode() : Node("system_diagnostic_graph_aggregator") { + // Init diagnostics graph. + { + const auto file = declare_parameter("graph_file"); + const auto mode = declare_parameter("mode"); + graph_.init(file, mode); + graph_.debug(); + } + + // Init plugins + if (declare_parameter("mode_availability")) { + modes_ = std::make_unique(*this, graph_.nodes()); + } + // Init ros interface. { using std::placeholders::_1; @@ -31,32 +44,30 @@ MainNode::MainNode() : Node("system_diagnostic_graph_aggregator") const auto callback = std::bind(&MainNode::on_diag, this, _1); sub_input_ = create_subscription("/diagnostics", qos_input, callback); pub_graph_ = create_publisher("/diagnostics_graph", qos_graph); - pub_modes_ = create_publisher( - "/system/operation_mode/availability", rclcpp::QoS(1)); const auto rate = rclcpp::Rate(declare_parameter("rate")); timer_ = rclcpp::create_timer(this, get_clock(), rate.period(), [this]() { on_timer(); }); } +} - // Init diagnostics graph. - { - const auto file = declare_parameter("graph_file"); - graph_.create(file); - graph_.debug(); - } +MainNode::~MainNode() +{ + // for unique_ptr } void MainNode::on_timer() { - const auto data = graph_.report(now()); + const auto stamp = now(); + graph_.update(stamp); graph_.debug(); - pub_graph_->publish(data); - pub_modes_->publish(graph_.summary(now())); + pub_graph_->publish(graph_.message()); + + if (modes_) modes_->update(stamp); } void MainNode::on_diag(const DiagnosticArray::ConstSharedPtr msg) { - graph_.callback(*msg); + graph_.callback(*msg, now()); } } // namespace system_diagnostic_graph diff --git a/system/system_diagnostic_graph/src/main.hpp b/system/system_diagnostic_graph/src/main.hpp index 4cc659c978611..6deb0518cd9d0 100644 --- a/system/system_diagnostic_graph/src/main.hpp +++ b/system/system_diagnostic_graph/src/main.hpp @@ -15,11 +15,14 @@ #ifndef MAIN_HPP_ #define MAIN_HPP_ +#include "core/graph.hpp" +#include "core/modes.hpp" #include "core/types.hpp" -#include "core/update.hpp" #include +#include + namespace system_diagnostic_graph { @@ -27,13 +30,14 @@ class MainNode : public rclcpp::Node { public: MainNode(); + ~MainNode(); private: - DiagGraph graph_; + Graph graph_; + std::unique_ptr modes_; rclcpp::TimerBase::SharedPtr timer_; rclcpp::Subscription::SharedPtr sub_input_; rclcpp::Publisher::SharedPtr pub_graph_; - rclcpp::Publisher::SharedPtr pub_modes_; void on_timer(); void on_diag(const DiagnosticArray::ConstSharedPtr msg); }; diff --git a/system/system_diagnostic_graph/src/tool.hpp b/system/system_diagnostic_graph/src/tool.hpp index 11f6a2632386b..5ad19b8460c4b 100644 --- a/system/system_diagnostic_graph/src/tool.hpp +++ b/system/system_diagnostic_graph/src/tool.hpp @@ -15,7 +15,6 @@ #ifndef TOOL_HPP_ #define TOOL_HPP_ -#include "core/graph.hpp" #include "core/types.hpp" #include diff --git a/system/system_diagnostic_monitor/CMakeLists.txt b/system/system_diagnostic_monitor/CMakeLists.txt deleted file mode 100644 index 909210f99d55e..0000000000000 --- a/system/system_diagnostic_monitor/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.14) -project(system_diagnostic_monitor) - -find_package(autoware_cmake REQUIRED) -autoware_package() - -ament_auto_package(INSTALL_TO_SHARE config launch script) diff --git a/system/system_diagnostic_monitor/README.md b/system/system_diagnostic_monitor/README.md deleted file mode 100644 index 8dccca34db8c5..0000000000000 --- a/system/system_diagnostic_monitor/README.md +++ /dev/null @@ -1 +0,0 @@ -# System diagnostic monitor diff --git a/system/system_diagnostic_monitor/config/autoware.yaml b/system/system_diagnostic_monitor/config/autoware.yaml deleted file mode 100644 index 892a5da851ba7..0000000000000 --- a/system/system_diagnostic_monitor/config/autoware.yaml +++ /dev/null @@ -1,74 +0,0 @@ -files: - - { package: system_diagnostic_monitor, path: config/map.yaml } - - { package: system_diagnostic_monitor, path: config/localization.yaml } - - { package: system_diagnostic_monitor, path: config/planning.yaml } - - { package: system_diagnostic_monitor, path: config/perception.yaml } - - { package: system_diagnostic_monitor, path: config/control.yaml } - - { package: system_diagnostic_monitor, path: config/vehicle.yaml } - - { package: system_diagnostic_monitor, path: config/system.yaml } - -nodes: - - name: /autoware - type: and - list: - - { type: unit, name: /autoware/operation/stop } - - { type: unit, name: /autoware/operation/autonomous } - - { type: unit, name: /autoware/operation/local } - - { type: unit, name: /autoware/operation/remote } - - { type: unit, name: /autoware/operation/emergency-stop } - - { type: unit, name: /autoware/operation/comfortable-stop } - - { type: unit, name: /autoware/operation/pull-over } - - - name: /autoware/operation/stop - type: debug-ok - - - name: /autoware/operation/autonomous - type: and - list: - - { type: unit, name: /autoware/map } - - { type: unit, name: /autoware/localization } - - { type: unit, name: /autoware/planning } - - { type: unit, name: /autoware/perception } - - { type: unit, name: /autoware/control } - - { type: unit, name: /autoware/vehicle } - - { type: unit, name: /autoware/system } - - - name: /autoware/operation/local - type: and - list: - - { type: unit, name: /autoware/vehicle } - - { type: unit, name: /autoware/system } - - - name: /autoware/operation/remote - type: and - list: - - { type: unit, name: /autoware/vehicle } - - { type: unit, name: /autoware/system } - - - name: /autoware/operation/emergency-stop - type: and - list: - - { type: unit, name: /autoware/vehicle } - - { type: unit, name: /autoware/system } - - - name: /autoware/operation/comfortable-stop - type: and - list: - - { type: unit, name: /autoware/map } - - { type: unit, name: /autoware/localization } - - { type: unit, name: /autoware/planning } - - { type: unit, name: /autoware/perception } - - { type: unit, name: /autoware/control } - - { type: unit, name: /autoware/vehicle } - - { type: unit, name: /autoware/system } - - - name: /autoware/operation/pull-over - type: and - list: - - { type: unit, name: /autoware/map } - - { type: unit, name: /autoware/localization } - - { type: unit, name: /autoware/planning } - - { type: unit, name: /autoware/perception } - - { type: unit, name: /autoware/control } - - { type: unit, name: /autoware/vehicle } - - { type: unit, name: /autoware/system } diff --git a/system/system_diagnostic_monitor/config/control.yaml b/system/system_diagnostic_monitor/config/control.yaml deleted file mode 100644 index 8884a79847c71..0000000000000 --- a/system/system_diagnostic_monitor/config/control.yaml +++ /dev/null @@ -1,10 +0,0 @@ -nodes: - - name: /autoware/control - type: and - list: - - type: diag - name: "topic_state_monitor_trajectory_follower_control_cmd: control_topic_status" - hardware: topic_state_monitor - - type: diag - name: "topic_state_monitor_control_command_control_cmd: control_topic_status" - hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/localization.yaml b/system/system_diagnostic_monitor/config/localization.yaml deleted file mode 100644 index 26d680b6c4f0f..0000000000000 --- a/system/system_diagnostic_monitor/config/localization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -nodes: - - name: /autoware/localization - type: and - list: - - { type: diag, name: "component_state_diagnostics: localization_state" } diff --git a/system/system_diagnostic_monitor/config/map.yaml b/system/system_diagnostic_monitor/config/map.yaml deleted file mode 100644 index 7bee7419200bd..0000000000000 --- a/system/system_diagnostic_monitor/config/map.yaml +++ /dev/null @@ -1,7 +0,0 @@ -nodes: - - name: /autoware/map - type: and - list: - - type: diag - name: "topic_state_monitor_vector_map: map_topic_status" - hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/perception.yaml b/system/system_diagnostic_monitor/config/perception.yaml deleted file mode 100644 index b6b4ec27d5596..0000000000000 --- a/system/system_diagnostic_monitor/config/perception.yaml +++ /dev/null @@ -1,7 +0,0 @@ -nodes: - - name: /autoware/perception - type: and - list: - - type: diag - name: "topic_state_monitor_object_recognition_objects: perception_topic_status" - hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/planning.yaml b/system/system_diagnostic_monitor/config/planning.yaml deleted file mode 100644 index c29059193081b..0000000000000 --- a/system/system_diagnostic_monitor/config/planning.yaml +++ /dev/null @@ -1,15 +0,0 @@ -nodes: - - name: /autoware/planning - type: if - cond: - type: diag - name: "component_state_diagnostics: route_state" - then: - type: and - list: - - type: diag - name: "topic_state_monitor_mission_planning_route: planning_topic_status" - hardware: topic_state_monitor - - type: diag - name: "topic_state_monitor_scenario_planning_trajectory: planning_topic_status" - hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/system.yaml b/system/system_diagnostic_monitor/config/system.yaml deleted file mode 100644 index 0377e68daaae6..0000000000000 --- a/system/system_diagnostic_monitor/config/system.yaml +++ /dev/null @@ -1,7 +0,0 @@ -nodes: - - name: /autoware/system - type: and - list: - - type: diag - name: "topic_state_monitor_system_emergency_control_cmd: system_topic_status" - hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/config/vehicle.yaml b/system/system_diagnostic_monitor/config/vehicle.yaml deleted file mode 100644 index 4826c96e5f72f..0000000000000 --- a/system/system_diagnostic_monitor/config/vehicle.yaml +++ /dev/null @@ -1,10 +0,0 @@ -nodes: - - name: /autoware/vehicle - type: and - list: - - type: diag - name: "topic_state_monitor_vehicle_status_velocity_status: vehicle_topic_status" - hardware: topic_state_monitor - - type: diag - name: "topic_state_monitor_vehicle_status_steering_status: vehicle_topic_status" - hardware: topic_state_monitor diff --git a/system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml b/system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml deleted file mode 100644 index a13b1dc9b78bc..0000000000000 --- a/system/system_diagnostic_monitor/launch/system_diagnostic_monitor.launch.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/system/system_diagnostic_monitor/package.xml b/system/system_diagnostic_monitor/package.xml deleted file mode 100644 index d410e75000876..0000000000000 --- a/system/system_diagnostic_monitor/package.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - system_diagnostic_monitor - 0.1.0 - The system_diagnostic_monitor package - Takagi, Isamu - Apache License 2.0 - - ament_cmake_auto - autoware_cmake - - system_diagnostic_graph - - autoware_adapi_v1_msgs - - ament_lint_auto - autoware_lint_common - - - ament_cmake - - diff --git a/system/system_diagnostic_monitor/script/component_state_diagnostics.py b/system/system_diagnostic_monitor/script/component_state_diagnostics.py deleted file mode 100755 index 8e12ed6656674..0000000000000 --- a/system/system_diagnostic_monitor/script/component_state_diagnostics.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2023 The Autoware Contributors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from autoware_adapi_v1_msgs.msg import LocalizationInitializationState as LocalizationState -from autoware_adapi_v1_msgs.msg import RouteState -from diagnostic_msgs.msg import DiagnosticArray -from diagnostic_msgs.msg import DiagnosticStatus -import rclpy -import rclpy.node -import rclpy.qos - - -class ComponentStateDiagnostics(rclpy.node.Node): - def __init__(self): - super().__init__("component_state_diagnostics") - durable_qos = rclpy.qos.QoSProfile( - depth=1, - durability=rclpy.qos.DurabilityPolicy.TRANSIENT_LOCAL, - reliability=rclpy.qos.ReliabilityPolicy.RELIABLE, - ) - - self.timer = self.create_timer(0.5, self.on_timer) - self.pub = self.create_publisher(DiagnosticArray, "/diagnostics", 1) - self.sub1 = self.create_subscription( - LocalizationState, - "/api/localization/initialization_state", - self.on_localization, - durable_qos, - ) - self.sub2 = self.create_subscription( - RouteState, "/api/routing/state", self.on_routing, durable_qos - ) - - self.diags = DiagnosticArray() - self.diags.status.append( - DiagnosticStatus( - level=DiagnosticStatus.STALE, name="component_state_diagnostics: localization_state" - ) - ) - self.diags.status.append( - DiagnosticStatus( - level=DiagnosticStatus.STALE, name="component_state_diagnostics: route_state" - ) - ) - - def on_timer(self): - self.diags.header.stamp = self.get_clock().now().to_msg() - self.pub.publish(self.diags) - - def on_localization(self, msg): - self.diags.status[0].level = ( - DiagnosticStatus.OK - if msg.state == LocalizationState.INITIALIZED - else DiagnosticStatus.ERROR - ) - - def on_routing(self, msg): - self.diags.status[1].level = ( - DiagnosticStatus.OK if msg.state != RouteState.UNSET else DiagnosticStatus.ERROR - ) - - -if __name__ == "__main__": - rclpy.init() - rclpy.spin(ComponentStateDiagnostics()) - rclpy.shutdown() From 992584bb6b03c04bf189567be055f67c962b038f Mon Sep 17 00:00:00 2001 From: Shunsuke Miura <37187849+miursh@users.noreply.github.com> Date: Wed, 18 Oct 2023 17:32:25 +0900 Subject: [PATCH 500/547] chore(traffic_light_fine_detector): update onnx link in readme (#5339) chore(traffic_light_fine_detector): update onnx in readme Signed-off-by: Shunsuke Miura --- perception/traffic_light_fine_detector/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/perception/traffic_light_fine_detector/README.md b/perception/traffic_light_fine_detector/README.md index dcc89c76387c6..9e69c22fdc17b 100644 --- a/perception/traffic_light_fine_detector/README.md +++ b/perception/traffic_light_fine_detector/README.md @@ -16,7 +16,8 @@ The model was fine-tuned on around 17,000 TIER IV internal images of Japanese tr ### Trained Onnx model -- +You can download the ONNX file using these instructions. +Please visit [autoware-documentation](https://github.com/autowarefoundation/autoware-documentation/blob/main/docs/models/index.md) for more information. ## Inner-workings / Algorithms From 9a7fac5dd38294990a3a0be40f1837e349d38027 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 18 Oct 2023 18:12:18 +0900 Subject: [PATCH 501/547] fix(perception_reproducer): publish objects even if out of recorded timestamp (#5343) Signed-off-by: kosuke55 --- .../perception_replayer/perception_replayer_common.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index 01079fdc3f8c1..f7453ad32ce58 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -153,6 +153,11 @@ def kill_online_perception_node(self): pass def binary_search(self, data, timestamp): + if data[-1][0] < timestamp: + return data[-1][1] + elif data[0][0] > timestamp: + return data[0][1] + low, high = 0, len(data) - 1 while low <= high: From a92b251516c0fdd2c8a7625fe725dfc25c046ba8 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 18 Oct 2023 18:39:10 +0900 Subject: [PATCH 502/547] perf(perception_reproducer): improve ego pose extraction (#5344) perf(perception_reproducer): imporove ego pose extraction Signed-off-by: kosuke55 --- .../perception_replayer/perception_replayer_common.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py index f7453ad32ce58..7bf54c0278a27 100644 --- a/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py +++ b/planning/planning_debug_tools/scripts/perception_replayer/perception_replayer_common.py @@ -180,10 +180,4 @@ def find_topics_by_timestamp(self, timestamp): return objects_data, traffic_signals_data def find_ego_odom_by_timestamp(self, timestamp): - ego_odom_data = None - for data in self.rosbag_ego_odom_data: - if timestamp < data[0]: - ego_odom_data = data[1] - break - - return ego_odom_data + return self.binary_search(self.rosbag_ego_odom_data, timestamp) From c6278ceace794c01cdd6bdefc460a90a77793167 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 18 Oct 2023 19:02:06 +0900 Subject: [PATCH 503/547] fix(lane_change): fix debug marker (#5346) Signed-off-by: kosuke55 --- .../path_safety_checker/safety_check.cpp | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index e36115c62e148..08ebd01ba5d05 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -157,7 +157,7 @@ Polygon2d createExtendedPolygon( { debug.forward_lon_offset = forward_lon_offset; debug.backward_lon_offset = backward_lon_offset; - debug.lat_offset = (left_lat_offset + right_lat_offset) / 2; + debug.lat_offset = std::max(std::abs(left_lat_offset), std::abs(right_lat_offset)); } const auto p1 = @@ -324,17 +324,15 @@ std::vector getCollidedPolygons( const auto & ego_polygon = interpolated_data->poly; const auto & ego_velocity = interpolated_data->velocity; - { - debug.expected_ego_pose = ego_pose; - debug.expected_obj_pose = obj_pose; - debug.extended_ego_polygon = ego_polygon; - debug.extended_obj_polygon = obj_polygon; - } - // check overlap if (boost::geometry::overlaps(ego_polygon, obj_polygon)) { debug.unsafe_reason = "overlap_polygon"; collided_polygons.push_back(obj_polygon); + + debug.expected_ego_pose = ego_pose; + debug.expected_obj_pose = obj_pose; + debug.extended_ego_polygon = ego_polygon; + debug.extended_obj_polygon = obj_polygon; continue; } @@ -366,19 +364,17 @@ std::vector getCollidedPolygons( : createExtendedPolygon( obj_pose, target_object.shape, lon_offset, lat_margin, is_stopped_object, debug); - { + // check overlap with extended polygon + if (boost::geometry::overlaps(extended_ego_polygon, extended_obj_polygon)) { + debug.unsafe_reason = "overlap_extended_polygon"; + collided_polygons.push_back(obj_polygon); + debug.rss_longitudinal = rss_dist; debug.inter_vehicle_distance = min_lon_length; debug.extended_ego_polygon = extended_ego_polygon; debug.extended_obj_polygon = extended_obj_polygon; debug.is_front = is_object_front; } - - // check overlap with extended polygon - if (boost::geometry::overlaps(extended_ego_polygon, extended_obj_polygon)) { - debug.unsafe_reason = "overlap_extended_polygon"; - collided_polygons.push_back(obj_polygon); - } } return collided_polygons; From 8530b6bb4155b040f8ff845ec030aa836218b94e Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Wed, 18 Oct 2023 21:04:44 +0900 Subject: [PATCH 504/547] feat(pid_longitudinal_controller): transit to STOPPED even if the sign of the ego velocity goes opposite (#5255) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(pid_longitudinal_controller): transit to STOPPED even if the sign of the ego velocity goes opposite Signed-off-by: Takayuki Murooka * pid_longitudinal_controller.cpp を更新 Co-authored-by: Takamasa Horibe * fix Signed-off-by: Takayuki Murooka --------- Signed-off-by: Takayuki Murooka Co-authored-by: Takamasa Horibe --- .../src/pid_longitudinal_controller.cpp | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp index b80874a8a2e57..a134fd775155b 100644 --- a/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp +++ b/control/pid_longitudinal_controller/src/pid_longitudinal_controller.cpp @@ -501,9 +501,26 @@ void PidLongitudinalController::updateControlState(const ControlData & control_d m_enable_keep_stopped_until_steer_convergence && !lateral_sync_data_.is_steer_converged; const bool stopping_condition = stop_dist < p.stopping_state_stop_dist; - if ( - std::fabs(current_vel) > p.stopped_state_entry_vel || - std::fabs(current_acc) > p.stopped_state_entry_acc) { + + const bool is_stopped = std::abs(current_vel) < p.stopped_state_entry_vel && + std::abs(current_acc) < p.stopped_state_entry_acc; + // Case where the ego slips in the opposite direction of the gear due to e.g. a slope is also + // considered as a stop + const bool is_not_running = [&]() { + if (control_data.shift == Shift::Forward) { + if (is_stopped || current_vel < 0.0) { + // NOTE: Stopped or moving backward + return true; + } + } else { + if (is_stopped || 0.0 < current_vel) { + // NOTE: Stopped or moving forward + return true; + } + } + return false; + }(); + if (!is_not_running) { m_last_running_time = std::make_shared(clock_->now()); } const bool stopped_condition = From 89db33569a0b9a5e01c752b69dc3f4014db1be15 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 18 Oct 2023 21:38:00 +0900 Subject: [PATCH 505/547] feat(lane_change): disable cancel when ego is out of current lanes (#5348) --- .../src/scene_module/lane_change/interface.cpp | 2 +- .../src/scene_module/lane_change/normal.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 51875952d40fb..0a0cc3436bce7 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -147,7 +147,7 @@ ModuleStatus LaneChangeInterface::updateState() return ModuleStatus::RUNNING; } - if (module_type_->isEgoOnPreparePhase()) { + if (module_type_->isEgoOnPreparePhase() && module_type_->isAbleToReturnCurrentLane()) { RCLCPP_WARN_STREAM_THROTTLE( getLogger().get_child(module_type_->getModuleTypeStr()), *clock_, 5000, "Lane change path is unsafe. Cancel lane change."); diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index ce07cda882692..de4afc9260da0 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -510,6 +510,12 @@ bool NormalLaneChange::isAbleToReturnCurrentLane() const return false; } + if (!utils::isEgoWithinOriginalLane( + status_.current_lanes, getEgoPose(), planner_data_->parameters, + lane_change_parameters_->cancel.overhang_tolerance)) { + return false; + } + const auto nearest_idx = motion_utils::findFirstNearestSegmentIndexWithSoftConstraints( status_.lane_change_path.path.points, getEgoPose(), planner_data_->parameters.ego_nearest_dist_threshold, From 2ad1d816fa1bd31e0976fc9679c98ae2a9d56d4b Mon Sep 17 00:00:00 2001 From: Maxime CLEMENT <78338830+maxime-clem@users.noreply.github.com> Date: Wed, 18 Oct 2023 23:23:25 +0900 Subject: [PATCH 506/547] perf(drivable area expansion): use faster lateral offset and nearest line calculations (#5349) Signed-off-by: Maxime CLEMENT --- .../drivable_area_expansion.hpp | 5 ++- .../drivable_area_expansion/map_utils.hpp | 6 ++-- .../utils/drivable_area_expansion/types.hpp | 4 +++ .../drivable_area_expansion.cpp | 35 +++++++++++-------- .../drivable_area_expansion/map_utils.cpp | 14 +++++--- 5 files changed, 39 insertions(+), 25 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp index 19ea89a3ce3c7..3f6f107cdce51 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp @@ -68,14 +68,13 @@ void apply_bound_change_rate_limit( /// @brief calculate the maximum distance by which a bound can be expanded /// @param [in] path_poses input path /// @param [in] bound bound points -/// @param [in] uncrossable_lines lines that limit the bound expansion +/// @param [in] uncrossable_segments segments that limit the bound expansion, indexed in a Rtree /// @param [in] uncrossable_polygons polygons that limit the bound expansion /// @param [in] params parameters with the buffer distance to keep with lines, /// and the static maximum expansion distance std::vector calculate_maximum_distance( const std::vector & path_poses, const std::vector bound, - const std::vector & uncrossable_lines, - const std::vector & uncrossable_polygons, + const SegmentRtree & uncrossable_lines, const std::vector & uncrossable_polygons, const DrivableAreaExpansionParameters & params); /// @brief expand a bound by the given lateral distances away from the path diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp index 6f96b83237310..8c6bdb8a6943b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp @@ -25,12 +25,12 @@ namespace drivable_area_expansion { -/// @brief Extract uncrossable linestrings from the lanelet map that are in range of ego +/// @brief Extract uncrossable segments from the lanelet map that are in range of ego /// @param[in] lanelet_map lanelet map /// @param[in] ego_point point of the current ego position /// @param[in] params parameters with linestring types that cannot be crossed and maximum range -/// @return the uncrossable linestrings -MultiLineString2d extract_uncrossable_lines( +/// @return the uncrossable segments stored in a rtree +SegmentRtree extract_uncrossable_segments( const lanelet::LaneletMap & lanelet_map, const Point & ego_point, const DrivableAreaExpansionParameters & params); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp index 7db92c163f567..8da1521db6c28 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/types.hpp @@ -22,6 +22,8 @@ #include #include +#include + namespace drivable_area_expansion { using autoware_auto_perception_msgs::msg::PredictedObjects; @@ -39,6 +41,8 @@ using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; using tier4_autoware_utils::Segment2d; +typedef boost::geometry::index::rtree> SegmentRtree; + struct PointDistance { Point2d point; diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp index 25cf917d27135..3008f98331c92 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/drivable_area_expansion.cpp @@ -36,12 +36,10 @@ namespace drivable_area_expansion namespace { - Point2d convert_point(const Point & p) { return Point2d{p.x, p.y}; } - } // namespace void reuse_previous_poses( @@ -61,11 +59,17 @@ void reuse_previous_poses( const auto deviation = motion_utils::calcLateralOffset(prev_poses, path.points.front().point.pose.position); if (first_idx && deviation < params.max_reuse_deviation) { + LineString2d path_ls; + for (const auto & p : path.points) path_ls.push_back(convert_point(p.point.pose.position)); for (auto idx = *first_idx; idx < prev_poses.size(); ++idx) { - if ( - motion_utils::calcLateralOffset(path.points, prev_poses[idx].position) > - params.max_reuse_deviation) - break; + double lateral_offset = std::numeric_limits::max(); + for (auto segment_idx = 0LU; segment_idx + 1 < path_ls.size(); ++segment_idx) { + const auto projection = point_to_line_projection( + convert_point(prev_poses[idx].position), path_ls[segment_idx], + path_ls[segment_idx + 1]); + lateral_offset = std::min(projection.distance, lateral_offset); + } + if (lateral_offset > params.max_reuse_deviation) break; cropped_poses.push_back(prev_poses[idx]); cropped_curvatures.push_back(prev_curvatures[idx]); } @@ -172,8 +176,7 @@ void apply_bound_change_rate_limit( std::vector calculate_maximum_distance( const std::vector & path_poses, const std::vector bound, - const std::vector & uncrossable_lines, - const std::vector & uncrossable_polygons, + const SegmentRtree & uncrossable_segments, const std::vector & uncrossable_polygons, const DrivableAreaExpansionParameters & params) { // TODO(Maxime): improve performances (dont use bg::distance ? use rtree ?) @@ -183,9 +186,13 @@ std::vector calculate_maximum_distance( for (const auto & p : bound) bound_ls.push_back(convert_point(p)); for (const auto & p : path_poses) path_ls.push_back(convert_point(p.position)); for (auto i = 0UL; i + 1 < bound_ls.size(); ++i) { - const LineString2d segment_ls = {bound_ls[i], bound_ls[i + 1]}; - for (const auto & uncrossable_line : uncrossable_lines) { - const auto bound_to_line_dist = boost::geometry::distance(segment_ls, uncrossable_line); + const Segment2d segment_ls = {bound_ls[i], bound_ls[i + 1]}; + std::vector query_result; + boost::geometry::index::query( + uncrossable_segments, boost::geometry::index::nearest(segment_ls, 1), + std::back_inserter(query_result)); + if (!query_result.empty()) { + const auto bound_to_line_dist = boost::geometry::distance(segment_ls, query_result.front()); const auto dist_limit = std::max(0.0, bound_to_line_dist - params.avoid_linestring_dist); maximum_distances[i] = std::min(maximum_distances[i], dist_limit); maximum_distances[i + 1] = std::min(maximum_distances[i + 1], dist_limit); @@ -267,7 +274,7 @@ void expand_drivable_area( // crop first/last non deviating path_poses const auto & params = planner_data->drivable_area_expansion_parameters; const auto & route_handler = *planner_data->route_handler; - const auto uncrossable_lines = extract_uncrossable_lines( + const auto uncrossable_segments = extract_uncrossable_segments( *route_handler.getLaneletMapPtr(), planner_data->self_odometry->pose.pose.position, params); const auto uncrossable_polygons = create_object_footprints(*planner_data->dynamic_object, params); const auto preprocessing_ms = stop_watch.toc("preprocessing"); @@ -295,9 +302,9 @@ void expand_drivable_area( stop_watch.tic("max_dist"); const auto max_left_expansions = calculate_maximum_distance( - path_poses, path.left_bound, uncrossable_lines, uncrossable_polygons, params); + path_poses, path.left_bound, uncrossable_segments, uncrossable_polygons, params); const auto max_right_expansions = calculate_maximum_distance( - path_poses, path.right_bound, uncrossable_lines, uncrossable_polygons, params); + path_poses, path.right_bound, uncrossable_segments, uncrossable_polygons, params); for (auto i = 0LU; i < left_expansions.size(); ++i) left_expansions[i] = std::min(left_expansions[i], max_left_expansions[i]); for (auto i = 0LU; i < right_expansions.size(); ++i) diff --git a/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp b/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp index deeb787cf39f6..6ed14138c62e4 100644 --- a/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp +++ b/planning/behavior_path_planner/src/utils/drivable_area_expansion/map_utils.cpp @@ -24,22 +24,26 @@ namespace drivable_area_expansion { -MultiLineString2d extract_uncrossable_lines( +SegmentRtree extract_uncrossable_segments( const lanelet::LaneletMap & lanelet_map, const Point & ego_point, const DrivableAreaExpansionParameters & params) { - MultiLineString2d uncrossable_lines_in_range; + SegmentRtree uncrossable_segments_in_range; LineString2d line; const auto ego_p = Point2d{ego_point.x, ego_point.y}; for (const auto & ls : lanelet_map.lineStringLayer) { if (has_types(ls, params.avoid_linestring_types)) { line.clear(); for (const auto & p : ls) line.push_back(Point2d{p.x(), p.y()}); - if (boost::geometry::distance(line, ego_p) < params.max_path_arc_length) - uncrossable_lines_in_range.push_back(line); + for (auto segment_idx = 0LU; segment_idx + 1 < line.size(); ++segment_idx) { + Segment2d segment = {line[segment_idx], line[segment_idx + 1]}; + if (boost::geometry::distance(segment, ego_p) < params.max_path_arc_length) { + uncrossable_segments_in_range.insert(segment); + } + } } } - return uncrossable_lines_in_range; + return uncrossable_segments_in_range; } bool has_types(const lanelet::ConstLineString3d & ls, const std::vector & types) From b0196439a149318a10f4cb3a22d2ee7a541ff886 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Thu, 19 Oct 2023 10:18:31 +0900 Subject: [PATCH 507/547] fix(goal_planner): mutex lock for all getter and setter of status (#5202) * fix(goal_planner): mutex lock for all getter and setter of status Signed-off-by: kosuke55 * use transaction instead of recursive_mutex Signed-off-by: kosuke55 fix Signed-off-by: kosuke55 * fix increment Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../goal_planner/goal_planner_module.hpp | 175 +++++-- .../utils/goal_planner/util.hpp | 2 +- .../goal_planner/goal_planner_module.cpp | 476 ++++++++++-------- .../src/utils/goal_planner/util.cpp | 2 +- 4 files changed, 396 insertions(+), 259 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index a82cff16756f8..632872471ce29 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -74,28 +74,143 @@ enum class PathType { FREESPACE, }; -struct PullOverStatus +class PullOverStatus; // Forward declaration for Transaction +// class that locks the PullOverStatus when multiple values are being updated from +// an external source. +class Transaction { - std::shared_ptr pull_over_path{}; - std::shared_ptr lane_parking_pull_over_path{}; - size_t current_path_idx{0}; - bool require_increment_{true}; // if false, keep current path idx. - std::shared_ptr prev_stop_path{nullptr}; - std::shared_ptr prev_stop_path_after_approval{nullptr}; - // stop path after approval, stop path is not updated until safety is confirmed - lanelet::ConstLanelets current_lanes{}; // TODO(someone): explain - lanelet::ConstLanelets pull_over_lanes{}; // TODO(someone): explain - std::vector lanes{}; // current + pull_over - bool has_decided_path{false}; // if true, the path has is decided and safe against static objects - bool is_safe_static_objects{false}; // current path is safe against *static* objects - bool is_safe_dynamic_objects{false}; // current path is safe against *dynamic* objects - bool prev_is_safe{false}; - bool prev_is_safe_dynamic_objects{false}; - bool has_decided_velocity{false}; - bool has_requested_approval{false}; - bool is_ready{false}; +public: + explicit Transaction(PullOverStatus & status); + ~Transaction(); + +private: + PullOverStatus & status_; }; +#define DEFINE_SETTER_GETTER(TYPE, NAME) \ +public: \ + void set_##NAME(const TYPE & value) \ + { \ + if (!is_in_transaction_) { \ + const std::lock_guard lock(mutex_); \ + NAME##_ = value; \ + } else { \ + NAME##_ = value; \ + } \ + } \ + \ + TYPE get_##NAME() const \ + { \ + if (!is_in_transaction_) { \ + const std::lock_guard lock(mutex_); \ + return NAME##_; \ + } \ + return NAME##_; \ + } + +class PullOverStatus +{ +public: + // Reset all data members to their initial states + void reset() + { + const std::lock_guard lock(mutex_); + pull_over_path_ = nullptr; + lane_parking_pull_over_path_ = nullptr; + current_path_idx_ = 0; + require_increment_ = true; + prev_stop_path_ = nullptr; + prev_stop_path_after_approval_ = nullptr; + current_lanes_.clear(); + pull_over_lanes_.clear(); + lanes_.clear(); + has_decided_path_ = false; + is_safe_static_objects_ = false; + is_safe_dynamic_objects_ = false; + prev_is_safe_ = false; + prev_is_safe_dynamic_objects_ = false; + has_decided_velocity_ = false; + has_requested_approval_ = false; + is_ready_ = false; + } + + // lock all data members + Transaction startTransaction() { return Transaction(*this); } + + DEFINE_SETTER_GETTER(std::shared_ptr, pull_over_path) + DEFINE_SETTER_GETTER(std::shared_ptr, lane_parking_pull_over_path) + DEFINE_SETTER_GETTER(size_t, current_path_idx) + DEFINE_SETTER_GETTER(bool, require_increment) + DEFINE_SETTER_GETTER(std::shared_ptr, prev_stop_path) + DEFINE_SETTER_GETTER(std::shared_ptr, prev_stop_path_after_approval) + DEFINE_SETTER_GETTER(lanelet::ConstLanelets, current_lanes) + DEFINE_SETTER_GETTER(lanelet::ConstLanelets, pull_over_lanes) + DEFINE_SETTER_GETTER(std::vector, lanes) + DEFINE_SETTER_GETTER(bool, has_decided_path) + DEFINE_SETTER_GETTER(bool, is_safe_static_objects) + DEFINE_SETTER_GETTER(bool, is_safe_dynamic_objects) + DEFINE_SETTER_GETTER(bool, prev_is_safe) + DEFINE_SETTER_GETTER(bool, prev_is_safe_dynamic_objects) + DEFINE_SETTER_GETTER(bool, has_decided_velocity) + DEFINE_SETTER_GETTER(bool, has_requested_approval) + DEFINE_SETTER_GETTER(bool, is_ready) + DEFINE_SETTER_GETTER(std::shared_ptr, last_approved_time) + DEFINE_SETTER_GETTER(std::shared_ptr, last_increment_time) + DEFINE_SETTER_GETTER(std::shared_ptr, last_path_update_time) + DEFINE_SETTER_GETTER(std::shared_ptr, last_approved_pose) + DEFINE_SETTER_GETTER(std::optional, modified_goal_pose) + DEFINE_SETTER_GETTER(Pose, refined_goal_pose) + DEFINE_SETTER_GETTER(GoalCandidates, goal_candidates) + DEFINE_SETTER_GETTER(std::vector, pull_over_path_candidates) + DEFINE_SETTER_GETTER(std::optional, closest_start_pose) + + void push_goal_candidate(const GoalCandidate & goal_candidate) + { + std::lock_guard lock(mutex_); + goal_candidates_.push_back(goal_candidate); + } + +private: + std::shared_ptr pull_over_path_{nullptr}; + std::shared_ptr lane_parking_pull_over_path_{nullptr}; + size_t current_path_idx_{0}; + bool require_increment_{true}; + std::shared_ptr prev_stop_path_{nullptr}; + std::shared_ptr prev_stop_path_after_approval_{nullptr}; + lanelet::ConstLanelets current_lanes_{}; + lanelet::ConstLanelets pull_over_lanes_{}; + std::vector lanes_{}; + bool has_decided_path_{false}; + bool is_safe_static_objects_{false}; + bool is_safe_dynamic_objects_{false}; + bool prev_is_safe_{false}; + bool prev_is_safe_dynamic_objects_{false}; + bool has_decided_velocity_{false}; + bool has_requested_approval_{false}; + bool is_ready_{false}; + + // save last time and pose + std::shared_ptr last_approved_time_; + std::shared_ptr last_increment_time_; + std::shared_ptr last_path_update_time_; + std::shared_ptr last_approved_pose_; + + // goal modification + std::optional modified_goal_pose_; + Pose refined_goal_pose_{}; + GoalCandidates goal_candidates_{}; + + // pull over path + std::vector pull_over_path_candidates_; + std::optional closest_start_pose_; + + friend class Transaction; + mutable std::mutex mutex_; + bool is_in_transaction_{false}; +}; + +#undef DEFINE_SETTER_GETTER + struct FreespacePlannerDebugData { bool is_planning{false}; @@ -155,8 +270,6 @@ class GoalPlannerModule : public SceneModuleInterface bool canTransitIdleToRunningState() override { return false; } - PullOverStatus status_; - mutable StartGoalPlannerData goal_planner_data_; std::shared_ptr parameters_; @@ -174,29 +287,19 @@ class GoalPlannerModule : public SceneModuleInterface // goal searcher std::shared_ptr goal_searcher_; - std::optional modified_goal_pose_; - Pose refined_goal_pose_{}; - GoalCandidates goal_candidates_{}; // collision detector // need to be shared_ptr to be used in planner and goal searcher std::shared_ptr occupancy_grid_map_; - // pull over path - std::vector pull_over_path_candidates_; - std::optional closest_start_pose_; - // check stopped and stuck state std::deque odometry_buffer_stopped_; std::deque odometry_buffer_stuck_; tier4_autoware_utils::LinearRing2d vehicle_footprint_; - // save last time and pose - std::unique_ptr last_approved_time_; - std::unique_ptr last_increment_time_; - std::unique_ptr last_path_update_time_; - std::unique_ptr last_approved_pose_; + std::mutex mutex_; + PullOverStatus status_; // approximate distance from the start point to the end point of pull_over. // this is used as an assumed value to decelerate, etc., before generating the actual path. @@ -210,7 +313,6 @@ class GoalPlannerModule : public SceneModuleInterface // pre-generate lane parking paths in a separate thread rclcpp::TimerBase::SharedPtr lane_parking_timer_; rclcpp::CallbackGroup::SharedPtr lane_parking_timer_cb_group_; - std::mutex mutex_; // generate freespace parking paths in a separate thread rclcpp::TimerBase::SharedPtr freespace_parking_timer_; @@ -245,7 +347,6 @@ class GoalPlannerModule : public SceneModuleInterface bool hasFinishedCurrentPath(); bool isOnModifiedGoal() const; double calcModuleRequestLength() const; - void resetStatus(); bool needPathUpdate(const double path_update_duration) const; bool isStuck(); bool hasDecidedPath() const; @@ -268,8 +369,8 @@ class GoalPlannerModule : public SceneModuleInterface BehaviorModuleOutput planWithGoalModification(); BehaviorModuleOutput planWaitingApprovalWithGoalModification(); void selectSafePullOverPath(); - void sortPullOverPathCandidatesByGoalPriority( - std::vector & pull_over_path_candidates, + std::vector sortPullOverPathCandidatesByGoalPriority( + const std::vector & pull_over_path_candidates, const GoalCandidates & goal_candidates) const; // deal with pull over partial paths diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp index 1dc6cc411a31f..062a84bcd5aef 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/util.hpp @@ -63,7 +63,7 @@ MarkerArray createPosesMarkerArray( MarkerArray createTextsMarkerArray( const std::vector & poses, std::string && ns, const std_msgs::msg::ColorRGBA & color); MarkerArray createGoalCandidatesMarkerArray( - GoalCandidates & goal_candidates, const std_msgs::msg::ColorRGBA & color); + const GoalCandidates & goal_candidates, const std_msgs::msg::ColorRGBA & color); MarkerArray createNumObjectsToAvoidTextsMarkerArray( const GoalCandidates & goal_candidates, std::string && ns, const std_msgs::msg::ColorRGBA & color); diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index c6e68f8e81af7..dc1ac1466397d 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -51,6 +51,18 @@ using tier4_autoware_utils::inverseTransformPose; namespace behavior_path_planner { +Transaction::Transaction(PullOverStatus & status) : status_(status) +{ + status_.mutex_.lock(); + status_.is_in_transaction_ = true; +} + +Transaction::~Transaction() +{ + status_.mutex_.unlock(); + status_.is_in_transaction_ = false; +} + GoalPlannerModule::GoalPlannerModule( const std::string & name, rclcpp::Node & node, const std::shared_ptr & parameters, @@ -113,16 +125,7 @@ GoalPlannerModule::GoalPlannerModule( freespace_parking_timer_cb_group_); } - resetStatus(); -} - -void GoalPlannerModule::resetStatus() -{ - PullOverStatus initial_status{}; - status_ = initial_status; - pull_over_path_candidates_.clear(); - closest_start_pose_.reset(); - goal_candidates_.clear(); + status_.reset(); } // This function is needed for waiting for planner_data_ @@ -139,17 +142,15 @@ void GoalPlannerModule::updateOccupancyGrid() void GoalPlannerModule::onTimer() { // already generated pull over candidate paths - if (!pull_over_path_candidates_.empty()) { + if (!status_.get_pull_over_path_candidates().empty()) { return; } // goals are not yet available. - if (goal_candidates_.empty()) { + if (status_.get_goal_candidates().empty()) { return; } - mutex_.lock(); - const auto goal_candidates = goal_candidates_; - mutex_.unlock(); + const auto goal_candidates = status_.get_goal_candidates(); // generate valid pull over path candidates and calculate closest start pose const auto current_lanes = utils::getExtendedCurrentLanes( @@ -177,7 +178,6 @@ void GoalPlannerModule::onTimer() } } }; - // plan candidate paths and set them to the member variable if (parameters_->path_priority == "efficient_path") { for (const auto & planner : pull_over_planners_) { @@ -199,10 +199,9 @@ void GoalPlannerModule::onTimer() } // set member variables - mutex_.lock(); - pull_over_path_candidates_ = path_candidates; - closest_start_pose_ = closest_start_pose; - mutex_.unlock(); + const auto transaction = status_.startTransaction(); + status_.set_pull_over_path_candidates(path_candidates); + status_.set_closest_start_pose(closest_start_pose); } void GoalPlannerModule::onFreespaceParkingTimer() @@ -457,19 +456,23 @@ ModuleStatus GoalPlannerModule::updateState() bool GoalPlannerModule::planFreespacePath() { - mutex_.lock(); goal_searcher_->setPlannerData(planner_data_); - goal_searcher_->update(goal_candidates_); - const auto goal_candidates = goal_candidates_; - debug_data_.freespace_planner.num_goal_candidates = goal_candidates.size(); - debug_data_.freespace_planner.is_planning = true; - mutex_.unlock(); + auto goal_candidates = status_.get_goal_candidates(); + goal_searcher_->update(goal_candidates); + status_.set_goal_candidates(goal_candidates); + + { + const std::lock_guard lock(mutex_); + debug_data_.freespace_planner.num_goal_candidates = goal_candidates.size(); + debug_data_.freespace_planner.is_planning = true; + } for (size_t i = 0; i < goal_candidates.size(); i++) { const auto goal_candidate = goal_candidates.at(i); - mutex_.lock(); - debug_data_.freespace_planner.current_goal_idx = i; - mutex_.unlock(); + { + const std::lock_guard lock(mutex_); + debug_data_.freespace_planner.current_goal_idx = i; + } if (!goal_candidate.is_safe) { continue; @@ -480,17 +483,22 @@ bool GoalPlannerModule::planFreespacePath() if (!freespace_path) { continue; } - mutex_.lock(); - status_.pull_over_path = std::make_shared(*freespace_path); - status_.current_path_idx = 0; - status_.is_safe_static_objects = true; - modified_goal_pose_ = goal_candidate; - last_path_update_time_ = std::make_unique(clock_->now()); - debug_data_.freespace_planner.is_planning = false; - mutex_.unlock(); + + { + const auto transaction = status_.startTransaction(); + status_.set_pull_over_path(std::make_shared(*freespace_path)); + status_.set_current_path_idx(0); + status_.set_is_safe_static_objects(true); + status_.set_modified_goal_pose(goal_candidate); + status_.set_last_path_update_time(std::make_shared(clock_->now())); + const std::lock_guard lock(mutex_); + debug_data_.freespace_planner.is_planning = false; + } + return true; } + const std::lock_guard lock(mutex_); debug_data_.freespace_planner.is_planning = false; return false; } @@ -502,11 +510,11 @@ void GoalPlannerModule::returnToLaneParking() return; } - if (!status_.lane_parking_pull_over_path) { + if (!status_.get_lane_parking_pull_over_path()) { return; } - const PathWithLaneId path = status_.lane_parking_pull_over_path->getFullPath(); + const PathWithLaneId path = status_.get_lane_parking_pull_over_path()->getFullPath(); if (checkCollision(path)) { return; } @@ -519,13 +527,14 @@ void GoalPlannerModule::returnToLaneParking() return; } - mutex_.lock(); - status_.is_safe_static_objects = true; - status_.has_decided_path = false; - status_.pull_over_path = status_.lane_parking_pull_over_path; - status_.current_path_idx = 0; - last_path_update_time_ = std::make_unique(clock_->now()); - mutex_.unlock(); + { + const auto transaction = status_.startTransaction(); + status_.set_is_safe_static_objects(true); + status_.set_has_decided_path(false); + status_.set_pull_over_path(status_.get_lane_parking_pull_over_path()); + status_.set_current_path_idx(0); + status_.set_last_path_update_time(std::make_shared(clock_->now())); + } RCLCPP_INFO(getLogger(), "return to lane parking"); } @@ -537,22 +546,22 @@ void GoalPlannerModule::generateGoalCandidates() // with old architecture, module instance is not cleared when new route is received // so need to reset status here. // todo: move this check out of this function after old architecture is removed - if (!goal_candidates_.empty()) { + if (!status_.get_goal_candidates().empty()) { return; } // calculate goal candidates const Pose goal_pose = route_handler->getOriginalGoalPose(); - refined_goal_pose_ = calcRefinedGoal(goal_pose); + status_.set_refined_goal_pose(calcRefinedGoal(goal_pose)); if (goal_planner_utils::isAllowedGoalModification(route_handler)) { goal_searcher_->setPlannerData(planner_data_); - goal_searcher_->setReferenceGoal(refined_goal_pose_); - goal_candidates_ = goal_searcher_->search(); + goal_searcher_->setReferenceGoal(status_.get_refined_goal_pose()); + status_.set_goal_candidates(goal_searcher_->search()); } else { GoalCandidate goal_candidate{}; goal_candidate.goal_pose = goal_pose; goal_candidate.distance_from_original_goal = 0.0; - goal_candidates_.push_back(goal_candidate); + status_.push_goal_candidate(goal_candidate); } } @@ -573,10 +582,12 @@ BehaviorModuleOutput GoalPlannerModule::plan() } } -void GoalPlannerModule::sortPullOverPathCandidatesByGoalPriority( - std::vector & pull_over_path_candidates, +std::vector GoalPlannerModule::sortPullOverPathCandidatesByGoalPriority( + const std::vector & pull_over_path_candidates, const GoalCandidates & goal_candidates) const { + auto sorted_pull_over_path_candidates = pull_over_path_candidates; + // Create a map of goal_id to its index in goal_candidates std::map goal_id_to_index; for (size_t i = 0; i < goal_candidates.size(); ++i) { @@ -585,7 +596,7 @@ void GoalPlannerModule::sortPullOverPathCandidatesByGoalPriority( // Sort pull_over_path_candidates based on the order in goal_candidates std::stable_sort( - pull_over_path_candidates.begin(), pull_over_path_candidates.end(), + sorted_pull_over_path_candidates.begin(), sorted_pull_over_path_candidates.end(), [&goal_id_to_index](const auto & a, const auto & b) { return goal_id_to_index[a.goal_id] < goal_id_to_index[b.goal_id]; }); @@ -593,7 +604,7 @@ void GoalPlannerModule::sortPullOverPathCandidatesByGoalPriority( // Sort pull_over_path_candidates based on the order in efficient_path_order if (parameters_->path_priority == "efficient_path") { std::stable_sort( - pull_over_path_candidates.begin(), pull_over_path_candidates.end(), + sorted_pull_over_path_candidates.begin(), sorted_pull_over_path_candidates.end(), [this](const auto & a, const auto & b) { const auto & order = parameters_->efficient_path_order; const auto a_pos = std::find(order.begin(), order.end(), magic_enum::enum_name(a.type)); @@ -601,20 +612,27 @@ void GoalPlannerModule::sortPullOverPathCandidatesByGoalPriority( return a_pos < b_pos; }); } + + return sorted_pull_over_path_candidates; } void GoalPlannerModule::selectSafePullOverPath() { // select safe lane pull over path from candidates - mutex_.lock(); - goal_searcher_->setPlannerData(planner_data_); - goal_searcher_->update(goal_candidates_); - sortPullOverPathCandidatesByGoalPriority(pull_over_path_candidates_, goal_candidates_); - const auto pull_over_path_candidates = pull_over_path_candidates_; - const auto goal_candidates = goal_candidates_; - mutex_.unlock(); + std::vector pull_over_path_candidates{}; + GoalCandidates goal_candidates{}; + { + const auto transaction = status_.startTransaction(); + goal_searcher_->setPlannerData(planner_data_); + goal_candidates = status_.get_goal_candidates(); + goal_searcher_->update(goal_candidates); + status_.set_goal_candidates(goal_candidates); + status_.set_pull_over_path_candidates(sortPullOverPathCandidatesByGoalPriority( + status_.get_pull_over_path_candidates(), status_.get_goal_candidates())); + pull_over_path_candidates = status_.get_pull_over_path_candidates(); + status_.set_is_safe_static_objects(false); + } - status_.is_safe_static_objects = false; for (const auto & pull_over_path : pull_over_path_candidates) { // check if goal is safe const auto goal_candidate_it = std::find_if( @@ -633,71 +651,69 @@ void GoalPlannerModule::selectSafePullOverPath() continue; } - status_.is_safe_static_objects = true; - mutex_.lock(); - status_.pull_over_path = std::make_shared(pull_over_path); - status_.current_path_idx = 0; - status_.lane_parking_pull_over_path = status_.pull_over_path; - modified_goal_pose_ = *goal_candidate_it; - last_path_update_time_ = std::make_unique(clock_->now()); - mutex_.unlock(); + // found safe pull over path + { + const auto transaction = status_.startTransaction(); + status_.set_is_safe_static_objects(true); + status_.set_pull_over_path(std::make_shared(pull_over_path)); + status_.set_current_path_idx(0); + status_.set_lane_parking_pull_over_path(status_.get_pull_over_path()); + status_.set_modified_goal_pose(*goal_candidate_it); + status_.set_last_path_update_time(std::make_shared(clock_->now())); + } break; } + if (!status_.get_is_safe_static_objects()) { + return; + } + // decelerate before the search area start - if (status_.is_safe_static_objects) { - const auto search_start_offset_pose = calcLongitudinalOffsetPose( - status_.pull_over_path->getFullPath().points, refined_goal_pose_.position, - -parameters_->backward_goal_search_length - planner_data_->parameters.base_link2front - - approximate_pull_over_distance_); - auto & first_path = status_.pull_over_path->partial_paths.front(); - - if (search_start_offset_pose) { - decelerateBeforeSearchStart(*search_start_offset_pose, first_path); - } else { - // if already passed the search start offset pose, set pull_over_velocity to first_path. - const auto min_decel_distance = calcFeasibleDecelDistance( - planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, - parameters_->pull_over_velocity); - for (auto & p : first_path.points) { - const double distance_from_ego = calcSignedArcLengthFromEgo(first_path, p.point.pose); - if (min_decel_distance && distance_from_ego < *min_decel_distance) { - continue; - } - p.point.longitudinal_velocity_mps = std::min( - p.point.longitudinal_velocity_mps, static_cast(parameters_->pull_over_velocity)); + const auto search_start_offset_pose = calcLongitudinalOffsetPose( + status_.get_pull_over_path()->getFullPath().points, status_.get_refined_goal_pose().position, + -parameters_->backward_goal_search_length - planner_data_->parameters.base_link2front - + approximate_pull_over_distance_); + auto & first_path = status_.get_pull_over_path()->partial_paths.front(); + if (search_start_offset_pose) { + decelerateBeforeSearchStart(*search_start_offset_pose, first_path); + } else { + // if already passed the search start offset pose, set pull_over_velocity to first_path. + const auto min_decel_distance = calcFeasibleDecelDistance( + planner_data_, parameters_->maximum_deceleration, parameters_->maximum_jerk, + parameters_->pull_over_velocity); + for (auto & p : first_path.points) { + const double distance_from_ego = calcSignedArcLengthFromEgo(first_path, p.point.pose); + if (min_decel_distance && distance_from_ego < *min_decel_distance) { + continue; } + p.point.longitudinal_velocity_mps = std::min( + p.point.longitudinal_velocity_mps, static_cast(parameters_->pull_over_velocity)); } } - - // generate drivable area for each partial path - for (auto & path : status_.pull_over_path->partial_paths) { - const size_t ego_idx = planner_data_->findEgoIndex(path.points); - utils::clipPathLength(path, ego_idx, planner_data_->parameters); - } } void GoalPlannerModule::setLanes() { - status_.current_lanes = utils::getExtendedCurrentLanes( + const auto transaction = status_.startTransaction(); + status_.set_current_lanes(utils::getExtendedCurrentLanes( planner_data_, parameters_->backward_goal_search_length, parameters_->forward_goal_search_length, - /*forward_only_in_route*/ false); - status_.pull_over_lanes = goal_planner_utils::getPullOverLanes( + /*forward_only_in_route*/ false)); + status_.set_pull_over_lanes(goal_planner_utils::getPullOverLanes( *(planner_data_->route_handler), left_side_parking_, parameters_->backward_goal_search_length, - parameters_->forward_goal_search_length); - status_.lanes = - utils::generateDrivableLanesWithShoulderLanes(status_.current_lanes, status_.pull_over_lanes); + parameters_->forward_goal_search_length)); + status_.set_lanes(utils::generateDrivableLanesWithShoulderLanes( + status_.get_current_lanes(), status_.get_pull_over_lanes())); } void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) { - if (!status_.is_safe_static_objects) { + if (!status_.get_is_safe_static_objects()) { // situation : not safe against static objects use stop_path setStopPath(output); } else if ( parameters_->safety_check_params.enable_safety_check && !isSafePath() && - status_.has_decided_path && isActivated()) { + status_.get_has_decided_path() && isActivated()) { // situation : not safe against dynamic objects after approval // insert stop point in current path if ego is able to stop with acceleration and jerk // constraints @@ -722,36 +738,37 @@ void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) setModifiedGoal(output); // set hazard and turn signal - if (status_.has_decided_path) { + if (status_.get_has_decided_path()) { setTurnSignalInfo(output); } // for the next loop setOutput(). // this is used to determine whether to generate a new stop path or keep the current stop path. - status_.prev_is_safe = status_.is_safe_static_objects; - status_.prev_is_safe_dynamic_objects = - parameters_->safety_check_params.enable_safety_check ? isSafePath() : true; + const auto transaction = status_.startTransaction(); + status_.set_prev_is_safe(status_.get_is_safe_static_objects()); + status_.set_prev_is_safe_dynamic_objects( + parameters_->safety_check_params.enable_safety_check ? isSafePath() : true); } void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) { - if (status_.prev_is_safe || status_.prev_stop_path == nullptr) { + if (status_.get_prev_is_safe() || !status_.get_prev_stop_path()) { // safe -> not_safe or no prev_stop_path: generate new stop_path output.path = std::make_shared(generateStopPath()); - status_.prev_stop_path = output.path; + const auto transaction = status_.startTransaction(); + status_.set_prev_stop_path(output.path); // set stop path as pull over path - mutex_.lock(); - PullOverPath pull_over_path{}; - status_.pull_over_path = std::make_shared(pull_over_path); - status_.current_path_idx = 0; - status_.pull_over_path->partial_paths.push_back(*output.path); - last_path_update_time_ = std::make_unique(clock_->now()); - mutex_.unlock(); + auto stop_pull_over_path = std::make_shared(); + stop_pull_over_path->partial_paths.push_back(*output.path); + status_.set_pull_over_path(stop_pull_over_path); + status_.set_current_path_idx(0); + status_.set_last_path_update_time(std::make_shared(clock_->now())); + RCLCPP_WARN_THROTTLE( getLogger(), *clock_, 5000, "Not found safe pull_over path, generate stop path"); } else { // not_safe -> not_safe: use previous stop path - output.path = status_.prev_stop_path; + output.path = status_.get_prev_stop_path(); RCLCPP_WARN_THROTTLE( getLogger(), *clock_, 5000, "Not found safe pull_over path, use previous stop path"); } @@ -761,7 +778,7 @@ void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) void GoalPlannerModule::setStopPathFromCurrentPath(BehaviorModuleOutput & output) { // safe or not safe(no feasible stop_path found) -> not_safe: try to find new feasible stop_path - if (status_.prev_is_safe_dynamic_objects || status_.prev_stop_path_after_approval == nullptr) { + if (status_.get_prev_is_safe_dynamic_objects() || !status_.get_prev_stop_path_after_approval()) { auto current_path = getCurrentPath(); const auto stop_path = behavior_path_planner::utils::start_goal_planner_common::generateFeasibleStopPath( @@ -769,7 +786,7 @@ void GoalPlannerModule::setStopPathFromCurrentPath(BehaviorModuleOutput & output parameters_->maximum_jerk_for_stop); if (stop_path) { output.path = std::make_shared(*stop_path); - status_.prev_stop_path_after_approval = output.path; + status_.set_prev_stop_path_after_approval(output.path); RCLCPP_WARN_THROTTLE(getLogger(), *clock_, 5000, "Collision detected, generate stop path"); } else { output.path = std::make_shared(getCurrentPath()); @@ -777,11 +794,10 @@ void GoalPlannerModule::setStopPathFromCurrentPath(BehaviorModuleOutput & output getLogger(), *clock_, 5000, "Collision detected, no feasible stop path found, cannot stop."); } - std::lock_guard lock(mutex_); - last_path_update_time_ = std::make_unique(clock_->now()); + status_.set_last_path_update_time(std::make_shared(clock_->now())); } else { // not_safe safe(no feasible stop path found) -> not_safe: use previous stop path - output.path = status_.prev_stop_path_after_approval; + output.path = status_.get_prev_stop_path_after_approval(); RCLCPP_WARN_THROTTLE(getLogger(), *clock_, 5000, "Collision detected, use previous stop path"); } output.reference_path = getPreviousModuleOutput().reference_path; @@ -789,13 +805,13 @@ void GoalPlannerModule::setStopPathFromCurrentPath(BehaviorModuleOutput & output void GoalPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) const { - if (status_.pull_over_path->type == PullOverPlannerType::FREESPACE) { + if (status_.get_pull_over_path()->type == PullOverPlannerType::FREESPACE) { const double drivable_area_margin = planner_data_->parameters.vehicle_width; output.drivable_area_info.drivable_margin = planner_data_->parameters.vehicle_width / 2.0 + drivable_area_margin; } else { const auto target_drivable_lanes = utils::getNonOverlappingExpandedLanes( - *output.path, status_.lanes, planner_data_->drivable_area_expansion_parameters); + *output.path, status_.get_lanes(), planner_data_->drivable_area_expansion_parameters); DrivableAreaInfo current_drivable_area_info; current_drivable_area_info.drivable_lanes = target_drivable_lanes; @@ -807,10 +823,10 @@ void GoalPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) const void GoalPlannerModule::setModifiedGoal(BehaviorModuleOutput & output) const { const auto & route_handler = planner_data_->route_handler; - if (status_.is_safe_static_objects) { + if (status_.get_is_safe_static_objects()) { PoseWithUuidStamped modified_goal{}; modified_goal.uuid = route_handler->getRouteUuid(); - modified_goal.pose = modified_goal_pose_->goal_pose; + modified_goal.pose = status_.get_modified_goal_pose()->goal_pose; modified_goal.header = route_handler->getRouteHeader(); output.modified_goal = modified_goal; } else { @@ -850,12 +866,12 @@ void GoalPlannerModule::updateSteeringFactor( bool GoalPlannerModule::hasDecidedPath() const { // once decided, keep the decision - if (status_.has_decided_path) { + if (status_.get_has_decided_path()) { return true; } // if path is not safe, not decided - if (!status_.is_safe_static_objects) { + if (!status_.get_is_safe_static_objects()) { return false; } @@ -867,10 +883,10 @@ bool GoalPlannerModule::hasDecidedPath() const return false; } const size_t start_pose_segment_idx = motion_utils::findNearestSegmentIndex( - getCurrentPath().points, status_.pull_over_path->start_pose.position); + getCurrentPath().points, status_.get_pull_over_path()->start_pose.position); const double dist_to_parking_start_pose = calcSignedArcLength( getCurrentPath().points, current_pose.position, *ego_segment_idx, - status_.pull_over_path->start_pose.position, start_pose_segment_idx); + status_.get_pull_over_path()->start_pose.position, start_pose_segment_idx); return dist_to_parking_start_pose < parameters_->decide_path_distance; } @@ -879,15 +895,15 @@ void GoalPlannerModule::decideVelocity() const double current_vel = planner_data_->self_odometry->twist.twist.linear.x; // decide velocity to guarantee turn signal lighting time - if (!status_.has_decided_velocity) { - auto & first_path = status_.pull_over_path->partial_paths.front(); + if (!status_.get_has_decided_velocity()) { + auto & first_path = status_.get_pull_over_path()->partial_paths.front(); const auto vel = static_cast(std::max(current_vel, parameters_->pull_over_minimum_velocity)); for (auto & p : first_path.points) { p.point.longitudinal_velocity_mps = std::min(p.point.longitudinal_velocity_mps, vel); } } - status_.has_decided_velocity = true; + status_.set_has_decided_velocity(true); } BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() @@ -901,21 +917,26 @@ BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() setLanes(); // Check if it needs to decide path - status_.has_decided_path = hasDecidedPath(); + status_.set_has_decided_path(hasDecidedPath()); // Use decided path - if (status_.has_decided_path) { + if (status_.get_has_decided_path()) { if (isActivated() && isWaitingApproval()) { - last_approved_time_ = std::make_unique(clock_->now()); - last_approved_pose_ = std::make_unique(planner_data_->self_odometry->pose.pose); + { + const auto transaction = status_.startTransaction(); + status_.set_last_approved_time(std::make_shared(clock_->now())); + status_.set_last_approved_pose( + std::make_shared(planner_data_->self_odometry->pose.pose)); + } clearWaitingApproval(); decideVelocity(); } transitionToNextPathIfFinishingCurrentPath(); - } else if (!pull_over_path_candidates_.empty() && needPathUpdate(path_update_duration)) { + } else if ( + !status_.get_pull_over_path_candidates().empty() && needPathUpdate(path_update_duration)) { // if the final path is not decided and enough time has passed since last path update, // select safe path from lane parking pull over path candidates - // and set it to status_.pull_over_path + // and set it to status_.get_pull_over_path() selectSafePullOverPath(); } // else: stop path is generated and set by setOutput() @@ -923,21 +944,21 @@ BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() // set output and status BehaviorModuleOutput output{}; setOutput(output); - path_candidate_ = std::make_shared(status_.pull_over_path->getFullPath()); + path_candidate_ = std::make_shared(status_.get_pull_over_path()->getFullPath()); path_reference_ = getPreviousModuleOutput().reference_path; // return to lane parking if it is possible - if (status_.pull_over_path->type == PullOverPlannerType::FREESPACE) { + if (status_.get_pull_over_path()->type == PullOverPlannerType::FREESPACE) { returnToLaneParking(); } const auto distance_to_path_change = calcDistanceToPathChange(); - if (status_.has_decided_path) { + if (status_.get_has_decided_path()) { updateRTCStatus(distance_to_path_change.first, distance_to_path_change.second); } // TODO(tkhmy) add handle status TRYING updateSteeringFactor( - {status_.pull_over_path->start_pose, modified_goal_pose_->goal_pose}, + {status_.get_pull_over_path()->start_pose, status_.get_modified_goal_pose()->goal_pose}, {distance_to_path_change.first, distance_to_path_change.second}, SteeringFactor::TURNING); // For debug @@ -947,7 +968,7 @@ BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() printParkingPositionError(); } - setStopReason(StopReason::GOAL_PLANNER, status_.pull_over_path->getFullPath()); + setStopReason(StopReason::GOAL_PLANNER, status_.get_pull_over_path()->getFullPath()); return output; } @@ -976,20 +997,21 @@ BehaviorModuleOutput GoalPlannerModule::planWaitingApprovalWithGoalModification( out.modified_goal = plan().modified_goal; // update status_ out.path = std::make_shared(generateStopPath()); out.reference_path = getPreviousModuleOutput().reference_path; - path_candidate_ = status_.is_safe_static_objects - ? std::make_shared(status_.pull_over_path->getFullPath()) - : out.path; + path_candidate_ = + status_.get_is_safe_static_objects() + ? std::make_shared(status_.get_pull_over_path()->getFullPath()) + : out.path; path_reference_ = getPreviousModuleOutput().reference_path; const auto distance_to_path_change = calcDistanceToPathChange(); // generate drivable area info for new architecture - if (status_.pull_over_path->type == PullOverPlannerType::FREESPACE) { + if (status_.get_pull_over_path()->type == PullOverPlannerType::FREESPACE) { const double drivable_area_margin = planner_data_->parameters.vehicle_width; out.drivable_area_info.drivable_margin = planner_data_->parameters.vehicle_width / 2.0 + drivable_area_margin; } else { const auto target_drivable_lanes = utils::getNonOverlappingExpandedLanes( - *out.path, status_.lanes, planner_data_->drivable_area_expansion_parameters); + *out.path, status_.get_lanes(), planner_data_->drivable_area_expansion_parameters); DrivableAreaInfo current_drivable_area_info; current_drivable_area_info.drivable_lanes = target_drivable_lanes; @@ -997,21 +1019,21 @@ BehaviorModuleOutput GoalPlannerModule::planWaitingApprovalWithGoalModification( current_drivable_area_info, getPreviousModuleOutput().drivable_area_info); } - if (status_.has_decided_path) { + if (status_.get_has_decided_path()) { updateRTCStatus(distance_to_path_change.first, distance_to_path_change.second); } updateSteeringFactor( - {status_.pull_over_path->start_pose, modified_goal_pose_->goal_pose}, + {status_.get_pull_over_path()->start_pose, status_.get_modified_goal_pose()->goal_pose}, {distance_to_path_change.first, distance_to_path_change.second}, SteeringFactor::APPROACHING); - setStopReason(StopReason::GOAL_PLANNER, status_.pull_over_path->getFullPath()); + setStopReason(StopReason::GOAL_PLANNER, status_.get_pull_over_path()->getFullPath()); return out; } std::pair GoalPlannerModule::calcDistanceToPathChange() const { - const auto & full_path = status_.pull_over_path->getFullPath(); + const auto & full_path = status_.get_pull_over_path()->getFullPath(); const auto ego_segment_idx = motion_utils::findNearestSegmentIndex( full_path.points, planner_data_->self_odometry->pose.pose, std::numeric_limits::max(), @@ -1021,15 +1043,15 @@ std::pair GoalPlannerModule::calcDistanceToPathChange() const } const size_t start_pose_segment_idx = motion_utils::findNearestSegmentIndex( - full_path.points, status_.pull_over_path->start_pose.position); + full_path.points, status_.get_pull_over_path()->start_pose.position); const double dist_to_parking_start_pose = calcSignedArcLength( full_path.points, planner_data_->self_odometry->pose.pose.position, *ego_segment_idx, - status_.pull_over_path->start_pose.position, start_pose_segment_idx); + status_.get_pull_over_path()->start_pose.position, start_pose_segment_idx); const size_t goal_pose_segment_idx = motion_utils::findNearestSegmentIndex( - full_path.points, modified_goal_pose_->goal_pose.position); + full_path.points, status_.get_modified_goal_pose()->goal_pose.position); const double dist_to_parking_finish_pose = calcSignedArcLength( full_path.points, planner_data_->self_odometry->pose.pose.position, *ego_segment_idx, - modified_goal_pose_->goal_pose.position, goal_pose_segment_idx); + status_.get_modified_goal_pose()->goal_pose.position, goal_pose_segment_idx); return {dist_to_parking_start_pose, dist_to_parking_finish_pose}; } @@ -1047,17 +1069,17 @@ PathWithLaneId GoalPlannerModule::generateStopPath() const double current_vel = planner_data_->self_odometry->twist.twist.linear.x; const double pull_over_velocity = parameters_->pull_over_velocity; - if (status_.current_lanes.empty()) { + if (status_.get_current_lanes().empty()) { return PathWithLaneId{}; } // generate reference path const auto s_current = - lanelet::utils::getArcCoordinates(status_.current_lanes, current_pose).length; + lanelet::utils::getArcCoordinates(status_.get_current_lanes(), current_pose).length; const double s_start = std::max(0.0, s_current - common_parameters.backward_path_length); const double s_end = s_current + common_parameters.forward_path_length; auto reference_path = - route_handler->getCenterLinePath(status_.current_lanes, s_start, s_end, true); + route_handler->getCenterLinePath(status_.get_current_lanes(), s_start, s_end, true); // if not approved stop road lane. // stop point priority is @@ -1068,17 +1090,20 @@ PathWithLaneId GoalPlannerModule::generateStopPath() // difference between the outer and inner sides) // 4. feasible stop const auto search_start_offset_pose = calcLongitudinalOffsetPose( - reference_path.points, refined_goal_pose_.position, + reference_path.points, status_.get_refined_goal_pose().position, -parameters_->backward_goal_search_length - common_parameters.base_link2front - approximate_pull_over_distance_); - if (!status_.is_safe_static_objects && !closest_start_pose_ && !search_start_offset_pose) { + if ( + !status_.get_is_safe_static_objects() && !status_.get_closest_start_pose() && + !search_start_offset_pose) { return generateFeasibleStopPath(); } const Pose stop_pose = - status_.is_safe_static_objects - ? status_.pull_over_path->start_pose - : (closest_start_pose_ ? closest_start_pose_.value() : *search_start_offset_pose); + status_.get_is_safe_static_objects() + ? status_.get_pull_over_path()->start_pose + : (status_.get_closest_start_pose() ? status_.get_closest_start_pose().value() + : *search_start_offset_pose); // if stop pose is closer than min_stop_distance, stop as soon as possible const double ego_to_stop_distance = calcSignedArcLengthFromEgo(reference_path, stop_pose); @@ -1124,10 +1149,11 @@ PathWithLaneId GoalPlannerModule::generateFeasibleStopPath() // generate stop reference path const auto s_current = - lanelet::utils::getArcCoordinates(status_.current_lanes, current_pose).length; + lanelet::utils::getArcCoordinates(status_.get_current_lanes(), current_pose).length; const double s_start = std::max(0.0, s_current - common_parameters.backward_path_length); const double s_end = s_current + common_parameters.forward_path_length; - auto stop_path = route_handler->getCenterLinePath(status_.current_lanes, s_start, s_end, true); + auto stop_path = + route_handler->getCenterLinePath(status_.get_current_lanes(), s_start, s_end, true); // calc minimum stop distance under maximum deceleration const auto min_stop_distance = calcFeasibleDecelDistance( @@ -1148,15 +1174,16 @@ PathWithLaneId GoalPlannerModule::generateFeasibleStopPath() void GoalPlannerModule::transitionToNextPathIfFinishingCurrentPath() { - if (isActivated() && last_approved_time_ != nullptr) { + if (isActivated() && status_.get_last_approved_time()) { // if using arc_path and finishing current_path, get next path // enough time for turn signal - const bool has_passed_enough_time = (clock_->now() - *last_approved_time_).seconds() > - planner_data_->parameters.turn_signal_search_time; + const bool has_passed_enough_time = + (clock_->now() - *status_.get_last_approved_time()).seconds() > + planner_data_->parameters.turn_signal_search_time; - if (hasFinishedCurrentPath() && has_passed_enough_time && status_.require_increment_) { + if (hasFinishedCurrentPath() && has_passed_enough_time && status_.get_require_increment()) { if (incrementPathIndex()) { - last_increment_time_ = std::make_unique(clock_->now()); + status_.set_last_increment_time(std::make_shared(clock_->now())); } } } @@ -1164,23 +1191,23 @@ void GoalPlannerModule::transitionToNextPathIfFinishingCurrentPath() bool GoalPlannerModule::incrementPathIndex() { - if (status_.current_path_idx == status_.pull_over_path->partial_paths.size() - 1) { + if (status_.get_current_path_idx() == status_.get_pull_over_path()->partial_paths.size() - 1) { return false; } - status_.current_path_idx = status_.current_path_idx + 1; + status_.set_current_path_idx(status_.get_current_path_idx() + 1); return true; } PathWithLaneId GoalPlannerModule::getCurrentPath() const { - if (status_.pull_over_path == nullptr) { + if (status_.get_pull_over_path() == nullptr) { return PathWithLaneId{}; } - if (status_.pull_over_path->partial_paths.size() <= status_.current_path_idx) { + if (status_.get_pull_over_path()->partial_paths.size() <= status_.get_current_path_idx()) { return PathWithLaneId{}; } - return status_.pull_over_path->partial_paths.at(status_.current_path_idx); + return status_.get_pull_over_path()->partial_paths.at(status_.get_current_path_idx()); } bool GoalPlannerModule::isStopped( @@ -1209,6 +1236,7 @@ bool GoalPlannerModule::isStopped( bool GoalPlannerModule::isStopped() { + const std::lock_guard lock(mutex_); return isStopped(odometry_buffer_stopped_, parameters_->th_stopped_time); } @@ -1218,18 +1246,19 @@ bool GoalPlannerModule::isStuck() return false; } + const std::lock_guard lock(mutex_); constexpr double stuck_time = 5.0; if (!isStopped(odometry_buffer_stuck_, stuck_time)) { return false; } // not found safe path - if (!status_.is_safe_static_objects) { + if (!status_.get_is_safe_static_objects()) { return true; } // any path has never been found - if (!status_.pull_over_path) { + if (!status_.get_pull_over_path()) { return false; } @@ -1248,12 +1277,12 @@ bool GoalPlannerModule::hasFinishedCurrentPath() bool GoalPlannerModule::isOnModifiedGoal() const { - if (!modified_goal_pose_) { + if (!status_.get_modified_goal_pose()) { return false; } const Pose current_pose = planner_data_->self_odometry->pose.pose; - return calcDistance2d(current_pose, modified_goal_pose_->goal_pose) < + return calcDistance2d(current_pose, status_.get_modified_goal_pose()->goal_pose) < parameters_->th_arrived_distance; } @@ -1262,9 +1291,9 @@ TurnSignalInfo GoalPlannerModule::calcTurnSignalInfo() const TurnSignalInfo turn_signal{}; // output const auto & current_pose = planner_data_->self_odometry->pose.pose; - const auto & start_pose = status_.pull_over_path->start_pose; - const auto & end_pose = status_.pull_over_path->end_pose; - const auto full_path = status_.pull_over_path->getFullPath(); + const auto & start_pose = status_.get_pull_over_path()->start_pose; + const auto & end_pose = status_.get_pull_over_path()->end_pose; + const auto full_path = status_.get_pull_over_path()->getFullPath(); // calc TurnIndicatorsCommand { @@ -1287,7 +1316,9 @@ TurnSignalInfo GoalPlannerModule::calcTurnSignalInfo() const // ego decelerates so that current pose is the point `turn_light_on_threshold_time` seconds // before starting pull_over turn_signal.desired_start_point = - last_approved_pose_ && status_.has_decided_path ? *last_approved_pose_ : current_pose; + status_.get_last_approved_pose() && status_.get_has_decided_path() + ? *status_.get_last_approved_pose() + : current_pose; turn_signal.desired_end_point = end_pose; turn_signal.required_start_point = start_pose; turn_signal.required_end_point = end_pose; @@ -1344,7 +1375,7 @@ bool GoalPlannerModule::hasEnoughDistance(const PullOverPath & pull_over_path) c // so need enough distance to restart. // distance to restart should be less than decide_path_distance. // otherwise, the goal would change immediately after departure. - const bool is_separated_path = status_.pull_over_path->partial_paths.size() > 1; + const bool is_separated_path = status_.get_pull_over_path()->partial_paths.size() > 1; const double distance_to_start = calcSignedArcLength( pull_over_path.getFullPath().points, current_pose.position, pull_over_path.start_pose.position); const double distance_to_restart = parameters_->decide_path_distance / 2; @@ -1375,10 +1406,10 @@ void GoalPlannerModule::keepStoppedWithCurrentPath(PathWithLaneId & path) { constexpr double keep_stop_time = 2.0; constexpr double keep_current_idx_buffer_time = 2.0; - if (last_increment_time_) { - const auto time_diff = (clock_->now() - *last_increment_time_).seconds(); + if (status_.get_last_increment_time()) { + const auto time_diff = (clock_->now() - *status_.get_last_increment_time()).seconds(); if (time_diff < keep_stop_time) { - status_.require_increment_ = false; + status_.set_require_increment(false); for (auto & p : path.points) { p.point.longitudinal_velocity_mps = 0.0; } @@ -1386,7 +1417,7 @@ void GoalPlannerModule::keepStoppedWithCurrentPath(PathWithLaneId & path) // require increment only when the time passed is enough // to prevent increment before driving // when the end of the current path is close to the current pose - status_.require_increment_ = true; + status_.set_require_increment(true); } } } @@ -1552,7 +1583,7 @@ bool GoalPlannerModule::isCrossingPossible( bool GoalPlannerModule::isCrossingPossible(const PullOverPath & pull_over_path) const { - const lanelet::ConstLanelets lanes = utils::transformToLanelets(status_.lanes); + const lanelet::ConstLanelets lanes = utils::transformToLanelets(status_.get_lanes()); const Pose & start_pose = pull_over_path.start_pose; const Pose & end_pose = pull_over_path.end_pose; @@ -1587,11 +1618,12 @@ bool GoalPlannerModule::isSafePath() const const size_t ego_seg_idx = planner_data_->findEgoSegmentIndex(pull_over_path.points); const std::pair terminal_velocity_and_accel = utils::start_goal_planner_common::getPairsTerminalVelocityAndAccel( - status_.pull_over_path->pairs_terminal_velocity_and_accel, status_.current_path_idx); + status_.get_pull_over_path()->pairs_terminal_velocity_and_accel, + status_.get_current_path_idx()); RCLCPP_DEBUG( getLogger(), "pairs_terminal_velocity_and_accel for goal_planner: %f, %f", terminal_velocity_and_accel.first, terminal_velocity_and_accel.second); - RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.current_path_idx); + RCLCPP_DEBUG(getLogger(), "current_path_idx %ld", status_.get_current_path_idx()); utils::start_goal_planner_common::updatePathProperty( ego_predicted_path_params_, terminal_velocity_and_accel); // TODO(Sugahara): shoule judge is_object_front properly @@ -1612,7 +1644,7 @@ bool GoalPlannerModule::isSafePath() const pull_over_lanes, route_handler, filtered_objects, objects_filtering_params_); const double hysteresis_factor = - status_.prev_is_safe_dynamic_objects ? 1.0 : parameters_->hysteresis_factor_expand_rate; + status_.get_prev_is_safe_dynamic_objects() ? 1.0 : parameters_->hysteresis_factor_expand_rate; utils::start_goal_planner_common::updateSafetyCheckTargetObjectsData( goal_planner_data_, filtered_objects, target_objects_on_lane, ego_predicted_path); @@ -1674,29 +1706,31 @@ void GoalPlannerModule::setDebugData() }; if (goal_planner_utils::isAllowedGoalModification(planner_data_->route_handler)) { // Visualize pull over areas - const auto color = status_.has_decided_path ? createMarkerColor(1.0, 1.0, 0.0, 0.999) // yellow - : createMarkerColor(0.0, 1.0, 0.0, 0.999); // green - const double z = refined_goal_pose_.position.z; + const auto color = status_.get_has_decided_path() + ? createMarkerColor(1.0, 1.0, 0.0, 0.999) // yellow + : createMarkerColor(0.0, 1.0, 0.0, 0.999); // green + const double z = status_.get_refined_goal_pose().position.z; add(goal_planner_utils::createPullOverAreaMarkerArray( goal_searcher_->getAreaPolygons(), header, color, z)); // Visualize goal candidates - add(goal_planner_utils::createGoalCandidatesMarkerArray(goal_candidates_, color)); + const auto goal_candidates = status_.get_goal_candidates(); + add(goal_planner_utils::createGoalCandidatesMarkerArray(goal_candidates, color)); } // Visualize path and related pose - if (status_.is_safe_static_objects) { + if (status_.get_is_safe_static_objects()) { add(createPoseMarkerArray( - status_.pull_over_path->start_pose, "pull_over_start_pose", 0, 0.3, 0.3, 0.9)); + status_.get_pull_over_path()->start_pose, "pull_over_start_pose", 0, 0.3, 0.3, 0.9)); add(createPoseMarkerArray( - status_.pull_over_path->end_pose, "pull_over_end_pose", 0, 0.3, 0.3, 0.9)); - add( - createPathMarkerArray(status_.pull_over_path->getFullPath(), "full_path", 0, 0.0, 0.5, 0.9)); + status_.get_pull_over_path()->end_pose, "pull_over_end_pose", 0, 0.3, 0.3, 0.9)); + add(createPathMarkerArray( + status_.get_pull_over_path()->getFullPath(), "full_path", 0, 0.0, 0.5, 0.9)); add(createPathMarkerArray(getCurrentPath(), "current_path", 0, 0.9, 0.5, 0.0)); // visualize each partial path - for (size_t i = 0; i < status_.pull_over_path->partial_paths.size(); ++i) { - const auto & partial_path = status_.pull_over_path->partial_paths.at(i); + for (size_t i = 0; i < status_.get_pull_over_path()->partial_paths.size(); ++i) { + const auto & partial_path = status_.get_pull_over_path()->partial_paths.at(i); add( createPathMarkerArray(partial_path, "partial_path_" + std::to_string(i), 0, 0.9, 0.5, 0.9)); } @@ -1742,16 +1776,17 @@ void GoalPlannerModule::setDebugData() // Visualize planner type text { visualization_msgs::msg::MarkerArray planner_type_marker_array{}; - const auto color = status_.is_safe_static_objects ? createMarkerColor(1.0, 1.0, 1.0, 0.99) - : createMarkerColor(1.0, 0.0, 0.0, 0.99); + const auto color = status_.get_is_safe_static_objects() + ? createMarkerColor(1.0, 1.0, 1.0, 0.99) + : createMarkerColor(1.0, 0.0, 0.0, 0.99); auto marker = createDefaultMarker( header.frame_id, header.stamp, "planner_type", 0, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, createMarkerScale(0.0, 0.0, 1.0), color); - marker.pose = modified_goal_pose_ ? modified_goal_pose_->goal_pose - : planner_data_->self_odometry->pose.pose; - marker.text = magic_enum::enum_name(status_.pull_over_path->type); - marker.text += " " + std::to_string(status_.current_path_idx) + "/" + - std::to_string(status_.pull_over_path->partial_paths.size() - 1); + marker.pose = status_.get_modified_goal_pose() ? status_.get_modified_goal_pose()->goal_pose + : planner_data_->self_odometry->pose.pose; + marker.text = magic_enum::enum_name(status_.get_pull_over_path()->type); + marker.text += " " + std::to_string(status_.get_current_path_idx()) + "/" + + std::to_string(status_.get_pull_over_path()->partial_paths.size() - 1); if (isStuck()) { marker.text += " stuck"; } else if (isStopped()) { @@ -1769,7 +1804,7 @@ void GoalPlannerModule::setDebugData() } // Visualize debug poses - const auto & debug_poses = status_.pull_over_path->debug_poses; + const auto & debug_poses = status_.get_pull_over_path()->debug_poses; for (size_t i = 0; i < debug_poses.size(); ++i) { add(createPoseMarkerArray( debug_poses.at(i), "debug_pose_" + std::to_string(i), 0, 0.3, 0.3, 0.3)); @@ -1781,7 +1816,8 @@ void GoalPlannerModule::printParkingPositionError() const const auto current_pose = planner_data_->self_odometry->pose.pose; const double real_shoulder_to_map_shoulder = 0.0; - const Pose goal_to_ego = inverseTransformPose(current_pose, modified_goal_pose_->goal_pose); + const Pose goal_to_ego = + inverseTransformPose(current_pose, status_.get_modified_goal_pose()->goal_pose); const double dx = goal_to_ego.position.x; const double dy = goal_to_ego.position.y; const double distance_from_real_shoulder = @@ -1790,7 +1826,7 @@ void GoalPlannerModule::printParkingPositionError() const getLogger(), "current pose to goal, dx:%f dy:%f dyaw:%f from_real_shoulder:%f", dx, dy, tier4_autoware_utils::rad2deg( tf2::getYaw(current_pose.orientation) - - tf2::getYaw(modified_goal_pose_->goal_pose.orientation)), + tf2::getYaw(status_.get_modified_goal_pose()->goal_pose.orientation)), distance_from_real_shoulder); } @@ -1816,10 +1852,10 @@ bool GoalPlannerModule::needPathUpdate(const double path_update_duration) const bool GoalPlannerModule::hasEnoughTimePassedSincePathUpdate(const double duration) const { - if (!last_path_update_time_) { + if (!status_.get_last_path_update_time()) { return true; } - return (clock_->now() - *last_path_update_time_).seconds() > duration; + return (clock_->now() - *status_.get_last_path_update_time()).seconds() > duration; } } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index ff26c7a3654c3..e7fa5e42dc092 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -171,7 +171,7 @@ MarkerArray createNumObjectsToAvoidTextsMarkerArray( } MarkerArray createGoalCandidatesMarkerArray( - GoalCandidates & goal_candidates, const std_msgs::msg::ColorRGBA & color) + const GoalCandidates & goal_candidates, const std_msgs::msg::ColorRGBA & color) { GoalCandidates safe_goal_candidates{}; std::copy_if( From eaf9f684dc092ee1681221b7e724081ff1f0833f Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Thu, 19 Oct 2023 12:58:42 +0900 Subject: [PATCH 508/547] test(ndt_scan_matcher): add test_ndt_scan_matcher_launch.py (#5347) * Added test_ndt_scan_matcher_launch.py Signed-off-by: Shintaro Sakoda * Added assert and raising exception Signed-off-by: Shintaro Sakoda --------- Signed-off-by: Shintaro Sakoda --- localization/ndt_scan_matcher/CMakeLists.txt | 5 + localization/ndt_scan_matcher/package.xml | 1 + .../src/map_update_module.cpp | 15 +-- .../test/test_ndt_scan_matcher_launch.py | 101 ++++++++++++++++++ 4 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 localization/ndt_scan_matcher/test/test_ndt_scan_matcher_launch.py diff --git a/localization/ndt_scan_matcher/CMakeLists.txt b/localization/ndt_scan_matcher/CMakeLists.txt index 6f51b5210d853..955234f95ff18 100644 --- a/localization/ndt_scan_matcher/CMakeLists.txt +++ b/localization/ndt_scan_matcher/CMakeLists.txt @@ -41,6 +41,11 @@ link_directories(${PCL_LIBRARY_DIRS}) target_link_libraries(ndt_scan_matcher ${PCL_LIBRARIES}) if(BUILD_TESTING) + add_ros_test( + test/test_ndt_scan_matcher_launch.py + TIMEOUT "30" + ) + find_package(ament_cmake_gtest REQUIRED) ament_add_gtest(test_tpe test/test_tpe.cpp diff --git a/localization/ndt_scan_matcher/package.xml b/localization/ndt_scan_matcher/package.xml index 92c690a708492..986179b9e02e2 100644 --- a/localization/ndt_scan_matcher/package.xml +++ b/localization/ndt_scan_matcher/package.xml @@ -38,6 +38,7 @@ ament_cmake_cppcheck ament_lint_auto + ros_testing ament_cmake diff --git a/localization/ndt_scan_matcher/src/map_update_module.cpp b/localization/ndt_scan_matcher/src/map_update_module.cpp index 9c94467dc94c0..7512ae2e77a21 100644 --- a/localization/ndt_scan_matcher/src/map_update_module.cpp +++ b/localization/ndt_scan_matcher/src/map_update_module.cpp @@ -55,12 +55,6 @@ MapUpdateModule::MapUpdateModule( pcd_loader_client_ = node->create_client("pcd_loader_service"); - while (!pcd_loader_client_->wait_for_service(std::chrono::seconds(1)) && rclcpp::ok()) { - RCLCPP_INFO( - logger_, - "Waiting for pcd loader service. Check if the enable_differential_load in " - "pointcloud_map_loader is set `true`."); - } double map_update_dt = 1.0; auto period_ns = std::chrono::duration_cast( @@ -117,7 +111,14 @@ void MapUpdateModule::update_map(const geometry_msgs::msg::Point & position) request->area.radius = static_cast(dynamic_map_loading_map_radius_); request->cached_ids = ndt_ptr_->getCurrentMapIDs(); - // // send a request to map_loader + while (!pcd_loader_client_->wait_for_service(std::chrono::seconds(1)) && rclcpp::ok()) { + RCLCPP_INFO( + logger_, + "Waiting for pcd loader service. Check if the enable_differential_load in " + "pointcloud_map_loader is set `true`."); + } + + // send a request to map_loader auto result{pcd_loader_client_->async_send_request( request, [](rclcpp::Client::SharedFuture) {})}; diff --git a/localization/ndt_scan_matcher/test/test_ndt_scan_matcher_launch.py b/localization/ndt_scan_matcher/test/test_ndt_scan_matcher_launch.py new file mode 100644 index 0000000000000..2dc4958c4704f --- /dev/null +++ b/localization/ndt_scan_matcher/test/test_ndt_scan_matcher_launch.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +# Copyright 2023 TIER IV, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +from ament_index_python import get_package_share_directory +import launch +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import AnyLaunchDescriptionSource +from launch.logging import get_logger +import launch_testing +import pytest +import rclpy +from std_srvs.srv import SetBool + +logger = get_logger(__name__) + + +@pytest.mark.launch_test +def generate_test_description(): + test_ndt_scan_matcher_launch_file = os.path.join( + get_package_share_directory("ndt_scan_matcher"), + "launch", + "ndt_scan_matcher.launch.xml", + ) + ndt_scan_matcher = IncludeLaunchDescription( + AnyLaunchDescriptionSource(test_ndt_scan_matcher_launch_file), + ) + + return launch.LaunchDescription( + [ + ndt_scan_matcher, + # Start tests right away - no need to wait for anything + launch_testing.actions.ReadyToTest(), + ] + ) + + +class TestNDTScanMatcher(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Initialize the ROS context for the test node + rclpy.init() + + @classmethod + def tearDownClass(cls): + # Shutdown the ROS context + rclpy.shutdown() + + def setUp(self): + # Create a ROS node for tests + self.test_node = rclpy.create_node("test_node") + + def tearDown(self): + self.test_node.destroy_node() + + @staticmethod + def print_message(stat): + logger.debug("===========================") + logger.debug(stat) + + def test_launch(self): + # Trigger ndt_scan_matcher to activate the node + cli_trigger = self.test_node.create_client(SetBool, "/trigger_node") + while not cli_trigger.wait_for_service(timeout_sec=1.0): + continue + + request = SetBool.Request() + request.data = True + future = cli_trigger.call_async(request) + rclpy.spin_until_future_complete(self.test_node, future) + + if future.result() is not None: + self.test_node.get_logger().info("Result of bool service: %s" % future.result().message) + self.assertTrue(future.result().success, "ndt_scan_matcher is not activated") + else: + self.test_node.get_logger().error( + "Exception while calling service: %r" % future.exception() + ) + raise self.failureException("service trigger failed") + + +@launch_testing.post_shutdown_test() +class TestProcessOutput(unittest.TestCase): + def test_exit_code(self, proc_info): + # Check that process exits with code 0: no error + launch_testing.asserts.assertExitCodes(proc_info) From e67badaf95f53e049b2360f8c3c24025935942f7 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Thu, 19 Oct 2023 13:41:20 +0900 Subject: [PATCH 509/547] refactor(ndt_scan_matcher): rename `align_using_monte_carlo` , etc. (#5351) * Renamed align_using_monte_carlo to align_pose Signed-off-by: Shintaro SAKODA * Removed the waste arg ndt_ptr Signed-off-by: Shintaro SAKODA * Fixed log messages Signed-off-by: Shintaro SAKODA * style(pre-commit): autofix --------- Signed-off-by: Shintaro SAKODA Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../ndt_scan_matcher_core.hpp | 3 +- .../src/ndt_scan_matcher_core.cpp | 32 ++++++++----------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp index a07eb9cd5c8d0..153418e5a8f75 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp @@ -92,8 +92,7 @@ class NDTScanMatcher : public rclcpp::Node void callback_regularization_pose( geometry_msgs::msg::PoseWithCovarianceStamped::ConstSharedPtr pose_conv_msg_ptr); - geometry_msgs::msg::PoseWithCovarianceStamped align_using_monte_carlo( - const std::shared_ptr & ndt_ptr, + geometry_msgs::msg::PoseWithCovarianceStamped align_pose( const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_with_cov); void transform_sensor_measurement( diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 25033c8c83c01..b7f32d2de53ff 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -764,38 +764,33 @@ void NDTScanMatcher::service_ndt_align( map_update_module_->update_map(initial_pose_msg_in_map_frame.pose.pose.position); } + // mutex Map + std::lock_guard lock(ndt_ptr_mtx_); + if (ndt_ptr_->getInputTarget() == nullptr) { res->success = false; - RCLCPP_WARN(get_logger(), "No InputTarget"); + RCLCPP_WARN( + get_logger(), "No InputTarget. Please check the map file and the map_loader service"); return; } if (ndt_ptr_->getInputSource() == nullptr) { res->success = false; - RCLCPP_WARN(get_logger(), "No InputSource"); + RCLCPP_WARN(get_logger(), "No InputSource. Please check the input lidar topic"); return; } - // mutex Map - std::lock_guard lock(ndt_ptr_mtx_); - (*state_ptr_)["state"] = "Aligning"; - res->pose_with_covariance = align_using_monte_carlo(ndt_ptr_, initial_pose_msg_in_map_frame); + res->pose_with_covariance = align_pose(initial_pose_msg_in_map_frame); (*state_ptr_)["state"] = "Sleeping"; res->success = true; res->pose_with_covariance.pose.covariance = req->pose_with_covariance.pose.covariance; } -geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_carlo( - const std::shared_ptr & ndt_ptr, +geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_pose( const geometry_msgs::msg::PoseWithCovarianceStamped & initial_pose_with_cov) { - if (ndt_ptr->getInputTarget() == nullptr || ndt_ptr->getInputSource() == nullptr) { - RCLCPP_WARN(get_logger(), "No Map or Sensor PointCloud"); - return geometry_msgs::msg::PoseWithCovarianceStamped(); - } - - output_pose_with_cov_to_log(get_logger(), "align_using_monte_carlo_input", initial_pose_with_cov); + output_pose_with_cov_to_log(get_logger(), "align_pose_input", initial_pose_with_cov); const auto base_rpy = get_rpy(initial_pose_with_cov); const Eigen::Map covariance = { @@ -856,8 +851,8 @@ geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_ initial_pose.orientation = tf2::toMsg(tf_quaternion); const Eigen::Matrix4f initial_pose_matrix = pose_to_matrix4f(initial_pose); - ndt_ptr->align(*output_cloud, initial_pose_matrix); - const pclomp::NdtResult ndt_result = ndt_ptr->getResult(); + ndt_ptr_->align(*output_cloud, initial_pose_matrix); + const pclomp::NdtResult ndt_result = ndt_ptr_->getResult(); Particle particle( initial_pose, matrix4f_to_pose(ndt_result.pose), ndt_result.transform_probability, @@ -891,7 +886,7 @@ geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_ auto sensor_points_in_map_ptr = std::make_shared>(); tier4_autoware_utils::transformPointCloud( - *ndt_ptr->getInputSource(), *sensor_points_in_map_ptr, ndt_result.pose); + *ndt_ptr_->getInputSource(), *sensor_points_in_map_ptr, ndt_result.pose); publish_point_cloud(initial_pose_with_cov.header.stamp, map_frame_, sensor_points_in_map_ptr); } @@ -904,8 +899,7 @@ geometry_msgs::msg::PoseWithCovarianceStamped NDTScanMatcher::align_using_monte_ result_pose_with_cov_msg.header.frame_id = map_frame_; result_pose_with_cov_msg.pose.pose = best_particle_ptr->result_pose; - output_pose_with_cov_to_log( - get_logger(), "align_using_monte_carlo_output", result_pose_with_cov_msg); + output_pose_with_cov_to_log(get_logger(), "align_pose_output", result_pose_with_cov_msg); RCLCPP_INFO_STREAM(get_logger(), "best_score," << best_particle_ptr->score); return result_pose_with_cov_msg; From 64d80800c500d574bc14c1b65028f2fbfeeebc53 Mon Sep 17 00:00:00 2001 From: Zulfaqar Azmi <93502286+zulfaqar-azmi-t4@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:43:32 +0900 Subject: [PATCH 510/547] refactor(behavior_path_planner): remove unused headers (#5341) * refactor(behavior_path_planner): remove unused headers Signed-off-by: Muhammad Zulfaqar Azmi * style(pre-commit): autofix * additional headers Signed-off-by: Muhammad Zulfaqar * additional headers removed Signed-off-by: Muhammad Zulfaqar --------- Signed-off-by: Muhammad Zulfaqar Azmi Signed-off-by: Muhammad Zulfaqar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../behavior_path_planner/behavior_path_planner_node.hpp | 7 ------- .../include/behavior_path_planner/parameters.hpp | 1 - .../include/behavior_path_planner/planner_manager.hpp | 3 --- .../dynamic_avoidance/dynamic_avoidance_module.hpp | 5 +++++ .../scene_module/goal_planner/goal_planner_module.hpp | 2 ++ .../scene_module/scene_module_interface.hpp | 1 + .../behavior_path_planner/steering_factor_interface.hpp | 8 +++----- .../utils/avoidance/avoidance_module_data.hpp | 2 +- .../behavior_path_planner/utils/avoidance/utils.hpp | 3 --- .../utils/drivable_area_expansion/footprints.hpp | 6 ------ .../utils/drivable_area_expansion/parameters.hpp | 2 -- .../utils/drivable_area_expansion/path_projection.hpp | 1 - .../geometric_parallel_parking.hpp | 2 -- .../utils/goal_planner/default_fixed_goal_planner.hpp | 1 - .../utils/goal_planner/fixed_goal_planner_base.hpp | 2 -- .../utils/goal_planner/goal_searcher.hpp | 3 --- .../utils/goal_planner/goal_searcher_base.hpp | 2 -- .../utils/goal_planner/pull_over_planner_base.hpp | 1 - .../utils/lane_change/lane_change_module_data.hpp | 8 -------- .../behavior_path_planner/utils/lane_change/utils.hpp | 5 ----- .../utils/lane_following/module_data.hpp | 3 --- .../utils/path_safety_checker/objects_filtering.hpp | 1 - .../utils/path_safety_checker/safety_check.hpp | 4 ---- .../utils/path_shifter/path_shifter.hpp | 3 --- .../behavior_path_planner/utils/side_shift/util.hpp | 3 --- .../start_goal_planner_common/common_module_data.hpp | 1 - .../utils/start_planner/freespace_pull_out.hpp | 1 - .../utils/start_planner/pull_out_planner_base.hpp | 6 ++---- .../behavior_path_planner/utils/start_planner/util.hpp | 2 -- .../include/behavior_path_planner/utils/utils.hpp | 8 -------- .../src/behavior_path_planner_node.cpp | 8 ++++++-- .../src/marker_utils/avoidance/debug.cpp | 3 --- .../src/marker_utils/lane_change/debug.cpp | 2 -- .../behavior_path_planner/src/marker_utils/utils.cpp | 1 - planning/behavior_path_planner/src/planner_manager.cpp | 1 - .../src/scene_module/avoidance/avoidance_module.cpp | 5 ----- .../dynamic_avoidance/dynamic_avoidance_module.cpp | 3 +-- .../scene_module/goal_planner/goal_planner_module.cpp | 3 +-- .../lane_change/avoidance_by_lane_change.cpp | 1 - .../src/scene_module/lane_change/external_request.cpp | 9 --------- .../src/scene_module/lane_change/interface.cpp | 2 -- .../src/scene_module/lane_change/manager.cpp | 1 - .../src/scene_module/lane_change/normal.cpp | 2 +- .../src/scene_module/side_shift/side_shift_module.cpp | 1 - .../scene_module/start_planner/start_planner_module.cpp | 2 -- .../behavior_path_planner/src/utils/avoidance/utils.cpp | 1 - .../geometric_parallel_parking.cpp | 3 --- .../utils/goal_planner/default_fixed_goal_planner.cpp | 1 - .../src/utils/goal_planner/geometric_pull_over.cpp | 2 -- .../src/utils/goal_planner/goal_searcher.cpp | 2 -- .../src/utils/goal_planner/shift_pull_over.cpp | 2 -- .../src/utils/goal_planner/util.cpp | 4 ---- .../src/utils/lane_change/utils.cpp | 4 ++-- .../src/utils/path_safety_checker/objects_filtering.cpp | 1 + .../src/utils/path_safety_checker/safety_check.cpp | 6 +++--- .../src/utils/path_shifter/path_shifter.cpp | 2 -- planning/behavior_path_planner/src/utils/path_utils.cpp | 2 -- planning/behavior_path_planner/src/utils/utils.cpp | 5 ++--- planning/behavior_path_planner/test/input.hpp | 7 ++++++- 59 files changed, 37 insertions(+), 146 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp b/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp index e8ad6c7e2f987..420a5a8aa6ee5 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/behavior_path_planner_node.hpp @@ -17,13 +17,7 @@ #include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/planner_manager.hpp" -#include "behavior_path_planner/scene_module/avoidance/manager.hpp" -#include "behavior_path_planner/scene_module/dynamic_avoidance/manager.hpp" -#include "behavior_path_planner/scene_module/goal_planner/manager.hpp" -#include "behavior_path_planner/scene_module/lane_change/manager.hpp" #include "behavior_path_planner/scene_module/scene_module_interface.hpp" -#include "behavior_path_planner/scene_module/side_shift/manager.hpp" -#include "behavior_path_planner/scene_module/start_planner/manager.hpp" #include "behavior_path_planner/steering_factor_interface.hpp" #include "tier4_autoware_utils/ros/logger_level_configure.hpp" @@ -66,7 +60,6 @@ using autoware_auto_vehicle_msgs::msg::HazardLightsCommand; using autoware_auto_vehicle_msgs::msg::TurnIndicatorsCommand; using autoware_planning_msgs::msg::LaneletRoute; using autoware_planning_msgs::msg::PoseWithUuidStamped; -using geometry_msgs::msg::TwistStamped; using nav_msgs::msg::OccupancyGrid; using nav_msgs::msg::Odometry; using rcl_interfaces::msg::SetParametersResult; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp index e3a3784c5e928..40df1f5157c71 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp @@ -18,7 +18,6 @@ #include #include -#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index 4b9b0bff4be63..c0437aa69764a 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -18,20 +18,17 @@ #include "behavior_path_planner/scene_module/scene_module_interface.hpp" #include "behavior_path_planner/scene_module/scene_module_manager_interface.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" -#include "behavior_path_planner/utils/lane_following/module_data.hpp" #include "tier4_autoware_utils/ros/debug_publisher.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" #include -#include "tier4_debug_msgs/msg/float64_stamped.hpp" #include #include #include #include -#include #include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp index ab8e3cfd70dff..6c522c79a8774 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp @@ -18,8 +18,10 @@ #include "behavior_path_planner/scene_module/scene_module_interface.hpp" #include +#include #include +#include #include #include #include @@ -35,6 +37,9 @@ namespace behavior_path_planner { +using autoware_auto_perception_msgs::msg::PredictedPath; +using tier4_autoware_utils::Polygon2d; + struct MinMaxValue { double min_value{0.0}; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 632872471ce29..251b72ea6eadb 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,7 @@ using behavior_path_planner::utils::path_safety_checker::ObjectsFilteringParams; using behavior_path_planner::utils::path_safety_checker::PoseWithVelocityStamped; using behavior_path_planner::utils::path_safety_checker::SafetyCheckParams; using behavior_path_planner::utils::path_safety_checker::TargetObjectsOnLane; +using tier4_autoware_utils::Polygon2d; enum class PathType { NONE = 0, diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp index 68faa3806f614..7f24683e5680c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/scene_module_interface.hpp @@ -16,6 +16,7 @@ #define BEHAVIOR_PATH_PLANNER__SCENE_MODULE__SCENE_MODULE_INTERFACE_HPP_ #include "behavior_path_planner/data_manager.hpp" +#include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/utils.hpp" diff --git a/planning/behavior_path_planner/include/behavior_path_planner/steering_factor_interface.hpp b/planning/behavior_path_planner/include/behavior_path_planner/steering_factor_interface.hpp index 8afe0576ec1aa..3a334cf67edbf 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/steering_factor_interface.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/steering_factor_interface.hpp @@ -15,15 +15,13 @@ #ifndef BEHAVIOR_PATH_PLANNER__STEERING_FACTOR_INTERFACE_HPP_ #define BEHAVIOR_PATH_PLANNER__STEERING_FACTOR_INTERFACE_HPP_ -#include "rclcpp/rclcpp.hpp" +#include -#include "autoware_adapi_v1_msgs/msg/steering_factor_array.hpp" -#include "geometry_msgs/msg/pose.hpp" -#include "unique_identifier_msgs/msg/uuid.hpp" +#include +#include #include #include -#include namespace steering_factor_interface { diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp index d04b35a3b185b..74464be0fa5ea 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/avoidance_module_data.hpp @@ -15,7 +15,7 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__AVOIDANCE__AVOIDANCE_MODULE_DATA_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__AVOIDANCE__AVOIDANCE_MODULE_DATA_HPP_ -#include "behavior_path_planner/marker_utils/utils.hpp" +#include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp index 8cb09cd47b444..0237fb458ea0b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/avoidance/utils.hpp @@ -18,11 +18,8 @@ #include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/avoidance/avoidance_module_data.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" -#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" -#include #include -#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp index 418a9a5a68572..9591ac0582e04 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/footprints.hpp @@ -20,7 +20,6 @@ #include -#include "autoware_auto_planning_msgs/msg/detail/path_point__struct.hpp" #include #include #include @@ -31,11 +30,6 @@ #include #include -#include -#include -#include -#include - namespace drivable_area_expansion { /// @brief translate a polygon by some (x,y) vector diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp index e7275960b0888..2f9604223a248 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/parameters.hpp @@ -15,8 +15,6 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__PARAMETERS_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__DRIVABLE_AREA_EXPANSION__PARAMETERS_HPP_ -#include "behavior_path_planner/utils/drivable_area_expansion/types.hpp" - #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp index 93afaad825582..3f5327acbca68 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/drivable_area_expansion/path_projection.hpp @@ -21,7 +21,6 @@ #include -#include #include namespace drivable_area_expansion diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp index c2d36fdd6e0d7..f4ab10db61b9c 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/geometric_parallel_parking/geometric_parallel_parking.hpp @@ -30,9 +30,7 @@ #include -#include #include -#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/default_fixed_goal_planner.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/default_fixed_goal_planner.hpp index 40c339e507683..9cede67fff6e5 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/default_fixed_goal_planner.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/default_fixed_goal_planner.hpp @@ -20,7 +20,6 @@ #include #include -#include namespace behavior_path_planner { diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/fixed_goal_planner_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/fixed_goal_planner_base.hpp index 3bc0e960fe468..f587051442dcd 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/fixed_goal_planner_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/fixed_goal_planner_base.hpp @@ -16,13 +16,11 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__GOAL_PLANNER__FIXED_GOAL_PLANNER_BASE_HPP_ #include "behavior_path_planner/data_manager.hpp" -#include "behavior_path_planner/parameters.hpp" #include #include #include -#include using autoware_auto_planning_msgs::msg::PathWithLaneId; using geometry_msgs::msg::Pose; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp index 9d957ae9767c9..2ed58b9678e70 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher.hpp @@ -18,14 +18,11 @@ #include "behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp" #include "behavior_path_planner/utils/occupancy_grid_based_collision_detector/occupancy_grid_based_collision_detector.hpp" -#include "autoware_auto_planning_msgs/msg/path_point_with_lane_id.hpp" - #include #include namespace behavior_path_planner { -using autoware_auto_planning_msgs::msg::PathPointWithLaneId; using tier4_autoware_utils::LinearRing2d; using BasicPolygons2d = std::vector; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp index 7364ea91339e4..ab319111f6da6 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/goal_searcher_base.hpp @@ -16,12 +16,10 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__GOAL_PLANNER__GOAL_SEARCHER_BASE_HPP_ #include "behavior_path_planner/data_manager.hpp" -#include "behavior_path_planner/parameters.hpp" #include "behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp" #include -#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp index 20788e53309ec..54bba6aee2fc2 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/goal_planner/pull_over_planner_base.hpp @@ -16,7 +16,6 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__GOAL_PLANNER__PULL_OVER_PLANNER_BASE_HPP_ #include "behavior_path_planner/data_manager.hpp" -#include "behavior_path_planner/parameters.hpp" #include "behavior_path_planner/utils/create_vehicle_footprint.hpp" #include "behavior_path_planner/utils/goal_planner/goal_planner_parameters.hpp" diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp index 8b9ef52394cdd..c664ae3e15aef 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/lane_change_module_data.hpp @@ -16,17 +16,9 @@ #include "behavior_path_planner/utils/avoidance/avoidance_module_data.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" -#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" -#include "lanelet2_core/geometry/Lanelet.h" - -#include "autoware_auto_planning_msgs/msg/path_point_with_lane_id.hpp" #include -#include -#include -#include -#include #include namespace behavior_path_planner diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp index cd8f293b2e610..ac712fa2f0fa5 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_change/utils.hpp @@ -15,12 +15,10 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__LANE_CHANGE__UTILS_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__LANE_CHANGE__UTILS_HPP_ -#include "behavior_path_planner/marker_utils/lane_change/debug.hpp" #include "behavior_path_planner/parameters.hpp" #include "behavior_path_planner/utils/lane_change/lane_change_module_data.hpp" #include "behavior_path_planner/utils/lane_change/lane_change_path.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" -#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" #include "behavior_path_planner/utils/utils.hpp" #include @@ -32,10 +30,7 @@ #include -#include #include -#include -#include #include namespace behavior_path_planner::utils::lane_change diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp index 93e4542568745..98d52b79e2edf 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/lane_following/module_data.hpp @@ -15,9 +15,6 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__LANE_FOLLOWING__MODULE_DATA_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__LANE_FOLLOWING__MODULE_DATA_HPP_ -#include -#include - namespace behavior_path_planner { diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp index 15b15bac40b51..67588ed0a67b6 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp @@ -27,7 +27,6 @@ #include #include -#include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 64f5c9e5fc3e0..8409116236169 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -16,7 +16,6 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__PATH_SAFETY_CHECKER__SAFETY_CHECK_HPP_ #include "behavior_path_planner/data_manager.hpp" -#include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include @@ -35,7 +34,6 @@ #include #endif -#include #include namespace behavior_path_planner::utils::path_safety_checker @@ -52,8 +50,6 @@ using tier4_autoware_utils::Point2d; using tier4_autoware_utils::Polygon2d; using vehicle_info_util::VehicleInfo; -namespace bg = boost::geometry; - bool isTargetObjectOncoming( const geometry_msgs::msg::Pose & vehicle_pose, const geometry_msgs::msg::Pose & object_pose); diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_shifter/path_shifter.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_shifter/path_shifter.hpp index 4b1115a049ab0..91ed95b14b905 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_shifter/path_shifter.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_shifter/path_shifter.hpp @@ -15,8 +15,6 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__PATH_SHIFTER__PATH_SHIFTER_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__PATH_SHIFTER__PATH_SHIFTER_HPP_ -#include "behavior_path_planner/parameters.hpp" - #include #include @@ -24,7 +22,6 @@ #include -#include #include #include #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/side_shift/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/side_shift/util.hpp index 65d79a2a4977b..6cdfd84d79075 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/side_shift/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/side_shift/util.hpp @@ -20,9 +20,6 @@ #include #include -#include -#include - namespace behavior_path_planner { using autoware_auto_planning_msgs::msg::PathWithLaneId; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp index 55b8bdf777692..7a973a3bd699f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_goal_planner_common/common_module_data.hpp @@ -15,7 +15,6 @@ #ifndef BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__COMMON_MODULE_DATA_HPP_ #define BEHAVIOR_PATH_PLANNER__UTILS__START_GOAL_PLANNER_COMMON__COMMON_MODULE_DATA_HPP_ -#include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp index 2b2de183b2dac..d78df16f89a80 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/freespace_pull_out.hpp @@ -24,7 +24,6 @@ #include #include -#include namespace behavior_path_planner { diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp index cb662efd767bf..4c9271d57127d 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/pull_out_planner_base.hpp @@ -16,7 +16,6 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__START_PLANNER__PULL_OUT_PLANNER_BASE_HPP_ #include "behavior_path_planner/data_manager.hpp" -#include "behavior_path_planner/parameters.hpp" #include "behavior_path_planner/utils/create_vehicle_footprint.hpp" #include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" #include "behavior_path_planner/utils/start_planner/start_planner_parameters.hpp" @@ -27,14 +26,13 @@ #include #include -#include +namespace behavior_path_planner +{ using autoware_auto_planning_msgs::msg::PathWithLaneId; using geometry_msgs::msg::Pose; using tier4_autoware_utils::LinearRing2d; -namespace behavior_path_planner -{ enum class PlannerType { NONE = 0, SHIFT = 1, diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp index f2dba684de991..e7f51b2e86d5b 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/start_planner/util.hpp @@ -18,7 +18,6 @@ #include "behavior_path_planner/data_manager.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" -#include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" #include @@ -32,7 +31,6 @@ #include #include -#include namespace behavior_path_planner::start_planner_utils { diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp index 3e03a09d3adf8..f418c9c7300f7 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/utils.hpp @@ -16,14 +16,8 @@ #define BEHAVIOR_PATH_PLANNER__UTILS__UTILS_HPP_ #include "behavior_path_planner/data_manager.hpp" -#include "behavior_path_planner/marker_utils/utils.hpp" -#include "behavior_path_planner/utils/lane_change/lane_change_module_data.hpp" -#include "behavior_path_planner/utils/lane_following/module_data.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" -#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" -#include "behavior_path_planner/utils/start_planner/pull_out_path.hpp" #include "motion_utils/trajectory/trajectory.hpp" -#include "object_recognition_utils/predicted_path_utils.hpp" #include #include @@ -51,8 +45,6 @@ #include #include #include -#include -#include #include namespace behavior_path_planner::utils diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index 3fb9060835698..52adfc72ce761 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -15,8 +15,13 @@ #include "behavior_path_planner/behavior_path_planner_node.hpp" #include "behavior_path_planner/marker_utils/utils.hpp" +#include "behavior_path_planner/scene_module/avoidance/manager.hpp" +#include "behavior_path_planner/scene_module/dynamic_avoidance/manager.hpp" +#include "behavior_path_planner/scene_module/goal_planner/manager.hpp" #include "behavior_path_planner/scene_module/lane_change/interface.hpp" -#include "behavior_path_planner/utils/drivable_area_expansion/map_utils.hpp" +#include "behavior_path_planner/scene_module/lane_change/manager.hpp" +#include "behavior_path_planner/scene_module/side_shift/manager.hpp" +#include "behavior_path_planner/scene_module/start_planner/manager.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include @@ -26,7 +31,6 @@ #include #include -#include #include namespace diff --git a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp index 14a7d5be6a58a..ce9cedc816a19 100644 --- a/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/avoidance/debug.cpp @@ -14,9 +14,7 @@ #include "behavior_path_planner/marker_utils/avoidance/debug.hpp" -#include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include @@ -29,7 +27,6 @@ namespace marker_utils::avoidance_marker { -using behavior_path_planner::AvoidLine; using tier4_autoware_utils::appendMarkerArray; using tier4_autoware_utils::calcDistance2d; using tier4_autoware_utils::calcOffsetPose; diff --git a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp index a1b77b1802999..a7604124ebe43 100644 --- a/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp +++ b/planning/behavior_path_planner/src/marker_utils/lane_change/debug.cpp @@ -25,8 +25,6 @@ #include #include -#include -#include #include #include diff --git a/planning/behavior_path_planner/src/marker_utils/utils.cpp b/planning/behavior_path_planner/src/marker_utils/utils.cpp index 03ac135ec4bc1..031926f2d9d1d 100644 --- a/planning/behavior_path_planner/src/marker_utils/utils.cpp +++ b/planning/behavior_path_planner/src/marker_utils/utils.cpp @@ -17,7 +17,6 @@ #include "behavior_path_planner/marker_utils/colors.hpp" #include "behavior_path_planner/utils/path_safety_checker/path_safety_checker_parameters.hpp" #include "behavior_path_planner/utils/path_utils.hpp" -#include "behavior_path_planner/utils/utils.hpp" #include #include diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index d0e55ffa574fd..4c3d7cff0c24c 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -14,7 +14,6 @@ #include "behavior_path_planner/planner_manager.hpp" -#include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" #include "tier4_autoware_utils/ros/debug_publisher.hpp" #include "tier4_autoware_utils/system/stop_watch.hpp" diff --git a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp index b4b0a871eec62..84064a1c38bcb 100644 --- a/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/avoidance/avoidance_module.cpp @@ -52,17 +52,12 @@ namespace behavior_path_planner { -using behavior_path_planner::utils::path_safety_checker::CollisionCheckDebug; using motion_utils::calcLongitudinalOffsetPose; using motion_utils::calcSignedArcLength; using motion_utils::findNearestIndex; -using motion_utils::findNearestSegmentIndex; using tier4_autoware_utils::appendMarkerArray; using tier4_autoware_utils::calcDistance2d; -using tier4_autoware_utils::calcInterpolatedPose; using tier4_autoware_utils::calcLateralDeviation; -using tier4_autoware_utils::calcLongitudinalDeviation; -using tier4_autoware_utils::calcYawDeviation; using tier4_autoware_utils::getPoint; using tier4_autoware_utils::getPose; using tier4_autoware_utils::toHexString; diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index 24c074bcb629b..d8434593d2214 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -14,8 +14,8 @@ #include "behavior_path_planner/scene_module/dynamic_avoidance/dynamic_avoidance_module.hpp" -#include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "object_recognition_utils/predicted_path_utils.hpp" #include "signal_processing/lowpass_filter_1d.hpp" #include @@ -30,7 +30,6 @@ #include #include #include -#include #include #include namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index dc1ac1466397d..6253097b1692d 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -17,11 +17,11 @@ #include "behavior_path_planner/utils/create_vehicle_footprint.hpp" #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" +#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/utils.hpp" -#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" #include @@ -38,7 +38,6 @@ #include using behavior_path_planner::utils::start_goal_planner_common::calcFeasibleDecelDistance; -using motion_utils::calcDecelDistWithJerkAndAccConstraints; using motion_utils::calcLongitudinalOffsetPose; using motion_utils::calcSignedArcLength; using motion_utils::findFirstNearestSegmentIndexWithSoftConstraints; diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp index 1f305e3988828..daba02e1cee89 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/avoidance_by_lane_change.cpp @@ -17,7 +17,6 @@ #include "behavior_path_planner/utils/avoidance/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" -#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include #include diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/external_request.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/external_request.cpp index a74a51b7e2cdb..3a304a9b5bb53 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/external_request.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/external_request.cpp @@ -14,18 +14,9 @@ #include "behavior_path_planner/scene_module/lane_change/external_request.hpp" -#include "behavior_path_planner/scene_module/scene_module_visitor.hpp" -#include "behavior_path_planner/utils/lane_change/utils.hpp" -#include "behavior_path_planner/utils/path_utils.hpp" - #include -#include -#include #include -#include -#include -#include namespace behavior_path_planner { diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp index 0a0cc3436bce7..22c8ac022ad1a 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/interface.cpp @@ -19,12 +19,10 @@ #include "behavior_path_planner/scene_module/scene_module_interface.hpp" #include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/lane_change/utils.hpp" -#include "behavior_path_planner/utils/path_utils.hpp" #include #include -#include #include #include #include diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp index c6995ada1bfa6..78e05c940a814 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/manager.cpp @@ -21,7 +21,6 @@ #include #include -#include #include namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp index de4afc9260da0..de120592e7505 100644 --- a/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp +++ b/planning/behavior_path_planner/src/scene_module/lane_change/normal.cpp @@ -15,9 +15,9 @@ #include "behavior_path_planner/scene_module/lane_change/normal.hpp" #include "behavior_path_planner/marker_utils/utils.hpp" -#include "behavior_path_planner/scene_module/scene_module_visitor.hpp" #include "behavior_path_planner/utils/lane_change/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" +#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" diff --git a/planning/behavior_path_planner/src/scene_module/side_shift/side_shift_module.cpp b/planning/behavior_path_planner/src/scene_module/side_shift/side_shift_module.cpp index 4e9680cee4d59..2232c6d750d55 100644 --- a/planning/behavior_path_planner/src/scene_module/side_shift/side_shift_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/side_shift/side_shift_module.cpp @@ -32,7 +32,6 @@ using motion_utils::calcSignedArcLength; using motion_utils::findNearestIndex; using motion_utils::findNearestSegmentIndex; using tier4_autoware_utils::calcDistance2d; -using tier4_autoware_utils::calcOffsetPose; using tier4_autoware_utils::getPoint; SideShiftModule::SideShiftModule( diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index c828a747856d8..ed14292747e68 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -20,7 +20,6 @@ #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "motion_utils/trajectory/trajectory.hpp" -#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include #include @@ -39,7 +38,6 @@ using motion_utils::calcLongitudinalOffsetPose; using tier4_autoware_utils::calcOffsetPose; -using tier4_autoware_utils::inverseTransformPoint; namespace behavior_path_planner { diff --git a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp index 8fb30a28f1272..a9f40dad86ae4 100644 --- a/planning/behavior_path_planner/src/utils/avoidance/utils.cpp +++ b/planning/behavior_path_planner/src/utils/avoidance/utils.cpp @@ -18,7 +18,6 @@ #include "behavior_path_planner/utils/avoidance/utils.hpp" #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/path_utils.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include diff --git a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp index 3d315e7213f96..d5bf67f754ab7 100644 --- a/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp +++ b/planning/behavior_path_planner/src/utils/geometric_parallel_parking/geometric_parallel_parking.cpp @@ -17,7 +17,6 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_planner/util.hpp" #include "behavior_path_planner/utils/utils.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include "tier4_autoware_utils/geometry/geometry.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" @@ -37,8 +36,6 @@ #include -#include -#include #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp b/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp index 7efdbdf1552d5..4c9832c374c63 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/default_fixed_goal_planner.cpp @@ -21,7 +21,6 @@ #include #include -#include namespace behavior_path_planner { diff --git a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp index a80f7892b0bef..017ecb218bea3 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/geometric_pull_over.cpp @@ -15,8 +15,6 @@ #include "behavior_path_planner/utils/goal_planner/geometric_pull_over.hpp" #include "behavior_path_planner/utils/goal_planner/util.hpp" -#include "behavior_path_planner/utils/path_utils.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp index 12044980ebd81..1e6dc1776359b 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/goal_searcher.cpp @@ -20,7 +20,6 @@ #include "lanelet2_extension/regulatory_elements/no_parking_area.hpp" #include "lanelet2_extension/regulatory_elements/no_stopping_area.hpp" #include "lanelet2_extension/utility/utilities.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include @@ -37,7 +36,6 @@ using lane_departure_checker::LaneDepartureChecker; using lanelet::autoware::NoParkingArea; using lanelet::autoware::NoStoppingArea; using tier4_autoware_utils::calcOffsetPose; -using tier4_autoware_utils::inverseTransformPose; // Sort with smaller longitudinal distances taking precedence over smaller lateral distances. struct SortByLongitudinalDistance diff --git a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp index e1bdaf977dad8..60cc2d19c5f35 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/shift_pull_over.cpp @@ -16,8 +16,6 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" #include "behavior_path_planner/utils/path_utils.hpp" -#include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp index e7fa5e42dc092..1cd985d566f73 100644 --- a/planning/behavior_path_planner/src/utils/goal_planner/util.cpp +++ b/planning/behavior_path_planner/src/utils/goal_planner/util.cpp @@ -14,9 +14,6 @@ #include "behavior_path_planner/utils/goal_planner/util.hpp" -#include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" -#include "behavior_path_planner/utils/path_utils.hpp" - #include #include #include @@ -29,7 +26,6 @@ #include #include -#include #include #include #include diff --git a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp index 6d25cb8751a8e..d5867c40824d2 100644 --- a/planning/behavior_path_planner/src/utils/lane_change/utils.cpp +++ b/planning/behavior_path_planner/src/utils/lane_change/utils.cpp @@ -14,13 +14,14 @@ #include "behavior_path_planner/utils/lane_change/utils.hpp" +#include "behavior_path_planner/marker_utils/utils.hpp" #include "behavior_path_planner/parameters.hpp" #include "behavior_path_planner/utils/lane_change/lane_change_module_data.hpp" #include "behavior_path_planner/utils/lane_change/lane_change_path.hpp" -#include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "object_recognition_utils/predicted_path_utils.hpp" #include #include @@ -39,7 +40,6 @@ #include #include -#include #include #include diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp index dfc9f76b25e33..a7c604985de3a 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/objects_filtering.cpp @@ -15,6 +15,7 @@ #include "behavior_path_planner/utils/path_safety_checker/objects_filtering.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "object_recognition_utils/predicted_path_utils.hpp" #include #include diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 08ebd01ba5d05..8ebc144a34584 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -14,11 +14,8 @@ #include "behavior_path_planner/utils/path_safety_checker/safety_check.hpp" -#include "behavior_path_planner/marker_utils/utils.hpp" #include "interpolation/linear_interpolation.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include "motion_utils/trajectory/trajectory.hpp" -#include "object_recognition_utils/predicted_path_utils.hpp" #include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include @@ -27,6 +24,9 @@ namespace behavior_path_planner::utils::path_safety_checker { + +namespace bg = boost::geometry; + void appendPointToPolygon(Polygon2d & polygon, const geometry_msgs::msg::Point & geom_point) { Point2d point; diff --git a/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp b/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp index 21d4ff666e28a..df2d82b1072d4 100644 --- a/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp +++ b/planning/behavior_path_planner/src/utils/path_shifter/path_shifter.cpp @@ -15,13 +15,11 @@ #include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include "behavior_path_planner/utils/path_utils.hpp" -#include "behavior_path_planner/utils/utils.hpp" #include #include #include -#include #include #include #include diff --git a/planning/behavior_path_planner/src/utils/path_utils.cpp b/planning/behavior_path_planner/src/utils/path_utils.cpp index 1ac63f4a4be87..3ac3c09ba0d97 100644 --- a/planning/behavior_path_planner/src/utils/path_utils.cpp +++ b/planning/behavior_path_planner/src/utils/path_utils.cpp @@ -15,7 +15,6 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/utils.hpp" -#include "motion_utils/trajectory/path_with_lane_id.hpp" #include #include @@ -23,7 +22,6 @@ #include #include -// #include #include #include diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index cb0486b6cd01b..6927327e24ef4 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -15,7 +15,9 @@ #include "behavior_path_planner/utils/utils.hpp" #include "behavior_path_planner/utils/drivable_area_expansion/drivable_area_expansion.hpp" +#include "behavior_path_planner/utils/path_shifter/path_shifter.hpp" #include "motion_utils/trajectory/path_with_lane_id.hpp" +#include "object_recognition_utils/predicted_path_utils.hpp" #include #include @@ -24,9 +26,6 @@ #include #include -#include "autoware_auto_perception_msgs/msg/predicted_object.hpp" -#include "autoware_auto_perception_msgs/msg/predicted_path.hpp" - #include #include diff --git a/planning/behavior_path_planner/test/input.hpp b/planning/behavior_path_planner/test/input.hpp index de167bc8b4bcb..ededb32f78be0 100644 --- a/planning/behavior_path_planner/test/input.hpp +++ b/planning/behavior_path_planner/test/input.hpp @@ -16,9 +16,11 @@ #define INPUT_HPP_ #endif // INPUT_HPP_ -#include "behavior_path_planner/utils/utils.hpp" #include "autoware_auto_planning_msgs/msg/path_point.hpp" +#include "autoware_auto_planning_msgs/msg/path_point_with_lane_id.hpp" +#include "autoware_auto_planning_msgs/msg/path_with_lane_id.hpp" +#include "geometry_msgs/msg/pose.hpp" #include "geometry_msgs/msg/twist.hpp" #include @@ -26,6 +28,9 @@ namespace behavior_path_planner { using autoware_auto_planning_msgs::msg::PathPoint; +using autoware_auto_planning_msgs::msg::PathPointWithLaneId; +using autoware_auto_planning_msgs::msg::PathWithLaneId; +using geometry_msgs::msg::Pose; using geometry_msgs::msg::Twist; PathWithLaneId generateStraightSamplePathWithLaneId( float initial_pose_value, float pose_increment, size_t point_sample); From 45b05031b3ed3fa83033b2e7c3763115b121d589 Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Thu, 19 Oct 2023 15:03:22 +0900 Subject: [PATCH 511/547] perf(pointcloud_preprocessor): move print out of hot loop (#5283) * perf(pointcloud_preprocessor): move print out of hot loop Signed-off-by: Vincent Richard * style(pre-commit): autofix --------- Signed-off-by: Vincent Richard Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../src/crop_box_filter/crop_box_filter_nodelet.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sensing/pointcloud_preprocessor/src/crop_box_filter/crop_box_filter_nodelet.cpp b/sensing/pointcloud_preprocessor/src/crop_box_filter/crop_box_filter_nodelet.cpp index 0cae2c90c2f16..20d1f5c8b3d6d 100644 --- a/sensing/pointcloud_preprocessor/src/crop_box_filter/crop_box_filter_nodelet.cpp +++ b/sensing/pointcloud_preprocessor/src/crop_box_filter/crop_box_filter_nodelet.cpp @@ -119,7 +119,8 @@ void CropBoxFilterComponent::faster_filter( stop_watch_ptr_->toc("processing_time", true); if (indices) { - RCLCPP_WARN(get_logger(), "Indices are not supported and will be ignored"); + RCLCPP_WARN_THROTTLE( + get_logger(), *get_clock(), 1000, "Indices are not supported and will be ignored"); } int x_offset = input->fields[pcl::getFieldIndex(*input, "x")].offset; @@ -129,6 +130,8 @@ void CropBoxFilterComponent::faster_filter( output.data.resize(input->data.size()); size_t output_size = 0; + int skipped_count = 0; + for (size_t global_offset = 0; global_offset + input->point_step <= input->data.size(); global_offset += input->point_step) { Eigen::Vector4f point; @@ -138,7 +141,7 @@ void CropBoxFilterComponent::faster_filter( point[3] = 1; if (!std::isfinite(point[0]) || !std::isfinite(point[1]) || !std::isfinite(point[2])) { - RCLCPP_WARN(this->get_logger(), "Ignoring point containing NaN values"); + skipped_count++; continue; } @@ -162,6 +165,12 @@ void CropBoxFilterComponent::faster_filter( } } + if (skipped_count > 0) { + RCLCPP_WARN_THROTTLE( + get_logger(), *get_clock(), 1000, "%d points contained NaN values and have been ignored", + skipped_count); + } + output.data.resize(output_size); // Note that tf_input_orig_frame_ is the input frame, while tf_input_frame_ is the frame of the From 48568b737e07d580c66e2819aa6c15bd94a10a42 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Thu, 19 Oct 2023 16:05:03 +0900 Subject: [PATCH 512/547] fix(ndt_scan_matcher): fix `validate_num_iteration` (#5354) Fixed validate_num_iteration Signed-off-by: Shintaro SAKODA --- .../ndt_scan_matcher/src/ndt_scan_matcher_core.cpp | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index b7f32d2de53ff..8e583fd3a666b 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -448,20 +448,8 @@ void NDTScanMatcher::callback_sensor_points( } // perform several validations - /***************************************************************************** - The reason the add 2 to the ndt_ptr_->getMaximumIterations() is that there are bugs in - implementation of ndt. - 1. gradient descent method ends when the iteration is greater than max_iteration if it does not - converge (be careful it's 'greater than' instead of 'greater equal than'.) - https://github.com/tier4/autoware.iv/blob/2323e5baa0b680d43a9219f5fb3b7a11dd9edc82/localization/pose_estimator/ndt_scan_matcher/ndt_omp/include/ndt_omp/ndt_omp_impl.hpp#L212 - 2. iterate iteration count when end of gradient descent function. - https://github.com/tier4/autoware.iv/blob/2323e5baa0b680d43a9219f5fb3b7a11dd9edc82/localization/pose_estimator/ndt_scan_matcher/ndt_omp/include/ndt_omp/ndt_omp_impl.hpp#L217 - - These bugs are now resolved in original pcl implementation. - https://github.com/PointCloudLibrary/pcl/blob/424c1c6a0ca97d94ca63e5daff4b183a4db8aae4/registration/include/pcl/registration/impl/ndt.hpp#L73-L180 - *****************************************************************************/ bool is_ok_iteration_num = - validate_num_iteration(ndt_result.iteration_num, ndt_ptr_->getMaximumIterations() + 2); + validate_num_iteration(ndt_result.iteration_num, ndt_ptr_->getMaximumIterations()); bool is_local_optimal_solution_oscillation = false; if (!is_ok_iteration_num) { is_local_optimal_solution_oscillation = validate_local_optimal_solution_oscillation( From 0d0a22c175a333a487f434c01da2ebb15621db24 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Thu, 19 Oct 2023 16:17:36 +0900 Subject: [PATCH 513/547] feat(planner_manager): limit iteration number by parameter (#5352) Signed-off-by: satoshi-ota --- .../config/behavior_path_planner.param.yaml | 1 + .../behavior_path_planner/parameters.hpp | 1 + .../behavior_path_planner/planner_manager.hpp | 4 +++- .../src/behavior_path_planner_node.cpp | 3 ++- .../src/planner_manager.cpp | 17 ++++++++++++++--- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/planning/behavior_path_planner/config/behavior_path_planner.param.yaml b/planning/behavior_path_planner/config/behavior_path_planner.param.yaml index 95c60612bfdb7..d333cbfd778cb 100644 --- a/planning/behavior_path_planner/config/behavior_path_planner.param.yaml +++ b/planning/behavior_path_planner/config/behavior_path_planner.param.yaml @@ -1,6 +1,7 @@ /**: ros__parameters: verbose: false + max_iteration_num: 100 planning_hz: 10.0 backward_path_length: 5.0 forward_path_length: 300.0 diff --git a/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp b/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp index 40df1f5157c71..a4e6fc18ab050 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/parameters.hpp @@ -76,6 +76,7 @@ struct LateralAccelerationMap struct BehaviorPathPlannerParameters { bool verbose; + size_t max_iteration_num{100}; ModuleConfigParameters config_avoidance; ModuleConfigParameters config_avoidance_by_lc; diff --git a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp index c0437aa69764a..1d1ad56342f4d 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/planner_manager.hpp @@ -94,7 +94,7 @@ struct SceneModuleStatus class PlannerManager { public: - PlannerManager(rclcpp::Node & node, const bool verbose); + PlannerManager(rclcpp::Node & node, const size_t max_iteration_num, const bool verbose); /** * @brief run all candidate and approved modules. @@ -443,6 +443,8 @@ class PlannerManager mutable std::shared_ptr debug_msg_ptr_; + size_t max_iteration_num_{100}; + bool verbose_{false}; }; } // namespace behavior_path_planner diff --git a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp index 52adfc72ce761..0777822650dfb 100644 --- a/planning/behavior_path_planner/src/behavior_path_planner_node.cpp +++ b/planning/behavior_path_planner/src/behavior_path_planner_node.cpp @@ -136,7 +136,7 @@ BehaviorPathPlannerNode::BehaviorPathPlannerNode(const rclcpp::NodeOptions & nod const std::lock_guard lock(mutex_manager_); // for planner_manager_ const auto & p = planner_data_->parameters; - planner_manager_ = std::make_shared(*this, p.verbose); + planner_manager_ = std::make_shared(*this, p.max_iteration_num, p.verbose); const auto register_and_create_publisher = [&](const auto & manager, const bool create_publishers) { @@ -276,6 +276,7 @@ BehaviorPathPlannerParameters BehaviorPathPlannerNode::getCommonParam() BehaviorPathPlannerParameters p{}; p.verbose = declare_parameter("verbose"); + p.max_iteration_num = declare_parameter("max_iteration_num"); const auto get_scene_module_manager_param = [&](std::string && ns) { ModuleConfigParameters config; diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index 4c3d7cff0c24c..442049654d64e 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -28,9 +28,11 @@ namespace behavior_path_planner { -PlannerManager::PlannerManager(rclcpp::Node & node, const bool verbose) +PlannerManager::PlannerManager( + rclcpp::Node & node, const size_t max_iteration_num, const bool verbose) : logger_(node.get_logger().get_child("planner_manager")), clock_(*node.get_clock()), + max_iteration_num_{max_iteration_num}, verbose_{verbose} { processing_time_.emplace("total_time", 0.0); @@ -81,7 +83,7 @@ BehaviorModuleOutput PlannerManager::run(const std::shared_ptr & da return output; } - while (rclcpp::ok()) { + for (size_t itr_num = 0;; ++itr_num) { /** * STEP1: get approved modules' output */ @@ -127,8 +129,17 @@ BehaviorModuleOutput PlannerManager::run(const std::shared_ptr & da addApprovedModule(highest_priority_module); clearCandidateModules(); debug_info_.emplace_back(highest_priority_module, Action::ADD, "To Approval"); + + if (itr_num >= max_iteration_num_) { + RCLCPP_WARN_THROTTLE( + logger_, clock_, 1000, "Reach iteration limit (max: %ld). Output current result.", + max_iteration_num_); + processing_time_.at("total_time") = stop_watch_.toc("total_time", true); + return candidate_modules_output; + } } - return BehaviorModuleOutput{}; + + return BehaviorModuleOutput{}; // something wrong. }(); std::for_each( From c75d581016c559e7bd8a6bf3e63b173c13e30d63 Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Thu, 19 Oct 2023 17:18:14 +0900 Subject: [PATCH 514/547] feat(tier4_perception_launch): add parameter to control detection_by_tracker on/off (#5313) * add parameter to control detection_by_tracker on/off Signed-off-by: yoshiri * style(pre-commit): autofix * Update launch/tier4_perception_launch/launch/perception.launch.xml Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> --------- Signed-off-by: yoshiri Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Shunsuke Miura <37187849+miursh@users.noreply.github.com> --- ...ra_lidar_fusion_based_detection.launch.xml | 19 ++++++++++++----- ...ar_radar_fusion_based_detection.launch.xml | 21 +++++++++++++------ .../lidar_based_detection.launch.xml | 19 ++++++++++++----- .../launch/perception.launch.xml | 3 +++ 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml index 67e123ea5b018..59fe3f13f1231 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_fusion_based_detection.launch.xml @@ -192,7 +192,7 @@ - + @@ -329,19 +329,24 @@ + + + + - + - + + @@ -356,16 +361,20 @@ + + - + + + - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml index 07640495a5e19..11deaad5d06cc 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/camera_lidar_radar_fusion_based_detection.launch.xml @@ -217,7 +217,7 @@ - + @@ -367,21 +367,26 @@ + + + + - + - + + - + @@ -394,16 +399,20 @@ + + - + + + - + diff --git a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml index c844db39f4e9d..822c251ddad33 100644 --- a/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml +++ b/launch/tier4_perception_launch/launch/object_recognition/detection/lidar_based_detection.launch.xml @@ -58,7 +58,7 @@ - + @@ -128,18 +128,23 @@ + + + + - + - + + @@ -154,16 +159,20 @@ + + - + + + - + diff --git a/launch/tier4_perception_launch/launch/perception.launch.xml b/launch/tier4_perception_launch/launch/perception.launch.xml index 63ea0cc7f0f3d..347606d91759f 100644 --- a/launch/tier4_perception_launch/launch/perception.launch.xml +++ b/launch/tier4_perception_launch/launch/perception.launch.xml @@ -96,6 +96,9 @@ + + + Date: Thu, 19 Oct 2023 23:12:44 +0900 Subject: [PATCH 515/547] feat(map_based_prediction): remove crossing fence path (#5356) * remove crossing fence path Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../map_based_prediction_node.hpp | 8 +++ .../src/map_based_prediction_node.cpp | 49 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp index 3d01ab96e9b62..c4c7ff4a8e942 100644 --- a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp @@ -165,6 +165,14 @@ class MapBasedPredictionNode : public rclcpp::Node void mapCallback(const HADMapBin::ConstSharedPtr msg); void objectsCallback(const TrackedObjects::ConstSharedPtr in_objects); + bool doesPathCrossAnyFence(const PredictedPath & predicted_path); + bool doesPathCrossFence( + const PredictedPath & predicted_path, const lanelet::ConstLineString3d & fence_line); + lanelet::BasicLineString2d convertToFenceLine(const lanelet::ConstLineString3d & fence); + bool isIntersecting( + const geometry_msgs::msg::Point & point1, const geometry_msgs::msg::Point & point2, + const lanelet::ConstPoint3d & point3, const lanelet::ConstPoint3d & point4); + PredictedObjectKinematics convertToPredictedKinematics( const TrackedObjectKinematics & tracked_object); diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 745bccf6dbabe..6604a9dc1539a 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -976,6 +976,46 @@ void MapBasedPredictionNode::objectsCallback(const TrackedObjects::ConstSharedPt pub_calculation_time_->publish(calculation_time_msg); } +bool MapBasedPredictionNode::doesPathCrossAnyFence(const PredictedPath & predicted_path) +{ + const lanelet::ConstLineStrings3d & all_fences = + lanelet::utils::query::getAllFences(lanelet_map_ptr_); + for (const auto & fence_line : all_fences) { + if (doesPathCrossFence(predicted_path, fence_line)) { + return true; + } + } + return false; +} + +bool MapBasedPredictionNode::doesPathCrossFence( + const PredictedPath & predicted_path, const lanelet::ConstLineString3d & fence_line) +{ + // check whether the predicted path cross with fence + for (size_t i = 0; i < predicted_path.path.size(); ++i) { + for (size_t j = 0; j < fence_line.size() - 1; ++j) { + if (isIntersecting( + predicted_path.path[i].position, predicted_path.path[i + 1].position, fence_line[j], + fence_line[j + 1])) { + return true; + } + } + } + return false; +} + +bool MapBasedPredictionNode::isIntersecting( + const geometry_msgs::msg::Point & point1, const geometry_msgs::msg::Point & point2, + const lanelet::ConstPoint3d & point3, const lanelet::ConstPoint3d & point4) +{ + const auto p1 = tier4_autoware_utils::createPoint(point1.x, point1.y, 0.0); + const auto p2 = tier4_autoware_utils::createPoint(point2.x, point2.y, 0.0); + const auto p3 = tier4_autoware_utils::createPoint(point3.x(), point3.y(), 0.0); + const auto p4 = tier4_autoware_utils::createPoint(point4.x(), point4.y(), 0.0); + const auto intersection = tier4_autoware_utils::intersect(p1, p2, p3, p4); + return intersection.has_value(); +} + PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( const TrackedObject & object) { @@ -995,6 +1035,7 @@ PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( } } + // If the object is in the crosswalk, generate path to the crosswalk edge if (crossing_crosswalk) { const auto edge_points = getCrosswalkEdgePoints(crossing_crosswalk.get()); @@ -1018,6 +1059,8 @@ PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( predicted_object.kinematics.predicted_paths.push_back(predicted_path); } + // If the object is not crossing the crosswalk, in the road lanelets, try to find the closest + // crosswalk and generate path to the crosswalk edge } else if (withinRoadLanelet(object, lanelet_map_ptr_)) { lanelet::ConstLanelet closest_crosswalk{}; const auto & obj_pose = object.kinematics.pose_with_covariance.pose; @@ -1048,6 +1091,8 @@ PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( } } + // If the object is not crossing the crosswalk, not in the road lanelets, try to find the edge + // points for all crosswalks and generate path to the crosswalk edge } else { for (const auto & crosswalk : crosswalks_) { const auto edge_points = getCrosswalkEdgePoints(crosswalk); @@ -1080,6 +1125,10 @@ PredictedObject MapBasedPredictionNode::getPredictedObjectAsCrosswalkUser( if (predicted_path.path.empty()) { continue; } + // If the predicted path to the crosswalk is crossing the fence, don't use it + if (doesPathCrossAnyFence(predicted_path)) { + continue; + } predicted_object.kinematics.predicted_paths.push_back(predicted_path); } From 55e0deabf8f9f4fab0aa4c8b50f6db2d7a706610 Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:33:57 +0900 Subject: [PATCH 516/547] fix(route_handler): fix getting next lane logic to prevent from unexpected path cut (#5355) Signed-off-by: satoshi-ota --- planning/route_handler/src/route_handler.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/planning/route_handler/src/route_handler.cpp b/planning/route_handler/src/route_handler.cpp index ba9c46f157f2b..cf309a294d81f 100644 --- a/planning/route_handler/src/route_handler.cpp +++ b/planning/route_handler/src/route_handler.cpp @@ -855,12 +855,11 @@ bool RouteHandler::getNextLaneletWithinRoute( return false; } - lanelet::ConstLanelet start_lanelet; - const bool flag_check = getClosestLaneletWithinRoute(route_ptr_->start_pose, &start_lanelet); + const auto start_lane_id = route_ptr_->segments.front().preferred_primitive.id; const auto following_lanelets = routing_graph_ptr_->following(lanelet); for (const auto & llt : following_lanelets) { - if (!(flag_check && start_lanelet.id() == llt.id()) && exists(route_lanelets_, llt)) { + if (start_lane_id != llt.id() && exists(route_lanelets_, llt)) { *next_lanelet = llt; return true; } From d36be5529cc66b27be0edb314b7647b210663b37 Mon Sep 17 00:00:00 2001 From: Yoshi Ri Date: Fri, 20 Oct 2023 11:14:21 +0900 Subject: [PATCH 517/547] feat(map_based_prediction): enable to control lateral path convergence speed by setting control time horizon (#5285) * enable to control lateral path convergence speed by setting control time horizon Signed-off-by: yoshiri * update readme Signed-off-by: yoshiri * add comment in generate path function Signed-off-by: yoshiri --------- Signed-off-by: yoshiri --- perception/map_based_prediction/README.md | 16 ++++++++++++++ .../config/map_based_prediction.param.yaml | 1 + .../map_based_prediction_node.hpp | 1 + .../map_based_prediction/path_generator.hpp | 5 +++-- .../src/map_based_prediction_node.cpp | 5 ++++- .../src/path_generator.cpp | 22 +++++++++++++------ 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/perception/map_based_prediction/README.md b/perception/map_based_prediction/README.md index 7631b47772862..6d3d7f5e035a9 100644 --- a/perception/map_based_prediction/README.md +++ b/perception/map_based_prediction/README.md @@ -109,6 +109,21 @@ For the additional information, here we show how we calculate lateral velocity. Currently, we use the upper method with a low-pass filter to calculate lateral velocity. +### Path generation + +Path generation is generated on the frenet frame. The path is generated by the following steps: + +1. Get the frenet frame of the reference path. +2. Generate the frenet frame of the current position of the object and the finite position of the object. +3. Optimize the path in each longitudinal and lateral coordinate of the frenet frame. (Use the quintic polynomial to fit start and end conditions.) +4. Convert the path to the global coordinate. + +See paper [2] for more details. + +#### Tuning lateral path shape + +`lateral_control_time_horizon` parameter supports the tuning of the lateral path shape. This parameter is used to calculate the time to reach the reference path. The smaller the value, the more the path will be generated to reach the reference path quickly. (Mostly the center of the lane.) + ### Path prediction for crosswalk users This module treats **Pedestrians** and **Bicycles** as objects using the crosswalk, and outputs prediction path based on map and estimated object's velocity, assuming the object has intention to cross the crosswalk, if the objects satisfies at least one of the following conditions: @@ -155,6 +170,7 @@ If the target object is inside the road or crosswalk, this module outputs one or | ---------------------------------------------------------------- | ----- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- | | `enable_delay_compensation` | [-] | bool | flag to enable the time delay compensation for the position of the object | | `prediction_time_horizon` | [s] | double | predict time duration for predicted path | +| `lateral_control_time_horizon` | [s] | double | time duration for predicted path will reach the reference path (mostly center of the lane) | | `prediction_sampling_delta_time` | [s] | double | sampling time for points in predicted path | | `min_velocity_for_map_based_prediction` | [m/s] | double | apply map-based prediction to the objects with higher velocity than this value | | `min_crosswalk_user_velocity` | [m/s] | double | minimum velocity used when crosswalk user's velocity is calculated | diff --git a/perception/map_based_prediction/config/map_based_prediction.param.yaml b/perception/map_based_prediction/config/map_based_prediction.param.yaml index 31fae9c811092..260ae45da2e05 100644 --- a/perception/map_based_prediction/config/map_based_prediction.param.yaml +++ b/perception/map_based_prediction/config/map_based_prediction.param.yaml @@ -2,6 +2,7 @@ ros__parameters: enable_delay_compensation: true prediction_time_horizon: 10.0 #[s] + lateral_control_time_horizon: 5.0 #[s] prediction_sampling_delta_time: 0.5 #[s] min_velocity_for_map_based_prediction: 1.39 #[m/s] min_crosswalk_user_velocity: 1.39 #[m/s] diff --git a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp index c4c7ff4a8e942..01f39057aef36 100644 --- a/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/map_based_prediction_node.hpp @@ -135,6 +135,7 @@ class MapBasedPredictionNode : public rclcpp::Node // Parameters bool enable_delay_compensation_; double prediction_time_horizon_; + double lateral_control_time_horizon_; double prediction_time_horizon_rate_for_validate_lane_length_; double prediction_sampling_time_interval_; double min_velocity_for_map_based_prediction_; diff --git a/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp b/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp index 7bb1542557d2c..4da4f62be2ede 100644 --- a/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp +++ b/perception/map_based_prediction/include/map_based_prediction/path_generator.hpp @@ -81,8 +81,8 @@ class PathGenerator { public: PathGenerator( - const double time_horizon, const double sampling_time_interval, - const double min_crosswalk_user_velocity); + const double time_horizon, const double lateral_time_horizon, + const double sampling_time_interval, const double min_crosswalk_user_velocity); PredictedPath generatePathForNonVehicleObject(const TrackedObject & object); @@ -102,6 +102,7 @@ class PathGenerator private: // Parameters double time_horizon_; + double lateral_time_horizon_; double sampling_time_interval_; double min_crosswalk_user_velocity_; diff --git a/perception/map_based_prediction/src/map_based_prediction_node.cpp b/perception/map_based_prediction/src/map_based_prediction_node.cpp index 6604a9dc1539a..08fc06330b1d8 100644 --- a/perception/map_based_prediction/src/map_based_prediction_node.cpp +++ b/perception/map_based_prediction/src/map_based_prediction_node.cpp @@ -694,6 +694,8 @@ MapBasedPredictionNode::MapBasedPredictionNode(const rclcpp::NodeOptions & node_ { enable_delay_compensation_ = declare_parameter("enable_delay_compensation"); prediction_time_horizon_ = declare_parameter("prediction_time_horizon"); + lateral_control_time_horizon_ = + declare_parameter("lateral_control_time_horizon"); // [s] for lateral control point prediction_sampling_time_interval_ = declare_parameter("prediction_sampling_delta_time"); min_velocity_for_map_based_prediction_ = declare_parameter("min_velocity_for_map_based_prediction"); @@ -740,7 +742,8 @@ MapBasedPredictionNode::MapBasedPredictionNode(const rclcpp::NodeOptions & node_ declare_parameter("prediction_time_horizon_rate_for_validate_shoulder_lane_length"); path_generator_ = std::make_shared( - prediction_time_horizon_, prediction_sampling_time_interval_, min_crosswalk_user_velocity_); + prediction_time_horizon_, lateral_control_time_horizon_, prediction_sampling_time_interval_, + min_crosswalk_user_velocity_); sub_objects_ = this->create_subscription( "~/input/objects", 1, diff --git a/perception/map_based_prediction/src/path_generator.cpp b/perception/map_based_prediction/src/path_generator.cpp index 547132410badf..5cb7e186b7cc4 100644 --- a/perception/map_based_prediction/src/path_generator.cpp +++ b/perception/map_based_prediction/src/path_generator.cpp @@ -23,9 +23,10 @@ namespace map_based_prediction { PathGenerator::PathGenerator( - const double time_horizon, const double sampling_time_interval, + const double time_horizon, const double lateral_time_horizon, const double sampling_time_interval, const double min_crosswalk_user_velocity) : time_horizon_(time_horizon), + lateral_time_horizon_(lateral_time_horizon), sampling_time_interval_(sampling_time_interval), min_crosswalk_user_velocity_(min_crosswalk_user_velocity) { @@ -213,18 +214,25 @@ FrenetPath PathGenerator::generateFrenetPath( { FrenetPath path; const double duration = time_horizon_; + const double lateral_duration = lateral_time_horizon_; // Compute Lateral and Longitudinal Coefficients to generate the trajectory - const Eigen::Vector3d lat_coeff = calcLatCoefficients(current_point, target_point, duration); + const Eigen::Vector3d lat_coeff = + calcLatCoefficients(current_point, target_point, lateral_duration); const Eigen::Vector2d lon_coeff = calcLonCoefficients(current_point, target_point, duration); path.reserve(static_cast(duration / sampling_time_interval_)); for (double t = 0.0; t <= duration; t += sampling_time_interval_) { - const double d_next = current_point.d + current_point.d_vel * t + 0 * 2 * std::pow(t, 2) + - lat_coeff(0) * std::pow(t, 3) + lat_coeff(1) * std::pow(t, 4) + - lat_coeff(2) * std::pow(t, 5); - const double s_next = current_point.s + current_point.s_vel * t + 2 * 0 * std::pow(t, 2) + - lon_coeff(0) * std::pow(t, 3) + lon_coeff(1) * std::pow(t, 4); + const double current_acc = + 0.0; // Currently we assume the object is traveling at a constant speed + const double d_next_ = current_point.d + current_point.d_vel * t + + current_acc * 2.0 * std::pow(t, 2) + lat_coeff(0) * std::pow(t, 3) + + lat_coeff(1) * std::pow(t, 4) + lat_coeff(2) * std::pow(t, 5); + // t > lateral_duration: 0.0, else d_next_ + const double d_next = t > lateral_duration ? 0.0 : d_next_; + const double s_next = current_point.s + current_point.s_vel * t + + 2.0 * current_acc * std::pow(t, 2) + lon_coeff(0) * std::pow(t, 3) + + lon_coeff(1) * std::pow(t, 4); if (s_next > max_length) { break; } From 915d47dbb1bc01f27b33a9e4577fb14e83a609e6 Mon Sep 17 00:00:00 2001 From: Yuki TAKAGI <141538661+yuki-takagi-66@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:42:06 +0900 Subject: [PATCH 518/547] fix(obstacle_stop_planner, dynamic avoidance): fix coordinate transformation bug (#5248) fix a known bug https://github.com/autowarefoundation/autoware.universe/pull/5180 Signed-off-by: Yuki Takagi --- .../dynamic_avoidance_module.cpp | 21 ++++++++----------- .../src/adaptive_cruise_control.cpp | 19 +++++++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp index d8434593d2214..2c5e78a267d5a 100644 --- a/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/dynamic_avoidance/dynamic_avoidance_module.cpp @@ -32,6 +32,7 @@ #include #include #include + namespace behavior_path_planner { namespace @@ -105,21 +106,17 @@ std::pair projectObstacleVelocityToTrajectory( const std::vector & path_points, const PredictedObject & object) { const auto & obj_pose = object.kinematics.initial_pose_with_covariance.pose; - const double obj_vel_norm = std::hypot( - object.kinematics.initial_twist_with_covariance.twist.linear.x, - object.kinematics.initial_twist_with_covariance.twist.linear.y); - - const size_t obj_idx = motion_utils::findNearestIndex(path_points, obj_pose.position); - const double obj_yaw = tf2::getYaw(obj_pose.orientation); - const double obj_vel_yaw = - obj_yaw + std::atan2( - object.kinematics.initial_twist_with_covariance.twist.linear.y, - object.kinematics.initial_twist_with_covariance.twist.linear.x); + const size_t obj_idx = motion_utils::findNearestIndex(path_points, obj_pose.position); const double path_yaw = tf2::getYaw(path_points.at(obj_idx).point.pose.orientation); - const double diff_yaw = tier4_autoware_utils::normalizeRadian(obj_vel_yaw - path_yaw); - return std::make_pair(obj_vel_norm * std::cos(diff_yaw), obj_vel_norm * std::sin(diff_yaw)); + const Eigen::Rotation2Dd R_ego_to_obstacle(obj_yaw - path_yaw); + const Eigen::Vector2d obstacle_velocity( + object.kinematics.initial_twist_with_covariance.twist.linear.x, + object.kinematics.initial_twist_with_covariance.twist.linear.y); + const Eigen::Vector2d projected_velocity = R_ego_to_obstacle * obstacle_velocity; + + return std::make_pair(projected_velocity[0], projected_velocity[1]); } double calcObstacleMaxLength(const autoware_auto_perception_msgs::msg::Shape & shape) diff --git a/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp b/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp index b33342b9dea4f..c0f57489078d6 100644 --- a/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp +++ b/planning/obstacle_stop_planner/src/adaptive_cruise_control.cpp @@ -427,9 +427,12 @@ bool AdaptiveCruiseController::estimatePointVelocityFromObject( obj_vel_norm = std::hypot( obj.kinematics.initial_twist_with_covariance.twist.linear.x, obj.kinematics.initial_twist_with_covariance.twist.linear.y); - obj_vel_yaw = std::atan2( - obj.kinematics.initial_twist_with_covariance.twist.linear.y, - obj.kinematics.initial_twist_with_covariance.twist.linear.x); + + const double obj_yaw = + tf2::getYaw(obj.kinematics.initial_pose_with_covariance.pose.orientation); + obj_vel_yaw = obj_yaw + std::atan2( + obj.kinematics.initial_twist_with_covariance.twist.linear.y, + obj.kinematics.initial_twist_with_covariance.twist.linear.x); get_obj = true; break; } @@ -451,9 +454,13 @@ void AdaptiveCruiseController::calculateProjectedVelocityFromObject( double obj_vel_norm = std::hypot( object.kinematics.initial_twist_with_covariance.twist.linear.x, object.kinematics.initial_twist_with_covariance.twist.linear.y); - double obj_vel_yaw = std::atan2( - object.kinematics.initial_twist_with_covariance.twist.linear.y, - object.kinematics.initial_twist_with_covariance.twist.linear.x); + + const double obj_yaw = + tf2::getYaw(object.kinematics.initial_pose_with_covariance.pose.orientation); + const double obj_vel_yaw = + obj_yaw + std::atan2( + object.kinematics.initial_twist_with_covariance.twist.linear.y, + object.kinematics.initial_twist_with_covariance.twist.linear.x); *velocity = obj_vel_norm * std::cos(tier4_autoware_utils::normalizeRadian(obj_vel_yaw - traj_yaw)); From e519c4b5f5e2900d1a62fc0182ade9b2df125fab Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Fri, 20 Oct 2023 16:51:31 +0900 Subject: [PATCH 519/547] chore(behavior_velocity_crosswalk_module): add maintainer (#5363) * add maintainer Signed-off-by: kyoichi-sugahara * style(pre-commit): autofix --------- Signed-off-by: kyoichi-sugahara Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- planning/behavior_velocity_crosswalk_module/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/planning/behavior_velocity_crosswalk_module/package.xml b/planning/behavior_velocity_crosswalk_module/package.xml index 66f326ed799af..3830aa9edddff 100644 --- a/planning/behavior_velocity_crosswalk_module/package.xml +++ b/planning/behavior_velocity_crosswalk_module/package.xml @@ -9,6 +9,7 @@ Tomoya Kimura Shumpei Wakabayashi Takayuki Murooka + Kyoichi Sugahara Apache License 2.0 From 33bc810a591fefa9b52339d03ac54ff92472bc28 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Fri, 20 Oct 2023 19:04:52 +0900 Subject: [PATCH 520/547] feat(intersection): timeout static occlusion with traffic light (#5353) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 1 + .../src/debug.cpp | 14 +- .../src/manager.cpp | 2 + .../src/scene_intersection.cpp | 152 +++++++++++++----- .../src/scene_intersection.hpp | 23 ++- .../src/util.cpp | 2 +- .../src/util_type.hpp | 4 +- 7 files changed, 146 insertions(+), 52 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index 1a5143678ddce..be8e822b31d5c 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -76,6 +76,7 @@ maximum_peeking_distance: 6.0 # [m] attention_lane_crop_curvature_threshold: 0.25 attention_lane_curvature_calculation_ds: 0.5 + static_occlusion_with_traffic_light_timeout: 3.5 enable_rtc: intersection: false # If set to true, the scene modules require approval from the rtc (request to cooperate) function. If set to false, the modules can be executed without requiring rtc approval diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index 3d5874c3c89e8..f472c4bf51e31 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -188,14 +188,6 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( &debug_marker_array); } - if (debug_data_.intersection_area) { - appendMarkerArray( - debug::createPolygonMarkerArray( - debug_data_.intersection_area.value(), "intersection_area", lane_id_, now, 0.3, 0.0, 0.0, - 0.0, 1.0, 0.0), - &debug_marker_array); - } - if (debug_data_.stuck_vehicle_detect_area) { appendMarkerArray( debug::createPolygonMarkerArray( @@ -309,6 +301,12 @@ motion_utils::VirtualWalls IntersectionModule::createVirtualWalls() if (debug_data_.occlusion_stop_wall_pose) { wall.style = motion_utils::VirtualWallType::stop; wall.text = "intersection_occlusion"; + if (debug_data_.static_occlusion_with_traffic_light_timeout) { + std::stringstream timeout; + timeout << std::setprecision(2) + << debug_data_.static_occlusion_with_traffic_light_timeout.value(); + wall.text += "(" + timeout.str() + ")"; + } wall.ns = "intersection_occlusion" + std::to_string(module_id_) + "_"; wall.pose = debug_data_.occlusion_stop_wall_pose.value(); virtual_walls.push_back(wall); diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 22fa57f20e79f..0b4131b55ed90 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -172,6 +172,8 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".occlusion.attention_lane_crop_curvature_threshold"); ip.occlusion.attention_lane_curvature_calculation_ds = getOrDeclareParameter(node, ns + ".occlusion.attention_lane_curvature_calculation_ds"); + ip.occlusion.static_occlusion_with_traffic_light_timeout = getOrDeclareParameter( + node, ns + ".occlusion.static_occlusion_with_traffic_light_timeout"); } void IntersectionModuleManager::launchNewModules( diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index b37b70f290ff6..1bbff9ecaf048 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -129,6 +129,11 @@ IntersectionModule::IntersectionModule( planner_param_.occlusion.before_creep_stop_time); temporal_stop_before_attention_state_machine_.setState(StateMachine::State::STOP); } + { + static_occlusion_timeout_state_machine_.setMarginTime( + planner_param_.occlusion.static_occlusion_with_traffic_light_timeout); + static_occlusion_timeout_state_machine_.setState(StateMachine::State::STOP); + } decision_state_pub_ = node_.create_publisher("~/debug/intersection/decision_state", 1); @@ -589,6 +594,8 @@ void reactRTCApprovalByDecisionResult( planning_utils::setVelocityFromIndex(occlusion_peeking_stop_line, 0.0, path); debug_data->occlusion_stop_wall_pose = planning_utils::getAheadPose(occlusion_peeking_stop_line, baselink2front, *path); + debug_data->static_occlusion_with_traffic_light_timeout = + decision_result.static_occlusion_timeout; { tier4_planning_msgs::msg::StopFactor stop_factor; stop_factor.stop_pose = path->points.at(occlusion_peeking_stop_line).point.pose; @@ -648,6 +655,8 @@ void reactRTCApprovalByDecisionResult( planning_utils::setVelocityFromIndex(stop_line_idx, 0.0, path); debug_data->occlusion_stop_wall_pose = planning_utils::getAheadPose(stop_line_idx, baselink2front, *path); + debug_data->static_occlusion_with_traffic_light_timeout = + decision_result.static_occlusion_timeout; { tier4_planning_msgs::msg::StopFactor stop_factor; stop_factor.stop_pose = path->points.at(stop_line_idx).point.pose; @@ -1017,6 +1026,19 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } return state_machine.getState() == StateMachine::State::GO; }; + auto stoppedAtPosition = [&](const size_t pos, const double duration) { + const double dist_stopline = fromEgoDist(pos); + const bool approached_dist_stopline = + (std::fabs(dist_stopline) < planner_param_.common.stop_overshoot_margin); + const bool over_stopline = (dist_stopline < -planner_param_.common.stop_overshoot_margin); + const bool is_stopped = planner_data_->isVehicleStopped(duration); + if (over_stopline) { + return true; + } else if (is_stopped && approached_dist_stopline) { + return true; + } + return false; + }; // stuck vehicle detection is viable even if attention area is empty // so this needs to be checked before attention area validation @@ -1111,13 +1133,7 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( debug_data_.adjacent_area = intersection_lanelets.adjacent_area(); // get intersection area - const auto intersection_area = planner_param_.common.use_intersection_area - ? util::getIntersectionArea(assigned_lanelet, lanelet_map_ptr) - : std::nullopt; - if (intersection_area) { - const auto intersection_area_2d = intersection_area.value(); - debug_data_.intersection_area = toGeomPoly(intersection_area_2d); - } + const auto intersection_area = util::getIntersectionArea(assigned_lanelet, lanelet_map_ptr); auto target_objects = generateTargetObjects(intersection_lanelets, intersection_area); @@ -1143,8 +1159,9 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( } if (initial_green_light_observed_time_) { const auto now = clock_->now(); - const bool exist_close_vehicles = - std::any_of(target_objects.all.begin(), target_objects.all.end(), [&](const auto & object) { + const bool exist_close_vehicles = std::any_of( + target_objects.all_attention_objects.begin(), target_objects.all_attention_objects.end(), + [&](const auto & object) { return object.dist_to_stop_line.has_value() && object.dist_to_stop_line.value() < planner_param_.collision_detection.yield_on_green_traffic_light @@ -1194,27 +1211,30 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const double occlusion_dist_thr = std::fabs( std::pow(planner_param_.occlusion.max_vehicle_velocity_for_rss, 2) / (2 * planner_param_.occlusion.min_vehicle_brake_for_rss)); - const auto blocking_attention_objects = target_objects.parked_attention_objects; - for (const auto & blocking_attention_object : blocking_attention_objects) { - debug_data_.blocking_attention_objects.objects.push_back(blocking_attention_object.object); - } - // debug_data_.blocking_attention_objects.objects = blocking_attention_objects; - const bool is_occlusion_cleared = + auto occlusion_status = (enable_occlusion_detection_ && !occlusion_attention_lanelets.empty() && !is_prioritized) - ? isOcclusionCleared( + ? getOcclusionStatus( *planner_data_->occupancy_grid, occlusion_attention_area, adjacent_lanelets, first_attention_area, interpolated_path_info, occlusion_attention_divisions, - blocking_attention_objects, occlusion_dist_thr) - : true; + target_objects, current_pose, occlusion_dist_thr) + : OcclusionType::NOT_OCCLUDED; occlusion_stop_state_machine_.setStateWithMarginTime( - is_occlusion_cleared ? StateMachine::State::GO : StateMachine::STOP, + occlusion_status == OcclusionType::NOT_OCCLUDED ? StateMachine::State::GO : StateMachine::STOP, logger_.get_child("occlusion_stop"), *clock_); const bool is_occlusion_cleared_with_margin = (occlusion_stop_state_machine_.getState() == StateMachine::State::GO); - // distinguish if ego detected occlusion or RTC detects occlusion const bool ext_occlusion_requested = (is_occlusion_cleared_with_margin && !occlusion_activated_); + if (ext_occlusion_requested) { + occlusion_status = OcclusionType::RTC_OCCLUDED; + } const bool is_occlusion_state = (!is_occlusion_cleared_with_margin || ext_occlusion_requested); + if (is_occlusion_state && occlusion_status == OcclusionType::NOT_OCCLUDED) { + occlusion_status = prev_occlusion_status_; + } else { + prev_occlusion_status_ = occlusion_status; + } + // Safe if (!is_occlusion_state && !has_collision_with_margin) { return IntersectionModule::Safe{closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; @@ -1225,11 +1245,13 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; } // Occluded + // occlusion_status is assured to be not NOT_OCCLUDED const bool stopped_at_default_line = stoppedForDuration( default_stop_line_idx, planner_param_.occlusion.before_creep_stop_time, before_creep_state_machine_); if (stopped_at_default_line) { - // in this case ego will temporarily stop before entering attention area + // if specified the parameter occlusion.temporal_stop_before_attention_area OR + // has_no_traffic_light_, ego will temporarily stop before entering attention area const bool temporal_stop_before_attention_required = (planner_param_.occlusion.temporal_stop_before_attention_area || !has_traffic_light_) ? !stoppedForDuration( @@ -1246,20 +1268,51 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( temporal_stop_before_attention_required, closest_idx, first_attention_stop_line_idx, occlusion_stop_line_idx}; } + // following remaining block is "has_traffic_light_" + // if ego is stuck by static occlusion in the presence of traffic light, start timeout count + const bool is_static_occlusion = occlusion_status == OcclusionType::STATICALLY_OCCLUDED; + const bool is_stuck_by_static_occlusion = + stoppedAtPosition(occlusion_stop_line_idx, planner_param_.occlusion.before_creep_stop_time) && + is_static_occlusion; + if (has_collision_with_margin) { + // if collision is detected, timeout is reset + static_occlusion_timeout_state_machine_.setState(StateMachine::State::STOP); + } else if (is_stuck_by_static_occlusion) { + static_occlusion_timeout_state_machine_.setStateWithMarginTime( + StateMachine::State::GO, logger_.get_child("static_occlusion"), *clock_); + } + const bool release_static_occlusion_stuck = + (static_occlusion_timeout_state_machine_.getState() == StateMachine::State::GO); + if (!has_collision_with_margin && release_static_occlusion_stuck) { + return IntersectionModule::Safe{ + closest_idx, collision_stop_line_idx, occlusion_stop_line_idx}; + } + // occlusion_status is either STATICALLY_OCCLUDED or DYNAMICALLY_OCCLUDED + const double max_timeout = + planner_param_.occlusion.static_occlusion_with_traffic_light_timeout + + planner_param_.occlusion.stop_release_margin_time; + const std::optional static_occlusion_timeout = + is_stuck_by_static_occlusion + ? std::make_optional( + max_timeout - static_occlusion_timeout_state_machine_.getDuration() - + occlusion_stop_state_machine_.getDuration()) + : (is_static_occlusion ? std::make_optional(max_timeout) : std::nullopt); if (has_collision_with_margin) { return IntersectionModule::OccludedCollisionStop{is_occlusion_cleared_with_margin, temporal_stop_before_attention_required, closest_idx, collision_stop_line_idx, first_attention_stop_line_idx, - occlusion_stop_line_idx}; + occlusion_stop_line_idx, + static_occlusion_timeout}; } else { return IntersectionModule::PeekingTowardOcclusion{is_occlusion_cleared_with_margin, temporal_stop_before_attention_required, closest_idx, collision_stop_line_idx, first_attention_stop_line_idx, - occlusion_stop_line_idx}; + occlusion_stop_line_idx, + static_occlusion_timeout}; } } else { const auto occlusion_stop_line = @@ -1359,6 +1412,7 @@ util::TargetObjects IntersectionModule::generateTargetObjects( auto & container = is_parked_vehicle ? target_objects.parked_attention_objects : target_objects.attention_objects; if (intersection_area) { + const auto & obj_pos = object.kinematics.initial_pose_with_covariance.pose.position; const auto obj_poly = tier4_autoware_utils::toPolygon2d(object); const auto intersection_area_2d = intersection_area.value(); const auto belong_attention_lanelet_id = util::checkAngleForTargetLanelets( @@ -1366,18 +1420,18 @@ util::TargetObjects IntersectionModule::generateTargetObjects( planner_param_.common.consider_wrong_direction_vehicle, planner_param_.common.attention_area_margin, is_parked_vehicle); if (belong_attention_lanelet_id) { - const auto id = belong_adjacent_lanelet_id.value(); + const auto id = belong_attention_lanelet_id.value(); util::TargetObject target_object; target_object.object = object; target_object.attention_lanelet = attention_lanelets.at(id); target_object.stop_line = attention_lanelet_stoplines.at(id); container.push_back(target_object); - } else if (bg::within(obj_poly, intersection_area_2d)) { + } else if (bg::within(Point2d{obj_pos.x, obj_pos.y}, intersection_area_2d)) { util::TargetObject target_object; target_object.object = object; target_object.attention_lanelet = std::nullopt; target_object.stop_line = std::nullopt; - container.push_back(target_object); + target_objects.intersection_area_objects.push_back(target_object); } } else if (const auto belong_attention_lanelet_id = util::checkAngleForTargetLanelets( object_direction, attention_lanelets, @@ -1395,12 +1449,12 @@ util::TargetObjects IntersectionModule::generateTargetObjects( } } for (const auto & object : target_objects.attention_objects) { - target_objects.all.push_back(object); + target_objects.all_attention_objects.push_back(object); } for (const auto & object : target_objects.parked_attention_objects) { - target_objects.all.push_back(object); + target_objects.all_attention_objects.push_back(object); } - for (auto & object : target_objects.all) { + for (auto & object : target_objects.all_attention_objects) { object.calc_dist_to_stop_line(); } return target_objects; @@ -1467,7 +1521,7 @@ bool IntersectionModule::checkCollision( // check collision between predicted_path and ego_area bool collision_detected = false; - for (const auto & target_object : target_objects->all) { + for (const auto & target_object : target_objects->all_attention_objects) { const auto & object = target_object.object; // If the vehicle is expected to stop before their stopline, ignore if ( @@ -1566,14 +1620,14 @@ bool IntersectionModule::checkCollision( return collision_detected; } -bool IntersectionModule::isOcclusionCleared( +IntersectionModule::OcclusionType IntersectionModule::getOcclusionStatus( const nav_msgs::msg::OccupancyGrid & occ_grid, const std::vector & attention_areas, const lanelet::ConstLanelets & adjacent_lanelets, const lanelet::CompoundPolygon3d & first_attention_area, const util::InterpolatedPathInfo & interpolated_path_info, const std::vector & lane_divisions, - const std::vector & blocking_attention_objects, + const util::TargetObjects & target_objects, const geometry_msgs::msg::Pose & current_pose, const double occlusion_dist_thr) { const auto & path_ip = interpolated_path_info.path; @@ -1582,7 +1636,7 @@ bool IntersectionModule::isOcclusionCleared( const auto first_attention_area_idx = util::getFirstPointInsidePolygon(path_ip, lane_interval_ip, first_attention_area); if (!first_attention_area_idx) { - return false; + return OcclusionType::NOT_OCCLUDED; } const auto first_inside_attention_idx_ip_opt = @@ -1697,6 +1751,10 @@ bool IntersectionModule::isOcclusionCleared( // re-use attention_mask attention_mask = cv::Mat(width, height, CV_8UC1, cv::Scalar(0)); // (3.1) draw all cells on attention_mask behind blocking vehicles as not occluded + const auto & blocking_attention_objects = target_objects.parked_attention_objects; + for (const auto & blocking_attention_object : blocking_attention_objects) { + debug_data_.blocking_attention_objects.objects.push_back(blocking_attention_object.object); + } std::vector> blocking_polygons; for (const auto & blocking_attention_object : blocking_attention_objects) { const Polygon2d obj_poly = tier4_autoware_utils::toPolygon2d(blocking_attention_object.object); @@ -1808,8 +1866,9 @@ bool IntersectionModule::isOcclusionCleared( }; struct NearestOcclusionPoint { - int64 division_index; - double dist; + int64 division_index{0}; + int64 point_index{0}; + double dist{0.0}; geometry_msgs::msg::Point point; geometry_msgs::msg::Point projection; } nearest_occlusion_point; @@ -1848,7 +1907,6 @@ bool IntersectionModule::isOcclusionCleared( acc_dist += dist; acc_dist_it = point_it; const auto [valid, idx_x, idx_y] = coord2index(point_it->x(), point_it->y()); - // TODO(Mamoru Sobue): add handling for blocking vehicles if (!valid) continue; const auto pixel = occlusion_mask.at(height - 1 - idx_y, idx_x); if (pixel == BLOCKED) { @@ -1858,7 +1916,7 @@ bool IntersectionModule::isOcclusionCleared( if (acc_dist < min_dist) { min_dist = acc_dist; nearest_occlusion_point = { - std::distance(division.begin(), point_it), acc_dist, + division_index, std::distance(division.begin(), point_it), acc_dist, tier4_autoware_utils::createPoint(point_it->x(), point_it->y(), origin.z), tier4_autoware_utils::createPoint(projection_it->x(), projection_it->y(), origin.z)}; } @@ -1867,11 +1925,27 @@ bool IntersectionModule::isOcclusionCleared( } if (min_dist == std::numeric_limits::infinity() || min_dist > occlusion_dist_thr) { - return true; + return OcclusionType::NOT_OCCLUDED; } + debug_data_.nearest_occlusion_projection = std::make_pair(nearest_occlusion_point.point, nearest_occlusion_point.projection); - return false; + LineString2d ego_occlusion_line; + ego_occlusion_line.emplace_back(current_pose.position.x, current_pose.position.y); + ego_occlusion_line.emplace_back(nearest_occlusion_point.point.x, nearest_occlusion_point.point.y); + for (const auto & attention_object : target_objects.all_attention_objects) { + const auto obj_poly = tier4_autoware_utils::toPolygon2d(attention_object.object); + if (bg::intersects(obj_poly, ego_occlusion_line)) { + return OcclusionType::DYNAMICALLY_OCCLUDED; + } + } + for (const auto & attention_object : target_objects.intersection_area_objects) { + const auto obj_poly = tier4_autoware_utils::toPolygon2d(attention_object.object); + if (bg::intersects(obj_poly, ego_occlusion_line)) { + return OcclusionType::DYNAMICALLY_OCCLUDED; + } + } + return OcclusionType::STATICALLY_OCCLUDED; } /* diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index beb13ef7bef7a..c604f01c7a86b 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -141,9 +141,17 @@ class IntersectionModule : public SceneModuleInterface } absence_traffic_light; double attention_lane_crop_curvature_threshold; double attention_lane_curvature_calculation_ds; + double static_occlusion_with_traffic_light_timeout; } occlusion; }; + enum OcclusionType { + NOT_OCCLUDED, + STATICALLY_OCCLUDED, + DYNAMICALLY_OCCLUDED, + RTC_OCCLUDED, // actual occlusion does not exist, only disapproved by RTC + }; + struct Indecisive { std::string error; @@ -172,6 +180,7 @@ class IntersectionModule : public SceneModuleInterface size_t first_stop_line_idx{0}; size_t occlusion_stop_line_idx{0}; }; + // A state peeking to occlusion limit line in the presence of traffic light struct PeekingTowardOcclusion { // NOTE: if intersection_occlusion is disapproved externally through RTC, @@ -182,7 +191,12 @@ class IntersectionModule : public SceneModuleInterface size_t collision_stop_line_idx{0}; size_t first_attention_stop_line_idx{0}; size_t occlusion_stop_line_idx{0}; + // if null, it is dynamic occlusion and shows up intersection_occlusion(dyn) + // if valid, it contains the remaining time to release the static occlusion stuck and shows up + // intersection_occlusion(x.y) + std::optional static_occlusion_timeout{std::nullopt}; }; + // A state detecting both collision and occlusion in the presence of traffic light struct OccludedCollisionStop { bool is_actually_occlusion_cleared{false}; @@ -191,6 +205,9 @@ class IntersectionModule : public SceneModuleInterface size_t collision_stop_line_idx{0}; size_t first_attention_stop_line_idx{0}; size_t occlusion_stop_line_idx{0}; + // if null, it is dynamic occlusion and shows up intersection_occlusion(dyn) + // if valid, it contains the remaining time to release the static occlusion stuck + std::optional static_occlusion_timeout{std::nullopt}; }; struct OccludedAbsenceTrafficLight { @@ -262,6 +279,7 @@ class IntersectionModule : public SceneModuleInterface bool is_go_out_{false}; bool is_permanent_go_{false}; DecisionResult prev_decision_result_{Indecisive{""}}; + OcclusionType prev_occlusion_status_; // Parameter PlannerParam planner_param_; @@ -276,6 +294,7 @@ class IntersectionModule : public SceneModuleInterface StateMachine before_creep_state_machine_; //! for two phase stop StateMachine occlusion_stop_state_machine_; StateMachine temporal_stop_before_attention_state_machine_; + StateMachine static_occlusion_timeout_state_machine_; // for pseudo-collision detection when ego just entered intersection on green light and upcoming // vehicles are very slow @@ -317,14 +336,14 @@ class IntersectionModule : public SceneModuleInterface const size_t closest_idx, const size_t last_intersection_stop_line_candidate_idx, const double time_delay, const util::TrafficPrioritizedLevel & traffic_prioritized_level); - bool isOcclusionCleared( + OcclusionType getOcclusionStatus( const nav_msgs::msg::OccupancyGrid & occ_grid, const std::vector & attention_areas, const lanelet::ConstLanelets & adjacent_lanelets, const lanelet::CompoundPolygon3d & first_attention_area, const util::InterpolatedPathInfo & interpolated_path_info, const std::vector & lane_divisions, - const std::vector & blocking_attention_objects, + const util::TargetObjects & target_objects, const geometry_msgs::msg::Pose & current_pose, const double occlusion_dist_thr); /* diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 403509a926022..d3b2ca3baceb4 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -1279,7 +1279,7 @@ void cutPredictPathWithDuration( util::TargetObjects * target_objects, const rclcpp::Clock::SharedPtr clock, const double time_thr) { const rclcpp::Time current_time = clock->now(); - for (auto & target_object : target_objects->all) { // each objects + for (auto & target_object : target_objects->all_attention_objects) { // each objects for (auto & predicted_path : target_object.object.kinematics.predicted_paths) { // each predicted paths const auto origin_path = predicted_path; diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index 3f09b54f88be4..fdcf05a97a7a9 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -42,7 +42,6 @@ struct DebugData std::optional pass_judge_wall_pose{std::nullopt}; std::optional> attention_area{std::nullopt}; std::optional> occlusion_attention_area{std::nullopt}; - std::optional intersection_area{std::nullopt}; std::optional ego_lane{std::nullopt}; std::optional> adjacent_area{std::nullopt}; std::optional stuck_vehicle_detect_area{std::nullopt}; @@ -57,6 +56,7 @@ struct DebugData nearest_occlusion_projection{std::nullopt}; autoware_auto_perception_msgs::msg::PredictedObjects blocking_attention_objects; std::optional absence_traffic_light_creep_wall{std::nullopt}; + std::optional static_occlusion_with_traffic_light_timeout{std::nullopt}; }; struct InterpolatedPathInfo @@ -191,7 +191,7 @@ struct TargetObjects std::vector attention_objects; std::vector parked_attention_objects; std::vector intersection_area_objects; - std::vector all; // TODO(Mamoru Sobue): avoid copy + std::vector all_attention_objects; // TODO(Mamoru Sobue): avoid copy }; enum class TrafficPrioritizedLevel { From 89ae3bbcb0ca6dd11c3178c667ae404cc1575da7 Mon Sep 17 00:00:00 2001 From: Mehmet Dogru <48479081+mehmetdogru@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:36:31 +0300 Subject: [PATCH 521/547] fix(route_handler): filter out start lanelets wrt start checkpoint orientation (#5358) Signed-off-by: Mehmet Dogru --- planning/route_handler/src/route_handler.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/planning/route_handler/src/route_handler.cpp b/planning/route_handler/src/route_handler.cpp index cf309a294d81f..d74a8712a6e3f 100644 --- a/planning/route_handler/src/route_handler.cpp +++ b/planning/route_handler/src/route_handler.cpp @@ -14,6 +14,7 @@ #include "route_handler/route_handler.hpp" +#include #include #include #include @@ -2127,10 +2128,24 @@ bool RouteHandler::planPathLaneletsBetweenCheckpoints( double shortest_path_length2d = std::numeric_limits::max(); for (const auto & st_llt : start_lanelets) { + // check if the angle difference between start_checkpoint and start lanelet center line + // orientation is in yaw_threshold range + double yaw_threshold = M_PI / 2.0; + bool is_proper_angle = false; + { + double lanelet_angle = lanelet::utils::getLaneletAngle(st_llt, start_checkpoint.position); + double pose_yaw = tf2::getYaw(start_checkpoint.orientation); + double angle_diff = std::abs(autoware_utils::normalize_radian(lanelet_angle - pose_yaw)); + + if (angle_diff <= std::abs(yaw_threshold)) { + is_proper_angle = true; + } + } + optional_route = routing_graph_ptr_->getRoute(st_llt, goal_lanelet, 0); - if (!optional_route) { + if (!optional_route || !is_proper_angle) { RCLCPP_ERROR_STREAM( - logger_, "Failed to find a proper path!" + logger_, "Failed to find a proper route!" << std::endl << " - start checkpoint: " << toString(start_checkpoint) << std::endl << " - goal checkpoint: " << toString(goal_checkpoint) << std::endl From 98c24b824c68605c28513de47a36aaf5a4a89817 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Sat, 21 Oct 2023 11:00:34 +0900 Subject: [PATCH 522/547] perf(motion_utils): faster removeOverlapPoints (#5360) Signed-off-by: Takayuki Murooka --- .../include/motion_utils/trajectory/trajectory.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp index dcdefe61e4000..5d773c5be32d9 100644 --- a/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp +++ b/common/motion_utils/include/motion_utils/trajectory/trajectory.hpp @@ -177,8 +177,7 @@ T removeOverlapPoints(const T & points, const size_t start_idx = 0) for (size_t i = start_idx + 1; i < points.size(); ++i) { const auto prev_p = tier4_autoware_utils::getPoint(dst.back()); const auto curr_p = tier4_autoware_utils::getPoint(points.at(i)); - const double dist = tier4_autoware_utils::calcDistance2d(prev_p, curr_p); - if (dist < eps) { + if (std::abs(prev_p.x - curr_p.x) < eps && std::abs(prev_p.y - curr_p.y) < eps) { continue; } dst.push_back(points.at(i)); From 1502e66edba93f8e39cf9e1e190c947d32b17702 Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Mon, 23 Oct 2023 14:12:54 +0900 Subject: [PATCH 523/547] feat(intersection): use own max acc/jerk param (#5370) Signed-off-by: Mamoru Sobue --- .../config/intersection.param.yaml | 3 +++ .../behavior_velocity_intersection_module/src/manager.cpp | 4 ++++ .../src/scene_intersection.cpp | 3 ++- .../src/scene_intersection.hpp | 3 +++ .../behavior_velocity_intersection_module/src/util.cpp | 8 +++----- .../behavior_velocity_intersection_module/src/util.hpp | 3 ++- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index be8e822b31d5c..fde55dc7a6c55 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -12,6 +12,9 @@ use_intersection_area: false path_interpolation_ds: 0.1 # [m] consider_wrong_direction_vehicle: false + max_accel: -2.8 + max_jerk: -5.0 + delay_response_time: 0.5 stuck_vehicle: turn_direction: left: true diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index 0b4131b55ed90..a65e06eedcdf0 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -62,6 +62,10 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) getOrDeclareParameter(node, ns + ".common.path_interpolation_ds"); ip.common.consider_wrong_direction_vehicle = getOrDeclareParameter(node, ns + ".common.consider_wrong_direction_vehicle"); + ip.common.max_accel = getOrDeclareParameter(node, ns + ".common.max_accel"); + ip.common.max_jerk = getOrDeclareParameter(node, ns + ".common.max_jerk"); + ip.common.delay_response_time = + getOrDeclareParameter(node, ns + ".common.delay_response_time"); ip.stuck_vehicle.turn_direction.left = getOrDeclareParameter(node, ns + ".stuck_vehicle.turn_direction.left"); diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 1bbff9ecaf048..4def152567b32 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -987,7 +987,8 @@ IntersectionModule::DecisionResult IntersectionModule::modifyPathVelocityDetail( const auto intersection_stop_lines_opt = util::generateIntersectionStopLines( first_conflicting_area, dummy_first_attention_area, planner_data_, interpolated_path_info, planner_param_.stuck_vehicle.use_stuck_stopline, planner_param_.common.stop_line_margin, - peeking_offset, path); + planner_param_.common.max_accel, planner_param_.common.max_jerk, + planner_param_.common.delay_response_time, peeking_offset, path); if (!intersection_stop_lines_opt) { return IntersectionModule::Indecisive{"failed to generate intersection_stop_lines"}; } diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index c604f01c7a86b..764f5bd7fe058 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -58,6 +58,9 @@ class IntersectionModule : public SceneModuleInterface bool use_intersection_area; bool consider_wrong_direction_vehicle; double path_interpolation_ds; + double max_accel; + double max_jerk; + double delay_response_time; } common; struct StuckVehicle { diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index d3b2ca3baceb4..27310f2129937 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -269,7 +269,8 @@ std::optional generateIntersectionStopLines( const lanelet::CompoundPolygon3d & first_detection_area, const std::shared_ptr & planner_data, const InterpolatedPathInfo & interpolated_path_info, const bool use_stuck_stopline, - const double stop_line_margin, const double peeking_offset, + const double stop_line_margin, const double peeking_offset, const double max_accel, + const double max_jerk, const double delay_response_time, autoware_auto_planning_msgs::msg::PathWithLaneId * original_path) { const auto & path_ip = interpolated_path_info.path; @@ -350,11 +351,8 @@ std::optional generateIntersectionStopLines( // (4) pass judge line position on interpolated path const double velocity = planner_data->current_velocity->twist.linear.x; const double acceleration = planner_data->current_acceleration->accel.accel.linear.x; - const double max_stop_acceleration = planner_data->max_stop_acceleration_threshold; - const double max_stop_jerk = planner_data->max_stop_jerk_threshold; - const double delay_response_time = planner_data->delay_response_time; const double braking_dist = planning_utils::calcJudgeLineDistWithJerkLimit( - velocity, acceleration, max_stop_acceleration, max_stop_jerk, delay_response_time); + velocity, acceleration, max_accel, max_jerk, delay_response_time); int pass_judge_ip_int = static_cast(first_footprint_inside_detection_ip) - std::ceil(braking_dist / ds); const auto pass_judge_line_ip = static_cast( diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index ef658a25fce55..125d3bdfb570a 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -70,7 +70,8 @@ std::optional generateIntersectionStopLines( const lanelet::CompoundPolygon3d & first_detection_area, const std::shared_ptr & planner_data, const InterpolatedPathInfo & interpolated_path_info, const bool use_stuck_stopline, - const double stop_line_margin, const double peeking_offset, + const double stop_line_margin, const double peeking_offset, const double max_accel, + const double max_jerk, const double delay_response_time, autoware_auto_planning_msgs::msg::PathWithLaneId * original_path); std::optional getFirstPointInsidePolygon( From 6832af073897f841f8901dd8c2038ffee7318d72 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 23 Oct 2023 15:07:59 +0900 Subject: [PATCH 524/547] fix(start_planner): fix invalid lane id access (#5372) Signed-off-by: kosuke55 --- .../src/scene_module/start_planner/start_planner_module.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index ed14292747e68..6b56c88eb942a 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -624,6 +624,9 @@ lanelet::ConstLanelets StartPlannerModule::getPathRoadLanes(const PathWithLaneId std::vector lane_ids; for (const auto & p : path.points) { for (const auto & id : p.lane_ids) { + if (id == lanelet::InvalId) { + continue; + } if (route_handler->isShoulderLanelet(lanelet_layer.get(id))) { continue; } From eca2d255e16aa3cd1b96a9ec7c09fa8e1b626af1 Mon Sep 17 00:00:00 2001 From: kminoda <44218668+kminoda@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:45:19 +0900 Subject: [PATCH 525/547] fix(behavior_path_planner): fix iteration num initial value (#5369) fix(behavior_path_planner): fix iter num initial value Signed-off-by: kminoda --- planning/behavior_path_planner/src/planner_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/behavior_path_planner/src/planner_manager.cpp b/planning/behavior_path_planner/src/planner_manager.cpp index 442049654d64e..79e6055d26a81 100644 --- a/planning/behavior_path_planner/src/planner_manager.cpp +++ b/planning/behavior_path_planner/src/planner_manager.cpp @@ -83,7 +83,7 @@ BehaviorModuleOutput PlannerManager::run(const std::shared_ptr & da return output; } - for (size_t itr_num = 0;; ++itr_num) { + for (size_t itr_num = 1;; ++itr_num) { /** * STEP1: get approved modules' output */ From 911a2727bcb17760ffc5978ba3570a87e2c36089 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Mon, 23 Oct 2023 17:19:30 +0900 Subject: [PATCH 526/547] feat(processing_time_checker): add a script to display processing time (#5375) * feat(processing_time_checker): add script to visualize processing time Signed-off-by: Takamasa Horibe * add maintainer Signed-off-by: Takamasa Horibe * update readme Signed-off-by: Takamasa Horibe * add empty case Signed-off-by: Takamasa Horibe * fix format Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- planning/planning_debug_tools/CMakeLists.txt | 1 + planning/planning_debug_tools/README.md | 23 ++++ .../image/processing_time_checker.png | Bin 0 -> 162295 bytes planning/planning_debug_tools/package.xml | 1 + .../scripts/processing_time_checker.py | 104 ++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 planning/planning_debug_tools/image/processing_time_checker.png create mode 100755 planning/planning_debug_tools/scripts/processing_time_checker.py diff --git a/planning/planning_debug_tools/CMakeLists.txt b/planning/planning_debug_tools/CMakeLists.txt index bf7e36c4c0c0a..58f73ca3836c2 100644 --- a/planning/planning_debug_tools/CMakeLists.txt +++ b/planning/planning_debug_tools/CMakeLists.txt @@ -48,6 +48,7 @@ ament_auto_package( ) install(PROGRAMS + scripts/processing_time_checker.py scripts/trajectory_visualizer.py scripts/closest_velocity_checker.py scripts/perception_replayer/perception_reproducer.py diff --git a/planning/planning_debug_tools/README.md b/planning/planning_debug_tools/README.md index fe9615614de81..aa46c0e2fc7ef 100644 --- a/planning/planning_debug_tools/README.md +++ b/planning/planning_debug_tools/README.md @@ -4,6 +4,8 @@ This package contains several planning-related debug tools. - **Trajectory analyzer**: visualizes the information (speed, curvature, yaw, etc) along the trajectory - **Closest velocity checker**: prints the velocity information indicated by each modules +- **Perception reproducer**: generates detected objects from rosbag data in planning simulator environment +- **processing time checker**: displays processing_time of modules on the terminal ## Trajectory analyzer @@ -241,3 +243,24 @@ ros2 run planning_debug_tools perception_replayer.py -b ``` Instead of publishing predicted objects, you can publish detected/tracked objects by designating `-d` or `-t`, respectively. + +## Processing time checker + +The purpose of the Processing Time Subscriber is to monitor and visualize the processing times of various ROS 2 topics in a system. By providing a real-time terminal-based visualization, users can easily confirm the processing time performance as in the picture below. + +![processing_time_checker](image/processing_time_checker.png) + +You can run the program by the following command. + +```bash +ros2 run planning_debug_tools processing_time_checker.py -f -m +``` + +This program subscribes to ROS 2 topics that have a suffix of `processing_time_ms`. + +The program allows users to customize two parameters via command-line arguments: + +- --max_display_time (or -m): This sets the maximum display time in milliseconds. The default value is 150ms. +- --display_frequency (or -f): This sets the frequency at which the terminal UI updates. The default value is 5Hz. + +By adjusting these parameters, users can tailor the display to their specific monitoring needs. diff --git a/planning/planning_debug_tools/image/processing_time_checker.png b/planning/planning_debug_tools/image/processing_time_checker.png new file mode 100644 index 0000000000000000000000000000000000000000..fb064372796ae1729d600f82e9fb0ee6cf4c1138 GIT binary patch literal 162295 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

Gt;~0 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 YJqg1R=HZ7aAR&^JnEbnfH+nw*2fdP;*8l(j literal 0 HcmV?d00001 diff --git a/planning/planning_debug_tools/package.xml b/planning/planning_debug_tools/package.xml index deadcd54fffdd..14db1277cc02a 100644 --- a/planning/planning_debug_tools/package.xml +++ b/planning/planning_debug_tools/package.xml @@ -6,6 +6,7 @@ The planning_debug_tools package Takamasa Horibe Taiki Tanaka + Takayuki Murooka Apache License 2.0 Takamasa Horibe diff --git a/planning/planning_debug_tools/scripts/processing_time_checker.py b/planning/planning_debug_tools/scripts/processing_time_checker.py new file mode 100755 index 0000000000000..3b80ce4e5588f --- /dev/null +++ b/planning/planning_debug_tools/scripts/processing_time_checker.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +# Copyright 2023 TIER IV, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import argparse +import os +import sys + +import rclpy +from rclpy.node import Node +from tier4_debug_msgs.msg import Float64Stamped + + +class ProcessingTimeSubscriber(Node): + def __init__(self, max_display_time=150, display_frequency=5.0): + super().__init__("processing_time_subscriber") + self.data_map = {} # {topic_name: data_value} + self.max_display_time = max_display_time + self.get_topic_list() + + # Timer to print data at given frequency + self.create_timer(1.0 / display_frequency, self.print_data) + + def get_topic_list(self): + # Get list of existing topics in the system + topic_list = self.get_topic_names_and_types() + + # Subscribe to topics with 'processing_time_ms' suffix + for topic, types in topic_list: + if topic.endswith("processing_time_ms"): + self.create_subscription( + Float64Stamped, topic, lambda msg, topic=topic: self.callback(msg, topic), 1 + ) + self.get_logger().info(f"Subscribed to {topic} | Type: {types}") + + def callback(self, msg, topic): + self.data_map[topic] = msg.data + + def print_data(self): + # Clear terminal + os.system("cls" if os.name == "nt" else "clear") + + if not self.data_map: + print("No topics available.") + return + + # Get the maximum topic name length for alignment + max_topic_length = max(map(len, self.data_map.keys())) + + # Generate the header based on the max_display_time + header_str = "topics".ljust(max_topic_length) + ":" + for i in range(0, self.max_display_time + 1, 20): + header_str += f" {i}ms".ljust(20) + + # Print the header + print(header_str) + print("-" * len(header_str)) + + # Print each topic's data + for topic, data in self.data_map.items(): + # Round the data to the third decimal place for display + data_rounded = round(data, 3) + # Calculate the number of bars to be displayed (clamped to max_display_time) + num_bars = int(min(data, self.max_display_time - 1)) + 1 + print(f"{topic.ljust(max_topic_length)}: {'|' * num_bars} ({data_rounded}ms)") + + +def main(args=None): + # Get the command line arguments before argparse processes them + cmd_args = sys.argv[1:] + + parser = argparse.ArgumentParser(description="Processing Time Subscriber Parameters") + parser.add_argument( + "-m", "--max_display_time", type=int, default=150, help="Maximum display time in ms" + ) + parser.add_argument( + "-f", "--display_frequency", type=float, default=5.0, help="Display frequency in Hz" + ) + args = parser.parse_args() + + rclpy.init(args=cmd_args) # Use the original command line arguments here + subscriber = ProcessingTimeSubscriber( + max_display_time=args.max_display_time, display_frequency=args.display_frequency + ) + rclpy.spin(subscriber) + subscriber.destroy_node() + rclpy.shutdown() + + +if __name__ == "__main__": + main() From fdbe64a470eca3c016b69501ed7a76bcd574a53e Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Mon, 23 Oct 2023 17:28:47 +0900 Subject: [PATCH 527/547] refactor(start_planner): guard for invalid lane id (#5376) Signed-off-by: kosuke55 --- .../src/scene_module/start_planner/start_planner_module.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 6b56c88eb942a..62f3dbfe191ac 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -639,9 +639,7 @@ lanelet::ConstLanelets StartPlannerModule::getPathRoadLanes(const PathWithLaneId lanelet::ConstLanelets path_lanes; path_lanes.reserve(lane_ids.size()); for (const auto & id : lane_ids) { - if (id != lanelet::InvalId) { - path_lanes.push_back(lanelet_layer.get(id)); - } + path_lanes.push_back(lanelet_layer.get(id)); } return path_lanes; From 69813cbc879b7cac763ffd7415a346ec2a88441d Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Mon, 23 Oct 2023 20:27:52 +0900 Subject: [PATCH 528/547] feat(duplicated_node_checker): add duplicated node names to msg (#5382) * add duplicated node names to msg Signed-off-by: kyoichi-sugahara * align with launcher repository Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../config/duplicated_node_checker.param.yaml | 1 + .../duplicated_node_checker_core.hpp | 1 + .../src/duplicated_node_checker_core.cpp | 9 ++++++++- .../system_error_monitor.planning_simulation.param.yaml | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/system/duplicated_node_checker/config/duplicated_node_checker.param.yaml b/system/duplicated_node_checker/config/duplicated_node_checker.param.yaml index 5b8c019de5a20..54b4f691b6eb1 100644 --- a/system/duplicated_node_checker/config/duplicated_node_checker.param.yaml +++ b/system/duplicated_node_checker/config/duplicated_node_checker.param.yaml @@ -1,3 +1,4 @@ /**: ros__parameters: update_rate: 10.0 + add_duplicated_node_names_to_msg: false # if true, duplicated node names are added to msg diff --git a/system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp b/system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp index ec086058e9041..9d806754f3c20 100644 --- a/system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp +++ b/system/duplicated_node_checker/include/duplicated_node_checker/duplicated_node_checker_core.hpp @@ -48,6 +48,7 @@ class DuplicatedNodeChecker : public rclcpp::Node diagnostic_updater::Updater updater_{this}; rclcpp::TimerBase::SharedPtr timer_; + bool add_duplicated_node_names_to_msg_; }; } // namespace duplicated_node_checker diff --git a/system/duplicated_node_checker/src/duplicated_node_checker_core.cpp b/system/duplicated_node_checker/src/duplicated_node_checker_core.cpp index c9aa483724532..46c02d9d6e133 100644 --- a/system/duplicated_node_checker/src/duplicated_node_checker_core.cpp +++ b/system/duplicated_node_checker/src/duplicated_node_checker_core.cpp @@ -28,6 +28,7 @@ DuplicatedNodeChecker::DuplicatedNodeChecker(const rclcpp::NodeOptions & node_op : Node("duplicated_node_checker", node_options) { double update_rate = declare_parameter("update_rate"); + add_duplicated_node_names_to_msg_ = declare_parameter("add_duplicated_node_names_to_msg"); updater_.setHardwareID("duplicated_node_checker"); updater_.add("duplicated_node_checker", this, &DuplicatedNodeChecker::produceDiagnostics); @@ -63,8 +64,14 @@ void DuplicatedNodeChecker::produceDiagnostics(diagnostic_updater::DiagnosticSta std::string msg; int level; if (identical_names.size() > 0) { - msg = "Error"; level = DiagnosticStatus::ERROR; + msg = "Error: Duplicated nodes detected"; + if (add_duplicated_node_names_to_msg_) { + std::set unique_identical_names(identical_names.begin(), identical_names.end()); + for (const auto & name : unique_identical_names) { + msg += " " + name; + } + } for (auto name : identical_names) { stat.add("Duplicated Node Name", name); } diff --git a/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml b/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml index c396e2e9f5ed8..9784259490ec2 100644 --- a/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml +++ b/system/system_error_monitor/config/system_error_monitor.planning_simulation.param.yaml @@ -40,6 +40,7 @@ /autoware/system/service_log_checker: { sf_at: "warn", lf_at: "none", spf_at: "none" } /autoware/system/duplicated_node_checker: default # /autoware/system/resource_monitoring: { sf_at: "warn", lf_at: "error", spf_at: "none" } + # /autoware/system/duplicated_node_checker: default /autoware/vehicle/node_alive_monitoring: default From 20c0d15a60f908d9028bcadf1d25cafa920b4885 Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 24 Oct 2023 00:52:53 +0900 Subject: [PATCH 529/547] fix(goal_planner): fixed goal memory leak (#5381) Signed-off-by: kosuke55 --- .../scene_module/goal_planner/goal_planner_module.hpp | 6 ------ .../src/scene_module/goal_planner/goal_planner_module.cpp | 4 +++- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 251b72ea6eadb..a072c77526ef5 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -166,12 +166,6 @@ class PullOverStatus DEFINE_SETTER_GETTER(std::vector, pull_over_path_candidates) DEFINE_SETTER_GETTER(std::optional, closest_start_pose) - void push_goal_candidate(const GoalCandidate & goal_candidate) - { - std::lock_guard lock(mutex_); - goal_candidates_.push_back(goal_candidate); - } - private: std::shared_ptr pull_over_path_{nullptr}; std::shared_ptr lane_parking_pull_over_path_{nullptr}; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 6253097b1692d..6c431ce5806c3 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -560,7 +560,9 @@ void GoalPlannerModule::generateGoalCandidates() GoalCandidate goal_candidate{}; goal_candidate.goal_pose = goal_pose; goal_candidate.distance_from_original_goal = 0.0; - status_.push_goal_candidate(goal_candidate); + GoalCandidates goal_candidates{}; + goal_candidates.push_back(goal_candidate); + status_.set_goal_candidates(goal_candidates); } } From 3d2a7f6038627ca52a4f0c2af6f5a8d56bc6962b Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 24 Oct 2023 00:54:47 +0900 Subject: [PATCH 530/547] refactor(goal_planner): rebuild state transition (#5371) * refactor(goal_planner): rebuild state transition Signed-off-by: kosuke55 * fix occ Signed-off-by: kosuke55 * fix canTransitIdleToRunningState Signed-off-by: kosuke55 --------- Signed-off-by: kosuke55 --- .../goal_planner/goal_planner_module.hpp | 19 +++--- .../goal_planner/goal_planner_module.cpp | 59 ++++++++----------- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index a072c77526ef5..34ffb61dcd8d5 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -240,31 +240,28 @@ class GoalPlannerModule : public SceneModuleInterface } } - // TODO(someone): remove this, and use base class function - [[deprecated]] BehaviorModuleOutput run() override; - bool isExecutionRequested() const override; - bool isExecutionReady() const override; - // TODO(someone): remove this, and use base class function - [[deprecated]] ModuleStatus updateState() override; + CandidateOutput planCandidate() const override; BehaviorModuleOutput plan() override; BehaviorModuleOutput planWaitingApproval() override; - void processOnEntry() override; + bool isExecutionRequested() const override; + bool isExecutionReady() const override; void processOnExit() override; + void updateData() override; void setParameters(const std::shared_ptr & parameters); void acceptVisitor( [[maybe_unused]] const std::shared_ptr & visitor) const override { } - // not used, but need to override - CandidateOutput planCandidate() const override { return CandidateOutput{}; }; - private: + // The start_planner activates when it receives a new route, + // so there is no need to terminate the goal planner. + // If terminating it, it may switch to lane following and could generate an inappropriate path. bool canTransitSuccessState() override { return false; } bool canTransitFailureState() override { return false; } - bool canTransitIdleToRunningState() override { return false; } + bool canTransitIdleToRunningState() override { return true; } mutable StartGoalPlannerData goal_planner_data_; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 6c431ce5806c3..e71b1ac7936be 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -124,6 +124,13 @@ GoalPlannerModule::GoalPlannerModule( freespace_parking_timer_cb_group_); } + // Initialize safety checker + if (parameters_->safety_check_params.enable_safety_check) { + initializeSafetyCheckParameters(); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + goal_planner_data_.collision_check); + } + status_.reset(); } @@ -228,16 +235,18 @@ void GoalPlannerModule::onFreespaceParkingTimer() } } -BehaviorModuleOutput GoalPlannerModule::run() +void GoalPlannerModule::updateData() { - current_state_ = ModuleStatus::RUNNING; - updateOccupancyGrid(); - - if (!isActivated()) { - return planWaitingApproval(); + // Initialize Occupancy Grid Map + // This operation requires waiting for `planner_data_`, hence it is executed here instead of in + // the constructor. Ideally, this operation should only need to be performed once. + if ( + parameters_->use_occupancy_grid_for_goal_search || + parameters_->use_occupancy_grid_for_path_collision_check) { + initializeOccupancyGridMap(); } - return plan(); + updateOccupancyGrid(); } void GoalPlannerModule::initializeOccupancyGridMap() @@ -264,22 +273,6 @@ void GoalPlannerModule::initializeSafetyCheckParameters() objects_filtering_params_, parameters_); } -void GoalPlannerModule::processOnEntry() -{ - // Initialize occupancy grid map - if ( - parameters_->use_occupancy_grid_for_goal_search || - parameters_->use_occupancy_grid_for_path_collision_check) { - initializeOccupancyGridMap(); - } - // Initialize safety checker - if (parameters_->safety_check_params.enable_safety_check) { - initializeSafetyCheckParameters(); - utils::start_goal_planner_common::initializeCollisionCheckDebugMap( - goal_planner_data_.collision_check); - } -} - void GoalPlannerModule::processOnExit() { resetPathCandidate(); @@ -446,13 +439,6 @@ Pose GoalPlannerModule::calcRefinedGoal(const Pose & goal_pose) const return refined_goal_pose; } -ModuleStatus GoalPlannerModule::updateState() -{ - // start_planner module will be run when setting new goal, so not need finishing pull_over module. - // Finishing it causes wrong lane_following path generation. - return current_state_; -} - bool GoalPlannerModule::planFreespacePath() { goal_searcher_->setPlannerData(planner_data_); @@ -907,6 +893,12 @@ void GoalPlannerModule::decideVelocity() status_.set_has_decided_velocity(true); } +CandidateOutput GoalPlannerModule::planCandidate() const +{ + return CandidateOutput( + status_.get_pull_over_path() ? status_.get_pull_over_path()->getFullPath() : PathWithLaneId()); +} + BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() { constexpr double path_update_duration = 1.0; @@ -945,7 +937,7 @@ BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() // set output and status BehaviorModuleOutput output{}; setOutput(output); - path_candidate_ = std::make_shared(status_.get_pull_over_path()->getFullPath()); + path_candidate_ = std::make_shared(planCandidate().path_candidate); path_reference_ = getPreviousModuleOutput().reference_path; // return to lane parking if it is possible @@ -998,10 +990,7 @@ BehaviorModuleOutput GoalPlannerModule::planWaitingApprovalWithGoalModification( out.modified_goal = plan().modified_goal; // update status_ out.path = std::make_shared(generateStopPath()); out.reference_path = getPreviousModuleOutput().reference_path; - path_candidate_ = - status_.get_is_safe_static_objects() - ? std::make_shared(status_.get_pull_over_path()->getFullPath()) - : out.path; + path_candidate_ = std::make_shared(planCandidate().path_candidate); path_reference_ = getPreviousModuleOutput().reference_path; const auto distance_to_path_change = calcDistanceToPathChange(); From 0938f5b69b4046216e05553a2cf1b8a966afd65f Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Tue, 24 Oct 2023 02:17:03 +0900 Subject: [PATCH 531/547] fix(goal_planner): use recursive mutex instead of transaction (#5379) Revert "use transaction instead of recursive_mutex" This reverts commit 654fa8cd0dbf036cf693abbd137acae9936c327c. --- .../goal_planner/goal_planner_module.hpp | 57 ++++++------------- .../goal_planner/goal_planner_module.cpp | 49 +++++++--------- 2 files changed, 36 insertions(+), 70 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 34ffb61dcd8d5..21372ed6e2c2f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -76,47 +76,29 @@ enum class PathType { FREESPACE, }; -class PullOverStatus; // Forward declaration for Transaction -// class that locks the PullOverStatus when multiple values are being updated from -// an external source. -class Transaction -{ -public: - explicit Transaction(PullOverStatus & status); - ~Transaction(); - -private: - PullOverStatus & status_; -}; - -#define DEFINE_SETTER_GETTER(TYPE, NAME) \ -public: \ - void set_##NAME(const TYPE & value) \ - { \ - if (!is_in_transaction_) { \ - const std::lock_guard lock(mutex_); \ - NAME##_ = value; \ - } else { \ - NAME##_ = value; \ - } \ - } \ - \ - TYPE get_##NAME() const \ - { \ - if (!is_in_transaction_) { \ - const std::lock_guard lock(mutex_); \ - return NAME##_; \ - } \ - return NAME##_; \ +#define DEFINE_SETTER_GETTER(TYPE, NAME) \ +public: \ + void set_##NAME(const TYPE & value) \ + { \ + const std::lock_guard lock(mutex_); \ + NAME##_ = value; \ + } \ + \ + TYPE get_##NAME() const \ + { \ + const std::lock_guard lock(mutex_); \ + return NAME##_; \ } class PullOverStatus { public: + explicit PullOverStatus(std::recursive_mutex & mutex) : mutex_(mutex) {} + // Reset all data members to their initial states void reset() { - const std::lock_guard lock(mutex_); + std::lock_guard lock(mutex_); pull_over_path_ = nullptr; lane_parking_pull_over_path_ = nullptr; current_path_idx_ = 0; @@ -136,9 +118,6 @@ class PullOverStatus is_ready_ = false; } - // lock all data members - Transaction startTransaction() { return Transaction(*this); } - DEFINE_SETTER_GETTER(std::shared_ptr, pull_over_path) DEFINE_SETTER_GETTER(std::shared_ptr, lane_parking_pull_over_path) DEFINE_SETTER_GETTER(size_t, current_path_idx) @@ -200,9 +179,7 @@ class PullOverStatus std::vector pull_over_path_candidates_; std::optional closest_start_pose_; - friend class Transaction; - mutable std::mutex mutex_; - bool is_in_transaction_{false}; + std::recursive_mutex & mutex_; }; #undef DEFINE_SETTER_GETTER @@ -291,7 +268,7 @@ class GoalPlannerModule : public SceneModuleInterface tier4_autoware_utils::LinearRing2d vehicle_footprint_; - std::mutex mutex_; + std::recursive_mutex mutex_; PullOverStatus status_; // approximate distance from the start point to the end point of pull_over. diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index e71b1ac7936be..95ac47494d016 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -50,25 +50,14 @@ using tier4_autoware_utils::inverseTransformPose; namespace behavior_path_planner { -Transaction::Transaction(PullOverStatus & status) : status_(status) -{ - status_.mutex_.lock(); - status_.is_in_transaction_ = true; -} - -Transaction::~Transaction() -{ - status_.mutex_.unlock(); - status_.is_in_transaction_ = false; -} - GoalPlannerModule::GoalPlannerModule( const std::string & name, rclcpp::Node & node, const std::shared_ptr & parameters, const std::unordered_map > & rtc_interface_ptr_map) : SceneModuleInterface{name, node, rtc_interface_ptr_map}, parameters_{parameters}, - vehicle_info_{vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo()} + vehicle_info_{vehicle_info_util::VehicleInfoUtil(node).getVehicleInfo()}, + status_{mutex_} { LaneDepartureChecker lane_departure_checker{}; lane_departure_checker.setVehicleInfo(vehicle_info_); @@ -205,9 +194,11 @@ void GoalPlannerModule::onTimer() } // set member variables - const auto transaction = status_.startTransaction(); - status_.set_pull_over_path_candidates(path_candidates); - status_.set_closest_start_pose(closest_start_pose); + { + const std::lock_guard lock(mutex_); + status_.set_pull_over_path_candidates(path_candidates); + status_.set_closest_start_pose(closest_start_pose); + } } void GoalPlannerModule::onFreespaceParkingTimer() @@ -447,7 +438,7 @@ bool GoalPlannerModule::planFreespacePath() status_.set_goal_candidates(goal_candidates); { - const std::lock_guard lock(mutex_); + const std::lock_guard lock(mutex_); debug_data_.freespace_planner.num_goal_candidates = goal_candidates.size(); debug_data_.freespace_planner.is_planning = true; } @@ -455,7 +446,7 @@ bool GoalPlannerModule::planFreespacePath() for (size_t i = 0; i < goal_candidates.size(); i++) { const auto goal_candidate = goal_candidates.at(i); { - const std::lock_guard lock(mutex_); + const std::lock_guard lock(mutex_); debug_data_.freespace_planner.current_goal_idx = i; } @@ -470,20 +461,19 @@ bool GoalPlannerModule::planFreespacePath() } { - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); status_.set_pull_over_path(std::make_shared(*freespace_path)); status_.set_current_path_idx(0); status_.set_is_safe_static_objects(true); status_.set_modified_goal_pose(goal_candidate); status_.set_last_path_update_time(std::make_shared(clock_->now())); - const std::lock_guard lock(mutex_); debug_data_.freespace_planner.is_planning = false; } return true; } - const std::lock_guard lock(mutex_); + const std::lock_guard lock(mutex_); debug_data_.freespace_planner.is_planning = false; return false; } @@ -513,7 +503,7 @@ void GoalPlannerModule::returnToLaneParking() } { - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); status_.set_is_safe_static_objects(true); status_.set_has_decided_path(false); status_.set_pull_over_path(status_.get_lane_parking_pull_over_path()); @@ -609,7 +599,7 @@ void GoalPlannerModule::selectSafePullOverPath() std::vector pull_over_path_candidates{}; GoalCandidates goal_candidates{}; { - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); goal_searcher_->setPlannerData(planner_data_); goal_candidates = status_.get_goal_candidates(); goal_searcher_->update(goal_candidates); @@ -640,7 +630,7 @@ void GoalPlannerModule::selectSafePullOverPath() // found safe pull over path { - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); status_.set_is_safe_static_objects(true); status_.set_pull_over_path(std::make_shared(pull_over_path)); status_.set_current_path_idx(0); @@ -681,7 +671,7 @@ void GoalPlannerModule::selectSafePullOverPath() void GoalPlannerModule::setLanes() { - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); status_.set_current_lanes(utils::getExtendedCurrentLanes( planner_data_, parameters_->backward_goal_search_length, parameters_->forward_goal_search_length, @@ -731,7 +721,7 @@ void GoalPlannerModule::setOutput(BehaviorModuleOutput & output) // for the next loop setOutput(). // this is used to determine whether to generate a new stop path or keep the current stop path. - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); status_.set_prev_is_safe(status_.get_is_safe_static_objects()); status_.set_prev_is_safe_dynamic_objects( parameters_->safety_check_params.enable_safety_check ? isSafePath() : true); @@ -742,7 +732,7 @@ void GoalPlannerModule::setStopPath(BehaviorModuleOutput & output) if (status_.get_prev_is_safe() || !status_.get_prev_stop_path()) { // safe -> not_safe or no prev_stop_path: generate new stop_path output.path = std::make_shared(generateStopPath()); - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); status_.set_prev_stop_path(output.path); // set stop path as pull over path auto stop_pull_over_path = std::make_shared(); @@ -916,7 +906,7 @@ BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() if (status_.get_has_decided_path()) { if (isActivated() && isWaitingApproval()) { { - const auto transaction = status_.startTransaction(); + const std::lock_guard lock(mutex_); status_.set_last_approved_time(std::make_shared(clock_->now())); status_.set_last_approved_pose( std::make_shared(planner_data_->self_odometry->pose.pose)); @@ -1226,7 +1216,7 @@ bool GoalPlannerModule::isStopped( bool GoalPlannerModule::isStopped() { - const std::lock_guard lock(mutex_); + const std::lock_guard lock(mutex_); return isStopped(odometry_buffer_stopped_, parameters_->th_stopped_time); } @@ -1236,7 +1226,6 @@ bool GoalPlannerModule::isStuck() return false; } - const std::lock_guard lock(mutex_); constexpr double stuck_time = 5.0; if (!isStopped(odometry_buffer_stuck_, stuck_time)) { return false; From 8a66cbae0ac31735bc7ccf915818dd5a631349ec Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 24 Oct 2023 09:29:32 +0900 Subject: [PATCH 532/547] fix(crosswalk): fix duplicated crosswalk launch (#5383) Signed-off-by: Takayuki Murooka --- .../src/manager.cpp | 21 ++++++++++++------- .../src/scene_crosswalk.cpp | 8 +++---- .../src/scene_crosswalk.hpp | 4 ++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/planning/behavior_velocity_crosswalk_module/src/manager.cpp b/planning/behavior_velocity_crosswalk_module/src/manager.cpp index 94e87d0174193..dd8dc95a8ad3c 100644 --- a/planning/behavior_velocity_crosswalk_module/src/manager.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/manager.cpp @@ -128,8 +128,9 @@ void CrosswalkModuleManager::launchNewModules(const PathWithLaneId & path) { const auto rh = planner_data_->route_handler_; - const auto launch = [this, &path](const auto id, const auto & use_regulatory_element) { - if (isModuleRegistered(id)) { + const auto launch = [this, &path]( + const auto lane_id, const std::optional & reg_elem_id) { + if (isModuleRegistered(lane_id)) { return; } @@ -137,24 +138,28 @@ void CrosswalkModuleManager::launchNewModules(const PathWithLaneId & path) const auto logger = logger_.get_child("crosswalk_module"); const auto lanelet_map_ptr = planner_data_->route_handler_->getLaneletMapPtr(); + // NOTE: module_id is always a lane id so that isModuleRegistered works correctly in the case + // where both regulatory element and non-regulatory element crosswalks exist. registerModule(std::make_shared( - node_, id, lanelet_map_ptr, p, use_regulatory_element, logger, clock_)); - generateUUID(id); - updateRTCStatus(getUUID(id), true, std::numeric_limits::lowest(), path.header.stamp); + node_, lane_id, reg_elem_id, lanelet_map_ptr, p, logger, clock_)); + generateUUID(lane_id); + updateRTCStatus( + getUUID(lane_id), true, std::numeric_limits::lowest(), path.header.stamp); }; const auto crosswalk_leg_elem_map = planning_utils::getRegElemMapOnPath( path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); for (const auto & crosswalk : crosswalk_leg_elem_map) { - launch(crosswalk.first->id(), true); + // NOTE: The former id is a lane id, and the latter one is a regulatory element's id. + launch(crosswalk.first->crosswalkLanelet().id(), crosswalk.first->id()); } const auto crosswalk_lanelets = getCrosswalksOnPath( planner_data_->current_odometry->pose, path, rh->getLaneletMapPtr(), rh->getOverallGraphPtr()); for (const auto & crosswalk : crosswalk_lanelets) { - launch(crosswalk.id(), false); + launch(crosswalk.id(), std::nullopt); } } @@ -172,7 +177,7 @@ CrosswalkModuleManager::getModuleExpiredFunction(const PathWithLaneId & path) path, rh->getLaneletMapPtr(), planner_data_->current_odometry->pose); for (const auto & crosswalk : crosswalk_leg_elem_map) { - crosswalk_id_set.insert(crosswalk.first->id()); + crosswalk_id_set.insert(crosswalk.first->crosswalkLanelet().id()); } return [crosswalk_id_set](const std::shared_ptr & scene_module) { diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp index fe96985743b94..56f5072a2ade9 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.cpp @@ -176,20 +176,20 @@ tier4_debug_msgs::msg::StringStamped createStringStampedMessage( } // namespace CrosswalkModule::CrosswalkModule( - rclcpp::Node & node, const int64_t module_id, const lanelet::LaneletMapPtr & lanelet_map_ptr, - const PlannerParam & planner_param, const bool use_regulatory_element, + rclcpp::Node & node, const int64_t module_id, const std::optional & reg_elem_id, + const lanelet::LaneletMapPtr & lanelet_map_ptr, const PlannerParam & planner_param, const rclcpp::Logger & logger, const rclcpp::Clock::SharedPtr clock) : SceneModuleInterface(module_id, logger, clock), module_id_(module_id), planner_param_(planner_param), - use_regulatory_element_(use_regulatory_element) + use_regulatory_element_(reg_elem_id) { velocity_factor_.init(VelocityFactor::CROSSWALK); passed_safety_slow_point_ = false; if (use_regulatory_element_) { const auto reg_elem_ptr = std::dynamic_pointer_cast( - lanelet_map_ptr->regulatoryElementLayer.get(module_id)); + lanelet_map_ptr->regulatoryElementLayer.get(*reg_elem_id)); stop_lines_ = reg_elem_ptr->stopLines(); crosswalk_ = reg_elem_ptr->crosswalkLanelet(); } else { diff --git a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp index 063ea4f83cd45..0768101179857 100644 --- a/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp +++ b/planning/behavior_velocity_crosswalk_module/src/scene_crosswalk.hpp @@ -299,8 +299,8 @@ class CrosswalkModule : public SceneModuleInterface }; CrosswalkModule( - rclcpp::Node & node, const int64_t module_id, const lanelet::LaneletMapPtr & lanelet_map_ptr, - const PlannerParam & planner_param, const bool use_regulatory_element, + rclcpp::Node & node, const int64_t module_id, const std::optional & reg_elem_id, + const lanelet::LaneletMapPtr & lanelet_map_ptr, const PlannerParam & planner_param, const rclcpp::Logger & logger, const rclcpp::Clock::SharedPtr clock); bool modifyPathVelocity(PathWithLaneId * path, StopReason * stop_reason) override; From 7c3bde1a6bdcda425999b5f02f06ffe5712fc709 Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Tue, 24 Oct 2023 09:33:04 +0900 Subject: [PATCH 533/547] docs(mpc_lateral_controller): update parameter description (#5309) * update parameter Signed-off-by: kyoichi-sugahara * fix description Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- control/mpc_lateral_controller/README.md | 169 +++++++++++++---------- 1 file changed, 96 insertions(+), 73 deletions(-) diff --git a/control/mpc_lateral_controller/README.md b/control/mpc_lateral_controller/README.md index 7579e3da265c4..50e9d44a0a7a4 100644 --- a/control/mpc_lateral_controller/README.md +++ b/control/mpc_lateral_controller/README.md @@ -67,7 +67,7 @@ Set the following from the [controller_node](../trajectory_follower_node/README. - `autoware_auto_planning_msgs/Trajectory` : reference trajectory to follow. - `nav_msgs/Odometry`: current odometry -- `autoware_auto_vehicle_msgs/SteeringReport` current steering +- `autoware_auto_vehicle_msgs/SteeringReport`: current steering ### Outputs @@ -89,83 +89,108 @@ can be calculated by providing the current steer, velocity, and pose to function The default parameters defined in `param/lateral_controller_defaults.param.yaml` are adjusted to the AutonomouStuff Lexus RX 450h for under 40 km/h driving. -| Name | Type | Description | Default value | -| :------------------------------------------- | :----- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | -| show_debug_info | bool | display debug info | false | -| traj_resample_dist | double | distance of waypoints in resampling [m] | 0.1 | -| enable_path_smoothing | bool | path smoothing flag. This should be true when uses path resampling to reduce resampling noise. | true | -| path_filter_moving_ave_num | int | number of data points moving average filter for path smoothing | 35 | -| path_smoothing_times | int | number of times of applying path smoothing filter | 1 | -| curvature_smoothing_num_ref_steer | double | index distance of points used in curvature calculation for reference steering command: p(i-num), p(i), p(i+num). larger num makes less noisy values. | 35 | -| curvature_smoothing_num_traj | double | index distance of points used in curvature calculation for trajectory: p(i-num), p(i), p(i+num). larger num makes less noisy values. | 1 | -| extend_trajectory_for_end_yaw_control | bool | trajectory extending flag for end yaw control. | true | -| steering_lpf_cutoff_hz | double | cutoff frequency of lowpass filter for steering output command [hz] | 3.0 | -| admissible_position_error | double | stop vehicle when following position error is larger than this value [m]. | 5.0 | -| admissible_yaw_error_rad | double | stop vehicle when following yaw angle error is larger than this value [rad]. | 1.57 | -| stop_state_entry_ego_speed \*1 | double | threshold value of the ego vehicle speed used to the stop state entry condition | 0.0 | -| stop_state_entry_target_speed \*1 | double | threshold value of the target speed used to the stop state entry condition | 0.0 | -| converged_steer_rad | double | threshold value of the steer convergence | 0.1 | -| keep_steer_control_until_converged | bool | keep steer control until steer is converged | true | -| new_traj_duration_time | double | threshold value of the time to be considered as new trajectory | 1.0 | -| new_traj_end_dist | double | threshold value of the distance between trajectory ends to be considered as new trajectory | 0.3 | -| mpc_converged_threshold_rps | double | threshold value to be sure output of the optimization is converged, it is used in stopped state | 0.3 | - -(\*1) To prevent unnecessary steering movement, the steering command is fixed to the previous value in the stop state. - -#### MPC algorithm - -| Name | Type | Description | Default value | -| :-------------------------------------------------- | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------- | -| qp_solver_type | string | QP solver option. described below in detail. | unconstraint_fast | -| mpc_vehicle_model_type | string | vehicle model option. described below in detail. | kinematics | -| mpc_prediction_horizon | int | total prediction step for MPC | 70 | -| mpc_prediction_sampling_time | double | prediction period for one step [s] | 0.1 | -| mpc_weight_lat_error | double | weight for lateral error | 0.1 | -| mpc_weight_heading_error | double | weight for heading error | 0.0 | -| mpc_weight_heading_error_squared_vel_coeff | double | weight for heading error \* velocity | 5.0 | -| mpc_weight_steering_input | double | weight for steering error (steer command - reference steer) | 1.0 | -| mpc_weight_steering_input_squared_vel_coeff | double | weight for steering error (steer command - reference steer) \* velocity | 0.25 | -| mpc_weight_lat_jerk | double | weight for lateral jerk (steer(i) - steer(i-1)) \* velocity | 0.0 | -| mpc_weight_terminal_lat_error | double | terminal cost weight for lateral error | 1.0 | -| mpc_weight_steer_rate | double | weight for steering rate [rad/s] | 0.0 | -| mpc_weight_steer_acc | double | weight for derivatives of the steering rate [rad/ss] | 0.0 | -| mpc_weight_terminal_heading_error | double | terminal cost weight for heading error | 0.1 | -| mpc_low_curvature_thresh_curvature | double | trajectory curvature threshold to change the weight. If the curvature is lower than this value, the `low_curvature_weight_**` values will be used. | 0.0 | -| mpc_low_curvature_weight_lat_error | double | [used in a low curvature trajectory] weight for lateral error | 0.0 | -| mpc_low_curvature_weight_heading_error | double | [used in a low curvature trajectory] weight for heading error | 0.0 | -| mpc_low_curvature_weight_heading_error_squared_vel | double | [used in a low curvature trajectory] weight for heading error \* velocity | 0.0 | -| mpc_low_curvature_weight_steering_input | double | [used in a low curvature trajectory] weight for steering error (steer command - reference steer) | 0.0 | -| mpc_low_curvature_weight_steering_input_squared_vel | double | [used in a low curvature trajectory] weight for steering error (steer command - reference steer) \* velocity | 0.0 | -| mpc_low_curvature_weight_lat_jerk | double | [used in a low curvature trajectory] weight for lateral jerk (steer(i) - steer(i-1)) \* velocity | 0.0 | -| mpc_low_curvature_weight_steer_rate | double | [used in a low curvature trajectory] weight for steering rate [rad/s] | 0.0 | -| mpc_low_curvature_weight_steer_acc | double | [used in a low curvature trajectory] weight for derivatives of the steering rate [rad/ss] | 0.0 | -| mpc_zero_ff_steer_deg | double | threshold of feedforward angle [deg]. feedforward angle smaller than this value is set to zero. | 2.0 | - -#### Steering offset remover - -Defined in the `steering_offset` namespace. This logic is designed as simple as possible, with minimum design parameters. - -| Name | Type | Description | Default value | -| :---------------------------------- | :----- | :----------------------------------------------------------------------------------------------- | :------------ | -| enable_auto_steering_offset_removal | bool | Estimate the steering offset and apply compensation | true | -| update_vel_threshold | double | If the velocity is smaller than this value, the data is not used for the offset estimation. | 5.56 | -| update_steer_threshold | double | If the steering angle is larger than this value, the data is not used for the offset estimation. | 0.035 | -| average_num | double | The average of this number of data is used as a steering offset. | 1000 | -| steering_offset_limit | double | The angle limit to be applied to the offset compensation. | 0.02 | - -#### Vehicle model +#### System + +| Name | Type | Description | Default value | +| :------------------------ | :------ | :-------------------------------------------------------------------------- | :------------ | +| traj_resample_dist | double | distance of waypoints in resampling [m] | 0.1 | +| use_steer_prediction | boolean | flag for using steer prediction (do not use steer measurement) | false | +| admissible_position_error | double | stop vehicle when following position error is larger than this value [m] | 5.0 | +| admissible_yaw_error_rad | double | stop vehicle when following yaw angle error is larger than this value [rad] | 1.57 | + +#### Path Smoothing + +| Name | Type | Description | Default value | +| :-------------------------------- | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | +| enable_path_smoothing | boolean | path smoothing flag. This should be true when uses path resampling to reduce resampling noise. | false | +| path_filter_moving_ave_num | int | number of data points moving average filter for path smoothing | 25 | +| curvature_smoothing_num_traj | int | index distance of points used in curvature calculation for trajectory: p(i-num), p(i), p(i+num). larger num makes less noisy values. | 15 | +| curvature_smoothing_num_ref_steer | int | index distance of points used in curvature calculation for reference steering command: p(i-num), p(i), p(i+num). larger num makes less noisy values. | 15 | + +#### Trajectory Extending + +| Name | Type | Description | Default value | +| :------------------------------------ | :------ | :-------------------------------------------- | :------------ | +| extend_trajectory_for_end_yaw_control | boolean | trajectory extending flag for end yaw control | true | + +#### MPC Optimization + +| Name | Type | Description | Default value | +| :-------------------------------------------------- | :----- | :----------------------------------------------------------------------------------------------------------- | :------------ | +| qp_solver_type | string | QP solver option. described below in detail. | "osqp" | +| mpc_prediction_horizon | int | total prediction step for MPC | 50 | +| mpc_prediction_dt | double | prediction period for one step [s] | 0.1 | +| mpc_weight_lat_error | double | weight for lateral error | 1.0 | +| mpc_weight_heading_error | double | weight for heading error | 0.0 | +| mpc_weight_heading_error_squared_vel | double | weight for heading error \* velocity | 0.3 | +| mpc_weight_steering_input | double | weight for steering error (steer command - reference steer) | 1.0 | +| mpc_weight_steering_input_squared_vel | double | weight for steering error (steer command - reference steer) \* velocity | 0.25 | +| mpc_weight_lat_jerk | double | weight for lateral jerk (steer(i) - steer(i-1)) \* velocity | 0.1 | +| mpc_weight_steer_rate | double | weight for steering rate [rad/s] | 0.0 | +| mpc_weight_steer_acc | double | weight for derivatives of the steering rate [rad/ss] | 0.000001 | +| mpc_low_curvature_weight_lat_error | double | [used in a low curvature trajectory] weight for lateral error | 0.1 | +| mpc_low_curvature_weight_heading_error | double | [used in a low curvature trajectory] weight for heading error | 0.0 | +| mpc_low_curvature_weight_heading_error_squared_vel | double | [used in a low curvature trajectory] weight for heading error \* velocity | 0.3 | +| mpc_low_curvature_weight_steering_input | double | [used in a low curvature trajectory] weight for steering error (steer command - reference steer) | 1.0 | +| mpc_low_curvature_weight_steering_input_squared_vel | double | [used in a low curvature trajectory] weight for steering error (steer command - reference steer) \* velocity | 0.25 | +| mpc_low_curvature_weight_lat_jerk | double | [used in a low curvature trajectory] weight for lateral jerk (steer(i) - steer(i-1)) \* velocity | 0.0 | +| mpc_low_curvature_weight_steer_rate | double | [used in a low curvature trajectory] weight for steering rate [rad/s] | 0.0 | +| mpc_low_curvature_weight_steer_acc | double | [used in a low curvature trajectory] weight for derivatives of the steering rate [rad/ss] | 0.000001 | +| mpc_low_curvature_thresh_curvature | double | threshold of curvature to use "low_curvature" parameter | 0.0 | +| mpc_weight_terminal_lat_error | double | terminal lateral error weight in matrix Q to improve mpc stability | 1.0 | +| mpc_weight_terminal_heading_error | double | terminal heading error weight in matrix Q to improve mpc stability | 0.1 | +| mpc_zero_ff_steer_deg | double | threshold that feed-forward angle becomes zero | 0.5 | +| mpc_acceleration_limit | double | limit on the vehicle's acceleration | 2.0 | +| mpc_velocity_time_constant | double | time constant used for velocity smoothing | 0.3 | +| mpc_min_prediction_length | double | minimum prediction length | 5.0 | + +#### Vehicle Model | Name | Type | Description | Default value | | :----------------------------------- | :------- | :--------------------------------------------------------------------------------- | :------------------- | +| vehicle_model_type | string | vehicle model type for mpc prediction | "kinematics" | | input_delay | double | steering input delay time for delay compensation | 0.24 | -| vehicle_model_steer_tau | double | steering dynamics time constant | 0.3 | -| steer_rate_lim_dps_list_by_curvature | [double] | steering angle rate limit list depending on curvature [deg/s] | [10.0, 20.0, 30.0] | +| vehicle_model_steer_tau | double | steering dynamics time constant (1d approximation) [s] | 0.3 | +| steer_rate_lim_dps_list_by_curvature | [double] | steering angle rate limit list depending on curvature [deg/s] | [40.0, 50.0, 60.0] | | curvature_list_for_steer_rate_lim | [double] | curvature list for steering angle rate limit interpolation in ascending order [/m] | [0.001, 0.002, 0.01] | -| steer_rate_lim_dps_list_by_velocity | [double] | steering angle rate limit list depending on velocity [deg/s] | [40.0, 30.0, 20.0] | +| steer_rate_lim_dps_list_by_velocity | [double] | steering angle rate limit list depending on velocity [deg/s] | [60.0, 50.0, 40.0] | | velocity_list_for_steer_rate_lim | [double] | velocity list for steering angle rate limit interpolation in ascending order [m/s] | [10.0, 15.0, 20.0] | | acceleration_limit | double | acceleration limit for trajectory velocity modification [m/ss] | 2.0 | | velocity_time_constant | double | velocity dynamics time constant for trajectory velocity modification [s] | 0.3 | +#### Lowpass Filter for Noise Reduction + +| Name | Type | Description | Default value | +| :------------------------ | :----- | :------------------------------------------------------------------ | :------------ | +| steering_lpf_cutoff_hz | double | cutoff frequency of lowpass filter for steering output command [hz] | 3.0 | +| error_deriv_lpf_cutoff_hz | double | cutoff frequency of lowpass filter for error derivative [Hz] | 5.0 | + +#### Stop State + +| Name | Type | Description | Default value | +| :------------------------------------------- | :------ | :---------------------------------------------------------------------------------------------- | :------------ | +| stop_state_entry_ego_speed \*1 | double | threshold value of the ego vehicle speed used to the stop state entry condition | 0.001 | +| stop_state_entry_target_speed \*1 | double | threshold value of the target speed used to the stop state entry condition | 0.001 | +| converged_steer_rad | double | threshold value of the steer convergence | 0.1 | +| keep_steer_control_until_converged | boolean | keep steer control until steer is converged | true | +| new_traj_duration_time | double | threshold value of the time to be considered as new trajectory | 1.0 | +| new_traj_end_dist | double | threshold value of the distance between trajectory ends to be considered as new trajectory | 0.3 | +| mpc_converged_threshold_rps | double | threshold value to be sure output of the optimization is converged, it is used in stopped state | 0.01 | + +(\*1) To prevent unnecessary steering movement, the steering command is fixed to the previous value in the stop state. + +#### Steer Offset + +Defined in the `steering_offset` namespace. This logic is designed as simple as possible, with minimum design parameters. + +| Name | Type | Description | Default value | +| :---------------------------------- | :------ | :----------------------------------------------------------------------------------------------- | :------------ | +| enable_auto_steering_offset_removal | boolean | Estimate the steering offset and apply compensation | true | +| update_vel_threshold | double | If the velocity is smaller than this value, the data is not used for the offset estimation | 5.56 | +| update_steer_threshold | double | If the steering angle is larger than this value, the data is not used for the offset estimation. | 0.035 | +| average_num | int | The average of this number of data is used as a steering offset. | 1000 | +| steering_offset_limit | double | The angle limit to be applied to the offset compensation. | 0.02 | + ##### For dynamics model (WIP) | Name | Type | Description | Default value | @@ -215,10 +240,8 @@ If you want to adjust the effect only in the high-speed range, you can use `weig - `weight_steering_input`: Reduce oscillation of tracking. - `weight_steering_input_squared_vel_coeff`: Reduce oscillation of tracking in high speed range. - `weight_lat_jerk`: Reduce lateral jerk. -- `weight_terminal_lat_error`: Preferable to set a higher value than normal lateral weight `weight_lat_error` for - stability. -- `weight_terminal_heading_error`: Preferable to set a higher value than normal heading weight `weight_heading_error` - for stability. +- `weight_terminal_lat_error`: Preferable to set a higher value than normal lateral weight `weight_lat_error` for stability. +- `weight_terminal_heading_error`: Preferable to set a higher value than normal heading weight `weight_heading_error` for stability. #### Other tips for tuning From 763a9b842affd2752edd2dd783ebddcb7f385644 Mon Sep 17 00:00:00 2001 From: Tomohito ANDO Date: Tue, 24 Oct 2023 11:52:24 +0900 Subject: [PATCH 534/547] feat(behavior_velocity_run_out): ignore momentary detection caused by false positive (#5359) * feat(behavior_velocity_run_out): ignore momentary detection caused by false positive Signed-off-by: Tomohito Ando * style(pre-commit): autofix --------- Signed-off-by: Tomohito Ando Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../README.md | 5 ++++ .../config/run_out.param.yaml | 5 ++++ .../src/manager.cpp | 7 ++++++ .../src/scene.cpp | 25 ++++++++++++++++++- .../src/scene.hpp | 6 +++-- .../src/utils.hpp | 7 ++++++ 6 files changed, 52 insertions(+), 3 deletions(-) diff --git a/planning/behavior_velocity_run_out_module/README.md b/planning/behavior_velocity_run_out_module/README.md index ee762653245c6..a36cfdf6485c6 100644 --- a/planning/behavior_velocity_run_out_module/README.md +++ b/planning/behavior_velocity_run_out_module/README.md @@ -217,6 +217,11 @@ You can choose whether to use this feature by parameter of `slow_down_limit.enab | `max_jerk` | double | [m/s^3] minimum jerk deceleration for safe brake. | | `max_acc` | double | [m/s^2] minimum accel deceleration for safe brake. | +| Parameter /ignore_momentary_detection | Type | Description | +| ------------------------------------- | ------ | ----------------------------------------------------------------- | +| `enable` | bool | [-] whether to ignore momentary detection | +| `time_threshold` | double | [sec] ignores detections that persist for less than this duration | + ### Future extensions / Unimplemented parts - Calculate obstacle's min velocity and max velocity from covariance diff --git a/planning/behavior_velocity_run_out_module/config/run_out.param.yaml b/planning/behavior_velocity_run_out_module/config/run_out.param.yaml index f9668549f2fb2..2641214ac5ad4 100644 --- a/planning/behavior_velocity_run_out_module/config/run_out.param.yaml +++ b/planning/behavior_velocity_run_out_module/config/run_out.param.yaml @@ -48,3 +48,8 @@ enable: true max_jerk: -0.7 # [m/s^3] minimum jerk deceleration for safe brake. max_acc : -2.0 # [m/s^2] minimum accel deceleration for safe brake. + + # prevent abrupt stops caused by false positives in perception + ignore_momentary_detection: + enable: true + time_threshold: 0.15 # [sec] ignores detections that persist for less than this duration diff --git a/planning/behavior_velocity_run_out_module/src/manager.cpp b/planning/behavior_velocity_run_out_module/src/manager.cpp index b262bf00cb57a..a5253f59b60f9 100644 --- a/planning/behavior_velocity_run_out_module/src/manager.cpp +++ b/planning/behavior_velocity_run_out_module/src/manager.cpp @@ -119,6 +119,13 @@ RunOutModuleManager::RunOutModuleManager(rclcpp::Node & node) p.max_acc = getOrDeclareParameter(node, ns_m + ".max_acc"); } + { + auto & p = planner_param_.ignore_momentary_detection; + const std::string ns_param = ns + ".ignore_momentary_detection"; + p.enable = getOrDeclareParameter(node, ns_param + ".enable"); + p.time_threshold = getOrDeclareParameter(node, ns_param + ".time_threshold"); + } + debug_ptr_ = std::make_shared(node); setDynamicObstacleCreator(node, debug_ptr_); } diff --git a/planning/behavior_velocity_run_out_module/src/scene.cpp b/planning/behavior_velocity_run_out_module/src/scene.cpp index b91d1d1fe1ae7..54902f52c47da 100644 --- a/planning/behavior_velocity_run_out_module/src/scene.cpp +++ b/planning/behavior_velocity_run_out_module/src/scene.cpp @@ -137,7 +137,7 @@ bool RunOutModule::modifyPathVelocity( } boost::optional RunOutModule::detectCollision( - const std::vector & dynamic_obstacles, const PathWithLaneId & path) const + const std::vector & dynamic_obstacles, const PathWithLaneId & path) { if (path.points.size() < 2) { RCLCPP_WARN_STREAM(logger_, "path doesn't have enough points."); @@ -181,6 +181,11 @@ boost::optional RunOutModule::detectCollision( continue; } + // ignore momentary obstacle detection to avoid sudden stopping by false positive + if (isMomentaryDetection()) { + return {}; + } + debug_ptr_->pushCollisionPoints(obstacle_selected->collision_points); debug_ptr_->pushNearestCollisionPoint(obstacle_selected->nearest_collision_point); @@ -188,6 +193,7 @@ boost::optional RunOutModule::detectCollision( } // no collision detected + first_detected_time_.reset(); return {}; } @@ -812,4 +818,21 @@ void RunOutModule::publishDebugValue( debug_ptr_->publishDebugValue(); } +bool RunOutModule::isMomentaryDetection() +{ + if (!planner_param_.ignore_momentary_detection.enable) { + return false; + } + + if (!first_detected_time_) { + first_detected_time_ = std::make_shared(clock_->now()); + return true; + } + + const auto now = clock_->now(); + const double elapsed_time_since_detection = (now - *first_detected_time_).seconds(); + RCLCPP_DEBUG_STREAM(logger_, "elapsed_time_since_detection: " << elapsed_time_since_detection); + + return elapsed_time_since_detection < planner_param_.ignore_momentary_detection.time_threshold; +} } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_run_out_module/src/scene.hpp b/planning/behavior_velocity_run_out_module/src/scene.hpp index 20bd8ffc799ff..925d8ea8b234c 100644 --- a/planning/behavior_velocity_run_out_module/src/scene.hpp +++ b/planning/behavior_velocity_run_out_module/src/scene.hpp @@ -62,13 +62,13 @@ class RunOutModule : public SceneModuleInterface std::unique_ptr dynamic_obstacle_creator_; std::shared_ptr debug_ptr_; std::unique_ptr state_machine_; + std::shared_ptr first_detected_time_; // Function Polygons2d createDetectionAreaPolygon(const PathWithLaneId & smoothed_path) const; boost::optional detectCollision( - const std::vector & dynamic_obstacles, - const PathWithLaneId & path_points) const; + const std::vector & dynamic_obstacles, const PathWithLaneId & path_points); float calcCollisionPositionOfVehicleSide( const geometry_msgs::msg::Point & point, const geometry_msgs::msg::Pose & base_pose) const; @@ -141,6 +141,8 @@ class RunOutModule : public SceneModuleInterface const PathWithLaneId & path, const std::vector extracted_obstacles, const boost::optional & dynamic_obstacle, const geometry_msgs::msg::Pose & current_pose) const; + + bool isMomentaryDetection(); }; } // namespace behavior_velocity_planner diff --git a/planning/behavior_velocity_run_out_module/src/utils.hpp b/planning/behavior_velocity_run_out_module/src/utils.hpp index 8fc3d48de4858..b25a8fc94bff3 100644 --- a/planning/behavior_velocity_run_out_module/src/utils.hpp +++ b/planning/behavior_velocity_run_out_module/src/utils.hpp @@ -126,6 +126,12 @@ struct DynamicObstacleParam float points_interval; // [m] }; +struct IgnoreMomentaryDetection +{ + bool enable; + double time_threshold; +}; + struct PlannerParam { CommonParam common; @@ -138,6 +144,7 @@ struct PlannerParam DynamicObstacleParam dynamic_obstacle; SlowDownLimit slow_down_limit; Smoother smoother; + IgnoreMomentaryDetection ignore_momentary_detection; }; enum class DetectionMethod { From 0f9eb8af887b1f72611b60b03fed41013556e356 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Tue, 24 Oct 2023 14:13:29 +0900 Subject: [PATCH 535/547] feat(imu_corrector): changed input topic of GyroBiasEstimator from ndt_pose to ekf_odom (#5374) Changed input topic of GyroBiasEstimator from pose to odom Signed-off-by: Shintaro Sakoda --- .../launch/gyro_bias_estimator.launch.xml | 4 ++-- sensing/imu_corrector/src/gyro_bias_estimator.cpp | 13 ++++++------- sensing/imu_corrector/src/gyro_bias_estimator.hpp | 6 ++++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml b/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml index 0d7cba9faa3a6..e16ccef446aba 100644 --- a/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml +++ b/sensing/imu_corrector/launch/gyro_bias_estimator.launch.xml @@ -1,14 +1,14 @@ - + - + diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.cpp b/sensing/imu_corrector/src/gyro_bias_estimator.cpp index 21bb51dc5f1f1..50e4a702ec949 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimator.cpp +++ b/sensing/imu_corrector/src/gyro_bias_estimator.cpp @@ -43,9 +43,9 @@ GyroBiasEstimator::GyroBiasEstimator() imu_sub_ = create_subscription( "~/input/imu_raw", rclcpp::SensorDataQoS(), [this](const Imu::ConstSharedPtr msg) { callback_imu(msg); }); - pose_sub_ = create_subscription( - "~/input/pose", rclcpp::SensorDataQoS(), - [this](const PoseWithCovarianceStamped::ConstSharedPtr msg) { callback_pose(msg); }); + odom_sub_ = create_subscription( + "~/input/odom", rclcpp::SensorDataQoS(), + [this](const Odometry::ConstSharedPtr msg) { callback_odom(msg); }); gyro_bias_pub_ = create_publisher("~/output/gyro_bias", rclcpp::SensorDataQoS()); auto timer_callback = std::bind(&GyroBiasEstimator::timer_callback, this); @@ -88,12 +88,11 @@ void GyroBiasEstimator::callback_imu(const Imu::ConstSharedPtr imu_msg_ptr) } } -void GyroBiasEstimator::callback_pose(const PoseWithCovarianceStamped::ConstSharedPtr pose_msg_ptr) +void GyroBiasEstimator::callback_odom(const Odometry::ConstSharedPtr odom_msg_ptr) { - // push pose_msg to queue geometry_msgs::msg::PoseStamped pose; - pose.header = pose_msg_ptr->header; - pose.pose = pose_msg_ptr->pose.pose; + pose.header = odom_msg_ptr->header; + pose.pose = odom_msg_ptr->pose.pose; pose_buf_.push_back(pose); } diff --git a/sensing/imu_corrector/src/gyro_bias_estimator.hpp b/sensing/imu_corrector/src/gyro_bias_estimator.hpp index 7eb06bcdcb365..821cba0b213ff 100644 --- a/sensing/imu_corrector/src/gyro_bias_estimator.hpp +++ b/sensing/imu_corrector/src/gyro_bias_estimator.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -38,6 +39,7 @@ class GyroBiasEstimator : public rclcpp::Node using PoseWithCovarianceStamped = geometry_msgs::msg::PoseWithCovarianceStamped; using Vector3Stamped = geometry_msgs::msg::Vector3Stamped; using Vector3 = geometry_msgs::msg::Vector3; + using Odometry = nav_msgs::msg::Odometry; public: GyroBiasEstimator(); @@ -45,7 +47,7 @@ class GyroBiasEstimator : public rclcpp::Node private: void update_diagnostics(diagnostic_updater::DiagnosticStatusWrapper & stat); void callback_imu(const Imu::ConstSharedPtr imu_msg_ptr); - void callback_pose(const PoseWithCovarianceStamped::ConstSharedPtr pose_msg_ptr); + void callback_odom(const Odometry::ConstSharedPtr odom_msg_ptr); void timer_callback(); static geometry_msgs::msg::Vector3 transform_vector3( @@ -55,7 +57,7 @@ class GyroBiasEstimator : public rclcpp::Node const std::string output_frame_ = "base_link"; rclcpp::Subscription::SharedPtr imu_sub_; - rclcpp::Subscription::SharedPtr pose_sub_; + rclcpp::Subscription::SharedPtr odom_sub_; rclcpp::Publisher::SharedPtr gyro_bias_pub_; rclcpp::TimerBase::SharedPtr timer_; From 91481daab67f95623894a37cec231e7d42661b1d Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 24 Oct 2023 15:15:01 +0900 Subject: [PATCH 536/547] chore(processing_time_checker): update for windows size and rich display (#5389) Signed-off-by: Takamasa Horibe --- .../scripts/processing_time_checker.py | 71 ++++++++++++++++--- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/planning/planning_debug_tools/scripts/processing_time_checker.py b/planning/planning_debug_tools/scripts/processing_time_checker.py index 3b80ce4e5588f..fa6bc57fb7ae5 100755 --- a/planning/planning_debug_tools/scripts/processing_time_checker.py +++ b/planning/planning_debug_tools/scripts/processing_time_checker.py @@ -16,6 +16,7 @@ import argparse +from collections import deque import os import sys @@ -25,11 +26,28 @@ class ProcessingTimeSubscriber(Node): - def __init__(self, max_display_time=150, display_frequency=5.0): + def __init__( + self, + max_display_time=150, + display_frequency=5.0, + window_size=1, + bar_scale=2, + display_character="|", + ): super().__init__("processing_time_subscriber") self.data_map = {} # {topic_name: data_value} + self.data_queue_map = {} # {topic_name: deque} + self.window_size = window_size self.max_display_time = max_display_time + self.bar_scale = bar_scale + self.display_character = display_character + + # Initialize a set to keep track of subscribed topics + self.subscribed_topics = set() + + # Call get_topic_list immediately and set a timer to call it regularly self.get_topic_list() + self.create_timer(1.0, self.get_topic_list) # update topic list every 1 second # Timer to print data at given frequency self.create_timer(1.0 / display_frequency, self.print_data) @@ -40,14 +58,22 @@ def get_topic_list(self): # Subscribe to topics with 'processing_time_ms' suffix for topic, types in topic_list: - if topic.endswith("processing_time_ms"): + if topic.endswith("processing_time_ms") and topic not in self.subscribed_topics: self.create_subscription( Float64Stamped, topic, lambda msg, topic=topic: self.callback(msg, topic), 1 ) self.get_logger().info(f"Subscribed to {topic} | Type: {types}") + self.subscribed_topics.add(topic) def callback(self, msg, topic): - self.data_map[topic] = msg.data + # Add the new data to the queue + if topic not in self.data_queue_map: + self.data_queue_map[topic] = deque(maxlen=self.window_size) + self.data_queue_map[topic].append(msg.data) + + # Calculate the average + avg_value = sum(self.data_queue_map[topic]) / len(self.data_queue_map[topic]) + self.data_map[topic] = avg_value def print_data(self): # Clear terminal @@ -61,9 +87,9 @@ def print_data(self): max_topic_length = max(map(len, self.data_map.keys())) # Generate the header based on the max_display_time - header_str = "topics".ljust(max_topic_length) + ":" - for i in range(0, self.max_display_time + 1, 20): - header_str += f" {i}ms".ljust(20) + header_str = f"topics (window size = {self.window_size})".ljust(max_topic_length) + ":" + for i in range(0, self.max_display_time + 1, 10): + header_str += f" {i}ms".ljust(self.bar_scale * 10) # Print the header print(header_str) @@ -74,8 +100,12 @@ def print_data(self): # Round the data to the third decimal place for display data_rounded = round(data, 3) # Calculate the number of bars to be displayed (clamped to max_display_time) - num_bars = int(min(data, self.max_display_time - 1)) + 1 - print(f"{topic.ljust(max_topic_length)}: {'|' * num_bars} ({data_rounded}ms)") + num_bars = ( + int(min(data * self.bar_scale, self.max_display_time * self.bar_scale - 1)) + 1 + ) # Convert bar_scale's reciprocal to milliseconds + print( + f"{topic.ljust(max_topic_length)}: {self.display_character * num_bars} [{data_rounded}ms]" + ) def main(args=None): @@ -84,16 +114,37 @@ def main(args=None): parser = argparse.ArgumentParser(description="Processing Time Subscriber Parameters") parser.add_argument( - "-m", "--max_display_time", type=int, default=150, help="Maximum display time in ms" + "-m", "--max_display_time", type=int, default=50, help="Maximum display time in ms" ) parser.add_argument( "-f", "--display_frequency", type=float, default=5.0, help="Display frequency in Hz" ) + parser.add_argument( + "-w", "--window_size", type=int, default=5, help="Number of samples to average" + ) + parser.add_argument( + "-bs", + "--bar_scale", + type=int, + default=2, + help="Number of bars per second. Default is 2 bars per second.", + ) + parser.add_argument( + "-dc", + "--display_character", + type=str, + default="|", + help="Character to use for the display. Default is '|'.", + ) args = parser.parse_args() rclpy.init(args=cmd_args) # Use the original command line arguments here subscriber = ProcessingTimeSubscriber( - max_display_time=args.max_display_time, display_frequency=args.display_frequency + max_display_time=args.max_display_time, + display_frequency=args.display_frequency, + window_size=args.window_size, + bar_scale=args.bar_scale, + display_character=args.display_character, ) rclpy.spin(subscriber) subscriber.destroy_node() From 189438e2729e5152d58b8ac7357c5c7202ec5b98 Mon Sep 17 00:00:00 2001 From: Takamasa Horibe Date: Tue, 24 Oct 2023 15:16:56 +0900 Subject: [PATCH 537/547] refactor(topic_state_monitor): add state log message (#5378) * refactor(topic_state_monitor): add state log message Signed-off-by: Takamasa Horibe * add debug print Signed-off-by: Takamasa Horibe --------- Signed-off-by: Takamasa Horibe --- system/default_ad_api/src/operation_mode.cpp | 18 ++++++++++++++---- system/default_ad_api/src/operation_mode.hpp | 3 ++- .../src/topic_state_monitor_core.cpp | 11 +++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/system/default_ad_api/src/operation_mode.cpp b/system/default_ad_api/src/operation_mode.cpp index a61a1b75697ab..829585ed4b8b4 100644 --- a/system/default_ad_api/src/operation_mode.cpp +++ b/system/default_ad_api/src/operation_mode.cpp @@ -46,12 +46,11 @@ OperationModeNode::OperationModeNode(const rclcpp::NodeOptions & options) for (size_t i = 0; i < module_names.size(); ++i) { const auto name = "/system/component_state_monitor/component/autonomous/" + module_names[i]; const auto qos = rclcpp::QoS(1).transient_local(); - const auto callback = [this, i](const ModeChangeAvailable::ConstSharedPtr msg) { - module_states_[i] = msg->available; + const auto callback = [this, i, module_names](const ModeChangeAvailable::ConstSharedPtr msg) { + module_states_[module_names[i]] = msg->available; }; sub_module_states_.push_back(create_subscription(name, qos, callback)); } - module_states_.resize(module_names.size()); timer_ = rclcpp::create_timer( this, get_clock(), rclcpp::Rate(5.0).period(), std::bind(&OperationModeNode::on_timer, this)); @@ -137,11 +136,22 @@ void OperationModeNode::on_state(const OperationModeState::Message::ConstSharedP void OperationModeNode::on_timer() { bool autonomous_available = true; + std::string unhealthy_components = ""; for (const auto & state : module_states_) { - autonomous_available &= state; + if (!state.second) { + unhealthy_components += unhealthy_components.empty() ? state.first : ", " + state.first; + } + autonomous_available &= state.second; } mode_available_[OperationModeState::Message::AUTONOMOUS] = autonomous_available; + if (!unhealthy_components.empty()) { + RCLCPP_INFO_THROTTLE( + get_logger(), *get_clock(), 3000, + "%s component state is unhealthy. Autonomous is not available.", + unhealthy_components.c_str()); + } + update_state(); } diff --git a/system/default_ad_api/src/operation_mode.hpp b/system/default_ad_api/src/operation_mode.hpp index 5f5699f42cab7..1830b7041b566 100644 --- a/system/default_ad_api/src/operation_mode.hpp +++ b/system/default_ad_api/src/operation_mode.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -65,7 +66,7 @@ class OperationModeNode : public rclcpp::Node Cli cli_mode_; Cli cli_control_; - std::vector module_states_; + std::unordered_map module_states_; std::vector::SharedPtr> sub_module_states_; void on_change_to_stop( diff --git a/system/topic_state_monitor/src/topic_state_monitor_core.cpp b/system/topic_state_monitor/src/topic_state_monitor_core.cpp index 326193a58dc4e..68fa3764d8ecd 100644 --- a/system/topic_state_monitor/src/topic_state_monitor_core.cpp +++ b/system/topic_state_monitor/src/topic_state_monitor_core.cpp @@ -151,6 +151,13 @@ void TopicStateMonitorNode::checkTopicStatus(diagnostic_updater::DiagnosticStatu stat.addf("topic", "%s", node_param_.topic.c_str()); } + const auto print_warn = [&](const std::string & msg) { + RCLCPP_WARN_THROTTLE(get_logger(), *get_clock(), 3000, "%s", msg.c_str()); + }; + const auto print_debug = [&](const std::string & msg) { + RCLCPP_DEBUG_THROTTLE(get_logger(), *get_clock(), 3000, "%s", msg.c_str()); + }; + // Judge level int8_t level = DiagnosticStatus::OK; if (topic_status == TopicStatus::Ok) { @@ -159,15 +166,19 @@ void TopicStateMonitorNode::checkTopicStatus(diagnostic_updater::DiagnosticStatu } else if (topic_status == TopicStatus::NotReceived) { level = DiagnosticStatus::ERROR; stat.add("status", "NotReceived"); + print_debug(node_param_.topic + " has not received."); } else if (topic_status == TopicStatus::WarnRate) { level = DiagnosticStatus::WARN; stat.add("status", "WarnRate"); + print_warn(node_param_.topic + " topic rate has dropped to the warning level."); } else if (topic_status == TopicStatus::ErrorRate) { level = DiagnosticStatus::ERROR; stat.add("status", "ErrorRate"); + print_warn(node_param_.topic + " topic rate has dropped to the error level."); } else if (topic_status == TopicStatus::Timeout) { level = DiagnosticStatus::ERROR; stat.add("status", "Timeout"); + print_warn(node_param_.topic + " topic is timeout."); } // Add key-value From cc0b202353b8ef55cb5f54f9ade446bdd7d7a242 Mon Sep 17 00:00:00 2001 From: SakodaShintaro Date: Tue, 24 Oct 2023 16:26:06 +0900 Subject: [PATCH 538/547] refactor(localization): add localization_util package (#5377) * Added localization_util Signed-off-by: Shintaro Sakoda * Fixed library Signed-off-by: Shintaro Sakoda * Separated a package tree_structured_parzen_estimator Signed-off-by: Shintaro Sakoda * style(pre-commit): autofix * Fixed library Signed-off-by: Shintaro Sakoda * Added maintainer Signed-off-by: Shintaro Sakoda --------- Signed-off-by: Shintaro Sakoda Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- localization/localization_util/CMakeLists.txt | 15 +++++++ localization/localization_util/README.md | 5 +++ .../localization_util}/matrix_type.hpp | 6 +-- .../pose_array_interpolator.hpp | 8 ++-- .../tf2_listener_module.hpp | 6 +-- .../include/localization_util}/util_func.hpp | 6 +-- localization/localization_util/package.xml | 40 +++++++++++++++++++ .../src/pose_array_interpolator.cpp | 2 +- .../src/tf2_listener_module.cpp | 2 +- .../src/util_func.cpp | 4 +- localization/ndt_scan_matcher/CMakeLists.txt | 12 ------ .../ndt_scan_matcher/map_update_module.hpp | 4 +- .../ndt_scan_matcher_core.hpp | 2 +- localization/ndt_scan_matcher/package.xml | 2 + localization/ndt_scan_matcher/src/debug.cpp | 2 +- .../src/ndt_scan_matcher_core.cpp | 8 ++-- .../CMakeLists.txt | 23 +++++++++++ .../README.md | 5 +++ .../tree_structured_parzen_estimator.hpp | 6 +-- .../package.xml | 23 +++++++++++ .../src/tree_structured_parzen_estimator.cpp | 2 +- .../test/test_tpe.cpp | 2 +- 22 files changed, 143 insertions(+), 42 deletions(-) create mode 100644 localization/localization_util/CMakeLists.txt create mode 100644 localization/localization_util/README.md rename localization/{ndt_scan_matcher/include/ndt_scan_matcher => localization_util/include/localization_util}/matrix_type.hpp (84%) rename localization/{ndt_scan_matcher/include/ndt_scan_matcher => localization_util/include/localization_util}/pose_array_interpolator.hpp (90%) rename localization/{ndt_scan_matcher/include/ndt_scan_matcher => localization_util/include/localization_util}/tf2_listener_module.hpp (88%) rename localization/{ndt_scan_matcher/include/ndt_scan_matcher => localization_util/include/localization_util}/util_func.hpp (96%) create mode 100644 localization/localization_util/package.xml rename localization/{ndt_scan_matcher => localization_util}/src/pose_array_interpolator.cpp (98%) rename localization/{ndt_scan_matcher => localization_util}/src/tf2_listener_module.cpp (97%) rename localization/{ndt_scan_matcher => localization_util}/src/util_func.cpp (99%) create mode 100644 localization/tree_structured_parzen_estimator/CMakeLists.txt create mode 100644 localization/tree_structured_parzen_estimator/README.md rename localization/{ndt_scan_matcher/include/ndt_scan_matcher => tree_structured_parzen_estimator/include/tree_structured_parzen_estimator}/tree_structured_parzen_estimator.hpp (90%) create mode 100644 localization/tree_structured_parzen_estimator/package.xml rename localization/{ndt_scan_matcher => tree_structured_parzen_estimator}/src/tree_structured_parzen_estimator.cpp (98%) rename localization/{ndt_scan_matcher => tree_structured_parzen_estimator}/test/test_tpe.cpp (96%) diff --git a/localization/localization_util/CMakeLists.txt b/localization/localization_util/CMakeLists.txt new file mode 100644 index 0000000000000..9490ffb67723b --- /dev/null +++ b/localization/localization_util/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.14) +project(localization_util) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +ament_auto_add_library(localization_util SHARED + src/util_func.cpp + src/pose_array_interpolator.cpp + src/tf2_listener_module.cpp +) + +ament_auto_package( + INSTALL_TO_SHARE +) diff --git a/localization/localization_util/README.md b/localization/localization_util/README.md new file mode 100644 index 0000000000000..a3e980464d0c6 --- /dev/null +++ b/localization/localization_util/README.md @@ -0,0 +1,5 @@ +# localization_util + +`localization_util`` is a localization utility package. + +This package does not have a node, it is just a library. diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/matrix_type.hpp b/localization/localization_util/include/localization_util/matrix_type.hpp similarity index 84% rename from localization/ndt_scan_matcher/include/ndt_scan_matcher/matrix_type.hpp rename to localization/localization_util/include/localization_util/matrix_type.hpp index 038ed4549eb2f..d9ec9b369127a 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/matrix_type.hpp +++ b/localization/localization_util/include/localization_util/matrix_type.hpp @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NDT_SCAN_MATCHER__MATRIX_TYPE_HPP_ -#define NDT_SCAN_MATCHER__MATRIX_TYPE_HPP_ +#ifndef LOCALIZATION_UTIL__MATRIX_TYPE_HPP_ +#define LOCALIZATION_UTIL__MATRIX_TYPE_HPP_ #include using Matrix6d = Eigen::Matrix; using RowMatrixXd = Eigen::Matrix; -#endif // NDT_SCAN_MATCHER__MATRIX_TYPE_HPP_ +#endif // LOCALIZATION_UTIL__MATRIX_TYPE_HPP_ diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/pose_array_interpolator.hpp b/localization/localization_util/include/localization_util/pose_array_interpolator.hpp similarity index 90% rename from localization/ndt_scan_matcher/include/ndt_scan_matcher/pose_array_interpolator.hpp rename to localization/localization_util/include/localization_util/pose_array_interpolator.hpp index f1e7dfb3f544b..998d8465733f5 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/pose_array_interpolator.hpp +++ b/localization/localization_util/include/localization_util/pose_array_interpolator.hpp @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NDT_SCAN_MATCHER__POSE_ARRAY_INTERPOLATOR_HPP_ -#define NDT_SCAN_MATCHER__POSE_ARRAY_INTERPOLATOR_HPP_ +#ifndef LOCALIZATION_UTIL__POSE_ARRAY_INTERPOLATOR_HPP_ +#define LOCALIZATION_UTIL__POSE_ARRAY_INTERPOLATOR_HPP_ -#include "ndt_scan_matcher/util_func.hpp" +#include "localization_util/util_func.hpp" #include @@ -59,4 +59,4 @@ class PoseArrayInterpolator const geometry_msgs::msg::Point & reference_point, const double distance_tolerance_m_) const; }; -#endif // NDT_SCAN_MATCHER__POSE_ARRAY_INTERPOLATOR_HPP_ +#endif // LOCALIZATION_UTIL__POSE_ARRAY_INTERPOLATOR_HPP_ diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/tf2_listener_module.hpp b/localization/localization_util/include/localization_util/tf2_listener_module.hpp similarity index 88% rename from localization/ndt_scan_matcher/include/ndt_scan_matcher/tf2_listener_module.hpp rename to localization/localization_util/include/localization_util/tf2_listener_module.hpp index 159acbd75ce3d..b332f9effe0e0 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/tf2_listener_module.hpp +++ b/localization/localization_util/include/localization_util/tf2_listener_module.hpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NDT_SCAN_MATCHER__TF2_LISTENER_MODULE_HPP_ -#define NDT_SCAN_MATCHER__TF2_LISTENER_MODULE_HPP_ +#ifndef LOCALIZATION_UTIL__TF2_LISTENER_MODULE_HPP_ +#define LOCALIZATION_UTIL__TF2_LISTENER_MODULE_HPP_ #include @@ -40,4 +40,4 @@ class Tf2ListenerModule tf2_ros::TransformListener tf2_listener_; }; -#endif // NDT_SCAN_MATCHER__TF2_LISTENER_MODULE_HPP_ +#endif // LOCALIZATION_UTIL__TF2_LISTENER_MODULE_HPP_ diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/util_func.hpp b/localization/localization_util/include/localization_util/util_func.hpp similarity index 96% rename from localization/ndt_scan_matcher/include/ndt_scan_matcher/util_func.hpp rename to localization/localization_util/include/localization_util/util_func.hpp index 5c37cef36c422..bd9a2b9305e25 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/util_func.hpp +++ b/localization/localization_util/include/localization_util/util_func.hpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NDT_SCAN_MATCHER__UTIL_FUNC_HPP_ -#define NDT_SCAN_MATCHER__UTIL_FUNC_HPP_ +#ifndef LOCALIZATION_UTIL__UTIL_FUNC_HPP_ +#define LOCALIZATION_UTIL__UTIL_FUNC_HPP_ #include #include @@ -91,4 +91,4 @@ void output_pose_with_cov_to_log( const rclcpp::Logger logger, const std::string & prefix, const geometry_msgs::msg::PoseWithCovarianceStamped & pose_with_cov); -#endif // NDT_SCAN_MATCHER__UTIL_FUNC_HPP_ +#endif // LOCALIZATION_UTIL__UTIL_FUNC_HPP_ diff --git a/localization/localization_util/package.xml b/localization/localization_util/package.xml new file mode 100644 index 0000000000000..20f9b160212b5 --- /dev/null +++ b/localization/localization_util/package.xml @@ -0,0 +1,40 @@ + + + + localization_util + 0.1.0 + The localization_util package + Yamato Ando + Masahiro Sakamoto + Shintaro Sakoda + Kento Yabuuchi + Apache License 2.0 + Yamato Ando + + ament_cmake_auto + autoware_cmake + + fmt + geometry_msgs + nav_msgs + rclcpp + sensor_msgs + std_msgs + std_srvs + tf2 + tf2_eigen + tf2_geometry_msgs + tf2_ros + tf2_sensor_msgs + tier4_autoware_utils + tier4_debug_msgs + tier4_localization_msgs + visualization_msgs + + ament_cmake_cppcheck + ament_lint_auto + + + ament_cmake + + diff --git a/localization/ndt_scan_matcher/src/pose_array_interpolator.cpp b/localization/localization_util/src/pose_array_interpolator.cpp similarity index 98% rename from localization/ndt_scan_matcher/src/pose_array_interpolator.cpp rename to localization/localization_util/src/pose_array_interpolator.cpp index f09b71523e804..ebc904fcf8d38 100644 --- a/localization/ndt_scan_matcher/src/pose_array_interpolator.cpp +++ b/localization/localization_util/src/pose_array_interpolator.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ndt_scan_matcher/pose_array_interpolator.hpp" +#include "localization_util/pose_array_interpolator.hpp" PoseArrayInterpolator::PoseArrayInterpolator( rclcpp::Node * node, const rclcpp::Time & target_ros_time, diff --git a/localization/ndt_scan_matcher/src/tf2_listener_module.cpp b/localization/localization_util/src/tf2_listener_module.cpp similarity index 97% rename from localization/ndt_scan_matcher/src/tf2_listener_module.cpp rename to localization/localization_util/src/tf2_listener_module.cpp index 364952a631f06..8a19ceec9f30d 100644 --- a/localization/ndt_scan_matcher/src/tf2_listener_module.cpp +++ b/localization/localization_util/src/tf2_listener_module.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ndt_scan_matcher/tf2_listener_module.hpp" +#include "localization_util/tf2_listener_module.hpp" #include diff --git a/localization/ndt_scan_matcher/src/util_func.cpp b/localization/localization_util/src/util_func.cpp similarity index 99% rename from localization/ndt_scan_matcher/src/util_func.cpp rename to localization/localization_util/src/util_func.cpp index 52cb7844ab241..865cc02cff293 100644 --- a/localization/ndt_scan_matcher/src/util_func.cpp +++ b/localization/localization_util/src/util_func.cpp @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ndt_scan_matcher/util_func.hpp" +#include "localization_util/util_func.hpp" -#include "ndt_scan_matcher/matrix_type.hpp" +#include "localization_util/matrix_type.hpp" static std::random_device seed_gen; diff --git a/localization/ndt_scan_matcher/CMakeLists.txt b/localization/ndt_scan_matcher/CMakeLists.txt index 955234f95ff18..79491a85a0a66 100644 --- a/localization/ndt_scan_matcher/CMakeLists.txt +++ b/localization/ndt_scan_matcher/CMakeLists.txt @@ -29,12 +29,8 @@ ament_auto_add_executable(ndt_scan_matcher src/debug.cpp src/ndt_scan_matcher_node.cpp src/ndt_scan_matcher_core.cpp - src/util_func.cpp - src/pose_array_interpolator.cpp - src/tf2_listener_module.cpp src/map_module.cpp src/map_update_module.cpp - src/tree_structured_parzen_estimator.cpp ) link_directories(${PCL_LIBRARY_DIRS}) @@ -45,14 +41,6 @@ if(BUILD_TESTING) test/test_ndt_scan_matcher_launch.py TIMEOUT "30" ) - - find_package(ament_cmake_gtest REQUIRED) - ament_add_gtest(test_tpe - test/test_tpe.cpp - src/tree_structured_parzen_estimator.cpp - ) - target_include_directories(test_tpe PRIVATE include) - target_link_libraries(test_tpe) endif() ament_auto_package( diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/map_update_module.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/map_update_module.hpp index 4f630bb8c5898..13721c03ca22e 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/map_update_module.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/map_update_module.hpp @@ -15,10 +15,10 @@ #ifndef NDT_SCAN_MATCHER__MAP_UPDATE_MODULE_HPP_ #define NDT_SCAN_MATCHER__MAP_UPDATE_MODULE_HPP_ +#include "localization_util/tf2_listener_module.hpp" +#include "localization_util/util_func.hpp" #include "ndt_scan_matcher/debug.hpp" #include "ndt_scan_matcher/particle.hpp" -#include "ndt_scan_matcher/tf2_listener_module.hpp" -#include "ndt_scan_matcher/util_func.hpp" #include #include diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp index 153418e5a8f75..e5f46b5022b51 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp +++ b/localization/ndt_scan_matcher/include/ndt_scan_matcher/ndt_scan_matcher_core.hpp @@ -17,9 +17,9 @@ #define FMT_HEADER_ONLY +#include "localization_util/tf2_listener_module.hpp" #include "ndt_scan_matcher/map_module.hpp" #include "ndt_scan_matcher/map_update_module.hpp" -#include "ndt_scan_matcher/tf2_listener_module.hpp" #include diff --git a/localization/ndt_scan_matcher/package.xml b/localization/ndt_scan_matcher/package.xml index 986179b9e02e2..a9083c4ae0ae4 100644 --- a/localization/ndt_scan_matcher/package.xml +++ b/localization/ndt_scan_matcher/package.xml @@ -19,6 +19,7 @@ fmt geometry_msgs libpcl-all-dev + localization_util nav_msgs ndt_omp pcl_conversions @@ -34,6 +35,7 @@ tier4_autoware_utils tier4_debug_msgs tier4_localization_msgs + tree_structured_parzen_estimator visualization_msgs ament_cmake_cppcheck diff --git a/localization/ndt_scan_matcher/src/debug.cpp b/localization/ndt_scan_matcher/src/debug.cpp index 9c82e06d89b80..941f682de5803 100644 --- a/localization/ndt_scan_matcher/src/debug.cpp +++ b/localization/ndt_scan_matcher/src/debug.cpp @@ -14,7 +14,7 @@ #include "ndt_scan_matcher/debug.hpp" -#include "ndt_scan_matcher/util_func.hpp" +#include "localization_util/util_func.hpp" visualization_msgs::msg::MarkerArray make_debug_markers( const builtin_interfaces::msg::Time & stamp, const std::string & map_frame_, diff --git a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp index 8e583fd3a666b..b38e3f0826184 100644 --- a/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp +++ b/localization/ndt_scan_matcher/src/ndt_scan_matcher_core.cpp @@ -14,11 +14,11 @@ #include "ndt_scan_matcher/ndt_scan_matcher_core.hpp" -#include "ndt_scan_matcher/matrix_type.hpp" +#include "localization_util/matrix_type.hpp" +#include "localization_util/pose_array_interpolator.hpp" +#include "localization_util/util_func.hpp" #include "ndt_scan_matcher/particle.hpp" -#include "ndt_scan_matcher/pose_array_interpolator.hpp" -#include "ndt_scan_matcher/tree_structured_parzen_estimator.hpp" -#include "ndt_scan_matcher/util_func.hpp" +#include "tree_structured_parzen_estimator/tree_structured_parzen_estimator.hpp" #include #include diff --git a/localization/tree_structured_parzen_estimator/CMakeLists.txt b/localization/tree_structured_parzen_estimator/CMakeLists.txt new file mode 100644 index 0000000000000..7b687a12a003c --- /dev/null +++ b/localization/tree_structured_parzen_estimator/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.14) +project(tree_structured_parzen_estimator) + +find_package(autoware_cmake REQUIRED) +autoware_package() + +ament_auto_add_library(tree_structured_parzen_estimator SHARED + src/tree_structured_parzen_estimator.cpp +) + +if(BUILD_TESTING) + find_package(ament_cmake_gtest REQUIRED) + ament_add_gtest(test_tpe + test/test_tpe.cpp + src/tree_structured_parzen_estimator.cpp + ) + target_include_directories(test_tpe PRIVATE include) + target_link_libraries(test_tpe) +endif() + +ament_auto_package( + INSTALL_TO_SHARE +) diff --git a/localization/tree_structured_parzen_estimator/README.md b/localization/tree_structured_parzen_estimator/README.md new file mode 100644 index 0000000000000..2b21e65929485 --- /dev/null +++ b/localization/tree_structured_parzen_estimator/README.md @@ -0,0 +1,5 @@ +# tree_structured_parzen_estimator + +`tree_structured_parzen_estimator`` is a package for black-box optimization. + +This package does not have a node, it is just a library. diff --git a/localization/ndt_scan_matcher/include/ndt_scan_matcher/tree_structured_parzen_estimator.hpp b/localization/tree_structured_parzen_estimator/include/tree_structured_parzen_estimator/tree_structured_parzen_estimator.hpp similarity index 90% rename from localization/ndt_scan_matcher/include/ndt_scan_matcher/tree_structured_parzen_estimator.hpp rename to localization/tree_structured_parzen_estimator/include/tree_structured_parzen_estimator/tree_structured_parzen_estimator.hpp index 3c79ab75dba48..b7b522b4e6b76 100644 --- a/localization/ndt_scan_matcher/include/ndt_scan_matcher/tree_structured_parzen_estimator.hpp +++ b/localization/tree_structured_parzen_estimator/include/tree_structured_parzen_estimator/tree_structured_parzen_estimator.hpp @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NDT_SCAN_MATCHER__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ -#define NDT_SCAN_MATCHER__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ +#ifndef TREE_STRUCTURED_PARZEN_ESTIMATOR__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ +#define TREE_STRUCTURED_PARZEN_ESTIMATOR__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ /* A implementation of tree-structured parzen estimator (TPE) @@ -77,4 +77,4 @@ class TreeStructuredParzenEstimator const Input base_stddev_; }; -#endif // NDT_SCAN_MATCHER__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ +#endif // TREE_STRUCTURED_PARZEN_ESTIMATOR__TREE_STRUCTURED_PARZEN_ESTIMATOR_HPP_ diff --git a/localization/tree_structured_parzen_estimator/package.xml b/localization/tree_structured_parzen_estimator/package.xml new file mode 100644 index 0000000000000..b699d5c69e610 --- /dev/null +++ b/localization/tree_structured_parzen_estimator/package.xml @@ -0,0 +1,23 @@ + + + + tree_structured_parzen_estimator + 0.1.0 + The tree_structured_parzen_estimator package + Yamato Ando + Masahiro Sakamoto + Shintaro Sakoda + Kento Yabuuchi + Apache License 2.0 + Shintaro Sakoda + + ament_cmake_auto + autoware_cmake + + ament_cmake_cppcheck + ament_lint_auto + + + ament_cmake + + diff --git a/localization/ndt_scan_matcher/src/tree_structured_parzen_estimator.cpp b/localization/tree_structured_parzen_estimator/src/tree_structured_parzen_estimator.cpp similarity index 98% rename from localization/ndt_scan_matcher/src/tree_structured_parzen_estimator.cpp rename to localization/tree_structured_parzen_estimator/src/tree_structured_parzen_estimator.cpp index b92c8a075252c..99c70a844f331 100644 --- a/localization/ndt_scan_matcher/src/tree_structured_parzen_estimator.cpp +++ b/localization/tree_structured_parzen_estimator/src/tree_structured_parzen_estimator.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ndt_scan_matcher/tree_structured_parzen_estimator.hpp" +#include "tree_structured_parzen_estimator/tree_structured_parzen_estimator.hpp" #include #include diff --git a/localization/ndt_scan_matcher/test/test_tpe.cpp b/localization/tree_structured_parzen_estimator/test/test_tpe.cpp similarity index 96% rename from localization/ndt_scan_matcher/test/test_tpe.cpp rename to localization/tree_structured_parzen_estimator/test/test_tpe.cpp index fb7190f4a1c74..32eb66e70fb16 100644 --- a/localization/ndt_scan_matcher/test/test_tpe.cpp +++ b/localization/tree_structured_parzen_estimator/test/test_tpe.cpp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "ndt_scan_matcher/tree_structured_parzen_estimator.hpp" +#include "tree_structured_parzen_estimator/tree_structured_parzen_estimator.hpp" #include From db2768626a2e60e859934e0155b42088ef592be5 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 24 Oct 2023 17:03:54 +0900 Subject: [PATCH 539/547] fix(intersection): wrong argument order (#5386) Signed-off-by: Takayuki Murooka --- planning/behavior_velocity_intersection_module/src/util.cpp | 4 ++-- planning/behavior_velocity_intersection_module/src/util.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 27310f2129937..42412bbccd424 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -269,8 +269,8 @@ std::optional generateIntersectionStopLines( const lanelet::CompoundPolygon3d & first_detection_area, const std::shared_ptr & planner_data, const InterpolatedPathInfo & interpolated_path_info, const bool use_stuck_stopline, - const double stop_line_margin, const double peeking_offset, const double max_accel, - const double max_jerk, const double delay_response_time, + const double stop_line_margin, const double max_accel, const double max_jerk, + const double delay_response_time, const double peeking_offset, autoware_auto_planning_msgs::msg::PathWithLaneId * original_path) { const auto & path_ip = interpolated_path_info.path; diff --git a/planning/behavior_velocity_intersection_module/src/util.hpp b/planning/behavior_velocity_intersection_module/src/util.hpp index 125d3bdfb570a..5faacd4325b06 100644 --- a/planning/behavior_velocity_intersection_module/src/util.hpp +++ b/planning/behavior_velocity_intersection_module/src/util.hpp @@ -70,8 +70,8 @@ std::optional generateIntersectionStopLines( const lanelet::CompoundPolygon3d & first_detection_area, const std::shared_ptr & planner_data, const InterpolatedPathInfo & interpolated_path_info, const bool use_stuck_stopline, - const double stop_line_margin, const double peeking_offset, const double max_accel, - const double max_jerk, const double delay_response_time, + const double stop_line_margin, const double max_accel, const double max_jerk, + const double delay_response_time, const double peeking_offset, autoware_auto_planning_msgs::msg::PathWithLaneId * original_path); std::optional getFirstPointInsidePolygon( From f400b912c1d0570a94b5fcf969f32fca2739b33a Mon Sep 17 00:00:00 2001 From: Satoshi OTA <44889564+satoshi-ota@users.noreply.github.com> Date: Tue, 24 Oct 2023 21:39:33 +0900 Subject: [PATCH 540/547] fix(utils): remove sharp angle bound (#5384) * fix(utils): remove sharp angle bound Signed-off-by: satoshi-ota * fix(utils): guard invalid access Signed-off-by: satoshi-ota * chore(utils): add comment Signed-off-by: satoshi-ota --------- Signed-off-by: satoshi-ota --- .../behavior_path_planner/src/utils/utils.cpp | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 6927327e24ef4..9b10dfc32457f 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -2011,6 +2011,11 @@ void makeBoundLongitudinallyMonotonic( std::vector ret = bound; auto itr = std::next(ret.begin()); while (std::next(itr) != ret.end()) { + if (itr == ret.begin()) { + itr++; + continue; + } + const auto p1 = *std::prev(itr); const auto p2 = *itr; const auto p3 = *std::next(itr); @@ -2024,11 +2029,20 @@ void makeBoundLongitudinallyMonotonic( const auto dist_3to2 = tier4_autoware_utils::calcDistance3d(p3, p2); constexpr double epsilon = 1e-3; + + // Remove overlapped point. + if (dist_1to2 < epsilon || dist_3to2 < epsilon) { + itr = std::prev(ret.erase(itr)); + continue; + } + + // If the angle between the points is sharper than 45 degrees, remove the middle point. if (std::cos(M_PI_4) < product / dist_1to2 / dist_3to2 + epsilon) { - itr = ret.erase(itr); - } else { - itr++; + itr = std::prev(ret.erase(itr)); + continue; } + + itr++; } return ret; From fe650db2ce9dea71c9767cd3f0619c345eaf46bf Mon Sep 17 00:00:00 2001 From: Mamoru Sobue Date: Wed, 25 Oct 2023 10:38:52 +0900 Subject: [PATCH 541/547] feat(intersection): check path margin for overshoot vehicles on red light (#5394) --- .../config/intersection.param.yaml | 2 + .../src/debug.cpp | 6 +++ .../src/manager.cpp | 3 ++ .../src/scene_intersection.cpp | 51 ++++++++++++++++++- .../src/scene_intersection.hpp | 4 ++ .../src/util.cpp | 11 +++- .../src/util_type.hpp | 1 + 7 files changed, 74 insertions(+), 4 deletions(-) diff --git a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml index fde55dc7a6c55..82a5f65622d0b 100644 --- a/planning/behavior_velocity_intersection_module/config/intersection.param.yaml +++ b/planning/behavior_velocity_intersection_module/config/intersection.param.yaml @@ -56,6 +56,8 @@ object_dist_to_stopline: 10.0 # [m] ignore_on_amber_traffic_light: object_expected_deceleration: 2.0 # [m/ss] + ignore_on_red_traffic_light: + object_margin_to_path: 2.0 occlusion: enable: false diff --git a/planning/behavior_velocity_intersection_module/src/debug.cpp b/planning/behavior_velocity_intersection_module/src/debug.cpp index f472c4bf51e31..8d9beb34d05ee 100644 --- a/planning/behavior_velocity_intersection_module/src/debug.cpp +++ b/planning/behavior_velocity_intersection_module/src/debug.cpp @@ -230,6 +230,12 @@ visualization_msgs::msg::MarkerArray IntersectionModule::createDebugMarkerArray( debug_data_.amber_ignore_targets, "amber_ignore_targets", module_id_, now, 0.0, 1.0, 0.0), &debug_marker_array, now); + appendMarkerArray( + debug::createObjectsMarkerArray( + debug_data_.red_overshoot_ignore_targets, "red_overshoot_ignore_targets", module_id_, now, + 0.0, 1.0, 0.0), + &debug_marker_array, now); + appendMarkerArray( debug::createObjectsMarkerArray( debug_data_.stuck_targets, "stuck_targets", module_id_, now, 0.99, 0.99, 0.2), diff --git a/planning/behavior_velocity_intersection_module/src/manager.cpp b/planning/behavior_velocity_intersection_module/src/manager.cpp index a65e06eedcdf0..3c0d7fa0b4f1c 100644 --- a/planning/behavior_velocity_intersection_module/src/manager.cpp +++ b/planning/behavior_velocity_intersection_module/src/manager.cpp @@ -139,6 +139,9 @@ IntersectionModuleManager::IntersectionModuleManager(rclcpp::Node & node) ip.collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration = getOrDeclareParameter( node, ns + ".collision_detection.ignore_on_amber_traffic_light.object_expected_deceleration"); + ip.collision_detection.ignore_on_red_traffic_light.object_margin_to_path = + getOrDeclareParameter( + node, ns + ".collision_detection.ignore_on_red_traffic_light.object_margin_to_path"); ip.occlusion.enable = getOrDeclareParameter(node, ns + ".occlusion.enable"); ip.occlusion.occlusion_attention_area_length = diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp index 4def152567b32..53a99a1ee6b4d 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -1519,18 +1520,64 @@ bool IntersectionModule::checkCollision( .object_expected_deceleration)); return dist_to_stop_line > braking_distance; }; - + const auto isTolerableOvershoot = [&](const util::TargetObject & target_object) { + if ( + !target_object.attention_lanelet || !target_object.dist_to_stop_line || + !target_object.stop_line) { + return false; + } + const double dist_to_stop_line = target_object.dist_to_stop_line.value(); + const double v = target_object.object.kinematics.initial_twist_with_covariance.twist.linear.x; + const double braking_distance = + v * v / + (2.0 * std::fabs(planner_param_.collision_detection.ignore_on_amber_traffic_light + .object_expected_deceleration)); + if (dist_to_stop_line > braking_distance) { + return false; + } + const auto stopline_front = target_object.stop_line.value().front(); + const auto stopline_back = target_object.stop_line.value().back(); + tier4_autoware_utils::LineString2d object_line; + object_line.emplace_back( + (stopline_front.x() + stopline_back.x()) / 2.0, + (stopline_front.y() + stopline_back.y()) / 2.0); + const auto stopline_mid = object_line.front(); + const auto endpoint = target_object.attention_lanelet.value().centerline().back(); + object_line.emplace_back(endpoint.x(), endpoint.y()); + std::vector intersections; + bg::intersection(object_line, ego_lane.centerline2d().basicLineString(), intersections); + if (intersections.empty()) { + return false; + } + const auto collision_point = intersections.front(); + // distance from object expected stop position to collision point + const double stopline_to_object = -1.0 * dist_to_stop_line + braking_distance; + const double stopline_to_collision = + std::hypot(collision_point.x() - stopline_mid.x(), collision_point.y() - stopline_mid.y()); + const double object2collision = stopline_to_collision - stopline_to_object; + const double margin = + planner_param_.collision_detection.ignore_on_red_traffic_light.object_margin_to_path; + return (object2collision > margin) || (object2collision < 0); + }; // check collision between predicted_path and ego_area bool collision_detected = false; for (const auto & target_object : target_objects->all_attention_objects) { const auto & object = target_object.object; // If the vehicle is expected to stop before their stopline, ignore + const bool expected_to_stop_before_stopline = expectedToStopBeforeStopLine(target_object); if ( traffic_prioritized_level == util::TrafficPrioritizedLevel::PARTIALLY_PRIORITIZED && - expectedToStopBeforeStopLine(target_object)) { + expected_to_stop_before_stopline) { debug_data_.amber_ignore_targets.objects.push_back(object); continue; } + const bool is_tolerable_overshoot = isTolerableOvershoot(target_object); + if ( + traffic_prioritized_level == util::TrafficPrioritizedLevel::FULLY_PRIORITIZED && + !expected_to_stop_before_stopline && is_tolerable_overshoot) { + debug_data_.red_overshoot_ignore_targets.objects.push_back(object); + continue; + } for (const auto & predicted_path : object.kinematics.predicted_paths) { if ( predicted_path.confidence < diff --git a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp index 764f5bd7fe058..93fec171ec0d2 100644 --- a/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp +++ b/planning/behavior_velocity_intersection_module/src/scene_intersection.hpp @@ -118,6 +118,10 @@ class IntersectionModule : public SceneModuleInterface { double object_expected_deceleration; } ignore_on_amber_traffic_light; + struct IgnoreOnRedTrafficLight + { + double object_margin_to_path; + } ignore_on_red_traffic_light; } collision_detection; struct Occlusion { diff --git a/planning/behavior_velocity_intersection_module/src/util.cpp b/planning/behavior_velocity_intersection_module/src/util.cpp index 42412bbccd424..25eaaf4d8cb9d 100644 --- a/planning/behavior_velocity_intersection_module/src/util.cpp +++ b/planning/behavior_velocity_intersection_module/src/util.cpp @@ -841,9 +841,16 @@ IntersectionLanelets getObjectiveLanelets( result.attention_stop_lines_.push_back(stop_line); } result.attention_non_preceding_ = std::move(detection_lanelets); - // TODO(Mamoru Sobue): find stop lines for attention_non_preceding_ if needed for (unsigned i = 0; i < result.attention_non_preceding_.size(); ++i) { - result.attention_non_preceding_stop_lines_.push_back(std::nullopt); + std::optional stop_line = std::nullopt; + const auto & ll = result.attention_non_preceding_.at(i); + const auto traffic_lights = ll.regulatoryElementsAs(); + for (const auto & traffic_light : traffic_lights) { + const auto stop_line_opt = traffic_light->stopLine(); + if (!stop_line_opt) continue; + stop_line = stop_line_opt.get(); + } + result.attention_non_preceding_stop_lines_.push_back(stop_line); } result.conflicting_ = std::move(conflicting_ex_ego_lanelets); result.adjacent_ = planning_utils::getConstLaneletsFromIds(lanelet_map_ptr, associative_ids); diff --git a/planning/behavior_velocity_intersection_module/src/util_type.hpp b/planning/behavior_velocity_intersection_module/src/util_type.hpp index fdcf05a97a7a9..73e60aea6471a 100644 --- a/planning/behavior_velocity_intersection_module/src/util_type.hpp +++ b/planning/behavior_velocity_intersection_module/src/util_type.hpp @@ -49,6 +49,7 @@ struct DebugData std::vector candidate_collision_object_polygons; autoware_auto_perception_msgs::msg::PredictedObjects conflicting_targets; autoware_auto_perception_msgs::msg::PredictedObjects amber_ignore_targets; + autoware_auto_perception_msgs::msg::PredictedObjects red_overshoot_ignore_targets; autoware_auto_perception_msgs::msg::PredictedObjects stuck_targets; autoware_auto_perception_msgs::msg::PredictedObjects yield_stuck_targets; std::vector occlusion_polygons; From cb0279964cb84db290fca6528a97c00d9eb188a7 Mon Sep 17 00:00:00 2001 From: Yuki TAKAGI <141538661+yuki-takagi-66@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:28:24 +0900 Subject: [PATCH 542/547] fix(goal_planner): change debug polygons to contain the nominal margin (#5365) * fixed so that the visualize polygons contain the nominal margin, with some refactoring --------- Signed-off-by: Yuki Takagi Co-authored-by: Kyoichi Sugahara --- .../path_safety_checker/safety_check.hpp | 14 ++---- .../goal_planner/goal_planner_module.cpp | 45 ++++++++++++++----- .../path_safety_checker/safety_check.cpp | 44 +++--------------- 3 files changed, 42 insertions(+), 61 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp index 8409116236169..b8bd1629d5f3f 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/utils/path_safety_checker/safety_check.hpp @@ -107,10 +107,6 @@ bool checkCollision( const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, const double hysteresis_factor, CollisionCheckDebug & debug); -std::vector generatePolygonsWithStoppingAndInertialMargin( - const PathWithLaneId & ego_path, const double base_to_front, const double base_to_rear, - const double width, const double maximum_deceleration, const double max_extra_stopping_margin); - /** * @brief Iterate the points in the ego and target's predicted path and * perform safety check for each of the iterated points. @@ -133,13 +129,9 @@ std::vector getCollidedPolygons( const BehaviorPathPlannerParameters & common_parameters, const RSSparams & rss_parameters, const double hysteresis_factor, CollisionCheckDebug & debug); -/** - * @brief Check collision between ego polygons with margin and objects. - * @return Has collision or not - */ -bool checkCollisionWithMargin( - const std::vector & ego_polygons, const PredictedObjects & dynamic_objects, - const double collision_check_margin); +bool checkPolygonsIntersects( + const std::vector & polys_1, const std::vector & polys_2); + } // namespace behavior_path_planner::utils::path_safety_checker #endif // BEHAVIOR_PATH_PLANNER__UTILS__PATH_SAFETY_CHECKER__SAFETY_CHECK_HPP_ diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 95ac47494d016..46b358948863b 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -22,6 +22,7 @@ #include "behavior_path_planner/utils/path_utils.hpp" #include "behavior_path_planner/utils/start_goal_planner_common/utils.hpp" #include "behavior_path_planner/utils/utils.hpp" +#include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" #include "tier4_autoware_utils/math/unit_conversion.hpp" #include @@ -1314,7 +1315,6 @@ bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const return true; } } - if (!parameters_->use_object_recognition) { return false; } @@ -1328,20 +1328,41 @@ bool GoalPlannerModule::checkCollision(const PathWithLaneId & path) const utils::path_safety_checker::isPolygonOverlapLanelet); const auto pull_over_lane_stop_objects = utils::path_safety_checker::filterObjectsByVelocity( pull_over_lane_objects, parameters_->th_moving_object_velocity); - const auto common_parameters = planner_data_->parameters; - const double base_link2front = common_parameters.base_link2front; - const double base_link2rear = common_parameters.base_link2rear; - const double vehicle_width = common_parameters.vehicle_width; - - const auto ego_polygons_expanded = - utils::path_safety_checker::generatePolygonsWithStoppingAndInertialMargin( - path, base_link2front, base_link2rear, vehicle_width, parameters_->maximum_deceleration, + std::vector obj_polygons; + for (const auto & object : pull_over_lane_stop_objects.objects) { + obj_polygons.push_back(tier4_autoware_utils::toPolygon2d(object)); + } + + std::vector ego_polygons_expanded; + const auto curvatures = motion_utils::calcCurvature(path.points); + for (size_t i = 0; i < path.points.size(); ++i) { + const auto p = path.points.at(i); + + const double extra_stopping_margin = std::min( + std::pow(p.point.longitudinal_velocity_mps, 2) * 0.5 / parameters_->maximum_deceleration, parameters_->object_recognition_collision_check_max_extra_stopping_margin); + + double extra_lateral_margin = (-1) * curvatures.at(i) * p.point.longitudinal_velocity_mps * + std::abs(p.point.longitudinal_velocity_mps); + extra_lateral_margin = + std::clamp(extra_lateral_margin, -extra_stopping_margin, extra_stopping_margin); + + const auto lateral_offset_pose = + tier4_autoware_utils::calcOffsetPose(p.point.pose, 0.0, extra_lateral_margin / 2.0, 0.0); + const auto ego_polygon = tier4_autoware_utils::toFootprint( + lateral_offset_pose, + planner_data_->parameters.base_link2front + + parameters_->object_recognition_collision_check_margin + extra_stopping_margin, + planner_data_->parameters.base_link2rear + + parameters_->object_recognition_collision_check_margin, + planner_data_->parameters.vehicle_width + + parameters_->object_recognition_collision_check_margin * 2.0 + + std::abs(extra_lateral_margin)); + ego_polygons_expanded.push_back(ego_polygon); + } debug_data_.ego_polygons_expanded = ego_polygons_expanded; - return utils::path_safety_checker::checkCollisionWithMargin( - ego_polygons_expanded, pull_over_lane_stop_objects, - parameters_->object_recognition_collision_check_margin); + return utils::path_safety_checker::checkPolygonsIntersects(ego_polygons_expanded, obj_polygons); } bool GoalPlannerModule::hasEnoughDistance(const PullOverPath & pull_over_path) const diff --git a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp index 8ebc144a34584..40aa184105edb 100644 --- a/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp +++ b/planning/behavior_path_planner/src/utils/path_safety_checker/safety_check.cpp @@ -18,7 +18,7 @@ #include "motion_utils/trajectory/trajectory.hpp" #include "tier4_autoware_utils/geometry/boost_polygon_utils.hpp" -#include +#include #include #include @@ -380,44 +380,12 @@ std::vector getCollidedPolygons( return collided_polygons; } -std::vector generatePolygonsWithStoppingAndInertialMargin( - const PathWithLaneId & ego_path, const double base_to_front, const double base_to_rear, - const double width, const double maximum_deceleration, const double max_extra_stopping_margin) +bool checkPolygonsIntersects( + const std::vector & polys_1, const std::vector & polys_2) { - std::vector polygons; - const auto curvatures = motion_utils::calcCurvature(ego_path.points); - - for (size_t i = 0; i < ego_path.points.size(); ++i) { - const auto p = ego_path.points.at(i); - - const double extra_stopping_margin = std::min( - std::pow(p.point.longitudinal_velocity_mps, 2) * 0.5 / maximum_deceleration, - max_extra_stopping_margin); - - double extra_lateral_margin = (-1) * curvatures[i] * p.point.longitudinal_velocity_mps * - std::abs(p.point.longitudinal_velocity_mps); - extra_lateral_margin = - std::clamp(extra_lateral_margin, -extra_stopping_margin, extra_stopping_margin); - - const auto lateral_offset_pose = - tier4_autoware_utils::calcOffsetPose(p.point.pose, 0.0, extra_lateral_margin / 2.0, 0.0); - const auto ego_polygon = tier4_autoware_utils::toFootprint( - lateral_offset_pose, base_to_front + extra_stopping_margin, base_to_rear, - width + std::abs(extra_lateral_margin)); - polygons.push_back(ego_polygon); - } - return polygons; -} - -bool checkCollisionWithMargin( - const std::vector & ego_polygons, const PredictedObjects & dynamic_objects, - const double collision_check_margin) -{ - for (const auto & ego_polygon : ego_polygons) { - for (const auto & object : dynamic_objects.objects) { - const auto obj_polygon = tier4_autoware_utils::toPolygon2d(object); - const double distance = boost::geometry::distance(obj_polygon, ego_polygon); - if (distance < collision_check_margin) { + for (const auto & poly_1 : polys_1) { + for (const auto & poly_2 : polys_2) { + if (boost::geometry::intersects(poly_1, poly_2)) { return true; } } From 92f78a476d47bf0811cbb69315fd403be574224f Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 25 Oct 2023 11:39:02 +0900 Subject: [PATCH 543/547] fix(goal_planner): revert "refactor(goal_planner): rebuild state transition (#5371)" (#5399) This reverts commit 3d2a7f6038627ca52a4f0c2af6f5a8d56bc6962b. --- .../goal_planner/goal_planner_module.hpp | 19 +++--- .../goal_planner/goal_planner_module.cpp | 59 +++++++++++-------- 2 files changed, 46 insertions(+), 32 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp index 21372ed6e2c2f..343302a022a69 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/goal_planner/goal_planner_module.hpp @@ -217,28 +217,31 @@ class GoalPlannerModule : public SceneModuleInterface } } - CandidateOutput planCandidate() const override; - BehaviorModuleOutput plan() override; - BehaviorModuleOutput planWaitingApproval() override; + // TODO(someone): remove this, and use base class function + [[deprecated]] BehaviorModuleOutput run() override; bool isExecutionRequested() const override; bool isExecutionReady() const override; + // TODO(someone): remove this, and use base class function + [[deprecated]] ModuleStatus updateState() override; + BehaviorModuleOutput plan() override; + BehaviorModuleOutput planWaitingApproval() override; + void processOnEntry() override; void processOnExit() override; - void updateData() override; void setParameters(const std::shared_ptr & parameters); void acceptVisitor( [[maybe_unused]] const std::shared_ptr & visitor) const override { } + // not used, but need to override + CandidateOutput planCandidate() const override { return CandidateOutput{}; }; + private: - // The start_planner activates when it receives a new route, - // so there is no need to terminate the goal planner. - // If terminating it, it may switch to lane following and could generate an inappropriate path. bool canTransitSuccessState() override { return false; } bool canTransitFailureState() override { return false; } - bool canTransitIdleToRunningState() override { return true; } + bool canTransitIdleToRunningState() override { return false; } mutable StartGoalPlannerData goal_planner_data_; diff --git a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp index 46b358948863b..87417bfe6a42d 100644 --- a/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/goal_planner/goal_planner_module.cpp @@ -114,13 +114,6 @@ GoalPlannerModule::GoalPlannerModule( freespace_parking_timer_cb_group_); } - // Initialize safety checker - if (parameters_->safety_check_params.enable_safety_check) { - initializeSafetyCheckParameters(); - utils::start_goal_planner_common::initializeCollisionCheckDebugMap( - goal_planner_data_.collision_check); - } - status_.reset(); } @@ -227,18 +220,16 @@ void GoalPlannerModule::onFreespaceParkingTimer() } } -void GoalPlannerModule::updateData() +BehaviorModuleOutput GoalPlannerModule::run() { - // Initialize Occupancy Grid Map - // This operation requires waiting for `planner_data_`, hence it is executed here instead of in - // the constructor. Ideally, this operation should only need to be performed once. - if ( - parameters_->use_occupancy_grid_for_goal_search || - parameters_->use_occupancy_grid_for_path_collision_check) { - initializeOccupancyGridMap(); + current_state_ = ModuleStatus::RUNNING; + updateOccupancyGrid(); + + if (!isActivated()) { + return planWaitingApproval(); } - updateOccupancyGrid(); + return plan(); } void GoalPlannerModule::initializeOccupancyGridMap() @@ -265,6 +256,22 @@ void GoalPlannerModule::initializeSafetyCheckParameters() objects_filtering_params_, parameters_); } +void GoalPlannerModule::processOnEntry() +{ + // Initialize occupancy grid map + if ( + parameters_->use_occupancy_grid_for_goal_search || + parameters_->use_occupancy_grid_for_path_collision_check) { + initializeOccupancyGridMap(); + } + // Initialize safety checker + if (parameters_->safety_check_params.enable_safety_check) { + initializeSafetyCheckParameters(); + utils::start_goal_planner_common::initializeCollisionCheckDebugMap( + goal_planner_data_.collision_check); + } +} + void GoalPlannerModule::processOnExit() { resetPathCandidate(); @@ -431,6 +438,13 @@ Pose GoalPlannerModule::calcRefinedGoal(const Pose & goal_pose) const return refined_goal_pose; } +ModuleStatus GoalPlannerModule::updateState() +{ + // start_planner module will be run when setting new goal, so not need finishing pull_over module. + // Finishing it causes wrong lane_following path generation. + return current_state_; +} + bool GoalPlannerModule::planFreespacePath() { goal_searcher_->setPlannerData(planner_data_); @@ -884,12 +898,6 @@ void GoalPlannerModule::decideVelocity() status_.set_has_decided_velocity(true); } -CandidateOutput GoalPlannerModule::planCandidate() const -{ - return CandidateOutput( - status_.get_pull_over_path() ? status_.get_pull_over_path()->getFullPath() : PathWithLaneId()); -} - BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() { constexpr double path_update_duration = 1.0; @@ -928,7 +936,7 @@ BehaviorModuleOutput GoalPlannerModule::planWithGoalModification() // set output and status BehaviorModuleOutput output{}; setOutput(output); - path_candidate_ = std::make_shared(planCandidate().path_candidate); + path_candidate_ = std::make_shared(status_.get_pull_over_path()->getFullPath()); path_reference_ = getPreviousModuleOutput().reference_path; // return to lane parking if it is possible @@ -981,7 +989,10 @@ BehaviorModuleOutput GoalPlannerModule::planWaitingApprovalWithGoalModification( out.modified_goal = plan().modified_goal; // update status_ out.path = std::make_shared(generateStopPath()); out.reference_path = getPreviousModuleOutput().reference_path; - path_candidate_ = std::make_shared(planCandidate().path_candidate); + path_candidate_ = + status_.get_is_safe_static_objects() + ? std::make_shared(status_.get_pull_over_path()->getFullPath()) + : out.path; path_reference_ = getPreviousModuleOutput().reference_path; const auto distance_to_path_change = calcDistanceToPathChange(); From ce3db689c924f192350c23f3e35ed8f3acc52e5a Mon Sep 17 00:00:00 2001 From: Kosuke Takeuchi Date: Wed, 25 Oct 2023 11:39:12 +0900 Subject: [PATCH 544/547] fix(lane_change): fix terminal stop distance (#5392) * fix(lane_change): fix terminal stop distance Signed-off-by: kosuke55 * make current lanes include path front id Signed-off-by: kosuke55 * fixup! make current lanes include path front id --------- Signed-off-by: kosuke55 --- .../behavior_path_planner/src/utils/utils.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/planning/behavior_path_planner/src/utils/utils.cpp b/planning/behavior_path_planner/src/utils/utils.cpp index 9b10dfc32457f..35dd7675ae3fc 100644 --- a/planning/behavior_path_planner/src/utils/utils.cpp +++ b/planning/behavior_path_planner/src/utils/utils.cpp @@ -3081,9 +3081,24 @@ lanelet::ConstLanelets getCurrentLanesFromPath( lanelet::ConstLanelet current_lane; lanelet::utils::query::getClosestLanelet(reference_lanes, current_pose, ¤t_lane); - - return route_handler->getLaneletSequence( + auto current_lanes = route_handler->getLaneletSequence( current_lane, current_pose, p.backward_path_length, p.forward_path_length); + + // Extend the 'current_lanes' with previous lanes until it contains 'front_lane_ids'. + const auto front_lane_ids = path.points.front().lane_ids; + auto have_front_lanes = [front_lane_ids](const auto & lanes) { + return std::any_of(lanes.begin(), lanes.end(), [&](const auto & lane) { + return std::find(front_lane_ids.begin(), front_lane_ids.end(), lane.id()) != + front_lane_ids.end(); + }); + }; + while (!have_front_lanes(current_lanes)) { + const auto extended_lanes = extendPrevLane(route_handler, current_lanes); + if (extended_lanes.size() == current_lanes.size()) break; + current_lanes = extended_lanes; + } + + return current_lanes; } lanelet::ConstLanelets extendNextLane( From aabbe102ed62f2dd5b8994f171b9bad675b5fc0b Mon Sep 17 00:00:00 2001 From: Kyoichi Sugahara Date: Wed, 25 Oct 2023 11:54:15 +0900 Subject: [PATCH 545/547] refactor(behavior_path_planner): change start planner's variable name (#5390) * change variable name Signed-off-by: kyoichi-sugahara * change variable name to driving_forward Signed-off-by: kyoichi-sugahara * fix typo Signed-off-by: kyoichi-sugahara --------- Signed-off-by: kyoichi-sugahara --- .../start_planner/start_planner_module.hpp | 7 +-- .../scene_module/start_planner/manager.cpp | 4 +- .../start_planner/start_planner_module.cpp | 60 +++++++++---------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp index 7ab404d99bb12..8124f8f993906 100644 --- a/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp +++ b/planning/behavior_path_planner/include/behavior_path_planner/scene_module/start_planner/start_planner_module.hpp @@ -59,10 +59,9 @@ struct PullOutStatus PlannerType planner_type{PlannerType::NONE}; PathWithLaneId backward_path{}; lanelet::ConstLanelets pull_out_lanes{}; - bool is_safe_static_objects{false}; // current path is safe against static objects + bool found_pull_out_path{false}; // current path is safe against static objects bool is_safe_dynamic_objects{false}; // current path is safe against dynamic objects - bool back_finished{false}; // if backward driving is not required, this is also set to true - // todo: rename to clear variable name. + bool driving_forward{false}; // if ego is driving on backward path, this is set to false bool backward_driving_complete{ false}; // after backward driving is complete, this is set to true (warning: this is set to // false at next cycle after backward driving is complete) @@ -120,7 +119,7 @@ class StartPlannerModule : public SceneModuleInterface } // Condition to disable simultaneous execution - bool isBackFinished() const { return status_.back_finished; } + bool isDrivingForward() const { return status_.driving_forward; } bool isFreespacePlanning() const { return status_.planner_type == PlannerType::FREESPACE; } private: diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp index 30810bda252c3..46db10417d19e 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/manager.cpp @@ -320,7 +320,7 @@ bool StartPlannerModuleManager::isSimultaneousExecutableAsApprovedModule() const const auto start_planner_ptr = std::dynamic_pointer_cast(observer.lock()); // Currently simultaneous execution with other modules is not supported while backward driving - if (!start_planner_ptr->isBackFinished()) { + if (!start_planner_ptr->isDrivingForward()) { return false; } @@ -349,7 +349,7 @@ bool StartPlannerModuleManager::isSimultaneousExecutableAsCandidateModule() cons const auto start_planner_ptr = std::dynamic_pointer_cast(observer.lock()); // Currently simultaneous execution with other modules is not supported while backward driving - if (!start_planner_ptr->isBackFinished()) { + if (start_planner_ptr->isDrivingForward()) { return false; } diff --git a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp index 62f3dbfe191ac..8228542141c77 100644 --- a/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp +++ b/planning/behavior_path_planner/src/scene_module/start_planner/start_planner_module.cpp @@ -135,8 +135,8 @@ void StartPlannerModule::updateData() if (has_received_new_route) { status_ = PullOutStatus(); } - // check safety status after back finished - if (parameters_->safety_check_params.enable_safety_check && status_.back_finished) { + // check safety status when driving forward + if (parameters_->safety_check_params.enable_safety_check && status_.driving_forward) { status_.is_safe_dynamic_objects = isSafePath(); } else { status_.is_safe_dynamic_objects = true; @@ -184,15 +184,15 @@ bool StartPlannerModule::isExecutionRequested() const bool StartPlannerModule::isExecutionReady() const { - // when is_safe_static_objects is false,the path is not generated and approval shouldn't be + // when found_pull_out_path is false,the path is not generated and approval shouldn't be // allowed - if (!status_.is_safe_static_objects) { - RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against static objects"); + if (!status_.found_pull_out_path) { + RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Pull over path is not found"); return false; } if ( - parameters_->safety_check_params.enable_safety_check && status_.back_finished && + parameters_->safety_check_params.enable_safety_check && status_.driving_forward && isWaitingApproval()) { if (!isSafePath()) { RCLCPP_ERROR_THROTTLE(getLogger(), *clock_, 5000, "Path is not safe against dynamic objects"); @@ -250,7 +250,7 @@ BehaviorModuleOutput StartPlannerModule::plan() } BehaviorModuleOutput output; - if (!status_.is_safe_static_objects) { + if (!status_.found_pull_out_path) { RCLCPP_WARN_THROTTLE( getLogger(), *clock_, 5000, "Not found safe pull out path, publish stop path"); const auto output = generateStopOutput(); @@ -262,7 +262,7 @@ BehaviorModuleOutput StartPlannerModule::plan() PathWithLaneId path; // Check if backward motion is finished - if (status_.back_finished) { + if (status_.driving_forward) { // Increment path index if the current path is finished if (hasFinishedCurrentPath()) { RCLCPP_INFO(getLogger(), "Increment path index"); @@ -317,7 +317,7 @@ BehaviorModuleOutput StartPlannerModule::plan() return SteeringFactor::STRAIGHT; }); - if (status_.back_finished) { + if (status_.driving_forward) { const double start_distance = motion_utils::calcSignedArcLength( path.points, planner_data_->self_odometry->pose.pose.position, status_.pull_out_path.start_pose.position); @@ -369,7 +369,7 @@ PathWithLaneId StartPlannerModule::getFullPath() const pull_out_path.points.end(), partial_path.points.begin(), partial_path.points.end()); } - if (status_.back_finished) { + if (status_.driving_forward) { // not need backward path or finish it return pull_out_path; } @@ -396,7 +396,7 @@ BehaviorModuleOutput StartPlannerModule::planWaitingApproval() } BehaviorModuleOutput output; - if (!status_.is_safe_static_objects) { + if (!status_.found_pull_out_path) { RCLCPP_WARN_THROTTLE( getLogger(), *clock_, 5000, "Not found safe pull out path, publish stop path"); clearWaitingApproval(); @@ -414,7 +414,7 @@ BehaviorModuleOutput StartPlannerModule::planWaitingApproval() planner_data_, backward_path_length, std::numeric_limits::max(), /*forward_only_in_route*/ true); - auto stop_path = status_.back_finished ? getCurrentPath() : status_.backward_path; + auto stop_path = status_.driving_forward ? getCurrentPath() : status_.backward_path; const auto drivable_lanes = generateDrivableLanes(stop_path); const auto & dp = planner_data_->drivable_area_expansion_parameters; const auto expanded_lanes = utils::expandLanelets( @@ -441,7 +441,7 @@ BehaviorModuleOutput StartPlannerModule::planWaitingApproval() return SteeringFactor::STRAIGHT; }); - if (status_.back_finished) { + if (status_.driving_forward) { const double start_distance = motion_utils::calcSignedArcLength( stop_path.points, planner_data_->self_odometry->pose.pose.position, status_.pull_out_path.start_pose.position); @@ -502,7 +502,7 @@ void StartPlannerModule::planWithPriority( const auto & pull_out_start_pose = start_pose_candidates.at(i); // Set back_finished to true if the current start pose is same to refined_start_pose - status_.back_finished = + status_.driving_forward = tier4_autoware_utils::calcDistance2d(pull_out_start_pose, refined_start_pose) < 0.01; planner->setPlannerData(planner_data_); @@ -512,9 +512,9 @@ void StartPlannerModule::planWithPriority( return false; } // use current path if back is not needed - if (status_.back_finished) { + if (status_.driving_forward) { const std::lock_guard lock(mutex_); - status_.is_safe_static_objects = true; + status_.found_pull_out_path = true; status_.pull_out_path = *pull_out_path; status_.pull_out_start_pose = pull_out_start_pose; status_.planner_type = planner->getPlannerType(); @@ -535,7 +535,7 @@ void StartPlannerModule::planWithPriority( // Update status variables with the next path information { const std::lock_guard lock(mutex_); - status_.is_safe_static_objects = true; + status_.found_pull_out_path = true; status_.pull_out_path = *pull_out_path_next; status_.pull_out_start_pose = pull_out_start_pose_next; status_.planner_type = planner->getPlannerType(); @@ -587,7 +587,7 @@ void StartPlannerModule::planWithPriority( // not found safe path if (status_.planner_type != PlannerType::FREESPACE) { const std::lock_guard lock(mutex_); - status_.is_safe_static_objects = false; + status_.found_pull_out_path = false; status_.planner_type = PlannerType::NONE; } } @@ -730,7 +730,7 @@ void StartPlannerModule::updatePullOutStatus() void StartPlannerModule::updateStatusAfterBackwardDriving() { - status_.back_finished = true; + status_.driving_forward = true; status_.backward_driving_complete = true; // request start_planner approval waitApproval(); @@ -835,7 +835,7 @@ bool StartPlannerModule::isOverlappedWithLane( bool StartPlannerModule::hasFinishedPullOut() const { - if (!status_.back_finished || !status_.is_safe_static_objects) { + if (!status_.driving_forward || !status_.found_pull_out_path) { return false; } @@ -874,7 +874,7 @@ bool StartPlannerModule::isBackwardDrivingComplete() const const double ego_vel = utils::l2Norm(planner_data_->self_odometry->twist.twist.linear); const bool is_stopped = ego_vel < parameters_->th_stopped_velocity; - const bool back_finished = !status_.back_finished && is_near && is_stopped; + const bool back_finished = !status_.driving_forward && is_near && is_stopped; if (back_finished) { RCLCPP_INFO(getLogger(), "back finished"); } @@ -916,7 +916,7 @@ bool StartPlannerModule::isStuck() } // not found safe path - if (!status_.is_safe_static_objects) { + if (!status_.found_pull_out_path) { return true; } @@ -943,7 +943,7 @@ TurnSignalInfo StartPlannerModule::calcTurnSignalInfo() const const Pose & end_pose = status_.pull_out_path.end_pose; // turn on hazard light when backward driving - if (!status_.back_finished) { + if (!status_.driving_forward) { turn_signal.hazard_signal.command = HazardLightsCommand::ENABLE; const auto back_start_pose = planner_data_->route_handler->getOriginalStartPose(); turn_signal.desired_start_point = back_start_pose; @@ -1157,7 +1157,7 @@ BehaviorModuleOutput StartPlannerModule::generateStopOutput() { const std::lock_guard lock(mutex_); - status_.back_finished = true; + status_.driving_forward = true; status_.planner_type = PlannerType::STOP; status_.pull_out_path.partial_paths.clear(); status_.pull_out_path.partial_paths.push_back(stop_path); @@ -1209,8 +1209,8 @@ bool StartPlannerModule::planFreespacePath() status_.pull_out_path = *freespace_path; status_.pull_out_start_pose = current_pose; status_.planner_type = freespace_planner_->getPlannerType(); - status_.is_safe_static_objects = true; - status_.back_finished = true; + status_.found_pull_out_path = true; + status_.driving_forward = true; return true; } @@ -1231,7 +1231,7 @@ void StartPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) cons DrivableAreaInfo current_drivable_area_info; current_drivable_area_info.drivable_lanes = target_drivable_lanes; output.drivable_area_info = - status_.back_finished + status_.driving_forward ? utils::combineDrivableAreaInfo( current_drivable_area_info, getPreviousModuleOutput().drivable_area_info) : current_drivable_area_info; @@ -1291,13 +1291,13 @@ void StartPlannerModule::setDebugData() const const auto header = planner_data_->route_handler->getRouteHeader(); { visualization_msgs::msg::MarkerArray planner_type_marker_array{}; - const auto color = status_.is_safe_static_objects ? createMarkerColor(1.0, 1.0, 1.0, 0.99) - : createMarkerColor(1.0, 0.0, 0.0, 0.99); + const auto color = status_.found_pull_out_path ? createMarkerColor(1.0, 1.0, 1.0, 0.99) + : createMarkerColor(1.0, 0.0, 0.0, 0.99); auto marker = createDefaultMarker( header.frame_id, header.stamp, "planner_type", 0, visualization_msgs::msg::Marker::TEXT_VIEW_FACING, createMarkerScale(0.0, 0.0, 1.0), color); marker.pose = status_.pull_out_start_pose; - if (!status_.back_finished) { + if (!status_.driving_forward) { marker.text = "BACK -> "; } marker.text += magic_enum::enum_name(status_.planner_type); From ae5035f4d4b724a6dde175d4ac9c53ed0f740905 Mon Sep 17 00:00:00 2001 From: Fumiya Watanabe Date: Wed, 25 Oct 2023 13:24:57 +0900 Subject: [PATCH 546/547] fix(motion_utils): fix build error (#5404) Signed-off-by: Fumiya Watanabe --- .../motion_utils/vehicle/vehicle_state_checker.hpp | 8 ++++---- common/motion_utils/src/vehicle/vehicle_state_checker.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/common/motion_utils/include/motion_utils/vehicle/vehicle_state_checker.hpp b/common/motion_utils/include/motion_utils/vehicle/vehicle_state_checker.hpp index f5f423f881ba2..6a9bd01a5a9a0 100644 --- a/common/motion_utils/include/motion_utils/vehicle/vehicle_state_checker.hpp +++ b/common/motion_utils/include/motion_utils/vehicle/vehicle_state_checker.hpp @@ -55,11 +55,11 @@ class VehicleStopChecker : public VehicleStopCheckerBase protected: rclcpp::Subscription::SharedPtr sub_odom_; - Odometry::SharedPtr odometry_ptr_; + Odometry::ConstSharedPtr odometry_ptr_; private: static constexpr double velocity_buffer_time_sec = 10.0; - void onOdom(const Odometry::SharedPtr msg); + void onOdom(const Odometry::ConstSharedPtr msg); }; class VehicleArrivalChecker : public VehicleStopChecker @@ -74,9 +74,9 @@ class VehicleArrivalChecker : public VehicleStopChecker rclcpp::Subscription::SharedPtr sub_trajectory_; - Trajectory::SharedPtr trajectory_ptr_; + Trajectory::ConstSharedPtr trajectory_ptr_; - void onTrajectory(const Trajectory::SharedPtr msg); + void onTrajectory(const Trajectory::ConstSharedPtr msg); }; } // namespace motion_utils diff --git a/common/motion_utils/src/vehicle/vehicle_state_checker.cpp b/common/motion_utils/src/vehicle/vehicle_state_checker.cpp index 6ed9472d39e0f..194c49ae3bff8 100644 --- a/common/motion_utils/src/vehicle/vehicle_state_checker.cpp +++ b/common/motion_utils/src/vehicle/vehicle_state_checker.cpp @@ -88,7 +88,7 @@ VehicleStopChecker::VehicleStopChecker(rclcpp::Node * node) std::bind(&VehicleStopChecker::onOdom, this, _1)); } -void VehicleStopChecker::onOdom(const Odometry::SharedPtr msg) +void VehicleStopChecker::onOdom(const Odometry::ConstSharedPtr msg) { odometry_ptr_ = msg; @@ -128,7 +128,7 @@ bool VehicleArrivalChecker::isVehicleStoppedAtStopPoint(const double stop_durati th_arrived_distance_m; } -void VehicleArrivalChecker::onTrajectory(const Trajectory::SharedPtr msg) +void VehicleArrivalChecker::onTrajectory(const Trajectory::ConstSharedPtr msg) { trajectory_ptr_ = msg; } From 71848084513463e86729693d41b39c2bf9aa3288 Mon Sep 17 00:00:00 2001 From: Shohei Sakai Date: Wed, 25 Oct 2023 13:39:03 +0900 Subject: [PATCH 547/547] feat(map_loader): display curbstone as marker array (#4958) display curbstone as marker array Signed-off-by: Shohei Sakai Co-authored-by: kminoda <44218668+kminoda@users.noreply.github.com> --- .../lanelet2_map_visualization_node.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_visualization_node.cpp b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_visualization_node.cpp index 53794fb93b7c6..c325f183fd783 100644 --- a/map/map_loader/src/lanelet2_map_loader/lanelet2_map_visualization_node.cpp +++ b/map/map_loader/src/lanelet2_map_loader/lanelet2_map_visualization_node.cpp @@ -127,13 +127,14 @@ void Lanelet2MapVisualizationNode::onMapBin( lanelet::utils::query::getAllPolygonsByType(viz_lanelet_map, "hatched_road_markings"); std::vector no_parking_reg_elems = lanelet::utils::query::noParkingAreas(all_lanelets); + lanelet::ConstLineStrings3d curbstones = lanelet::utils::query::curbstones(viz_lanelet_map); std_msgs::msg::ColorRGBA cl_road, cl_shoulder, cl_cross, cl_partitions, cl_pedestrian_markings, cl_ll_borders, cl_shoulder_borders, cl_stoplines, cl_trafficlights, cl_detection_areas, cl_speed_bumps, cl_crosswalks, cl_parking_lots, cl_parking_spaces, cl_lanelet_id, cl_obstacle_polygons, cl_no_stopping_areas, cl_no_obstacle_segmentation_area, cl_no_obstacle_segmentation_area_for_run_out, cl_hatched_road_markings_area, - cl_hatched_road_markings_line, cl_no_parking_areas; + cl_hatched_road_markings_line, cl_no_parking_areas, cl_curbstones; setColor(&cl_road, 0.27, 0.27, 0.27, 0.999); setColor(&cl_shoulder, 0.15, 0.15, 0.15, 0.999); setColor(&cl_cross, 0.27, 0.3, 0.27, 0.5); @@ -156,6 +157,7 @@ void Lanelet2MapVisualizationNode::onMapBin( setColor(&cl_hatched_road_markings_area, 0.3, 0.3, 0.3, 0.5); setColor(&cl_hatched_road_markings_line, 0.5, 0.5, 0.5, 0.999); setColor(&cl_no_parking_areas, 0.42, 0.42, 0.42, 0.5); + setColor(&cl_curbstones, 0.1, 0.1, 0.2, 0.999); visualization_msgs::msg::MarkerArray map_marker_array; @@ -242,6 +244,10 @@ void Lanelet2MapVisualizationNode::onMapBin( &map_marker_array, lanelet::visualization::noParkingAreasAsMarkerArray(no_parking_reg_elems, cl_no_parking_areas)); + insertMarkerArray( + &map_marker_array, + lanelet::visualization::lineStringsAsMarkerArray(curbstones, "curbstone", cl_curbstones, 0.2)); + pub_marker_->publish(map_marker_array); }