-
Notifications
You must be signed in to change notification settings - Fork 61
feat(planning_data_analyzer): add open-loop lane keeping metric #383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Copyright 2026 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 "lane_keeping.hpp" | ||
|
|
||
| #include <cmath> | ||
| #include <optional> | ||
| #include <vector> | ||
|
|
||
| namespace autoware::planning_data_analyzer::metrics | ||
| { | ||
|
|
||
| double calculate_lane_keeping_score( | ||
| const std::vector<LaneKeepingEvaluationPoint> & evaluation_points, | ||
| const LaneKeepingParameters & parameters) | ||
| { | ||
| if ( | ||
| evaluation_points.empty() || parameters.max_lateral_deviation < 0.0 || | ||
| parameters.max_continuous_violation_time < 0.0) { | ||
| return 0.0; | ||
| } | ||
|
|
||
| std::optional<rclcpp::Duration> violation_start_time; | ||
|
|
||
| for (const auto & evaluation_point : evaluation_points) { | ||
| if (evaluation_point.is_in_intersection || !std::isfinite(evaluation_point.lateral_deviation)) { | ||
| violation_start_time.reset(); | ||
| continue; | ||
| } | ||
|
|
||
| if (std::abs(evaluation_point.lateral_deviation) <= parameters.max_lateral_deviation) { | ||
| violation_start_time.reset(); | ||
| continue; | ||
| } | ||
|
|
||
| if (!violation_start_time.has_value()) { | ||
| violation_start_time = evaluation_point.time_from_start; | ||
| } | ||
|
|
||
| const double violation_duration = | ||
| (evaluation_point.time_from_start - violation_start_time.value()).seconds(); | ||
| if (violation_duration >= parameters.max_continuous_violation_time) { | ||
| return 0.0; | ||
| } | ||
| } | ||
|
|
||
| return 1.0; | ||
| } | ||
|
|
||
| } // namespace autoware::planning_data_analyzer::metrics |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| // Copyright 2026 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 METRICS__LANE_KEEPING_HPP_ | ||
| #define METRICS__LANE_KEEPING_HPP_ | ||
|
|
||
| #include <rclcpp/rclcpp.hpp> | ||
|
|
||
| #include <vector> | ||
|
|
||
| namespace autoware::planning_data_analyzer::metrics | ||
| { | ||
|
|
||
| struct LaneKeepingParameters | ||
| { | ||
| double max_lateral_deviation{0.5}; | ||
| double max_continuous_violation_time{2.0}; | ||
| }; | ||
|
|
||
| struct LaneKeepingEvaluationPoint | ||
| { | ||
| rclcpp::Duration time_from_start{0, 0}; | ||
| double lateral_deviation{0.0}; | ||
| bool is_in_intersection{false}; | ||
| }; | ||
|
|
||
| /** | ||
| * @brief Evaluate the binary lane-keeping subscore for one trajectory. | ||
| * | ||
| * Returns `0.0` when the continuous over-threshold duration reaches the configured limit, | ||
| * otherwise returns `1.0`. | ||
| */ | ||
| double calculate_lane_keeping_score( | ||
| const std::vector<LaneKeepingEvaluationPoint> & evaluation_points, | ||
| const LaneKeepingParameters & parameters = LaneKeepingParameters{}); | ||
|
|
||
| } // namespace autoware::planning_data_analyzer::metrics | ||
|
|
||
| #endif // METRICS__LANE_KEEPING_HPP_ |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |||||||||
| #include "history_comfort.hpp" | ||||||||||
|
|
||||||||||
| #include <autoware/lanelet2_utils/geometry.hpp> | ||||||||||
| #include <autoware/lanelet2_utils/intersection.hpp> | ||||||||||
| #include <autoware/motion_utils/trajectory/trajectory.hpp> | ||||||||||
| #include <autoware_lanelet2_extension/utility/utilities.hpp> | ||||||||||
| #include <autoware_utils_geometry/geometry.hpp> | ||||||||||
|
|
@@ -27,6 +28,7 @@ | |||||||||
| #include <cmath> | ||||||||||
| #include <limits> | ||||||||||
| #include <memory> | ||||||||||
| #include <optional> | ||||||||||
| #include <string> | ||||||||||
| #include <unordered_set> | ||||||||||
| #include <vector> | ||||||||||
|
|
@@ -221,12 +223,29 @@ lanelet::ConstLanelets collect_route_relevant_lanelets_for_trajectory( | |||||||||
| return drivable_lanelets; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| std::optional<lanelet::ConstLanelet> find_reference_lanelet( | ||||||||||
| const geometry_msgs::msg::Pose & pose, | ||||||||||
| const std::shared_ptr<autoware::route_handler::RouteHandler> & route_handler) | ||||||||||
| { | ||||||||||
| if (!route_handler || !route_handler->isHandlerReady()) { | ||||||||||
| return std::nullopt; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| lanelet::ConstLanelet closest_lanelet; | ||||||||||
| if (route_handler->getClosestLaneletWithinRoute(pose, &closest_lanelet)) { | ||||||||||
| return closest_lanelet; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return std::nullopt; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| } // namespace | ||||||||||
|
|
||||||||||
| TrajectoryPointMetrics calculate_trajectory_point_metrics( | ||||||||||
| const std::shared_ptr<SynchronizedData> & sync_data, | ||||||||||
| const std::shared_ptr<autoware::route_handler::RouteHandler> & route_handler, | ||||||||||
| const HistoryComfortParameters & history_comfort_params, | ||||||||||
| const LaneKeepingParameters & lane_keeping_params, | ||||||||||
| const autoware::vehicle_info_utils::VehicleInfo & vehicle_info) | ||||||||||
| { | ||||||||||
| TrajectoryPointMetrics metrics; | ||||||||||
|
|
@@ -286,17 +305,47 @@ TrajectoryPointMetrics calculate_trajectory_point_metrics( | |||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Calculate lateral deviation from preferred lane | ||||||||||
| if (route_handler && route_handler->isHandlerReady()) { | ||||||||||
| const auto preferred_lanes = route_handler->getPreferredLanelets(); | ||||||||||
| if (!preferred_lanes.empty()) { | ||||||||||
| for (size_t i = 0; i < num_points; ++i) { | ||||||||||
| const auto arc_coordinates = autoware::experimental::lanelet2_utils::get_arc_coordinates( | ||||||||||
| preferred_lanes, trajectory.points[i].pose); | ||||||||||
| metrics.lateral_deviations[i] = arc_coordinates.distance; | ||||||||||
| std::vector<LaneKeepingEvaluationPoint> lane_keeping_evaluation_points; | ||||||||||
| lane_keeping_evaluation_points.reserve(num_points); | ||||||||||
|
|
||||||||||
| // Calculate lateral deviation from the local route lane at each pose. | ||||||||||
| if (!route_handler) { | ||||||||||
| metrics.lane_keeping_reason = "unavailable_no_route_handler"; | ||||||||||
| } else if (!route_handler->isHandlerReady()) { | ||||||||||
| metrics.lane_keeping_reason = "unavailable_route_handler_not_ready"; | ||||||||||
| } else { | ||||||||||
| for (size_t i = 0; i < num_points; ++i) { | ||||||||||
| const auto & point = trajectory.points[i]; | ||||||||||
| const auto reference_lanelet = find_reference_lanelet(point.pose, route_handler); | ||||||||||
| if (!reference_lanelet.has_value()) { | ||||||||||
| metrics.lateral_deviations[i] = std::numeric_limits<double>::quiet_NaN(); | ||||||||||
|
||||||||||
| metrics.lateral_deviations[i] = std::numeric_limits<double>::quiet_NaN(); | |
| metrics.lateral_deviations[i] = std::numeric_limits<double>::quiet_NaN(); | |
| lane_keeping_samples.push_back(LaneKeepingSample{ | |
| point.time_from_start, metrics.lateral_deviations[i], false}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied in 1cb4555e. I now append a same-timestamp non-finite LK evaluation point when no reference lanelet is found, so missing-lanelet gaps break the continuous-violation window.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -174,10 +174,14 @@ void OpenLoopEvaluator::evaluate( | |||||||||||||||||||||||||||||||||||||||||||
| for (const auto & eval_data : evaluation_data_list) { | ||||||||||||||||||||||||||||||||||||||||||||
| // Calculate trajectory point metrics | ||||||||||||||||||||||||||||||||||||||||||||
| auto trajectory_metrics = metrics::calculate_trajectory_point_metrics( | ||||||||||||||||||||||||||||||||||||||||||||
| eval_data.synchronized_data, route_handler_, history_comfort_params_, vehicle_info_); | ||||||||||||||||||||||||||||||||||||||||||||
| eval_data.synchronized_data, route_handler_, history_comfort_params_, lane_keeping_params_, | ||||||||||||||||||||||||||||||||||||||||||||
| vehicle_info_); | ||||||||||||||||||||||||||||||||||||||||||||
| // Evaluate trajectory | ||||||||||||||||||||||||||||||||||||||||||||
| auto metrics = evaluate_trajectory(eval_data); | ||||||||||||||||||||||||||||||||||||||||||||
| metrics.history_comfort = trajectory_metrics.history_comfort; | ||||||||||||||||||||||||||||||||||||||||||||
| metrics.lane_keeping = trajectory_metrics.lane_keeping; | ||||||||||||||||||||||||||||||||||||||||||||
| metrics.lane_keeping_available = trajectory_metrics.lane_keeping_available; | ||||||||||||||||||||||||||||||||||||||||||||
| metrics.lane_keeping_reason = trajectory_metrics.lane_keeping_reason; | ||||||||||||||||||||||||||||||||||||||||||||
| metrics.drivable_area_compliance = trajectory_metrics.drivable_area_compliance; | ||||||||||||||||||||||||||||||||||||||||||||
| metrics.drivable_area_compliance_available = | ||||||||||||||||||||||||||||||||||||||||||||
| trajectory_metrics.drivable_area_compliance_available; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -801,6 +805,8 @@ void OpenLoopEvaluator::save_metrics_to_bag( | |||||||||||||||||||||||||||||||||||||||||||
| std_msgs::msg::Float64 scalar_msg; | ||||||||||||||||||||||||||||||||||||||||||||
| scalar_msg.data = metrics.history_comfort; | ||||||||||||||||||||||||||||||||||||||||||||
| bag_writer.write(scalar_msg, metric_topic("history_comfort"), message_timestamp); | ||||||||||||||||||||||||||||||||||||||||||||
| scalar_msg.data = metrics.lane_keeping; | ||||||||||||||||||||||||||||||||||||||||||||
| bag_writer.write(scalar_msg, metric_topic("lane_keeping"), message_timestamp); | ||||||||||||||||||||||||||||||||||||||||||||
| scalar_msg.data = metrics.drivable_area_compliance; | ||||||||||||||||||||||||||||||||||||||||||||
| bag_writer.write(scalar_msg, metric_topic("drivable_area_compliance"), message_timestamp); | ||||||||||||||||||||||||||||||||||||||||||||
| std_msgs::msg::Bool availability_msg; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1054,6 +1060,24 @@ nlohmann::json OpenLoopEvaluator::get_summary_as_json() const | |||||||||||||||||||||||||||||||||||||||||||
| emit_metric( | ||||||||||||||||||||||||||||||||||||||||||||
| "aggregate", "history_comfort", "Binary history comfort subscore across trajectories [-]", | ||||||||||||||||||||||||||||||||||||||||||||
| history_comfort_values); | ||||||||||||||||||||||||||||||||||||||||||||
| std::vector<double> lane_keeping_values; | ||||||||||||||||||||||||||||||||||||||||||||
| lane_keeping_values.reserve(metrics_list_.size()); | ||||||||||||||||||||||||||||||||||||||||||||
| std::size_t lane_keeping_available_count = 0; | ||||||||||||||||||||||||||||||||||||||||||||
| std::map<std::string, std::size_t> lane_keeping_reason_counts; | ||||||||||||||||||||||||||||||||||||||||||||
| for (const auto & m : metrics_list_) { | ||||||||||||||||||||||||||||||||||||||||||||
| ++lane_keeping_reason_counts[m.lane_keeping_reason]; | ||||||||||||||||||||||||||||||||||||||||||||
| if (m.lane_keeping_available) { | ||||||||||||||||||||||||||||||||||||||||||||
| lane_keeping_values.push_back(m.lane_keeping); | ||||||||||||||||||||||||||||||||||||||||||||
| ++lane_keeping_available_count; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| emit_metric( | ||||||||||||||||||||||||||||||||||||||||||||
| "aggregate", "lane_keeping", "Binary lane keeping subscore across trajectories [-]", | ||||||||||||||||||||||||||||||||||||||||||||
| lane_keeping_values); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1067
to
+1076
|
||||||||||||||||||||||||||||||||||||||||||||
| for (const auto & m : metrics_list_) { | |
| lane_keeping_values.push_back(m.lane_keeping); | |
| } | |
| emit_metric( | |
| "aggregate", "lane_keeping", "Binary lane keeping subscore across trajectories [-]", | |
| lane_keeping_values); | |
| std::size_t lane_keeping_available_count = 0; | |
| std::map<std::string, std::size_t> lane_keeping_reason_counts; | |
| for (const auto & m : metrics_list_) { | |
| ++lane_keeping_reason_counts[m.lane_keeping_reason]; | |
| if (m.lane_keeping_available) { | |
| lane_keeping_values.push_back(m.lane_keeping); | |
| ++lane_keeping_available_count; | |
| } | |
| } | |
| emit_metric( | |
| "aggregate", "lane_keeping", "Binary lane keeping subscore across trajectories [-]", | |
| lane_keeping_values); | |
| j["aggregate/lane_keeping_available_count"] = lane_keeping_available_count; | |
| j["aggregate/lane_keeping_unavailable_count"] = | |
| metrics_list_.size() - lane_keeping_available_count; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applied in 1cb4555e. LK now reports lane_keeping_available and lane_keeping_reason, and the aggregate summary only uses available LK values while also reporting available/unavailable counts and reason counts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think adding arguments to this function is not a good idea, since each parameter is used only to calculate each parameters. Should be fixed in another PR