Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions viewers/watch-the-mouse-think/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.venv/
__pycache__/
*.pyc
data/
out/
assets/
remotion/
*.mp4
29 changes: 29 additions & 0 deletions viewers/watch-the-mouse-think/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# watch the mouse think

A small 3D viewer for Allen Neuropixels recordings. Streams a session from
DANDI, places each recorded unit at its true CCF coordinate inside the Allen
mouse brain atlas, glows region-by-region as activity flows through.

Inspired by [Jérôme Lecoq's tweet](https://twitter.com/LecoqJerome) about the
[OpenScope Community Predictive Processing](https://github.com/AllenNeuralDynamics/openscope-community-predictive-processing)
project.

## Run

```bash
python3.12 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python fetch_brain_mesh.py # ~7MB of Allen CCF region meshes
.venv/bin/python prep_session.py # streams a slice from DANDI

python3 -m http.server 8788
open http://localhost:8788/viewer.html
```

## Honest framing

- **Data:** Allen Visual Coding Neuropixels, DANDI [000021](https://dandiarchive.org/dandiset/000021), session `715093703`.
- **Stim:** drifting gratings (randomized by design — so the on-screen "sequence surprise" panel reacts to trial *transitions*, not to predictive-coding deviants).
- **3D anatomy:** AP and DV are real CCF microns. ML is reconstructed per-unit by sampling each unit's anatomical region's actual mesh extent — DANDI 000021's NWB packaging mirrors the ML coord (z) from DV (y); see `prep_session.py:load_region_ml_bounds`.

Built to drop onto the OpenScope PP data once those NWBs ship CCF registration.
130 changes: 130 additions & 0 deletions viewers/watch-the-mouse-think/fetch_brain_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
Download Allen CCF meshes and convert them to compact binary form
the viewer can load fast.

assets/brain.bin — whole brain shell (structure 997, "root")
assets/region_<acronym>.bin — one per recorded region
assets/regions.json — index { acronym: { file, color, ccf_id } }

Each .bin layout:
HEADER: n_vertices uint32, n_indices uint32
VERTS: float32 * n_vertices * 3 (Allen CCF microns)
INDICES: uint32 * n_indices (flat triangle list)
"""
import json
import struct
import urllib.request
from pathlib import Path

import numpy as np

HERE = Path(__file__).parent
ASSETS = HERE / "assets"
ASSETS.mkdir(exist_ok=True)

MESH_URL_TMPL = ("https://download.alleninstitute.org/informatics-archive/"
"current-release/mouse_ccf/annotation/ccf_2017/"
"structure_meshes/{sid}.obj")

# Allen Mouse Brain CCF structure IDs. Verified against the Allen ontology.
# Acronym -> (structure_id, display_color_hex).
REGIONS = {
# whole brain (translucent container; rendered separately)
"root": (997, "#9bb0d0"),
# visual cortex hierarchy
"VISp": (385, "#9bd8ff"),
"VISl": (409, "#7cc7ff"),
"VISpm": (533, "#c0a8ff"),
"VISam": (394, "#b9a6ff"),
"VISrl": (417, "#85d0c5"),
# thalamic visual relays
"LGd": (170, "#ffd166"),
"LP": (218, "#ffb466"),
"PO": (1020, "#8ed6ff"),
# hippocampus subfields
"CA1": (382, "#ff8da3"),
"CA3": (463, "#ff7090"),
"DG": (726, "#ff5b80"),
# midbrain / pretectum
"APN": (215, "#88f0c0"),
}


def fetch_obj(structure_id: int) -> Path:
obj_path = ASSETS / f"struct_{structure_id}.obj"
if obj_path.exists() and obj_path.stat().st_size > 0:
return obj_path
url = MESH_URL_TMPL.format(sid=structure_id)
try:
urllib.request.urlretrieve(url, obj_path)
except Exception as e:
if obj_path.exists():
obj_path.unlink()
raise RuntimeError(f"download {structure_id} ({url}): {e}")
return obj_path


def parse_obj(path: Path):
verts, faces = [], []
with open(path, "rb") as f:
for line in f:
if not line:
continue
t = line[:2]
if t == b"v ":
_, x, y, z = line.split()[:4]
verts.append((float(x), float(y), float(z)))
elif t == b"f ":
idxs = [int(p.split(b"/")[0]) - 1 for p in line.split()[1:]]
for k in range(1, len(idxs) - 1):
faces.append((idxs[0], idxs[k], idxs[k + 1]))
return np.asarray(verts, dtype=np.float32), np.asarray(faces, dtype=np.uint32).ravel()


def write_bin(verts: np.ndarray, indices: np.ndarray, path: Path):
with open(path, "wb") as f:
f.write(struct.pack("<II", verts.shape[0], indices.size))
verts.tofile(f)
indices.tofile(f)


def bake_one(acronym: str, sid: int) -> tuple[Path, int, int] | None:
try:
obj = fetch_obj(sid)
except RuntimeError as e:
print(f"[skip] {acronym} (sid={sid}) — {e}")
return None
v, idx = parse_obj(obj)
if v.size == 0:
print(f"[skip] {acronym} (sid={sid}) — empty mesh")
return None
out_name = "brain.bin" if acronym == "root" else f"region_{acronym}.bin"
out_path = ASSETS / out_name
write_bin(v, idx, out_path)
print(f"[bake] {acronym:<6} sid={sid:<5} {v.shape[0]:>6} verts {idx.size//3:>6} tris "
f"{out_path.stat().st_size/1e6:.2f} MB -> {out_name}")
return out_path, v.shape[0], idx.size


def main():
print(f"[start] fetching {len(REGIONS)} meshes from Allen CCF …")
index = {}
for acronym, (sid, color) in REGIONS.items():
result = bake_one(acronym, sid)
if result is None:
continue
out_path, nV, nI = result
index[acronym] = {
"file": ("assets/" + out_path.name),
"ccf_id": sid,
"color": color,
"n_verts": int(nV),
"n_tris": int(nI // 3),
}
with open(ASSETS / "regions.json", "w") as f:
json.dump(index, f, indent=2)
print(f"[done] wrote assets/regions.json with {len(index)} entries")


if __name__ == "__main__":
main()
Loading