| title | Preprocessing |
|---|---|
| description | The one-time conversion from the published dataset to the binary bundle the viewer loads. |
The viewer does not load the published .npy files directly. A one-time preprocessing step reads the Figshare distribution and writes a manifest plus a set of typed-array blobs that together make up the bundle the viewer fetches at startup.
This page documents the decisions baked into that conversion, so that quantities seen in the viewer can be related back to the published dataset.
Only cells with valid coordinates are retained; approximately 274,455 cells pass through every subsequent step. NaN values in the calcium-trace, stimulus-correlation, and swim-correlation arrays are zero-filled rather than dropped, so a cell retains its position and transcriptomic identity even when its functional readout is unavailable.
- Axes are reordered to match the renderer's convention.
- Coordinates are centered on the origin so that the orbit pivot is meaningful.
- The AP axis is flipped so that anterior renders upward.
The published calcium traces are 268 samples at 2 Hz, representing a 134 s mean stimulus cycle. The preprocessor ships them at this native rate; no temporal downsampling is applied.
These traces are representative mean stimulus cycles from the published post-processed arrays, not raw trial-by-trial recordings.
The traces are affine-quantized to uint16 over an auto-fit range. The quantization step is roughly three orders of magnitude below the per-sample measurement noise, making the conversion effectively lossless, and the uint16 storage halves the file size relative to float32.
Source-specimen labels in the published data are remapped to a dense 0 / 1 / 2 for use within the viewer's arrays. Unknown labels raise an error rather than silently aliasing.
The t-SNE embedding is centered and scaled to approximately the [-50, 50] box. This decouples the pixel projection in the viewer from the absolute scale of the upstream embedding.
Indices 1…332 in the cluster array correspond one-to-one with the 332 named subtypes. Index 0 is reserved for Unassigned. Cluster names (e.g. pou4f2_cckb) are the stable identifiers across dataset versions; indices may shift.
The dataset assigns each cell to one of 16 focal anatomical groupings (plus Unassigned at index 0). The preprocessor attaches paper-canonical names to these 17 indices and emits them in the manifest:
Brain_reg |
Abbreviation | Full name |
|---|---|---|
| 0 | Unassigned | (cells outside the 16 focal groupings) |
| 1 | InfMO | Inferior medulla oblongata |
| 2 | IntMO | Intermediate medulla oblongata |
| 3 | SupMO | Superior medulla oblongata |
| 4 | SupRaphe | Superior dorsal raphe |
| 5 | Cb | Cerebellum |
| 6 | Tg | Tegmentum |
| 7 | NI | Nucleus isthmi |
| 8 | OTpv | Optic tectum periventricular layer |
| 9 | OTnp | Optic tectum neuropil |
| 10 | Pt | Pretectum |
| 11 | preTh | Prethalamus |
| 12 | Th | Dorsal thalamus |
| 13 | Hab | Habenula |
| 14 | HypTh | Hypothalamus |
| 15 | SubP | Subpallium |
| 16 | Pal | Dorsal pallium |
The integer-to-name mapping isn't shipped with the source data. It was recovered by intersecting Brain_reg.npy with BrainRegions_All.npy (the cell × 112-atlas-region matrix) and resolving ties against the paper's 16-region list. The default region colors are sampled directly from the paper's region figure legend (data/brain_regions.png), with optional Turbo and high-contrast categorical palettes available in the same 16-region slot order; Unassigned is rendered as a dedicated neutral gray rather than a hue in every palette.
The published dataset also ships a 112-region mapZebrain atlas (Modified from Kunst et al., 2019) as a cell × region boolean matrix in BrainRegions_All.npy, with names in each fish's region_names.npy. The atlas is hierarchical and overlapping: each cell can sit in 0–9 regions (e.g. a cerebellar cell is in both cerebellum and rhombencephalon).
The preprocessor packs this matrix into a 14-byte little-endian bitfield per cell (~3.84 MB in memory; shipped as atlasRegionMask.bin.gz, ~0.12 MB on disk) and emits the cleaned region names (_ → space) in the manifest as atlasRegionNames. The viewer decodes membership with (mask[i*14 + (r>>3)] >> (r&7)) & 1. The 112-region atlas is filter-only. It does not drive a color scheme.
The stimulus on-windows, in seconds, are extracted from the regressor traces and written into the manifest. The Detail panel uses these to shade the corresponding bands on the ΔF/F trace.
The viewer loads big_corr_regsAllMix from the published dataset. This array holds the cycle-wide Pearson r between each cell's activity trace and each stimulus regressor (maximum of the regular and delayed variants), and the manuscript uses it for Fig 5C/D and for naming stimulus-responsive subtypes. The dataset also publishes a windowed variant (high_corr_perSimMix, median across the 9 presentations); that array biases everything positive and is not currently surfaced in the viewer.
The viewer loads swim_corr_All, the per-cell Pearson r between each cell's calcium activity and estimated swim power (windowed variance of the ephys tail-electrode channel). NaN values are zero-filled in the same pattern as the stimulus correlations. This is the channel surfaced by the Swim card and the swim color scheme.
Each binary blob is written to disk as <name>.bin.gz (gzip level 6). Static hosts such as GitHub Pages and S3 serve these files opaquely, and the viewer decompresses them in the browser via DecompressionStream('gzip'). Compression varies sharply by file: the sparse gene-call and atlas-membership bitfields collapse to a few percent of their raw size, while the noise-dominated activity trace lands around 77%. The overall on-disk bundle is approximately 125 MB versus ~225 MB raw.
The manifest is a small JSON file that records:
- cell count and the counts of genes, clusters, regions, atlas regions, and stimuli,
- name arrays (genes, clusters, focal regions, mapZebrain atlas regions, stimuli),
- stimulus on-windows in seconds,
- the trace sample rate (2 Hz, the published native rate),
- quantization parameters needed to recover trace values,
- the list of binary blob filenames. Per-blob byte sizes aren't shipped; the viewer derives them from the cell count, gene count, and other scalars above.
The viewer fetches the manifest first, then issues the remaining requests for the binary blobs in parallel.
scripts/fetch_meshes.py downloads mapZebrain's three whole-brain reference
meshes and converts them into the viewer's coordinate space. It is optional —
the viewer runs without it, and the Brain models controls in Settings stay
disabled until it has been run. It needs network access and must run after
scripts/preprocess.py, which emits the voxelCenter it reads.
python3 scripts/fetch_meshes.py| mesh | source | triangles |
|---|---|---|
| outline | api.mapzebrain.org/media/Brains/Outline/Outline_new.stl |
54,874 |
| fibers | api.mapzebrain.org/media/Brains/Fibers/Fibers.stl |
9,758 |
| cell bodies | api.mapzebrain.org/media/Brains/Cell_bodies/Cell bodies.stl |
9,756 |
The meshes are generated on mapZebrain's side from the reference-brain TIFF
masks by ImageJ's 3D Viewer, so their vertices are in reference-volume voxel
indices (597 × 974 × 359 = LR × AP × DV) — the same space as the WARP
dataset's Coords_All.npy. Converting them is therefore exactly the transform
preprocessing already applies to cell positions:
out_x = stl_x - voxelCenter[0] # LR
out_y = -(stl_y - voxelCenter[1]) # AP, negated so rostral is +y
out_z = stl_z - voxelCenter[2] # DV
voxelCenter is read from neurons.json rather than hardcoded, so the mesh
center cannot drift from the cell center.
Worth keeping straight, because two of them look alike:
| space | x | y | z |
|---|---|---|---|
| mapZebrain voxel / WARP raw | LR column | AP row (caudal +) | DV slice (dorsal +) |
preprocessed (positions.bin) |
lateral, centered | rostral + | dorsal + |
| world (what the camera sees) | rostral + | lateral | dorsal + |
The last hop is a group transform in the viewer, not in preprocessing — see
src/components/brain/volumeTransform.ts. It rotates the volume 90° so the
brain's long axis lies across the wide 3D panel, which is why the default view
shows the fish pointing screen-right. It is also a mirror (determinant −1), so
anatomical left/right in world space cannot be derived from the voxel axes.
Blobs are non-indexed float32 vertex positions, 9 floats per triangle, gzipped
like every other binary. They get their own manifest rather than entries in
neurons.json, which keeps the primary load path untouched and lets the viewer
fetch a mesh lazily, only when a toggle turns it on.
preprocessed/meshOutline.bin.gz 0.59 MB gz (1.98 MB raw)
preprocessed/meshFibers.bin.gz 0.10 MB gz (0.35 MB raw)
preprocessed/meshCellBodies.bin.gz 0.10 MB gz (0.35 MB raw)
preprocessed/meshes.json
The script asserts each mesh's triangle count matches its STL header and that
the transformed outline mesh encloses the cell bounds from neurons.json on all
six faces, exiting non-zero and naming the offending axis otherwise. That
containment is what catches a coordinate-transform regression; without it a
wrong flip produces a brain drawn confidently in the wrong place.
Appending ?mock=1 to the viewer URL bypasses the preprocessed bundle and synthesizes a 10,000-cell dataset with plausible gene, cluster, and stimulus distributions. Mock data is intended for UI demonstration only; none of the numerical values are meaningful.