Files
photoanalyzer/tests/unit/test_phash.py

53 lines
1.7 KiB
Python

"""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