use tracktor::filters::glmb::{
GlmbFilter, GlmbTruncationConfig, extract_best_hypothesis, extract_marginal_states,
};
use tracktor::filters::lmb::{LabeledBirthModel, NoBirthModel};
use tracktor::prelude::*;
struct GlmbBirthModel {
locations: Vec<(f64, f64, f64)>,
}
impl GlmbBirthModel {
fn new() -> Self {
Self {
locations: vec![
(0.02, 20.0, 20.0), (0.02, 80.0, 80.0), (0.02, 50.0, 50.0), ],
}
}
}
impl LabeledBirthModel<f64, 4> for GlmbBirthModel {
fn birth_tracks(&self, label_gen: &mut LabelGenerator) -> Vec<BernoulliTrack<f64, 4>> {
let cov = StateCovariance::from_matrix(nalgebra::matrix![
100.0, 0.0, 0.0, 0.0;
0.0, 100.0, 0.0, 0.0;
0.0, 0.0, 25.0, 0.0;
0.0, 0.0, 0.0, 25.0
]);
self.locations
.iter()
.map(|(existence, x, y)| {
BernoulliTrack::new(
label_gen.next_label(),
*existence,
GaussianState::new(1.0, StateVector::from_array([*x, *y, 0.0, 0.0]), cov),
)
})
.collect()
}
fn expected_birth_count(&self) -> f64 {
self.locations.iter().map(|(e, _, _)| e).sum()
}
}
fn main() {
println!("GLMB Filter: Multi-Target Tracking");
println!("===================================\n");
example_basic_tracking();
println!("\n");
example_compare_methods();
}
fn example_basic_tracking() {
println!("Example 1: Basic GLMB Tracking with step_joint()");
println!("-------------------------------------------------\n");
let transition = ConstantVelocity2D::new(1.0, 0.99); let observation = PositionSensor2D::new(5.0, 0.95); let clutter = UniformClutter2D::new(2.0, (0.0, 100.0), (0.0, 100.0)); let birth = GlmbBirthModel::new();
let truncation = GlmbTruncationConfig {
log_weight_threshold: -15.0, max_hypotheses: 100, max_per_cardinality: Some(20), };
let filter = GlmbFilter::with_truncation(
transition,
observation,
clutter,
birth,
truncation,
10, );
let mut state = filter.initial_state();
let dt = 1.0;
let measurements_per_step = [
vec![[22.0, 21.0], [68.0, 32.0], [50.0, 90.0]],
vec![[24.0, 23.0], [67.0, 35.0]],
vec![[65.0, 38.0], [10.0, 10.0]],
vec![[28.0, 25.0], [64.0, 41.0]],
vec![[30.0, 26.0], [62.0, 44.0], [80.0, 80.0]],
];
println!("Initial state: {} hypotheses\n", state.num_hypotheses());
for (t, meas_data) in measurements_per_step.iter().enumerate() {
let measurements: Vec<Measurement<f64, 2>> = meas_data
.iter()
.map(|m| Measurement::from_array(*m))
.collect();
println!("Time {}: {} measurements", t, measurements.len());
let (new_state, stats) = filter.step_joint(state, &measurements, dt);
state = new_state;
println!(
" Hypotheses: {}, Issues: {}",
state.num_hypotheses(),
if stats.has_issues() { "yes" } else { "none" }
);
let estimates = extract_best_hypothesis(&state);
println!(" Best hypothesis tracks: {}", estimates.len());
for est in &estimates {
println!(
" Label {:?}: position=({:.1}, {:.1})",
est.label,
est.state.as_slice()[0],
est.state.as_slice()[1]
);
}
println!(" MAP cardinality: {}", state.map_cardinality());
let marginals = extract_marginal_states(&state, 0.5);
if !marginals.is_empty() {
println!(" Marginal estimates (r > 0.5): {}", marginals.len());
}
println!();
}
}
fn example_compare_methods() {
println!("Example 2: Comparing step() vs step_joint()");
println!("--------------------------------------------\n");
let transition = ConstantVelocity2D::new(1.0, 0.99);
let observation = PositionSensor2D::new(5.0, 0.95);
let clutter = UniformClutter2D::new(1.0, (0.0, 100.0), (0.0, 100.0));
let birth = NoBirthModel;
let filter = GlmbFilter::new(transition, observation, clutter, birth, 5);
let label1 = Label::new(0, 0);
let label2 = Label::new(0, 1);
let cov: StateCovariance<f64, 4> = StateCovariance::from_matrix(nalgebra::matrix![
10.0, 0.0, 0.0, 0.0;
0.0, 10.0, 0.0, 0.0;
0.0, 0.0, 1.0, 0.0;
0.0, 0.0, 0.0, 1.0
]);
let track1 = tracktor::filters::glmb::GlmbTrack::new(
label1,
GaussianState::new(1.0, StateVector::from_array([20.0, 20.0, 1.0, 0.5]), cov),
);
let track2 = tracktor::filters::glmb::GlmbTrack::new(
label2,
GaussianState::new(1.0, StateVector::from_array([60.0, 40.0, -0.5, 1.0]), cov),
);
let state1 =
tracktor::filters::glmb::GlmbFilterState::from_tracks(vec![track1.clone(), track2.clone()]);
let state2 = tracktor::filters::glmb::GlmbFilterState::from_tracks(vec![track1, track2]);
let measurements = vec![
Measurement::from_array([21.5, 21.0]), ];
let dt = 1.0;
println!("Running step() (separate predict + update)...");
let start1 = std::time::Instant::now();
let (result1, _) = filter.step(state1, &measurements, dt);
let time1 = start1.elapsed();
println!("Running step_joint() (fast joint predict-update)...");
let start2 = std::time::Instant::now();
let (result2, _) = filter.step_joint(state2, &measurements, dt);
let time2 = start2.elapsed();
println!("\nResults:");
println!(
" step(): {} hypotheses, took {:?}",
result1.num_hypotheses(),
time1
);
println!(
" step_joint(): {} hypotheses, took {:?}",
result2.num_hypotheses(),
time2
);
let est1 = extract_best_hypothesis(&result1);
let est2 = extract_best_hypothesis(&result2);
println!("\n Best hypothesis comparison:");
println!(" step() tracks: {}", est1.len());
println!(" step_joint() tracks: {}", est2.len());
if !est1.is_empty() && !est2.is_empty() {
let x1 = est1[0].state.as_slice()[0];
let x2 = est2[0].state.as_slice()[0];
let y1 = est1[0].state.as_slice()[1];
let y2 = est2[0].state.as_slice()[1];
println!("\n Position from step(): ({:.2}, {:.2})", x1, y1);
println!(" Position from step_joint(): ({:.2}, {:.2})", x2, y2);
println!(
" Difference: ({:.4}, {:.4})",
(x1 - x2).abs(),
(y1 - y2).abs()
);
}
println!("\nNote: step_joint() is more efficient when there are many hypotheses");
println!("sharing the same track sets, as it shares computation across them.");
}