Spaces:
Sleeping
Sleeping
File size: 6,014 Bytes
ad8d926 | 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 153 154 155 156 157 158 159 160 161 162 163 164 | """Métriques natives enregistrées dans le registre typé (Sprint 34).
Ce module est un démonstrateur d'enregistrement : il expose les
métriques scalaires existantes (CER, WER, MER, WIL) sous une forme
unitaire dans le registre, plus un stub typé hétérogène pour les
jonctions ``(TEXT, ALTO)``.
L'import du module suffit à peupler le registre — le décorateur
``@register_metric`` s'exécute à l'import. Les sprints suivants (axe A
du plan d'évolution) ajouteront ici les métriques structurelles
(``reading_order_f1``, ``layout_f1``), philologiques (``unicode_block_*``,
``mufi_coverage``), et de fiabilité (``ece``, ``mce``).
Important — pas de double calcul
-------------------------------
Ces wrappers ne **remplacent pas** ``compute_metrics`` du module
``metrics.py``. Ils existent pour les nouveaux chemins (pipelines
composées qui calculent par jonction). Le rapport HTML existant
continue à passer par ``compute_metrics`` et reste donc strictement
identique octet par octet (critère de la Phase 0.3).
"""
from __future__ import annotations
import logging
from picarones.evaluation.metric_registry import register_metric
from picarones.domain.artifacts import ArtifactType
logger = logging.getLogger(__name__)
try:
import jiwer
_JIWER_AVAILABLE = True
except ImportError:
_JIWER_AVAILABLE = False
# ──────────────────────────────────────────────────────────────────────────
# Métriques scalaires (TEXT, TEXT) — wrappers fins autour de jiwer
# ──────────────────────────────────────────────────────────────────────────
def _safe_jiwer_call(fn, reference: str, hypothesis: str) -> float:
"""Wrapper qui gère les cas dégénérés (références ou hypothèses vides)."""
if not _JIWER_AVAILABLE:
raise RuntimeError(
"jiwer n'est pas installé — installer avec `pip install jiwer`"
)
if not reference:
return 0.0 if not hypothesis else 1.0
if not hypothesis:
return 1.0
return fn(reference, hypothesis)
@register_metric(
name="cer",
input_types=(ArtifactType.TEXT, ArtifactType.TEXT),
description="Character Error Rate (distance d'édition normalisée par la longueur de la GT).",
higher_is_better=False,
tags={"text", "edit_distance", "error_rate"},
)
def cer(reference: str, hypothesis: str) -> float:
"""CER brut sur les caractères, via jiwer."""
return _safe_jiwer_call(jiwer.cer, reference, hypothesis)
@register_metric(
name="wer",
input_types=(ArtifactType.TEXT, ArtifactType.TEXT),
description="Word Error Rate.",
higher_is_better=False,
tags={"text", "edit_distance", "error_rate"},
)
def wer(reference: str, hypothesis: str) -> float:
"""WER brut, via jiwer."""
return _safe_jiwer_call(jiwer.wer, reference, hypothesis)
@register_metric(
name="mer",
input_types=(ArtifactType.TEXT, ArtifactType.TEXT),
description="Match Error Rate (jiwer).",
higher_is_better=False,
tags={"text", "error_rate"},
)
def mer(reference: str, hypothesis: str) -> float:
return _safe_jiwer_call(jiwer.mer, reference, hypothesis)
@register_metric(
name="wil",
input_types=(ArtifactType.TEXT, ArtifactType.TEXT),
description="Word Information Lost (jiwer).",
higher_is_better=False,
tags={"text", "error_rate"},
)
def wil(reference: str, hypothesis: str) -> float:
return _safe_jiwer_call(jiwer.wil, reference, hypothesis)
# ──────────────────────────────────────────────────────────────────────────
# Métrique typée hétérogène (TEXT, ALTO) — stub démonstrateur
# ──────────────────────────────────────────────────────────────────────────
@register_metric(
name="text_preservation_after_reconstruction",
input_types=(ArtifactType.TEXT, ArtifactType.ALTO),
description=(
"Taux de tokens de la GT texte présents dans le texte extrait de "
"l'ALTO produit (preuve de concept ; remplaçable par une mesure "
"alignée par les sprints futurs)."
),
higher_is_better=True,
tags={"structure", "preservation", "stub"},
)
def text_preservation_after_reconstruction(
reference_text: str,
hypothesis_alto: str,
) -> float:
"""Stub démonstrateur d'une jonction texte → ALTO.
Sprints à venir (axe A du plan d'évolution) remplaceront cette
implémentation par une vraie mesure de préservation : extraction
structurée du texte ALTO via le parser dédié, alignement, calcul
déterministe. Pour l'instant la mesure est volontairement simple
pour démontrer le mécanisme.
Parameters
----------
reference_text:
Texte GT (niveau ``ArtifactType.RAW_TEXT``).
hypothesis_alto:
ALTO XML brut produit par un module de reconstruction (niveau
``ArtifactType.ALTO``).
Returns
-------
float
Taux de tokens uniques de ``reference_text`` apparaissant dans
``hypothesis_alto`` (case-insensitive). ``1.0`` = tous les
tokens préservés.
"""
if not reference_text:
return 1.0
ref_tokens = {tok.lower() for tok in reference_text.split() if tok}
if not ref_tokens:
return 1.0
alto_text = hypothesis_alto.lower()
preserved = sum(1 for tok in ref_tokens if tok in alto_text)
return preserved / len(ref_tokens)
__all__ = [
"cer",
"wer",
"mer",
"wil",
"text_preservation_after_reconstruction",
]
|