minor changes
106
.dockerignore
Normal file
|
|
@ -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
|
||||
41
.env.example
Normal file
|
|
@ -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
|
||||
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
./mlruns*
|
||||
44
Dockerfile
Normal file
|
|
@ -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"]
|
||||
116
README.MD
Normal file
|
|
@ -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.
|
||||
108
app/README.MD
Normal file
|
|
@ -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
|
||||
834
app/main.py
Normal file
|
|
@ -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()
|
||||
71
app/requirements.txt
Normal file
|
|
@ -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
|
||||
1889
app/streamlit_app.py
Normal file
63
cityscapes_config.json
Normal file
|
|
@ -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"]
|
||||
}
|
||||
47
docker-compose.yml.example
Normal file
|
|
@ -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
|
||||
21162
docs/Cordts_The_Cityscapes_Dataset_CVPR_2016_paper.pdf
Normal file
230
docs/Multi Segmentation Project - Dashboard Specs html code
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cahier des Charges Dashboard</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 15px 35px rgba(0,0,0,0.1);
|
||||
max-width: 950px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #7f8c8d;
|
||||
font-size: 16px;
|
||||
margin-bottom: 25px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
padding: 15px 12px;
|
||||
text-align: center;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px;
|
||||
border: 2px solid #ecf0f1;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.category {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.exploratoire {
|
||||
background: linear-gradient(135deg, #e74c3c, #c0392b);
|
||||
}
|
||||
|
||||
.comparaison {
|
||||
background: linear-gradient(135deg, #f39c12, #d68910);
|
||||
}
|
||||
|
||||
.accessibilite {
|
||||
background: linear-gradient(135deg, #27ae60, #229954);
|
||||
}
|
||||
|
||||
.technique {
|
||||
background: linear-gradient(135deg, #8e44ad, #7d3c98);
|
||||
}
|
||||
|
||||
.requirement {
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.implementation {
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
color: #2c3e50;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tech-stack {
|
||||
margin-top: 20px;
|
||||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
border-left: 5px solid #6c757d;
|
||||
}
|
||||
|
||||
.tech-stack h3 {
|
||||
color: #495057;
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.tech-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tech-item {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
padding: 5px 12px;
|
||||
border-radius: 15px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="title">Cahier des Charges Dashboard Interactif</div>
|
||||
<div class="subtitle">Spécifications et implémentation - Projet DataSpace</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%;">Catégorie</th>
|
||||
<th style="width: 45%;">Exigences Projet</th>
|
||||
<th style="width: 35%;">Implémentation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="category exploratoire">ANALYSE EXPLORATOIRE</td>
|
||||
<td class="requirement">
|
||||
• Visualisation contenu dataset<br>
|
||||
• Analyses statistiques describe()<br>
|
||||
• 2+ graphiques interactifs<br>
|
||||
• Distribution des classes
|
||||
</td>
|
||||
<td class="implementation">
|
||||
Streamlit + Plotly interactif<br>
|
||||
Métriques dataset Cityscapes<br>
|
||||
Distribution pixel-wise classes<br>
|
||||
Graphiques responsive
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="category comparaison">COMPARAISON MODÈLES</td>
|
||||
<td class="requirement">
|
||||
• Sélection données entrée<br>
|
||||
• Upload images personnalisées<br>
|
||||
• Prédiction temps réel<br>
|
||||
• Affichage résultats comparatifs
|
||||
</td>
|
||||
<td class="implementation">
|
||||
Interface upload + dropdown<br>
|
||||
Inférence FPN vs SegFormer<br>
|
||||
Visualisation masques côte-à-côte<br>
|
||||
Métriques temps d'exécution
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="category accessibilite">ACCESSIBILITÉ WCAG</td>
|
||||
<td class="requirement">
|
||||
• Critères WCAG essentiels<br>
|
||||
• Contraste couleurs<br>
|
||||
• Navigation clavier<br>
|
||||
• Labels descriptifs
|
||||
</td>
|
||||
<td class="implementation">
|
||||
Palette couleurs accessibles<br>
|
||||
Alt-text images générées<br>
|
||||
Semantic HTML structure<br>
|
||||
Focus indicators
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="category technique">DÉPLOIEMENT CLOUD</td>
|
||||
<td class="requirement">
|
||||
• Application déployée online<br>
|
||||
• Interface responsive<br>
|
||||
• Performance acceptable<br>
|
||||
• Code source accessible
|
||||
</td>
|
||||
<td class="implementation">
|
||||
Serveur NAS personnel (Intel N100)<br>
|
||||
Streamlit containerisé<br>
|
||||
Optimisation modèles CPU<br>
|
||||
Repository GitHub documenté
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="tech-stack">
|
||||
<h3>🛠️ Stack Technique</h3>
|
||||
<div class="tech-items">
|
||||
<div class="tech-item">FastAPI</div>
|
||||
<div class="tech-item">Streamlit</div>
|
||||
<div class="tech-item">PyTorch</div>
|
||||
<div class="tech-item">Plotly</div>
|
||||
<div class="tech-item">PIL/OpenCV</div>
|
||||
<div class="tech-item">Docker</div>
|
||||
<div class="tech-item">Intel N100</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/dashboard_specifications.png
Normal file
|
After Width: | Height: | Size: 707 KiB |
BIN
docs/fpn_resnet34_confusion_matrix.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
docs/fpn_resnet34_training_curves.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
docs/github_cd_street-vision-poc.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
docs/iou_delta_iou_per_class.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
docs/mIoU_accuracy.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
docs/mIoU_accuracy_upscayl_3x_ultrasharp-4x.png
Normal file
|
After Width: | Height: | Size: 6 MiB |
295
docs/note_methodologique.md
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
# Note Méthodologique : Preuve de concept SegFormer vs FPN
|
||||
|
||||
## Dataset retenu
|
||||
|
||||
### Cityscapes Dataset : Référence en Segmentation Urbaine
|
||||
|
||||
Le **Cityscapes Dataset** constitue la référence internationale pour l'évaluation des algorithmes de segmentation sémantique en environnement urbain. Développé par l'Université de Tübingen et Mercedes-Benz, ce dataset comprend **25,000 images** haute résolution (2048×1024 pixels) capturées dans 50 villes allemandes et suisses.
|
||||
|
||||
**Composition du dataset :**
|
||||
- **Train** : 2,975 images finement annotées
|
||||
- **Validation** : 500 images avec annotations de référence
|
||||
- **Test** : 1,525 images (annotations non-publiques)
|
||||
- **Coarse** : 20,000 images avec annotations grossières
|
||||
|
||||
**Architecture de classes hiérarchique :**
|
||||
Le dataset organise 34 classes détaillées en **8 méta-classes** pour la segmentation :
|
||||
|
||||
| Méta-classe | Classes incluses | 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 |
|
||||
|
||||
**Particularité méthodologique critique :**
|
||||
Contrairement au protocole original préservant le ratio 2:1 natif (2048×1024), nos expérimentations utilisent des **images carrées (1:1)** aux résolutions 512×512 et 768×768. Cette déformation géométrique, motivée par les contraintes GPU T4, permet d'analyser l'impact du ratio d'image sur les différentes philosophies architecturales (CNN vs Transformer).
|
||||
|
||||
---
|
||||
|
||||
## Les concepts de l'algorithme récent
|
||||
|
||||
### SegFormer : Architecture Transformer Vision-Optimisée Temps Réel
|
||||
|
||||
**SegFormer** représente une avancée dans la segmentation sémantique en introduisant une architecture **Transformer** spécifiquement conçue pour l'efficacité computationnelle, contrairement aux approches Transformer classiques gourmandes en ressources.
|
||||
|
||||
### Innovations Architecturales Clés
|
||||
|
||||
**1. Encodeur Hiérarchique Sans Encodage Positionnel**
|
||||
|
||||
L'encodeur SegFormer **abandonne les encodages positionnels** au profit d'une architecture hiérarchique qui capture naturellement les relations spatiales :
|
||||
|
||||
$$\text{Feature Maps} = \{F_1, F_2, F_3, F_4\}$$
|
||||
|
||||
avec résolutions $\{\frac{H}{4}, \frac{H}{8}, \frac{H}{16}, \frac{H}{32}\}$
|
||||
|
||||
**Efficient Self-Attention :**
|
||||
Réduction de complexité par sous-échantillonnage des clés :
|
||||
|
||||
$$\text{Attention}(Q,K,V) = \text{Softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
|
||||
|
||||
avec complexité réduite de $\mathcal{O}(N^2)$ à $\mathcal{O}\left(\frac{N^2}{R}\right)$ où $R$ est le facteur de réduction.
|
||||
|
||||
**2. Mix-FFN : Hybridation CNN-Transformer**
|
||||
|
||||
Innovation clé : intégration de **convolutions 3×3** dans les couches Feed-Forward :
|
||||
|
||||
$$\text{Mix-FFN}(x) = \text{MLP}(\text{GELU}(\text{Conv}_{3×3}(\text{MLP}(x)) + x))$$
|
||||
|
||||
Cette approche permet :
|
||||
- **Élimination totale des encodages positionnels**
|
||||
- **Capture locale + compréhension globale** simultanées
|
||||
- **Robustesse aux variations de résolution** d'entrée
|
||||
|
||||
**3. Décodeur MLP Ultra-Léger**
|
||||
|
||||
Simplification drastique vs décodeurs complexes traditionnels :
|
||||
|
||||
$$\text{Seg} = \text{MLP}\left(\text{Concat}[\text{Upsample}(F_1), F_2, F_3, F_4]\right)$$
|
||||
|
||||
**Avantages :**
|
||||
- **Réduction massive des paramètres** : Focus sur l'encodeur performant
|
||||
- **Inférence accélérée** : Décodeur 10× plus rapide que ASPP/PSP
|
||||
- **Architecture unifiée** : Traitement multi-échelle sans modules ad-hoc
|
||||
|
||||
### Positionnement Temps Réel vs État de l'Art
|
||||
|
||||
**Analyse critique du benchmark original :**
|
||||
Le papier SegFormer révèle que **SegFormer-B0** (3.7M paramètres) atteint **75.3% mIoU** sur Cityscapes à résolution réduite (côté court 768px), égalant **DeepLabv3+ + MobileNetV2** (75.2% mIoU) à résolution pleine. Cette **efficacité computationnelle supérieure** constitue l'argument central de notre hypothèse de recherche.
|
||||
|
||||
**Hypothèse de transfert :**
|
||||
Si DeepLabv3+ surpasse théoriquement FPN, alors SegFormer-B0 devrait également surpasser notre baseline FPN+EfficientNetB0 (78.67% mIoU), particulièrement à 768×768 pixels.
|
||||
|
||||
---
|
||||
|
||||
## La modélisation
|
||||
|
||||
### Méthodologie Expérimentale Adaptée
|
||||
|
||||
**Contraintes et adaptations :**
|
||||
Notre protocole d'évaluation diffère substantiellement du papier original pour s'adapter aux contraintes pratiques :
|
||||
|
||||
**Configuration matérielle :**
|
||||
- **Plateforme** : Google Colab Pro avec GPU T4 (15Go VRAM)
|
||||
- **Framework** : Migration TensorFlow/Keras → PyTorch + Segmentation Models PyTorch
|
||||
- **Contraintes** : Plan payant avec unités de calcul limitées
|
||||
|
||||
**Déformation géométrique :**
|
||||
- **Protocole original** : Préservation ratio 2:1 natif Cityscapes
|
||||
- **Notre protocole** : Déformation vers ratio 1:1 (images carrées)
|
||||
- **Résolutions testées** : 512×512 et 768×768 pixels
|
||||
- **Impact attendu** : Analyse différentielle CNN vs Transformer sous contrainte géométrique
|
||||
|
||||
### Preprocessing et Modèles Pré-entraînés
|
||||
|
||||
**Modèles SegFormer (pré-entraînés ADE20K) :**
|
||||
- **SegFormer-B0** : `segformer-b0-512x512-ade-160k` (3.7M paramètres)
|
||||
- **SegFormer-B1** : `segformer-b1-512x512-ade-160k` (13.7M paramètres, exploration scaling)
|
||||
|
||||
**Modèles FPN (pré-entraînés ImageNet) :**
|
||||
- **FPN+ResNet34** : 23.16M paramètres
|
||||
- **FPN+EfficientNet-B0** : 5.76M paramètres (baseline de référence @224x224 : 74.6% mIoU)
|
||||
|
||||
### Configuration d'Entraînement Optimisée
|
||||
|
||||
**Configuration SegFormer :**
|
||||
- **Loss Function** : Jaccard Loss pour optimisation IoU directe
|
||||
- **Optimiseur** : AdamW (lr=1e-4, weight_decay=1e-4, spécialisé Transformers)
|
||||
- **Scheduler** : Cosine Annealing (convergence douce, min_lr=1e-6)
|
||||
- **Epochs** : Maximum 30 avec early stopping (patience=5)
|
||||
|
||||
**Configuration FPN :**
|
||||
- **Loss Function** : Focal + Jaccard Loss combinées (poids 1:1)
|
||||
- **Optimiseur** : Adam (lr=1.5e-4, weight_decay=1e-5, optimisé CNN)
|
||||
- **Scheduler** : ReduceLROnPlateau (adaptation réactive, patience=4)
|
||||
- **Epochs** : Maximum 20 avec early stopping (patience=7)
|
||||
|
||||
**Stratégie d'entraînement :**
|
||||
Entraînement séquentiel des 4 modèles principaux à 512×512, puis scaling à 768×768 pour analyse comparative résolution/performance.
|
||||
|
||||
---
|
||||
|
||||
## Une synthèse des résultats
|
||||
|
||||

|
||||
*Fig.1: Mean IoU et Accuracy par expérience*
|
||||
|
||||
### Performance Comparative : SegFormer vs FPN sous Contrainte Géométrique
|
||||
|
||||
**Résultats principaux (mIoU sur Cityscapes Validation) :**
|
||||
|
||||
| **Modèle** | **mIoU** | **Gain vs Baseline** | **Paramètres** | **Taille** | **Temps CPU** |
|
||||
|------------|----------|----------------------|----------------|------------|---------------|
|
||||
| **SegFormer-B1** | **79.98%** | **+1.31%** | **13.68M** | **52MB** | **1.39s** |
|
||||
| **FPN+ResNet34** | **79.22%** | **+0.55%** | **23.16M** | **88MB** | **0.73s** |
|
||||
| **FPN+EfficientNet-B0** | **78.67%** | *baseline* | **5.76M** | **22MB** | **0.48s** |
|
||||
| **SegFormer-B0** | **77.91%** | **-0.76%** | **3.72M** | **14MB** | **0.87s** |
|
||||
|
||||
*Temps d'inférence : CPU Intel Xeon @ 2.20GHz (2 vCPUs) Google Colab*
|
||||
|
||||
### Révélations Critiques : Impact du Ratio d'Image
|
||||
|
||||
**Résultat majeur inattendu :**
|
||||
Contrairement aux prédictions du benchmark original, **SegFormer-B0 sous-performe face aux baselines FPN** sur images carrées (77.91% vs 78.67% mIoU). Cette observation révèle une **sensibilité architecturale différentielle** aux déformations géométriques.
|
||||
|
||||
**Hypothèse explicative - Robustesse géométrique :**
|
||||
|
||||
1. **CNN (FPN)** : Les convolutions avec leurs kernels fixes et pooling local gèrent naturellement les déformations géométriques
|
||||
2. **Transformer (SegFormer)** : L'attention globale, optimisée sur ratio 2:1 natif, souffre de la perte d'information spatiale lors de la déformation 1:1
|
||||
3. **Scaling architectural** : SegFormer-B1 compense cette limitation par sa capacité supérieure (+1.31% mIoU)
|
||||
|
||||
### Analyse Détaillée par Classes Sémantiques
|
||||
|
||||

|
||||
*Fig.2: IoU et Δ IoU par Classe entre SegFormer-B1 et FPN-ResNet34*
|
||||
|
||||
**Performance par méta-classes Cityscapes :**
|
||||
|
||||
| **Classe** | **SegFormer-B1** | **FPN+ResNet34** | **Gain** | **Analyse** |
|
||||
|------------|-------------------|-------------------|----------|-------------|
|
||||
| **Flat** | **93.35%** | **93.38%** | **-0.03%** | Performance équivalente |
|
||||
| **Human** | **71.13%** | **69.35%** | **+1.78%** | ⭐ **Attention globale critique** |
|
||||
| **Vehicle** | **88.80%** | **87.75%** | **+1.05%** | ⭐ **Contexte multi-échelle** |
|
||||
| **Construction** | **85.94%** | **85.06%** | **+0.88%** | Structures complexes |
|
||||
| **Object** | **49.24%** | **48.68%** | **+0.56%** | ⭐ **Signalétique petite taille** |
|
||||
| **Nature** | **89.14%** | **88.13%** | **+1.01%** | Textures variées |
|
||||
| **Sky** | **92.35%** | **91.67%** | **+0.68%** | Régions homogènes |
|
||||
| **Void** | **69.89%** | **69.76%** | **+0.13%** | Zones ambiguës |
|
||||
|
||||
**Patterns d'amélioration SegFormer-B1 :**
|
||||
- **Classes sécuritaires critiques** (Human +1.78%, Vehicle +1.05%) : Validation de l'attention globale pour objets contextuels
|
||||
- **Objets petite taille** (Object +0.56%) : Meilleure capture multi-échelle vs convolutions locales
|
||||
- **Performance équivalente** sur surfaces homogènes (Flat -0.03%)
|
||||
|
||||
### Performance Surprenante vs Benchmark Original
|
||||
|
||||
**Observation contre-intuitive :**
|
||||
Nos modèles atteignent des performances **supérieures au papier original** malgré la contrainte géométrique @768x768 :
|
||||
- **SegFormer-B0 : 77.91%** vs 75.3%
|
||||
- **FPN baselines : 78.67-79.22%** vs DeepLabV3+ 75.2%
|
||||
- **SegFormer-B1 : 79.98%** vs ~76-78% (en effectuant un scaling grossier B0→B1 en se basant sur l'écart de performances de B0-->B5)
|
||||
|
||||
|
||||
**Hypothèses explicatives :**
|
||||
1. **Optimisation du ratio 1:1** : Réduction de l'aire d'image (×2) concentre l'information spatiale
|
||||
2. **Optimisation d'hyperparamètres** : Par exemple, utilisation de fonctions de perte Jaccard (+ Focal) ? Non mentionné dans SegFormer (2021) ni DeepLabV3 (2018)
|
||||
3. **Encodeurs** : EfficientNetB0 à peine plus long que MobileNetV2 et performances au-dessus. ResNet34 au-dessus également mais plus lourd (peut-être pertinent en temps réel...?)
|
||||
3. **Framework PyTorch** : Optimisations modernes vs implémentations TensorFlow du papier DeepLabV3+ (2018)
|
||||
|
||||
---
|
||||
|
||||
## L'analyse de la feature importance globale et locale du nouveau modèle
|
||||
|
||||
### Limitations Méthodologiques de l'Explicabilité Transformer
|
||||
|
||||
L'analyse d'explicabilité des architectures Transformer pour la segmentation dense représente un **défi technique majeur** dépassant le cadre de cette preuve de concept. Les mécanismes d'attention multi-têtes, Mix-FFN, et fusion hiérarchique créent des interactions complexes difficiles à décomposer causalement.
|
||||
|
||||
### Insights Architecturaux via Patterns de Performance
|
||||
|
||||
**Analyse indirecte par performance différentielle :**
|
||||
|
||||
Bien que l'explicabilité fine soit inaccessible, les patterns de performance révèlent les mécanismes sous-jacents :
|
||||
|
||||
1. **Attention globale validée** : Gains significatifs sur classes contextuelles (Human +1.78%, Vehicle +1.05%) suggèrent une **compréhension scénique** supérieure aux CNN
|
||||
2. **Sensibilité géométrique** : Sous-performance SegFormer-B0 vs FPN sur images déformées révèle une **dépendance aux relations spatiales natives**
|
||||
3. **Scaling architectural efficace** : Bond B0→B1 (+2.07% mIoU) confirme l'efficacité du scaling Transformer
|
||||
|
||||
**Limitation fondamentale :**
|
||||
Les cartes d'attention ne correspondent pas nécessairement à l'importance causale réelle. Une explicabilité rigoureuse nécessiterait des approches spécialisées comme **Deep Taylor Decomposition** ou **Integrated Gradients** adaptées aux architectures hybrides CNN-Transformer.
|
||||
|
||||
---
|
||||
|
||||
## Limites et améliorations possibles
|
||||
|
||||
### Limitations Expérimentales Critiques
|
||||
|
||||
**1. Contraintes Protocolaires Majeures**
|
||||
- **Déformation géométrique forcée** : Ratio 2:1→1:1 non-représentatif du déploiement réel
|
||||
- **Résolutions limitées** : GPU T4 contraint l'exploration des résolutions supérieures (>768px). Augmentation importante de l'utilisation de VRAM et réduction drastique du `batch_size` --> entraînements plus longs (plus d'époques)
|
||||
- **Framework migration** : TensorFlow→PyTorch peut introduire des biais de comparaison
|
||||
- **Métriques d'évaluation** : Temps d'inférence CPU non-représentatifs du déploiement GPU optimisé
|
||||
|
||||
**2. Généralisation Limitée**
|
||||
- **Dataset unique** : Évaluation exclusive sur Cityscapes urbain allemand
|
||||
- **Conditions météo** : Uniquement conditions favorables (pas de nuit/intempéries)
|
||||
- **Scaling incomplet** : SegFormer-B2+ non-testés faute de ressources computationnelles
|
||||
|
||||
### Améliorations Prioritaires Identifiées
|
||||
|
||||
**1. Robustesse Protocolaire**
|
||||
- **Préservation ratio natif** : Tests sur images 2:1 sans déformation géométrique (contraintes budgétaires/temps limitant cette approche)
|
||||
- **Résolutions supérieures** : Évaluation à 1024×512 ou résolutions natives pour validation écologique
|
||||
- **Evaluation multi-dataset** : Validation BDD100K, Mapillary Vistas pour généralisation
|
||||
|
||||
**2. Scaling Architectural Complet**
|
||||
- **SegFormer variants complets** : B2, B3, B4 pour validation des gains à tous les niveaux de complexité vs papier original
|
||||
- **Évaluation différentielle** : Déterminer si les améliorations observées sont spécifiques aux modèles légers (B0-B1) ou généralisables aux variants lourds
|
||||
- **Optimisations déploiement** : TensorRT, quantization FP16/INT8 pour inférence temps réel selon niveau de complexité
|
||||
|
||||
**3. Analyse Causale Approfondie**
|
||||
1. **Prototypage rapide** : SegFormer-B1 comme référence architecturale moderne pour projets nécessitant performance maximale
|
||||
2. **Contraintes embarquées** : FPN+EfficientNet-B0 optimal pour déploiement edge ultra-contraint à 768×768, avec évaluation requise à résolutions supérieures pour validation complète
|
||||
3. **Adaptabilité clients** : Gamme SegFormer (B0-B1+) offrant scaling performance/ressources selon besoins spécifiques
|
||||
|
||||
### Perspectives de Déploiement Pratique
|
||||
|
||||
**Applications industrielles validées :**
|
||||
1. **Prototypage rapide** : SegFormer-B1 comme référence architecturale moderne pour projets nécessitant performance maximale
|
||||
2. **Contraintes embarquées** : FPN+EfficientNet-B0 optimal pour déploiement edge ultra-contraint à 768×768, avec évaluation requise à résolutions supérieures pour validation complète
|
||||
3. **Adaptabilité clients** : Gamme SegFormer (B0-B1+) offrant scaling performance/ressources selon besoins spécifiques
|
||||
|
||||
**ROI technique contextuel :**
|
||||
Cette preuve de concept valide l'intérêt des architectures Transformer tout en révélant leurs **sensibilités pratiques** (ratio d'image, scaling nécessaire). L'approche constitue une **base méthodologique** solide pour futurs développements production avec contraintes réelles explicitées.
|
||||
|
||||
---
|
||||
|
||||
## Conclusion Méthodologique
|
||||
|
||||
### Contribution Analytique Principale
|
||||
|
||||
Cette étude apporte une **perspective complémentaire critique** aux évaluations académiques standard en révélant :
|
||||
|
||||
1. **Impact géométrique différentiel** : Sensibilité CNN vs Transformer aux déformations d'image
|
||||
2. **Scaling architectural requis** : Nécessité SegFormer-B1+ pour surpasser baselines CNN établies en ratio 1:1
|
||||
3. **Trade-offs pratiques** : Compromis performance/efficacité sous contraintes réelles
|
||||
|
||||
### Validation des Hypothèses de Recherche
|
||||
|
||||
**Hypothèses confirmées :**
|
||||
- ✅ **SegFormer-B1 surpasse FPN** (+1.31% mIoU) avec efficacité paramétrique supérieure
|
||||
- ✅ **Amélioration classes critiques** validée (Human +1.78%, Vehicle +1.05%)
|
||||
- ✅ **Architecture Transformer viable** pour segmentation temps réel avec scaling approprié
|
||||
|
||||
**Hypothèses infirmées :**
|
||||
- ❌ **SegFormer-B0 sous-performant** vs FPN sur images 1:1
|
||||
- ❌ **Robustesse géométrique** Transformer surestimée vs CNN
|
||||
|
||||
### Positionnement Scientifique
|
||||
|
||||
Cette preuve de concept se distingue par son **réalisme méthodologique** : elle expose les limitations pratiques des architectures d'avant-garde sous contraintes déploiement réelles, offrant une analyse **nuancée et reproductible** aux équipes techniques devant opérer des choix architecturaux informés.
|
||||
|
||||
L'approche constitue une **base technique solide** pour futurs développements industriels nécessitant des compromis éclairés entre innovation architecturale et contraintes opérationnelles.
|
||||
BIN
docs/note_methodologique.pdf
Normal file
BIN
docs/p8_technical_note.pdf
Normal file
77
docs/plan_previsionnel.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Plan Prévisionnel - Preuve de Concept Segmentation Sémantique
|
||||
|
||||
**Projet** : Amélioration de la segmentation sémantique temps réel avec SegFormer
|
||||
|
||||
---
|
||||
|
||||
## Dataset Retenu
|
||||
|
||||
**Cityscapes** : Dataset de référence pour la segmentation sémantique de scènes urbaines comprenant 2975 images haute résolution (2048×1024) annotées pixel par pixel. Les images sont capturées dans **50 villes allemandes différentes** avec **34 classes principales** que nous regroupons en **8 catégories pertinentes** (flat, vehicle, human, construction, object, nature, sky, void).
|
||||
|
||||
**Particularité méthodologique** : Les images seront testées aux résolutions 512×512 et 768×768 avec **déformation du ratio d'origine** (passage de 2:1 à 1:1) pour optimiser les contraintes computationnelles GPU T4. Cette modification permettra d'analyser l'impact du ratio d'image sur les différentes architectures.
|
||||
|
||||
---
|
||||
|
||||
## Modèle Envisagé
|
||||
|
||||
### Algorithme : SegFormer (Focus Temps Réel)
|
||||
|
||||
**Justifications du choix :**
|
||||
SegFormer (Xie et al., 2021) représente une approche Transformer vision-optimisée pour la segmentation sémantique. **L'analyse du benchmark Cityscapes révèle que SegFormer-B0 égale les performances de DeepLabv3+** (75.3% vs 75.2% mIoU) tout en fonctionnant à **résolution réduite** (côté court 768px vs résolution pleine). Cette efficacité computationnelle supérieure suggère un potentiel d'amélioration face à notre baseline FPN, DeepLabv3+ étant théoriquement plus performant que FPN.
|
||||
|
||||
**Objectif et hypothèses de recherche :**
|
||||
1. **SegFormer-B0** devrait surpasser FPN+EfficientNetB0 (78.67% mIoU baseline) grâce à l'attention globale native
|
||||
2. **SegFormer-B1** permettra d'explorer le scaling architectural (non documenté dans le papier original)
|
||||
3. **Impact du ratio d'image** : Analyser comment la déformation 2:1→1:1 affecte les architectures CNN vs Transformer
|
||||
|
||||
**Contraintes et adaptations :**
|
||||
- Migration framework obligatoire : TensorFlow/Keras → PyTorch pour accès aux modèles SegFormer récents
|
||||
- Résolutions contraintes par GPU T4 : 512×512 et 768×768 maximum
|
||||
- Protocole différent du papier original : images carrées vs rectangulaires natives
|
||||
|
||||
---
|
||||
|
||||
## Références Bibliographiques
|
||||
|
||||
1. **Xie, E., Wang, W., Yu, Z., et al.** (2021). *SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers*. NeurIPS 2021. [Architecture Transformer de référence]
|
||||
|
||||
2. **Chen, L.C., Zhu, Y., Papandreou, G., et al.** (2018). *Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation*. ECCV 2018. [Baseline comparative DeepLabv3+]
|
||||
|
||||
3. **Cordts, M., Omran, M., Ramos, S., et al.** (2016). *The Cityscapes Dataset for Semantic Urban Scene Understanding*. CVPR 2016. [Dataset de référence]
|
||||
|
||||
---
|
||||
|
||||
## Explication de la Démarche de Test
|
||||
|
||||
### Méthode Baseline
|
||||
**FPN + EfficientNetB0/ResNet34** : Réentraînement des modèles du projet précédent aux nouvelles résolutions 512×512 et 768×768 avec déformation du ratio d'image. Performance baseline FPN+EfficientNetB0 : 78.67% mIoU (baseline de référence).
|
||||
|
||||
### Méthode de Test SegFormer
|
||||
**Évaluation comparative temps réel** :
|
||||
1. **SegFormer-B0** : Modèle compact ciblé (3.7M paramètres) pour validation du concept temps réel
|
||||
2. **SegFormer-B1** : Exploration scaling architectural (13.7M paramètres) non documenté dans le papier
|
||||
3. **Comparaison 512×512 vs 768×768** : Trade-off résolution/performance sur images déformées
|
||||
4. **Analyse différentielle** : Impact du ratio d'image 1:1 vs 2:1 natif Cityscapes
|
||||
|
||||
**Métriques d'évaluation** :
|
||||
- Mean IoU (métrique principale) et accuracy pixel-wise
|
||||
- Performance par classe sémantique (focus classes critiques : Human, Vehicle)
|
||||
- Temps d'inférence CPU/GPU pour validation déploiement pratique
|
||||
|
||||
### Preuve de Concept
|
||||
**Interface Streamlit** déployée sur serveur NAS personnel (CPU Intel N100) permettant :
|
||||
- Upload d'images urbaines avec **inférence comparative multi-modèles**
|
||||
- **Visualisation côte-à-côte** : FPN+ResNet34 vs SegFormer-B1 (meilleurs représentants)
|
||||
- Affichage temps d'inférence réel et métriques de performance détaillées
|
||||
- **Analyse exploratoire dataset** : Distribution classes, statistiques pixel-wise interactives
|
||||
|
||||
**Hypothèses de validation** :
|
||||
1. **SegFormer-B1 > FPN baselines** malgré contraintes ratio d'image
|
||||
2. **SegFormer-B0 vs FPN** : Révélation possible de limitations Transformer sur images déformées
|
||||
3. **Performances générales** potentiellement supérieures au papier original grâce à l'optimisation ratio 1:1
|
||||
|
||||
**Code réutilisé** : Framework `segmentation_models_pytorch` pour les implémentations. **Application originale** : protocole d'évaluation sur images carrées et analyse comparative CNN vs Transformer sous contraintes géométriques spécifiques.
|
||||
|
||||
---
|
||||
|
||||
**Note méthodologique** : Cette approche permet d'analyser les trade-offs pratiques architecture/performance dans un contexte de contraintes réelles (hardware limité, déformation d'images), offrant une perspective complémentaire aux évaluations académiques standard.
|
||||
BIN
docs/plan_previsionnel.pdf
Normal file
BIN
docs/segformer_b1_confusion_matrix.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
docs/segformer_b1_training_curves.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
docs/street-vision-dockerhub_poc.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/watchtower_street-vision-poc.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
57
nginx.conf
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# This reverse proxy configuration aims to make calls to different services running under different ports (Streamlit :8501 and FastAPI :8000) by calling only one port : the nginx one (configurable via NGINX_PORT)
|
||||
# The NGINX_PORT has to be opened (Dockerfile) and used by app.py (streamlit) in order to make right calls.
|
||||
worker_processes 1;
|
||||
|
||||
events { worker_connections 1024; }
|
||||
|
||||
http {
|
||||
# ✅ AJOUT: Limite d'upload augmentée
|
||||
client_max_body_size 200M; # Permet uploads jusqu'à 200MB
|
||||
|
||||
server {
|
||||
listen ${NGINX_PORT};
|
||||
|
||||
# ✅ AJOUT: Timeouts pour gros fichiers
|
||||
client_body_timeout 60s;
|
||||
client_header_timeout 60s;
|
||||
keepalive_timeout 60s;
|
||||
send_timeout 60s;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# ✅ AJOUT: Timeouts pour uploads
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8501/;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# ✅ AJOUT: Timeouts pour Streamlit uploads
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location /health {
|
||||
return 200 'OK';
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
notebooks/content/data/new_images_to_predict/paris_street.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
BIN
notebooks/content/data/train_images_sample/class_repartition.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
1
notebooks/p9_notebook.ipynb
Normal file
671
notebooks/requirements.txt
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
absl-py==1.4.0
|
||||
absolufy-imports==0.3.1
|
||||
accelerate==1.10.1
|
||||
aiofiles==24.1.0
|
||||
aiohappyeyeballs==2.6.1
|
||||
aiohttp==3.12.15
|
||||
aiosignal==1.4.0
|
||||
alabaster==1.0.0
|
||||
albucore==0.0.24
|
||||
albumentations==2.0.8
|
||||
ale-py==0.11.2
|
||||
alembic==1.16.5
|
||||
altair==5.5.0
|
||||
annotated-types==0.7.0
|
||||
antlr4-python3-runtime==4.9.3
|
||||
anyio==4.10.0
|
||||
anywidget==0.9.18
|
||||
argon2-cffi==25.1.0
|
||||
argon2-cffi-bindings==25.1.0
|
||||
array_record==0.8.1
|
||||
arviz==0.22.0
|
||||
astropy==7.1.0
|
||||
astropy-iers-data==0.2025.8.25.0.36.58
|
||||
astunparse==1.6.3
|
||||
atpublic==5.1
|
||||
attrs==25.3.0
|
||||
audioread==3.0.1
|
||||
Authlib==1.6.2
|
||||
autograd==1.8.0
|
||||
babel==2.17.0
|
||||
backcall==0.2.0
|
||||
beartype==0.21.0
|
||||
beautifulsoup4==4.13.5
|
||||
betterproto==2.0.0b6
|
||||
bigframes==2.17.0
|
||||
bigquery-magics==0.10.3
|
||||
bleach==6.2.0
|
||||
blinker==1.9.0
|
||||
blis==1.3.0
|
||||
blobfile==3.0.0
|
||||
blosc2==3.7.2
|
||||
bokeh==3.7.3
|
||||
boto3==1.40.21
|
||||
botocore==1.40.21
|
||||
Bottleneck==1.4.2
|
||||
bqplot==0.12.45
|
||||
branca==0.8.1
|
||||
Brotli==1.1.0
|
||||
build==1.3.0
|
||||
CacheControl==0.14.3
|
||||
cachetools==5.5.2
|
||||
catalogue==2.0.10
|
||||
certifi==2025.8.3
|
||||
cffi==1.17.1
|
||||
chardet==5.2.0
|
||||
charset-normalizer==3.4.3
|
||||
chex==0.1.90
|
||||
clarabel==0.11.1
|
||||
click==8.2.1
|
||||
cloudpathlib==0.21.1
|
||||
cloudpickle==3.1.1
|
||||
cmake==3.31.6
|
||||
cmdstanpy==1.2.5
|
||||
colorcet==3.1.0
|
||||
colorlover==0.3.0
|
||||
colour==0.1.5
|
||||
community==1.0.0b1
|
||||
confection==0.1.5
|
||||
cons==0.4.7
|
||||
contourpy==1.3.3
|
||||
cramjam==2.11.0
|
||||
cryptography==43.0.3
|
||||
cuda-python==12.6.2.post1
|
||||
cudf-cu12 @ https://pypi.nvidia.com/cudf-cu12/cudf_cu12-25.6.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
|
||||
cudf-polars-cu12==25.6.0
|
||||
cufflinks==0.17.3
|
||||
cuml-cu12==25.6.0
|
||||
cupy-cuda12x==13.3.0
|
||||
curl_cffi==0.13.0
|
||||
cuvs-cu12==25.6.1
|
||||
cvxopt==1.3.2
|
||||
cvxpy==1.6.7
|
||||
cycler==0.12.1
|
||||
cyipopt==1.5.0
|
||||
cymem==2.0.11
|
||||
Cython==3.0.12
|
||||
dask==2025.5.0
|
||||
dask-cuda==25.6.0
|
||||
dask-cudf-cu12==25.6.0
|
||||
databricks-sdk==0.64.0
|
||||
dataproc-spark-connect==0.8.3
|
||||
datasets==4.0.0
|
||||
db-dtypes==1.4.3
|
||||
dbus-python==1.2.18
|
||||
debugpy==1.8.15
|
||||
decorator==4.4.2
|
||||
defusedxml==0.7.1
|
||||
diffusers==0.35.1
|
||||
dill==0.3.8
|
||||
distributed==2025.5.0
|
||||
distributed-ucxx-cu12==0.44.0
|
||||
distro==1.9.0
|
||||
dlib==19.24.6
|
||||
dm-tree==0.1.9
|
||||
docker==7.1.0
|
||||
docstring_parser==0.17.0
|
||||
docutils==0.21.2
|
||||
dopamine_rl==4.1.2
|
||||
dotenv==0.9.9
|
||||
duckdb==1.3.2
|
||||
earthengine-api==1.5.24
|
||||
easydict==1.13
|
||||
editdistance==0.8.1
|
||||
eerepr==0.1.2
|
||||
einops==0.8.1
|
||||
en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl#sha256=1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85
|
||||
entrypoints==0.4
|
||||
et_xmlfile==2.0.0
|
||||
etils==1.13.0
|
||||
etuples==0.3.10
|
||||
Farama-Notifications==0.0.4
|
||||
fastai==2.8.4
|
||||
fastapi==0.116.1
|
||||
fastcore==1.8.8
|
||||
fastdownload==0.0.7
|
||||
fastjsonschema==2.21.2
|
||||
fastprogress==1.0.3
|
||||
fastrlock==0.8.3
|
||||
fasttransform==0.0.2
|
||||
ffmpy==0.6.1
|
||||
filelock==3.19.1
|
||||
firebase-admin==6.9.0
|
||||
Flask==3.1.2
|
||||
flatbuffers==25.2.10
|
||||
flax==0.10.6
|
||||
folium==0.20.0
|
||||
fonttools==4.59.1
|
||||
frozendict==2.4.6
|
||||
frozenlist==1.7.0
|
||||
fsspec==2025.3.0
|
||||
future==1.0.0
|
||||
gast==0.6.0
|
||||
gcsfs==2025.3.0
|
||||
GDAL==3.8.4
|
||||
gdown==5.2.0
|
||||
geemap==0.35.3
|
||||
geocoder==1.38.1
|
||||
geographiclib==2.1
|
||||
geopandas==1.1.1
|
||||
geopy==2.4.1
|
||||
gin-config==0.5.0
|
||||
gitdb==4.0.12
|
||||
GitPython==3.1.45
|
||||
glob2==0.7
|
||||
google==2.0.3
|
||||
google-adk==1.12.0
|
||||
google-ai-generativelanguage==0.6.15
|
||||
google-api-core==2.25.1
|
||||
google-api-python-client==2.179.0
|
||||
google-auth==2.38.0
|
||||
google-auth-httplib2==0.2.0
|
||||
google-auth-oauthlib==1.2.2
|
||||
google-cloud-aiplatform==1.110.0
|
||||
google-cloud-appengine-logging==1.6.2
|
||||
google-cloud-audit-log==0.3.2
|
||||
google-cloud-bigquery==3.36.0
|
||||
google-cloud-bigquery-connection==1.18.3
|
||||
google-cloud-bigquery-storage==2.32.0
|
||||
google-cloud-bigtable==2.32.0
|
||||
google-cloud-core==2.4.3
|
||||
google-cloud-dataproc==5.21.0
|
||||
google-cloud-datastore==2.21.0
|
||||
google-cloud-firestore==2.21.0
|
||||
google-cloud-functions==1.20.4
|
||||
google-cloud-language==2.17.2
|
||||
google-cloud-logging==3.12.1
|
||||
google-cloud-resource-manager==1.14.2
|
||||
google-cloud-secret-manager==2.24.0
|
||||
google-cloud-spanner==3.57.0
|
||||
google-cloud-speech==2.33.0
|
||||
google-cloud-storage==2.19.0
|
||||
google-cloud-trace==1.16.2
|
||||
google-cloud-translate==3.21.1
|
||||
google-colab @ file:///colabtools/dist/google_colab-1.0.0.tar.gz
|
||||
google-crc32c==1.7.1
|
||||
google-genai==1.31.0
|
||||
google-generativeai==0.8.5
|
||||
google-pasta==0.2.0
|
||||
google-resumable-media==2.7.2
|
||||
googleapis-common-protos==1.70.0
|
||||
googledrivedownloader==1.1.0
|
||||
gradio==5.43.1
|
||||
gradio_client==1.12.1
|
||||
graphene==3.4.3
|
||||
graphql-core==3.2.6
|
||||
graphql-relay==3.2.0
|
||||
graphviz==0.21
|
||||
greenlet==3.2.4
|
||||
groovy==0.1.2
|
||||
grpc-google-iam-v1==0.14.2
|
||||
grpc-interceptor==0.15.4
|
||||
grpcio==1.74.0
|
||||
grpcio-status==1.71.2
|
||||
grpclib==0.4.8
|
||||
gspread==6.2.1
|
||||
gspread-dataframe==4.0.0
|
||||
gunicorn==23.0.0
|
||||
gym==0.25.2
|
||||
gym-notices==0.1.0
|
||||
gymnasium==1.2.0
|
||||
h11==0.16.0
|
||||
h2==4.3.0
|
||||
h5netcdf==1.6.4
|
||||
h5py==3.14.0
|
||||
hdbscan==0.8.40
|
||||
hf-xet==1.1.8
|
||||
hf_transfer==0.1.9
|
||||
highspy==1.11.0
|
||||
holidays==0.79
|
||||
holoviews==1.21.0
|
||||
hpack==4.1.0
|
||||
html5lib==1.1
|
||||
httpcore==1.0.9
|
||||
httpimport==1.4.1
|
||||
httplib2==0.22.0
|
||||
httpx==0.28.1
|
||||
httpx-sse==0.4.1
|
||||
huggingface-hub==0.34.4
|
||||
humanize==4.13.0
|
||||
hyperframe==6.1.0
|
||||
hyperopt==0.2.7
|
||||
ibis-framework==9.5.0
|
||||
idna==3.10
|
||||
imageio==2.37.0
|
||||
imageio-ffmpeg==0.6.0
|
||||
imagesize==1.4.1
|
||||
imbalanced-learn==0.14.0
|
||||
immutabledict==4.2.1
|
||||
importlib_metadata==8.7.0
|
||||
importlib_resources==6.5.2
|
||||
imutils==0.5.4
|
||||
inflect==7.5.0
|
||||
iniconfig==2.1.0
|
||||
intel-cmplr-lib-ur==2025.2.1
|
||||
intel-openmp==2025.2.1
|
||||
ipyevents==2.0.2
|
||||
ipyfilechooser==0.6.0
|
||||
ipykernel==6.17.1
|
||||
ipyleaflet==0.20.0
|
||||
ipyparallel==8.8.0
|
||||
ipython==7.34.0
|
||||
ipython-genutils==0.2.0
|
||||
ipython-sql==0.5.0
|
||||
ipytree==0.2.2
|
||||
ipywidgets==7.7.1
|
||||
itsdangerous==2.2.0
|
||||
jaraco.classes==3.4.0
|
||||
jaraco.context==6.0.1
|
||||
jaraco.functools==4.3.0
|
||||
jax==0.5.3
|
||||
jax-cuda12-pjrt==0.5.3
|
||||
jax-cuda12-plugin==0.5.3
|
||||
jaxlib==0.5.3
|
||||
jeepney==0.9.0
|
||||
jieba==0.42.1
|
||||
Jinja2==3.1.6
|
||||
jiter==0.10.0
|
||||
jmespath==1.0.1
|
||||
joblib==1.5.1
|
||||
jsonpatch==1.33
|
||||
jsonpickle==4.1.1
|
||||
jsonpointer==3.0.0
|
||||
jsonschema==4.25.1
|
||||
jsonschema-specifications==2025.4.1
|
||||
jupyter-client==6.1.12
|
||||
jupyter-console==6.1.0
|
||||
jupyter-leaflet==0.20.0
|
||||
jupyter-server==1.16.0
|
||||
jupyter_core==5.8.1
|
||||
jupyter_kernel_gateway @ git+https://github.com/googlecolab/kernel_gateway@b134e9945df25c2dcb98ade9129399be10788671
|
||||
jupyterlab_pygments==0.3.0
|
||||
jupyterlab_widgets==3.0.15
|
||||
jupytext==1.17.2
|
||||
kaggle==1.7.4.5
|
||||
kagglehub==0.3.12
|
||||
keras==3.10.0
|
||||
keras-hub==0.21.1
|
||||
keras-nlp==0.21.1
|
||||
keyring==25.6.0
|
||||
keyrings.google-artifactregistry-auth==1.1.2
|
||||
kiwisolver==1.4.9
|
||||
langchain==0.3.27
|
||||
langchain-core==0.3.74
|
||||
langchain-text-splitters==0.3.9
|
||||
langcodes==3.5.0
|
||||
langsmith==0.4.16
|
||||
language_data==1.3.0
|
||||
launchpadlib==1.10.16
|
||||
lazr.restfulclient==0.14.4
|
||||
lazr.uri==1.0.6
|
||||
lazy_loader==0.4
|
||||
libclang==18.1.1
|
||||
libcudf-cu12 @ https://pypi.nvidia.com/libcudf-cu12/libcudf_cu12-25.6.0-py3-none-manylinux_2_28_x86_64.whl
|
||||
libcugraph-cu12==25.6.0
|
||||
libcuml-cu12==25.6.0
|
||||
libcuvs-cu12==25.6.1
|
||||
libkvikio-cu12==25.6.0
|
||||
libpysal==4.13.0
|
||||
libraft-cu12==25.6.0
|
||||
librmm-cu12==25.6.0
|
||||
librosa==0.11.0
|
||||
libucx-cu12==1.18.1
|
||||
libucxx-cu12==0.44.0
|
||||
lightgbm @ file:///tmp/lightgbm/LightGBM/dist/lightgbm-4.6.0-py3-none-linux_x86_64.whl
|
||||
linkify-it-py==2.0.3
|
||||
llvmlite==0.43.0
|
||||
locket==1.0.0
|
||||
logical-unification==0.4.6
|
||||
lxml==5.4.0
|
||||
Mako==1.1.3
|
||||
marisa-trie==1.3.0
|
||||
Markdown==3.8.2
|
||||
markdown-it-py==4.0.0
|
||||
MarkupSafe==3.0.2
|
||||
matplotlib==3.10.0
|
||||
matplotlib-inline==0.1.7
|
||||
matplotlib-venn==1.1.2
|
||||
mcp==1.13.1
|
||||
mdit-py-plugins==0.5.0
|
||||
mdurl==0.1.2
|
||||
miniKanren==1.0.5
|
||||
missingno==0.5.2
|
||||
mistune==3.1.3
|
||||
mizani==0.13.5
|
||||
mkl==2025.2.0
|
||||
ml_dtypes==0.5.3
|
||||
mlflow==3.3.2
|
||||
mlflow-skinny==3.3.2
|
||||
mlflow-tracing==3.3.2
|
||||
mlxtend==0.23.4
|
||||
more-itertools==10.7.0
|
||||
moviepy==1.0.3
|
||||
mpmath==1.3.0
|
||||
msgpack==1.1.1
|
||||
multidict==6.6.4
|
||||
multipledispatch==1.0.0
|
||||
multiprocess==0.70.16
|
||||
multitasking==0.0.12
|
||||
murmurhash==1.0.13
|
||||
music21==9.3.0
|
||||
namex==0.1.0
|
||||
narwhals==2.2.0
|
||||
natsort==8.4.0
|
||||
nbclassic==1.3.1
|
||||
nbclient==0.10.2
|
||||
nbconvert==7.16.6
|
||||
nbformat==5.10.4
|
||||
ndindex==1.10.0
|
||||
nest-asyncio==1.6.0
|
||||
networkx==3.5
|
||||
nibabel==5.3.2
|
||||
nltk==3.9.1
|
||||
notebook==6.5.7
|
||||
notebook_shim==0.2.4
|
||||
numba==0.60.0
|
||||
numba-cuda==0.11.0
|
||||
numexpr==2.11.0
|
||||
numpy==2.0.2
|
||||
nvidia-cublas-cu12==12.6.4.1
|
||||
nvidia-cuda-cupti-cu12==12.6.80
|
||||
nvidia-cuda-nvcc-cu12==12.5.82
|
||||
nvidia-cuda-nvrtc-cu12==12.6.77
|
||||
nvidia-cuda-runtime-cu12==12.6.77
|
||||
nvidia-cudnn-cu12==9.10.2.21
|
||||
nvidia-cufft-cu12==11.3.0.4
|
||||
nvidia-cufile-cu12==1.11.1.6
|
||||
nvidia-curand-cu12==10.3.7.77
|
||||
nvidia-cusolver-cu12==11.7.1.2
|
||||
nvidia-cusparse-cu12==12.5.4.2
|
||||
nvidia-cusparselt-cu12==0.7.1
|
||||
nvidia-ml-py==12.575.51
|
||||
nvidia-nccl-cu12==2.27.3
|
||||
nvidia-nvjitlink-cu12==12.6.85
|
||||
nvidia-nvtx-cu12==12.6.77
|
||||
nvtx==0.2.13
|
||||
nx-cugraph-cu12 @ https://pypi.nvidia.com/nx-cugraph-cu12/nx_cugraph_cu12-25.6.0-py3-none-any.whl
|
||||
oauth2client==4.1.3
|
||||
oauthlib==3.3.1
|
||||
omegaconf==2.3.0
|
||||
openai==1.101.0
|
||||
opencv-contrib-python==4.12.0.88
|
||||
opencv-python==4.12.0.88
|
||||
opencv-python-headless==4.12.0.88
|
||||
openpyxl==3.1.5
|
||||
opentelemetry-api==1.36.0
|
||||
opentelemetry-exporter-gcp-trace==1.9.0
|
||||
opentelemetry-resourcedetector-gcp==1.9.0a0
|
||||
opentelemetry-sdk==1.36.0
|
||||
opentelemetry-semantic-conventions==0.57b0
|
||||
opt_einsum==3.4.0
|
||||
optax==0.2.5
|
||||
optree==0.17.0
|
||||
orbax-checkpoint==0.11.23
|
||||
orjson==3.11.2
|
||||
osqp==1.0.4
|
||||
packaging==25.0
|
||||
pandas==2.2.2
|
||||
pandas-datareader==0.10.0
|
||||
pandas-gbq==0.29.2
|
||||
pandas-stubs==2.2.2.240909
|
||||
pandocfilters==1.5.1
|
||||
panel==1.7.5
|
||||
param==2.2.1
|
||||
parso==0.8.5
|
||||
parsy==2.1
|
||||
partd==1.4.2
|
||||
patsy==1.0.1
|
||||
peewee==3.18.2
|
||||
peft==0.17.1
|
||||
pexpect==4.9.0
|
||||
pickleshare==0.7.5
|
||||
pillow==11.3.0
|
||||
platformdirs==4.3.8
|
||||
plotly==5.24.1
|
||||
plotnine==0.14.5
|
||||
pluggy==1.6.0
|
||||
plum-dispatch==2.5.7
|
||||
ply==3.11
|
||||
polars==1.25.2
|
||||
pooch==1.8.2
|
||||
portpicker==1.5.2
|
||||
preshed==3.0.10
|
||||
prettytable==3.16.0
|
||||
proglog==0.1.12
|
||||
progressbar2==4.5.0
|
||||
prometheus_client==0.22.1
|
||||
promise==2.3
|
||||
prompt_toolkit==3.0.51
|
||||
propcache==0.3.2
|
||||
prophet==1.1.7
|
||||
proto-plus==1.26.1
|
||||
protobuf==5.29.5
|
||||
psutil==5.9.5
|
||||
psycopg2==2.9.10
|
||||
psygnal==0.14.1
|
||||
ptyprocess==0.7.0
|
||||
py-cpuinfo==9.0.0
|
||||
py4j==0.10.9.7
|
||||
pyarrow==18.1.0
|
||||
pyasn1==0.6.1
|
||||
pyasn1_modules==0.4.2
|
||||
pycairo==1.28.0
|
||||
pycocotools==2.0.10
|
||||
pycparser==2.22
|
||||
pycryptodomex==3.23.0
|
||||
pydantic==2.11.7
|
||||
pydantic-settings==2.10.1
|
||||
pydantic_core==2.33.2
|
||||
pydata-google-auth==1.9.1
|
||||
pydot==3.0.4
|
||||
pydotplus==2.0.2
|
||||
PyDrive2==1.21.3
|
||||
pydub==0.25.1
|
||||
pyerfa==2.0.1.5
|
||||
pygame==2.6.1
|
||||
pygit2==1.18.2
|
||||
Pygments==2.19.2
|
||||
PyGObject==3.42.0
|
||||
PyJWT==2.10.1
|
||||
pylibcudf-cu12 @ https://pypi.nvidia.com/pylibcudf-cu12/pylibcudf_cu12-25.6.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
|
||||
pylibcugraph-cu12==25.6.0
|
||||
pylibraft-cu12==25.6.0
|
||||
pymc==5.25.1
|
||||
pynndescent==0.5.13
|
||||
pynvjitlink-cu12==0.7.0
|
||||
pynvml==12.0.0
|
||||
pyogrio==0.11.1
|
||||
pyomo==6.9.3
|
||||
PyOpenGL==3.1.10
|
||||
pyOpenSSL==24.2.1
|
||||
pyparsing==3.2.3
|
||||
pyperclip==1.9.0
|
||||
pyproj==3.7.2
|
||||
pyproject_hooks==1.2.0
|
||||
pyshp==2.3.1
|
||||
PySocks==1.7.1
|
||||
pyspark==3.5.1
|
||||
pytensor==2.31.7
|
||||
pytest==8.4.1
|
||||
python-apt==0.0.0
|
||||
python-box==7.3.2
|
||||
python-dateutil==2.9.0.post0
|
||||
python-dotenv==1.1.1
|
||||
python-louvain==0.16
|
||||
python-multipart==0.0.20
|
||||
python-slugify==8.0.4
|
||||
python-snappy==0.7.3
|
||||
python-utils==3.9.1
|
||||
pytz==2025.2
|
||||
pyviz_comms==3.0.6
|
||||
PyWavelets==1.9.0
|
||||
PyYAML==6.0.2
|
||||
pyzmq==26.2.1
|
||||
raft-dask-cu12==25.6.0
|
||||
rapids-dask-dependency==25.6.0
|
||||
rapids-logger==0.1.1
|
||||
ratelim==0.1.6
|
||||
referencing==0.36.2
|
||||
regex==2024.11.6
|
||||
requests==2.32.4
|
||||
requests-oauthlib==2.0.0
|
||||
requests-toolbelt==1.0.0
|
||||
requirements-parser==0.9.0
|
||||
rich==13.9.4
|
||||
rmm-cu12==25.6.0
|
||||
roman-numerals-py==3.1.0
|
||||
rpds-py==0.27.0
|
||||
rpy2==3.5.17
|
||||
rsa==4.9.1
|
||||
ruff==0.12.10
|
||||
s3transfer==0.13.1
|
||||
safehttpx==0.1.6
|
||||
safetensors==0.6.2
|
||||
scikit-image==0.25.2
|
||||
scikit-learn==1.6.1
|
||||
scipy==1.16.1
|
||||
scooby==0.10.1
|
||||
scs==3.2.8
|
||||
seaborn==0.13.2
|
||||
SecretStorage==3.3.3
|
||||
segmentation_models_pytorch==0.5.0
|
||||
semantic-version==2.10.0
|
||||
Send2Trash==1.8.3
|
||||
sentence-transformers==5.1.0
|
||||
sentencepiece==0.2.1
|
||||
sentry-sdk==2.35.0
|
||||
setuptools==75.2.0
|
||||
shap==0.48.0
|
||||
shapely==2.1.1
|
||||
shellingham==1.5.4
|
||||
simple-parsing==0.1.7
|
||||
simplejson==3.20.1
|
||||
simsimd==6.5.1
|
||||
six==1.17.0
|
||||
sklearn-pandas==2.2.0
|
||||
slicer==0.0.8
|
||||
smart_open==7.3.0.post1
|
||||
smmap==5.0.2
|
||||
sniffio==1.3.1
|
||||
snowballstemmer==3.0.1
|
||||
sortedcontainers==2.4.0
|
||||
soundfile==0.13.1
|
||||
soupsieve==2.7
|
||||
soxr==0.5.0.post1
|
||||
spacy==3.8.7
|
||||
spacy-legacy==3.0.12
|
||||
spacy-loggers==1.0.5
|
||||
spanner-graph-notebook==1.1.7
|
||||
Sphinx==8.2.3
|
||||
sphinxcontrib-applehelp==2.0.0
|
||||
sphinxcontrib-devhelp==2.0.0
|
||||
sphinxcontrib-htmlhelp==2.1.0
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==2.0.0
|
||||
sphinxcontrib-serializinghtml==2.0.0
|
||||
SQLAlchemy==2.0.43
|
||||
sqlglot==25.20.2
|
||||
sqlparse==0.5.3
|
||||
srsly==2.5.1
|
||||
sse-starlette==3.0.2
|
||||
stanio==0.5.1
|
||||
starlette==0.47.3
|
||||
statsmodels==0.14.5
|
||||
stringzilla==3.12.6
|
||||
stumpy==1.13.0
|
||||
sympy==1.13.3
|
||||
tables==3.10.2
|
||||
tabulate==0.9.0
|
||||
tbb==2022.2.0
|
||||
tblib==3.1.0
|
||||
tcmlib==1.4.0
|
||||
tenacity==8.5.0
|
||||
tensorboard==2.19.0
|
||||
tensorboard-data-server==0.7.2
|
||||
tensorflow==2.19.0
|
||||
tensorflow-datasets==4.9.9
|
||||
tensorflow-hub==0.16.1
|
||||
tensorflow-metadata==1.17.2
|
||||
tensorflow-probability==0.25.0
|
||||
tensorflow-text==2.19.0
|
||||
tensorflow_decision_forests==1.12.0
|
||||
tensorstore==0.1.76
|
||||
termcolor==3.1.0
|
||||
terminado==0.18.1
|
||||
text-unidecode==1.3
|
||||
textblob==0.19.0
|
||||
tf-slim==1.1.0
|
||||
tf_keras==2.19.0
|
||||
thinc==8.3.6
|
||||
threadpoolctl==3.6.0
|
||||
tifffile==2025.6.11
|
||||
tiktoken==0.11.0
|
||||
timm==1.0.19
|
||||
tinycss2==1.4.0
|
||||
tokenizers==0.21.4
|
||||
toml==0.10.2
|
||||
tomlkit==0.13.3
|
||||
toolz==0.12.1
|
||||
torch==2.8.0
|
||||
torchao==0.10.0
|
||||
torchaudio==2.8.0
|
||||
torchdata==0.11.0
|
||||
torchsummary==1.5.1
|
||||
torchtune==0.6.1
|
||||
torchvision==0.23.0
|
||||
tornado==6.4.2
|
||||
tqdm==4.67.1
|
||||
traitlets==5.7.1
|
||||
traittypes==0.2.1
|
||||
transformers==4.55.4
|
||||
treelite==4.4.1
|
||||
treescope==0.1.10
|
||||
triton==3.4.0
|
||||
tsfresh==0.21.0
|
||||
tweepy==4.16.0
|
||||
typeguard==4.4.4
|
||||
typer==0.16.1
|
||||
types-pytz==2025.2.0.20250809
|
||||
types-setuptools==80.9.0.20250822
|
||||
typing-inspection==0.4.1
|
||||
typing_extensions==4.15.0
|
||||
tzdata==2025.2
|
||||
tzlocal==5.3.1
|
||||
uc-micro-py==1.0.3
|
||||
ucx-py-cu12==0.44.0
|
||||
ucxx-cu12==0.44.0
|
||||
umap-learn==0.5.9.post2
|
||||
umf==0.11.0
|
||||
uritemplate==4.2.0
|
||||
urllib3==2.5.0
|
||||
uvicorn==0.35.0
|
||||
vega-datasets==0.9.0
|
||||
wadllib==1.3.6
|
||||
wandb==0.21.1
|
||||
wasabi==1.1.3
|
||||
watchdog==6.0.0
|
||||
wcwidth==0.2.13
|
||||
weasel==0.4.1
|
||||
webcolors==24.11.1
|
||||
webencodings==0.5.1
|
||||
websocket-client==1.8.0
|
||||
websockets==15.0.1
|
||||
Werkzeug==3.1.3
|
||||
wheel==0.45.1
|
||||
widgetsnbextension==3.6.10
|
||||
wordcloud==1.9.4
|
||||
wrapt==1.17.3
|
||||
wurlitzer==3.1.1
|
||||
xarray==2025.8.0
|
||||
xarray-einstats==0.9.1
|
||||
xgboost==3.0.4
|
||||
xlrd==2.0.2
|
||||
xxhash==3.5.0
|
||||
xyzservices==2025.4.0
|
||||
yarl==1.20.1
|
||||
ydf==0.13.0
|
||||
yellowbrick==1.5
|
||||
yfinance==0.2.65
|
||||
zict==3.0.0
|
||||
zipp==3.23.0
|
||||
zstandard==0.24.0
|
||||
52
start.sh
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Set default port si non fourni
|
||||
export NGINX_PORT=${NGINX_PORT:-8080}
|
||||
export MULTISEG_API_BASE_URL=${MULTISEG_API_BASE_URL:-http://localhost:$NGINX_PORT/api}
|
||||
|
||||
echo "=== Multi-Segmentation API Docker Startup ==="
|
||||
echo "NGINX_PORT: $NGINX_PORT"
|
||||
echo "API_BASE_URL: $MULTISEG_API_BASE_URL"
|
||||
|
||||
# Substitute environment variables dans nginx config
|
||||
envsubst '${NGINX_PORT}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
||||
|
||||
# S'assurer que les répertoires nginx sont accessibles
|
||||
mkdir -p /var/cache/nginx /var/run/nginx /var/log/nginx
|
||||
chown -R www-data:www-data /var/cache/nginx /var/run/nginx /var/log/nginx
|
||||
|
||||
# Créer le dossier uploads temporaire
|
||||
mkdir -p /tmp/uploads
|
||||
|
||||
echo "Starting FastAPI server..."
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --loop asyncio &
|
||||
|
||||
echo "Starting Streamlit app..."
|
||||
streamlit run app/streamlit_app.py \
|
||||
--server.port 8501 \
|
||||
--server.address 0.0.0.0 \
|
||||
--server.enableXsrfProtection false \
|
||||
--server.enableCORS false \
|
||||
--server.headless true \
|
||||
&
|
||||
|
||||
# Vérifier que FastAPI est prêt
|
||||
echo "Waiting for FastAPI to be ready..."
|
||||
until curl -s http://127.0.0.1:8000/health > /dev/null; do
|
||||
echo " FastAPI not ready yet, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "✅ FastAPI is ready!"
|
||||
|
||||
# Vérifier que Streamlit est prêt
|
||||
echo "Waiting for Streamlit to be ready..."
|
||||
until curl -s http://127.0.0.1:8501 > /dev/null; do
|
||||
echo " Streamlit not ready yet, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "✅ Streamlit is ready!"
|
||||
|
||||
echo "🚀 Launching NGINX reverse proxy on port $NGINX_PORT..."
|
||||
nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
|
||||
0
utils/__init__.py
Normal file
658
utils/utils.py
Normal file
|
|
@ -0,0 +1,658 @@
|
|||
import os
|
||||
import json
|
||||
import tempfile
|
||||
import numpy as np
|
||||
import torch
|
||||
import matplotlib.pyplot as plt
|
||||
import segmentation_models_pytorch as smp
|
||||
import mlflow
|
||||
import re
|
||||
import ast
|
||||
import time
|
||||
import random
|
||||
|
||||
# Visualisation
|
||||
import matplotlib.patches as mpatches
|
||||
from matplotlib.colors import ListedColormap
|
||||
|
||||
# Preprocessing/Images
|
||||
import albumentations as A
|
||||
from albumentations.pytorch import ToTensorV2
|
||||
|
||||
|
||||
def update_colors_to_wcag(mapping_config):
|
||||
"""
|
||||
Met à jour les couleurs dans mapping_config pour utiliser les couleurs WCAG accessibles
|
||||
avec redistribution sémantique intuitive finale
|
||||
"""
|
||||
# Redistribution sémantique finale :
|
||||
# Flat → gris moyen, Human → rouge, Vehicle → violet, Construction → orange,
|
||||
# Object → teal, Nature → vert, Sky → bleu, Void → gris foncé
|
||||
wcag_colors = np.array([
|
||||
[102, 102, 102], # Flat: gris moyen (routes, trottoirs)
|
||||
[204, 0, 0], # Human: rouge (piétons, cyclistes)
|
||||
[102, 51, 153], # Vehicle: violet (véhicules)
|
||||
[204, 102, 0], # Construction: orange (bâtiments)
|
||||
[0, 102, 102], # Object: teal (objets, signalétique)
|
||||
[0, 102, 0], # Nature: vert (végétation)
|
||||
[0, 102, 204], # Sky: bleu (ciel)
|
||||
[51, 51, 51] # Void: gris foncé (zones non-définies)
|
||||
], dtype=np.uint8)
|
||||
|
||||
# S'assurer qu'on a assez de couleurs pour le nombre de classes
|
||||
num_classes = mapping_config['num_classes']
|
||||
if len(wcag_colors) < num_classes:
|
||||
print(f"⚠️ Attention: {num_classes} classes mais seulement {len(wcag_colors)} couleurs WCAG disponibles")
|
||||
# Compléter avec les couleurs originales si nécessaire
|
||||
original_colors = mapping_config['group_colors']
|
||||
while len(wcag_colors) < num_classes:
|
||||
wcag_colors = np.vstack([wcag_colors, original_colors[len(wcag_colors):len(wcag_colors)+1]])
|
||||
|
||||
mapping_config['group_colors'] = wcag_colors[:num_classes]
|
||||
print(f"✅ Couleurs mises à jour vers WCAG pour {num_classes} classes")
|
||||
return mapping_config
|
||||
|
||||
|
||||
def load_cityscapes_config(config_path="../cityscapes_config.json", verbose=True, use_wcag_colors=False):
|
||||
"""
|
||||
Charge la configuration Cityscapes depuis un fichier JSON.
|
||||
|
||||
Args:
|
||||
config_path: Chemin vers le fichier de configuration
|
||||
verbose: Affichage détaillé
|
||||
use_wcag_colors: Utiliser les couleurs WCAG accessibles
|
||||
"""
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
|
||||
class_groups = config["class_groups"]
|
||||
cityscapes_mapping = {int(k): v for k, v in config["cityscapes_mapping"].items()}
|
||||
id_to_label = {int(k): v for k, v in config["id_to_label"].items()}
|
||||
group_colors = np.array(config["group_colors"], dtype=np.uint8)
|
||||
group_names = config["group_names"]
|
||||
|
||||
id_to_group = np.zeros(256, dtype=np.uint8)
|
||||
for id_, group_id in cityscapes_mapping.items():
|
||||
id_to_group[id_] = group_id
|
||||
|
||||
num_classes = len(group_names)
|
||||
|
||||
mapping_config = {
|
||||
"class_groups": class_groups,
|
||||
"cityscapes_mapping": cityscapes_mapping,
|
||||
"id_to_label": id_to_label,
|
||||
"group_colors": group_colors,
|
||||
"group_names": group_names,
|
||||
"id_to_group": id_to_group,
|
||||
"num_classes": num_classes
|
||||
}
|
||||
|
||||
# Appliquer les couleurs WCAG si demandé
|
||||
if use_wcag_colors:
|
||||
mapping_config = update_colors_to_wcag(mapping_config)
|
||||
|
||||
if verbose:
|
||||
print("== Groupes de classes Cityscapes ==")
|
||||
for group, classes in class_groups.items():
|
||||
print(f" - {group} : {classes}")
|
||||
|
||||
print("\n== Mapping ID Cityscapes → Groupe ==")
|
||||
for id_ in sorted(cityscapes_mapping.keys()):
|
||||
label = id_to_label.get(id_, "Label inconnu")
|
||||
group_id = cityscapes_mapping[id_]
|
||||
print(f" ID {id_:2d} ('{label}') → groupe '{group_names[group_id]}' ({group_id})")
|
||||
|
||||
print("\n== Liste des groupes et couleurs associées ==")
|
||||
for i, (name, color) in enumerate(zip(group_names, mapping_config['group_colors'])):
|
||||
print(f" Groupe {i}: {name} - Couleur RGB : {color}")
|
||||
|
||||
print(f"\nNombre total de groupes/classes : {num_classes}")
|
||||
|
||||
return mapping_config
|
||||
|
||||
|
||||
def map_mask_ids(mask, id_to_group_mapping):
|
||||
"""
|
||||
Applique le mapping des IDs Cityscapes vers les groupes
|
||||
ADAPTÉ PYTORCH : Utilise torch au lieu de tf
|
||||
"""
|
||||
if isinstance(mask, np.ndarray):
|
||||
mask = torch.from_numpy(mask)
|
||||
|
||||
if isinstance(id_to_group_mapping, np.ndarray):
|
||||
if len(id_to_group_mapping) != 256:
|
||||
raise ValueError(f"id_to_group_mapping doit avoir 256 éléments, reçu: {len(id_to_group_mapping)}")
|
||||
mapping_tensor = torch.tensor(id_to_group_mapping, dtype=torch.long)
|
||||
else:
|
||||
mapping_tensor = torch.tensor(list(id_to_group_mapping), dtype=torch.long)
|
||||
|
||||
# S'assurer que les valeurs du masque sont dans la plage valide [0, 255]
|
||||
mask_clipped = torch.clamp(mask, 0, 255)
|
||||
mapped_mask = mapping_tensor[mask_clipped.long()]
|
||||
return mapped_mask
|
||||
|
||||
def colorize_mask(mask, group_colors):
|
||||
"""
|
||||
Applique la colorisation du masque avec les couleurs définies
|
||||
INCHANGÉ - Compatible PyTorch (prend numpy en entrée)
|
||||
"""
|
||||
if isinstance(mask, torch.Tensor):
|
||||
mask = mask.numpy()
|
||||
|
||||
colored_mask = np.zeros((mask.shape[0], mask.shape[1], 3), dtype=np.uint8)
|
||||
num_classes = len(group_colors)
|
||||
|
||||
for group_id in range(num_classes):
|
||||
colored_mask[mask == group_id] = group_colors[group_id]
|
||||
|
||||
return colored_mask
|
||||
|
||||
|
||||
def get_preprocessing_fn(img_size=(512, 512), encoder=None):
|
||||
"""
|
||||
Retourne une fonction qui prétraite à la fois les images et les masques
|
||||
pour l'inférence, en reproduisant exactement le pipeline CityscapesDataset.
|
||||
"""
|
||||
|
||||
if encoder is None:
|
||||
mean, std = [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]
|
||||
else:
|
||||
mean, std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
|
||||
|
||||
# Pipeline unifié (gérera image ET masque ensemble)
|
||||
transform = A.Compose([
|
||||
A.Resize(height=img_size[0], width=img_size[1]),
|
||||
A.Normalize(mean=mean, std=std),
|
||||
ToTensorV2()
|
||||
])
|
||||
|
||||
def preprocess(image_np, mask_np=None):
|
||||
if mask_np is not None:
|
||||
# Traitement simultané image + masque
|
||||
transformed = transform(image=image_np, mask=mask_np)
|
||||
return transformed['image'], transformed['mask'].long()
|
||||
else:
|
||||
# Image seule
|
||||
transformed = transform(image=image_np)
|
||||
return transformed['image'], None
|
||||
|
||||
return preprocess
|
||||
|
||||
def apply_random_augmentation(image_np, prob=0.7):
|
||||
"""
|
||||
Applique des transformations d'augmentation aléatoires à une image,
|
||||
similaires à celles utilisées pendant l'entraînement.
|
||||
|
||||
Args:
|
||||
image_np (np.ndarray): Image numpy array (H, W, 3)
|
||||
prob (float): Probabilité d'appliquer chaque transformation
|
||||
|
||||
Returns:
|
||||
tuple: (image_augmentée, liste_des_transformations_appliquées)
|
||||
"""
|
||||
transforms_applied = []
|
||||
|
||||
# Définir les transformations possibles
|
||||
possible_transforms = [
|
||||
{
|
||||
"name": "Rotation aléatoire",
|
||||
"transform": A.Rotate(limit=15, p=prob),
|
||||
"description": "Rotation de -15° à +15°"
|
||||
},
|
||||
{
|
||||
"name": "Flip horizontal",
|
||||
"transform": A.HorizontalFlip(p=0.5),
|
||||
"description": "Miroir horizontal (50% chance)"
|
||||
},
|
||||
{
|
||||
"name": "Changement de luminosité",
|
||||
"transform": A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=prob),
|
||||
"description": "Luminosité et contraste ±20%"
|
||||
},
|
||||
{
|
||||
"name": "Flou gaussien léger",
|
||||
"transform": A.GaussianBlur(blur_limit=(1, 3), p=prob*0.3),
|
||||
"description": "Flou gaussien subtil"
|
||||
},
|
||||
{
|
||||
"name": "Décalage couleur",
|
||||
"transform": A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=15, val_shift_limit=10, p=prob),
|
||||
"description": "Ajustement teinte/saturation/valeur"
|
||||
},
|
||||
{
|
||||
"name": "Zoom aléatoire",
|
||||
"transform": A.RandomScale(scale_limit=0.1, p=prob*0.5),
|
||||
"description": "Zoom de -10% à +10%"
|
||||
}
|
||||
]
|
||||
|
||||
# Sélectionner aléatoirement quelques transformations
|
||||
num_transforms = random.randint(1, min(4, len(possible_transforms)))
|
||||
selected_transforms = random.sample(possible_transforms, num_transforms)
|
||||
|
||||
# Créer le pipeline d'augmentation
|
||||
augmentation_pipeline = []
|
||||
for transform_info in selected_transforms:
|
||||
augmentation_pipeline.append(transform_info["transform"])
|
||||
transforms_applied.append(f"{transform_info['name']}: {transform_info['description']}")
|
||||
|
||||
# Appliquer les transformations
|
||||
compose = A.Compose(augmentation_pipeline)
|
||||
|
||||
try:
|
||||
augmented = compose(image=image_np)
|
||||
augmented_image = augmented['image']
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'augmentation: {e}")
|
||||
# En cas d'erreur, retourner l'image originale
|
||||
augmented_image = image_np.copy()
|
||||
transforms_applied = ["Erreur - image originale conservée"]
|
||||
|
||||
return augmented_image, transforms_applied
|
||||
|
||||
# Version simplifiée de preprocess_image_and_mask utilisant la nouvelle get_preprocessing_fn
|
||||
def preprocess_image_and_mask(image_path, mask_path=None, img_size=(512, 512),
|
||||
encoder_name=None, id_to_group_mapping=None):
|
||||
"""
|
||||
Version simplifiée utilisant get_preprocessing_fn pour la cohérence avec CityscapesDataset
|
||||
"""
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch
|
||||
|
||||
print(f"🔄 Préprocessing de l'image: {os.path.basename(image_path)}")
|
||||
|
||||
# Chargement des données
|
||||
image_pil = Image.open(image_path).convert('RGB')
|
||||
image_np = np.array(image_pil)
|
||||
img_display = np.array(image_pil.resize(img_size, Image.BILINEAR)) # Pour affichage
|
||||
|
||||
mask_np = None
|
||||
if mask_path and os.path.exists(mask_path):
|
||||
print(f"🔄 Préprocessing du masque: {os.path.basename(mask_path)}")
|
||||
mask_pil = Image.open(mask_path).convert('L')
|
||||
mask_np = np.array(mask_pil)
|
||||
print(f" Valeurs uniques avant mapping: {np.unique(mask_np)[:10]}...")
|
||||
|
||||
# Utiliser notre preprocessing unifié
|
||||
preprocess_fn = get_preprocessing_fn(img_size=img_size, encoder=encoder_name)
|
||||
img_preprocessed, mask_preprocessed = preprocess_fn(image_np, mask_np)
|
||||
|
||||
# Appliquer le mapping si fourni ET si on a un masque
|
||||
if mask_preprocessed is not None and id_to_group_mapping is not None:
|
||||
mask_preprocessed = map_mask_ids(mask_preprocessed, id_to_group_mapping)
|
||||
print(f" Valeurs uniques après mapping: {torch.unique(mask_preprocessed)}")
|
||||
|
||||
return img_preprocessed, img_display, mask_preprocessed
|
||||
|
||||
# Version mise à jour de run_inference_and_visualize
|
||||
def run_inference_and_visualize(image_path, model, encoder_name, img_size, mapping_config,
|
||||
mask_path=None, save_dir=None, show_stats=True, show_plot=True):
|
||||
"""
|
||||
Version simplifiée utilisant preprocess_image_and_mask
|
||||
"""
|
||||
print(f"=== INFÉRENCE PYTORCH SUR {os.path.basename(image_path)} ===")
|
||||
|
||||
# Utiliser la version simplifiée du preprocessing
|
||||
img_preprocessed, img_display, mask_gt = preprocess_image_and_mask(
|
||||
image_path=image_path,
|
||||
mask_path=mask_path,
|
||||
img_size=img_size or (512, 512),
|
||||
encoder_name=encoder_name,
|
||||
id_to_group_mapping=mapping_config.get('id_to_group')
|
||||
)
|
||||
|
||||
print("🔮 Exécution de l'inférence PyTorch...")
|
||||
model.eval()
|
||||
device = next(model.parameters()).device
|
||||
|
||||
with torch.no_grad():
|
||||
# Ajouter dimension batch
|
||||
img_batch = img_preprocessed.unsqueeze(0).to(device) # [1, 3, H, W]
|
||||
|
||||
# ⏱️ MESURE DU TEMPS D'INFÉRENCE
|
||||
start_time = time.time()
|
||||
predictions = model(img_batch) # [1, num_classes, H, W]
|
||||
end_time = time.time()
|
||||
|
||||
inference_time = end_time - start_time
|
||||
|
||||
predicted_mask = torch.argmax(predictions[0], dim=0).cpu().numpy() # [H, W]
|
||||
|
||||
print(f"✅ Inférence terminée - Shape: {predicted_mask.shape}")
|
||||
print(f"⏱️ Temps d'inférence: {inference_time*1000:.1f} ms ({inference_time:.4f} s)")
|
||||
|
||||
# [Le reste du code de visualisation reste identique]
|
||||
# Visualisation
|
||||
num_plots = 2 if mask_gt is None else 4
|
||||
fig, axes = plt.subplots(1, num_plots, figsize=(5*num_plots, 5))
|
||||
if num_plots == 1:
|
||||
axes = [axes]
|
||||
|
||||
axes[0].imshow(img_display)
|
||||
axes[0].set_title("Image Originale")
|
||||
axes[0].axis('off')
|
||||
|
||||
predicted_colored = colorize_mask(predicted_mask, mapping_config['group_colors'])
|
||||
axes[1].imshow(predicted_colored)
|
||||
axes[1].set_title("Masque Prédit")
|
||||
axes[1].axis('off')
|
||||
|
||||
if mask_gt is not None:
|
||||
mask_gt_np = mask_gt.numpy() if isinstance(mask_gt, torch.Tensor) else mask_gt
|
||||
gt_colored = colorize_mask(mask_gt_np, mapping_config['group_colors'])
|
||||
axes[2].imshow(gt_colored)
|
||||
axes[2].set_title("Masque de Vérité")
|
||||
axes[2].axis('off')
|
||||
|
||||
# Visualisation des erreurs
|
||||
diff_mask = (mask_gt_np != predicted_mask).astype(float)
|
||||
axes[3].imshow(img_display)
|
||||
axes[3].imshow(diff_mask, alpha=0.5, cmap='Reds')
|
||||
axes[3].set_title("Erreurs (en rouge)")
|
||||
axes[3].axis('off')
|
||||
|
||||
colors_normalized = mapping_config['group_colors'] / 255.0
|
||||
custom_cmap = ListedColormap(colors_normalized)
|
||||
|
||||
legend_elements = []
|
||||
for i, (name, color) in enumerate(zip(mapping_config['group_names'], colors_normalized)):
|
||||
legend_elements.append(mpatches.Patch(color=color, label=f'{i}: {name}'))
|
||||
|
||||
fig.legend(handles=legend_elements,
|
||||
loc='center left',
|
||||
bbox_to_anchor=(1.01, 0.5),
|
||||
fontsize=9,
|
||||
frameon=True,
|
||||
fancybox=True,
|
||||
shadow=True)
|
||||
|
||||
plt.tight_layout()
|
||||
fig.subplots_adjust(right=0.95)
|
||||
|
||||
if save_dir:
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
img_name = os.path.splitext(os.path.basename(image_path))[0]
|
||||
fig_path = os.path.join(save_dir, f'{img_name}_inference.png')
|
||||
fig.savefig(fig_path, dpi=150, bbox_inches='tight')
|
||||
print(f"💾 Résultat sauvegardé: {fig_path}")
|
||||
|
||||
|
||||
# Statistiques
|
||||
if show_stats:
|
||||
print("\n📊 Statistiques du masque prédit:")
|
||||
unique_classes, counts = np.unique(predicted_mask, return_counts=True)
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / predicted_mask.size) * 100
|
||||
print(f" {mapping_config['group_names'][class_id]}: {count} pixels ({percentage:.1f}%)")
|
||||
|
||||
if mask_gt is not None:
|
||||
mask_gt_np = mask_gt.numpy() if isinstance(mask_gt, torch.Tensor) else mask_gt
|
||||
print("\n📊 Statistiques du masque de vérité:")
|
||||
unique_classes, counts = np.unique(mask_gt_np, return_counts=True)
|
||||
for class_id, count in zip(unique_classes, counts):
|
||||
if class_id < len(mapping_config['group_names']):
|
||||
percentage = (count / mask_gt_np.size) * 100
|
||||
print(f" {mapping_config['group_names'][class_id]}: {count} pixels ({percentage:.1f}%)")
|
||||
|
||||
if show_plot:
|
||||
plt.show()
|
||||
return predicted_mask, inference_time
|
||||
else:
|
||||
return predicted_mask, inference_time, fig
|
||||
|
||||
|
||||
def load_model(run_id=None, experiment_name="OC Projet 9",
|
||||
metric="test_mean_iou", top_n=1):
|
||||
"""
|
||||
Charge un modèle PyTorch depuis MLflow avec plusieurs méthodes de sélection
|
||||
|
||||
Args:
|
||||
run_id (str, optional): Run ID spécifique (prioritaire si fourni)
|
||||
experiment_name (str): Nom de l'expérience MLflow
|
||||
metric (str): Métrique pour classer les runs
|
||||
top_n (int): Position dans le classement (1=meilleur, 2=deuxième, etc.)
|
||||
|
||||
Returns:
|
||||
tuple: (model, source_run_id, encoder_name, script_img_size)
|
||||
"""
|
||||
|
||||
print(f"\n🔍 Chargement d'un modèle PyTorch depuis MLflow...")
|
||||
|
||||
client = mlflow.tracking.MlflowClient()
|
||||
|
||||
# 1. Sélection du run selon les paramètres
|
||||
if run_id:
|
||||
print(f"🎯 Mode run_id spécifique: {run_id}")
|
||||
try:
|
||||
run = mlflow.get_run(run_id)
|
||||
source_run_id = run_id
|
||||
print(f"✅ Run trouvé: {run.data.tags.get('mlflow.runName', 'Unknown')}")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"❌ Run {run_id} introuvable: {e}")
|
||||
|
||||
else:
|
||||
print(f"🏆 Mode classement: top_{top_n} de '{metric}' dans '{experiment_name}'")
|
||||
try:
|
||||
# Récupérer l'expérience
|
||||
experiment = client.get_experiment_by_name(experiment_name)
|
||||
if experiment is None:
|
||||
raise RuntimeError(f"❌ Expérience '{experiment_name}' non trouvée")
|
||||
|
||||
# Rechercher les runs avec la métrique, triés par ordre décroissant
|
||||
filter_string = f"status = 'FINISHED' and metrics.{metric} >= 0"
|
||||
order_by = f"metrics.{metric} DESC"
|
||||
|
||||
runs_df = mlflow.search_runs(
|
||||
experiment_ids=[experiment.experiment_id],
|
||||
filter_string=filter_string,
|
||||
order_by=[order_by],
|
||||
max_results=top_n
|
||||
)
|
||||
|
||||
if runs_df.empty:
|
||||
raise RuntimeError(f"❌ Aucun run avec '{metric}' trouvé dans '{experiment_name}'")
|
||||
|
||||
if len(runs_df) < top_n:
|
||||
raise RuntimeError(f"❌ Seulement {len(runs_df)} runs trouvés, impossible d'obtenir le top_{top_n}")
|
||||
|
||||
# Prendre le run à la position top_n
|
||||
selected_run = runs_df.iloc[top_n - 1]
|
||||
source_run_id = selected_run['run_id']
|
||||
metric_value = selected_run[f'metrics.{metric}']
|
||||
run_name = selected_run.get('tags.mlflow.runName', 'Unknown')
|
||||
|
||||
print(f"✅ Run sélectionné (position #{top_n}):")
|
||||
print(f" • Nom: {run_name}")
|
||||
print(f" • Run ID: {source_run_id}")
|
||||
print(f" • {metric}: {metric_value:.4f}")
|
||||
|
||||
# Afficher le contexte
|
||||
print(f"📊 Contexte du classement:")
|
||||
for i, (_, row) in enumerate(runs_df.head(min(5, len(runs_df))).iterrows()):
|
||||
indicator = "👑" if i == (top_n - 1) else f"{i+1}."
|
||||
name = row.get('tags.mlflow.runName', 'Unknown')[:30]
|
||||
value = row[f'metrics.{metric}']
|
||||
print(f" {indicator} {name}: {value:.4f}")
|
||||
|
||||
run = mlflow.get_run(source_run_id)
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"❌ Erreur lors de la sélection: {e}")
|
||||
|
||||
# 2-5. [Reste du code identique - extraction params, détection variante, chargement...]
|
||||
try:
|
||||
params = run.data.params
|
||||
|
||||
encoder_name = params.get("encoder_name", None)
|
||||
architecture = params.get("architecture", "FPN")
|
||||
num_classes = int(params.get("num_classes", 8))
|
||||
script_img_size_raw = params.get("script_img_size", None)
|
||||
run_name = run.data.tags.get('mlflow.runName', 'Unknown')
|
||||
|
||||
# Parser script_img_size
|
||||
script_img_size = None
|
||||
if script_img_size_raw:
|
||||
try:
|
||||
script_img_size = ast.literal_eval(script_img_size_raw)
|
||||
if isinstance(script_img_size, (tuple, list)) and len(script_img_size) == 2:
|
||||
script_img_size = tuple(script_img_size)
|
||||
except:
|
||||
script_img_size = None
|
||||
|
||||
print(f"\n📋 Paramètres du modèle:")
|
||||
print(f"🏗️ Architecture: {architecture}")
|
||||
print(f"🔧 Encodeur: {encoder_name}")
|
||||
print(f"📊 Nombre de classes: {num_classes}")
|
||||
print(f"🖼️ Script img size: {script_img_size}")
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"❌ Erreur lors de la récupération des paramètres: {e}")
|
||||
|
||||
# Détecter la variante SegFormer
|
||||
def detect_segformer_variant_from_name(run_name, params):
|
||||
segformer_pattern = r'SegFormer[_-]?b(\d+)'
|
||||
match = re.search(segformer_pattern, run_name, re.IGNORECASE)
|
||||
if match:
|
||||
variant_num = match.group(1)
|
||||
detected = f"mit_b{variant_num}"
|
||||
print(f"🔍 Variante détectée: 'SegFormer_b{variant_num}' → {detected}")
|
||||
return detected
|
||||
|
||||
if encoder_name and encoder_name.startswith("mit_b"):
|
||||
print(f"🔍 Variante depuis paramètres: {encoder_name}")
|
||||
return encoder_name
|
||||
|
||||
b_pattern = r'\bb(\d+)\b'
|
||||
match = re.search(b_pattern, run_name, re.IGNORECASE)
|
||||
if match:
|
||||
variant_num = match.group(1)
|
||||
detected = f"mit_b{variant_num}"
|
||||
print(f"🔍 Pattern détecté: 'b{variant_num}' → {detected}")
|
||||
return detected
|
||||
|
||||
print(f"🔍 Aucune variante détectée, utilisation de mit_b0")
|
||||
return "mit_b0"
|
||||
|
||||
detected_variant = detect_segformer_variant_from_name(run_name, params)
|
||||
|
||||
# Télécharger et charger le modèle
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
try:
|
||||
artifacts = client.list_artifacts(source_run_id)
|
||||
model_artifact_candidates = [a.path for a in artifacts if a.path.endswith('.pth')]
|
||||
|
||||
if not model_artifact_candidates:
|
||||
raise RuntimeError("❌ Aucun fichier .pth trouvé")
|
||||
|
||||
model_artifact = model_artifact_candidates[0]
|
||||
print(f"⬇️ Téléchargement de {model_artifact}...")
|
||||
|
||||
local_path = mlflow.artifacts.download_artifacts(
|
||||
run_id=source_run_id,
|
||||
artifact_path=model_artifact,
|
||||
dst_path=tmpdir
|
||||
)
|
||||
|
||||
checkpoint = torch.load(local_path, map_location='cpu')
|
||||
if isinstance(checkpoint, dict) and 'state_dict' in checkpoint:
|
||||
state_dict = checkpoint['state_dict']
|
||||
else:
|
||||
state_dict = checkpoint
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"❌ Erreur lors du téléchargement: {e}")
|
||||
|
||||
# Créer le modèle
|
||||
try:
|
||||
is_transformer = any('patch_embed' in key for key in state_dict.keys())
|
||||
|
||||
if is_transformer:
|
||||
print(f"🔧 Création SegFormer ({detected_variant})")
|
||||
model = smp.Segformer(encoder_name=detected_variant, classes=num_classes)
|
||||
encoder_name = detected_variant
|
||||
else:
|
||||
print("🔧 Création modèle CNN")
|
||||
if encoder_name is None or encoder_name == "None":
|
||||
encoder_name = "efficientnet-b0"
|
||||
|
||||
if architecture.lower() == "fpn":
|
||||
model = smp.FPN(encoder_name=encoder_name, classes=num_classes)
|
||||
elif architecture.lower() == "unet":
|
||||
model = smp.Unet(encoder_name=encoder_name, classes=num_classes)
|
||||
elif architecture.lower() == "deeplabv3plus":
|
||||
model = smp.DeepLabV3Plus(encoder_name=encoder_name, classes=num_classes)
|
||||
else:
|
||||
raise ValueError(f"Architecture {architecture} non supportée")
|
||||
|
||||
print("🔄 Chargement des poids...")
|
||||
model.load_state_dict(state_dict, strict=True)
|
||||
model.eval()
|
||||
print("✅ Modèle chargé avec succès!")
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
model.load_state_dict(state_dict, strict=False)
|
||||
model.eval()
|
||||
print("✅ Chargement partiel réussi!")
|
||||
except Exception as e2:
|
||||
raise RuntimeError(f"❌ Erreur lors du chargement: {e2}")
|
||||
|
||||
print(f"🎉 Modèle chargé depuis le run {source_run_id}")
|
||||
return model, source_run_id, encoder_name, script_img_size
|
||||
|
||||
|
||||
def load_best_model_from_registry(model_name):
|
||||
"""
|
||||
Charge le meilleur modèle depuis le MLflow Model Registry
|
||||
(Version refactorisée - wrapper autour de load_model)
|
||||
|
||||
Args:
|
||||
model_name (str): Nom du modèle dans le Registry
|
||||
|
||||
Returns:
|
||||
tuple: (model, source_run_id, encoder_name, script_img_size)
|
||||
"""
|
||||
print(f"🏛️ Récupération du modèle '{model_name}' depuis le Registry...")
|
||||
|
||||
client = mlflow.tracking.MlflowClient()
|
||||
|
||||
try:
|
||||
# Récupérer la version en Production
|
||||
versions = client.search_model_versions(f"name='{model_name}'")
|
||||
prod_versions = [v for v in versions if v.current_stage == "Production"]
|
||||
|
||||
if not prod_versions:
|
||||
raise RuntimeError(f"❌ Aucune version en Production trouvée pour {model_name}")
|
||||
|
||||
latest_prod = max(prod_versions, key=lambda v: int(v.version))
|
||||
print(f"📦 Version Registry: {latest_prod.version} (Stage: {latest_prod.current_stage})")
|
||||
|
||||
# Récupérer le source_run_id depuis le tag du Registry
|
||||
source_run_id = latest_prod.tags.get("source_run_id")
|
||||
if not source_run_id:
|
||||
raise RuntimeError(f"❌ Tag 'source_run_id' absent pour la version {latest_prod.version}")
|
||||
|
||||
print(f"🔗 Source run détecté: {source_run_id}")
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"❌ Erreur lors de l'accès au Registry: {e}")
|
||||
|
||||
# Déléguer le chargement à load_model avec le run_id trouvé
|
||||
print("➡️ Délégation vers load_model...")
|
||||
return load_model(run_id=source_run_id)
|
||||
|
||||
|
||||
# Fonctions de convenance
|
||||
def load_best_model(experiment_name="OC Projet 9", metric="test_mean_iou"):
|
||||
"""Charge le meilleur modèle selon une métrique"""
|
||||
return load_model(experiment_name=experiment_name, metric=metric, top_n=1)
|
||||
|
||||
def load_second_best_model(experiment_name="OC Projet 9", metric="test_mean_iou"):
|
||||
"""Charge le deuxième meilleur modèle selon une métrique"""
|
||||
return load_model(experiment_name=experiment_name, metric=metric, top_n=2)
|
||||
|
||||
def load_model_by_run_id(run_id):
|
||||
"""Charge un modèle par run_id spécifique"""
|
||||
return load_model(run_id=run_id)
|
||||