import os
import sys
import requests
import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import io
import base64
import pickle
# Configuration de l'API
API_BASE_URL = os.getenv("MULTISEG_API_BASE_URL", "http://127.0.0.1:8080/api")
# Import des fonctions utils pour l'affichage local
sys.path.append('/app')
sys.path.append('.')
from utils.utils import colorize_mask
# Configuration de la page
st.set_page_config(
page_title="Street Vision - Segmentation Comparison",
page_icon="🚗",
layout="wide"
)
# CSS personnalisé mis à jour
st.markdown("""
""", unsafe_allow_html=True)
# En-tête principal
st.markdown("""
🚗 Street Vision - Comparaison de Modèles
Évaluation comparative SegFormer vs FPN sur le dataset Cityscapes
""", unsafe_allow_html=True)
# Fonctions utilitaires
def get_models_info():
"""Récupère les informations des deux modèles"""
try:
response = requests.get(f"{API_BASE_URL}/models")
if response.status_code == 200:
return response.json()
else:
st.error(f"Erreur lors de la récupération des infos modèles: {response.status_code}")
return None
except Exception as e:
st.error(f"Erreur de connexion à l'API: {e}")
return None
def get_dataset_info():
"""Récupère les informations sur le dataset"""
try:
response = requests.get(f"{API_BASE_URL}/dataset-info")
if response.status_code == 200:
return response.json()
else:
st.error(f"Erreur lors de la récupération des infos dataset: {response.status_code}")
return None
except Exception as e:
st.error(f"Erreur de connexion à l'API: {e}")
return None
def get_train_images():
"""Récupère la liste des images d'entraînement"""
try:
response = requests.get(f"{API_BASE_URL}/train-images")
if response.status_code == 200:
return response.json()
else:
st.error(f"Erreur lors de la récupération des images: {response.status_code}")
return None
except Exception as e:
st.error(f"Erreur de connexion à l'API: {e}")
return None
def get_class_repartition():
"""Récupère le graphique de répartition des classes"""
try:
response = requests.get(f"{API_BASE_URL}/class-repartition", timeout=10)
if response.status_code == 200:
return response.content
else:
return None
except Exception as e:
st.warning(f"Graphique de répartition non disponible: {e}")
return None
def preprocess_train_image(filename, apply_augmentation=False):
"""Lance le preprocessing sur une image d'entraînement"""
try:
data = {
"filename": filename,
"apply_augmentation": apply_augmentation
}
response = requests.post(
f"{API_BASE_URL}/preprocess-train-image",
json=data,
headers={"Content-Type": "application/json"}
)
if response.status_code == 200:
return response.json()
else:
st.error(f"Erreur API: {response.status_code} - {response.text}")
return None
except Exception as e:
st.error(f"Erreur lors du preprocessing: {e}")
return None
def get_sample_images():
"""Récupère la liste des images d'exemple"""
try:
response = requests.get(f"{API_BASE_URL}/sample-images")
if response.status_code == 200:
return response.json()
else:
st.error(f"Erreur lors de la récupération des images: {response.status_code}")
return None
except Exception as e:
st.error(f"Erreur de connexion à l'API: {e}")
return None
def compare_sample_image(filename):
"""Lance une comparaison sur une image d'exemple"""
try:
data = {"filename": filename}
response = requests.post(
f"{API_BASE_URL}/compare-sample",
json=data,
headers={"Content-Type": "application/json"}
)
if response.status_code == 200:
return response.json()
else:
st.error(f"Erreur API: {response.status_code} - {response.text}")
return None
except Exception as e:
st.error(f"Erreur lors de la comparaison: {e}")
return None
def compare_uploaded_image(uploaded_file):
"""Lance une comparaison sur une image uploadée"""
try:
files = {"file": (uploaded_file.name, uploaded_file.getvalue(), uploaded_file.type)}
response = requests.post(f"{API_BASE_URL}/compare-upload", files=files)
if response.status_code == 200:
return response.json()
else:
st.error(f"Erreur API: {response.status_code} - {response.text}")
return None
except Exception as e:
st.error(f"Erreur lors de la comparaison: {e}")
return None
def display_comparison_results(result, models_info):
"""Affiche les résultats de comparaison entre les deux modèles"""
if not result or not result['success']:
st.error("Aucun résultat de comparaison disponible")
return
# Informations des modèles
st.markdown("### 🏆 Modèles Comparés")
col1, col2 = st.columns(2)
with col1:
model1 = result['model1_info']
# Formater les métriques séparément
miou1 = f"{model1.get('test_mean_iou'):.4f}" if model1.get('test_mean_iou') is not None else 'N/A'
acc1 = f"{model1.get('test_accuracy'):.4f}" if model1.get('test_accuracy') is not None else 'N/A'
st.markdown(f"""
🥇 Modèle #1
{model1.get('architecture', 'N/A')} - {model1['encoder_name']}
📐 Taille: {model1['input_size']}
📊 mIoU: {miou1}
🎯 Accuracy: {acc1}
""", unsafe_allow_html=True)
with col2:
model2 = result['model2_info']
# Formater les métriques séparément
miou2 = f"{model2.get('test_mean_iou'):.4f}" if model2.get('test_mean_iou') is not None else 'N/A'
acc2 = f"{model2.get('test_accuracy'):.4f}" if model2.get('test_accuracy') is not None else 'N/A'
st.markdown(f"""
🥈 Modèle #2
{model2.get('architecture', 'N/A')} - {model2['encoder_name']}
📐 Taille: {model2['input_size']}
📊 mIoU: {miou2}
🎯 Accuracy: {acc2}
""", unsafe_allow_html=True)
# Comparaison des temps d'inférence
st.markdown("### ⏱️ Comparaison des Performances")
speed_comp = result['speed_comparison']
model1_time = result['model1_inference_time'] * 1000
model2_time = result['model2_inference_time'] * 1000
col1, col2, col3 = st.columns(3)
with col1:
winner_class = "speed-winner" if speed_comp['faster_model'] == model1['encoder_name'] else "speed-slower"
st.markdown(f"""
🥇 {model1.get('architecture', 'N/A')} - {model1['encoder_name']}
{model1_time:.1f} ms
""", unsafe_allow_html=True)
with col2:
st.markdown(f"""
⚡ Comparaison
{speed_comp['faster_model']}
est {speed_comp['speedup_ratio']}x plus rapide
Différence: {speed_comp['time_difference_ms']:.1f} ms
""", unsafe_allow_html=True)
with col3:
winner_class = "speed-winner" if speed_comp['faster_model'] == model2['encoder_name'] else "speed-slower"
st.markdown(f"""
🥈 {model2.get('architecture', 'N/A')} - {model2['encoder_name']}
{model2_time:.1f} ms
""", unsafe_allow_html=True)
# Affichage des segmentations
if result.get('figure_data'):
st.markdown("### 🎨 Résultats de Segmentation")
try:
fig_bytes = base64.b64decode(result['figure_data'])
fig = pickle.loads(fig_bytes)
st.pyplot(fig)
plt.close(fig)
except Exception as e:
st.error(f"Erreur lors de l'affichage: {e}")
# Comparaison des statistiques
st.markdown("### 📊 Répartition des classes par modèle")
col1, col2 = st.columns(2)
with col1:
if result.get('model1_stats'):
display_model_stats_chart(result['model1_stats'], models_info, f"{model1.get('architecture', 'N/A')} - {model1['encoder_name']}")
with col2:
if result.get('model2_stats'):
display_model_stats_chart(result['model2_stats'], models_info, f"{model2.get('architecture', 'N/A')} - {model2['encoder_name']}")
# Analyse comparative des classes
if result.get('model1_stats') and result.get('model2_stats'):
display_class_comparison(result['model1_stats'], result['model2_stats'], model1, model2, models_info)
def display_model_stats_chart(stats, models_info, title):
"""Affiche les statistiques d'un modèle sous forme de graphique"""
if not stats:
st.warning("Aucune statistique disponible")
return
# Utiliser l'ordre des classes du modèle pour la cohérence
if models_info and 'class_names' in models_info:
# Ordonner selon l'ordre du modèle
ordered_class_names = [name for name in models_info['class_names'] if name in stats]
else:
# Fallback : ordre alphabétique
ordered_class_names = sorted(stats.keys())
percentages = [stats[name]['percentage'] for name in ordered_class_names]
# Récupérer les couleurs des classes
colors = []
if models_info and 'class_colors' in models_info and 'class_names' in models_info:
model_class_names = models_info['class_names']
model_class_colors = models_info['class_colors']
for class_name in ordered_class_names:
try:
idx = model_class_names.index(class_name)
color = model_class_colors[idx]
colors.append([c/255.0 for c in color])
except (ValueError, IndexError):
colors.append([0.5, 0.5, 0.5])
else:
colors = plt.cm.Set3(np.linspace(0, 1, len(ordered_class_names)))
# Créer le graphique
fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(ordered_class_names, percentages, color=colors, alpha=0.8, edgecolor='white', linewidth=1)
ax.set_xlabel('Classes de Segmentation', fontsize=10, fontweight='bold')
ax.set_ylabel('Pourcentage (%)', fontsize=10, fontweight='bold')
ax.set_title(title, fontsize=11, fontweight='bold', pad=15)
# Rotation des labels selon le nombre de classes
if len(ordered_class_names) <= 4:
plt.xticks(rotation=0, ha='center', fontsize=9)
elif len(ordered_class_names) <= 6:
plt.xticks(rotation=30, ha='right', fontsize=8)
else:
plt.xticks(rotation=45, ha='right', fontsize=8)
plt.yticks(fontsize=9)
# Valeurs sur les barres
for bar, percentage in zip(bars, percentages):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + max(percentages) * 0.01,
f'{percentage:.1f}%',
ha='center', va='bottom', fontsize=8, fontweight='bold')
plt.tight_layout()
ax.grid(axis='y', alpha=0.2, linestyle='--')
ax.set_axisbelow(True)
# Style des axes
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Couleur de fond
fig.patch.set_facecolor('white')
ax.set_facecolor('#fafafa')
st.pyplot(fig)
plt.close(fig)
def display_class_comparison(stats1, stats2, model1, model2, models_info):
"""Affiche une comparaison détaillée des classes entre les deux modèles"""
st.markdown("### 🔍 Analyse Comparative par Classe")
# Utiliser l'ordre des classes du modèle pour la cohérence
if models_info and 'class_names' in models_info:
# Combiner toutes les classes présentes en conservant l'ordre du modèle
all_classes_in_stats = set(stats1.keys()) | set(stats2.keys())
ordered_all_classes = [name for name in models_info['class_names'] if name in all_classes_in_stats]
else:
# Fallback : ordre alphabétique
all_classes = set(stats1.keys()) | set(stats2.keys())
ordered_all_classes = sorted(all_classes)
comparison_data = []
for class_name in ordered_all_classes:
pct1 = stats1.get(class_name, {}).get('percentage', 0)
pct2 = stats2.get(class_name, {}).get('percentage', 0)
diff = pct1 - pct2
comparison_data.append({
'class': class_name,
'model1_pct': pct1,
'model2_pct': pct2,
'difference': diff
})
# Créer un graphique comparatif des barres
st.markdown("**📊 Comparaison des classes entre les modèles:**")
# Préparation des données
class_names = [data['class'] for data in comparison_data]
model1_values = [data['model1_pct'] for data in comparison_data]
model2_values = [data['model2_pct'] for data in comparison_data]
# Récupérer les couleurs des classes
colors = []
if models_info and 'class_colors' in models_info and 'class_names' in models_info:
model_class_names = models_info['class_names']
model_class_colors = models_info['class_colors']
for class_name in class_names:
try:
idx = model_class_names.index(class_name)
color = model_class_colors[idx]
hex_color = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
colors.append(hex_color)
except (ValueError, IndexError):
colors.append("#999999")
else:
colors = ['#999999'] * len(class_names)
# Créer le graphique comparatif
fig, ax = plt.subplots(figsize=(12, 7))
x = np.arange(len(class_names)) # Positions des barres
width = 0.35 # Largeur des barres
bars1 = ax.bar(x - width/2, model1_values, width, label=f"{model1.get('architecture', 'N/A')} - {model1['encoder_name']}",
color=colors, alpha=0.8, edgecolor='white')
# Barres hachurées pour le modèle 2
bars2 = ax.bar(x + width/2, model2_values, width, label=f"{model2.get('architecture', 'N/A')} - {model2['encoder_name']}",
color=colors, alpha=0.6, edgecolor='white', hatch='///')
ax.set_xlabel('Classes', fontsize=11, fontweight='bold')
ax.set_ylabel('Pourcentage (%)', fontsize=11, fontweight='bold')
ax.set_title('Comparaison des classes entre les deux modèles', fontsize=13, fontweight='bold', pad=15)
ax.set_xticks(x)
# Rotation des labels selon le nombre de classes
if len(class_names) <= 4:
ax.set_xticklabels(class_names, fontsize=10)
elif len(class_names) <= 6:
ax.set_xticklabels(class_names, rotation=30, ha='right', fontsize=9)
else:
ax.set_xticklabels(class_names, rotation=45, ha='right', fontsize=9)
ax.legend(loc='upper right', fontsize=10)
ax.grid(axis='y', alpha=0.2, linestyle='--')
ax.set_axisbelow(True)
# Style des axes
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Valeurs sur les barres
for bar1, bar2, val1, val2 in zip(bars1, bars2, model1_values, model2_values):
height1 = bar1.get_height()
height2 = bar2.get_height()
ax.text(bar1.get_x() + bar1.get_width()/2., height1 + 0.5,
f'{val1:.1f}%', ha='center', va='bottom', fontsize=8, fontweight='bold')
ax.text(bar2.get_x() + bar2.get_width()/2., height2 + 0.5,
f'{val2:.1f}%', ha='center', va='bottom', fontsize=8, fontweight='bold')
plt.tight_layout()
st.pyplot(fig)
plt.close(fig)
# Tableau détaillé avec un beau format
st.markdown("**🔎 Détails des différences par classe:**")
# Préparer les données pour le tableau
table_data = []
for i, data in enumerate(comparison_data):
class_color = colors[i]
diff_value = data['difference']
# Classification de la différence
if abs(diff_value) < 1:
diff_category = "Similaire"
diff_emoji = "🟢"
elif abs(diff_value) < 5:
diff_category = "Différence modérée"
diff_emoji = "🟡"
else:
diff_category = "Différence importante"
diff_emoji = "🔴"
table_data.append({
"Classe": data['class'],
f"🥇 {model1.get('architecture', 'N/A')[:15]} (%)": f"{data['model1_pct']:.1f}%",
f"🥈 {model2.get('architecture', 'N/A')[:15]} (%)": f"{data['model2_pct']:.1f}%",
"Différence": f"{diff_value:+.1f}%",
"Catégorie": f"{diff_emoji} {diff_category}"
})
# Créer le DataFrame et l'afficher
df = pd.DataFrame(table_data)
# Utiliser st.dataframe avec style personnalisé
st.dataframe(
df,
use_container_width=True,
hide_index=True,
column_config={
"Classe": st.column_config.TextColumn("🎯 Classe", width="medium"),
f"🥇 {model1.get('architecture', 'N/A')[:15]} (%)": st.column_config.TextColumn(width="medium"),
f"🥈 {model2.get('architecture', 'N/A')[:15]} (%)": st.column_config.TextColumn(width="medium"),
"Différence": st.column_config.TextColumn("📊 Différence", width="small"),
"Catégorie": st.column_config.TextColumn("📈 Évaluation", width="medium")
}
)
# Onglets principaux
main_tab1, main_tab2 = st.tabs(["📊 Présentation Jeu de données Cityscapes", "🔄 Comparaison de performances"])
# ===== ONGLET 1: PRÉSENTATION DATASET =====
with main_tab1:
st.markdown("## 📊 Analyse Exploratoire - Dataset Cityscapes")
# Informations générales sur le dataset
dataset_info = get_dataset_info()
if dataset_info:
# Section méthodologique
st.markdown("""
### 🎯 Note Méthodologique : Preuve de concept SegFormer vs FPN
#### Dataset retenu
**Cityscapes Dataset : Référence en Segmentation Urbaine**
Le **Cityscapes Dataset** constitue la référence internationale pour l'évaluation des algorithmes de segmentation sémantique en environnement urbain. Développé par l'Université de Tübingen et Mercedes-Benz, ce dataset comprend **25,000 images** haute résolution (2048×1024 pixels) capturées dans 50 villes allemandes et suisses.
""")
# Composition du dataset
col1, col2 = st.columns(2)
with col1:
st.markdown("**🗂️ Composition du dataset :**")
splits_info = dataset_info.get('splits', {})
for split_name, split_info in splits_info.items():
st.write(f"• **{split_name.title()}** : {split_info['images']} images - {split_info['usage']}")
with col2:
st.markdown("**🔬 Utilisation pour ce projet :**")
project_splits = dataset_info.get('project_splits', {})
for split_name, split_info in project_splits.items():
st.write(f"• **{split_name.title()}** : {split_info['images']} images ({split_info['percentage']}%) - {split_info['source']}")
# Architecture des classes
st.markdown("### 🏗️ Architecture de classes hiérarchique")
st.markdown("""
Le dataset organise 34 classes détaillées en **8 méta-classes** pour la segmentation :
| Méta-classe | Description | Importance |
|-------------|-------------|------------|
| **Flat** | Route, trottoir, parkings | Navigation de base |
| **Human** | Piéton, cycliste | **Sécurité critique** |
| **Vehicle** | Voiture, camion, bus, train | Obstacles dynamiques |
| **Construction** | Bâtiment, mur, clôture, pont | Structure urbaine |
| **Object** | Poteau, feu, panneau | Signalétique |
| **Nature** | Végétation, terrain | Environnement |
| **Sky** | Ciel | Contexte |
| **Void** | Zones non-définies | Exclusion |
""")
# Graphique de répartition des classes
st.markdown("### 📊 Répartition des classes dans le dataset d'entraînement")
class_repartition_data = get_class_repartition()
if class_repartition_data:
try:
class_repartition_image = Image.open(io.BytesIO(class_repartition_data))
st.image(
class_repartition_image,
caption="Distribution des classes dans les images d'entraînement Cityscapes",
use_container_width=True
)
except Exception as e:
st.warning("Impossible d'afficher le graphique de répartition des classes")
else:
st.info("Graphique de répartition des classes non disponible")
# Spécificités techniques
st.markdown("""
### ⚙️ Spécificités techniques
- **Résolutions d'acquisition** : Données natives 2048×1024 pixels
- **Conditions d'acquisition** : Conditions météo bonnes à moyennes, pas de conditions adverses (pluie, neige)
- **Diversité géographique** : Villes allemandes et suisses (architectures variées)
- **Annotations pixel-perfect** : Masques de segmentation précis au pixel près
Ce dataset permet d'évaluer la capacité des modèles à comprendre les scènes urbaines complexes, élément fondamental pour les applications de conduite autonome et de surveillance urbaine intelligente.
""")
# Section preprocessing des images
st.markdown("---")
st.markdown("### 🔧 Exemple de Preprocessing d'Images")
# Récupérer les images d'entraînement disponibles
train_data = get_train_images()
if train_data and train_data['images']:
# Sélecteur d'image
image_options = {img['display_name']: img['filename'] for img in train_data['images']}
selected_display_name = st.selectbox(
"Choisissez une image d'entraînement pour voir les transformations:",
options=list(image_options.keys()),
help=f"{train_data['total_count']} images disponibles"
)
selected_filename = image_options[selected_display_name]
# Options de preprocessing
col1, col2 = st.columns(2)
with col1:
show_augmentation = st.checkbox(
"Inclure l'augmentation aléatoire",
help="Affiche un exemple d'augmentation de données utilisée pendant l'entraînement"
)
with col2:
if st.button("🔧 Appliquer le Preprocessing", type="primary"):
with st.spinner("Application des transformations..."):
result = preprocess_train_image(selected_filename, show_augmentation)
if result and result['success']:
st.success("Preprocessing terminé!")
# Affichage des images transformées
st.markdown("#### Résultats des transformations :")
cols = st.columns(3 if show_augmentation else 2)
# Image originale
with cols[0]:
st.markdown("**Image Originale (redimensionnée)**")
if result.get('original_image'):
original_img_data = base64.b64decode(result['original_image'])
original_img = Image.open(io.BytesIO(original_img_data))
st.image(original_img, use_container_width=True)
# Image normalisée
with cols[1]:
st.markdown("**Après Normalisation**")
if result.get('normalized_image'):
normalized_img_data = base64.b64decode(result['normalized_image'])
normalized_img = Image.open(io.BytesIO(normalized_img_data))
st.image(normalized_img, use_container_width=True)
# Image avec augmentation (si demandée)
if show_augmentation and result.get('augmented_image'):
with cols[2]:
st.markdown("**Avec Augmentation Aléatoire**")
augmented_img_data = base64.b64decode(result['augmented_image'])
augmented_img = Image.open(io.BytesIO(augmented_img_data))
st.image(augmented_img, use_container_width=True)
# Explications des transformations
if result.get('transformations_applied'):
st.markdown("#### 📋 Transformations appliquées :")
for i, transform in enumerate(result['transformations_applied'], 1):
st.markdown(f"{i}. {transform}")
# Explications détaillées
with st.expander("ℹ️ Détails des transformations"):
st.markdown("""
**Normalisation standard :**
- Redimensionnement à 512×512 pixels
- Normalisation des valeurs de pixels de [0-255] vers [0-1]
- Cette normalisation uniforme est appliquée à tous les modèles (paramètre encoder non utilisé)
**Augmentation de données (si activée) :**
- Transformations aléatoires appliquées pendant l'entraînement
- Permet d'améliorer la robustesse et la généralisation du modèle
- Chaque image d'entraînement peut être transformée différemment à chaque époque
""")
else:
st.error("Erreur lors du preprocessing")
else:
st.warning("Aucune image d'entraînement disponible pour la démonstration")
# ===== ONGLET 2: COMPARAISON DE PERFORMANCES =====
with main_tab2:
# Variables de session pour les résultats
if "sample_comparison_result" not in st.session_state:
st.session_state.sample_comparison_result = None
if "upload_comparison_result" not in st.session_state:
st.session_state.upload_comparison_result = None
def reset_sample_comparison():
st.session_state.sample_comparison_result = None
def reset_upload_comparison():
st.session_state.upload_comparison_result = None
# Sous-onglets pour la comparaison
sub_tab1, sub_tab2 = st.tabs(["🏙️ Images Test Cityscapes", "📤 Upload Personnel"])
# SUB-TAB 1: Images Test Cityscapes
with sub_tab1:
st.markdown("## 🏙️ Comparaison sur Images Test Cityscapes")
st.markdown("Comparez les performances des deux modèles sur des images Cityscapes avec masques de vérité.")
# Récupérer les images d'exemple
sample_data = get_sample_images()
if sample_data and sample_data['images']:
# Sélecteur d'image
image_options = {img['display_name']: img['filename'] for img in sample_data['images']}
selected_display_name = st.selectbox(
"Choisissez une image:",
options=list(image_options.keys()),
help=f"{sample_data['total_count']} images disponibles"
)
selected_filename = image_options[selected_display_name]
# Afficher les infos de l'image sélectionnée
selected_img_info = next(img for img in sample_data['images'] if img['filename'] == selected_filename)
col1, col2 = st.columns([2, 1])
with col1:
st.info(f"📁 **Fichier:** {selected_filename}")
with col2:
ground_truth_status = "✅ Disponible" if selected_img_info['has_ground_truth'] else "❌ Non disponible"
st.info(f"🎯 **Vérité terrain:** {ground_truth_status}")
# Prévisualisation de l'image sélectionnée
if selected_filename:
try:
image_url = f"{API_BASE_URL}/sample-image/{selected_filename}"
response = requests.get(image_url, timeout=5)
if response.status_code == 200:
sample_image = Image.open(io.BytesIO(response.content))
st.image(
sample_image,
caption=f"Aperçu: {selected_display_name}",
use_container_width=True
)
except:
st.info("📷 Aperçu non disponible - L'image sera chargée lors de la comparaison")
# Bouton de comparaison
if st.button("🔄 Lancer la Comparaison", key="compare_sample", type="primary"):
if selected_filename:
with st.spinner("Comparaison en cours..."):
result = compare_sample_image(selected_filename)
if result and result['success']:
st.session_state.sample_comparison_result = result
st.success("✅ Comparaison terminée!")
st.rerun()
else:
st.error("❌ Erreur lors de la comparaison")
else:
st.warning("Aucune image d'exemple disponible")
# Affichage des résultats de comparaison
if st.session_state.sample_comparison_result:
st.markdown("---")
models_info = get_models_info()
display_comparison_results(st.session_state.sample_comparison_result, models_info)
# Bouton de reset
if st.button("🔄 Nouvelle Comparaison", key="reset_sample", type="secondary"):
reset_sample_comparison()
st.rerun()
# SUB-TAB 2: Upload Personnel
with sub_tab2:
st.markdown("## 📤 Comparaison sur Image Personnelle")
st.markdown("Uploadez une image pour comparer les performances des deux modèles.")
uploaded_file = st.file_uploader(
"Sélectionnez une image:",
type=['jpg', 'jpeg', 'png'],
help="Taille maximale recommandée: 5MB"
)
if uploaded_file is not None:
# Afficher l'image uploadée
image = Image.open(uploaded_file)
st.image(image, caption=f"Image uploadée: {uploaded_file.name}", use_container_width=True)
# Informations sur le fichier
file_size_mb = len(uploaded_file.getvalue()) / 1024 / 1024
st.info(f"📁 **Fichier:** {uploaded_file.name} | 📊 **Taille:** {file_size_mb:.2f} MB")
# Bouton de comparaison
if st.button("🔄 Lancer la Comparaison", key="compare_upload", type="primary"):
with st.spinner("Comparaison en cours..."):
result = compare_uploaded_image(uploaded_file)
if result and result['success']:
st.session_state.upload_comparison_result = result
st.success("✅ Comparaison terminée!")
st.rerun()
else:
st.error("❌ Erreur lors de la comparaison")
# Affichage des résultats de comparaison
if st.session_state.upload_comparison_result:
st.markdown("---")
models_info = get_models_info()
display_comparison_results(st.session_state.upload_comparison_result, models_info)
# Bouton de reset
if st.button("🔄 Nouvelle Comparaison", key="reset_upload", type="secondary"):
reset_upload_comparison()
st.rerun()
# Sidebar avec informations des modèles
with st.sidebar:
st.markdown("## 🔧 Informations des Modèles")
models_info = get_models_info()
if models_info:
st.success("✅ Modèles chargés")
# Modèle 1
best_model = models_info.get('best_model', {})
st.markdown("**🥇 Meilleur Modèle:**")
st.write(f"**Architecture:** {best_model.get('architecture', 'N/A')}")
st.write(f"**Encoder:** {best_model.get('encoder_name', 'N/A')}")
st.write(f"**Résolution:** {best_model.get('input_size', 'N/A')}")
# Modèle 2
second_model = models_info.get('second_best_model', {})
st.markdown("**🥈 Deuxième Modèle:**")
st.write(f"**Architecture:** {second_model.get('architecture', 'N/A')}")
st.write(f"**Encoder:** {second_model.get('encoder_name', 'N/A')}")
st.write(f"**Résolution:** {second_model.get('input_size', 'N/A')}")
else:
st.error("❌ Modèles non disponibles")
st.markdown("---")
st.markdown("## 🎯 Classes Segmentées (IoU)")
# Affichage avec carrés colorés et IoU
if models_info and 'class_names' in models_info and 'class_colors' in models_info:
class_names = models_info['class_names']
class_colors = models_info['class_colors']
# Récupérer les IoU des modèles
best_ious = best_model.get('class_ious', {}) if models_info else {}
second_ious = second_model.get('class_ious', {}) if models_info else {}
for i, (class_name, color) in enumerate(zip(class_names, class_colors)):
color_hex = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
# Construire la chaîne IoU
iou_str = ""
if best_ious.get(class_name) is not None and second_ious.get(class_name) is not None:
best_iou = best_ious[class_name]
second_iou = second_ious[class_name]
iou_str = f" (🥇{best_iou:.2f} - 🥈{second_iou:.2f})"
elif best_ious.get(class_name) is not None:
best_iou = best_ious[class_name]
iou_str = f" (🥇{best_iou:.2f})"
elif second_ious.get(class_name) is not None:
second_iou = second_ious[class_name]
iou_str = f" (🥈{second_iou:.2f})"
st.markdown(f"""
{i}. {class_name}{iou_str}
""", unsafe_allow_html=True)
# Footer
st.markdown("---")
st.markdown("""
🚗 Street Vision - Comparaison de Modèles -
Évaluation comparative de modèles de segmentation sémantique
Développé avec FastAPI, Streamlit et PyTorch
""", unsafe_allow_html=True)