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

Add semantic latent trajectory views

Browse files
Files changed (2) hide show
  1. app.py +114 -9
  2. data/latent_trajectories.csv +0 -0
app.py CHANGED
@@ -67,6 +67,7 @@ scalability = load_csv("scalability_summary.csv")
67
  neuron_shap = load_csv("neuron_shap_summary.csv")
68
  trial_shapley = load_csv("trial_shapley_summary.csv")
69
  latent_samples = load_csv("latent_samples.csv")
 
70
 
71
 
72
  def present_rows(df: pd.DataFrame) -> pd.DataFrame:
@@ -104,6 +105,56 @@ DATASETS = ordered_unique(prediction.get("dataset", pd.Series(dtype=str)))
104
  MODELS = sorted(set(prediction.get("model", pd.Series(dtype=str)).dropna().astype(str)) | set(MODEL_INFO))
105
  DEFAULT_SELECTED_MODELS = [m for m in DEFAULT_MODELS if m in MODELS] or MODELS[:10]
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  def model_metadata_frame() -> pd.DataFrame:
109
  rows = []
@@ -256,10 +307,26 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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))
@@ -272,7 +339,10 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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",
@@ -292,7 +362,16 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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]
@@ -306,7 +385,11 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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"],
@@ -317,13 +400,14 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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
  ],
@@ -331,14 +415,34 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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(
@@ -359,6 +463,7 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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),
364
  ],
@@ -366,8 +471,8 @@ def latent_space_figure(dataset: str, models: list[str] | None) -> go.Figure:
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
  ),
 
67
  neuron_shap = load_csv("neuron_shap_summary.csv")
68
  trial_shapley = load_csv("trial_shapley_summary.csv")
69
  latent_samples = load_csv("latent_samples.csv")
70
+ latent_trajectories = load_csv("latent_trajectories.csv")
71
 
72
 
73
  def present_rows(df: pd.DataFrame) -> pd.DataFrame:
 
105
  MODELS = sorted(set(prediction.get("model", pd.Series(dtype=str)).dropna().astype(str)) | set(MODEL_INFO))
106
  DEFAULT_SELECTED_MODELS = [m for m in DEFAULT_MODELS if m in MODELS] or MODELS[:10]
107
 
108
+ CONDITION_LABELS = {
109
+ "monkey": {
110
+ "0": "Up",
111
+ "1": "Up-right",
112
+ "2": "Right",
113
+ "3": "Down-right",
114
+ "4": "Down",
115
+ "5": "Down-left",
116
+ "6": "Left",
117
+ "7": "Up-left",
118
+ },
119
+ "allen_neuropixels": {
120
+ str(i): f"{angle} deg"
121
+ for i, angle in enumerate([0, 45, 90, 135, 180, 225, 270, 315])
122
+ },
123
+ "speech": {
124
+ "0": "Do nothing",
125
+ "1": "ban",
126
+ "2": "choice",
127
+ "3": "day",
128
+ "4": "feel",
129
+ "5": "kite",
130
+ "6": "though",
131
+ "7": "were",
132
+ },
133
+ }
134
+
135
+
136
+ def condition_sort_key(value: object) -> tuple[int, float | str]:
137
+ try:
138
+ return (0, float(value))
139
+ except (TypeError, ValueError):
140
+ return (1, str(value))
141
+
142
+
143
+ def condition_label(dataset: str, condition: object) -> str:
144
+ if pd.isna(condition):
145
+ return "Unknown"
146
+ text = str(condition)
147
+ mapped = CONDITION_LABELS.get(dataset, {}).get(text)
148
+ if mapped is not None:
149
+ return mapped
150
+ if dataset == "ratinabox":
151
+ try:
152
+ idx = int(float(text))
153
+ return f"x{idx % 10}, y{idx // 10}"
154
+ except ValueError:
155
+ return text
156
+ return text
157
+
158
 
159
  def model_metadata_frame() -> pd.DataFrame:
160
  rows = []
 
307
  for col in ["x", "y", "z"]:
308
  plot_df[col] = pd.to_numeric(plot_df[col], errors="coerce")
309
  plot_df["condition_num"] = pd.to_numeric(plot_df["condition"], errors="coerce")
310
+ plot_df["condition_label"] = plot_df["condition"].map(
311
+ lambda value: condition_label(dataset, value)
312
+ )
313
  plot_df = plot_df.dropna(subset=["x", "y", "z"])
314
  if plot_df.empty:
315
  return empty_figure("Latent-space samples are empty after filtering.")
316
 
317
+ trajectory_df = latent_trajectories[
318
+ (latent_trajectories["dataset"].astype(str) == str(dataset))
319
+ & (latent_trajectories["model"].astype(str) == str(model))
320
+ ].copy()
321
+ for col in ["x", "y", "z"]:
322
+ if col in trajectory_df:
323
+ trajectory_df[col] = pd.to_numeric(trajectory_df[col], errors="coerce")
324
+ if not trajectory_df.empty:
325
+ trajectory_df["condition_label"] = trajectory_df["condition"].map(
326
+ lambda value: condition_label(dataset, value)
327
+ )
328
+ trajectory_df = trajectory_df.dropna(subset=["x", "y", "z"])
329
+
330
  sessions = ordered_unique(plot_df["session_label"])
331
  n_cols = 2
332
  n_rows = int(np.ceil(len(sessions) / n_cols))
 
339
  vertical_spacing=0.105,
340
  )
341
 
342
+ condition_values = sorted(
343
+ plot_df["condition"].astype(str).unique(),
344
+ key=condition_sort_key,
345
+ )
346
  use_categorical_conditions = len(condition_values) <= 12
347
  direction_palette = [
348
  "#B23AEE",
 
362
  condition: direction_palette[idx % len(direction_palette)]
363
  for idx, condition in enumerate(condition_values)
364
  }
365
+ if dataset == "monkey":
366
+ condition_prefix = "Reach direction"
367
+ elif dataset == "ratinabox":
368
+ condition_prefix = "Position bin"
369
+ elif dataset == "allen_neuropixels":
370
+ condition_prefix = "Orientation"
371
+ elif dataset == "speech":
372
+ condition_prefix = "Cue"
373
+ else:
374
+ condition_prefix = "Condition"
375
 
376
  for session_idx, session in enumerate(sessions):
377
  session_df = plot_df[plot_df["session_label"].astype(str) == session]
 
385
  cond_df = session_df[session_df["condition"].astype(str) == condition]
386
  if cond_df.empty:
387
  continue
388
+ trace_name = condition_label(dataset, condition)
389
+ session_traj = trajectory_df[
390
+ (trajectory_df["session_label"].astype(str) == session)
391
+ & (trajectory_df["condition"].astype(str) == condition)
392
+ ].sort_values("time_index")
393
  fig.add_trace(
394
  go.Scatter3d(
395
  x=cond_df["x"],
 
400
  legendgroup=condition,
401
  showlegend=session_idx == 0,
402
  marker=dict(
403
+ size=1.9 if not session_traj.empty else 2.6,
404
+ opacity=0.28 if not session_traj.empty else 0.72,
405
  color=condition_colors[condition],
406
  ),
407
  customdata=np.stack(
408
  [
409
  cond_df["session_label"].astype(str),
410
+ cond_df["condition_label"].astype(str),
411
  cond_df["trial_index"].astype(str),
412
  cond_df["time_index"].astype(str),
413
  ],
 
415
  ),
416
  hovertemplate=(
417
  "session=%{customdata[0]}<br>"
418
+ f"{condition_prefix.lower()}=%{{customdata[1]}}<br>"
419
+ "trial=%{customdata[2]} time=%{customdata[3]}"
420
  "<extra></extra>"
421
  ),
422
  ),
423
  row=row,
424
  col=col,
425
  )
426
+ if not session_traj.empty:
427
+ fig.add_trace(
428
+ go.Scatter3d(
429
+ x=session_traj["x"],
430
+ y=session_traj["y"],
431
+ z=session_traj["z"],
432
+ mode="lines",
433
+ name=trace_name,
434
+ legendgroup=condition,
435
+ showlegend=False,
436
+ line=dict(color=condition_colors[condition], width=5),
437
+ hovertemplate=(
438
+ f"{condition_prefix.lower()}={trace_name}<br>"
439
+ "time=%{customdata}<extra></extra>"
440
+ ),
441
+ customdata=session_traj["time_index"],
442
+ ),
443
+ row=row,
444
+ col=col,
445
+ )
446
  else:
447
  fig.add_trace(
448
  go.Scatter3d(
 
463
  customdata=np.stack(
464
  [
465
  session_df["condition"].astype(str),
466
+ session_df["condition"].map(lambda value: condition_label(dataset, value)).astype(str),
467
  session_df["trial_index"].astype(str),
468
  session_df["time_index"].astype(str),
469
  ],
 
471
  ),
472
  hovertemplate=(
473
  "session=%{fullData.name}<br>"
474
+ f"{condition_prefix.lower()}=%{{customdata[1]}}<br>"
475
+ "trial=%{customdata[2]} time=%{customdata[3]}"
476
  "<extra></extra>"
477
  ),
478
  ),
data/latent_trajectories.csv ADDED
The diff for this file is too large to render. See raw diff