AVGen-Bench-Leaderboard / leaderboard.py
Yif29's picture
Update leaderboard sorting and readability
dfd445e verified
Raw
History Blame Contribute Delete
12.1 kB
from __future__ import annotations
import html
import math
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
import pandas as pd
DATA_PATH = Path(__file__).parent / "data" / "leaderboard.csv"
METRIC_COLUMNS = [
"Vis",
"Aud (PQ)",
"AV",
"Lip",
"Text",
"Face",
"Music",
"Speech",
"Lo-Phy",
"Hi-Phy",
"Holistic",
"Total",
]
SORT_COLUMNS = ["Rank", "Model", "Components", "Component Type", *METRIC_COLUMNS]
SORT_CHOICES = [
("Rank", "Rank"),
("Model", "Model"),
("Components", "Components"),
("Type", "Component Type"),
*[(metric, metric) for metric in METRIC_COLUMNS],
]
LOWER_IS_BETTER = {"AV", "Lip"}
NUMERIC_COLUMNS = METRIC_COLUMNS
FORMATTERS = {
"Vis": "{:.3f}",
"Aud (PQ)": "{:.2f}",
"AV": "{:.2f}",
"Lip": "{:.2f}",
"Text": "{:.2f}",
"Face": "{:.2f}",
"Music": "{:.2f}",
"Speech": "{:.2f}",
"Lo-Phy": "{:.2f}",
"Hi-Phy": "{:.2f}",
"Holistic": "{:.2f}",
"Total": "{:.2f}",
}
GROUP_WEIGHTS = {
"Basic Uni-modal": 0.2,
"Basic Cross-modal": 0.2,
"Fine-grained": 0.6,
}
GROUP_DIMENSIONS = {
"Basic Uni-modal": ["Vis", "Aud (PQ)"],
"Basic Cross-modal": ["AV", "Lip"],
"Fine-grained": ["Text", "Face", "Music", "Speech", "Lo-Phy", "Hi-Phy", "Holistic"],
}
@dataclass(frozen=True)
class Standing:
best: float | None
second: float | None
def load_leaderboard(path: Path = DATA_PATH) -> pd.DataFrame:
df = pd.read_csv(path)
for column in NUMERIC_COLUMNS:
df[column] = pd.to_numeric(df[column], errors="coerce")
df = df.sort_values("Total", ascending=False, na_position="last").reset_index(drop=True)
df.insert(0, "Rank", range(1, len(df) + 1))
return df
def filter_leaderboard(
df: pd.DataFrame,
component_type: str = "All",
query: str = "",
sort_by: str = "Total",
sort_order: str = "Descending",
) -> pd.DataFrame:
view = df.copy()
if component_type != "All":
view = view[view["Component Type"] == component_type]
query = query.strip().lower()
if query:
mask = (
view["Model"].str.lower().str.contains(query, regex=False)
| view["Components"].str.lower().str.contains(query, regex=False)
)
view = view[mask]
sort_by = _normalize_sort_column(sort_by)
ascending = _is_ascending_sort(sort_by, sort_order)
if sort_by in view.columns:
sort_kwargs = {
"ascending": ascending,
"na_position": "last",
"kind": "mergesort",
}
if sort_by in {"Model", "Components", "Component Type"}:
sort_kwargs["key"] = lambda column: column.astype(str).str.casefold()
view = view.sort_values(sort_by, **sort_kwargs)
return view.reset_index(drop=True)
def metric_standings(df: pd.DataFrame) -> dict[str, Standing]:
standings: dict[str, Standing] = {}
for metric in METRIC_COLUMNS:
values = sorted(
{float(v) for v in df[metric].dropna()},
reverse=metric not in LOWER_IS_BETTER,
)
standings[metric] = Standing(
best=values[0] if values else None,
second=values[1] if len(values) > 1 else None,
)
return standings
def normalized_score(metric: str, value: float) -> float:
if metric == "Vis":
return _clamp(value * 100.0, 0.0, 100.0)
if metric == "Aud (PQ)":
return _clamp(value * 10.0, 0.0, 100.0)
if metric == "AV":
return _clamp(100.0 * (1.0 - value / 0.5), 0.0, 100.0)
if metric == "Lip":
return _clamp(100.0 * (1.0 - value / 8.0), 0.0, 100.0)
if metric == "Lo-Phy":
return _clamp(value * 20.0, 0.0, 100.0)
return _clamp(value, 0.0, 100.0)
def compute_total_from_metrics(row: pd.Series) -> float:
group_scores: list[float] = []
group_weights: list[float] = []
for group_name, metrics in GROUP_DIMENSIONS.items():
values = []
for metric in metrics:
value = row.get(metric)
if pd.isna(value):
continue
values.append(normalized_score(metric, float(value)))
if values:
group_scores.append(sum(values) / len(values))
group_weights.append(GROUP_WEIGHTS[group_name])
if not group_scores:
return float("nan")
weighted = sum(score * weight for score, weight in zip(group_scores, group_weights))
return weighted / sum(group_weights)
def render_summary(df: pd.DataFrame, view: pd.DataFrame) -> str:
top = df.sort_values("Total", ascending=False).iloc[0]
open_source = df[df["Component Type"] == "Open-source"].sort_values("Total", ascending=False)
best_open = open_source.iloc[0] if len(open_source) else None
best_av = df.sort_values("AV", ascending=True).iloc[0]
best_speech = df.sort_values("Speech", ascending=False).iloc[0]
cards = [
_summary_card("Models", f"{len(view)} / {len(df)}", "shown in current view"),
_summary_card("Top Total", _score(top["Total"]), str(top["Model"])),
_summary_card(
"Best Open-source",
_score(best_open["Total"]) if best_open is not None else "NA",
str(best_open["Model"]) if best_open is not None else "No entry",
),
_summary_card("Lowest AV Offset", _score(best_av["AV"]), str(best_av["Model"])),
_summary_card("Highest Speech", _score(best_speech["Speech"]), str(best_speech["Model"])),
]
return '<div class="summary-grid">' + "".join(cards) + "</div>"
def render_table(
df: pd.DataFrame,
standings: dict[str, Standing],
sort_by: str = "Total",
sort_order: str = "Descending",
) -> str:
if df.empty:
return '<div class="empty-state">No matching models.</div>'
sort_by = _normalize_sort_column(sort_by)
headers = [
("Rank", "Rank"),
("Model", "Model"),
("Components", "Components"),
("Type", "Component Type"),
*[(metric, metric) for metric in METRIC_COLUMNS],
]
header_html = "".join(_header_cell(label, column, sort_by, sort_order) for label, column in headers)
rows = []
for _, row in df.iterrows():
cells = [
f'<td class="rank-cell">#{int(row["Rank"])}</td>',
f'<td class="model-cell">{html.escape(str(row["Model"]))}</td>',
f'<td class="components-cell">{render_component_badges(str(row["Components"]))}</td>',
f'<td>{_type_badge(str(row["Component Type"]))}</td>',
]
for metric in METRIC_COLUMNS:
cells.append(_metric_cell(metric, row[metric], standings[metric]))
rows.append("<tr>" + "".join(cells) + "</tr>")
return (
'<div class="table-shell"><table class="leaderboard-table">'
f"<thead><tr>{header_html}</tr></thead><tbody>{''.join(rows)}</tbody>"
"</table></div>"
)
def render_component_badges(components: str) -> str:
badges = []
for component in _split_components(components):
lowered = component.lower()
kind = "proprietary" if "proprietary" in lowered else "open" if "open-source" in lowered else "neutral"
label = component.replace(" (Proprietary)", "").replace(" (Open-source)", "")
badges.append(f'<span class="component-badge {kind}">{html.escape(label)}</span>')
return "".join(badges)
def render_profile(df: pd.DataFrame, model: str) -> str:
if df.empty:
return ""
if not model or model not in set(df["Model"]):
model = str(df.sort_values("Total", ascending=False).iloc[0]["Model"])
row = df[df["Model"] == model].iloc[0]
metric_blocks = []
for metric in METRIC_COLUMNS[:-1]:
value = float(row[metric])
normalized = normalized_score(metric, value)
direction = "lower is better" if metric in LOWER_IS_BETTER else "higher is better"
metric_blocks.append(
f"""
<div class="profile-metric">
<div class="profile-metric-head">
<span>{html.escape(metric)}</span>
<strong>{_format_metric(metric, value)}</strong>
</div>
<div class="bar-track"><div class="bar-fill" style="width: {normalized:.1f}%"></div></div>
<small>{normalized:.1f} normalized, {direction}</small>
</div>
"""
)
return f"""
<div class="profile-panel">
<div>
<p class="eyebrow">Model profile</p>
<h2>{html.escape(str(row["Model"]))}</h2>
<div class="profile-components">{render_component_badges(str(row["Components"]))}</div>
</div>
<div class="profile-total">
<span>Total</span>
<strong>{_score(row["Total"])}</strong>
</div>
<div class="profile-grid">{''.join(metric_blocks)}</div>
</div>
"""
def render_methodology() -> str:
groups = "".join(
f"""
<div class="method-card">
<h3>{html.escape(name)}</h3>
<strong>{weight:.1f}</strong>
<p>{html.escape(', '.join(metrics))}</p>
</div>
"""
for name, weight in GROUP_WEIGHTS.items()
for metrics in [GROUP_DIMENSIONS[name]]
)
return f"""
<div class="methodology">
<div class="method-grid">{groups}</div>
<p>
Total uses AVGen-Bench Scheme 2: group-weighted normalized metrics with
Vis x 100, Aud(PQ) x 10, Lo-Phy x 20, AV = 100 * max(0, 1 - AV / 0.5),
Lip = 100 * max(0, 1 - Lip / 8), and the remaining metrics already on
a 0-100 scale.
</p>
</div>
"""
def model_choices(df: pd.DataFrame) -> list[str]:
return list(df.sort_values("Total", ascending=False)["Model"])
def _metric_cell(metric: str, value: float, standing: Standing) -> str:
if pd.isna(value):
return '<td class="metric-cell muted">NA</td>'
numeric = float(value)
classes = ["metric-cell"]
if _close(numeric, standing.best):
classes.append("best")
elif _close(numeric, standing.second):
classes.append("second")
return f'<td class="{" ".join(classes)}">{_format_metric(metric, numeric)}</td>'
def _type_badge(component_type: str) -> str:
kind = component_type.lower().replace("-", "").replace(" ", "")
return f'<span class="type-badge {html.escape(kind)}">{html.escape(component_type)}</span>'
def _summary_card(label: str, value: str, detail: str) -> str:
return f"""
<div class="summary-card">
<span>{html.escape(label)}</span>
<strong>{html.escape(value)}</strong>
<small>{html.escape(detail)}</small>
</div>
"""
def _header_cell(label: str, column: str, sort_by: str, sort_order: str) -> str:
if column != sort_by:
return f"<th>{html.escape(label)}</th>"
direction = "ascending" if _is_ascending_sort(sort_by, sort_order) else "descending"
indicator = "&uarr;" if direction == "ascending" else "&darr;"
return (
f'<th class="sorted" aria-sort="{direction}">'
f"{html.escape(label)}"
f'<span class="sort-indicator" aria-hidden="true">{indicator}</span>'
"</th>"
)
def _normalize_sort_column(sort_by: str) -> str:
if sort_by == "Type":
return "Component Type"
return sort_by if sort_by in SORT_COLUMNS else "Total"
def _is_ascending_sort(sort_by: str, sort_order: str) -> bool:
if sort_order == "Best first":
return sort_by in LOWER_IS_BETTER
return sort_order == "Ascending"
def _split_components(value: str) -> Iterable[str]:
return [part.strip() for part in value.split("|") if part.strip()]
def _format_metric(metric: str, value: float) -> str:
return FORMATTERS[metric].format(float(value))
def _score(value: float) -> str:
if pd.isna(value):
return "NA"
return f"{float(value):.2f}"
def _close(left: float, right: float | None) -> bool:
if right is None:
return False
return math.isclose(float(left), float(right), rel_tol=0.0, abs_tol=1e-9)
def _clamp(value: float, low: float, high: float) -> float:
return max(low, min(high, value))