diff --git a/src/search_index/mod.rs b/src/search_index/mod.rs index b21f4e6..6ac509a 100644 --- a/src/search_index/mod.rs +++ b/src/search_index/mod.rs @@ -1,3 +1,30 @@ +//! +//! This module provides the Database for Images and compare methods to search in it. +//! +//! The database Struct provides the Images and has a threadpool to efficiently process all given features for all Images +//! +//! +//! to generate a database you need a vector of paths of picture that you want to save and search in it. +//! You also need a Vector of Feature generator functions that generates the feature of every image +//! +//!``` +//! # use std::path::{PathBuf}; +//! # use imsearch::image::Image; +//! # use imsearch::search_index; +//! use imsearch::search_index::FeatureGenerator; +//! +//! let path: Vec = Vec::new(); +//! let features: Vec = Vec::new(); +//! +//! let Database = search_index::Database::new(&path, features ); +//! ``` +//! +//! +//!This Library provides some Feature generator functions but you can also create your own. +//!The Feature generator has to fit in the "FeatureGenerator" type to work with the database. +//! +//! + use crate::image::Image; use crate::multithreading::{Task, ThreadPool}; use serde::{Deserialize, Serialize}; @@ -7,11 +34,13 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; +///this trait provides a function to compare objects and returns a f32 between 0 and 1. +/// 1 is identical and 0 is different. with this trait you get the similarity between the objects trait WeightedCmp { fn weighted(&self, other: &Self) -> f32; } -/// Every feature returns a known and sized type +/// Every feature returns a known and sized type from this enum #[derive(Debug, Clone, Serialize, Deserialize)] pub enum FeatureResult { /// A boolean. Just a boolean @@ -31,7 +60,7 @@ pub enum FeatureResult { Char(char), ///A String ;) String(String), - ///a f32 between 0 and 1 + ///a f32 between 0 and 1 where 1 is 100% and 0 is 0% Percent(f32), } @@ -42,7 +71,6 @@ impl Default for FeatureResult { } /// 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) { @@ -62,6 +90,14 @@ impl PartialEq for FeatureResult { } } +///in this trait we compare the types to get the similarity between them where 1 is identical and 0 is completly different +/// +/// the Vec type compares each member recursive. +/// the Rgba type returns the Delta E similarity of the Colors +/// the Indices type is compared with the cosines similarity +/// the Percent type returns the 1 - difference +/// +/// impl WeightedCmp for FeatureResult { fn weighted(&self, other: &Self) -> f32 { match (self, other) { @@ -97,18 +133,18 @@ impl WeightedCmp for FeatureResult { 0. } } - (Self::Rgba(l0, l1, l2, _), Self::Rgba(r0, r1, r2,_)) => { - let lableft = rgb_to_lab(vec![*l0,*l1,*l2]); - let labright = rgb_to_lab(vec![*r0,*r1,*r2]); + (Self::Rgba(l0, l1, l2, _), Self::Rgba(r0, r1, r2, _)) => { + let lableft = rgb_to_lab(vec![*l0, *l1, *l2]); + let labright = rgb_to_lab(vec![*r0, *r1, *r2]); - let mut result = ((lableft[0]-labright[0])*(lableft[0]-labright[0]) - +(lableft[1]-labright[1])*(lableft[1]-labright[1]) - +(lableft[2]-labright[2])*(lableft[2]-labright[2])).sqrt(); //euclidian distance between two colors: Delta E + let mut result = ((lableft[0] - labright[0]) * (lableft[0] - labright[0]) + + (lableft[1] - labright[1]) * (lableft[1] - labright[1]) + + (lableft[2] - labright[2]) * (lableft[2] - labright[2])) + .sqrt(); //euclidian distance between two colors: Delta E if result > 100. { result = 0.; - } - else { - result = 1. - result/100.; + } else { + result = 1. - result / 100.; } result @@ -117,15 +153,13 @@ impl WeightedCmp for FeatureResult { let mut up = 0_u64; let mut left = 0_u64; let mut right = 0_u64; - for (a,b) in l.iter().zip(r.iter()).map(|(a, b)| (a,b)){ - left += a*a; - right += b*b; - up += a*b; - } - - - let mut result = up as f32 / ((left * right) as f32).sqrt();//cosines similarity + for (a, b) in l.iter().zip(r.iter()).map(|(a, b)| (a, b)) { + left += a * a; + right += b * b; + up += a * b; + } + let mut result = up as f32 / ((left * right) as f32).sqrt(); //cosines similarity if result.is_nan() { if left == right { @@ -158,14 +192,27 @@ impl WeightedCmp for FeatureResult { } } +///this function transforms rgb values to lab values fn rgb_to_lab(rgb: Vec) -> [f32; 3] { - let r = rgb[0] / 255.0; - let g = rgb[1] / 255.0; - let b = rgb[2] / 255.0; + let r = rgb[0] / 255.0; + let g = rgb[1] / 255.0; + let b = rgb[2] / 255.0; - let r = if r > 0.04045 { ((r + 0.055) / 1.055).powf(2.4) } else { r / 12.92 }; - let g = if g > 0.04045 { ((g + 0.055) / 1.055).powf(2.4) } else { g / 12.92 }; - let b = if b > 0.04045 { ((b + 0.055) / 1.055).powf(2.4) } else { b / 12.92 }; + let r = if r > 0.04045 { + ((r + 0.055) / 1.055).powf(2.4) + } else { + r / 12.92 + }; + let g = if g > 0.04045 { + ((g + 0.055) / 1.055).powf(2.4) + } else { + g / 12.92 + }; + let b = if b > 0.04045 { + ((b + 0.055) / 1.055).powf(2.4) + } else { + b / 12.92 + }; let x = r * 0.4124 + g * 0.3576 + b * 0.1805; let y = r * 0.2126 + g * 0.7152 + b * 0.0722; @@ -175,9 +222,21 @@ fn rgb_to_lab(rgb: Vec) -> [f32; 3] { let y = y / 1.0; let z = z / 1.08883; - let x = if x > 0.008856 { x.powf(1.0 / 3.0) } else { (7.787 * x) + (16.0 / 116.0) }; - let y = if y > 0.008856 { y.powf(1.0 / 3.0) } else { (7.787 * y) + (16.0 / 116.0) }; - let z = if z > 0.008856 { z.powf(1.0 / 3.0) } else { (7.787 * z) + (16.0 / 116.0) }; + let x = if x > 0.008856 { + x.powf(1.0 / 3.0) + } else { + (7.787 * x) + (16.0 / 116.0) + }; + let y = if y > 0.008856 { + y.powf(1.0 / 3.0) + } else { + (7.787 * y) + (16.0 / 116.0) + }; + let z = if z > 0.008856 { + z.powf(1.0 / 3.0) + } else { + (7.787 * z) + (16.0 / 116.0) + }; let l = (116.0 * y) - 16.0; let a = 500.0 * (x - y); @@ -186,9 +245,11 @@ fn rgb_to_lab(rgb: Vec) -> [f32; 3] { [l, a, b] } - pub type FeatureGenerator = fn(Arc>) -> (String, FeatureResult); +///The Database stores the images with the feature generators. +///It also stores the threadpool +///the images of the Database can get serialized using Serde_Json. the complete Database cant get serialized #[derive(Default)] pub struct Database { images: IndexedImages, @@ -200,6 +261,9 @@ pub struct Database { } impl Database { + ///This function search the Database after the Similarity to a given Image in a specific feature. + /// It returns a Vector of all images and a f32 value which represents the Similarity in percent. + /// pub fn search(&self, imagepath: &Path, feature: FeatureGenerator) -> Vec<(PathBuf, f32)> { self.images.search(imagepath, feature) } @@ -215,7 +279,7 @@ impl Database { } /// with add_image you can add images in a existing database. - /// databases from a file are read only + /// databases from a file are read only. pub fn add_image(&mut self, path: &Path) { if !self.generators.is_empty() { self.images @@ -225,6 +289,7 @@ impl Database { } } + /// with from_file you can generate a Database out of a given path to a serialized database pub fn from_file(path: &Path) -> Self { let filestring = fs::read_to_string(path).expect("can't read that file"); let images = serde_json::from_str::(&filestring) @@ -237,12 +302,15 @@ impl Database { } } +///IndexedImages stores the images of the Database and is serializable #[derive(Serialize, Deserialize, Default, PartialEq, Debug)] struct IndexedImages { images: HashMap>, } impl IndexedImages { + ///the new function generates all images and generates every feature so it can store these. + fn new( imagepaths: &Vec, features: &[FeatureGenerator], @@ -271,6 +339,9 @@ impl IndexedImages { } } + ///This function search the Database after the Similarity to a given Image in a specific feature. + /// It returns a Vector of all images and a f32 value which represents the Similarity in percent. + /// fn search(&self, imagepath: &Path, feature: FeatureGenerator) -> Vec<(PathBuf, f32)> { let image: Arc> = Arc::new(Image::default()); //todo!("Image reader function") let search_feat = feature(image); @@ -286,6 +357,7 @@ impl IndexedImages { result } + ///this function lets you add images to the Indexed Image struct fn add_image( &mut self, path: &Path, @@ -317,69 +389,71 @@ fn average_luminance(image: Arc>) -> (String, FeatureResult) { mod tests { use super::*; + ///this function tests the Serialization of the Database #[test] fn conversion() { - let mut images: HashMap> = HashMap::new(); - let mut feat: HashMap = HashMap::new(); + let mut feat: HashMap = HashMap::new(); feat.insert(String::from("average-brightness"), FeatureResult::F32(0.0)); images.insert(PathBuf::new(), feat); let data = IndexedImages { images }; let _as_json = serde_json::to_string(&data).expect("couldnt convert"); println!("{:?}", _as_json); - let data_after_conversion = serde_json::from_str::(&_as_json).expect("couldnt convert from string"); + let data_after_conversion = + serde_json::from_str::(&_as_json).expect("couldnt convert from string"); assert_eq!(data, data_after_conversion); } + ///this function tests Edgecases for the cosine_similarity in the weightet function #[test] - fn cosine_similarity(){ - let vec1 = FeatureResult::Indices(vec!{1, 3, 4}); - let vec2 = FeatureResult::Indices(vec!{1, 3, 4}); - + fn cosine_similarity() { + let vec1 = FeatureResult::Indices(vec![1, 3, 4]); + let vec2 = FeatureResult::Indices(vec![1, 3, 4]); assert_eq!(1., vec1.weighted(&vec2)); // both are identical - let vec2 = FeatureResult::Indices(vec!{0, 0, 0}); + let vec2 = FeatureResult::Indices(vec![0, 0, 0]); assert_eq!(0., vec1.weighted(&vec2)); // one is 0 - let vec1 = FeatureResult::Indices(vec!{0, 0, 0}); + let vec1 = FeatureResult::Indices(vec![0, 0, 0]); assert_eq!(1., vec1.weighted(&vec2)); // both are 0 assert_eq!(1., vec2.weighted(&vec1)); // it shouldn't change if the Values are switched - let vec1 = FeatureResult::Indices(vec!{7, 3, 4}); - let vec2 = FeatureResult::Indices(vec!{1, 5, 2}); + let vec1 = FeatureResult::Indices(vec![7, 3, 4]); + let vec2 = FeatureResult::Indices(vec![1, 5, 2]); assert_eq!(vec1.weighted(&vec2), vec2.weighted(&vec1)); println!("{:?}", vec1.weighted(&vec2)); - - let mut vec1 = vec![5;9999]; - vec1.push( 1); - let vec1 = FeatureResult::Indices(vec1); - let vec2 = FeatureResult::Indices(vec!{7;10000}); + let mut vec1 = vec![5; 9999]; + vec1.push(1); + let vec1 = FeatureResult::Indices(vec1); + let vec2 = FeatureResult::Indices(vec![7; 10000]); println!("{:?}", vec1.weighted(&vec2)); - - - } + ///this function tests all of the weighted function #[test] - fn weighted() { + fn weighted() { + let vec1 = FeatureResult::Vec(vec![ + FeatureResult::Bool(true), + FeatureResult::Char('c'), + FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]), + FeatureResult::F32(44.543), + ]); - let vec1 = FeatureResult::Vec(vec![FeatureResult::Bool(true), - FeatureResult::Char('c'), - FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]), - FeatureResult::F32(44.543) ]); + let vec2 = FeatureResult::Vec(vec![ + FeatureResult::Bool(true), + FeatureResult::Char('c'), + FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]), + FeatureResult::F32(44.543), + ]); + assert_eq!(1., vec2.weighted(&vec1)); - let vec2 = FeatureResult::Vec(vec![FeatureResult::Bool(true), - FeatureResult::Char('c'), - FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]), - FeatureResult::F32(44.543) ]); - assert_eq!(1., vec2.weighted(&vec1)); - - - let vec2 = FeatureResult::Vec(vec![FeatureResult::Bool(true), - FeatureResult::Char('c'), - FeatureResult::F32(44.543) , - FeatureResult::Vec(vec![FeatureResult::Percent(0.5)])]); + let vec2 = FeatureResult::Vec(vec![ + FeatureResult::Bool(true), + FeatureResult::Char('c'), + FeatureResult::F32(44.543), + FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]), + ]); assert_eq!(0.5, vec2.weighted(&vec1)); println!("{:?}", vec1.weighted(&vec2)); @@ -395,43 +469,35 @@ mod tests { let value2 = FeatureResult::String(String::from("notTesting")); assert_eq!(0., value1.weighted(&value2)); - let value2 = FeatureResult::String(String::from("Testing")); - assert_eq!(1., value1.weighted(&value2)) ; - - } + assert_eq!(1., value1.weighted(&value2)); + } + ///this test is for the rgba values in the weighted function #[test] fn weighted_rgba() { - let value1 = FeatureResult::Rgba(32.6754,42.432,43.87,255.); - let value2 = FeatureResult::Rgba(32.6754,42.432,43.87,255.); - assert_eq!(1., value1.weighted(&value2)) ; + let value1 = FeatureResult::Rgba(32.6754, 42.432, 43.87, 255.); + let value2 = FeatureResult::Rgba(32.6754, 42.432, 43.87, 255.); + assert_eq!(1., value1.weighted(&value2)); - let value1 = FeatureResult::Rgba(255.,255.,0.,255.); - let value2 = FeatureResult::Rgba(0.,0.,0.,255.); + let value1 = FeatureResult::Rgba(255., 255., 0., 255.); + let value2 = FeatureResult::Rgba(0., 0., 0., 255.); //assert_eq!(1., value1.weighted(&value2)) ; println!("Yellow to Black: {:?}", value1.weighted(&value2)); - let value1 = FeatureResult::Rgba(255.,255.,0.,255.); - let value2 = FeatureResult::Rgba(200.,255.,55.,255.); + let value1 = FeatureResult::Rgba(255., 255., 0., 255.); + let value2 = FeatureResult::Rgba(200., 255., 55., 255.); //assert_eq!(1., value1.weighted(&value2)) ; println!("yellow to light green: {:?}", value1.weighted(&value2)); - let value1 = FeatureResult::Rgba(3.,8.,255.,255.); - let value2 = FeatureResult::Rgba(3.,106.,255.,255.); + let value1 = FeatureResult::Rgba(3., 8., 255., 255.); + let value2 = FeatureResult::Rgba(3., 106., 255., 255.); //assert_eq!(1., value1.weighted(&value2)) ; println!("blue to dark blue: {:?}", value1.weighted(&value2)); - let value1 = FeatureResult::Rgba(255.,106.,122.,255.); - let value2 = FeatureResult::Rgba(255.,1.,28.,255.); + let value1 = FeatureResult::Rgba(255., 106., 122., 255.); + let value2 = FeatureResult::Rgba(255., 1., 28., 255.); //assert_eq!(1., value1.weighted(&value2)) ; println!("Red to light red: {:?}", value1.weighted(&value2)); - - } - - - - } -