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)
|
||||||