From dc6586141745f3ccbe4fe5bee69a31c90c3773b7 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Tue, 12 Sep 2023 22:59:16 +0900 Subject: [PATCH 1/8] 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 | 127 ++++++++++++++++-- 6 files changed, 162 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 12ebadf770996..faa7cfcd8e96a 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 @@ -104,12 +104,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 c3fa364da269e..879f2e0b8b43d 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,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; } @@ -102,6 +107,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 @@ -194,6 +202,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 266e184a06a08..7a5a4ef93d4a5 100644 --- a/planning/obstacle_cruise_planner/src/node.cpp +++ b/planning/obstacle_cruise_planner/src/node.cpp @@ -397,11 +397,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 @@ -438,9 +445,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_); @@ -911,7 +929,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 03cae6e6f9d88..4656988750e65 100644 --- a/planning/obstacle_cruise_planner/src/planner_interface.cpp +++ b/planning/obstacle_cruise_planner/src/planner_interface.cpp @@ -15,6 +15,10 @@ #include "obstacle_cruise_planner/planner_interface.hpp" #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" namespace @@ -204,6 +208,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( @@ -259,16 +276,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( @@ -282,29 +302,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( @@ -314,11 +333,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 @@ -395,6 +415,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 6479c945b3ddee5eb277fff2da95a8149af9a901 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 2/8] 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 zcmY&cHYQ9p*|u$KvTfVed-{C!d;6oV^IW^+WMP`U472(Z|&ARr(J65_)0ARyp9ARwP6pdo=z(%c)Efj{8(f)a|*z>f#CK?v}S z0>Vho$h6y3OV6>F8bZ&qmyq|3o^db9kC9dC6BrsD2f=jAwk4tyAcJ}pzw-^M<<_z5I5X%uPuA<0q6DtS4o1l9!kOYPA->bg-2 z1*=u?(Lb$w@X|nB3gl#a!?o-ww8>&weaF&FATUt;EXG(+KZzBXE})iQB>D=aLm=qwH_ zVnifX4w2xwH3`0wam zLoT-)6WdP~_h+kx#bMtIdNyr9{zwEk9!>?;TP`cB6aq8Tj3eopS)rCkGRkJpj34@Y zy7p9TrVhNX(Sp*+LW0!}C-ah7|CYrz%XpH_uH(*G8TOeG738S?qM#@%8SIN7wCyUF z{T{q^+b#M)BrX-xrxOdp(%NRnV@8vS%o5yeWI@!DY6I1-`p^J}cccLvWOl~L(`&)v zVpG6kY?;~96dteZ?A3q0#US}}=JiXBHzPZB&x-wo6*muO!w;E5(7b)`)0kA#*rQu$ zC*alwUzgVIpC_#Lc=^XGMVxqHVksyABht!_CbM9%T3}8W$kH)=DkYTVd##m@LKRdj zp_B?TtrZQwl{NwKU&|>@Mg!*TPg!lPyOuaUig)j(M3c=4F86U63OyD8nB3v1|X9ZpEWfKby+p!u-~}2U|(E0;3?am?(9&K zbMo}Njp1{88xvO_^RucefLNB1z{M3dsS%Ts#QI}(yw^-!aPzq+_C;D+!@kZh;`R!BoJ`Nj`E>1^Fe;;gzU2>_(TV}K`tjoj4v!nE zv$OM9Du;s6-<^#ve?zxi*TV>E+nQy%lPhuZ+%nIBZi~Gwm0&bA3B ztI3%uJyGcnJG0Vw2vaZ_%hUiNQwW%>6MW}S)@j=b52xkfdJPB8Kv~hCsS%64$eLI6 z0TAljh!rkf+LZ698s231d?*1;?MYmIsrSqIdC~pvY=1;TA9uHSD=mlxMl~I|IQ6|A zX6pF|bc zl)V#Y6&dqq7>WrH9D3`!+*-yZ4^ zqebm>aMp2y`1<%-VCd087I;A6ImAX>eFK3H0{gBydcd2bw)M2CSmBCnd49EF(#?$b z)iAHJ(^XUB=-y|$0fHNgF&r%^^SXMCy#bnL!#BK|mq8 zo99sQoz8OpblreKy&>3v=_GGrd_TARmoMVaJie}U0l)1G5QdF|Hyo4ZM;BO93V7E; z%VlH*aVUr_>atSnt1PDYvFpSQtoZiPzL#Qm3CX?%Geb49!DrtKfgT`M81>zozM%5N z6kLUSba;Ru*qm84SqRIfKU!mBr}=WV1Wk<%3?7!I^;Sq3T*oj7g^YcSiBe#5aADs)>v}r#n+sV6F>2>WN5jL-4_@>%bhVB#awjp z->vg=>B9mm^UCmk<5>5+$MyB~4G0e2-rYUHc)mI8nb+}-m^AsbN)(7ZkAC~f>-AQ} zvC{_>qgbBW`xUt^3lU8YOspRbf1UNx2`zPi=P0PyzJaQ^c&~_0x>aMgB{FBa1efsV ztUg4s`tK@n&)ll(#_#4z1r|>A+k;y9yxQxot@Z5%%?E|oE&~0}X7`+9+iT8aa?WV% zQRnV0_O{P7T%$kkcbk@m)bkOoBy6R9d(x`Vix!U(WLe}ul}hRg>B=fG)_X3kg9>>$ z6%imE53*yn1)+an8S_bbzuwLtA?_y2|GsQrTI_jL(_DA|^Q15E$VpcJU4g5G_u0Jh zv83YM0guk%rT9CHoeJozjJZ6N_n&M^<7F9*TwW{NG=-X-RKFFGBOooO?g zBYUZ8O%m6m`uSzFdj=X#&&>RyV)b03;&X=*HJs^Tsy{KRU};ruGtf#Hc((OhA1gfz z+b{R6>boo{EEBtlG6@~j)+j6wqY&5U(Me>YoTcNJymB2qG!&ZSxpR>WZIW&1qdVxFmc_-h`UUpCvcjK%0Lt+_U9)$i z6Xes2)SH?Hw&{u&<5yXb0~j|e#PLV2V+>M}8iU~L>4h0HaypR*^oq~<=Ggtio6cZz zHl)#x!?Cnn?fsxau9ufVV`t{K}`41{cDRxNH%)DdyTjoe>8b*pneXRxeWe1j_7 z+><@nK9xGVuNQ!awypQJF{EC`l~Zg?dU)snF~)!Hg(JB=cElU7Ct#s_=J{BX{jXoY z^tXCLQ+Yj;2M5K0z^tX$c5z{|RB!1Xm-Dd$Enz5cH_S+t@h_%~`c7J}=;t8nDN;vC zU)esT!sVs@ko^AR78dKPO3l`@uzWS+57d2HeTu_!ScpszvrU5PskG~Fjx02_CQ44x zqU)?)E{FAK?*|~{l2QaT^N&waD~8<5mkILIW~hjUEq~>ms>dgTTMUYG0f&pFRAo<2 zQPs^TbS($T!{AH677|cMZ-R;i}btm+}O)76|P=&IU-n_4LH5vpCYzJAq1H19q_v!m1!Ta)t zO2RwTyH^J8Rf-@RI=DAqfq>xbH=sLAITn7aF2kZBI;K*tDQCXa}q+r zEx%t|Y!(X_t%kr2*D5!YEcjkjr_J0PGtaUTVd(T)Y(f&7-EF|@S&Bi|pPvPCaDyph z1n>4xvqCrs8%7nn7lh|LUY&>Ch8JZoYfDaEj!!NEq(IiA#TR(6)u;Q^@zepxTXs$B zUxeQ=1Wk{go^L9KAIVwKASzdw!T=i<2oya@S`A6woTUfZafvn0U-DP@<3OSA?L~vl zw1q#IVUHiO)VjgDIe*I_99ZNP+ZwQ6tS!L=^W7;CC1u#njRPbSo^*rNYB3ivLBNAp`4RKb@Z~dx zzDr*mwE1Hvj#Jv;_V|qujPhBa^1RlKynAQt&!eN0kP3bsHb5H-GGoAF7e7%11KBix3PJel}tDo51dZG{A zR*?1J*uJW5_2lN|gvU`n{)i2^ZAh2tP0Ceq;wuQ@UVdF{#mS@XM!vunby>lIq8CJ} z=TXI%!pFqK;jn>>&!?NrwQ$?$e9{5B$Ov^wS5dM6LQx~wsAoLNZ!>wFDs@awSs zgl_MKTjjPkQ^?~p)mhFM>e-HncW2E`mTZ^KF8;lPW(ttu%?|jmjKL)W-BlAhK zB`QpgM~V)dxYPMEvBHVSTg^_Z#62<2FIz|Sx8S!?8?^K1nc1aj#Bq4NyC7`IdX|Tp zNO_s$fw?pTl1g$5)wEff`z4om0}bidx9h+h7Wxa;+aFA0q z*&eplS98wJUD@!Ky_^};57k)k90QB&4G>nT7L%<3F>i|2PdF0(`RQ5eRm)&qLO%Jr zPOQv`xt?Ep%4mKpVq=;kT!2Qag#zP@*mPWH-FUr`8mqqY6YTHhQ0T#B6wq4 zGURm0N+`!NX!gkCr-Gcx_Qu-_+})34H)Rj4SA#Q#{h6T}MGNAS!IypOAfRrBMEjb7 zXg%KMo1>bcwK^H{cW`ucx9xt7h?6rJs6QF?`{9$>thHD5UVb#x{;^*H=RPouMgX!{qw z!ND1{zBt*wP}FukL@NVX(L>GSPW|D_Gu*zLYTa?b)q{TfJGGBrOoEtG$M{#cX^{P& zcVCUXM@G00X=C_+zEQXReB%&D!SJPEXy_oM|zA>&v!O#U~LVf3&qpyxmX_0HQeQb<@s&Te+N^5e`!;eluFu{w8Me4C_(D##_SOZiO5>1`67XrJa94Jr z#PjkKw;vvHZ=IayQl!L+{%z0rPXcDf2q?1f9ji1Z4)j+^Swhl^yl=U5rxBDQfod*Fj%@fC2+BfhBnTGdGxtyI7x%je(H=rAHZ}v$`qe(d2m3r{+#t4~G4Z3Ho*FSh` zC{TB>TsNBN)elp-DDGWcnVnmVjNfi-Ws@7=zYTU^z4_PwJXEdDia7cW6Dt@(m182t zTrnQHmk>LP8~Q*NPp30hs#XIa3ZMXNlc`YfeHIsRl!{5mq*!~4p0w|2b71(gAb}%_ zU(Kf5TVJ)9r%AtWE%F<09S_#CSjg6F=OgHz%$vJ93q!ZBLEfZ8s;^Ba!2GzRMzmJn zWT{1X{*2oZRuom2m8DHUL=pM$fEuZv`AQV4TFa2^wO=j^6ysZAlPvgsE!)dkP&k1U zFU}6cT$)_(M`xIQ#WE#{oRvHXJZN6BGP=j(tacOI%LIPT%B09>o7YhlX%#zYQLNYQ zHndcH2m{Xsgc3z#f#e7>Lav6k)F^(bQQ{5=JudGZbkj{$_R=a=>ab#PrLCxSL9#l( zc6JLqCc*C3w;pVNMffdgB{o=bC_vR0_^yq0mD!d)8X=f(qj30!W|HOCIJ|y(YI4$W znpVfe{W|VFaNpU0_YxN`zS0aXFMD)p%J#cqzV}+RB4&2-XobnYLbC6Api9_mlo^?U zFWwo?T(wm#B3m55k8BU$#w7LjWo>&0$SSEuwadBB4bWjySYs6mFYZJ24bo~87KgM- z(zO=lHV@Xox90k)Sca7I$C|>vFNIY!(dBH7)3VG*zUJD~NGJ{c1ir|EPnDO~*30|l z63V*$k#snLuE64Ak_V#LyCen&4Rf}^a zM)d3U4=l?S3wNa(lId7yW@Tl+l1shR{RD}4rY3-b#?2TEQnRFNj7+Pol|~ItZ{|hE zc8j+bgC?NdJ~1{mFd+8w@|wjRLns&>pzh1l}pg96@q6@u7V{!Y=A3r(u*w!z zg#Uf}hxw54hBMLE33p$%Y1R?aR*;gJf_7`HYG)}Snsc9MPW{PvRmJTc3U^xiOMw; z?!$xXT=qtly^lfb1dQJt+ZCVu;SLc z7BpGoMb%OGY*h?;-YF;el4hBj79(#%3ZWJ3@>83y1+OA*c|B!p&KhqgWfbN_zWcB^ zn6Eu~?3izFf_BS1zD@fmd7QR)z&4~sjBe{ywtvDg#=HhK4NpuDmJdJN;4$75T-&8P z1cZ^1F(fREkcB1g?e!VJJv08bq{d^T(MqX2bIkq%owhzAWoO0UG)t*HWDbUyh6K{+ z?TKyYcTDs*Sx)f}%o@Pcz7y!J8rjvM3*y01u*s~fwBQTi*|5__dSa;FAD3dYl-ulhwbaQ!{Z!5O#LX7W0vv{QmZ;?{ag9(@X)TofYMeP)kLpqQM_*GcpkuL!{R zdhnHqCA|a?b4I;xaESmI0xN8`vqAby?cl23GrYf9Wf}{~TbdeuJ-VA??{N8LO8NW% zc=3_{Vu|1o_BjP5TcIbjQR$Qy8Fv-nS-g(+e}-CdjMl+o`86`bXS|DofOl|PUZsyH zH`HrnZ)I4$TT>OTrCi@$Toqm9pDILgV$bg{KW{O$#aREPT|!tyq{{2n&Cky-BqStL zxlH|^7^2C-=D-NgpTjiqvX;)=(a=l;)Ple)wm6jvGk5m3^N(yRbbI5Nq9H`E179S= z;lXH|vx3zVBVycxy1S+CEYyYEL~c{mFUytg2pS-H++@{YcRdjCxW$1|$JEpm9-BEN zoyT3-S$sZl-TOTa*r(}YrII5=5$GQfRuBInEVdX;Z!x)7Z5s{kB`I`DV}f}wRvg?~ zi%s0JMa_lJ)lh&sUs}7yci*6L-thUX<^190dfap>7e*mpI=Xkvl^qB1?tFdR7Yr)w z{q}f1%4)S)$=L+R?+O3Ikn}<{O~OkJC!<<4*wS<0QyYqD7hzH4E_Q7ki^Wnt1!i@H zSIqu;p&3HcR#4Q|*2V)MR*SXPq~E`hyL);j&FW@x=UBF*_}(@Es1=9Z#%E?m5fPUY z+($G=KuL+y`HVW0p1_L!zhsVPK>F{swnCJefQLkuotqvlbT10f;~J(AkHP&6?VX?o=X34s>}+=1 zE%2To1R`SM+-9L-C)Sh*FepTuiwyyJ1qBY*D`F7!viR=q?kzVfD=TKRnUE}@;D1;S zs-0wsprVB;lEpz=pZ@9a*?C2)fkeCg9iwPBdXzrUI%#FA`*<3CVAn+*nLPH^;qS=< z8ogGRj2RV#=C7_2hz{mAWc!04rURtC)cB&3nCdrY&B*n2OcXTN4a@>^=mS}<_otr{ zL*3eW4o%u>&F9WO56z^PxY9a-duEInShzsTM8IN6lqB-mBVKkuT|6n;qpcrC==X)8 zQEN5-0S@Ek)-SrLT#48t#G<9V++2FgC1wzywm}pdB6Q9}(KCL>TN{XDUzXaIuE$<46i8{+cz* zQ>oDAf83dG3PQw{yxtqF5LFcnDLF~T!pawjYEOqzuq;$i(J^wy8aN-udY6aiXh1SN ztHm=6GJpeapo;nrJw`ZDX``MZ#PHWG!%r1*=S7p)40B9bpzv-{RQ3R<&RPKq5OsG&jSIfD^k!Rf3OzICr&W1I;fuwE^`ZYg|MgOQc$>In(LIXdT zJ9zzkHB9%o{{AL*JXaP9I4uExNDRJ1(%BYgYu?wJ=`E2fP(>w?g6YXdTYcb?mT(9H zXG4)hXCJKvw; zwj}U+K23CZzaJqUCf@;@g@S@26oJh`4*c+Xu`=9^FpVqfI{*hV3w91(LG@7(Wdz`e zX#7>vHRP}68;CAhQUD?c@FTwpZ(^$bv??YSmF{i^OxeHF8H+RRuYa&H4PbYz(@7dK zUD6UZkx)`niNarN02al#@;Q~I&w+Drfjx9W*+?DSiGZ2eXODN(<>#C-t*7IskK^qh zmWB&QXtKUAXj$feI8_FhCE(?f@^XOR6yJlP#wXu5HWdPyIBJy448NPkV>s;Gx`vli zQi0XbYG7eId5Fr3g(b-ZyzP_L{3)hSUVF4V)QI6UjB%S^hIaqD#op%q9jMJxRZs!7 zQ8LSsSZx_`kjy!J@47|qxb+Ltf3yHi9W`a&-i;m}9P~V-)ssGtHkG9JoP-%4bSt*I z=ka&}Q@iaXk2mg4!WV6Pk|EO4iJ;>+UyivBqLC)j_uPGId6go(um)JAOrUUu+%~Sd zxLBw=(Vl^__cV4N7n7o+2tb~%$?$p#1xjeUn}Z+y08JcP2`4TqYdlw~=5e=ZTx-3a z-mvBdM^{n(>jEC1H#J`-MOh&L*qS`p7(F*XPA7h()UoBEwKNiv(95svdrt&8s3*i( zGucUMTwfOVLDJ>~pXD;bG+3E?8UjOWkV;p%Wtgw)rV2x=Mq<5vI-4-|e)M2?d%U#l z3JROjDL&$0i0OKpqZG5T#$1OmNaRA;+8g`e#6p`fC`4?(dL7h-xqGTxp4(0`he`LY zZYnzt#yRa8uiZP3X;oW&q&(KmgA?9hq>+JpvWnpEx$TH2cz)S)|WCIqdV-0?f-8p&H{Z%e$M9--_7$1nV{!*@Kc?7bs* zQBYSmP|tFjw`ST*aHJn8s^SOvbG<*lU*2-|6L8Dm;NXA?VZJnuO6BS=RfP?(1==o~ z5FbbeHnT}*tSqmZ=Dsf!-alnEW*+jESy9X?u?%jDO#>7vy3y5fBy(reg3U{QkBI}M zpWut_z*u=>S~=McEEkGXHi)P_24`(~kx)wRnneoOke?l~LCWxdZS9{Y)NI5;P-{2E;;)}Y<^`hpYcO(I z$mM7eAK%k%laySqa4yk$#3YWKwM_=F1o?SdU!LDPXMv1zj&8I>OpbWjvrb;xN*}7m zb_;&h`JSj{#9Xr$C%k+;Yd6!5kKD13+;OJe>5rifULZQ;%Jqg4VQcV7j0wq>nJFpi zi4{Ecb=6J9yWr>kaTA=l63C4MF3Na%z-({%;?S9O_YdznG`6zIAsh){H`a3V@)%sN z4BDU1xFF#%JAcu&57IQO$gk>Y#eQULe<&1Gs|{dQn;o~y8rHJ4TAccJySlm@4kmo# zX*8~Gre#(1bAg*sBpIW(gx&@I2tt0coFvXG_viCKf18{X#)!|ww^oOX=P26~dv+8< z4QCvvP8=M`Wl9%HODm2rirA&?Jo__trk@Uc9-60nxr_fW3Yz+eo^7dR-W)pXlisMW z*jzd-O;Gf9dlvZ}tZDPcVm86wIlPJ9eU9Y93TW{jU9ohms~vX$Y3;lqg{r7koivltgx&yjO9d~XvNc{2e|JFnHf zteeP;@zk^RhH5;d35Yt=$nW4nMBTL}^_r#tF*_D?b(4PHiZ;CEjM{GcogCK!sCh!n z$DVGFlh|zu!?KaFuq4jc+Df=`&F6qzBZ<)v*45Ru6*$%Kc-)D}EW`Ie{)=evaIray zoQ0g-tT?m-&1MOb#x=K}_6G%LxhilqlJ~)4sdJ8-{?Ykct+~P$_g>B2ASCN@{-*OV zdOaTaguRLh{aL3-d|*={q;J=X_K_8HM9O3(WG+W$;S3T!;JD|5{_ya%nWV8x^u52KxuXVO;ef@l6ydkO8ruO8NwPq0OC*fq?;s!!95g-z#wd z-}}YyHbRt0H0P31ge;&DU2#&9z|BvHbK`ah_Iuu`fpdQMe*8Xo06=5 zXjX=YROCLiQ9p6?wofT@^OHGH#ZDt>!0uC#0Ojq+N}ta=m<$Gy zo1IS8R|&LYVPRoIA|v;JPYaYvl%YpQaeu&84z%)KglR@3A zSS(7xHp0%;u;mbSH}|dIJAH70gQB-@Ao}<3!T11n);9|wTZ7A0E-+Uj^0=ngR!Z*o z6CM{=Y+n?g^sEfSOVb6{BbAxuxxx)qrAua;yGJL1`nDB$(4-`%k7Br-{zC~9NdZZ1 zNv(M>jmrO#1Jzw76a=eT%XfWNpo;J2x~cCGamr|0F|f1;ME z=fpU>J;VH~l@JqYFZYtG0Zq_*JG%7ruGs=X^-|B&G~#fFGAufkrH^rTzWB@A)Agj) z(qtlMzQo5KC1L$8A=fhuHg+-D=HUf&hZ1$B#?JdaS~|VOrw%4~;r$jFpi2}J`^r!2 zVP%Ku;%sVa+jAc95J{F(B`U(+v<#Eo&^@Hb=I^#s9t~wB)P8J!XSHhXddrB>;@*QLCD1*!BOdmBp>u={ zqk6Z1-Rog3sQxlgH2m<=bP~sgH~jIeZPDT{_-u6F)4b^AX8P4Moop#~XvZR0Syy++ z_%P`byl76z_@qLCuepJrG7_h;b)1S#$g8{Ll+&nN!b;B=NsZds-wZUgrH2PSI_@LP z%O;-i-NS^dF-_jGda$S)o4X+hkFHn@)_`uz(HvyeZA;G3jfL$84v&t zP>Ula744GS{ShTx)C9ebnSW#SK)MJ7#>S+6$W2TWGS|Aap*1YwLc^58kdwn6n1dtF z9Byf)<)=erxQ)$w=h9_X%tqbahwH!YmN(M%%1C>{%*e@QfZPrrMT)MJI>-bw9@mYz z9DZYSN67J~S^13054Zm8YAnW^N%pXaL;C**00H4~_fbenB#0z%U=PBIG`MEdGtmx%;|jjdP3 zjzLaKE@@zgMCJVCyqN|*&l7GPjG@c-Wpw*(^kAY44%Y+B#c7w~k7gj(Q`BBp$y=MI zY{F%<0~^SLlj%aHJ#=ExqU5qS6IQqMP%k z^~cOo%Tlyw>1DJ-ut+l4eMSh09Jy@$H!+ho-XqbgZp9P zTuCx(7jQ>{!m%&yuW+^MOs8D&9*c9jgyg;WugR9=on_8Wo7&fX%QI#z7gHmk79Lg% zMNPj`qPstfB~YzNz}gZ~By#3G)Gd?gHR96Lsjeq%B;JD^yKto)<(jc zT2EV=6zNz@K6`?%_Xg3eEO0)Mqe8AHk66;)?XMzY&@+uhF?75`J3|TC3-NhALRre! z>C#WcL0OCbuFFcYKTtd201k9SqXFmo;c!H!Db8PGo4w_%Eu{e98bldx>lhpyZ+~FJ z-=wj}Ryc?22g+p85A?siLZSjW?+l`oD+{e*;FceC$wer?^F&3UpABQHKo z>rJCnb-iDOKunyTMNc0!Qc4Z~R6VbV##Ik^;9bA|>44dJzj>vF{oUSBlnfWaAg}p!cc@IxChw0)f z%);ot_$&m-XMT0-e@$fYK((LGjZrYm7tLvbV!Z5m42QSl?Ix#O8LI7cciEq(H+o-4 zzwIGZt1d9m8bI&TPwLpAX>JxBKf`0ND{VIw&PD}A$_F~oh|FRc{Ncw?1 z84-F5y^j{d)Eq~ivfvfoPoVxUtBZVe0WD(I)HR{vC01~YZ(8gP?9~rO>g*M*4e^TZ z5=~>ne~EJTF1l{;Fcu9TGM1YisQ`G=q)r{kd|=?=TLo2`y>NLwfE~cRJ6Y`Uyk8Yl zRmDtCPoKq&|A2B4hM(S;uw_xubrc+ffomu=eaKsDd!N?O+RtR2jIBAD}1mJP=&ITMBsw zM;6@R-{13$&?>dM$-aP1Zpy_dguF#OE(G;Wx;&z+8s-nWiR z_;|2RFjICOjPd(z5jj1*DLOuCOs{Vcua>cE9V@id%lk{kICe%jJFxhWus3I_dSS3u1}22c13g1VeGkSL9-3a3PxAq;7f=tJh?8y$4b(1d>6NYo zefkr)j}$9*Iyej!8D-Xe030#W>z-I+3#xOD)%B~4s(lS#M`r&P-`)h{l4V$%&aurH zV{4necQps&@{LUYfD`bP-DxA`P9(ZUH9TIO2 zh_xHqV9X6qpRvCjnW^XvHzVwrb6%Z{B)C4DBXX!U=K=J6P`Ez`S6m4r(!tIO3)j@|9^;}1Z2khGqK-LIFY~fb&3vS)yo1W z?2lSZ!44*GonXV{#aGgW`cSv#%L!HLE;^@oE9OhRG1GWZ<=dmS+2sR12_3h`%U-@acM9}*uR*H5I^`}CpcC+-GvMNwXvfW}ahejjYO)aN#)FO9e}HXD3- zktqT-@a)EH9e_!tpQ_ifm(_Z>(U7Y57B9lCZ+RA=vHLPb6Oyk~%N$o+Os(1YyRng- z)8U}=H-S(gP~yxod-*TanPUK_!oa}bVvs26VnILZpD^_GH_#)~n6v_#JepY!!U`(m zGnR=3eU$JAI1dJXOQ6i&V0m+Y`RpF(BVgeu+K@ZYFQ&{m>~_SPhKpkjPCa5Up3jo2a#NC5gm$hzHY)k&g>MMTEWM^_Ja1x^AxtUszVf2Y zl{!+ZN9B6!;U}JnxyAv51*E+;K7WanOJt@Oj0ZyMr!(L6blyRL%I5HRpqJ&jMO|%i zejlh>3%RFH|8yaMn&zm5>4fS!&RfJ|dc;o`R!gBU;=^A3BWHPNPd zb+UYe zX{a!1ol!o*8L+P}mjn3Rm%S+5Hxoi1+~gJ{nK{r4l}h^?e)~8k0Vs9|w-*?&5C4+& zSWTM^vAg{NgfMY9nvO6GCrts(|M1ghD<(mChBC0QmN?R5bO4Z+``ZXgh>9=ykK0Om z#gaImYI_@X3IfE%U6D(aHiD5ynKp18Rb1NaF6(Z3=E1(_<=Q-?R(JD#33(+9I(IVA zs!f|RV?o%OG%lzi=S}X4*xa~Mt>>ck!aFX!5Xhu!U{bhmChnH3T0Zr7*bHvHUvp=+ zT1jME_fYtv2jEtM9}QatYZ&BTm97|xh`^MT7+T`qA53x`g_M*423r!5ROtu&$J`4= z-2~n4vm;4=0fgahJig4E*=LRGOr`PkT1v;2ABo-0!wvCjzuW(bs9fP<5xSrJ#v3An z(1G)8|)6gLRG;knx;3ki8~|^q!EXa6jMj(S5#C1jqNV zcE+11I`9=Fsc=Ec=uSnDc41FE%bAOBVT&mE>`xY#u8{NQCMnO@MmHxx537KxK(HJXfOH1)M9|2rOs9XL=c>+IWoM?H)us;|ND>FaK7**Zec}AydZU>vIefgI{^XWO-;i?OHPcWBr z?92?4{!G_d*<|Zmps!QiDEMs~SjijP+6#Fq@1s7}n<{<7*V`9Q!oX3fwXGU3x20BO z(dFUSHySRJ*tj?mX=!-A_ZKFBPlLy#_i?*D`q2IW{c#8h-xr^~@RvqcpboI=2gk_t zl zmoDM>J;!iCQ`@-nwr!zKNMjpqO(=)z9WyY9^ea=oL%%p{tQN%Gr1^sUuDh&)2lzJp zqn;HjqYMs@)jp_Eb(8^mCNp?X^fa2u?dfuSd-O451KlC#*QZrJ-CY&P^o$44nsl`u zYLGO%jxqg*@s~sHgD@HQjiJAhwnP^%INeR$4sk+kog7s#L)3a6=8y_F)m)^XFj-6h^-=4ziGw)o|diK<^ zAN86T9o?)^&zyQ{jj2KV`<${HG6+fJ&u& z2atY{a&zP8d(zF&`OJ3ebb@6bep0N>xksR7WsNr+j@M&M!elanzhAMJbqpp3(xn7= z1vD-WCkO~0JV-D=sa?Ii&`cDF6V7~(ls(I#&^4>c#nA*C_C2a9-8UJLi-PZbC!huY z^i{?%qmvyKF^@DRJ}EdqGTNvetpZAU#i)pV>~_CP_%6>l$0hNyWm^4ALRk$~?4C3x z`C}Yb2dgr6yqQAP^5gcsNU7@n3Xkm2C@Ix&WN-m!h5gd&f6DrQcNxCO_5!j&@g;qt zfgWl8uT%{OX*B@>jnrava`o?91^57%{o?m&(&`Yz)MDNZC3zwz#O(n7KkdDZh!h6K2G#o|EV+9EaIuem z3S<>5Nl8f`S=P>u!PDL8a86XbbkqztjOLEOb_6vENgz;3dOe@Ax!s)%0Vu$P+3>k2 zZ27iImVV6556J<(V8Su z1R+;zb3$w0jUHS9EzBE5)Mw3Q^pZ&Gc5nS}mLqt~*0|CMR$9;qu$}VjgFJ0Sk9+)! zO632Ly^G-wS4drS)eC=Pu7Af+ zGljly+~z@`E-g+;(z3E0GY0{MI7vzXH^LU){W2Ty%(C4MbNQk*oZ%%TM8!gKseUmn z%g125B^-&*H}-J3JxttfdyVb$0F8#K1wf-=uPmV&ii#7FejIgs z2=rFcJJ2oCtWmZyKPRZS_r0Ffu*!ZG9`aOL@2!6~psv-%GoQFPq#cEHZS!)WI{nzgeVT$%5F#}5^yrG=K6CN*H;VyX+BC{ zwe#bSJ9^WdAunb|B6c=3!fW6$u}KfY)6j-K-BDnJZSahOr@@U*!M z3|=?&jUuF_ECjAsbcR@qwHH!?9HR_VX-S_>4y{KAmRP3f#QPiuR|lI`jFd8e>^P zLPC?7!sw-Fvw(#K)$E)c4%@9>7N%VQeJ4esJP;t%F6nANtSiTR^JYZxY7AMga|=k};=G<(@~XP@N897- zc>JUbFs+&3nXP%1U{aavApc=aVGUMO!MT9-M~0tdt%n4kFET54r+FVj zSrMKKy1vC%wi076{*!%|@|ywE52QE^99+o1_IFyOmhHMN2o`Bwdx-Ubl-Py05{Vrg z2UJ6)vJOlGb0L}CsSy>R??_Nz4sG9|MY)*+%16z;6n^KnNq8_^O5OFJ7`{AQ0(4s5 zmf*tYZ|LkgS}?PEb?Twv;RbInj~|(Rll{IVfi(1w93?G=lWMOenr7LpzDci0F}G&E z=TG;Xyb|LJ?SM2~MaP2VA>H-Gcu&{L5=A-~@vSkY=jb3V7h}=c1MB6l#S>?}kn%@7F%g=|+kk{kRHzKUrtt}?E?tzHR6vV6RhDs+TPLP3w{>ss+ zld5TKo$a}_{q>~rm4>~mvzJhZDWz7Ms@x6d_|h?u%+PVUAf&b#(5#`2%N6rV zx`CB;10D!|iT}seTSry7y-}keA|-;-Eh^p8tr!SMN~d%;(jig;0@Bh-OLsRC(kaqi z(hXAIvpx5B$2ab{|DEF)ynFBUuJy#6^Oc;wB%fZx!4=%5? z3mHBceeIKAav_e2`%YLE%Kys9&EoclzIRx>EjlYH=-OJ8x?dkUP_oYj8O2pBI3JT2siZp;G&}t9G-Q%) zFmNelpM9Ct1r@ni!wNk?sqtg^|0uLPHa0v$=h2^U3-^%@2N1*9Qps+ zubWPDF9o@AZ}7&cT^5|PAbtIj_@2N2>gao2o6xY+iDBVjm#sMRj-nW|7d|!}-yWZl zcpt6hGxVCx+0aZ~Co#dQ681MSl0l4iK4fR<>FGAd7cMu13TBHKp5z*8Xj3vUFqD}L zE_Bg#t7p0l#*lSC`qS{(_~RDKwfp1^IoHeTVhb#9j%Xp5TkL=PLmomraAaU+Ht1fQ zTq|7+WlG_@Q8D>++;$YTIuz$k0S`qL%4uX{mRUR$#|Rv)#1QM-w{HkG2EfR2eHhZo z;L9Lh`LpG@jGRx&R!=5}^x`dxzL=7JAycJ)S$No}{HvkyI#c)2JX2JsI?{3SRdt3= zHc{E9R~g8`%orBsQ64jAdE|Mj#VcB7bvv1jf$I}5l=Fx-bz+v(KF%s1HxZTC^Z9tV z?!928{F@seP_syrkM0{35gBPaYG#^OedRm!q+_#qX=%wkSl7}!rK`GVvDXm)=*I+R za;+=jSV(rZoLl))0x@vc&+~y?em`ik)VB70txH|86whLo&oR`YD4+G(llS18v~&q5 zm|xT4f%FyBRF7@uo|Hd?@h)szNsc`B^PDcl;%IVlZ;7k9x+kvD}KhPeh*}Kr2Hx&z7QIXct(t)^vukAYYTRl zc*s=Z&CJ}~M-y~RW+OU*v{zy_qBciayF^EIf#+5Y-uAV#Klq^AH}fN4QpTmAkOpNV zMw8rCU_hZOxk|IN`5E(RSs53+%8wx-889RAiAt2x+In2=CA$2%qAAK{@Z%GU`UNL3 z31w!t-s8e)Ep^+Y=i!fiOa<>vv}w=R$VD`5Q>?29=|q(KS&DDz@LCP|$PjGOuFp{W z9*2H-Q^D`;KyiFyWb~TP*P*&sp1IVHnJ4)}_jYvksmtF(icYO({`-Ghm^ihhO(`V< zjE)QYsbnAVH!j#7HRqeSaIAW(UeVWV9wkq-&OD2yyPN7tqg3K@_2No^;^)sXnG|_S z6#r=3X7&zyG0i?q@*gujGfx?eC4}DZuSkD-*Ks7vz`aD<4jOa$wB?nReSm!MC@6Y` zPo`QYtsAikj<&b#M;H4D5-0O9Z{!#(cSS>ugk3t`bI>o^zw|RQ0~f|1%M%rDOU+!f z%>;9y9N*g8n7RHG66$~GjS?_aslbGDblX$!LmHof;n8{N+4!JCZ8~2#4X3C*II2C}_~Cu?F>7erzLEJX{}h2DFHihWz&y&)1>b11BN^lLKj``bt)>O>YA{}aSPT1enXO7ie2x;lpXR8->OfR4f?U2eW6Cj6cl9aQ$ClCDczzsF;HplnC!21 zym_g-@Q8xF_QNzQBFdV9ArIo{U!I>FOxgGE{niha{d1+%hbr>*uwnnRg1qcc9;NUS zR|+6#A&uQ5Ab4?ob$K@DgR?fg`UmqXJc3n*!JkCTXPlkicja6C`V7w(H_?e;0vl7@ zu2U89IhSd{-!4(@u>9;SsHo8D-y>^Is@xGelr_>Lx| zLh#LOiX<8iJ)_LrC7Q-i_0E&O0p&}5V-@=4>ZZTU?%fTYrhNA_^7LHea(CpZ_A#UO z8pZ3Z-R-h<8zQP5W~9SI&lOH>hl_<_G_=@0A;Bu44+EdK58Z^~IoGN;j&MjQZof4g zt-C72py+&$OtJp>^x?63K7OtLy*&rEDWv?#C))87II>^Uf46wi`3_V3_wqK@kOeqhFwolD4~{^R2800SNU z?M$7^lx;@_Lu;4GrXaaa0Tw;k`}VOJsbbqL<^}arT}i-$N{#zz>F5GL(ZK?YBqOOmow7>G)5*oC}8e{vlsP+$naGX;MsjQZ4Yz|y`wk5MVIeqW4 z#j7@K@=rwf?GD+W2j`9-_9|{~Ed-h;`<+_4u^W3^Zy>+ETD=wHHPLvuL!(vMxc^JX zx^0*vcx&mKumQhOzUk%5d*$=uSI4{gr|bFY+gknp*cb+LFAC9&_qN*UUusualx;1U zGO>j_28{Qa(I&}W;mw23uNOd} zgg*HJGx=PU?#>iO6qnrb2RrhpNQFECv5z&ZoScz>v4&+x#qNVK6}$b)>)Zb3ZoUlG zK6a#0NnZ`TOHwdJKguf$h*XVloIc4QBxqS!T#Vy3Cj@EOZ3=-X9Ff!a+tV4knYgLj zHj*fpdWXKk`y`X??yutc!#SPtrrNLL$2BVZ1$`~G+khQ>U@>HzmwZTa7W2GEO&K2t zrJ1FkF%eriX5}(d_rj7i#gIm?LRJ27rp#hJtf6P;Lo%zrh{4ep9I1|XCyxh8&dQ^C zQN8>d*K>icDudsQ$`Zz)$PBNjG?lX zZ+VR0%;|o8Ce`u9gKgJl1iQh6I_C+Pbb0eumF6_2*~COdv{+V37`xm0II8SU^==ZY zk!+9rNoIxg!e|Y71;z^({`~10`TnNxI=L-@Y;rg2Lqj{ayo3Y>ZDb&nR)aO~%n|C0 zj?j|nN>~lZqMJk0N5m7pie)EV*tRD)v_BeCIrF}}k&O|laSiAiI_JWoO;=M2)u=va zbY=S>sHjIu_SN|mZ>sv952ie}LPR77J`qavr1ZTfS@c|k>#rmvY_-|tosN)>V-BWt zayUN%s9W;~LcQT=j?&@Upbw<+g=MAcll6C_+taliajy}2QQ&4?hUUHIl!o(^l&En# z_a%{MN;iuA>mODng|ZHVz5{jBA$=5hVo@~w9N|S|uyy3$r$$Xa*ZCMk&*sfil1PW9 zXpV0&i?oAc+hicuWuA&qHYvZ7k!d&KvOC??rSv@2rF7qYTK@3+A2_UEUeo?6)R6^> z;fs*VW6+->s!sH*9L_d&jbBfBEM7IuNJ{l@$@Wk`P+XQ18$kvewZxvz`%P){*g9li z2XT#yKPMzeEw&pv#1fO@KO1w> zMHPaVGiu2s?bLBwFGlFO%-x$6>(Ye-=5A3iUzRw|QQEz8D}Gf6KYLir`;rpmwD}7L zseKzbk|H*@O^_5%0!GZMOK=@YCQ-gJ=l8zMYB+~RkUH;fFh159zq`x~d6 zmH*uzeAp=Y8kIw7;yAzosH|inL*iC}&Zmbh@veSr?jJ|q*TY;<)UAe$UvQOJxVZ_a zcq>KAk2c$GsGFJP;J4;Hm65p<+;HtjmqME17?qQj)sj?`_)}Rv?P__H@EPFH(gMJ6 zy-W)SAd1p$lViJjl@jq%U~G<7K!7ylWh)4@B_K_Wh60#3TF}QFi7&<{ZfCcjGIKM{ zJ9Xey)Aq5s z?&zO$Kc;kIWz;BK`bzHKD5G)D;roZc=odMBe^@3&JqnGz9R7&W z!p5tvB&V70j!_d^j&FI`PjVJ-$r)vTMpVfeo3o8nX@jpE9eIEhcBsGda0XF3?3{o* z6blH|`&5|)QcqvM8kCR3yjF=5 z;fi=fL;*hyS}b_%uP;wH9XD0~`_MPJk`g@k&iaSzo!@y-Z?mGD9{v`1pq`y1`LTux z&`hM>lyUL>d$KQ@rPHWsXflEn0=qh2W9rYh7*2s$=BP$I2oad9SNoD6YAPrww16&s zZM-xKK2ly&^EJ#en*YlCCY``jclCF}VaCdK})a64CdwNmJn=K!;Eo@#TE zZtF-Wbb4ZZFB}}Wn3VJ8erZ^PJ{XU<=hH=-u>tI?^Bue)qo~>lVZC?M&#TN z&t@k2AR+3A#=+4|%w_VKs~a5UwHm;lp8V0eP_{JO5F^#EFO==<>`r^}TfaRpc_lJVh(#FIy<-}i68);>+jG19@9eR-@1*VQ z99oF9{SNH7ir{m|3)3B<}z#IzfqW`^zM5JzQ1XG3xPT8yjG1{8*!@BX( zf6ye|cCr0kE{8iUE}8?#-BF z6W6({?M(hY+(TEdGa!KPf;*% z%wG%}j$*TS;tu zp1Dmtkf7m%edzk5Q#beW0xKcwo*DaY6P*{5O@RqP*e${BxV5U)Vq$Gl(hto8L6jb7 zF)L;(v*7_og|y$Za~kZ$MqW!uNK~1Za}|8>eB&89ZYc3sFi(9Q{n1vKH)7&4F_FA{ z5i#e>yVZDo$p%w>G@vk8MMNANh*CF`t{irWrdZ@((9)Wf{n1VOcS2D^92<(Q_c><+W#$$S68uq2RmuVBU;+Wo)^wA#F^um&ZwV&d9j$|BiM9I zPM@OV;tb{}ux&K8v|Rpt&srNR)Zxy+hftEyW#o^%Mv+l|D9nHR%8ae{S9_7`8zJ-A ztw2h8hKi!&Ncv6tXL%P_eFqtSMUzw-EzQp~RIA-7YH`~3XTG*0k)Iz~u3V*5FL=L; ziaC<8y80xIOcnk)=B1uG>E!HISeAZW6{!7)yrufbFYZ--i51%%;~KiNH0!o7PDRvq zN+?%yq>&nikL23!Sry$Ym8!bCLmy)ivg{S+_*H2YH>gMB^qEmWpa655MSEjgF;-+o`%#eaor z({g2KX2$4seLj2kC$|v7CLN4l#fri7%9L*olfM>^clwJHi8{WC{=HYSC&oH++Scc3 zpSCu}qp{mi8}aHp8(t4i8zRXza**qYKb2Y}Y_Ts4$;!CX)Rjz=o4vWf=wyi0@3l+5 zCA6u3;yiygRE)qRs_&sPCyW*+-(x~ke!ge^!NVo|0};+Oo3}oio?4?bqgKXzLv|~~ za`XW+hO+TjOsur$viR*Xf(0yPuXqx2MY5|{C%x|?0lX!){0O4M^JM};!u>WP zo!wCtoxwsKQOI`U;6^nXs&rJsH|1ppfsbHVU!L z3GT~J#0v99NMa+7K;#*$t|+9B9fXy-B-)cVzQ{FHXuTC()6sHpM$TNKk|O*b3JZf2 z{%1x;v?3xReHg4=>EEasig8MksSsJfLJy?$sqMjGVRVll-wSSQ0TnO_pUu~a@WdHh^6(`uFPs^-7#F4e1_bFK-$ znwZg`+|KA`4YofhG_BL2B;wsM-+AXlBbvV|cU68UDl|M67M$O;uYh;zvS(a(d?+VS zx!xB%n_#_}DK_%JsFWl>b+`5GfSk`Wkwv4zX7e{k=<4c$?e_SF_Ot?>l1d6wENbP^I?x{16=0hP*~W1dT*zD?cc^i?URQvB2c z2}#KoY5C619s}4&k7#Hhs@(CME(;uPy24s6wp3-ZgfO~XoAwm? z?LwbjJe@*A`@-u*2`aHS+$sO#nts&>s(K$g$rxFfL_^>E z4jRz(6oD5wL_{*@e-UgYGa(jCjhMlcbOgg)b63Q47ul9la%{(G(o8PR(qdhqgxO1V>0 znm~Y_9gXR%Pn0vUHG*Tb9hAePKN#AI2!&U7e@0Qc%yU1Se6z)wTle7>`&r~GZLG$T z8STj;-cl87n$sjTcLUAdnUR|n0?Kk{6;w?Ry^=aM`j6}+8`)9WUiP>LWC&&*JWGvU z;0z=e+4NIFqZBqeSTtiZs=K0=@{}?-@t5LMBR%tbY|!E`e1E zOZMT5<_tqA^>Q=RC!8;I-)qa7cWf4xR54&ilDX{qy!09G$3d#^ZGYnAX&~v3Zb0HQ)Ytc89tv|*b0JR^fkf+nFT+MZZo|9H_ytmp`O6R=7c$(tFi5U zbQ4m4cHNnKv^}K}mNGvg(6)O&n64K#u4wwb)mBq!Vp3`R=)b5IFWAt-Ols*gGi|*% z{W$qxjVF#KfU4P!D}+LjH3|Iec{J9EkLrmD zsi^uRXrvPDtSOKQM0VoGQ|c0xp1)v;(L;>2nw`M=E;Z^UwW{6Xfu?zPe;;TFcB=kjwU zY6jQ@sIp0+QTXFQM-S`|r#P<9{^tuou8Hw|$W3`=R{59p!`| zCni1p5m=Rjh*7ua3&(9Tp4VHiWb|wCPPa->$eMYGGDQU_z??hTWrgHwKeq9A3b59F zu}KL??zbvle!cy@q7xoc*eNDT{evJPj4I2XfUUb$6X|nA)Kb}baF?wxk<9jY#A-z# zRtr4~W(p_(FY@y8`VTKk8VOwz`iJDi5k1ZvxH_)@qlWCmFI(U3wmI=TQAm|-MPQfp zjY}(_%L_tH6A>Td-&k;w{FayLNiNOu4-7ycl6d^H6Nk;k0lI2~W0(3bB)?O%wdJ_J zI%x#Xdnr_Kdk)bPO!~1mIexuZbxmCNd;xVI3&GUp+vw*A`}=6d1d`0Hi$^J59z@Db zY0{&tXMn5Yl#ag+>^;tkeWC21qWnzaDTYyLoa5F6za_b?M}cpI<$5QKh;?W=O#@Do zpscF;u}$x3?&!kC2Ds2lWfb=I^gLo?!_m>vA?1k*n}c4PksRh*P_YOTfR{5bV2oRy|!(7%0v*HfpZW$U!?Si(#E8w&%2M363bQyg~M zF6B8P%&irU)@#Tl@-@T!D10*V{rmUd$7;I+dm zHV#`A3dwn>BOC6#l zWn^dv0!v$v>bOX!%GH4rO%e#rCRY*o9DT0~#NE|^WZS-brzALp4aJx7_Ea?xi4k#e zf!fyB#FsGDxm-S`om+ID$Lq=+a1egiLwd-sH_^~a4SwEJFEe@CpDdiaK?7xLlI`{s zKWs?YC(D2U!GRLndNpb^+u#A?Tu{|Z&iVANA3p0~l4XCte^@@oF#Exh$>t)5ZUsp? z311liFMb5zB}qde_);pNcYX0DJ?77$=Z)=rm1orPA=3zN8xrA~Zh-0=W; z4VlkzllWxXiAwbH@J+SXM;aQMyJTeD^Q{5n^=?k<#h^3XhiXQO18#7z8ffr1hi(dp z;9Z#Nw})Oc2Lc{GAtCTkZT+Ixuc10}*~qUn8P3GsM45D)!ymX8Rq%v=YbZ+ptp@s! z@&2~^f)e16#VG;LlV?0yl5iC$2I}!vi2pPLgAA3pVkj2NPp%Wa@e;$^B#)OS+8ekv z6%_8ll4ba@zXzfvB0RkLXCz&By%hjGNYHl$-y!FFUszaZ--1c~oT407m5@#Q^{ZFt zV6&J#^7yFcEzBFk3fjWG7VV!%>>U^ivc~1`%k0M}W@rDgk7cRw^hc0{$Akl9w z2P<{gSLfw5Cz+5Scpr1VhHqg!=3G@ZP(>P~3q8LvKmb#A-7GRxvYIJ(M41E5i(|@x z0l|lS4S81`)?2OA2pjYQi30C}00JCcW%?X6t7yS#W4w(9G z{(y?b=kb>d2@DKBk#PE3O*3kz>_d(`0Mr5}sqboIV01KkU_cIfv4z7ah^Yy2uW3Cm z_Qb`WJwrGKnGV;?u21U~BQRc=9+{t?|3o1e12ObFg%p6dDF`(7CeqKqKw?SB9LqYS z{22UoGUYUry``n*W(y;37~Wz^(o!Oua^1*xKO7VX5Dq9hYZIeM{V?Hnm`&mHx)K68 zx6Es>c`2U-Hsd3N&4F*vK9O<<-F^5Hk<;_E>*KSt=}CF5dZ1loqFMo%l!fl!2ZI{K zA;rXjmxLrR zHkJs~WQ+jEC<#Uk)6ml111*83COJ^Q#(@5ES^SpU0#`|Ds|fs7Ut9wtqX@Tc@+->G zla)T%T*LG6QurrH>eO5EHZ(NHPv_7Ono`*6|3LQCTX0M-zGj_LZ>p7f34LO5004Y;@9&2F zfAHoDxFDs~ge&69mrfXPA{KHkf(Db7ni@Is=MU(Kh+(<_+MgF1(jH2PkdA{0NbKN1 zX88Pt z2jczsW*b8x2G~Aa_b}Y(K=te(Tq_I)0}b3GAJlIIvzD!rC7G8I{^XXg_kAn|;vW?4)O9Auf>sSCp@gkFrYC z^P%uV7;lM*i8-&t8JkSklq2v~F}^r>Z1yS?)<1jt+ADQ^%%~=1({%g4zgb#Y8LF~f zG^5f{?4jAR!t7&Pg#8hTC>cgjy1~(HzQwQ1ax$jz>ZtH0D(ZKNpWhKx=TCzLz}7)o zk_--UITYZK`J6i({^%%i+~QV~j^8qlXDs(%Agxo_I3rMaRVzHKy$r($w?>Aw7bL7T6xC$x@P%4+f?wl2R4V ze_w@W#6ehsE#e)-_mjoaOCQQ7GjRZB=O4jiTTHkVR8>1c=&?RsTM664?DFpsEDKyt z^aU(XppR5U1Qc|FR~)ZDy7pP{EUIYUW*xm={Q50*E`K<>PX>=W*llOW#@4jyAFK}E z3N082C$kd6?ltHx?%ur%bz~03Pt&2HA@;b}n=|zZGxctqfbZ4SLvsclrWnnC-&({o zv4F7$vz8voG*t&rYmONCX|3~8INeAnJTx^ZBP*-)ZSxIqI_%t?Z*^Jw762ugB1qJK z7(AX|UTy=yifoFA#|VP95;H_Rl9Gv;dFi>UfDdU^ZcMXm+Wg$lNJxLc(dMY%vCU|| zY;0{!V{n@FsQd?9D1JluC3becLbwz`ySO$}UkfX;0{UTC5#Y2**xw={#&M>#>Q96a z?dBcW+45mpn#pUcYttG2-3e=z=3_GDmEcW~#tK6g;MX>ZYseH8dpN8>H3kdFj? zz2xWEF7H2qKsUv6oBzTkBy0!pXX8+&57=@rp?%9v{KtJz7Qu5Nw$+g;VQfWB zS{D~Q4+j6y+tS--cc@YbOFlhEkN%xUSaCtzn!|#TzN%__vY&O7k3uY>hxC*(wvUNF zwrQwvOrN3V&n*FA?7G`!e+H7Yzg<-+ng8tyJRh@hZ8$*tx_Y)_ck19E{aVclyuT17 zb{M50?2&ghL($uqJukFB z=F)R&4mDq2w?`|f%lytx*atx?-lXoX%&>>2t1-83r}9cDmSVS^$33Eg$dk zQsd!beev>#{4f4*LViQwQXL}Nl+eF-Ng?d;F|S1|6p2nY`FYmxJe0>7>AtpO>klh- z-9^xXgF1!n{b8J6z0#_8r9~{0cmM0)J50SvOiVQ;n{}M}TR~zaT!V#o-EXlzqR$qF zRFC*^GYle?e*F4u1T7ae*xzKug|nRU4Hm<1$k634cA7Rv`Jw~DMrg&NLuX?3{`_!qVctsZWT=2WZfRlg$^4KiAn@S2dlnNED` ziM;$>;FK>QRUd9m#(w;WJ~j1c<#rvA4Em4bUP(#m1IzS@oL^z<6=LV|{rj~^WvcYI z5M1E*H(5v~lNBsej$|A8sLOr0;G#DFR{n0S)6NPUHYDfOSu2!Q7(}< zZh9$^Tc+Q1TL*^CpHoTYHqR%jJ4}T~dnwOfFEp-gI2)@(L^pNqtZ=SczdSZfTOmj`wMe5DrBdKvb-73-q@|akz{)O7_?9T>6bh;6|3|$bk7Y7) z=f1BTaOIENc*vmQWn%F~Mwla|ViCP!V_-O$h%j^2j8;3mO3ia#YHi>f8HIp@oL~^{Q3S2Qjg-stH!F>z zv6V(4be;0|hj_?eH5R=@iux=)KQ1|ytA>6e8)`5d#<_0P7=^-7%D$aNbr z_nxkU)`rmpsa4PzLW**ai*f~aheN+eOXH^HsZ-a*Q z#w*6eHp32_AMtqi^R}}$Shl%L9~}=$MUgvRJ3ZL(DvlSsek&Awk6+&}@zYU{*OFlk z#YIfB^dV@21|D-D+lOBMrDb`xJG^1~N51*~J21?pL`XerOAMSp~z<}DZVbA;U|t~BIHSc z=9q8rGlykUl_T0}8%g>2rDuCs-0?w)r*j|)ts6CSQ8(8wI+%aRiO!piY?9@4`-WyG z%Q|1rKbTmpt={pwtu2r_==IWfLnE0ia=MK*JxVA1A-l)=sujn#)wkrX?vnM#hW$nQ zI{5dXCwA`#-H-ikly{}P$}Z4Cb@aq>j@LS|^^2TaAhsJ+-H7exaej?O^64JkR>}p9oC0XH_i^$ z$heT^LN~Zpu}94Khx14PMf;&wJdpYV3xEUf+*F%m3z}b79~o7qtT+m+s)a6!xX1}p zGwFW@)BM>Q&$HdcCmFCL=e$1R?>{-Jn|E{JIcR@__Ul2_=g3}?Dw$dKTK~VuLPwHb ztf9a2W(|_jucL^@h$3RI1vT__e+cvEg=$N<-lYeRqv&w}95Utp8+F)Ck!SAlU5e(o zh!BnmpNa0Ce%B*pgk9|AoQg&MQ!31HF_-@w=@oAlGVfJ=b}-nMe+&%#27N)@-#@uv z

Q`R}!D?4d@xbR=R32R1lOQ$>E#1vd%sNh zk`pxv*rcbB$oy{3)I=bo+#2~Q`qei(A3uq=IZ15&bEN;c#r%>8-kV$2hQsExSJujf zWQ-^Tst3h{kcK;-dwh0%6X!@*= zTy_}*8KYCPOy4hke{i-(&X4J663)juBAu{3# zqI<(oEaU!Bzq0Gv|710Hr^>!0{U+OsZ{Ddzxw|*c`%~yRIq|5dQZ{CuDTcFxmp&FC zVZIX*zPPc2uAxf@Nyp>yZFIjl+%{j$Oh$F4^7`WqoZ(Ul%bnS&>S>Ct@BhebZjNS6 zUl*>Eie=GW+O{>VoV#Hz!n0qzGETK&F~ikhh9>@R*g#NvqDbMh&iZez{i=NYHy%SKdmL z+pLrX%1$(S_aq7s%WewNT4@t#te+DQS_GOsG z)CTZk&S+}pD=Q{STg@mHX7QyYZi*ps@vsSzDY-h!heEF}a<+swQEuJ=!BB$^OBd8r zD*$(|ZjP5CPB&CT|0NLxKy5y{c3SZ0XMcU#Km8qlTPq?NIIzB3sddO!JgGGa}35ijQ)iOfi|DF~Y z14Ju|4YYh|gPqVIIzwwiEaF}@5uT$72m@^-2FURN)$50B_8(pliU6sPKA)Z1UIYby z`#^g(+Dm|LV@nOF>lXcy*j+*^)5cJQBcneySq_Cbx_9#KmB;RdE4Fc`?tBmW1aEwI zQNhd-g?dk2qjl3CzQW5bio$Av>nYn`Eb3yJcj!y(7`bDVf(dt9WzKI%Bx#_BUF-9B zSiiNC_PlbvI$tr{K5uM3{WJGt0q4vvtNzjIGUw-+%E<#51h~P{cuj5}9zD9#zp_ew zJ#U2)g-=&vuo3_fE@a5`g70d<7H>AyZk*SKfuV2yRU*l@>H4Rqisb-h!AS$f*D@#p zGR(&c%*1mP+S=Nn_fjs@hJUyG!fAYmnwt8bR8&4-G_ldw4|lXZ@k?hCBU?CgFx#=1 zmPe{7p6uN{C0E($!Hpfvb;1h#{9hs9+Sn-B;aGv_@3tl@)95i-)XUzEWXng_Sb?`8 zKr?nAFMxy4ZCN?}vLkei5Wh=7PVR|1O!kvL5(3AN-G{(@&5Y@t5VL^Yav?=Yrg~%>y#d@5#JNj z2qV9@aF2iSH+RW(S#0&Byy`)fIpwvx6;_()c=z{KEaE9gis_nD`@Goj8-hdI_Vuxr@#XZ4*U-&!qCzR(WJ9OZ%kvrN2 z2D2?dJwbm@pj`1ZKOonQ{-U+KJuiE3JWayx zd=oDv;nkP<_Jh-^*^Vsv#nZ#_arep&j@BMdUk*-@#o<`NsN}5wN3pMEwD(t>_Sd6F z_i9=0zJKZYZAw^IV5fm2#!)7Y;8s3e&tiGTLuV%sfnTZQ_ZIK1&nJG%8HEZGAw{C0 z;Q)OSI2Y!D6e&737Q~Qxpg(cgo{D35lZlIupYF&EYkTDTZ*8F?CVkOdc#H7qg@ZbM zk#U~6)RTgh?d?7YM7f8_upU1;bi88X+)gn`xponXg8ZSqBnDR2P{0vO;ku_#%5vWO z0(ML22wz#)ngo5g=gYA=fXZ0_I(4WG?Z`5Cur7gGu61;@yk`S(r6xjU3wYv(HofNV z?qK`XzC8%M`|xKmZb`Vj&xYB3F}!rzd|i}>dN5S&p6u;DsFG$+R;f21MwKyYJ^Lxs zJDlBNkg892;9_R#OPM*N-GvcldBXh zJcaum$fRM~l2>cyVdlba2Y&^y*uLNusUC5eQhrK_#IznY1iJ`#xI+_=u)WI0%ltlq zIbO>N-=78#*x16vsXWS*FGUb}llNcVq-9_TM5=Fid6hCdTkOEe!0^3~sC7i)6*)3X zCBQ|IX5!V50X@apuKou8ml&IY+}=*cst7 z+_zFY`@|hRYGw$CxW#yh9~2|Fckg;<-m2g}EiEZ2fs0V|0MGaJ^W(6Z;=8yg&^l&9 zK!+OA7jCNP^Uqg)Ja;!&J-7%aWyI{&)PxskCjPxt=x;%)MC6mdCp(3XQDk=WEvODP zCD#f0Cf|WOz-Z$3dQC_$Ub$#*&(!EM2*k-xL^GqRu0BC<4Nq61fsF$A6nwn%{!cpd z>3*;TsdZ!lt!h-J<8sgZomD5L+44AV?Esrapy*Y+!}Pcp~7_{5~ax3UGGtezG~}+ET2?m7FRlqYd7PacAo&cqsvtWn;p#J~xPxG-*z!Qd`@CDK%E-DHiS`t{QG~gZp z0&VC!K}Bd_4}etq=$c+sge#>?c*R`=yix-xHrntoOkXMrN+vCipU~YvnZTu_EP$mV zBqV$Z+#B-RPzDy{1Tf+QV_w(k<)D7y6LN%4EASPG!fr7yLr9U?T^&ez16YT_rx?cn zW#G@G-2AmP6&Tvs*J2Sc|rVg4Iz@6m?PiG7~S}1rWSwM6K zPrU=g4GjmAo3L&iJ(>d zg#1+*L%H-;*Otvy z&POttspAKZJ8z^)Y~&@WCA&xat%nqQiPoU#fC$g3S=EHaRwu_KnoZvi@d@_e0-!U9 z8(*M8`+2SwN?wVeTWBVmb3-Tn!a; zYF;_>m)Y`Z0IK7IucWi9>%#i_Synrd*vD>AYC9qHcW}GkJ=pxf+d)mz16&D+y+|O1 z&PA(ee}iuGU-?U2Kn)Q7pSz{wpHg}=9Y0~Zm~(Lrt;5cr`tJBeAa%eP1zAMg<#b*R zPC|Ltq^$z5CC?$n0^aX~hLeImHT*vID680)tBa~Smy|J&TS z%m3=1k7sN0>fb3+?_IoqG%jD##kExod{xWZ$oEJ@370xEQRl)5O|rAIGrPxM2Oy|G z3q%8p_#V*ntQ1d#_j%pfNx*hj~#cYIVFA{@9O`9@hToe9a9YU(O~IucD*_6 z@+4|`=6dg>=lz{3Y>BP~u)4gzLV0IfV>=@*?rpqJjA=!-rBV0KWs6aJnZ}Y;e*QN} zdPO}Mb{I6=(odx2AN`0%e$<4m_(3;mmI^&k@Z%Ko?3j)X+t}q(S@Kh7t+8HcPyX~w z;=@*!GBKey?oVEZX-{LAua`u$C)C>?qjbea@EBZN@Ck?(3kK;amMheAM&W-VH|L%t zw1{Oaj_AE8TPu!hYPy0e613OZ;XWVb!;Y7Px4(4#?H=gzA|cZ!+fe5=Ld|9p<`h!!gR?BSpL6-Xj+m8 zhv|8~!ZS|;u(UFls1S8Um5aK`-y#(GfVYL?aYFfU*9z;K`It=>uvg>L2>A_~qX(m2hz}v^L9IhaYJ& zY8F93(HxWOkf{Fsi^Wv!btljjCZjpgL$-*m0(W#YS@0YHhrkIlB~XWkhK5RZ)R+SS z+^knz1lIxGxN+mlV?)U#S1h1^KmL8u1$#a4Q9?)>51IA2g`{}3{?9w**Rg0BzXkml zf3rtI`K`JR*uP}W+mLw^Jz5@dxc>cg=$Del-cz_RS@L6Zd`xgoAl8<-1cIS7=g09MxDx1d*nO{SjcAP}#G*J>rT+S%;<7*oq+Rj2U;P3qOFCWFVIG6;gm z1%0Wcc+NfOLII3TOiV;jS;TyDGbf0-C&$Rg5M>v_sdyPFzO`)kpQF?qD+s*IUmD*M zhkG%Cb8~Zt;H(fhSarDdBs4}TaS(NfekXUhDRNBfb@^80a@tzdzn(oQf2Vgf`lOZ5 zVY`2k5shM6yl*)Phmf=2nMAH3U9h#7I=hD@a_FD!y7LWW?b<&t0?w}{Rx{7ND6Sn@ zWkni42%FNRn3JxP987iinlQ>wjoOj%STq4V(%|6+mo0|I#>NhXZ>s~@0hHs9_IAW% z59}h7q4dC^V#uo>YiSLk<$2#;7Sy-b$Uu>%z!gcUA|RH(I7j5;qb~v_*&Em0)=wPU`8#v)-b}wiu$n?E9^0=|Ac^ zM5U?S4?c`ZckowU(P$S(;p--A5#Bo<7G3x9`gyxZ%lebvk9`x?%Z8Z7dXo0vN^eZm z1M?h9R8QuwnV4Gq8*7rNu6_mhH;q$Z5sUowk-Q`s;Q`JG&J;rLinyHvQI?us3j4kq2@*6pe&NZX*#0 z`1g9HoM__c4VzWZj(HAqY#J2vTRbH3qd5t2eaCURlVy?EP{_O#E=#qzQTn;g7W;18 z8|RUX1-0N{$5M}TL!?A<6qg0RhzGZu-VJmM37$SQvhFH8Z7N5om%5?DUkAt%&UYGA`9M)4EU{nWpA^9`dzJjbH5U*qC>r<2rU{e|CH(m+- zYKaJYM^Q6;oMRtfp|85T@?|nqmo12H#YRG6dEklcDrIk1k_z`h+gL7jV(&vb-sDBP z_mX*@rCD@(y^Lp7wo;-3Q(mK-Hfe6W!WIJRd*0^WVA2JQd|*h(@_Yc*3eW^{0z+DY z&b#+vD}x~gJ5ZgJUYZs32pLj@IcgAmJG);9fS2ip{)|FbXnrQ|DlF z>vkE|ip_{HicMCodZP_i0-3e=--Vjy57{b9bS0lpe?5%r`MSEomIh`Vj7cypG$`%+%UO({{;qs>=vTiJJ zXUzC@I#@3y(W)fp?tAIR*_Fg5LL_k*7!j5*GZxuL@x+fXzrp}Ug5^o+!Y=t;ZRLdq zZhmqO&AM>ulX)P2KD2#-RX7jYL}$=L{Vp@b16N=p$V%lk=paLT zfEWgY4fSJqIMVB4kxJno_285aq6_%0*~nbewENFvBdXt$?0w5aFTe4Y?X~fi=#23w zo4mzjO-QixRG~)9DIH6ik&sJs*p za`TOvKVP%y!><$8gPl&ExH{|_?t9%sGO>1!*i2(G*x6T&EguBX3vn*tyq@U&8t(z8wJd9ug7bnkS!B!9PNxR8Wo9Cc;(as#<8Rb zMRNu?%&vrLw5wNRugRp(?+CLySc{*0A6z>>k6C7@&*Fie*9goeE>=aasX+_4+#S2P z1JNf>64BTB;E2qLUZ3GyU0oskJ>iBX&3}8>SMPssf_j^(?9<1OvT>K6iF%7{_ut~9 z<*RX-ZFl<;sgyVlKXB!3I3i|r_;h~a=;$dB5Z)jVboFZKi32{yI!}DrW?vc>%f;#N zy}uh*bsfbAdJPx-gY#*HV~hVb9*~UGEF~Y% z>7BAji+2W(h(a?{&6{ONAaDE+V#u}isaKT*&Q+Ao)1ET@35}bPZwy` zhH9QmZ+JjY@54lxmMvNsr>0y8G#9#GoU?8f$#M?>(&8w(Pt=}vDd6z zuUPeCaIpTs*QcO(90tu?20~PV%l!eWDd;Ss7EWS6pIqI`&{zDDbApud@T0)X4Etw= z_O%G!WEg`9n|9Y{7#^Lh=(tS|Gy7R%Fc3SDL7K zx1n0^$_*C`_N0iWXxF>)fJ_>r97FlJYJt3=~fxNCx0gwL^Ug zO>?s6U(;LIr0EXnl28iW2NevQ;>Ttq^iVPkm~crVX343|$QeK!BsM~PCz2L9GAa}v zPA6$BcXoPO1TqpJ@blr%OI4X+3>&##yB}PA&5r!9;SYb*BWex!58rGRM7J<*vC`y? zf>6WF&5`ZYE0ZB)8pW!wq@@j8d@)|fKf3-3<2+(hq5qcY|FHGm@mThM_%M}*l1fs9 zq#`?ttdv5LP*#ZSRrVgKJ1T_AN>pVZ7_c-3ialDCY@)HJ3px+J*=n!ul+l8a?Q9vnr0`Yx4i2WPauBRd1XzH6?$WVW|GYYuzu}e1B$-%13*&Uqnuzuo8%^;ocjfn7K`%yR zM0A^wC=d%e{#+~D7yr&nd#=sc9!P(Q_zfopSaQYL#E^FtyNRKKSRn@DVW*#h>G=1_ z(ENKcS%jSl{y&h!MjFFqz&%b{Ie^s>6Sg28eNSgQAk4zD1+N2ZnWyLGbisE1I{%C9 z0G9B$@5=sN1rTTa6wvhsT*t4dtbnfPiKv*!IIqx~t(0m36G%}>Gt6ccAa$WBw`Fgi(V&=^(& z+UV=Ual1}kH8L{Fea0>z5R5)4Ll>r2|NXZCy!^Tz74^PN?C`$$_h?VGBcawHQjWDH z(V*T8zhd7M8S*T^T{D@>zW!SBvGo7+RiVM$=S>)Q!q5{ub&ISa8u3eb&H78T!wGFB z-|d9$@>knHgAJSRCzI%YW0*fxKFPN<5a1lQs{(iSR-8*I%S(oPtt~khhI;-NeThrn@T-I5AlEDfElnXWH_Bg zuEHP?f^@3v{8TaTQc=|@;uFNcD25Bn7m6uP*H-ZOSb&1@f|*pDt?iL;4-V^Kgy1kd zi;Q&ZarBYApOnN-l(M$t?c_&bANcRhQ&^xg0mP19OicB__GNv>46}iej=kEkFHBFO z*w(NY;o{oqUGX1~ThMaRybNnr?TxAwglB}<+D~S}uoq#8>HLE<+i_(mYim;!lG@&y zw0R*-w}6gK)=JLwP5AF8x<~Qf{#>r2<|>29Dg3j3 zBn^Ihy6PvOO~CUC=G)bO{`{$^qR3?84!AxYCVLpFlT6Rdlr%I@n46n}EnQ{R@e6M9 zEi&9h)ViR^+caD^hlfWPyqFNWFaE>lzz#0VLGh{t*1`?tqzh&xCAMObAx z=ICPO7#`L%)T= z$tyRwON*cY829a*SQtN3?Br-P<|rj4Md%dFHBV8bFGdN+AARO;LNho zCrXm<*fF#;8p*9<*JGx2GE1!i;m~ww@f_2)?SGGGxu`yl9?WeZJ;k*nxUDuZ=Nq1h+zn0%DjxIRyOzbt!{g+;3O5zDv782|Y)h)QE; zRIRV0lT(IHg_o7HyXSarULIljw2OvD%F?pt>Niuv2Z2|U+H&x(u?)NwsPMPP$K7)t z{LNzn5h#f&;1n`cy_;~BVQ~GS5X;=72vA3&qQb%C!IOO?G?M=!wU-&Oi^}Yvn)j;-$^p3cce}7#G`N^#2b8Zk@ev8YG;n z)v2+L(S>akY{24mMZ-O%ka>*7%qgz1Y(KJh1u|)%DZav$3s#rWYpc zR(@I*t^G1ZMPO-TGlWT|9uD@f7mwvA&`5Dn8jsjTxXXS71^PV7uI zZ&^AKM`7%!(!|kJS>*Sh!^*s^YGz59{fSWB;4kTh$Ni6S>Po$!V5wqqvH8JrY(ne8 zml&lTPRmA9)?9iJ8c&`yghZq|mDI-H6X-G&&bledB z6Fy#^m4;3IzgqNlITAc3x&-|f8U#r{1{BO)UOqlV6$`o7i$ulx+cwLt@4J=Qo+Y>E zJDDknvzphhKYm;)oL{u4QOw3!=C$^zdU+~^Ir-CjnYfC_;|cLT=Y_PmwMrZL7Edf> zQ9kD@-!5iv@r@@Sm)miG`s-EwCYrWU9CD20TR?@#&@FUcjDss9dED!8FK=%E3CDPO zpJ6yk@&SS;sC3RlG4>Npvb4rySI57aq&o|8_G#Rt*4Ikx zy?2&x!@(ZigE|`r#}RZ7)6*8%d~&vIGsRb{hl@@oC%=oizQ76ms1hWr9H84be1YEa zK$nufYKgDFRtG!!U!KR>IZkQVX6-XnS(FwXHTaIEEc4av^^gLCgR)L)Lq3a3bmwT6AN;7i5E`e4Hv1JtHD8wC^M$Ly~ncLJXEiLsk0YKBC8fS)n z0)&1IjWrHWTOoS9MpQ8`_;1%1IL=MaJnak+|9Igl6ct3#C0$onht!@4i5N!IkDHAy zL<80z4?>mpcLbn6J5(bw{eEM?OeLMeX~CZ6RR&s$;-QgeAW~|krYdd@Y^Ny9R?pR4 zVxeobn9vdG@Op{zuZqJI5wu2sJNHfASl4P@rg56xJ~jt8Wnm9^2{8Eor*rTCrw>s z<4QU9P%@Of2$3077XA7yi9J|Cv6$F#K0n?u2cAZB$2gdvT!`{YLdwbh&z_0v=^ep< z6G`q1yww2O^X%UtusT(QYLqb00;P}7yjKKWwPr)2$1Z#6e=*^B@#+E{t{?ov|CWFO zzBV$!+*-%*!ZCq^zK&)ehw53B=EM|%Wb%b@=&+L04JaRC>(Xj-3M@BI`mI*~^ox3) zX1}3z-843&L;TL^H905YvkR{R>z(aRBt`M2eD=O9vs}cqGM?$Tzl-#{-i++U?QyA` ztb&F5*yxs<&?9PjQRXP)U3ms`dZ)Gs?WTb{JrUacJKc)zUiVf`W0`1rob2>qxUB~(STP)=rze=RsC-c3^(?I6kYExFQp}Z8 zG1TJS6#vR34_8<hS`bX(W3=)<5dO2+l@O~g5T|w|4}7ZAY!~vYjv`Yk5T>k(vyfd zy_xZB^XYG)jC|%9TW&9K4Ulf3*Il3GQ$OZXF~_I;q}Be!`trifFe~dcVfCeA)|1@6FV3pvb+ZF=DI96ZY5=~7zp-rn6ssttNdi~d7YXn$xc5$ zM-rA>goI>yIhef=-TmInO2UC$_f|meA|>P{&=^*ts{Tq5idqC{or282@HP1f+ni8a z6DALTs)LhAMlDbQY@?vi2T=yxMs#xJg!#kdi&jiN6)lY8A4-;u<-0`dc^HQoKl0hw zf4zWlm8wZI>siK^3p);Q=JVP7;O&j%i@B8;wRVaxB>yw-RF!$Box^TQn}^FQ6g#ZC zW%;D!Pt7q~Oz(Q3fJ$aj?F0Xhmvdj3b`;HqmJXZh?4_Z5dpk~q9 zvCjtUlJDzGf0h}gSqe7nl*dgjc*1(@VLsq{X10Z??Lh)xF`Gf zv)0i;X#KBevDbrZ8{que*;$i{GZ!!rLQOsJ{X6!~i zX9|8IHR@q`?%8qc_{b+)CR2OPg2{cLYnMJpmjN#%hd44GdrWqSrn%g(o z-&*!NY<|g{>J?!wce(tKe7IlJ43e0Ah{>9u-X_zXYy``b4f-(sDm8w7lmOj@-dM&rrQ^Zsp zZZ<@fP%u0X<<m3Xo( z7aMxzZc(RveLH;P5T%jD{ew{^P^?igFf`%;;1P2PpFy4`yVk~qVwwr<0cu5(4+vv~ zLjC5=Llkti+Zo&wrv5!%fwzEu&@KV!=g`PWlWdAQ4LvhDR%n>`2%O<^h?NwbR=h^% zVqHTC>GuWUI}xGx&v&l6{`!%q@s&-NS!YROP2iYcv-z#6jJI;R2h{tyUFrm!lT0-C z8`0%{*oA&7v$k^YQ6k-3xUdOX?4)V?W1v5EvxVf)GO(&|4hawcP2GdbBQ!YsIL%H* zLLjg84>d`d!@~l{;|eY$;F*)Ab`$^QyqK#LT=>TY(1mZM3j!J;t0S~-SWoxGcS`os zrT#lvdEyfLN$i9dliKw4Up(^?l(110@;Ufm&&h0;-3BaA??aU4sbXxCIZd_Ha8PoyyAh}q395X zg0V47OfFS=l5yyjvi0!=G-!eEjAz5K}kzFo%Mz=Aa>CKC@{}zvl)>gpTz?_1`ZOLnW z$%4{Ht!Zd1-u3mjx}19kpu8 zs`X3R`7XVy%wi+UZ9)-RKTg0Og34`+#fJd_BhtuZ)!I+XQ@>vjcddT2(aqOai&CGxV=Cx3BMuCe1$io9& zYhT;*Sn~*t?zh`0*eu=7r2S~oC;WZ@$XTgee=R+kd*My|?bEqqGghsdww03Yf^=6> zgkI;}sSydk=oCa@C?I?c@|^w}jfxvevHAotmNiP+#cyFD%iCW>UdPBQ4ybWESbU3_&V=Ia6&-?L$EyGm2J*~CIUlB zLUIlYQ<7eHYrI_R<>*Ak-J3s<7{xVPApEk`T%!)tnE#pA`}Og@9#OW}8fs&f#@oNsUL(F)j*_TRY(fnh(+1xiVblL$vK zltd3e3Is|>ln5|lC2-xOaf;9g=}SVkO5y(Le*gYP zaNgL2Q(DIWT9Y43>Rg!WlY@~Ys79YA>|9)2z#E0`hW3<6jEx9f4vzjoJT8b@ef_|M zs~605LkIO8vL#>vfw%CWCG39a1aP1Qe~fpeN>?@2)G(bArH{}LAQIS@3f;Pr`O{QO@4=`z@8TSa+#^+B5@85tQN z+Z`HGwEO;4ia{GKO-_D3`|~#=wRLsup`>>37bYPP1^)$4Yy&EE0QXT~7O@dB05I?3f#U=i z{Ft59`C|m2QfKX-tlT}7v6X!|Fq`}9g= zTu#o{cr_H6lz`wB^05-=UloSn%Hlk+5(xw;B3riw{qN{k_{vVu9Ez@x~=+qSO8gI&PB^8#U|~ zIPEV7uySy4u)miM)&WfE4FnP5YT$1+S8w&nL|nk{qyv?Hlq%~pnOhjgrXbn^$K zWRuk`ai9N|29Jc`oC`~bdnGpy9XYbe(9kfJIgko75AjV-F{6OUS#Fq< z3H0?32somz%1Nm55o?!#S%6tWctH$n{&z@1)e-(}aLgs#C+^%i1H74-I3svfM+WX% zd?Fn?fWCI+=b+y(IZuAiot`YLj6H7~~ z+qWCPZvHa(0#ee&B z-%C;-J*ri;^U2B}9od0OT#I{b(tTAYR^PxN=(f8XLII&`MpeD}{<*a`?($EtBnA2# z9Q&&{Yi0(|IsD$7{+nA&m_i#KKo{_bL%ct_`B9t~zk4cq@%oxD$|{&gN1|6p_i9q# z#o_P&tUA5x(&ftl_-XGFHhfq{=Bq*1N-K~zRtQA6rmjYN!oH6*&AjgnfJ5}Ps#8%r z>Mn_uzG621bnu2zN3!s3y1t=Oak}^cowSGsiMDe3VzZ*RAA^d^&FThp_9cKcX#DCl z`_ua;JT=O<`Em7(4ZSX$`Skt|cq3!=(>{KJQ@MCJ>`bYG=|29q-J`=Y@yY^>D&U4X z5<>#a;{)kUp>Jb!TRDN(q{MT*f%x708^5EGB(a(54HvW?!F4eCxVVq$TPAWNfD4x% z%j*o+E&rV}UgTq3&Bh4PIxOqnFXE6!ygN{ChGsh*^hef6PsT)xw=~@8OqV~9t`!trWUxtOr4ffsFdSy{w27Ucbv2RU72zESIt#XJwuv3D8Er+j`qj#xdjK4ng2|eJrfpi0B{vgJ3}@}y@c%Ll2#}*tA|v4cJXuc(SPWhA2RIjK zy3AD2W|_l=Bpv}6RU#i;83=m%ApcCU(}FUP^Cc(~Yh5QT?o)WCj%+f@8a6z8;|8s2 za=S2Bnb+LTG@H`>K@3J!gKpQ$3Yv>f8!&1QiqT9jWJu*+*xScFA$k7LaMJO|p;-)4 z#khuit7E;OWrQZa;tLWW$vs1FZrM!)3eT49=B1 zF>~IHD!AAicG5d-`{t2Q4iScg_;cDqL`R@+o_urd0?X&hHL+iPw+y;O?VxQYgf6h< z!nx80=LsfoZ!~r*_r=Dh(Dn?_qTmERdJVAfUwaT$xWvQN zKr&gmZnj$bFFqW#532D}J>GOT#3nz61~3zFEF019slu-bmG9p{Girqg(tSq~H@djG zkYKj~W?uZ*PPi?*i(^Zxu#i=p#CA;oVHQx)*?EA5CfI6_&Wtbr&BvuSnV*pfBEJev zDhm=QkIp5!jI|dsc^2tjUyoe;wDehJhppGp{o(Q4q0{buMK?Ut%K}NeB{RPizlx!s ztXmHW&M+vs`Pk|GtC$ZmOKoIx=IkM=pBt7qMy6Q!cTLYSElf$@Jhc$v>k zWWGYEm(uLI&}q4%;(aZ`yJzQ}T{2p$v4Y^V_Qo!%e$qkSAUYYtBj0A`1U~dMgu8vW zD~;I3s`y|SbEC*et%dE;td>w8W#~Tvx2dY6`4NnJjM_9sNZyq7+hvyJJ@83vFNaEghwp&+yjYp!ax z#vEZ8j8o=oyj16QZ~4~(r+Q_lM(lugy!w>$sgg@|odY_wKPF{Se%AR?ntZtH9u-Ai zpwLpekSz8zxtY(%#(1}~c&pJ4_Jz0^F*W+UN_h(-x-P%_qjx8nb z(eGHtb~yW>J86&nsUpf`<|FGFLCMGOAL#wnG_LNWRsV2sYPv3U1CAeN76aC?A2&@e zE>=M(^Gp1rMJ(AT7`>361&&8Be(v47ce=YI^qR-6S~zl)>G5J8MH5Q*5LA%4a};#^ zXn-R}z;yK=ej0eBj}$yf@R-sFuNbINe9;x}+OvmPMH=9{>28l%71%vcO%l4-feP=L zxw&gX?YEDrC%cqmiDhac${NELt(em77C#L6jLv&o%5dZ&3Jf(akIrGs`(^I z`Tz-x)u6lN>1iKOc7@i{)A6MIxUuW~K;tWBW~T!+O;;&QnQVT{4okW%I~6i;9te9E zSE3|*ucza4xT*b=NrG(@lf1nAtfQr}$hLT-nbw z|CZFJtudr8gWqR2EjO6cZiZH;>ykBN3cFEc3>0ia$VTV^}d)vMX*-7qqnEMz;g$h z>8}UMqt>sNbgwm-@znOHg9%3`V0J`IERq;FV`TSW#{nz32&FF68$lBrfumNUBdEg4 z2BL^3q!|w4kA#1qI3)TINE5bh+xE&**P}hGW8=T{%Tv5EyB!X2C_3TCbP;78PEEk~ zDzs9+`&h`2AQdF4UkDv^Jyi^DXMIeVs`(i^hu`)0*YvQM|KmrI%J z41NoA(RnvL`^hHk&gxi`Ys2d&%1N=9!0}F zxRRW`N2E{I)x0*X#S*h5Gj+)Xi>mnZ8_5q-30iVc3TcS4)Kz*u%KBbs!)))9Qoc{hSw9ES)kqG^^i`jip^Nk?-X`Cp>gPvuOW4+ZFu4Doy1he(8@(ER_!hEe zNBLcY^R8ciSsm#Q<>e{*FuNeoN02xO+D%;Op>HFFX)yqxFs0*l`8{o=|gig`Bun zyReZx?W9g z;`Sy{`#G<~$xsG*?M<5ZO}TxhUv`aGkB{3`?4gw5N~5)(ef&(te*To7ELumt?a#Df z(xuz@^TcS+E@fHOl9o$UHE#AyWvezrVZD_qb-~jTUcR1g>b2$4c86U@xd#(%{bq*g zqPP9hJ)SAK-17MH9q*ryhU?lbn^y%ji)Py|a%%}RR)jiQmtJV0*~FLN9buqn#b(CL zSiHd4Y%%;N7gAf@aHo^M_^fw)l7*7iet4wZx%?vVUEqNLMXB#1Uu&J1Ncy@s{ z6g#+$X;K`PTnK4|kR1<&*pOfo4AAao(s`45>{oP9DpRtO7D%o1cV7%55gJh z%a@xrdCu!`@12ih3tAr^?^hSU;HSB!C(HHqp7{G)ckxOArR^2yQYhqoI2`10d8uH? z!9S>Ho1;0|6z5lEyNSQ(r>5h$Zf`_EkV@*ojx5H~m38Mx{mNSIq~^C_)wRiV7NNz@ z<<@8~f9g`1HSXEEKmRf1ditX^m9<}?n>cjK)ShQo*Na7H*5$|EKW#KKArS1hmLBk> zox17wNapc=!fKU$)%GKqprD{WU4PYYSR2vra)d|-+LPatB(gg{`?uhwPcJMOfOF?S zFohE}uXaw@EjL`L{J&w;F{H`i`6}^8yg+WuvnW)ACtAeyA+}2j?E24d7b)_b<%#<_*1w9{n59^pY9d5Vg53`wc z$a?+xSxZE?@4U@1n(bLR&SS=o-ZUY@5i;d3A|x1uy4CS7LRo=l5iLA=TR3hUsasCT zK&wX(GkkqFOV^(J4W+)X8LKqkO^pSCfD4Jz6#t2&@4*@{qM$NLt*QUb-+g5A4g~7IaO&ugyJJ2nWG>U|?!W z4ra5Q>dI@k^P|4~ofRukz*O)N7|FB4b+89|cxUz3ae7Z&P23t_MnGuxK<950<9w{s zq$2u7#IWVC;|3@!Dyp9{kM$M1`8+30+!WfN{EkvpL*pO`DJiMQ!YADylRfJFHsX(* zhw(Sj2cvOu9C|8+eP_<+v?*#4iF<*{^q9rx*o8Ulz!h{gtEHJxBP!dw(0G~KDjA>Te1NL+=j|q{L#%u zgp6*l)=t9q5*R6}L^R5CrQ5kbhIZrzPxcLK%3>={>gNl^DZ$!of#iYGN~zn^Aj%48 zAdx|j1_C4i>bBtn!Qzi*|1Z8R7><>7_4Px5jxot)ga$lEEt2yuVo$vcIvF7n!Vvg9 zeDpEoJ^o@8Uz!OV63i1KTxJQxIMyiQn?aUZ^Xs;gsd1%opi(F&SF$ZXXuA$Lg@|31 zl^Z@_Ab~3MEC$V`WANI)jm{Ji2-Z*~bmj%;km%5Hg3Nl<5^OUDeYm`h3ZY!3RevGL`5vbz`D!i{RL6t%!&{mhN8Vgs{S%zYXSv5{2 zwyQ%v&up(;zI?fpBP}ZGu#nyN>T7bM1O^8O00OF4%*4OOcm$J~c#O>;-FmB*+>&nM z4u|B7fuy%9@E9S%P+XCX8u;F|n5`TCUFULOKVXRwq(FENwqhJDF$b>-!XudQ(FF4I z+I8ui?6ZKd%h@Jshp*S3kM5VZvZD4eVCL-KdVrt)mzmQGrRIczM$qeD-(pOIFY=d> zkxCpwU?eZ0hakQmD*?XYc;fwqoN*OmmV7KP^ehU^=Czcc(nYiD_e`?#Zn<5;;ggPy zN=VqcWy==A3cg}i`j4{0k_HrPM4j7R@0zpCiCclGxYsZqCKCoxke|1}ZWdwRsQt}a zJBzWg@zCd6IVy9-AM=yiE_B|444`Me>%6!3z>=K2+?hv_7meDp9|vCAve=EuMIHHH z7#ove!dxO_<6{!Molwzk8_@$vX5uaU%U(u)%&uM&GHdSxOF zv(%>Yb|jg3JXFpY>KfS}eEDT^i}q^;blIJIs=-Dk{Q#e5l+!YBO08YpgQ_zUV`IEO zgwEsdI+SGhff9p$Q`*jMfism((CP>RN@|>_OFvH==*4cp@!U%||-66fz^8{jcXc?Z@AL_WNn0 z@AjZj^xnJqFWDaWdVzsAksOJ*e*5#u9h=V=Mkvk7NbLK^1vu``%FWNe&%fpZ7VCie zWAOeiDYCRB;q(lPZcG_mtjgwg?%auBJsAKm2-bk6Do8pIQwueoB{Sba7IYlemxT!I3b_YGht+DmULSaAe zJdJg+H66yi)b<}VEXY*44OwNYk6PCB>_2Jy;hJ&Mi@?ujc8$h@qxQ8$;r??q^#|$; zHLh9-T)%KmqJm7@Vk0%#UTxLU#VtZ6p6ooV9dnU$MnTJW&eQ90O)cj=>al*l!LQNA zy#J0|pI?RnZ^#?6G(fB0+LEMDghNMwAzc?i8~E>fmI*cTe_2;oDBgoo2FEG+O(P}%XpeS)abyQ7gl_ewBvayE>}j!_;weHIfBG$O>4 zhZjJv!F9F_|C*Y&tfSYoI~+fC`C9~gKJysmpBwAb?9ZH+;-T3qHX`5Vt!-X!xb|>k zf=a3RIL)1%Q8n(jf2=!;B=wm&)maynj#^ylY%D@^O%ZnI6%nQNYx2#we3k6vGP ze|kp1OGn#0TW9(|mY;faG%-c{s*824XI!;K)5Uh?beL-;Vxda*0;w?Oc$j&$pSk&t z!2xsm=RjuMqgbH};~{kG_wV1PPZZBTQ&3e?Gsf+vV3e-8h$H6W0}O;m zIf|ziY=lLmFvyHcg;WV)m*1@WA|CkCbQz^F+zDhe6m7;ptJEmE>`HgzK(e`cQ$$@b zVCHy`e%u_3SOp&BmTl%0h&vE1rdE=4@62iaipAuy0J*9YQ35U5aa zMkyyHBK0jj*exz%g$j(=8-S@g#wOoBChCq~q;790b2j6mT^R51Bc`~hUr{NlCAU{z zGXM9G_Q3rMR%g)2^M^M&4A)X)nnciIz^%p5Q1%yX&;21&mA!TOj%&6Zi?S${P)E2q zwb!@z3HT3xk(fGrN+9xvb%M|nbNl#d?m*#5GkL}8^XX;O_5wK$I((+g1|MFH`ovB9 zCf^TPZtGFz$(+y5(z{;pRje19&P-E&w&%Tk*}ECvdCX-NCK|d*dQ3g~V#V%`y|FD$ z6th?Z{4wQ5C>T9a%|Ta?)jN&}Vmwc1@6bQOb|?bF)UG2{(bamq z3LPb+Ds&X5EwVDBQ=*yUx=uD{KLO*Ct8qc|CT=lKI>#!kYE`sx2Yp@gFAOv$>U)+r(Q14y;9YeACDT5X8#a0?H=IQ z(l}mQU;lRBd(?8i{{B=5s-TC}@@!)&ckP5Ltyh$Mv?RrWi`F!R)j#IT_Vm4kI(8GW#b3o84M%GFr#{yoy1muNQNf^Wpe)ek7*UzY^X43j& zdxY)*-pLH5kVW57ThG2*YYCP>u=P!jfl@bpsg%k z=Zsu$Smb6YS_{icamZ4>SAj8d$?t3Pdsaq&7?evv{f?K59YDUkt(=WZ{%faINv% zlhsZU{4F>N$y(j&&e@HbDMVk~g+KP-$7fvfmAZ~DagvrF9s@R}$vadAn6nnE0ZU2s_c?^^W~!P>o>wz z9L!e@G`YIUG>>8^nU+PVia(1W$!>xRR#^xm}IVwnE!3E|c#Bau!gJ2hf z$Kls7&G0uDk|2s5L6wP(Qy@VkfcBMZc-nFH2>LQ4No-CA<0MViN@Qn>mh59L)XA%#kQ@bMEKUBP83 z*P37I9`YC7JnPc;&BpDU)-Cm1kM1uoyRM|XEu%7zq#b&|c&y)o%OSAOrV*SBb-8=7 zn13fJb3%Fu+1yF<-hytctxODrP8Af>C+LMl_f>>v%;xNzBK8~Lg|lUW&bwElA5#v9 z$_aCw(4n8Oru&HkHc=&&!MtE*Ga`5PCEu!w`H`h6xyX?VR=n`M1iw)z`*xk0l$2(| zRV%5KLWp^|*$zX>g}Z+>R+<`6!7Eqi&qBgI=}hZQor2}VLl$cT5}?FSn|$25g@y*y z77LjZI~0FX?7xsDA@k|Yo1vBWhfD{TO6wb3er4ZOHYA(7rbFUmmMP~WbSklbtKr~a zO+}YeMf&tNgFCA47;~Dl&bUp{Ja#O*xO%C0#%<7CbT!t>{451awPit8ovVUxNOx$) z<{b=|Z%8c3DBt}rx{qhAVXKSguK>aGFM@s+=BlWqXrv`_&K&V0bz%U`AmCWO#eUa;l?~2+?56Q_Nea;-Mtas9}Xot0&l0Gsl zFffv>RE8E`(f(PUS-_h|WfqeDW_iK-&SfjZFWm=ruxY=Dam_aTdSh;j@(OQg@Vc>f zSR{*w@rgGpCTCc>$i6TzUTMyn(_Y|oNpR!Zw>rL&Co-hO$cKiZ?TZeUKo&*RMTl@~ zwr>8iVXt7$phA_$kMb;+MLFhR*KXr}#*6u+)<0P-=($tFN~bN4*MC@Qjfo3sU2dP9 zDB$jNisOrP9de9Z4>F?$xdv2ZjCeN_iEd@W+^wvsiPF$;^r0Gr&o6)GWof3eoH`YV z#&`FwT{n?juIlgaWiB=*>ei^1n!3943VR{9C~1`wlf8KHku14<2a7Vlaf`csuOrA? zLIKVXs@iHySs-q*X*)?!734 zpvmbMdD`+n$ZuDM=NER~cCZ~&{%&wZJHm4Fx$WMJsmjGvOj<5=G_5j|*GE-_pEs@3 z+#ee@U+SOOy+U$YabA2m;^~wVM^wja&Y{u0#qF;u&z{lB+~4*#cV+q<&-U$RZ>|;F z9-89#^=vUBNg>RW-zc6e(;}+WtFz>dZTs}1=sg>wsfwXk^+aA%p}Byu9qKNwWYn_G zkH5&QW?!DLTkl};J1D608>f)~d`3)WaawtKK(TsFfItL!6J;r8xD1%MrQ61kk0N~o znUFqFP}KUUl2d)SFimu`YD5Tfa=O*3;TXhNYc>*+TeY{t`x03KO$11j0Py0w z`Ti_buf`ylQ1sy=FXtLMgy99Az?$)cocm_IynZ^4T^+Z^H*pX!F3oR~nXGu%7$&2r z$p8vTdlj$=q?`izEOJh1)~Jbwx>1_U5KykUGz*MeUCJNrJ}oGy&9gs^yWcCeBJ5sG zfH>DFJNaA(rk1RVmh$w9JNf5-+M3! zj$$PT9x1Z?J+olCbo)DBMW)-xav1gmvMB+N#ZrUFceXLSM_CAqimeL9fJq(zF@cP! z5mOByqVwG6O5%fY=?E zTtm#mfE+);fD;6brYB72YemoNTNXOrkuqfYEG(QZD*W*C?ayDvhAz-cRpnoCzco6v zHRqYC{9UO`gO3^6@gdR@_M=@5Y6Y)S3Pq`sa%QhBFLc{bJ!QCZ<-+i&KM-()!|5MC z-n{ksGBGg`0v;o+&t{d5XAk$o>7z_7&*nIIr*Q}sL~Y%X>b5AL#2e`7KzQZi&CIBJ z`}jztcKC;fHvrWjOulMu*AYU2Ac0Me*;8pnBnCjUtV_)u$W_1O4qqtC( zzq?UB)9&_&>%svuv!mT&P~FeSL$Ko!#Y(VhAb|!s^A>`o?~bYC$Nqk*9l>4FOeM68 z{KoQ;SbB$Y8^<1(0cE-^2s<4+0y}H2#eY>4kN!2={;}lVmBuS!9IQtcY6GiY?+OYH zl>uN*Fk43&rdeKpTPgFc(u zF$jed0!m$>;NqV}^Vsm&aIAxuJs;Mi^^T4X36oP!gJrCnc8&~Fg+w;Vt5DCIvz3bu zU+sP)mRgOiXyDV6$u(^;7S*!+-jh%4PWj^=0cHA!Bo%Iqrj?R<*8`!_ci0aM16+2@ zE^&*AolLmeuV7RGnZCY5Q{>b=+RQ(~CUMLgCMPL*!ly{FDGkR4Y_=G{v_@H< ztG@015IykgNX;+bqjIhsoGOrNhX>2q+&R;^Z+qMK?>X?y%HP+}G57p(oK(hsLWhhd zkx-_?VjiPC9*~`&hi0frmD)Pv@CihHP&6nhDItqxj=#D-G}146m9-%7@8hZ#w*9u( zsW1ZF@(CO$%nsF3mQyuypTT?STJarM6!4PZsnprJS%M*5TGF`FkGJTUV}(xt+?ynl zx7Ic>{+U#q^fGI0pUZj~wg?L&c+mR9UCs;)LV>F)cNco{!I6;%YRQ#IxENTf6S%St z+E2uNfSZKVGeILpy!$1G%A6<9dJ*W$I^gXS$) z#Wih%LqY<6t4m8O$tKic=4>umxn-Bqx@OX{M~!rJ67|_-!QXAab`wv9fE-~r`?A>x zEy0UqOm{btfc-W3L!GXOJ*^?@;%9#l8ykC_u0QnhF-**n za(^x@T^clIy5R8^tUbg^+zG<|1he5zp{pKGp`$Ii`J`Wz^55|~qEYCC*n z=0ELgl(;%Y3 zb&wjS;PQLPgoTBB9R?}#?57CuM-o}ZwEYdCq8L7NZrRdWu7W37S-oBGH=Io5I% zPY{hE9DxG6rog2?4j>hpL~qGxCWz@VsST{_9Vh|S#NTdg5BA+m{>;1Kxb(fLDL%=c zN)&p4T!!lmBHt%G^KCpa)_bj(c$LjmGtZ{RX#>ZJ6T4|>XpjSd;NWQm*D~OHG5|`0 zdLCP-fO=mRd*yIc!5s9oytMf4*r$p;@$b!7WKnP6qDD2wO`}VcSLI} z+$g>yia9727GvW~Ek+mDxt6^i+OK}=j|?3(ROe949s8_jPGh(^VN!#KU8Z9$=6ccr=R=tmxtWjfUP{ zk5E^iy>n(FENs8awV83vU!H+S{Y)iZ!((I%+XI+!U-!O=1?K;N%Vwin{I6i`%utD^ z)jU4`@y&IAtk_ez@>Y|%%(*s**wafKe8N>6xB!|4aKZkAmk$n62a6K?M|fR-+2A@| z#Up9a=M4c&HyHo@U?W^wbPOM+Ml;hIvZzbtOV|nvcTZ({uE7K++4Vh~UgThPNm#t& zMIqz_UB?<>R#UI2G~?D|czrmjxPHB>vVOUt4;Nv`ef_~=!0tb1ymSN&-@PbS#Z=93 zbSPMgtUlrl{)jHm{)-^$L%R&t%=cR^CKp9cU(-2kclK#T>7hV!Ekl;tRL-^4M03N3 z$4|`oreaQh2=){xApW9$=v)ZzE{sTnu3g2~p~FxQB?~sP5leJ7Zr+iGTc?fdi37{A z&l|Os?w`QmF1v~e0O9?GVhS`M$TjuzA}e%Cj80~NCIM!0C0eVP_&(;ZsJ;NigyF|s z19YOOtUl}(I7r9I>Io*@d0`=TsYsqC4`kf7Dm9$0n>f3LFmG-JdD=uWKitGVqE;O z$B%a)*up4}qjmMd7_qAoYU6m+7N}+)nzX&{6l&R+Yc=dOzv&a!@mK;R-1i;{L61nn z3cnYZ-dS5RM0E4G5+4l%_;eFaO1;}8|_Gl0$R+O*X-^CuzcMDPj^qk2N{B7rx`-9J^G;i%W=5iMNq zE0U6%Q5(UX1&^*8v-#WfH6sWiupl{d;>1(;l0i z37O2s%|t0=K~O79p9||H+XIZJsik8{i6JT+m(P2yj<9eK+heA zf(bK$qRWfI)+0NBC17$lP1Ja8CW$zXI3o;=@M&4X8V#h$XuJzTR*Kf^%(L}g=vhn) zH#;JHq4iJhID}5+cU`2w6~uL;7Bf_Wiv&ig7u0kt&F9i75%b<@j_2;F#LG0pu61{2t^+eMGK* zj7d~;$_-&$ER=)#?>aEaKv9kn4M1T=poBzWoVi+lzpKz$00#{PV|lv*gBmok@a{mN zxZwl-Z&OB|KxwZX@2m_$BibxS_C(s-&c(d4XZhGukf1VLgPHyj|)OdM(fM-stP+@CQ2R})lPGn8Qd2{p88-;^MjAL>Zl9@XRV_f8mPo15_DnR1G{++;4-iG{=FdHKFU_gL@7g?(p3BnYrK*D7R z+p0$qx=|qKUtZWl*#z@w0P`(ie4&!m89%+q!NO9dMj~wUbr){t3lP$pp_`$Lr&^S( zc#^xU$~)gKuF_ngS`-;K>>~m0LtMs;%*GnsxS7$VcR_Cy2PhdvG@+V3|ezEkO&Q-AB^6&2BjupXMaW}Aebw^Z~6lT)n9 z3sYKi=QS*u>PH1di#a=*%XgEfni)uDpIH%R;5wF~>smi*GnYSKXe)Xw{qUHnlV+WA zz`mxuqgvCwQsm36U%oDM7W|B&q`SlLpkB$<2JU>@BovH3yd))7N5R7t7mN>+%?hKF zYB+scwhaD1T)lTZ*ZuxKURS#`NkvGaAz2}_Wfb9RkSKd)W$)1-Ar#3XRAD=@~SI(>0M31z{JywAI=Uxt(CN zpIWdlu{?G{ZeiAz>EW>(^?g^n^Fq`5_VE3txVjXfqp;KUk3?Ro{obt>!pds5=c&A8 z8YZT~9O}aq!@neq4NoNO`(dGRXUo3%@zJB%v$v1T)ffqlv~%1oU#gvSOYLV{m;CPh zmH;86^-}8G9t_6Usx3OADroISbEt+LN;sFCWS<5-J?q)d`8vca=eQTB5<~0%*shR@x?H(yk>q!S z`@4gZa!o#J_d7=)3U9`qco&rqF9ndk7}5Qd;?lzS4)~8&#YMRq#S6zid3H`RuFg(k zrWBO0%Jin_jTYBduRo#PqAYxME=prh%6(P#f9r00rA_l+*>$D-_$om^L1k&-=-#1= z7u$S>jKv~M*H4svxuDrUq-L)pB(ZQ&FUK4R>><)1p+>{5wvYL09CvS-zV zl$@GM1>5XL9agKEq2oGxnF_>}$`zb70f$M&E7xgEi}$^pc=UG?IwGZ}#)z7UP!K`F z!x4Pqgcdi`vZE6aa6i9dX!Ie4J`*^^H1Zv6T+A54Fdar{v(MeTcO`li%TVCH^BnW_ z^@Sbp`S5C#KtGy?HR8)&6n*mUqqK>uw#$iH_-Q;$SsnOFd$Q?ySIsqt$fc5?yjYXf zndP;WV{LqKa$U%t9}EjGw&%8c@6)Xxyz1B@h2Up9p0Iw#!uUoUr_APx%Be$qq&$pv160d=Of;gP`q&WKDhi6Kf?PmjekpKv5HM816TV+97w^!Efw*TK!J0cZagA`S#+`wG))LAed2!#}x0s}t`?i){q9C6UD zcj%PuTgMktdxXPm${ZDj`%-nB@`0M(^_L%X#XcHrFkqoHP8=1@-g!4K*-wRE+`<$}aJa&JEXRz_r zn$WP%W1Y#FsR=pfX_LbjW{mrIPwc-{I-4%5`~B#HvbZb`6{WoIKcbT}r+Nfy847|k z>5tp+4ZM_fy>Y@yQCl8rn!N36+)U{ao z%KCB9eV@3BDeZzwhvn$aop_yZO!jijC|2E(>QwXEU$r4LU&L*lz-h;vXs_o+rmH5( zGpz%bO@iC8?(Kj}q3KI)fs=8@2ms(KFxp~sz<+dcVcW&2PWUO`GsB%S^ zTIM}%*dY3T@)pxlNL;U@qMKjljWkx)9V8DZF+8_)8X7KC4 zg8^l;3T&?ce+MnAV9MCLln9F`&kU-_8T_ctXS2Gmj!dBt5Y%d}_N zDgfs&3vfV;m^%h+M=R3|hjEDTj(6m)6m;j-uR+GOgN`_dlAS&=Iodwew&-;iSj@ z#?*XWqLFRUrT9N3{P7-}K8PM_b;PyvPpnuxrScUqRj+;UI8U*NT z(;eFqJGm=@c<(N?WH?uRytb&ou%@c5rN8ZCtFfa(Cc}~3lBTAm>U8y6O<<8@%MlSO z3Tc)jN6N5%x-g_cr_c@QhyVYcPFE_-Z_6P^!PHz0;~XthIK7N_10@1=LlPcjU-eS_ zY(=x!FhvO*sJl~<7ps_Z3}zdlxLve(_r6a4(bosx5ybZ$0*z!%C{P8 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 9180b18adeed8..978d026a3c859 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 2f253b8907ae22b997d9352c0a6369f19c98ee11 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 3/8] 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 5f92d25022a22..29074a6566197 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 8b60391f14f3597a13b7fd89700d177af810cdc7 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 4/8] 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 29074a6566197..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 @@ -338,22 +338,11 @@ - - - - - - - - - - - - + From 3b81472ace6e9ec6c789f8cf3a1d169a2704c6a4 Mon Sep 17 00:00:00 2001 From: Takayuki Murooka Date: Thu, 21 Sep 2023 12:58:24 +0900 Subject: [PATCH 5/8] 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 ee33062854ab9..911a39052f4dd 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 @@ -181,7 +181,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 643f8a6f91023..8ff323727f25e 100644 --- a/control/mpc_lateral_controller/src/mpc.cpp +++ b/control/mpc_lateral_controller/src/mpc.cpp @@ -173,13 +173,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; @@ -389,13 +396,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 af680b5050037..c86cc0ea91577 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 891a48299c81d..8624fc448104a 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 d3e67576f024ef09103c6e53bfba59653cedb2f8 Mon Sep 17 00:00:00 2001 From: Yamato Ando Date: Wed, 20 Sep 2023 11:34:41 +0900 Subject: [PATCH 6/8] 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 6e7e194f7cf72..8e29bfed14a28 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 6a81ed5520477390c432663785e647f3da75363a 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 7/8] 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 c6001e9731916a59a504dde86c8c50ebea53867a 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 8/8] 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);