feat(deskew): correction automatique du skew au chargement des PDFs
Nouveau module pipeline/deskew.py basé sur cv2.HoughLinesP : - détecte les lignes quasi-horizontales (±15° de l'horizontale) - prend la médiane de leurs angles (robuste aux outliers) - seuils : |angle|>0.3° pour corriger, |angle|>10° = suspect (on ne corrige pas) - PIL.rotate() avec BICUBIC + fillcolor blanc, sans expand Intégré dans pipeline/ingest.py (paramètre `deskew=True` par défaut). L'angle appliqué est tracé dans un fichier `page_XX.skew` à côté de l'image, pour audit. Mesuré sur les 18 dossiers de l'échantillon 2018 CARC : seule OGC 1 a un skew au-dessus du seuil (+0.91°), les 17 autres sont déjà droits. Le deskew corrige OGC 1 en 0.00° résiduel (vérif visuelle en-tête OK). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
"""PDF → images PNG 300 dpi avec cache par hash SHA256."""
|
||||
"""PDF → images PNG 300 dpi avec cache par hash SHA256.
|
||||
|
||||
Applique optionnellement un deskew automatique (redressement) sur chaque page
|
||||
pour corriger le biais d'inclinaison des scans. Voir pipeline/deskew.py.
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from pdf2image import convert_from_path
|
||||
from PIL import Image
|
||||
|
||||
from .deskew import deskew_image, MIN_ANGLE_DEG
|
||||
|
||||
DEFAULT_DPI = 300
|
||||
CACHE_ROOT = Path(".cache/images")
|
||||
|
||||
@@ -18,23 +24,37 @@ def pdf_hash(pdf_path: str) -> str:
|
||||
return h.hexdigest()[:16]
|
||||
|
||||
|
||||
def pdf_to_images(pdf_path: str, dpi: int = DEFAULT_DPI, cache_root: Path = CACHE_ROOT) -> list[Path]:
|
||||
def pdf_to_images(pdf_path: str, dpi: int = DEFAULT_DPI,
|
||||
cache_root: Path = CACHE_ROOT,
|
||||
deskew: bool = True) -> list[Path]:
|
||||
"""Convertit un PDF en PNG 300 dpi. Retourne la liste des chemins (1 par page).
|
||||
|
||||
Le cache est indexé par hash du PDF : un PDF inchangé n'est jamais reconverti.
|
||||
|
||||
Avec `deskew=True` (défaut), chaque page est redressée si son angle de skew
|
||||
dépasse le seuil défini dans `pipeline.deskew.MIN_ANGLE_DEG` (0.3°). L'angle
|
||||
appliqué est persisté dans un fichier `<page>.skew` à côté (pour audit).
|
||||
"""
|
||||
cache_root = Path(cache_root)
|
||||
h = pdf_hash(pdf_path)
|
||||
out_dir = cache_root / h
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
existing = sorted(out_dir.glob("page_*.png"))
|
||||
# Le glob est strict pour ne pas attraper les crops intermédiaires
|
||||
# (page_XX_recodage.png, etc.)
|
||||
existing = sorted(p for p in out_dir.glob("page_*.png")
|
||||
if p.stem.replace("page_", "").isdigit())
|
||||
if existing:
|
||||
return existing
|
||||
|
||||
pages = convert_from_path(pdf_path, dpi)
|
||||
paths = []
|
||||
for i, img in enumerate(pages, 1):
|
||||
if deskew:
|
||||
img, applied = deskew_image(img)
|
||||
if applied != 0.0:
|
||||
# Trace d'audit : on note l'angle corrigé
|
||||
(out_dir / f"page_{i:02d}.skew").write_text(f"{applied:.3f}\n")
|
||||
p = out_dir / f"page_{i:02d}.png"
|
||||
img.save(p, "PNG", optimize=True)
|
||||
paths.append(p)
|
||||
|
||||
Reference in New Issue
Block a user