diff --git a/tools/reaction_analyzer/CMakeLists.txt b/tools/reaction_analyzer/CMakeLists.txt index e05dbf0c3b55d..b5b5be4d4ad0c 100644 --- a/tools/reaction_analyzer/CMakeLists.txt +++ b/tools/reaction_analyzer/CMakeLists.txt @@ -10,8 +10,12 @@ find_package(Eigen3 REQUIRED) ament_auto_add_library(reaction_analyzer SHARED include/reaction_analyzer_node.hpp src/reaction_analyzer_node.cpp + include/subscriber.hpp + src/subscriber.cpp include/topic_publisher.hpp src/topic_publisher.cpp + include/utils.hpp + src/utils.cpp ) target_include_directories(reaction_analyzer diff --git a/tools/reaction_analyzer/README.md b/tools/reaction_analyzer/README.md index 3a2257c38bfc7..8ada7254e5021 100644 --- a/tools/reaction_analyzer/README.md +++ b/tools/reaction_analyzer/README.md @@ -112,26 +112,27 @@ the `path_bag_without_object` and `path_bag_with_object` parameters with the pat ## Parameters -| Name | Type | Description | -| ------------------------------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `timer_period` | double | [s] Period for the main processing timer. | -| `published_time_expire_duration` | double | [s] Time horizon of the PublishedTime message vector. | -| `test_iteration` | int | Number of iterations for the test. | -| `output_file_path` | string | Directory path where test results and statictics will be stored. | -| `object_search_radius_offset` | double | [m] Additional radius added to the search area when looking for objects. | -| `spawn_time_after_init` | double | [s] Time delay after initialization before spawning objects. Only valid `perception_planning` mode. | -| `spawn_distance_threshold` | double | [m] Distance threshold for spawning objects. Only valid `planning_control` mode. | -| `spawned_pointcloud_sampling_distance` | double | [m] Sampling distance for point clouds of spawned objects. Only valid `planning_control` mode. | -| `dummy_perception_publisher_period` | double | [s] Publishing period for the dummy perception data. Only valid `planning_control` mode. | -| `first_brake_params.debug_control_commands` | bool | Debug publish flag. | -| `first_brake_params.control_cmd_buffer_time_interval` | double | [s] Time interval for buffering control commands. | -| `first_brake_params.min_number_descending_order_control_cmd` | int | Minimum number of control commands in descending order for triggering brake. | -| `first_brake_params.min_jerk_for_brake_cmd` | double | [m/s³] Minimum jerk value for issuing a brake command. | -| `initialization_pose` | struct | Initial pose of the vehicle, containing `x`, `y`, `z`, `roll`, `pitch`, and `yaw` fields. Only valid `planning_control` mode. | -| `entity_params` | struct | Parameters for entities (e.g., obstacles), containing `x`, `y`, `z`, `roll`, `pitch`, `yaw`, `x_dimension`, `y_dimension`, and `z_dimension`. | -| `goal_pose` | struct | Goal pose of the vehicle, containing `x`, `y`, `z`, `roll`, `pitch`, and `yaw` fields. | -| `topic_publisher.path_bag_without_object` | string | Path to the ROS bag file without objects. Only valid `perception_planning` mode. | -| `topic_publisher.path_bag_with_object` | string | Path to the ROS bag file with objects. Only valid `perception_planning` mode. | -| `topic_publisher.pointcloud_publisher.pointcloud_publisher_type` | string | Defines how the PointCloud2 messages are going to be published. Modes explained above. | -| `topic_publisher.pointcloud_publisher.pointcloud_publisher_period` | double | [s] Publishing period of the PointCloud2 messages. | -| `reaction_chain` | struct | List of the nodes with their topics and topic's message types. | +| Name | Type | Description | +| ---------------------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `timer_period` | double | [s] Period for the main processing timer. | +| `test_iteration` | int | Number of iterations for the test. | +| `output_file_path` | string | Directory path where test results and statictics will be stored. | +| `object_search_radius_offset` | double | [m] Additional radius added to the search area when looking for objects. | +| `spawn_time_after_init` | double | [s] Time delay after initialization before spawning objects. Only valid `perception_planning` mode. | +| `spawn_distance_threshold` | double | [m] Distance threshold for spawning objects. Only valid `planning_control` mode. | +| `spawned_pointcloud_sampling_distance` | double | [m] Sampling distance for point clouds of spawned objects. Only valid `planning_control` mode. | +| `dummy_perception_publisher_period` | double | [s] Publishing period for the dummy perception data. Only valid `planning_control` mode. | +| `initialization_pose` | struct | Initial pose of the vehicle, containing `x`, `y`, `z`, `roll`, `pitch`, and `yaw` fields. Only valid `planning_control` mode. | +| `entity_params` | struct | Parameters for entities (e.g., obstacles), containing `x`, `y`, `z`, `roll`, `pitch`, `yaw`, `x_dimension`, `y_dimension`, and `z_dimension`. | +| `goal_pose` | struct | Goal pose of the vehicle, containing `x`, `y`, `z`, `roll`, `pitch`, and `yaw` fields. | +| `topic_publisher.path_bag_without_object` | string | Path to the ROS bag file without objects. Only valid `perception_planning` mode. | +| `topic_publisher.path_bag_with_object` | string | Path to the ROS bag file with objects. Only valid `perception_planning` mode. | +| `topic_publisher.pointcloud_publisher.pointcloud_publisher_type` | string | Defines how the PointCloud2 messages are going to be published. Modes explained above. | +| `topic_publisher.pointcloud_publisher.pointcloud_publisher_period` | double | [s] Publishing period of the PointCloud2 messages. | +| `reaction_params.first_brake_params.debug_control_commands` | bool | Debug publish flag. | +| `reaction_params.first_brake_params.control_cmd_buffer_time_interval` | double | [s] Time interval for buffering control commands. | +| `reaction_params.first_brake_params.min_number_descending_order_control_cmd` | int | Minimum number of control commands in descending order for triggering brake. | +| `reaction_params.first_brake_params.min_jerk_for_brake_cmd` | double | [m/s³] Minimum jerk value for issuing a brake command. | +| `reaction_params.search_zero_vel_params.max_looking_distance` | double | [m] Maximum looking distance for zero velocity on trajectory | +| `reaction_params.search_entity_params.search_radius` | double | [m] Searching radius for spawned entity. Distance between ego pose and entity pose. | +| `reaction_chain` | struct | List of the nodes with their topics and topic's message types. | diff --git a/tools/reaction_analyzer/include/reaction_analyzer_node.hpp b/tools/reaction_analyzer/include/reaction_analyzer_node.hpp index 1ab03e8bd8dab..81c6f75abaa92 100644 --- a/tools/reaction_analyzer/include/reaction_analyzer_node.hpp +++ b/tools/reaction_analyzer/include/reaction_analyzer_node.hpp @@ -15,30 +15,30 @@ #ifndef REACTION_ANALYZER_NODE_HPP_ #define REACTION_ANALYZER_NODE_HPP_ -#include <motion_utils/trajectory/trajectory.hpp> +#include "tf2/transform_datatypes.h" + +#include <pcl/impl/point_types.hpp> +#include <pcl_ros/transforms.hpp> #include <rclcpp/rclcpp.hpp> -#include <rosbag2_cpp/reader.hpp> +#include <subscriber.hpp> +#include <tf2_eigen/tf2_eigen/tf2_eigen.hpp> #include <tier4_autoware_utils/geometry/geometry.hpp> #include <tier4_autoware_utils/math/unit_conversion.hpp> #include <topic_publisher.hpp> +#include <utils.hpp> #include <autoware_adapi_v1_msgs/msg/localization_initialization_state.hpp> #include <autoware_adapi_v1_msgs/msg/operation_mode_state.hpp> #include <autoware_adapi_v1_msgs/msg/route_state.hpp> #include <autoware_adapi_v1_msgs/srv/change_operation_mode.hpp> -#include <autoware_auto_control_msgs/msg/ackermann_control_command.hpp> -#include <autoware_auto_perception_msgs/msg/detected_objects.hpp> -#include <autoware_auto_perception_msgs/msg/predicted_objects.hpp> -#include <autoware_auto_perception_msgs/msg/tracked_objects.hpp> -#include <autoware_auto_planning_msgs/msg/trajectory.hpp> -#include <autoware_internal_msgs/msg/published_time.hpp> #include <geometry_msgs/msg/pose_with_covariance_stamped.hpp> #include <nav_msgs/msg/odometry.hpp> -#include <message_filters/cache.h> -#include <message_filters/subscriber.h> -#include <message_filters/sync_policies/exact_time.h> -#include <message_filters/synchronizer.h> +#include <boost/uuid/string_generator.hpp> +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> + #include <pcl/common/distances.h> #include <pcl/point_types.h> #include <pcl_conversions/pcl_conversions.h> @@ -56,61 +56,18 @@ using autoware_adapi_v1_msgs::msg::LocalizationInitializationState; using autoware_adapi_v1_msgs::msg::OperationModeState; using autoware_adapi_v1_msgs::msg::RouteState; using autoware_adapi_v1_msgs::srv::ChangeOperationMode; -using autoware_auto_control_msgs::msg::AckermannControlCommand; -using autoware_auto_perception_msgs::msg::DetectedObject; -using autoware_auto_perception_msgs::msg::DetectedObjects; using autoware_auto_perception_msgs::msg::PredictedObject; using autoware_auto_perception_msgs::msg::PredictedObjects; -using autoware_auto_perception_msgs::msg::TrackedObject; -using autoware_auto_perception_msgs::msg::TrackedObjects; -using autoware_auto_planning_msgs::msg::Trajectory; -using autoware_internal_msgs::msg::PublishedTime; using geometry_msgs::msg::Pose; using nav_msgs::msg::Odometry; using sensor_msgs::msg::PointCloud2; -// Buffers to be used to store subscribed messages -using ControlCommandBuffer = - std::pair<std::vector<AckermannControlCommand>, std::optional<AckermannControlCommand>>; -using TrajectoryBuffer = std::optional<Trajectory>; -using PointCloud2Buffer = std::optional<PointCloud2>; -using PredictedObjectsBuffer = std::optional<PredictedObjects>; -using DetectedObjectsBuffer = std::optional<DetectedObjects>; -using TrackedObjectsBuffer = std::optional<TrackedObjects>; - -// Variant to store different types of buffers -using BufferVariant = std::variant< - ControlCommandBuffer, TrajectoryBuffer, PointCloud2Buffer, PredictedObjectsBuffer, - DetectedObjectsBuffer, TrackedObjectsBuffer>; - -// The supported message types -enum class SubscriberMessageType { - Unknown = 0, - AckermannControlCommand = 1, - Trajectory = 2, - PointCloud2 = 3, - DetectedObjects = 4, - PredictedObjects = 5, - TrackedObjects = 6, -}; - // The running mode of the node enum class RunningMode { PerceptionPlanning = 0, PlanningControl = 1, }; -// The configuration of the topic to be subscribed which are defined in reaction_chain -struct TopicConfig -{ - std::string node_name; - std::string topic_address; - std::string time_debug_topic_address; - SubscriberMessageType message_type; -}; - -using ChainModules = std::vector<TopicConfig>; - struct PoseParams { double x; @@ -138,68 +95,25 @@ struct NodeParams { std::string running_mode; double timer_period; - double published_time_expire_duration; std::string output_file_path; size_t test_iteration; - double object_search_radius_offset; double spawn_time_after_init; double spawn_distance_threshold; double spawned_pointcloud_sampling_distance; double dummy_perception_publisher_period; - bool debug_control_commands; - double control_cmd_buffer_time_interval; - double min_jerk_for_brake_cmd; - size_t min_number_descending_order_control_cmd; PoseParams initial_pose; PoseParams goal_pose; EntityParams entity_params; }; -// class PublishedTimeSubscriber -//{ -// public: -// PublishedTimeSubscriber(const std::string & topic_name, rclcpp::Node * node) : node_(node) -// { -// // Initialize subscriber with cache -// message_subscriber_ = -// std::make_shared<message_filters::Subscriber<PublishedTime>>(node, topic_name); -// cache_ = std::make_shared<message_filters::Cache<PublishedTime>>( -// *message_subscriber_, 10); // Cache size of 100 messages -// } -// -// std::optional<PublishedTime> getPublishedTime(const std_msgs::msg::Header & header) -// { -// for (const auto & msg : cache_->getInterval(rclcpp::Time(0), node_->now())) { -// if (msg->header.stamp == header.stamp && msg->header.frame_id == header.frame_id) { -// return *msg; -// } -// } -// return std::nullopt; // Return an empty optional if no match is found -// } -// -// std::optional<PublishedTime> getPublishedTime(const rclcpp::Time & stamp) -// { -// for (const auto & msg : cache_->getInterval(rclcpp::Time(0), node_->now())) { -// if (msg->header.stamp == stamp) { -// return *msg; -// } -// } -// return std::nullopt; // Return an empty optional if no match is found -// } -// -// private: -// std::shared_ptr<message_filters::Subscriber<PublishedTime>> message_subscriber_; -// std::shared_ptr<message_filters::Cache<PublishedTime>> cache_; -// rclcpp::Node * node_; -// }; - class ReactionAnalyzerNode : public rclcpp::Node { public: explicit ReactionAnalyzerNode(rclcpp::NodeOptions options); - ~ReactionAnalyzerNode() = default; + Odometry::ConstSharedPtr odometry_; + private: std::mutex mutex_; RunningMode node_running_mode_; @@ -211,7 +125,6 @@ class ReactionAnalyzerNode : public rclcpp::Node geometry_msgs::msg::Pose entity_pose_; geometry_msgs::msg::PoseWithCovarianceStamped init_pose_; geometry_msgs::msg::PoseStamped goal_pose_; - double entity_search_radius_; // Subscribers rclcpp::Subscription<Odometry>::SharedPtr sub_kinematics_; @@ -230,39 +143,17 @@ class ReactionAnalyzerNode : public rclcpp::Node tf2_ros::TransformListener tf_listener_{tf_buffer_}; // Variables - std::vector<rclcpp::SubscriptionBase::SharedPtr> subscribers_; - std::unordered_map<std::string, BufferVariant> message_buffers_; - std::unique_ptr<message_filters::Subscriber<PublishedTime>> time_debug_sub_; - std::unordered_map<std::string, std::vector<PublishedTime>> published_time_vector_map_; - // std::unordered_map<std::string, PublishedTimeSubscriber> published_time_subscriber_map_; - std::unordered_map<std::string, std::vector<double>> test_results_; - std::optional<rclcpp::Time> last_test_environment_init_request_time_; std::optional<rclcpp::Time> test_environment_init_time_; std::optional<rclcpp::Time> spawn_cmd_time_; std::atomic<bool> spawn_object_cmd_{false}; std::atomic<bool> is_object_spawned_message_published_{false}; - bool is_output_printed_{false}; bool is_vehicle_initialized_{false}; bool is_route_set_{false}; size_t test_iteration_count_{0}; // Functions - rclcpp::SubscriptionOptions createSubscriptionOptions(); - - bool searchPointcloudNearEntity(const pcl::PointCloud<pcl::PointXYZ> & pcl_pointcloud); - - bool searchPredictedObjectsNearEntity(const PredictedObjects & predicted_objects); - - bool searchDetectedObjectsNearEntity(const DetectedObjects & detected_objects); - - bool searchTrackedObjectsNearEntity(const TrackedObjects & tracked_objects); - - bool loadChainModules(); - - bool initSubscribers(const reaction_analyzer::ChainModules & modules); - void initAnalyzerVariables(); void initPointcloud(); @@ -274,27 +165,16 @@ class ReactionAnalyzerNode : public rclcpp::Node const RouteState::ConstSharedPtr & route_state_ptr, const OperationModeState::ConstSharedPtr & operation_mode_ptr); - void pushPublishedTime( - std::vector<PublishedTime> & published_time_vec, const PublishedTime & published_time); - - void setControlCommandToBuffer( - std::vector<AckermannControlCommand> & buffer, const AckermannControlCommand & cmd); - - std::optional<size_t> findFirstBrakeIdx( - const std::vector<AckermannControlCommand> & cmd_array, - const std::optional<rclcpp::Time> & spawn_cmd_time); + void callOperationModeServiceWithoutResponse(); void spawnObstacle(const geometry_msgs::msg::Point & ego_pose); void calculateResults( - const std::unordered_map<std::string, BufferVariant> & message_buffers, - const std::unordered_map<std::string, std::vector<PublishedTime>> & published_time_vector_map, + const std::unordered_map<std::string, subscriber::BufferVariant> & message_buffers, const rclcpp::Time & spawn_cmd_time); void onTimer(); - bool allReacted(const std::unordered_map<std::string, BufferVariant> & message_buffers); - void dummyPerceptionPublisher(); void reset(); @@ -310,52 +190,6 @@ class ReactionAnalyzerNode : public rclcpp::Node void operationModeCallback(OperationModeState::ConstSharedPtr msg_ptr); - // Callbacks for modules are subscribed - void controlCommandOutputCallback( - const std::string & node_name, const AckermannControlCommand::ConstSharedPtr & msg_ptr); - - // void controlCommandOutputCallback( - // const std::string & node_name, const AckermannControlCommand::ConstSharedPtr & msg_ptr, - // const PublishedTime::ConstSharedPtr & published_time_ptr); - - void trajectoryOutputCallback( - const std::string & node_name, const Trajectory::ConstSharedPtr & msg_ptr); - - // void trajectoryOutputCallback( - // const std::string & node_name, const Trajectory::ConstSharedPtr & msg_ptr, - // const PublishedTime::ConstSharedPtr & published_time_ptr); - - void pointcloud2OutputCallback( - const std::string & node_name, const PointCloud2::ConstSharedPtr & msg_ptr); - - // void pointcloud2OutputCallback( - // const std::string & node_name, const PointCloud2::ConstSharedPtr & msg_ptr, - // const PublishedTime::ConstSharedPtr & published_time_ptr); - - void predictedObjectsOutputCallback( - const std::string & node_name, const PredictedObjects::ConstSharedPtr & msg_ptr); - - // void predictedObjectsOutputCallback( - // const std::string & node_name, const PredictedObjects::ConstSharedPtr & msg_ptr, - // const PublishedTime::ConstSharedPtr & published_time_ptr); - - void detectedObjectsOutputCallback( - const std::string & node_name, const DetectedObjects::ConstSharedPtr & msg_ptr); - - // void detectedObjectsOutputCallback( - // const std::string & node_name, const DetectedObjects::ConstSharedPtr & msg_ptr, - // const PublishedTime::ConstSharedPtr & published_time_ptr); - - void trackedObjectsOutputCallback( - const std::string & node_name, const TrackedObjects::ConstSharedPtr & msg_ptr); - - // void trackedObjectsOutputCallback( - // const std::string & node_name, const TrackedObjects::ConstSharedPtr & msg_ptr, - // const PublishedTime::ConstSharedPtr & published_time_ptr); - - void publishedTimeOutputCallback( - const std::string & node_name, const PublishedTime::ConstSharedPtr & msg_ptr); - // Timer rclcpp::TimerBase::SharedPtr timer_; rclcpp::TimerBase::SharedPtr dummy_perception_timer_; @@ -363,18 +197,15 @@ class ReactionAnalyzerNode : public rclcpp::Node // Client rclcpp::Client<ChangeOperationMode>::SharedPtr client_change_to_autonomous_; - void callOperationModeServiceWithoutResponse(); - // Pointers std::shared_ptr<topic_publisher::TopicPublisher> topic_publisher_ptr_; + std::unique_ptr<subscriber::SubscriberBase> subscriber_ptr_; PointCloud2::SharedPtr entity_pointcloud_ptr_; PredictedObjects::SharedPtr predicted_objects_ptr_; - Odometry::ConstSharedPtr odometry_; LocalizationInitializationState::ConstSharedPtr initialization_state_ptr_; RouteState::ConstSharedPtr current_route_state_ptr_; OperationModeState::ConstSharedPtr operation_mode_ptr_; }; - } // namespace reaction_analyzer #endif // REACTION_ANALYZER_NODE_HPP_ diff --git a/tools/reaction_analyzer/include/subscriber.hpp b/tools/reaction_analyzer/include/subscriber.hpp new file mode 100644 index 0000000000000..5d9eba0643e1d --- /dev/null +++ b/tools/reaction_analyzer/include/subscriber.hpp @@ -0,0 +1,248 @@ +// Copyright 2024 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SUBSCRIBER_HPP_ +#define SUBSCRIBER_HPP_ +#include <motion_utils/trajectory/trajectory.hpp> +#include <pcl/impl/point_types.hpp> +#include <pcl_ros/transforms.hpp> +#include <rclcpp/rclcpp.hpp> +#include <tf2_eigen/tf2_eigen/tf2_eigen.hpp> +#include <tier4_autoware_utils/geometry/geometry.hpp> +#include <tier4_autoware_utils/math/unit_conversion.hpp> +#include <utils.hpp> + +#include <autoware_auto_control_msgs/msg/ackermann_control_command.hpp> +#include <autoware_auto_perception_msgs/msg/detected_objects.hpp> +#include <autoware_auto_perception_msgs/msg/predicted_objects.hpp> +#include <autoware_auto_perception_msgs/msg/tracked_objects.hpp> +#include <autoware_auto_planning_msgs/msg/trajectory.hpp> +#include <autoware_internal_msgs/msg/published_time.hpp> +#include <geometry_msgs/msg/pose_with_covariance_stamped.hpp> +#include <nav_msgs/msg/odometry.hpp> +#include <sensor_msgs/msg/point_cloud2.hpp> + +#include <message_filters/cache.h> +#include <message_filters/subscriber.h> +#include <message_filters/sync_policies/exact_time.h> +#include <message_filters/synchronizer.h> +#include <pcl/common/distances.h> +#include <pcl/point_types.h> +#include <pcl_conversions/pcl_conversions.h> +#include <tf2_ros/buffer.h> +#include <tf2_ros/transform_listener.h> + +#include <memory> +#include <mutex> +#include <utility> +#include <variant> + +namespace reaction_analyzer::subscriber +{ +using autoware_auto_control_msgs::msg::AckermannControlCommand; +using autoware_auto_perception_msgs::msg::DetectedObject; +using autoware_auto_perception_msgs::msg::DetectedObjects; +using autoware_auto_perception_msgs::msg::PredictedObject; +using autoware_auto_perception_msgs::msg::PredictedObjects; +using autoware_auto_perception_msgs::msg::TrackedObject; +using autoware_auto_perception_msgs::msg::TrackedObjects; +using autoware_auto_planning_msgs::msg::Trajectory; +using autoware_internal_msgs::msg::PublishedTime; +using geometry_msgs::msg::Pose; +using nav_msgs::msg::Odometry; +using sensor_msgs::msg::PointCloud2; + +// Buffers to be used to store subscribed messages +using ControlCommandBuffer = + std::pair<std::vector<AckermannControlCommand>, std::optional<AckermannControlCommand>>; +using TrajectoryBuffer = std::optional<Trajectory>; +using PointCloud2Buffer = std::optional<PointCloud2>; +using PredictedObjectsBuffer = std::optional<PredictedObjects>; +using DetectedObjectsBuffer = std::optional<DetectedObjects>; +using TrackedObjectsBuffer = std::optional<TrackedObjects>; + +// Variant to store different types of buffers +using BufferVariant = std::variant< + ControlCommandBuffer, TrajectoryBuffer, PointCloud2Buffer, PredictedObjectsBuffer, + DetectedObjectsBuffer, TrackedObjectsBuffer>; + +template <typename MessageType> +struct SubscriberVariables +{ + using ExactTimePolicy = message_filters::sync_policies::ExactTime<MessageType, PublishedTime>; + + std::unique_ptr<message_filters::Subscriber<MessageType>> sub1_; + std::unique_ptr<message_filters::Subscriber<PublishedTime>> sub2_; + std::unique_ptr<message_filters::Synchronizer<ExactTimePolicy>> synchronizer_; + // tmp: only for the messages who don't have header e.g. AckermannControlCommand + std::unique_ptr<message_filters::Cache<PublishedTime>> cache_; +}; + +// Variant to create subscribers for different message types +using SubscriberVariablesVariant = std::variant< + SubscriberVariables<PointCloud2>, SubscriberVariables<DetectedObjects>, + SubscriberVariables<TrackedObjects>, SubscriberVariables<PredictedObjects>, + SubscriberVariables<Trajectory>, SubscriberVariables<AckermannControlCommand>>; + +// The supported message types +enum class SubscriberMessageType { + UNKNOWN = 0, + ACKERMANN_CONTROL_COMMAND = 1, + TRAJECTORY = 2, + POINTCLOUD2 = 3, + DETECTED_OBJECTS = 4, + PREDICTED_OBJECTS = 5, + TRACKED_OBJECTS = 6, +}; + +// Reaction Types +// TODO: For the future work, we can add more reaction types e.g. first shifted trajectory. +enum class ReactionType { + UNKNOWN = 0, + FIRST_BRAKE = 1, + SEARCH_ZERO_VEL = 2, + SEARCH_ENTITY = 3, +}; + +// The configuration of the topic to be subscribed which are defined in reaction_chain +struct TopicConfig +{ + std::string node_name; + std::string topic_address; + std::string time_debug_topic_address; + SubscriberMessageType message_type; +}; + +// Place for the reaction functions' parameter configuration +struct FirstBrakeParams +{ + bool debug_control_commands; + double control_cmd_buffer_time_interval; + size_t min_number_descending_order_control_cmd; + double min_jerk_for_brake_cmd; +}; + +struct SearchZeroVelParams +{ + double max_looking_distance; +}; + +struct SearchEntityParams +{ + double search_radius; +}; + +// Place for the store the reaction parameter configuration (currently only for first brake) +struct ReactionParams +{ + FirstBrakeParams first_brake_params; + SearchZeroVelParams search_zero_vel_params; + SearchEntityParams search_entity_params; +}; + +using ChainModules = std::vector<TopicConfig>; + +class SubscriberBase +{ +public: + explicit SubscriberBase( + rclcpp::Node * node, Odometry::ConstSharedPtr & odometry, geometry_msgs::msg::Pose entity_pose, + std::atomic<bool> & spawn_object_cmd); + + ~SubscriberBase() = default; + + std::optional<std::unordered_map<std::string, BufferVariant>> getMessageBuffersMap(); + void reset(); + +private: + std::mutex mutex_; + + rclcpp::Node * node_; + Odometry::ConstSharedPtr odometry_; + geometry_msgs::msg::Pose entity_pose_; + std::atomic<bool> & spawn_object_cmd_; + + // Variables to be initialized in constructor + ChainModules chain_modules_; + ReactionParams reaction_params_{}; + + // Variants + std::unordered_map<std::string, SubscriberVariablesVariant> subscriber_variables_map_; + std::unordered_map<std::string, BufferVariant> message_buffers_; + + // tf + std::shared_ptr<tf2_ros::Buffer> tf_buffer_; + std::shared_ptr<tf2_ros::TransformListener> tf_listener_; + + // Functions + void initReactionChainsAndParams(); + + bool initSubscribers(); + + bool searchPointcloudNearEntity(const pcl::PointCloud<pcl::PointXYZ> & pcl_pointcloud); + + bool searchPredictedObjectsNearEntity(const PredictedObjects & predicted_objects); + + bool searchDetectedObjectsNearEntity(const DetectedObjects & detected_objects); + + bool searchTrackedObjectsNearEntity(const TrackedObjects & tracked_objects); + + void setControlCommandToBuffer( + std::vector<AckermannControlCommand> & buffer, const AckermannControlCommand & cmd); + + std::optional<size_t> findFirstBrakeIdx(const std::vector<AckermannControlCommand> & cmd_array); + + // Callbacks for modules are subscribed + void controlCommandOutputCallback( + const std::string & node_name, const AckermannControlCommand::ConstSharedPtr & msg_ptr); + + void trajectoryOutputCallback( + const std::string & node_name, const Trajectory::ConstSharedPtr & msg_ptr); + + void trajectoryOutputCallback( + const std::string & node_name, const Trajectory::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr); + + void pointcloud2OutputCallback( + const std::string & node_name, const PointCloud2::ConstSharedPtr & msg_ptr); + + void pointcloud2OutputCallback( + const std::string & node_name, const PointCloud2::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr); + + void predictedObjectsOutputCallback( + const std::string & node_name, const PredictedObjects::ConstSharedPtr & msg_ptr); + + void predictedObjectsOutputCallback( + const std::string & node_name, const PredictedObjects::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr); + + void detectedObjectsOutputCallback( + const std::string & node_name, const DetectedObjects::ConstSharedPtr & msg_ptr); + + void detectedObjectsOutputCallback( + const std::string & node_name, const DetectedObjects::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr); + + void trackedObjectsOutputCallback( + const std::string & node_name, const TrackedObjects::ConstSharedPtr & msg_ptr); + + void trackedObjectsOutputCallback( + const std::string & node_name, const TrackedObjects::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr); +}; + +} // namespace reaction_analyzer::subscriber + +#endif // SUBSCRIBER_HPP_ diff --git a/tools/reaction_analyzer/include/topic_publisher.hpp b/tools/reaction_analyzer/include/topic_publisher.hpp index 19f0f6ce74d85..939abab591d84 100644 --- a/tools/reaction_analyzer/include/topic_publisher.hpp +++ b/tools/reaction_analyzer/include/topic_publisher.hpp @@ -17,6 +17,7 @@ #include <rclcpp/rclcpp.hpp> #include <rosbag2_cpp/reader.hpp> +#include <utils.hpp> #include <autoware_auto_control_msgs/msg/ackermann_control_command.hpp> #include <autoware_auto_perception_msgs/msg/detected_objects.hpp> @@ -69,18 +70,21 @@ enum class PublisherMessageType { struct TopicPublisherParams { - std::string path_bag_without_object; - std::string path_bag_with_object; - std::string pointcloud_publisher_type; - double pointcloud_publisher_period; + std::string path_bag_without_object; // Path to the bag file without object + std::string path_bag_with_object; // Path to the bag file with object + std::string pointcloud_publisher_type; // Type of the pointcloud publisher + double pointcloud_publisher_period; // Period of the pointcloud publisher }; enum class PointcloudPublisherType { - AsyncPublisher = 0, - SyncHeaderSyncPublisher = 1, - AsyncHeaderSyncPublisher = 2, + AsyncPublisher = 0, // Asynchronous publisher + SyncHeaderSyncPublisher = 1, // Synchronous publisher with header synchronization + AsyncHeaderSyncPublisher = 2, // Asynchronous publisher with header synchronization }; +/** + * @brief Message type template struct for the variables of the Publisher. + */ template <typename MessageType> struct PublisherVariables { @@ -91,8 +95,12 @@ struct PublisherVariables rclcpp::TimerBase::SharedPtr timer; }; +/** + * @brief Struct for accessing the variables of the Publisher. + */ struct PublisherVarAccessor { + // Template struct to check if a type has a header member. template <typename T, typename = std::void_t<>> struct has_header : std::false_type { @@ -103,6 +111,7 @@ struct PublisherVarAccessor { }; + // Template struct to check if a type has a stamp member. template <typename T, typename = std::void_t<>> struct has_stamp : std::false_type { @@ -133,28 +142,24 @@ struct PublisherVarAccessor publisherVar.publisher->publish(std::move(msg_to_be_published)); } - // Set Period template <typename T> void setPeriod(T & publisherVar, double newPeriod) { publisherVar.period_ns = newPeriod; } - // Get Period template <typename T> double getPeriod(const T & publisherVar) const { return publisherVar.period_ns; } - // Get Empty Area Message template <typename T> std::shared_ptr<void> getEmptyAreaMessage(const T & publisherVar) const { return std::static_pointer_cast<void>(publisherVar.empty_area_message); } - // Get Object Spawned Message template <typename T> std::shared_ptr<void> getObjectSpawnedMessage(const T & publisherVar) const { @@ -174,6 +179,10 @@ using PublisherVariablesVariant = std::variant< PublisherVariables<autoware_auto_vehicle_msgs::msg::TurnIndicatorsReport>, PublisherVariables<autoware_auto_vehicle_msgs::msg::VelocityReport>>; +using LidarOutputPair = std::pair< + std::shared_ptr<PublisherVariables<PointCloud2>>, + std::shared_ptr<PublisherVariables<PointCloud2>>>; + class TopicPublisher { public: @@ -187,7 +196,7 @@ class TopicPublisher private: std::mutex mutex_; - // Initialize + // Initialized variables rclcpp::Node * node_; std::atomic<bool> & spawn_object_cmd_; std::optional<rclcpp::Time> & spawn_cmd_time_; @@ -207,10 +216,7 @@ class TopicPublisher // Variables PointcloudPublisherType pointcloud_publisher_type_; std::unordered_map<std::string, PublisherVariablesVariant> topic_publisher_map_; - std::unordered_map< - std::string, std::pair< - std::shared_ptr<PublisherVariables<PointCloud2>>, - std::shared_ptr<PublisherVariables<PointCloud2>>>> + std::unordered_map<std::string, LidarOutputPair> lidar_pub_variable_pair_map_; // used to publish pointcloud_raw and pointcloud_raw_ex bool is_object_spawned_message_published{false}; std::shared_ptr<rclcpp::TimerBase> one_shot_timer_shared_ptr_; @@ -219,7 +225,6 @@ class TopicPublisher std::unordered_map<std::string, rclcpp::TimerBase::SharedPtr> pointcloud_publish_timers_map_; rclcpp::TimerBase::SharedPtr pointcloud_sync_publish_timer_; }; - } // namespace reaction_analyzer::topic_publisher #endif // TOPIC_PUBLISHER_HPP_ diff --git a/tools/reaction_analyzer/include/utils.hpp b/tools/reaction_analyzer/include/utils.hpp new file mode 100644 index 0000000000000..70fc39c73906d --- /dev/null +++ b/tools/reaction_analyzer/include/utils.hpp @@ -0,0 +1,75 @@ +// Copyright 2024 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef UTILS_HPP_ +#define UTILS_HPP_ + +#include <rclcpp/rclcpp.hpp> +#include <tier4_autoware_utils/geometry/geometry.hpp> + +#include <autoware_auto_planning_msgs/msg/trajectory.hpp> + +#include <string> + +// The reaction_analyzer namespace contains utility functions for the Reaction Analyzer tool. +namespace reaction_analyzer +{ +using autoware_auto_planning_msgs::msg::Trajectory; +using autoware_auto_planning_msgs::msg::TrajectoryPoint; + +/** + * @brief Sorts the test results by their median value. + * + * @param test_results An unordered map containing the test results. + * @return A vector of tuples. Each tuple contains a string (the test name), a vector of doubles + * (the test results), and a double (the median value). + */ +std::vector<std::tuple<std::string, std::vector<double>, double>> sortResultsByMedian( + const std::unordered_map<std::string, std::vector<double>> test_results); + +/** + * @brief Creates a subscription option with a new callback group. + * + * @param node A pointer to the node for which the subscription options are being created. + * @return The created SubscriptionOptions. + */ +rclcpp::SubscriptionOptions createSubscriptionOptions(rclcpp::Node * node); + +/** + * @brief Splits a string by a given delimiter. + * + * @param str The string to be split. + * @param delimiter The delimiter to split the string by. + * @return A vector of strings, each of which is a segment of the original string split by the + * delimiter. + */ +std::vector<std::string> split(const std::string & str, char delimiter); + +/** + * @brief Get the index of the trajectory point that is a certain distance away from the current + * point. + * + * This function iterates over the trajectory from the current point and returns the index of the + * first point that is at least the specified distance away. + * + * @param traj The trajectory to search. + * @param curr_id The index of the current point in the trajectory. + * @param distance The distance to search for a point. + * @return The index of the point in the trajectory that is at least the specified distance away + * from the current point. + */ +size_t getIndexAfterDistance(const Trajectory & traj, const size_t curr_id, const double distance); +} // namespace reaction_analyzer + +#endif // UTILS_HPP_ diff --git a/tools/reaction_analyzer/package.xml b/tools/reaction_analyzer/package.xml index faf5797d00433..05081eac01751 100644 --- a/tools/reaction_analyzer/package.xml +++ b/tools/reaction_analyzer/package.xml @@ -3,7 +3,7 @@ <package format="3"> <name>reaction_analyzer</name> <version>1.0.0</version> - <description>Nodes to measure reaction times of the nodes</description> + <description>Analyzer that measures reaction times of the nodes</description> <maintainer email="berkay@leodrive.ai">Berkay Karaman</maintainer> diff --git a/tools/reaction_analyzer/param/reaction_analyzer.param.yaml b/tools/reaction_analyzer/param/reaction_analyzer.param.yaml index 37ef3bba86e38..082cdaf864265 100644 --- a/tools/reaction_analyzer/param/reaction_analyzer.param.yaml +++ b/tools/reaction_analyzer/param/reaction_analyzer.param.yaml @@ -1,19 +1,13 @@ /**: ros__parameters: timer_period: 0.033 # s - published_time_expire_duration: 5.0 # s test_iteration: 15 output_file_path: /home/berkay/projects/reaction_analyzer_results/ object_search_radius_offset: 0.1 # m - spawn_time_after_init: 5.0 # s - spawn_distance_threshold: 15.0 # m + spawn_time_after_init: 5.0 # s for perception_planning mode + spawn_distance_threshold: 15.0 # m # for planning_control mode spawned_pointcloud_sampling_distance: 0.1 dummy_perception_publisher_period: 0.1 # s - first_brake_params: - debug_control_commands: false - control_cmd_buffer_time_interval: 1.0 # s - min_number_descending_order_control_cmd: 3 - min_jerk_for_brake_cmd: 0.3 # m/s^3 initialization_pose: x: 81247.9609375 y: 49828.87890625 @@ -44,68 +38,79 @@ pointcloud_publisher: pointcloud_publisher_type: "sync_header_sync_publish" # "async_header_sync_publish", "sync_header_sync_publish" or "async_publish" pointcloud_publisher_period: 0.1 # s - reaction_chain: -# common_ground_filter: -# topic_name: /perception/obstacle_segmentation/single_frame/pointcloud_raw -# time_debug_topic_name: /perception/obstacle_segmentation/single_frame/pointcloud_raw/debug/published_time -# message_type: sensor_msgs::msg::PointCloud2 -# occupancy_grid_map_outlier: -# topic_name: /perception/obstacle_segmentation/pointcloud -# time_debug_topic_name: /perception/obstacle_segmentation/pointcloud/debug/published_time -# message_type: sensor_msgs::msg::PointCloud2 -# multi_object_tracker: -# topic_name: /perception/object_recognition/tracking/near_objects -# time_debug_topic_name: /perception/object_recognition/tracking/near_objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::TrackedObjects -# lidar_centerpoint: -# topic_name: /perception/object_recognition/detection/centerpoint/objects -# time_debug_topic_name: /perception/object_recognition/detection/centerpoint/objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::DetectedObjects -# obstacle_pointcloud_based_validator: -# topic_name: /perception/object_recognition/detection/centerpoint/validation/objects -# time_debug_topic_name: /perception/object_recognition/detection/centerpoint/validation/objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::DetectedObjects -# decorative_tracker_merger: -# topic_name: /perception/object_recognition/tracking/objects -# time_debug_topic_name: /perception/object_recognition/tracking/objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::TrackedObjects -# detected_object_feature_remover: -# topic_name: /perception/object_recognition/detection/clustering/objects -# time_debug_topic_name: /perception/object_recognition/detection/clustering/objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::DetectedObjects -# detection_by_tracker: -# topic_name: /perception/object_recognition/detection/detection_by_tracker/objects -# time_debug_topic_name: /perception/object_recognition/detection/detection_by_tracker/objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::DetectedObjects -# object_lanelet_filter: -# topic_name: /perception/object_recognition/detection/objects -# time_debug_topic_name: /perception/object_recognition/detection/objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::DetectedObjects -# map_based_prediction: -# topic_name: /perception/object_recognition/objects -# time_debug_topic_name: /perception/object_recognition/objects/debug/published_time -# message_type: autoware_auto_perception_msgs::msg::PredictedObjects - obstacle_stop_planner: - topic_name: /planning/scenario_planning/lane_driving/trajectory - time_debug_topic_name: /planning/scenario_planning/lane_driving/trajectory/debug/published_time - message_type: autoware_auto_planning_msgs::msg::Trajectory - scenario_selector: - topic_name: /planning/scenario_planning/scenario_selector/trajectory - time_debug_topic_name: /planning/scenario_planning/scenario_selector/trajectory/debug/published_time - message_type: autoware_auto_planning_msgs::msg::Trajectory - motion_velocity_smoother: - topic_name: /planning/scenario_planning/motion_velocity_smoother/trajectory - time_debug_topic_name: /planning/scenario_planning/motion_velocity_smoother/trajectory/debug/published_time - message_type: autoware_auto_planning_msgs::msg::Trajectory - planning_validator: - topic_name: /planning/scenario_planning/trajectory - time_debug_topic_name: /planning/scenario_planning/trajectory/debug/published_time - message_type: autoware_auto_planning_msgs::msg::Trajectory - trajectory_follower: - topic_name: /control/trajectory_follower/control_cmd - time_debug_topic_name: /control/trajectory_follower/control_cmd/debug/published_time - message_type: autoware_auto_control_msgs::msg::AckermannControlCommand - vehicle_cmd_gate: - topic_name: /control/command/control_cmd - time_debug_topic_name: /control/command/control_cmd/debug/published_time - message_type: autoware_auto_control_msgs::msg::AckermannControlCommand + subscriber: + reaction_params: + first_brake_params: + debug_control_commands: false + control_cmd_buffer_time_interval: 1.0 # s + min_number_descending_order_control_cmd: 3 + min_jerk_for_brake_cmd: 0.3 # m/s^3 + search_zero_vel_params: + max_looking_distance: 15.0 # m + search_entity_params: + search_radius: 5.0 # m + reaction_chain: + obstacle_stop_planner: + topic_name: /planning/scenario_planning/lane_driving/trajectory + time_debug_topic_name: /planning/scenario_planning/lane_driving/trajectory/debug/published_time + message_type: autoware_auto_planning_msgs::msg::Trajectory + scenario_selector: + topic_name: /planning/scenario_planning/scenario_selector/trajectory + time_debug_topic_name: /planning/scenario_planning/scenario_selector/trajectory/debug/published_time + message_type: autoware_auto_planning_msgs::msg::Trajectory + motion_velocity_smoother: + topic_name: /planning/scenario_planning/motion_velocity_smoother/trajectory + time_debug_topic_name: /planning/scenario_planning/motion_velocity_smoother/trajectory/debug/published_time + message_type: autoware_auto_planning_msgs::msg::Trajectory + planning_validator: + topic_name: /planning/scenario_planning/trajectory + time_debug_topic_name: /planning/scenario_planning/trajectory/debug/published_time + message_type: autoware_auto_planning_msgs::msg::Trajectory +# trajectory_follower: +# topic_name: /control/trajectory_follower/control_cmd +# time_debug_topic_name: /control/trajectory_follower/control_cmd/debug/published_time +# message_type: autoware_auto_control_msgs::msg::AckermannControlCommand +# vehicle_cmd_gate: +# topic_name: /control/command/control_cmd +# time_debug_topic_name: /control/command/control_cmd/debug/published_time +# message_type: autoware_auto_control_msgs::msg::AckermannControlCommand + common_ground_filter: + topic_name: /perception/obstacle_segmentation/single_frame/pointcloud_raw + time_debug_topic_name: /perception/obstacle_segmentation/single_frame/pointcloud_raw/debug/published_time + message_type: sensor_msgs::msg::PointCloud2 + occupancy_grid_map_outlier: + topic_name: /perception/obstacle_segmentation/pointcloud + time_debug_topic_name: /perception/obstacle_segmentation/pointcloud/debug/published_time + message_type: sensor_msgs::msg::PointCloud2 + multi_object_tracker: + topic_name: /perception/object_recognition/tracking/near_objects + time_debug_topic_name: /perception/object_recognition/tracking/near_objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::TrackedObjects + lidar_centerpoint: + topic_name: /perception/object_recognition/detection/centerpoint/objects + time_debug_topic_name: /perception/object_recognition/detection/centerpoint/objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::DetectedObjects + obstacle_pointcloud_based_validator: + topic_name: /perception/object_recognition/detection/centerpoint/validation/objects + time_debug_topic_name: /perception/object_recognition/detection/centerpoint/validation/objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::DetectedObjects + decorative_tracker_merger: + topic_name: /perception/object_recognition/tracking/objects + time_debug_topic_name: /perception/object_recognition/tracking/objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::TrackedObjects + detected_object_feature_remover: + topic_name: /perception/object_recognition/detection/clustering/objects + time_debug_topic_name: /perception/object_recognition/detection/clustering/objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::DetectedObjects + detection_by_tracker: + topic_name: /perception/object_recognition/detection/detection_by_tracker/objects + time_debug_topic_name: /perception/object_recognition/detection/detection_by_tracker/objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::DetectedObjects + object_lanelet_filter: + topic_name: /perception/object_recognition/detection/objects + time_debug_topic_name: /perception/object_recognition/detection/objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::DetectedObjects + map_based_prediction: + topic_name: /perception/object_recognition/objects + time_debug_topic_name: /perception/object_recognition/objects/debug/published_time + message_type: autoware_auto_perception_msgs::msg::PredictedObjects diff --git a/tools/reaction_analyzer/src/reaction_analyzer_node.cpp b/tools/reaction_analyzer/src/reaction_analyzer_node.cpp index aa6cad7ee7623..95ac106375897 100644 --- a/tools/reaction_analyzer/src/reaction_analyzer_node.cpp +++ b/tools/reaction_analyzer/src/reaction_analyzer_node.cpp @@ -14,244 +14,12 @@ #include "reaction_analyzer_node.hpp" -#include "tf2/transform_datatypes.h" - -#include <pcl/impl/point_types.hpp> -#include <pcl_ros/transforms.hpp> -#include <tf2_eigen/tf2_eigen/tf2_eigen.hpp> - -#include <boost/uuid/string_generator.hpp> -#include <boost/uuid/uuid.hpp> -#include <boost/uuid/uuid_generators.hpp> -#include <boost/uuid/uuid_io.hpp> - -#include <message_filters/sync_policies/approximate_time.h> - -#include <algorithm> +#include <functional> #include <memory> namespace reaction_analyzer { -// Callbacks - -void ReactionAnalyzerNode::publishedTimeOutputCallback( - const std::string & node_name, const PublishedTime::ConstSharedPtr & msg_ptr) -{ - std::lock_guard<std::mutex> lock(mutex_); - auto & published_time_vector = published_time_vector_map_[node_name]; - pushPublishedTime(published_time_vector, *msg_ptr); -} - -void ReactionAnalyzerNode::controlCommandOutputCallback( - const std::string & node_name, const AckermannControlCommand::ConstSharedPtr & msg_ptr) -{ - std::lock_guard<std::mutex> lock(mutex_); - auto & variant = message_buffers_[node_name]; - const auto is_spawned = spawn_cmd_time_; - if (!std::holds_alternative<ControlCommandBuffer>(variant)) { - ControlCommandBuffer buffer(std::vector<AckermannControlCommand>{*msg_ptr}, std::nullopt); - variant = buffer; - } - - if (std::get<ControlCommandBuffer>(variant).second) { - // reacted - return; - } - setControlCommandToBuffer(std::get<ControlCommandBuffer>(variant).first, *msg_ptr); - const auto brake_idx = - findFirstBrakeIdx(std::get<ControlCommandBuffer>(variant).first, is_spawned); - if (brake_idx) { - std::get<ControlCommandBuffer>(variant).second = - std::get<ControlCommandBuffer>(variant).first.at(brake_idx.value()); - RCLCPP_INFO(this->get_logger(), "Reacted %s", node_name.c_str()); - } -} - -void ReactionAnalyzerNode::trajectoryOutputCallback( - const std::string & node_name, const Trajectory::ConstSharedPtr & msg_ptr) -{ - mutex_.lock(); - auto variant = message_buffers_[node_name]; - const auto current_odometry_ptr = odometry_; - const auto is_spawned = spawn_cmd_time_; - if (!std::holds_alternative<TrajectoryBuffer>(variant)) { - TrajectoryBuffer buffer(std::nullopt); - variant = buffer; - message_buffers_[node_name] = variant; - } - mutex_.unlock(); - - if (!current_odometry_ptr || !is_spawned || std::get<TrajectoryBuffer>(variant).has_value()) { - return; - } - - const auto nearest_seg_idx = motion_utils::findNearestSegmentIndex( - msg_ptr->points, current_odometry_ptr->pose.pose.position); - - const auto nearest_objects_seg_idx = - motion_utils::findNearestIndex(msg_ptr->points, entity_pose_.position); - - const auto zero_vel_idx = motion_utils::searchZeroVelocityIndex( - msg_ptr->points, nearest_seg_idx, nearest_objects_seg_idx); - - if (zero_vel_idx) { - std::get<TrajectoryBuffer>(variant) = *msg_ptr; - RCLCPP_INFO(this->get_logger(), "Reacted %s", node_name.c_str()); - mutex_.lock(); - message_buffers_[node_name] = variant; - mutex_.unlock(); - } -} - -void ReactionAnalyzerNode::pointcloud2OutputCallback( - const std::string & node_name, const PointCloud2::ConstSharedPtr & msg_ptr) -{ - mutex_.lock(); - auto variant = message_buffers_[node_name]; - const auto is_spawned = spawn_cmd_time_; - if (!std::holds_alternative<PointCloud2Buffer>(variant)) { - PointCloud2Buffer buffer(std::nullopt); - variant = buffer; - message_buffers_[node_name] = variant; - } - mutex_.unlock(); - - if (!is_spawned || std::get<PointCloud2Buffer>(variant).has_value()) { - return; - } - - // transform pointcloud - geometry_msgs::msg::TransformStamped transform_stamped{}; - try { - transform_stamped = tf_buffer_.lookupTransform( - "map", msg_ptr->header.frame_id, this->now(), rclcpp::Duration::from_seconds(0.5)); - } catch (tf2::TransformException & ex) { - RCLCPP_ERROR_STREAM( - get_logger(), "Failed to look up transform from " << msg_ptr->header.frame_id << " to map"); - return; - } - - // transform by using eigen matrix - PointCloud2 transformed_points{}; - const Eigen::Matrix4f affine_matrix = - tf2::transformToEigen(transform_stamped.transform).matrix().cast<float>(); - pcl_ros::transformPointCloud(affine_matrix, *msg_ptr, transformed_points); - - pcl::PointCloud<pcl::PointXYZ> pcl_pointcloud; - pcl::fromROSMsg(transformed_points, pcl_pointcloud); - - if (searchPointcloudNearEntity(pcl_pointcloud)) { - std::get<PointCloud2Buffer>(variant) = *msg_ptr; - RCLCPP_INFO(this->get_logger(), "Reacted %s", node_name.c_str()); - mutex_.lock(); - message_buffers_[node_name] = variant; - mutex_.unlock(); - } -} - -void ReactionAnalyzerNode::predictedObjectsOutputCallback( - const std::string & node_name, const PredictedObjects::ConstSharedPtr & msg_ptr) -{ - mutex_.lock(); - auto variant = message_buffers_[node_name]; - const auto is_spawned = spawn_cmd_time_; - if (!std::holds_alternative<PredictedObjectsBuffer>(variant)) { - PredictedObjectsBuffer buffer(std::nullopt); - variant = buffer; - message_buffers_[node_name] = variant; - } - mutex_.unlock(); - - if ( - !is_spawned || msg_ptr->objects.empty() || - std::get<PredictedObjectsBuffer>(variant).has_value()) { - return; - } - - if (searchPredictedObjectsNearEntity(*msg_ptr)) { - std::get<PredictedObjectsBuffer>(variant) = *msg_ptr; - RCLCPP_INFO(this->get_logger(), "Reacted %s", node_name.c_str()); - - mutex_.lock(); - message_buffers_[node_name] = variant; - mutex_.unlock(); - } -} - -void ReactionAnalyzerNode::detectedObjectsOutputCallback( - const std::string & node_name, const DetectedObjects::ConstSharedPtr & msg_ptr) -{ - mutex_.lock(); - auto variant = message_buffers_[node_name]; - const auto is_spawned = spawn_cmd_time_; - if (!std::holds_alternative<DetectedObjectsBuffer>(variant)) { - DetectedObjectsBuffer buffer(std::nullopt); - variant = buffer; - message_buffers_[node_name] = variant; - } - mutex_.unlock(); - if ( - !is_spawned || msg_ptr->objects.empty() || - std::get<DetectedObjectsBuffer>(variant).has_value()) { - return; - } - - // transform objects - geometry_msgs::msg::TransformStamped transform_stamped{}; - try { - transform_stamped = tf_buffer_.lookupTransform( - "map", msg_ptr->header.frame_id, this->now(), rclcpp::Duration::from_seconds(0.5)); - } catch (tf2::TransformException & ex) { - RCLCPP_ERROR_STREAM( - get_logger(), "Failed to look up transform from " << msg_ptr->header.frame_id << " to map"); - return; - } - - DetectedObjects output_objs; - output_objs = *msg_ptr; - for (auto & obj : output_objs.objects) { - geometry_msgs::msg::PoseStamped output_stamped, input_stamped; - input_stamped.pose = obj.kinematics.pose_with_covariance.pose; - tf2::doTransform(input_stamped, output_stamped, transform_stamped); - obj.kinematics.pose_with_covariance.pose = output_stamped.pose; - } - if (searchDetectedObjectsNearEntity(output_objs)) { - std::get<DetectedObjectsBuffer>(variant) = *msg_ptr; - RCLCPP_INFO(this->get_logger(), "Reacted %s", node_name.c_str()); - mutex_.lock(); - message_buffers_[node_name] = variant; - mutex_.unlock(); - } -} - -void ReactionAnalyzerNode::trackedObjectsOutputCallback( - const std::string & node_name, const TrackedObjects::ConstSharedPtr & msg_ptr) -{ - mutex_.lock(); - auto variant = message_buffers_[node_name]; - const auto is_spawned = spawn_cmd_time_; - if (!std::holds_alternative<TrackedObjectsBuffer>(variant)) { - TrackedObjectsBuffer buffer(std::nullopt); - variant = buffer; - message_buffers_[node_name] = variant; - } - mutex_.unlock(); - if ( - !is_spawned || msg_ptr->objects.empty() || - std::get<TrackedObjectsBuffer>(variant).has_value()) { - return; - } - - if (searchTrackedObjectsNearEntity(*msg_ptr)) { - std::get<TrackedObjectsBuffer>(variant) = *msg_ptr; - RCLCPP_INFO(this->get_logger(), "Reacted %s", node_name.c_str()); - mutex_.lock(); - message_buffers_[node_name] = variant; - mutex_.unlock(); - } -} - void ReactionAnalyzerNode::operationModeCallback(OperationModeState::ConstSharedPtr msg_ptr) { std::lock_guard<std::mutex> lock(mutex_); @@ -267,7 +35,7 @@ void ReactionAnalyzerNode::routeStateCallback(RouteState::ConstSharedPtr msg_ptr void ReactionAnalyzerNode::vehiclePoseCallback(Odometry::ConstSharedPtr msg_ptr) { std::lock_guard<std::mutex> lock(mutex_); - odometry_ = std::move(msg_ptr); + odometry_ = msg_ptr; } void ReactionAnalyzerNode::initializationStateCallback( @@ -296,25 +64,14 @@ ReactionAnalyzerNode::ReactionAnalyzerNode(rclcpp::NodeOptions options) node_params_.timer_period = get_parameter("timer_period").as_double(); node_params_.test_iteration = get_parameter("test_iteration").as_int(); - node_params_.published_time_expire_duration = - get_parameter("published_time_expire_duration").as_double(); node_params_.output_file_path = get_parameter("output_file_path").as_string(); - node_params_.object_search_radius_offset = - get_parameter("object_search_radius_offset").as_double(); + node_params_.spawn_time_after_init = get_parameter("spawn_time_after_init").as_double(); node_params_.spawn_distance_threshold = get_parameter("spawn_distance_threshold").as_double(); node_params_.spawned_pointcloud_sampling_distance = get_parameter("spawned_pointcloud_sampling_distance").as_double(); node_params_.dummy_perception_publisher_period = get_parameter("dummy_perception_publisher_period").as_double(); - node_params_.debug_control_commands = - get_parameter("first_brake_params.debug_control_commands").as_bool(); - node_params_.control_cmd_buffer_time_interval = - get_parameter("first_brake_params.control_cmd_buffer_time_interval").as_double(); - node_params_.min_number_descending_order_control_cmd = - get_parameter("first_brake_params.min_number_descending_order_control_cmd").as_int(); - node_params_.min_jerk_for_brake_cmd = - get_parameter("first_brake_params.min_jerk_for_brake_cmd").as_double(); // Position parameters node_params_.initial_pose.x = get_parameter("initialization_pose.x").as_double(); @@ -341,31 +98,26 @@ ReactionAnalyzerNode::ReactionAnalyzerNode(rclcpp::NodeOptions options) node_params_.entity_params.y_l = get_parameter("entity_params.y_dimension").as_double(); node_params_.entity_params.z_l = get_parameter("entity_params.z_dimension").as_double(); - // initialize the reaction chain - if (!loadChainModules()) { - RCLCPP_ERROR( - get_logger(), "Modules in chain are invalid. Node couldn't be initialized. Failed."); - return; - } - - initAnalyzerVariables(); - sub_kinematics_ = create_subscription<Odometry>( "input/kinematics", 1, std::bind(&ReactionAnalyzerNode::vehiclePoseCallback, this, _1), - createSubscriptionOptions()); + createSubscriptionOptions(this)); sub_localization_init_state_ = create_subscription<LocalizationInitializationState>( "input/localization_initialization_state", rclcpp::QoS(1).transient_local(), std::bind(&ReactionAnalyzerNode::initializationStateCallback, this, _1), - createSubscriptionOptions()); + createSubscriptionOptions(this)); sub_route_state_ = create_subscription<RouteState>( "input/routing_state", rclcpp::QoS{1}.transient_local(), - std::bind(&ReactionAnalyzerNode::routeStateCallback, this, _1), createSubscriptionOptions()); + std::bind(&ReactionAnalyzerNode::routeStateCallback, this, _1), + createSubscriptionOptions(this)); sub_operation_mode_ = create_subscription<OperationModeState>( "input/operation_mode_state", rclcpp::QoS{1}.transient_local(), - std::bind(&ReactionAnalyzerNode::operationModeCallback, this, _1), createSubscriptionOptions()); + std::bind(&ReactionAnalyzerNode::operationModeCallback, this, _1), + createSubscriptionOptions(this)); pub_goal_pose_ = create_publisher<geometry_msgs::msg::PoseStamped>("output/goal", rclcpp::QoS(1)); + initAnalyzerVariables(); + if (node_running_mode_ == RunningMode::PlanningControl) { pub_initial_pose_ = create_publisher<geometry_msgs::msg::PoseWithCovarianceStamped>( "output/initialpose", rclcpp::QoS(1)); @@ -388,6 +140,11 @@ ReactionAnalyzerNode::ReactionAnalyzerNode(rclcpp::NodeOptions options) std::make_shared<topic_publisher::TopicPublisher>(this, spawn_object_cmd_, spawn_cmd_time_); } + // Load the subscriber to listen the topics for reactions + odometry_ = std::make_shared<Odometry>(); // initialize the odometry before init the subscriber + subscriber_ptr_ = + std::make_unique<subscriber::SubscriberBase>(this, odometry_, entity_pose_, spawn_object_cmd_); + const auto period_ns = std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::duration<double>(node_params_.timer_period)); timer_ = rclcpp::create_timer( @@ -401,9 +158,7 @@ void ReactionAnalyzerNode::onTimer() const auto initialization_state_ptr = initialization_state_ptr_; const auto route_state_ptr = current_route_state_ptr_; const auto operation_mode_ptr = operation_mode_ptr_; - const auto message_buffers = message_buffers_; const auto spawn_cmd_time = spawn_cmd_time_; - const auto published_time_vector_map = published_time_vector_map_; mutex_.unlock(); // Init the test environment @@ -417,11 +172,12 @@ void ReactionAnalyzerNode::onTimer() if (!spawn_cmd_time) return; - if (!allReacted(message_buffers)) return; + const auto message_buffers = subscriber_ptr_->getMessageBuffersMap(); - if (!is_output_printed_) { - calculateResults(message_buffers, published_time_vector_map, spawn_cmd_time.value()); - } else { + if (message_buffers) { + // if reacted, calculate the results + + calculateResults(message_buffers.value(), spawn_cmd_time.value()); reset(); } } @@ -449,7 +205,7 @@ void ReactionAnalyzerNode::dummyPerceptionPublisher() geometry_msgs::msg::TransformStamped transform_stamped{}; try { transform_stamped = tf_buffer_.lookupTransform( - "base_link", "map", this->now(), rclcpp::Duration::from_seconds(0.5)); + "base_link", "map", this->now(), rclcpp::Duration::from_seconds(0.1)); } catch (tf2::TransformException & ex) { RCLCPP_ERROR_STREAM(get_logger(), "Failed to look up transform from map to base_link"); return; @@ -501,58 +257,8 @@ void ReactionAnalyzerNode::spawnObstacle(const geometry_msgs::msg::Point & ego_p } } -bool ReactionAnalyzerNode::allReacted( - const std::unordered_map<std::string, BufferVariant> & message_buffers) -{ - bool all_reacted = true; - for (const auto & [key, variant] : message_buffers) { - if (auto * control_message = std::get_if<ControlCommandBuffer>(&variant)) { - if (!control_message->second) { - all_reacted = false; - } - } else if (auto * planning_message = std::get_if<TrajectoryBuffer>(&variant)) { - if (!planning_message->has_value()) { - all_reacted = false; - } - } else if (auto * pointcloud_message = std::get_if<PointCloud2Buffer>(&variant)) { - if (!pointcloud_message->has_value()) { - all_reacted = false; - } - } else if (auto * predicted_objects_message = std::get_if<PredictedObjectsBuffer>(&variant)) { - if (!predicted_objects_message->has_value()) { - all_reacted = false; - } - } else if (auto * detected_objects_message = std::get_if<DetectedObjectsBuffer>(&variant)) { - if (!detected_objects_message->has_value()) { - all_reacted = false; - } - } else if (auto * tracked_objects_message = std::get_if<TrackedObjectsBuffer>(&variant)) { - if (!tracked_objects_message->has_value()) { - all_reacted = false; - } - } - } - return all_reacted; -} - -std::optional<size_t> findConjugatePublishedTimeIdx( - const std::vector<PublishedTime> & published_time_vector, const rclcpp::Time & time) -{ - auto it = std::find_if( - published_time_vector.begin(), published_time_vector.end(), - [&time](const PublishedTime & timeInVector) { return timeInVector.header.stamp == time; }); - - if (it != published_time_vector.end()) { - return std::optional<int>( - std::distance(published_time_vector.begin(), it)); // Return the index of the found time - } else { - return std::nullopt; - } -} - void ReactionAnalyzerNode::calculateResults( - const std::unordered_map<std::string, BufferVariant> & message_buffers, - const std::unordered_map<std::string, std::vector<PublishedTime>> & published_time_vector_map, + const std::unordered_map<std::string, subscriber::BufferVariant> & message_buffers, const rclcpp::Time & spawn_cmd_time) { auto createDurationMs = [](const rclcpp::Time & start_time, const rclcpp::Time & end_time) { @@ -562,123 +268,35 @@ void ReactionAnalyzerNode::calculateResults( for (const auto & [key, variant] : message_buffers) { rclcpp::Time reaction_time; - if (auto * control_message = std::get_if<ControlCommandBuffer>(&variant)) { + if (auto * control_message = std::get_if<subscriber::ControlCommandBuffer>(&variant)) { if (control_message->second) { - auto it = published_time_vector_map.find(key); - if (it != published_time_vector_map.end()) { - const auto & published_time_vec = it->second; - const auto idx = - findConjugatePublishedTimeIdx(published_time_vec, control_message->second->stamp); - if (idx) { - reaction_time = rclcpp::Time(published_time_vec.at(idx.value()).published_stamp); - } else { - RCLCPP_ERROR( - this->get_logger(), "Published time for %s node is not found", key.c_str()); - - reaction_time = control_message->second->stamp; - } - } else { - // It might do not have a published time debug topic - reaction_time = control_message->second->stamp; - } + reaction_time = control_message->second->stamp; } - } else if (auto * planning_message = std::get_if<TrajectoryBuffer>(&variant)) { + } else if (auto * planning_message = std::get_if<subscriber::TrajectoryBuffer>(&variant)) { if (planning_message->has_value()) { - auto it = published_time_vector_map.find(key); - if (it != published_time_vector_map.end()) { - const auto & published_time_vec = it->second; - const auto idx = findConjugatePublishedTimeIdx( - published_time_vec, planning_message->value().header.stamp); - if (idx) { - reaction_time = rclcpp::Time(published_time_vec.at(idx.value()).published_stamp); - } else { - RCLCPP_ERROR( - this->get_logger(), "Published time for %s node is not found", key.c_str()); - - reaction_time = planning_message->value().header.stamp; - } - } else { - reaction_time = planning_message->value().header.stamp; - } + reaction_time = planning_message->value().header.stamp; } - } else if (auto * pointcloud_message = std::get_if<PointCloud2Buffer>(&variant)) { + } else if (auto * pointcloud_message = std::get_if<subscriber::PointCloud2Buffer>(&variant)) { if (pointcloud_message->has_value()) { - auto it = published_time_vector_map.find(key); - if (it != published_time_vector_map.end()) { - const auto & published_time_vec = it->second; - const auto idx = findConjugatePublishedTimeIdx( - published_time_vec, pointcloud_message->value().header.stamp); - if (idx) { - reaction_time = rclcpp::Time(published_time_vec.at(idx.value()).published_stamp); - } else { - RCLCPP_ERROR( - this->get_logger(), "Published time for %s node is not found", key.c_str()); - - reaction_time = pointcloud_message->value().header.stamp; - } - } else { - reaction_time = pointcloud_message->value().header.stamp; - } + reaction_time = pointcloud_message->value().header.stamp; } - } else if (auto * predicted_objects_message = std::get_if<PredictedObjectsBuffer>(&variant)) { + } else if ( + auto * predicted_objects_message = + std::get_if<subscriber::PredictedObjectsBuffer>(&variant)) { if (predicted_objects_message->has_value()) { - auto it = published_time_vector_map.find(key); - if (it != published_time_vector_map.end()) { - const auto & published_time_vec = it->second; - const auto idx = findConjugatePublishedTimeIdx( - published_time_vec, predicted_objects_message->value().header.stamp); - if (idx) { - reaction_time = rclcpp::Time(published_time_vec.at(idx.value()).published_stamp); - } else { - RCLCPP_ERROR( - this->get_logger(), "Published time for %s node is not found", key.c_str()); - - reaction_time = predicted_objects_message->value().header.stamp; - } - } else { - reaction_time = predicted_objects_message->value().header.stamp; - } + reaction_time = predicted_objects_message->value().header.stamp; } - } else if (auto * detected_objects_message = std::get_if<DetectedObjectsBuffer>(&variant)) { + } else if ( + auto * detected_objects_message = std::get_if<subscriber::DetectedObjectsBuffer>(&variant)) { if (detected_objects_message->has_value()) { - auto it = published_time_vector_map.find(key); - if (it != published_time_vector_map.end()) { - const auto & published_time_vec = it->second; - const auto idx = findConjugatePublishedTimeIdx( - published_time_vec, detected_objects_message->value().header.stamp); - if (idx) { - reaction_time = rclcpp::Time(published_time_vec.at(idx.value()).published_stamp); - } else { - RCLCPP_ERROR( - this->get_logger(), "Published time for %s node is not found", key.c_str()); - - reaction_time = detected_objects_message->value().header.stamp; - } - } else { - reaction_time = detected_objects_message->value().header.stamp; - } + reaction_time = detected_objects_message->value().header.stamp; } - } else if (auto * tracked_objects_message = std::get_if<TrackedObjectsBuffer>(&variant)) { + } else if ( + auto * tracked_objects_message = std::get_if<subscriber::TrackedObjectsBuffer>(&variant)) { if (tracked_objects_message->has_value()) { - auto it = published_time_vector_map.find(key); - if (it != published_time_vector_map.end()) { - const auto & published_time_vec = it->second; - const auto idx = findConjugatePublishedTimeIdx( - published_time_vec, tracked_objects_message->value().header.stamp); - if (idx) { - reaction_time = rclcpp::Time(published_time_vec.at(idx.value()).published_stamp); - } else { - RCLCPP_ERROR( - this->get_logger(), "Published time for %s node is not found", key.c_str()); - - reaction_time = tracked_objects_message->value().header.stamp; - } - } else { - reaction_time = tracked_objects_message->value().header.stamp; - } + reaction_time = tracked_objects_message->value().header.stamp; } } - const auto duration = createDurationMs(spawn_cmd_time, reaction_time); RCLCPP_INFO( @@ -686,61 +304,6 @@ void ReactionAnalyzerNode::calculateResults( test_results_[key].emplace_back(duration); } test_iteration_count_++; - is_output_printed_ = true; -} - -bool ReactionAnalyzerNode::loadChainModules() -{ - auto split = [](const std::string & str, const char delim) { - std::vector<std::string> elems; - std::stringstream ss(str); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; - }; - - auto stringToMessageType = [](const std::string & input) { - if (input == "autoware_auto_control_msgs::msg::AckermannControlCommand") { - return SubscriberMessageType::AckermannControlCommand; - } else if (input == "autoware_auto_planning_msgs::msg::Trajectory") { - return SubscriberMessageType::Trajectory; - } else if (input == "sensor_msgs::msg::PointCloud2") { - return SubscriberMessageType::PointCloud2; - } else if (input == "autoware_auto_perception_msgs::msg::PredictedObjects") { - return SubscriberMessageType::PredictedObjects; - } else if (input == "autoware_auto_perception_msgs::msg::DetectedObjects") { - return SubscriberMessageType::DetectedObjects; - } else if (input == "autoware_auto_perception_msgs::msg::TrackedObjects") { - return SubscriberMessageType::TrackedObjects; - } else { - return SubscriberMessageType::Unknown; - } - }; - - // get the topic addresses and message types of the modules in chain - const auto param_key = std::string("reaction_chain"); - const auto module_names = this->list_parameters({param_key}, 3).prefixes; - ChainModules chain_modules; - for (const auto & module_name : module_names) { - const auto splitted_name = split(module_name, '.'); - TopicConfig tmp; - tmp.node_name = splitted_name.back(); - tmp.topic_address = this->get_parameter(module_name + ".topic_name").as_string(); - tmp.time_debug_topic_address = - this->get_parameter_or(module_name + ".time_debug_topic_name", std::string("")); - tmp.message_type = - stringToMessageType(this->get_parameter(module_name + ".message_type").as_string()); - if (tmp.message_type != SubscriberMessageType::Unknown) { - chain_modules.emplace_back(tmp); - } else { - RCLCPP_WARN( - this->get_logger(), "Unknown message type for module name: %s, skipping..", - tmp.node_name.c_str()); - } - } - return (initSubscribers(chain_modules)); } void ReactionAnalyzerNode::initAnalyzerVariables() @@ -758,15 +321,6 @@ void ReactionAnalyzerNode::initAnalyzerVariables() entity_pose_.orientation.set__z(entity_q_orientation.z()); entity_pose_.orientation.set__w(entity_q_orientation.w()); - // find minimum radius of sphere that encloses the entity - - entity_search_radius_ = - std::sqrt( - std::pow(node_params_.entity_params.x_l, 2) + std::pow(node_params_.entity_params.y_l, 2) + - std::pow(node_params_.entity_params.z_l, 2)) / - 2.0 + - node_params_.object_search_radius_offset; - tf2::Quaternion goal_pose_q_orientation; goal_pose_q_orientation.setRPY( tier4_autoware_utils::deg2rad(node_params_.goal_pose.roll), @@ -843,101 +397,6 @@ void ReactionAnalyzerNode::initPointcloud() pcl::toROSMsg(point_cloud, *entity_pointcloud_ptr_); } -bool ReactionAnalyzerNode::initSubscribers(const reaction_analyzer::ChainModules & modules) -{ - if (modules.empty()) { - RCLCPP_ERROR(get_logger(), "No module to initialize, failed."); - return false; - } - for (const auto & module : modules) { - if (!module.time_debug_topic_address.empty()) { - auto callback = [this, module](const PublishedTime::ConstSharedPtr & msg) { - this->publishedTimeOutputCallback(module.node_name, msg); - }; - auto subscriber = this->create_subscription<PublishedTime>( - module.time_debug_topic_address, rclcpp::QoS(1), callback, createSubscriptionOptions()); - subscribers_.push_back(subscriber); - published_time_vector_map_[module.node_name] = std::vector<PublishedTime>(); - } else { - RCLCPP_WARN( - this->get_logger(), "Time debug topic is not provided for module name: %s, skipping..", - module.node_name.c_str()); - } - switch (module.message_type) { - case SubscriberMessageType::AckermannControlCommand: { - auto callback = [this, module](const AckermannControlCommand::ConstSharedPtr & msg) { - this->controlCommandOutputCallback(module.node_name, msg); - }; - auto subscriber = this->create_subscription<AckermannControlCommand>( - module.topic_address, rclcpp::QoS(1), callback, createSubscriptionOptions()); - subscribers_.push_back(subscriber); - break; - } - case SubscriberMessageType::Trajectory: { - auto callback = [this, module](const Trajectory::ConstSharedPtr & msg) { - this->trajectoryOutputCallback(module.node_name, msg); - }; - auto subscriber = this->create_subscription<Trajectory>( - module.topic_address, rclcpp::QoS(1), callback, createSubscriptionOptions()); - subscribers_.push_back(subscriber); - break; - } - case SubscriberMessageType::PointCloud2: { - auto callback = [this, module](const PointCloud2::ConstSharedPtr & msg) { - this->pointcloud2OutputCallback(module.node_name, msg); - }; - auto subscriber = this->create_subscription<PointCloud2>( - module.topic_address, rclcpp::SensorDataQoS(), callback, createSubscriptionOptions()); - subscribers_.push_back(subscriber); - break; - } - case SubscriberMessageType::PredictedObjects: { - auto callback = [this, module](const PredictedObjects::ConstSharedPtr & msg) { - this->predictedObjectsOutputCallback(module.node_name, msg); - }; - auto subscriber = this->create_subscription<PredictedObjects>( - module.topic_address, rclcpp::QoS(1), callback, createSubscriptionOptions()); - subscribers_.push_back(subscriber); - break; - } - case SubscriberMessageType::DetectedObjects: { - auto callback = [this, module](const DetectedObjects::ConstSharedPtr & msg) { - this->detectedObjectsOutputCallback(module.node_name, msg); - }; - auto subscriber = this->create_subscription<DetectedObjects>( - module.topic_address, rclcpp::QoS(1), callback, createSubscriptionOptions()); - subscribers_.push_back(subscriber); - break; - } - case SubscriberMessageType::TrackedObjects: { - auto callback = [this, module](const TrackedObjects::ConstSharedPtr & msg) { - this->trackedObjectsOutputCallback(module.node_name, msg); - }; - auto subscriber = this->create_subscription<TrackedObjects>( - module.topic_address, rclcpp::QoS(1), callback, createSubscriptionOptions()); - subscribers_.push_back(subscriber); - break; - } - case SubscriberMessageType::Unknown: - RCLCPP_WARN( - this->get_logger(), "Unknown message type for module name: %s, skipping..", - module.node_name.c_str()); - break; - default: - RCLCPP_WARN( - this->get_logger(), "Unknown message type for module name: %s, skipping..", - module.node_name.c_str()); - break; - } - } - if (subscribers_.empty()) { - RCLCPP_ERROR( - get_logger(), "Subscribers for modules are empty. Node couldn't be initialized. Failed"); - return false; - } - return true; -} - void ReactionAnalyzerNode::initPredictedObjects() { auto generateUUIDMsg = [](const std::string & input) { @@ -970,106 +429,6 @@ void ReactionAnalyzerNode::initPredictedObjects() predicted_objects_ptr_ = std::make_shared<PredictedObjects>(pred_objects); } -void ReactionAnalyzerNode::setControlCommandToBuffer( - std::vector<AckermannControlCommand> & buffer, const AckermannControlCommand & cmd) -{ - const auto last_cmd_time = cmd.stamp; - if (!buffer.empty()) { - for (auto itr = buffer.begin(); itr != buffer.end();) { - const auto expired = (rclcpp::Time(last_cmd_time) - rclcpp::Time(itr->stamp)).seconds() > - node_params_.control_cmd_buffer_time_interval; - - if (expired) { - itr = buffer.erase(itr); - continue; - } - - itr++; - } - } - buffer.emplace_back(cmd); -} - -void ReactionAnalyzerNode::pushPublishedTime( - std::vector<PublishedTime> & published_time_vec, const PublishedTime & published_time) -{ - published_time_vec.emplace_back(published_time); - if (published_time_vec.size() > 1) { - for (auto itr = published_time_vec.begin(); itr != published_time_vec.end();) { - const auto expired = - (rclcpp::Time(published_time.header.stamp) - rclcpp::Time(itr->header.stamp)).seconds() > - node_params_.published_time_expire_duration; - - if (expired) { - itr = published_time_vec.erase(itr); - continue; - } - - itr++; - } - } -} - -std::optional<size_t> ReactionAnalyzerNode::findFirstBrakeIdx( - const std::vector<AckermannControlCommand> & cmd_array, - const std::optional<rclcpp::Time> & spawn_cmd_time) -{ - if ( - cmd_array.size() < static_cast<size_t>(node_params_.min_number_descending_order_control_cmd) || - !spawn_cmd_time) - return {}; - - // wait for enough data after spawn_cmd_time - if ( - rclcpp::Time( - cmd_array.at(cmd_array.size() - node_params_.min_number_descending_order_control_cmd).stamp) < - spawn_cmd_time) - return {}; - - for (size_t i = 0; - i < cmd_array.size() - node_params_.min_number_descending_order_control_cmd + 1; ++i) { - size_t decreased_cmd_counter = 1; // because # of the decreased cmd = iteration + 1 - for (size_t j = i; j < cmd_array.size() - 1; ++j) { - const auto & cmd_first = cmd_array.at(j).longitudinal; - const auto & cmd_second = cmd_array.at(j + 1).longitudinal; - constexpr double jerk_time_epsilon = 0.001; - const auto jerk = - abs(cmd_second.acceleration - cmd_first.acceleration) / - std::max( - (rclcpp::Time(cmd_second.stamp) - rclcpp::Time(cmd_first.stamp)).seconds(), - jerk_time_epsilon); - - if ( - (cmd_second.acceleration < cmd_first.acceleration) && - (jerk > node_params_.min_jerk_for_brake_cmd)) { - decreased_cmd_counter++; - } else { - break; - } - } - if ( - decreased_cmd_counter < - static_cast<size_t>(node_params_.min_number_descending_order_control_cmd)) - continue; - if (node_params_.debug_control_commands) { - std::stringstream ss; - - // debug print to show the first brake command in the all control commands - for (size_t k = 0; k < cmd_array.size(); ++k) { - if (k == i + 1) { - ss << "First Brake(" << cmd_array.at(k).longitudinal.acceleration << ") "; - } else { - ss << cmd_array.at(k).longitudinal.acceleration << " "; - } - } - - RCLCPP_INFO(this->get_logger(), "%s", ss.str().c_str()); - } - return i + 1; - } - return {}; -} - void ReactionAnalyzerNode::initEgoForTest( const LocalizationInitializationState::ConstSharedPtr & initialization_state_ptr, const RouteState::ConstSharedPtr & route_state_ptr, @@ -1130,17 +489,6 @@ void ReactionAnalyzerNode::initEgoForTest( } } -rclcpp::SubscriptionOptions ReactionAnalyzerNode::createSubscriptionOptions() -{ - rclcpp::CallbackGroup::SharedPtr callback_group = - this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); - - auto sub_opt = rclcpp::SubscriptionOptions(); - sub_opt.callback_group = callback_group; - - return sub_opt; -} - void ReactionAnalyzerNode::callOperationModeServiceWithoutResponse() { auto req = std::make_shared<ChangeOperationMode::Request>(); @@ -1160,71 +508,6 @@ void ReactionAnalyzerNode::callOperationModeServiceWithoutResponse() }); } -bool ReactionAnalyzerNode::searchPointcloudNearEntity( - const pcl::PointCloud<pcl::PointXYZ> & pcl_pointcloud) -{ - bool isAnyPointWithinRadius = std::any_of( - pcl_pointcloud.points.begin(), pcl_pointcloud.points.end(), [this](const auto & point) { - return tier4_autoware_utils::calcDistance3d(entity_pose_.position, point) <= - entity_search_radius_; - }); - - if (isAnyPointWithinRadius) { - return true; - } - return false; -} - -bool ReactionAnalyzerNode::searchPredictedObjectsNearEntity( - const PredictedObjects & predicted_objects) -{ - bool isAnyObjectWithinRadius = std::any_of( - predicted_objects.objects.begin(), predicted_objects.objects.end(), - [this](const PredictedObject & object) { - return tier4_autoware_utils::calcDistance3d( - entity_pose_.position, - object.kinematics.initial_pose_with_covariance.pose.position) <= - entity_search_radius_; - }); - - if (isAnyObjectWithinRadius) { - return true; - } - return false; -} - -bool ReactionAnalyzerNode::searchDetectedObjectsNearEntity(const DetectedObjects & detected_objects) -{ - bool isAnyObjectWithinRadius = std::any_of( - detected_objects.objects.begin(), detected_objects.objects.end(), - [this](const DetectedObject & object) { - return tier4_autoware_utils::calcDistance3d( - entity_pose_.position, object.kinematics.pose_with_covariance.pose.position) <= - entity_search_radius_; - }); - - if (isAnyObjectWithinRadius) { - return true; - } - return false; -} - -bool ReactionAnalyzerNode::searchTrackedObjectsNearEntity(const TrackedObjects & tracked_objects) -{ - bool isAnyObjectWithinRadius = std::any_of( - tracked_objects.objects.begin(), tracked_objects.objects.end(), - [this](const TrackedObject & object) { - return tier4_autoware_utils::calcDistance3d( - entity_pose_.position, object.kinematics.pose_with_covariance.pose.position) <= - entity_search_radius_; - }); - - if (isAnyObjectWithinRadius) { - return true; - } - return false; -} - void ReactionAnalyzerNode::reset() { if (test_iteration_count_ >= node_params_.test_iteration) { @@ -1238,53 +521,20 @@ void ReactionAnalyzerNode::reset() test_environment_init_time_ = std::nullopt; last_test_environment_init_request_time_ = std::nullopt; spawn_object_cmd_ = false; - is_output_printed_ = false; is_object_spawned_message_published_ = false; if (topic_publisher_ptr_) { topic_publisher_ptr_->reset(); } std::lock_guard<std::mutex> lock(mutex_); - message_buffers_.clear(); spawn_cmd_time_ = std::nullopt; - for (auto & [key, value] : published_time_vector_map_) { - value.clear(); - } + subscriber_ptr_->reset(); RCLCPP_INFO(this->get_logger(), "Test - %zu is done, resetting..", test_iteration_count_); } void ReactionAnalyzerNode::writeResultsToFile() { // sort the results w.r.t the median value - - const auto sort_by_median = - [this]() -> std::vector<std::tuple<std::string, std::vector<double>, double>> { - std::vector<std::tuple<std::string, std::vector<double>, double>> sorted_data; - - for (const auto & pair : test_results_) { - auto vec = pair.second; - - // Calculate the median - std::sort(vec.begin(), vec.end()); - double median = 0.0; - size_t size = vec.size(); - if (size % 2 == 0) { - median = (vec[size / 2 - 1] + vec[size / 2]) / 2.0; - } else { - median = vec[size / 2]; - } - - sorted_data.emplace_back(pair.first, pair.second, median); - } - - // Sort based on the computed median - std::sort(sorted_data.begin(), sorted_data.end(), [](const auto & a, const auto & b) { - return std::get<2>(a) < std::get<2>(b); // Change to > for descending order - }); - - return sorted_data; - }; - - const auto sorted_data_by_median = sort_by_median(); + const auto sorted_data_by_median = sortResultsByMedian(test_results_); // create csv file auto now = std::chrono::system_clock::now(); diff --git a/tools/reaction_analyzer/src/subscriber.cpp b/tools/reaction_analyzer/src/subscriber.cpp new file mode 100644 index 0000000000000..43fd05515fa0a --- /dev/null +++ b/tools/reaction_analyzer/src/subscriber.cpp @@ -0,0 +1,1088 @@ +// Copyright 2024 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "subscriber.hpp" + +#include <algorithm> +#include <memory> +#include <utility> + +namespace reaction_analyzer::subscriber +{ + +SubscriberBase::SubscriberBase( + rclcpp::Node * node, Odometry::ConstSharedPtr & odometry, + const geometry_msgs::msg::Pose entity_pose, std::atomic<bool> & spawn_object_cmd) +: node_(node), odometry_(odometry), entity_pose_(entity_pose), spawn_object_cmd_(spawn_object_cmd) +{ + // init tf + tf_buffer_ = std::make_shared<tf2_ros::Buffer>(node_->get_clock()); + tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_); + + // init reaction parameters and chain configuration + initReactionChainsAndParams(); + initSubscribers(); +} + +void SubscriberBase::initReactionChainsAndParams() +{ + auto stringToMessageType = [](const std::string & input) { + if (input == "autoware_auto_control_msgs::msg::AckermannControlCommand") { + return SubscriberMessageType::ACKERMANN_CONTROL_COMMAND; + } else if (input == "autoware_auto_planning_msgs::msg::Trajectory") { + return SubscriberMessageType::TRAJECTORY; + } else if (input == "sensor_msgs::msg::PointCloud2") { + return SubscriberMessageType::POINTCLOUD2; + } else if (input == "autoware_auto_perception_msgs::msg::PredictedObjects") { + return SubscriberMessageType::PREDICTED_OBJECTS; + } else if (input == "autoware_auto_perception_msgs::msg::DetectedObjects") { + return SubscriberMessageType::DETECTED_OBJECTS; + } else if (input == "autoware_auto_perception_msgs::msg::TrackedObjects") { + return SubscriberMessageType::TRACKED_OBJECTS; + } else { + return SubscriberMessageType::UNKNOWN; + } + }; + + auto stringToReactionType = [](const std::string & input) { + if (input == "first_brake_params") { + return ReactionType::FIRST_BRAKE; + } else if (input == "search_zero_vel_params") { + return ReactionType::SEARCH_ZERO_VEL; + } else if (input == "search_entity_params") { + return ReactionType::SEARCH_ENTITY; + } else { + return ReactionType::UNKNOWN; + } + }; + + // Init Chains: get the topic addresses and message types of the modules in chain + { + const auto param_key = std::string("subscriber.reaction_chain"); + const auto module_names = node_->list_parameters({param_key}, 3).prefixes; + for (const auto & module_name : module_names) { + const auto splitted_name = split(module_name, '.'); + TopicConfig tmp; + tmp.node_name = splitted_name.back(); + tmp.topic_address = node_->get_parameter(module_name + ".topic_name").as_string(); + tmp.time_debug_topic_address = + node_->get_parameter_or(module_name + ".time_debug_topic_name", std::string("")); + tmp.message_type = + stringToMessageType(node_->get_parameter(module_name + ".message_type").as_string()); + if (tmp.message_type != SubscriberMessageType::UNKNOWN) { + chain_modules_.emplace_back(tmp); + } else { + RCLCPP_WARN( + node_->get_logger(), "Unknown message type for module name: %s, skipping..", + tmp.node_name.c_str()); + } + } + } + + // Init Params: get the parameters for the reaction functions + { + const auto param_key = std::string("subscriber.reaction_params"); + const auto module_names = node_->list_parameters({param_key}, 3).prefixes; + for (const auto & module_name : module_names) { + const auto splitted_name = split(module_name, '.'); + const auto type = stringToReactionType(splitted_name.back()); + switch (type) { + case ReactionType::FIRST_BRAKE: { + reaction_params_.first_brake_params.debug_control_commands = + node_->get_parameter(module_name + ".debug_control_commands").as_bool(); + reaction_params_.first_brake_params.control_cmd_buffer_time_interval = + node_->get_parameter(module_name + ".control_cmd_buffer_time_interval").as_double(); + reaction_params_.first_brake_params.min_number_descending_order_control_cmd = + node_->get_parameter(module_name + ".min_number_descending_order_control_cmd").as_int(); + reaction_params_.first_brake_params.min_jerk_for_brake_cmd = + node_->get_parameter(module_name + ".min_jerk_for_brake_cmd").as_double(); + RCLCPP_INFO_ONCE( + node_->get_logger(), + "First brake parameters are set: debug_control_commands %s, " + "control_cmd_buffer_time_interval %f, min_number_descending_order_control_cmd %zu, " + "min_jerk_for_brake_cmd %f", + reaction_params_.first_brake_params.debug_control_commands ? "true" : "false", + reaction_params_.first_brake_params.control_cmd_buffer_time_interval, + reaction_params_.first_brake_params.min_number_descending_order_control_cmd, + reaction_params_.first_brake_params.min_jerk_for_brake_cmd); + break; + } + case ReactionType::SEARCH_ZERO_VEL: { + reaction_params_.search_zero_vel_params.max_looking_distance = + node_->get_parameter(module_name + ".max_looking_distance").as_double(); + RCLCPP_INFO_ONCE( + node_->get_logger(), "Search Zero Vel parameters are set: max_looking_distance %f", + reaction_params_.search_zero_vel_params.max_looking_distance); + break; + } + case ReactionType::SEARCH_ENTITY: { + reaction_params_.search_entity_params.search_radius = + node_->get_parameter(module_name + ".search_radius").as_double(); + RCLCPP_INFO_ONCE( + node_->get_logger(), "Search Entity parameters are set: search_radius %f", + reaction_params_.search_entity_params.search_radius); + break; + } + default: + RCLCPP_WARN( + node_->get_logger(), "Unknown reaction type for module name: %s, skipping..", + splitted_name.back().c_str()); + break; + } + } + } +} + +bool SubscriberBase::initSubscribers() +{ + if (chain_modules_.empty()) { + RCLCPP_ERROR(node_->get_logger(), "No module to initialize subscribers, failed."); + return false; + } + + for (const auto & module : chain_modules_) { + switch (module.message_type) { + case SubscriberMessageType::ACKERMANN_CONTROL_COMMAND: { + SubscriberVariables<AckermannControlCommand> subscriber_variable; + + if (!module.time_debug_topic_address.empty()) { + // If not empty, user should define a time debug topic + // NOTE: Because message_filters package does not support the messages without headers, we + // can not use the synchronizer. After we reacted, we are going to use the cache + // of the both PublishedTime and AckermannControlCommand subscribers to find the messages + // which have same header time. + + std::function<void(const AckermannControlCommand::ConstSharedPtr &)> callback = + [this, module](const AckermannControlCommand::ConstSharedPtr & ptr) { + this->controlCommandOutputCallback(module.node_name, ptr); + }; + subscriber_variable.sub1_ = + std::make_unique<message_filters::Subscriber<AckermannControlCommand>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.sub1_->registerCallback(std::bind(callback, std::placeholders::_1)); + + subscriber_variable.sub2_ = std::make_unique<message_filters::Subscriber<PublishedTime>>( + node_, module.time_debug_topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + constexpr int cache_size = 5; + subscriber_variable.cache_ = std::make_unique<message_filters::Cache<PublishedTime>>( + *subscriber_variable.sub2_, cache_size); + + } else { + std::function<void(const AckermannControlCommand::ConstSharedPtr &)> callback = + [this, module](const AckermannControlCommand::ConstSharedPtr & ptr) { + this->controlCommandOutputCallback(module.node_name, ptr); + }; + + subscriber_variable.sub1_ = + std::make_unique<message_filters::Subscriber<AckermannControlCommand>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.sub1_->registerCallback(std::bind(callback, std::placeholders::_1)); + RCLCPP_WARN( + node_->get_logger(), + "PublishedTime will not be used for node name: %s. Header timestamp will be used for " + "calculations", + module.node_name.c_str()); + } + // set the variable to map with the topic address + subscriber_variables_map_[module.node_name] = std::move(subscriber_variable); + break; + } + case SubscriberMessageType::TRAJECTORY: { + SubscriberVariables<Trajectory> subscriber_variable; + + if (!module.time_debug_topic_address.empty()) { + std::function<void( + const Trajectory::ConstSharedPtr &, const PublishedTime::ConstSharedPtr &)> + callback = [this, module]( + const Trajectory::ConstSharedPtr & ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) { + this->trajectoryOutputCallback(module.node_name, ptr, published_time_ptr); + }; + + subscriber_variable.sub1_ = std::make_unique<message_filters::Subscriber<Trajectory>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + subscriber_variable.sub2_ = std::make_unique<message_filters::Subscriber<PublishedTime>>( + node_, module.time_debug_topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.synchronizer_ = std::make_unique< + message_filters::Synchronizer<SubscriberVariables<Trajectory>::ExactTimePolicy>>( + SubscriberVariables<Trajectory>::ExactTimePolicy(10), *subscriber_variable.sub1_, + *subscriber_variable.sub2_); + + subscriber_variable.synchronizer_->registerCallback( + std::bind(callback, std::placeholders::_1, std::placeholders::_2)); + + } else { + std::function<void(const Trajectory::ConstSharedPtr &)> callback = + [this, module](const Trajectory::ConstSharedPtr & msg) { + this->trajectoryOutputCallback(module.node_name, msg); + }; + subscriber_variable.sub1_ = std::make_unique<message_filters::Subscriber<Trajectory>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.sub1_->registerCallback(std::bind(callback, std::placeholders::_1)); + RCLCPP_WARN( + node_->get_logger(), + "PublishedTime will not be used for node name: %s. Header timestamp will be used for " + "calculations", + module.node_name.c_str()); + } + subscriber_variables_map_[module.node_name] = std::move(subscriber_variable); + break; + } + case SubscriberMessageType::POINTCLOUD2: { + SubscriberVariables<PointCloud2> subscriber_variable; + + if (!module.time_debug_topic_address.empty()) { + std::function<void( + const PointCloud2::ConstSharedPtr &, const PublishedTime::ConstSharedPtr &)> + callback = [this, module]( + const PointCloud2::ConstSharedPtr & ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) { + this->pointcloud2OutputCallback(module.node_name, ptr, published_time_ptr); + }; + + subscriber_variable.sub1_ = std::make_unique<message_filters::Subscriber<PointCloud2>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + subscriber_variable.sub2_ = std::make_unique<message_filters::Subscriber<PublishedTime>>( + node_, module.time_debug_topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.synchronizer_ = std::make_unique< + message_filters::Synchronizer<SubscriberVariables<PointCloud2>::ExactTimePolicy>>( + SubscriberVariables<PointCloud2>::ExactTimePolicy(10), *subscriber_variable.sub1_, + *subscriber_variable.sub2_); + + subscriber_variable.synchronizer_->registerCallback( + std::bind(callback, std::placeholders::_1, std::placeholders::_2)); + + } else { + std::function<void(const PointCloud2::ConstSharedPtr &)> callback = + [this, module](const PointCloud2::ConstSharedPtr & msg) { + this->pointcloud2OutputCallback(module.node_name, msg); + }; + subscriber_variable.sub1_ = std::make_unique<message_filters::Subscriber<PointCloud2>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.sub1_->registerCallback(std::bind(callback, std::placeholders::_1)); + RCLCPP_WARN( + node_->get_logger(), + "PublishedTime will not be used for node name: %s. Header timestamp will be used for " + "calculations", + module.node_name.c_str()); + } + subscriber_variables_map_[module.node_name] = std::move(subscriber_variable); + break; + } + case SubscriberMessageType::PREDICTED_OBJECTS: { + SubscriberVariables<PredictedObjects> subscriber_variable; + + if (!module.time_debug_topic_address.empty()) { + std::function<void( + const PredictedObjects::ConstSharedPtr &, const PublishedTime::ConstSharedPtr &)> + callback = [this, module]( + const PredictedObjects::ConstSharedPtr & ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) { + this->predictedObjectsOutputCallback(module.node_name, ptr, published_time_ptr); + }; + subscriber_variable.sub1_ = + std::make_unique<message_filters::Subscriber<PredictedObjects>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + subscriber_variable.sub2_ = std::make_unique<message_filters::Subscriber<PublishedTime>>( + node_, module.time_debug_topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.synchronizer_ = std::make_unique< + message_filters::Synchronizer<SubscriberVariables<PredictedObjects>::ExactTimePolicy>>( + SubscriberVariables<PredictedObjects>::ExactTimePolicy(10), *subscriber_variable.sub1_, + *subscriber_variable.sub2_); + + subscriber_variable.synchronizer_->registerCallback( + std::bind(callback, std::placeholders::_1, std::placeholders::_2)); + + } else { + std::function<void(const PredictedObjects::ConstSharedPtr &)> callback = + [this, module](const PredictedObjects::ConstSharedPtr & msg) { + this->predictedObjectsOutputCallback(module.node_name, msg); + }; + subscriber_variable.sub1_ = + std::make_unique<message_filters::Subscriber<PredictedObjects>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.sub1_->registerCallback(std::bind(callback, std::placeholders::_1)); + RCLCPP_WARN( + node_->get_logger(), + "PublishedTime will not be used for node name: %s. Header timestamp will be used for " + "calculations", + module.node_name.c_str()); + } + subscriber_variables_map_[module.node_name] = std::move(subscriber_variable); + break; + } + case SubscriberMessageType::DETECTED_OBJECTS: { + SubscriberVariables<DetectedObjects> subscriber_variable; + + if (!module.time_debug_topic_address.empty()) { + std::function<void( + const DetectedObjects::ConstSharedPtr &, const PublishedTime::ConstSharedPtr &)> + callback = [this, module]( + const DetectedObjects::ConstSharedPtr & ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) { + this->detectedObjectsOutputCallback(module.node_name, ptr, published_time_ptr); + }; + + subscriber_variable.sub1_ = + std::make_unique<message_filters::Subscriber<DetectedObjects>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + subscriber_variable.sub2_ = std::make_unique<message_filters::Subscriber<PublishedTime>>( + node_, module.time_debug_topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.synchronizer_ = std::make_unique< + message_filters::Synchronizer<SubscriberVariables<DetectedObjects>::ExactTimePolicy>>( + SubscriberVariables<DetectedObjects>::ExactTimePolicy(10), *subscriber_variable.sub1_, + *subscriber_variable.sub2_); + + subscriber_variable.synchronizer_->registerCallback( + std::bind(callback, std::placeholders::_1, std::placeholders::_2)); + + } else { + std::function<void(const DetectedObjects::ConstSharedPtr &)> callback = + [this, module](const DetectedObjects::ConstSharedPtr & msg) { + this->detectedObjectsOutputCallback(module.node_name, msg); + }; + subscriber_variable.sub1_ = + std::make_unique<message_filters::Subscriber<DetectedObjects>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.sub1_->registerCallback(std::bind(callback, std::placeholders::_1)); + RCLCPP_WARN( + node_->get_logger(), + "PublishedTime will not be used for node name: %s. Header timestamp will be used for " + "calculations", + module.node_name.c_str()); + } + subscriber_variables_map_[module.node_name] = std::move(subscriber_variable); + + break; + } + case SubscriberMessageType::TRACKED_OBJECTS: { + SubscriberVariables<TrackedObjects> subscriber_variable; + if (!module.time_debug_topic_address.empty()) { + std::function<void( + const TrackedObjects::ConstSharedPtr &, const PublishedTime::ConstSharedPtr &)> + callback = [this, module]( + const TrackedObjects::ConstSharedPtr & ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) { + this->trackedObjectsOutputCallback(module.node_name, ptr, published_time_ptr); + }; + + subscriber_variable.sub1_ = std::make_unique<message_filters::Subscriber<TrackedObjects>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + subscriber_variable.sub2_ = std::make_unique<message_filters::Subscriber<PublishedTime>>( + node_, module.time_debug_topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.synchronizer_ = std::make_unique< + message_filters::Synchronizer<SubscriberVariables<TrackedObjects>::ExactTimePolicy>>( + SubscriberVariables<TrackedObjects>::ExactTimePolicy(10), *subscriber_variable.sub1_, + *subscriber_variable.sub2_); + + subscriber_variable.synchronizer_->registerCallback( + std::bind(callback, std::placeholders::_1, std::placeholders::_2)); + + } else { + std::function<void(const TrackedObjects::ConstSharedPtr &)> callback = + [this, module](const TrackedObjects::ConstSharedPtr & msg) { + this->trackedObjectsOutputCallback(module.node_name, msg); + }; + subscriber_variable.sub1_ = std::make_unique<message_filters::Subscriber<TrackedObjects>>( + node_, module.topic_address, rclcpp::QoS(1).get_rmw_qos_profile(), + createSubscriptionOptions(node_)); + + subscriber_variable.sub1_->registerCallback(std::bind(callback, std::placeholders::_1)); + RCLCPP_WARN( + node_->get_logger(), + "PublishedTime will not be used for node name: %s. Header timestamp will be used for " + "calculations", + module.node_name.c_str()); + } + subscriber_variables_map_[module.node_name] = std::move(subscriber_variable); + break; + } + case SubscriberMessageType::UNKNOWN: + RCLCPP_WARN( + node_->get_logger(), "Unknown message type for module name: %s, skipping..", + module.node_name.c_str()); + break; + default: + RCLCPP_WARN( + node_->get_logger(), "Unknown message type for module name: %s, skipping..", + module.node_name.c_str()); + break; + } + } + std::cout << "---------- size: " << subscriber_variables_map_.size() << std::endl; + return true; +} + +std::optional<std::unordered_map<std::string, BufferVariant>> SubscriberBase::getMessageBuffersMap() +{ + std::lock_guard<std::mutex> lock(mutex_); + if (message_buffers_.empty()) { + RCLCPP_ERROR(node_->get_logger(), "No message buffers are initialized."); + return std::nullopt; + } + + // Check all reacted or not + bool all_reacted = true; + for (const auto & [key, variant] : message_buffers_) { + if (auto * control_message = std::get_if<ControlCommandBuffer>(&variant)) { + if (!control_message->second) { + all_reacted = false; + } + } else if (auto * planning_message = std::get_if<TrajectoryBuffer>(&variant)) { + if (!planning_message->has_value()) { + all_reacted = false; + } + } else if (auto * pointcloud_message = std::get_if<PointCloud2Buffer>(&variant)) { + if (!pointcloud_message->has_value()) { + all_reacted = false; + } + } else if (auto * predicted_objects_message = std::get_if<PredictedObjectsBuffer>(&variant)) { + if (!predicted_objects_message->has_value()) { + all_reacted = false; + } + } else if (auto * detected_objects_message = std::get_if<DetectedObjectsBuffer>(&variant)) { + if (!detected_objects_message->has_value()) { + all_reacted = false; + } + } else if (auto * tracked_objects_message = std::get_if<TrackedObjectsBuffer>(&variant)) { + if (!tracked_objects_message->has_value()) { + all_reacted = false; + } + } + } + if (!all_reacted) { + return std::nullopt; + } + return message_buffers_; +} + +void SubscriberBase::reset() +{ + std::lock_guard<std::mutex> lock(mutex_); + message_buffers_.clear(); +} + +// Callbacks + +void SubscriberBase::controlCommandOutputCallback( + const std::string & node_name, const AckermannControlCommand::ConstSharedPtr & msg_ptr) +{ + std::lock_guard<std::mutex> lock(mutex_); + auto & variant = message_buffers_[node_name]; + if (!std::holds_alternative<ControlCommandBuffer>(variant)) { + ControlCommandBuffer buffer(std::vector<AckermannControlCommand>{*msg_ptr}, std::nullopt); + variant = buffer; + } + auto & cmd_buffer = std::get<ControlCommandBuffer>(variant); + if (cmd_buffer.second) { + // reacted + return; + } + setControlCommandToBuffer(cmd_buffer.first, *msg_ptr); + if (!spawn_object_cmd_) return; + + const auto brake_idx = findFirstBrakeIdx(cmd_buffer.first); + if (brake_idx) { + RCLCPP_INFO(node_->get_logger(), "%s reacted without published time", node_name.c_str()); + const auto brake_cmd = cmd_buffer.first.at(brake_idx.value()); + + // TODO(brkay54): update here if message_filters package add the support for the messages which + // does not have header + const auto & subscriber_variant = + std::get<SubscriberVariables<AckermannControlCommand>>(subscriber_variables_map_[node_name]); + + // check if the cache was initialized or not, if there is, use it to set the published time + if (subscriber_variant.cache_) { + // use cache to get the published time + const auto published_time_vec = + subscriber_variant.cache_->getSurroundingInterval(brake_cmd.stamp, node_->now()); + if (!published_time_vec.empty()) { + for (const auto & published_time : published_time_vec) { + if (published_time->header.stamp == brake_cmd.stamp) { + cmd_buffer.second = brake_cmd; + cmd_buffer.second->stamp = published_time->published_stamp; + RCLCPP_INFO(node_->get_logger(), "%s reacted with published time", node_name.c_str()); + return; + } + } + RCLCPP_ERROR( + node_->get_logger(), "Published time couldn't found for the node: %s", node_name.c_str()); + + } else { + RCLCPP_ERROR( + node_->get_logger(), "Published time vector is empty for the node: %s", + node_name.c_str()); + } + } else { + cmd_buffer.second = brake_cmd; + } + } +} + +void SubscriberBase::trajectoryOutputCallback( + const std::string & node_name, const Trajectory::ConstSharedPtr & msg_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + const auto current_odometry_ptr = odometry_; + if (!std::holds_alternative<TrajectoryBuffer>(variant)) { + TrajectoryBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + + if ( + !current_odometry_ptr || !spawn_object_cmd_ || + std::get<TrajectoryBuffer>(variant).has_value()) { + return; + } + + const auto nearest_seg_idx = motion_utils::findNearestSegmentIndex( + msg_ptr->points, current_odometry_ptr->pose.pose.position); + + const auto nearest_objects_seg_idx = + motion_utils::findNearestIndex(msg_ptr->points, entity_pose_.position); + + const auto zero_vel_idx = motion_utils::searchZeroVelocityIndex( + msg_ptr->points, nearest_seg_idx, nearest_objects_seg_idx); + + if (zero_vel_idx) { + std::get<TrajectoryBuffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "%s reacted without published time", node_name.c_str()); + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +void SubscriberBase::trajectoryOutputCallback( + const std::string & node_name, const Trajectory::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + const auto current_odometry_ptr = odometry_; + if (!std::holds_alternative<TrajectoryBuffer>(variant)) { + TrajectoryBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + + if ( + !current_odometry_ptr || !spawn_object_cmd_ || + std::get<TrajectoryBuffer>(variant).has_value()) { + return; + } + + const auto nearest_seg_idx = motion_utils::findNearestSegmentIndex( + msg_ptr->points, current_odometry_ptr->pose.pose.position); + + // find the target index which we will search for zero velocity + auto tmp_target_idx = getIndexAfterDistance( + *msg_ptr, nearest_seg_idx, reaction_params_.search_zero_vel_params.max_looking_distance); + if (tmp_target_idx == msg_ptr->points.size() - 1) { + tmp_target_idx = msg_ptr->points.size() - 2; // Last trajectory points might be zero velocity + } + const auto target_idx = tmp_target_idx; + const auto zero_vel_idx = + motion_utils::searchZeroVelocityIndex(msg_ptr->points, nearest_seg_idx, target_idx); + + if (zero_vel_idx) { + std::get<TrajectoryBuffer>(variant) = *msg_ptr; + + // set published time + std::get<TrajectoryBuffer>(variant)->header.stamp = published_time_ptr->published_stamp; + RCLCPP_INFO(node_->get_logger(), "%s reacted with published time", node_name.c_str()); + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +void SubscriberBase::pointcloud2OutputCallback( + const std::string & node_name, const PointCloud2::ConstSharedPtr & msg_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<PointCloud2Buffer>(variant)) { + PointCloud2Buffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + + if (!spawn_object_cmd_ || std::get<PointCloud2Buffer>(variant).has_value()) { + return; + } + + // transform pointcloud + geometry_msgs::msg::TransformStamped transform_stamped{}; + try { + transform_stamped = tf_buffer_->lookupTransform( + "map", msg_ptr->header.frame_id, node_->now(), rclcpp::Duration::from_seconds(0.1)); + } catch (tf2::TransformException & ex) { + RCLCPP_ERROR_STREAM( + node_->get_logger(), + "Failed to look up transform from " << msg_ptr->header.frame_id << " to map"); + return; + } + + // transform by using eigen matrix + PointCloud2 transformed_points{}; + const Eigen::Matrix4f affine_matrix = + tf2::transformToEigen(transform_stamped.transform).matrix().cast<float>(); + pcl_ros::transformPointCloud(affine_matrix, *msg_ptr, transformed_points); + + pcl::PointCloud<pcl::PointXYZ> pcl_pointcloud; + pcl::fromROSMsg(transformed_points, pcl_pointcloud); + + if (searchPointcloudNearEntity(pcl_pointcloud)) { + std::get<PointCloud2Buffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "%s reacted without published time", node_name.c_str()); + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} +void SubscriberBase::pointcloud2OutputCallback( + const std::string & node_name, const PointCloud2::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<PointCloud2Buffer>(variant)) { + PointCloud2Buffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + + if (!spawn_object_cmd_ || std::get<PointCloud2Buffer>(variant).has_value()) { + return; + } + + // transform pointcloud + geometry_msgs::msg::TransformStamped transform_stamped{}; + try { + transform_stamped = tf_buffer_->lookupTransform( + "map", msg_ptr->header.frame_id, node_->now(), rclcpp::Duration::from_seconds(0.1)); + } catch (tf2::TransformException & ex) { + RCLCPP_ERROR_STREAM( + node_->get_logger(), + "Failed to look up transform from " << msg_ptr->header.frame_id << " to map"); + return; + } + + // transform by using eigen matrix + PointCloud2 transformed_points{}; + const Eigen::Matrix4f affine_matrix = + tf2::transformToEigen(transform_stamped.transform).matrix().cast<float>(); + pcl_ros::transformPointCloud(affine_matrix, *msg_ptr, transformed_points); + + pcl::PointCloud<pcl::PointXYZ> pcl_pointcloud; + pcl::fromROSMsg(transformed_points, pcl_pointcloud); + + if (searchPointcloudNearEntity(pcl_pointcloud)) { + std::get<PointCloud2Buffer>(variant) = *msg_ptr; + // set published time + std::get<PointCloud2Buffer>(variant)->header.stamp = published_time_ptr->published_stamp; + RCLCPP_INFO(node_->get_logger(), "%s reacted with published time", node_name.c_str()); + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} +void SubscriberBase::predictedObjectsOutputCallback( + const std::string & node_name, const PredictedObjects::ConstSharedPtr & msg_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<PredictedObjectsBuffer>(variant)) { + PredictedObjectsBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + + if ( + !spawn_object_cmd_ || msg_ptr->objects.empty() || + std::get<PredictedObjectsBuffer>(variant).has_value()) { + return; + } + + if (searchPredictedObjectsNearEntity(*msg_ptr)) { + std::get<PredictedObjectsBuffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "%s reacted without published time", node_name.c_str()); + + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +void SubscriberBase::predictedObjectsOutputCallback( + const std::string & node_name, const PredictedObjects::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<PredictedObjectsBuffer>(variant)) { + PredictedObjectsBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + + if ( + !spawn_object_cmd_ || msg_ptr->objects.empty() || + std::get<PredictedObjectsBuffer>(variant).has_value()) { + return; + } + + if (searchPredictedObjectsNearEntity(*msg_ptr)) { + std::get<PredictedObjectsBuffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "%s reacted with published time", node_name.c_str()); + + // set published time + std::get<PredictedObjectsBuffer>(variant)->header.stamp = published_time_ptr->published_stamp; + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +void SubscriberBase::detectedObjectsOutputCallback( + const std::string & node_name, const DetectedObjects::ConstSharedPtr & msg_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<DetectedObjectsBuffer>(variant)) { + DetectedObjectsBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + if ( + !spawn_object_cmd_ || msg_ptr->objects.empty() || + std::get<DetectedObjectsBuffer>(variant).has_value()) { + return; + } + + // transform objects + geometry_msgs::msg::TransformStamped transform_stamped{}; + try { + transform_stamped = tf_buffer_->lookupTransform( + "map", msg_ptr->header.frame_id, node_->now(), rclcpp::Duration::from_seconds(0.1)); + } catch (tf2::TransformException & ex) { + RCLCPP_ERROR_STREAM( + node_->get_logger(), + "Failed to look up transform from " << msg_ptr->header.frame_id << " to map"); + return; + } + + DetectedObjects output_objs; + output_objs = *msg_ptr; + for (auto & obj : output_objs.objects) { + geometry_msgs::msg::PoseStamped output_stamped, input_stamped; + input_stamped.pose = obj.kinematics.pose_with_covariance.pose; + tf2::doTransform(input_stamped, output_stamped, transform_stamped); + obj.kinematics.pose_with_covariance.pose = output_stamped.pose; + } + if (searchDetectedObjectsNearEntity(output_objs)) { + std::get<DetectedObjectsBuffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "%s reacted without published time", node_name.c_str()); + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +void SubscriberBase::detectedObjectsOutputCallback( + const std::string & node_name, const DetectedObjects::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<DetectedObjectsBuffer>(variant)) { + DetectedObjectsBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + if ( + !spawn_object_cmd_ || msg_ptr->objects.empty() || + std::get<DetectedObjectsBuffer>(variant).has_value()) { + return; + } + + // transform objects + geometry_msgs::msg::TransformStamped transform_stamped{}; + try { + transform_stamped = tf_buffer_->lookupTransform( + "map", msg_ptr->header.frame_id, node_->now(), rclcpp::Duration::from_seconds(0.1)); + } catch (tf2::TransformException & ex) { + RCLCPP_ERROR_STREAM( + node_->get_logger(), + "Failed to look up transform from " << msg_ptr->header.frame_id << " to map"); + return; + } + + DetectedObjects output_objs; + output_objs = *msg_ptr; + for (auto & obj : output_objs.objects) { + geometry_msgs::msg::PoseStamped output_stamped, input_stamped; + input_stamped.pose = obj.kinematics.pose_with_covariance.pose; + tf2::doTransform(input_stamped, output_stamped, transform_stamped); + obj.kinematics.pose_with_covariance.pose = output_stamped.pose; + } + if (searchDetectedObjectsNearEntity(output_objs)) { + std::get<DetectedObjectsBuffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "%s reacted with published time", node_name.c_str()); + + // set published time + std::get<DetectedObjectsBuffer>(variant)->header.stamp = published_time_ptr->published_stamp; + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +void SubscriberBase::trackedObjectsOutputCallback( + const std::string & node_name, const TrackedObjects::ConstSharedPtr & msg_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<TrackedObjectsBuffer>(variant)) { + TrackedObjectsBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + if ( + !spawn_object_cmd_ || msg_ptr->objects.empty() || + std::get<TrackedObjectsBuffer>(variant).has_value()) { + return; + } + + if (searchTrackedObjectsNearEntity(*msg_ptr)) { + std::get<TrackedObjectsBuffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "Reacted %s", node_name.c_str()); + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +void SubscriberBase::trackedObjectsOutputCallback( + const std::string & node_name, const TrackedObjects::ConstSharedPtr & msg_ptr, + const PublishedTime::ConstSharedPtr & published_time_ptr) +{ + mutex_.lock(); + auto variant = message_buffers_[node_name]; + if (!std::holds_alternative<TrackedObjectsBuffer>(variant)) { + TrackedObjectsBuffer buffer(std::nullopt); + variant = buffer; + message_buffers_[node_name] = variant; + } + mutex_.unlock(); + if ( + !spawn_object_cmd_ || msg_ptr->objects.empty() || + std::get<TrackedObjectsBuffer>(variant).has_value()) { + return; + } + + if (searchTrackedObjectsNearEntity(*msg_ptr)) { + std::get<TrackedObjectsBuffer>(variant) = *msg_ptr; + RCLCPP_INFO(node_->get_logger(), "%s reacted with published time", node_name.c_str()); + // set published time + std::get<TrackedObjectsBuffer>(variant)->header.stamp = published_time_ptr->published_stamp; + mutex_.lock(); + message_buffers_[node_name] = variant; + mutex_.unlock(); + } +} + +bool SubscriberBase::searchPointcloudNearEntity( + const pcl::PointCloud<pcl::PointXYZ> & pcl_pointcloud) +{ + bool isAnyPointWithinRadius = std::any_of( + pcl_pointcloud.points.begin(), pcl_pointcloud.points.end(), [this](const auto & point) { + return tier4_autoware_utils::calcDistance3d(entity_pose_.position, point) <= + reaction_params_.search_entity_params.search_radius; + }); + + if (isAnyPointWithinRadius) { + return true; + } + return false; +} + +bool SubscriberBase::searchPredictedObjectsNearEntity(const PredictedObjects & predicted_objects) +{ + bool isAnyObjectWithinRadius = std::any_of( + predicted_objects.objects.begin(), predicted_objects.objects.end(), + [this](const PredictedObject & object) { + return tier4_autoware_utils::calcDistance3d( + entity_pose_.position, + object.kinematics.initial_pose_with_covariance.pose.position) <= + reaction_params_.search_entity_params.search_radius; + }); + + if (isAnyObjectWithinRadius) { + return true; + } + return false; +} + +bool SubscriberBase::searchDetectedObjectsNearEntity(const DetectedObjects & detected_objects) +{ + bool isAnyObjectWithinRadius = std::any_of( + detected_objects.objects.begin(), detected_objects.objects.end(), + [this](const DetectedObject & object) { + return tier4_autoware_utils::calcDistance3d( + entity_pose_.position, object.kinematics.pose_with_covariance.pose.position) <= + reaction_params_.search_entity_params.search_radius; + }); + + if (isAnyObjectWithinRadius) { + return true; + } + return false; +} + +bool SubscriberBase::searchTrackedObjectsNearEntity(const TrackedObjects & tracked_objects) +{ + bool isAnyObjectWithinRadius = std::any_of( + tracked_objects.objects.begin(), tracked_objects.objects.end(), + [this](const TrackedObject & object) { + return tier4_autoware_utils::calcDistance3d( + entity_pose_.position, object.kinematics.pose_with_covariance.pose.position) <= + reaction_params_.search_entity_params.search_radius; + }); + + if (isAnyObjectWithinRadius) { + return true; + } + return false; +} + +std::optional<size_t> SubscriberBase::findFirstBrakeIdx( + const std::vector<AckermannControlCommand> & cmd_array) +{ + if ( + cmd_array.size() < + reaction_params_.first_brake_params.min_number_descending_order_control_cmd) { + RCLCPP_WARN_ONCE( + node_->get_logger(), + "Control command buffer size is less than the minimum required size for first brake check"); + return {}; + } + + for (size_t i = 0; + i < cmd_array.size() - + reaction_params_.first_brake_params.min_number_descending_order_control_cmd + 1; + ++i) { + size_t decreased_cmd_counter = 1; // because # of the decreased cmd = iteration + 1 + for (size_t j = i; j < cmd_array.size() - 1; ++j) { + const auto & cmd_first = cmd_array.at(j).longitudinal; + const auto & cmd_second = cmd_array.at(j + 1).longitudinal; + constexpr double jerk_time_epsilon = 0.001; + const auto jerk = + abs(cmd_second.acceleration - cmd_first.acceleration) / + std::max( + (rclcpp::Time(cmd_second.stamp) - rclcpp::Time(cmd_first.stamp)).seconds(), + jerk_time_epsilon); + + if ( + (cmd_second.acceleration < cmd_first.acceleration) && + (jerk > reaction_params_.first_brake_params.min_jerk_for_brake_cmd)) { + decreased_cmd_counter++; + } else { + break; + } + } + if ( + decreased_cmd_counter < + static_cast<size_t>( + reaction_params_.first_brake_params.min_number_descending_order_control_cmd)) + continue; + if (reaction_params_.first_brake_params.debug_control_commands) { + std::stringstream ss; + + // debug print to show the first brake command in the all control commands + for (size_t k = 0; k < cmd_array.size(); ++k) { + if (k == i + 1) { + ss << "First Brake(" << cmd_array.at(k).longitudinal.acceleration << ") "; + } else { + ss << cmd_array.at(k).longitudinal.acceleration << " "; + } + } + + RCLCPP_INFO(node_->get_logger(), "%s", ss.str().c_str()); + } + return i + 1; + } + return {}; +} + +void SubscriberBase::setControlCommandToBuffer( + std::vector<AckermannControlCommand> & buffer, const AckermannControlCommand & cmd) +{ + const auto last_cmd_time = cmd.stamp; + if (!buffer.empty()) { + for (auto itr = buffer.begin(); itr != buffer.end();) { + const auto expired = (rclcpp::Time(last_cmd_time) - rclcpp::Time(itr->stamp)).seconds() > + reaction_params_.first_brake_params.control_cmd_buffer_time_interval; + + if (expired) { + itr = buffer.erase(itr); + continue; + } + + itr++; + } + } + buffer.emplace_back(cmd); +} +} // namespace reaction_analyzer::subscriber diff --git a/tools/reaction_analyzer/src/topic_publisher.cpp b/tools/reaction_analyzer/src/topic_publisher.cpp index 6f2d27beb3e5d..079bc8fb057e5 100644 --- a/tools/reaction_analyzer/src/topic_publisher.cpp +++ b/tools/reaction_analyzer/src/topic_publisher.cpp @@ -145,17 +145,6 @@ void TopicPublisher::reset() void TopicPublisher::initRosbagPublishers() { - // Necessary lambda functions for string manipulation and message type conversion - auto split = [](const std::string & str, const char delim) { - std::vector<std::string> elems; - std::stringstream ss(str); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; - }; - auto string_to_publisher_message_type = [](const std::string & input) { if (input == "sensor_msgs/msg/PointCloud2") { return PublisherMessageType::PointCloud2; @@ -223,6 +212,7 @@ void TopicPublisher::initRosbagPublishers() current_topic.c_str()); continue; } + // Record timestamp timestamps_per_topic[current_topic].emplace_back(bag_message->time_stamp); // Deserialize and store the first message as a sample @@ -498,7 +488,7 @@ void TopicPublisher::initRosbagPublishers() return string_to_publisher_message_type( it->topic_metadata.type); // Return the message type if found } else { - return PublisherMessageType::Unknown; // + return PublisherMessageType::Unknown; } }; @@ -780,14 +770,13 @@ void TopicPublisher::initRosbagPublishers() } } - // initialize timers and message publishers - std::unordered_map<std::string, PublisherVariables<PointCloud2>> pointcloud_variables_map; // temp map for pointcloud publishers + // initialize timers and message publishers for (auto & [topic_name, variant] : topic_publisher_map_) { PublisherVarAccessor accessor; - const auto & topic_ref = topic_name; // Create a reference to topic_name for capture + const auto & topic_ref = topic_name; const auto period_ns = std::chrono::duration<double, std::nano>( std::visit([&](const auto & var) { return accessor.getPeriod(var); }, variant)); @@ -825,11 +814,7 @@ void TopicPublisher::initRosbagPublishers() variant); } - // Set the pointcloud publisher - - const auto period_pointcloud_ns = std::chrono::duration_cast<std::chrono::nanoseconds>( - std::chrono::duration<double>(topic_publisher_params_.pointcloud_publisher_period)); - + // Set the point cloud publisher timers if (pointcloud_variables_map.empty()) { RCLCPP_ERROR(node_->get_logger(), "No pointcloud publishers found!"); rclcpp::shutdown(); @@ -856,6 +841,9 @@ void TopicPublisher::initRosbagPublishers() } // Create the timer(s) to publish PointCloud2 Messages + const auto period_pointcloud_ns = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::duration<double>(topic_publisher_params_.pointcloud_publisher_period)); + if (pointcloud_publisher_type_ != PointcloudPublisherType::AsyncPublisher) { // Create 1 timer to publish all PointCloud2 messages pointcloud_sync_publish_timer_ = node_->create_wall_timer(period_pointcloud_ns, [this]() { @@ -888,5 +876,4 @@ void TopicPublisher::initRosbagPublishers() one_shot_timer_shared_ptr_ = one_shot_timer; // Store a weak pointer to the timer } } - } // namespace reaction_analyzer::topic_publisher diff --git a/tools/reaction_analyzer/src/utils.cpp b/tools/reaction_analyzer/src/utils.cpp new file mode 100644 index 0000000000000..9b9775e479d14 --- /dev/null +++ b/tools/reaction_analyzer/src/utils.cpp @@ -0,0 +1,85 @@ +// Copyright 2024 The Autoware Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "utils.hpp" + +namespace reaction_analyzer +{ +rclcpp::SubscriptionOptions createSubscriptionOptions(rclcpp::Node * node) +{ + rclcpp::CallbackGroup::SharedPtr callback_group = + node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive); + + auto sub_opt = rclcpp::SubscriptionOptions(); + sub_opt.callback_group = callback_group; + + return sub_opt; +} + +std::vector<std::tuple<std::string, std::vector<double>, double>> sortResultsByMedian( + const std::unordered_map<std::string, std::vector<double>> test_results) +{ + std::vector<std::tuple<std::string, std::vector<double>, double>> sorted_data; + for (const auto & pair : test_results) { + auto vec = pair.second; + + // Calculate the median + std::sort(vec.begin(), vec.end()); + double median = 0.0; + size_t size = vec.size(); + if (size % 2 == 0) { + median = (vec[size / 2 - 1] + vec[size / 2]) / 2.0; + } else { + median = vec[size / 2]; + } + + sorted_data.emplace_back(pair.first, pair.second, median); + } + + // Sort based on the computed median + std::sort(sorted_data.begin(), sorted_data.end(), [](const auto & a, const auto & b) { + return std::get<2>(a) < std::get<2>(b); // Change to > for descending order + }); + + return sorted_data; +} + +std::vector<std::string> split(const std::string & str, char delimiter) +{ + std::vector<std::string> elements; + std::stringstream ss(str); + std::string item; + while (std::getline(ss, item, delimiter)) { + elements.push_back(item); + } + return elements; +} + +size_t getIndexAfterDistance(const Trajectory & traj, const size_t curr_id, const double distance) +{ + // Get Current Trajectory Point + const TrajectoryPoint & curr_p = traj.points.at(curr_id); + + size_t target_id = curr_id; + double current_distance = 0.0; + for (size_t traj_id = curr_id + 1; traj_id < traj.points.size(); ++traj_id) { + current_distance = tier4_autoware_utils::calcDistance3d(traj.points.at(traj_id), curr_p); + if (current_distance >= distance) { + break; + } + target_id = traj_id; + } + return target_id; +} +} // namespace reaction_analyzer