This commit is contained in:
@@ -23,5 +23,5 @@ fn main() {
|
||||
debug!("Phash integer:\n{phash}");
|
||||
debug!("Phash binary:\n{phash:064b}");
|
||||
let output = URL_SAFE_NO_PAD.encode(phash.to_be_bytes());
|
||||
println!("{output}")
|
||||
println!("{output}");
|
||||
}
|
||||
|
||||
+2
-8
@@ -24,17 +24,11 @@ fn main() {
|
||||
|
||||
let img = image::open(cfg.path)
|
||||
.expect("Unable to open file");
|
||||
// let conf = viuer::Config {
|
||||
// width: Some(16),
|
||||
// height: Some(8),
|
||||
// ..Default::default()
|
||||
// };
|
||||
// viuer::print(&img, &conf).expect("Image printing failed.");
|
||||
|
||||
let phash: u64 = desc.describe(&img);
|
||||
let store =
|
||||
let _store =
|
||||
DescriptorStore::new(Box::new(desc))
|
||||
.with_file("dct50.messagepack");
|
||||
info!("Phash integer:\n{phash}");
|
||||
info!("Phash binary:\n{phash:064b}");
|
||||
store.print_nn(phash, cfg.distance, cfg.show_images);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use image_similarity::descriptors::{Descriptor, DCT};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let desc = DCT::new();
|
||||
|
||||
//Initialize hashmap, empty or from cached file:
|
||||
let count = fs::read_dir("img").unwrap().count();
|
||||
let map_file = fs::read("map.messagepack");
|
||||
let mut map: HashMap<String, u64> = match map_file {
|
||||
Ok(f) => rmp_serde::from_slice(&f).unwrap(),
|
||||
Err(_e) => HashMap::with_capacity(count),
|
||||
};
|
||||
|
||||
//Calculate phashes for all images
|
||||
for node in fs::read_dir("img").unwrap() {
|
||||
let file = node.expect("Error walking directory");
|
||||
let name = file.file_name().into_string().expect("Issue with filename");
|
||||
if !map.contains_key(&name) {
|
||||
let img = image::open(file.path()).expect("Unable to open file");
|
||||
let phash = desc.describe(&img);
|
||||
map.insert(name, phash);
|
||||
}
|
||||
}
|
||||
|
||||
for (key, val) in map.iter() {
|
||||
println!("{key: >15}: {val}\t {val:b}");
|
||||
}
|
||||
|
||||
//Serialize hashmap with MessagePack:
|
||||
let _serialized: Vec<u8> = rmp_serde::to_vec(&map).unwrap();
|
||||
//fs::write("map.messagepack",&serialized).unwrap();
|
||||
}
|
||||
+3
-11
@@ -16,23 +16,15 @@ fn main() {
|
||||
|
||||
let cfg = Cfg::parse();
|
||||
|
||||
let img = image::open(&cfg.path)
|
||||
let img = image::open(cfg.path)
|
||||
.expect("Unable to open file");
|
||||
|
||||
let mutators = get_all_mutators();
|
||||
|
||||
let conf = viuer::Config {
|
||||
width: Some(16),
|
||||
height: Some(8),
|
||||
..Default::default()
|
||||
};
|
||||
for mutator in mutators {
|
||||
let mutated = mutator.mutate(&img);
|
||||
let filename = "mut".to_string() + &mutator.tag() + &"png".to_string();
|
||||
println!("Saving to {}", filename);
|
||||
let filename = "mut".to_string() + &mutator.tag() + "png";
|
||||
println!("Saving to {filename}");
|
||||
mutated.save(filename).expect("Saving image failed");
|
||||
if cfg.show_images {
|
||||
viuer::print(&mutated, &conf).expect("Image printing failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-16
@@ -29,10 +29,11 @@ impl DCT {
|
||||
50..=100 => 200.0 - 2.0*quality as f32,
|
||||
_ => 100.0 // Invalid input: set to base quality
|
||||
};
|
||||
for i in 0..64 {
|
||||
quantization_matrix[i] = ((scalar * self.quantization_matrix[i] as f32 + 50.0) / 100.0).floor() as u8;
|
||||
if quantization_matrix[i] == 0 {
|
||||
quantization_matrix[i] = 1;
|
||||
//for i in 0..64 {
|
||||
for (_, cell) in quantization_matrix.iter_mut().enumerate() {
|
||||
*cell = ((scalar * *cell as f32 + 50.0) / 100.0).floor() as u8;
|
||||
if *cell == 0 {
|
||||
*cell = 1;
|
||||
}
|
||||
}
|
||||
self.quantization_matrix = quantization_matrix;
|
||||
@@ -40,17 +41,17 @@ impl DCT {
|
||||
}
|
||||
|
||||
///
|
||||
fn dct(&self, img: &image::DynamicImage) -> [f64; 64] {
|
||||
pub fn dct(&self, img: &image::DynamicImage) -> [f64; 64] {
|
||||
let mut dct_values: [f64; 64] = [0.0; 64];
|
||||
for u in 0..8 {
|
||||
for v in 0..8 {
|
||||
let k = (v*8)+u;
|
||||
let mut alpha = 0.25;
|
||||
if u == 0 {
|
||||
alpha = alpha / SQRT_2
|
||||
alpha /= SQRT_2
|
||||
}
|
||||
if v == 0 {
|
||||
alpha = alpha / SQRT_2
|
||||
alpha /= SQRT_2
|
||||
}
|
||||
let v: f64 = v as f64;
|
||||
let u: f64 = u as f64;
|
||||
@@ -72,7 +73,8 @@ impl DCT {
|
||||
|
||||
fn idct(&self, dct_values: [f64; 64]) -> [u8; 64] {
|
||||
let mut reconstructed: [u8; 64] = [0; 64];
|
||||
for k in 0..64 {
|
||||
//for k in 0..64 {
|
||||
for (k, item) in reconstructed.iter_mut().enumerate() {
|
||||
let x = (k%8) as f64;
|
||||
let y = (k/8) as f64;
|
||||
let mut sum = 0.0;
|
||||
@@ -80,10 +82,10 @@ impl DCT {
|
||||
for v in 0..8 {
|
||||
let mut alpha = 1.0;
|
||||
if u == 0 {
|
||||
alpha = alpha / SQRT_2
|
||||
alpha /= SQRT_2
|
||||
}
|
||||
if v == 0 {
|
||||
alpha = alpha / SQRT_2
|
||||
alpha /= SQRT_2
|
||||
}
|
||||
let uv = (v*8)+u;
|
||||
let v = v as f64;
|
||||
@@ -96,12 +98,18 @@ impl DCT {
|
||||
}
|
||||
}
|
||||
sum = 127.0 + (0.25 * sum).round();
|
||||
reconstructed[k] = std::cmp::min(255_u8, sum as u8);
|
||||
*item = std::cmp::min(255_u8, sum as u8);
|
||||
}
|
||||
reconstructed
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DCT {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for DCT {
|
||||
fn info(&self) -> String {
|
||||
"dct".to_string()
|
||||
@@ -124,7 +132,7 @@ impl Descriptor for DCT {
|
||||
if log_enabled!(Level::Debug) {
|
||||
resized.save("resize.png").expect("Error saving file");
|
||||
// De-quantization:
|
||||
let mut dequant = dct_values.clone();
|
||||
let mut dequant = dct_values;
|
||||
for i in 0..64 {
|
||||
dequant[i] = dct_values[i] * self.quantization_matrix[i] as f64;
|
||||
}
|
||||
@@ -189,8 +197,8 @@ impl Descriptor for DCT {
|
||||
}
|
||||
|
||||
// Shift masks
|
||||
sign_mask = sign_mask << 1;
|
||||
pearson_mask = pearson_mask << 1;
|
||||
sign_mask <<= 1;
|
||||
pearson_mask <<= 1;
|
||||
debug!("Sign mask: {:028b}", sign_mask);
|
||||
debug!("Pearson mask: {:028b}", pearson_mask);
|
||||
}
|
||||
@@ -199,11 +207,11 @@ impl Descriptor for DCT {
|
||||
|
||||
let mut mask = sign_mask;
|
||||
debug!("Mask: {:064b}", mask);
|
||||
mask = mask << 28;
|
||||
mask <<= 28;
|
||||
debug!("Mask: {:064b}", mask);
|
||||
mask += pearson_mask;
|
||||
debug!("Mask: {:064b}", mask);
|
||||
mask = mask << 8;
|
||||
mask <<= 8;
|
||||
debug!("Mask: {:064b}", mask);
|
||||
// TODO: Do something with these last 8 bits.
|
||||
mask
|
||||
|
||||
@@ -55,10 +55,8 @@ impl Descriptor for Median {
|
||||
fn describe(&self, img: &image::DynamicImage) -> u64 {
|
||||
let img = self.resize(img);
|
||||
let mut values: [u8; 64] = [0; 64];
|
||||
let mut i: usize = 0;
|
||||
for (_, _, pix) in img.pixels() {
|
||||
for (i, (_, _, pix)) in img.pixels().enumerate() {
|
||||
values[i] = pix[0];
|
||||
i = i+1;
|
||||
}
|
||||
let median = median64(&values);
|
||||
let mut mask: u64 = 0;
|
||||
@@ -67,15 +65,16 @@ impl Descriptor for Median {
|
||||
if pix[0] > median {
|
||||
mask += 1;
|
||||
}
|
||||
mask = mask << 1;
|
||||
mask <<= 1;
|
||||
}
|
||||
mask
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_all_descriptors() -> Vec<Box<dyn Descriptor>> {
|
||||
let mut descriptors: Vec<Box<dyn Descriptor>> = Vec::with_capacity(2);
|
||||
descriptors.push(Box::new(DCT::new()));
|
||||
descriptors.push(Box::new(Median));
|
||||
let descriptors: Vec<Box<dyn Descriptor>> =
|
||||
vec![Box::new(DCT::new()), Box::new(Median)];
|
||||
// descriptors.push(Box::new(DCT::new()));
|
||||
// descriptors.push(Box::new(Median));
|
||||
descriptors
|
||||
}
|
||||
+2
-1
@@ -4,4 +4,5 @@
|
||||
//! If two images are (almost) the same, their descriptions will be the same.
|
||||
pub mod descriptors;
|
||||
pub mod store;
|
||||
pub mod mutators;
|
||||
pub mod mutators;
|
||||
pub mod wasm;
|
||||
+6
-14
@@ -28,7 +28,6 @@ fn main() {
|
||||
}
|
||||
|
||||
for node in fs::read_dir(cfg.path).unwrap() {
|
||||
//println!("{:?}", node);
|
||||
let file = node.expect("Error walking directory");
|
||||
let name = match file.file_name().into_string() {
|
||||
Ok(v) => v,
|
||||
@@ -54,7 +53,7 @@ fn main() {
|
||||
error!("Failed to process {}: {}", name, e);
|
||||
continue
|
||||
}
|
||||
};
|
||||
}.thumbnail_exact(8, 8);
|
||||
|
||||
//Store the phashes of the base image
|
||||
for store in &mut stores {
|
||||
@@ -72,7 +71,7 @@ fn main() {
|
||||
}
|
||||
for store in &stores {
|
||||
store.save().expect("Error saving store");
|
||||
println!("{}", store);
|
||||
println!("{store}");
|
||||
}
|
||||
|
||||
// Create PR-curve graphs from stores
|
||||
@@ -96,18 +95,17 @@ fn main() {
|
||||
.disable_mesh()
|
||||
.x_desc("Recall")
|
||||
.y_desc("Precision")
|
||||
.x_label_formatter(&|x| format!("{:.3}", x))
|
||||
.y_label_formatter(&|x| format!("{:.3}", x))
|
||||
.x_label_formatter(&|x| format!("{x:.3}"))
|
||||
.y_label_formatter(&|x| format!("{x:.3}"))
|
||||
.draw().unwrap();
|
||||
let colors = [&RED, &BLUE, &CYAN, &MAGENTA, &BLACK, &GREEN, &YELLOW];
|
||||
let n = colors.len();
|
||||
let mut i = 0;
|
||||
for mutator_stats in stats {
|
||||
for (i, mutator_stats) in stats.into_iter().enumerate() {
|
||||
// And we can draw something in the drawing area
|
||||
let pr_curve = mutator_stats.pr_curve(max_threshold);
|
||||
println!("{}:", mutator_stats.name);
|
||||
for (x, y) in pr_curve.clone() {
|
||||
println!("{}, {}", x, y);
|
||||
println!("{x}, {y}");
|
||||
}
|
||||
let color = colors[i % n];
|
||||
chart.draw_series(LineSeries::new(
|
||||
@@ -116,17 +114,11 @@ fn main() {
|
||||
).point_size(2)).unwrap()
|
||||
.label(mutator_stats.name)
|
||||
.legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], color));
|
||||
i += 1;
|
||||
}
|
||||
//chart.configure_series_labels().border_style(BLACK).draw().unwrap();
|
||||
chart.configure_series_labels()
|
||||
.position(SeriesLabelPosition::MiddleLeft)
|
||||
//.legend_area_size(5)
|
||||
.border_style(BLACK)
|
||||
//.background_style(BLUE.mix(0.1))
|
||||
//.label_font(("Calibri", 20))
|
||||
.draw()
|
||||
.unwrap();
|
||||
//println!("{}", store);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -72,10 +72,12 @@ impl Mutator for Blur {
|
||||
}
|
||||
|
||||
pub fn get_all_mutators() -> Vec<Box<dyn Mutator>> {
|
||||
let mut mutators: Vec<Box<dyn Mutator>> = Vec::with_capacity(4);
|
||||
mutators.push(Box::new(Flip));
|
||||
mutators.push(Box::new(Hue));
|
||||
mutators.push(Box::new(Sharp));
|
||||
mutators.push(Box::new(Blur));
|
||||
let mutators: Vec<Box<dyn Mutator>> = //Vec::with_capacity(4);
|
||||
vec![
|
||||
Box::new(Flip),
|
||||
Box::new(Hue),
|
||||
Box::new(Sharp),
|
||||
Box::new(Blur),
|
||||
];
|
||||
mutators
|
||||
}
|
||||
+30
-71
@@ -34,8 +34,7 @@ impl PRStats {
|
||||
};
|
||||
let tpos = self.true_positives[t] as f64;
|
||||
let fpos = self.false_positives[t] as f64;
|
||||
let p = tpos / (tpos + fpos);
|
||||
p
|
||||
tpos / (tpos + fpos)
|
||||
}
|
||||
pub fn recall(&self, threshold: usize) -> f64 {
|
||||
let t = match threshold {
|
||||
@@ -44,8 +43,7 @@ impl PRStats {
|
||||
};
|
||||
let tpos = self.true_positives[t] as f64;
|
||||
let fneg = self.false_negatives[t] as f64;
|
||||
let r = tpos / (tpos + fneg);
|
||||
r
|
||||
tpos / (tpos + fneg)
|
||||
}
|
||||
pub fn pr(&self, threshold: usize) -> (f64, f64) {
|
||||
let t = match threshold {
|
||||
@@ -87,7 +85,7 @@ pub struct DescriptorStore {
|
||||
}
|
||||
|
||||
impl DescriptorStore {
|
||||
/// Makes a new empty DescriptorStore with default settings
|
||||
/// Makes a new empty `DescriptorStore` with default settings
|
||||
pub fn new(descriptor: Box<dyn Descriptor>) -> Self {
|
||||
let map: HashMap<u64, Vec<String>> = HashMap::new();
|
||||
//let seen: HashSet<String> = HashSet::new();
|
||||
@@ -105,23 +103,22 @@ impl DescriptorStore {
|
||||
pub fn with_file<P: AsRef<Path>>(mut self, path: P) -> Self {
|
||||
self.save_location = std::path::PathBuf::from(path.as_ref());
|
||||
let map_file = fs::read(&self.save_location);
|
||||
match map_file {
|
||||
Ok(f) => {
|
||||
self.map = rmp_serde::from_slice(&f).unwrap();
|
||||
for i in self.map.keys() {
|
||||
self.bktree.insert(*i);
|
||||
}
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
for bucket in self.map.values() {
|
||||
for element in bucket {
|
||||
if !element.starts_with("mut.") {
|
||||
seen.insert(element.clone());
|
||||
}
|
||||
if let Ok(f) = map_file {
|
||||
self.map = rmp_serde::from_slice(&f).unwrap();
|
||||
for i in self.map.keys() {
|
||||
self.bktree.insert(*i);
|
||||
}
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
for bucket in self.map.values() {
|
||||
for element in bucket {
|
||||
if !element.starts_with("mut.") {
|
||||
seen.insert(element.clone());
|
||||
}
|
||||
}
|
||||
self.seen = Some(seen);
|
||||
},
|
||||
Err(_) => info!("{} not found, starting from empty store.", self.save_location.display()),
|
||||
}
|
||||
self.seen = Some(seen);
|
||||
} else {
|
||||
info!("{} not found, starting from empty store.", self.save_location.display());
|
||||
};
|
||||
self
|
||||
}
|
||||
@@ -147,8 +144,8 @@ impl DescriptorStore {
|
||||
Ok(value) => value,
|
||||
Err(_e) => return Err(SaveError::Serialization),
|
||||
};
|
||||
match fs::write(&self.save_location, &serialized) {
|
||||
Ok(_) => Ok(()),
|
||||
match fs::write(&self.save_location, serialized) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_e) => Err(SaveError::File),
|
||||
}
|
||||
}
|
||||
@@ -167,7 +164,7 @@ impl DescriptorStore {
|
||||
},
|
||||
None => {
|
||||
for bucket in self.map.values() {
|
||||
if bucket.contains(&value) {
|
||||
if bucket.contains(value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -178,7 +175,7 @@ impl DescriptorStore {
|
||||
|
||||
pub fn get(&self, value: &String) -> Option<u64> {
|
||||
for (key, bucket) in self.map.iter() {
|
||||
if bucket.contains(&value) {
|
||||
if bucket.contains(value) {
|
||||
return Some(*key);
|
||||
}
|
||||
}
|
||||
@@ -187,7 +184,7 @@ impl DescriptorStore {
|
||||
|
||||
/// Inserts a single value into the store
|
||||
pub fn insert(&mut self, key: &u64, value: String) {
|
||||
let bucket = match self.map.get(&key) {
|
||||
let bucket = match self.map.get(key) {
|
||||
Some(b) => {
|
||||
let mut n = b.clone();
|
||||
if !n.contains(&value) {
|
||||
@@ -213,7 +210,9 @@ impl DescriptorStore {
|
||||
continue
|
||||
}
|
||||
};
|
||||
if !self.has_value(&name) { // !self.contains(name.to_string()) {
|
||||
if !self.has_value(&name) {
|
||||
debug!("{} already known, skipping.", name);
|
||||
} else {
|
||||
info!("Processing {}", name);
|
||||
let img = match image::open(file.path()) {
|
||||
Ok(v) => v,
|
||||
@@ -227,8 +226,6 @@ impl DescriptorStore {
|
||||
println!("{} duplicate of {:?}", name, self.map.get(&phash));
|
||||
}
|
||||
self.insert(&phash, name);
|
||||
} else {
|
||||
debug!("{} already known, skipping.", name);
|
||||
}
|
||||
self.save().expect("error");
|
||||
}
|
||||
@@ -238,15 +235,15 @@ impl DescriptorStore {
|
||||
|
||||
pub fn store(&mut self, img: &DynamicImage, name: String) {
|
||||
if !self.has_value(&name) {
|
||||
let phash = self.descriptor.describe(&img);
|
||||
let phash = self.descriptor.describe(img);
|
||||
self.insert(&phash, name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Nearest neighbours
|
||||
pub fn nn(&self, from: u64, max_distance: usize) -> Vec<(&u64, isize)> {
|
||||
let mut neighbours = self.bktree.find(from, max_distance.try_into().unwrap());
|
||||
neighbours.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
let neighbours = self.bktree.find(from, max_distance.try_into().unwrap());
|
||||
//neighbours.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
neighbours
|
||||
}
|
||||
|
||||
@@ -263,44 +260,6 @@ impl DescriptorStore {
|
||||
results
|
||||
}
|
||||
|
||||
pub fn print_nn(&self, from: u64, max_distance: usize, show_images: bool) {
|
||||
//println!("{from:064b}");
|
||||
let neighbours = self.nn(from, max_distance);
|
||||
let (term_width, _) = viuer::terminal_size();
|
||||
let mut x = 0;
|
||||
let mut y = 8;
|
||||
for (element, distance) in neighbours {
|
||||
let elements = self.map.get(element);
|
||||
match elements {
|
||||
Some(paths) => {
|
||||
for path in paths {
|
||||
println!("{element:064b}: {:?} (distance: {distance})", path);
|
||||
if show_images {
|
||||
if x+16 >= term_width {
|
||||
x = 0;
|
||||
y += 8;
|
||||
}
|
||||
let conf = viuer::Config {
|
||||
width: Some(16),
|
||||
height: Some(8),
|
||||
x,
|
||||
y,
|
||||
use_kitty: false,
|
||||
..Default::default()
|
||||
};
|
||||
x += 16;
|
||||
let path = "data/".to_string() + path;
|
||||
let img = image::open(&path).unwrap();
|
||||
let img = img.grayscale().thumbnail_exact(8, 8);
|
||||
viuer::print(&img, &conf).expect("Image printing failed.");
|
||||
}
|
||||
}
|
||||
},
|
||||
None => ()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_most_dups(&self) {
|
||||
let mut most = 0;
|
||||
for bucket in self.map.values() {
|
||||
@@ -319,7 +278,7 @@ impl DescriptorStore {
|
||||
let value_entry = format!("{value}\n");
|
||||
output.push_str(&value_entry);
|
||||
}
|
||||
output.push_str("\n");
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
print!("DescriptorStore:\n{}Total: {}", output, self.map.len())
|
||||
@@ -415,7 +374,7 @@ impl fmt::Display for DescriptorStore {
|
||||
let value_entry = format!("\t{value}\n");
|
||||
output.push_str(&value_entry);
|
||||
}
|
||||
output.push_str("\n");
|
||||
output.push('\n');
|
||||
}
|
||||
write!(f, "DescriptorStore:\n{}Total: {}", output, self.map.len())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user