josephsoo commited on
Commit
6e6636e
·
1 Parent(s): c3a9029

Stabilize latent hover and add full target spaces

Browse files
app.py CHANGED
@@ -193,7 +193,7 @@ CONDITION_LABELS = {
193
  for index, angle in enumerate([0, 45, 90, 135, 180, 225, 270, 315])
194
  },
195
  "speech": {
196
- "0": "Do nothing",
197
  "1": "ban",
198
  "2": "choice",
199
  "3": "day",
@@ -217,21 +217,17 @@ DIRECTION_PALETTE = [
217
  ]
218
  DIRECTION_LEGEND_ORDER = [2, 1, 0, 7, 6, 5, 4, 3]
219
  DIRECTION_LEGEND_LABELS = ["0°", "45°", "90°", "135°", "180°", "225°", "270°", "315°"]
220
- SPEECH_PALETTE = {
221
- "3": "#E69F00",
222
- "2": "#56B4E9",
223
- "4": "#009E73",
224
- "7": "#999933",
225
- "6": "#0072B2",
226
- "1": "#D55E00",
227
- "5": "#CC79A7",
228
- "0": "#000000",
229
- }
230
- ALLEN_PALETTE = {
231
- str(index): color
232
- for index, color in enumerate(
233
- ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7", "#000000"]
234
- )
235
  }
236
  RATINABOX_SCALE = [
237
  [0.0, "#440154"],
@@ -317,6 +313,7 @@ DOWNLOADABLE_FILES = {
317
  "dataset_overview.csv",
318
  "dataset_example_neural.csv",
319
  "dataset_example_targets.csv",
 
320
  "feature_example_raster.csv",
321
  "clean_prediction_summary.csv",
322
  "robustness_summary.csv",
@@ -348,11 +345,11 @@ TRIAL_HISTORICAL_TRAJECTORY_COLUMNS = [
348
  ]
349
 
350
 
351
- def load_csv(name: str) -> pd.DataFrame:
352
  path = DATA_DIR / name
353
  if not path.exists():
354
  raise FileNotFoundError(f"Missing dashboard data: {path}")
355
- return pd.read_csv(path)
356
 
357
 
358
  def load_historical_trajectories() -> pd.DataFrame:
@@ -418,6 +415,11 @@ def load_historical_trajectories() -> pd.DataFrame:
418
  dataset_overview = load_csv("dataset_overview.csv")
419
  dataset_example_neural = load_csv("dataset_example_neural.csv")
420
  dataset_example_targets = load_csv("dataset_example_targets.csv")
 
 
 
 
 
421
  feature_example_raster = load_csv("feature_example_raster.csv")
422
  prediction = load_csv("clean_prediction_summary.csv")
423
  robustness = load_csv("robustness_summary.csv")
@@ -888,14 +890,260 @@ def dataset_cards(selected_dataset: str) -> list[html.Article]:
888
  return cards
889
 
890
 
891
- def dataset_example_figures(dataset: str) -> tuple[go.Figure, go.Figure, str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  metadata = dataset_overview[dataset_overview["dataset"].astype(str).eq(dataset)].iloc[0]
893
  neural = dataset_example_neural[
894
  dataset_example_neural["dataset"].astype(str).eq(dataset)
895
  ].copy()
896
- target = dataset_example_targets[
897
- dataset_example_targets["dataset"].astype(str).eq(dataset)
898
- ].copy()
899
  for column in (
900
  "time_index",
901
  "time_ms",
@@ -951,101 +1199,15 @@ def dataset_example_figures(dataset: str) -> tuple[go.Figure, go.Figure, str]:
951
  )
952
  )
953
  neural_figure.add_vline(x=0, line_color="#D55E00", line_dash="dash")
954
- neural_figure.update_layout(title="Neural activity")
955
  neural_figure.update_xaxes(title="Time from scoring onset (ms)")
956
  neural_figure.update_yaxes(title="Neural features", showticklabels=False)
957
  figure_layout(neural_figure, height=470)
 
958
 
959
  if dataset in {"allen_neuropixels", "speech"}:
960
- label = str(target["target_label"].iloc[0])
961
- target_figure = go.Figure()
962
- if dataset == "allen_neuropixels":
963
- angle = np.deg2rad(float(label.split("°")[0]))
964
- x = np.asarray([-np.cos(angle), np.cos(angle)])
965
- y = np.asarray([-np.sin(angle), np.sin(angle)])
966
- target_figure.add_trace(
967
- go.Scatter(
968
- x=x,
969
- y=y,
970
- mode="lines",
971
- line=dict(color="#E69F00", width=12),
972
- hoverinfo="skip",
973
- )
974
- )
975
- target_figure.update_xaxes(visible=False, range=[-1.25, 1.25])
976
- target_figure.update_yaxes(
977
- visible=False,
978
- range=[-1.25, 1.25],
979
- scaleanchor="x",
980
- scaleratio=1,
981
- )
982
- target_figure.add_annotation(
983
- text=label,
984
- x=0.5,
985
- y=0.08,
986
- xref="paper",
987
- yref="paper",
988
- showarrow=False,
989
- font=dict(size=18),
990
- )
991
- target_figure.update_layout(title="Target orientation")
992
- else:
993
- target_figure.add_annotation(
994
- text=label,
995
- x=0.5,
996
- y=0.52,
997
- xref="paper",
998
- yref="paper",
999
- showarrow=False,
1000
- font=dict(size=36, color=FEATURE_COLOR),
1001
- )
1002
- target_figure.update_xaxes(visible=False)
1003
- target_figure.update_yaxes(visible=False)
1004
- target_figure.update_layout(title="Attempted-word target")
1005
- figure_layout(target_figure, height=470)
1006
  else:
1007
- for column in ("time_ms", "target_0", "target_1"):
1008
- target[column] = pd.to_numeric(target[column], errors="coerce")
1009
- if dataset == "mc_pacman":
1010
- target_figure = go.Figure(
1011
- go.Scatter(
1012
- x=target["time_ms"],
1013
- y=target["target_0"],
1014
- mode="lines",
1015
- line=dict(color="#D55E00", width=3),
1016
- hovertemplate="Time=%{x:.0f} ms<br>Force=%{y:.4f}<extra></extra>",
1017
- )
1018
- )
1019
- target_figure.add_vline(x=0, line_color="#71808D", line_dash="dash")
1020
- target_figure.update_layout(title="Target force")
1021
- target_figure.update_xaxes(title="Time from scoring onset (ms)")
1022
- target_figure.update_yaxes(title="Force")
1023
- else:
1024
- target_figure = go.Figure(
1025
- go.Scatter(
1026
- x=target["target_0"],
1027
- y=target["target_1"],
1028
- mode="lines+markers",
1029
- line=dict(color=PREDICTION_COLOR, width=3),
1030
- marker=dict(
1031
- size=5,
1032
- color=target["time_ms"],
1033
- colorscale="Viridis",
1034
- showscale=True,
1035
- colorbar=dict(title="Time (ms)", thickness=13),
1036
- ),
1037
- customdata=target["time_ms"],
1038
- hovertemplate=(
1039
- "x=%{x:.3f}<br>y=%{y:.3f}<br>"
1040
- "Time=%{customdata:.0f} ms<extra></extra>"
1041
- ),
1042
- )
1043
- )
1044
- title = "Target hand position" if dataset == "monkey" else "Target position"
1045
- target_figure.update_layout(title=title)
1046
- target_figure.update_xaxes(title="x", scaleanchor="y", scaleratio=1)
1047
- target_figure.update_yaxes(title="y")
1048
- figure_layout(target_figure, height=470)
1049
 
1050
  shown = int(metadata.example_features_shown)
1051
  total = int(metadata.array_shape.split("×")[-1].strip())
@@ -1054,8 +1216,11 @@ def dataset_example_figures(dataset: str) -> tuple[go.Figure, go.Figure, str]:
1054
  if shown == total
1055
  else f"{shown} of {total} neural features are shown for legibility"
1056
  )
1057
- description = f"One example trial; {feature_text}."
1058
- return neural_figure, target_figure, description
 
 
 
1059
 
1060
 
1061
  def overview_cards(dataset: str, models: Sequence[str] | None) -> list[html.Div]:
@@ -2231,14 +2396,11 @@ def latent_space_figure(
2231
  points["color_label"],
2232
  points["trial_index"],
2233
  points["time_index"],
 
2234
  ],
2235
  axis=-1,
2236
  ),
2237
- hovertemplate=(
2238
- "Recording=%{customdata[0]}<br>"
2239
- f"{condition_name}=%{{customdata[1]}}<br>Trial=%{{customdata[2]}}; time bin=%{{customdata[3]}}"
2240
- "<extra></extra>"
2241
- ),
2242
  ),
2243
  row=row_index,
2244
  col=column_index,
@@ -2254,7 +2416,7 @@ def latent_space_figure(
2254
  legendgroup=condition,
2255
  showlegend=False,
2256
  line=dict(color=colors[condition], width=5),
2257
- hovertemplate=f"{condition_name}={label}<br>Time bin=%{{customdata}}<extra></extra>",
2258
  customdata=means["time_index"],
2259
  ),
2260
  row=row_index,
@@ -2285,14 +2447,11 @@ def latent_space_figure(
2285
  session_samples["color_label"],
2286
  session_samples["trial_index"],
2287
  session_samples["time_index"],
 
2288
  ],
2289
  axis=-1,
2290
  ),
2291
- hovertemplate=(
2292
- "Recording=%{customdata[0]}<br>"
2293
- f"{condition_name}=%{{customdata[1]}}<br>Trial=%{{customdata[2]}}; time bin=%{{customdata[3]}}"
2294
- "<extra></extra>"
2295
- ),
2296
  ),
2297
  row=row_index,
2298
  col=column_index,
@@ -2598,30 +2757,41 @@ app.layout = html.Div(
2598
  class_name="axis-datasets",
2599
  ),
2600
  panel(
2601
- "Example benchmark input and target",
2602
  html.Div(
2603
  [
2604
- graph_box(
2605
- "dataset-neural-example",
2606
- "Example trial neural activity for the selected dataset.",
 
 
 
 
 
 
 
 
 
 
 
 
2607
  ),
2608
- graph_box(
2609
- "dataset-target-example",
2610
- "Paired task target for the selected example trial.",
2611
  ),
2612
  ],
2613
- className="chart-grid two",
2614
  ),
2615
  html.Div(id="dataset-example-description", className="dataset-example-note"),
2616
  html.Div(
2617
  [
2618
  source_link("dataset_overview.csv", "Dataset manifest"),
2619
  source_link("dataset_example_neural.csv", "Example neural data"),
2620
- source_link("dataset_example_targets.csv", "Example targets"),
2621
  ],
2622
  className="download-grid panel-downloads",
2623
  ),
2624
- subtitle="Examples are drawn from the preprocessed arrays used in the benchmark.",
2625
  class_name="axis-datasets",
2626
  ),
2627
  ],
@@ -2703,6 +2873,12 @@ app.layout = html.Div(
2703
  ],
2704
  className="inline-controls",
2705
  ),
 
 
 
 
 
 
2706
  graph_box(
2707
  "latent-space",
2708
  "Aligned latent representations for each recording.",
@@ -2890,10 +3066,45 @@ app.layout = html.Div(
2890
  )
2891
 
2892
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2893
  @app.callback(
2894
  Output("dataset-cards", "children"),
2895
  Output("dataset-neural-example", "figure"),
2896
- Output("dataset-target-example", "figure"),
2897
  Output("dataset-example-description", "children"),
2898
  Input("dataset-filter", "value"),
2899
  )
 
193
  for index, angle in enumerate([0, 45, 90, 135, 180, 225, 270, 315])
194
  },
195
  "speech": {
196
+ "0": "rest",
197
  "1": "ban",
198
  "2": "choice",
199
  "3": "day",
 
217
  ]
218
  DIRECTION_LEGEND_ORDER = [2, 1, 0, 7, 6, 5, 4, 3]
219
  DIRECTION_LEGEND_LABELS = ["0°", "45°", "90°", "135°", "180°", "225°", "270°", "315°"]
220
+ SPEECH_PALETTE = {str(index): color for index, color in enumerate(DIRECTION_PALETTE)}
221
+ ALLEN_PALETTE = {str(index): color for index, color in enumerate(DIRECTION_PALETTE)}
222
+ MC_PROFILE_LABELS = {
223
+ 1: "Slow ascending ramp",
224
+ 2: "Slow descending ramp",
225
+ 3: "Fast ascending ramp",
226
+ 4: "Fast descending ramp",
227
+ 5: "Sine, 0.25 Hz",
228
+ 6: "Sine, 1 Hz",
229
+ 7: "Sine, 3 Hz",
230
+ 8: "Chirp, 0–3 Hz",
 
 
 
 
231
  }
232
  RATINABOX_SCALE = [
233
  [0.0, "#440154"],
 
313
  "dataset_overview.csv",
314
  "dataset_example_neural.csv",
315
  "dataset_example_targets.csv",
316
+ "dataset_targets.csv",
317
  "feature_example_raster.csv",
318
  "clean_prediction_summary.csv",
319
  "robustness_summary.csv",
 
345
  ]
346
 
347
 
348
+ def load_csv(name: str, **read_csv_kwargs) -> pd.DataFrame:
349
  path = DATA_DIR / name
350
  if not path.exists():
351
  raise FileNotFoundError(f"Missing dashboard data: {path}")
352
+ return pd.read_csv(path, **read_csv_kwargs)
353
 
354
 
355
  def load_historical_trajectories() -> pd.DataFrame:
 
415
  dataset_overview = load_csv("dataset_overview.csv")
416
  dataset_example_neural = load_csv("dataset_example_neural.csv")
417
  dataset_example_targets = load_csv("dataset_example_targets.csv")
418
+ dataset_targets = load_csv(
419
+ "dataset_targets.csv",
420
+ dtype={"target_label": "string"},
421
+ low_memory=False,
422
+ )
423
  feature_example_raster = load_csv("feature_example_raster.csv")
424
  prediction = load_csv("clean_prediction_summary.csv")
425
  robustness = load_csv("robustness_summary.csv")
 
890
  return cards
891
 
892
 
893
+ def target_space_graph(figure: go.Figure, label: str) -> html.Div:
894
+ return html.Div(
895
+ dcc.Graph(
896
+ figure=figure,
897
+ config={"displaylogo": False, "responsive": True},
898
+ ),
899
+ className="target-space-graph",
900
+ role="img",
901
+ **{"aria-label": label},
902
+ )
903
+
904
+
905
+ def target_space_legend(items: Sequence[tuple[str, str]]) -> html.Div:
906
+ return html.Div(
907
+ [
908
+ html.Span(
909
+ [
910
+ html.Span(
911
+ className="target-legend-swatch",
912
+ style={"backgroundColor": color},
913
+ ),
914
+ label,
915
+ ],
916
+ className="target-legend-item",
917
+ )
918
+ for label, color in items
919
+ ],
920
+ className="target-space-legend",
921
+ )
922
+
923
+
924
+ def target_class_space(dataset: str, frame: pd.DataFrame) -> html.Div:
925
+ counts = frame.groupby("condition_id", sort=True).size()
926
+ example_class = int(frame.loc[frame["is_example"], "condition_id"].iloc[0])
927
+ cards = []
928
+ for raw_class, count in counts.items():
929
+ class_id = int(raw_class)
930
+ color = DIRECTION_PALETTE[class_id % len(DIRECTION_PALETTE)]
931
+ label = condition_label(dataset, class_id)
932
+ selected = class_id == example_class
933
+ if dataset == "allen_neuropixels":
934
+ glyph = html.Div(
935
+ [html.Span(className="orientation-arrow-head")],
936
+ className="orientation-arrow",
937
+ style={
938
+ "backgroundColor": color,
939
+ "color": color,
940
+ "transform": f"rotate({-class_id * 45}deg)",
941
+ },
942
+ )
943
+ else:
944
+ glyph = html.Div(
945
+ label,
946
+ className="speech-target-word",
947
+ style={"color": color},
948
+ )
949
+ cards.append(
950
+ html.Div(
951
+ [
952
+ html.Div(glyph, className="target-class-glyph"),
953
+ (
954
+ html.Div(label, className="target-class-label")
955
+ if dataset == "allen_neuropixels"
956
+ else None
957
+ ),
958
+ html.Div(f"{int(count)} trials", className="target-class-count"),
959
+ html.Span("Example", className="target-example-badge") if selected else None,
960
+ ],
961
+ className=(
962
+ "target-class-card target-class-card-selected"
963
+ if selected
964
+ else "target-class-card"
965
+ ),
966
+ style={"borderTopColor": color},
967
+ )
968
+ )
969
+ context = (
970
+ "Eight stimulus orientations"
971
+ if dataset == "allen_neuropixels"
972
+ else "Seven attempted words and rest"
973
+ )
974
+ return html.Div(
975
+ [
976
+ html.Div(
977
+ [
978
+ html.Strong("Target space"),
979
+ html.Span(f"{len(frame):,} trials · {context}"),
980
+ ],
981
+ className="dataset-viz-heading",
982
+ ),
983
+ html.Div(cards, className="target-class-grid"),
984
+ ],
985
+ className="target-space-content",
986
+ )
987
+
988
+
989
+ def separated_trajectory_values(
990
+ frame: pd.DataFrame,
991
+ x_column: str,
992
+ y_column: str,
993
+ ) -> tuple[list[float | None], list[float | None]]:
994
+ x_values: list[float | None] = []
995
+ y_values: list[float | None] = []
996
+ for _, trial in frame.groupby("trial_index", sort=False):
997
+ trial = trial.sort_values("time_index")
998
+ x_values.extend(trial[x_column].astype(float).tolist())
999
+ y_values.extend(trial[y_column].astype(float).tolist())
1000
+ x_values.append(None)
1001
+ y_values.append(None)
1002
+ return x_values, y_values
1003
+
1004
+
1005
+ def target_trajectory_space(dataset: str, frame: pd.DataFrame) -> html.Div:
1006
+ for column in ["trial_index", "condition_id", "time_index", "time_ms", "target_0", "target_1"]:
1007
+ frame[column] = pd.to_numeric(frame[column], errors="coerce")
1008
+ example = frame[frame["is_example"]].sort_values("time_index")
1009
+ figure = go.Figure()
1010
+ legend_items: list[tuple[str, str]] = []
1011
+
1012
+ if dataset == "monkey":
1013
+ for condition_id, group in frame.groupby("condition_id", sort=True):
1014
+ condition_id = int(condition_id)
1015
+ color = DIRECTION_PALETTE[condition_id % len(DIRECTION_PALETTE)]
1016
+ x_values, y_values = separated_trajectory_values(group, "target_0", "target_1")
1017
+ figure.add_trace(
1018
+ go.Scattergl(
1019
+ x=x_values,
1020
+ y=y_values,
1021
+ mode="lines",
1022
+ line=dict(color=color, width=1),
1023
+ opacity=0.16,
1024
+ hoverinfo="skip",
1025
+ showlegend=False,
1026
+ )
1027
+ )
1028
+ legend_items.append((condition_label(dataset, condition_id), color))
1029
+ figure.add_trace(
1030
+ go.Scattergl(
1031
+ x=example["target_0"],
1032
+ y=example["target_1"],
1033
+ mode="lines",
1034
+ line=dict(color="#102A3A", width=4),
1035
+ customdata=example["time_ms"],
1036
+ hovertemplate="Horizontal position=%{x:.3f}<br>Vertical position=%{y:.3f}<br>Time=%{customdata:.0f} ms<extra>Example</extra>",
1037
+ showlegend=False,
1038
+ )
1039
+ )
1040
+ figure.update_xaxes(title="Horizontal hand position")
1041
+ figure.update_yaxes(title="Vertical hand position", scaleanchor="x", scaleratio=1)
1042
+ caption = f"{frame['trial_index'].nunique():,} hand trajectories · color = reach direction"
1043
+ aria_label = "All macaque hand-position targets, colored by reach direction."
1044
+ elif dataset == "mc_pacman":
1045
+ for condition_id, group in frame.groupby("condition_id", sort=True):
1046
+ condition_id = int(condition_id)
1047
+ color = DIRECTION_PALETTE[condition_id % len(DIRECTION_PALETTE)]
1048
+ x_values, y_values = separated_trajectory_values(group, "time_ms", "target_0")
1049
+ figure.add_trace(
1050
+ go.Scattergl(
1051
+ x=x_values,
1052
+ y=y_values,
1053
+ mode="lines",
1054
+ line=dict(color=color, width=0.8),
1055
+ opacity=0.075,
1056
+ hoverinfo="skip",
1057
+ showlegend=False,
1058
+ )
1059
+ )
1060
+ mean_profile = group.groupby("time_ms", sort=True)["target_0"].mean()
1061
+ figure.add_trace(
1062
+ go.Scatter(
1063
+ x=mean_profile.index,
1064
+ y=mean_profile.values,
1065
+ mode="lines",
1066
+ line=dict(color=color, width=2.2),
1067
+ hovertemplate=f"{MC_PROFILE_LABELS[condition_id]}<br>Time=%{{x:.0f}} ms<br>Mean force=%{{y:.3f}}<extra></extra>",
1068
+ showlegend=False,
1069
+ )
1070
+ )
1071
+ legend_items.append((MC_PROFILE_LABELS[condition_id], color))
1072
+ figure.add_trace(
1073
+ go.Scatter(
1074
+ x=example["time_ms"],
1075
+ y=example["target_0"],
1076
+ mode="lines",
1077
+ line=dict(color="#102A3A", width=3.5),
1078
+ hovertemplate="Time=%{x:.0f} ms<br>Force=%{y:.3f}<extra>Example</extra>",
1079
+ showlegend=False,
1080
+ )
1081
+ )
1082
+ figure.add_vline(x=0, line_color="#71808D", line_dash="dash")
1083
+ figure.update_xaxes(title="Time from scoring onset (ms)")
1084
+ figure.update_yaxes(title="Force")
1085
+ caption = f"{frame['trial_index'].nunique():,} force trajectories · color = force profile"
1086
+ aria_label = "All force targets, colored by force profile."
1087
+ else:
1088
+ figure.add_trace(
1089
+ go.Histogram2d(
1090
+ x=frame["target_0"],
1091
+ y=frame["target_1"],
1092
+ nbinsx=44,
1093
+ nbinsy=44,
1094
+ colorscale=RATINABOX_SCALE,
1095
+ showscale=False,
1096
+ hovertemplate="x=%{x:.3f}<br>y=%{y:.3f}<br>Samples=%{z}<extra></extra>",
1097
+ )
1098
+ )
1099
+ figure.add_trace(
1100
+ go.Scatter(
1101
+ x=example["target_0"],
1102
+ y=example["target_1"],
1103
+ mode="lines",
1104
+ line=dict(color="#FFFFFF", width=5),
1105
+ hoverinfo="skip",
1106
+ showlegend=False,
1107
+ )
1108
+ )
1109
+ figure.add_trace(
1110
+ go.Scatter(
1111
+ x=example["target_0"],
1112
+ y=example["target_1"],
1113
+ mode="lines",
1114
+ line=dict(color="#102A3A", width=2.5),
1115
+ customdata=example["time_ms"],
1116
+ hovertemplate="x=%{x:.3f}<br>y=%{y:.3f}<br>Time=%{customdata:.0f} ms<extra>Example</extra>",
1117
+ showlegend=False,
1118
+ )
1119
+ )
1120
+ figure.update_xaxes(title="x position", range=[0, 1])
1121
+ figure.update_yaxes(title="y position", range=[0, 1], scaleanchor="x", scaleratio=1)
1122
+ caption = f"{len(frame):,} position samples · color = occupancy density"
1123
+ aria_label = "All simulated position targets shown as spatial occupancy density."
1124
+
1125
+ figure_layout(figure, height=410)
1126
+ figure.update_layout(margin=dict(l=62, r=24, t=18, b=62), showlegend=False)
1127
+ return html.Div(
1128
+ [
1129
+ html.Div(
1130
+ [html.Strong("Target space"), html.Span(caption)],
1131
+ className="dataset-viz-heading",
1132
+ ),
1133
+ target_space_graph(figure, aria_label),
1134
+ target_space_legend(legend_items) if legend_items else None,
1135
+ ],
1136
+ className="target-space-content",
1137
+ )
1138
+
1139
+
1140
+ def dataset_example_figures(dataset: str) -> tuple[go.Figure, html.Div, str]:
1141
  metadata = dataset_overview[dataset_overview["dataset"].astype(str).eq(dataset)].iloc[0]
1142
  neural = dataset_example_neural[
1143
  dataset_example_neural["dataset"].astype(str).eq(dataset)
1144
  ].copy()
1145
+ targets = dataset_targets[dataset_targets["dataset"].astype(str).eq(dataset)].copy()
1146
+ targets["is_example"] = targets["is_example"].astype(str).str.lower().eq("true")
 
1147
  for column in (
1148
  "time_index",
1149
  "time_ms",
 
1199
  )
1200
  )
1201
  neural_figure.add_vline(x=0, line_color="#D55E00", line_dash="dash")
 
1202
  neural_figure.update_xaxes(title="Time from scoring onset (ms)")
1203
  neural_figure.update_yaxes(title="Neural features", showticklabels=False)
1204
  figure_layout(neural_figure, height=470)
1205
+ neural_figure.update_layout(margin=dict(l=58, r=62, t=18, b=62))
1206
 
1207
  if dataset in {"allen_neuropixels", "speech"}:
1208
+ target_component = target_class_space(dataset, targets)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1209
  else:
1210
+ target_component = target_trajectory_space(dataset, targets)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1211
 
1212
  shown = int(metadata.example_features_shown)
1213
  total = int(metadata.array_shape.split("×")[-1].strip())
 
1216
  if shown == total
1217
  else f"{shown} of {total} neural features are shown for legibility"
1218
  )
1219
+ description = (
1220
+ f"The highlighted target corresponds to the neural activity shown at left; "
1221
+ f"{feature_text}."
1222
+ )
1223
+ return neural_figure, target_component, description
1224
 
1225
 
1226
  def overview_cards(dataset: str, models: Sequence[str] | None) -> list[html.Div]:
 
2396
  points["color_label"],
2397
  points["trial_index"],
2398
  points["time_index"],
2399
+ np.repeat(condition_name, len(points)),
2400
  ],
2401
  axis=-1,
2402
  ),
2403
+ hoverinfo="none",
 
 
 
 
2404
  ),
2405
  row=row_index,
2406
  col=column_index,
 
2416
  legendgroup=condition,
2417
  showlegend=False,
2418
  line=dict(color=colors[condition], width=5),
2419
+ hoverinfo="skip",
2420
  customdata=means["time_index"],
2421
  ),
2422
  row=row_index,
 
2447
  session_samples["color_label"],
2448
  session_samples["trial_index"],
2449
  session_samples["time_index"],
2450
+ np.repeat(condition_name, len(session_samples)),
2451
  ],
2452
  axis=-1,
2453
  ),
2454
+ hoverinfo="none",
 
 
 
 
2455
  ),
2456
  row=row_index,
2457
  col=column_index,
 
2757
  class_name="axis-datasets",
2758
  ),
2759
  panel(
2760
+ "Neural activity and task targets",
2761
  html.Div(
2762
  [
2763
+ html.Div(
2764
+ [
2765
+ html.Div(
2766
+ [
2767
+ html.Strong("Example neural activity"),
2768
+ html.Span("One benchmark trial"),
2769
+ ],
2770
+ className="dataset-viz-heading",
2771
+ ),
2772
+ graph_box(
2773
+ "dataset-neural-example",
2774
+ "Example trial neural activity for the selected dataset.",
2775
+ ),
2776
+ ],
2777
+ className="dataset-example-card",
2778
  ),
2779
+ html.Div(
2780
+ id="dataset-target-space",
2781
+ className="dataset-example-card",
2782
  ),
2783
  ],
2784
+ className="dataset-example-grid",
2785
  ),
2786
  html.Div(id="dataset-example-description", className="dataset-example-note"),
2787
  html.Div(
2788
  [
2789
  source_link("dataset_overview.csv", "Dataset manifest"),
2790
  source_link("dataset_example_neural.csv", "Example neural data"),
2791
+ source_link("dataset_targets.csv", "All targets"),
2792
  ],
2793
  className="download-grid panel-downloads",
2794
  ),
 
2795
  class_name="axis-datasets",
2796
  ),
2797
  ],
 
2873
  ],
2874
  className="inline-controls",
2875
  ),
2876
+ html.Div(
2877
+ "Hover a point to inspect its recording, condition, trial and time bin.",
2878
+ id="latent-hover-detail",
2879
+ className="latent-hover-detail",
2880
+ **{"aria-live": "polite"},
2881
+ ),
2882
  graph_box(
2883
  "latent-space",
2884
  "Aligned latent representations for each recording.",
 
3066
  )
3067
 
3068
 
3069
+ app.clientside_callback(
3070
+ """
3071
+ function(desktopHover, desktopClick, mobileHover, mobileClick) {
3072
+ const fallback = "Hover a point to inspect its recording, condition, trial and time bin.";
3073
+ const triggered = dash_clientside.callback_context.triggered[0];
3074
+ if (!triggered) {
3075
+ return fallback;
3076
+ }
3077
+ const prop = triggered.prop_id || "";
3078
+ let eventData = null;
3079
+ if (prop.startsWith("latent-space-mobile.clickData")) {
3080
+ eventData = mobileClick;
3081
+ } else if (prop.startsWith("latent-space-mobile.hoverData")) {
3082
+ eventData = mobileHover;
3083
+ } else if (prop.startsWith("latent-space.clickData")) {
3084
+ eventData = desktopClick;
3085
+ } else {
3086
+ eventData = desktopHover;
3087
+ }
3088
+ const point = eventData && eventData.points && eventData.points[0];
3089
+ const values = point && point.customdata;
3090
+ if (!Array.isArray(values) || values.length < 5) {
3091
+ return fallback;
3092
+ }
3093
+ return `${values[0]} · ${values[4]}: ${values[1]} · Trial ${values[2]} · Time bin ${values[3]}`;
3094
+ }
3095
+ """,
3096
+ Output("latent-hover-detail", "children"),
3097
+ Input("latent-space", "hoverData"),
3098
+ Input("latent-space", "clickData"),
3099
+ Input("latent-space-mobile", "hoverData"),
3100
+ Input("latent-space-mobile", "clickData"),
3101
+ )
3102
+
3103
+
3104
  @app.callback(
3105
  Output("dataset-cards", "children"),
3106
  Output("dataset-neural-example", "figure"),
3107
+ Output("dataset-target-space", "children"),
3108
  Output("dataset-example-description", "children"),
3109
  Input("dataset-filter", "value"),
3110
  )
assets/styles.css CHANGED
@@ -465,6 +465,164 @@ h2 {
465
  content: "↗";
466
  }
467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
  .dataset-example-note {
469
  margin-top: 12px;
470
  color: #637581;
@@ -607,6 +765,21 @@ h2 {
607
  margin-top: 14px !important;
608
  }
609
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
610
  .latent-space-mobile,
611
  .latent-mobile-legend {
612
  display: none;
@@ -977,6 +1150,10 @@ h2 {
977
  grid-template-columns: 1fr;
978
  }
979
 
 
 
 
 
980
  }
981
 
982
  @media (max-width: 820px) {
@@ -1054,6 +1231,21 @@ h2 {
1054
  }
1055
 
1056
  @media (max-width: 560px) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
  .heatmap-graph {
1058
  display: none;
1059
  }
 
465
  content: "↗";
466
  }
467
 
468
+ .dataset-example-grid {
469
+ display: grid;
470
+ grid-template-columns: repeat(2, minmax(0, 1fr));
471
+ gap: 18px;
472
+ align-items: stretch;
473
+ }
474
+
475
+ .dataset-example-card {
476
+ min-width: 0;
477
+ overflow: hidden;
478
+ border: 1px solid #e0e7eb;
479
+ border-radius: 9px;
480
+ background: #ffffff;
481
+ }
482
+
483
+ .dataset-viz-heading {
484
+ display: flex;
485
+ min-height: 58px;
486
+ align-items: baseline;
487
+ justify-content: space-between;
488
+ gap: 14px;
489
+ padding: 15px 16px 11px;
490
+ border-bottom: 1px solid #edf1f4;
491
+ }
492
+
493
+ .dataset-viz-heading strong {
494
+ color: #203442;
495
+ font-size: 14px;
496
+ font-weight: 780;
497
+ }
498
+
499
+ .dataset-viz-heading span {
500
+ color: #6c7d88;
501
+ font-size: 10px;
502
+ font-weight: 650;
503
+ text-align: right;
504
+ }
505
+
506
+ .dataset-example-card .graph-box,
507
+ .target-space-graph {
508
+ overflow: visible;
509
+ border: 0;
510
+ border-radius: 0;
511
+ }
512
+
513
+ .target-class-grid {
514
+ display: grid;
515
+ grid-template-columns: repeat(4, minmax(0, 1fr));
516
+ gap: 10px;
517
+ padding: 16px;
518
+ }
519
+
520
+ .target-class-card {
521
+ position: relative;
522
+ display: flex;
523
+ min-width: 0;
524
+ min-height: 154px;
525
+ flex-direction: column;
526
+ align-items: center;
527
+ justify-content: center;
528
+ gap: 8px;
529
+ padding: 18px 8px 12px;
530
+ border: 1px solid #e1e7eb;
531
+ border-top: 4px solid;
532
+ border-radius: 8px;
533
+ background: #fbfcfd;
534
+ text-align: center;
535
+ }
536
+
537
+ .target-class-card-selected {
538
+ border-color: #183a4e;
539
+ box-shadow: 0 0 0 2px #183a4e inset;
540
+ background: #ffffff;
541
+ }
542
+
543
+ .target-class-glyph {
544
+ display: flex;
545
+ min-height: 50px;
546
+ align-items: center;
547
+ justify-content: center;
548
+ }
549
+
550
+ .orientation-arrow {
551
+ position: relative;
552
+ width: 48px;
553
+ height: 5px;
554
+ border-radius: 999px;
555
+ }
556
+
557
+ .orientation-arrow-head {
558
+ position: absolute;
559
+ top: 50%;
560
+ right: -1px;
561
+ width: 0;
562
+ height: 0;
563
+ border-top: 7px solid transparent;
564
+ border-bottom: 7px solid transparent;
565
+ border-left: 11px solid currentColor;
566
+ color: inherit;
567
+ transform: translate(5px, -50%);
568
+ }
569
+
570
+ .speech-target-word {
571
+ color: #213746;
572
+ font-size: 17px;
573
+ font-weight: 780;
574
+ }
575
+
576
+ .target-class-label {
577
+ color: #253b49;
578
+ font-size: 12px;
579
+ font-weight: 760;
580
+ }
581
+
582
+ .target-class-count {
583
+ color: #74848f;
584
+ font-size: 10px;
585
+ }
586
+
587
+ .target-example-badge {
588
+ position: absolute;
589
+ top: 7px;
590
+ right: 7px;
591
+ padding: 2px 5px;
592
+ border-radius: 999px;
593
+ background: #183a4e;
594
+ color: #ffffff;
595
+ font-size: 8px;
596
+ font-weight: 800;
597
+ letter-spacing: 0.035em;
598
+ text-transform: uppercase;
599
+ }
600
+
601
+ .target-space-legend {
602
+ display: grid;
603
+ grid-template-columns: repeat(4, minmax(0, 1fr));
604
+ gap: 6px 12px;
605
+ padding: 4px 16px 15px;
606
+ }
607
+
608
+ .target-legend-item {
609
+ display: flex;
610
+ min-width: 0;
611
+ gap: 6px;
612
+ align-items: flex-start;
613
+ color: #596b77;
614
+ font-size: 9px;
615
+ line-height: 1.25;
616
+ }
617
+
618
+ .target-legend-swatch {
619
+ width: 9px;
620
+ height: 9px;
621
+ flex: 0 0 auto;
622
+ margin-top: 1px;
623
+ border-radius: 50%;
624
+ }
625
+
626
  .dataset-example-note {
627
  margin-top: 12px;
628
  color: #637581;
 
765
  margin-top: 14px !important;
766
  }
767
 
768
+ .latent-hover-detail {
769
+ display: flex;
770
+ min-height: 40px;
771
+ align-items: center;
772
+ margin-top: 12px;
773
+ padding: 8px 12px;
774
+ border: 1px solid #d7e4e8;
775
+ border-radius: 7px;
776
+ background: #f4f9fa;
777
+ color: #38515e;
778
+ font-size: 12px;
779
+ font-weight: 650;
780
+ line-height: 1.35;
781
+ }
782
+
783
  .latent-space-mobile,
784
  .latent-mobile-legend {
785
  display: none;
 
1150
  grid-template-columns: 1fr;
1151
  }
1152
 
1153
+ .dataset-example-grid {
1154
+ grid-template-columns: 1fr;
1155
+ }
1156
+
1157
  }
1158
 
1159
  @media (max-width: 820px) {
 
1231
  }
1232
 
1233
  @media (max-width: 560px) {
1234
+ .dataset-viz-heading {
1235
+ align-items: flex-start;
1236
+ flex-direction: column;
1237
+ gap: 2px;
1238
+ }
1239
+
1240
+ .dataset-viz-heading span {
1241
+ text-align: left;
1242
+ }
1243
+
1244
+ .target-class-grid,
1245
+ .target-space-legend {
1246
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1247
+ }
1248
+
1249
  .heatmap-graph {
1250
  display: none;
1251
  }
data/dataset_targets.csv ADDED
The diff for this file is too large to render. See raw diff
 
data/release_manifest.json CHANGED
@@ -6,7 +6,7 @@
6
  "manuscript_working_version": "manuscript_v7",
7
  "schema_version": 1,
8
  "source": "paper/results, active consistency and feature-attribution artifacts, benchmark dataset arrays, and Figure 5 prediction sidecars",
9
- "source_git_revision": "8524efc5a3a40f6ec43c3ab626424ac4597f49cf",
10
  "source_repository": "https://github.com/TangLab-UBC/behavior_benchmarking",
11
  "source_worktree_dirty": true,
12
  "tables": {
@@ -30,6 +30,10 @@
30
  "rows": 5,
31
  "sha256": "b5fc3f2db54c30d4a7c038e5df869c3f3f3a9e609b84cea72400942f94d7605e"
32
  },
 
 
 
 
33
  "feature_example_raster.csv": {
34
  "rows": 1057,
35
  "sha256": "f905653af836026122424e4bc7984417512f4755412301e2c98803de5ab15215"
 
6
  "manuscript_working_version": "manuscript_v7",
7
  "schema_version": 1,
8
  "source": "paper/results, active consistency and feature-attribution artifacts, benchmark dataset arrays, and Figure 5 prediction sidecars",
9
+ "source_git_revision": "6f4397578797a2c812f6177ee83e9df3edd00804",
10
  "source_repository": "https://github.com/TangLab-UBC/behavior_benchmarking",
11
  "source_worktree_dirty": true,
12
  "tables": {
 
30
  "rows": 5,
31
  "sha256": "b5fc3f2db54c30d4a7c038e5df869c3f3f3a9e609b84cea72400942f94d7605e"
32
  },
33
+ "dataset_targets.csv": {
34
+ "rows": 72260,
35
+ "sha256": "215f87938c8e0376a0924a096e4f672eb316b3e5199bb3f6cc58f716c847ca21"
36
+ },
37
  "feature_example_raster.csv": {
38
  "rows": 1057,
39
  "sha256": "f905653af836026122424e4bc7984417512f4755412301e2c98803de5ab15215"
validate_data.py CHANGED
@@ -54,6 +54,10 @@ REQUIRED_COLUMNS = {
54
  "dataset", "trial_index", "time_index", "time_ms", "target_0",
55
  "target_1", "target_label",
56
  },
 
 
 
 
57
  "feature_example_raster.csv": {
58
  "dataset", "trial_index", "display_index", "feature_index",
59
  "feature_group", "validation_value", "group_order", "n_time",
@@ -119,6 +123,7 @@ UNIQUE_KEYS = {
119
  "dataset_overview.csv": ["dataset"],
120
  "dataset_example_neural.csv": ["dataset", "time_index", "feature_display_index"],
121
  "dataset_example_targets.csv": ["dataset", "time_index"],
 
122
  "feature_example_raster.csv": ["dataset", "feature_index"],
123
  "clean_prediction_summary.csv": ["model", "dataset"],
124
  "robustness_summary.csv": ["model", "dataset"],
@@ -262,7 +267,7 @@ def validate_local(data_dir: Path) -> dict[str, pd.DataFrame]:
262
  if not path.exists():
263
  errors.append(f"missing required table: {path}")
264
  continue
265
- frame = pd.read_csv(path)
266
  missing = sorted(columns - set(frame.columns))
267
  _require(not missing, f"{name}: missing columns {missing}", errors)
268
  if missing:
@@ -343,6 +348,7 @@ def validate_local(data_dir: Path) -> dict[str, pd.DataFrame]:
343
  "dataset_overview.csv",
344
  "dataset_example_neural.csv",
345
  "dataset_example_targets.csv",
 
346
  "feature_example_raster.csv",
347
  ):
348
  if name in frames:
@@ -391,6 +397,61 @@ def validate_local(data_dir: Path) -> dict[str, pd.DataFrame]:
391
  errors,
392
  )
393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  if "feature_example_raster.csv" in frames:
395
  raster = frames["feature_example_raster.csv"]
396
  value_columns = sorted(
@@ -423,6 +484,7 @@ def validate_local(data_dir: Path) -> dict[str, pd.DataFrame]:
423
  "dataset_overview.csv",
424
  "dataset_example_neural.csv",
425
  "dataset_example_targets.csv",
 
426
  )
427
  ):
428
  overview_trials = frames["dataset_overview.csv"].set_index("dataset")[
@@ -434,9 +496,15 @@ def validate_local(data_dir: Path) -> dict[str, pd.DataFrame]:
434
  target_trials = frames["dataset_example_targets.csv"].groupby("dataset")[
435
  "trial_index"
436
  ].first().astype(int)
 
 
 
 
 
437
  _require(
438
  overview_trials.equals(neural_trials.reindex(overview_trials.index))
439
- and overview_trials.equals(target_trials.reindex(overview_trials.index)),
 
440
  "dataset examples: manifest, neural and target trial indices differ",
441
  errors,
442
  )
 
54
  "dataset", "trial_index", "time_index", "time_ms", "target_0",
55
  "target_1", "target_label",
56
  },
57
+ "dataset_targets.csv": {
58
+ "dataset", "trial_index", "trial_id", "condition_id", "target_label",
59
+ "time_index", "time_ms", "target_0", "target_1", "is_example",
60
+ },
61
  "feature_example_raster.csv": {
62
  "dataset", "trial_index", "display_index", "feature_index",
63
  "feature_group", "validation_value", "group_order", "n_time",
 
123
  "dataset_overview.csv": ["dataset"],
124
  "dataset_example_neural.csv": ["dataset", "time_index", "feature_display_index"],
125
  "dataset_example_targets.csv": ["dataset", "time_index"],
126
+ "dataset_targets.csv": ["dataset", "trial_index", "time_index"],
127
  "feature_example_raster.csv": ["dataset", "feature_index"],
128
  "clean_prediction_summary.csv": ["model", "dataset"],
129
  "robustness_summary.csv": ["model", "dataset"],
 
267
  if not path.exists():
268
  errors.append(f"missing required table: {path}")
269
  continue
270
+ frame = pd.read_csv(path, low_memory=False)
271
  missing = sorted(columns - set(frame.columns))
272
  _require(not missing, f"{name}: missing columns {missing}", errors)
273
  if missing:
 
348
  "dataset_overview.csv",
349
  "dataset_example_neural.csv",
350
  "dataset_example_targets.csv",
351
+ "dataset_targets.csv",
352
  "feature_example_raster.csv",
353
  ):
354
  if name in frames:
 
397
  errors,
398
  )
399
 
400
+ if "dataset_targets.csv" in frames:
401
+ targets = frames["dataset_targets.csv"]
402
+ expected_rows = {
403
+ "monkey": 15_950,
404
+ "allen_neuropixels": 598,
405
+ "speech": 168,
406
+ "mc_pacman": 40_544,
407
+ "ratinabox": 15_000,
408
+ }
409
+ observed_rows = targets.groupby("dataset").size().to_dict()
410
+ _require(
411
+ observed_rows == expected_rows,
412
+ f"all targets: row counts are {observed_rows}",
413
+ errors,
414
+ )
415
+ for column in ["trial_index", "condition_id", "time_index"]:
416
+ values = pd.to_numeric(targets[column], errors="coerce")
417
+ _require(
418
+ values.notna().all() and np.allclose(values, np.round(values)),
419
+ f"all targets: invalid {column} values",
420
+ errors,
421
+ )
422
+ example_mask = targets["is_example"].astype(str).str.lower().eq("true")
423
+ example_trials = targets.loc[example_mask].groupby("dataset")[
424
+ "trial_index"
425
+ ].nunique()
426
+ _require(
427
+ example_trials.reindex(sorted(DATASETS)).eq(1).all(),
428
+ "all targets: expected one highlighted trial per dataset",
429
+ errors,
430
+ )
431
+ classification = targets[targets["dataset"].isin({"allen_neuropixels", "speech"})]
432
+ class_sets = classification.groupby("dataset")["condition_id"].apply(
433
+ lambda values: set(pd.to_numeric(values, errors="coerce").astype(int))
434
+ )
435
+ _require(
436
+ class_sets.map(lambda values: values == set(range(8))).all()
437
+ and classification["target_label"].notna().all(),
438
+ "all targets: classification labels or class coverage differ",
439
+ errors,
440
+ )
441
+ continuous = targets[targets["dataset"].isin({"monkey", "mc_pacman", "ratinabox"})]
442
+ target_0 = pd.to_numeric(continuous["target_0"], errors="coerce")
443
+ _require(
444
+ target_0.notna().all() and np.isfinite(target_0.to_numpy()).all(),
445
+ "all targets: continuous target_0 values must be finite",
446
+ errors,
447
+ )
448
+ two_dimensional = continuous[continuous["dataset"].isin({"monkey", "ratinabox"})]
449
+ target_1 = pd.to_numeric(two_dimensional["target_1"], errors="coerce")
450
+ _require(
451
+ target_1.notna().all() and np.isfinite(target_1.to_numpy()).all(),
452
+ "all targets: two-dimensional target values must be finite",
453
+ errors,
454
+ )
455
  if "feature_example_raster.csv" in frames:
456
  raster = frames["feature_example_raster.csv"]
457
  value_columns = sorted(
 
484
  "dataset_overview.csv",
485
  "dataset_example_neural.csv",
486
  "dataset_example_targets.csv",
487
+ "dataset_targets.csv",
488
  )
489
  ):
490
  overview_trials = frames["dataset_overview.csv"].set_index("dataset")[
 
496
  target_trials = frames["dataset_example_targets.csv"].groupby("dataset")[
497
  "trial_index"
498
  ].first().astype(int)
499
+ all_targets = frames["dataset_targets.csv"]
500
+ all_target_mask = all_targets["is_example"].astype(str).str.lower().eq("true")
501
+ all_target_trials = all_targets.loc[all_target_mask].groupby("dataset")[
502
+ "trial_index"
503
+ ].first().astype(int)
504
  _require(
505
  overview_trials.equals(neural_trials.reindex(overview_trials.index))
506
+ and overview_trials.equals(target_trials.reindex(overview_trials.index))
507
+ and overview_trials.equals(all_target_trials.reindex(overview_trials.index)),
508
  "dataset examples: manifest, neural and target trial indices differ",
509
  errors,
510
  )