- A (wired, imports project modules): e2e_map_roles, anonymize_demo, grounding_e2e_resolve_engine - B (orphan projection, standalone benches): enrichment_eval_multi, extract_easily_bench_cases, extract_record_bench_cases, grounding_eval_multi
119 lines
4.9 KiB
Python
119 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""ZIP de démo (Amina + Dom) : capture + JSON de ce que Léa récupère.
|
|
|
|
Règle d'anonymisation (décision Dom 30/06) : on garde TOUT lisible — interface,
|
|
menus, libellés, valeurs cliniques — et on ne masque QUE l'identité directe du
|
|
patient, qui se trouve dans le BANDEAU DU HAUT (titre du dossier / onglets).
|
|
|
|
- Capture : floutage CIBLÉ de la bande supérieure uniquement (top_frac). Le reste
|
|
(menus de navigation, formulaire, valeurs) reste lisible — c'est l'interface
|
|
qu'on apprend et ce qui sert à naviguer.
|
|
- JSON : vraies valeurs des champs (lisibles), + une section `patient` où nom /
|
|
prénom / date de naissance sont remplacés par des tokens.
|
|
|
|
Tourne sur le DGX. Le détail (vraies valeurs) n'est pas affiché par le script —
|
|
seuls des compteurs et la plage Y floutée le sont (pas de PID dans les logs).
|
|
"""
|
|
import argparse
|
|
import json
|
|
import sys
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
from core.llm.ocr_extractor import extract_grid_from_image # noqa: E402
|
|
from core.extraction.role_mapper import tokens_from_grid # noqa: E402
|
|
|
|
from PIL import Image, ImageFilter # noqa: E402
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--image", required=True)
|
|
ap.add_argument("--extraction-json", required=True)
|
|
ap.add_argument("--out", default="/tmp/demo_lecture_ecran.zip")
|
|
ap.add_argument("--top-frac", type=float, default=0.15,
|
|
help="fraction haute de l'écran à flouter (bandeau identité patient)")
|
|
a = ap.parse_args()
|
|
|
|
grid = extract_grid_from_image(a.image)
|
|
tokens = tokens_from_grid(grid)
|
|
fields = json.loads(Path(a.extraction_json).read_text())
|
|
|
|
img = Image.open(a.image).convert("RGB")
|
|
H = img.height
|
|
seuil = int(a.top_frac * H)
|
|
|
|
# Floutage CIBLÉ : uniquement les tokens texte de la bande supérieure
|
|
# (bandeau d'identité patient). Tout le reste reste lisible.
|
|
blurred = 0
|
|
ys = []
|
|
PAD = 2
|
|
for t in tokens:
|
|
if not t.bbox:
|
|
continue
|
|
x0, y0, x1, y1 = t.bbox
|
|
if y0 < seuil: # token dans le bandeau du haut
|
|
xx0 = max(0, x0 - PAD); yy0 = max(0, y0 - PAD)
|
|
xx1 = min(img.width, x1 + PAD); yy1 = min(img.height, y1 + PAD)
|
|
if xx1 > xx0 and yy1 > yy0:
|
|
region = img.crop((xx0, yy0, xx1, yy1)).filter(ImageFilter.GaussianBlur(12))
|
|
img.paste(region, (xx0, yy0))
|
|
blurred += 1
|
|
ys.append(y0)
|
|
|
|
# JSON démo : vraies valeurs des champs + identité patient tokenisée
|
|
demo = {
|
|
"ecran": "Dossier patient — Urgences (DPI réel)",
|
|
"note": "Données cliniques réelles. Identité directe du patient remplacée par des tokens ; le reste est ce que Léa lit tel quel.",
|
|
"patient": {
|
|
"nom": "[nom]",
|
|
"prenom": "[prenom]",
|
|
"date_naissance": "[date de naissance]",
|
|
},
|
|
"champs": [
|
|
{"label": f.get("label"),
|
|
"valeur": f.get("value"),
|
|
"confiance_ocr": round(float(f.get("confidence", 0)), 2),
|
|
"ancre_ocr": bool(f.get("anchored"))}
|
|
for f in fields
|
|
],
|
|
}
|
|
|
|
tmp = Path("/tmp/_demo_build"); tmp.mkdir(exist_ok=True)
|
|
for old in tmp.glob("*"):
|
|
old.unlink()
|
|
cap = tmp / "capture.png"
|
|
img.save(cap)
|
|
js = tmp / "ce_que_lea_recupere.json"
|
|
js.write_text(json.dumps(demo, ensure_ascii=False, indent=2))
|
|
readme = tmp / "LISEZMOI.txt"
|
|
readme.write_text(
|
|
"DÉMO — Lecture d'écran par Léa (RPA 100% vision)\n"
|
|
"================================================\n\n"
|
|
"1) capture.png : un vrai écran de dossier patient (Urgences). Tout est\n"
|
|
" lisible (interface, menus, libellés, valeurs cliniques) ; SEUL le\n"
|
|
" bandeau d'identité du patient (en haut) est flouté.\n\n"
|
|
"2) ce_que_lea_recupere.json : ce que Léa extrait de cet écran. L'OCR fournit\n"
|
|
" les valeurs exactes (vérité), le modèle de vision identifie le RÔLE de\n"
|
|
" chaque champ. Valeurs cliniques réelles ; identité patient = tokens\n"
|
|
" [nom]/[prenom]/[date de naissance]. 0 hallucination (valeur = OCR).\n\n"
|
|
f" {len(demo['champs'])} champs reconnus sur cet écran.\n"
|
|
)
|
|
|
|
with zipfile.ZipFile(a.out, "w", zipfile.ZIP_DEFLATED) as z:
|
|
z.write(cap, cap.name)
|
|
z.write(js, js.name)
|
|
z.write(readme, readme.name)
|
|
|
|
plage = f"{min(ys)}..{max(ys)}px" if ys else "—"
|
|
print(f"# Hauteur image : {H}px | seuil bandeau = {seuil}px (top {a.top_frac:.0%})")
|
|
print(f"# Tokens floutés (bandeau haut) : {blurred} | plage Y : {plage}")
|
|
print(f"# Tokens TOTAL : {len(tokens)} (le reste reste lisible)")
|
|
print(f"# Champs JSON (vraies valeurs) : {len(demo['champs'])}")
|
|
print(f"# ZIP : {a.out}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|