use crate::features::{BinaryDescriptor, Keypoint, MatchPair};
#[must_use]
pub fn hamming_u64_batch(a: &[u8; 32], b: &[u8; 32]) -> u32 {
let mut total = 0u32;
let mut offset = 0;
while offset + 8 <= 32 {
let a_u64 = u64::from_le_bytes([
a[offset],
a[offset + 1],
a[offset + 2],
a[offset + 3],
a[offset + 4],
a[offset + 5],
a[offset + 6],
a[offset + 7],
]);
let b_u64 = u64::from_le_bytes([
b[offset],
b[offset + 1],
b[offset + 2],
b[offset + 3],
b[offset + 4],
b[offset + 5],
b[offset + 6],
b[offset + 7],
]);
total += (a_u64 ^ b_u64).count_ones();
offset += 8;
}
total
}
#[must_use]
pub fn find_nearest_neighbor(
query: &BinaryDescriptor,
targets: &[BinaryDescriptor],
) -> Option<(usize, u32, u32)> {
if targets.is_empty() {
return None;
}
let mut best_dist = u32::MAX;
let mut second_best_dist = u32::MAX;
let mut best_idx = 0;
for (j, target) in targets.iter().enumerate() {
let dist = hamming_u64_batch(&query.data, &target.data);
if dist < best_dist {
second_best_dist = best_dist;
best_dist = dist;
best_idx = j;
} else if dist < second_best_dist {
second_best_dist = dist;
}
}
Some((best_idx, best_dist, second_best_dist))
}
#[must_use]
pub fn batch_nearest_neighbor(
keypoints1: &[Keypoint],
descs1: &[BinaryDescriptor],
keypoints2: &[Keypoint],
descs2: &[BinaryDescriptor],
max_distance: u32,
ratio_threshold: f32,
) -> Vec<MatchPair> {
let mut matches = Vec::new();
for (i, desc1) in descs1.iter().enumerate() {
if let Some((best_idx, best_dist, second_best)) = find_nearest_neighbor(desc1, descs2) {
if best_dist <= max_distance && second_best > 0 {
let ratio = best_dist as f32 / second_best as f32;
if ratio < ratio_threshold {
if i < keypoints1.len() && best_idx < keypoints2.len() {
matches.push(MatchPair::new(
i,
best_idx,
best_dist,
keypoints1[i].point,
keypoints2[best_idx].point,
));
}
}
}
}
}
matches
}
#[must_use]
pub fn pairwise_distances(descs1: &[BinaryDescriptor], descs2: &[BinaryDescriptor]) -> Vec<u32> {
let n1 = descs1.len();
let n2 = descs2.len();
let mut distances = vec![0u32; n1 * n2];
for (i, d1) in descs1.iter().enumerate() {
for (j, d2) in descs2.iter().enumerate() {
distances[i * n2 + j] = hamming_u64_batch(&d1.data, &d2.data);
}
}
distances
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hamming_identical() {
let a = [0xAA_u8; 32];
let b = [0xAA_u8; 32];
assert_eq!(hamming_u64_batch(&a, &b), 0);
}
#[test]
fn test_hamming_all_different() {
let a = [0xFF_u8; 32];
let b = [0x00_u8; 32];
assert_eq!(hamming_u64_batch(&a, &b), 256);
}
#[test]
fn test_hamming_known_value() {
let mut a = [0u8; 32];
let mut b = [0u8; 32];
a[0] = 0b1111_0000; b[0] = 0b0000_1111; assert_eq!(hamming_u64_batch(&a, &b), 8); }
#[test]
fn test_hamming_single_bit() {
let a = [0u8; 32];
let mut b = [0u8; 32];
b[0] = 1;
assert_eq!(hamming_u64_batch(&a, &b), 1);
}
#[test]
fn test_hamming_consistency_with_byte_method() {
let a = BinaryDescriptor::new([0x5A; 32]);
let b = BinaryDescriptor::new([0xA5; 32]);
let byte_result = a.hamming_distance(&b);
let u64_result = hamming_u64_batch(&a.data, &b.data);
assert_eq!(byte_result, u64_result);
}
#[test]
fn test_find_nearest_neighbor_empty() {
let query = BinaryDescriptor::new([0; 32]);
assert!(find_nearest_neighbor(&query, &[]).is_none());
}
#[test]
fn test_find_nearest_neighbor_single() {
let query = BinaryDescriptor::new([0; 32]);
let targets = vec![BinaryDescriptor::new([0xFF; 32])];
let result = find_nearest_neighbor(&query, &targets);
assert!(result.is_some());
let (idx, dist, _) = result.expect("should exist");
assert_eq!(idx, 0);
assert_eq!(dist, 256);
}
#[test]
fn test_find_nearest_neighbor_best_match() {
let query = BinaryDescriptor::new([0; 32]);
let targets = vec![
BinaryDescriptor::new([0xFF; 32]), BinaryDescriptor::new([0x01; 32]), BinaryDescriptor::new([0x0F; 32]), ];
let (idx, dist, _) = find_nearest_neighbor(&query, &targets).expect("should exist");
assert_eq!(idx, 1);
assert_eq!(dist, 32);
}
#[test]
fn test_batch_nearest_neighbor() {
let kp1 = vec![
Keypoint::new(10.0, 10.0, 1.0, 0.0, 1.0),
Keypoint::new(20.0, 20.0, 1.0, 0.0, 1.0),
];
let kp2 = vec![
Keypoint::new(11.0, 11.0, 1.0, 0.0, 1.0),
Keypoint::new(21.0, 21.0, 1.0, 0.0, 1.0),
];
let d1 = vec![
BinaryDescriptor::new([0; 32]),
BinaryDescriptor::new([0xFF; 32]),
];
let d2 = vec![
BinaryDescriptor::new([0; 32]), BinaryDescriptor::new([0xFF; 32]), ];
let matches = batch_nearest_neighbor(&kp1, &d1, &kp2, &d2, 256, 0.9);
assert_eq!(matches.len(), 2);
}
#[test]
fn test_batch_nearest_neighbor_with_threshold() {
let kp1 = vec![Keypoint::new(10.0, 10.0, 1.0, 0.0, 1.0)];
let kp2 = vec![
Keypoint::new(11.0, 11.0, 1.0, 0.0, 1.0),
Keypoint::new(12.0, 12.0, 1.0, 0.0, 1.0),
];
let d1 = vec![BinaryDescriptor::new([0xFF; 32])];
let d2 = vec![
BinaryDescriptor::new([0x00; 32]), BinaryDescriptor::new([0x01; 32]), ];
let matches = batch_nearest_neighbor(&kp1, &d1, &kp2, &d2, 10, 0.9);
assert!(matches.is_empty());
}
#[test]
fn test_pairwise_distances() {
let d1 = vec![
BinaryDescriptor::new([0; 32]),
BinaryDescriptor::new([0xFF; 32]),
];
let d2 = vec![
BinaryDescriptor::new([0; 32]),
BinaryDescriptor::new([0xFF; 32]),
];
let dists = pairwise_distances(&d1, &d2);
assert_eq!(dists.len(), 4);
assert_eq!(dists[0], 0); assert_eq!(dists[1], 256); assert_eq!(dists[2], 256); assert_eq!(dists[3], 0); }
#[test]
fn test_pairwise_distances_empty() {
let d1: Vec<BinaryDescriptor> = vec![];
let d2 = vec![BinaryDescriptor::new([0; 32])];
let dists = pairwise_distances(&d1, &d2);
assert!(dists.is_empty());
}
#[test]
fn test_symmetry() {
let a = [
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08,
];
let b = [
0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99,
0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0xFF, 0xFE, 0xFD, 0xFC, 0xFB,
0xFA, 0xF9, 0xF8, 0xF7,
];
assert_eq!(hamming_u64_batch(&a, &b), hamming_u64_batch(&b, &a));
}
}