Skip to content

Datashs/galica-postocr

Repository files navigation

Pipeline post-OCR pour corpus historiques Gallica

DOI: 10.5281/zenodo.20112806

Pipeline de normalisation post-OCR pour corpus historiques français du XIXe siècle issus de Gallica.
Développé sur l'Annuaire de l'Institut de droit international (1877), généralisable à tout corpus OCR similaire.
Conçu pour le traitement reproductible des corpus historiques, avec contrôle philologique explicite et validation humaine intégrée.

Fonctionnalités

normalisation OCR ; corrections auditables ; tests ; validation humaine ; rapports détaillés.

Pourquoi ces scripts

Les textes récupérés sur Gallica au format texte brut sont souvent inutilisables directement pour l'analyse. L'OCR produit des erreurs systématiques et prévisibles : apostrophes non standard, tirets typographiques variés, ordinaux mal formés, ligatures manquantes, ponctuations collées, guillemets parasites, chiffres romains déformés. Sur un corpus de 116 000 mots, ces erreurs représentent plusieurs milliers de corrections à apporter.

On pourrait confier ce nettoyage à un grand modèle de langage. On ne le fait pas. Voici pourquoi.

Ce que fait un LLM : il produit un résultat visuellement convaincant en faisant des choix dont la logique est opaque. On ne sait pas exactement ce qu'il a changé, pourquoi, ni s'il a introduit des erreurs en corrigeant d'autres. Sur un corpus destiné à la recherche, c'est inacceptable.

Ce que font ces scripts : chacun fait une chose précise, documentée, vérifiable. Chaque règle a été testée sur le corpus réel. Les faux positifs ont été comptés et documentés dans le code. Les règles trop dangereuses ont été supprimées avec explication. Le résultat est reproductible et auditable — on peut retracer chaque modification.

C'est ce qu'on pourrait appeler une philologie computationnelle explicite : les opérations sont visibles, les décisions sont justifiées, les limites sont nommées.

Cette démarche a aussi une dimension pédagogique explicite. Ces scripts sont conçus pour être lus autant que pour être utilisés. Chaque règle conservée dans le code est documentée — avec les cas qu'elle traite, les cas qu'elle ne traite pas, et les raisons pour lesquelles certaines règles initialement envisagées ont été abandonnées. Un étudiant ou un thésard en histoire qui ouvre 08_abrev.py trouvera non seulement le code, mais l'explication de pourquoi parpar. a été supprimé (927 faux positifs, 0 vrai positif sur le corpus de test), pourquoi cl n'est pas traité comme une abréviation (c'est une erreur OCR pour et dans ce corpus), et ce que cela implique pour les décisions à prendre sur un autre corpus.

L'enjeu n'est pas d'apprendre Python. C'est d'apprendre à travailler son matériau — à ne pas déléguer les choix techniques à un outil dont on ne comprend pas les décisions, à documenter ce qu'on a fait et pourquoi, à distinguer ce qu'on sait de ce qu'on suppose. Ce sont les mêmes exigences que la critique des sources, appliquées à l'outillage numérique.

Cette approche s'inscrit dans une réflexion plus large sur ce que devrait être la formation des historiens à l'ère des grands modèles de langage. La tentation est forte de confier le nettoyage, l'extraction, l'analyse à des outils puissants et accessibles — et les résultats sont souvent visuellement convaincants. Mais un résultat convaincant n'est pas un résultat contrôlé. Sur un corpus destiné à la recherche, la différence est essentielle : ce qu'on ne peut pas expliquer, on ne peut pas publier.


Ce que le pipeline ne prétend pas faire

  • Corriger toutes les erreurs OCR — seulement celles qui sont systématiques et prévisibles
  • Remplacer la relecture humaine
  • Fonctionner sans ajustement sur n'importe quel corpus XIXe

Les scripts 15 et 16 intègrent une validation humaine obligatoire à chaque cycle. Ce n'est pas un défaut de conception — c'est le moment où le jugement disciplinaire de l'historien entre dans la boucle, là où aucun outil ne peut le remplacer. Le script 17 articule deux modes de supervision (ex-post et ex-ante) en fonction de la valeur de la distance de Levenshtein.


Structure du projet

PostOCR/ scripts/ ← scripts de normalisation 01Normalise.py 02apost.py 03tirets.py 04_controle.py 05_espaces.py 06_ordinaux.py 07_mois.py 08_abrev.py 09_ponctuation.py 10_virgules.py 11_romains.py 12_refs.py 13_guillemets.py 14_ligatures.py 15_decoupage.py 16_inconnus.py 17_levenshtein.py postocr.py ← pipeline complet (scripts 02-14)

    test_corpus.py            ← audit sur un corpus utilisateur
    Lexiq/
        lefff_formes.txt      ← dictionnaire Lefff (à télécharger séparément)
corpus/
    raw/                      ← fichiers OCR Gallica bruts
        mondoc.txt
    processed/                ← sorties du pipeline
        
    rapports/                 ← rapports de modifications (.md)
modeles/                      ← modèles d'apprentissage scripts 15 et 16
    modele_decoupe.json
    modele_formes_inconnues.json

Convention de nommage : AAAA_nom.txt pour les fichiers corpus (ex : 1877_jette.txt).


Dépendances

Python 3.8 ou supérieur. Aucune bibliothèque externe pour les scripts 02 à 14 — stdlib uniquement.

Les scripts 10, 15 et 16 nécessitent le dictionnaire Lefff (Lexique des Formes Fléchies du Français, environ 110 000 entrées). À télécharger séparément et placer dans scripts/Lexiq/lefff_formes.txt.

Les scripts 15 et 16 acceptent optionnellement langid pour la détection de langue sur les corpus multilingues : Le script 17 nécessite l'usage de langid pour éviter la multiplication des faux-positifs.

bash
pip install langid

Si langid n'est pas installé, les scripts 14, 15 et 16 fonctionnent avec des heuristiques de repli.


Mode d'emploi

Préalable — Validation et audit sur un corpus utilisateur

Le dépôt inclut un script test_corpus.py permettant d’évaluer le comportement du pipeline sur un corpus utilisateur. Défini à partir d'un matériau qui a ses particularités propres il peut s'avérer moins utile en d'autres contextes, ou nécessitant des modifications.

Marche à suivre

bash
python scripts/test_corpus.py corpus/raw/votre_corpus.txt

Pour obtenir un audit détaillé des corrections appliquées :

bash
python scripts/test_corpus.py corpus/raw/votre_corpus.txt --audit

Pour limiter le nombre d’exemples affichés :

bash
python scripts/test_corpus.py corpus/raw/votre_corpus.txt --audit --max 30

Fonctions

Ce script permet notamment :

  • la vérification de l’intégrité structurelle des fichiers ;
  • le contrôle de la préservation des paragraphes ;
  • l’inspection des corrections appliquées ;
  • l’identification de transformations potentiellement problématiques ;
  • un audit manuel sur corpus réel.

L’objectif n’est pas de proposer une suite de tests unitaires exhaustive, mais un outil d’évaluation méthodologique adapté aux usages des humanités numériques et au contrôle philologique des corrections post-OCR.

Étape 1 — 01Normalise.py — normalisation Unicode

Ce script applique une normalisation Unicode NFC à l’ensemble du corpus.

Cette étape garantit une représentation cohérente des caractères accentués, ligatures et diacritiques avant l’application des règles post-OCR.

Elle permet notamment :

  • l’uniformisation des caractères composés ;
  • la stabilisation des expressions régulières ;
  • la cohérence des traitements lexicaux et statistiques ;
  • la réduction des ambiguïtés liées aux encodages OCR.

Cette opération constitue un pré-traitement fondamental du pipeline et doit être exécutée avant les autres scripts.

Étape 2 — Corrections déterministes (scripts 02-14)

Orchestrateur : postocr.py

Il est possible de lancer les scripts individuellement ou bien d'utiliser l'orchestrateur postocr

Le script postocr.py constitue le point d’entrée principal du pipeline de post-traitement OCR.

Il applique automatiquement les scripts de normalisation déterministes (02 à 14, hors ancien script 10) sur un corpus texte brut issu de Gallica ou d’un autre OCR historique.

Le pipeline produit :

  • un fichier texte normalisé ;
  • un rapport Markdown documentant les transformations appliquées ;
  • un résumé du nombre de corrections par étape ;
  • des exemples contextualisés de modifications.

Exemple :

bash
python scripts/postocr.py corpus/raw/mon_corpus.txt

Avec rapport détaillé :

bash
python scripts/postocr.py corpus/raw/mon_corpus.txt --rapport

Le pipeline applique successivement :

Étape Fonction
01 normalisation Unicode
02–14 corrections déterministes
15–16 validation semi-automatique manuelle
17 rapprochement probabiliste par distance lexicale

Les scripts 15 et 16 restent volontairement interactifs afin de préserver le contrôle philologique des corrections ambiguës.

Étape 3 — validation interactive (scripts 15-16)

Le script 15 détecte les mots fusionnés par l'OCR (ledroitle droit) et propose des découpages à valider.

bash
python scripts/15_decoupage.py corpus/processed/1877_jette_postocr.txt

Le script exporte un fichier TSV à valider dans Numbers ou Excel :

  • y — découpe correcte
  • n — faux positif, ne plus proposer ce mot
  • c — découpe incorrecte, saisir la bonne dans la colonne correction
  • ? — incertain, reviendra au cycle suivant

Appuyer sur Entrée une fois le fichier validé. Répéter jusqu'à satisfaction. Le modèle d'apprentissage est sauvegardé dans modeles/modele_decoupe.json et persiste entre les sessions.

Le script 16 détecte les tokens absents du Lefff qui apparaissent plusieurs fois — probablement des erreurs OCR systématiques (congréscongrès).

bash
python scripts/16_inconnus.py corpus/processed/1877_jette_postocr.txt

Même logique de validation que le script 15. Pour chaque forme marquée y, saisir la correction dans la colonne correction.

Étape 4 — Correction probabiliste (script 17)

Script 17 — correction probabiliste des hapax

Le script 17 introduit un troisième régime de correction fondé sur la distance de Damerau-Levenshtein appliquée aux hapax absents du Lefff.

Contrairement aux scripts 02–14 (règles déterministes) et aux scripts 15–16 (validation humaine cas par cas), ce script distingue deux niveaux de confiance :

  • corrections à distance 1 : appliquées automatiquement avec audit post-hoc ;
  • corrections à distance 2 : soumises à une décision globale du chercheur après inspection d’un échantillon représentatif.

Le script formalise ainsi explicitement différents régimes de supervision humaine selon le niveau d’incertitude algorithmique.


Description des scripts

|--------|----------|:---:|:---:|

Script Fonction Corrections sur jette (1877) Faux positifs
test_corpus.py Audit du corpus sur lequel vous souhaitez tester le pipeline
postocr.py Orchestrateur du pipeline (scripts 02–14)
01Normalise.py Normalisation Unicode — préalable à tout traitement
02apost.py Apostrophes non standard → U+0027 0 0
03tirets.py Tirets U+2013/2014/2212 → tiret ASCII 2 841 0
04_controle.py Caractères de contrôle, BOM 1 0
05_espaces.py Espaces multiples et spéciaux 0 0
06_ordinaux.py 1ere→1re, 2me→2e, 3me→3e… 285 0
07_mois.py Mois avec majuscule → minuscule 107 0
08_abrev.py M→M., Dr→Dr., pp→pp., etc. 113 0
09_ponctuation.py Espaces autour de :;!? 1 217 0
10_virgules.py Virgules collées (filtre Lefff) ~45 0
11_romains.py Vil→VII, T. Il→T. II, T. Vit→T. VII 12 0
12_refs.py T.VI→T. VI, pp.N→pp. N, et ss,→et ss. 34 0
13_guillemets.py Guillemets droits parasites OCR 36 0
14_ligatures.py oeuvre→œuvre, voeu→vœu, coeur→cœur 79 0
15_decoupage.py Mots collés — cycle interactif variable ~25%
16_inconnus.py Formes inconnues — cycle interactif variable variable
17_levenshtein.py Correction probabiliste des hapax variable variable

Script 10 : nécessite le Lefff. Sans le dictionnaire, retourne le texte inchangé avec un avertissement.

Scripts 15 et 16 : les faux positifs sont gérés par la validation humaine — le modèle apprend à ne plus les proposer.


Choix techniques

Pourquoi pas de règle générale pour la virgule (script 09)

Le script 09 traite les ponctuations doubles (:;!?) qui ont des règles typographiques uniformes en français. La virgule a été volontairement exclue : ses exceptions sont trop nombreuses (nombres décimaux, abréviations, listes bibliographiques). Le script 10 traite séparément le cas mot,mot avec le filtre Lefff comme garde-fou.

Pourquoi le Lefff plutôt qu'un modèle de langue

Le Lefff est un lexique de référence : chaque entrée a été vérifiée. Il ne fait pas de généralisation probabiliste. Sur un corpus du XIXe siècle avec des noms propres étrangers (Rolin-Jaequemyns, Holtzendorff, Mancini), un modèle de langue ferait des suppositions difficiles à contrôler. Le Lefff dit exactement ce qu'il sait et rien d'autre.

Pourquoi les modèles JSON sont cumulatifs (scripts 15 et 16)

Sur quarante annuaires similaires, les erreurs OCR sont souvent les mêmes d'un volume à l'autre. Un modèle cumulatif signifie que les décisions prises sur l'annuaire 1877 s'appliquent automatiquement sur les suivants — sans revalider ce qu'on a déjà traité.

Ce qui a été exclu et pourquoi

  • Script 10 original (points de suspension) : exclu définitivement — les points de conduite dans les tableaux et tables des matières seraient détruits.
  • Ligature æ : désactivée — tous les ae du corpus sont des noms propres flamands (Jaequemyns ×40).
  • parpar. comme abréviation : 927 faux positifs sur le corpus de test, 0 vrai positif. Supprimé.
  • Correction des coupures de mots en fin de ligne : nécessite une validation humaine ligne par ligne — trop hétérogène pour être automatisé de façon sûre.

Paramètres configurables

Chaque script expose ses paramètres ajustables en tête de fichier, avant tout le code. Les principaux :

06_ordinaux.pyroman=False par défaut : les ordinaux romains (XIXme) ne sont pas corrigés sans activer explicitement ce mode.

10_virgules.pyMIN_LONGUEUR = 2 : longueur minimale des tokens traités. Règle complémentaire : si les deux tokens font ≤ 2 chars simultanément, la virgule n'est pas corrigée (de,la reste intact).

15_decoupage.pySEUIL_MIN, LIMITE_EXPORT, NB_CYCLES_MAX, PREFIXE_SORTIE.

16_inconnus.pySEUIL_MIN = 2, SEUIL_MAX = 10 : seules les formes apparaissant entre 2 et 10 fois sont proposées. En dessous : trop de bruit. Au-delà : probablement un terme du domaine.


Corpus de développement

Annuaire de l'Institut de droit international, première année, 1877.
Source : Gallica BnF — OCR texte brut.
763 276 caractères, ~116 000 mots, 4 920 paragraphes.

L'Institut de droit international est une organisation savante fondée en 1873, réunissant des juristes internationaux pour codifier le droit international. L'Annuaire contient les statuts, les travaux des sessions, un tableau chronologique des faits internationaux, les textes de traités, et une bibliographie. Structure en cinq parties stables d'un volume à l'autre — ce qui rend le corpus particulièrement adapté à une généralisation sur les quarante volumes de la collection. Pour une étude de l'institution exploitant ses annuaires voir Rygiel, Philippe. L'ordre des circulations?. L'Institut de Droit international et la régulation des migrations (1870-1920). Sorbonne (Editions de la Sorbonne), 2021.'


Licence

Scripts : MIT.
Corpus OCR Gallica : domaine public (documents antérieurs à 1900).
Lefff : licence LGPLLR — voir la documentation du Lefff.