use crate::core::{Point2, Result, ColmapError};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub type FeatureId = u32;
pub use crate::core::image::ImageId;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Feature {
pub point: Point2,
pub descriptor: Vec<u8>,
pub scale: f32,
pub angle: f32,
pub response: f32,
pub octave: i32,
pub point3d_id: Option<u32>,
}
impl Feature {
pub fn new(
point: Point2,
descriptor: Vec<u8>,
scale: f32,
angle: f32,
response: f32,
octave: i32,
) -> Self {
Self {
point,
descriptor,
scale,
angle,
response,
octave,
point3d_id: None,
}
}
pub fn simple(point: Point2, descriptor: Vec<u8>) -> Self {
Self::new(point, descriptor, 1.0, 0.0, 0.0, 0)
}
pub fn is_triangulated(&self) -> bool {
self.point3d_id.is_some()
}
pub fn set_point3d_id(&mut self, point3d_id: u32) {
self.point3d_id = Some(point3d_id);
}
pub fn clear_point3d_id(&mut self) {
self.point3d_id = None;
}
pub fn descriptor_distance(&self, other: &Feature) -> Result<f32> {
if self.descriptor.len() != other.descriptor.len() {
return Err(ColmapError::FeatureExtraction(
"Descriptor lengths do not match".to_string(),
));
}
if self.is_binary_descriptor() {
Ok(self.hamming_distance(&other.descriptor) as f32)
} else {
Ok(self.euclidean_distance(&other.descriptor))
}
}
fn is_binary_descriptor(&self) -> bool {
self.descriptor.len() % 8 == 0 &&
self.descriptor.iter().all(|&x| x == 0 || x == 1)
}
fn hamming_distance(&self, other: &[u8]) -> u32 {
self.descriptor
.iter()
.zip(other.iter())
.map(|(&a, &b)| (a ^ b).count_ones())
.sum()
}
fn euclidean_distance(&self, other: &[u8]) -> f32 {
self.descriptor
.iter()
.zip(other.iter())
.map(|(&a, &b)| {
let diff = a as f32 - b as f32;
diff * diff
})
.sum::<f32>()
.sqrt()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FeatureMatch {
pub image1_id: ImageId,
pub image2_id: ImageId,
pub feature1_idx: usize,
pub feature2_idx: usize,
pub distance: f32,
pub confidence: f32,
}
impl FeatureMatch {
pub fn new(
image1_id: ImageId,
image2_id: ImageId,
feature1_idx: usize,
feature2_idx: usize,
distance: f32,
) -> Self {
Self {
image1_id,
image2_id,
feature1_idx,
feature2_idx,
distance,
confidence: 1.0 / (1.0 + distance), }
}
pub fn set_confidence(&mut self, confidence: f32) {
self.confidence = confidence.clamp(0.0, 1.0);
}
pub fn image_pair(&self) -> (ImageId, ImageId) {
(self.image1_id, self.image2_id)
}
pub fn feature_pair(&self) -> (usize, usize) {
(self.feature1_idx, self.feature2_idx)
}
pub fn is_valid(&self, max_distance: f32, min_confidence: f32) -> bool {
self.distance <= max_distance && self.confidence >= min_confidence
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureMatches {
pub image1_id: ImageId,
pub image2_id: ImageId,
pub matches: Vec<FeatureMatch>,
pub verified: bool,
}
impl FeatureMatches {
pub fn new(image1_id: ImageId, image2_id: ImageId) -> Self {
Self {
image1_id,
image2_id,
matches: Vec::new(),
verified: false,
}
}
pub fn add_match(&mut self, feature_match: FeatureMatch) {
self.matches.push(feature_match);
}
pub fn count(&self) -> usize {
self.matches.len()
}
pub fn filter_matches(&mut self, max_distance: f32, min_confidence: f32) {
self.matches.retain(|m| m.is_valid(max_distance, min_confidence));
}
pub fn sort_by_distance(&mut self) {
self.matches.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap());
}
pub fn sort_by_confidence(&mut self) {
self.matches.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
}
pub fn average_distance(&self) -> f32 {
if self.matches.is_empty() {
return 0.0;
}
self.matches.iter().map(|m| m.distance).sum::<f32>() / self.matches.len() as f32
}
pub fn average_confidence(&self) -> f32 {
if self.matches.is_empty() {
return 0.0;
}
self.matches.iter().map(|m| m.confidence).sum::<f32>() / self.matches.len() as f32
}
pub fn mark_verified(&mut self) {
self.verified = true;
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FeatureDatabase {
image_features: HashMap<ImageId, Vec<Feature>>,
feature_matches: HashMap<(ImageId, ImageId), FeatureMatches>,
}
impl FeatureDatabase {
pub fn new() -> Self {
Self {
image_features: HashMap::new(),
feature_matches: HashMap::new(),
}
}
pub fn add_image_features(&mut self, image_id: ImageId, features: Vec<Feature>) {
self.image_features.insert(image_id, features);
}
pub fn get_image_features(&self, image_id: ImageId) -> Option<&Vec<Feature>> {
self.image_features.get(&image_id)
}
pub fn get_image_features_mut(&mut self, image_id: ImageId) -> Option<&mut Vec<Feature>> {
self.image_features.get_mut(&image_id)
}
pub fn add_feature_matches(&mut self, matches: FeatureMatches) {
let key = (matches.image1_id, matches.image2_id);
self.feature_matches.insert(key, matches);
}
pub fn get_feature_matches(&self, image1_id: ImageId, image2_id: ImageId) -> Option<&FeatureMatches> {
self.feature_matches.get(&(image1_id, image2_id))
.or_else(|| self.feature_matches.get(&(image2_id, image1_id)))
}
pub fn get_feature_matches_mut(&mut self, image1_id: ImageId, image2_id: ImageId) -> Option<&mut FeatureMatches> {
if self.feature_matches.contains_key(&(image1_id, image2_id)) {
self.feature_matches.get_mut(&(image1_id, image2_id))
} else {
self.feature_matches.get_mut(&(image2_id, image1_id))
}
}
pub fn feature_count(&self, image_id: ImageId) -> usize {
self.image_features.get(&image_id).map_or(0, |f| f.len())
}
pub fn triangulated_count(&self, image_id: ImageId) -> usize {
self.image_features
.get(&image_id)
.map_or(0, |features| features.iter().filter(|f| f.is_triangulated()).count())
}
pub fn image_ids(&self) -> Vec<ImageId> {
self.image_features.keys().copied().collect()
}
pub fn match_pairs(&self) -> Vec<(ImageId, ImageId)> {
self.feature_matches.keys().copied().collect()
}
pub fn remove_image_features(&mut self, image_id: ImageId) -> Option<Vec<Feature>> {
self.feature_matches.retain(|(id1, id2), _| *id1 != image_id && *id2 != image_id);
self.image_features.remove(&image_id)
}
pub fn clear(&mut self) {
self.image_features.clear();
self.feature_matches.clear();
}
pub fn stats(&self) -> FeatureDatabaseStats {
let total_features: usize = self.image_features.values().map(|f| f.len()).sum();
let total_matches: usize = self.feature_matches.values().map(|m| m.count()).sum();
let triangulated_features: usize = self.image_features
.values()
.flat_map(|features| features.iter())
.filter(|f| f.is_triangulated())
.count();
FeatureDatabaseStats {
image_count: self.image_features.len(),
total_features,
triangulated_features,
match_pairs: self.feature_matches.len(),
total_matches,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureDatabaseStats {
pub image_count: usize,
pub total_features: usize,
pub triangulated_features: usize,
pub match_pairs: usize,
pub total_matches: usize,
}
impl FeatureDatabaseStats {
pub fn triangulation_rate(&self) -> f32 {
if self.total_features == 0 {
0.0
} else {
self.triangulated_features as f32 / self.total_features as f32
}
}
pub fn average_features_per_image(&self) -> f32 {
if self.image_count == 0 {
0.0
} else {
self.total_features as f32 / self.image_count as f32
}
}
pub fn average_matches_per_pair(&self) -> f32 {
if self.match_pairs == 0 {
0.0
} else {
self.total_matches as f32 / self.match_pairs as f32
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::Point2;
#[test]
fn test_feature_creation() {
let point = Point2::new(100.0, 200.0);
let descriptor = vec![1, 2, 3, 4];
let feature = Feature::simple(point, descriptor.clone());
assert_eq!(feature.point, point);
assert_eq!(feature.descriptor, descriptor);
assert!(!feature.is_triangulated());
}
#[test]
fn test_feature_match() {
let match1 = FeatureMatch::new(1, 2, 0, 1, 0.5);
assert_eq!(match1.image_pair(), (1, 2));
assert_eq!(match1.feature_pair(), (0, 1));
assert!(match1.is_valid(1.0, 0.1));
assert!(!match1.is_valid(0.1, 0.1));
}
#[test]
fn test_feature_database() {
let mut db = FeatureDatabase::new();
let features = vec![
Feature::simple(Point2::new(10.0, 20.0), vec![1, 2, 3]),
Feature::simple(Point2::new(30.0, 40.0), vec![4, 5, 6]),
];
db.add_image_features(1, features);
assert_eq!(db.feature_count(1), 2);
assert_eq!(db.triangulated_count(1), 0);
let stats = db.stats();
assert_eq!(stats.image_count, 1);
assert_eq!(stats.total_features, 2);
}
#[test]
fn test_descriptor_distance() {
let feature1 = Feature::simple(Point2::new(0.0, 0.0), vec![1, 2, 3, 4]);
let feature2 = Feature::simple(Point2::new(0.0, 0.0), vec![1, 2, 3, 5]);
let distance = feature1.descriptor_distance(&feature2).unwrap();
assert!(distance > 0.0);
}
}