Files
rpa_vision_v3/tools/anonymize_demo.py
Dom bb1ea42318
Some checks failed
tests / Lint (ruff + black) (push) Failing after 1m49s
tests / Tests unitaires (sans GPU) (push) Failing after 1m53s
tests / Tests sécurité (critique) (push) Has been skipped
feat(tools): add 7 wired+bench utility scripts (A+B classification)
- 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
2026-07-02 13:27:04 +02:00

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()