File size: 8,786 Bytes
6c28497
 
 
 
433b881
 
 
 
 
 
c00bf8d
433b881
6c28497
c9396ac
cf918df
 
2e0d90d
6c28497
 
 
 
 
 
 
433b881
6c28497
 
 
433b881
 
c00bf8d
 
 
 
a853c2f
 
 
 
 
 
 
 
0034587
 
 
 
 
 
 
 
cf918df
 
 
 
 
 
 
 
2e0d90d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c00bf8d
 
 
 
 
 
 
 
 
 
 
 
 
433b881
 
6c28497
 
 
 
 
 
 
 
 
 
433b881
 
6c28497
433b881
6c28497
433b881
 
 
 
 
 
 
 
 
 
 
 
 
 
6c28497
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c00bf8d
 
 
433b881
c00bf8d
433b881
 
c00bf8d
433b881
 
6c28497
c00bf8d
 
 
 
 
 
 
433b881
c00bf8d
 
 
 
 
433b881
 
 
7778ae6
c9396ac
433b881
6c28497
433b881
 
6c28497
433b881
6c28497
433b881
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2e0d90d
433b881
 
 
 
 
 
 
2e0d90d
 
 
 
 
 
 
 
 
 
 
433b881
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c00bf8d
433b881
c00bf8d
433b881
 
6c28497
 
 
 
433b881
 
 
 
 
 
c00bf8d
 
 
 
 
48620f0
c00bf8d
 
bf91db4
c00bf8d
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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("<br>", 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')} |
                """
            )