Session Files¶
File types produced per session¶
| File suffix | Description |
|---|---|
*.msw.session.yaml |
Session metadata: format version, task, subject, process settings, task settings |
*.msw.df.jsonl |
Trial dataframe: one JSON object per line; first line is a version header |
*.msw.log |
Session-scoped text log (INFO-level output) |
*.msw.plot_spec.yaml |
Plot specification for the online monitor |
*.msw.csv |
Raw Bpod event log: present only in legacy sessions (pre-v2) |
For multi-subprotocol tasks (optotagging), each subprotocol writes its own JSONL:
| File | Description |
|---|---|
{basename}_{protocol}.msw.df.jsonl |
Per-subprotocol trial dataframe |
session_manifest.yaml |
Lists subprotocols, their JSONL files, barcodes, and completion status |
.msw.session.yaml structure¶
msw_format_version: 2
process:
msw_version: "2.1.1"
git_commit: "abc1234"
session_uuid: "d4e5f6..."
task: sequence
subject: mouse001
setup: setup-npx2
serial_port: /dev/ttyACM5
out_path: /data/data
session_folder: /data/data/mouse001/mouse001__20260526_100149_223167__sequence
session_basename: mouse001__20260526_100149_223167__sequence
datetime: "20260526_100149_223167"
task_settings:
start_level: 1
stop_trials: 500
...
# present only when --host openephys was passed
host_session:
backend: openephys
session_name: "mouse001__20260525_074402__ephys_multi_behavior"
subject: mouse001
parent_directory: "/data/rig1"
oe_session_name: "mouse001__20260525_074402__pxi"
status: IDLE
host_session is written at session start when --host openephys is passed.
It captures which external session container this behavioural acquisition was
nested inside, linking back to the ephys data directory. session_name
matches the second path component of base_text as set by oe-remote, which
is also the folder name passed as linked_to to generate_session_paths().
Older session files use the key host_acquisition or parent_acquisition
(still read by the reader for backward compatibility).
.msw.df.jsonl structure¶
First line is a version header:
Subsequent lines are one trial dict each:
{"Bpod start timestamp": 0.0, "Trial start timestamp": 1.23, "trial_index": 0, "outcome": "correct", ...}
Read via load_trial_data(filepath) (returns a list of dicts, header skipped) or
read_trial_df(filepath) (returns a pandas DataFrame).
read_session_data() return keys¶
from murineshiftwork.readers.session import read_session_data
| Key | Type | Description |
|---|---|---|
namespace_version |
str \| None |
Basename datetime format: "v1" (microsecond precision) or None if unparseable |
artifact_format |
str |
Storage format: "session_yaml", "separate_json", or "legacy" |
msw_version |
str |
Version string from process.msw_version; "legacy" or "< 1.0.0" for older sessions |
is_legacy_session |
bool |
True if session uses pre-.msw. file naming |
is_complete_session |
bool |
True if all required files are present and loaded |
is_ephys_session |
bool |
True if settings.ephys key is present |
df |
DataFrame \| None |
Trial dataframe from .msw.df.jsonl or .msw.df.pkl |
settings.task |
dict |
Task settings |
settings.process |
dict |
Process/run settings (non-legacy sessions) |
settings.stage |
dict |
Stage settings (if present in session YAML) |
namespace_version and artifact_format are derived by detect_session_format()
from murineshiftwork.readers.namespace. Call that function directly if you need
format info without loading session data.
Artifact format constants¶
| Constant | Value | Description |
|---|---|---|
ARTIFACT_FORMAT_SESSION_YAML |
"session_yaml" |
Single .msw.session.yaml (v2+, current) |
ARTIFACT_FORMAT_SEPARATE_JSON |
"separate_json" |
Two separate .msw.settings.process.json + .msw.settings.task.json files |
ARTIFACT_FORMAT_LEGACY |
"legacy" |
task_settings.py + switching.pkl/csv |
Data directory layout¶
All sessions use a consistent 3-level layout: subject / session / acquisition.
The session directory is the outer container; the acquisition directory is where
.msw. files are written.
Standalone (no host, MSW auto-generates the session container):
<data_dir>/
└── <subject>/
└── <subject>__<datetime>__session_<task>/ ← SESSION container (standalone)
├── acquisition_manifest.yaml
└── <subject>__<datetime>__<task>/ ← ACQUISITION dir
├── session_manifest.yaml
├── <basename>.msw.session.yaml
├── <basename>.msw.df.jsonl
├── <basename>.msw.log
└── <basename>.msw.plot_spec.yaml
Host-linked (e.g. Open Ephys via --host openephys; session container
name comes from the host):
<data_dir>/
└── <subject>/
└── <subject>__<datetime>__ephys/ ← SESSION container (from OE)
├── acquisition_manifest.yaml ← written by MSW
├── Record Node 101/ ← written by Open Ephys
└── <subject>__<datetime>__<task>/ ← ACQUISITION dir
├── session_manifest.yaml
├── <basename>.msw.session.yaml
└── ...
Optotagging (multi-subprotocol, host-linked):
<data_dir>/
└── <subject>/
└── <subject>__<datetime>__session_optotagging/ ← SESSION container
├── acquisition_manifest.yaml
└── <subject>__<datetime>__optotagging/ ← ACQUISITION dir
├── session_manifest.yaml
├── <basename>.msw.session.yaml
├── <basename>.msw.log
├── <basename>__power_ramp/
│ ├── <basename>_power_ramp.msw.df.jsonl
│ ├── <basename>_power_ramp.msw.csv
│ └── <basename>_power_ramp.<camera>.avi
├── <basename>__following_test/
│ ├── <basename>_following_test.msw.df.jsonl
│ └── <basename>_following_test.msw.csv
└── <basename>__antidromic_primary/
├── <basename>_antidromic_primary.msw.df.jsonl
└── <basename>_antidromic_primary.msw.csv
Each subprotocol gets a {basename}__{protocol}/ subdirectory containing its
JSONL trial data, Bpod CSV event log, and any camera recordings. The
acquisition dir holds only the shared .msw.session.yaml, .msw.log, and
session_manifest.yaml.
Legacy format (before namespace v3, depth-2, no session container):
<data_dir>/
└── <subject>/
└── <subject>__<datetime>__<task>/ ← session dir at depth 2
├── <basename>.msw.session.yaml # or .settings.process.json for older
├── <basename>.msw.df.jsonl # or .df.pkl / switching.pkl for legacy
└── <basename>.msw.csv # Bpod raw, may be absent
load_subject() detects legacy 2-level data automatically and loads it as-is.
Sequence task: subject state fields¶
At session end, save_session_end() writes a summary to the per-subject state store
(~/.murineshiftwork/sequence/<subject>_level.json) and the labwatch payload:
| Field | Description |
|---|---|
level |
Training level at session end |
session_start_level |
Training level at session start |
total_trials |
Total update() calls (includes barcodes and no-response trials) |
task_trials |
Trials with a poke response that were scored (excludes no-response and barcodes) |
no_response_trials |
Trials where the animal did not initiate within init_port_timeout_s |
session_reward_count |
Number of valve openings (rewards delivered) |
session_liquid_ul |
Total water dispensed (µL) |
The exit log line reads: "Session end: 'mouse001': level 12, trials 312 (289 task, 23 no-response)".
Central log file¶
A separate per-run log is written to ~/.murineshiftwork/logs/ with the filename:
For example: setup-1--2026-05-21T143201--mouse001--sequence.log
The central log contains DEBUG-level output from all modules. The session log
(<session_basename>.msw.log) contains INFO-level output. Both are kept.
Up to 100 central log files are retained; older ones are pruned automatically.
Override the central log path with --log-file <path> (msw run only).