From 3ff01f7a5bd0a430a4274cc68c60a63beb6bbd68 Mon Sep 17 00:00:00 2001 From: rbezen Date: Wed, 28 May 2025 08:36:04 +0300 Subject: [PATCH 1/2] Bug fix- pass to positional sort unsigned observations (self assign) --- src/trackers/visual_sort/voting.rs | 43 ++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/trackers/visual_sort/voting.rs b/src/trackers/visual_sort/voting.rs index fc3bc0e..4cabb22 100644 --- a/src/trackers/visual_sort/voting.rs +++ b/src/trackers/visual_sort/voting.rs @@ -3,7 +3,7 @@ use crate::trackers::sort::voting::SortVoting; use crate::trackers::sort::VotingType; use crate::trackers::visual_sort::observation_attributes::VisualObservationAttributes; use crate::utils::bbox::Universal2DBox; -use crate::voting::best::BestFitVoting; +use crate::voting::best::BestFitVotingWithFallback; use crate::voting::Voting; use itertools::Itertools; use log::debug; @@ -49,25 +49,39 @@ impl Voting for VisualVoting { where T: IntoIterator>, { - let topn_feature_voting: BestFitVoting = BestFitVoting::new( - self.max_allowed_feature_distance, - self.min_winner_feature_votes, - ); + let topn_feature_voting: BestFitVotingWithFallback = + BestFitVotingWithFallback::new( + self.max_allowed_feature_distance, + self.min_winner_feature_votes, + ); let (distances, distances_clone) = distances.into_iter().tee(); - let feature_winners = topn_feature_voting.winners(distances); - debug!("TopN winners: {:#?}", &feature_winners); + // First round: feature-based voting + let raw_feature_winners: HashMap> = + topn_feature_voting.winners(distances); + debug!("TopN raw_feature_winners: {:#?}", &raw_feature_winners); let mut excluded_tracks = HashSet::new(); - let mut feature_winners = feature_winners + let mut feature_winners = HashMap::new(); + + raw_feature_winners .into_iter() - .map(|(from, w)| { - let winner_track = w[0].winner_track; - excluded_tracks.insert(winner_track); - (from, vec![(winner_track, VotingType::Visual)]) - }) - .collect::>(); + .for_each(|(from, winner_list)| { + let TopNVotingElt { + winner_track, + query_track, + .. + } = winner_list[0]; + + if winner_track != query_track { + excluded_tracks.insert(winner_track); + feature_winners.insert(from, vec![(winner_track, VotingType::Visual)]); + } + }); + + debug!("TopN winners: {:#?}", &feature_winners); + debug!("Excluded tracks: {:#?}", &excluded_tracks); let mut remaining_candidates = HashSet::new(); let mut remaining_tracks = HashSet::new(); @@ -95,6 +109,7 @@ impl Voting for VisualVoting { .into_iter() .map(|(from, winner)| (from, vec![(winner[0], VotingType::Positional)])); + debug!("positional_winners: {:#?}", &positional_winners); feature_winners.extend(positional_winners); feature_winners } From d7e61eed2055020f4ca6cafc5f8af601c552134b Mon Sep 17 00:00:00 2001 From: rbezen Date: Wed, 28 May 2025 08:36:18 +0300 Subject: [PATCH 2/2] Add for visual sort fallback matching for non-best matches --- src/track/voting/best.rs | 98 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/track/voting/best.rs b/src/track/voting/best.rs index 4676952..e951861 100644 --- a/src/track/voting/best.rs +++ b/src/track/voting/best.rs @@ -127,3 +127,101 @@ where res } } + +pub struct BestFitVotingWithFallback +where + OA: ObservationAttributes, +{ + pub max_distance: f32, + pub min_votes: usize, + _phantom: PhantomData, +} + +impl BestFitVotingWithFallback +where + OA: ObservationAttributes, +{ + pub fn new(max_distance: f32, min_votes: usize) -> Self { + Self { + max_distance, + min_votes, + _phantom: PhantomData, + } + } +} + +impl Voting for BestFitVotingWithFallback +where + OA: ObservationAttributes, +{ + type WinnerObject = TopNVotingElt; + + fn winners(&self, distances: T) -> HashMap> + where + T: IntoIterator>, + { + let mut max_dist = -1.0_f32; + + // Step 1: group all distances by (from, to), filter by max_distance + let grouped: HashMap<(u64, u64), Vec> = distances + .into_iter() + .filter_map(|d| match d.feature_distance { + Some(f) if f <= self.max_distance => { + max_dist = max_dist.max(f); + Some(((d.from, d.to), f)) + } + _ => None, + }) + .into_group_map(); + + // Step 2: filter by min_votes + let filtered: Vec = grouped + .into_iter() + .filter(|(_, v)| v.len() >= self.min_votes) + .map(|((from, to), dists)| { + let weight = dists.into_iter().map(|d| (max_dist - d) as f64).sum(); + TopNVotingElt { + query_track: from, + winner_track: to, + weight, + } + }) + .collect(); + + // Step 3: group by query (from), and sort each list by descending weight + let mut per_query = filtered.into_iter().into_group_map_by(|e| e.query_track); + + for candidates in per_query.values_mut() { + candidates.sort_by(|a, b| b.weight.partial_cmp(&a.weight).unwrap()); + } + + // Step 4: assign each query to its best available winner (fallback to self) + let mut used_winners = HashSet::new(); + let mut final_map = HashMap::new(); + + for (query_id, candidates) in per_query { + let mut assigned = false; + for mut cand in candidates { + if !used_winners.contains(&cand.winner_track) { + used_winners.insert(cand.winner_track); + final_map.insert(query_id, vec![cand]); + assigned = true; + break; + } + } + + if !assigned { + final_map.insert( + query_id, + vec![TopNVotingElt { + query_track: query_id, + winner_track: query_id, + weight: 0.0, + }], + ); + } + } + + final_map + } +} \ No newline at end of file