| """Unit tests for utils/image.py.""" |
|
|
| from __future__ import annotations |
|
|
| import base64 |
|
|
| import cv2 |
| import numpy as np |
| import pytest |
|
|
| from utils.image import ( |
| BBox, |
| bytes_to_numpy, |
| base64_to_numpy, |
| numpy_to_base64, |
| image_hash, |
| bytes_hash, |
| crop_face, |
| resize_with_aspect, |
| draw_boxes, |
| ) |
|
|
|
|
| class TestImageRoundtrip: |
| def test_bytes_to_numpy_valid(self, sample_image_bytes): |
| img = bytes_to_numpy(sample_image_bytes) |
| assert img is not None |
| 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_roundtrip(self, sample_image_bytes): |
| b64 = base64.b64encode(sample_image_bytes).decode() |
| img = base64_to_numpy(b64) |
| assert img is not None |
| assert img.shape[0] > 0 |
|
|
| def test_base64_with_data_uri_prefix(self, sample_image_bytes): |
| b64 = "data:image/jpeg;base64," + base64.b64encode(sample_image_bytes).decode() |
| img = base64_to_numpy(b64) |
| assert img is not None |
|
|
| 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) |
| |
| raw = base64.b64decode(b64) |
| assert len(raw) > 0 |
|
|
|
|
| class TestBBox: |
| def test_bbox_to_dict(self): |
| b = BBox(10, 20, 100, 200) |
| d = b.to_dict() |
| assert d == {"x": 10, "y": 20, "w": 100, "h": 200} |
|
|
| def test_bbox_area(self): |
| b = BBox(0, 0, 100, 50) |
| assert b.area == 5000 |
|
|
| def test_bbox_to_face_recognition_tuple(self): |
| b = BBox(10, 20, 100, 200) |
| assert b.to_face_recognition_tuple() == (20, 110, 220, 10) |
|
|
|
|
| class TestCropFace: |
| def test_crop_face_basic(self): |
| img = np.zeros((300, 300, 3), dtype=np.uint8) |
| img[100:200, 100:200] = 255 |
| bbox = BBox(100, 100, 100, 100) |
| crop = crop_face(img, bbox, margin=0.0) |
| assert crop.shape[0] == 100 |
| assert crop.shape[1] == 100 |
| assert (crop == 255).all() |
|
|
| def test_crop_face_with_margin(self): |
| img = np.zeros((300, 300, 3), dtype=np.uint8) |
| bbox = BBox(100, 100, 50, 50) |
| crop = crop_face(img, bbox, margin=0.2) |
| |
| assert crop.shape[0] == 70 |
| assert crop.shape[1] == 70 |
|
|
| def test_crop_face_clamps_to_bounds(self): |
| img = np.zeros((100, 100, 3), dtype=np.uint8) |
| bbox = BBox(0, 0, 80, 80) |
| crop = crop_face(img, bbox, margin=0.5) |
| |
| assert crop.shape[0] <= 100 |
| assert crop.shape[1] <= 100 |
|
|
|
|
| class TestResize: |
| def test_resize_with_aspect_no_resize_needed(self): |
| img = np.zeros((100, 200, 3), dtype=np.uint8) |
| resized = resize_with_aspect(img, max_dim=300) |
| assert resized.shape == img.shape |
|
|
| def test_resize_with_aspect_landscape(self): |
| img = np.zeros((100, 400, 3), dtype=np.uint8) |
| resized = resize_with_aspect(img, max_dim=200) |
| assert resized.shape[1] == 200 |
| assert resized.shape[0] == 50 |
|
|
| def test_resize_with_aspect_portrait(self): |
| img = np.zeros((400, 100, 3), dtype=np.uint8) |
| resized = resize_with_aspect(img, max_dim=200) |
| assert resized.shape[0] == 200 |
| assert resized.shape[1] == 50 |
|
|
|
|
| class TestHashing: |
| def test_image_hash_stable(self, sample_image_bytes): |
| img = bytes_to_numpy(sample_image_bytes) |
| h1 = image_hash(img) |
| h2 = image_hash(img) |
| assert h1 == h2 |
|
|
| def test_image_hash_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 image_hash(img1) != image_hash(img2) |
|
|
| def test_bytes_hash_stable(self): |
| assert bytes_hash(b"hello") == bytes_hash(b"hello") |
| assert bytes_hash(b"hello") != bytes_hash(b"world") |
|
|
|
|
| class TestDrawBoxes: |
| def test_draw_boxes_with_dicts(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) |
| |
| assert (img == 0).all() |
|
|
| def test_draw_boxes_with_bbox_objects(self): |
| img = np.zeros((300, 300, 3), dtype=np.uint8) |
| boxes = [BBox(50, 50, 100, 100)] |
| out = draw_boxes(img, boxes) |
| assert not np.array_equal(img, out) |
|
|