Stabilize latent hover and add full target spaces
Browse files- app.py +346 -135
- assets/styles.css +192 -0
- data/dataset_targets.csv +0 -0
- data/release_manifest.json +5 -1
- validate_data.py +70 -2
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": "
|
| 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 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 897 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 =
|
| 1058 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
"
|
| 2602 |
html.Div(
|
| 2603 |
[
|
| 2604 |
-
|
| 2605 |
-
|
| 2606 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2607 |
),
|
| 2608 |
-
|
| 2609 |
-
"dataset-target-
|
| 2610 |
-
"
|
| 2611 |
),
|
| 2612 |
],
|
| 2613 |
-
className="
|
| 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("
|
| 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-
|
| 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": "
|
| 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 |
)
|