added Documentary
This commit is contained in:
parent
5b5e8a48f5
commit
6b140e0978
|
@ -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::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;
|
||||
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
|
||||
|
||||
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>) -> [f32; 3] {
|
||||
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>) -> [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>) -> [f32; 3] {
|
|||
[l, a, b]
|
||||
}
|
||||
|
||||
|
||||
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)]
|
||||
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::<IndexedImages>(&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<PathBuf, HashMap<String, FeatureResult>>,
|
||||
}
|
||||
|
||||
impl IndexedImages {
|
||||
///the new function generates all images and generates every feature so it can store these.
|
||||
|
||||
fn new(
|
||||
imagepaths: &Vec<PathBuf>,
|
||||
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<Image<f32>> = 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,9 +389,9 @@ fn average_luminance(image: Arc<Image<f32>>) -> (String, FeatureResult) {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
///this function tests the Serialization of the Database
|
||||
#[test]
|
||||
fn conversion() {
|
||||
|
||||
let mut images: HashMap<PathBuf, HashMap<String, FeatureResult>> = HashMap::new();
|
||||
let mut feat: HashMap<String, FeatureResult> = HashMap::new();
|
||||
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");
|
||||
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);
|
||||
}
|
||||
|
||||
///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 mut vec1 = vec![5; 9999];
|
||||
vec1.push(1);
|
||||
let vec1 = FeatureResult::Indices(vec1);
|
||||
let vec2 = FeatureResult::Indices(vec!{7;10000});
|
||||
let vec2 = FeatureResult::Indices(vec![7; 10000]);
|
||||
println!("{:?}", vec1.weighted(&vec2));
|
||||
|
||||
|
||||
|
||||
}
|
||||
///this function tests all of the weighted function
|
||||
#[test]
|
||||
fn weighted() {
|
||||
|
||||
let vec1 = FeatureResult::Vec(vec![FeatureResult::Bool(true),
|
||||
let vec1 = FeatureResult::Vec(vec![
|
||||
FeatureResult::Bool(true),
|
||||
FeatureResult::Char('c'),
|
||||
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::Vec(vec![FeatureResult::Percent(0.5)]),
|
||||
FeatureResult::F32(44.543) ]);
|
||||
FeatureResult::F32(44.543),
|
||||
]);
|
||||
assert_eq!(1., vec2.weighted(&vec1));
|
||||
|
||||
|
||||
let vec2 = FeatureResult::Vec(vec![FeatureResult::Bool(true),
|
||||
let vec2 = FeatureResult::Vec(vec![
|
||||
FeatureResult::Bool(true),
|
||||
FeatureResult::Char('c'),
|
||||
FeatureResult::F32(44.543) ,
|
||||
FeatureResult::Vec(vec![FeatureResult::Percent(0.5)])]);
|
||||
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));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue