Source code for optimization.visualization

"""
Optuna optimization result visualization and plotting.
"""

# src/optimization/visualization.py

# import os
import numpy as np
import matplotlib.pyplot as plt
from itertools import combinations
from pathlib import Path

from optuna import Study
import optuna.visualization as vis
from plotly.io import show
import plotly.graph_objects as go

from common.logger import logger


[docs] def visualize_study(study: Study, best_trial, output_dir: Path, run_detection: bool = False): """ Generate and save visualizations of an Optuna study. Depending on the optimization mode, this function produces: - **NR-IQA mode**: * Pareto front (Entropy, BRISQUE, PIQE) * Slice plots for each parameter vs. metric * Contour plots (interpolated surfaces) for parameter pairs - **Detection mode**: * Optimization history (F1-score) * Contour plots for parameter pairs vs. F1-score Parameters ---------- study : optuna.study.Study The Optuna study object containing all trials. best_trial : optuna.trial.FrozenTrial The best trial selected from the study. output_dir : Path Directory where contour plots will be saved as PNG files. run_detection : bool, default=False If True, generate visualizations for detection (F1-score). If False, generate visualizations for NR-IQA metrics. Notes ----- - Uses both Plotly (interactive) and Matplotlib (static) visualizations. - Contour plots are saved to disk in ``output_dir``. - Interactive plots may fail to display in some environments; warnings are logged. - Metrics visualized in NR-IQA mode: Entropy, BRISQUE, NIQE, PIQE. - In detection mode, only F1-score is visualized. """ best_params = best_trial.params best_values = best_trial.values params = ["facteur_swir", "beta", "level", "gamma_value"] metrics = ["Entropy", "BRISQUE", "NIQE", "PIQE"] try: if not run_detection: # ➤ Pareto front pareto_fig = vis.plot_pareto_front( study, targets=lambda t: (t.values[0], t.values[1], t.values[3]), target_names=["Entropy", "BRISQUE", "PIQE"] ) try: show(pareto_fig) except Exception as e: logger.warning(f"🔕 Affichage Pareto désactivé : {e}") trace_best = go.Scatter3d( x=[best_values[0]], y=[best_values[1]], z=[best_values[3]], mode='markers+text', marker=dict(size=6, color='red'), text=["Best (Tchebycheff)"], name="Tchebycheff" ) pareto_fig.add_trace(trace_best) pareto_fig.show() # ➤ Courbes slice for param in params: for i, metric in enumerate(metrics): fig = vis.plot_slice( study, params=[param], target=lambda t: t.values[i], target_name=metric ) fig.show() # ➤ Courbes interpolées (contourf) df = study.trials_dataframe(attrs=("params", "values")) for (p1, p2) in combinations(params, 2): for idx, metric in enumerate(metrics): df_sub = df[[f"params_{p1}", f"params_{p2}", f"values_{idx}"]].dropna() df_sub = df_sub.rename(columns={ f"params_{p1}": p1, f"params_{p2}": p2, f"values_{idx}": metric }) x, y, z = df_sub[p1].values, df_sub[p2].values, df_sub[metric].values xi = np.linspace(x.min(), x.max(), 100) yi = np.linspace(y.min(), y.max(), 100) xi, yi = np.meshgrid(xi, yi) from scipy.interpolate import griddata zi = griddata((x, y), z, (xi, yi), method="linear") if metric == "Entropy": zi = np.clip(zi, 0, 8) cmap = "viridis" else: zi = np.clip(zi, 0, 100) cmap = "viridis_r" plt.figure(figsize=(6, 4)) contourf = plt.contourf(xi, yi, zi, levels=20, cmap=cmap) contours = plt.contour(xi, yi, zi, levels=10, colors='black', linewidths=0.8) plt.clabel(contours, inline=True, fontsize=8) plt.colorbar(contourf, label=metric) plt.title(f"{metric}{p1} vs {p2}") plt.xlabel(p1) plt.ylabel(p2) plt.tight_layout() filepath = output_dir / f"contour_{metric}_{p1}_vs_{p2}.png" plt.savefig(filepath) plt.close() else: # ➤ Historique optimisation F1 fig = vis.plot_optimization_history(study) fig.show() df = study.trials_dataframe(attrs=("params", "values")) for (p1, p2) in combinations(params, 2): df_sub = df[[f"params_{p1}", f"params_{p2}", "values_0"]].dropna() df_sub = df_sub.rename(columns={ f"params_{p1}": p1, f"params_{p2}": p2, "values_0": "f1_score" }) x, y, z = df_sub[p1].values, df_sub[p2].values, df_sub["f1_score"].values xi = np.linspace(x.min(), x.max(), 100) yi = np.linspace(y.min(), y.max(), 100) xi, yi = np.meshgrid(xi, yi) from scipy.interpolate import griddata zi = griddata((x, y), z, (xi, yi), method="linear") zi = np.clip(zi, 0, 1) plt.figure(figsize=(6, 4)) contourf = plt.contourf(xi, yi, zi, levels=20, cmap="magma", vmin=0, vmax=1) contours = plt.contour(xi, yi, zi, levels=10, colors='black', linewidths=0.8) plt.clabel(contours, inline=True, fontsize=8) plt.colorbar(contourf, label="f1_score") plt.title(f"f1_score — {p1} vs {p2}") plt.xlabel(p1) plt.ylabel(p2) plt.tight_layout() filepath = output_dir / f"contour_f1score_{p1}_vs_{p2}.png" plt.savefig(filepath) plt.close() except Exception as e: logger.warning(f"🔕 Visualisation désactivée ou échouée : {e}")