import os # pour vérifier la présence des fichiers de modèles import streamlit as st # librairie pour le dashboard from PIL import Image # pour ouvrir les images from inference import ( # fonctions importées du fichier inference.py load_model, get_val_transform, predict_from_pil ) # ----------------------------------------------------------- # Modèles disponibles (théoriques) # ----------------------------------------------------------- ALL_MODEL_FILES = { "Modèle EfficientnetB0 Baseline": "efficientnet_fire.pt", "Modèle EfficientnetB0 FE": "efficientnet_fire_2.pt", "Modèle EfficientnetB0 FT": "efficientnet_fire_3.pt", "Modèle YOLOv8": "yolov8_fire.pt", "Modèle Inception3": "inception3_fire.pt", } # Ne garder que les modèles réellement présents dans le repo MODEL_FILES = { name: path for name, path in ALL_MODEL_FILES.items() if os.path.exists(path) } if len(MODEL_FILES) == 0: st.error("❌ Aucun modèle trouvé dans le repository. Ajoutez au moins un fichier .pt.") st.stop() # ----------------------------------------------------------- # Métriques par modèle (à remplir plus tard si besoin) # Clé = chemin du fichier de poids (ex: 'efficientnet_fire.pt') # ----------------------------------------------------------- MODEL_METRICS = { "efficientnet_fire.pt": { "accuracy": 0.94, "precision": 0.92, "recall": 0.89, "f1": 0.90, "fp": 12, "fn": 4, }, "efficientnet_fire_2.pt": { "accuracy": 0.7727, "precision": 0.9111, "recall": 0.7704, "f1": 0.8349, "fp": 275, "fn": 840, }, "efficientnet_fire_3.pt": { "accuracy": 0.7717, "precision": 0.8713, "recall": 0.8142, "f1": 0.8418, "fp": 440, "fn": 680, }, # "yolov8_fire.pt": { # "accuracy": 0.7717, # "precision": 0.8713, # "recall": 0.8142, # "f1": 0.8418, # "fp": 440, # "fn": 680, # }, # "inception3_fire.pt": { # "accuracy": 0.7717, # "precision": 0.8713, # "recall": 0.8142, # "f1": 0.8418, # "fp": 440, # "fn": 680, # }, } # ----------------------------------------------------------- # Configuration de la page # ----------------------------------------------------------- st.set_page_config( page_title="Fire Detection Dashboard", # titre de l’onglet du navigateur page_icon="🔥", # icône (emoji) layout="centered" # mise en page centrée ) # ----------------------------------------------------------- # Sidebar : choix du modèle + paramètres # ----------------------------------------------------------- st.sidebar.title("⚙️ Paramètres") selected_model_name = st.sidebar.selectbox( "Choisir le modèle à utiliser", options=list(MODEL_FILES.keys()), index=0 ) selected_model_path = MODEL_FILES[selected_model_name] st.sidebar.markdown(f"🧠 Modèle sélectionné : **{selected_model_name}**") st.sidebar.markdown( """ Ce dashboard prédit **FIRE / NO FIRE** sur des images. - Classe 0 : **no_fire** - Classe 1 : **fire** """ ) threshold = st.sidebar.slider( "Seuil de détection du feu (probabilité minimale pour 'fire')", min_value=0.1, max_value=0.9, value=0.5, step=0.05, ) st.sidebar.markdown(f"Seuil actuel : **{threshold:.2f}**") # ----------------------------------------------------------- # Chargement du modèle (en fonction du choix) # ----------------------------------------------------------- @st.cache_resource def load_app_model(model_path: str): """ Charge le modèle, le device et la transform une seule fois pour un chemin donné, puis les réutilise pour toutes les prédictions. """ model, device = load_model(model_path) # charge les poids du modèle choisi transform = get_val_transform() # transform validation/inférence return model, device, transform model, device, transform = load_app_model(selected_model_path) # Récupération éventuelle des métriques pour ce modèle metrics = MODEL_METRICS.get(selected_model_path, None) # ----------------------------------------------------------- # Titre principal + guide d’utilisation # ----------------------------------------------------------- st.title("🔥 Fire Detection Dashboard") st.markdown( """ Ce prototype permet de tester un modèle de détection de feu sur des images individuelles. """ ) st.markdown( """ ### 🧭 Guide d’utilisation 1. 🧠 **Choisissez le modèle** dans la barre latérale 2. 🖼️ **Chargez une image** - JPG, JPEG, PNG - Maximum 200MB 3. 🔍 **Obtenez la prédiction** (FEU / PAS DE FEU DÉTECTÉ + probabilité) 4. 📊 **Comparez les modèles** pour explorer leurs différences 5. ⚙️ **Ajustez le seuil** pour un modèle +/- strict """ ) st.markdown("
", unsafe_allow_html=True) # ----------------------------------------------------------- # Zone d'upload d'image # ----------------------------------------------------------- uploaded_file = st.file_uploader( "📂 Déposez une image ici (ou cliquez sur Browse Files pour choisir une image)", type=["jpg", "jpeg", "png"], help="Formats supportés : JPG, JPEG, PNG\nMaximum 200MB par image", accept_multiple_files=False ) # ----------------------------------------------------------- # Si aucune image n'est encore uploadée # ----------------------------------------------------------- if uploaded_file is None: st.info("👉 En attente d'une image. Charger une photo de forêt, flamme, paysage, etc.") else: # ------------------------------------------------------- # Afficher l'image uploadée # ------------------------------------------------------- image = Image.open(uploaded_file) # ------------------------------------------------------- # Prédiction # ------------------------------------------------------- with st.spinner("Analyse de l'image en cours..."): label, prob, annotated_image = predict_from_pil( image=image, model=model, device=device, transform=transform, threshold=threshold ) # Image originale st.image(image, caption="Image chargée", use_container_width=True) # Si YOLO a fourni une image annotée, on l'affiche if annotated_image is not None: st.image( annotated_image, caption="Zones détectées (modèle de détection)", use_container_width=True, ) # ------------------------------------------------------- # Affichage du résultat avec couleur # ------------------------------------------------------- prob_percent = prob * 100 if label == "fire": st.error( f"🔥 **FEU DÉTECTÉ** \nProbabilité de feu : **{prob_percent:.2f}%** \n(Seuil utilisé : {threshold:.2f})" ) else: st.success( f"✅ **PAS DE FEU DÉTECTÉ** \nProbabilité de feu : **{prob_percent:.2f}%** \n(Seuil utilisé : {threshold:.2f})" ) # ------------------------------------------------------- # Détails techniques (expander) # ------------------------------------------------------- with st.expander("🔍 Détails techniques"): st.markdown( f""" - Modèle utilisé : **{selected_model_name}** - Fichier de poids : `{selected_model_path}` - Label retourné : **{label}** - Probabilité brute de la classe *fire* : **{prob:.4f}** - Seuil de décision : **{threshold:.2f}** Si `prob_fire >= seuil` → prédiction = *fire*, sinon → *no_fire*. """ ) # ------------------------------------------------------- # Performances du modèle (expander conditionnel) # ------------------------------------------------------- if metrics is not None: with st.expander("📊 Performances du modèle (validation) – ⚠️ Valeurs fictives pour EfficientnetB0 Baseline"): st.markdown( f""" | Métriques | Valeurs | |----------------------|-------| | Accuracy | {metrics.get('accuracy', 'non disponible')} | | Precision (fire) | {metrics.get('precision', 'non disponible')} | | Recall (fire) | {metrics.get('recall', 'non disponible')} | | F1-score (fire) | {metrics.get('f1', 'non disponible')} | | False Positives (FP) | {metrics.get('fp', 'non disponible')} | | False Negatives (FN) | {metrics.get('fn', 'non disponible')} | """ )