| import os |
| import streamlit as st |
| from PIL import Image |
| from inference import ( |
| load_model, |
| get_val_transform, |
| predict_from_pil |
| ) |
|
|
| |
| |
| |
| 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", |
| } |
|
|
| |
| 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() |
|
|
| |
| |
| |
| |
| 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, |
| }, |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
|
|
| |
| |
| |
| st.set_page_config( |
| page_title="Fire Detection Dashboard", |
| page_icon="🔥", |
| layout="centered" |
| ) |
|
|
| |
| |
| |
| 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}**") |
|
|
| |
| |
| |
| @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) |
| transform = get_val_transform() |
| return model, device, transform |
|
|
| model, device, transform = load_app_model(selected_model_path) |
|
|
| |
| metrics = MODEL_METRICS.get(selected_model_path, None) |
|
|
| |
| |
| |
| 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("<br>", unsafe_allow_html=True) |
|
|
| |
| |
| |
| 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 |
| ) |
|
|
| |
| |
| |
| if uploaded_file is None: |
| st.info("👉 En attente d'une image. Charger une photo de forêt, flamme, paysage, etc.") |
| else: |
| |
| |
| |
| image = Image.open(uploaded_file) |
|
|
| |
| |
| |
| 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 |
| ) |
|
|
| |
| st.image(image, caption="Image chargée", use_container_width=True) |
|
|
| |
| if annotated_image is not None: |
| st.image( |
| annotated_image, |
| caption="Zones détectées (modèle de détection)", |
| use_container_width=True, |
| ) |
|
|
| |
| |
| |
| 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})" |
| ) |
|
|
| |
| |
| |
| 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*. |
| """ |
| ) |
|
|
| |
| |
| |
| 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')} | |
| """ |
| ) |
|
|