Skip to content

Commit 1571ebe

Browse files
added updates lazily of propagation model
Signed-off-by: fbattocchia <florencia.battochia@creativa77.com.ar>
1 parent 8086f76 commit 1571ebe

7 files changed

Lines changed: 75 additions & 45 deletions

File tree

beluga_amcl/include/beluga_amcl/amcl_node.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
#include "beluga_amcl/message_filters.hpp"
4444
#include "beluga_amcl/ros2_common.hpp"
4545

46+
#include <deque>
47+
4648
/**
4749
* \file
4850
* \brief ROS 2 integration of the 2D AMCL algorithm.
@@ -160,6 +162,10 @@ class AmclNode : public BaseAMCLNode {
160162
std::optional<Sophus::SE2d> last_known_odom_transform_in_map_;
161163
/// Whether to broadcast transforms or not.
162164
bool enable_tf_broadcast_{false};
165+
166+
using OdometryMotion = std::pair<tf2::TimePoint, Sophus::SE2d>;
167+
/// Buffer for queued odometry motions (timestamp, pose)
168+
std::deque<OdometryMotion> odometry_motion_buffer_;
163169
};
164170

165171
} // namespace beluga_amcl

beluga_amcl/src/amcl_node.cpp

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ AmclNode::AmclNode(const rclcpp::NodeOptions& options) : BaseAMCLNode{"amcl", ""
191191
descriptor.floating_point_range[0].from_value = 0.0;
192192
descriptor.floating_point_range[0].to_value = 100.0;
193193
descriptor.floating_point_range[0].step = 0.0;
194-
declare_parameter("increase_propagation", rclcpp::ParameterValue(0.0), descriptor);
194+
declare_parameter("propagation_rate", rclcpp::ParameterValue(0.0), descriptor);
195195
}
196196
}
197197

@@ -264,12 +264,14 @@ void AmclNode::do_activate(const rclcpp_lifecycle::State&) {
264264

265265
// Setup increased propagation timer if enabled
266266
{
267-
const double propagation_freq = get_parameter("increase_propagation").as_double();
267+
const double propagation_freq = get_parameter("propagation_rate").as_double();
268268
if (propagation_freq > 0.0) {
269269
auto period = std::chrono::duration<double>(1.0 / propagation_freq);
270270
propagation_timer_ =
271271
create_wall_timer(period, std::bind(&AmclNode::propagation_timer_callback, this), common_callback_group_);
272272
RCLCPP_INFO(get_logger(), "Created propagation timer at %.1f Hz", propagation_freq);
273+
// Initialize odometry motion buffer
274+
odometry_motion_buffer_.clear();
273275
}
274276
}
275277
}
@@ -290,6 +292,7 @@ void AmclNode::do_cleanup(const rclcpp_lifecycle::State&) {
290292
particle_filter_.reset();
291293
likelihood_field_pub_.reset(); // attaching likelihood_field_pub_ lifespan to particle_filter_ lifespan
292294
enable_tf_broadcast_ = false;
295+
odometry_motion_buffer_.clear();
293296
}
294297

295298
auto AmclNode::get_initial_estimate() const -> std::optional<std::pair<Sophus::SE2d, Eigen::Matrix3d>> {
@@ -499,7 +502,7 @@ auto AmclNode::get_base_pose_in_odom(const tf2::TimePoint& time) const -> std::o
499502
base_pose_in_odom);
500503
return base_pose_in_odom;
501504
} catch (const tf2::TransformException& error) {
502-
RCLCPP_WARN(get_logger(), "Could not get base pose in odom: %s", error.what());
505+
RCLCPP_ERROR(get_logger(), "Could not transform from odom to base: %s", error.what());
503506
return std::nullopt;
504507
}
505508
}
@@ -511,16 +514,15 @@ void AmclNode::propagation_timer_callback() {
511514
return;
512515
}
513516

514-
// Get current base pose in odom frame (latest available)
515-
auto base_pose_in_odom = get_base_pose_in_odom(tf2::TimePointZero);
517+
const auto base_pose_in_odom = get_base_pose_in_odom(tf2::TimePointZero);
516518
if (!base_pose_in_odom.has_value()) {
517519
return;
518520
}
519521

520-
// Force a propagation-only update (without sensor data)
521-
particle_filter_->update_propagation(base_pose_in_odom.value());
522-
523-
RCLCPP_INFO(get_logger(), "Forced propagation update executed");
522+
const auto now = this->now();
523+
const auto time = tf2_ros::fromMsg(now);
524+
// Queue odometry motion (timestamp, pose) for later processing in laser_callback
525+
odometry_motion_buffer_.emplace_back(time, base_pose_in_odom.value());
524526
}
525527

526528
void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_scan) {
@@ -530,8 +532,21 @@ void AmclNode::laser_callback(sensor_msgs::msg::LaserScan::ConstSharedPtr laser_
530532
return;
531533
}
532534

535+
// If propagation_rate is enabled, process odometry buffer up to lidar timestamp
536+
if (get_parameter("propagation_rate").as_double() > 0.0) {
537+
const auto laser_scan_stamp = tf2_ros::fromMsg(laser_scan->header.stamp);
538+
while (!odometry_motion_buffer_.empty()) {
539+
const auto& [odom_time, odom_pose] = odometry_motion_buffer_.front();
540+
if (odom_time >= laser_scan_stamp) {
541+
break;
542+
}
543+
particle_filter_->update(odom_pose);
544+
odometry_motion_buffer_.pop_front();
545+
}
546+
}
547+
533548
// Get base pose in odom frame at laser scan timestamp
534-
auto base_pose_in_odom = get_base_pose_in_odom(tf2_ros::fromMsg(laser_scan->header.stamp));
549+
const auto base_pose_in_odom = get_base_pose_in_odom(tf2_ros::fromMsg(laser_scan->header.stamp));
535550
if (!base_pose_in_odom.has_value()) {
536551
return;
537552
}

beluga_amcl/test/test_amcl_node.cpp

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class AmclNodeUnderTest : public beluga_amcl::AmclNode {
5151

5252
/// Expose propagation timer callback for testing
5353
void propagation_timer_callback() { AmclNode::propagation_timer_callback(); }
54+
55+
/// Expose odometry_motion_buffer_ for testing
56+
const auto& odometry_motion_buffer() const { return odometry_motion_buffer_; }
5457
};
5558

5659
/// Base node fixture class with common utilities.
@@ -705,7 +708,7 @@ TEST_F(TestNode, TransformValue) {
705708
}
706709

707710
TEST_F(TestNode, PropagationTimerNotCreatedWhenDisabled) {
708-
amcl_node_->set_parameter(rclcpp::Parameter{"increase_propagation", 0.0});
711+
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", 0.0});
709712
amcl_node_->configure();
710713
amcl_node_->activate();
711714
tester_node_->publish_map();
@@ -716,7 +719,7 @@ TEST_F(TestNode, PropagationTimerNotCreatedWhenDisabled) {
716719
}
717720

718721
TEST_F(TestNode, PropagationTimerCreatedWhenEnabled) {
719-
amcl_node_->set_parameter(rclcpp::Parameter{"increase_propagation", 10.0});
722+
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", 10.0});
720723
amcl_node_->configure();
721724
amcl_node_->activate();
722725
tester_node_->publish_map();
@@ -727,32 +730,45 @@ TEST_F(TestNode, PropagationTimerCreatedWhenEnabled) {
727730
}
728731

729732
TEST_F(TestNode, PropagationTimerIntegrationTest) {
730-
amcl_node_->set_parameter(rclcpp::Parameter{"increase_propagation", 5.0});
733+
amcl_node_->set_parameter(rclcpp::Parameter{"propagation_rate", 5.0});
731734
amcl_node_->set_parameter(rclcpp::Parameter{"set_initial_pose", true});
732735
amcl_node_->configure();
733736
amcl_node_->activate();
734737
tester_node_->publish_map();
735738
ASSERT_TRUE(wait_for_initialization());
736739

740+
tester_node_->publish_odom_to_base_tf(Sophus::SE2d{});
741+
742+
// Wait for several timer executions to fill the odometry buffer
743+
spin_for(500ms, amcl_node_, tester_node_);
744+
745+
// Check that odometry_motion_buffer_ has values before laser scan
746+
EXPECT_FALSE(amcl_node_->odometry_motion_buffer().empty());
747+
const auto buffer_size_before = amcl_node_->odometry_motion_buffer().size();
748+
737749
// Configure TF so get_base_pose_in_odom works by publishing a laser scan with transform
738750
tester_node_->publish_laser_scan_with_odom_to_base(Sophus::SE2d{});
739751

740-
// Wait for several timer executions
741-
spin_for(500ms, amcl_node_, tester_node_);
752+
// Wait for the callback to process the buffer
753+
spin_for(100ms, amcl_node_, tester_node_);
754+
755+
// Check that odometry_motion_buffer_ was consumed up to the lidar timestamp
756+
EXPECT_LT(amcl_node_->odometry_motion_buffer().size(), buffer_size_before);
742757

743758
// Verify particle filter still exists and has particles
744759
EXPECT_TRUE(amcl_node_->particle_filter() != nullptr);
745760
EXPECT_GT(amcl_node_->particle_filter()->particles().size(), 0UL);
746761
}
747762

748763
TEST_F(TestNode, PropagationTimerWithoutParticles) {
764+
// Verify that particle filter remains uninitialized
765+
EXPECT_FALSE(amcl_node_->particle_filter() != nullptr);
766+
749767
// Call propagation timer callback when particle filter is not initialized
750-
// This should trigger early return due to !particle_filter_ check
751768
amcl_node_->propagation_timer_callback();
752769

753-
// Verify that particle filter remains uninitialized
754-
// The callback should have returned early without creating the filter
755-
EXPECT_FALSE(amcl_node_->particle_filter() != nullptr);
770+
// The callback should have returned early without filling the queue
771+
EXPECT_TRUE(amcl_node_->odometry_motion_buffer().empty());
756772
}
757773

758774
TEST_F(TestNode, PropagationTimerWithoutBaseToOdom) {
@@ -766,17 +782,12 @@ TEST_F(TestNode, PropagationTimerWithoutBaseToOdom) {
766782
EXPECT_TRUE(amcl_node_->is_initialized());
767783
EXPECT_TRUE(amcl_node_->particle_filter() != nullptr);
768784

769-
// Capture the current particles before calling the propagation callback
770-
const auto particles_before = amcl_node_->particle_filter()->particles();
771-
EXPECT_GT(particles_before.size(), 0UL);
772-
773785
// Call propagation callback without any transform data
774786
// This should return early because get_base_pose_in_odom() will fail
775787
amcl_node_->propagation_timer_callback();
776788

777-
// Verify particles remain unchanged since no propagation occurred
778-
const auto particles_after = amcl_node_->particle_filter()->particles();
779-
EXPECT_EQ(particles_before.size(), particles_after.size());
789+
// The callback should have returned early without filling the queue
790+
EXPECT_TRUE(amcl_node_->odometry_motion_buffer().empty());
780791
}
781792

782793
class TestParameterValue : public ::testing::TestWithParam<rclcpp::Parameter> {};

beluga_amcl/test/test_utils/node_testing.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,15 @@ class TesterNode : public rclcpp::Node {
244244
tf_broadcaster_->sendTransform(transform_laser);
245245
}
246246

247+
void publish_odom_to_base_tf(const Sophus::SE2d& transform) {
248+
auto msg = geometry_msgs::msg::TransformStamped{};
249+
msg.header.stamp = now();
250+
msg.header.frame_id = "odom";
251+
msg.child_frame_id = "base_footprint";
252+
msg.transform = tf2::toMsg(transform);
253+
tf_broadcaster_->sendTransform(msg);
254+
}
255+
247256
void publish_laser_scan_with_odom_to_base(const Sophus::SE2d& transform) {
248257
const auto timestamp = now();
249258

beluga_ros/include/beluga_ros/amcl.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class Amcl {
218218
*
219219
* \param base_pose_in_odom Base pose in the odometry frame.
220220
*/
221-
void update_propagation(Sophus::SE2d base_pose_in_odom);
221+
void update(Sophus::SE2d base_pose_in_odom);
222222

223223
/// Update particles based on motion and sensor information.
224224
/**

beluga_ros/src/amcl.cpp

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,14 @@ void Amcl::update_map(beluga_ros::OccupancyGrid map) {
5050
std::visit([&](auto& sensor_model) { sensor_model.update_map(std::move(map)); }, sensor_model_);
5151
}
5252

53-
void Amcl::update_propagation(Sophus::SE2d base_pose_in_odom) {
54-
if (particles_.empty()) {
55-
return;
53+
void Amcl::update(Sophus::SE2d base_pose_in_odom) {
54+
if (!particles_.empty()) {
55+
std::visit(
56+
[&, this](auto& policy, auto& motion_model) {
57+
particles_ |= beluga::actions::propagate(policy, motion_model(control_action_window_ << base_pose_in_odom));
58+
},
59+
execution_policy_, motion_model_);
5660
}
57-
58-
// Force propagation without checking update_policy_
59-
std::visit(
60-
[&, this](auto& policy, auto& motion_model) {
61-
particles_ |= beluga::actions::propagate(policy, motion_model(control_action_window_ << base_pose_in_odom)) |
62-
beluga::actions::normalize(policy);
63-
},
64-
execution_policy_, motion_model_);
6561
}
6662

6763
auto Amcl::update(Sophus::SE2d base_pose_in_odom, beluga_ros::LaserScan laser_scan)

beluga_ros/test/test_amcl.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,4 @@ TEST(TestAmcl, UpdateWithParticlesForced) {
148148
estimate = amcl.update(Sophus::SE2d{}, make_dummy_laser_scan());
149149
ASSERT_TRUE(estimate.has_value());
150150
}
151-
152-
TEST(TestAmcl, UpdatePropagationWithNoParticles) {
153-
auto amcl = make_amcl();
154-
amcl.update_propagation(Sophus::SE2d{});
155-
ASSERT_EQ(amcl.particles().size(), 0);
156-
}
157-
158151
} // namespace

0 commit comments

Comments
 (0)