Fixes compilation, horizontal flip for DCT. Adds PR calculation.
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
DCT is now somewhat invariant to horizontal flips. First version of precision recall variable is calculated.
This commit is contained in:
@@ -15,7 +15,7 @@ fn main() {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
let cfg = Cfg::parse();
|
let cfg = Cfg::parse();
|
||||||
|
|
||||||
let desc = DCT::new().with_quality(50);
|
let desc = DCT::new().with_quality(95);
|
||||||
|
|
||||||
let img = image::open(cfg.path)
|
let img = image::open(cfg.path)
|
||||||
.expect("Unable to open file");
|
.expect("Unable to open file");
|
||||||
|
|||||||
+2
-2
@@ -18,7 +18,7 @@ struct Cfg {
|
|||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let desc = DCT::new().with_quality(50);
|
let desc = DCT::new().with_quality(95);
|
||||||
|
|
||||||
let cfg = Cfg::parse();
|
let cfg = Cfg::parse();
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ fn main() {
|
|||||||
// viuer::print(&img, &conf).expect("Image printing failed.");
|
// viuer::print(&img, &conf).expect("Image printing failed.");
|
||||||
let phash: u64 = desc.describe(&img);
|
let phash: u64 = desc.describe(&img);
|
||||||
let store =
|
let store =
|
||||||
DescriptorStore::new(desc)
|
DescriptorStore::new(Box::new(desc))
|
||||||
.with_file("dct50.messagepack");
|
.with_file("dct50.messagepack");
|
||||||
info!("Phash integer:\n{phash}");
|
info!("Phash integer:\n{phash}");
|
||||||
info!("Phash binary:\n{phash:064b}");
|
info!("Phash binary:\n{phash:064b}");
|
||||||
|
|||||||
+21
-12
@@ -113,21 +113,25 @@ impl Descriptor for DCT {
|
|||||||
|
|
||||||
// Quantization:
|
// Quantization:
|
||||||
// debug!("Using quantization matrix:\n{}", print_matrix(self.quantization_matrix));
|
// debug!("Using quantization matrix:\n{}", print_matrix(self.quantization_matrix));
|
||||||
for i in 0..64 {
|
// for i in 0..64 {
|
||||||
dct_values[i] = (dct_values[i] / self.quantization_matrix[i] as f64).round();
|
// dct_values[i] = (dct_values[i] / self.quantization_matrix[i] as f64).round();
|
||||||
}
|
// if dct_values[i] == -0.0 && i % 2 != 0 {
|
||||||
debug!("DCT-coefficients, quantized:\n {}", print_matrix(dct_values));
|
// dct_values[i] = 0.0;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// debug!("DCT-coefficients, quantized:\n {}", print_matrix(dct_values));
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
if log_enabled!(Level::Debug) {
|
||||||
resized.save("resize.png").expect("Error saving file");
|
resized.save("resize.png").expect("Error saving file");
|
||||||
// De-quantization:
|
// De-quantization:
|
||||||
|
let mut dequant = dct_values.clone();
|
||||||
for i in 0..64 {
|
for i in 0..64 {
|
||||||
dct_values[i] = dct_values[i] * self.quantization_matrix[i] as f64;
|
dequant[i] = dct_values[i] * self.quantization_matrix[i] as f64;
|
||||||
}
|
}
|
||||||
//debug!("DCT-coefficients, de-quantized:\n {}", print_matrix(dct_values));
|
//debug!("DCT-coefficients, de-quantized:\n {}", print_matrix(dct_values));
|
||||||
|
|
||||||
// Reconstruction original pixel values:
|
// Reconstruction original pixel values:
|
||||||
let reconstructed = self.idct(dct_values);
|
let reconstructed = self.idct(dequant);
|
||||||
|
|
||||||
save_buffer(
|
save_buffer(
|
||||||
"reconstructed.png",
|
"reconstructed.png",
|
||||||
@@ -158,24 +162,27 @@ impl Descriptor for DCT {
|
|||||||
// If first horizontal AC coefficient is negative
|
// If first horizontal AC coefficient is negative
|
||||||
// This might account for horizontal flips when applied to all horizontal coefficients.
|
// This might account for horizontal flips when applied to all horizontal coefficients.
|
||||||
let sign_mult = dct_values[1].signum();
|
let sign_mult = dct_values[1].signum();
|
||||||
|
//debug!("Sign multiplier of first horizontal component: {}", sign_mult);
|
||||||
|
|
||||||
// Mask that indicates if a coefficient is bigger or smaller than previous in order
|
// Mask that indicates if a coefficient is bigger or smaller than previous in order
|
||||||
let mut pearson_mask: u64 = 0;
|
let mut pearson_mask: u64 = 0;
|
||||||
let mut prev = dct_values[0];
|
let mut prev = dct_values[49].abs();
|
||||||
|
|
||||||
|
|
||||||
for i in zigzag {
|
for i in zigzag {
|
||||||
let cur = dct_values[i];
|
let cur = dct_values[i].abs();
|
||||||
if cur > prev {
|
if cur > prev {
|
||||||
pearson_mask += 1;
|
pearson_mask += 1;
|
||||||
}
|
}
|
||||||
prev = cur;
|
prev = cur;
|
||||||
|
|
||||||
let signum = dct_values[i].signum();
|
let mut signum = dct_values[i].signum();
|
||||||
|
if i % 2 != 0 {
|
||||||
|
signum *= sign_mult;
|
||||||
|
}
|
||||||
|
//debug!("[{}]: {} has signum {}, and i%2 is {}", i, cur, signum, i%2);
|
||||||
// Only multiply sign if dct-coefficient contains a horizontal component.
|
// Only multiply sign if dct-coefficient contains a horizontal component.
|
||||||
if
|
if
|
||||||
i % 8 != 0 && sign_mult * signum < 0.0
|
|
||||||
||
|
|
||||||
signum < 0.0
|
signum < 0.0
|
||||||
{
|
{
|
||||||
sign_mask += 1;
|
sign_mask += 1;
|
||||||
@@ -184,9 +191,11 @@ impl Descriptor for DCT {
|
|||||||
// Shift masks
|
// Shift masks
|
||||||
sign_mask = sign_mask << 1;
|
sign_mask = sign_mask << 1;
|
||||||
pearson_mask = pearson_mask << 1;
|
pearson_mask = pearson_mask << 1;
|
||||||
}
|
|
||||||
debug!("Sign mask: {:028b}", sign_mask);
|
debug!("Sign mask: {:028b}", sign_mask);
|
||||||
debug!("Pearson mask: {:028b}", pearson_mask);
|
debug!("Pearson mask: {:028b}", pearson_mask);
|
||||||
|
}
|
||||||
|
// debug!("Sign mask: {:028b}", sign_mask);
|
||||||
|
// debug!("Pearson mask: {:028b}", pearson_mask);
|
||||||
|
|
||||||
let mut mask = sign_mask;
|
let mut mask = sign_mask;
|
||||||
debug!("Mask: {:064b}", mask);
|
debug!("Mask: {:064b}", mask);
|
||||||
|
|||||||
+7
-2
@@ -26,7 +26,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for node in fs::read_dir(cfg.path).unwrap() {
|
for node in fs::read_dir(cfg.path).unwrap() {
|
||||||
println!("{:?}", node);
|
//println!("{:?}", node);
|
||||||
let file = node.expect("Error walking directory");
|
let file = node.expect("Error walking directory");
|
||||||
let name = match file.file_name().into_string() {
|
let name = match file.file_name().into_string() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
@@ -56,8 +56,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for store in &stores {
|
for store in &stores {
|
||||||
println!("{}", store);
|
println!("{}", store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for store in &stores {
|
||||||
|
println!("Store for {}: ", store.descriptor.info());
|
||||||
|
store.print_stats();
|
||||||
|
//println!("{}", store);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+100
-4
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use crate::descriptors::Descriptor;
|
use crate::descriptors::Descriptor;
|
||||||
|
use crate::mutators::get_all_mutators;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -16,10 +17,19 @@ pub enum SaveError {
|
|||||||
File,
|
File,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Struct for calculating Recall-Precision per mutation
|
||||||
|
struct PRStats {
|
||||||
|
true_positives: u64,
|
||||||
|
false_positives: u64,
|
||||||
|
false_negatives: u64,
|
||||||
|
tag: String,
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
/// Uses a hashmap to map descriptors to buckets of files.
|
/// Uses a hashmap to map descriptors to buckets of files.
|
||||||
/// Also keeps a BK-tree for quick distance ranking
|
/// Also keeps a BK-tree for quick distance ranking
|
||||||
pub struct DescriptorStore {
|
pub struct DescriptorStore {
|
||||||
descriptor: Box<dyn Descriptor>,
|
pub descriptor: Box<dyn Descriptor>,
|
||||||
/// Main hashmap that maps descriptors to buckets of filenames
|
/// Main hashmap that maps descriptors to buckets of filenames
|
||||||
map: HashMap<u64, Vec<String>>,
|
map: HashMap<u64, Vec<String>>,
|
||||||
|
|
||||||
@@ -71,6 +81,22 @@ impl DescriptorStore {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stores non-mutated images in a hashset for quick membership checks
|
||||||
|
pub fn see(&mut self, value: String) {
|
||||||
|
if !value.starts_with("mut.") {
|
||||||
|
match &mut self.seen {
|
||||||
|
Some(set) => {
|
||||||
|
set.insert(value);
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let mut seen: HashSet<String> = HashSet::new();
|
||||||
|
seen.insert(value);
|
||||||
|
self.seen = Some(seen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(&self) -> Result<(), SaveError> {
|
pub fn save(&self) -> Result<(), SaveError> {
|
||||||
let serialized: Vec<u8> = match rmp_serde::to_vec(&self.map) {
|
let serialized: Vec<u8> = match rmp_serde::to_vec(&self.map) {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
@@ -111,13 +137,14 @@ impl DescriptorStore {
|
|||||||
Some(b) => {
|
Some(b) => {
|
||||||
let mut n = b.clone();
|
let mut n = b.clone();
|
||||||
if !n.contains(&value) {
|
if !n.contains(&value) {
|
||||||
n.push(value);
|
n.push(value.clone());
|
||||||
}
|
}
|
||||||
n
|
n
|
||||||
},
|
},
|
||||||
None => vec![value],
|
None => vec![value.clone()],
|
||||||
};
|
};
|
||||||
self.map.insert(*key, bucket);
|
self.map.insert(*key, bucket);
|
||||||
|
self.see(value);
|
||||||
self.bktree.insert(*key);
|
self.bktree.insert(*key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +179,7 @@ impl DescriptorStore {
|
|||||||
self.save().expect("error");
|
self.save().expect("error");
|
||||||
}
|
}
|
||||||
// We have done our bulk loading, so we can unset our hashset cache:
|
// We have done our bulk loading, so we can unset our hashset cache:
|
||||||
self.seen = None;
|
//self.seen = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store(&mut self, img: &DynamicImage, name: String) {
|
pub fn store(&mut self, img: &DynamicImage, name: String) {
|
||||||
@@ -230,6 +257,75 @@ impl DescriptorStore {
|
|||||||
}
|
}
|
||||||
print!("DescriptorStore:\n{}Total: {}", output, self.map.len())
|
print!("DescriptorStore:\n{}Total: {}", output, self.map.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn print_stats(&self) {
|
||||||
|
let mut mutator_stats = Vec::new();
|
||||||
|
for mutator in get_all_mutators() {
|
||||||
|
mutator_stats.push(
|
||||||
|
PRStats {
|
||||||
|
true_positives: 0,
|
||||||
|
false_positives: 0,
|
||||||
|
false_negatives: 0,
|
||||||
|
tag: "mut".to_string() + &mutator.tag(),
|
||||||
|
name: mutator.info(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let set = self.seen.clone().unwrap();
|
||||||
|
|
||||||
|
for image in set.iter() {
|
||||||
|
debug!("Checking {}", image);
|
||||||
|
|
||||||
|
// find the bucket this image is in
|
||||||
|
for (key, bucket) in self.map.iter() {
|
||||||
|
if bucket.contains(&image) {
|
||||||
|
debug!("{} found in bucket {}", image, key);
|
||||||
|
for value in bucket {
|
||||||
|
if value != image && value.ends_with(image) {
|
||||||
|
// Mutated version of image in same bucket
|
||||||
|
for mutator in &mut mutator_stats {
|
||||||
|
mutator.false_negatives += 1;
|
||||||
|
if value.starts_with(&mutator.tag) {
|
||||||
|
mutator.true_positives += 1;
|
||||||
|
mutator.false_negatives -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("We matched {} to {}!", value, image);
|
||||||
|
} else if value != image {
|
||||||
|
// Other image in same bucket
|
||||||
|
//false_positives += 1;
|
||||||
|
for mutator in &mut mutator_stats {
|
||||||
|
if value.starts_with(&mutator.tag) {
|
||||||
|
mutator.false_positives += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
for mutator in &mut mutator_stats {
|
||||||
|
// println!("\t{} true positives: {}", mutator.name, mutator.true_positives);
|
||||||
|
// println!("\t{} false positives: {}", mutator.name, mutator.false_positives);
|
||||||
|
println!(
|
||||||
|
"\t{} Precision: {:.3}", mutator.name,
|
||||||
|
mutator.true_positives as f64 /
|
||||||
|
(mutator.true_positives + mutator.false_positives) as f64
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"\t{} Recall: {:.3}", mutator.name,
|
||||||
|
mutator.true_positives as f64 /
|
||||||
|
//(mutator.true_positives + mutator.false_negatives) as f64
|
||||||
|
set.len() as f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//println!("Images processed: {}", set.len())
|
||||||
|
// println!("{} true positives", true_positives);
|
||||||
|
// println!("{} false positives", false_positives);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DescriptorStore {
|
impl fmt::Display for DescriptorStore {
|
||||||
|
|||||||
Reference in New Issue
Block a user