Skip to content

Commit c3457d2

Browse files
committed
nav2_collision_monitor: modified CostmapSource + dataset-based bag test (v1)
Signed-off-by: Mengting Yang <[email protected]>
1 parent a640c92 commit c3457d2

File tree

14 files changed

+709
-393
lines changed

14 files changed

+709
-393
lines changed

nav2_collision_monitor/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ find_package(nav2_common REQUIRED)
88
find_package(nav2_costmap_2d REQUIRED)
99
find_package(nav2_msgs REQUIRED)
1010
find_package(nav2_util REQUIRED)
11+
find_package(rosgraph_msgs REQUIRED)
1112
find_package(rclcpp REQUIRED)
1213
find_package(rclcpp_components REQUIRED)
1314
find_package(rclcpp_lifecycle REQUIRED)
@@ -148,6 +149,7 @@ install(DIRECTORY params DESTINATION share/${PROJECT_NAME})
148149

149150
if(BUILD_TESTING)
150151
find_package(ament_lint_auto REQUIRED)
152+
find_package(launch_testing_ament_cmake REQUIRED)
151153
# the following line skips the linter which checks for copyrights
152154
set(ament_cmake_copyright_FOUND TRUE)
153155
set(ament_cmake_cpplint_FOUND TRUE)
@@ -167,6 +169,7 @@ ament_export_dependencies(
167169
nav2_costmap_2d
168170
nav2_msgs
169171
nav2_util
172+
rosgraph_msgs
170173
rclcpp
171174
rclcpp_lifecycle
172175
sensor_msgs

nav2_collision_monitor/include/nav2_collision_monitor/costmap.hpp

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,70 @@
1-
// Copyright (c) 2025 <Your Name>
1+
// Copyright (c) 2025 Angsa Robotics
2+
// Copyright (c) 2025 lotusymt
3+
//
24
// Licensed under the Apache License, Version 2.0 (the "License");
3-
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
415
#ifndef NAV2_COLLISION_MONITOR__COSTMAP_HPP_
516
#define NAV2_COLLISION_MONITOR__COSTMAP_HPP_
617

7-
#include <memory> // shared_ptr / weak_ptr
8-
#include <string> // std::string
9-
#include <vector> // std::vector
10-
#include "nav2_collision_monitor/source.hpp" // Base class Source (CM’s plugin interface)
11-
#include "nav2_msgs/msg/costmap.hpp" // CHANGE: use Nav2 Costmap message
18+
#include <memory>
19+
#include <string>
20+
#include <vector>
21+
#include "nav2_collision_monitor/source.hpp"
22+
#include "nav2_msgs/msg/costmap.hpp"
1223
#include <nav2_ros_common/lifecycle_node.hpp>
1324
#include <nav2_ros_common/node_utils.hpp>
1425

1526
namespace nav2_collision_monitor
1627
{
1728

18-
class CostmapSource : public Source // Inherit CM’s Source (same as other inputs)
29+
class CostmapSource : public Source
1930
{
2031
public:
21-
CostmapSource( // Constructor declaration (no return type)
22-
const nav2::LifecycleNode::WeakPtr & node , // Non-owning pointer to lifecycle node
23-
const std::string & source_name, // Name used in params (e.g., "costmap")
24-
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,// Shared TF buffer
25-
const std::string & base_frame_id, // Usually "base_link"
26-
const std::string & global_frame_id, // Usually "odom" or "map"
27-
const tf2::Duration & transform_tolerance, // TF timing slack
28-
const rclcpp::Duration & source_timeout, // Max data age allowed
29-
const bool base_shift_correction); // Whether to correct base motion drift
32+
CostmapSource(
33+
34+
const nav2::LifecycleNode::WeakPtr & node,
35+
const std::string & source_name,
36+
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
37+
const std::string & base_frame_id,
38+
const std::string & global_frame_id,
39+
const tf2::Duration & transform_tolerance,
40+
const rclcpp::Duration & source_timeout,
41+
const bool base_shift_correction);
3042

31-
~CostmapSource(); // Virtual dtor not required; explicit is fine
43+
~CostmapSource();
3244

33-
void configure(); // Where we declare params & create subscription
45+
void configure();
3446

35-
bool getData( // CM calls this each tick to fetch obstacle points
47+
bool getData(
3648
const rclcpp::Time & curr_time,
3749
std::vector<Point> & data) override;
3850

39-
void getParameters(std::string & source_topic); // Read: <source_name>.topic, <source_name>.cost_threshold
51+
void getParameters(std::string & source_topic);
4052

4153
private:
4254
void dataCallback(nav2_msgs::msg::Costmap::ConstSharedPtr msg);
4355
// ↑ Store the latest Costmap message; we’ll read it in getData()
4456

4557
nav2_msgs::msg::Costmap::ConstSharedPtr data_; // Latest costmap message (thread-safe shared ptr)
4658
rclcpp::Subscription<nav2_msgs::msg::Costmap>::SharedPtr data_sub_;
47-
// ↑ The actual ROS2 subscription
4859

49-
int cost_threshold_{254}; // Default: lethal cells only (254). Make it a param.
50-
// (Nav2 costs: 0 free, 253 inscribed, 254 lethal, 255 unknown)
60+
// Threshold for considering a cell as an obstacle. Valid range: 0..255.
61+
// Typical choices: 253 (inscribed), 254 (lethal). Inflation = 1..252.
62+
int cost_threshold_{253};
63+
64+
// Whether 255 (NO_INFORMATION) should be treated as an obstacle.
65+
bool treat_unknown_as_obstacle_{true};
66+
5167

52-
5368
};
5469

5570
} // namespace nav2_collision_monitor

nav2_collision_monitor/package.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
<test_depend>ament_cmake_gtest</test_depend>
3333
<test_depend>ament_lint_common</test_depend>
3434
<test_depend>ament_lint_auto</test_depend>
35+
<test_depend>launch_testing_ament_cmake</test_depend>
36+
<test_depend>rosgraph_msgs</test_depend>
37+
3538

3639
<export>
3740
<build_type>ament_cmake</build_type>

nav2_collision_monitor/params/collision_monitor_params.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,9 @@ collision_monitor:
102102
max_height: 0.5
103103
use_global_height: False
104104
enabled: True
105+
costmap:
106+
type: "costmap"
107+
topic: "/local_costmap/costmap"
108+
cost_threshold: 254
109+
enabled: True
110+
treat_unknown_as_obstacle: True

nav2_collision_monitor/src/collision_monitor_node.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ bool CollisionMonitor::configurePolygons(
308308
polygons_.push_back(
309309
std::make_shared<VelocityPolygon>(
310310
node, polygon_name, tf_buffer_, base_frame_id, transform_tolerance));
311-
}else { // Error if something else
311+
} else { // Error if something else
312312
RCLCPP_ERROR(
313313
get_logger(),
314314
"[%s]: Unknown polygon type: %s",
@@ -381,10 +381,11 @@ bool CollisionMonitor::configureSources(
381381
auto src = std::make_shared<CostmapSource>(
382382
node, source_name, tf_buffer_, base_frame_id, odom_frame_id,
383383
transform_tolerance, source_timeout, base_shift_correction);
384+
385+
src->configure();
386+
384387
sources_.push_back(src);
385-
continue;
386-
}
387-
else { // Error if something else
388+
} else { // Error if something else
388389
RCLCPP_ERROR(
389390
get_logger(),
390391
"[%s]: Unknown source type: %s",
Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
1-
#include "nav2_msgs/msg/costmap.hpp"
2-
#include "nav2_collision_monitor/costmap.hpp"
1+
// Copyright (c) 2025 Angsa Robotics
2+
// Copyright (c) 2025 lotusymt
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
#include "nav2_msgs/msg/costmap.hpp"
16+
#include "nav2_collision_monitor/costmap.hpp"
317
#include <functional>
418
#include <cmath>
5-
#include <tf2/time.hpp> // tf2::Duration, tf2::TimePoint, durationFromSec()
6-
#include <tf2_ros/buffer.hpp> // tf2_ros::Buffer (you already use it)
19+
#include <tf2/time.hpp>
20+
#include <tf2_ros/buffer.hpp>
721
#include <tf2_ros/transform_listener.hpp>
8-
#include <nav2_ros_common/lifecycle_node.hpp> // nav2::LifecycleNode
22+
#include <nav2_ros_common/lifecycle_node.hpp>
923
#include <nav2_ros_common/node_utils.hpp>
1024

1125
namespace nav2_collision_monitor
1226
{
1327

1428

15-
CostmapSource::CostmapSource(
16-
// const nav2_util::LifecycleNode::WeakPtr & node,
29+
CostmapSource::CostmapSource(
1730
const nav2::LifecycleNode::WeakPtr & node,
1831
const std::string & source_name,
1932
const std::shared_ptr<tf2_ros::Buffer> tf_buffer,
@@ -27,17 +40,17 @@ CostmapSource::CostmapSource(
2740
transform_tolerance, source_timeout, base_shift_correction),
2841
data_(nullptr)
2942
{
30-
RCLCPP_INFO(logger_, "[%s]: Creating CostmapSource", source_name_.c_str());
43+
RCLCPP_INFO(logger_, "[%s]: Creating CostmapSource", source_name_.c_str());
3144
}
3245

3346

34-
CostmapSource::~CostmapSource()
47+
CostmapSource::~CostmapSource()
3548
{
36-
RCLCPP_INFO(logger_, "[%s]: Destroying CostmapSource", source_name_.c_str());
49+
RCLCPP_INFO(logger_, "[%s]: Destroying CostmapSource", source_name_.c_str());
3750
data_sub_.reset();
3851
}
3952

40-
void CostmapSource::configure()
53+
void CostmapSource::configure()
4154
{
4255
Source::configure();
4356
auto node = node_.lock();
@@ -48,14 +61,14 @@ void CostmapSource::configure()
4861
getParameters(source_topic);
4962
rclcpp::QoS qos = rclcpp::SystemDefaultsQoS();
5063

51-
64+
5265
data_sub_ = node->create_subscription<nav2_msgs::msg::Costmap>(
5366
source_topic,
5467
std::bind(&CostmapSource::dataCallback, this, std::placeholders::_1),
5568
qos);
5669
}
5770

58-
bool CostmapSource::getData(
71+
bool CostmapSource::getData(
5972
const rclcpp::Time & curr_time,
6073
std::vector<Point> & data)
6174
{
@@ -66,51 +79,71 @@ bool CostmapSource::getData(
6679
if (!sourceValid(data_->header.stamp, curr_time)) {
6780
return false;
6881
}
69-
tf2::Transform tf_transform;
70-
if (!getTransform(curr_time, data_->header, tf_transform)) {
71-
return false;
82+
tf2::Transform tf_transform; tf_transform.setIdentity();
83+
const std::string src = data_->header.frame_id;
84+
if (src != base_frame_id_) {
85+
if (!getTransform(curr_time, data_->header, tf_transform)) {
86+
RCLCPP_WARN(logger_, "[%s] TF %s->%s unavailable at t=%.3f",
87+
source_name_.c_str(), src.c_str(), base_frame_id_.c_str(), curr_time.seconds());
88+
return false;
89+
}
7290
}
7391

92+
7493
// Extract lethal/inscribed cells and transform to base frame
75-
const auto & cm = *data_;
76-
const auto & meta = cm.metadata;
77-
78-
for (unsigned int y = 0; y < meta.size_y; ++y) {
79-
for (unsigned int x = 0; x < meta.size_x; ++x) {
80-
const int idx = y * meta.size_x + x;
81-
82-
// CHANGE: Costmap costs are 0..255 (0 free, 253 inscribed, 254 lethal, 255 unknown)
83-
if (cm.data[idx] >= cost_threshold_) {
84-
const double wx = meta.origin.position.x + (x + 0.5) * meta.resolution;
85-
const double wy = meta.origin.position.y + (y + 0.5) * meta.resolution;
94+
const auto & cm = *data_;
95+
const auto & meta = cm.metadata;
96+
97+
for (unsigned int y = 0; y < meta.size_y; ++y) {
98+
for (unsigned int x = 0; x < meta.size_x; ++x) {
99+
const int idx = y * meta.size_x + x;
100+
101+
const uint8_t c = cm.data[idx];
102+
const bool is_obstacle = (c >= cost_threshold_ && c < 255) ||
103+
(treat_unknown_as_obstacle_ && c == 255);
104+
if (is_obstacle) {
105+
const double wx = meta.origin.position.x + (x + 0.5) * meta.resolution;
106+
const double wy = meta.origin.position.y + (y + 0.5) * meta.resolution;
86107
tf2::Vector3 p_v3_s(wx, wy, 0.0);
87108
tf2::Vector3 p_v3_b = tf_transform * p_v3_s;
88109
data.push_back({p_v3_b.x(), p_v3_b.y()});
110+
RCLCPP_INFO(logger_, "[%s] Lethal cell at (%f, %f)",
111+
source_name_.c_str(), wx, wy);
89112
}
90113
}
91114
}
92115
return true;
93116
}
94117

95-
void CostmapSource::getParameters(std::string & source_topic)
118+
void CostmapSource::getParameters(std::string & source_topic)
96119
{
97120
auto node = node_.lock();
98121
if (!node) {
99122
throw std::runtime_error{"Failed to lock node"};
100123
}
101124
getCommonParameters(source_topic);
102125

126+
// Cost threshold (0–255). 253 = inscribed 254 = lethal; 255 = NO_INFORMATION.
127+
const auto thresh_name = source_name_ + ".cost_threshold";
128+
nav2::declare_parameter_if_not_declared(
129+
node, thresh_name, rclcpp::ParameterValue(253));
130+
int v = node->get_parameter(thresh_name).as_int();
131+
v = std::max(0, std::min(255, v)); // clamp
132+
if (v != node->get_parameter(thresh_name).as_int()) {
133+
RCLCPP_WARN(node->get_logger(), "Clamping %s to %d", thresh_name.c_str(), v);
134+
}
135+
cost_threshold_ = v;
103136

104-
105-
// Todo: use a cost threshold suitable for Nav2 costmaps; default 254 (lethal)
137+
// Whether 255 (NO_INFORMATION) should be treated as an obstacle.
138+
const auto unk_name = source_name_ + ".treat_unknown_as_obstacle";
106139
nav2::declare_parameter_if_not_declared(
107-
node, source_name_ + ".cost_threshold", rclcpp::ParameterValue(254));
108-
cost_threshold_ = node->get_parameter(source_name_ + ".cost_threshold").as_int();
140+
node, unk_name, rclcpp::ParameterValue(true));
141+
treat_unknown_as_obstacle_ = node->get_parameter(unk_name).as_bool();
109142
}
110143

111-
void CostmapSource::dataCallback(nav2_msgs::msg::Costmap::ConstSharedPtr msg)
144+
void CostmapSource::dataCallback(nav2_msgs::msg::Costmap::ConstSharedPtr msg)
112145
{
113146
data_ = msg;
114147
}
115148

116-
} // namespace nav2_collision_monitor
149+
}

nav2_collision_monitor/test/CMakeLists.txt

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -60,37 +60,29 @@ target_link_libraries(collision_detector_node_test
6060
)
6161

6262

63+
# Install bag + params so the launch file can find them via get_package_share_directory()
64+
install(DIRECTORY bags/cm_moving_obstacle
65+
DESTINATION share/${PROJECT_NAME}/test/bags)
66+
install(FILES collision_monitor_node_bag.yaml
67+
DESTINATION share/${PROJECT_NAME}/test)
6368

69+
find_package(GTest REQUIRED)
6470

65-
ament_add_gtest(costmap_source_test
66-
test_costmap_source.cpp # (correct path — no "test/" prefix)
71+
add_executable(collision_monitor_node_bag_gtest
72+
collision_monitor_node_bag.cpp
73+
)
74+
target_link_libraries(collision_monitor_node_bag_gtest
75+
GTest::gtest_main
76+
${monitor_library_name}
77+
rclcpp::rclcpp
78+
tf2_ros::tf2_ros
79+
nav2_util::nav2_util_core
80+
${nav2_msgs_TARGETS}
6781
)
6882

69-
if (TARGET costmap_source_test)
70-
# Let the test see your package headers (optional if you link the core lib PUBLIC)
71-
target_include_directories(costmap_source_test PRIVATE
72-
${CMAKE_CURRENT_SOURCE_DIR}/../include
73-
)
74-
75-
# Link against your core library so PUBLIC includes/defs propagate
76-
# (use your real target name; from earlier it’s collision_monitor_core)
77-
target_link_libraries(costmap_source_test
78-
collision_monitor_core
79-
${geometry_msgs_TARGETS}
80-
nav2_costmap_2d::nav2_costmap_2d_core
81-
nav2_costmap_2d::nav2_costmap_2d_client
82-
${nav2_msgs_TARGETS}
83-
nav2_util::nav2_util_core
84-
rclcpp::rclcpp
85-
rclcpp_lifecycle::rclcpp_lifecycle
86-
${sensor_msgs_TARGETS}
87-
tf2::tf2
88-
tf2_ros::tf2_ros
89-
${visualization_msgs_TARGETS}
90-
point_cloud_transport::point_cloud_transport
91-
)
92-
93-
# The deprecation messages are produced via `#warning` → silence them for this test only
94-
target_compile_options(costmap_source_test PRIVATE -Wno-error=cpp)
95-
endif()
83+
add_launch_test(collision_monitor_node_bag.launch.py
84+
TIMEOUT 120
85+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
86+
ENV TEST_EXECUTABLE=$<TARGET_FILE:collision_monitor_node_bag_gtest>
87+
)
9688

Binary file not shown.

0 commit comments

Comments
 (0)