face-intel / tests /unit /test_vision_core.py
Marwan
Restructure + add reverse face search (PimEyes-style)
f5eeb1c
Raw
History Blame Contribute Delete
7.89 kB
"""Unit tests for cores.vision — the shared image-operations layer."""
from __future__ import annotations
import base64
import hashlib
import cv2
import numpy as np
import pytest
from cores.vision import (
bytes_to_numpy, base64_to_numpy, numpy_to_base64, numpy_to_bytes,
url_to_bytes, sniff_format,
BBox, crop_region, resize_with_aspect, clamp_box, boxes_iou,
to_gray, to_rgb, to_bgr, guess_color_profile, dominant_colors,
sha256_bytes, sha256_image, phash, dhash, ahash, whash, hamming_distance,
brightness, contrast, sharpness, noise_level, quality_score,
draw_boxes,
)
class TestDecode:
def test_bytes_to_numpy_valid(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
assert img.ndim == 3
assert img.shape[2] == 3
def test_bytes_to_numpy_invalid(self):
with pytest.raises(ValueError):
bytes_to_numpy(b"not an image")
def test_base64_to_numpy_with_data_uri(self, sample_image_b64):
img = base64_to_numpy("data:image/jpeg;base64," + sample_image_b64)
assert img is not None
def test_numpy_to_bytes_roundtrip(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
raw = numpy_to_bytes(img)
img2 = bytes_to_numpy(raw)
assert img.shape == img2.shape
def test_numpy_to_base64(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
b64 = numpy_to_base64(img)
assert isinstance(b64, str)
def test_sniff_format_jpeg(self, sample_image_bytes):
assert sniff_format(sample_image_bytes) == "jpeg"
def test_sniff_format_png(self):
png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 50
assert sniff_format(png) == "png"
def test_sniff_format_unknown(self):
assert sniff_format(b"\x00\x01\x02\x03") is None
def test_sniff_format_empty(self):
assert sniff_format(b"") is None
class TestGeometry:
def test_bbox_to_dict(self):
b = BBox(10, 20, 100, 200)
assert b.to_dict() == {"x": 10, "y": 20, "w": 100, "h": 200}
def test_bbox_area(self):
assert BBox(0, 0, 100, 50).area == 5000
def test_crop_region_no_margin(self):
img = np.zeros((300, 300, 3), dtype=np.uint8)
img[100:200, 100:200] = 255
crop = crop_region(img, BBox(100, 100, 100, 100), margin=0.0)
assert crop.shape == (100, 100, 3)
assert (crop == 255).all()
def test_crop_region_with_margin(self):
img = np.zeros((300, 300, 3), dtype=np.uint8)
crop = crop_region(img, BBox(100, 100, 50, 50), margin=0.2)
assert crop.shape == (70, 70, 3)
def test_crop_region_clamps_bounds(self):
img = np.zeros((100, 100, 3), dtype=np.uint8)
crop = crop_region(img, BBox(0, 0, 80, 80), margin=0.5)
assert crop.shape[0] <= 100
assert crop.shape[1] <= 100
def test_resize_no_resize_needed(self):
img = np.zeros((100, 200, 3), dtype=np.uint8)
assert resize_with_aspect(img, max_dim=300).shape == img.shape
def test_resize_landscape(self):
img = np.zeros((100, 400, 3), dtype=np.uint8)
r = resize_with_aspect(img, max_dim=200)
assert r.shape[1] == 200
assert r.shape[0] == 50
def test_clamp_box(self):
b = clamp_box(BBox(-10, -10, 100, 100), width=50, height=50)
assert b.x == 0
assert b.y == 0
assert b.w == 50
assert b.h == 50
def test_boxes_iou_identical(self):
b = BBox(0, 0, 100, 100)
assert boxes_iou(b, b) == 1.0
def test_boxes_iou_disjoint(self):
a = BBox(0, 0, 10, 10)
b = BBox(100, 100, 10, 10)
assert boxes_iou(a, b) == 0.0
class TestColor:
def test_to_gray_from_bgr(self):
img = np.zeros((10, 10, 3), dtype=np.uint8)
gray = to_gray(img)
assert gray.shape == (10, 10)
def test_to_gray_passthrough(self):
gray = np.zeros((10, 10), dtype=np.uint8)
assert to_gray(gray).shape == (10, 10)
def test_to_rgb_swaps_channels(self):
# BGR [255, 0, 0] = blue → RGB should be [0, 0, 255]
img = np.array([[[255, 0, 0]]], dtype=np.uint8) # BGR: blue
rgb = to_rgb(img)
assert rgb[0, 0, 0] == 0 # R
assert rgb[0, 0, 1] == 0 # G
assert rgb[0, 0, 2] == 255 # B
def test_guess_color_profile_bgr(self):
assert guess_color_profile(np.zeros((10, 10, 3), dtype=np.uint8)) == "BGR"
def test_guess_color_profile_gray(self):
assert guess_color_profile(np.zeros((10, 10), dtype=np.uint8)) == "grayscale"
def test_dominant_colors_returns_hex(self):
img = np.zeros((100, 100, 3), dtype=np.uint8)
img[:] = [255, 0, 0] # all blue in BGR
colors = dominant_colors(img, k=3)
assert len(colors) == 3
for c in colors:
assert c.startswith("#")
class TestHashing:
def test_sha256_bytes_stable(self):
assert sha256_bytes(b"hello") == sha256_bytes(b"hello")
assert sha256_bytes(b"hello") != sha256_bytes(b"world")
def test_sha256_image_stable(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
assert sha256_image(img) == sha256_image(img)
def test_phash_stable(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
assert phash(img) == phash(img)
def test_phash_hex_length(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
# 8x8 = 64 bits = 16 hex chars
assert len(phash(img)) == 16
def test_dhash_stable(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
assert dhash(img) == dhash(img)
def test_ahash_stable(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
assert ahash(img) == ahash(img)
def test_whash_stable(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
# whash falls back to phash if pywt not installed
result = whash(img)
assert isinstance(result, str)
def test_hamming_distance_identical(self):
assert hamming_distance("ffff", "ffff") == 0
def test_hamming_distance_different(self):
assert hamming_distance("0000", "ffff") == 16
def test_hamming_distance_unequal_length(self):
assert hamming_distance("ff", "ffff") == 4
def test_phash_differs_for_different_images(self, sample_image_bytes, sample_face_image_bytes):
img1 = bytes_to_numpy(sample_image_bytes)
img2 = bytes_to_numpy(sample_face_image_bytes)
assert phash(img1) != phash(img2)
class TestQuality:
def test_brightness_black(self):
img = np.zeros((100, 100, 3), dtype=np.uint8)
assert brightness(img) < 5.0
def test_brightness_white(self):
img = np.full((100, 100, 3), 255, dtype=np.uint8)
assert brightness(img) > 250.0
def test_contrast_uniform(self):
img = np.full((100, 100, 3), 128, dtype=np.uint8)
assert contrast(img) < 1.0
def test_sharpness_uniform(self):
img = np.full((100, 100, 3), 128, dtype=np.uint8)
assert sharpness(img) < 1.0
def test_quality_score_in_range(self, sample_image_bytes):
img = bytes_to_numpy(sample_image_bytes)
q = quality_score(img)
assert 0.0 <= q <= 1.0
class TestDrawing:
def test_draw_boxes_does_not_modify_original(self):
img = np.zeros((300, 300, 3), dtype=np.uint8)
boxes = [BBox(50, 50, 100, 100)]
out = draw_boxes(img, boxes)
assert (img == 0).all()
assert not (out == 0).all()
def test_draw_boxes_accepts_dict(self):
img = np.zeros((300, 300, 3), dtype=np.uint8)
boxes = [{"x": 50, "y": 50, "w": 100, "h": 100}]
out = draw_boxes(img, boxes)
assert not np.array_equal(img, out)