diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..5ab9f6d
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,106 @@
+# Notebooks de développement mais GARDER les samples
+notebooks/*.ipynb
+notebooks/p8_notebook.ipynb
+notebooks/requirements.txt
+.ipynb_checkpoints/
+# GARDER: notebooks/content/data/test_images_sample/ (nécessaire pour l'app)
+
+# Environment files (variables passées via docker run ou compose)
+.env
+.env.*
+!.env.example
+
+# Python cache et build files
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# Virtual environments
+venv/
+env/
+ENV/
+env.bak/
+venv.bak/
+.venv/
+
+# IDE et editor files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+.DS_Store
+Thumbs.db
+
+# Git files
+.git/
+.gitignore
+.gitattributes
+
+# Documentation
+*.md
+!README.MD
+docs/
+documentation/
+
+# Test files
+tests/
+test_*/
+*_test.py
+debug_*.py
+pytest.ini
+tox.ini
+.coverage
+.pytest_cache/
+
+# Jupyter notebook outputs
+*.nbconvert
+
+# Fichiers temporaires
+.cache/
+.tmp/
+tmp/
+temp/
+*.tmp
+*.temp
+*.log
+logs/
+
+# OS generated files
+.DS_Store?
+ehthumbs.db
+Icon?
+
+# MLflow artifacts locaux (seront téléchargés depuis le registry)
+mlruns/
+mlruns.backup/
+mlartifacts/
+
+# Backup files
+*.bak
+*.backup
+
+# Large datasets (samples nécessaires sont gardés)
+*.zip
+*.tar.gz
+*.rar
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..49f2004
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,41 @@
+# =============================================================================
+# Configuration du serveur
+# =============================================================================
+# Port sur lequel nginx écoute (défaut: 8080 pour compatibilité Cloud Run)
+NGINX_PORT=8080
+
+# =============================================================================
+# Configuration MLflow (pour le versioning et le téléchargement des modèles)
+# =============================================================================
+# URI du serveur MLflow de tracking
+MLFLOW_TRACKING_URI=http://localhost:5000
+
+# URL du stockage S3/MinIO pour les artefacts MLflow
+MLFLOW_S3_ENDPOINT_URL=http://localhost:9000
+
+# Clés d'accès S3/MinIO
+AWS_ACCESS_KEY_ID=minio_access_key
+AWS_SECRET_ACCESS_KEY=minio_secret_key
+
+# =============================================================================
+# Configuration email (optionnel - pour les notifications)
+# =============================================================================
+# Serveur SMTP
+SMTP_SERVER=smtp.gmail.com
+SMTP_PORT=587
+
+# Identifiants email
+SMTP_EMAIL=noreply@yourcompany.com
+SMTP_PASSWORD=your_app_password
+SMTP_FROM_ALIAS=Multi-Segmentation API
+
+# Email de l'administrateur (destinataire des notifications)
+ADMIN_EMAIL=admin@yourcompany.com
+
+# =============================================================================
+# Configuration de l'API
+# =============================================================================
+# URL de base de l'API (utilisée par Streamlit pour faire les appels)
+# En développement local: http://localhost:8080/api
+# En production: https://your-domain.com/api
+MULTISEG_API_BASE_URL=http://localhost:8080/api
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..242c724
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+./mlruns*
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..925177b
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,44 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+# Install system dependencies (ajout de git pour segmentation_models)
+RUN apt-get update && \
+ apt-get install -y nginx curl gettext-base git && \
+ # Pour OpenCV si nécessaire
+ apt-get install -y libglib2.0-0 libsm6 libxext6 libxrender-dev libgomp1 && \
+ adduser --system --no-create-home --group www-data || true && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
+
+# Copy app/ d'abord pour avoir requirements.txt
+COPY app/ ./app/
+
+# Install requirements depuis le bon chemin
+RUN pip install --no-cache-dir -r app/requirements.txt
+
+# ✅ CORRECTION: Copy start.sh AVANT les permissions
+COPY start.sh ./start.sh
+
+# Copy du reste après installation (optimise les layers Docker)
+COPY utils/ ./utils/
+COPY cityscapes_config.json .
+COPY notebooks/content/data/test_images_sample/ ./notebooks/content/data/test_images_sample/
+COPY notebooks/content/data/train_images_sample/ ./notebooks/content/data/train_images_sample/
+
+# Copy nginx config
+COPY nginx.conf /etc/nginx/nginx.conf.template
+
+# ✅ CORRECTION: Setup permissions APRÈS avoir copié start.sh + correction fins de ligne
+RUN mkdir -p /var/log/nginx /var/lib/nginx /run && \
+ sed -i 's/\r$//' ./start.sh && \
+ chmod +x ./start.sh
+
+# Environment variables
+ENV NGINX_PORT=8080
+ENV MULTISEG_API_BASE_URL=http://localhost:8080/api
+
+EXPOSE $NGINX_PORT
+
+# ✅ CORRECTION: Chemin cohérent (./start.sh au lieu de /app/start.sh)
+CMD ["./start.sh"]
diff --git a/README.MD b/README.MD
new file mode 100644
index 0000000..6e14ca9
--- /dev/null
+++ b/README.MD
@@ -0,0 +1,116 @@
+# Street Vision PoC - Segmentation Sémantique Urbaine
+
+## 🎯 Description du Projet
+
+**Street Vision PoC** est une preuve de concept démontrant l'amélioration des performances de segmentation sémantique urbaine en comparant les architectures CNN traditionnelles (FPN) aux approches Transformer modernes (SegFormer).
+
+Ce projet évalue empiriquement le potentiel des **Vision Transformers** pour la compréhension de scènes urbaines complexes, avec un focus sur les classes critiques pour la sécurité (piétons, véhicules, signalétique).
+
+### 🔬 Contexte Technique
+
+- **Dataset** : Cityscapes (2,975 images haute résolution annotées pixel-par-pixel)
+- **Architectures comparées** :
+ - **CNN traditionnel** : FPN + EfficientNet-B0/ResNet34
+ - **Vision Transformer** : SegFormer-B0/B1
+- **Résolutions d'évaluation** : 512×512 et 768×768 pixels
+- **Métrique principale** : Mean Intersection over Union (mIoU)
+
+## 📊 Résultats Principaux
+
+| **Modèle** | **mIoU** | **Paramètres** | **Taille** | **Performance** |
+|------------|----------|----------------|------------|-----------------|
+| **SegFormer-B1** | **79.98%** | 13.68M | 52MB | â� **Meilleur** |
+| **FPN+ResNet34** | 79.22% | 23.16M | 88MB | 🔄 Baseline forte |
+| **FPN+EfficientNet-B0** | 78.67% | 5.76M | 22MB | 💡 Compact |
+| **SegFormer-B0** | 77.91% | 3.72M | 14MB | ⚡ Ultra-léger |
+
+**Analyse comparative** : SegFormer-B1 et FPN+ResNet34 atteignent des performances similaires (~79-80% mIoU), mais SegFormer-B1 présente l'avantage d'être plus compact et probablement plus rapide sur GPU. Les versions SegFormer-B2 à B5 promettraient des gains significatifs, mais mes contraintes matérielles n'ont pas permis leur évaluation.
+
+### 🎯 Gains Significatifs par Classes Critiques
+
+- **Human** : +1.78% IoU (sécurité critique)
+- **Vehicle** : +1.05% IoU (obstacles mobiles)
+- **Object** : +0.56% IoU (signalétique urbaine)
+
+## 🚀 Déploiement Rapide
+
+### Docker Hub
+
+L'application est containerisée et disponible sur Docker Hub :
+
+```bash
+docker pull grgmdmn/street-vision-poc_api
+```
+
+### Configuration avec Docker Compose
+
+1. Copiez le fichier d'exemple de configuration :
+```bash
+cp docker-compose.yml.example docker-compose.yml
+```
+
+2. Configurez les variables d'environnement dans `docker-compose.yml`.
+
+3. Lancez l'application :
+```bash
+docker-compose up -d
+```
+
+4. Accédez à l'interface : `http://localhost:$(port)`
+
+## 🎮 Fonctionnalités de l'Interface
+
+### Dashboard Interactif
+
+- **Upload d'images** : Glisser-déposer d'images urbaines
+- **Double inférence** : Comparaison simultanée FPN vs SegFormer
+- **Visualisation** : Masques de segmentation colorés par classe
+- **Métriques temps réel** : Temps d'inférence et scores de confiance
+- **Export** : Téléchargement des résultats de segmentation
+- **Accessibilité WCAG** : Interface conforme aux standards d'accessibilité
+
+### Analyse Exploratoire des Données
+
+- **Statistiques Cityscapes** : Distribution des classes, résolutions natives
+- **Graphiques interactifs** : Répartition pixel par méta-classe
+- **Exemples annotés** : Visualisation échantillons du dataset
+
+## 📈 Méthodologie d'Évaluation
+
+### Approche Comparative
+
+1. **Baseline CNN** : Réentraînement FPN+EfficientNet/ResNet aux résolutions supérieures
+2. **Nouvelle approche** : Évaluation SegFormer avec attention globale
+3. **Protocole rigoureux** : Validation croisée, métriques standardisées
+4. **Analyse par classe** : Performance détaillée sur 8 méta-classes urbaines
+
+### Limites Identifiées
+
+- **Résolution fixe** : Évaluation sans sliding window (vs protocole original)
+- **Ressources limitées** : Tests uniquement sur SegFormer-B0/B1
+- **Conditions contrôlées** : Dataset Cityscapes uniquement (météo favorable)
+
+## 🔬 Insights Techniques
+
+### Avantages SegFormer
+
+- **Attention globale** : Chaque pixel "voit" toute l'image simultanément
+- **Architecture plus légère** : Modèle compact avec moins de paramètres
+- **Robustesse contextuelle** : Meilleure gestion occlusions et objets complexes
+- **Potentiel d'amélioration** : Versions B2-B5 promettraient des gains significatifs
+
+### Trade-offs Identifiés
+
+- **Vitesse CPU classique** : ~2× plus lent que FPN sur processeurs haute performance (Xeon, 9600X)
+- **Vitesse CPU économe** : 3× plus rapide sur processeurs économes en énergie (N100)
+- **Optimisation GPU** : Architecture Transformer favorisée par accélération matérielle
+
+## 📚 Références
+
+- **Xie, E. et al.** (2021). *SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers*. NeurIPS 2021.
+- **Cordts, M. et al.** (2016). *The Cityscapes Dataset for Semantic Urban Scene Understanding*. CVPR 2016.
+- **Segmentation Models PyTorch** - [Framework d'implémentation utilisé](https://github.com/qubvel/segmentation_models.pytorch)
+
+---
+
+**Note** : Cette preuve de concept démontre le potentiel des architectures Transformer pour la segmentation urbaine, tout en soulignant l'importance des ressources computationnelles pour exploiter pleinement leurs capacités.
\ No newline at end of file
diff --git a/app/README.MD b/app/README.MD
new file mode 100644
index 0000000..7b27e38
--- /dev/null
+++ b/app/README.MD
@@ -0,0 +1,108 @@
+
+# Dashboard de Comparaison de Modèles
+
+## Conformité aux Exigences
+
+### Analyse Exploratoire des Données Non Structurées (Images)
+
+**Présentation d'exemples d'images selon les catégories :**
+- Visualisation d'images du dataset Cityscapes organisées par 8 méta-classes de segmentation
+- Sélection interactive d'images d'entraînement et de test avec prévisualisation
+- Interface de navigation par catégories avec noms d'affichage conviviaux
+
+**Comptage et répartition des données :**
+- Graphiques en secteurs (pie charts) détaillant la répartition des 25,000 images Cityscapes
+- Visualisation de l'utilisation projet : 80% train / 20% validation / 100% test
+- Graphique de distribution des classes dans le dataset d'entraînement
+- Tableaux récapitulatifs avec statistiques détaillées par split
+
+**Transformations des images :**
+- Démonstration interactive du preprocessing : redimensionnement et normalisation
+- Visualisation des augmentations de données aléatoires (rotation, flip, luminosité, etc.)
+- Comparaison avant/après transformations avec alignement visuel
+- Documentation complète des 6 types d'augmentations possibles
+
+### Sélection de Données d'Entrée
+
+**Interface de sélection multiple :**
+- Liste déroulante pour images test Cityscapes avec informations sur la vérité terrain
+- Upload de fichiers personnels (JPEG, PNG) avec validation de format
+- Prévisualisation des images sélectionnées avant traitement
+- Gestion des erreurs et feedback utilisateur en temps réel
+
+### Résultats de Prédiction
+
+**Comparaison complète de modèles :**
+- Affichage côte à côte des prédictions des deux meilleurs modèles
+- Visualisations avec image originale, masques prédits et vérité terrain
+- Détection et mise en évidence des erreurs de segmentation
+- Statistiques détaillées par classe avec pourcentages et nombre de pixels
+
+**Métriques de performance :**
+- Temps d'inférence comparatif avec analyse de vitesse
+- Scores de performance (mIoU, Accuracy) par modèle
+- IoU par classe pour analyse fine des performances
+- Graphiques comparatifs interactifs des résultats
+
+### Déploiement Cloud
+
+**Architecture déployée :**
+- Application FastAPI pour l'API de comparaison de modèles
+- Interface Streamlit pour le dashboard utilisateur
+- Déploiement sur infrastructure cloud avec haute disponibilité
+- Gestion automatique des ressources et montée en charge
+
+### Accessibilité WCAG
+
+**Conformité aux critères essentiels :**
+
+#### Contraste des Couleurs (WCAG AA)
+- Palette de couleurs avec ratios de contraste ≥ 4.1:1 pour éléments graphiques
+- Ratios de contraste ≥ 4.5:1 pour le texte sur fond
+- 8 couleurs principales validées : bleu (#0066CC), rouge (#CC0000), vert (#006600), etc.
+- Remplacement des dégradés par des couleurs unies à contraste élevé
+
+#### Alternatives Textuelles
+- Descriptions alternatives détaillées pour tous les graphiques générées automatiquement
+- Textes explicatifs pour chaque visualisation (pie charts, bar charts)
+- Labels ARIA appropriés pour les éléments interactifs
+- Documentation textuelle complète des transformations d'images
+
+#### Indication Non-Couleur Uniquement
+- Motifs et hachures complémentaires aux couleurs (optionnel via checkbox)
+- 6 motifs différents : diagonales, verticales, horizontales, points, croix
+- Différenciation par forme et texture en plus des couleurs
+- Bordures et contours renforcés pour la distinction visuelle
+
+#### Navigation Clavier
+- CSS focus visible avec contours bleus (#0066CC) de 2px
+- Support complet de la navigation par tabulation
+- Indicateurs visuels clairs pour les éléments focusés
+- Respect de l'ordre logique de navigation
+
+#### Tableaux de Données Accessibles
+- Tableaux structurés complémentaires à tous les graphiques
+- En-têtes appropriés avec scope et descriptions
+- Données exportables et lisibles par lecteurs d'écran
+- Configuration responsive pour tous les écrans
+
+**Fonctionnalités d'assistance intégrées :**
+- Checkbox "Activer les motifs (accessibilité visuelle)" pour personnalisation
+- Descriptions automatiques des graphiques avec statistiques complètes
+- Interface adaptable aux besoins des utilisateurs en situation de handicap
+- Documentation complète des fonctionnalités d'accessibilité via endpoint `/accessibility-info`
+
+## Architecture Technique
+
+**Stack technologique :**
+- Backend : FastAPI + PyTorch pour l'inférence des modèles
+- Frontend : Streamlit avec composants accessibles
+- Visualisations : Matplotlib avec palettes WCAG
+- API REST documentée avec validation Pydantic
+- Gestion d'état optimisée pour les comparaisons de modèles
+
+**Performance et fiabilité :**
+- Temps d'inférence optimisés (< 1 seconde par image)
+- Gestion mémoire efficace avec fermeture automatique des figures
+- Validation des formats et tailles de fichiers
+- Nettoyage automatique des fichiers temporaires
diff --git a/app/main.py b/app/main.py
new file mode 100644
index 0000000..a0c8cc8
--- /dev/null
+++ b/app/main.py
@@ -0,0 +1,834 @@
+import os
+import sys
+import traceback
+import tempfile
+import shutil
+from datetime import datetime
+from typing import Optional, List
+from pathlib import Path
+import mlflow
+from fastapi import FastAPI, HTTPException, File, UploadFile
+from fastapi.responses import FileResponse
+from pydantic import BaseModel
+import numpy as np
+import pickle
+import base64
+import matplotlib.pyplot as plt
+import matplotlib.image as mpimg
+
+
+# Configuration MLflow avec variables d'environnement
+def configure_mlflow():
+ """Configure MLflow avec les variables d'environnement"""
+ print("=== DEBUG VARIABLES D'ENVIRONNEMENT ===")
+
+ mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI", "").strip()
+ mlflow_s3_endpoint_url = os.getenv("MLFLOW_S3_ENDPOINT_URL", "").strip()
+ aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID", "").strip()
+ aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY", "").strip()
+
+ print(f"MLFLOW_TRACKING_URI: {mlflow_tracking_uri}")
+ print(f"MLFLOW_S3_ENDPOINT_URL: {mlflow_s3_endpoint_url}")
+ print(f"AWS_ACCESS_KEY_ID: {aws_access_key_id}")
+ print(f"AWS_SECRET_ACCESS_KEY: {'***' if aws_secret_access_key else None} ({'set' if aws_secret_access_key else 'not set'})")
+ print("============================================")
+
+ if not mlflow_tracking_uri:
+ raise ValueError("MLFLOW_TRACKING_URI not found in environment variables")
+
+ mlflow.set_tracking_uri(mlflow_tracking_uri)
+ print(f"MLflow Tracking URI: {mlflow_tracking_uri}")
+
+ if not mlflow_s3_endpoint_url or not aws_access_key_id or not aws_secret_access_key:
+ raise ValueError("Variables d'environnement MLflow manquantes (S3_ENDPOINT, ACCESS_KEY, SECRET_KEY)")
+
+ os.environ["MLFLOW_S3_ENDPOINT_URL"] = mlflow_s3_endpoint_url
+ os.environ["AWS_ACCESS_KEY_ID"] = aws_access_key_id
+ os.environ["AWS_SECRET_ACCESS_KEY"] = aws_secret_access_key
+ print(f"MLflow S3 Endpoint: {mlflow_s3_endpoint_url}")
+ print("✅ Identifiants AWS configurés")
+ print("✅ Configuration MLflow terminée")
+
+# Import des fonctions utils
+sys.path.append('.')
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+from utils.utils import (
+ load_cityscapes_config,
+ load_model,
+ run_inference_and_visualize,
+ colorize_mask
+)
+
+# Configuration
+MODEL_NAME = "StreetsSegmentation"
+SAMPLE_IMAGES_DIR = "./notebooks/content/data/test_images_sample"
+TRAIN_IMAGES_DIR = "./notebooks/content/data/train_images_sample"
+CITYSCAPES_CONFIG_PATH = "./cityscapes_config.json"
+TEMP_UPLOAD_DIR = "/tmp/uploads"
+
+# Modèles de données mis à jour
+class ModelInfo(BaseModel):
+ name: str
+ run_id: str
+ encoder_name: str
+ input_size: tuple
+ rank: int
+ architecture: Optional[str] = None
+ test_mean_iou: Optional[float] = None
+ test_accuracy: Optional[float] = None
+ class_ious: Optional[dict] = None
+
+class ComparisonResult(BaseModel):
+ success: bool
+ message: str
+ model1_info: ModelInfo
+ model2_info: ModelInfo
+ image_path: Optional[str] = None
+ mask_path: Optional[str] = None
+ model1_stats: Optional[dict] = None
+ model2_stats: Optional[dict] = None
+ model1_inference_time: float
+ model2_inference_time: float
+ speed_comparison: dict
+ ground_truth_available: bool = False
+ figure_data: Optional[str] = None
+
+class ImageInfo(BaseModel):
+ filename: str
+ display_name: str
+ has_ground_truth: bool
+
+class AvailableImagesResponse(BaseModel):
+ images: List[ImageInfo]
+ total_count: int
+
+class PreprocessingRequest(BaseModel):
+ filename: str
+ apply_augmentation: bool = False
+
+class PreprocessingResult(BaseModel):
+ success: bool
+ message: str
+ original_image: Optional[str] = None
+ normalized_image: Optional[str] = None
+ augmented_image: Optional[str] = None
+ transformations_applied: Optional[list] = None
+
+class SampleImageRequest(BaseModel):
+ filename: str
+
+# Variables globales pour les deux modèles
+best_model = None
+second_best_model = None
+best_model_info = {}
+second_best_model_info = {}
+mapping_config = None
+
+def get_model_detailed_info(run_id, mapping_config):
+ """Récupère les informations détaillées d'un modèle depuis MLflow"""
+ client = mlflow.tracking.MlflowClient()
+
+ try:
+ run = mlflow.get_run(run_id)
+ params = run.data.params
+ metrics = run.data.metrics
+
+ # Architecture depuis le paramètre model_architecture
+ architecture = params.get("model_architecture", "Unknown").split('_')[0]
+
+ # Métriques de performance
+ test_mean_iou = metrics.get("test_mean_iou", None)
+ test_accuracy = metrics.get("test_accuracy", None)
+
+ # IoU par classe depuis les métriques class_X_iou
+ class_ious = {}
+ if mapping_config and 'group_names' in mapping_config:
+ for i, class_name in enumerate(mapping_config['group_names']):
+ metric_key = f"class_{i}_iou"
+ if metric_key in metrics:
+ class_ious[class_name] = round(metrics[metric_key], 3)
+
+ return {
+ "architecture": architecture,
+ "test_mean_iou": test_mean_iou,
+ "test_accuracy": test_accuracy,
+ "class_ious": class_ious
+ }
+ except Exception as e:
+ print(f"⚠� Erreur lors de la récupération des infos détaillées: {e}")
+ return {
+ "architecture": "Unknown",
+ "test_mean_iou": None,
+ "test_accuracy": None,
+ "class_ious": {}
+ }
+
+def initialize_models():
+ """Initialise les deux meilleurs modèles au démarrage"""
+ global best_model, second_best_model, best_model_info, second_best_model_info, mapping_config
+
+ try:
+ print("🔄 Configuration de MLflow...")
+ configure_mlflow()
+
+ print("🔄 Chargement de la configuration Cityscapes avec couleurs WCAG...")
+ mapping_config = load_cityscapes_config(CITYSCAPES_CONFIG_PATH, verbose=False, use_wcag_colors=True)
+ print("✅ Configuration Cityscapes chargée avec couleurs WCAG")
+
+
+ print("🔄 Chargement du meilleur modèle...")
+ best_model, best_run_id, best_encoder_name, best_img_size = load_model(
+ experiment_name="OC Projet 9",
+ metric="test_mean_iou",
+ top_n=1
+ )
+
+ # Récupérer les infos détaillées du meilleur modèle
+ best_detailed_info = get_model_detailed_info(best_run_id, mapping_config)
+
+ best_model_info = {
+ "name": "Meilleur modèle",
+ "run_id": best_run_id,
+ "encoder_name": best_encoder_name,
+ "input_size": best_img_size,
+ "rank": 1,
+ "architecture": best_detailed_info["architecture"],
+ "test_mean_iou": best_detailed_info["test_mean_iou"],
+ "test_accuracy": best_detailed_info["test_accuracy"],
+ "class_ious": best_detailed_info["class_ious"]
+ }
+
+ print("🔄 Chargement du deuxième meilleur modèle...")
+ second_best_model, second_run_id, second_encoder_name, second_img_size = load_model(
+ experiment_name="OC Projet 9",
+ metric="test_mean_iou",
+ top_n=2
+ )
+
+ # Récupérer les infos détaillées du deuxième modèle
+ second_detailed_info = get_model_detailed_info(second_run_id, mapping_config)
+
+ second_best_model_info = {
+ "name": "Deuxième modèle",
+ "run_id": second_run_id,
+ "encoder_name": second_encoder_name,
+ "input_size": second_img_size,
+ "rank": 2,
+ "architecture": second_detailed_info["architecture"],
+ "test_mean_iou": second_detailed_info["test_mean_iou"],
+ "test_accuracy": second_detailed_info["test_accuracy"],
+ "class_ious": second_detailed_info["class_ious"]
+ }
+
+ os.makedirs(TEMP_UPLOAD_DIR, exist_ok=True)
+
+ except Exception as e:
+ print(f"� Erreur lors de l'initialisation: {e}")
+ traceback.print_exc()
+ raise e
+
+def get_accessibility_info():
+ """Retourne les informations d'accessibilité pour le frontend"""
+ return {
+ "wcag_compliant": True,
+ "color_palette": {
+ "primary_blue": "#0066CC",
+ "primary_red": "#CC0000",
+ "primary_green": "#006600",
+ "primary_orange": "#CC6600",
+ "secondary_purple": "#663399",
+ "secondary_teal": "#006666",
+ "neutral_dark": "#333333",
+ "neutral_medium": "#666666"
+ },
+ "contrast_ratios": {
+ "primary_colors_vs_white": "≥ 4.1:1 (WCAG AA)",
+ "text_vs_background": "≥ 4.5:1 (WCAG AA)"
+ },
+ "features": [
+ "Couleurs à contraste élevé",
+ "Motifs alternatifs aux couleurs",
+ "Descriptions textuelles alternatives en cas d'absence d'images",
+ "Tableaux de données complémentaires",
+ "Navigation clavier supportée"
+ ]
+ }
+
+
+# Initialisation de l'API
+app = FastAPI(
+ title="Multi-Class Segmentation Comparison API",
+ description="API de comparaison de segmentation sémantique avec deux modèles",
+ version="2.0.0"
+)
+
+@app.on_event("startup")
+async def startup_event():
+ try:
+ initialize_models()
+ print("🚀 API démarrée avec succès - Modèles chargés!")
+ except Exception as e:
+ print(f"⚠� ATTENTION: Impossible de charger les modèles au démarrage: {e}")
+
+@app.get("/")
+def root():
+ return {
+ "message": "Multi-Class Segmentation Comparison API - Online",
+ "models_loaded": best_model is not None and second_best_model is not None,
+ "best_model": best_model_info if best_model else None,
+ "second_best_model": second_best_model_info if second_best_model else None,
+ "num_classes": mapping_config['num_classes'] if mapping_config else None
+ }
+
+@app.get("/health")
+def health_check():
+ if best_model is None or second_best_model is None or mapping_config is None:
+ raise HTTPException(status_code=503, detail="Models not loaded")
+ return {
+ "status": "healthy",
+ "models_loaded": True,
+ "timestamp": datetime.utcnow().isoformat()
+ }
+
+
+# Modifier le endpoint /models pour inclure des couleurs accessibles :
+@app.get("/models", response_model=dict)
+def get_models_info():
+ if best_model is None or second_best_model is None:
+ raise HTTPException(status_code=503, detail="Models not loaded")
+
+ # NOUVELLE RÉPARTITION DES COULEURS selon votre mapping final
+ wcag_accessible_colors = [
+ [102, 102, 102], # Flat: neutral_medium (gris moyen pour routes/trottoirs)
+ [204, 0, 0], # Human: primary_red (rouge pour piétons/cyclistes)
+ [102, 51, 153], # Vehicle: secondary_purple (violet pour véhicules)
+ [204, 102, 0], # Construction: primary_orange (orange pour bâtiments)
+ [0, 102, 102], # Object: secondary_teal (teal pour objets/signalétique)
+ [0, 102, 0], # Nature: primary_green (vert pour végétation)
+ [0, 102, 204], # Sky: primary_blue (bleu pour ciel)
+ [51, 51, 51] # Void: neutral_dark (gris foncé pour zones non-définies)
+ ]
+
+ # Mettre à jour la configuration avec les nouvelles couleurs
+ if mapping_config:
+ mapping_config['group_colors'] = np.array(wcag_accessible_colors, dtype=np.uint8)
+
+ return {
+ "best_model": best_model_info,
+ "second_best_model": second_best_model_info,
+ "num_classes": mapping_config['num_classes'],
+ "class_names": mapping_config['group_names'],
+ "class_colors": wcag_accessible_colors,
+ "accessibility_info": get_accessibility_info(),
+ "preprocessing_resolution": best_model_info.get("input_size", (512, 512))
+ }
+
+
+
+@app.get("/dataset-info")
+def get_dataset_info():
+ """Retourne les informations sur le dataset"""
+ return {
+ "name": "Cityscapes Dataset",
+ "description": "Référence internationale pour l'évaluation des algorithmes de segmentation sémantique en environnement urbain",
+ "total_images": 25000,
+ "resolution": "2048×1024 pixels",
+ "cities": 50,
+ "countries": ["Allemagne", "Suisse"],
+ "splits": {
+ "train": {"images": 2975, "usage": "Entraînement avec annotations fines"},
+ "validation": {"images": 500, "usage": "Validation avec annotations de référence"},
+ "test": {"images": 1525, "usage": "Test (annotations non-publiques)"},
+ "coarse": {"images": 20000, "usage": "Entraînement avec annotations grossières"}
+ },
+ "project_splits": {
+ "train": {"images": 2380, "percentage": 80, "source": "Cityscapes train"},
+ "validation": {"images": 595, "percentage": 20, "source": "Cityscapes train"},
+ "test": {"images": 500, "percentage": 100, "source": "Cityscapes val"}
+ },
+ "meta_classes": mapping_config['group_names'] if mapping_config else [],
+ "num_classes": mapping_config['num_classes'] if mapping_config else 0
+ }
+
+@app.get("/train-images", response_model=AvailableImagesResponse)
+def get_train_images():
+ """Retourne la liste des images d'entraînement disponibles"""
+ try:
+ original_dir = Path(TRAIN_IMAGES_DIR) / "original"
+ mask_dir = Path(TRAIN_IMAGES_DIR) / "mask"
+
+ if not original_dir.exists():
+ raise HTTPException(status_code=404, detail="Train images directory not found")
+
+ images = []
+ for img_path in original_dir.glob("*leftImg8bit.png"):
+ # Construire le nom du masque correspondant
+ base_name = img_path.stem.replace("_leftImg8bit", "")
+ mask_name = f"{base_name}_gtFine_labelIds.png"
+ mask_path = mask_dir / mask_name
+
+ # Créer un nom d'affichage plus convivial
+ display_name = base_name.replace("_", " ").title()
+
+ images.append(ImageInfo(
+ filename=img_path.name,
+ display_name=display_name,
+ has_ground_truth=mask_path.exists()
+ ))
+
+ # Trier par nom pour un affichage cohérent
+ images.sort(key=lambda x: x.filename)
+
+ return AvailableImagesResponse(
+ images=images,
+ total_count=len(images)
+ )
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error listing train images: {str(e)}")
+
+@app.get("/train-image/{filename}")
+def get_train_image(filename: str):
+ """Sert une image d'entraînement pour prévisualisation"""
+ try:
+ original_dir = Path(TRAIN_IMAGES_DIR) / "original"
+ image_path = original_dir / filename
+
+ if not image_path.exists():
+ raise HTTPException(status_code=404, detail=f"Train image {filename} not found")
+
+ # Vérifier que c'est bien un fichier image attendu
+ if not filename.endswith('_leftImg8bit.png'):
+ raise HTTPException(status_code=400, detail="Invalid image filename format")
+
+ return FileResponse(
+ image_path,
+ media_type="image/png",
+ headers={"Content-Disposition": f"inline; filename={filename}"}
+ )
+
+ except HTTPException:
+ raise
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error serving train image: {str(e)}")
+
+@app.get("/class-repartition")
+def get_class_repartition():
+ """Sert le graphique de répartition des classes"""
+ try:
+ graph_path = Path(TRAIN_IMAGES_DIR) / "class_repartition.png"
+
+ if not graph_path.exists():
+ raise HTTPException(status_code=404, detail="Class repartition graph not found")
+
+ return FileResponse(
+ graph_path,
+ media_type="image/png",
+ headers={"Content-Disposition": "inline; filename=class_repartition.png"}
+ )
+
+ except HTTPException:
+ raise
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error serving class repartition graph: {str(e)}")
+
+@app.post("/preprocess-train-image", response_model=PreprocessingResult)
+def preprocess_train_image(request: PreprocessingRequest):
+ """Prétraite une image d'entraînement et retourne les versions transformées"""
+ if mapping_config is None:
+ raise HTTPException(status_code=503, detail="Configuration not loaded")
+
+ # Vérifier que le meilleur modèle est chargé
+ if best_model is None or not best_model_info:
+ raise HTTPException(status_code=503, detail="Best model not loaded")
+
+ try:
+ original_dir = Path(TRAIN_IMAGES_DIR) / "original"
+ image_path = original_dir / request.filename
+
+ if not image_path.exists():
+ raise HTTPException(status_code=404, detail=f"Train image {request.filename} not found")
+
+ # Importer les fonctions nécessaires
+ import numpy as np
+ import matplotlib.pyplot as plt
+ from PIL import Image
+ import io
+ import base64
+ from utils.utils import get_preprocessing_fn, apply_random_augmentation
+
+ # Charger l'image originale
+ original_pil = Image.open(image_path).convert('RGB')
+ original_np = np.array(original_pil)
+
+ # UTILISER LA RÉSOLUTION DU MEILLEUR MODÈLE au lieu de (512, 512)
+ target_size = best_model_info.get("input_size", (512, 512))
+ print(f"🎯 Utilisation de la taille du meilleur modèle: {target_size}")
+
+ # Appliquer le preprocessing avec la résolution du meilleur modèle
+ preprocess_fn = get_preprocessing_fn(img_size=target_size, encoder=None)
+ normalized_tensor, _ = preprocess_fn(original_np, None)
+
+ # Convertir le tensor en image pour affichage
+ normalized_np = normalized_tensor.permute(1, 2, 0).numpy()
+ # Dénormaliser pour l'affichage (tensor était normalisé 0-1)
+ normalized_display = (normalized_np * 255).astype(np.uint8)
+
+ # Créer les images en base64 pour l'envoi
+ def img_to_base64(img_array):
+ img_pil = Image.fromarray(img_array)
+ buffer = io.BytesIO()
+ img_pil.save(buffer, format='PNG')
+ return base64.b64encode(buffer.getvalue()).decode()
+
+ # Image originale redimensionnée pour comparaison (avec la taille du meilleur modèle)
+ original_resized = np.array(original_pil.resize(target_size))
+
+ result = PreprocessingResult(
+ success=True,
+ message="Preprocessing completed successfully",
+ original_image=img_to_base64(original_resized),
+ normalized_image=img_to_base64(normalized_display),
+ transformations_applied=[f"Resize {target_size}", "Normalization (0-1)"]
+ )
+
+ # Appliquer l'augmentation si demandée
+ if request.apply_augmentation:
+ augmented_np, transforms_applied = apply_random_augmentation(original_np)
+ # Appliquer aussi le preprocessing à l'image augmentée
+ augmented_tensor, _ = preprocess_fn(augmented_np, None)
+ augmented_display = (augmented_tensor.permute(1, 2, 0).numpy() * 255).astype(np.uint8)
+
+ result.augmented_image = img_to_base64(augmented_display)
+ result.transformations_applied.extend(transforms_applied)
+
+ return result
+
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Preprocessing error: {str(e)}")
+
+@app.get("/best-model-resolution")
+def get_best_model_resolution():
+ """Retourne la résolution utilisée par le meilleur modèle"""
+ if best_model is None or not best_model_info:
+ raise HTTPException(status_code=503, detail="Best model not loaded")
+
+ return {
+ "model_name": best_model_info.get("name", "Unknown"),
+ "encoder_name": best_model_info.get("encoder_name", "Unknown"),
+ "input_size": best_model_info.get("input_size", (512, 512)),
+ "architecture": best_model_info.get("architecture", "Unknown")
+ }
+
+@app.get("/sample-images", response_model=AvailableImagesResponse)
+def get_sample_images():
+ try:
+ original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
+ mask_dir = Path(SAMPLE_IMAGES_DIR) / "mask"
+ if not original_dir.exists():
+ raise HTTPException(status_code=404, detail="Sample images directory not found")
+ images = []
+ for img_path in original_dir.glob("*leftImg8bit.png"):
+ base_name = img_path.stem.replace("_leftImg8bit", "")
+ mask_name = f"{base_name}_gtFine_labelIds.png"
+ mask_path = mask_dir / mask_name
+ display_name = base_name.replace("_", " ").title()
+ images.append(ImageInfo(
+ filename=img_path.name,
+ display_name=display_name,
+ has_ground_truth=mask_path.exists()
+ ))
+ images.sort(key=lambda x: x.filename)
+ return AvailableImagesResponse(images=images, total_count=len(images))
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error listing sample images: {str(e)}")
+
+@app.get("/sample-image/{filename}")
+def get_sample_image(filename: str):
+ try:
+ original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
+ image_path = original_dir / filename
+ if not image_path.exists():
+ raise HTTPException(status_code=404, detail=f"Sample image {filename} not found")
+ if not filename.endswith('_leftImg8bit.png'):
+ raise HTTPException(status_code=400, detail="Invalid image filename format")
+ return FileResponse(
+ image_path,
+ media_type="image/png",
+ headers={"Content-Disposition": f"inline; filename={filename}"}
+ )
+ except HTTPException:
+ raise
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Error serving image: {str(e)}")
+
+@app.post("/compare-sample", response_model=ComparisonResult)
+def compare_sample_image(request: SampleImageRequest):
+ if best_model is None or second_best_model is None or mapping_config is None:
+ raise HTTPException(status_code=503, detail="Models not loaded")
+ try:
+ original_dir = Path(SAMPLE_IMAGES_DIR) / "original"
+ image_path = original_dir / request.filename
+ if not image_path.exists():
+ raise HTTPException(status_code=404, detail=f"Sample image {request.filename} not found")
+ base_name = image_path.stem.replace("_leftImg8bit", "")
+ mask_name = f"{base_name}_gtFine_labelIds.png"
+ mask_path = Path(SAMPLE_IMAGES_DIR) / "mask" / mask_name
+ mask_path_str = str(mask_path) if mask_path.exists() else None
+
+ # Inférence modèle 1 avec sa résolution spécifique
+ pred1, t1, fig1 = run_inference_and_visualize(
+ image_path=str(image_path),
+ mask_path=mask_path_str,
+ model=best_model,
+ encoder_name=None, # Normalisé de la même manière
+ img_size=best_model_info["input_size"], # Utilise la résolution du modèle
+ mapping_config=mapping_config,
+ save_dir=None, show_stats=False, show_plot=False
+ )
+ # Inférence modèle 2 avec sa résolution spécifique
+ pred2, t2, fig2 = run_inference_and_visualize(
+ image_path=str(image_path),
+ mask_path=mask_path_str,
+ model=second_best_model,
+ encoder_name=None, # Normalisé de la même manière
+ img_size=second_best_model_info["input_size"], # Utilise la résolution du modèle
+ mapping_config=mapping_config,
+ save_dir=None, show_stats=False, show_plot=False
+ )
+
+ # Créer une figure combinée avec les deux modèles
+ # Nombre de colonnes : 3 si pas de GT, 4 si GT disponible
+ num_cols = 3 if not (mask_path_str and os.path.exists(mask_path_str)) else 4
+ fig, axes = plt.subplots(2, num_cols, figsize=(5*num_cols, 8))
+
+ # Récupérer les images depuis les figures individuelles
+ axes1 = fig1.get_axes()
+ axes2 = fig2.get_axes()
+
+ # Ligne 1 : Modèle 1
+ for i in range(min(len(axes1), num_cols)):
+ ax_src = axes1[i]
+ ax_dst = axes[0, i]
+
+ # Copier toutes les images de l'axe source
+ for img_obj in ax_src.get_images():
+ ax_dst.imshow(
+ img_obj.get_array(),
+ cmap=img_obj.get_cmap(),
+ alpha=img_obj.get_alpha(),
+ vmin=img_obj.get_clim()[0] if img_obj.get_clim() else None,
+ vmax=img_obj.get_clim()[1] if img_obj.get_clim() else None
+ )
+
+ # Copier le titre avec nom du modèle
+ original_title = ax_src.get_title()
+ if i == 0:
+ new_title = "Image originale"
+ elif "Prédit" in original_title:
+ new_title = f"Prédiction {best_model_info['architecture']} - {best_model_info['encoder_name']}"
+ else:
+ new_title = original_title
+
+ ax_dst.set_title(new_title)
+ ax_dst.axis('off')
+
+ # Ligne 2 : Modèle 2
+ for i in range(min(len(axes2), num_cols)):
+ ax_src = axes2[i]
+ ax_dst = axes[1, i]
+
+ # Copier toutes les images de l'axe source
+ for img_obj in ax_src.get_images():
+ ax_dst.imshow(
+ img_obj.get_array(),
+ cmap=img_obj.get_cmap(),
+ alpha=img_obj.get_alpha(),
+ vmin=img_obj.get_clim()[0] if img_obj.get_clim() else None,
+ vmax=img_obj.get_clim()[1] if img_obj.get_clim() else None
+ )
+
+ # Copier le titre avec nom du modèle
+ original_title = ax_src.get_title()
+ if i == 0:
+ new_title = "Image originale"
+ elif "Prédit" in original_title:
+ new_title = f"Prédiction {second_best_model_info['architecture']} - {second_best_model_info['encoder_name']}"
+ else:
+ new_title = original_title
+
+ ax_dst.set_title(new_title)
+ ax_dst.axis('off')
+
+ plt.tight_layout()
+
+ # Fermer les figures individuelles pour libérer la mémoire
+ plt.close(fig1)
+ plt.close(fig2)
+
+ fig_b64 = base64.b64encode(pickle.dumps(fig)).decode()
+ # Stats
+ def calc(mask):
+ uc, cnts = np.unique(mask, return_counts=True)
+ stats = {}
+ for cid, c in zip(uc, cnts):
+ if cid < len(mapping_config['group_names']):
+ stats[mapping_config['group_names'][cid]] = {
+ "pixels": int(c),
+ "percentage": round((c/mask.size)*100, 1)
+ }
+ return stats
+ stats1, stats2 = calc(pred1), calc(pred2)
+ faster = best_model_info['encoder_name'] if t1 < t2 else second_best_model_info['encoder_name']
+ speedup = round(max(t1, t2) / min(t1, t2), 2)
+ speed_comp = {"faster_model": faster, "speedup_ratio": speedup, "time_difference_ms": abs(t1-t2)*1000}
+ return ComparisonResult(
+ success=True, message="Comparison completed successfully",
+ model1_info=ModelInfo(**best_model_info), model2_info=ModelInfo(**second_best_model_info),
+ image_path=str(image_path), mask_path=mask_path_str,
+ model1_stats=stats1, model2_stats=stats2,
+ model1_inference_time=t1, model2_inference_time=t2,
+ speed_comparison=speed_comp, ground_truth_available=mask_path.exists(),
+ figure_data=fig_b64
+ )
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Comparison error: {str(e)}")
+
+@app.post("/compare-upload", response_model=ComparisonResult)
+def compare_uploaded_image(file: UploadFile = File(...)):
+ if best_model is None or second_best_model is None or mapping_config is None:
+ raise HTTPException(status_code=503, detail="Models not loaded")
+ allowed_types = ["image/jpeg", "image/jpg", "image/png"]
+ if file.content_type not in allowed_types:
+ raise HTTPException(status_code=400, detail=f"Invalid file type. Allowed: {', '.join(allowed_types)}")
+ temp_file_path = None
+ try:
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ temp_filename = f"upload_{timestamp}_{file.filename}"
+ temp_file_path = os.path.join(TEMP_UPLOAD_DIR, temp_filename)
+ with open(temp_file_path, "wb") as buffer:
+ shutil.copyfileobj(file.file, buffer)
+ # Inférences avec résolutions spécifiques
+ pred1, t1, fig1 = run_inference_and_visualize(
+ image_path=temp_file_path, mask_path=None,
+ model=best_model, encoder_name=None, # Normalisé de la même manière
+ img_size=best_model_info["input_size"], mapping_config=mapping_config,
+ save_dir=None, show_stats=False, show_plot=False,
+ )
+ pred2, t2, fig2 = run_inference_and_visualize(
+ image_path=temp_file_path, mask_path=None,
+ model=second_best_model, encoder_name=None, # Normalisé de la même manière
+ img_size=second_best_model_info["input_size"], mapping_config=mapping_config,
+ save_dir=None, show_stats=False, show_plot=False,
+ )
+
+ # Figure combinée pour uploads (pas de GT, donc 2 colonnes seulement)
+ fig, axes = plt.subplots(2, 2, figsize=(10, 8))
+
+ # Récupérer les images depuis les figures individuelles
+ axes1 = fig1.get_axes()
+ axes2 = fig2.get_axes()
+
+ # Ligne 1 : Modèle 1 (Image originale + Prédiction)
+ for i in range(min(len(axes1), 2)):
+ ax_src = axes1[i]
+ ax_dst = axes[0, i]
+
+ # Copier toutes les images de l'axe source
+ for img_obj in ax_src.get_images():
+ ax_dst.imshow(
+ img_obj.get_array(),
+ cmap=img_obj.get_cmap(),
+ alpha=img_obj.get_alpha(),
+ vmin=img_obj.get_clim()[0] if img_obj.get_clim() else None,
+ vmax=img_obj.get_clim()[1] if img_obj.get_clim() else None
+ )
+
+ # Copier le titre avec nom du modèle
+ original_title = ax_src.get_title()
+ if i == 0:
+ new_title = "Image originale"
+ else:
+ new_title = f"Prédiction {best_model_info['architecture']} - {best_model_info['encoder_name']}"
+
+ ax_dst.set_title(new_title)
+ ax_dst.axis('off')
+
+ # Ligne 2 : Modèle 2 (Image originale + Prédiction)
+ for i in range(min(len(axes2), 2)):
+ ax_src = axes2[i]
+ ax_dst = axes[1, i]
+
+ # Copier toutes les images de l'axe source
+ for img_obj in ax_src.get_images():
+ ax_dst.imshow(
+ img_obj.get_array(),
+ cmap=img_obj.get_cmap(),
+ alpha=img_obj.get_alpha(),
+ vmin=img_obj.get_clim()[0] if img_obj.get_clim() else None,
+ vmax=img_obj.get_clim()[1] if img_obj.get_clim() else None
+ )
+
+ # Copier le titre avec nom du modèle
+ original_title = ax_src.get_title()
+ if i == 0:
+ new_title = "Image originale"
+ else:
+ new_title = f"Prédiction {second_best_model_info['architecture']} - {second_best_model_info['encoder_name']}"
+
+ ax_dst.set_title(new_title)
+ ax_dst.axis('off')
+
+ plt.tight_layout()
+
+ # Fermer les figures individuelles pour libérer la mémoire
+ plt.close(fig1)
+ plt.close(fig2)
+
+ fig_b64 = base64.b64encode(pickle.dumps(fig)).decode()
+ # Stats
+ def calc(mask):
+ uc, cnts = np.unique(mask, return_counts=True)
+ stats = {}
+ for cid, c in zip(uc, cnts):
+ if cid < len(mapping_config['group_names']):
+ stats[mapping_config['group_names'][cid]] = {
+ "pixels": int(c),
+ "percentage": round((c/mask.size)*100, 1)
+ }
+ return stats
+ stats1, stats2 = calc(pred1), calc(pred2)
+ faster = best_model_info['encoder_name'] if t1 < t2 else second_best_model_info['encoder_name']
+ speedup = round(max(t1, t2) / min(t1, t2), 2)
+ speed_comp = {"faster_model": faster, "speedup_ratio": speedup, "time_difference_ms": abs(t1-t2)*1000}
+ return ComparisonResult(
+ success=True, message="Comparison completed successfully",
+ model1_info=ModelInfo(**best_model_info), model2_info=ModelInfo(**second_best_model_info),
+ image_path=temp_file_path,
+ model1_stats=stats1, model2_stats=stats2,
+ model1_inference_time=t1, model2_inference_time=t2,
+ speed_comparison=speed_comp, ground_truth_available=False,
+ figure_data=fig_b64
+ )
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Comparison error: {str(e)}")
+ finally:
+ if temp_file_path and os.path.exists(temp_file_path):
+ try: os.remove(temp_file_path)
+ except Exception: pass
+
+@app.get("/metrics")
+def get_metrics():
+ return {
+ "models_loaded": best_model is not None and second_best_model is not None,
+ "best_model_info": best_model_info,
+ "second_best_model_info": second_best_model_info,
+ "num_classes": mapping_config['num_classes'] if mapping_config else None,
+ "timestamp": datetime.utcnow().isoformat()
+ }
+
+# Ajouter cette route après les autres routes :
+@app.get("/accessibility-info")
+def get_accessibility_information():
+ """Retourne les informations sur la conformité d'accessibilité"""
+ return get_accessibility_info()
\ No newline at end of file
diff --git a/app/requirements.txt b/app/requirements.txt
new file mode 100644
index 0000000..eff910c
--- /dev/null
+++ b/app/requirements.txt
@@ -0,0 +1,71 @@
+# =============================================================================
+# Multi-Class Segmentation API - Requirements
+# =============================================================================
+
+# ===== CORE WEB FRAMEWORK =====
+fastapi==0.115.14
+uvicorn==0.35.0
+starlette==0.46.2
+pydantic==2.11.7
+pydantic_core==2.33.2
+python-multipart==0.0.20 # uploads FastAPI
+
+# ===== STREAMLIT =====
+streamlit==1.46.1
+altair==5.5.0 # utilisé par Streamlit
+blinker==1.9.0 # dépendance Streamlit
+
+# ===== HTTP CLIENTS =====
+requests==2.32.4
+httpx==0.28.1 # utilisé en interne par FastAPI
+httpcore==1.0.9
+h11==0.16.0
+
+# ===== MACHINE LEARNING CORE (PyTorch) =====
+torch==2.8.0 # version stable PyTorch
+torchvision==0.23.0 # utile pour les modèles pré-entraînés
+torchaudio==2.8.0 # optionnel (mais recommandé pour compatibilité PyTorch)
+segmentation-models-pytorch==0.5.0
+albumentations==2.0.8
+# version PyTorch de ToTensor
+albumentations[imgaug,opencv]
+
+numpy>=1.24.0,<2.0.0
+scipy==1.16.0
+scikit-learn==1.7.0
+
+# ===== COMPUTER VISION =====
+pillow==11.3.0
+opencv-python-headless==4.9.0.80
+scikit-image==0.25.2
+imageio==2.37.0
+
+# ===== DATA SCIENCE =====
+pandas==2.3.0
+matplotlib==3.10.3
+networkx==3.5
+tifffile==2025.6.11
+lazy_loader==0.4
+
+# ===== MLFLOW & MODEL REGISTRY =====
+mlflow==3.2.0
+mlflow-skinny==3.2.0
+mlflow-tracing==3.2.0
+
+# ===== AWS/S3 SUPPORT =====
+boto3==1.39.2
+botocore==1.39.2
+s3transfer==0.13.0
+
+# ===== UTILITIES =====
+click==8.2.1
+tqdm==4.67.1
+joblib==1.5.1
+python-dateutil
+packaging
+typing_extensions==4.14.0
+
+# ===== MATPLOTLIB ADDONS =====
+cycler==0.12.1
+fonttools==4.58.5
+plotly==5.24.1
diff --git a/app/streamlit_app.py b/app/streamlit_app.py
new file mode 100644
index 0000000..c80c8dd
--- /dev/null
+++ b/app/streamlit_app.py
@@ -0,0 +1,1889 @@
+import os
+import sys
+import requests
+import streamlit as st
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import plotly.express as px
+import plotly.graph_objects as go
+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 avec titre approprié (WCAG 2.4.2)
+st.set_page_config(
+ page_title="Street Vision - Segmentation Comparison | Dashboard Accessible",
+ 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):
+ """Version corrigée qui règle le problème de lisibilité avec les motifs"""
+ fig, ax = plt.subplots(figsize=(6, 6), tight_layout=True)
+
+ 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)]
+
+ # SOLUTION : texte avec fond contrasté
+ uniform_textprops = {
+ 'fontsize': 10, 'fontweight': 'bold', 'color': 'white',
+ 'bbox': dict(boxstyle="round,pad=0.3", facecolor='black', alpha=0.8)
+ }
+
+ try:
+ wedges, texts, autotexts = ax.pie(
+ sizes, labels=labels, colors=colors, autopct='%1.0f%%', startangle=90, shadow=True,
+ textprops=uniform_textprops, wedgeprops={'edgecolor': 'white', 'linewidth': 2},
+ pctdistance=0.85 # Éloigner les pourcentages
+ )
+
+ # Améliorer la lisibilité des pourcentages
+ for autotext in autotexts:
+ autotext.set_bbox(dict(boxstyle="round,pad=0.4", facecolor='black', alpha=0.9))
+ autotext.set_color('white')
+ autotext.set_fontweight('bold')
+ autotext.set_fontsize(9)
+
+ # Motifs APRÈS configuration du texte
+ if use_patterns:
+ patterns = ['///', '|||', '---', '...', 'xxx', '+++']
+ for wedge, pattern in zip(wedges, patterns[:len(wedges)]):
+ if pattern:
+ wedge.set_hatch(pattern)
+
+ ax.set_title(title, fontsize=10, fontweight='bold', pad=15, color='#333333')
+ ax.axis('equal')
+ chart_displayed = True
+
+ except Exception as e:
+ 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
+
+# Fonctions Plotly pour graphiques interactifs
+def create_interactive_pie_chart(sizes, labels, title, models_info=None):
+ """Crée un pie chart interactif avec les couleurs des classes définies dans models_info"""
+
+ # Utiliser les couleurs des classes si disponibles
+ if models_info and 'class_colors' in models_info and 'class_names' in models_info:
+ colors = []
+ class_names = models_info['class_names']
+ class_colors = models_info['class_colors']
+
+ for label in labels:
+ if label in class_names:
+ idx = class_names.index(label)
+ color_rgb = class_colors[idx]
+ color_hex = f"#{color_rgb[0]:02x}{color_rgb[1]:02x}{color_rgb[2]:02x}"
+ colors.append(color_hex)
+ else:
+ # Couleur par défaut si classe non trouvée
+ colors.append('#666666')
+ else:
+ # Fallback vers les couleurs WCAG
+ accessible_colors_list = list(WCAG_ACCESSIBLE_COLORS.values())
+ colors = accessible_colors_list[:len(sizes)]
+
+ df = pd.DataFrame({'labels': labels, 'values': sizes, 'colors': colors})
+
+ fig = go.Figure(data=[go.Pie(
+ labels=df['labels'],
+ values=df['values'],
+ marker=dict(
+ colors=df['colors'],
+ line=dict(color='#FFFFFF', width=3),
+ pattern=dict(
+ shape=['', '/', '\\', '|', '-', '+', 'x', '.'][:len(sizes)],
+ size=8,
+ solidity=0.3
+ )
+ ),
+ textinfo='label+percent',
+ textposition='auto',
+ textfont=dict(
+ size=13,
+ color='white',
+ family="Arial Black"
+ ),
+ hovertemplate='%{label}
Valeur: %{value}
Pourcentage: %{percent}
',
+ hoverlabel=dict(
+ bgcolor="rgba(255,255,255,0.95)",
+ bordercolor="#333333",
+ font=dict(size=14, color="#333333")
+ )
+ )])
+
+ fig.update_layout(
+ title={
+ 'text': title,
+ 'x': 0.5,
+ 'xanchor': 'center',
+ 'font': {'size': 16, 'color': '#333333', 'family': 'Arial Black'}
+ },
+ showlegend=True,
+ legend=dict(
+ orientation="v",
+ yanchor="middle",
+ y=0.5,
+ xanchor="left",
+ x=1.02,
+ font=dict(size=12, color='#333333'),
+ bgcolor='rgba(255,255,255,0.9)',
+ bordercolor='#333333',
+ ),
+ margin=dict(l=20, r=120, t=60, b=20),
+ height=400,
+ plot_bgcolor='rgba(0,0,0,0)',
+ paper_bgcolor='rgba(0,0,0,0)',
+ font=dict(color='#333333')
+ )
+
+ return fig
+
+
+def create_interactive_comparison_chart(model1_stats, model2_stats, model1_name, model2_name):
+ """Crée un graphique en barres interactif WCAG-compliant pour comparer les deux modèles"""
+ classes = list(set(model1_stats.keys()) | set(model2_stats.keys()))
+
+ data = []
+ for class_name in classes:
+ val1 = model1_stats.get(class_name, {}).get('percentage', 0)
+ val2 = model2_stats.get(class_name, {}).get('percentage', 0)
+
+ data.append({'Classe': class_name, 'Modèle': model1_name, 'Pourcentage': val1, 'Pixels': model1_stats.get(class_name, {}).get('pixels', 0)})
+ data.append({'Classe': class_name, 'Modèle': model2_name, 'Pourcentage': val2, 'Pixels': model2_stats.get(class_name, {}).get('pixels', 0)})
+
+ df = pd.DataFrame(data)
+
+ # Filtrer les données pour supprimer les lignes vides potentielles
+ df = df[df['Modèle'].notna() & (df['Modèle'] != '')]
+
+ fig = px.bar(
+ df, x='Classe', y='Pourcentage', color='Modèle', barmode='group',
+ title='Comparaison interactive des classes détectées',
+ hover_data=['Pixels'],
+ color_discrete_map={model1_name: '#0066CC', model2_name: '#CC0000'}
+ # PAS de pattern_shape du tout
+ )
+
+ fig.update_layout(
+ title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 16, 'color': '#333333', 'family': 'Arial Black'}},
+ xaxis=dict(
+ title='Classes de segmentation',
+ titlefont=dict(size=14, color='#666666'), # Plus clair comme vos pie charts
+ tickfont=dict(size=12, color='#666666'), # Plus clair
+ tickangle=45,
+ showgrid=True,
+ gridcolor='#E8E8E8', # Grille beaucoup plus claire
+ gridwidth=1,
+ linecolor='#AAAAAA', # Axe gris moyen
+ linewidth=1,
+ showline=True
+ ),
+ yaxis=dict(
+ title='Pourcentage de l\'image (%)',
+ titlefont=dict(size=14, color='#666666'), # Plus clair
+ tickfont=dict(size=12, color='#666666'), # Plus clair
+ showgrid=True,
+ gridcolor='#E8E8E8', # Grille beaucoup plus claire
+ gridwidth=1,
+ linecolor='#AAAAAA', # Axe gris moyen
+ linewidth=1,
+ showline=True
+ ),
+ legend=dict(
+ title='Modèles',
+ font=dict(size=12, color='#333333'), # Comme vos pie charts
+ bgcolor='rgba(255,255,255,0.9)',
+ bordercolor='#333333',
+ ),
+ hovermode='x unified',
+ height=500,
+ plot_bgcolor='rgba(0,0,0,0)', # Transparent
+ paper_bgcolor='rgba(0,0,0,0)', # Transparent
+ font=dict(color='#333333') # Sans spécifier size qui peut poser problème
+ )
+
+ fig.update_traces(
+ texttemplate='%{y:.1f}%',
+ textposition='outside',
+ textfont=dict(size=11, color='#666666'), # Plus clair pour lisibilité
+ marker=dict(line=dict(color='#FFFFFF', width=1)) # Bordures blanches comme vos pie charts
+ )
+
+ return fig
+
+
+def create_dataset_interactive_overview():
+ """Vue d'ensemble interactive du dataset avec les MÊMES couleurs que 'Visualisations accessibles'"""
+
+ # RÉORGANISER les données pour que l'ordre soit logique et que les couleurs correspondent
+ dataset_data = {
+ 'Split': ['Train (notre projet)', 'Validation (notre projet)', 'Test final', 'Non utilisé'],
+ 'Images': [2380, 595, 500, 21525],
+ 'Utilisation': ['Entraînement', 'Validation', 'Test', 'Non utilisé']
+ }
+
+ df = pd.DataFrame(dataset_data)
+
+ # Couleurs dans l'ordre exact des données
+ colors = [
+ WCAG_ACCESSIBLE_COLORS['primary_blue'], # Train (notre projet) - bleu
+ WCAG_ACCESSIBLE_COLORS['primary_red'], # Validation (notre projet) - rouge
+ WCAG_ACCESSIBLE_COLORS['primary_orange'], # Test final - orange
+ WCAG_ACCESSIBLE_COLORS['neutral_dark'] # Non utilisé - gris foncé
+ ]
+
+ # FORCER l'ordre avec category_orders
+ fig = px.pie(
+ df,
+ values='Images',
+ names='Split',
+ title='Répartition interactive du dataset Cityscapes',
+ color_discrete_sequence=colors,
+ hover_data=['Utilisation'],
+ category_orders={'Split': ['Train (notre projet)', 'Validation (notre projet)', 'Test final', 'Non utilisé']}
+ )
+
+ # FORCER la correspondance couleur-catégorie avec color_discrete_map
+ color_map = {
+ 'Train (notre projet)': WCAG_ACCESSIBLE_COLORS['primary_blue'],
+ 'Validation (notre projet)': WCAG_ACCESSIBLE_COLORS['primary_red'],
+ 'Test final': WCAG_ACCESSIBLE_COLORS['primary_orange'],
+ 'Non utilisé': WCAG_ACCESSIBLE_COLORS['neutral_dark']
+ }
+
+ fig.update_traces(
+ marker=dict(
+ colors=[color_map[split] for split in df['Split']], # Forcer les couleurs exactes
+ line=dict(color='#FFFFFF', width=3),
+ pattern=dict(
+ shape=['|', '/', '\\', ''],
+ size=8,
+ solidity=0.3
+ )
+ ),
+ textposition='auto',
+ textinfo='percent+label',
+ textfont=dict(size=12, color='white', family="Arial Black"),
+ hovertemplate='%{label}
Images: %{value}
Usage: %{customdata[0]}'
+ )
+
+ fig.update_layout(
+ title={'x': 0.5, 'xanchor': 'center', 'font': {'size': 16, 'color': '#333333'}},
+ height=500,
+ legend=dict(
+ font=dict(size=12, color='#333333'),
+ bgcolor='rgba(255,255,255,0.9)',
+ bordercolor='#333333',
+ ),
+ font=dict(color='#333333')
+ )
+
+ return fig
+
+
+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
+
+# Fonction pour générer du texte alternatif (WCAG 1.1.1)
+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
+
+# Fonction pour générer du texte alternatif (WCAG 1.1.1)
+def generate_alt_text_for_images(image_type, model_name=None, stats=None):
+ """Génère un texte alternatif détaillé pour les images de segmentation"""
+ if image_type == "original":
+ return "Image originale en couleur montrant une scène urbaine typique du dataset Cityscapes"
+ elif image_type == "segmentation":
+ base_text = f"Masque de segmentation sémantique prédit par le modèle {model_name or 'IA'}"
+ if stats:
+ # Ajouter les statistiques principales
+ main_classes = sorted(stats.items(), key=lambda x: x[1]['percentage'], reverse=True)[:3]
+ stats_text = ". Classes principales détectées: " + ", ".join([
+ f"{name} ({data['percentage']:.1f}%)" for name, data in main_classes
+ ])
+ return base_text + stats_text
+ return base_text
+ elif image_type == "comparison":
+ return "Visualisation comparative des prédictions de deux modèles de segmentation sémantique"
+ else:
+ return "Image de segmentation sémantique"
+
+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 generate_comprehensive_alt_text(image_type, model_name=None, stats=None, comparison_data=None):
+ """Génère un texte alternatif complet selon WCAG 1.1.1"""
+ if image_type == "preprocessing_original":
+ return "Image urbaine originale redimensionnée à la résolution d'entraînement du modèle, montrant une scène typique du dataset Cityscapes avec véhicules, piétons et infrastructure urbaine"
+
+ elif image_type == "preprocessing_normalized":
+ return "Même image après normalisation des pixels pour l'entraînement du modèle IA, les couleurs peuvent paraître légèrement différentes"
+
+ elif image_type == "preprocessing_augmented":
+ return "Image avec augmentations de données appliquées (rotation, changement de luminosité, etc.) pour améliorer la robustesse du modèle"
+
+ elif image_type == "segmentation_prediction":
+ base_text = f"Masque de segmentation sémantique coloré prédit par le modèle {model_name or 'IA'}"
+ if stats:
+ main_classes = sorted(stats.items(), key=lambda x: x[1]['percentage'], reverse=True)[:3]
+ stats_text = ". Classes principales détectées : " + ", ".join([
+ f"{name} ({data['percentage']:.1f}% de l'image)" for name, data in main_classes
+ ])
+ return base_text + stats_text
+ return base_text
+
+ elif image_type == "comparison_grid":
+ return "Grille comparative montrant l'image originale et les prédictions de segmentation de deux modèles d'IA différents côte à côte pour évaluation"
+
+ else:
+ return "Image de segmentation sémantique urbaine générée par intelligence artificielle"
+
+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 amélioré pour l'accessibilité complète
+ENHANCED_WCAG_CSS = """
+
+"""
+
+st.markdown(ENHANCED_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, context=""):
+ """Affiche les résultats de comparaison avec graphiques interactifs et accessibilité complète"""
+ 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 avec descriptions alternatives CONDITIONNELLES
+ 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}")
+ # Description alternative SEULEMENT en cas d'erreur
+ alt_text = generate_comprehensive_alt_text("comparison_grid")
+ st.warning(f"**Description alternative:** {alt_text}")
+
+ # Section interactive (CE2 - Graphiques interactifs) - SEULEMENT 2 ONGLETS
+ st.markdown("### 📊 Analyse Interactive des Résultats")
+
+ if result.get('model1_stats') and result.get('model2_stats'):
+ # SUPPRIMER l'onglet "Performances"
+ tab1, tab2 = st.tabs(["🥧 Répartitions", "📊 Comparaison"])
+
+ with tab1:
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown("#### Répartition Modèle 1")
+ sizes1 = [data['percentage'] for data in result['model1_stats'].values()]
+ labels1 = list(result['model1_stats'].keys())
+
+ fig_pie1 = create_interactive_pie_chart(sizes1, labels1,
+ f"Distribution - {result['model1_info'].get('architecture', 'N/A')} {result['model1_info']['encoder_name']}",
+ models_info)
+ # AJOUTER key unique pour éviter l'erreur Streamlit
+ st.plotly_chart(fig_pie1, use_container_width=True, key=f"pie_model1_{context}")
+
+ with col2:
+ st.markdown("#### Répartition Modèle 2")
+ sizes2 = [data['percentage'] for data in result['model2_stats'].values()]
+ labels2 = list(result['model2_stats'].keys())
+
+ fig_pie2 = create_interactive_pie_chart(sizes2, labels2,
+ f"Distribution - {result['model2_info'].get('architecture', 'N/A')} {result['model2_info']['encoder_name']}",
+ models_info)
+ # AJOUTER key unique pour éviter l'erreur Streamlit
+ st.plotly_chart(fig_pie2, use_container_width=True, key=f"pie_model2_{context}")
+
+ with tab2:
+ st.markdown("#### Comparaison Interactive des Classes")
+
+ fig_comparison = create_interactive_comparison_chart(
+ result['model1_stats'], result['model2_stats'],
+ f"{result['model1_info'].get('architecture', 'N/A')} {result['model1_info']['encoder_name']}",
+ f"{result['model2_info'].get('architecture', 'N/A')} {result['model2_info']['encoder_name']}"
+ )
+ # AJOUTER key unique pour éviter l'erreur Streamlit
+ st.plotly_chart(fig_comparison, use_container_width=True, key=f"comparison_chart_{context}")
+
+ st.info("💡 **Interactivité :** Survolez les barres, cliquez sur la légende, zoomez en sélectionnant une zone.")
+ # SUPPRIMER la description systématique
+
+ # Garder seulement le tableau détaillé (sans graphique redondant)
+ if result.get('model1_stats') and result.get('model2_stats'):
+ display_class_comparison(result['model1_stats'], result['model2_stats'],
+ result['model1_info'], result['model2_info'], 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 uniquement le tableau détaillé de comparaison des classes (sans graphique redondant)"""
+ st.markdown("### 🔎 Tableau Détaillé des Comparaisons")
+
+ # 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]
+
+ # SUPPRIMER tout le code de génération du graphique matplotlib
+ # GARDER seulement le tableau
+
+ 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"
+ )
+
+# 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**
+ """)
+
+ # Spécificités techniques
+ st.markdown("""
+ ### ⚙� Spécificités techniques
+
+ - **Taille Datase**: 25 000 images haute résolution
+ - **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 (Train + Val)
+
+ 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.
+ """)
+
+ # Composition du dataset avec pie charts NOUVEAU
+ st.markdown("### 🗂� Composition et utilisation du dataset")
+
+ # 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)"])
+
+
+
+
+ st.markdown("### 📊 Exploration Interactive du Dataset")
+
+ fig_dataset = create_dataset_interactive_overview()
+
+ try:
+ st.plotly_chart(fig_dataset, use_container_width=True, key="dataset_overview")
+ except Exception as e:
+ st.warning("**Description :** Graphique circulaire interactif montrant la répartition de l'utilisation du dataset Cityscapes dans notre projet.")
+ st.info("💡 **Interactivité :** Survolez les sections pour voir les détails, cliquez sur la légende pour masquer/afficher des catégories.")
+
+ # 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")
+
+ # 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, "sample")
+
+ # 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, "upload")
+
+ # 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)
+
+
+with st.sidebar:
+ st.markdown("---")
+ st.markdown("## ♿ Conformité WCAG 2.1")
+
+ criteria_status = {
+ "1.1.1 Contenu non textuel": "✅",
+ "1.4.1 Utilisation de la couleur": "✅",
+ "1.4.3 Contraste (minimum)": "✅",
+ "1.4.4 Redimensionnement du texte": "✅",
+ "2.4.2 Titre de page": "✅"
+ }
+
+ for criterion, status in criteria_status.items():
+ st.write(f"{status} {criterion}")
+
+ st.markdown("**Niveau :** AA | **Référence :** [WCAG 2.1](https://www.w3.org/WAI/WCAG21/quickref/)")
+
diff --git a/cityscapes_config.json b/cityscapes_config.json
new file mode 100644
index 0000000..4b61838
--- /dev/null
+++ b/cityscapes_config.json
@@ -0,0 +1,63 @@
+{
+ "class_groups": {
+ "flat": ["road", "sidewalk", "parking", "rail track"],
+ "human": ["person", "rider"],
+ "vehicle": ["car", "truck", "bus", "train", "motorcycle", "bicycle", "caravan", "trailer"],
+ "construction": ["building", "wall", "fence", "guard rail", "bridge", "tunnel"],
+ "object": ["pole", "pole group", "traffic sign", "traffic light"],
+ "nature": ["vegetation", "terrain"],
+ "sky": ["sky"],
+ "void": ["unlabeled", "ego vehicle", "rectification border", "out of roi", "static", "dynamic", "ground"]
+ },
+ "cityscapes_mapping": {
+ "0": 7, "1": 7, "2": 7, "3": 7, "4": 7, "5": 7, "6": 7,
+ "7": 0, "8": 0, "9": 0, "10": 0,
+ "11": 3, "12": 3, "13": 3, "14": 3, "15": 3, "16": 3,
+ "17": 4, "18": 4, "19": 4, "20": 4,
+ "21": 5, "22": 5,
+ "23": 6,
+ "24": 1, "25": 1,
+ "26": 2, "27": 2, "28": 2, "29": 2, "30": 2, "31": 2, "32": 2, "33": 2
+ },
+ "id_to_label": {
+ "0": "unlabeled",
+ "1": "ego vehicle",
+ "2": "rectification border",
+ "3": "out of roi",
+ "4": "static",
+ "5": "dynamic",
+ "6": "ground",
+ "7": "road",
+ "8": "sidewalk",
+ "9": "parking",
+ "10": "rail track",
+ "11": "building",
+ "12": "wall",
+ "13": "fence",
+ "14": "guard rail",
+ "15": "bridge",
+ "16": "tunnel",
+ "17": "pole",
+ "18": "pole group",
+ "19": "traffic light",
+ "20": "traffic sign",
+ "21": "vegetation",
+ "22": "terrain",
+ "23": "sky",
+ "24": "person",
+ "25": "rider",
+ "26": "car",
+ "27": "truck",
+ "28": "bus",
+ "29": "caravan",
+ "30": "trailer",
+ "31": "train",
+ "32": "motorcycle",
+ "33": "bicycle"
+ },
+ "group_colors": [
+ [128, 64, 128], [220, 20, 60], [0, 0, 142], [70, 70, 70],
+ [220, 220, 0], [107, 142, 35], [70, 130, 180], [0, 0, 0]
+ ],
+ "group_names": ["flat", "human", "vehicle", "construction", "object", "nature", "sky", "void"]
+}
diff --git a/docker-compose.yml.example b/docker-compose.yml.example
new file mode 100644
index 0000000..f05e457
--- /dev/null
+++ b/docker-compose.yml.example
@@ -0,0 +1,47 @@
+version: '3.8'
+
+services:
+ street-vision-api:
+ # ✅ PRODUCTION: Image depuis Docker Hub
+ image: grgmdmn/street-vision_api:latest
+
+ container_name: street_vision_api
+
+ ports:
+ - "${NGINX_PORT:-8080}:${NGINX_PORT:-8080}" # Port nginx configurable
+
+ environment:
+ # Configuration du port nginx
+ NGINX_PORT: "${NGINX_PORT:-8080}"
+
+ # Configuration MLflow pour le suivi des modèles
+ MLFLOW_TRACKING_URI: "${MLFLOW_TRACKING_URI}" # ex: https://mlflow.domain.com
+ MLFLOW_S3_ENDPOINT_URL: "${MLFLOW_S3_ENDPOINT_URL}" # ex: url MinIO ou S3
+ AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" # clé ID S3
+ AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" # clé secrète S3
+
+ # Configuration email pour notifications (optionnel)
+ SMTP_SERVER: "${SMTP_SERVER:-}" # serveur SMTP
+ SMTP_PORT: "${SMTP_PORT:-587}" # port SMTP (ex: 587)
+ SMTP_EMAIL: "${SMTP_EMAIL:-}" # email expéditeur
+ SMTP_PASSWORD: "${SMTP_PASSWORD:-}" # mot de passe SMTP
+ SMTP_FROM_ALIAS: "${SMTP_FROM_ALIAS:-Street Vision API}" # alias (optionnel)
+ ADMIN_EMAIL: "${ADMIN_EMAIL:-}" # email admin destinataire
+
+ # URL de base de l'API (utilisée par Streamlit pour rediriger vers FastAPI)
+ MULTISEG_API_BASE_URL: "${MULTISEG_API_BASE_URL}" # ex: https://vision.domain.com/api
+
+ # Pour une mise à jour automatique par watchtower
+ labels:
+ com.centurylinklabs.watchtower.enable: "true"
+
+ # Seulement si vous avez un reverse proxy server
+ # networks:
+ # - swag
+
+ restart: unless-stopped
+
+# Seulement si vous avez un reverse proxy server
+# networks:
+# swag:
+# external: true
diff --git a/docs/Cordts_The_Cityscapes_Dataset_CVPR_2016_paper.pdf b/docs/Cordts_The_Cityscapes_Dataset_CVPR_2016_paper.pdf
new file mode 100644
index 0000000..5145822
--- /dev/null
+++ b/docs/Cordts_The_Cityscapes_Dataset_CVPR_2016_paper.pdf
@@ -0,0 +1,21162 @@
+%PDF-1.3
+1 0 obj
+<<
+/Kids [ 3 0 R 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R ]
+/Type /Pages
+/Count 11
+>>
+endobj
+2 0 obj
+<<
+/Title (The Cityscapes Dataset for Semantic Urban Scene Understanding)
+/Producer (PyPDF2)
+/Author (Marius Cordts\054 Mohamed Omran\054 Sebastian Ramos\054 Timo Rehfeld\054 Markus Enzweiler\054 Rodrigo Benenson\054 Uwe Franke\054 Stefan Roth\054 Bernt Schiele)
+/Subject (2016 IEEE Conference on Computer Vision and Pattern Recognition)
+>>
+endobj
+3 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 15 0 R
+/Resources <<
+/XObject <<
+/x8 16 0 R
+/x10 19 0 R
+/R48 22 0 R
+/x6 23 0 R
+/x12 26 0 R
+/R51 29 0 R
+/R52 30 0 R
+>>
+/ExtGState <<
+/s9 31 0 R
+/s11 34 0 R
+/a0 <<
+/CA 1
+/ca 1
+>>
+/R37 37 0 R
+/s5 38 0 R
+/s7 41 0 R
+>>
+/Font <<
+/R49 44 0 R
+/R46 48 0 R
+/R44 51 0 R
+/R42 55 0 R
+/R40 58 0 R
+/F2 62 0 R
+/F1 63 0 R
+/R38 64 0 R
+/R55 68 0 R
+/R53 71 0 R
+>>
+/ProcSet [ /Text /ImageC /ImageB /PDF /ImageI ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 75 0 R 76 0 R 77 0 R 78 0 R 79 0 R 80 0 R 81 0 R 82 0 R 83 0 R 84 0 R 85 0 R 86 0 R 87 0 R 88 0 R 89 0 R 90 0 R 91 0 R 92 0 R 93 0 R 94 0 R 95 0 R 96 0 R 97 0 R 98 0 R 99 0 R 100 0 R 101 0 R ]
+/Type /Page
+>>
+endobj
+4 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 102 0 R
+/Resources <<
+/XObject <<
+/R78 103 0 R
+/R79 107 0 R
+/R76 108 0 R
+/R98 110 0 R
+/R99 111 0 R
+/R89 112 0 R
+/R88 113 0 R
+/R82 114 0 R
+/R83 115 0 R
+/R91 116 0 R
+/R81 117 0 R
+/R80 118 0 R
+/R87 119 0 R
+/R86 120 0 R
+/R85 121 0 R
+/R84 122 0 R
+/R93 123 0 R
+/R106 124 0 R
+/R101 125 0 R
+/R100 126 0 R
+/R103 127 0 R
+/R102 128 0 R
+/R105 129 0 R
+/R104 130 0 R
+/R95 131 0 R
+/R94 132 0 R
+/R96 133 0 R
+/R92 134 0 R
+/R97 135 0 R
+/R90 136 0 R
+>>
+/ExtGState <<
+/R109 137 0 R
+/R37 37 0 R
+/R110 138 0 R
+/R75 109 0 R
+/R69 104 0 R
+/R68 139 0 R
+/R77 105 0 R
+>>
+/Font <<
+/R72 140 0 R
+/R113 143 0 R
+/R70 146 0 R
+/R111 149 0 R
+/R107 153 0 R
+/R53 71 0 R
+/F2 156 0 R
+/R115 157 0 R
+/F1 161 0 R
+/R38 64 0 R
+/R55 68 0 R
+/R40 58 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 162 0 R 163 0 R 164 0 R 165 0 R 166 0 R ]
+/Type /Page
+>>
+endobj
+5 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 167 0 R
+/Resources <<
+/XObject <<
+/R145 168 0 R
+/R144 169 0 R
+/R146 170 0 R
+/R141 171 0 R
+/R140 172 0 R
+/R143 173 0 R
+/R142 174 0 R
+/R133 175 0 R
+/R134 176 0 R
+/R135 177 0 R
+/R136 178 0 R
+/R138 179 0 R
+/R139 181 0 R
+>>
+/ExtGState <<
+/R37 37 0 R
+/R69 104 0 R
+/R68 139 0 R
+/R77 105 0 R
+/R137 180 0 R
+>>
+/Font <<
+/R147 182 0 R
+/R72 140 0 R
+/R55 68 0 R
+/R70 146 0 R
+/R149 185 0 R
+/R40 58 0 R
+/F2 189 0 R
+/R131 190 0 R
+/F1 194 0 R
+/R38 64 0 R
+/R115 157 0 R
+/R53 71 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 195 0 R 196 0 R 197 0 R 198 0 R 199 0 R 200 0 R 201 0 R 202 0 R ]
+/Type /Page
+>>
+endobj
+6 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 203 0 R
+/Resources <<
+/XObject <<
+/R192 204 0 R
+/R193 205 0 R
+/R190 206 0 R
+/R191 207 0 R
+/R185 208 0 R
+/R187 209 0 R
+/R194 210 0 R
+/R195 211 0 R
+/R189 212 0 R
+/R188 213 0 R
+/R186 214 0 R
+/R183 215 0 R
+/R182 216 0 R
+/R184 217 0 R
+>>
+/ExtGState <<
+/R37 37 0 R
+/R69 104 0 R
+/R77 105 0 R
+/R137 180 0 R
+>>
+/Font <<
+/F2 218 0 R
+/R131 190 0 R
+/R147 182 0 R
+/F1 219 0 R
+/R72 140 0 R
+/R55 68 0 R
+/R70 146 0 R
+/R40 58 0 R
+/R53 71 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 220 0 R 221 0 R 222 0 R 223 0 R 224 0 R 225 0 R 226 0 R 227 0 R 228 0 R 229 0 R 230 0 R 231 0 R 232 0 R 233 0 R 234 0 R 235 0 R 236 0 R 237 0 R 238 0 R 239 0 R 240 0 R 241 0 R 242 0 R 243 0 R 244 0 R ]
+/Type /Page
+>>
+endobj
+7 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 245 0 R
+/Resources <<
+/ExtGState <<
+/R37 37 0 R
+>>
+/Font <<
+/R113 143 0 R
+/R53 71 0 R
+/F2 246 0 R
+/F1 247 0 R
+/R38 64 0 R
+/R55 68 0 R
+/R40 58 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 248 0 R 249 0 R 250 0 R 251 0 R 252 0 R 253 0 R 254 0 R 255 0 R 256 0 R 257 0 R 258 0 R 259 0 R 260 0 R 261 0 R ]
+/Type /Page
+>>
+endobj
+8 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 262 0 R
+/Resources <<
+/ExtGState <<
+/R37 37 0 R
+>>
+/Font <<
+/R147 182 0 R
+/R55 68 0 R
+/R70 146 0 R
+/R40 58 0 R
+/F2 263 0 R
+/R131 190 0 R
+/F1 264 0 R
+/R38 64 0 R
+/R53 71 0 R
+/R276 265 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 269 0 R 270 0 R 271 0 R 272 0 R 273 0 R 274 0 R 275 0 R 276 0 R 277 0 R 278 0 R 279 0 R 280 0 R 281 0 R 282 0 R 283 0 R 284 0 R 285 0 R 286 0 R 287 0 R 288 0 R 289 0 R 290 0 R 291 0 R 292 0 R 293 0 R 294 0 R 295 0 R 296 0 R 297 0 R 298 0 R 299 0 R 300 0 R 301 0 R 302 0 R 303 0 R 304 0 R 305 0 R 306 0 R 307 0 R 308 0 R 309 0 R 310 0 R 311 0 R 312 0 R 313 0 R 314 0 R 315 0 R 316 0 R 317 0 R 318 0 R 319 0 R 320 0 R 321 0 R ]
+/Type /Page
+>>
+endobj
+9 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 322 0 R
+/Resources <<
+/ExtGState <<
+/R37 37 0 R
+>>
+/Font <<
+/R113 143 0 R
+/R70 146 0 R
+/R53 71 0 R
+/F2 323 0 R
+/R131 190 0 R
+/F1 324 0 R
+/R38 64 0 R
+/R55 68 0 R
+/R40 58 0 R
+/R115 157 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 325 0 R 326 0 R 327 0 R 328 0 R 329 0 R 330 0 R 331 0 R 332 0 R 333 0 R 334 0 R 335 0 R 336 0 R 337 0 R 338 0 R 339 0 R 340 0 R 341 0 R 342 0 R 343 0 R 344 0 R 345 0 R 346 0 R 347 0 R 348 0 R 349 0 R 350 0 R 351 0 R 352 0 R 353 0 R 354 0 R 355 0 R 356 0 R 357 0 R 358 0 R 359 0 R 360 0 R 361 0 R 362 0 R 363 0 R 364 0 R 365 0 R ]
+/Type /Page
+>>
+endobj
+10 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 366 0 R
+/Resources <<
+/XObject <<
+/R361 367 0 R
+/R360 368 0 R
+/R344 369 0 R
+/R349 370 0 R
+/R348 371 0 R
+/R350 372 0 R
+/R346 373 0 R
+/R352 374 0 R
+/R353 375 0 R
+/R354 376 0 R
+/R355 377 0 R
+/R356 378 0 R
+/R357 379 0 R
+/R341 380 0 R
+/R340 381 0 R
+/R343 382 0 R
+/R342 383 0 R
+/R359 384 0 R
+/R347 385 0 R
+/R358 386 0 R
+/R351 387 0 R
+>>
+/ExtGState <<
+/R37 37 0 R
+>>
+/Font <<
+/R147 182 0 R
+/R72 140 0 R
+/R55 68 0 R
+/R70 146 0 R
+/R40 58 0 R
+/F2 388 0 R
+/R131 190 0 R
+/F1 389 0 R
+/R38 64 0 R
+/R115 157 0 R
+/R53 71 0 R
+>>
+/ProcSet [ /Text /ImageC /ImageB /PDF /ImageI ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 390 0 R 391 0 R 392 0 R 393 0 R 394 0 R 395 0 R 396 0 R 397 0 R 398 0 R 399 0 R 400 0 R 401 0 R 402 0 R ]
+/Type /Page
+>>
+endobj
+11 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 403 0 R
+/Resources <<
+/ExtGState <<
+/R37 37 0 R
+>>
+/Font <<
+/F2 404 0 R
+/F1 405 0 R
+/R40 58 0 R
+/R38 64 0 R
+/R53 71 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 406 0 R 407 0 R 408 0 R 409 0 R 410 0 R 411 0 R 412 0 R 413 0 R 414 0 R 415 0 R 416 0 R 417 0 R 418 0 R 419 0 R 420 0 R 421 0 R 422 0 R 423 0 R 424 0 R 425 0 R 426 0 R 427 0 R 428 0 R 429 0 R 430 0 R 431 0 R 432 0 R 433 0 R 434 0 R 435 0 R 436 0 R 437 0 R 438 0 R 439 0 R 440 0 R 441 0 R 442 0 R 443 0 R 444 0 R 445 0 R 446 0 R 447 0 R 448 0 R 449 0 R 450 0 R 451 0 R 452 0 R 453 0 R 454 0 R 455 0 R 456 0 R 457 0 R 458 0 R 459 0 R 460 0 R 461 0 R 462 0 R 463 0 R 464 0 R 465 0 R 466 0 R 467 0 R ]
+/Type /Page
+>>
+endobj
+12 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 468 0 R
+/Resources <<
+/ExtGState <<
+/R37 37 0 R
+>>
+/Font <<
+/F2 469 0 R
+/F1 470 0 R
+/R40 58 0 R
+/R53 71 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 471 0 R 472 0 R 473 0 R 474 0 R 475 0 R 476 0 R 477 0 R 478 0 R 479 0 R 480 0 R 481 0 R 482 0 R 483 0 R 484 0 R 485 0 R 486 0 R 487 0 R 488 0 R 489 0 R 490 0 R 491 0 R 492 0 R 493 0 R 494 0 R 495 0 R 496 0 R 497 0 R 498 0 R 499 0 R 500 0 R 501 0 R 502 0 R 503 0 R 504 0 R 505 0 R 506 0 R 507 0 R 508 0 R 509 0 R 510 0 R 511 0 R 512 0 R 513 0 R 514 0 R 515 0 R 516 0 R 517 0 R 518 0 R 519 0 R 520 0 R 521 0 R 522 0 R 523 0 R 524 0 R ]
+/Type /Page
+>>
+endobj
+13 0 obj
+<<
+/Parent 1 0 R
+/Rotate 0
+/Contents 525 0 R
+/Resources <<
+/ExtGState <<
+/R37 37 0 R
+>>
+/Font <<
+/F2 526 0 R
+/F1 527 0 R
+/R40 58 0 R
+/R53 71 0 R
+>>
+/ProcSet [ /ImageC /Text /PDF /ImageI /ImageB ]
+>>
+/CropBox [ 0 0 612 792 ]
+/MediaBox [ 0 0 612 792 ]
+/Annots [ 528 0 R 529 0 R 530 0 R 531 0 R 532 0 R 533 0 R 534 0 R ]
+/Type /Page
+>>
+endobj
+14 0 obj
+<<
+/Type /Catalog
+/Pages 1 0 R
+>>
+endobj
+15 0 obj
+<<
+/Length 22951
+>>
+stream
+q
+q
+q
+0.10000 0 0 0.10000 0 0 cm
+/R37 gs
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R38 14.34620 Tf
+1 0 0 1 96.76020 675.06700 Tm
+[ (The) -249.99300 (Cityscapes) -250.00700 (Dataset) -250.00300 (f) 24.99230 (or) -249.99500 (Semantic) -250 (Urban) -249.99600 (Scene) -250.01200 (Understanding) ] TJ
+/R40 11.95520 Tf
+-12.43630 -37.85820 Td
+[ (Marius) -249.99400 (Cordts) ] TJ
+/R42 7.97010 Tf
+68.74100 4.33906 Td
+[ (1) -0.29866 ] TJ
+/R44 7.97010 Tf
+4.23398 0 Td
+[ (\073) -0.09955 ] TJ
+/R42 7.97010 Tf
+2.35312 0 Td
+[ (2) -0.29866 ] TJ
+/R40 11.95520 Tf
+33.44220 -4.33906 Td
+[ (Mohamed) -250.00400 (Omran) ] TJ
+/R42 7.97010 Tf
+84.66600 4.33906 Td
+[ (3) -0.30019 ] TJ
+/R40 11.95520 Tf
+33.44180 -4.33906 Td
+[ (Sebastian) -249.98300 (Ramos) ] TJ
+/R42 7.97010 Tf
+82.02420 4.33906 Td
+[ (1) -0.30019 ] TJ
+/R44 7.97010 Tf
+4.23398 0 Td
+[ (\073) -0.10109 ] TJ
+/R42 7.97010 Tf
+2.35195 0 Td
+[ (4) -0.30019 ] TJ
+/R40 11.95520 Tf
+33.44300 -4.33906 Td
+[ (T) 34.98980 (imo) -249.99300 (Rehfeld) ] TJ
+/R42 7.97010 Tf
+66.32580 4.33906 Td
+[ (1) -0.30019 ] TJ
+/R44 7.97010 Tf
+4.23398 0 Td
+[ (\073) -0.09802 ] TJ
+/R42 7.97010 Tf
+2.35312 0 Td
+[ (2) -0.30019 ] TJ
+/R40 11.95520 Tf
+-447.69700 -18.28590 Td
+[ (Markus) -250.01400 (Enzweiler) ] TJ
+/R42 7.97010 Tf
+87.97700 4.33789 Td
+[ (1) -0.29866 ] TJ
+/R40 11.95520 Tf
+29.04690 -4.33789 Td
+[ (Rodrigo) -250.01600 (Benenson) ] TJ
+/R42 7.97010 Tf
+89.32890 4.33789 Td
+[ (3) -0.30019 ] TJ
+/R40 11.95520 Tf
+29.04730 -4.33789 Td
+[ (Uwe) -249.99500 (Frank) 10.00440 (e) ] TJ
+/R42 7.97010 Tf
+58.63980 4.33789 Td
+[ (1) -0.30019 ] TJ
+/R40 11.95520 Tf
+29.04690 -4.33789 Td
+[ (Stef) 10.00440 (an) -250.01000 (Roth) ] TJ
+/R42 7.97010 Tf
+56.66720 4.33789 Td
+[ (2) -0.30019 ] TJ
+/R40 11.95520 Tf
+29.04800 -4.33789 Td
+[ (Bernt) -249.98900 (Schiele) ] TJ
+/R42 7.97010 Tf
+64.74800 4.33789 Td
+[ (3) -0.30019 ] TJ
+-400.18600 -22.53400 Td
+[ (1) -0.29866 ] TJ
+/R40 11.95520 Tf
+4.73203 -4.33906 Td
+[ (Daimler) -250.00300 (A) 39.99410 (G) -249.98800 (R\046D\054) ] TJ
+/R42 7.97010 Tf
+93.82380 4.33906 Td
+[ (2) -0.30019 ] TJ
+/R40 11.95520 Tf
+4.73203 -4.33906 Td
+[ (TU) -249.99300 (Darmstadt\054) ] TJ
+/R42 7.97010 Tf
+74.70700 4.33906 Td
+[ (3) -0.30019 ] TJ
+/R40 11.95520 Tf
+4.73203 -4.33906 Td
+[ (MPI) -250.01400 (Informatics\054) ] TJ
+/R42 7.97010 Tf
+85.33480 4.33906 Td
+[ (4) -0.30019 ] TJ
+/R40 11.95520 Tf
+4.73320 -4.33906 Td
+[ (TU) -249.99300 (Dresden) ] TJ
+ET
+Q
+0 1 0 0 k
+q
+10 0 0 10 0 0 cm
+BT
+/R46 11.95520 Tf
+1 0 0 1 204.11300 586.77900 Tm
+[ (www\056cityscapes\055) -41.99230 (dataset\056net) ] TJ
+ET
+Q
+q
+1633.54000 0 0 638.10000 501.12000 5087.71000 cm
+/R48 Do
+Q
+1 g
+q
+10 0 0 10 0 0 cm
+BT
+/R49 7.97010 Tf
+1 0 0 1 59.91290 513.67200 Tm
+[ (tr) 10.02360 (ain\057v) 25 (al) -277.99700 (\226) -277.97600 <026e65> -278.02300 (annotation) -277.98600 (\226) ] TJ
+/R42 7.97010 Tf
+97.63200 0 Td
+[ (3) -0.29866 (4) -0.30019 (7) -0.29866 (5) -0.29866 ] TJ
+/R49 7.97010 Tf
+19.15310 0 Td
+(images) Tj
+ET
+Q
+q
+1633.44000 0 0 638.06200 2159.37000 5087.71000 cm
+/R51 Do
+Q
+q
+10 0 0 10 0 0 cm
+BT
+/R49 7.97010 Tf
+1 0 0 1 223.28700 513.67200 Tm
+[ (tr) 10.02280 (ain) -277.98300 (\226) -277.97700 (coarse) -278.00400 (annotation) -277.98600 (\226) ] TJ
+/R42 7.97010 Tf
+96.50080 0 Td
+[ (2) -0.30019 (0) -177.29200 (0) -0.30019 (0) -0.29713 (0) -0.30019 ] TJ
+/R49 7.97010 Tf
+24.79800 0 Td
+(images) Tj
+ET
+Q
+q
+1633.54000 0 0 638.10000 3817.61000 5087.71000 cm
+/R52 Do
+Q
+q
+10 0 0 10 0 0 cm
+BT
+/R49 7.97010 Tf
+1 0 0 1 398.09600 513.67200 Tm
+[ (test) -278.02300 (\226) -277.97400 <026e65> -278.02300 (annotation) -277.98600 (\226) ] TJ
+/R42 7.97010 Tf
+82.85590 0 Td
+[ (1) -0.30019 (5) -0.30019 (2) -0.30019 (5) -0.29407 ] TJ
+/R49 7.97010 Tf
+19.15310 0 Td
+(images) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R38 11.95520 Tf
+1 0 0 1 145.99500 481.87200 Tm
+(Abstract) Tj
+/R53 9.96260 Tf
+-83.92770 -23.90980 Td
+[ (V) 73.99130 (isual) -331.99200 (under) 10.01550 (standing) -332.01700 (of) -330.99100 (comple) 20.00770 (x) -331.98900 (urban) -331.99600 (str) 36.99380 (eet) -331.98600 (scenes) -332.01800 (is) ] TJ
+-11.95510 -11.95510 Td
+[ (an) -352.01000 (enabling) -351.98500 (factor) -351.99400 (for) -352.00100 (a) -352.01500 (wide) -351.99900 (r) 14.98400 (ang) 10.01300 (e) -351.98500 (of) -352.00700 (applications\056) -617.01000 (Ob\055) ] TJ
+11.95510 TL
+T*
+[ (ject) -440 (detection) -441.00100 (has) -439.98900 <62656e6502746564> -441.01000 (enormously) -440.01000 (fr) 44.98510 (om) -441.01700 (lar) 36.99140 (g) 10.00320 (e\055scale) ] TJ
+11.95590 TL
+T*
+[ (datasets\054) -442.00400 (especially) -403.01000 (in) -403.99900 (the) -402.98400 (conte) 20.00520 (xt) -404.00900 (of) -403.99900 (deep) -402.98900 (learning) 15.01470 (\056) -771.00600 (F) 105.00700 (or) ] TJ
+11.95510 TL
+T*
+[ (semantic) -345.99600 (urban) -345.01500 (scene) -345.98400 (under) 10.01550 (standing) 10.00450 (\054) -368.99500 (howe) 14.99750 (ver) 110.99900 (\054) -370.01400 (no) -346.01100 (curr) 36.98650 (ent) ] TJ
+T*
+[ (dataset) -412.99800 (adequately) -413.00200 (captur) 36.99870 (es) -412.00800 (the) -412.98300 (comple) 20.00890 (xity) -412.98500 (of) -413.01700 (r) 37.01830 (eal\055world) ] TJ
+T*
+[ (urban) -316 (scenes\056) -507.00700 (T) 92 (o) -315.00100 (addr) 36.99510 (ess) -316.01800 (this\054) -331.99500 (we) -316.01700 (intr) 44.99740 (oduce) ] TJ
+/R40 9.96260 Tf
+182.30200 0 Td
+(Cityscapes) Tj
+/R53 9.96260 Tf
+43.16720 0 Td
+[ (\054) -331.98100 (a) ] TJ
+-225.46900 -11.95510 Td
+[ (benc) 15.01830 (hmark) -370.00200 (suite) -369.99600 (and) -370.00200 (lar) 36.99260 (g) 10.00320 (e\055scal) 0.98635 (e) -369.98200 (dataset) -369.98500 (to) -370.00400 (tr) 14.99010 (ain) -370 (and) -370.00200 (test) ] TJ
+T*
+[ (appr) 44.99370 (oac) 14.98340 (hes) -348.01400 (for) -346.98300 (pixel\055le) 14.98770 (vel) -347.98200 (and) -348.00600 (i) 0.98758 (nstance\055le) 14.99750 (vel) -347.98400 (semantic) -347.99600 (la\055) ] TJ
+11.95590 TL
+T*
+[ (beling) 15.00300 (\056) -618.01200 (Cityscapes) -351.98300 (is) -352.98600 (comprised) -351.99200 (of) -352.98800 (a) -352.01500 (lar) 36.99140 (g) 10.00320 (e) 9.99343 (\054) -378.99300 (diver) 10.00810 (se) -352.01700 (set) -352.99000 (of) ] TJ
+11.95510 TL
+T*
+[ (ster) 36.98950 (eo) -253.01600 (video) -251.98900 (sequences) -253.00700 (r) 37.01960 (ecor) 37.01590 (ded) -251.99100 (in) -253.00400 (str) 36.99380 (eets) -253.01200 (fr) 44.98510 (om) ] TJ
+/R55 9.96260 Tf
+190.55600 0 Td
+(50) Tj
+/R53 9.96260 Tf
+12.47930 0 Td
+[ (dif) 18.01660 (fer) 36.98400 (ent) ] TJ
+-203.03500 -11.95510 Td
+(cities\056) Tj
+/R55 9.96260 Tf
+29.75590 0 Td
+(5000) Tj
+/R53 9.96260 Tf
+23.46520 0 Td
+[ (of) -354.98700 (these) -356.00900 (ima) 10.01300 (g) 10.00320 (es) -354.99800 (have) -354.99800 (high) -355.01700 (quality) -355.98700 (pixel\055le) 14.98770 (vel) ] TJ
+-53.22110 -11.95510 Td
+(annotations\073) Tj
+/R55 9.96260 Tf
+54.44490 0 Td
+[ (20) -166.98200 (000) ] TJ
+/R53 9.96260 Tf
+30.11410 0 Td
+[ (additional) -356 (ima) 10.01300 (g) 10.00320 (es) -356.01700 (have) -356.01700 (coar) 10.01060 (se) -356.01700 (anno\055) ] TJ
+-84.55900 -11.95470 Td
+[ (tations) -392.99000 (to) -392.98100 (enable) -394 (methods) -393.00500 (that) -393.00800 (le) 14.98030 (ver) 15.01470 (a) 10.00320 (g) 10.00320 (e) -392.99800 (lar) 36.99140 (g) 10.00320 (e) -392.99800 (volumes) -393.99600 (of) ] TJ
+T*
+[ (weakly\055labeled) -387.98400 (data\056) -722.99200 (Crucially) 55.01290 (\054) -422.00600 (our) -387.01800 (ef) 18 (fort) -387.98900 (e) 19.99180 (xceeds) -387.98200 (pr) 36.98650 (e) 15.01220 (vi\055) ] TJ
+11.95590 TL
+T*
+[ (ous) -326.00800 (attempts) -325.98800 (in) -326.01200 (terms) -326.00600 (of) -326.01200 (dataset) -325.99300 (size) 10.01300 (\054) -344.99900 (annotation) -326.00700 (ric) 14.98770 (hness\054) ] TJ
+11.95510 TL
+T*
+[ (scene) -325.00600 (variability) 55.01900 (\054) -344.99900 (and) -324.99000 (comple) 20.00770 (xity) 55.01660 (\056) -535.98700 (Our) -324.99000 (accompanying) -325.99200 (em\055) ] TJ
+T*
+[ (pirical) -381.00100 (study) -381.01500 (pr) 44.98390 (o) 10.00320 (vides) -380.00500 (an) -380.98500 (in\055depth) -381.00800 (analysis) -380.99800 (of) -380.98300 (the) -380.98800 (dataset) ] TJ
+T*
+[ (c) 15.01280 (har) 14.99380 (acteristics\054) -241.01600 (as) -239.00800 (well) -238.99600 (as) -239.00800 (a) -239.01400 (performance) -240.00900 (e) 15.01220 (valuation) -239.01600 (of) -239.00700 (se) 15.01960 (v\055) ] TJ
+T*
+[ (er) 15.01890 (al) -249.98500 (state\055of\055the\055art) -249.98600 (appr) 44.99370 (oac) 14.98400 (hes) -249.99000 (based) -249.98000 (on) -249.98800 (our) -249.98000 (benc) 15.01960 (hmark\056) ] TJ
+/R38 11.95520 Tf
+35.24380 TL
+T*
+[ (1\056) -249.99000 (Intr) 18.01460 (oduction) ] TJ
+/R40 9.96260 Tf
+11.95510 -18.92930 Td
+[ (V) 59.99310 (isual) -338.98100 (scene) -337.98500 (understanding) -338.99700 (has) -338.01500 (mo) 14.99500 (v) 14.98280 (ed) -339.00200 (from) -338.01200 (an) -339.00200 (elusi) 24.99830 (v) 14.98280 (e) ] TJ
+-11.95510 -11.95470 Td
+[ (goal) -411.01700 (to) -411.01700 (a) -410.99500 (focus) -410.00100 (of) -410.98900 (much) -411.01100 (recent) -410.99400 (research) -411.00300 (in) -411.01700 (computer) -411.00700 (vi\055) ] TJ
+11.95510 TL
+T*
+[ (sion) -242.01500 (\133) ] TJ
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 72.44840 152.73100 Tm
+(27) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 82.41090 152.73100 Tm
+[ (\135\056) -307.98800 (Semantic) -242.00500 (reasoning) -242.01700 (about) -243 (the) -241.99100 (contents) -242.01100 (of) -241.99600 (a) -242.98400 (scene) ] TJ
+-32.29880 -11.95510 Td
+[ (is) -459.98800 (thereby) -459.99100 (done) -459.99100 (on) -459.99200 (se) 25.01670 (v) 14.98280 (eral) -460.00700 (le) 25.01790 (v) 14.98280 (els) -459.99300 (of) -460 (abstraction\056) -940.01000 (Scene) ] TJ
+11.95510 TL
+T*
+[ (recognition) -410.99500 (aims) -409.98900 (to) -411.01700 (determine) -411.00900 (the) -410.00300 (o) 14.98280 (v) 14.98280 (erall) -410.98800 (scene) -410.99300 (cate) 15.01220 (gory) ] TJ
+11.95590 TL
+T*
+[ (by) -257.00600 (putting) -257.01300 (emphasis) -256.00300 (on) -257.00600 (understanding) -257.01100 (its) -256.99600 (global) -256.99100 (properties\054) ] TJ
+/R53 9.96260 Tf
+11.95510 TL
+T*
+[ (e) 15.01280 (\056g) ] TJ
+/R40 9.96260 Tf
+11.74610 0 Td
+[ (\056) -360.99600 (\133) ] TJ
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 71.26290 104.91000 Tm
+(46) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 81.22540 104.91000 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 85.74840 104.91000 Tm
+(82) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 95.71090 104.91000 Tm
+[ (\135\056) -642.01100 (Scene) -360.01200 (labeling) -361.01000 (methods\054) -388.99400 (on) -360.00900 (the) -360.99100 (other) -360.98900 (hand\054) ] TJ
+-45.59880 -11.95510 Td
+[ (seek) -305.00500 (to) -304.01500 (identify) -305.01800 (the) -303.98100 (indi) 25 (vidual) -304.98300 (constituent) -303.98100 (parts) -304.99800 (of) -303.98600 (a) -305.01300 (whole) ] TJ
+11.95510 TL
+T*
+[ (scene) -323.00700 (as) -323.98300 (well) -322.99300 (as) -323.00400 (their) -324.01200 (interrelations) -323.00600 (on) -324.01500 (a) -323.01000 (more) -322.99500 (local) -324.01500 (pix) 14.99500 (el\055) ] TJ
+258.75000 400.87200 Td
+[ (and) -295.98500 (instance\055le) 24.99340 (v) 14.98280 (el\054) ] TJ
+/R53 9.96260 Tf
+77.81800 0 Td
+[ (e) 15.01220 (\056g) ] TJ
+/R40 9.96260 Tf
+11.74610 0 Td
+[ (\056) -295.98700 (\133) ] TJ
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 407.18300 481.87200 Tm
+(41) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 417.14600 481.87200 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 421.23000 481.87200 Tm
+(71) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 431.19300 481.87200 Tm
+[ (\135\056) -448.00400 (Specialized) -296.00500 (object\055centric) ] TJ
+-122.33100 -11.95510 Td
+[ (methods) -275.01500 (f) 9.99588 (all) -273.98400 (some) 24.99580 (where) -275.01800 (in) -275.00300 (between) -275.00800 (by) -273.98400 (focusing) -274.98800 (on) -275.00300 (detect\055) ] TJ
+11.95510 TL
+T*
+[ (ing) -231.98300 (a) -232.00500 (certain) -231.99800 (subset) -232.01500 (of) -231.99800 (\050mostly) -232 (dynamic\051) -232.01500 (scene) -232 (constituents\054) ] TJ
+/R53 9.96260 Tf
+T*
+[ (e) 15.01220 (\056g) ] TJ
+/R40 9.96260 Tf
+11.74610 0 Td
+[ (\056) -194.98500 (\133) ] TJ
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 328.35900 446.00700 Tm
+(6) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 333.34000 446.00700 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 336.22000 446.00700 Tm
+(12) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 346.18200 446.00700 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 349.06100 446.00700 Tm
+(13) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 359.02400 446.00700 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 361.90300 446.00700 Tm
+(15) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 371.86600 446.00700 Tm
+[ (\135\056) -291.99000 (Despite) -195.98700 <7369676e690263616e74> -195.01600 (adv) 24.98110 (ances\054) -206.00500 (visual) -196.02100 (scene) ] TJ
+-63.00350 -11.95630 Td
+[ (understanding) -323 (remains) -324.00500 (challenging\054) -341.00700 (particularly) -322.98000 (when) -324.01500 (tak\055) ] TJ
+11.95510 TL
+T*
+[ (ing) -250.02000 (human) -250.01500 (performance) -249.99700 (as) -249.99700 (a) -250.00200 (reference\056) ] TJ
+11.95510 -15.13090 Td
+[ (The) -329 (resurrection) -330.01100 (of) -329.00100 (deep) -329.98400 (learning) -328.98400 (\133) ] TJ
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 463.37200 406.96500 Tm
+(34) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 473.33400 406.96500 Tm
+[ (\135) -329.00900 (has) -330.01800 (had) -329 (a) -329.98900 (major) ] TJ
+-164.47200 -11.95510 Td
+[ (impact) -365.01500 (on) -364.00800 (the) -364.99100 (current) -364.99500 (state\055of\055the\055art) -364.00800 (in) -364.98800 (machine) -364.98300 (learning) ] TJ
+11.95470 TL
+T*
+[ (and) -354.99500 (computer) -355.99700 (vision\056) -626.01600 (Man) 14.98770 (y) -354.99500 (top\055performing) -356.01900 (methods) -355.00400 (in) -356.00900 (a) ] TJ
+T*
+[ (v) 24.98110 (ariety) -231.99300 (of) -231.99800 (applications) -231.01800 (are) -232.01000 (no) 24.98600 (w) 10.00320 (adays) -231.99100 (b) 20.00160 (uilt) -232.01500 (around) -231.98600 (deep) -232 (neu\055) ] TJ
+T*
+[ (ral) -394.01300 (netw) 10.00810 (orks) -395.02000 (\133) ] TJ
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 366.97400 359.14500 Tm
+(30) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 376.93700 359.14500 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 381.67900 359.14500 Tm
+(41) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 391.64100 359.14500 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 396.39400 359.14500 Tm
+(66) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 406.35600 359.14500 Tm
+[ (\135\056) -743.01300 (A) -394.00800 (major) -395.01700 (contrib) 20.01630 (uting) -393.98300 (f) 9.99343 (actor) -394.99300 (to) ] TJ
+-97.49410 -11.95590 Td
+[ (their) -203.01300 (success) -203.01500 (is) -201.99300 (the) -203.01800 (a) 19.99180 (v) 24.98110 (ailability) -202.98100 (of) -202.98600 (lar) 17.99700 (ge\055scale\054) -212.00400 (publicly) -202.98600 (a) 19.99180 (v) 24.98110 (ail\055) ] TJ
+11.95510 TL
+T*
+[ (able) -376.01300 (datasets) -375.99600 (such) -376.00300 (as) ] TJ
+/R53 9.96260 Tf
+89.68400 0 Td
+[ (Ima) 10.00320 (g) 10.00320 (eNet) ] TJ
+/R40 9.96260 Tf
+42.46090 0 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 444.32500 335.23400 Tm
+(59) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 454.28700 335.23400 Tm
+(\135\054) Tj
+/R53 9.96260 Tf
+9.86914 0 Td
+[ (P) 89.98990 (ASCAL) -376.01600 (V) 30 (OC) ] TJ
+/R40 9.96260 Tf
+61.86800 0 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 529.34200 335.23400 Tm
+(14) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 539.30400 335.23400 Tm
+(\135\054) Tj
+/R53 9.96260 Tf
+-230.44200 -11.95510 Td
+[ (P) 89.98990 (ASCAL\055Conte) 19.98200 (xt) ] TJ
+/R40 9.96260 Tf
+72.48400 0 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 384.66400 323.27900 Tm
+(45) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 394.62600 323.27900 Tm
+[ (\135\054) -396.99300 (and) ] TJ
+/R53 9.96260 Tf
+27.81210 0 Td
+[ (Micr) 44.98270 (osoft) -367.98800 (COCO) ] TJ
+/R40 9.96260 Tf
+73.29690 0 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 499.05300 323.27900 Tm
+(38) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 509.01500 323.27900 Tm
+[ (\135) -367.98300 (that) -366.98300 (al\055) ] TJ
+-200.15300 -11.95510 Td
+[ (lo) 24.98850 (w) -249.99500 (deep) -249.99500 (neural) -249.99300 (netw) 10.00810 (orks) -249.98300 (to) -249.98800 (de) 25.01540 (v) 14.98280 (elop) -249.98300 (their) -249.98800 (full) -249.98300 (potential\056) ] TJ
+11.95510 -15.13090 Td
+[ (Despite) -369.99700 (the) -370.00900 (e) 15.01220 (xisting) -370.99400 (g) 4.98446 (ap) -370.01700 (to) -370.00700 (human) -369.99200 (performance\054) -400.98700 (scene) ] TJ
+-11.95510 -11.95510 Td
+[ (understanding) -440.98000 (approaches) -440 (ha) 19.99670 (v) 14.98280 (e) -440.99000 (started) -439.99000 (to) -441.01500 (become) -441.01500 (essen\055) ] TJ
+11.95590 TL
+T*
+[ (tial) -397.01500 (components) -396.99000 (of) -398.01000 (adv) 24.98110 (anced) -396.99700 (real\055w) 9.99343 (orld) -397.01700 (systems\056) -751.99200 (A) -396.98800 (par) 19.99180 (\055) ] TJ
+11.95510 TL
+T*
+[ (ticularly) -280.00500 (popular) -280.01200 (and) -279.98800 (challenging) -279.99700 (application) -281.01700 (in) 40.00310 (v) 20.00160 (olv) 14.99750 (es) -279.99200 (self\055) ] TJ
+T*
+[ (dri) 24.98600 (ving) -540.00500 (cars\054) -612.01800 (which) -540.01300 (mak) 10.01300 (e) -539.01300 (e) 15.01220 (xtreme) -540.01300 (demands) -540.00300 (on) -540.01800 (system) ] TJ
+T*
+[ (performance) -370.01700 (and) -370.01200 (reliability) 65.00880 (\056) -670 (Consequently) 64.98430 (\054) -400.98700 <7369676e690263616e74> -370.00700 (re\055) ] TJ
+T*
+[ (search) -363.98300 (ef) 25.00810 (forts) -364.98600 (ha) 19.99670 (v) 14.98280 (e) -363.98300 (gone) -364.00800 (into) -365.01300 (ne) 25.01540 (w) -364.01300 (vision) -363.98300 (technologies) -364.99800 (for) ] TJ
+T*
+[ (understanding) -412.00500 (comple) 14.99750 (x) -412.00500 (traf) 25.01050 <0263> -411.02000 (scenes) -412.00500 (and) -412.00500 (dri) 24.98600 (ving) -411.98500 (scenar) 19.99180 (\055) ] TJ
+11.95590 TL
+T*
+[ (ios) -332.98600 (\133) ] TJ
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 327.12300 200.55200 Tm
+(4) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 332.10500 200.55200 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 336.43800 200.55200 Tm
+(16) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 346.40100 200.55200 Tm
+(\226) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 351.38200 200.55200 Tm
+(18) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 361.34500 200.55200 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 365.68800 200.55200 Tm
+(58) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 375.65100 200.55200 Tm
+(\054) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 379.98400 200.55200 Tm
+(62) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 389.94700 200.55200 Tm
+[ (\135\056) -559.98500 (Also) -332.98400 (in) -332.99300 (this) -333.01800 (area\054) -353.99500 (research) -333.01800 (progress) ] TJ
+-81.08480 -11.95510 Td
+[ (can) -359.98200 (be) -358.99900 (hea) 19.99420 (vily) -359.99400 (link) 9.98363 (ed) -360.01800 (to) -358.98700 (the) -360.00900 (e) 15.01220 (xistence) -359.99900 (of) -358.99900 (datasets) -359.99900 (such) -360.00900 (as) ] TJ
+11.95510 TL
+(the) '
+/R53 9.96260 Tf
+14.10700 0 Td
+[ (KITTI) -194.01200 (V) 73.99260 (ision) -194.01700 (Benc) 15.01710 (hmark) -193.99200 (Suite) ] TJ
+/R40 9.96260 Tf
+121.96400 0 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 448.25100 176.64100 Tm
+(19) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 458.21300 176.64100 Tm
+(\135\054) Tj
+/R53 9.96260 Tf
+7.85273 0 Td
+[ (CamV) 74.01220 (id) ] TJ
+/R40 9.96260 Tf
+34.12190 0 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 503.50500 176.64100 Tm
+(7) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 508.48700 176.64100 Tm
+(\135\054) Tj
+/R53 9.96260 Tf
+7.85352 0 Td
+(Leuven) Tj
+/R40 9.96260 Tf
+-207.47800 -11.95510 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 312.18000 164.68600 Tm
+(35) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 322.14200 164.68600 Tm
+[ (\135\054) -214.00400 (and) ] TJ
+/R53 9.96260 Tf
+24.37380 0 Td
+[ (Daimler) -204.99800 (Urban) -205.98000 (Se) 39.99330 (gmentation) ] TJ
+/R40 9.96260 Tf
+119.19100 0 Td
+(\133) Tj
+ET
+Q
+0 1 0 rg
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 469.02500 164.68600 Tm
+(61) Tj
+ET
+Q
+0 g
+q
+10 0 0 10 0 0 cm
+BT
+/R40 9.96260 Tf
+1 0 0 1 478.98700 164.68600 Tm
+[ (\135) -204.99000 (datasets\056) -294.99000 (These) ] TJ
+-170.12500 -11.95510 Td
+[ (urban) -340.01400 (scene) -339.98500 (datasets) -340.00200 (are) -339.99500 (often) -340.01200 (much) -340.00200 (smaller) -340.00200 (than) -340.00700 (datasets) ] TJ
+11.95510 TL
+T*
+[ (addressing) -286.00400 (more) -285.00100 (general) -285.99100 (settings\056) -416.98400 (Moreo) 14.99260 (v) 14.98280 (er) 39.98350 (\054) -295.00500 (we) -285.99100 (ar) 17.98720 (gue) -285.98700 (that) ] TJ
+11.95590 TL
+T*
+[ (the) 14.98520 (y) -457.01700 (do) -457.99200 (not) -457.00500 (fully) -457.98000 (capture) -456.98300 (the) -457.99200 (v) 24.98110 (ariability) -456.98700 (and) -457.99700 (comple) 14.99750 (xity) ] TJ
+11.95510 TL
+T*
+[ (of) -343 (real\055w) 9.99343 (orld) -342.00700 (inner) 20.00650 (\055city) -342.98900 (traf) 25.01050 <0263> -342.01200 (scenes\056) -588.01500 (Both) -343.01100 (shortcomings) ] TJ
+T*
+[ (currently) -425.01200 (inhibit) -424.98300 (further) -424.98500 (progress) -425.01200 (in) -425.01700 (visual) -425.00300 (understanding) ] TJ
+T*
+[ (of) -382.01200 (street) -382.00700 (scenes\056) -707.01200 (T) 79.99160 (o) -382.01000 (this) -383.01000 (end\054) -414.98000 (we) -382.01500 (propose) -381.99500 (the) ] TJ
+/R53 9.96260 Tf
+193.08300 0 Td
+(Cityscapes) Tj
+/R40 9.96260 Tf
+-193.08300 -11.95510 Td
+[ (benchmark) -360.98100 (suit) 1.00473 (e) -361.00300 (and) -360.99400 (a) -359.98400 (corresponding) -361.00300 (dataset\054) -387.99900 <73706563690263616c6c79> ] TJ
+-13.74100 -29.88790 Td
+(1) Tj
+ET
+Q
+Q
+Q
+q
+q
+1 1 1 rg
+/a0 gs
+48.40600 786.42200 515.18800 -52.69900 re
+f
+q
+/s5 gs
+/x6 Do
+Q
+q
+/s7 gs
+/x8 Do
+Q
+q
+/s9 gs
+/x10 Do
+Q
+q
+/s11 gs
+/x12 Do
+Q
+Q
+Q
+Q
+q
+1 0 0 1 0 0 cm
+BT
+/F1 12 Tf
+14.40000 TL
+ET
+1 1 1 rg
+n
+270 47 72 14 re
+f*
+0.50000 0.50000 0.50000 rg
+BT
+/F2 9 Tf
+10.80000 TL
+ET
+BT
+1 0 0 1 297 50 Tm
+(3213) Tj
+T*
+ET
+Q
+
+endstream
+endobj
+16 0 obj
+<<
+/Filter /FlateDecode
+/Resources <<
+/ExtGState <<
+/a0 <<
+/CA 1
+/ca 1
+>>
+>>
+/XObject <<
+/x18 17 0 R
+>>
+>>
+/Length 28
+/Group <<
+/Type /Group
+/S /Transparency
+/CS /DeviceRGB
+/I true
+>>
+/BBox [ 78 746 96 765 ]
+/Type /XObject
+/Subtype /Form
+>>
+stream
+xœ+äÒO4PH/VЯ0´PpÉç
+ä 0 ž
+
+endstream
+endobj
+17 0 obj
+<<
+/Filter /FlateDecode
+/Resources 18 0 R
+/Length 107
+/Type /XObject
+/BBox [ 78 746 96 765 ]
+/Subtype /Form
+>>
+stream
+xœe�»
ACó©Â˜ý̬wÊ Ž ‰=p™õü,?Ð]%…¡áª+H-<ñÚpºloÓ䮬ž9L±©C>ØJÇœc�Êèõ ¾ƒ"82wÒ8VõSnÿGW;Û"
+endstream
+endobj
+18 0 obj
+<<
+/ExtGState <<
+/a0 <<
+/CA 1
+/ca 1
+>>
+>>
+>>
+endobj
+19 0 obj
+<<
+/Filter /FlateDecode
+/Resources <<
+/ExtGState <<
+/a0 <<
+/CA 1
+/ca 1
+>>
+>>
+/XObject <<
+/x21 20 0 R
+>>
+>>
+/Length 28
+/Group <<
+/Type /Group
+/S /Transparency
+/CS /DeviceRGB
+/I true
+>>
+/BBox [ 96 752 110 775 ]
+/Type /XObject
+/Subtype /Form
+>>
+stream
+xœ+äÒO4PH/VЯ02TpÉç
+ä 0w˜
+
+endstream
+endobj
+20 0 obj
+<<
+/Filter /FlateDecode
+/Resources 21 0 R
+/Length 118
+/Type /XObject
+/BBox [ 96 752 110 775 ]
+/Subtype /Form
+>>
+stream
+xœeÎ=QàžSp„Ã#lãZìêýq’×Í|ÅdîÀdì…LÃB:D¨[ácÅÓ¸>¡™*FI&î(\Tù…í=s–˜²×&ŒáGuJÑ©Iªÿ�Y\I³ é}Ìü#
·ùë.ð�!.ð
+endstream
+endobj
+21 0 obj
+<<
+/ExtGState <<
+/a0 <<
+/CA 1
+/ca 1
+>>
+>>
+>>
+endobj
+22 0 obj
+<<
+/Filter /DCTDecode
+/BitsPerComponent 8
+/Height 200
+/Length 11847
+/ColorSpace /DeviceRGB
+/Width 512
+/Subtype /Image
+>>
+stream
+ÿØÿî Adobe d ÿÛ C
+
$, !$4.763.22:ASF:=N>22HbINVX]^]8EfmeZlS[]YÿÛ C**Y;2;YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYÿÀ È " ÿÄ
+ÿÄ µ } !1AQa"q2�‘¡#B±ÁRÑð$3br‚
+%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ
+ÿÄ µ w !1AQaq"2�B‘¡±Á #3RðbrÑ
+$4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ? ä׆Z› Ž•ëRõDìGËØiÆiÎ2M78÷ u5,Gƒô¨‡SRÄh$8#Ì