davidformation commited on
Commit
959c5b4
·
verified ·
1 Parent(s): dfb550e

Delete appli_demo.py

Browse files
Files changed (1) hide show
  1. appli_demo.py +0 -350
appli_demo.py DELETED
@@ -1,350 +0,0 @@
1
- import pandas as pd
2
- import torch
3
- import streamlit as st
4
- from pathlib import Path
5
- import math
6
- from PIL import Image
7
- from health_multimodal.image.inference_engine import ImageInferenceEngine
8
- from health_multimodal.image.model.pretrained import get_biovil_t_image_encoder
9
- from health_multimodal.image.data.transforms import create_chest_xray_transform_for_inference
10
- from transformers import AutoTokenizer, AutoModel
11
- import os
12
- import io
13
-
14
- # 1. Configuration de la page
15
- st.set_page_config(page_title="MIROIR", layout="wide")
16
- st.markdown("### 🩺 MIROIR : Modèle d'Intelligence pour le Rapprochement d'Observations, d'Images et de Rapports")
17
-
18
- # Configuration du chemin MLflow
19
- BASE_DIR = Path(__file__).resolve().parent
20
- LOCAL_WEIGHTS_PATH = os.path.join(BASE_DIR, "mon_modele", "data", "model.pt2")
21
-
22
- IS_HUGGINGFACE = "SPACE_ID" in os.environ
23
-
24
- if IS_HUGGINGFACE:
25
- CSV_NAME = "chexpert_matches_sample_demo.csv"
26
- IMAGE_PREFIX = ""
27
- else:
28
- CSV_NAME = "chexpert_matches_sample.csv"
29
- IMAGE_PREFIX = "CheXpert/CheXpert-v1.0-small/"
30
-
31
-
32
- class SafeMLflowUnpickler(torch.serialization.pickle.Unpickler):
33
- def find_class(self, module, name):
34
- if "cloudpickle" in module or "_make_skeleton_class" in name:
35
- return object
36
- try:
37
- return super().find_class(module, name)
38
- except Exception:
39
- return object
40
-
41
- def custom_safe_load(filepath, device):
42
- """Lit le fichier MLmodel de manière brute sans exécuter le code obsolète"""
43
- with open(filepath, 'rb') as f:
44
- return torch.load(f, map_location=device, weights_only=False, pickle_module=torch.serialization.pickle)
45
-
46
- @st.cache_resource
47
- def load_models():
48
- device = "cuda" if torch.cuda.is_available() else "cpu"
49
-
50
- # 1. Encodeur d'images
51
- image_encoder = get_biovil_t_image_encoder()
52
- transform = create_chest_xray_transform_for_inference(resize=512, center_crop_size=448)
53
- image_inference_engine = ImageInferenceEngine(image_encoder, transform)
54
-
55
- # 2. Tokenizer
56
- tokenizer = AutoTokenizer.from_pretrained("microsoft/BiomedVLP-BioViL-T", trust_remote_code=True)
57
-
58
- # 3. Architecture du Modèle Textuel
59
- try:
60
- new_text_model = AutoModel.from_pretrained("microsoft/BiomedVLP-BioViL-T", trust_remote_code=True)
61
- except Exception as e:
62
- st.error(f"Impossible d'initialiser l'architecture : {e}")
63
- new_text_model = None
64
-
65
-
66
- if new_text_model is not None and os.path.exists(LOCAL_WEIGHTS_PATH):
67
- try:
68
- state_dict = torch.load(LOCAL_WEIGHTS_PATH, map_location=device, weights_only=False)
69
-
70
- if isinstance(state_dict, dict):
71
- # Extraction si c'est encapsulé
72
- if "state_dict" in state_dict:
73
- state_dict = state_dict["state_dict"]
74
-
75
- cleaned_state_dict = {}
76
- for k, v in state_dict.items():
77
- name = k.replace("model.", "").replace("text_model.", "")
78
- if isinstance(v, torch.Tensor):
79
- cleaned_state_dict[name] = v
80
-
81
- new_text_model.load_state_dict(cleaned_state_dict, strict=False)
82
- else:
83
- st.warning("⚠️ Les poids du fichier MLflow étaient corrompus par la version de Python. Exécution sur l'architecture BioViL-T de base.")
84
-
85
- except Exception as e:
86
- st.error(f"Erreur d'application des poids : {e}")
87
-
88
-
89
- if new_text_model is not None and hasattr(new_text_model, "eval"):
90
- new_text_model.eval()
91
-
92
- return image_inference_engine, tokenizer, new_text_model, device
93
-
94
-
95
-
96
- # Chargement du dataset principal
97
- #@st.cache_data
98
- #def load_dataset():
99
- # try:
100
- # return pd.read_csv("./chexpert_matches_sample.csv", index_col=0)
101
- # except Exception:
102
- # return None
103
-
104
-
105
- @st.cache_data
106
- def load_dataset():
107
- csv_path = BASE_DIR / CSV_NAME
108
- try:
109
- return pd.read_csv(csv_path, index_col=0)
110
- except Exception as e:
111
- st.error(f"Fichier `{CSV_NAME}` introuvable à l'emplacement {csv_path} : {e}")
112
- return None
113
-
114
-
115
- # Initialisation des composants
116
- image_engine, tokenizer, new_text_model, device = load_models()
117
- df_matches = load_dataset()
118
-
119
- # Fonction de calcul de similarité
120
- def compute_similarity(image_path, text_content):
121
- local_device = "cuda" if torch.cuda.is_available() else "cpu"
122
-
123
- if new_text_model is None:
124
- st.error("Le modèle de cross-attention n'est pas chargé.")
125
- return 0.0
126
-
127
- try:
128
- path_object = Path(image_path)
129
-
130
- # Extraction de l'embedding d'image
131
- with torch.no_grad():
132
- image_embedding = image_engine.get_projected_global_embedding(path_object)
133
- if not isinstance(image_embedding, torch.Tensor):
134
- image_embedding = torch.tensor(image_embedding).to(local_device)
135
- else:
136
- image_embedding = image_embedding.to(local_device)
137
-
138
- if image_embedding.ndim == 1:
139
- image_embedding = image_embedding.unsqueeze(0)
140
- image_embedding = image_embedding / image_embedding.norm(dim=-1, keepdim=True)
141
-
142
- # Tokenisation du rapport textuel
143
- inputs = tokenizer(text_content, return_tensors="pt", padding="max_length", truncation=True, max_length=512).to(local_device)
144
-
145
- # Inférence via ton modèle personnalisé
146
- with torch.no_grad():
147
- if hasattr(new_text_model, "get_projected_text_embeddings"):
148
- text_embedding = new_text_model.get_projected_text_embeddings(
149
- input_ids=inputs["input_ids"],
150
- attention_mask=inputs["attention_mask"]
151
- )
152
- else:
153
- outputs = new_text_model(input_ids=inputs["input_ids"], attention_mask=inputs["attention_mask"])
154
- text_embedding = outputs.pooler_output if hasattr(outputs, "pooler_output") else outputs[0][:, 0, :]
155
-
156
- if text_embedding.ndim == 1:
157
- text_embedding = text_embedding.unsqueeze(0)
158
- text_embedding = text_embedding / text_embedding.norm(dim=-1, keepdim=True)
159
-
160
- # Calcul du score
161
- similarity = torch.mm(image_embedding, text_embedding.t()).item()
162
- similarity_prob = 1 / (1 + math.exp(-similarity * 4))
163
- return similarity_prob
164
-
165
- except Exception as e:
166
- st.error(f"Erreur lors de l'exécution du modèle : {e}")
167
- return 0.0
168
-
169
- # --- RECHERCHE ET SELECTION DE DONNÉES ---
170
- if "current_pair" not in st.session_state:
171
- st.session_state.current_pair = None
172
- if "last_loaded_patient" not in st.session_state:
173
- st.session_state.last_loaded_patient = None
174
- if "reset_counter" not in st.session_state:
175
- st.session_state.reset_counter = 0
176
-
177
- col_action1, col_action2 = st.columns([1, 1])
178
-
179
- with col_action1:
180
- st.markdown("##### 🎲 Option 1 : Génération aléatoire")
181
- if st.button("✅ Générer une paire CORRECTE (Match)", use_container_width=True):
182
- if df_matches is not None:
183
- st.session_state.current_pair = df_matches.sample(1).iloc[0]
184
- st.session_state.last_loaded_patient = "random"
185
- st.session_state.reset_counter += 1
186
-
187
- if "patient_input_key" in st.session_state:
188
- st.session_state.patient_input_key = ""
189
-
190
- st.rerun()
191
- else:
192
- st.error("Fichier `chexpert_matches_sample.csv` introuvable.")
193
-
194
- with col_action2:
195
- st.markdown("##### 🔍 Option 2 : Recherche par Patient")
196
-
197
- col_saisie, col_bouton = st.columns([0.7, 0.3])
198
-
199
- with col_saisie:
200
- patient_id = st.text_input(
201
- "Saisir l'ID du Patient (ex: patient64541) :",
202
- placeholder="patientXXXXX",
203
- key="patient_input_key",
204
- label_visibility="collapsed" # Masque le label pour un alignement parfait avec le bouton
205
- )
206
-
207
- with col_bouton:
208
- bouton_reset = st.button("🔄 Réinitialiser", use_container_width=True)
209
-
210
- if bouton_reset:
211
- st.session_state.reset_counter += 1
212
- st.session_state.last_loaded_patient = None # Force le rechargement des widgets du bas
213
- st.rerun()
214
-
215
- if patient_id and df_matches is not None:
216
- patient_records = df_matches[df_matches['path_to_image'].str.contains(patient_id, na=False, case=False)]
217
-
218
- if not patient_records.empty:
219
- if len(patient_records) > 1:
220
- st.success(f"🎵 {len(patient_records)} observation(s) trouvée(s) pour le `{patient_id}`.")
221
- choices = [f"Observation {i+1} - Image: {Path(row['path_to_image']).name}" for i, row in patient_records.iterrows()]
222
-
223
- selected_index = st.selectbox(
224
- "Sélectionnez l'examen à analyser :",
225
- range(len(choices)),
226
- format_func=lambda x: choices[x],
227
- key=f"select_{patient_id}"
228
- )
229
-
230
- nouvelle_paire = patient_records.iloc[selected_index]
231
- identifiant_unique = f"{patient_id}_{selected_index}"
232
-
233
- if st.session_state.last_loaded_patient != identifiant_unique:
234
- st.session_state.current_pair = nouvelle_paire
235
- st.session_state.last_loaded_patient = identifiant_unique
236
- st.rerun()
237
- else:
238
- if st.session_state.last_loaded_patient != patient_id:
239
- st.session_state.current_pair = patient_records.iloc[0]
240
- st.session_state.last_loaded_patient = patient_id
241
- st.rerun()
242
- else:
243
- st.warning(f"Aucun enregistrement trouvé pour l'ID `{patient_id}`.")
244
-
245
-
246
- if st.session_state.current_pair is not None:
247
- pair = st.session_state.current_pair
248
- col1, col2 = st.columns([0.6, 1.4])
249
-
250
- v_id = st.session_state.reset_counter
251
- vient_de_la_recherche = "patient_input_key" in st.session_state and st.session_state.patient_input_key != ""
252
-
253
- with col1:
254
- st.markdown("#### 🖼️ Radiographie Thoracique :")
255
-
256
- cle_dynamique_radio = f"radio_{pair['path_to_image']}_v{v_id}"
257
-
258
- img_source = st.radio(
259
- "Source de l'image :",
260
- ["Image de la paire sélectionnée", "Uploader une autre image"],
261
- key=cle_dynamique_radio
262
- )
263
-
264
-
265
-
266
- image_to_process = None
267
- if img_source == "Image de la paire sélectionnée":
268
- raw_path = str(pair["path_to_image"])
269
- clean_path = raw_path.replace("\\", "/")
270
- image_path = IMAGE_PREFIX + str(pair["path_to_image"])
271
- if "CheXpert-v1.0-small/CheXpert-v1.0-small" in image_path:
272
- image_path = image_path.replace("CheXpert/CheXpert-v1.0-small/CheXpert-v1.0-small", "CheXpert/CheXpert-v1.0-small")
273
-
274
- st.caption(f"`{image_path}`")
275
-
276
- try:
277
- image_to_process = image_path
278
- img_display = Image.open(image_path)
279
- st.image(img_display, use_container_width=250)
280
- except FileNotFoundError:
281
- st.warning("⚠️ Image absente de l'arborescence. Bascule automatique sur l'upload.")
282
- img_source = "Uploader une autre image"
283
- image_to_process = None
284
-
285
-
286
-
287
-
288
- if img_source == "Uploader une autre image":
289
- cle_dynamique_upload = f"upload_{pair['path_to_image']}_v{v_id}"
290
-
291
- uploaded_file = st.file_uploader(
292
- "Choisissez une radiographie (JPG/PNG)",
293
- type=["jpg", "jpeg", "png"],
294
- key=cle_dynamique_upload
295
- )
296
-
297
- if uploaded_file is not None:
298
- img_display = Image.open(uploaded_file)
299
- st.image(img_display, use_container_width=250)
300
- image_to_process = "temp_uploaded_img.jpg"
301
- img_display.convert("RGB").save(image_to_process)
302
-
303
- with col2:
304
- st.markdown("#### 📝 Compte Rendu (Analyse Cross-Attention) :")
305
- default_text = pair["section_impression"] if pd.notna(pair["section_impression"]) else pair.get("report", "")
306
-
307
- edited_text = st.text_area(
308
- "Texte éditable :",
309
- value=str(default_text),
310
- height=300,
311
- key=f"text_area_{pair['path_to_image']}_v{v_id}"
312
- )
313
-
314
- if image_to_process is not None and edited_text:
315
- with st.spinner("Analyse de la cohérence..."):
316
- score = compute_similarity(image_to_process, edited_text)
317
-
318
- st.markdown("#### 📊 Résultat de l'analyse :")
319
-
320
- SEUIL = 0.50
321
- is_match = score >= SEUIL
322
-
323
- if is_match:
324
- st.markdown(
325
- f"""
326
- <div style="background-color: #d4edda; color: #155724; padding: 20px; border-radius: 10px; border-left: 8px solid #28a745; text-align: center;">
327
- <span style="font-size: 30px; font-weight: bold; letter-spacing: 2px;">✅ MATCH</span>
328
- <div style="margin-top: 10px; font-size: 18px; font-weight: 500;">
329
- Score de similarité cosinus : <span style="font-size: 24px; font-weight: bold; font-family: monospace;">{score:.4f}</span>
330
- </div>
331
- </div>
332
- """,
333
- unsafe_allow_html=True
334
- )
335
- else:
336
- st.markdown(
337
- f"""
338
- <div style="background-color: #f8d7da; color: #721c24; padding: 20px; border-radius: 10px; border-left: 8px solid #dc3545; text-align: center;">
339
- <span style="font-size: 30px; font-weight: bold; letter-spacing: 2px;">🚨 MISMATCH</span>
340
- <div style="margin-top: 10px; font-size: 18px; font-weight: 500;">
341
- Score de similarité cosinus : <span style="font-size: 24px; font-weight: bold; font-family: monospace;">{score:.4f}</span>
342
- </div>
343
- </div>
344
- """,
345
- unsafe_allow_html=True
346
- )
347
- else:
348
- st.info("Fournissez une image et un texte pour exécuter l'analyse.")
349
- else:
350
- st.write("👈 Chargez un exemple ou recherchez un patient pour commencer.")