NWB alignment#
The NWB alignment file (<project>/.ethograph/alignment.nwb) stores trial
timing, media file paths, and stream offsets. It is the single source of
truth for “what file corresponds to what trial, and when did it start”.
See Pairing and alignment for the user-facing walkthrough.
At runtime the same interface is available via dt.nwb_alignment on a
loaded TrialTree.
Pairing media files#
- class ethograph.io.pairing.SourceSpec(stream, folder=None, files=(), pattern=None, device=None, software=None, extension=None)[source]#
One media source of a rig: where its files are and how they map to trials/devices.
- ethograph.io.pairing.discover_media(sources)[source]#
The pairing table:
trial+ one{stream}_{device}column per source/device, full paths.Each cell is the file’s full path, so the table says where every file is and
pair_media()needs no root; the NWB trials table receives the basenames.pattern
None: files natsorted; row i is trial i+1; device fromdeviceor the default.pattern set: named groups build trial + device columns; a file the pattern does not match is skipped (logged at INFO).
Extensions: only those in
ethograph.io.validationVIDEO_/AUDIO_/POSE_EXTENSIONSfor the source’s stream;extensionnarrows a scanned folder to one of them (a DLC folder holds.h5and.csvtwins of every file).Fail fast: two pattern-less sources with different file counts ->
ValueErrornaming both counts; a pattern that matches no file at all ->ValueError; two files on one (trial, device) ->ValueErrornaming them; an empty folder ->ValueError.A trial present in one source but not another leaves
""in the missing cell (like the current template).
- Return type:
- ethograph.io.pairing.pair_media(trial_table, stream_rates=None, session_wide=None, output_path=None, pose_fps=None, individuals=None, on_existing='extend')[source]#
Write
.ethograph/alignment.nwbfrom a pairing table.Each per-trial
{stream}_{device}column becomes a trials-table column plus oneImageSerieswhose segments start at the trial starts;session_widestreams each get oneImageSerieswith astarting_time.start_time/stop_timeare optional — omitted, they are inferred from the media files the table names.What happens to a file that is already there is the caller’s decision, never the filesystem’s (
on_existing). Annotating somebody’s NWB and building a session’s own alignment are different jobs, and a function that guessed between them by looking at the disk was eventually called from the path that wanted the other one.on_existing="extend"(the default) adds to an existing NWB that already has a trials table — a neuroconv-written source — in place throughedit_nwb(), never rebuilding it: per-trial columns from trial_table become new trial columns (row count must equal the file’s trials, elseValueError) andsession_widestreams acquisitionImageSeries; existing acquisition names are left alone.on_existing="replace"deletes the file and writes it afresh — what a drop or the wizard does, because an alignment built from dropped files is derived data and rebuilding it must be idempotent.- Parameters:
trial_table (
DataFrame) –trial+{stream}_{device}columns holding each file’s full path (asdiscover_media()returns); a bare filename is accepted when times are given. The NWB trials table receives the basenames.start_time/stop_timeoptional.stream_rates (
dict[str,float] |None) – Sampling rate per stream, e.g.{"video": 30.0, "audio": 48000.0}; a{stream}_{device}key ("video_cam-2": 60.0) overrides its stream’s rate.session_wide (
dict[str,tuple[str,float,float]] |None) –{"{stream}_{device}": (file, rate_hz, starting_time_s)}for streams that are one file spanning the whole session rather than one file per trial.output_path (
str|Path|None) – Where to write the.nwbfile.on_existing (
str) – What an existing output_path means:"extend"pairs into its trials table,"replace"rebuilds it from scratch. Anything building a session’s own alignment passes"replace", so running it twice is the same as once.pose_fps (
float|None) – Frame rate for probing pose files, when inferring times from a table whose only media columns arepose_*.individuals (
Optional[Sequence[str]]) – The individuals this session labels. Recorded in the session record, which is their one home; a dataset’s individual dim is checked against it on load.
- Return type:
The in-memory
NWBFile.
Creating alignment files#
- ethograph.io.nwb_alignment.make_nwb_alignment(nwb_path=None)[source]#
Create a EmpytAlignment from an NWB path, falling back to base EmpytAlignment.
- ethograph.io.nwb_alignment.discover_nwb(source)[source]#
Find the session’s alignment record from the session folder or any file in it.
Search order: 1.
<session>/.ethograph/alignment.nwb2. Any.nwbfile in<session>/.ethograph/
- ethograph.io.nwb_alignment.sync_acquisition_for_streams(nwbfile, stream_rates, media_paths=None)[source]#
Create ImageSeries acquisition items for ALL external media streams.
Reads the trials table to discover
{stream}_{device}columns. For each stream+device pair, creates anImageSeriesinnwbfile.acquisitionwithexternal_file,starting_frame, andrate(ortimestampsif offsets are present).- Parameters:
nwbfile (
NWBFile) – NWB file with a populated trials table.stream_rates (
dict[str,float]) – Mapping of stream name to sampling rate, e.g.{"video": 30.0, "audio": 44100.0, "pose": 30.0}. A{stream}_{device}key ("video_cam-2": 60.0) overrides its stream’s rate.media_paths (
DataFrame|None) – The pairing table the trials came from, one row per trial in the same order, whose media cells hold full paths. When given,external_filerecords those paths, so a reader finds the media without being told the folder; the trials table keeps the basenames either way.
- Return type:
Reading alignment metadata#
- class ethograph.io.nwb_alignment.NWBAlignment(nwb_path)[source]#
Session metadata backed by an NWB file.
All external media (video, audio, pose) are stored as ImageSeries in acquisition with
{stream}_{device}naming. Timing comes fromrateortimestampson the ImageSeries.- devices(stream)[source]#
Discover devices from trials table columns AND acquisition items.
Three sources, checked in order:
Trials table columns (
video_cam_1-> devicecam_1).Acquisition ImageSeries following
{stream}_{device}naming.Acquisition ImageSeries whose
external_fileextensions match known media types (e.g..mp4-> video,.wav-> audio).
- electrical_series()[source]#
Discover ElectricalSeries in acquisition. Returns list of {name, path, n_channels, rate}.
- get_media(trial, stream, device=None)[source]#
Return the trial table’s media filename for stream/device.
A stored value that is an absolute local path (e.g. baked in by a template authored on a different machine) is reduced to its basename, so callers can always safely join it onto their own video/audio/pose folder instead of resolving to a path that only existed on the authoring machine.
- get_stream_rate(stream, device=None)[source]#
Read the sampling rate for a stream from its acquisition ImageSeries.
- property has_real_timing: bool#
Whether the trials table has meaningful start/stop times.
Returns False when all trials have identical placeholder timing (e.g. start=0.0, stop=1.0 for every row), which indicates the NWB was generated without real session timing.
- resolve_media_path(trial, stream, device=None, fallback_folder=None)[source]#
Resolve the full path for a media file.
Try the ImageSeries
external_filepath for this trial (if on disk).Fallback: trial table filename +
fallback_folder.Returns
Noneif unresolvable.
A video may be an image folder (
io/image_sequence.py), so “on disk” meansmedia_exists(), never a bareisfile.
- stream_offset_for_trial(trial, stream, device=None)[source]#
Trial-relative time of sample 0 of the file that holds trial.
0.0 for per-trial files, negative for a session-wide file that started before the trial. The file is found by time (the trial’s start falls in its span), never by position – see
_file_for_trial.- Return type: