diff --git a/src/lib.rs b/src/lib.rs index 2b26d05..466dbdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ extern crate core; pub mod image; pub mod multithreading; +pub mod search_index; pub fn add(left: usize, right: usize) -> usize { left + right diff --git a/src/search_index/mod.rs b/src/search_index/mod.rs new file mode 100644 index 0000000..5b5e0a7 --- /dev/null +++ b/src/search_index/mod.rs @@ -0,0 +1,171 @@ +use std::collections::HashMap; +use std::default::Default; +use std::path::{Path, PathBuf}; +use serde::{Deserialize, Serialize}; +use crate::image::Image; + + +trait WeightedCmp { + fn weighted(&self, other: &Self) -> f32; +} + + +/// Every feature returns a known and sized type +#[derive(Debug, Clone, Serialize, Deserialize)] +enum FeatureResult { + /// A boolean. Just a boolean + Bool(bool), + /// Signed 32-bit integer + I32(i32), + /// 32-bit single precision floating point + /// can be used for aspect ratio or luminance + F32(f32), + /// Vector for nested multidimensional + Vec(Vec), + /// Standard RGBA color + RGBA(f32, f32, f32, f32), + /// Indices intended for the usage in histograms + Indices(Vec), + ///A Character :) + Char(char), + ///A String ;) + String(String), + ///a f32 between 0 and 1 + Percent(f32), +} + +impl Default for FeatureResult { + fn default() -> Self { + FeatureResult::Bool(false) + } +} + +/// For some feature return type we want to implement a custom compare function +/// for example: histograms are compared with cosine similarity +impl PartialEq for FeatureResult { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, + (Self::I32(l0), Self::I32(r0)) => l0 == r0, + (Self::F32(l0), Self::F32(r0)) => l0 == r0, + (Self::Vec(l0), Self::Vec(r0)) => l0 == r0, + (Self::RGBA(l0, l1, l2, l3), Self::RGBA(r0, r1, r2, r3)) => l0 == r0 && l1 == r1 && l2 == r2 && l3 == r3, + (Self::Indices(l), Self::Indices(r)) => l == r, + (Self::Char(l0), Self::Char(r0)) => l0 == r0, + (Self::String(l0), Self::String(r0)) => l0 == r0, + (Self::Percent(l0), Self::Percent(r0)) => l0 == r0, + _ => false, + } + } +} + +impl WeightedCmp for FeatureResult { + fn weighted(&self, other: &Self) -> f32 { + match (self, other) { + (Self::Bool(l0), Self::Bool(r0)) => if l0 == r0 { 1. } else { 0. }, + (Self::I32(l0), Self::I32(r0)) => if l0 == r0 { 1. } else { 0. }, + (Self::F32(l0), Self::F32(r0)) => if (l0 - r0).abs() > 0.5 { 1. } else { 0. }, + (Self::Vec(r), Self::Vec(l)) => if l == r { 1. } else { 0. }, + (Self::RGBA(l0, l1, l2, l3), Self::RGBA(r0, r1, r2, r3)) => { + let mut a = 0.; + if l0 == r0 { a += 0.25;} + if l1 == r1 { a += 0.25;} + if l2 == r2 { a += 0.25;} + if l3 == r3 { a += 0.25;} + a}, + (Self::Indices(l), Self::Indices(r)) => l.iter().zip(r.iter()).map(|(a, b)| a * b).sum::() as f32 / (l.iter().map(|a| a * a).sum::() as f32 * r.iter().map(|b| b * b).sum::() as f32).sqrt(), //cosines similarity + + (Self::Char(l0), Self::Char(r0)) => if l0 == r0 { 1. } else { 0. }, + (Self::String(l0), Self::String(r0)) => if l0 == r0 { 1. } else { 0. },//todo!("change it a bit") + (Self::Percent(l0), Self::Percent(r0)) => if l0 == r0 { 1. } else { 0. }, + + _ => 0., + } + } +} + + +type FeatureGenerator = Box) -> (String, FeatureResult)>; + +#[derive(Serialize, Deserialize, Default)] +struct Database { + images: HashMap>, + + /// keep feature generator for the case when we add a new image + /// this field is not serialized and needs to be wrapped in an option + #[serde(skip)] + generators: Option> +} + +impl Database { + + pub fn search (&self,image: &Path, feature: FeatureGenerator) -> Vec<(PathBuf,f32)>{ + let image: Image = Image::default(); //todo!("Image reader function") + let search_feat = feature(&image); + let mut result: Vec<(PathBuf,f32)> = Vec::new(); + + for image in &self.images { + for feat in image.1{ + if search_feat.0 == *feat.0 { + result.push((image.0.clone(), search_feat.1.weighted(feat.1))); + } + } + } + result + + } + + + ///the new function generates a new Database out of a vector of the Paths of the Images and a Vector of features + pub fn new(images: &Vec, features: Option>)-> Self{ + + let mut images_with_feats = HashMap::new(); + + for path in images { + let image: Image = Image::default(); //todo!("Image reader function") + let mut feats = HashMap::new(); + if let Some(gen) = &features{ + for generator in gen { + let (name, result) = generator(&image); + feats.insert(name, result); + } + images_with_feats.insert(image.path().clone(), feats); + } + } + Self{ + images: images_with_feats, + generators: features, + } + + + } + + + /// with add_image you can add images in a existing database. + /// databases from a file are read only + pub fn add_image(&mut self, path: &Path) { + let image: Image = Image::default(); //todo!("Image reader function") + let mut features = HashMap::new(); + if let Some(gen) = &self.generators{ + for generator in gen { + let (name, result) = generator(&image); + features.insert(name, result); + } + self.images.insert(image.path().clone(), features); + + } + else { panic!("database without generator functions is immutable") } + } +} + +/// example feature implementation +fn average_luminance(image: &Image) -> (String, FeatureResult) { + (String::from("average-brightness"), FeatureResult::F32(0.0)) +} + +#[test] +fn test() { + let data = Database::default(); + + let _as_json = serde_json::to_string(&data); +}