josephsoo commited on
Commit
eecdf8a
·
1 Parent(s): d252c13

Fix consistency columns and latent colors

Browse files
Files changed (3) hide show
  1. app.py +145 -30
  2. assets/styles.css +9 -0
  3. data/latent_samples.csv +0 -0
app.py CHANGED
@@ -263,6 +263,9 @@ def build_dataset_labels() -> dict[str, str]:
263
 
264
  DATASET_LABELS = build_dataset_labels()
265
  DATASETS = ordered_unique(prediction.get("dataset", pd.Series(dtype=str)))
 
 
 
266
  MODEL_SET = set(prediction.get("model", pd.Series(dtype=str)).dropna().astype(str))
267
  MODELS = [model for model in PAPER_MODEL_ORDER if model in MODEL_SET]
268
  MODELS += sorted(model for model in MODEL_SET if model not in set(MODELS))
@@ -346,15 +349,54 @@ def condition_label(dataset: str, condition: object) -> str:
346
  return text
347
 
348
 
349
- def condition_axis_label(dataset: str) -> str:
 
 
 
 
 
 
350
  return {
351
  "monkey": "Reach direction",
352
  "allen_neuropixels": "Orientation",
353
  "speech": "Cue",
354
- "ratinabox": "Position bin",
355
  }.get(dataset, "Condition")
356
 
357
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  def session_display_label(dataset: str, session: object) -> str:
359
  text = "" if pd.isna(session) else str(session)
360
  if dataset == "monkey":
@@ -409,6 +451,14 @@ def sort_table(df: pd.DataFrame, sort_by: list[dict] | None, default: list[tuple
409
  )
410
 
411
 
 
 
 
 
 
 
 
 
412
  def records(df: pd.DataFrame) -> list[dict]:
413
  clean = df.astype(object).where(pd.notna(df), None)
414
  return clean.to_dict("records")
@@ -810,7 +860,7 @@ def consistency_heatmap(models: list[str] | None) -> go.Figure:
810
  return fig_layout(fig, height=max(360, 25 * len(pivot.index) + 135))
811
 
812
 
813
- def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
814
  if not model:
815
  return empty_figure("No latent-space view is available for this selection.")
816
  if latent_samples.empty:
@@ -825,8 +875,10 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
825
 
826
  for col in ["x", "y", "z"]:
827
  plot_df[col] = pd.to_numeric(plot_df[col], errors="coerce")
828
- plot_df["condition_num"] = pd.to_numeric(plot_df["condition"], errors="coerce")
829
- plot_df["condition_label"] = plot_df["condition"].map(lambda value: condition_label(dataset, value))
 
 
830
  plot_df["session_display"] = plot_df["session_label"].map(lambda value: session_display_label(dataset, value))
831
  plot_df = plot_df.dropna(subset=["x", "y", "z"])
832
  if plot_df.empty:
@@ -840,9 +892,14 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
840
  if col in trajectory_df:
841
  trajectory_df[col] = pd.to_numeric(trajectory_df[col], errors="coerce")
842
  if not trajectory_df.empty:
843
- trajectory_df["condition_label"] = trajectory_df["condition"].map(lambda value: condition_label(dataset, value))
 
 
 
844
  trajectory_df["session_display"] = trajectory_df["session_label"].map(lambda value: session_display_label(dataset, value))
845
  trajectory_df = trajectory_df.dropna(subset=["x", "y", "z"])
 
 
846
 
847
  sessions = ordered_unique(plot_df["session_label"])
848
  session_titles = [session_display_label(dataset, session) for session in sessions]
@@ -858,8 +915,8 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
858
  vertical_spacing=0.12,
859
  )
860
 
861
- condition_values = sorted(plot_df["condition"].astype(str).unique(), key=condition_sort_key)
862
- use_categorical = len(condition_values) <= 12
863
  if dataset == "monkey":
864
  condition_colors = {
865
  condition: DIRECTION_PALETTE[int(float(condition)) % len(DIRECTION_PALETTE)]
@@ -875,7 +932,7 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
875
  condition: CATEGORICAL_PALETTE[idx % len(CATEGORICAL_PALETTE)]
876
  for idx, condition in enumerate(condition_values)
877
  }
878
- condition_name = condition_axis_label(dataset)
879
 
880
  for session_idx, session in enumerate(sessions):
881
  session_df = plot_df[plot_df["session_label"].astype(str) == str(session)]
@@ -887,13 +944,13 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
887
 
888
  if use_categorical:
889
  for condition in condition_values:
890
- cond_df = session_df[session_df["condition"].astype(str) == condition]
891
  if cond_df.empty:
892
  continue
893
- trace_name = condition_label(dataset, condition)
894
  session_traj = trajectory_df[
895
  (trajectory_df["session_label"].astype(str) == str(session))
896
- & (trajectory_df["condition"].astype(str) == condition)
897
  ].sort_values("time_index")
898
  fig.add_trace(
899
  go.Scatter3d(
@@ -912,7 +969,7 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
912
  customdata=np.stack(
913
  [
914
  np.repeat(display_session, len(cond_df)),
915
- cond_df["condition_label"].astype(str),
916
  cond_df["trial_index"].astype(str),
917
  cond_df["time_index"].astype(str),
918
  ],
@@ -960,15 +1017,17 @@ def latent_space_figure(dataset: str, model: str | None) -> go.Figure:
960
  marker=dict(
961
  size=2.8,
962
  opacity=0.72,
963
- color=session_df["condition_num"],
964
  colorscale=RATINABOX_SCALE,
 
 
965
  showscale=session_idx == 0,
966
  colorbar=dict(title=condition_name, thickness=12),
967
  ),
968
  customdata=np.stack(
969
  [
970
  np.repeat(display_session, len(session_df)),
971
- session_df["condition"].map(lambda value: condition_label(dataset, value)).astype(str),
972
  session_df["trial_index"].astype(str),
973
  session_df["time_index"].astype(str),
974
  ],
@@ -1332,18 +1391,34 @@ app.layout = html.Div(
1332
  ],
1333
  className="consistency-table-wrap",
1334
  ),
1335
- dcc.Graph(
1336
- id="latent-space",
1337
- config={
1338
- "displayModeBar": "hover",
1339
- "toImageButtonOptions": {
1340
- "format": "png",
1341
- "filename": "benchdash_latent_space",
1342
- "height": 900,
1343
- "width": 1200,
1344
- "scale": 2,
1345
- },
1346
- },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1347
  ),
1348
  ],
1349
  className="latent-grid",
@@ -1449,11 +1524,13 @@ def update_leaderboard(dataset: str, sort_by: list[dict] | None):
1449
  "method",
1450
  "task_score",
1451
  "robustness_auc",
1452
- "alignment_score",
1453
  "training_time_sec",
1454
  "peak_ram_gb",
1455
  "peak_vram_gb",
1456
  ]
 
 
 
1457
  sorted_df = sort_table(df, sort_by, [("task_score", False), ("model_order", True)])
1458
  table_df = sorted_df[[c for c in visible_cols + ["id", "model"] if c in sorted_df.columns]]
1459
  metric = df["metric"].dropna().iloc[0] if df["metric"].notna().any() else "score"
@@ -1467,6 +1544,26 @@ def update_leaderboard(dataset: str, sort_by: list[dict] | None):
1467
  )
1468
 
1469
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1470
  @app.callback(
1471
  Output("consistency-table", "columns"),
1472
  Output("consistency-table", "data"),
@@ -1477,18 +1574,36 @@ def update_leaderboard(dataset: str, sort_by: list[dict] | None):
1477
  Input("dataset-filter", "value"),
1478
  Input("consistency-table", "active_cell"),
1479
  Input("consistency-table", "sort_by"),
 
1480
  )
1481
- def update_consistency(dataset: str, active_cell: dict | None, sort_by: list[dict] | None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1482
  df = consistency_frame(dataset, None)
1483
  model = selected_consistency_model(df, active_cell)
1484
  visible_cols = ["method", "alignment_score", "n_sessions", "latent_dim", "n_pairwise"]
 
1485
  sorted_df = sort_table(df, sort_by, [("alignment_score", False), ("model_order", True)])
1486
  table_df = sorted_df[[c for c in visible_cols + ["id", "model"] if c in sorted_df.columns]]
1487
  return (
1488
  column_defs([c for c in visible_cols if c in table_df.columns]),
1489
  records(table_df),
1490
  sort_status(sort_by, ("alignment_score", False)),
1491
- latent_space_figure(dataset, model),
1492
  consistency_bar_figure(dataset, df),
1493
  consistency_heatmap(None),
1494
  )
 
263
 
264
  DATASET_LABELS = build_dataset_labels()
265
  DATASETS = ordered_unique(prediction.get("dataset", pd.Series(dtype=str)))
266
+ CONSISTENCY_DATASETS = set(
267
+ active_rows(consistency).get("dataset", pd.Series(dtype=str)).dropna().astype(str)
268
+ )
269
  MODEL_SET = set(prediction.get("model", pd.Series(dtype=str)).dropna().astype(str))
270
  MODELS = [model for model in PAPER_MODEL_ORDER if model in MODEL_SET]
271
  MODELS += sorted(model for model in MODEL_SET if model not in set(MODELS))
 
349
  return text
350
 
351
 
352
+ def condition_axis_label(dataset: str, color_mode: str = "condition") -> str:
353
+ if dataset == "ratinabox":
354
+ if color_mode == "x":
355
+ return "X position bin"
356
+ if color_mode == "y":
357
+ return "Y position bin"
358
+ return "Position bin"
359
  return {
360
  "monkey": "Reach direction",
361
  "allen_neuropixels": "Orientation",
362
  "speech": "Cue",
 
363
  }.get(dataset, "Condition")
364
 
365
 
366
+ def latent_color_label(dataset: str, color_mode: str, value: object) -> str:
367
+ if pd.isna(value):
368
+ return "Unknown"
369
+ if dataset == "ratinabox":
370
+ try:
371
+ idx = int(float(value))
372
+ except ValueError:
373
+ return str(value)
374
+ if color_mode == "x":
375
+ return f"x{idx}"
376
+ if color_mode == "y":
377
+ return f"y{idx}"
378
+ return condition_label(dataset, value)
379
+
380
+
381
+ def add_latent_color_columns(df: pd.DataFrame, dataset: str, color_mode: str) -> pd.DataFrame:
382
+ out = df.copy()
383
+ condition_num = pd.to_numeric(out["condition"], errors="coerce")
384
+ if condition_num.isna().any() or (condition_num < 0).any():
385
+ raise ValueError("Latent samples contain missing condition labels.")
386
+
387
+ if dataset == "ratinabox" and color_mode == "x":
388
+ values = (condition_num.astype(int) % 10).astype(str)
389
+ elif dataset == "ratinabox" and color_mode == "y":
390
+ values = (condition_num.astype(int) // 10).astype(str)
391
+ else:
392
+ values = condition_num.astype(int).astype(str)
393
+
394
+ out["color_value"] = values
395
+ out["color_num"] = pd.to_numeric(values, errors="coerce")
396
+ out["color_label"] = out["color_value"].map(lambda value: latent_color_label(dataset, color_mode, value))
397
+ return out
398
+
399
+
400
  def session_display_label(dataset: str, session: object) -> str:
401
  text = "" if pd.isna(session) else str(session)
402
  if dataset == "monkey":
 
451
  )
452
 
453
 
454
+ def supported_sort(sort_by: list[dict] | None, columns: Iterable[str]) -> list[dict] | None:
455
+ allowed = set(columns)
456
+ if not sort_by:
457
+ return None
458
+ filtered = [item for item in sort_by if item.get("column_id") in allowed]
459
+ return filtered or None
460
+
461
+
462
  def records(df: pd.DataFrame) -> list[dict]:
463
  clean = df.astype(object).where(pd.notna(df), None)
464
  return clean.to_dict("records")
 
860
  return fig_layout(fig, height=max(360, 25 * len(pivot.index) + 135))
861
 
862
 
863
+ def latent_space_figure(dataset: str, model: str | None, color_mode: str = "condition") -> go.Figure:
864
  if not model:
865
  return empty_figure("No latent-space view is available for this selection.")
866
  if latent_samples.empty:
 
875
 
876
  for col in ["x", "y", "z"]:
877
  plot_df[col] = pd.to_numeric(plot_df[col], errors="coerce")
878
+ try:
879
+ plot_df = add_latent_color_columns(plot_df, dataset, color_mode)
880
+ except ValueError as exc:
881
+ return empty_figure(str(exc))
882
  plot_df["session_display"] = plot_df["session_label"].map(lambda value: session_display_label(dataset, value))
883
  plot_df = plot_df.dropna(subset=["x", "y", "z"])
884
  if plot_df.empty:
 
892
  if col in trajectory_df:
893
  trajectory_df[col] = pd.to_numeric(trajectory_df[col], errors="coerce")
894
  if not trajectory_df.empty:
895
+ try:
896
+ trajectory_df = add_latent_color_columns(trajectory_df, dataset, color_mode)
897
+ except ValueError:
898
+ trajectory_df = trajectory_df.iloc[0:0].copy()
899
  trajectory_df["session_display"] = trajectory_df["session_label"].map(lambda value: session_display_label(dataset, value))
900
  trajectory_df = trajectory_df.dropna(subset=["x", "y", "z"])
901
+ if "color_value" not in trajectory_df.columns:
902
+ trajectory_df["color_value"] = pd.Series(dtype=str)
903
 
904
  sessions = ordered_unique(plot_df["session_label"])
905
  session_titles = [session_display_label(dataset, session) for session in sessions]
 
915
  vertical_spacing=0.12,
916
  )
917
 
918
+ condition_values = sorted(plot_df["color_value"].astype(str).unique(), key=condition_sort_key)
919
+ use_categorical = len(condition_values) <= 12 and dataset != "ratinabox"
920
  if dataset == "monkey":
921
  condition_colors = {
922
  condition: DIRECTION_PALETTE[int(float(condition)) % len(DIRECTION_PALETTE)]
 
932
  condition: CATEGORICAL_PALETTE[idx % len(CATEGORICAL_PALETTE)]
933
  for idx, condition in enumerate(condition_values)
934
  }
935
+ condition_name = condition_axis_label(dataset, color_mode)
936
 
937
  for session_idx, session in enumerate(sessions):
938
  session_df = plot_df[plot_df["session_label"].astype(str) == str(session)]
 
944
 
945
  if use_categorical:
946
  for condition in condition_values:
947
+ cond_df = session_df[session_df["color_value"].astype(str) == condition]
948
  if cond_df.empty:
949
  continue
950
+ trace_name = latent_color_label(dataset, color_mode, condition)
951
  session_traj = trajectory_df[
952
  (trajectory_df["session_label"].astype(str) == str(session))
953
+ & (trajectory_df["color_value"].astype(str) == condition)
954
  ].sort_values("time_index")
955
  fig.add_trace(
956
  go.Scatter3d(
 
969
  customdata=np.stack(
970
  [
971
  np.repeat(display_session, len(cond_df)),
972
+ cond_df["color_label"].astype(str),
973
  cond_df["trial_index"].astype(str),
974
  cond_df["time_index"].astype(str),
975
  ],
 
1017
  marker=dict(
1018
  size=2.8,
1019
  opacity=0.72,
1020
+ color=session_df["color_num"],
1021
  colorscale=RATINABOX_SCALE,
1022
+ cmin=0,
1023
+ cmax=9 if color_mode in {"x", "y"} else 99,
1024
  showscale=session_idx == 0,
1025
  colorbar=dict(title=condition_name, thickness=12),
1026
  ),
1027
  customdata=np.stack(
1028
  [
1029
  np.repeat(display_session, len(session_df)),
1030
+ session_df["color_label"].astype(str),
1031
  session_df["trial_index"].astype(str),
1032
  session_df["time_index"].astype(str),
1033
  ],
 
1391
  ],
1392
  className="consistency-table-wrap",
1393
  ),
1394
+ html.Div(
1395
+ [
1396
+ html.Div(
1397
+ [
1398
+ html.Label("Color by"),
1399
+ dcc.Dropdown(
1400
+ id="latent-color-mode",
1401
+ clearable=False,
1402
+ ),
1403
+ ],
1404
+ id="latent-color-control",
1405
+ className="control latent-color-control",
1406
+ ),
1407
+ dcc.Graph(
1408
+ id="latent-space",
1409
+ config={
1410
+ "displayModeBar": "hover",
1411
+ "toImageButtonOptions": {
1412
+ "format": "png",
1413
+ "filename": "benchdash_latent_space",
1414
+ "height": 900,
1415
+ "width": 1200,
1416
+ "scale": 2,
1417
+ },
1418
+ },
1419
+ ),
1420
+ ],
1421
+ className="latent-panel",
1422
  ),
1423
  ],
1424
  className="latent-grid",
 
1524
  "method",
1525
  "task_score",
1526
  "robustness_auc",
 
1527
  "training_time_sec",
1528
  "peak_ram_gb",
1529
  "peak_vram_gb",
1530
  ]
1531
+ if str(dataset) in CONSISTENCY_DATASETS:
1532
+ visible_cols.insert(3, "alignment_score")
1533
+ sort_by = supported_sort(sort_by, visible_cols)
1534
  sorted_df = sort_table(df, sort_by, [("task_score", False), ("model_order", True)])
1535
  table_df = sorted_df[[c for c in visible_cols + ["id", "model"] if c in sorted_df.columns]]
1536
  metric = df["metric"].dropna().iloc[0] if df["metric"].notna().any() else "score"
 
1544
  )
1545
 
1546
 
1547
+ @app.callback(
1548
+ Output("latent-color-mode", "options"),
1549
+ Output("latent-color-mode", "value"),
1550
+ Output("latent-color-control", "style"),
1551
+ Input("dataset-filter", "value"),
1552
+ )
1553
+ def update_latent_color_control(dataset: str):
1554
+ if str(dataset) == "ratinabox":
1555
+ return (
1556
+ [
1557
+ {"label": "Position bin", "value": "condition"},
1558
+ {"label": "X position", "value": "x"},
1559
+ {"label": "Y position", "value": "y"},
1560
+ ],
1561
+ "condition",
1562
+ {},
1563
+ )
1564
+ return ([{"label": condition_axis_label(str(dataset)), "value": "condition"}], "condition", {"display": "none"})
1565
+
1566
+
1567
  @app.callback(
1568
  Output("consistency-table", "columns"),
1569
  Output("consistency-table", "data"),
 
1574
  Input("dataset-filter", "value"),
1575
  Input("consistency-table", "active_cell"),
1576
  Input("consistency-table", "sort_by"),
1577
+ Input("latent-color-mode", "value"),
1578
  )
1579
+ def update_consistency(
1580
+ dataset: str,
1581
+ active_cell: dict | None,
1582
+ sort_by: list[dict] | None,
1583
+ color_mode: str | None,
1584
+ ):
1585
+ if str(dataset) not in CONSISTENCY_DATASETS:
1586
+ label = DATASET_LABELS.get(dataset, dataset)
1587
+ message = f"Cross-session alignment is not defined for {label}."
1588
+ return (
1589
+ [],
1590
+ [],
1591
+ message,
1592
+ empty_figure(message),
1593
+ empty_figure(message),
1594
+ consistency_heatmap(None),
1595
+ )
1596
  df = consistency_frame(dataset, None)
1597
  model = selected_consistency_model(df, active_cell)
1598
  visible_cols = ["method", "alignment_score", "n_sessions", "latent_dim", "n_pairwise"]
1599
+ sort_by = supported_sort(sort_by, visible_cols)
1600
  sorted_df = sort_table(df, sort_by, [("alignment_score", False), ("model_order", True)])
1601
  table_df = sorted_df[[c for c in visible_cols + ["id", "model"] if c in sorted_df.columns]]
1602
  return (
1603
  column_defs([c for c in visible_cols if c in table_df.columns]),
1604
  records(table_df),
1605
  sort_status(sort_by, ("alignment_score", False)),
1606
+ latent_space_figure(dataset, model, color_mode or "condition"),
1607
  consistency_bar_figure(dataset, df),
1608
  consistency_heatmap(None),
1609
  )
assets/styles.css CHANGED
@@ -228,6 +228,15 @@ h2 {
228
  align-items: start;
229
  }
230
 
 
 
 
 
 
 
 
 
 
231
  .chart-grid {
232
  display: grid;
233
  gap: 16px;
 
228
  align-items: start;
229
  }
230
 
231
+ .latent-panel {
232
+ min-width: 0;
233
+ }
234
+
235
+ .latent-color-control {
236
+ width: min(260px, 100%);
237
+ margin-bottom: 8px;
238
+ }
239
+
240
  .chart-grid {
241
  display: grid;
242
  gap: 16px;
data/latent_samples.csv CHANGED
The diff for this file is too large to render. See raw diff