Skip to content

Commit 9f407a3

Browse files
authored
Merge pull request #23 from int-brain-lab/refactor
Refactor
2 parents c709af4 + 09ac637 commit 9f407a3

72 files changed

Lines changed: 5008 additions & 572 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/models.py

Lines changed: 217 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import enum
1010
import uuid
1111
from datetime import datetime
12-
from typing import Any, Optional
12+
from typing import Any, ClassVar, Optional
1313

1414
from sqlalchemy import Column, DateTime, Enum as SAEnum, Index, JSON, func, select, text
1515
from sqlalchemy.orm import column_property
@@ -22,6 +22,27 @@
2222
# in, what a task measures, and how a model was trained for it.
2323

2424

25+
class DescribedEnum(str, enum.Enum):
26+
"""A str enum whose members carry the help text shown beside a form control.
27+
28+
Members are written ``name = value, description``, and the description rides on the
29+
member itself rather than sitting in a lookup table beside it — one place to read, and
30+
a member can't be added without one being noticed as missing.
31+
32+
``str.__new__`` with ``_value_`` set explicitly is what keeps the tuple from becoming
33+
the value: a member still compares equal to its own string, ``Modality("spikes")``
34+
still resolves, and the SQLAlchemy ``Enum`` column and pydantic both see the same
35+
values they always did.
36+
"""
37+
38+
def __new__(cls, value: str, description: str = ""):
39+
member = str.__new__(cls, value)
40+
member._value_ = value
41+
member.description = description
42+
43+
return member
44+
45+
2546
class TeamRole(str, enum.Enum):
2647
owner = "owner"
2748
collaborator = "collaborator"
@@ -61,40 +82,113 @@ class TaskType(str, enum.Enum):
6182
brain_region = "brain_region"
6283

6384

64-
class Modality(str, enum.Enum):
65-
anatomy = "anatomy"
66-
spikes = "spikes"
67-
behavior = "behavior"
68-
lfp = "lfp"
69-
waveforms = "waveforms"
85+
class Modality(DescribedEnum):
86+
anatomy = (
87+
"anatomy",
88+
"Brain region labels, assigned per-unit by mapping recording location to a reference "
89+
"brain atlas.",
90+
)
91+
spikes = (
92+
"spikes",
93+
"Ephys spike trains (following spike band filter, threshold crossing, spike sorting). "
94+
"Discrete per-neuron event times, optionally binned into firing-rate estimates.",
95+
)
96+
behavior = (
97+
"behavior",
98+
"Any behavioral signal relevant to the decision-making task, including task events and "
99+
"continuous traces from video and pose tracking.",
100+
)
101+
lfp = (
102+
"lfp",
103+
"Ephys local field potential (following LFP band filter, downsampling). Captures "
104+
"lower-frequency population-level signals distinct from spike-level activity.",
105+
)
106+
waveforms = (
107+
"waveforms",
108+
"Ephys spike waveforms (extracellular action potential shape captured in a short window "
109+
"around each detected spike).",
110+
)
70111

71112

72-
class TrainingParadigm(str, enum.Enum):
73-
TSS = "TSS" # Task-Specific Supervised
74-
TSU = "TSU" # Task-Specific Unsupervised (pretrained backbone)
75-
single_session = "single_session"
113+
class TrainingParadigm(DescribedEnum):
114+
TSS = (
115+
"TSS",
116+
"Task-Suite-Supervised (pretraining objective matches the supervision target of this "
117+
"task).",
118+
)
119+
TSU = (
120+
"TSU",
121+
"Task-Suite-Unsupervised (pretraining objective was unrelated to the supervision target "
122+
"of this task).",
123+
)
124+
single_session = (
125+
"single_session",
126+
"Trained from scratch on each individual session, no pretraining.",
127+
)
76128

77129

78-
class SupervisionRegime(str, enum.Enum):
79-
zero_shot = "zero_shot"
80-
few_shot = "few_shot"
81-
full = "full"
82-
other = "other"
130+
class SupervisionRegime(DescribedEnum):
131+
zero_shot = (
132+
"zero_shot",
133+
"No supervision data from this task is used to adapt the pretrained model to the eval "
134+
"session.",
135+
)
136+
few_shot = (
137+
"few_shot",
138+
"A subset of the available supervised data for this task is used to calibrate the "
139+
"pretrained model.",
140+
)
141+
full = (
142+
"full",
143+
"All available supervised data for this task is used to calibrate the pretrained model.",
144+
)
145+
other = (
146+
"other",
147+
"A regime not captured above; please describe it in the private narrative.",
148+
)
83149

84150

85-
class Calibration(str, enum.Enum):
86-
inductive = "inductive" # gradient-free at eval time
87-
transductive = "transductive" # requires gradients on eval set
151+
class Calibration(DescribedEnum):
152+
inductive = (
153+
"inductive",
154+
"Model evaluated on this task with no parameter updates needed.",
155+
)
156+
transductive = (
157+
"transductive",
158+
"Gradients used to update the model on this task, whether supervised directly on this "
159+
"task or calibrated via another objective.",
160+
)
88161

89162

90-
class FinetuningStrategy(str, enum.Enum):
91-
linear_probe = "linear_probe"
92-
mlp_probe = "mlp_probe"
93-
gradual_unfreezing = "gradual_unfreezing"
94-
full_finetuning = "full_finetuning"
95-
single_unit = "single_unit"
96-
multi_unit = "multi_unit"
97-
other = "other"
163+
class FinetuningStrategy(DescribedEnum):
164+
linear_probe = (
165+
"linear_probe",
166+
"Linear readout trained on frozen pretrained representations.",
167+
)
168+
mlp_probe = (
169+
"mlp_probe",
170+
"Multi-layer perceptron readout trained on frozen pretrained representations.",
171+
)
172+
gradual_unfreezing = (
173+
"gradual_unfreezing",
174+
"Layers progressively unfrozen and finetuned over the course of adaptation.",
175+
)
176+
full_finetuning = (
177+
"full_finetuning",
178+
"All model parameters updated during adaptation to this task.",
179+
)
180+
single_unit = (
181+
"single_unit",
182+
"TS3 probe fit and evaluated per individual unit.",
183+
)
184+
multi_unit = (
185+
"multi_unit",
186+
"TS3 probe fit using a consensus across multiple nearby units.",
187+
)
188+
other = (
189+
"other",
190+
"A strategy not captured above; please describe it in the private narrative.",
191+
)
98192

99193

100194
class Metric(str, enum.Enum):
@@ -105,6 +199,17 @@ class Metric(str, enum.Enum):
105199
r2 = "r2"
106200

107201

202+
# What each task suite asks a model to predict. Domain fact rather than a column: it is
203+
# fixed by what the suites are, and both the submission forms (which modality can't be an
204+
# *extra input* when it is the target) and /api/meta read it. Here so there is one copy —
205+
# it previously lived only in the frontend's task schema.
206+
SUITE_OUTPUT_MODALITY: dict[TaskSuite, Modality] = {
207+
TaskSuite.ts1: Modality.behavior,
208+
TaskSuite.ts2: Modality.spikes,
209+
TaskSuite.ts3: Modality.anatomy,
210+
}
211+
212+
108213
# ── Helpers ────────────────────────────────────────────────────────────────────
109214

110215

@@ -222,6 +327,32 @@ class Model(SQLModel, table=True):
222327
team: Team | None = Relationship(back_populates="models")
223328
submissions: list["Submission"] = Relationship(back_populates="model")
224329

330+
# Help text for the create and edit forms, keyed by field name and served by
331+
# /api/meta. Here rather than on the response schemas so the wording sits with the
332+
# column it describes and can't drift from it; ``test_models`` asserts every key is a
333+
# real field.
334+
FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {
335+
"link_project": "Link to project homepage.",
336+
"link_weights": "Link to model weights (e.g. Huggingface).",
337+
"link_code": "Link to model code (e.g. GitHub).",
338+
"publication_doi": "DOI of affiliated publication.",
339+
"n_parameters": "Total number of non-embedding model parameters.",
340+
"temporal_context_s": (
341+
"Duration (s) of context window used, including and preceding the target window. "
342+
"If context length varies across tasks for this model, report the primary/default "
343+
"value here and note task-specific deviations in the submission narrative."
344+
),
345+
"is_pretrained": (
346+
"Is this a pretrained foundation model, or trained from scratch on every session?"
347+
),
348+
"pretrained_in_modalities": "If pretrained, which modalities were accepted as input.",
349+
"pretrained_out_modalities": "If pretrained, which modalities were used for supervision.",
350+
"pretraining_data": (
351+
"Describe the corpus of pretraining data used (all sessions, a subset, external "
352+
"data)."
353+
),
354+
}
355+
225356

226357
class Submission(SQLModel, table=True):
227358
"""Uploaded prediction zip + scoring state."""
@@ -262,6 +393,22 @@ class Submission(SQLModel, table=True):
262393
sa_relationship_kwargs={"cascade": "all, delete-orphan"},
263394
)
264395

396+
# See Model.FIELD_DESCRIPTIONS.
397+
FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {
398+
"label": (
399+
"Name of this submission, identifying the base model and what distinguishes this "
400+
"particular variant."
401+
),
402+
"narrative_public": (
403+
"A narrative describing this submission, which is made public on the leaderboard."
404+
),
405+
"narrative_private": (
406+
"This is space for writing comments that are kept private, including notes to the "
407+
"administrators."
408+
),
409+
"is_public": "Is this submission ready to be published on the leaderboard?",
410+
}
411+
265412

266413
class SubmissionUser(SQLModel, table=True):
267414
"""M2M bridge — Submission ↔ User."""
@@ -329,6 +476,49 @@ class TaskSubmission(SQLModel, table=True):
329476
sa_relationship_kwargs={"cascade": "all, delete-orphan", "uselist": False},
330477
)
331478

479+
# See Model.FIELD_DESCRIPTIONS.
480+
FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {
481+
"extra_input_modality": (
482+
"Does this model require any modalities other than spikes as input for this task? "
483+
"This necessarily excludes task-related supervision targets within the target window."
484+
),
485+
"training_paradigm": (
486+
"Which paradigm is used to train this model on this task? Single-session models are "
487+
"trained from scratch on each session, with no pretraining used. If adapting a "
488+
"pretrained foundation model, did the pretraining objective match the supervision "
489+
"target of this task (Task-Suite-Supervised, TSS) or was it unrelated "
490+
"(Task-Suite-Unsupervised, TSU)? Note: TSS implies the training objective itself was "
491+
"aligned with this task, not just the modalities used in input and output (e.g., "
492+
"forecasting, not just spikes-to-spikes). Refer to the BrainWideBench paper for more "
493+
"details and examples with existing baselines."
494+
),
495+
"supervision_regime": (
496+
"To what degree is this model supervised on this task? Zero-shot means no supervision "
497+
"is needed to adapt a pretrained model on the eval session. Few-shot means a subset "
498+
"of the available supervised data is used to calibrate a pretrained model. Full means "
499+
"all available supervised data is used to calibrate a pretrained model. Note that "
500+
"this specifically refers to data pertaining to this task (i.e. supervision data), "
501+
"not other available data in the dataset. Single-session models implicitly cannot be "
502+
"used in a zero-shot fashion, though can use few-shot or use full supervision. If "
503+
"there is another paradigm not listed, please specify in the private description."
504+
),
505+
"calibration": (
506+
"Are gradients required to update a pretrained model in order to adapt to this task, "
507+
"whether that adaptation is supervised on this task directly or calibrated via some "
508+
"other objective (both count as transductive)? Or can it be evaluated on this task "
509+
"with no updates to the model parameters (inductive)? Note: here, inductive implies "
510+
"zero-shot since a model that never uses eval data to adapt also does not use "
511+
"supervision on this task. Single-session models are always transductive, since they "
512+
"are trained from scratch on the eval session."
513+
),
514+
"finetuning_strategy": (
515+
"If finetuning was done from a pretrained model, what kind of strategy was used? "
516+
"Options include linear/MLP probing, gradual unfreezing, full finetuning, etc. For "
517+
"TS3, was a single-unit or multi-unit probe used? If a strategy not listed here was "
518+
"used, please select Other and describe it."
519+
),
520+
}
521+
332522

333523
class TaskScore(SQLModel, table=True):
334524
"""Mean ± SEM over seeds for one TaskSubmission.

0 commit comments

Comments
 (0)