feat(ui): calibration visuelle des zones via dessin à la souris
Nouveau module pipeline/zones_config.py : charge les zones d'extraction depuis un fichier zones_config.json (coordonnées relatives 0-1), avec fallback sur les constantes Python. Config partagée entre : - pipeline/extract.py (crop colonne Recodage) - pipeline/checkboxes.py (cases Accord/Désaccord) Zones configurables aujourd'hui (page recueil) : - codage_reco (crop zonal pour le second passage VLM) - accord_checkbox / desaccord_checkbox (densité de pixels) Mode "🔧 Calibration zones" ajouté dans pipeline/ui_overlay.py : - Sélection d'un PDF de référence (idéalement bien cadré) - Canvas interactif (streamlit-drawable-canvas) avec les zones existantes pré-dessinées en rouge - Dessin/déplacement/redimensionnement à la souris - Saisie d'un nom et description par zone - Sauvegarde en JSON (ou OGC_ZONES_CONFIG si défini) Permet au métier (Khalid) de recalibrer les zones sans toucher au code, par exemple si le formulaire ATIH évolue ou si les scans sont d'un autre établissement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,8 @@ from .prompts import (
|
||||
PAGE_TYPES, PROMPT_HEADER,
|
||||
SCHEMA_RECUEIL_RECODAGE, RECUEIL_RECODAGE_ZONE,
|
||||
)
|
||||
from .checkboxes import detect_accord_desaccord, RECUEIL_ACCORD_DESACCORD, parse_ghs_injustifie
|
||||
from .checkboxes import detect_accord_desaccord, RECUEIL_ACCORD_DESACCORD, parse_ghs_injustifie, CheckboxZones
|
||||
from .zones_config import load_config, get_zone
|
||||
from .validation import annotate as validate_annotate
|
||||
|
||||
|
||||
@@ -108,6 +109,23 @@ def parse_json_output(raw: str) -> dict | None:
|
||||
return {"_raw": raw, "_parse_error": str(e)}
|
||||
|
||||
|
||||
def _recueil_zones() -> tuple[tuple, CheckboxZones]:
|
||||
"""Charge les zones configurables pour la page recueil.
|
||||
|
||||
Retourne (recodage_zone, accord_desaccord_zones). Si la config n'a pas
|
||||
d'entrée, on retombe sur les constantes compilées.
|
||||
"""
|
||||
cfg = load_config()
|
||||
reco = get_zone("recueil", "codage_reco", cfg) or RECUEIL_RECODAGE_ZONE
|
||||
acc = get_zone("recueil", "accord_checkbox", cfg)
|
||||
des = get_zone("recueil", "desaccord_checkbox", cfg)
|
||||
if acc and des:
|
||||
cb = CheckboxZones(accord=acc, desaccord=des)
|
||||
else:
|
||||
cb = RECUEIL_ACCORD_DESACCORD
|
||||
return reco, cb
|
||||
|
||||
|
||||
def _extract_recodage_crop(image_path: Path, ocr: QwenVLOCR) -> dict | None:
|
||||
"""Second passage VLM sur le crop zonal de la colonne Recodage.
|
||||
|
||||
@@ -122,7 +140,8 @@ def _extract_recodage_crop(image_path: Path, ocr: QwenVLOCR) -> dict | None:
|
||||
try:
|
||||
img = Image.open(image_path)
|
||||
w, h = img.size
|
||||
x1, y1, x2, y2 = RECUEIL_RECODAGE_ZONE
|
||||
reco_zone, _ = _recueil_zones()
|
||||
x1, y1, x2, y2 = reco_zone
|
||||
crop = img.crop((int(x1 * w), int(y1 * h), int(x2 * w), int(y2 * h)))
|
||||
crop_path = image_path.parent / f"{image_path.stem}_recodage.png"
|
||||
crop.save(crop_path)
|
||||
@@ -267,7 +286,8 @@ def extract_dossier(pdf_path: str | Path, verbose: bool = True,
|
||||
# sur la fiche recueil. GLM-OCR / Qwen ne lisent pas les cases
|
||||
# à cocher (cf. scratch/test_prompt_crop_v2.py).
|
||||
if ptype == "recueil" and isinstance(parsed, dict):
|
||||
cb = detect_accord_desaccord(img_path, RECUEIL_ACCORD_DESACCORD)
|
||||
_, cb_zones = _recueil_zones()
|
||||
cb = detect_accord_desaccord(img_path, cb_zones)
|
||||
parsed["accord_desaccord"] = cb["decision"]
|
||||
parsed["_checkbox_debug"] = cb # ratios + diff pour audit
|
||||
# ghs_injustifie : Qwen renvoie parfois "0 SE 1 2 3 4 ATU FFM FSD"
|
||||
|
||||
Reference in New Issue
Block a user