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"
)
# Ajouts à placer au début de streamlit_app.py après les imports
# Palettes de couleurs conformes WCAG AA (ratio contraste ≥ 3:1)
WCAG_ACCESSIBLE_COLORS = {
'primary_blue': '#0066CC', # [0, 102, 204]
'primary_red': '#CC0000', # [204, 0, 0]
'primary_green': '#006600', # [0, 102, 0]
'primary_orange': '#CC6600', # [204, 102, 0]
'secondary_purple': '#663399', # [102, 51, 153]
'secondary_teal': '#006666', # [0, 102, 102]
'neutral_dark': '#333333', # [51, 51, 51]
'neutral_medium': '#666666', # [102, 102, 102]
'neutral_light': '#CCCCCC', # Pour éléments désactivés
'background_alt': '#F5F5F5' # Fond alternatif accessible
}
# Convertir hex vers RGB
def hex_to_rgb(hex_color):
"""Convertit une couleur hex en tuple RGB"""
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
# Couleurs RGB pour les masques (dans le même ordre que les classes)
WCAG_RGB_COLORS = [
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['neutral_medium']), # Flat: gris moyen [102, 102, 102]
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_red']), # Human: rouge [204, 0, 0]
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['secondary_purple']), # Vehicle: violet [102, 51, 153]
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_orange']), # Construction: orange [204, 102, 0]
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['secondary_teal']), # Object: teal [0, 102, 102]
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_green']), # Nature: vert [0, 102, 0]
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_blue']), # Sky: bleu [0, 102, 204]
hex_to_rgb(WCAG_ACCESSIBLE_COLORS['neutral_dark']) # Void: gris foncé [51, 51, 51]
]
# Motifs pour différencier sans couleur uniquement
ACCESSIBILITY_PATTERNS = {
'solid': None,
'diagonal_lines': '///',
'vertical_lines': '|||',
'horizontal_lines': '---',
'dots': '...',
'crosses': 'xxx'
}
def create_accessible_pie_chart_conditional(sizes, labels, title, use_patterns=False):
"""
Crée un pie chart accessible - descriptions uniquement si erreur d'affichage
"""
import matplotlib.pyplot as plt
import matplotlib.patches as patches
# Configuration uniforme (comme dans la solution qui marchait)
fig, ax = plt.subplots(figsize=(6, 6), tight_layout=True)
# Couleurs accessibles
accessible_colors = [
WCAG_ACCESSIBLE_COLORS['primary_blue'],
WCAG_ACCESSIBLE_COLORS['primary_red'],
WCAG_ACCESSIBLE_COLORS['primary_green'],
WCAG_ACCESSIBLE_COLORS['primary_orange'],
WCAG_ACCESSIBLE_COLORS['secondary_purple'],
WCAG_ACCESSIBLE_COLORS['secondary_teal']
]
colors = accessible_colors[:len(sizes)]
# Configuration uniforme des propriétés
uniform_textprops = {'fontsize': 8, 'fontweight': 'bold', 'color': '#333333'}
# Motifs pour accessibilité
patterns = None
if use_patterns:
pattern_list = list(ACCESSIBILITY_PATTERNS.values())
patterns = pattern_list[:len(sizes)]
try:
wedges, texts, autotexts = ax.pie(
sizes,
labels=labels,
colors=colors,
autopct='%1.0f%%',
startangle=90,
shadow=True,
textprops=uniform_textprops, # Propriétés uniformes
wedgeprops={'edgecolor': 'white', 'linewidth': 2}
)
# Ajouter des motifs si demandé
if patterns:
for wedge, pattern in zip(wedges, patterns):
if pattern:
wedge.set_hatch(pattern)
ax.set_title(title, fontsize=10, fontweight='bold', pad=15, color='#333333')
ax.axis('equal') # Assure un cercle parfait
chart_displayed = True
except Exception as e:
# En cas d'erreur, afficher la description alternative
ax.text(0.5, 0.5, f"Erreur d'affichage du graphique\n{str(e)}",
ha='center', va='center', transform=ax.transAxes,
fontsize=10, color='red', weight='bold')
chart_displayed = False
return fig, ax, {'colors': colors, 'patterns': patterns if use_patterns else None}, chart_displayed
def create_accessible_bar_chart_conditional(data, labels, title, colors=None, use_patterns=False):
"""
Crée un bar chart accessible - descriptions uniquement si erreur d'affichage
"""
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
if colors is None:
colors = [WCAG_ACCESSIBLE_COLORS['primary_blue']] * len(data)
# Motifs pour différenciation
patterns = None
if use_patterns:
pattern_list = list(ACCESSIBILITY_PATTERNS.values())
patterns = pattern_list[:len(data)]
try:
bars = ax.bar(
labels,
data,
color=colors,
alpha=0.8,
edgecolor='#333333',
linewidth=1
)
# Ajouter des motifs
if patterns:
for bar, pattern in zip(bars, patterns):
if pattern:
bar.set_hatch(pattern)
# Style accessible
ax.set_ylabel('Valeur', fontweight='bold', color='#333333')
ax.set_xlabel('Catégories', fontweight='bold', color='#333333')
ax.set_title(title, fontsize=14, fontweight='bold', pad=20, color='#333333')
# Grille pour faciliter la lecture
ax.grid(axis='y', alpha=0.3, linestyle='--', color='#666666')
ax.set_axisbelow(True)
# Valeurs sur les barres pour accessibilité
for bar, value in zip(bars, data):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + max(data) * 0.01,
f'{value:.1f}' if isinstance(value, float) else str(value),
ha='center', va='bottom', fontsize=10, fontweight='bold', color='#333333')
# Style des axes
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color('#666666')
ax.spines['bottom'].set_color('#666666')
plt.xticks(rotation=45, ha='right', color='#333333')
plt.yticks(color='#333333')
chart_displayed = True
except Exception as e:
# En cas d'erreur, message d'erreur
ax.text(0.5, 0.5, f"Erreur d'affichage du graphique\n{str(e)}",
ha='center', va='center', transform=ax.transAxes,
fontsize=12, color='red', weight='bold')
chart_displayed = False
return fig, ax, chart_displayed
def generate_alt_text_for_pie(sizes, labels, title):
"""
Génère un texte alternatif détaillé pour un pie chart
"""
total = sum(sizes)
descriptions = []
for size, label in zip(sizes, labels):
percentage = (size / total) * 100
descriptions.append(f"{label}: {size} éléments ({percentage:.1f}%)")
alt_text = f"Graphique en secteurs: {title}. "
alt_text += f"Total de {total} éléments répartis comme suit: "
alt_text += "; ".join(descriptions) + "."
return alt_text
def generate_alt_text_for_bar(data, labels, title, unit=""):
"""
Génère un texte alternatif détaillé pour un bar chart
"""
descriptions = []
for value, label in zip(data, labels):
descriptions.append(f"{label}: {value}{unit}")
alt_text = f"Graphique en barres: {title}. "
alt_text += "Valeurs: " + "; ".join(descriptions) + "."
return alt_text
def create_data_table(data, columns, title, description=""):
"""
Crée un tableau de données accessible complémentaire aux graphiques
"""
import pandas as pd
df = pd.DataFrame(data, columns=columns)
st.markdown(f"**Tableau de données: {title}**")
if description:
st.markdown(f"*{description}*")
# Configuration accessible du dataframe
st.dataframe(
df,
use_container_width=True,
hide_index=True,
column_config={
col: st.column_config.TextColumn(col, width="medium")
for col in columns
}
)
return df
# CSS personnalisé pour l'accessibilité WCAG
WCAG_CSS = """
"""
st.markdown(WCAG_CSS, 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 accessible"""
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:
ordered_class_names = [name for name in models_info['class_names'] if name in stats]
else:
ordered_class_names = sorted(stats.keys())
percentages = [stats[name]['percentage'] for name in ordered_class_names]
# Couleurs accessibles WCAG
accessible_colors = [
WCAG_ACCESSIBLE_COLORS['neutral_medium'], # Flat: gris moyen
WCAG_ACCESSIBLE_COLORS['primary_red'], # Human: rouge
WCAG_ACCESSIBLE_COLORS['secondary_purple'], # Vehicle: violet
WCAG_ACCESSIBLE_COLORS['primary_orange'], # Construction: orange
WCAG_ACCESSIBLE_COLORS['secondary_teal'], # Object: teal
WCAG_ACCESSIBLE_COLORS['primary_green'], # Nature: vert
WCAG_ACCESSIBLE_COLORS['primary_blue'], # Sky: bleu
WCAG_ACCESSIBLE_COLORS['neutral_dark'] # Void: gris foncé
]
colors = accessible_colors[:len(ordered_class_names)]
# Créer le graphique accessible
fig, ax, displayed = create_accessible_bar_chart_conditional(
percentages, ordered_class_names, title,
colors=colors, use_patterns=True
)
# Description alternative SEULEMENT si erreur d'affichage
if not displayed:
alt_text = generate_alt_text_for_bar(
percentages, ordered_class_names, title, unit="%"
)
st.warning(f"**Description du graphique:** {alt_text}")
st.pyplot(fig)
plt.close(fig)
# Tableau de données complémentaire (toujours affiché)
table_data = []
for name, percentage in zip(ordered_class_names, percentages):
pixels = stats[name]['pixels']
table_data.append([name, f"{percentage:.1f}%", f"{pixels:,} pixels"])
create_data_table(
table_data,
["Classe", "Pourcentage", "Nombre de pixels"],
f"Données - {title}",
"Répartition détaillée des classes de segmentation"
)
def display_class_comparison(stats1, stats2, model1, model2, models_info):
"""Affiche une comparaison accessible des classes entre les deux modèles"""
st.markdown("### 🔍 Analyse Comparative par Classe")
# Préparer les données
if models_info and 'class_names' in models_info:
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:
all_classes = set(stats1.keys()) | set(stats2.keys())
ordered_all_classes = sorted(all_classes)
class_names = ordered_all_classes
model1_values = [stats1.get(name, {}).get('percentage', 0) for name in class_names]
model2_values = [stats2.get(name, {}).get('percentage', 0) for name in class_names]
# Couleurs accessibles avec motifs différents
fig, ax = plt.subplots(figsize=(12, 7))
x = np.arange(len(class_names))
width = 0.35
# Couleurs accessibles
color1 = WCAG_ACCESSIBLE_COLORS['primary_blue']
color2 = WCAG_ACCESSIBLE_COLORS['primary_red']
bars1 = ax.bar(x - width/2, model1_values, width,
label=f"{model1.get('architecture', 'N/A')} - {model1['encoder_name']}",
color=color1, alpha=0.8, edgecolor='#333333')
bars2 = ax.bar(x + width/2, model2_values, width,
label=f"{model2.get('architecture', 'N/A')} - {model2['encoder_name']}",
color=color2, alpha=0.8, edgecolor='#333333', hatch='///')
# Style accessible
ax.set_xlabel('Classes', fontsize=11, fontweight='bold', color='#333333')
ax.set_ylabel('Pourcentage (%)', fontsize=11, fontweight='bold', color='#333333')
ax.set_title('Comparaison des classes entre les deux modèles',
fontsize=13, fontweight='bold', pad=15, color='#333333')
ax.set_xticks(x)
if len(class_names) <= 4:
ax.set_xticklabels(class_names, fontsize=10, color='#333333')
elif len(class_names) <= 6:
ax.set_xticklabels(class_names, rotation=30, ha='right', fontsize=9, color='#333333')
else:
ax.set_xticklabels(class_names, rotation=45, ha='right', fontsize=9, color='#333333')
ax.legend(loc='upper right', fontsize=10)
ax.grid(axis='y', alpha=0.3, linestyle='--', color='#666666')
ax.set_axisbelow(True)
# Style des axes
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color('#666666')
ax.spines['bottom'].set_color('#666666')
# Valeurs sur les barres
for bar1, bar2, val1, val2 in zip(bars1, bars2, model1_values, model2_values):
height1, height2 = bar1.get_height(), 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', color='#333333')
ax.text(bar2.get_x() + bar2.get_width()/2., height2 + 0.5,
f'{val2:.1f}%', ha='center', va='bottom', fontsize=8,
fontweight='bold', color='#333333')
plt.tight_layout()
# Description alternative
alt_text = f"Graphique comparatif en barres des classes entre {model1['encoder_name']} et {model2['encoder_name']}. "
comparisons = []
for name, val1, val2 in zip(class_names, model1_values, model2_values):
comparisons.append(f"{name}: {val1:.1f}% vs {val2:.1f}%")
alt_text += "Comparaisons: " + "; ".join(comparisons)
st.markdown(f"**Description du graphique:** {alt_text}")
st.pyplot(fig)
plt.close(fig)
# Tableau détaillé accessible
st.markdown("**🔎 Tableau détaillé des comparaisons:**")
table_data = []
for i, name in enumerate(class_names):
val1 = model1_values[i]
val2 = model2_values[i]
diff = val1 - val2
if abs(diff) < 1:
category = "🟢 Similaire"
elif abs(diff) < 5:
category = "🟡 Différence modérée"
else:
category = "🔴 Différence importante"
table_data.append([
name,
f"{val1:.1f}%",
f"{val2:.1f}%",
f"{diff:+.1f}%",
category
])
create_data_table(
table_data,
[
"Classe",
f"🥇 {model1.get('architecture', 'N/A')[:15]} (%)",
f"🥈 {model2.get('architecture', 'N/A')[:15]} (%)",
"Différence",
"Évaluation"
],
"Comparaison détaillée par classe",
"Analyse des écarts de performance entre les deux modèles pour chaque classe de segmentation"
)
# 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 MODIFIÉE
st.markdown("""
### 🎯 Présentation générale du dataset Cityscapes
#### 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 avec pie charts NOUVEAU
st.markdown("### 🗂️ Composition et utilisation du dataset")
# # Version avec tailles forcées absolument uniformes
# if dataset_info.get('splits'):
# splits_info = dataset_info['splits']
# project_splits = dataset_info.get('project_splits', {})
# st.markdown("#### 📊 Visualisations accessibles")
# # Option pour activer les motifs (accessibilité)
# use_patterns = st.checkbox(
# "🎨 Activer les motifs (accessibilité visuelle)",
# help="Active les motifs en plus des couleurs pour faciliter la distinction des données",
# value=True
# )
# # Organiser en 1 ligne de 4 colonnes
# col1, col2, col3, col4 = st.columns(4)
# # Configuration STRICTEMENT uniforme pour tous les graphiques
# CHART_WIDTH, CHART_HEIGHT = 400, 400 # Taille en pixels
# uniform_figsize = (5, 5) # Taille figure identique
# uniform_dpi = 100 # DPI identique
# # Style uniforme pour TOUS les éléments
# uniform_style = {
# 'textprops': {'fontsize': 8, 'fontweight': 'bold', 'color': '#333333'},
# 'wedgeprops': {'edgecolor': 'white', 'linewidth': 2},
# 'title_props': {'fontsize': 10, 'fontweight': 'bold', 'pad': 15, 'color': '#333333'},
# 'startangle': 90,
# 'shadow': True
# }
# # Couleurs WCAG accessibles
# accessible_colors = {
# 'blue': WCAG_ACCESSIBLE_COLORS['primary_blue'],
# 'red': WCAG_ACCESSIBLE_COLORS['primary_red'],
# 'orange': WCAG_ACCESSIBLE_COLORS['primary_orange'],
# 'gray': WCAG_ACCESSIBLE_COLORS['neutral_medium'],
# 'light_gray': WCAG_ACCESSIBLE_COLORS['neutral_light']
# }
# # Motifs pour accessibilité
# patterns = ['///', '|||', '---', '...', 'xxx', None] if use_patterns else [None] * 6
# # Fonction helper pour créer des pie charts uniformes
# def create_uniform_pie(ax, sizes, labels, colors, explode=None, patterns_slice=None, autopct='%1.0f%%'):
# try:
# wedges, texts, autotexts = ax.pie(
# sizes, labels=labels, colors=colors,
# autopct=autopct, explode=explode,
# **uniform_style
# )
# # Ajouter des motifs si demandé
# if patterns_slice and use_patterns:
# for wedge, pattern in zip(wedges, patterns_slice):
# if pattern:
# wedge.set_hatch(pattern)
# # FORCER les limites d'axes identiques pour TOUS
# ax.set_xlim(-1.5, 1.5)
# ax.set_ylim(-1.5, 1.5)
# ax.axis('equal')
# ax.set_aspect('equal', 'box')
# return True, wedges
# except Exception as e:
# ax.text(0.5, 0.5, f"Erreur d'affichage\n{str(e)}",
# ha='center', va='center', transform=ax.transAxes,
# fontsize=10, color='red', weight='bold')
# return False, None
# # Données pour le tableau récapitulatif
# pie_data_for_table = []
# with col1:
# # Pie chart Train (avec répartition projet)
# fig_train, ax_train = plt.subplots(figsize=uniform_figsize, dpi=uniform_dpi, tight_layout=True)
# train_total = splits_info['train']['images']
# train_our_train = project_splits['train']['images']
# train_our_val = project_splits['validation']['images']
# sizes = [train_our_train, train_our_val]
# labels = [f'Notre Entraînement\n{train_our_train} images (80%)',
# f'Notre Validation\n{train_our_val} images (20%)']
# colors = [accessible_colors['blue'], accessible_colors['red']]
# explode = (0.05, 0.05)
# chart_displayed, wedges = create_uniform_pie(
# ax_train, sizes, labels, colors, explode, patterns[:2]
# )
# ax_train.set_title(f'Split Train Original\n{train_total} images total',
# **uniform_style['title_props'])
# if not chart_displayed:
# alt_text = generate_alt_text_for_pie(sizes, labels, "Split Train")
# st.warning(f"**Description du graphique:** {alt_text}")
# st.pyplot(fig_train)
# plt.close(fig_train)
# pie_data_for_table.extend([
# ["Train - Entraînement", train_our_train, "80%", "Entraînement des modèles"],
# ["Train - Validation", train_our_val, "20%", "Validation pendant entraînement"]
# ])
# with col2:
# # Pie chart Validation (utilisation complète)
# fig_val, ax_val = plt.subplots(figsize=uniform_figsize, dpi=uniform_dpi, tight_layout=True)
# val_total = splits_info['validation']['images']
# val_our_test = project_splits['test']['images']
# sizes = [val_our_test]
# labels = [f'Notre Test Final\n{val_our_test} images (100%)']
# colors = [accessible_colors['orange']]
# chart_displayed, wedges = create_uniform_pie(
# ax_val, sizes, labels, colors, None, [patterns[2]]
# )
# ax_val.set_title(f'Split Validation Original\n{val_total} images total',
# **title_style)
# if not chart_displayed:
# alt_text = generate_alt_text_for_pie(sizes, labels, "Split Validation")
# st.warning(f"**Description du graphique:** {alt_text}")
# st.pyplot(fig_val)
# plt.close(fig_val)
# pie_data_for_table.append(["Validation - Test", val_our_test, "100%", "Test final des modèles"])
# with col3:
# # Pie chart Test (non utilisé)
# fig_test, ax_test = plt.subplots(figsize=uniform_figsize, dpi=uniform_dpi, tight_layout=True)
# test_total = splits_info['test']['images']
# sizes = [test_total]
# labels = [f'Non utilisé dans notre projet\n{test_total} images\n(annotations privées)']
# colors = [accessible_colors['gray']]
# chart_displayed, wedges = create_uniform_pie(
# ax_test, sizes, labels, colors, None, [patterns[3]], autopct=''
# )
# ax_test.set_title(f'Split Test Original\n{test_total} images total',
# **title_style)
# if not chart_displayed:
# alt_text = generate_alt_text_for_pie(sizes, labels, "Split Test")
# st.warning(f"**Description du graphique:** {alt_text}")
# st.pyplot(fig_test)
# plt.close(fig_test)
# pie_data_for_table.append(["Test", 0, "0%", "Non utilisé (annotations privées)"])
# with col4:
# # Pie chart Coarse (non utilisé)
# fig_coarse, ax_coarse = plt.subplots(figsize=uniform_figsize, dpi=uniform_dpi, tight_layout=True)
# coarse_total = splits_info['coarse']['images']
# sizes = [coarse_total]
# labels = [f'Non utilisé dans notre projet\n{coarse_total} images\n(annotations grossières)']
# colors = [accessible_colors['light_gray']]
# chart_displayed, wedges = create_uniform_pie(
# ax_coarse, sizes, labels, colors, None, [patterns[4]], autopct=''
# )
# ax_coarse.set_title(f'Split Coarse Original\n{coarse_total} images total',
# **title_style)
# if not chart_displayed:
# alt_text = generate_alt_text_for_pie(sizes, labels, "Split Coarse")
# st.warning(f"**Description du graphique:** {alt_text}")
# st.pyplot(fig_coarse)
# plt.close(fig_coarse)
# pie_data_for_table.append(["Coarse", 0, "0%", "Non utilisé (annotations grossières)"])
# # Tableau de données accessible (toujours affiché)
# st.markdown("#### 📋 Tableau récapitulatif des données")
# create_data_table(
# pie_data_for_table,
# ["Split", "Images utilisées", "Pourcentage", "Utilisation"],
# "Répartition du dataset Cityscapes",
# "Détail de l'utilisation de chaque split du dataset original dans notre projet"
# )
# Version finale qui combine : tailles uniformes + couleurs WCAG + descriptions conditionnelles
if dataset_info.get('splits'):
splits_info = dataset_info['splits']
project_splits = dataset_info.get('project_splits', {})
st.markdown("#### 📊 Visualisations accessibles")
# Option pour activer les motifs (accessibilité)
use_patterns = st.checkbox(
"🎨 Activer les motifs (accessibilité visuelle)",
help="Active les motifs en plus des couleurs pour faciliter la distinction des données",
value=True
)
# Organiser en 1 ligne de 4 colonnes
col1, col2, col3, col4 = st.columns(4)
# Configuration uniforme pour tous les graphiques (SOLUTION QUI MARCHAIT)
uniform_figsize = (6, 6)
uniform_textprops = {'fontsize': 8, 'fontweight': 'bold', 'color': '#333333'}
uniform_title_props = {'fontsize': 10, 'fontweight': 'bold', 'pad': 15}
# Couleurs WCAG accessibles
accessible_colors = {
'blue': WCAG_ACCESSIBLE_COLORS['primary_blue'],
'red': WCAG_ACCESSIBLE_COLORS['primary_red'],
'orange': WCAG_ACCESSIBLE_COLORS['primary_orange'],
'gray': WCAG_ACCESSIBLE_COLORS['neutral_medium'],
'light_gray': WCAG_ACCESSIBLE_COLORS['neutral_light']
}
# Motifs pour accessibilité
patterns = ['///', '|||', '---', '...', 'xxx', None] if use_patterns else [None] * 6
# Données pour le tableau récapitulatif
pie_data_for_table = []
with col1:
# Pie chart Train (avec répartition projet)
fig_train, ax_train = plt.subplots(figsize=uniform_figsize, tight_layout=True)
train_total = splits_info['train']['images']
train_our_train = project_splits['train']['images']
train_our_val = project_splits['validation']['images']
sizes = [train_our_train, train_our_val]
labels = [f'Notre Entraînement\n{train_our_train} images (80%)',
f'Notre Validation\n{train_our_val} images (20%)']
colors = [accessible_colors['blue'], accessible_colors['red']]
explode = (0.05, 0.05)
try:
wedges, texts, autotexts = ax_train.pie(
sizes, labels=labels, colors=colors, autopct='%1.0f%%',
startangle=90, explode=explode, shadow=True,
textprops=uniform_textprops,
wedgeprops={'edgecolor': 'white', 'linewidth': 2}
)
# Ajouter des motifs si demandé
if use_patterns:
for wedge, pattern in zip(wedges, patterns[:2]):
if pattern:
wedge.set_hatch(pattern)
ax_train.set_title(f'Split Train Original\n{train_total} images total',
**uniform_title_props, color='#333333')
ax_train.axis('equal')
chart_displayed = True
except Exception as e:
ax_train.text(0.5, 0.5, f"Erreur d'affichage\n{str(e)}",
ha='center', va='center', transform=ax_train.transAxes,
fontsize=10, color='red', weight='bold')
chart_displayed = False
# Description SEULEMENT si erreur
if not chart_displayed:
alt_text = generate_alt_text_for_pie(sizes, labels, "Split Train")
st.warning(f"**Description du graphique:** {alt_text}")
st.pyplot(fig_train)
plt.close(fig_train)
pie_data_for_table.extend([
["Train - Entraînement", train_our_train, "80%", "Entraînement des modèles"],
["Train - Validation", train_our_val, "20%", "Validation pendant entraînement"]
])
with col2:
# Pie chart Validation (utilisation complète)
fig_val, ax_val = plt.subplots(figsize=uniform_figsize, tight_layout=True)
val_total = splits_info['validation']['images']
val_our_test = project_splits['test']['images']
sizes = [val_our_test]
labels = [f'Notre Test Final\n{val_our_test} images (100%)']
colors = [accessible_colors['orange']]
try:
wedges, texts, autotexts = ax_val.pie(
sizes, labels=labels, colors=colors, autopct='%1.0f%%',
startangle=90, shadow=True, textprops=uniform_textprops,
wedgeprops={'edgecolor': 'white', 'linewidth': 2}
)
if use_patterns and patterns[2]:
wedges[0].set_hatch(patterns[2])
ax_val.set_title(f'Split Validation Original\n{val_total} images total',
**uniform_title_props, color='#333333')
ax_val.axis('equal')
chart_displayed = True
except Exception as e:
ax_val.text(0.5, 0.5, f"Erreur d'affichage\n{str(e)}",
ha='center', va='center', transform=ax_val.transAxes,
fontsize=10, color='red', weight='bold')
chart_displayed = False
if not chart_displayed:
alt_text = generate_alt_text_for_pie(sizes, labels, "Split Validation")
st.warning(f"**Description du graphique:** {alt_text}")
st.pyplot(fig_val)
plt.close(fig_val)
pie_data_for_table.append(["Validation - Test", val_our_test, "100%", "Test final des modèles"])
with col3:
# Pie chart Test (non utilisé)
fig_test, ax_test = plt.subplots(figsize=uniform_figsize, tight_layout=True)
test_total = splits_info['test']['images']
sizes = [test_total]
labels = [f'Non utilisé dans notre projet\n{test_total} images\n(annotations privées)']
colors = [accessible_colors['gray']]
try:
wedges, texts, autotexts = ax_test.pie(
sizes, labels=labels, colors=colors, autopct='',
startangle=90, shadow=True, textprops=uniform_textprops,
wedgeprops={'edgecolor': 'white', 'linewidth': 2}
)
if use_patterns and patterns[3]:
wedges[0].set_hatch(patterns[3])
ax_test.set_title(f'Split Test Original\n{test_total} images total',
**uniform_title_props, color='#333333')
ax_test.axis('equal')
chart_displayed = True
except Exception as e:
ax_test.text(0.5, 0.5, f"Erreur d'affichage\n{str(e)}",
ha='center', va='center', transform=ax_test.transAxes,
fontsize=10, color='red', weight='bold')
chart_displayed = False
if not chart_displayed:
alt_text = generate_alt_text_for_pie(sizes, labels, "Split Test")
st.warning(f"**Description du graphique:** {alt_text}")
st.pyplot(fig_test)
plt.close(fig_test)
pie_data_for_table.append(["Test", 0, "0%", "Non utilisé (annotations privées)"])
with col4:
# Pie chart Coarse (non utilisé)
fig_coarse, ax_coarse = plt.subplots(figsize=uniform_figsize, tight_layout=True)
coarse_total = splits_info['coarse']['images']
sizes = [coarse_total]
labels = [f'Non utilisé dans notre projet\n{coarse_total} images\n(annotations grossières)']
colors = [accessible_colors['light_gray']]
try:
wedges, texts, autotexts = ax_coarse.pie(
sizes, labels=labels, colors=colors, autopct='',
startangle=90, shadow=True, textprops=uniform_textprops,
wedgeprops={'edgecolor': 'white', 'linewidth': 2}
)
if use_patterns and patterns[4]:
wedges[0].set_hatch(patterns[4])
ax_coarse.set_title(f'Split Coarse Original\n{coarse_total} images total',
**uniform_title_props, color='#333333')
ax_coarse.axis('equal')
chart_displayed = True
except Exception as e:
ax_coarse.text(0.5, 0.5, f"Erreur d'affichage\n{str(e)}",
ha='center', va='center', transform=ax_coarse.transAxes,
fontsize=10, color='red', weight='bold')
chart_displayed = False
if not chart_displayed:
alt_text = generate_alt_text_for_pie(sizes, labels, "Split Coarse")
st.warning(f"**Description du graphique:** {alt_text}")
st.pyplot(fig_coarse)
plt.close(fig_coarse)
pie_data_for_table.append(["Coarse", 0, "0%", "Non utilisé (annotations grossières)"])
# Tableau de données accessible (toujours affiché)
st.markdown("#### 📋 Tableau récapitulatif des données")
create_data_table(
pie_data_for_table,
["Split", "Images utilisées", "Pourcentage", "Utilisation"],
"Répartition du dataset Cityscapes",
"Détail de l'utilisation de chaque split du dataset original dans notre projet"
)
# 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",
key="train_image_selector"
)
selected_filename = image_options[selected_display_name]
# Preview de l'image sélectionnée (avant preprocessing)
if selected_filename and "preprocessing_result" not in st.session_state:
try:
image_url = f"{API_BASE_URL}/train-image/{selected_filename}"
response = requests.get(image_url, timeout=5)
if response.status_code == 200:
preview_image = Image.open(io.BytesIO(response.content))
st.image(
preview_image,
caption=f"Aperçu de l'image sélectionnée : {selected_display_name}",
use_container_width=True
)
except:
st.info("Aperçu non disponible")
# 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. Cliquez plusieurs fois sur 'Appliquer le Preprocessing' pour tester différentes augmentations aléatoires !",
key="augmentation_checkbox"
)
with col2:
if st.button("🔧 Appliquer le Preprocessing", type="primary", key="apply_preprocessing"):
with st.spinner("Application des transformations..."):
result = preprocess_train_image(selected_filename, show_augmentation)
if result and result['success']:
st.session_state.preprocessing_result = result
st.success("Preprocessing terminé!")
st.rerun()
else:
st.error("Erreur lors du preprocessing")
# Bouton pour revenir à l'aperçu
if "preprocessing_result" in st.session_state:
if st.button("🔄 Choisir une nouvelle image", type="secondary", key="reset_preprocessing"):
del st.session_state.preprocessing_result
st.rerun()
# Affichage des résultats de preprocessing
if "preprocessing_result" in st.session_state:
result = st.session_state.preprocessing_result
# Affichage des images transformées sur toute la largeur
st.markdown("#### Résultats des transformations :")
if show_augmentation and result.get('augmented_image'):
cols = st.columns(3)
col_titles = ["Image Originale (redimensionnée)", "Après Normalisation", "Avec Augmentation Aléatoire"]
col_data = [result.get('original_image'), result.get('normalized_image'), result.get('augmented_image')]
else:
cols = st.columns(2)
col_titles = ["Image Originale (redimensionnée)", "Après Normalisation"]
col_data = [result.get('original_image'), result.get('normalized_image')]
for i, (col, title, data) in enumerate(zip(cols, col_titles, col_data)):
with col:
st.markdown(f"**{title}**")
if data:
img_data = base64.b64decode(data)
img = Image.open(io.BytesIO(img_data))
st.image(img, use_container_width=True)
# Explications des transformations appliquées
if result.get('transformations_applied'):
st.markdown("#### 📋 Transformations appliquées :")
# Préparer les transformations pour l'alignement
transforms = result['transformations_applied']
# Extraire les transformations de base (toujours présentes)
base_transforms = [t for t in transforms if "Resize" in t or "Normalization" in t]
augmentation_transforms = [t for t in transforms if "Resize" not in t and "Normalization" not in t]
if show_augmentation and result.get('augmented_image'):
# 3 colonnes quand augmentation active
col1, col2, col3 = st.columns(3)
with col1:
st.markdown("**Image Originale (redimensionnée)**")
resize_transform = next((t for t in base_transforms if "Resize" in t), "1. Resize (512x512)")
st.write(f"• {resize_transform}")
with col2:
st.markdown("**Après Normalisation**")
norm_transform = next((t for t in base_transforms if "Normalization" in t), "2. Normalization (0-1)")
st.write(f"• {norm_transform}")
with col3:
st.markdown("**Avec Augmentation Aléatoire**")
if augmentation_transforms:
for i, transform in enumerate(augmentation_transforms, 3):
st.write(f"• {i}. {transform}")
else:
st.write("• 3. Aucune augmentation appliquée (aléatoire)")
else:
# 2 colonnes quand pas d'augmentation
col1, col2 = st.columns(2)
with col1:
st.markdown("**Image Originale (redimensionnée)**")
resize_transform = next((t for t in base_transforms if "Resize" in t), "1. Resize (512x512)")
st.write(f"• {resize_transform}")
with col2:
st.markdown("**Après Normalisation**")
norm_transform = next((t for t in base_transforms if "Normalization" in t), "2. Normalization (0-1)")
st.write(f"• {norm_transform}")
# Explications détaillées avec description complète des augmentations
with st.expander("ℹ️ Détails des transformations possibles"):
st.markdown("""
**Normalisation standard (toujours appliquée) :**
- **Redimensionnement** : Toutes les images sont redimensionnées à 512×512 pixels
- **Normalisation** : Les valeurs de pixels sont normalisées de [0-255] vers [0-1]
- Cette normalisation uniforme est appliquée à tous les modèles (paramètre encoder non utilisé)
**Augmentations de données possibles (si activées) :**
Le système sélectionne aléatoirement entre 1 et 4 transformations parmi les suivantes :
1. **Rotation aléatoire** : Rotation de l'image entre -15° et +15°
2. **Flip horizontal** : Miroir horizontal avec 50% de probabilité
3. **Changement de luminosité/contraste** : Ajustement de ±20% de la luminosité et du contraste
4. **Flou gaussien léger** : Application d'un flou gaussien subtil (blur 1-3 pixels)
5. **Décalage couleur** : Modification de la teinte, saturation et valeur (±10-15%)
6. **Zoom aléatoire** : Zoom de -10% à +10% (avec probabilité réduite)
**Important :** Les augmentations sont **aléatoires** à chaque application. Cliquez plusieurs fois sur
"Appliquer le Preprocessing" pour voir différentes combinaisons ! Le résultat peut inclure une ou
plusieurs transformations, ou même aucune selon le hasard.
Ces transformations permettent d'améliorer la robustesse et la généralisation du modèle pendant
l'entraînement en exposant le modèle à des variations réalistes des données.
""")
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)