File size: 4,768 Bytes
23d337e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Unit tests for confidence/conflicts.py."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional

from confidence.conflicts import ConflictDetector
from models.providers import ProviderCapability
from providers.base import ProviderResult


@dataclass
class FakeBox:
    detector: str
    confidence: float


@dataclass
class FakeMatch:
    query_face_index: int
    best_match: Optional[str]
    distance: float
    recognizer: str


def _make_result(provider: str, capability: ProviderCapability,
                 normalized: dict, success: bool = True) -> ProviderResult:
    return ProviderResult(
        provider=provider,
        capability=capability,
        success=success,
        elapsed_ms=10.0,
        normalized=normalized,
    )


class TestConflictDetector:
    def test_no_conflicts_when_single_provider(self):
        detector = ConflictDetector()
        results = {
            "haar": _make_result("haar", ProviderCapability.DETECTION,
                                  {"num_faces": 1, "boxes": [{"x": 0, "y": 0, "w": 10, "h": 10}],
                                   "confidences": [1.0]}),
        }
        boxes = [FakeBox(detector="haar", confidence=1.0)]
        conflicts = detector.detect(results, boxes, [])
        assert conflicts == []

    def test_face_count_mismatch_detected(self):
        detector = ConflictDetector()
        results = {
            "haar": _make_result("haar", ProviderCapability.DETECTION, {"num_faces": 1}),
            "dnn": _make_result("dnn", ProviderCapability.DETECTION, {"num_faces": 2}),
        }
        conflicts = detector.detect(results, [], [])
        assert len(conflicts) == 1
        assert conflicts[0].kind == "face_count_mismatch"
        assert "haar" in conflicts[0].providers
        assert "dnn" in conflicts[0].providers

    def test_face_count_agreement_no_conflict(self):
        detector = ConflictDetector()
        results = {
            "haar": _make_result("haar", ProviderCapability.DETECTION, {"num_faces": 2}),
            "dnn": _make_result("dnn", ProviderCapability.DETECTION, {"num_faces": 2}),
        }
        conflicts = detector.detect(results, [], [])
        assert conflicts == []

    def test_match_disagreement_detected(self):
        detector = ConflictDetector()
        matches = [
            FakeMatch(query_face_index=0, best_match="alice", distance=0.3, recognizer="face_recognition"),
            FakeMatch(query_face_index=0, best_match="bob", distance=0.4, recognizer="deepface"),
        ]
        conflicts = detector.detect({}, [], matches)
        assert len(conflicts) == 1
        assert conflicts[0].kind == "match_disagreement"

    def test_match_agreement_no_conflict(self):
        detector = ConflictDetector()
        matches = [
            FakeMatch(query_face_index=0, best_match="alice", distance=0.3, recognizer="face_recognition"),
            FakeMatch(query_face_index=0, best_match="alice", distance=0.4, recognizer="deepface"),
        ]
        conflicts = detector.detect({}, [], matches)
        assert conflicts == []

    def test_quality_disagreement_detected(self):
        detector = ConflictDetector()
        results = {
            "image_quality": _make_result("image_quality", ProviderCapability.IMAGE_ANALYSIS,
                                            {"quality_score": 0.9}),
            "image_properties": _make_result("image_properties", ProviderCapability.IMAGE_ANALYSIS,
                                              {"quality_score": 0.4}),
        }
        conflicts = detector.detect(results, [], [])
        assert len(conflicts) == 1
        assert conflicts[0].kind == "quality_disagreement"

    def test_format_mismatch_detected(self):
        detector = ConflictDetector()
        results = {
            "exif": _make_result("exif", ProviderCapability.METADATA, {"format": "JPEG"}),
            "xmp": _make_result("xmp", ProviderCapability.METADATA, {"format": "PNG"}),
        }
        conflicts = detector.detect(results, [], [])
        assert len(conflicts) == 1
        assert conflicts[0].kind == "format_mismatch"

    def test_failed_results_ignored(self):
        detector = ConflictDetector()
        results = {
            "haar": _make_result("haar", ProviderCapability.DETECTION, {"num_faces": 1}, success=False),
            "dnn": _make_result("dnn", ProviderCapability.DETECTION, {"num_faces": 2}),
        }
        # Only dnn succeeded — no mismatch
        conflicts = detector.detect(results, [], [])
        # haar's normalized is not considered since success=False
        # But the conflict detector looks at success=True results only
        # So no conflict should be detected
        assert conflicts == []