josephsoo commited on
Commit
6fd2f25
·
1 Parent(s): bb9fb13

Fix latent consistency visualization

Browse files
Files changed (1) hide show
  1. app.py +162 -47
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
- plot_models = (
230
  score_df.sort_values("mean_r2", ascending=False)["model"]
231
  .astype(str)
232
- .head(4)
233
- .tolist()
234
  )
235
  else:
236
- plot_models = sorted(df["model"].astype(str).unique())[:4]
237
 
238
- plot_df = df[df["model"].astype(str).isin(plot_models)].copy()
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
- score_lookup = (
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(plot_models) / n_cols))
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=titles,
261
- horizontal_spacing=0.03,
262
- vertical_spacing=0.08,
263
  )
264
- palette = px.colors.qualitative.Set2 + px.colors.qualitative.Dark24
265
- sessions = ordered_unique(plot_df["session_label"])
266
- session_colors = {session: palette[idx % len(palette)] for idx, session in enumerate(sessions)}
267
-
268
- for model_idx, model in enumerate(plot_models):
269
- model_df = plot_df[plot_df["model"].astype(str) == model]
270
- row = model_idx // n_cols + 1
271
- col = model_idx % n_cols + 1
272
- for session in sessions:
273
- session_df = model_df[model_df["session_label"].astype(str) == session]
274
- if session_df.empty:
275
- continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- legendgroup=session,
284
- showlegend=model_idx == 0,
285
  marker=dict(
286
- size=2.4,
287
- opacity=0.58,
288
- color=session_colors[session],
 
 
 
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
- "condition=%{customdata[1]}<br>"
303
- "trial=%{customdata[2]} time=%{customdata[3]}"
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
- for idx in range(len(plot_models)):
 
 
 
 
 
 
 
312
  scene_id = "scene" if idx == 0 else f"scene{idx + 1}"
313
  fig.update_layout(
314
  **{
315
  scene_id: dict(
316
- xaxis=dict(title="", showgrid=False, zeroline=False, showticklabels=False),
317
- yaxis=dict(title="", showgrid=False, zeroline=False, showticklabels=False),
318
- zaxis=dict(title="", showgrid=False, zeroline=False, showticklabels=False),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  bgcolor="#ffffff",
320
- camera=dict(eye=dict(x=1.55, y=1.55, z=1.1)),
321
  )
322
  }
323
  )
324
- fig.update_layout(title=f"{DATASET_LABELS.get(dataset, dataset)} latent-space samples")
325
- return fig_layout(fig, height=720 if n_rows > 1 else 420)
 
 
 
 
 
 
 
 
 
 
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(id="latent-space", config={"displayModeBar": False}),
 
 
 
 
 
 
 
 
 
 
 
 
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"),