From 9186bfd1c5a0a76d4f751356528971ee7bcce92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Mureau?= Date: Thu, 4 Sep 2025 10:15:09 +0200 Subject: [PATCH] website color mapping update --- app/main.py | 37 ++++++++++++++---------- app/streamlit_app.py | 51 ++++++++++++++++++++++----------- utils/utils.py | 68 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 113 insertions(+), 43 deletions(-) diff --git a/app/main.py b/app/main.py index 73fa705..ad66b93 100644 --- a/app/main.py +++ b/app/main.py @@ -171,9 +171,10 @@ def initialize_models(): 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 de la configuration Cityscapes avec couleurs WCAG...") + mapping_config = load_cityscapes_config(CITYSCAPES_CONFIG_PATH, verbose=False, use_wcag_colors=True) + print("✅ Configuration Cityscapes chargée avec couleurs WCAG") + print("🔄 Chargement du meilleur modèle...") best_model, best_run_id, best_encoder_name, best_img_size = load_model( @@ -296,28 +297,34 @@ def get_models_info(): if best_model is None or second_best_model is None: raise HTTPException(status_code=503, detail="Models not loaded") - # Couleurs accessibles pour les classes - accessible_class_colors = [ - [0, 102, 204], # primary_blue - [204, 0, 0], # primary_red - [0, 102, 0], # primary_green - [204, 102, 0], # primary_orange - [102, 51, 153], # secondary_purple - [0, 102, 102], # secondary_teal - [51, 51, 51], # neutral_dark - [102, 102, 102] # neutral_medium + # NOUVELLE RÉPARTITION DES COULEURS selon votre mapping final + wcag_accessible_colors = [ + [102, 102, 102], # Flat: neutral_medium (gris moyen pour routes/trottoirs) + [204, 0, 0], # Human: primary_red (rouge pour piétons/cyclistes) + [102, 51, 153], # Vehicle: secondary_purple (violet pour véhicules) + [204, 102, 0], # Construction: primary_orange (orange pour bâtiments) + [0, 102, 102], # Object: secondary_teal (teal pour objets/signalétique) + [0, 102, 0], # Nature: primary_green (vert pour végétation) + [0, 102, 204], # Sky: primary_blue (bleu pour ciel) + [51, 51, 51] # Void: neutral_dark (gris foncé pour zones non-définies) ] + # Mettre à jour la configuration avec les nouvelles couleurs + if mapping_config: + mapping_config['group_colors'] = np.array(wcag_accessible_colors, dtype=np.uint8) + return { "best_model": best_model_info, "second_best_model": second_best_model_info, "num_classes": mapping_config['num_classes'], "class_names": mapping_config['group_names'], - "class_colors": accessible_class_colors, + "class_colors": wcag_accessible_colors, "accessibility_info": get_accessibility_info(), - "preprocessing_resolution": best_model_info.get("input_size", (512, 512)) # Ajout pour clarté + "preprocessing_resolution": best_model_info.get("input_size", (512, 512)) } + + @app.get("/dataset-info") def get_dataset_info(): """Retourne les informations sur le dataset""" diff --git a/app/streamlit_app.py b/app/streamlit_app.py index f398813..d0142a5 100644 --- a/app/streamlit_app.py +++ b/app/streamlit_app.py @@ -30,18 +30,37 @@ st.set_page_config( # Palettes de couleurs conformes WCAG AA (ratio contraste ≥ 3:1) WCAG_ACCESSIBLE_COLORS = { - 'primary_blue': '#0066CC', # Contraste 4.5:1 avec blanc - 'primary_red': '#CC0000', # Contraste 5.7:1 avec blanc - 'primary_green': '#006600', # Contraste 4.8:1 avec blanc - 'primary_orange': '#CC6600', # Contraste 4.2:1 avec blanc - 'secondary_purple': '#663399', # Contraste 4.1:1 avec blanc - 'secondary_teal': '#006666', # Contraste 4.4:1 avec blanc - 'neutral_dark': '#333333', # Contraste 12.6:1 avec blanc - 'neutral_medium': '#666666', # Contraste 5.7:1 avec blanc + 'primary_blue': '#0066CC', # [0, 102, 204] + 'primary_red': '#CC0000', # [204, 0, 0] + 'primary_green': '#006600', # [0, 102, 0] + 'primary_orange': '#CC6600', # [204, 102, 0] + 'secondary_purple': '#663399', # [102, 51, 153] + 'secondary_teal': '#006666', # [0, 102, 102] + 'neutral_dark': '#333333', # [51, 51, 51] + 'neutral_medium': '#666666', # [102, 102, 102] 'neutral_light': '#CCCCCC', # Pour éléments désactivés 'background_alt': '#F5F5F5' # Fond alternatif accessible } +# Convertir hex vers RGB +def hex_to_rgb(hex_color): + """Convertit une couleur hex en tuple RGB""" + hex_color = hex_color.lstrip('#') + return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) + +# Couleurs RGB pour les masques (dans le même ordre que les classes) +WCAG_RGB_COLORS = [ + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['neutral_medium']), # Flat: gris moyen [102, 102, 102] + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_red']), # Human: rouge [204, 0, 0] + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['secondary_purple']), # Vehicle: violet [102, 51, 153] + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_orange']), # Construction: orange [204, 102, 0] + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['secondary_teal']), # Object: teal [0, 102, 102] + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_green']), # Nature: vert [0, 102, 0] + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['primary_blue']), # Sky: bleu [0, 102, 204] + hex_to_rgb(WCAG_ACCESSIBLE_COLORS['neutral_dark']) # Void: gris foncé [51, 51, 51] +] + + # Motifs pour différencier sans couleur uniquement ACCESSIBILITY_PATTERNS = { 'solid': None, @@ -598,14 +617,14 @@ def display_model_stats_chart(stats, models_info, title): # Couleurs accessibles WCAG accessible_colors = [ - WCAG_ACCESSIBLE_COLORS['primary_blue'], - WCAG_ACCESSIBLE_COLORS['primary_red'], - WCAG_ACCESSIBLE_COLORS['primary_green'], - WCAG_ACCESSIBLE_COLORS['primary_orange'], - WCAG_ACCESSIBLE_COLORS['secondary_purple'], - WCAG_ACCESSIBLE_COLORS['secondary_teal'], - WCAG_ACCESSIBLE_COLORS['neutral_dark'], - WCAG_ACCESSIBLE_COLORS['neutral_medium'] + WCAG_ACCESSIBLE_COLORS['neutral_medium'], # Flat: gris moyen + WCAG_ACCESSIBLE_COLORS['primary_red'], # Human: rouge + WCAG_ACCESSIBLE_COLORS['secondary_purple'], # Vehicle: violet + WCAG_ACCESSIBLE_COLORS['primary_orange'], # Construction: orange + WCAG_ACCESSIBLE_COLORS['secondary_teal'], # Object: teal + WCAG_ACCESSIBLE_COLORS['primary_green'], # Nature: vert + WCAG_ACCESSIBLE_COLORS['primary_blue'], # Sky: bleu + WCAG_ACCESSIBLE_COLORS['neutral_dark'] # Void: gris foncé ] colors = accessible_colors[:len(ordered_class_names)] diff --git a/utils/utils.py b/utils/utils.py index 641a4b0..cd47f12 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -20,10 +20,47 @@ import albumentations as A from albumentations.pytorch import ToTensorV2 -def load_cityscapes_config(config_path="../cityscapes_config.json", verbose=True): +def update_colors_to_wcag(mapping_config): + """ + Met à jour les couleurs dans mapping_config pour utiliser les couleurs WCAG accessibles + avec redistribution sémantique intuitive finale + """ + # Redistribution sémantique finale : + # Flat → gris moyen, Human → rouge, Vehicle → violet, Construction → orange, + # Object → teal, Nature → vert, Sky → bleu, Void → gris foncé + wcag_colors = np.array([ + [102, 102, 102], # Flat: gris moyen (routes, trottoirs) + [204, 0, 0], # Human: rouge (piétons, cyclistes) + [102, 51, 153], # Vehicle: violet (véhicules) + [204, 102, 0], # Construction: orange (bâtiments) + [0, 102, 102], # Object: teal (objets, signalétique) + [0, 102, 0], # Nature: vert (végétation) + [0, 102, 204], # Sky: bleu (ciel) + [51, 51, 51] # Void: gris foncé (zones non-définies) + ], dtype=np.uint8) + + # S'assurer qu'on a assez de couleurs pour le nombre de classes + num_classes = mapping_config['num_classes'] + if len(wcag_colors) < num_classes: + print(f"⚠️ Attention: {num_classes} classes mais seulement {len(wcag_colors)} couleurs WCAG disponibles") + # Compléter avec les couleurs originales si nécessaire + original_colors = mapping_config['group_colors'] + while len(wcag_colors) < num_classes: + wcag_colors = np.vstack([wcag_colors, original_colors[len(wcag_colors):len(wcag_colors)+1]]) + + mapping_config['group_colors'] = wcag_colors[:num_classes] + print(f"✅ Couleurs mises à jour vers WCAG pour {num_classes} classes") + return mapping_config + + +def load_cityscapes_config(config_path="../cityscapes_config.json", verbose=True, use_wcag_colors=False): """ Charge la configuration Cityscapes depuis un fichier JSON. - INCHANGÉ - Compatible PyTorch + + Args: + config_path: Chemin vers le fichier de configuration + verbose: Affichage détaillé + use_wcag_colors: Utiliser les couleurs WCAG accessibles """ with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) @@ -40,6 +77,20 @@ def load_cityscapes_config(config_path="../cityscapes_config.json", verbose=True num_classes = len(group_names) + mapping_config = { + "class_groups": class_groups, + "cityscapes_mapping": cityscapes_mapping, + "id_to_label": id_to_label, + "group_colors": group_colors, + "group_names": group_names, + "id_to_group": id_to_group, + "num_classes": num_classes + } + + # Appliquer les couleurs WCAG si demandé + if use_wcag_colors: + mapping_config = update_colors_to_wcag(mapping_config) + if verbose: print("== Groupes de classes Cityscapes ==") for group, classes in class_groups.items(): @@ -52,20 +103,13 @@ def load_cityscapes_config(config_path="../cityscapes_config.json", verbose=True print(f" ID {id_:2d} ('{label}') → groupe '{group_names[group_id]}' ({group_id})") print("\n== Liste des groupes et couleurs associées ==") - for i, (name, color) in enumerate(zip(group_names, group_colors)): + for i, (name, color) in enumerate(zip(group_names, mapping_config['group_colors'])): print(f" Groupe {i}: {name} - Couleur RGB : {color}") print(f"\nNombre total de groupes/classes : {num_classes}") - return { - "class_groups": class_groups, - "cityscapes_mapping": cityscapes_mapping, - "id_to_label": id_to_label, - "group_colors": group_colors, - "group_names": group_names, - "id_to_group": id_to_group, - "num_classes": num_classes - } + return mapping_config + def map_mask_ids(mask, id_to_group_mapping): """