Spaces:
Sleeping
Sleeping
| # Snapshots de reproductibilité | |
| ## Pourquoi des snapshots ? | |
| Pour qu'un benchmark Picarones soit **citable scientifiquement**, un | |
| lecteur doit pouvoir, des années plus tard, comprendre exactement | |
| *ce qui a été mesuré*. Un rapport HTML qui dit « Tesseract 5.3.4 | |
| obtient un CER de 4,2 % sur ce corpus » est inutilisable s'il | |
| n'indique pas : | |
| - la **table de pricing** utilisée (qui a évolué entre temps), | |
| - la **version exacte des prompts** appliqués aux pipelines OCR+LLM, | |
| - le **profil de normalisation** effectivement appliqué (avec ses | |
| équivalences diplomatiques), | |
| - le **commit Picarones** utilisé pour produire le rapport, | |
| - les **paquets Python** installés au moment du run. | |
| Le module `picarones.reports.html.snapshot` agrège ces cinq dimensions et | |
| les embarque **dans le JSON du rapport**, sous la clé | |
| `report_data["snapshots"]`. Le rapport HTML reste auto-portant : un | |
| lecteur peut tout retrouver sans accès au repo source. | |
| ## Ce qu'un snapshot contient | |
| ```python | |
| from picarones.reports.html.snapshot import snapshot_all | |
| snap = snapshot_all( | |
| lang="fr", | |
| normalization_profile=profile, # Profile dataclass ou None | |
| ) | |
| ``` | |
| Retourne un dict avec quatre clés top-level : | |
| | Clé | Contenu | Source | | |
| |---|---|---| | |
| | `pricing` | YAML brut intégral de `picarones/data/pricing.yaml` | `pricing_snapshot()` | | |
| | `glossary` | Entrées du glossaire effectivement référencées dans la synthèse (langue rendue) | `glossary_snapshot()` | | |
| | `normalization` | Profil sérialisé (`diplomatic_table`, `exclude_chars`, drapeaux NFC/case-folding…) | `normalization_snapshot()` | | |
| | `environment` | Version Picarones, Python, plateforme OS, commit git, paquets installés (top 200) | `environment_snapshot()` | | |
| ### `pricing` | |
| Le YAML est embarqué **verbatim**. Si le tarif d'OpenAI change demain, | |
| le rapport d'aujourd'hui reste lisible — l'analyse Pareto coût garde | |
| sa valeur historique car elle pointe vers la table effectivement | |
| utilisée. | |
| ### `glossary` | |
| Pas le glossaire complet (~25 entrées) — seulement celles qui sont | |
| effectivement référencées par la synthèse narrative ou les vues | |
| ouvertes au moment du snapshot. Économie de poids ; un ancien rapport | |
| reste documenté même si le glossaire évolue. | |
| ### `normalization` | |
| Le profil contient : | |
| ```yaml | |
| name: medieval_french | |
| nfc: true | |
| casefold: false | |
| diplomatic_table: | |
| ſ: s | |
| u: v | |
| i: j | |
| ꝑ: per | |
| ⁊: et | |
| exclude_chars: | |
| - "·" | |
| - "¶" | |
| ``` | |
| Permet à un relecteur de comprendre exactement quelles équivalences | |
| ont été appliquées au moment du calcul de CER, **sans relancer**. | |
| ### `environment` | |
| ```yaml | |
| picarones_version: "0.9.0" | |
| python_version: "3.11.13" | |
| platform: "Linux 6.18.5-x86_64-with-glibc2.39" | |
| git_commit: "17cc5474..." | |
| installed_packages: | |
| - "click==8.3.3" | |
| - "defusedxml==0.7.1" | |
| - "jiwer==3.1.0" | |
| # ... top 200 trié alpha | |
| ``` | |
| Le `git_commit` est lu via `git rev-parse HEAD` (subprocess timeout 2 s, | |
| sans shell). Si le repo n'est pas un dépôt git, la clé reste `None`. | |
| ## Comment rejouer un benchmark à 5 ans d'écart | |
| ### Étape 1 — Récupérer le commit Picarones d'origine | |
| Dans le rapport HTML, ouvrir la console JS et inspecter `DATA.snapshots.environment.git_commit` : | |
| ```javascript | |
| > DATA.snapshots.environment.git_commit | |
| "17cc5474abc..." | |
| ``` | |
| Puis : | |
| ```bash | |
| git clone https://github.com/maribakulj/Picarones.git | |
| cd Picarones | |
| git checkout 17cc5474abc | |
| ``` | |
| ### Étape 2 — Récréer l'environnement Python | |
| Picarones livre des lock files : | |
| ```bash | |
| python -m venv .venv && source .venv/bin/activate | |
| pip install -r requirements-dev.lock | |
| ``` | |
| Si vous avez besoin d'une version Python différente (par exemple un | |
| ancien rapport rendu en Python 3.11.10), utiliser pyenv : | |
| ```bash | |
| pyenv install 3.11.10 | |
| pyenv local 3.11.10 | |
| ``` | |
| ### Étape 3 — Récréer le corpus + GT | |
| Picarones ne stocke **pas** les images du corpus dans le snapshot | |
| (les images appartiennent au déposant). Il faut donc re-récupérer le | |
| corpus original. Pour les imports IIIF, le manifeste est durable | |
| (les bibliothèques nationales versionnent) ; pour les uploads ZIP, | |
| l'utilisateur doit conserver son archive source. | |
| Si le corpus a déjà été chargé dans Gallica / HTR-United, le | |
| `metadata.source_url` de chaque `Document` permet le re-fetch. | |
| ### Étape 4 — Rejouer le benchmark | |
| ```bash | |
| picarones run \ | |
| --corpus ./corpus_recovered/ \ | |
| --engines tesseract,pero_ocr \ | |
| --output rerun.json \ | |
| --normalization medieval_french | |
| ``` | |
| ### Étape 5 — Vérifier la concordance | |
| Le commit + le lock file + le profil de normalisation garantissent | |
| que les métriques **CER / WER / MER / WIL** seront bit-à-bit | |
| identiques. | |
| Différences possibles légitimes : | |
| - L'OCR cloud (Mistral / Google / Azure) peut avoir évolué côté | |
| serveur — les chiffres peuvent diverger même avec un client | |
| identique. Pour un benchmark scientifiquement reproductible, | |
| privilégier **Tesseract / Pero OCR** (modèles versionnés et locaux). | |
| - Les LLMs évoluent constamment ; un pipeline OCR+LLM rejoué 6 mois | |
| plus tard peut donner d'autres résultats. Le snapshot des prompts | |
| reste utile mais ne reproduit pas le LLM lui-même. | |
| ## Snapshot et publication scientifique | |
| Pour un papier scientifique, citer Picarones doit indiquer : | |
| ```bibtex | |
| @misc{picarones_2026, | |
| title = {Picarones: Heritage OCR/HTR/VLM Benchmarking Platform}, | |
| author = {<auteurs>}, | |
| year = {2026}, | |
| doi = {10.5281/zenodo.<id>}, | |
| version= {1.1.0}, | |
| url = {https://github.com/maribakulj/Picarones} | |
| } | |
| ``` | |
| Et dans le matériel supplémentaire, joindre : | |
| 1. Le rapport HTML autonome (qui contient tout le snapshot). | |
| 2. `requirements-dev.lock` du moment du benchmark (pour ré-instancier | |
| l'environnement). | |
| 3. Le digest Docker si vous avez utilisé l'image officielle : | |
| `docker inspect ghcr.io/maribakulj/picarones:1.1.0 --format='{{.Id}}'`. | |
| Un évaluateur scientifique disposera ainsi des éléments matériels pour | |
| vérifier l'analyse — c'est l'exigence minimale d'une publication | |
| reproductible (cf. Stodden et al., *Computational reproducibility*). | |
| ## Limites assumées | |
| - **Le code source n'est pas embarqué dans le snapshot**. On embarque | |
| *l'identifiant* du commit, pas le diff. Si le repo est rendu | |
| privé ou supprimé, le snapshot devient orphelin. Mitigation : | |
| publier les versions sur Zenodo (DOI durable, garanti 20 ans). | |
| - **Les images du corpus** ne sont pas snapshottées (poids + droits | |
| d'auteur). Le déposant doit conserver son corpus. | |
| - **Le LLM cloud** ne peut pas être snapshotté — c'est une dépendance | |
| externe non reproductible. Pour la science, préférer les modèles | |
| ouverts (Llama, Phi, Mistral via Ollama). | |
| ## Tests | |
| `tests/report/test_reproducibility_snapshots.py` | |
| valide que `snapshot_all()` est : | |
| - déterministe (même input → même bytes en sortie), | |
| - complet (toutes les clés top-level présentes), | |
| - robuste (ne crashe pas si git absent, si pricing.yaml manquant…). | |
| `tests/test_reproducibility_ops.py` ajoute la validation | |
| de la chaîne **lock file + Docker digest + snapshot** comme contrat | |
| opérationnel. | |