feat(splash): afficher les étapes de chargement dans le splash
Demande utilisateur : voir défiler les étapes (chargement des dictionnaires,
des modèles...) dans le splash au démarrage — effet pro apprécié des clients.
Implémentation :
- Nouveau handler logging.Handler installé sur le root logger avant l'import
du core. Intercepte chaque log.info() et :
* Traduit le message technique en libellé "prod" lisible (table de
correspondance _LOG_TRANSLATIONS : "Gazetteers INSEE prénoms" →
"Chargement des prénoms français (INSEE)…", etc.)
* Pousse le libellé dans le splash tkinter (detail_var, label secondaire)
* Pousse aussi dans le splash natif PyInstaller via pyi_splash.update_text()
- Splash tkinter agrandi 440×200 → 480×240 pour la nouvelle ligne détail
- Couleur primaire magenta (#E91E63) pour cohérence avec la GUI principale
- Handler retiré quand le splash se ferme (évite impact sur la GUI)
L'utilisateur voit maintenant défiler :
Chargement des prénoms français (INSEE)…
Chargement des noms de famille (INSEE)…
Chargement des communes françaises (INSEE)…
Chargement des numéros FINESS…
Indexation des établissements de santé…
Chargement du lexique médical…
Chargement de la base médicamenteuse (BDPM)…
Chargement des stop-words…
Chargement du vocabulaire clinique…
Chargement des phrases protégées…
Moteur d'anonymisation prêt…
Interface prête — finalisation…
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
96
launcher.py
96
launcher.py
@@ -105,48 +105,104 @@ def check_models_ready():
|
|||||||
|
|
||||||
|
|
||||||
def launch_gui():
|
def launch_gui():
|
||||||
"""Launch the main GUI with a splash screen during the slow core import.
|
"""Launch the main GUI with a splash screen + progression des chargements.
|
||||||
|
|
||||||
Le chargement des gazetteers (INSEE 200k+ noms, FINESS 100k+ établissements,
|
Pendant les 15-30 s d'import du core (gazetteers INSEE/FINESS/BDPM,
|
||||||
BDPM 7k+ médicaments) prend 10–30 s. Sans feedback visuel, l'utilisateur
|
modèles ONNX), on intercepte les log.info() via un handler dédié pour
|
||||||
croit que l'application est plantée. Deux splashes collaborent :
|
les afficher DANS LE SPLASH au fur et à mesure. L'utilisateur voit
|
||||||
- Splash natif PyInstaller (image) : visible AVANT le démarrage Python,
|
défiler les étapes de chargement — effet pro.
|
||||||
pendant la décompression --onefile dans %TEMP%.
|
|
||||||
- Splash tkinter ci-dessous : progressbar + messages pendant l'import
|
Deux splashes collaborent :
|
||||||
des modèles et la construction de la GUI.
|
- Splash natif PyInstaller (image visible AVANT le démarrage Python) :
|
||||||
|
mis à jour via pyi_splash.update_text().
|
||||||
|
- Splash tkinter ci-dessous : zone "Étapes" qui affiche la ligne courante
|
||||||
|
et la dernière étape terminée.
|
||||||
"""
|
"""
|
||||||
log.info("Launching GUI...")
|
log.info("Launching GUI...")
|
||||||
|
|
||||||
_splash_update("Chargement de l'interface…")
|
|
||||||
|
|
||||||
splash = tk.Tk()
|
splash = tk.Tk()
|
||||||
splash.title("Anonymisation")
|
splash.title("Anonymisation")
|
||||||
splash.geometry("440x200")
|
splash.geometry("480x240")
|
||||||
splash.resizable(False, False)
|
splash.resizable(False, False)
|
||||||
|
|
||||||
frame = ttk.Frame(splash, padding=20)
|
frame = ttk.Frame(splash, padding=20)
|
||||||
frame.pack(fill="both", expand=True)
|
frame.pack(fill="both", expand=True)
|
||||||
ttk.Label(frame, text="Anonymisation", font=("", 14, "bold")).pack(pady=(0, 4))
|
ttk.Label(frame, text="Anonymisation", font=("", 14, "bold")).pack(pady=(0, 4))
|
||||||
ttk.Label(frame, text="Pseudonymisation de documents médicaux",
|
ttk.Label(frame, text="Pseudonymisation de documents médicaux",
|
||||||
foreground="#666666").pack(pady=(0, 12))
|
foreground="#666666").pack(pady=(0, 8))
|
||||||
|
|
||||||
status_var = tk.StringVar(value="Chargement des dictionnaires médicaux…")
|
status_var = tk.StringVar(value="Démarrage…")
|
||||||
ttk.Label(frame, textvariable=status_var, foreground="#1a568e").pack(pady=(0, 8))
|
ttk.Label(frame, textvariable=status_var, foreground="#E91E63",
|
||||||
|
font=("", 10, "bold")).pack(pady=(4, 4))
|
||||||
|
|
||||||
pb = ttk.Progressbar(frame, mode="indeterminate", length=380)
|
# Zone où défile le détail des étapes (dernier log.info interceptée)
|
||||||
|
detail_var = tk.StringVar(value="")
|
||||||
|
ttk.Label(frame, textvariable=detail_var, foreground="#666666",
|
||||||
|
font=("", 9), wraplength=440).pack(pady=(0, 8))
|
||||||
|
|
||||||
|
pb = ttk.Progressbar(frame, mode="indeterminate", length=420)
|
||||||
pb.pack(pady=4)
|
pb.pack(pady=4)
|
||||||
pb.start(10)
|
pb.start(10)
|
||||||
|
|
||||||
ttk.Label(frame, text="Première phase : ~15–30 s",
|
ttk.Label(frame, text="Merci de patienter — 15 à 30 secondes",
|
||||||
foreground="#999999", font=("", 8)).pack(pady=(6, 0))
|
foreground="#999999", font=("", 8)).pack(pady=(6, 0))
|
||||||
|
|
||||||
result = {"done": False, "error": None}
|
result = {"done": False, "error": None}
|
||||||
|
|
||||||
|
# --- Handler de logs qui alimente le splash ---
|
||||||
|
# Traduit les messages techniques vers des libellés "prod" plus lisibles.
|
||||||
|
_LOG_TRANSLATIONS = [
|
||||||
|
("Gazetteers INSEE prénoms", "Chargement des prénoms français (INSEE)…"),
|
||||||
|
("Gazetteers INSEE communes", "Chargement des communes françaises (INSEE)…"),
|
||||||
|
("Gazetteers INSEE noms de famille", "Chargement des noms de famille (INSEE)…"),
|
||||||
|
("Villes blacklist", "Chargement de la blacklist des villes…"),
|
||||||
|
("Gazetteer FINESS numéros", "Chargement des numéros FINESS…"),
|
||||||
|
("Gazetteer FINESS villes", "Chargement des villes FINESS…"),
|
||||||
|
("Gazetteer FINESS téléphones", "Chargement des téléphones FINESS…"),
|
||||||
|
("Gazetteer FINESS Aho-Corasick", "Indexation des établissements de santé…"),
|
||||||
|
("Gazetteer FINESS adresses", "Chargement des adresses FINESS…"),
|
||||||
|
("Gazetteer VILLE Aho-Corasick", "Indexation des villes…"),
|
||||||
|
("Whitelist termes médicaux", "Chargement du lexique médical…"),
|
||||||
|
("Whitelist médicaments", "Chargement de la base médicamenteuse (BDPM)…"),
|
||||||
|
("Stop-words manuels", "Chargement des stop-words…"),
|
||||||
|
("BDPM stop-words", "Chargement des médicaments BDPM…"),
|
||||||
|
("DPI labels blacklist", "Chargement des libellés DPI…"),
|
||||||
|
("Companion blacklist", "Chargement du vocabulaire clinique…"),
|
||||||
|
("Whitelist phrases", "Chargement des phrases protégées…"),
|
||||||
|
("FINESS mono-mots", "Chargement des sigles d'établissement…"),
|
||||||
|
("Core imported OK", "Moteur d'anonymisation prêt…"),
|
||||||
|
("GUI module imported OK", "Interface prête — finalisation…"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def _translate(msg: str) -> str:
|
||||||
|
for key, human in _LOG_TRANSLATIONS:
|
||||||
|
if key in msg:
|
||||||
|
return human
|
||||||
|
return msg
|
||||||
|
|
||||||
|
class _SplashHandler(logging.Handler):
|
||||||
|
def emit(self, record):
|
||||||
|
try:
|
||||||
|
msg = record.getMessage()
|
||||||
|
human = _translate(msg)
|
||||||
|
# Update splash tkinter (thread-safe via after)
|
||||||
|
splash.after(0, lambda m=human: detail_var.set(m[:120]))
|
||||||
|
# Update splash natif PyInstaller (si encore actif)
|
||||||
|
_splash_update(human)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
_splash_handler = _SplashHandler()
|
||||||
|
_splash_handler.setLevel(logging.INFO)
|
||||||
|
logging.getLogger().addHandler(_splash_handler)
|
||||||
|
|
||||||
def _do_import():
|
def _do_import():
|
||||||
try:
|
try:
|
||||||
|
status_var.set("Chargement des dictionnaires médicaux…")
|
||||||
import anonymizer_core_refactored_onnx # noqa
|
import anonymizer_core_refactored_onnx # noqa
|
||||||
log.info("Core imported OK")
|
log.info("Core imported OK")
|
||||||
result["step"] = "gui"
|
result["step"] = "gui"
|
||||||
|
status_var.set("Chargement de l'interface…")
|
||||||
import Pseudonymisation_Gui_V5 # noqa
|
import Pseudonymisation_Gui_V5 # noqa
|
||||||
log.info("GUI module imported OK")
|
log.info("GUI module imported OK")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -163,11 +219,13 @@ def launch_gui():
|
|||||||
_splash_close()
|
_splash_close()
|
||||||
|
|
||||||
def _poll():
|
def _poll():
|
||||||
# Met à jour le message selon l'étape atteinte par le thread d'import
|
|
||||||
if result.get("step") == "gui" and not result["done"]:
|
|
||||||
status_var.set("Chargement de l'interface…")
|
|
||||||
if result["done"]:
|
if result["done"]:
|
||||||
pb.stop()
|
pb.stop()
|
||||||
|
# Retirer le handler — la GUI principale utilise ses propres logs
|
||||||
|
try:
|
||||||
|
logging.getLogger().removeHandler(_splash_handler)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
splash.destroy()
|
splash.destroy()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in New Issue
Block a user