53 lines
1.7 KiB
Python
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
|