Fix latent consistency visualization
Browse files
app.py
CHANGED
|
@@ -189,6 +189,24 @@ def empty_figure(message: str) -> go.Figure:
|
|
| 189 |
return fig_layout(fig)
|
| 190 |
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
def parse_float_list(value: object) -> list[float]:
|
| 193 |
if pd.isna(value):
|
| 194 |
return []
|
|
@@ -226,53 +244,102 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
|
|
| 226 |
].copy()
|
| 227 |
if not score_df.empty:
|
| 228 |
score_df["mean_r2"] = pd.to_numeric(score_df["mean_r2"], errors="coerce")
|
| 229 |
-
|
| 230 |
score_df.sort_values("mean_r2", ascending=False)["model"]
|
| 231 |
.astype(str)
|
| 232 |
-
.
|
| 233 |
-
.tolist()
|
| 234 |
)
|
| 235 |
else:
|
| 236 |
-
|
| 237 |
|
| 238 |
-
plot_df = df[df["model"].astype(str)
|
| 239 |
for col in ["x", "y", "z"]:
|
| 240 |
plot_df[col] = pd.to_numeric(plot_df[col], errors="coerce")
|
|
|
|
| 241 |
plot_df = plot_df.dropna(subset=["x", "y", "z"])
|
| 242 |
if plot_df.empty:
|
| 243 |
return empty_figure("Latent-space samples are empty after filtering.")
|
| 244 |
|
| 245 |
-
|
| 246 |
-
score_df.set_index("model")["mean_r2"].to_dict() if not score_df.empty else {}
|
| 247 |
-
)
|
| 248 |
-
titles = []
|
| 249 |
-
for model in plot_models:
|
| 250 |
-
score = score_lookup.get(model)
|
| 251 |
-
suffix = "" if pd.isna(score) else f" R2={score:.2f}"
|
| 252 |
-
titles.append(f"{model}{suffix}")
|
| 253 |
-
|
| 254 |
n_cols = 2
|
| 255 |
-
n_rows = int(np.ceil(len(
|
| 256 |
fig = make_subplots(
|
| 257 |
rows=n_rows,
|
| 258 |
cols=n_cols,
|
| 259 |
specs=[[{"type": "scene"} for _ in range(n_cols)] for _ in range(n_rows)],
|
| 260 |
-
subplot_titles=
|
| 261 |
-
horizontal_spacing=0.
|
| 262 |
-
vertical_spacing=0.
|
| 263 |
)
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
fig.add_trace(
|
| 277 |
go.Scatter3d(
|
| 278 |
x=session_df["x"],
|
|
@@ -280,16 +347,17 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
|
|
| 280 |
z=session_df["z"],
|
| 281 |
mode="markers",
|
| 282 |
name=session,
|
| 283 |
-
|
| 284 |
-
showlegend=model_idx == 0,
|
| 285 |
marker=dict(
|
| 286 |
-
size=2.
|
| 287 |
-
opacity=0.
|
| 288 |
-
color=
|
|
|
|
|
|
|
|
|
|
| 289 |
),
|
| 290 |
customdata=np.stack(
|
| 291 |
[
|
| 292 |
-
session_df["model"].astype(str),
|
| 293 |
session_df["condition"].astype(str),
|
| 294 |
session_df["trial_index"].astype(str),
|
| 295 |
session_df["time_index"].astype(str),
|
|
@@ -297,10 +365,9 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
|
|
| 297 |
axis=-1,
|
| 298 |
),
|
| 299 |
hovertemplate=(
|
| 300 |
-
"model=%{customdata[0]}<br>"
|
| 301 |
"session=%{fullData.name}<br>"
|
| 302 |
-
"
|
| 303 |
-
"trial=%{customdata[
|
| 304 |
"<extra></extra>"
|
| 305 |
),
|
| 306 |
),
|
|
@@ -308,21 +375,57 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
|
|
| 308 |
col=col,
|
| 309 |
)
|
| 310 |
|
| 311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
scene_id = "scene" if idx == 0 else f"scene{idx + 1}"
|
| 313 |
fig.update_layout(
|
| 314 |
**{
|
| 315 |
scene_id: dict(
|
| 316 |
-
xaxis=dict(
|
| 317 |
-
|
| 318 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
bgcolor="#ffffff",
|
| 320 |
-
camera=dict(eye=dict(x=1.55, y=1.
|
| 321 |
)
|
| 322 |
}
|
| 323 |
)
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
|
| 327 |
|
| 328 |
def dataframe_table(
|
|
@@ -489,7 +592,19 @@ app.layout = html.Div(
|
|
| 489 |
children=[
|
| 490 |
panel(
|
| 491 |
"Cross-Session Latent Consistency",
|
| 492 |
-
dcc.Graph(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
dcc.Graph(id="consistency-bars", config={"displayModeBar": False}),
|
| 494 |
dcc.Graph(id="consistency-heatmap", config={"displayModeBar": False}),
|
| 495 |
dataframe_table("consistency-table"),
|
|
|
|
| 189 |
return fig_layout(fig)
|
| 190 |
|
| 191 |
|
| 192 |
+
def latent_fig_layout(fig: go.Figure, *, height: int) -> go.Figure:
|
| 193 |
+
fig.update_layout(
|
| 194 |
+
height=height,
|
| 195 |
+
paper_bgcolor="white",
|
| 196 |
+
plot_bgcolor="white",
|
| 197 |
+
margin=dict(l=8, r=8, t=76, b=84),
|
| 198 |
+
font=dict(family="Inter, Arial, sans-serif", size=13, color="#1f2933"),
|
| 199 |
+
legend=dict(
|
| 200 |
+
orientation="h",
|
| 201 |
+
yanchor="top",
|
| 202 |
+
y=-0.08,
|
| 203 |
+
xanchor="left",
|
| 204 |
+
x=0,
|
| 205 |
+
),
|
| 206 |
+
)
|
| 207 |
+
return fig
|
| 208 |
+
|
| 209 |
+
|
| 210 |
def parse_float_list(value: object) -> list[float]:
|
| 211 |
if pd.isna(value):
|
| 212 |
return []
|
|
|
|
| 244 |
].copy()
|
| 245 |
if not score_df.empty:
|
| 246 |
score_df["mean_r2"] = pd.to_numeric(score_df["mean_r2"], errors="coerce")
|
| 247 |
+
model = (
|
| 248 |
score_df.sort_values("mean_r2", ascending=False)["model"]
|
| 249 |
.astype(str)
|
| 250 |
+
.iloc[0]
|
|
|
|
| 251 |
)
|
| 252 |
else:
|
| 253 |
+
model = sorted(df["model"].astype(str).unique())[0]
|
| 254 |
|
| 255 |
+
plot_df = df[df["model"].astype(str) == model].copy()
|
| 256 |
for col in ["x", "y", "z"]:
|
| 257 |
plot_df[col] = pd.to_numeric(plot_df[col], errors="coerce")
|
| 258 |
+
plot_df["condition_num"] = pd.to_numeric(plot_df["condition"], errors="coerce")
|
| 259 |
plot_df = plot_df.dropna(subset=["x", "y", "z"])
|
| 260 |
if plot_df.empty:
|
| 261 |
return empty_figure("Latent-space samples are empty after filtering.")
|
| 262 |
|
| 263 |
+
sessions = ordered_unique(plot_df["session_label"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
n_cols = 2
|
| 265 |
+
n_rows = int(np.ceil(len(sessions) / n_cols))
|
| 266 |
fig = make_subplots(
|
| 267 |
rows=n_rows,
|
| 268 |
cols=n_cols,
|
| 269 |
specs=[[{"type": "scene"} for _ in range(n_cols)] for _ in range(n_rows)],
|
| 270 |
+
subplot_titles=sessions,
|
| 271 |
+
horizontal_spacing=0.035,
|
| 272 |
+
vertical_spacing=0.105,
|
| 273 |
)
|
| 274 |
+
|
| 275 |
+
condition_values = sorted(plot_df["condition"].astype(str).unique())
|
| 276 |
+
use_categorical_conditions = len(condition_values) <= 12
|
| 277 |
+
direction_palette = [
|
| 278 |
+
"#B23AEE",
|
| 279 |
+
"#3B1C54",
|
| 280 |
+
"#2DD4F6",
|
| 281 |
+
"#289285",
|
| 282 |
+
"#E3D724",
|
| 283 |
+
"#00A65A",
|
| 284 |
+
"#5B8FF9",
|
| 285 |
+
"#F97316",
|
| 286 |
+
"#E45756",
|
| 287 |
+
"#72B7B2",
|
| 288 |
+
"#54A24B",
|
| 289 |
+
"#B279A2",
|
| 290 |
+
]
|
| 291 |
+
condition_colors = {
|
| 292 |
+
condition: direction_palette[idx % len(direction_palette)]
|
| 293 |
+
for idx, condition in enumerate(condition_values)
|
| 294 |
+
}
|
| 295 |
+
condition_prefix = "Reach direction" if dataset == "monkey" else "Condition"
|
| 296 |
+
|
| 297 |
+
for session_idx, session in enumerate(sessions):
|
| 298 |
+
session_df = plot_df[plot_df["session_label"].astype(str) == session]
|
| 299 |
+
if session_df.empty:
|
| 300 |
+
continue
|
| 301 |
+
row = session_idx // n_cols + 1
|
| 302 |
+
col = session_idx % n_cols + 1
|
| 303 |
+
|
| 304 |
+
if use_categorical_conditions:
|
| 305 |
+
for condition in condition_values:
|
| 306 |
+
cond_df = session_df[session_df["condition"].astype(str) == condition]
|
| 307 |
+
if cond_df.empty:
|
| 308 |
+
continue
|
| 309 |
+
trace_name = f"{condition_prefix} {condition}"
|
| 310 |
+
fig.add_trace(
|
| 311 |
+
go.Scatter3d(
|
| 312 |
+
x=cond_df["x"],
|
| 313 |
+
y=cond_df["y"],
|
| 314 |
+
z=cond_df["z"],
|
| 315 |
+
mode="markers",
|
| 316 |
+
name=trace_name,
|
| 317 |
+
legendgroup=condition,
|
| 318 |
+
showlegend=session_idx == 0,
|
| 319 |
+
marker=dict(
|
| 320 |
+
size=2.6,
|
| 321 |
+
opacity=0.72,
|
| 322 |
+
color=condition_colors[condition],
|
| 323 |
+
),
|
| 324 |
+
customdata=np.stack(
|
| 325 |
+
[
|
| 326 |
+
cond_df["session_label"].astype(str),
|
| 327 |
+
cond_df["trial_index"].astype(str),
|
| 328 |
+
cond_df["time_index"].astype(str),
|
| 329 |
+
],
|
| 330 |
+
axis=-1,
|
| 331 |
+
),
|
| 332 |
+
hovertemplate=(
|
| 333 |
+
"session=%{customdata[0]}<br>"
|
| 334 |
+
f"{condition_prefix.lower()}={condition}<br>"
|
| 335 |
+
"trial=%{customdata[1]} time=%{customdata[2]}"
|
| 336 |
+
"<extra></extra>"
|
| 337 |
+
),
|
| 338 |
+
),
|
| 339 |
+
row=row,
|
| 340 |
+
col=col,
|
| 341 |
+
)
|
| 342 |
+
else:
|
| 343 |
fig.add_trace(
|
| 344 |
go.Scatter3d(
|
| 345 |
x=session_df["x"],
|
|
|
|
| 347 |
z=session_df["z"],
|
| 348 |
mode="markers",
|
| 349 |
name=session,
|
| 350 |
+
showlegend=False,
|
|
|
|
| 351 |
marker=dict(
|
| 352 |
+
size=2.6,
|
| 353 |
+
opacity=0.72,
|
| 354 |
+
color=session_df["condition_num"],
|
| 355 |
+
colorscale="Viridis",
|
| 356 |
+
showscale=session_idx == 0,
|
| 357 |
+
colorbar=dict(title=condition_prefix),
|
| 358 |
),
|
| 359 |
customdata=np.stack(
|
| 360 |
[
|
|
|
|
| 361 |
session_df["condition"].astype(str),
|
| 362 |
session_df["trial_index"].astype(str),
|
| 363 |
session_df["time_index"].astype(str),
|
|
|
|
| 365 |
axis=-1,
|
| 366 |
),
|
| 367 |
hovertemplate=(
|
|
|
|
| 368 |
"session=%{fullData.name}<br>"
|
| 369 |
+
f"{condition_prefix.lower()}=%{{customdata[0]}}<br>"
|
| 370 |
+
"trial=%{customdata[1]} time=%{customdata[2]}"
|
| 371 |
"<extra></extra>"
|
| 372 |
),
|
| 373 |
),
|
|
|
|
| 375 |
col=col,
|
| 376 |
)
|
| 377 |
|
| 378 |
+
extent = max(
|
| 379 |
+
float(np.nanpercentile(np.abs(plot_df["x"]), 99)),
|
| 380 |
+
float(np.nanpercentile(np.abs(plot_df["y"]), 99)),
|
| 381 |
+
float(np.nanpercentile(np.abs(plot_df["z"]), 99)),
|
| 382 |
+
1.0,
|
| 383 |
+
)
|
| 384 |
+
lim = extent * 1.08
|
| 385 |
+
for idx in range(len(sessions)):
|
| 386 |
scene_id = "scene" if idx == 0 else f"scene{idx + 1}"
|
| 387 |
fig.update_layout(
|
| 388 |
**{
|
| 389 |
scene_id: dict(
|
| 390 |
+
xaxis=dict(
|
| 391 |
+
title="",
|
| 392 |
+
range=[-lim, lim],
|
| 393 |
+
showgrid=False,
|
| 394 |
+
zeroline=False,
|
| 395 |
+
showticklabels=False,
|
| 396 |
+
),
|
| 397 |
+
yaxis=dict(
|
| 398 |
+
title="",
|
| 399 |
+
range=[-lim, lim],
|
| 400 |
+
showgrid=False,
|
| 401 |
+
zeroline=False,
|
| 402 |
+
showticklabels=False,
|
| 403 |
+
),
|
| 404 |
+
zaxis=dict(
|
| 405 |
+
title="",
|
| 406 |
+
range=[-lim, lim],
|
| 407 |
+
showgrid=False,
|
| 408 |
+
zeroline=False,
|
| 409 |
+
showticklabels=False,
|
| 410 |
+
),
|
| 411 |
+
aspectmode="cube",
|
| 412 |
bgcolor="#ffffff",
|
| 413 |
+
camera=dict(eye=dict(x=1.55, y=1.45, z=1.05)),
|
| 414 |
)
|
| 415 |
}
|
| 416 |
)
|
| 417 |
+
score = None
|
| 418 |
+
if not score_df.empty and model in set(score_df["model"].astype(str)):
|
| 419 |
+
score = float(score_df.loc[score_df["model"].astype(str) == model, "mean_r2"].iloc[0])
|
| 420 |
+
score_text = "" if score is None or pd.isna(score) else f" (mean R2={score:.2f})"
|
| 421 |
+
fig.update_layout(
|
| 422 |
+
title=(
|
| 423 |
+
f"{DATASET_LABELS.get(dataset, dataset)} latent samples: {model}{score_text}"
|
| 424 |
+
),
|
| 425 |
+
)
|
| 426 |
+
fig.for_each_annotation(lambda ann: ann.update(font=dict(size=11, color="#536271")))
|
| 427 |
+
fig.update_layout(legend=dict(title=condition_prefix))
|
| 428 |
+
return latent_fig_layout(fig, height=720 if n_rows > 1 else 430)
|
| 429 |
|
| 430 |
|
| 431 |
def dataframe_table(
|
|
|
|
| 592 |
children=[
|
| 593 |
panel(
|
| 594 |
"Cross-Session Latent Consistency",
|
| 595 |
+
dcc.Graph(
|
| 596 |
+
id="latent-space",
|
| 597 |
+
config={
|
| 598 |
+
"displayModeBar": "hover",
|
| 599 |
+
"toImageButtonOptions": {
|
| 600 |
+
"format": "png",
|
| 601 |
+
"filename": "benchdash_latent_space",
|
| 602 |
+
"height": 900,
|
| 603 |
+
"width": 1200,
|
| 604 |
+
"scale": 2,
|
| 605 |
+
},
|
| 606 |
+
},
|
| 607 |
+
),
|
| 608 |
dcc.Graph(id="consistency-bars", config={"displayModeBar": False}),
|
| 609 |
dcc.Graph(id="consistency-heatmap", config={"displayModeBar": False}),
|
| 610 |
dataframe_table("consistency-table"),
|