File size: 6,318 Bytes
a39757f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
150
151
152
import pytest
import numpy as np
import cv2
from app.domain.schemas.core import TextBlock, BoundingBox, OCRResult
from app.services.ocr.evaluator import detect_script, evaluate_detection_quality, select_or_merge_passes
from app.api.dependencies.ocr import get_ocr_engine

# ---------------------------------------------------------
# 1. Rich Raw Detection Schema
# ---------------------------------------------------------
def test_rich_raw_detections():
    bbox = BoundingBox(
        points=[(10.0, 20.0), (110.0, 20.0), (110.0, 50.0), (10.0, 50.0)],
        normalized_points=[(0.01, 0.02), (0.11, 0.02), (0.11, 0.05), (0.01, 0.05)],
        width=100.0,
        height=30.0,
        area=3000.0
    )
    block = TextBlock(
        box=bbox,
        text="29501011234567",
        confidence=0.98,
        x=10.0,
        y=20.0,
        width=100.0,
        height=30.0,
        page=1,
        detection_order=1,
        script="numeric"
    )
    assert block.x == 10.0
    assert block.y == 20.0
    assert block.width == 100.0
    assert block.height == 30.0
    assert block.page == 1
    assert block.detection_order == 1
    assert block.script == "numeric"

# ---------------------------------------------------------
# 2. Script Detection Per Block
# ---------------------------------------------------------
def test_script_detection_per_block():
    assert detect_script("جمهورية مصر العربية") == "ar"
    assert detect_script("Commercial Invoice") == "latin"
    assert detect_script("123456789") == "numeric"
    assert detect_script("Cairo القاهرة 2024") == "mixed"
    assert detect_script("###$$$") == "symbol"

# ---------------------------------------------------------
# 3. Multi-Signal Quality Evaluation
# ---------------------------------------------------------
def test_multi_signal_quality_evaluation():
    # A. High quality result
    bbox = BoundingBox(points=[(0, 0), (10, 0), (10, 10), (0, 10)], normalized_points=[(0,0),(0.1,0),(0.1,0.1),(0,0.1)], width=10, height=10, area=100)
    good_blocks = [
        TextBlock(box=bbox, text="Total Amount: 15000 EGP", confidence=0.95),
        TextBlock(box=bbox, text="Invoice Number: INV-9812", confidence=0.94),
        TextBlock(box=bbox, text="Date: 2024-05-15", confidence=0.96)
    ]
    good_res = OCRResult(blocks=good_blocks, total_time_ms=100, average_confidence=0.95, image_width=500, image_height=500)
    eval_good = evaluate_detection_quality(good_res)
    assert eval_good["composite_score"] > 0.65
    assert eval_good["has_structured_data"] is True
    assert eval_good["coherence_ratio"] > 0.80

    # B. Noisy gibberish result
    bad_blocks = [
        TextBlock(box=bbox, text="~!!@#$$%", confidence=0.25),
        TextBlock(box=bbox, text="^^^^||||", confidence=0.20)
    ]
    bad_res = OCRResult(blocks=bad_blocks, total_time_ms=100, average_confidence=0.22, image_width=500, image_height=500)
    eval_bad = evaluate_detection_quality(bad_res)
    assert eval_bad["composite_score"] < 0.40
    assert eval_bad["has_structured_data"] is False

# ---------------------------------------------------------
# 4. Multi-Pass Selection & Complementary Merging
# ---------------------------------------------------------
def test_multi_pass_selection_and_merging():
    bbox1 = BoundingBox(points=[(10, 10), (100, 10), (100, 30), (10, 30)], normalized_points=[(0,0),(0,0),(0,0),(0,0)], width=90, height=20, area=1800)
    bbox2 = BoundingBox(points=[(10, 50), (100, 50), (100, 70), (10, 70)], normalized_points=[(0,0),(0,0),(0,0),(0,0)], width=90, height=20, area=1800)
    bbox_footer = BoundingBox(points=[(10, 400), (200, 400), (200, 430), (10, 430)], normalized_points=[(0,0),(0,0),(0,0),(0,0)], width=190, height=30, area=5700)

    # Pass A found header (bbox1, bbox2)
    pass_a = OCRResult(
        blocks=[
            TextBlock(box=bbox1, text="Company Header", confidence=0.90),
            TextBlock(box=bbox2, text="Invoice 1234", confidence=0.88)
        ],
        total_time_ms=80,
        average_confidence=0.89,
        image_width=500,
        image_height=500
    )

    # Pass B found header (overlapping) AND footer (bbox_footer)
    pass_b = OCRResult(
        blocks=[
            TextBlock(box=bbox1, text="Company Header", confidence=0.89),
            TextBlock(box=bbox_footer, text="Tax Registration: 987654321", confidence=0.92)
        ],
        total_time_ms=80,
        average_confidence=0.90,
        image_width=500,
        image_height=500
    )

    merged, strategy = select_or_merge_passes(pass_a, pass_b)
    assert strategy == "merged"
    # Merged should have 3 blocks: header + invoice + footer!
    assert len(merged.blocks) == 3
    assert any("Tax Registration" in b.text for b in merged.blocks)

# ---------------------------------------------------------
# 5. EasyOCR Engine Production Inference
# ---------------------------------------------------------
def test_easyocr_engine_rich_inference():
    # Synthetic image with Arabic and English
    img = np.ones((400, 800, 3), dtype=np.uint8) * 255
    cv2.putText(img, "Tax Invoice 2024", (50, 150), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 0), 2)
    cv2.putText(img, "Total: 1500 EGP", (50, 250), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 0, 0), 2)

    engine = get_ocr_engine()
    res = engine.process_image(img)
    assert res is not None
    assert len(res.blocks) >= 2

    # Check rich attributes
    for block in res.blocks:
        assert block.width > 0
        assert block.height > 0
        assert block.page == 1
        assert block.detection_order >= 1
        assert block.script in ["latin", "numeric", "mixed", "ar", "symbol"]


def test_easyocr_engine_preserves_raw_text_without_post_correction(monkeypatch):
    from app.infrastructure.ocr.easyocr_engine import EasyOCREngine
    bbox = [[10, 10], [150, 10], [150, 40], [10, 40]]
    engine = object.__new__(EasyOCREngine)
    engine._initialized = True

    class Reader:
        def readtext(self, *_args, **_kwargs):
            return [(bbox, "محمل", 0.91)]

    engine.reader = Reader()
    result = engine.process_image(np.ones((100, 200, 3), dtype=np.uint8) * 255)
    assert result.blocks[0].text == "محمل"
    assert result.blocks[0].raw_text == "محمل"
    assert result.blocks[0].normalized_text == "محمل"