seeclick / trigger-off /src /evaluation /visualize.py
张志方
Mar 9, 2026, 6:05 PM
830703b
Raw
History Blame Contribute Delete
5.85 kB
"""
Visualisation utilities for Trigger-Off experiment results.
Requires: matplotlib
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _ensure_matplotlib() -> None:
try:
import matplotlib # noqa: F401
except ImportError as exc:
raise ImportError(
"matplotlib is required for visualisation. "
"Install with: pip install matplotlib"
) from exc
# ---------------------------------------------------------------------------
# Main results bar chart
# ---------------------------------------------------------------------------
def plot_main_results(
results_dict: Dict[str, Dict[str, float]],
metric: str = "step_acc",
title: str = "Trigger-Off: Main Results",
save_path: Optional[str] = None,
) -> None:
"""
Plot a bar chart comparing Step-Acc (or another metric) across baselines.
Args:
results_dict: {baseline_name: {metric_name: value, ...}}
e.g. {"SeeClick-Clean": {"step_acc": 0.72, ...},
"SeeClick+TriggerOff": {"step_acc": 0.68, ...}}
metric: Which metric to plot (default: "step_acc").
title: Chart title.
save_path: If provided, save the figure to this path.
"""
_ensure_matplotlib()
import matplotlib.pyplot as plt
import numpy as np
labels = list(results_dict.keys())
values = [results_dict[k].get(metric, 0.0) for k in labels]
x = np.arange(len(labels))
width = 0.55
fig, ax = plt.subplots(figsize=(max(6, len(labels) * 1.4), 5))
bars = ax.bar(x, values, width, color="steelblue", edgecolor="white", alpha=0.85)
ax.set_ylabel(metric.replace("_", " ").title())
ax.set_title(title)
ax.set_xticks(x)
ax.set_xticklabels(labels, rotation=20, ha="right", fontsize=9)
ax.set_ylim(0, min(1.05, max(values) * 1.2 + 0.05))
for bar, val in zip(bars, values):
ax.text(
bar.get_x() + bar.get_width() / 2.0,
bar.get_height() + 0.01,
f"{val:.3f}",
ha="center",
va="bottom",
fontsize=8,
)
fig.tight_layout()
if save_path:
fig.savefig(save_path, dpi=150, bbox_inches="tight")
else:
plt.show()
plt.close(fig)
# ---------------------------------------------------------------------------
# Ablation line chart
# ---------------------------------------------------------------------------
def plot_ablation(
ablation_results: Dict[str, List[float]],
variable_name: str,
x_values: List[Any],
metric: str = "step_acc",
title: Optional[str] = None,
save_path: Optional[str] = None,
) -> None:
"""
Plot a line chart for ablation experiments.
Args:
ablation_results: {series_name: [value_at_x1, value_at_x2, ...]}
e.g. {"clean_acc": [0.72, 0.71, 0.70],
"attack_acc": [0.40, 0.32, 0.28],
"immunity_acc": [0.70, 0.69, 0.68]}
variable_name: X-axis label, e.g. "poison_ratio".
x_values: Values on the X-axis, e.g. [0.10, 0.15, 0.20, 0.25].
metric: Metric label for Y axis (default: "step_acc").
title: Chart title.
save_path: If provided, save the figure to this path.
"""
_ensure_matplotlib()
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(7, 4))
markers = ["o", "s", "^", "D", "v", "P"]
for i, (series_name, y_values) in enumerate(ablation_results.items()):
marker = markers[i % len(markers)]
ax.plot(x_values, y_values, marker=marker, label=series_name, linewidth=2)
ax.set_xlabel(variable_name.replace("_", " ").title())
ax.set_ylabel(metric.replace("_", " ").title())
ax.set_title(title or f"Ablation: {variable_name}")
ax.legend(loc="best", fontsize=9)
ax.set_ylim(0, 1.05)
ax.grid(True, linestyle="--", alpha=0.5)
fig.tight_layout()
if save_path:
fig.savefig(save_path, dpi=150, bbox_inches="tight")
else:
plt.show()
plt.close(fig)
# ---------------------------------------------------------------------------
# Results table
# ---------------------------------------------------------------------------
def print_results_table(results: Dict[str, Any]) -> None:
"""
Print a nicely formatted results table to stdout.
Args:
results: Dict of {scenario_name: EvalResult_or_dict}.
"""
# Normalise to dicts
rows: Dict[str, Dict[str, Any]] = {}
for name, r in results.items():
if hasattr(r, "to_dict"):
rows[name] = r.to_dict()
elif isinstance(r, dict):
rows[name] = r
else:
rows[name] = {"value": r}
if not rows:
print("(No results to display.)")
return
# Determine columns
columns = ["step_acc", "task_sr", "asr", "immunity_rate"]
col_labels = {
"step_acc": "Step-Acc",
"task_sr": "Task-SR",
"asr": "ASR",
"immunity_rate": "Imm-Rate",
}
# Column widths
name_width = max(len(n) for n in rows) + 2
col_width = 10
header = f"{'Scenario':<{name_width}}" + "".join(
f"{col_labels.get(c, c):>{col_width}}" for c in columns
)
separator = "-" * len(header)
print("\n" + separator)
print(header)
print(separator)
for name, row in rows.items():
line = f"{name:<{name_width}}"
for col in columns:
val = row.get(col, None)
if val is None:
cell = "N/A"
else:
cell = f"{val:.4f}"
line += f"{cell:>{col_width}}"
print(line)
print(separator + "\n")