Docker development done. Pushing to dockerhub following cd github instructions
This commit is contained in:
parent
cdd6f9645b
commit
bb13d48c77
3 changed files with 431 additions and 284 deletions
242
app/main.py
242
app/main.py
|
|
@ -72,6 +72,10 @@ class ModelInfo(BaseModel):
|
||||||
encoder_name: str
|
encoder_name: str
|
||||||
input_size: tuple
|
input_size: tuple
|
||||||
rank: int
|
rank: int
|
||||||
|
architecture: Optional[str] = None # Ajout architecture
|
||||||
|
test_mean_iou: Optional[float] = None # Ajout mIoU
|
||||||
|
test_accuracy: Optional[float] = None # Ajout accuracy
|
||||||
|
class_ious: Optional[dict] = None # Ajout IoU par classe
|
||||||
|
|
||||||
class ComparisonResult(BaseModel):
|
class ComparisonResult(BaseModel):
|
||||||
success: bool
|
success: bool
|
||||||
|
|
@ -104,6 +108,45 @@ best_model_info = {}
|
||||||
second_best_model_info = {}
|
second_best_model_info = {}
|
||||||
mapping_config = None
|
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():
|
def initialize_models():
|
||||||
"""Initialise les deux meilleurs modèles au démarrage"""
|
"""Initialise les deux meilleurs modèles au démarrage"""
|
||||||
global best_model, second_best_model, best_model_info, second_best_model_info, mapping_config
|
global best_model, second_best_model, best_model_info, second_best_model_info, mapping_config
|
||||||
|
|
@ -122,12 +165,20 @@ def initialize_models():
|
||||||
metric="test_mean_iou",
|
metric="test_mean_iou",
|
||||||
top_n=1
|
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 = {
|
best_model_info = {
|
||||||
"name": "Meilleur modèle",
|
"name": "Meilleur modèle",
|
||||||
"run_id": best_run_id,
|
"run_id": best_run_id,
|
||||||
"encoder_name": best_encoder_name,
|
"encoder_name": best_encoder_name,
|
||||||
"input_size": best_img_size,
|
"input_size": best_img_size,
|
||||||
"rank": 1
|
"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...")
|
print("🔄 Chargement du deuxième meilleur modèle...")
|
||||||
|
|
@ -136,12 +187,20 @@ def initialize_models():
|
||||||
metric="test_mean_iou",
|
metric="test_mean_iou",
|
||||||
top_n=2
|
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 = {
|
second_best_model_info = {
|
||||||
"name": "Deuxième modèle",
|
"name": "Deuxième modèle",
|
||||||
"run_id": second_run_id,
|
"run_id": second_run_id,
|
||||||
"encoder_name": second_encoder_name,
|
"encoder_name": second_encoder_name,
|
||||||
"input_size": second_img_size,
|
"input_size": second_img_size,
|
||||||
"rank": 2
|
"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)
|
os.makedirs(TEMP_UPLOAD_DIR, exist_ok=True)
|
||||||
|
|
@ -257,40 +316,95 @@ def compare_sample_image(request: SampleImageRequest):
|
||||||
mask_path = Path(SAMPLE_IMAGES_DIR) / "mask" / mask_name
|
mask_path = Path(SAMPLE_IMAGES_DIR) / "mask" / mask_name
|
||||||
mask_path_str = str(mask_path) if mask_path.exists() else None
|
mask_path_str = str(mask_path) if mask_path.exists() else None
|
||||||
|
|
||||||
# Inférence modèle 1
|
# Inférence modèle 1 avec sa résolution spécifique
|
||||||
pred1, t1, _ = run_inference_and_visualize(
|
pred1, t1, fig1 = run_inference_and_visualize(
|
||||||
image_path=str(image_path),
|
image_path=str(image_path),
|
||||||
mask_path=mask_path_str,
|
mask_path=mask_path_str,
|
||||||
model=best_model,
|
model=best_model,
|
||||||
encoder_name=None,
|
encoder_name=None, # Normalisé de la même manière
|
||||||
img_size=best_model_info["input_size"],
|
img_size=best_model_info["input_size"], # Utilise la résolution du modèle
|
||||||
mapping_config=mapping_config,
|
mapping_config=mapping_config,
|
||||||
save_dir=None, show_stats=False, show_plot=False
|
save_dir=None, show_stats=False, show_plot=False
|
||||||
)
|
)
|
||||||
# Inférence modèle 2
|
# Inférence modèle 2 avec sa résolution spécifique
|
||||||
pred2, t2, _ = run_inference_and_visualize(
|
pred2, t2, fig2 = run_inference_and_visualize(
|
||||||
image_path=str(image_path),
|
image_path=str(image_path),
|
||||||
mask_path=mask_path_str,
|
mask_path=mask_path_str,
|
||||||
model=second_best_model,
|
model=second_best_model,
|
||||||
encoder_name=None,
|
encoder_name=None, # Normalisé de la même manière
|
||||||
img_size=second_best_model_info["input_size"],
|
img_size=second_best_model_info["input_size"], # Utilise la résolution du modèle
|
||||||
mapping_config=mapping_config,
|
mapping_config=mapping_config,
|
||||||
save_dir=None, show_stats=False, show_plot=False
|
save_dir=None, show_stats=False, show_plot=False
|
||||||
)
|
)
|
||||||
# Figure combinée
|
|
||||||
fig, axes = plt.subplots(2, 3, figsize=(15, 8))
|
# Créer une figure combinée avec les deux modèles
|
||||||
img_arr = mpimg.imread(str(image_path))
|
# Nombre de colonnes : 3 si pas de GT, 4 si GT disponible
|
||||||
axes[0, 0].imshow(img_arr); axes[0, 0].set_title("Image originale"); axes[0, 0].axis('off')
|
num_cols = 3 if not (mask_path_str and os.path.exists(mask_path_str)) else 4
|
||||||
axes[1, 0].imshow(img_arr); axes[1, 0].set_title("Image originale"); axes[1, 0].axis('off')
|
fig, axes = plt.subplots(2, num_cols, figsize=(5*num_cols, 8))
|
||||||
axes[0, 1].imshow(colorize_mask(pred1, mapping_config['group_colors']))
|
|
||||||
axes[0, 1].set_title(f"Prédiction {best_model_info['encoder_name']}"); axes[0, 1].axis('off')
|
# Récupérer les images depuis les figures individuelles
|
||||||
axes[1, 1].imshow(colorize_mask(pred2, mapping_config['group_colors']))
|
axes1 = fig1.get_axes()
|
||||||
axes[1, 1].set_title(f"Prédiction {second_best_model_info['encoder_name']}"); axes[1, 1].axis('off')
|
axes2 = fig2.get_axes()
|
||||||
if mask_path_str and os.path.exists(mask_path_str):
|
|
||||||
gt = mpimg.imread(mask_path_str)
|
# Ligne 1 : Modèle 1
|
||||||
axes[0, 2].imshow(gt); axes[0, 2].set_title("Ground Truth"); axes[0, 2].axis('off')
|
for i in range(min(len(axes1), num_cols)):
|
||||||
axes[1, 2].imshow(gt); axes[1, 2].set_title("Ground Truth"); axes[1, 2].axis('off')
|
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()
|
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()
|
fig_b64 = base64.b64encode(pickle.dumps(fig)).decode()
|
||||||
# Stats
|
# Stats
|
||||||
def calc(mask):
|
def calc(mask):
|
||||||
|
|
@ -333,28 +447,82 @@ def compare_uploaded_image(file: UploadFile = File(...)):
|
||||||
temp_file_path = os.path.join(TEMP_UPLOAD_DIR, temp_filename)
|
temp_file_path = os.path.join(TEMP_UPLOAD_DIR, temp_filename)
|
||||||
with open(temp_file_path, "wb") as buffer:
|
with open(temp_file_path, "wb") as buffer:
|
||||||
shutil.copyfileobj(file.file, buffer)
|
shutil.copyfileobj(file.file, buffer)
|
||||||
# Inférences
|
# Inférences avec résolutions spécifiques
|
||||||
pred1, t1, _ = run_inference_and_visualize(
|
pred1, t1, fig1 = run_inference_and_visualize(
|
||||||
image_path=temp_file_path, mask_path=None,
|
image_path=temp_file_path, mask_path=None,
|
||||||
model=best_model, encoder_name=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,
|
img_size=best_model_info["input_size"], mapping_config=mapping_config,
|
||||||
save_dir=None, show_stats=False, show_plot=False,
|
save_dir=None, show_stats=False, show_plot=False,
|
||||||
)
|
)
|
||||||
pred2, t2, _ = run_inference_and_visualize(
|
pred2, t2, fig2 = run_inference_and_visualize(
|
||||||
image_path=temp_file_path, mask_path=None,
|
image_path=temp_file_path, mask_path=None,
|
||||||
model=second_best_model, encoder_name=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,
|
img_size=second_best_model_info["input_size"], mapping_config=mapping_config,
|
||||||
save_dir=None, show_stats=False, show_plot=False,
|
save_dir=None, show_stats=False, show_plot=False,
|
||||||
)
|
)
|
||||||
# Figure
|
|
||||||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
# Figure combinée pour uploads (pas de GT, donc 2 colonnes seulement)
|
||||||
img_arr = mpimg.imread(temp_file_path)
|
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
|
||||||
axes[0].imshow(img_arr); axes[0].set_title("Image originale"); axes[0].axis('off')
|
|
||||||
axes[1].imshow(colorize_mask(pred1, mapping_config['group_colors']))
|
# Récupérer les images depuis les figures individuelles
|
||||||
axes[1].set_title(f"Prédiction {best_model_info['encoder_name']}"); axes[1].axis('off')
|
axes1 = fig1.get_axes()
|
||||||
axes[2].imshow(colorize_mask(pred2, mapping_config['group_colors']))
|
axes2 = fig2.get_axes()
|
||||||
axes[2].set_title(f"Prédiction {second_best_model_info['encoder_name']}"); axes[2].axis('off')
|
|
||||||
|
# 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()
|
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()
|
fig_b64 = base64.b64encode(pickle.dumps(fig)).decode()
|
||||||
# Stats
|
# Stats
|
||||||
def calc(mask):
|
def calc(mask):
|
||||||
|
|
@ -395,4 +563,4 @@ def get_metrics():
|
||||||
"second_best_model_info": second_best_model_info,
|
"second_best_model_info": second_best_model_info,
|
||||||
"num_classes": mapping_config['num_classes'] if mapping_config else None,
|
"num_classes": mapping_config['num_classes'] if mapping_config else None,
|
||||||
"timestamp": datetime.utcnow().isoformat()
|
"timestamp": datetime.utcnow().isoformat()
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import requests
|
import requests
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
@ -67,6 +68,47 @@ st.markdown("""
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
.color-box {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.2);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.comparison-table {
|
||||||
|
margin: 1rem 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.comparison-table th {
|
||||||
|
background-color: #667eea !important;
|
||||||
|
color: white !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
.comparison-table td {
|
||||||
|
text-align: center !important;
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
.diff-positive {
|
||||||
|
background-color: #d4edda !important;
|
||||||
|
color: #155724 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.diff-negative {
|
||||||
|
background-color: #f8d7da !important;
|
||||||
|
color: #721c24 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.diff-neutral {
|
||||||
|
background-color: #fff3cd !important;
|
||||||
|
color: #856404 !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
|
@ -151,23 +193,33 @@ def display_comparison_results(result, models_info):
|
||||||
|
|
||||||
with col1:
|
with col1:
|
||||||
model1 = result['model1_info']
|
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"""
|
st.markdown(f"""
|
||||||
<div class="comparison-card">
|
<div class="comparison-card">
|
||||||
<h4>🥇 Modèle #1</h4>
|
<h4>🥇 Modèle #1</h4>
|
||||||
<strong>{model1['encoder_name']}</strong><br>
|
<strong>{model1.get('architecture', 'N/A')} - {model1['encoder_name']}</strong><br>
|
||||||
📐 Taille: {model1['input_size']}<br>
|
📐 Taille: {model1['input_size']}<br>
|
||||||
🆔 Run: {model1['run_id'][:8]}...
|
📊 mIoU: {miou1}<br>
|
||||||
|
🎯 Accuracy: {acc1}
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
model2 = result['model2_info']
|
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"""
|
st.markdown(f"""
|
||||||
<div class="comparison-card">
|
<div class="comparison-card">
|
||||||
<h4>🥈 Modèle #2</h4>
|
<h4>🥈 Modèle #2</h4>
|
||||||
<strong>{model2['encoder_name']}</strong><br>
|
<strong>{model2.get('architecture', 'N/A')} - {model2['encoder_name']}</strong><br>
|
||||||
📐 Taille: {model2['input_size']}<br>
|
📐 Taille: {model2['input_size']}<br>
|
||||||
🆔 Run: {model2['run_id'][:8]}...
|
📊 mIoU: {miou2}<br>
|
||||||
|
🎯 Accuracy: {acc2}
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
|
@ -184,7 +236,7 @@ def display_comparison_results(result, models_info):
|
||||||
winner_class = "speed-winner" if speed_comp['faster_model'] == model1['encoder_name'] else "speed-slower"
|
winner_class = "speed-winner" if speed_comp['faster_model'] == model1['encoder_name'] else "speed-slower"
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<div class="comparison-card {winner_class}">
|
<div class="comparison-card {winner_class}">
|
||||||
<h5>🥇 {model1['encoder_name']}</h5>
|
<h5>🥇 {model1.get('architecture', 'N/A')} - {model1['encoder_name']}</h5>
|
||||||
<strong>{model1_time:.1f} ms</strong>
|
<strong>{model1_time:.1f} ms</strong>
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
@ -203,7 +255,7 @@ def display_comparison_results(result, models_info):
|
||||||
winner_class = "speed-winner" if speed_comp['faster_model'] == model2['encoder_name'] else "speed-slower"
|
winner_class = "speed-winner" if speed_comp['faster_model'] == model2['encoder_name'] else "speed-slower"
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<div class="comparison-card {winner_class}">
|
<div class="comparison-card {winner_class}">
|
||||||
<h5>🥈 {model2['encoder_name']}</h5>
|
<h5>🥈 {model2.get('architecture', 'N/A')} - {model2['encoder_name']}</h5>
|
||||||
<strong>{model2_time:.1f} ms</strong>
|
<strong>{model2_time:.1f} ms</strong>
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
@ -220,23 +272,21 @@ def display_comparison_results(result, models_info):
|
||||||
st.error(f"Erreur lors de l'affichage: {e}")
|
st.error(f"Erreur lors de l'affichage: {e}")
|
||||||
|
|
||||||
# Comparaison des statistiques
|
# Comparaison des statistiques
|
||||||
st.markdown("### 📊 Comparaison des Statistiques")
|
st.markdown("### 📊 Répartition des classes par modèle")
|
||||||
|
|
||||||
col1, col2 = st.columns(2)
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
with col1:
|
with col1:
|
||||||
st.markdown(f"**📈 {model1['encoder_name']} - Répartition des classes:**")
|
|
||||||
if result.get('model1_stats'):
|
if result.get('model1_stats'):
|
||||||
display_model_stats_chart(result['model1_stats'], models_info, f"Modèle 1 - {model1['encoder_name']}")
|
display_model_stats_chart(result['model1_stats'], models_info, f"{model1.get('architecture', 'N/A')} - {model1['encoder_name']}")
|
||||||
|
|
||||||
with col2:
|
with col2:
|
||||||
st.markdown(f"**📈 {model2['encoder_name']} - Répartition des classes:**")
|
|
||||||
if result.get('model2_stats'):
|
if result.get('model2_stats'):
|
||||||
display_model_stats_chart(result['model2_stats'], models_info, f"Modèle 2 - {model2['encoder_name']}")
|
display_model_stats_chart(result['model2_stats'], models_info, f"{model2.get('architecture', 'N/A')} - {model2['encoder_name']}")
|
||||||
|
|
||||||
# Analyse comparative des classes
|
# Analyse comparative des classes
|
||||||
if result.get('model1_stats') and result.get('model2_stats'):
|
if result.get('model1_stats') and result.get('model2_stats'):
|
||||||
display_class_comparison(result['model1_stats'], result['model2_stats'], model1, model2)
|
display_class_comparison(result['model1_stats'], result['model2_stats'], model1, model2, models_info)
|
||||||
|
|
||||||
def display_model_stats_chart(stats, models_info, title):
|
def display_model_stats_chart(stats, models_info, title):
|
||||||
"""Affiche les statistiques d'un modèle sous forme de graphique"""
|
"""Affiche les statistiques d'un modèle sous forme de graphique"""
|
||||||
|
|
@ -244,9 +294,15 @@ def display_model_stats_chart(stats, models_info, title):
|
||||||
st.warning("Aucune statistique disponible")
|
st.warning("Aucune statistique disponible")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Préparer les données pour le graphique
|
# Utiliser l'ordre des classes du modèle pour la cohérence
|
||||||
class_names = list(stats.keys())
|
if models_info and 'class_names' in models_info:
|
||||||
percentages = [data['percentage'] for data in stats.values()]
|
# 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
|
# Récupérer les couleurs des classes
|
||||||
colors = []
|
colors = []
|
||||||
|
|
@ -254,7 +310,7 @@ def display_model_stats_chart(stats, models_info, title):
|
||||||
model_class_names = models_info['class_names']
|
model_class_names = models_info['class_names']
|
||||||
model_class_colors = models_info['class_colors']
|
model_class_colors = models_info['class_colors']
|
||||||
|
|
||||||
for class_name in class_names:
|
for class_name in ordered_class_names:
|
||||||
try:
|
try:
|
||||||
idx = model_class_names.index(class_name)
|
idx = model_class_names.index(class_name)
|
||||||
color = model_class_colors[idx]
|
color = model_class_colors[idx]
|
||||||
|
|
@ -262,21 +318,21 @@ def display_model_stats_chart(stats, models_info, title):
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
colors.append([0.5, 0.5, 0.5])
|
colors.append([0.5, 0.5, 0.5])
|
||||||
else:
|
else:
|
||||||
colors = plt.cm.Set3(np.linspace(0, 1, len(class_names)))
|
colors = plt.cm.Set3(np.linspace(0, 1, len(ordered_class_names)))
|
||||||
|
|
||||||
# Créer le graphique
|
# Créer le graphique
|
||||||
fig, ax = plt.subplots(figsize=(8, 5))
|
fig, ax = plt.subplots(figsize=(8, 5))
|
||||||
|
|
||||||
bars = ax.bar(class_names, percentages, color=colors, alpha=0.8, edgecolor='white', linewidth=1)
|
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_xlabel('Classes de Segmentation', fontsize=10, fontweight='bold')
|
||||||
ax.set_ylabel('Pourcentage (%)', fontsize=10, fontweight='bold')
|
ax.set_ylabel('Pourcentage (%)', fontsize=10, fontweight='bold')
|
||||||
ax.set_title(title, fontsize=11, fontweight='bold', pad=15)
|
ax.set_title(title, fontsize=11, fontweight='bold', pad=15)
|
||||||
|
|
||||||
# Rotation des labels selon le nombre de classes
|
# Rotation des labels selon le nombre de classes
|
||||||
if len(class_names) <= 4:
|
if len(ordered_class_names) <= 4:
|
||||||
plt.xticks(rotation=0, ha='center', fontsize=9)
|
plt.xticks(rotation=0, ha='center', fontsize=9)
|
||||||
elif len(class_names) <= 6:
|
elif len(ordered_class_names) <= 6:
|
||||||
plt.xticks(rotation=30, ha='right', fontsize=8)
|
plt.xticks(rotation=30, ha='right', fontsize=8)
|
||||||
else:
|
else:
|
||||||
plt.xticks(rotation=45, ha='right', fontsize=8)
|
plt.xticks(rotation=45, ha='right', fontsize=8)
|
||||||
|
|
@ -305,15 +361,22 @@ def display_model_stats_chart(stats, models_info, title):
|
||||||
st.pyplot(fig)
|
st.pyplot(fig)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
|
|
||||||
def display_class_comparison(stats1, stats2, model1, model2):
|
def display_class_comparison(stats1, stats2, model1, model2, models_info):
|
||||||
"""Affiche une comparaison détaillée des classes entre les deux modèles"""
|
"""Affiche une comparaison détaillée des classes entre les deux modèles"""
|
||||||
st.markdown("### 🔍 Analyse Comparative par Classe")
|
st.markdown("### 🔍 Analyse Comparative par Classe")
|
||||||
|
|
||||||
# Combiner toutes les classes présentes
|
# Utiliser l'ordre des classes du modèle pour la cohérence
|
||||||
all_classes = set(stats1.keys()) | set(stats2.keys())
|
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 = []
|
comparison_data = []
|
||||||
for class_name in sorted(all_classes):
|
for class_name in ordered_all_classes:
|
||||||
pct1 = stats1.get(class_name, {}).get('percentage', 0)
|
pct1 = stats1.get(class_name, {}).get('percentage', 0)
|
||||||
pct2 = stats2.get(class_name, {}).get('percentage', 0)
|
pct2 = stats2.get(class_name, {}).get('percentage', 0)
|
||||||
diff = pct1 - pct2
|
diff = pct1 - pct2
|
||||||
|
|
@ -325,27 +388,122 @@ def display_class_comparison(stats1, stats2, model1, model2):
|
||||||
'difference': diff
|
'difference': diff
|
||||||
})
|
})
|
||||||
|
|
||||||
# Afficher sous forme de tableau interactif
|
# Créer un graphique comparatif des barres
|
||||||
col1, col2 = st.columns([2, 1])
|
st.markdown("**📊 Comparaison des classes entre les modèles:**")
|
||||||
|
|
||||||
with col1:
|
# Préparation des données
|
||||||
st.markdown("**📋 Tableau Comparatif:**")
|
class_names = [data['class'] for data in comparison_data]
|
||||||
for data in comparison_data:
|
model1_values = [data['model1_pct'] for data in comparison_data]
|
||||||
diff_color = "🟢" if abs(data['difference']) < 1 else ("🔴" if abs(data['difference']) > 5 else "🟡")
|
model2_values = [data['model2_pct'] for data in comparison_data]
|
||||||
diff_text = f"{data['difference']:+.1f}%"
|
|
||||||
|
|
||||||
st.markdown(f"""
|
|
||||||
**{data['class']}** {diff_color}
|
|
||||||
- {model1['encoder_name']}: {data['model1_pct']:.1f}%
|
|
||||||
- {model2['encoder_name']}: {data['model2_pct']:.1f}%
|
|
||||||
- Différence: {diff_text}
|
|
||||||
""")
|
|
||||||
|
|
||||||
with col2:
|
# Récupérer les couleurs des classes
|
||||||
st.markdown("**🎯 Légende:**")
|
colors = []
|
||||||
st.markdown("🟢 Différence < 1%")
|
if models_info and 'class_colors' in models_info and 'class_names' in models_info:
|
||||||
st.markdown("🟡 Différence 1-5%")
|
model_class_names = models_info['class_names']
|
||||||
st.markdown("🔴 Différence > 5%")
|
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")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Sidebar avec informations des modèles
|
# Sidebar avec informations des modèles
|
||||||
with st.sidebar:
|
with st.sidebar:
|
||||||
|
|
@ -358,37 +516,54 @@ with st.sidebar:
|
||||||
# Modèle 1
|
# Modèle 1
|
||||||
best_model = models_info.get('best_model', {})
|
best_model = models_info.get('best_model', {})
|
||||||
st.markdown("**🥇 Meilleur Modèle:**")
|
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"**Encoder:** {best_model.get('encoder_name', 'N/A')}")
|
||||||
st.write(f"**Run ID:** {best_model.get('run_id', 'N/A')[:8]}...")
|
st.write(f"**Résolution:** {best_model.get('input_size', 'N/A')}")
|
||||||
|
|
||||||
# Modèle 2
|
# Modèle 2
|
||||||
second_model = models_info.get('second_best_model', {})
|
second_model = models_info.get('second_best_model', {})
|
||||||
st.markdown("**🥈 Deuxième Modèle:**")
|
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"**Encoder:** {second_model.get('encoder_name', 'N/A')}")
|
||||||
st.write(f"**Run ID:** {second_model.get('run_id', 'N/A')[:8]}...")
|
st.write(f"**Résolution:** {second_model.get('input_size', 'N/A')}")
|
||||||
|
|
||||||
st.write(f"**Classes:** {models_info.get('num_classes', 'N/A')}")
|
|
||||||
else:
|
else:
|
||||||
st.error("❌ Modèles non disponibles")
|
st.error("❌ Modèles non disponibles")
|
||||||
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
st.markdown("## 🎯 Classes Segmentées")
|
st.markdown("## 🎯 Classes Segmentées (IoU)")
|
||||||
|
|
||||||
# Affichage avec carrés colorés
|
# Affichage avec carrés colorés et IoU
|
||||||
if models_info and 'class_names' in models_info and 'class_colors' in models_info:
|
if models_info and 'class_names' in models_info and 'class_colors' in models_info:
|
||||||
class_names = models_info['class_names']
|
class_names = models_info['class_names']
|
||||||
class_colors = models_info['class_colors']
|
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)):
|
for i, (class_name, color) in enumerate(zip(class_names, class_colors)):
|
||||||
color_hex = f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
|
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"""
|
st.markdown(f"""
|
||||||
<div style="
|
<div style="
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin: 0.3rem 0;
|
margin: 0.3rem 0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.85rem;
|
||||||
">
|
">
|
||||||
<div style="
|
<div style="
|
||||||
width: 14px;
|
width: 14px;
|
||||||
|
|
@ -398,7 +573,7 @@ with st.sidebar:
|
||||||
border: 1px solid rgba(0,0,0,0.2);
|
border: 1px solid rgba(0,0,0,0.2);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
"></div>
|
"></div>
|
||||||
<span style="font-weight: 500;">{i}. {class_name}</span>
|
<span style="font-weight: 500;">{i}. {class_name}{iou_str}</span>
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
||||||
|
|
|
||||||
196
utils/utils.py
196
utils/utils.py
|
|
@ -102,38 +102,6 @@ def colorize_mask(mask, group_colors):
|
||||||
|
|
||||||
return colored_mask
|
return colored_mask
|
||||||
|
|
||||||
# def get_preprocessing_fn(encoder_name=None):
|
|
||||||
# """
|
|
||||||
# Retourne la fonction de prétraitement adaptée au backbone utilisé.
|
|
||||||
# ADAPTÉ PYTORCH : utilise smp.encoders.get_preprocessing_params
|
|
||||||
# """
|
|
||||||
# if encoder_name is None:
|
|
||||||
# print("ℹ️ Modèle vanilla - normalisation simple")
|
|
||||||
# return lambda x: x / 255.0
|
|
||||||
# else:
|
|
||||||
# print(f"ℹ️ Préprocessing du backbone {encoder_name}")
|
|
||||||
# try:
|
|
||||||
# # PyTorch segmentation_models utilise get_preprocessing_params
|
|
||||||
# preprocess_params = smp.encoders.get_preprocessing_params(encoder_name)
|
|
||||||
# mean = preprocess_params['mean']
|
|
||||||
# std = preprocess_params['std']
|
|
||||||
|
|
||||||
# def preprocess_fn(x):
|
|
||||||
# # x doit être dans [0, 1] puis normalisé
|
|
||||||
# x = x / 255.0 if x.max() > 1.0 else x
|
|
||||||
# # Normalisation ImageNet ou spécifique au backbone
|
|
||||||
# if isinstance(x, torch.Tensor):
|
|
||||||
# for i in range(3): # RGB
|
|
||||||
# x[:, i, :, :] = (x[:, i, :, :] - mean[i]) / std[i]
|
|
||||||
# else: # numpy
|
|
||||||
# for i in range(3):
|
|
||||||
# x[:, :, i] = (x[:, :, i] - mean[i]) / std[i]
|
|
||||||
# return x
|
|
||||||
|
|
||||||
# return preprocess_fn
|
|
||||||
# except Exception as e:
|
|
||||||
# print(f"⚠️ Erreur preprocessing {encoder_name}: {e}, fallback simple")
|
|
||||||
# return lambda x: x / 255.0
|
|
||||||
|
|
||||||
def get_preprocessing_fn(img_size=(512, 512), encoder=None):
|
def get_preprocessing_fn(img_size=(512, 512), encoder=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -200,170 +168,6 @@ def preprocess_image_and_mask(image_path, mask_path=None, img_size=(512, 512),
|
||||||
|
|
||||||
return img_preprocessed, img_display, mask_preprocessed
|
return img_preprocessed, img_display, mask_preprocessed
|
||||||
|
|
||||||
|
|
||||||
# def preprocess_image_and_mask(image_path, mask_path=None, img_size=(512, 512), # ← CHANGÉ: 224→512
|
|
||||||
# encoder_name=None, id_to_group_mapping=None):
|
|
||||||
# """
|
|
||||||
# Préprocesse une image (et optionnellement un masque) avec le même pipeline que l'entraînement
|
|
||||||
# ADAPTÉ PYTORCH : Utilise PIL + torch au lieu de tf
|
|
||||||
# """
|
|
||||||
|
|
||||||
# print(f"🔄 Préprocessing de l'image: {os.path.basename(image_path)}")
|
|
||||||
|
|
||||||
# # Chargement et preprocessing de l'image
|
|
||||||
# img_pil = Image.open(image_path).convert('RGB')
|
|
||||||
# img_original = img_pil.resize(img_size, Image.BILINEAR)
|
|
||||||
|
|
||||||
# # Conversion en tensor et normalisation
|
|
||||||
# img_tensor = transforms.ToTensor()(img_original) # [3, H, W] dans [0, 1]
|
|
||||||
|
|
||||||
# # Appliquer le preprocessing du backbone
|
|
||||||
# preprocessing_fn = get_preprocessing_fn(encoder_name)
|
|
||||||
# if encoder_name is not None:
|
|
||||||
# # Pour les backbones pré-entraînés, on applique leur normalisation
|
|
||||||
# img_preprocessed = preprocessing_fn(img_tensor.permute(1, 2, 0).numpy())
|
|
||||||
# img_preprocessed = torch.from_numpy(img_preprocessed).permute(2, 0, 1).float()
|
|
||||||
# else:
|
|
||||||
# # Simple normalisation [0, 1]
|
|
||||||
# img_preprocessed = img_tensor.float()
|
|
||||||
|
|
||||||
# # Preprocessing du masque si fourni
|
|
||||||
# mask_preprocessed = 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') # Grayscale
|
|
||||||
# mask_resized = mask_pil.resize(img_size, Image.NEAREST)
|
|
||||||
# mask_tensor = torch.from_numpy(np.array(mask_resized)).long()
|
|
||||||
|
|
||||||
# print(f" Valeurs uniques avant mapping: {torch.unique(mask_tensor)[:10]}...")
|
|
||||||
|
|
||||||
# # Appliquer le mapping si fourni
|
|
||||||
# if id_to_group_mapping is not None:
|
|
||||||
# mask_tensor = map_mask_ids(mask_tensor, id_to_group_mapping)
|
|
||||||
# print(f" Valeurs uniques après mapping: {torch.unique(mask_tensor)}")
|
|
||||||
|
|
||||||
# mask_preprocessed = mask_tensor
|
|
||||||
|
|
||||||
# # img_original en numpy pour affichage
|
|
||||||
# img_display = np.array(img_original)
|
|
||||||
|
|
||||||
# return img_preprocessed, img_display, mask_preprocessed
|
|
||||||
|
|
||||||
# def run_inference_and_visualize(image_path, model, encoder_name, img_size, mapping_config,
|
|
||||||
# mask_path=None, save_dir=None, show_stats=True):
|
|
||||||
# """
|
|
||||||
# ADAPTÉ PYTORCH : Inférence avec modèle PyTorch
|
|
||||||
# Returns: (predicted_mask, inference_time_seconds)
|
|
||||||
# """
|
|
||||||
|
|
||||||
# print(f"=== INFÉRENCE PYTORCH SUR {os.path.basename(image_path)} ===")
|
|
||||||
|
|
||||||
# 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)")
|
|
||||||
|
|
||||||
# # 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}")
|
|
||||||
|
|
||||||
# plt.show()
|
|
||||||
|
|
||||||
# # 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}%)")
|
|
||||||
|
|
||||||
# return predicted_mask, inference_time
|
|
||||||
|
|
||||||
|
|
||||||
# Version mise à jour de run_inference_and_visualize
|
# Version mise à jour de run_inference_and_visualize
|
||||||
def run_inference_and_visualize(image_path, model, encoder_name, img_size, mapping_config,
|
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):
|
mask_path=None, save_dir=None, show_stats=True, show_plot=True):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue