US01-04: Detect and Decide Duplicates (#49)

This commit was merged in pull request #49.
This commit is contained in:
2026-07-15 17:14:24 +02:00
parent c25d45d8f7
commit 691d5a5651
9 changed files with 1137 additions and 4 deletions

52
tests/unit/test_phash.py Normal file
View File

@@ -0,0 +1,52 @@
"""Golden perceptual-hash relationships and confidence bands."""
import numpy as np
from PIL import Image
from photo_pipeline.services import hashing
from photo_pipeline.services.duplicates import NEAR_MAX, SIMILAR_MAX
def _structured(path, seed, size=(256, 192)):
rng = np.random.default_rng(seed)
w, h = size
base = np.zeros((h, w, 3), dtype=np.uint8)
for _ in range(6):
x0 = int(rng.integers(0, w - 60))
y0 = int(rng.integers(0, h - 60))
base[y0 : y0 + 60, x0 : x0 + 60] = rng.integers(0, 256, 3)
grad = np.linspace(0, 120, w, dtype=np.uint8)
base[:, :, 0] = np.clip(base[:, :, 0].astype(int) + grad[None, :], 0, 255)
Image.fromarray(base).save(path, quality=95)
return path
def test_phash_is_16_hex_chars(tmp_path):
value = hashing.phash(_structured(tmp_path / "a.jpg", 1))
assert len(value) == 16
int(value, 16) # parses as hex
def test_identical_pixels_have_zero_distance(tmp_path):
a = _structured(tmp_path / "a.jpg", 1)
assert hashing.phash_distance(hashing.phash(a), hashing.phash(a)) == 0
def test_resized_copy_stays_within_near_band(tmp_path):
a = _structured(tmp_path / "a.jpg", 1)
small = tmp_path / "a_small.jpg"
with Image.open(a) as image:
image.resize((image.width // 2, image.height // 2), Image.LANCZOS).save(small, quality=95)
distance = hashing.phash_distance(hashing.phash(a), hashing.phash(small))
assert distance <= NEAR_MAX
def test_distinct_images_exceed_similar_band(tmp_path):
a = _structured(tmp_path / "a.jpg", 1)
b = _structured(tmp_path / "b.jpg", 2)
distance = hashing.phash_distance(hashing.phash(a), hashing.phash(b))
assert distance > SIMILAR_MAX
def test_phash_is_versioned():
assert hashing.PHASH_VERSION >= 1