Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ members = [
]

[workspace.package]
version = "0.1.3"
version = "0.1.4"
edition = "2021"
license = "GPL-3.0-or-later"
authors = ["Aaron Stopher <aaron.stopher@gmail.com>"]
Expand Down
2 changes: 1 addition & 1 deletion crates/imgdd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ categories.workspace = true
readme = "README.md"

[dependencies]
imgddcore = { path = "../imgddcore", version = "0.1.3" }
imgddcore = { path = "../imgddcore", version = "0.1.4" }
image.workspace = true
anyhow.workspace = true
criterion = { version = "0.5.1", optional = true }
Expand Down
18 changes: 0 additions & 18 deletions crates/imgdd/benches/rust_benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,6 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
use imgdd::*;
use std::path::PathBuf;

fn benchmark_select_filter_type(c: &mut Criterion) {
c.bench_function("select_filter_type", |b| {
b.iter(|| {
black_box(select_filter_type(Some("nearest")));
});
});
}

fn benchmark_select_algo(c: &mut Criterion) {
c.bench_function("select_algo", |b| {
b.iter(|| {
black_box(select_algo(Some("dhash")));
});
});
}

fn benchmark_hash(c: &mut Criterion) {
let dir_path = PathBuf::from("../../imgs/test/single");

Expand Down Expand Up @@ -52,8 +36,6 @@ fn benchmark_dupes(c: &mut Criterion) {

criterion_group!(
rust_interface_benchmarks,
benchmark_select_filter_type,
benchmark_select_algo,
benchmark_hash,
benchmark_dupes
);
Expand Down
53 changes: 5 additions & 48 deletions crates/imgdd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,12 @@
//! Leverages perceptual hashing algorithms to identify duplicate or visually similar images in a directory.

use anyhow::Error;
use image::imageops::FilterType;
use imgddcore::dedupe::*;
use imgddcore::validate::*;
use imgddcore::dedupe::{collect_hashes, find_duplicates, sort_hashes};
use imgddcore::utils::{select_algo, select_filter_type};
use imgddcore::validate::validate_path;
use std::collections::HashMap;
use std::path::PathBuf;

/// Converts a string to a `FilterType`.
///
/// # Arguments
///
/// - `filter` - String specifying the filter type.
/// - **Options:** [`Nearest`, `Triangle`, `CatmullRom`, `Gaussian`, `Lanczos3`]
///
/// # Returns
///
/// - A `FilterType` enum corresponding to the input string.
#[inline]
pub fn select_filter_type(filter: Option<&str>) -> FilterType {
match filter.unwrap_or("nearest") {
ref f if f.eq_ignore_ascii_case("nearest") => FilterType::Nearest,
ref f if f.eq_ignore_ascii_case("triangle") => FilterType::Triangle,
ref f if f.eq_ignore_ascii_case("catmullrom") => FilterType::CatmullRom,
ref f if f.eq_ignore_ascii_case("gaussian") => FilterType::Gaussian,
ref f if f.eq_ignore_ascii_case("lanczos3") => FilterType::Lanczos3,
other => panic!("Unsupported filter type: {}", other),
}
}

/// Selects a hashing algorithm.
///
/// # Arguments
///
/// - `algo` - String specifying the hashing algorithm.
/// - **Options:** [`aHash`, `mHash`, `dHash`, `pHash`, `wHash`]
///
/// # Returns
///
/// - A standardized `&'static str` representing the selected algorithm.
#[inline]
pub fn select_algo(algo: Option<&str>) -> &'static str {
match algo.unwrap_or("dhash") {
input if input.eq_ignore_ascii_case("dhash") => "dhash",
input if input.eq_ignore_ascii_case("ahash") => "ahash",
input if input.eq_ignore_ascii_case("mhash") => "mhash",
input if input.eq_ignore_ascii_case("phash") => "phash",
input if input.eq_ignore_ascii_case("whash") => "whash",
other => panic!("Unsupported algorithm: {}", other),
}
}

/// Calculates hashes for all images in a directory recursively.
///
/// # Arguments
Expand Down Expand Up @@ -148,5 +104,6 @@ pub fn dupes(
let mut hash_paths = collect_hashes(validated_path, filter_type, selected_algo)?;
sort_hashes(&mut hash_paths);

Ok(find_duplicates(&hash_paths, remove)?)
// Ok(find_duplicates(&hash_paths, remove)?)
find_duplicates(&hash_paths, remove)
}
31 changes: 0 additions & 31 deletions crates/imgdd/tests/rust_tests.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,11 @@
#[cfg(test)]
mod tests {
use image::imageops::FilterType;
use imgdd::*;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;

#[test]
fn test_select_filter_type() {
assert_eq!(select_filter_type(Some("nearest")), FilterType::Nearest);
assert_eq!(select_filter_type(Some("triangle")), FilterType::Triangle);
assert_eq!(
select_filter_type(Some("catmullrom")),
FilterType::CatmullRom
);
assert_eq!(select_filter_type(Some("gaussian")), FilterType::Gaussian);
assert_eq!(select_filter_type(Some("lanczos3")), FilterType::Lanczos3);

let result = std::panic::catch_unwind(|| select_filter_type(Some("unsupported")));
assert!(
result.is_err(),
"Expected panic for unsupported filter type"
);
}

#[test]
fn test_select_algo() {
assert_eq!(select_algo(Some("dhash")), "dhash");
assert_eq!(select_algo(Some("ahash")), "ahash");
assert_eq!(select_algo(Some("mhash")), "mhash");
assert_eq!(select_algo(Some("phash")), "phash");
assert_eq!(select_algo(Some("whash")), "whash");

let result = std::panic::catch_unwind(|| select_algo(Some("unsupported")));
assert!(result.is_err(), "Expected panic for unsupported algorithm");
}

#[test]
fn test_hash_with_valid_inputs() {
let temp_dir = tempdir().unwrap();
Expand Down
19 changes: 19 additions & 0 deletions crates/imgddcore/benches/core_benches.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};

use imgddcore::dedupe::{collect_hashes, find_duplicates, open_image, sort_hashes};
use imgddcore::utils::{select_algo, select_filter_type};
use imgddcore::hashing::ImageHash;
use imgddcore::normalize::proc as normalize;
use std::path::PathBuf;
Expand All @@ -11,6 +12,22 @@ use std::path::PathBuf;
// To resolve this we must use hosted codspeed macro runners which require a pro plan.
// For now I will just leave this warning here.

fn benchmark_select_filter_type(c: &mut Criterion) {
c.bench_function("select_filter_type", |b| {
b.iter(|| {
black_box(select_filter_type(Some("nearest")));
});
});
}

fn benchmark_select_algo(c: &mut Criterion) {
c.bench_function("select_algo", |b| {
b.iter(|| {
black_box(select_algo(Some("dhash")));
});
});
}

fn open_image_bench(c: &mut Criterion) {
let path = PathBuf::from("../../imgs/test/single/file000898199107.jpg");

Expand Down Expand Up @@ -174,6 +191,8 @@ criterion_group! {

criterion_group!(
group3,
benchmark_select_filter_type,
benchmark_select_algo,
benchmark_ahash,
benchmark_mhash,
benchmark_dhash,
Expand Down
5 changes: 3 additions & 2 deletions crates/imgddcore/src/dedupe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub fn collect_hashes(
///
/// * `hash_paths` - A mutable reference to a vector of hash-path tuples.
#[inline]
pub fn sort_hashes(hash_paths: &mut Vec<(u64, PathBuf)>) {
pub fn sort_hashes(hash_paths: &mut [(u64, PathBuf)]) {
hash_paths.sort_by_key(|(hash, _)| *hash);
}

Expand Down Expand Up @@ -130,7 +130,8 @@ pub fn find_duplicates(
if hash1 == hash2 {
duplicates_map
.entry(*hash1)
.or_insert_with(Vec::new)
// .or_insert_with(Vec::new)
.or_default()
.extend(vec![path1.clone(), path2.clone()]);
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/imgddcore/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod dedupe;
pub mod hashing;
pub mod normalize;
pub mod utils;
pub mod validate;
45 changes: 45 additions & 0 deletions crates/imgddcore/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use image::imageops::FilterType;

/// Converts a string to a `FilterType`.
///
/// # Arguments
///
/// - `filter` - String specifying the filter type.
/// - **Options:** [`Nearest`, `Triangle`, `CatmullRom`, `Gaussian`, `Lanczos3`]
///
/// # Returns
///
/// - A `FilterType` enum corresponding to the input string.
#[inline]
pub fn select_filter_type(filter: Option<&str>) -> FilterType {
match filter.unwrap_or("nearest") {
f if f.eq_ignore_ascii_case("nearest") => FilterType::Nearest,
f if f.eq_ignore_ascii_case("triangle") => FilterType::Triangle,
f if f.eq_ignore_ascii_case("catmullrom") => FilterType::CatmullRom,
f if f.eq_ignore_ascii_case("gaussian") => FilterType::Gaussian,
f if f.eq_ignore_ascii_case("lanczos3") => FilterType::Lanczos3,
other => panic!("Unsupported filter type: {}", other),
}
}

/// Selects a hashing algorithm.
///
/// # Arguments
///
/// - `algo` - String specifying the hashing algorithm.
/// - **Options:** [`aHash`, `mHash`, `dHash`, `pHash`, `wHash`]
///
/// # Returns
///
/// - A standardized `&'static str` representing the selected algorithm.
#[inline]
pub fn select_algo(algo: Option<&str>) -> &'static str {
match algo.unwrap_or("dhash") {
input if input.eq_ignore_ascii_case("dhash") => "dhash",
input if input.eq_ignore_ascii_case("ahash") => "ahash",
input if input.eq_ignore_ascii_case("mhash") => "mhash",
input if input.eq_ignore_ascii_case("phash") => "phash",
input if input.eq_ignore_ascii_case("whash") => "whash",
other => panic!("Unsupported algorithm: {}", other),
}
}
2 changes: 1 addition & 1 deletion crates/imgddcore/tests/dedupe_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
mod tests {
use image::imageops::FilterType;
use image::{DynamicImage, Rgba};
use imgddcore::dedupe::*;
use imgddcore::dedupe::{collect_hashes, find_duplicates, open_image, sort_hashes};
use std::fs::File;
use std::io::Write;
use std::panic;
Expand Down
33 changes: 33 additions & 0 deletions crates/imgddcore/tests/utils_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use image::imageops::FilterType;
use imgddcore::utils::{select_algo, select_filter_type};


#[test]
fn test_select_filter_type() {
assert_eq!(select_filter_type(Some("nearest")), FilterType::Nearest);
assert_eq!(select_filter_type(Some("triangle")), FilterType::Triangle);
assert_eq!(
select_filter_type(Some("catmullrom")),
FilterType::CatmullRom
);
assert_eq!(select_filter_type(Some("gaussian")), FilterType::Gaussian);
assert_eq!(select_filter_type(Some("lanczos3")), FilterType::Lanczos3);

let result = std::panic::catch_unwind(|| select_filter_type(Some("unsupported")));
assert!(
result.is_err(),
"Expected panic for unsupported filter type"
);
}

#[test]
fn test_select_algo() {
assert_eq!(select_algo(Some("dhash")), "dhash");
assert_eq!(select_algo(Some("ahash")), "ahash");
assert_eq!(select_algo(Some("mhash")), "mhash");
assert_eq!(select_algo(Some("phash")), "phash");
assert_eq!(select_algo(Some("whash")), "whash");

let result = std::panic::catch_unwind(|| select_algo(Some("unsupported")));
assert!(result.is_err(), "Expected panic for unsupported algorithm");
}
2 changes: 1 addition & 1 deletion crates/imgddcore/tests/validate_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[cfg(test)]
mod tests {
use imgddcore::validate::*;
use imgddcore::validate::validate_path;
use std::path::PathBuf;
use tempfile::{tempdir, NamedTempFile};

Expand Down
4 changes: 2 additions & 2 deletions crates/imgddpy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "imgddpy"
version = "0.1.5"
version = "0.1.6"
edition.workspace = true
license.workspace = true
authors.workspace = true
Expand All @@ -12,7 +12,7 @@ homepage.workspace = true
readme = "README.md"

[dependencies]
imgddcore = { path = "../imgddcore", version = "0.1.3" }
imgddcore = { path = "../imgddcore", version = "0.1.4" }
pyo3 = { version = "0.23", features = ["extension-module", "abi3-py39"] }
image.workspace = true

Expand Down
Loading
Loading