Skip to content

Commit fb73b91

Browse files
zulfaqar-azmi-t4claudepre-commit-ci-lite[bot]Copilot
authored
feat(trajectory_selector): combine validator and concatenator (#12532)
* feat(concatenator): add concatenator Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * feat: combine concatenator with validator Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: remove explicit find package, and pre-commit Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: failing test Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: create public interface for concatenator, and move concatenator to detail folder Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * feat: separate validator to validator interface and initialize selector Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix loading parameters Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix(node): publish validated trajectories; remove dead member and unjustified mutable on_timer() computed the validated result but never called publish(), making the node a no-op at the output. Both integration tests were silently timing out because of this. Also removed sub_trajectories_ which was declared but never assigned in subscribers(), and dropped the unjustified `mutable` qualifier from time_keeper_ (no const method ever writes to it). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix(validator_interface): use validator_ptr_ in validate_trajectories validate_trajectories() was constructing a new TrajectoryValidator on every call (copying the plugins_ vector each time) instead of using the validator_ptr_ member that is initialized in the constructor for exactly this purpose. validator_ptr_ was live memory that was never called. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix(validator_interface): remove redundant diagnostics clear; merge duplicate DebugPublisher Two cleanups in validate_trajectories / publishers(): 1. The first diagnostics_interface_ptr_->clear() was dead work: the diagnostics are cleared again five lines later, just before the add_key_value loop, so the first call never had observable effect. 2. pub_validation_reports_ and pub_debug_ were both initialized to a DebugPublisher with the identical prefix "~/debug". A single DebugPublisher handles multiple sub-topics; the duplicate object added confusion without benefit. Removed pub_validation_reports_ and routed its one call-site through pub_debug_. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: add test Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: rename context Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * style(pre-commit): autofix Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: return if concatenated is empty Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * doc: docstring Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: remove failed spellcheck Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix initial processing time value Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: rename interface to wrapper Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * remove processing time and add unit test Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * style(pre-commit): autofix * separate trajectory selector Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: precommit Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * readme Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: addresses copilot comments Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> * fix: address minor copilot comment Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> --------- Signed-off-by: Zulfaqar Azmi <zulfaqar.azmi@tier4.jp> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 62be9cd commit fb73b91

36 files changed

Lines changed: 1833 additions & 719 deletions

planning/autoware_trajectory_concatenator/CMakeLists.txt

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,32 @@ generate_parameter_library(${PROJECT_NAME}_param
88
param/parameter_struct.yaml
99
)
1010

11-
include_directories(
12-
SYSTEM
13-
)
14-
1511
ament_auto_add_library(${PROJECT_NAME} SHARED
16-
src/trajectory_concatenator_node.cpp
12+
src/detail/trajectory_concatenator.cpp
1713
)
1814

1915
target_link_libraries(${PROJECT_NAME}
2016
${PROJECT_NAME}_param
2117
)
2218

23-
target_compile_options(${PROJECT_NAME} PUBLIC
24-
-Wno-error=deprecated-declarations
25-
)
26-
27-
rclcpp_components_register_node(${PROJECT_NAME}
28-
PLUGIN "autoware::trajectory_concatenator::TrajectoryConcatenatorNode"
29-
EXECUTABLE ${PROJECT_NAME}_node
30-
)
31-
32-
ament_auto_package(
33-
INSTALL_TO_SHARE
34-
config
35-
launch
36-
)
19+
if(BUILD_TESTING)
20+
find_package(ament_cmake_gtest REQUIRED)
21+
find_package(ament_lint_auto REQUIRED)
22+
ament_auto_find_test_dependencies()
23+
24+
ament_add_gtest(test_trajectory_concatenator
25+
test/test_trajectory_concatenator.cpp
26+
)
27+
target_link_libraries(test_trajectory_concatenator
28+
${PROJECT_NAME}
29+
)
30+
31+
ament_add_gtest(test_trajectory_concatenator_wrapper
32+
test/test_trajectory_concatenator_wrapper.cpp
33+
)
34+
target_link_libraries(test_trajectory_concatenator_wrapper
35+
${PROJECT_NAME}
36+
)
37+
endif()
38+
39+
ament_auto_package()

planning/autoware_trajectory_concatenator/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ This node aggregates trajectory candidates from multiple trajectory generators i
66

77
## Algorithm Overview
88

9-
When a message arrives on `~/input/trajectories`, the node splits it by `generator_id` and updates an in-memory buffer so that only the most recent trajectory set for each generator is retained.
9+
When a `CandidateTrajectories` message is received (from either `~/input/trajectories_generative` or `~/input/trajectories_backup`), the library splits it by `generator_id` and updates an in-memory buffer so that only the most recent trajectory set for each generator is retained.
1010

1111
A 100 ms timer then scans this buffer and drops any entry whose header stamp is older than the configured duration_time. Immediately after pruning, the timer concatenates all remaining trajectories and their accompanying `generator_info` arrays, publishes the aggregated message.
1212

1313
## Interface
1414

1515
### Topics
1616

17-
| Direction | Topic name | Message Type | Description |
18-
| ---------- | ----------------------- | ----------------------------------------------------------- | ---------------------------------------------- |
19-
| Subscriber | `~/input/trajectories` | `autoware_internal_planning_msgs/msg/CandidateTrajectories` | Trajectory sets produced by each generator |
20-
| Publisher | `~/output/trajectories` | `autoware_internal_planning_msgs/msg/CandidateTrajectories` | Concatenated list of all buffered trajectories |
17+
| Direction | Topic name | Message Type | Description |
18+
| ---------- | --------------------------------- | ----------------------------------------------------------- | ---------------------------------------------- |
19+
| Subscriber | `~/input/trajectories_generative` | `autoware_internal_planning_msgs/msg/CandidateTrajectories` | Trajectory sets from generative planners |
20+
| Subscriber | `~/input/trajectories_backup` | `autoware_internal_planning_msgs/msg/CandidateTrajectories` | Trajectory sets from backup planners |
21+
| Publisher | `~/output/trajectories` | `autoware_internal_planning_msgs/msg/CandidateTrajectories` | Concatenated list of all buffered trajectories |
2122

2223
### Parameters
2324

planning/autoware_trajectory_concatenator/config/concatenator.param.yaml

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2025 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef AUTOWARE__TRAJECTORY_CONCATENATOR__DETAIL__TRAJECTORY_CONCATENATOR_HPP_
16+
#define AUTOWARE__TRAJECTORY_CONCATENATOR__DETAIL__TRAJECTORY_CONCATENATOR_HPP_
17+
18+
#include <autoware_trajectory_concatenator/autoware_trajectory_concatenator_param.hpp>
19+
#include <builtin_interfaces/msg/time.hpp>
20+
21+
#include <autoware_internal_planning_msgs/msg/candidate_trajectories.hpp>
22+
23+
#include <string>
24+
#include <unordered_map>
25+
#include <utility>
26+
27+
namespace autoware::trajectory_concatenator
28+
{
29+
30+
using autoware_internal_planning_msgs::msg::CandidateTrajectories;
31+
32+
/**
33+
* @brief Aggregates candidate trajectories from multiple generators into a single set.
34+
*/
35+
class TrajectoryConcatenator
36+
{
37+
public:
38+
/**
39+
* @brief Constructs the concatenator with the given parameters.
40+
* @param params Initial concatenator parameters.
41+
*/
42+
explicit TrajectoryConcatenator(concatenator::Params params) : params_(std::move(params)) {}
43+
44+
/**
45+
* @brief Replaces the current parameters.
46+
* @param params New parameter values.
47+
*/
48+
void update_parameters(const concatenator::Params & params) { params_ = params; }
49+
50+
/**
51+
* @brief Stores the latest trajectories from the message's generator.
52+
* @param msg Candidate trajectories message from a single generator.
53+
*/
54+
void add_candidate(const CandidateTrajectories & msg);
55+
56+
/**
57+
* @brief Returns the merged set of all non-stale generator trajectories.
58+
* @param current_time Current ROS time used to filter out stale entries.
59+
*/
60+
[[nodiscard]] CandidateTrajectories get_concatenated(
61+
const builtin_interfaces::msg::Time & current_time);
62+
63+
private:
64+
concatenator::Params params_;
65+
std::unordered_map<std::string, CandidateTrajectories> buffer_;
66+
};
67+
68+
} // namespace autoware::trajectory_concatenator
69+
70+
#endif // AUTOWARE__TRAJECTORY_CONCATENATOR__DETAIL__TRAJECTORY_CONCATENATOR_HPP_
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2026 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef AUTOWARE__TRAJECTORY_CONCATENATOR__TRAJECTORY_CONCATENATOR_WRAPPER_HPP_
16+
#define AUTOWARE__TRAJECTORY_CONCATENATOR__TRAJECTORY_CONCATENATOR_WRAPPER_HPP_
17+
18+
#include <autoware/trajectory_concatenator/detail/trajectory_concatenator.hpp>
19+
#include <autoware_trajectory_concatenator/autoware_trajectory_concatenator_param.hpp>
20+
#include <builtin_interfaces/msg/time.hpp>
21+
#include <rclcpp/rclcpp.hpp>
22+
23+
#include <autoware_internal_planning_msgs/msg/candidate_trajectories.hpp>
24+
25+
#include <memory>
26+
#include <string>
27+
28+
namespace autoware::trajectory_concatenator
29+
{
30+
/**
31+
* @brief Adapter for TrajectoryConcatenator: handles parameter updates and thread safety.
32+
*/
33+
class TrajectoryConcatenatorWrapper
34+
{
35+
public:
36+
/**
37+
* @brief Constructs the wrapper and initialises the concatenator with declared parameters.
38+
* @param node Node used for parameter declaration and logging.
39+
* @param node_parameters_interface Parameter interface for declaring and reading parameters.
40+
*/
41+
TrajectoryConcatenatorWrapper(
42+
rclcpp::Node & node,
43+
rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface)
44+
: node_ptr_(&node),
45+
logger_(node.get_logger().get_child(interface_name_)),
46+
concatenator_params_listener_(node_parameters_interface),
47+
concatenator_params_(concatenator_params_listener_.get_params()),
48+
concatenator_ptr_(std::make_unique<TrajectoryConcatenator>(concatenator_params_))
49+
{
50+
}
51+
52+
/** @brief Reloads parameters from the parameter server if they have changed. */
53+
void update_parameters()
54+
{
55+
std::lock_guard<std::mutex> lock(concatenator_mutex_);
56+
if (concatenator_params_listener_.is_old(concatenator_params_)) {
57+
concatenator_params_ = concatenator_params_listener_.get_params();
58+
concatenator_ptr_->update_parameters(concatenator_params_);
59+
RCLCPP_INFO(logger_, "Trajectory Concatenator parameters are updated.");
60+
}
61+
}
62+
63+
/**
64+
* @brief Adds a set of candidate trajectories to the buffer.
65+
* @param candidate_trajectories Candidate trajectories from a single generator.
66+
*/
67+
void add_candidate(
68+
const autoware_internal_planning_msgs::msg::CandidateTrajectories & candidate_trajectories)
69+
{
70+
std::lock_guard<std::mutex> lock(concatenator_mutex_);
71+
concatenator_ptr_->add_candidate(candidate_trajectories);
72+
}
73+
74+
/** @brief Returns the merged set of all non-stale generator trajectories. */
75+
[[nodiscard]] autoware_internal_planning_msgs::msg::CandidateTrajectories get_concatenated()
76+
{
77+
update_parameters();
78+
const auto time_now = node_ptr_->get_clock()->now();
79+
80+
std::lock_guard<std::mutex> lock(concatenator_mutex_);
81+
return concatenator_ptr_->get_concatenated(time_now);
82+
};
83+
84+
private:
85+
std::string interface_name_{"trajectory_concatenator"};
86+
rclcpp::Node * node_ptr_{nullptr};
87+
rclcpp::Logger logger_;
88+
concatenator::ParamListener concatenator_params_listener_;
89+
concatenator::Params concatenator_params_;
90+
std::unique_ptr<trajectory_concatenator::TrajectoryConcatenator> concatenator_ptr_;
91+
std::mutex concatenator_mutex_;
92+
};
93+
94+
} // namespace autoware::trajectory_concatenator
95+
96+
#endif // AUTOWARE__TRAJECTORY_CONCATENATOR__TRAJECTORY_CONCATENATOR_WRAPPER_HPP_

planning/autoware_trajectory_concatenator/launch/trajectory_concatenator.launch.xml

Lines changed: 0 additions & 12 deletions
This file was deleted.

planning/autoware_trajectory_concatenator/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<depend>rclcpp</depend>
2727
<depend>rclcpp_components</depend>
2828

29+
<test_depend>ament_cmake_gtest</test_depend>
2930
<test_depend>ament_lint_auto</test_depend>
3031
<test_depend>autoware_lint_common</test_depend>
3132

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2025 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "autoware/trajectory_concatenator/detail/trajectory_concatenator.hpp"
16+
17+
#include <autoware_utils_uuid/uuid_helper.hpp>
18+
19+
#include <algorithm>
20+
#include <string>
21+
#include <unordered_map>
22+
#include <utility>
23+
#include <vector>
24+
25+
namespace autoware::trajectory_concatenator
26+
{
27+
28+
namespace
29+
{
30+
double to_seconds(const builtin_interfaces::msg::Time & time)
31+
{
32+
return static_cast<double>(time.sec) + static_cast<double>(time.nanosec) * 1e-9;
33+
}
34+
} // namespace
35+
using autoware_internal_planning_msgs::msg::CandidateTrajectory;
36+
37+
void TrajectoryConcatenator::add_candidate(const CandidateTrajectories & msg)
38+
{
39+
std::unordered_map<std::string, std::vector<CandidateTrajectory>> by_generator;
40+
by_generator.reserve(msg.generator_info.size());
41+
for (const auto & traj : msg.candidate_trajectories) {
42+
by_generator[autoware_utils_uuid::to_hex_string(traj.generator_id)].push_back(traj);
43+
}
44+
for (const auto & generator_info : msg.generator_info) {
45+
const auto uuid = autoware_utils_uuid::to_hex_string(generator_info.generator_id);
46+
auto it = by_generator.find(uuid);
47+
buffer_[uuid] =
48+
autoware_internal_planning_msgs::build<CandidateTrajectories>()
49+
.candidate_trajectories(
50+
it != by_generator.end() ? std::move(it->second) : std::vector<CandidateTrajectory>{})
51+
.generator_info({generator_info});
52+
}
53+
}
54+
55+
CandidateTrajectories TrajectoryConcatenator::get_concatenated(
56+
const builtin_interfaces::msg::Time & current_time)
57+
{
58+
std::vector<autoware_internal_planning_msgs::msg::CandidateTrajectory> trajectories;
59+
std::vector<autoware_internal_planning_msgs::msg::GeneratorInfo> generator_info;
60+
61+
const double current_time_sec = to_seconds(current_time);
62+
63+
// Prune expired entries individually
64+
for (auto it = buffer_.begin(); it != buffer_.end();) {
65+
auto & pre_combine = it->second;
66+
67+
pre_combine.candidate_trajectories.erase(
68+
std::remove_if(
69+
pre_combine.candidate_trajectories.begin(), pre_combine.candidate_trajectories.end(),
70+
[&](const auto & traj) {
71+
const double msg_time_sec = to_seconds(traj.header.stamp);
72+
return (current_time_sec - msg_time_sec) > params_.duration_time;
73+
}),
74+
pre_combine.candidate_trajectories.end());
75+
76+
if (pre_combine.candidate_trajectories.empty()) {
77+
it = buffer_.erase(it);
78+
} else {
79+
it++;
80+
}
81+
}
82+
83+
// Combine surviving entries
84+
for (const auto & [uuid, pre_combine] : buffer_) {
85+
trajectories.insert(
86+
trajectories.end(), pre_combine.candidate_trajectories.begin(),
87+
pre_combine.candidate_trajectories.end());
88+
generator_info.insert(
89+
generator_info.end(), pre_combine.generator_info.begin(), pre_combine.generator_info.end());
90+
}
91+
92+
return autoware_internal_planning_msgs::build<CandidateTrajectories>()
93+
.candidate_trajectories(trajectories)
94+
.generator_info(generator_info);
95+
}
96+
97+
} // namespace autoware::trajectory_concatenator

0 commit comments

Comments
 (0)