From 830facaf41bf5c8d23bb16dbc7507aa030482578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Mureau?= Date: Thu, 4 Sep 2025 10:15:38 +0200 Subject: [PATCH] website color mapping update --- app_2/main.py | 755 ---------------------------------- app_2/requirements.txt | 70 ---- app_2/streamlit_app.py | 913 ----------------------------------------- 3 files changed, 1738 deletions(-) delete mode 100644 app_2/main.py delete mode 100644 app_2/requirements.txt delete mode 100644 app_2/streamlit_app.py diff --git a/app_2/main.py b/app_2/main.py deleted file mode 100644 index 769a1c0..0000000 --- a/app_2/main.py +++ /dev/null @@ -1,755 +0,0 @@ -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...") - mapping_config = load_cityscapes_config(CITYSCAPES_CONFIG_PATH, verbose=False) - print("✅ Configuration Cityscapes chargée") - - 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 - -# 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() - } - -@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") - 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": mapping_config['group_colors'].tolist() - } - -@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") - - 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) - - # Appliquer le preprocessing standard (resize + normalisation) - preprocess_fn = get_preprocessing_fn(img_size=(512, 512), 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 - original_resized = np.array(original_pil.resize((512, 512))) - - result = PreprocessingResult( - success=True, - message="Preprocessing completed successfully", - original_image=img_to_base64(original_resized), - normalized_image=img_to_base64(normalized_display), - transformations_applied=["Resize (512x512)", "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("/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() - } \ No newline at end of file diff --git a/app_2/requirements.txt b/app_2/requirements.txt deleted file mode 100644 index ef6d4de..0000000 --- a/app_2/requirements.txt +++ /dev/null @@ -1,70 +0,0 @@ -# ============================================================================= -# 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 diff --git a/app_2/streamlit_app.py b/app_2/streamlit_app.py deleted file mode 100644 index 051152a..0000000 --- a/app_2/streamlit_app.py +++ /dev/null @@ -1,913 +0,0 @@ -import os -import sys -import requests -import streamlit as st -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -from PIL import Image -import io -import base64 -import pickle - -# Configuration de l'API -API_BASE_URL = os.getenv("MULTISEG_API_BASE_URL", "http://127.0.0.1:8080/api") - -# Import des fonctions utils pour l'affichage local -sys.path.append('/app') -sys.path.append('.') -from utils.utils import colorize_mask - -# Configuration de la page -st.set_page_config( - page_title="Street Vision - Segmentation Comparison", - page_icon="🚗", - layout="wide" -) - -# CSS personnalisé mis à jour -st.markdown(""" - -""", unsafe_allow_html=True) - -# En-tête principal -st.markdown(""" -
-

🚗 Street Vision - Comparaison de Modèles

-

Évaluation comparative SegFormer vs FPN sur le dataset Cityscapes

-
-""", unsafe_allow_html=True) - -# Fonctions utilitaires -def get_models_info(): - """Récupère les informations des deux modèles""" - try: - response = requests.get(f"{API_BASE_URL}/models") - if response.status_code == 200: - return response.json() - else: - st.error(f"Erreur lors de la récupération des infos modèles: {response.status_code}") - return None - except Exception as e: - st.error(f"Erreur de connexion à l'API: {e}") - return None - -def get_dataset_info(): - """Récupère les informations sur le dataset""" - try: - response = requests.get(f"{API_BASE_URL}/dataset-info") - if response.status_code == 200: - return response.json() - else: - st.error(f"Erreur lors de la récupération des infos dataset: {response.status_code}") - return None - except Exception as e: - st.error(f"Erreur de connexion à l'API: {e}") - return None - -def get_train_images(): - """Récupère la liste des images d'entraînement""" - try: - response = requests.get(f"{API_BASE_URL}/train-images") - if response.status_code == 200: - return response.json() - else: - st.error(f"Erreur lors de la récupération des images: {response.status_code}") - return None - except Exception as e: - st.error(f"Erreur de connexion à l'API: {e}") - return None - -def get_class_repartition(): - """Récupère le graphique de répartition des classes""" - try: - response = requests.get(f"{API_BASE_URL}/class-repartition", timeout=10) - if response.status_code == 200: - return response.content - else: - return None - except Exception as e: - st.warning(f"Graphique de répartition non disponible: {e}") - return None - -def preprocess_train_image(filename, apply_augmentation=False): - """Lance le preprocessing sur une image d'entraînement""" - try: - data = { - "filename": filename, - "apply_augmentation": apply_augmentation - } - response = requests.post( - f"{API_BASE_URL}/preprocess-train-image", - json=data, - headers={"Content-Type": "application/json"} - ) - - if response.status_code == 200: - return response.json() - else: - st.error(f"Erreur API: {response.status_code} - {response.text}") - return None - except Exception as e: - st.error(f"Erreur lors du preprocessing: {e}") - return None - -def get_sample_images(): - """Récupère la liste des images d'exemple""" - try: - response = requests.get(f"{API_BASE_URL}/sample-images") - if response.status_code == 200: - return response.json() - else: - st.error(f"Erreur lors de la récupération des images: {response.status_code}") - return None - except Exception as e: - st.error(f"Erreur de connexion à l'API: {e}") - return None - -def compare_sample_image(filename): - """Lance une comparaison sur une image d'exemple""" - try: - data = {"filename": filename} - response = requests.post( - f"{API_BASE_URL}/compare-sample", - json=data, - headers={"Content-Type": "application/json"} - ) - - if response.status_code == 200: - return response.json() - else: - st.error(f"Erreur API: {response.status_code} - {response.text}") - return None - except Exception as e: - st.error(f"Erreur lors de la comparaison: {e}") - return None - -def compare_uploaded_image(uploaded_file): - """Lance une comparaison sur une image uploadée""" - try: - files = {"file": (uploaded_file.name, uploaded_file.getvalue(), uploaded_file.type)} - response = requests.post(f"{API_BASE_URL}/compare-upload", files=files) - - if response.status_code == 200: - return response.json() - else: - st.error(f"Erreur API: {response.status_code} - {response.text}") - return None - except Exception as e: - st.error(f"Erreur lors de la comparaison: {e}") - return None - -def display_comparison_results(result, models_info): - """Affiche les résultats de comparaison entre les deux modèles""" - if not result or not result['success']: - st.error("Aucun résultat de comparaison disponible") - return - - # Informations des modèles - st.markdown("### 🏆 Modèles Comparés") - - col1, col2 = st.columns(2) - - with col1: - model1 = result['model1_info'] - # Formater les métriques séparément - miou1 = f"{model1.get('test_mean_iou'):.4f}" if model1.get('test_mean_iou') is not None else 'N/A' - acc1 = f"{model1.get('test_accuracy'):.4f}" if model1.get('test_accuracy') is not None else 'N/A' - - st.markdown(f""" -
-

🥇 Modèle #1

- {model1.get('architecture', 'N/A')} - {model1['encoder_name']}
- 📐 Taille: {model1['input_size']}
- 📊 mIoU: {miou1}
- 🎯 Accuracy: {acc1} -
- """, unsafe_allow_html=True) - - with col2: - model2 = result['model2_info'] - # Formater les métriques séparément - miou2 = f"{model2.get('test_mean_iou'):.4f}" if model2.get('test_mean_iou') is not None else 'N/A' - acc2 = f"{model2.get('test_accuracy'):.4f}" if model2.get('test_accuracy') is not None else 'N/A' - - st.markdown(f""" -
-

🥈 Modèle #2

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