File size: 4,671 Bytes
0b09377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0864c88
0b09377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a2bea75
0b09377
a2bea75
 
 
 
 
 
0b09377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a2bea75
0b09377
 
 
 
 
 
 
 
 
a2bea75
0b09377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a2bea75
0b09377
 
 
 
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
"""Workers de niveau module pour les pools d'exécution.

Deux workers correspondant aux deux modes d'exécution :

- :func:`_cpu_doc_worker` — pour ``ProcessPoolExecutor`` (moteurs
  CPU-bound, instanciés dans le sous-processus). Doit être picklable :
  c'est pour ça qu'il est défini au niveau module.
- :func:`_io_doc_worker` — pour ``ThreadPoolExecutor`` (moteurs
  IO-bound / API HTTP). L'instance du moteur est partagée entre les
  threads.

Les deux finissent par appeler :func:`_compute_document_result` du
sous-module :mod:`document` pour calculer toutes les métriques.
"""

from __future__ import annotations

from typing import Optional

from picarones.evaluation.benchmark_result import DocumentResult
from picarones.engines.base import BaseOCREngine
from picarones.measurements.runner.document import _compute_document_result


def _cpu_doc_worker(args: tuple) -> "DocumentResult":
    """Worker pour ProcessPoolExecutor (moteurs CPU-bound).

    Instancie le moteur dans le sous-processus, exécute l'OCR et calcule
    toutes les métriques.  Doit être une fonction de niveau module pour être
    sérialisable par ``pickle``.

    Le tuple ``args`` peut contenir, par compatibilité ascendante :
    - 7 éléments : legacy (Sprint 13)
    - 8 éléments : + ``corpus_lang`` (Sprint 87)
    - 9 éléments : + ``profile`` (chantier 2 post-Sprint 97)
    - 10 éléments : + ``normalization_profile`` (Sprint A14-S1, A.I.0 P0)
    """
    norm_profile = None
    if len(args) == 10:
        (engine_module, engine_class_name, engine_config, doc_id,
         image_path, ground_truth, char_exclude_chars, corpus_lang,
         profile, norm_profile) = args
    elif len(args) == 9:
        (engine_module, engine_class_name, engine_config, doc_id,
         image_path, ground_truth, char_exclude_chars, corpus_lang,
         profile) = args
    elif len(args) == 8:
        (engine_module, engine_class_name, engine_config, doc_id,
         image_path, ground_truth, char_exclude_chars, corpus_lang) = args
        profile = "standard"
    else:
        (engine_module, engine_class_name, engine_config, doc_id,
         image_path, ground_truth, char_exclude_chars) = args
        corpus_lang = "fr"
        profile = "standard"
    import importlib
    mod = importlib.import_module(engine_module)
    engine_cls = getattr(mod, engine_class_name)
    engine = engine_cls(config=engine_config)
    ocr_result = engine.run(image_path)
    char_exclude = frozenset(char_exclude_chars) if char_exclude_chars else None
    return _compute_document_result(
        doc_id=doc_id,
        image_path=image_path,
        ground_truth=ground_truth,
        ocr_result=ocr_result,
        char_exclude=char_exclude,
        corpus_lang=corpus_lang,
        profile=profile,
        normalization_profile=norm_profile,
    )


def _io_doc_worker(
    engine: BaseOCREngine,
    doc: object,
    char_exclude: Optional[frozenset],
    corpus_lang: str = "fr",
    profile: str = "standard",
    normalization_profile: Optional[object] = None,
) -> "DocumentResult":
    """Worker pour ThreadPoolExecutor (moteurs IO-bound / API).

    Exécute l'OCR et calcule les métriques dans un thread.  L'instance du
    moteur est partagée entre les threads — les adaptateurs HTTP sont
    généralement sans état mutable entre les appels.

    Si le document possède un texte OCR pré-calculé (corpus triplet) et que
    le moteur est un pipeline OCR+LLM, utilise ``run_with_ocr_text()`` pour
    court-circuiter l'étape OCR et tester directement la post-correction LLM.
    """
    doc_ocr_text = getattr(doc, "ocr_text", None)
    if doc_ocr_text is not None:
        # Corpus triplet — vérifier si le moteur supporte run_with_ocr_text
        run_with = getattr(engine, "run_with_ocr_text", None)
        if run_with is not None:
            ocr_result = run_with(doc.image_path, doc_ocr_text)  # type: ignore[attr-defined]
        else:
            # Moteur OCR classique — ignorer le texte OCR pré-calculé
            ocr_result = engine.run(doc.image_path)  # type: ignore[attr-defined]
    else:
        ocr_result = engine.run(doc.image_path)  # type: ignore[attr-defined]

    return _compute_document_result(
        doc_id=doc.doc_id,  # type: ignore[attr-defined]
        image_path=str(doc.image_path),  # type: ignore[attr-defined]
        ground_truth=doc.ground_truth,  # type: ignore[attr-defined]
        ocr_result=ocr_result,
        char_exclude=char_exclude,
        corpus_lang=corpus_lang,
        profile=profile,
        normalization_profile=normalization_profile,
    )


__all__ = ["_cpu_doc_worker", "_io_doc_worker"]