Qiu et al. 2026 Dataset
Mouse retinal ganglion cell axonal bouton responses to natural movies, recorded with chronic two-photon calcium imaging in the superficial layers of the superior colliculus of awake mice, originally published in Qiu et al. (2026): The functional organization of retinal input to the mouse superior colliculus, bioRxiv.
Dataset: huggingface.co/datasets/open-retina/open-retina
— ten sessions from three mice (~17k boutons after quality masking), one zip per session under
franke_lab/qiu_2026/.
Train on it with uv run openretina train --config-name qiu_2026_core_readout.
Dataset layout
Unlike the other datasets shipped with openretina, qiu_2026 arrives in the Sensorium FileTree format: one
.npy per trial at data/<stream>/{i}.npy for the four streams videos, responses, behavior and
pupil_center, positionally aligned to the rows of meta/trials/*.npy and NaN-padded at the end to a
length that is uniform within a session but differs between them (300, 450 or 960 frames); a clip trial's
valid portion is always its first 300 frames, i.e. 10 s at 30 Hz. The trial helpers hold the
shared trimming, splitting and session-discovery logic that keeps all four streams frame-aligned.
Four things set this dataset apart from the others:
- Behavior is folded into the input channels. The model input is
(3, T, 36, 64): one grayscale video channel plus two behavior channels (pupil size and locomotion) broadcast across every pixel. The video channel is z-scored with the session's shipped scalar statistics, each behavior channel with its own, so thenorm_mean/norm_stdon the returnedMoviesTrainTestSplitdescribe the video channel only. - Targets are raw calcium fluorescence, masked per session with the shipped
neurons_fluor_goodarray (an index array, not a boolean mask).spike_inferenceselects the transform:"raw"leaves the trace untouched,"subtract_min"shifts each neuron by its train-derived minimum to give the Poisson loss a non-negative target, and"cascade"is reserved for inferred spike rates and not yet implemented. - Pupil center is loaded separately by
pupil. It is not a model target: it feeds the shifter, which corrects each neuron's readout center for eye movements. Because openretina's sharedDataPointcarries only(inputs, targets), this subpackage defines its ownQiuDataPointandQiuMovieDataSetrather than changing the shared one. - Test clips are repeated within a session, but the design is not the same in every session: six
sessions share one set of six clips at 14–20 repeats each, while the four with the least training data
were tested on 94–96 distinct clips at 1–10 repeats (supersets of those same six). Repeats are keyed by
condition_hash. The video is identical across a condition's repeats but the behavior and eye position are not, and the model is nonlinear in both, so withper_repeat_test_behavior=Truethe test dataloader puts the repeats in the batch dimension and averages the predictions rather than the inputs. Non-clip trials in the sametesttier —chirp/moving barmovies in five sessions,monet2/trippynoise in the other four — are excluded by thestimulus_type="clip"filter. The dataset page has the per-session breakdown.
Dataloaders
dataloaders
qiu_2026 dataloaders that carry pupil_center alongside inputs/targets.
open-retina's shared DataPoint is a 2-field (inputs, targets) namedtuple; extending it would break
every existing dataset (see the integration plan §0.4). Instead this module defines a qiu-local
QiuDataPoint and a QiuMovieDataSet that slices a per-frame pupil trace with the same chunking as the
movie/responses, injected into :func:get_movie_dataloader via its dataset_cls/extra_arrays hook.
qiu_2026_dataloaders mirrors :func:multiple_movies_dataloaders but (a) splits the pupil trace
identically to the movie via :func:generate_movie_splits, (b) honors each session's curated validation
split (the validation_clip_indices stashed in session_kwargs) so validation trials are carved out of
training rather than chosen at random, (c) evaluates each test condition as one whole clip, and (d) by
default puts that condition's repeats in the batch dimension so the model runs once per repeat with that
repeat's own behavior, and the predictions -- not the inputs -- are averaged.
QiuMovieDataSet
QiuMovieDataSet(
movies,
responses,
roi_ids,
roi_coords,
group_assignment,
split,
chunk_size,
pupil_center,
)
Bases: MovieDataSet
MovieDataSet that additionally carries a per-frame pupil_center (2, T) trace.
pupil_center is sliced on its time axis (axis 1) with the exact chunk the movie (axis 1) and
responses (axis 0) are sliced with, and returned as the third field of a :class:QiuDataPoint.
Source code in openretina/data_io/qiu_2026/dataloaders.py
53 54 55 56 57 58 59 60 61 62 63 64 65 | |
QiuRepeatTestDataSet
QiuRepeatTestDataSet(
movies,
responses,
roi_ids,
roi_coords,
group_assignment,
split,
chunk_size,
behavior_by_repeat,
pupil_by_repeat,
)
Bases: MovieDataSet
Test dataset whose index dimension is the repeat, not a time chunk.
A test condition is one clip presented 15-20 times. The movie is identical on every repeat but the
animal's behavior is not, so each item re-assembles the (C, T, H, W) input from one shared video
channel plus that repeat's own behavior channels, and pairs it with that repeat's own pupil trace.
The target is the trial-averaged response, the same for every item: the model sees R inputs and
:meth:BaseCoreReadout.test_step averages the R predictions before scoring them, which is what the
trial-averaged response actually is -- the average over repeats of what the retina did while the
animal behaved as it did on that repeat.
Storing R assembled input tensors instead would cost ~R times the test movie memory for a video that never changes (several GB across the full dataset), so the video is kept once and broadcast here.
Source code in openretina/data_io/qiu_2026/dataloaders.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
qiu_2026_dataloaders
qiu_2026_dataloaders(
neuron_data_dictionary: dict[
str, ResponsesTrainTestSplit
],
movies_dictionary: dict[str, Any],
pupil_dictionary: dict[str, dict[str, Any]],
train_chunk_size: int = 50,
batch_size: int = 32,
seed: int = 42,
clip_length: int = 300,
num_val_clips: int = 10,
val_clip_indices: list[int] | None = None,
allow_over_boundaries: bool = False,
release_movies: bool = False,
per_repeat_test_behavior: bool = True,
) -> dict[str, dict[str, DataLoader]]
Build dict[split][session] -> DataLoader yielding :class:QiuDataPoint batches.
Validation-clip selection per session, in priority order: the explicit val_clip_indices argument, then
the session's curated session_kwargs["validation_clip_indices"], then a random draw of
num_val_clips. The curated path is the intended one — it removes the dataset's designated validation
trials from the training subset. Each test condition is evaluated as a single whole clip.
| PARAMETER | DESCRIPTION |
|---|---|
per_repeat_test_behavior
|
Evaluate each test condition by feeding every repeat's behavior and pupil
trace through the model separately and averaging the R predictions, instead of averaging the
R inputs and running the model once. The video is the same clip on every repeat, but the
behavior channels and the pupil centre are not, and the model is nonlinear in both, so
TYPE:
|
release_movies
|
Destructive. Drop each session's entry from Only frees anything if the caller passes its own dictionary directly. Going through
TYPE:
|
Source code in openretina/data_io/qiu_2026/dataloaders.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 | |
Stimuli
stimuli
qiu_2026 stimulus loader.
Builds a (C, T, H, W) input where C = 1 video + len(behavior_channels) behavior channels: the
grayscale movie plus the selected behavior traces (pupil size + locomotion) broadcast spatially across
every pixel. The video channel is z-scored with the session's shipped video statistics (a shared scalar);
each behavior channel is z-scored with its own shipped statistics inside the tensor, so the
norm_mean/norm_std stored on the container describe only the video channel.
Repeated test stimuli additionally keep their behavior per repeat (test_behavior_by_repeat_dict),
since the movie is identical across a condition's repeats but the animal's behavior is not.
load_stimuli_for_session
load_stimuli_for_session(
session_path: str | PathLike,
*,
behavior_channels: tuple[int, ...] = BEHAVIOR_CHANNELS,
stimulus_type: str = "clip",
video_deviation_tolerance: float = 0.1,
) -> MoviesTrainTestSplit
Build a :class:MoviesTrainTestSplit for one qiu_2026 session.
The continuous train movie concatenates all train+validation trials (each trimmed to its valid
length) in file-index order. test_dict holds one clip per condition_hash, averaged over the
condition's repeats in both the video and the behavior channels.
Averaging the video is near enough to a no-op: the repeats are the same clip, differing at most in
which of the pipeline's two temporal-resampling renderings of it they got (see
:func:_warn_on_video_deviation). Averaging the behavior is not — the animal behaves differently on
every repeat, and the model is nonlinear in those channels. So test_behavior_by_repeat_dict
additionally keeps each repeat's behavior as (repeats, n_behavior, T), letting the dataloader feed
them through the model one at a time and average the predictions instead (see
qiu_2026_dataloaders).
Only trials matching stimulus_type (see
:func:~openretina.data_io.qiu_2026.trials.read_stimulus_type) are included; some sessions' test
tier additionally contains non-clip functional-characterization trials with per-repeat frame-count
jitter that would otherwise break the repeat-stacking below.
| PARAMETER | DESCRIPTION |
|---|---|
video_deviation_tolerance
|
Warn when a condition's repeats differ by more than this mean
absolute deviation, in units of the video std. The default is calibrated for
TYPE:
|
Source code in openretina/data_io/qiu_2026/stimuli.py
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | |
load_all_stimuli
load_all_stimuli(
base_data_path: str | PathLike,
*,
behavior_channels: tuple[int, ...] = BEHAVIOR_CHANNELS,
stimulus_type: str = "clip",
sessions: list[str] | None = None,
video_deviation_tolerance: float = 0.1,
) -> dict[str, MoviesTrainTestSplit]
Load every discovered qiu_2026 session into a {session_key: MoviesTrainTestSplit} dict.
Source code in openretina/data_io/qiu_2026/stimuli.py
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
Responses
responses
qiu_2026 response loader.
Loads raw calcium fluorescence, applies the per-session neurons_fluor_good index mask, trims NaN
padding, and assembles a continuous train trace plus per-condition_hash test dictionaries (both the
trial-averaged test_dict and the per-repeat test_by_trial_dict).
spike_inference controls the response transform:
- "raw": fluorescence untouched (default; used by fast smoke tests).
- "subtract_min": subtract each neuron's train-derived minimum → non-negative, trainable interim
target for the Poisson loss until CASCADE is wired in.
- "cascade": CASCADE inferred spike rates (Rupprecht 2021) — not yet implemented.
load_responses_for_session
load_responses_for_session(
session_path: str | PathLike,
good_idx: ndarray | None = None,
*,
spike_inference: SpikeInference = "raw",
stimulus_type: str = "clip",
) -> ResponsesTrainTestSplit
Build a :class:ResponsesTrainTestSplit for one qiu_2026 session.
good_idx is the neurons_fluor_good index array; None keeps all neurons. Only trials
matching stimulus_type (see
:func:~openretina.data_io.qiu_2026.trials.read_stimulus_type) are included; some sessions' test
tier additionally contains non-clip functional-characterization trials with per-repeat frame-count
jitter that would otherwise break the repeat-stacking below.
Source code in openretina/data_io/qiu_2026/responses.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
load_all_responses
load_all_responses(
base_data_path: str | PathLike,
*,
apply_quality_mask: bool = True,
spike_inference: SpikeInference = "raw",
stimulus_type: str = "clip",
sessions: list[str] | None = None,
) -> dict[str, ResponsesTrainTestSplit]
Load every discovered qiu_2026 session into a {session_key: ResponsesTrainTestSplit} dict.
Source code in openretina/data_io/qiu_2026/responses.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
Pupil
pupil
qiu_2026 pupil-center loader.
Loads the (2, T) eye-position trace per trial, z-scores it with the shipped pupil statistics, trims
NaN padding, and splits it with the same train/test logic used by the stimulus and response loaders so
the trace stays frame-aligned. There is no container for pupil data (it is not a model target); the
per-session dict returned here is consumed by the qiu dataloader, which slices it into the shifter input.
Layout mirrors the response split: train is the continuous train+validation trace; test_dict holds
one trace per condition_hash, averaged over the condition's repeats to pair with the trial-averaged
test responses, and test_by_repeat_dict keeps the individual repeats, which differ because the eye
moves differently on every presentation of the same clip.
load_pupil_for_session
load_pupil_for_session(
session_path: str | PathLike,
*,
stimulus_type: str = "clip",
) -> dict[str, ndarray | dict[str, ndarray]]
Return the train trace plus both test layouts for one session.
{"train": (2, T_total), "test_dict": {condition: (2, T_cond)},
"test_by_repeat_dict": {condition: (repeats, 2, T_cond)}}.
Only trials matching stimulus_type (see
:func:~openretina.data_io.qiu_2026.trials.read_stimulus_type) are included; some sessions' test
tier additionally contains non-clip functional-characterization trials with per-repeat frame-count
jitter that would otherwise break the repeat-stacking below.
Source code in openretina/data_io/qiu_2026/pupil.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
load_all_pupil
load_all_pupil(
base_data_path: str | PathLike,
*,
stimulus_type: str = "clip",
sessions: list[str] | None = None,
) -> dict[str, dict[str, ndarray | dict[str, ndarray]]]
Load every discovered qiu_2026 session's pupil trace into a {session_key: {...}} dict.
Source code in openretina/data_io/qiu_2026/pupil.py
84 85 86 87 88 89 90 91 92 93 94 95 | |
Trial helpers
trials
Shared trial-splitting, NaN-trimming and session-discovery helpers for the qiu_2026 loaders.
Every per-trial array lives at data/<stream>/{i}.npy for i in 0..n_trials-1, aligned
positionally to row i of every meta/trials/*.npy array (NOT to the value stored in
trial_idx.npy, which carries the original DataJoint ids with gaps). NaN padding is strictly
trailing (whole-frame NaN) and identical across the four streams for a given trial, so the same
trim/split logic must be applied to all of them — hence these shared helpers, imported by
stimuli.py, responses.py and pupil.py so their outputs stay frame-aligned.
resolve_session_path
resolve_session_path(session_path: str | PathLike) -> Path
Return the on-disk session directory, unzipping (and deleting) a .zip if given one.
Source code in openretina/data_io/qiu_2026/trials.py
27 28 29 30 31 32 | |
read_tiers
read_tiers(session_path: str | PathLike) -> ndarray
Per-trial split labels ("train"/"validation"/"test"), one entry per trial file index.
Source code in openretina/data_io/qiu_2026/trials.py
39 40 41 | |
read_condition_hash
read_condition_hash(
session_path: str | PathLike,
) -> ndarray
Per-trial stimulus-condition hash (string); test repeats share a hash.
Source code in openretina/data_io/qiu_2026/trials.py
44 45 46 | |
read_stimulus_type
read_stimulus_type(session_path: str | PathLike) -> ndarray
Per-trial coarse stimulus category: "clip" for natural-movie clip trials (the paradigm every
loader trains/evaluates on by default), "other" for any other presentation.
The FileTree stores per-trial identity in disjoint DataJoint-table columns depending on which
presentation produced the trial; meta/trials/clip_movie_name.npy is populated (non-NaN) exactly
for clip trials and empty otherwise. The test tier of some sessions additionally contains
presentmoviearray trials (functional-characterization stimuli such as chirp/moving_bar,
identified via meta/trials/presentmoviearray_movie_name.npy), never mixed into train/
validation. These have per-repeat frame-count jitter unlike clips, so they must be filtered out
before test conditions are repeat-stacked. Determining "clip" from clip_movie_name alone is
sufficient and avoids depending on presentmoviearray_movie_name.npy, which not every session ships.
Source code in openretina/data_io/qiu_2026/trials.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
load_trial_array
load_trial_array(
session_path: str | PathLike, stream: str, index: int
) -> ndarray
Load data/<stream>/{index}.npy (NaN-padded to the full clip length).
Source code in openretina/data_io/qiu_2026/trials.py
73 74 75 | |
valid_length
valid_length(arr: ndarray, time_axis: int) -> int
Number of leading valid frames along time_axis (padding is trailing whole-frame NaN).
Source code in openretina/data_io/qiu_2026/trials.py
78 79 80 81 82 | |
trim_time
trim_time(
arr: ndarray, time_axis: int, n_frames: int
) -> ndarray
Slice arr to its first n_frames along time_axis.
Source code in openretina/data_io/qiu_2026/trials.py
85 86 87 88 89 | |
train_val_indices
train_val_indices(
tiers: ndarray,
stimulus_types: ndarray | None = None,
stimulus_type: str = "clip",
) -> list[int]
File indices of train+validation trials, in file-index order (== concatenated clip order).
stimulus_types (from :func:read_stimulus_type), if given, restricts to trials matching
stimulus_type. Train/validation trials are clip-only in every session seen so far, but this keeps
the guarantee explicit rather than assumed.
Source code in openretina/data_io/qiu_2026/trials.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | |
validation_clip_indices
validation_clip_indices(
tiers: ndarray,
stimulus_types: ndarray | None = None,
stimulus_type: str = "clip",
) -> list[int]
Positions of validation-tier trials within the train+val concatenation.
With one trial per fixed-length clip, clip index == rank in the train+val order, so these can be
passed as val_clip_indices to the dataloader to honour the dataset's curated validation split.
Source code in openretina/data_io/qiu_2026/trials.py
109 110 111 112 113 114 115 116 117 118 | |
test_conditions
test_conditions(
tiers: ndarray,
condition_hash: ndarray,
stimulus_types: ndarray | None = None,
stimulus_type: str = "clip",
) -> dict[str, list[int]]
Map each test condition_hash to its ordered list of trial file indices (first-seen order).
stimulus_types (from :func:read_stimulus_type), if given, restricts to trials matching
stimulus_type. Some sessions' test tier additionally contains non-clip trials (e.g. chirp/
moving_bar functional-characterization repeats) with per-repeat frame-count jitter; mixing them in
breaks the repeat-stacking done downstream in stimuli.py/responses.py/pupil.py.
Source code in openretina/data_io/qiu_2026/trials.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | |
session_key
session_key(session_path: str | PathLike) -> str
Stable session id: the directory/archive name with a trailing .zip removed.
Source code in openretina/data_io/qiu_2026/trials.py
141 142 143 144 | |
discover_sessions
discover_sessions(
base_path: str | PathLike,
sessions: list[str] | None = None,
) -> list[Path]
List session archives/directories under base_path (skipping the data-quality folder).
Source code in openretina/data_io/qiu_2026/trials.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | |
discover_quality_masks
discover_quality_masks(
base_path: str | PathLike,
) -> dict[str, ndarray]
Load data-quality/<prefix>_neurons_fluor_good.npy index arrays, keyed by <prefix>.
The <prefix> truncates the session's hash (e.g. ...-Fluorescence-7b7), so masks are matched
to sessions by session_key.startswith(prefix) in :func:match_quality_mask.
Source code in openretina/data_io/qiu_2026/trials.py
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
match_quality_mask
match_quality_mask(
key: str, masks: dict[str, ndarray]
) -> ndarray | None
Return the neurons_fluor_good index array whose (truncated) prefix matches key, or None.
Source code in openretina/data_io/qiu_2026/trials.py
183 184 185 186 187 188 189 190 191 192 | |
Constants
constants
Constants for the Qiu et al. 2026 (Franke lab) mouse dataset.
The data ships in Sensorium FileTree trial format (one .npy per trial), NaN-padded at the end to
a length that is uniform within a session but differs between sessions (300, 450 or 960 frames: the
longest trial that session holds). Every clip trial carries CLIP_LENGTH_CUT valid frames in
every tier, so the loaders trim by counting valid frames rather than assuming a padded length.