added Documentary

This commit is contained in:
Felix Müller 2023-06-17 22:05:17 +02:00
parent 5b5e8a48f5
commit 6b140e0978
1 changed files with 154 additions and 88 deletions

View File

@ -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<PathBuf> = Vec::new();
//! let features: Vec<FeatureGenerator> = 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::image::Image;
use crate::multithreading::{Task, ThreadPool}; use crate::multithreading::{Task, ThreadPool};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -7,11 +34,13 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; 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 { trait WeightedCmp {
fn weighted(&self, other: &Self) -> f32; 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)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FeatureResult { pub enum FeatureResult {
/// A boolean. Just a boolean /// A boolean. Just a boolean
@ -31,7 +60,7 @@ pub enum FeatureResult {
Char(char), Char(char),
///A String ;) ///A String ;)
String(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), Percent(f32),
} }
@ -42,7 +71,6 @@ impl Default for FeatureResult {
} }
/// For some feature return type we want to implement a custom compare function /// 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 { impl PartialEq for FeatureResult {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match (self, other) { 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 { impl WeightedCmp for FeatureResult {
fn weighted(&self, other: &Self) -> f32 { fn weighted(&self, other: &Self) -> f32 {
match (self, other) { match (self, other) {
@ -97,18 +133,18 @@ impl WeightedCmp for FeatureResult {
0. 0.
} }
} }
(Self::Rgba(l0, l1, l2, _), Self::Rgba(r0, r1, r2,_)) => { (Self::Rgba(l0, l1, l2, _), Self::Rgba(r0, r1, r2, _)) => {
let lableft = rgb_to_lab(vec![*l0,*l1,*l2]); let lableft = rgb_to_lab(vec![*l0, *l1, *l2]);
let labright = rgb_to_lab(vec![*r0,*r1,*r2]); let labright = rgb_to_lab(vec![*r0, *r1, *r2]);
let mut result = ((lableft[0]-labright[0])*(lableft[0]-labright[0]) let mut result = ((lableft[0] - labright[0]) * (lableft[0] - labright[0])
+(lableft[1]-labright[1])*(lableft[1]-labright[1]) + (lableft[1] - labright[1]) * (lableft[1] - labright[1])
+(lableft[2]-labright[2])*(lableft[2]-labright[2])).sqrt(); //euclidian distance between two colors: Delta E + (lableft[2] - labright[2]) * (lableft[2] - labright[2]))
.sqrt(); //euclidian distance between two colors: Delta E
if result > 100. { if result > 100. {
result = 0.; result = 0.;
} } else {
else { result = 1. - result / 100.;
result = 1. - result/100.;
} }
result result
@ -117,15 +153,13 @@ impl WeightedCmp for FeatureResult {
let mut up = 0_u64; let mut up = 0_u64;
let mut left = 0_u64; let mut left = 0_u64;
let mut right = 0_u64; let mut right = 0_u64;
for (a,b) in l.iter().zip(r.iter()).map(|(a, b)| (a,b)){ for (a, b) in l.iter().zip(r.iter()).map(|(a, b)| (a, b)) {
left += a*a; left += a * a;
right += b*b; right += b * b;
up += a*b; up += a * b;
} }
let mut result = up as f32 / ((left * right) as f32).sqrt(); //cosines similarity
let mut result = up as f32 / ((left * right) as f32).sqrt();//cosines similarity
if result.is_nan() { if result.is_nan() {
if left == right { 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>) -> [f32; 3] { fn rgb_to_lab(rgb: Vec<f32>) -> [f32; 3] {
let r = rgb[0] / 255.0; let r = rgb[0] / 255.0;
let g = rgb[1] / 255.0; let g = rgb[1] / 255.0;
let b = rgb[2] / 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 r = if r > 0.04045 {
let g = if g > 0.04045 { ((g + 0.055) / 1.055).powf(2.4) } else { g / 12.92 }; ((r + 0.055) / 1.055).powf(2.4)
let b = if b > 0.04045 { ((b + 0.055) / 1.055).powf(2.4) } else { b / 12.92 }; } 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 x = r * 0.4124 + g * 0.3576 + b * 0.1805;
let y = r * 0.2126 + g * 0.7152 + b * 0.0722; let y = r * 0.2126 + g * 0.7152 + b * 0.0722;
@ -175,9 +222,21 @@ fn rgb_to_lab(rgb: Vec<f32>) -> [f32; 3] {
let y = y / 1.0; let y = y / 1.0;
let z = z / 1.08883; 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 x = if x > 0.008856 {
let y = if y > 0.008856 { y.powf(1.0 / 3.0) } else { (7.787 * y) + (16.0 / 116.0) }; x.powf(1.0 / 3.0)
let z = if z > 0.008856 { z.powf(1.0 / 3.0) } else { (7.787 * z) + (16.0 / 116.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 l = (116.0 * y) - 16.0;
let a = 500.0 * (x - y); let a = 500.0 * (x - y);
@ -186,9 +245,11 @@ fn rgb_to_lab(rgb: Vec<f32>) -> [f32; 3] {
[l, a, b] [l, a, b]
} }
pub type FeatureGenerator = fn(Arc<Image<f32>>) -> (String, FeatureResult); pub type FeatureGenerator = fn(Arc<Image<f32>>) -> (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)] #[derive(Default)]
pub struct Database { pub struct Database {
images: IndexedImages, images: IndexedImages,
@ -200,6 +261,9 @@ pub struct Database {
} }
impl 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)> { pub fn search(&self, imagepath: &Path, feature: FeatureGenerator) -> Vec<(PathBuf, f32)> {
self.images.search(imagepath, feature) self.images.search(imagepath, feature)
} }
@ -215,7 +279,7 @@ impl Database {
} }
/// with add_image you can add images in a existing 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) { pub fn add_image(&mut self, path: &Path) {
if !self.generators.is_empty() { if !self.generators.is_empty() {
self.images 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 { pub fn from_file(path: &Path) -> Self {
let filestring = fs::read_to_string(path).expect("can't read that file"); let filestring = fs::read_to_string(path).expect("can't read that file");
let images = serde_json::from_str::<IndexedImages>(&filestring) let images = serde_json::from_str::<IndexedImages>(&filestring)
@ -237,12 +302,15 @@ impl Database {
} }
} }
///IndexedImages stores the images of the Database and is serializable
#[derive(Serialize, Deserialize, Default, PartialEq, Debug)] #[derive(Serialize, Deserialize, Default, PartialEq, Debug)]
struct IndexedImages { struct IndexedImages {
images: HashMap<PathBuf, HashMap<String, FeatureResult>>, images: HashMap<PathBuf, HashMap<String, FeatureResult>>,
} }
impl IndexedImages { impl IndexedImages {
///the new function generates all images and generates every feature so it can store these.
fn new( fn new(
imagepaths: &Vec<PathBuf>, imagepaths: &Vec<PathBuf>,
features: &[FeatureGenerator], 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)> { fn search(&self, imagepath: &Path, feature: FeatureGenerator) -> Vec<(PathBuf, f32)> {
let image: Arc<Image<f32>> = Arc::new(Image::default()); //todo!("Image reader function") let image: Arc<Image<f32>> = Arc::new(Image::default()); //todo!("Image reader function")
let search_feat = feature(image); let search_feat = feature(image);
@ -286,6 +357,7 @@ impl IndexedImages {
result result
} }
///this function lets you add images to the Indexed Image struct
fn add_image( fn add_image(
&mut self, &mut self,
path: &Path, path: &Path,
@ -317,9 +389,9 @@ fn average_luminance(image: Arc<Image<f32>>) -> (String, FeatureResult) {
mod tests { mod tests {
use super::*; use super::*;
///this function tests the Serialization of the Database
#[test] #[test]
fn conversion() { fn conversion() {
let mut images: HashMap<PathBuf, HashMap<String, FeatureResult>> = HashMap::new(); let mut images: HashMap<PathBuf, HashMap<String, FeatureResult>> = HashMap::new();
let mut feat: HashMap<String, FeatureResult> = HashMap::new(); let mut feat: HashMap<String, FeatureResult> = HashMap::new();
feat.insert(String::from("average-brightness"), FeatureResult::F32(0.0)); feat.insert(String::from("average-brightness"), FeatureResult::F32(0.0));
@ -328,58 +400,60 @@ mod tests {
let _as_json = serde_json::to_string(&data).expect("couldnt convert"); let _as_json = serde_json::to_string(&data).expect("couldnt convert");
println!("{:?}", _as_json); println!("{:?}", _as_json);
let data_after_conversion = serde_json::from_str::<IndexedImages>(&_as_json).expect("couldnt convert from string"); let data_after_conversion =
serde_json::from_str::<IndexedImages>(&_as_json).expect("couldnt convert from string");
assert_eq!(data, data_after_conversion); assert_eq!(data, data_after_conversion);
} }
///this function tests Edgecases for the cosine_similarity in the weightet function
#[test] #[test]
fn cosine_similarity(){ fn cosine_similarity() {
let vec1 = FeatureResult::Indices(vec!{1, 3, 4}); let vec1 = FeatureResult::Indices(vec![1, 3, 4]);
let vec2 = FeatureResult::Indices(vec!{1, 3, 4}); let vec2 = FeatureResult::Indices(vec![1, 3, 4]);
assert_eq!(1., vec1.weighted(&vec2)); // both are identical 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 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., vec1.weighted(&vec2)); // both are 0
assert_eq!(1., vec2.weighted(&vec1)); // it shouldn't change if the Values are switched assert_eq!(1., vec2.weighted(&vec1)); // it shouldn't change if the Values are switched
let vec1 = FeatureResult::Indices(vec!{7, 3, 4}); let vec1 = FeatureResult::Indices(vec![7, 3, 4]);
let vec2 = FeatureResult::Indices(vec!{1, 5, 2}); let vec2 = FeatureResult::Indices(vec![1, 5, 2]);
assert_eq!(vec1.weighted(&vec2), vec2.weighted(&vec1)); assert_eq!(vec1.weighted(&vec2), vec2.weighted(&vec1));
println!("{:?}", vec1.weighted(&vec2)); println!("{:?}", vec1.weighted(&vec2));
let mut vec1 = vec![5; 9999];
let mut vec1 = vec![5;9999]; vec1.push(1);
vec1.push( 1);
let vec1 = FeatureResult::Indices(vec1); let vec1 = FeatureResult::Indices(vec1);
let vec2 = FeatureResult::Indices(vec!{7;10000}); let vec2 = FeatureResult::Indices(vec![7; 10000]);
println!("{:?}", vec1.weighted(&vec2)); println!("{:?}", vec1.weighted(&vec2));
} }
///this function tests all of the weighted function
#[test] #[test]
fn weighted() { fn weighted() {
let vec1 = FeatureResult::Vec(vec![
let vec1 = FeatureResult::Vec(vec![FeatureResult::Bool(true), FeatureResult::Bool(true),
FeatureResult::Char('c'), FeatureResult::Char('c'),
FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]), FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]),
FeatureResult::F32(44.543) ]); FeatureResult::F32(44.543),
]);
let vec2 = FeatureResult::Vec(vec![FeatureResult::Bool(true), let vec2 = FeatureResult::Vec(vec![
FeatureResult::Bool(true),
FeatureResult::Char('c'), FeatureResult::Char('c'),
FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]), FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]),
FeatureResult::F32(44.543) ]); FeatureResult::F32(44.543),
]);
assert_eq!(1., vec2.weighted(&vec1)); assert_eq!(1., vec2.weighted(&vec1));
let vec2 = FeatureResult::Vec(vec![
let vec2 = FeatureResult::Vec(vec![FeatureResult::Bool(true), FeatureResult::Bool(true),
FeatureResult::Char('c'), FeatureResult::Char('c'),
FeatureResult::F32(44.543) , FeatureResult::F32(44.543),
FeatureResult::Vec(vec![FeatureResult::Percent(0.5)])]); FeatureResult::Vec(vec![FeatureResult::Percent(0.5)]),
]);
assert_eq!(0.5, vec2.weighted(&vec1)); assert_eq!(0.5, vec2.weighted(&vec1));
println!("{:?}", vec1.weighted(&vec2)); println!("{:?}", vec1.weighted(&vec2));
@ -395,43 +469,35 @@ mod tests {
let value2 = FeatureResult::String(String::from("notTesting")); let value2 = FeatureResult::String(String::from("notTesting"));
assert_eq!(0., value1.weighted(&value2)); assert_eq!(0., value1.weighted(&value2));
let value2 = FeatureResult::String(String::from("Testing")); 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] #[test]
fn weighted_rgba() { fn weighted_rgba() {
let value1 = FeatureResult::Rgba(32.6754,42.432,43.87,255.); let value1 = FeatureResult::Rgba(32.6754, 42.432, 43.87, 255.);
let value2 = 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)) ; assert_eq!(1., value1.weighted(&value2));
let value1 = FeatureResult::Rgba(255.,255.,0.,255.); let value1 = FeatureResult::Rgba(255., 255., 0., 255.);
let value2 = FeatureResult::Rgba(0.,0.,0.,255.); let value2 = FeatureResult::Rgba(0., 0., 0., 255.);
//assert_eq!(1., value1.weighted(&value2)) ; //assert_eq!(1., value1.weighted(&value2)) ;
println!("Yellow to Black: {:?}", value1.weighted(&value2)); println!("Yellow to Black: {:?}", value1.weighted(&value2));
let value1 = FeatureResult::Rgba(255.,255.,0.,255.); let value1 = FeatureResult::Rgba(255., 255., 0., 255.);
let value2 = FeatureResult::Rgba(200.,255.,55.,255.); let value2 = FeatureResult::Rgba(200., 255., 55., 255.);
//assert_eq!(1., value1.weighted(&value2)) ; //assert_eq!(1., value1.weighted(&value2)) ;
println!("yellow to light green: {:?}", value1.weighted(&value2)); println!("yellow to light green: {:?}", value1.weighted(&value2));
let value1 = FeatureResult::Rgba(3.,8.,255.,255.); let value1 = FeatureResult::Rgba(3., 8., 255., 255.);
let value2 = FeatureResult::Rgba(3.,106.,255.,255.); let value2 = FeatureResult::Rgba(3., 106., 255., 255.);
//assert_eq!(1., value1.weighted(&value2)) ; //assert_eq!(1., value1.weighted(&value2)) ;
println!("blue to dark blue: {:?}", value1.weighted(&value2)); println!("blue to dark blue: {:?}", value1.weighted(&value2));
let value1 = FeatureResult::Rgba(255.,106.,122.,255.); let value1 = FeatureResult::Rgba(255., 106., 122., 255.);
let value2 = FeatureResult::Rgba(255.,1.,28.,255.); let value2 = FeatureResult::Rgba(255., 1., 28., 255.);
//assert_eq!(1., value1.weighted(&value2)) ; //assert_eq!(1., value1.weighted(&value2)) ;
println!("Red to light red: {:?}", value1.weighted(&value2)); println!("Red to light red: {:?}", value1.weighted(&value2));
} }
} }