Skip to content

Commit b344a9e

Browse files
Solved some typos and bugs in the code, still getting stuck sometimes, but the recovery is better
1 parent eb1a8f5 commit b344a9e

4 files changed

Lines changed: 98 additions & 68 deletions

File tree

localization/beluga_demo_mh_amcl/config/mh_amcl_params.yaml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,22 @@ mh_amcl:
1818
min_candidate_angle: 1.047 # Equivalent to 60 degrees
1919

2020
# Threshold for low-quality hypotheses
21-
low_q_hypo_threshold: 0.15
22-
23-
# Threshold for very low-quality hypotheses
24-
very_low_q_hypo_threshold: 0.07
21+
low_q_hypo_threshold: 0.2
2522

2623
# Distance threshold for merging similar hypotheses (in meters)
2724
hypo_merge_distance: 0.4
2825

2926
# Angular threshold for merging similar hypotheses (in radians)
30-
hypo_merge_angle: 0.5 # Equivalent to 30 degrees
27+
hypo_merge_angle: 0.523 # Equivalent to 30 degrees
3128

3229
# Threshold to consider a hypothesis as "good"
33-
good_hypo_threshold: 0.8
30+
good_hypo_threshold: 0.5
3431

3532
# Minimum difference in quality between the best hypothesis and others
36-
min_hypo_diff_winner: 0.3
33+
min_hypo_diff_winner: 0.2
3734

3835
# Bond timeout
39-
bond_timeout: 4.0
36+
bond_timeout: 10.0
4037

4138
# Standard deviation for the LiDAR
4239
distance_perception_error: 0.05

localization/beluga_demo_mh_amcl/launch/bringup.launch.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ def generate_launch_description():
5757
'map.yaml'])},
5858
{'use_sim_time': True}
5959
],
60-
output='screen'
60+
output='screen',
61+
emulate_tty=True
6162
),
6263
# Node containing the localization functionality
6364
Node(
@@ -68,17 +69,20 @@ def generate_launch_description():
6869
parameters=[
6970
PathJoinSubstitution([pkg_mh_amcl, 'config', 'mh_amcl_params.yaml'])
7071
],
72+
output='screen',
73+
emulate_tty=True,
7174
),
7275
Node(
7376
package='nav2_lifecycle_manager',
7477
executable='lifecycle_manager',
7578
name='lifecycle_manager_localization',
76-
output='screen',
7779
parameters=[
7880
{"autostart": True},
7981
{'node_names': lifecycle_nodes},
8082
{'use_sim_time': True}
81-
]
83+
],
84+
output='screen',
85+
emulate_tty=True
8286
)
8387
]
8488
)

localization/beluga_demo_mh_amcl/src/mh_amcl/mh_amcl.cpp

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,9 @@ namespace mh_amcl
338338
}
339339

340340
last_time_ = last_laser_->header.stamp;
341+
342+
RCLCPP_DEBUG_STREAM(get_logger(),
343+
"Correct [" << (now() - start).seconds() << " secs]");
341344
}
342345

343346
void MH_AMCL_Node::reseed()
@@ -348,6 +351,9 @@ namespace mh_amcl
348351
{
349352
particles->reseed();
350353
}
354+
355+
RCLCPP_DEBUG_STREAM(get_logger(),
356+
"Reseed [" << (now() - start).seconds() << " secs]");
351357
}
352358

353359
void MH_AMCL_Node::initpose_callback(
@@ -545,25 +551,26 @@ namespace mh_amcl
545551
}
546552
}
547553

548-
// Select the best hypothesis
549-
current_hypothesis_ = *std::max_element(particles_population_.begin(), particles_population_.end(),
550-
[](const auto &a, const auto &b)
551-
{ return a->get_quality() < b->get_quality(); });
552-
current_hypothesis_q_ = current_hypothesis_->get_quality();
554+
// Find the best hypothesis currently in the population.
555+
auto best_it = std::max_element(particles_population_.begin(), particles_population_.end(),
556+
[](const auto &a, const auto &b)
557+
{ return a->get_quality() < b->get_quality(); });
558+
auto best_hypothesis = *best_it;
553559

554-
if (current_hypothesis_q_ < good_hypo_threshold_)
560+
// Only change if it's significantly better
561+
if (best_hypothesis->get_quality() > current_hypothesis_q_ + min_hypo_diff_winner_)
555562
{
556-
current_hypothesis_ = particles_population_.front();
557-
current_hypothesis_q_ = current_hypothesis_->get_quality();
563+
current_hypothesis_ = best_hypothesis;
558564
}
565+
current_hypothesis_q_ = current_hypothesis_->get_quality();
559566

560567
// Debug the output
561-
std::cout << "=====================================" << std::endl;
568+
RCLCPP_INFO(get_logger(), "====== Hypotheses Status ======");
562569
for (const auto &amcl : particles_population_)
563570
{
564-
std::cout << (amcl == current_hypothesis_ ? "->\t" : "") << amcl->get_quality() << std::endl;
571+
RCLCPP_INFO(get_logger(), "%s Quality: %f", (amcl == current_hypothesis_ ? "-> " : " "), amcl->get_quality());
565572
}
566-
std::cout << "=====================================" << std::endl;
573+
RCLCPP_INFO(get_logger(), "==============================");
567574
}
568575

569576
signed char MH_AMCL_Node::get_cost(const geometry_msgs::msg::Pose &pose)

localization/beluga_demo_mh_amcl/src/mh_amcl/particles_distribution.cpp

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,7 @@
1515
#include <algorithm>
1616
#include <cmath>
1717
#include <numeric>
18-
19-
#include <beluga_ros/particle_cloud.hpp>
20-
21-
#include <sensor_msgs/msg/laser_scan.hpp>
22-
#include <tf2_ros/buffer_interface.h>
23-
#include <visualization_msgs/msg/marker_array.hpp>
24-
25-
#include "beluga_demo_mh_amcl/particles_distribution.hpp"
18+
#include <range/v3/view.hpp>
2619

2720
#include <beluga/random/multivariate_normal_distribution.hpp>
2821
#include <beluga/actions/propagate.hpp>
@@ -32,9 +25,13 @@
3225
#include <beluga/algorithm/estimation.hpp>
3326
#include <beluga/motion/differential_drive_model.hpp>
3427
#include <beluga/views/sample.hpp>
35-
#include <beluga/views/particles.hpp>
3628

37-
#include <range/v3/view.hpp>
29+
#include <beluga_ros/particle_cloud.hpp>
30+
#include <sensor_msgs/msg/laser_scan.hpp>
31+
#include <tf2_ros/buffer_interface.h>
32+
#include <visualization_msgs/msg/marker_array.hpp>
33+
34+
#include "beluga_demo_mh_amcl/particles_distribution.hpp"
3835

3936
namespace mh_amcl
4037
{
@@ -110,9 +107,9 @@ namespace mh_amcl
110107
{
111108
parent_node->declare_parameter("good_hypo_threshold", 0.6);
112109
}
113-
if (!parent_node->has_parameter("good_hypo_threshold"))
110+
if (!parent_node->has_parameter("low_q_hypo_threshold"))
114111
{
115-
parent_node->declare_parameter("low_q_hypo_threshold", 0.25f);
112+
parent_node->declare_parameter("low_q_hypo_threshold", 0.25);
116113
}
117114
if (!parent_node->has_parameter("particles_step"))
118115
{
@@ -354,11 +351,11 @@ namespace mh_amcl
354351
std::shared_ptr<beluga_ros::OccupancyGrid> costmap)
355352
{
356353
std::string error;
357-
if (tf_buffer_.canTransform(scan.header.frame_id, "base_footprint",
354+
if (tf_buffer_.canTransform("base_footprint", scan.header.frame_id,
358355
tf2_ros::fromMsg(scan.header.stamp), &error))
359356
{
360357
auto bf2laser_msg =
361-
tf_buffer_.lookupTransform(scan.header.frame_id, "base_footprint",
358+
tf_buffer_.lookupTransform("base_footprint", scan.header.frame_id,
362359
tf2_ros::fromMsg(scan.header.stamp));
363360
tf2::fromMsg(bf2laser_msg, bf2laser_);
364361
}
@@ -370,52 +367,77 @@ namespace mh_amcl
370367
return;
371368
}
372369

373-
const double normal_comp_1 = utils::INV_SQRT_2PI / distance_perception_error_;
374-
375-
for (auto &p : particles_)
370+
// ! Review sensor likelihood math and code
371+
// Build a sensor-model callable: given a Particle, returns a weight (likelihood)
372+
auto sensor_likelihood = [this, &scan, &costmap](const Sophus::SE2d &state) -> double
376373
{
377-
p.hits = 0.0;
378-
}
374+
// Convert state -> tf2 transform or world coords needed for your existing ray-cast:
375+
tf2::Transform map2bf = utils::se2dToTf2Transform(state);
379376

380-
for (int j = 0; j < scan.ranges.size(); j++)
381-
{
382-
if (std::isnan(scan.ranges[j]) || std::isinf(scan.ranges[j]))
377+
// Loop over the scan to get the accumulated logarithmic likelihood and the valid beams (those that don't return infinite)
378+
double accum_log_likelihood = 0.0;
379+
int valid_beams = 0;
380+
// You may want to compute per-particle likelihood by summing log-likelihoods over beams
381+
for (size_t j = 0u; j < scan.ranges.size(); ++j)
383382
{
384-
continue;
385-
}
383+
double r = scan.ranges[j];
384+
if (!std::isfinite(r))
385+
continue;
386386

387-
tf2::Transform laser2point = get_transform_to_read(scan, j);
387+
tf2::Transform laser2point = get_transform_to_read(scan, static_cast<int>(j));
388+
double err_m = get_error_distance_to_obstacle(map2bf, bf2laser_, laser2point, scan, costmap, distance_perception_error_);
389+
if (!std::isinf(err_m))
390+
{
391+
double a = err_m / distance_perception_error_;
392+
double l = std::log(utils::INV_SQRT_2PI / distance_perception_error_) + (-0.5 * a * a);
393+
accum_log_likelihood += l;
394+
++valid_beams;
395+
}
396+
}
388397

389-
for (int i = 0; i < particles_.size(); i++)
398+
if (valid_beams == 0)
390399
{
391-
auto &p = particles_[i];
400+
// fallback small weight
401+
return 1e-9;
402+
}
403+
// Convert back from log-likelihood to linear scale (might underflow — you can return exp(accum_log_likelihood/valid_beams))
404+
double avg_log_l = accum_log_likelihood / static_cast<double>(valid_beams);
405+
double likelihood = std::exp(avg_log_l);
406+
return likelihood;
407+
};
392408

393-
tf2::Transform particle_pose_tf2 = utils::se2dToTf2Transform(p.state);
409+
// Apply Beluga reweight action and normalize
410+
particles_ |= beluga::actions::reweight(sensor_likelihood) | beluga::actions::normalize();
394411

395-
double calculated_distance = get_error_distance_to_obstacle(
396-
particle_pose_tf2, bf2laser_, laser2point, scan, costmap,
397-
distance_perception_error_);
412+
// ! Study if this can be included somehow in the same loop as the likelihood (probably not because Beluga's reweight action receives a state, not the whole particle)
413+
// Calculate quality based on the average probability of hits, as per the original implementation.
414+
// This is calculated separately from the particle weights for robustness.
415+
quality_ = 0.0;
416+
for (auto &p : particles_)
417+
{
418+
tf2::Transform map2bf = utils::se2dToTf2Transform(p.state);
419+
double total_prob = 0.0;
420+
for (size_t j = 0u; j < scan.ranges.size(); ++j)
421+
{
422+
if (std::isnan(scan.ranges[j]) || std::isinf(scan.ranges[j]))
423+
{
424+
continue;
425+
}
398426

399-
if (!std::isinf(calculated_distance))
427+
tf2::Transform laser2point = get_transform_to_read(scan, static_cast<int>(j));
428+
double err_m = get_error_distance_to_obstacle(map2bf, bf2laser_, laser2point, scan, costmap, distance_perception_error_);
429+
430+
if (!std::isinf(err_m))
400431
{
401-
const double a = calculated_distance / distance_perception_error_;
432+
const double o = distance_perception_error_;
433+
const double normal_comp_1 = utils::INV_SQRT_2PI / o;
434+
const double a = err_m / o;
402435
const double normal_comp_2 = std::exp(-0.5 * a * a);
403-
404436
double prob = std::clamp(normal_comp_1 * normal_comp_2, 0.0, 1.0);
405-
p.weight = std::max(p.weight + prob, 0.000001);
406-
407-
p.hits += prob;
437+
total_prob += prob;
408438
}
409439
}
410-
}
411-
412-
particles_ |= beluga::actions::normalize();
413-
414-
// Calculate quality
415-
quality_ = 0.0;
416-
for (auto &p : particles_)
417-
{
418-
p.hits = p.hits / static_cast<float>(scan.ranges.size());
440+
p.hits = total_prob / static_cast<float>(scan.ranges.size());
419441
quality_ = std::max(quality_, p.hits);
420442
}
421443
}

0 commit comments

Comments
 (0)