File size: 4,916 Bytes
99ad1af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""``AltoView`` — vue canonique 2, Sprint A14-S15.

Deuxième vue d'évaluation canonique : "quel pipeline produit le
meilleur ALTO exploitable ?".

Distinct de ``TextView`` (S14)
------------------------------
``TextView`` projette tout vers texte plat et ignore la structure
documentaire.  ``AltoView`` fait l'inverse : exige un ``ALTO_XML``
en entrée et mesure la **fidélité structurelle** (validité,
nombre de lignes, présence des bbox de mots, etc.).

Un même pipeline peut être évalué dans les deux vues.  Le rapport
HTML (S22) présentera les deux côte-à-côte pour qu'un lecteur
comprenne *pourquoi* deux pipelines avec le même CER produisent
des ALTO de qualités différentes.

Pipelines omis explicitement
----------------------------
Un pipeline qui ne produit pas d'``ALTO_XML`` (exemple : Tesseract
texte brut sans ALTO) ne peut pas être évalué dans ``AltoView``.
Le caller (typiquement un service applicatif au S19) doit
**omettre** ce pipeline du résultat ``AltoView`` plutôt que de lui
attribuer un score factice à 0.

Le pattern est démontré dans le test
``tests/evaluation/views/test_sprint_a14_s15_alto_view.py`` :
le caller boucle sur ``[TextView, AltoView]`` et pour chaque vue
filtre les pipelines dont l'artefact n'est pas dans
``view.candidate_types``.

Métriques par défaut
--------------------
- ``alto_validity`` — l'hypothèse est-elle structurellement
  cohérente ? (≥ 1 page, ≥ 1 bloc, ≥ 1 ligne).
- ``alto_line_count_ratio`` — ratio min/max du nombre de lignes.
- ``alto_word_box_coverage`` — fraction des mots qui ont une bbox.

Toutes ∈ [0, 1] avec ``higher_is_better=True``.

Reportées à un sprint suivant
-----------------------------
- ``textline_alignment`` (IoU des bbox de lignes).
- ``reading_order_consistency`` (Kendall tau sur les IDs).
- ``layout_f1`` (ICDAR 2015) via wrapper de
  ``evaluation/metrics/layout.py``.
"""

from __future__ import annotations

from picarones.domain.artifacts import ArtifactType
from picarones.domain.evaluation_spec import EvaluationView


#: Métriques calculées par défaut.  Toutes typées
#: ``(ALTO_XML, ALTO_XML)``.
DEFAULT_ALTO_METRICS: tuple[str, ...] = (
    "alto_validity",
    "alto_line_count_ratio",
    "alto_word_box_coverage",
)


#: Types acceptés.  Volontairement strict : seul ``ALTO_XML``
#: passe.  PAGE_XML pourrait être ajouté via une projection
#: ``page_to_alto`` (post-livraison) si le besoin se présente.
DEFAULT_ALTO_CANDIDATE_TYPES: frozenset[ArtifactType] = frozenset({
    ArtifactType.ALTO_XML,
})


#: Dimensions explicitement non évaluées.
DEFAULT_ALTO_IGNORED_DIMENSIONS: tuple[str, ...] = (
    # Qualité linguistique pure : c'est TextView (S14) qui la mesure.
    "linguistic_quality",
    # Recherchabilité fuzzy : c'est SearchView (S16).
    "search_recall",
    # Hallucinations contenu : c'est HallucinationView (post-S18).
    "content_hallucination",
)


#: Avertissement par défaut affiché en tête du bloc AltoView.
DEFAULT_ALTO_WARNINGS: tuple[str, ...] = (
    "Cette vue mesure la fidélité STRUCTURELLE de l'ALTO produit "
    "(validité, nombre de lignes, bbox).  La qualité TEXTUELLE de "
    "ce qui est dans cet ALTO est mesurée par TextView ; les deux "
    "doivent être lues ensemble pour juger un pipeline.",
    "Les pipelines qui ne produisent pas d'ALTO sont OMIS de cette "
    "vue.  Aucun score factice n'est attribué à un pipeline absent.",
)


def build_alto_view(
    *,
    name: str = "alto_documentary",
    description: str = (
        "Mesure la fidélité structurelle de l'ALTO produit par un "
        "pipeline (validité, lignes, bbox)."
    ),
    metric_names: tuple[str, ...] | None = None,
    extra_warnings: tuple[str, ...] = (),
    extra_ignored_dimensions: tuple[str, ...] = (),
) -> EvaluationView:
    """Construit la vue canonique AltoView.

    Pas de ``candidate_types`` paramétrable (la vue exige par
    nature ALTO_XML uniquement) ni de ``projection``
    (l'évaluation se fait sur l'ALTO tel quel, pas sur sa
    projection).

    Le caller qui veut une vue plus stricte (par exemple "exiger
    aussi le mapping vers une GT_ALTO précise") peut composer
    plusieurs ``AltoView`` paramétrées.
    """
    return EvaluationView(
        name=name,
        description=description,
        candidate_types=DEFAULT_ALTO_CANDIDATE_TYPES,
        projection=None,
        projections_by_source_type={},
        normalization_profile=None,
        metric_names=(
            metric_names if metric_names is not None
            else DEFAULT_ALTO_METRICS
        ),
        warnings=DEFAULT_ALTO_WARNINGS + extra_warnings,
        ignored_dimensions=DEFAULT_ALTO_IGNORED_DIMENSIONS + extra_ignored_dimensions,
    )


__all__ = [
    "build_alto_view",
    "DEFAULT_ALTO_METRICS",
    "DEFAULT_ALTO_CANDIDATE_TYPES",
    "DEFAULT_ALTO_IGNORED_DIMENSIONS",
    "DEFAULT_ALTO_WARNINGS",
]