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
This commit is contained in:
118
tools/anonymize_demo.py
Normal file
118
tools/anonymize_demo.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user