Spaces:
Sleeping
Sleeping
File size: 8,036 Bytes
6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 6352550 d906888 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# colors_utils.py
"""
Continuous, lightness-first color utilities for LLM metric tables.
Changes vs. old version:
- Replaces 5-bin red→yellow→green with continuous ramps.
- Uses colorblind-friendlier palettes (teal sequential; purple↔gray↔teal diverging).
- Renders a soft in-cell "progress pill" instead of painting the whole cell.
- Keeps API compatible: `get_metric_color(score, metric)` and `df_to_colored_html(df)`.
"""
from typing import Optional
import colorsys
import math
import pandas as pd
# -------------------------------
# Color mapping helpers
# -------------------------------
def _clamp(x: float, lo: float, hi: float) -> float:
return lo if x < lo else hi if x > hi else x
def _hsl_hex(h_deg: float, s: float, l: float) -> str:
"""
Convert HSL (H in degrees) to #RRGGBB.
Note: colorsys uses HLS (H, L, S). We pass in (H, L, S) accordingly.
"""
h = (h_deg % 360.0) / 360.0
r, g, b = colorsys.hls_to_rgb(h, l, s)
return "#{:02x}{:02x}{:02x}".format(int(r * 255), int(g * 255), int(b * 255))
def _seq_color_01(t: float, *, hue: float = 200.0, s: float = 0.55,
l_min: float = 0.18, l_max: float = 0.56, gamma: float = 0.85) -> str:
"""
Sequential ramp for metrics in [0,1] (e.g., BLEU, ROUGE, BERTScore).
Single hue (default teal ≈ 200°), smoothly varying lightness.
"""
t = _clamp(float(t), 0.0, 1.0) ** gamma
L = l_min + (l_max - l_min) * t
return _hsl_hex(hue, s, L)
def _div_color_m11(x: float, *, hue_lo: float = 280.0, hue_hi: float = 190.0,
s: float = 0.55, l_mid: float = 0.24, l_span: float = 0.22,
gamma: float = 0.9) -> str:
"""
Diverging ramp for metrics in [-1, 1] (e.g., BLEURT).
Purple (neg) ↔ neutral gray (0) ↔ teal (pos). Hue shifts only at ends;
the zero point is represented by a low-chroma grayish lightness.
"""
t = _clamp((float(x) + 1.0) / 2.0, 0.0, 1.0) ** gamma
# Interpolate lightness around a mid gray; avoid oversaturation at 0
if t < 0.5:
# Toward negative: darker purple
u = t / 0.5 # 0..1
L = l_mid - l_span * (1.0 - u)
return _hsl_hex(hue_lo, s, L)
else:
# Toward positive: brighter teal
u = (t - 0.5) / 0.5 # 0..1
L = l_mid + l_span * u
return _hsl_hex(hue_hi, s, L)
def _norm_for_bar(metric: str, score: Optional[float]) -> float:
"""
Normalize a score to [0,1] for bar width. BLEURT is [-1,1], others ~[0,1].
"""
if score is None or (isinstance(score, float) and not math.isfinite(score)):
return 0.0
m = metric.upper()
if m == "BLEURT":
return _clamp((float(score) + 1.0) / 2.0, 0.0, 1.0)
# BLEU, ROUGE, BERTSCORE default assumption: already ~[0,1]
return _clamp(float(score), 0.0, 1.0)
def get_metric_color(score: Optional[float], metric: str = "BLEU") -> str:
"""
Public API: map a (metric, score) to a visually pleasant, continuous color.
- BLEURT uses a diverging purple↔teal ramp with a neutral midpoint.
- BLEU/ROUGE/BERTSCORE use a single-hue teal sequential ramp.
Returns a neutral deep gray if score is None or not finite.
"""
if score is None or (isinstance(score, float) and not math.isfinite(score)):
return "#2f3240" # neutral
m = metric.upper()
if m == "BLEURT":
return _div_color_m11(float(score))
else:
return _seq_color_01(float(score))
def _readable_text_on(bg_hex: str) -> str:
"""
Choose black or white text for contrast on a given bg color.
"""
try:
r = int(bg_hex[1:3], 16) / 255.0
g = int(bg_hex[3:5], 16) / 255.0
b = int(bg_hex[5:7], 16) / 255.0
except Exception:
# default to white on bad input
return "#ffffff"
# Relative luminance (approx, with a gamma comp)
Y = 0.2126 * (r ** 2.2) + 0.7152 * (g ** 2.2) + 0.0722 * (b ** 2.2)
return "#0b0f14" if Y > 0.5 else "#ffffff"
# -------------------------------
# HTML rendering for DataFrames
# -------------------------------
def _metric_from_col(col: str) -> Optional[str]:
c = col.lower()
if c.startswith("bleurt_"):
return "BLEURT"
if c.startswith("bleu_"):
return "BLEU"
if c.startswith("rouge") or c.startswith("rougel_"):
return "ROUGE"
if c.startswith("bertscore_") or c.startswith("bert_score"):
return "BERTSCORE"
return None
def _fmt_value(val) -> str:
if val is None:
return ""
# keep audio id clean
try:
if isinstance(val, float):
return f"{val:.4f}"
return str(val)
except Exception:
return str(val)
def df_to_colored_html(df: pd.DataFrame) -> str:
"""
Render a dark-themed HTML table with soft, continuous "progress pills"
behind metric values. Keeps the background neutral (dark) to reduce
fatigue; the color is confined to the small bar.
"""
df_display = df.copy()
# Column order: ensure 'code_audio_transcription' first if present
headers = list(df_display.columns)
if "code_audio_transcription" in headers:
headers = ["code_audio_transcription"] + [h for h in headers if h != "code_audio_transcription"]
# Table shell
html = [
'<div style="font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;'
' font-size:14px; color:#e6ebf2;">',
'<table style="border-collapse:separate; border-spacing:0; width:100%; background:#0b0f14;'
' border:1px solid #202532; border-radius:10px; overflow:hidden;">'
"<thead><tr>"
]
# Header row
for h in headers:
html.append(
f'<th style="position:sticky; top:0; background:#121722; color:#c9d4e3;'
f' padding:10px 12px; border-bottom:1px solid #1b2030; text-align:center;'
f' font-weight:600; white-space:nowrap;">{h}</th>'
)
html.append("</tr></thead><tbody>")
# Rows
row_bg_a = "#0b0f14"
row_bg_b = "#0e131b"
for i, (_, row) in enumerate(df_display.iterrows()):
row_bg = row_bg_a if (i % 2 == 0) else row_bg_b
html.append(f"<tr style='background:{row_bg};'>")
for col in headers:
val = row.get(col, None)
metric = _metric_from_col(col)
disp = _fmt_value(val)
if metric is None:
# Non-metric column: plain cell
html.append(
"<td style='padding:10px 14px; border-bottom:1px solid #121722;"
" text-align:center; color:#e6ebf2; white-space:nowrap;'>"
f"{disp}</td>"
)
continue
# Metric column: compute bar + color
color = get_metric_color(val, metric) if pd.notnull(val) else "transparent"
width01 = _norm_for_bar(metric, float(val)) if pd.notnull(val) else 0.0
bar_width_pct = f"{width01 * 100:.1f}%"
text_color = "#e6ebf2" # keep neutral text on dark background
# A subtle inner background for the pill track
track_bg = "#141a24"
cell_html = f"""
<td style="padding:10px 14px; border-bottom:1px solid #121722; text-align:center; white-space:nowrap;">
<div style="position:relative; height:22px; border-radius:7px; background:{track_bg}; overflow:hidden;">
<div style="position:absolute; inset:0; width:{bar_width_pct}; background:{color}; opacity:.75;"></div>
<div style="position:relative; z-index:1; line-height:22px; color:{text_color};">{disp}</div>
</div>
</td>
"""
html.append(cell_html)
html.append("</tr>")
html.append("</tbody></table></div>")
return "".join(html)
|