Setup & Usage#
Overview#
The VR4Mice pipeline is a DataJoint-based workflow for ingesting raw rig data, computing analysis tables, running DLC and interpolation, and generating summary outputs. It is designed to run from the client container and can be scheduled via cron.
End-to-end workflow (data acquisition β emails)#
Acquisition (rig):
.picklefiles (and optional.npymetadata viagui_transfer/).Ingest:
run.py populateβ raw tables invr4mice/base.Analysis:
run.py analysisβbase_analysistables (DataFrame,BoxDataFrame,GitCommit).DLC + interpolation:
run.py dlcthenrun.py interp(includessession_metricstables).Latency:
run.py latencyβ photodiode alignment tables.Environment-specific (manual or via
cron_scenario.py):Local server:
run.py inputs_videos, thenrun.py fetchβ refreshes/shared/gui_menu.npyfor the rig GUI.AWS / remote (
--aws):run.py decisionon/data/processed.
Summaries:
run.py summaryβSummaryPlotsfor sessions with bothDataFrameandBoxDataFrame; cron also runssummary_emails.send_pending_summary_emailsto send or retry plot notification emails (see Emails).
After upgrading to DataJoint 2.x (once per rig, before relying on cron):
# Existing database only: add :<blob>: markers to column comments (dry-run first)
python scripts/migrate_to_dj2.py --dry-run
python scripts/migrate_to_dj2.py
# Always after a DJ 2.x upgrade (and after migrate_to_dj2 on existing DBs)
python run.py maintenance
migrate_to_dj2.pyβ metadata-only; fixes blob column comments on legacy databases. Skip on a fresh DJ 2.x install (it will report nothing to migrate).run.py maintenanceβ rebuilds DataJoint lineage tables for all pipeline schemas (includingsummary_emails). Does not repopulate computed tables.
Normal cron runs do not replace either step.
Repository layout (dj_pipeline)#
run.py: manual CLI entrypoint for running pipeline modes (user mode).cron_scenario.py: scheduled runner for full pipeline execution (crontab call).vr4mice/: core schema, analysis, and action code.gui_transfer/: GUI configuration and data transfer tools.base/: base schemas and actions (full and minimal modes: mice, exp tables, connections and email functions).backup/: backup helpers (local/remote sync scripts).cron_*.sh: cron wrappers for scheduled runs (standard, reboot, AWS).Dockerfile+requirements-docker.txt: client image build and pinned runtime dependencies.docker/entrypoint.sh: creates runtime user (PUID/PGID) and drops privileges withgosu.docker/cron_common.sh: shared helpers for cron scripts and compose exec.generators/: small utilities to create helper tables/files (e.g., add mice).parse_mice/: parsing helpers for mouse metadata files.mysql_access/: SQL templates for user setup.notebooks/: analysis and figure notebooks (research use).Logs are written on the server in a local
logs/folder and are not part of the repo.
vr4mice module guide#
See vr4mice/README.md. Key components:
schema/: DataJoint table definitions.analysis/: analysis helpers and plotting.actions/: orchestration helpers (ingest, sync, fetch).utils/: logging, schema config, bootstrap,populate_helpers,maintenance.
gui_transfer#
Rig GUI and transfer utilities:
gui.py+config/for metadata capture.utils/andmodules/for GUI logic.Dropdown menu file (
gui_menu.npy) generated on the server byfetch_data.pyand copied to the rig at GUI startup (see GUI dropdown menu and rig setup).Rig session filenames (
{mouse}_{date}_{attempt}, typed suffixes) β see Rig filename contract andgui_transfer/README.md.
base (schemas)#
base_schemas: fullexpandmiceschema definitions.base_min_schemas: minimalexp/micefor GUI + collab when full schemas are not available.
Getting Started β For Everyday Users#
This quick guide helps you connect to the database and run the pipeline without deployment experience.
Requirements#
Bash
Docker + Docker Compose
GNU Make
Database credentials (host/port, user, password)
Quick start (recommended)#
Use the interactive setup script to configure .env/env.py and start containers:
bash quick_start.sh
Quick start β client mode (connect to an existing DB)#
Use when the database already runs elsewhere.
bash quick_start.sh
Choose:
Mode:
clientDJ host:
ip:portor hostname (e.g.,127.0.0.1:3309)DJ user / DJ password
This starts only the client container.
Quick start β deployment mode (DB + client on server)#
Use when deploying both database and client containers on a server.
bash quick_start.sh
Choose:
Mode:
deploymentProvide mount paths for database, data, shared, and screen recordings
Set DB bind IP/port and MySQL root password
Defaults shown by the script:
/mnt/database/vr4mice/vr4mice_database/databaseβ/var/lib/mysql/mnt/database/sharedβ/shared/mnt/database/vr4mice/vr4mice_database/dataβ/data/mnt/neuropixel_data/vr4mice/raw_screen_recordingsβ/vr4mice_screen_recordingsrepo root (
./) β/app./base/base_min_schemasβ/base_schemas./base/base_actionsβ/base_actions
Quick start β import DB dumps#
Use this when you have restricted_dump_*.sql files or a .zip/.tar.gz archive.
bash quick_start.sh
When prompted:
Import DB dumps now? β
yesDump directory or .zip/.tar.gz archive β path to the dump folder or archive
The script creates missing databases and imports each restricted_dump_*.sql.
Imports stay in the foreground and show progress (via pv or dd status=progress
when available).
Step 1 β Clone the repo#
git clone git@github.com:MMathisLab/FreelyMovingVR4Mice.git
cd dj_pipeline
Step 2 β Configure credentials#
Update your credentials in .env (or env.py for local conda runs).
vim .env
Step 3 β Start the client container#
make client_build
make client_up
Step 4 β Connect via IPython or Jupyter#
make ipython
%run run.py connect
Optional Jupyter:
make notebook
Remote Jupyter via SSH tunnel:
ssh -NL 8887:localhost:8887 <user>@<server>
Append & if you want the tunnel in the background.
You can add an SSH config alias (e.g., wm) and then connect with:
ssh -NL 8887:localhost:8887 wm
Step 5 β Verify connection#
from vr4mice.schema import vr4mice
vr4mice.Dataset()
Stop the client#
make client_down
Jupyter / IPython access (recommended: Docker client)#
Use make notebook or make ipython in the client container β dependencies match requirements-docker.txt (DataJoint 2.x).
Host Jupyter (legacy / outside Docker)#
Install matching packages and base schemas:
pip install notebook "datajoint>=2.0.1" pip install base/base_actions/ pip install base/base_min_schemas/
Install graphviz if you want schema diagrams:
sudo apt install -y graphvizUpdate
env.py(server IP, credentials from your administrator)Start
jupyter notebook, open a Python 3 kernel, then:%run env.py %run run.py connect from vr4mice.schema import vr4mice vr4mice.Dataset()
Deployment (server)#
Server deployment (database + client containers)#
The server runs two containers:
Database (MySQL/DataJoint)
Client (pipeline runner)
Note: store MySQL admin credentials in ~/.my.cnf on the server.
make build_all
make up_all
make mysql
make ipython
%run run.py connect
Docker client: base packages and user mapping#
The client image is built from DeepLabCutβs Docker image. Build-time Python dependencies are pinned in requirements-docker.txt and installed by Dockerfile (pip install -r requirements-docker.txt). Pipeline commands use plain python -m pip (no conda activate). At build time and container start, docker/ensure_python_shims.sh symlinks the base imageβs Python into /usr/local/bin so make, cron, and docker compose exec all find python on the compose PATH.
base_schemas / base_actions are bind-mounted from the repo and installed at runtime with pip:
python -m pip install --user --no-deps /base_schemas/
python -m pip install --user --no-deps /base_actions/
make client_up, make base_install, and the cron scripts run pip as the callerβs UID/GID; packages and cache live under /app/.local and /app/.cache/pip on the bind-mounted repo (not under /home/<username>), so any user with access to the repo can run the client.
Running as your host user (file permissions on /app, /data, etc.) is handled by Docker, not conda:
Layer |
Mechanism |
|---|---|
Container start |
|
Compose |
passes |
|
|
Host conda (env.py, vr4mice_env on the rig, DLCliveGUI) is a separate install path for non-Docker workflows. It is not used inside the client container.
Mounts and storage#
In docker-compose.yml, map host storage for database and data volumes:
/dataand/data/summary_plotsmust exist and be writable./sharedis used for GUI menu exports.Use persistent paths (e.g.,
/mnt/database/...) on the server. Network mode:Default is
host. SetCLIENT_NETWORK_MODE=bridgein.env.composeif you need bridge networking.
Deployment defaults (quick_start.sh)#
When using bash quick_start.sh in deployment mode, the default host paths map to:
/mnt/database/vr4mice/vr4mice_database/databaseβ/var/lib/mysql/mnt/database/sharedβ/shared/mnt/database/vr4mice/vr4mice_database/dataβ/data/mnt/neuropixel_data/vr4mice/raw_screen_recordingsβ/vr4mice_screen_recordingsrepo root (
./) β/app./base/base_min_schemasβ/base_schemas./base/base_actionsβ/base_actions
Database deployment notes (server)#
Add user to Docker group:
sudo usermod -aG docker <username>
Create host storage paths (example):
mkdir -p /mnt/database/vr4mice/vr4mice_database/database mkdir -p /mnt/database/vr4mice/vr4mice_database/data mkdir -p /mnt/database/vr4mice/vr4mice_database/data/data/data mkdir -p /mnt/database/vr4mice/vr4mice_database/data/data/dlc_video mkdir -p /mnt/database/vr4mice/vr4mice_database/data/summary_plots mkdir -p /shared
Add MySQL credentials in
~/.my.cnfformake mysql:[client-vr4mice] host=127.0.0.1 user=root password=simple port=3309
Environment variables#
Pipeline (.env β copy from .env.example; loaded into the client container):
DJ_HOST(include port, e.g.127.0.0.1:3309),DJ_USER,DJ_PWDDJ_LAB,GUI,EMAIL,IMG_SRC,VR4MICE_EMAIL_RECIPIENTS,VR4MICE_EMAIL_SINCE
When using the local Docker database, DJ_HOST port must match DB_PORT in .env.compose.
Docker Compose (.env.compose β copy from .env.compose.example; used by make, cron, and docker compose only):
COMPOSE_PROJECT(defaultvr4mice) β must match betweenmake, cron scripts, and manualdocker compose -p β¦callsDB_BIND_IP,DB_PORT,MYSQL_ROOT_PASSWORDDB_DATA_PATH,SHARED_PATH,DATA_PATH,SCREEN_RECORDINGS_PATHDB_IMAGEβ Docker image for the local database service (see below)CLIENT_IMAGE,CLIENT_CONTAINER_NAME,DB_CONTAINER_NAME,CLIENT_NETWORK_MODE,JUPYTER_PORT
MySQL version (5.7 and 8.0)#
The database service in docker-compose.yml uses ${DB_IMAGE:-mysql:8.0}.
Situation |
What to use |
|---|---|
New install (empty |
|
Existing on-disk datadir from MySQL 5.7 |
|
If DB_IMAGE is not set in .env.compose, docker/compose_env.sh (used by make and cron) auto-detects the image from DB_DATA_PATH via docker/detect_mysql_image.sh. quick_start.sh writes the detected value into .env.compose.
Manual override in .env.compose:
DB_IMAGE=mysql:5.7 # legacy datadir
# DB_IMAGE=mysql:8.0 # new installs
Check what Compose will use:
bash docker/compose_env.sh get DB_IMAGE
Upgrading 5.7 β 8.0 (keep data): run MySQL 5.7 against the datadir, shut down cleanly, then switch to 8.0:
docker compose exec db mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" \
-e "SET GLOBAL innodb_fast_shutdown=0; SHUTDOWN;"
# set DB_IMAGE=mysql:8.0 in .env.compose, then:
make up_all
Fresh start (discard local datadir): stop the stack, move or delete DB_DATA_PATH, then start with DB_IMAGE=mysql:8.0.
If the db container exits immediately with code 1 and logs mention a failed upgrade from MySQL 5.7, the datadir version likely does not match DB_IMAGE β set DB_IMAGE=mysql:5.7 or wipe the datadir for a new 8.0 install.
Remote/AWS DB credentials for scheduled AWS runs live in .env-aws (copy from .env-aws.example); this file is not committed.
If you still have Docker settings in .env from an older setup, move them to .env.compose (Makefile falls back to .env for COMPOSE_PROJECT only during migration).
Notes:
Default summary email recipients come from
VR4MICE_EMAIL_RECIPIENTSin.env(comma-separated experimenter names, e.g.mathislab). The session experimenter fromexp.Sessionis added when present. See Emails.VR4MICE_EMAIL_RECIPIENTSis required if base schemas (exp/mice) are not in use, because experimenter names are otherwise missing from GUI metadata.VR4MICE_EMAIL_SINCE(formatYYYY-MM-DD) limits automatic summary emails to new sessions on or after that date. If unset, cron does not send summary emails (avoids emailing the full backlog). Set this on the rig when enablingEMAIL=true.
Schemas (what they do)#
vr4mice: core tables for datasets, raw signals, metadata, and derived features.base: links datasets toexpandmiceschema information.base_analysis: analysis outputs and summary plots.summary_emails: tracks summary plot notification emails (SummaryPlotEmail).dlc,interpolated_trajectories,latency_tests,decision,inputs_videos: downstream analysis modules.
Schema modes#
Two base schema modes are supported:
Full base schemas:
base_schemas(exp/mice full definitions).Minimal schemas:
base_min_schemas(minimal exp/mice for collab + basic metadata).
Both modes work; choose minimal when only GUI dropdowns and basic metadata are needed.
Data import/export (restricted dumps)#
See Data import/export for export/import workflows, or:
Exporting restricted dumps (
export_restricted_dump.sh)Importing dumps with
quick_start.shManual mysql import
See Deploy the DataJoint database locally for a full local deploy workflow that clones the data archive, runs quickstart, and connects Jupyter.
GUI vs non-GUI mode#
GUI mode (
GUI=true): expects.pickle+.npyinputs.Non-GUI (
GUI=false): uses.pickleonly; metadata is reconstructed as needed. Use GUI mode when experiment metadata is collected via the rig GUI.
Manual runs (run.py)#
Use --verbose for DEBUG logging and DataJoint lineage details (cron stays quiet by default).
Typical sequence:
%run run.py populate
%run run.py analysis
%run run.py summary
%run run.py fetch # refresh GUI dropdown menu on /shared
...
One-off after a DataJoint upgrade or when adding a new schema:
%run run.py maintenance
See Maintenance for what this step does and when it is required.
Maintenance#
python run.py maintenance rebuilds DataJoint lineage metadata. It is a one-off
setup step β not part of cron or normal populate / analysis runs.
What it maintains#
Each pipeline schema (mice, exp, vr4mice, base, base_analysis, dlc, β¦)
has a hidden table called ~lineage. DataJoint 2.x stores, per table attribute,
where that attribute came from in the foreign-key dependency graph (semantic
lineage). Joins and populate() use this to match attributes that share the same
origin, not just the same column name.
run.py maintenance calls schema.rebuild_lineage() on every pipeline schema (in
dependency order) and repopulates those ~lineage rows from current FK definitions.
What it does not touch#
Raw rig files (
.pickle,.npy, DLC outputs)Computed table contents (
DataFrame,SummaryPlots,DLC, etc.)Blob column comments (
scripts/migrate_to_dj2.pyhandles that separately)
When to run#
Situation |
|
|
|---|---|---|
Legacy DB upgraded to DJ 2.x |
Yes (first) |
Yes (after) |
Fresh DJ 2.x database |
Skip (no-op) |
Yes (once) |
New schema added to pipeline |
No |
Yes (once) |
Lineage / join errors |
Maybe |
Yes |
After a DJ 2.x upgrade on an existing database:
python scripts/migrate_to_dj2.py --dry-run
python scripts/migrate_to_dj2.py
python run.py maintenance
See also docs/migration/minimum_migration_guide.md.
Testing / quick sanity#
Populate test:
%run run.py populate from vr4mice.schema import vr4mice vr4mice.Dataset()
Analysis test:
%run run.py analysis
Cron and Docker operations#
Scheduled pipeline runs use wrapper shell scripts that call docker compose with project name vr4mice (override via COMPOSE_PROJECT in .env.compose or the environment). Shared logic lives in docker/cron_common.sh.
Architecture#
Component |
Role |
|---|---|
|
Runs populate β analysis β DLC β β¦ with per-step logging; exits non-zero if any step fails |
|
Shared compose/exec helpers (project name, UID/GID, pip install) |
|
Local nightly run ( |
|
AWS/remote run ( |
|
After host reboot: wait 120s, start |
|
Same compose project and exec pattern as cron scripts |
Docker client vs host conda: the client container uses system Python + pip (DeepLabCut base image). Conda on the rig or laptop (env.py, vr4mice_env, DLCliveGUI) is a separate install path and is not used inside the Docker client. Base package install and user mapping are described under Docker client: base packages and user mapping.
Makefile targets (common)#
Target |
Purpose |
|---|---|
|
Start client, install |
|
Start |
|
Re-install base packages in running client |
|
Interactive session in client |
|
Install/update crontab entries (see below) |
|
Manual local cron run |
|
Manual AWS cron run |
|
Manual reboot startup script |
All compose commands use docker compose -p vr4mice by default (COMPOSE_PROJECT in Makefile / .env.compose).
Crontab setup#
From dj_pipeline/ on the server:
make add-cron
This merges (does not wipe) your crontab and registers:
Schedule |
Script |
Log |
|---|---|---|
|
|
|
|
|
|
The nightly job runs local first; AWS runs only if local exits successfully (&& chain). cron_scenario.py must exit 0 (no failed steps) for AWS to start.
Prerequisites on the server#
.envβ pipeline / DataJoint credentials (see.env.example).env.composeβ Docker Compose settings (see.env.compose.example).env-awsβ remote DB credentials for AWS runs (see.env-aws.example)Docker daemon running; cron user in the
dockergroupRun
make add-cronfromdj_pipeline/(paths are resolved from that directory)
Manual test (before enabling cron)#
cd dj_pipeline
make cron-local && make cron-aws
# or:
bash cron_script.sh && bash cron_script_aws.sh
Migrating compose project name#
Older setups may have containers under project mysqltest. After updating:
COMPOSE_PROJECT=mysqltest make down_all # stop old stack
Then start with your project name in .env.compose (or legacy COMPOSE_PROJECT=mysqltest make up_all if you have not migrated yet).
Multiple deployments on one server#
Other vr4mice_* compose projects on the same host are fine for the primary stack (COMPOSE_PROJECT=vr4mice) as long as container names and DB_PORT do not clash.
make check-compose-project blocks only real collisions:
container names (
vr4mice_db,vr4mice_${USER}) already used by another compose projecthost port (
DB_PORT, default3309) already in use by another stack
For a second deployment (sandbox / personal DB), use unique values in .env.compose:
COMPOSE_PROJECT=vr4mice_dev
DB_CONTAINER_NAME=vr4mice_db_dev
CLIENT_CONTAINER_NAME=vr4mice_${USER}_dev
DB_PORT=3310
DB_DATA_PATH=/path/to/dev/database/
DATA_PATH=/path/to/dev/data/
Use separate data paths so MySQL volumes do not overwrite each other.
To skip the check (not recommended): VR4MICE_COMPOSE_FORCE=1 make up_all.
AWS mode (pipeline behaviour)#
--aws mode uses /data/processed and disables moving raw files.
run.py --aws populatecron_scenario.py --aws
Backup and AWS sync#
Backup helpers live in backup/ and cron scripts:
backup/backup.shcron_script_aws.sh
These scripts are intended for append-only sync to AWS or remote storage. If older versions should be replaced, manual cleanup may be required before re-populating. Update paths and credentials to match your storage backend.
Error handling and FailedSession#
Failures are recorded in vr4mice.FailedSession keyed by (dataset, failed_table_name):
Used as a per-table skip list during
populate_pending(a photodiode skip does not blockSummaryPlots).Some entries are intentional permanent skips (e.g. no photodiode signal, no trials after excluding initialization trial 1).
Remove entries to retry after fixing data or deploying a fix.
Example removal (one table):
from vr4mice.schema import vr4mice
(vr4mice.FailedSession() & 'dataset="Grizzly_2026-01-29_1"' & 'failed_table_name="DataFrame"').delete()
Bulk cleanup after duplicate-key fixes:
(vr4mice.FailedSession() & 'failed_table_name="SignalsPhotodiode"' & 'error_message LIKE "%Duplicate entry%"').delete()
GitCommit table#
base_analysis.GitCommit stores commit hash and modified files for provenance.
Emails#
Summary emails are sent when EMAIL=true and VR4MICE_EMAIL_SINCE is set.
Who receives emails#
Recipients are resolved in two layers:
Lab default list β
VR4MICE_EMAIL_RECIPIENTSin.env(comma-separated experimenter names looked up inexp.Experimenter), e.g.VR4MICE_EMAIL_RECIPIENTS=mathislabormathislab,experimenter_a,experimenter_b.Session experimenter β if
exp.Sessionhas an experimenter for that dataset, theirmailis added when not already on the list.
Names must match experimenter_name in exp.Experimenter (lowercase). Addresses always
come from exp.Experimenter.mail, not from the env var directly.
How to add or change recipients#
Add the person to
exp.Experimenterwith a validmailaddress (required for lookup). From Python or a notebook:from base_schemas.schemas import exp exp.Experimenter.insert1( dict( experimenter_name="experimenter_a", full_name="Example User", mail="user@example.com", ), skip_duplicates=True, )
Include them in the default list β add their
experimenter_nametoVR4MICE_EMAIL_RECIPIENTSin.env.Session-only recipient β assign that experimenter on
exp.Sessionfor the dataset; they are added automatically for that sessionβs summary email.
Enable on the rig:
EMAIL=true
VR4MICE_EMAIL_RECIPIENTS=mathislab
VR4MICE_EMAIL_SINCE=2026-07-01 # only sessions on/after this date
Tracking and retry: the summary_emails schema stores one row per send attempt in
SummaryPlotEmail (timestamp, recipients, error if any). After SummaryPlots populate,
cron calls send_pending_summary_emails to send or retry emails for eligible sessions
(those with a plot row, on/after VR4MICE_EMAIL_SINCE, and no successful send yet).
Inspect pending or sent emails:
from vr4mice.schema import summary_emails
summary_emails.pending_summary_email_keys()
(summary_emails.SummaryPlotEmail() & "send_error IS NULL").fetch()
Logs#
Runtime logs are written to the local logs/ folder on the server/container.
Cron logs per-step timing ([cron] start/done β¦) at INFO; routine skip and duplicate
messages are DEBUG unless you run with --verbose.