File size: 4,957 Bytes
7e25f7a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""Unit tests for cores.face.analysis — quality, blur, pose, clustering."""

from __future__ import annotations

import numpy as np
import pytest
import cv2

from cores.face import (
    blur_score, is_blurry, face_size, face_size_label,
    estimate_pose_landmark, face_orientation, face_quality_score,
    select_best_face, cluster_faces, find_duplicate_faces,
)
from cores.vision.geometry import BBox


class TestBlurScore:
    def test_sharp_image_high_score(self):
        # Random noise is "sharp"
        img = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
        assert blur_score(img) > 50

    def test_uniform_image_low_score(self):
        img = np.full((100, 100, 3), 128, dtype=np.uint8)
        assert blur_score(img) < 1.0

    def test_is_blurry_threshold(self):
        sharp = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
        blurry = np.full((100, 100, 3), 128, dtype=np.uint8)
        assert is_blurry(sharp, threshold=10) is False
        assert is_blurry(blurry, threshold=10) is True


class TestFaceSize:
    def test_face_size(self):
        assert face_size(BBox(0, 0, 100, 50)) == 5000

    def test_face_size_label(self):
        assert face_size_label(BBox(0, 0, 40, 40)) == "small"      # 1600
        assert face_size_label(BBox(0, 0, 80, 80)) == "medium"     # 6400
        assert face_size_label(BBox(0, 0, 120, 120)) == "large"    # 14400


class TestPoseEstimation:
    def test_no_landmarks_returns_unknown(self):
        yaw, pitch, roll, label = estimate_pose_landmark(None)
        assert label == "unknown"
        assert yaw == 0.0

    def test_frontal_pose(self):
        # Symmetric landmarks → near-zero yaw
        landmarks = {
            "left_eye": (40, 50),
            "right_eye": (60, 50),
            "nose": (50, 60),
        }
        yaw, pitch, roll, label = estimate_pose_landmark(landmarks)
        assert abs(yaw) < 5.0
        assert label == "frontal"

    def test_profile_pose(self):
        # Nose offset to one side → high yaw
        landmarks = {
            "left_eye": (40, 50),
            "right_eye": (60, 50),
            "nose": (75, 60),  # shifted right
        }
        yaw, _, _, label = estimate_pose_landmark(landmarks)
        assert yaw > 15.0
        assert label in ("profile", "extreme")


class TestFaceQuality:
    def test_quality_score_in_range(self):
        img = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
        bbox = BBox(0, 0, 100, 100)
        qs = face_quality_score(img, bbox)
        assert 0.0 <= qs <= 1.0

    def test_uniform_image_low_quality(self):
        """A uniform (blurry) image should have lower quality than a sharp one."""
        uniform = np.full((100, 100, 3), 128, dtype=np.uint8)
        sharp = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
        bbox = BBox(0, 0, 100, 100)
        qs_uniform = face_quality_score(uniform, bbox)
        qs_sharp = face_quality_score(sharp, bbox)
        # Sharp should score higher than uniform
        assert qs_sharp > qs_uniform


class TestSelectBestFace:
    def test_selects_frontal(self):
        # Two faces: one frontal, one profile
        idx = select_best_face(
            quality_scores=[0.5, 0.5],
            face_sizes=[10000, 10000],
            pose_labels=["frontal", "profile"],
        )
        assert idx == 0

    def test_selects_larger(self):
        idx = select_best_face(
            quality_scores=[0.5, 0.5],
            face_sizes=[5000, 15000],
            pose_labels=["frontal", "frontal"],
        )
        assert idx == 1


class TestClusterFaces:
    def test_clusters_identical_embeddings(self):
        emb = np.random.randn(128).astype(np.float32)
        embeddings = [emb, emb, emb]
        clusters = cluster_faces(embeddings, threshold=0.9)
        assert len(clusters) == 1
        assert clusters[0]["num_faces"] == 3

    def test_separates_different_embeddings(self):
        emb1 = np.random.randn(128).astype(np.float32)
        emb2 = -emb1  # opposite direction
        embeddings = [emb1, emb2]
        clusters = cluster_faces(embeddings, threshold=0.9)
        assert len(clusters) == 2

    def test_empty_embeddings(self):
        assert cluster_faces([]) == []


class TestDuplicateFaces:
    def test_overlapping_boxes_detected(self):
        boxes = [
            {"x": 0, "y": 0, "w": 100, "h": 100},
            {"x": 10, "y": 10, "w": 100, "h": 100},  # overlaps heavily
        ]
        dups = find_duplicate_faces(boxes, iou_threshold=0.5)
        assert 1 in dups

    def test_non_overlapping_not_duplicates(self):
        boxes = [
            {"x": 0, "y": 0, "w": 50, "h": 50},
            {"x": 200, "y": 200, "w": 50, "h": 50},
        ]
        dups = find_duplicate_faces(boxes)
        assert dups == []

    def test_single_face_no_duplicates(self):
        boxes = [{"x": 0, "y": 0, "w": 100, "h": 100}]
        assert find_duplicate_faces(boxes) == []