← Overview

Every step, in full.

The overview says what V-TRACE does. This says exactly how: the flags each command takes, the files a run reads and writes, and the reasons behind the defaults you would otherwise have to guess at.

Install and run

V-TRACE needs Python 3.9 or newer. Training, evaluation and prediction need a CUDA-capable PyTorch environment and exit with a clear message if torch.cuda.is_available() is false. Annotation needs neither: it runs entirely in the browser.

bash — install
$ python -m pip install vtrace-behavior
$ vtrace prepare --weights all
$ vtrace

vtrace prepare downloads model weights ahead of time and makes sure ffmpeg and ffprobe are on PATH, fetching bundled copies when they are not. Its --weights flag takes none, all, or one preset name.

The interactive session

Run vtrace with no arguments at a terminal and it opens a session: the annotator starts on a background thread, the start screen prints the address it is listening on, and a bordered command box opens under it. The box is where the command the configuration page wrote goes — paste it, press Enter, and it runs. Ordinary commands (demo download, help) run from the same box. Esc closes it and leaves the plain vtrace > prompt, where commands are typed without repeating the program's name, arrow keys walk the history, run opens the box again and exit leaves. Anywhere without a terminal, the same screen is printed and the program returns rather than waiting for input it will never get.

There is no tab completion and no reverse history search. The command set is short enough to read off the screen above, and a session that prints its whole surface on arrival does not need a second, hidden way to discover it.

vtrace — the start screen
 ██╗   ██╗     ████████╗██████╗  █████╗  ██████╗███████╗
 ██║   ██║     ╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██╔════╝
 ██║   ██║ ███╗   ██║   ██████╔╝███████║██║     █████╗
 ╚██╗ ██╔╝ ╚══╝   ██║   ██╔══██╗██╔══██║██║     ██╔══╝
  ╚████╔╝         ██║   ██║  ██║██║  ██║╚██████╗███████╗
   ╚═══╝          ╚═╝   ╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝╚══════╝
 Video-based Temporal Recognition and Annotation of
 Continuous Ethograms of Animal Behavior   v1.0.0

 Annotator running — open it in Chrome or Edge
   http://localhost:8765      read and label videos on this computer

 Type one of these and press enter:

   run                        open the command box: paste what the annotator wrote

   demo download              the official CalMS21 videos + the detector
   demo predict               label the 19 held-out test videos, write predictions
   demo train                 train on the 70 official training videos, score the test split

   help                       documentation, on the web
   exit                       leave the session

 The box below is run — paste what the annotator wrote, or type one.
 Esc leaves the box and comes back to this list.

Every line on the screen is a real command, so whatever is copied goes through the ordinary argument parser.

Under the start screen, the box. What is pasted into it is the session form the configuration page writes: a keyword per line, the flags under it, and then between steps. The folders in it are named, not located — <train#8b1d0c47> is the folder called train whose contents hash to those digits — and the first thing the session prints is where it found each one on this machine. Enter runs; ctrl-j starts a new line by hand; Esc closes the box. From a shell the same chain is one command line, with the folders written out.

vtrace — the command box, with a pasted run
run — paste the command from the annotator, or type one
train
--model maev2b
--output '<runs#3f9a2c1e>'
--pairs '<train#8b1d0c47>/mouse001.mp4=<train#8b1d0c47>/mouse001.csv'
then
eval
--pairs '<test#c41e77a0>/mouse071.mp4=<test#c41e77a0>/mouse071.csv'

enter runs it · ctrl-j starts a new line · esc goes back to the list

[ Run ]enter [ Back ]esc
  <runs#3f9a2c1e>/data/calms21/runs
  <train#8b1d0c47>/data/calms21/train
  <test#c41e77a0>/data/calms21/test

=== step 1/2: train --model maev2b --output /data/calms21/runs … ===

The box leaves the mouse to the terminal: while it is open, text on the screen can still be selected and the scrollback still scrolls, so the last command's output stays within reach. The two buttons are reached with tab, and their key is printed on them. Escape cancels, but the binding is deliberately not eager. Every sequence a terminal sends begins with an escape byte — an arrow key, a bracketed paste — and an eager binding would close the box on the first byte of any of them.

The annotator on its own

vtrace app serves the annotator without the session, on port 8765 or the next free port up to 8784. It binds loopback only. Over SSH, forward the port rather than looking for a host flag:

bash — over SSH
# on your laptop
$ ssh -L 8765:localhost:8765 you@your-gpu-box

# in that session, on the GPU box
$ vtrace app
Annotator on http://localhost:8765 — open it in Chrome or Edge

# then open http://localhost:8765 in the laptop's browser

Loopback only, and nothing is lost by it. The page reads videos from the computer running the browser, and the command it writes names folders rather than paths — so whichever machine the command is pasted into is the one that looks them up. Forwarding the port over SSH is the whole story.

The annotator

The annotator is one static HTML page. It reads videos and writes annotations through the File System Access API, so use Chrome or Edge; Firefox and Safari do not implement it and the page falls back to typed paths. Nothing is uploaded and there is no server-side state.

The handle for the last folder is kept in IndexedDB, so a reload reopens it by itself when the browser still holds the permission, and otherwise offers a one-click Reopen "<folder>" on the welcome screen. What the browser hands the page is a capability rather than a location, and a capability is exactly the kind of thing a browser may decide to ask about again.

The two pages do not share a folder. Choosing one on the Configuration page stores it for the command that page is writing; it does not open anything here, and the annotator's own pick does not change the command. The single thing that does cross over is measurement: Configuration probes each video's real frame rate, and the annotator prefers that over its toolbar default so frame numbers stay honest in a folder that mixes rates.

The annotator's opening screen: an Open Video Folder button above a dashed drop zone reading Drop MP4 / MKV files here.
Open Video Folder grants read and write on one directory. Dropping files in works too, but the page then has no folder to write back into, so saving is manual.

It is served over http://localhost rather than opened as a file:// URL for one reason that survives testing: every file:// page on a machine shares one origin, so a second copy of the page, or any other local page, would read the same IndexedDB down to the stored directory handles. Serving gives the page an origin of its own. Opened as a file it still works, and writes the same commands — folders are named in them either way.

Labeling

Click a behavior in the palette, then I at the start of a bout and O at the end. Arrow keys step one frame; Shift with them jumps a second. Each bout is stored as a time span and a frame span, so nothing drifts on a variable-frame-rate recording.

The annotator with a CalMS21 resident-intruder video loaded: the label attack shown over the video, a timeline strip of bouts below it, and the three CalMS21 behaviors in the palette at the bottom.
CalMS21 mouse008_task1_annotator1, part-way through labeling. The label under the playhead is drawn over the video; the strip below is the whole recording at once.

Annotations auto-save 500 ms after an edit into <video>.csv, in the same folder as the source video — the same file vtrace train --pairs reads.

Reviewing predictions

A prediction file is written beside the video it describes, in the shape the annotator imports. Open that folder in the annotator and each predicted bout can be accepted, rejected or corrected on the same timeline the labelling happened on.

The annotator in review mode: Accept prediction and Reject buttons in the transport bar, and a hatched rejected bout on the timeline strip.
A rejected bout stays on the timeline, hatched, so the decision is visible rather than silently gone. Dragging a predicted bout's edge instead marks it corrected.

The configuration page

The annotator's other page turns a folder and a recipe into the command that runs them. A browser is never told where a picked folder lives, only what it is called and what is in it — so the command names the folder rather than locating it: <train#8b1d0c47>/mouse001.mp4. The name is the folder's own; the eight digits after # are a fingerprint of its contents (the sorted names of its videos, or of everything in it when it holds none). The vtrace session looks the name up on the machine the command is pasted into and keeps only the folder whose contents match, so a same-named folder somewhere else is never taken. That machine is the only one whose paths matter: a folder mounted onto a laptop for annotation has a different path on the GPU box that trains on it, and the same name and files on both.

The configuration page's Run section: a live summary of task, config, encoder, projection, temporal head and output, above two dark panels — the session form with fingerprinted folder tokens, and the shell form with a Replace before running list under it.
The same configuration rendered twice. The session form carries fingerprints; the shell form carries bare names and lists, under the command, what to put in their place.

Only the session resolves. A plain vtrace train … from a shell or a job script refuses a token and prints what to replace it with — a job on a cluster runs unattended, where a lookup that guesses or asks is worse than one that never starts. The shell form on the page is written for that: bare tokens, and the list under them — <train> → the full path of the folder called “train” on the machine that runs this.

An empty output folder has nothing to fingerprint, so the page leaves a .vtrace-folder marker in it (eight hex digits) and the session matches on that instead. Folders once found are remembered in ~/.vtrace/folders.json, and VTRACE_ROOTS=/data:/mnt/lab adds places to search when the data is nowhere near the working directory.

Training data

Every command that reads annotated video takes explicit pairs. Each pair is VIDEO_PATH=ANNOTATION_PATH, both full paths; relative ones resolve against the working directory. A pair names its own files, so there is no folder to set first, and one source video can have several annotation files beside it with each pair choosing which to use. The annotation is the CSV described below, or a <video>.json written by an earlier version of the annotator (a combined annotations.json works too; the video's entry is found by its stem) — both are read the same way, so a folder labeled before the switch trains without a conversion step.

mouse008_task1_annotator1.csv
labelId,        timestamp,  endTimestamp
investigation,       5.433,         6.533
investigation,       9.267,         9.633
investigation,      38.467,        40.267
mount,              40.267,        41.500
attack,            196.933,       201.300

Real bouts from CalMS21 mouse008_task1_annotator1. Times are seconds. Containers may be .mp4, .avi, .mov, .mkv or .webm, mixed freely within one run. Padding the columns so they line up is optional and harmless — the reader trims the header and the values.

labelId
The behavior name. Free text; the class map is built from whatever appears across the run's videos.
timestamp
Where the bout starts, in seconds.
endTimestamp
Where it ends, in seconds.

Seconds are the only timing input. Prep maps each bout onto frames itself, by searching the video's own presentation timestamp table — so a frame number written into the CSV would be ignored, and a wrong one could not do any harm. That is also why a variable-frame-rate recording needs nothing special from you.

Columns the annotator adds

A CSV that has been through a review pass carries three more columns. Two of them are for the annotator's benefit; only review changes what training sees.

mouse008_task1_annotator1.csv — after a review pass
# trace-meta: {"annotation_version": "behavioral-annotator-v1",
#              "fps": 30, "duration": 240, "frame": 7200}
labelId,        timestamp,  endTimestamp,   score,  source,  review
investigation,       5.433,         6.533,        ,        ,
mount,              40.267,        41.500,  0.9412,   model,  accepted
attack,            196.933,       201.300,  0.8815,   model,  corrected
attack,            234.900,       236.633,  0.4102,   model,  rejected
score
The model's confidence, for a bout that came from one. Empty for a hand label.
source
model, or empty. Without it a reviewed prediction would come back as an anonymous human label with the confidence gone.
review
pending, accepted, rejected or corrected. Prep skips rejected rows — a bout the reviewer threw out is a false positive, and training must never see it. It stays in the file so the decision travels with the dataset.

What preparation writes

Preparation runs automatically as the first part of a training or evaluation command. It indexes each video whole and leaves one folder beside the source, <video>.vtrace/, holding everything cached for that video:

  • pts.npy plus pts.meta.json: the per-frame presentation timestamps, so a variable-frame-rate recording maps times to frames correctly instead of assuming a constant rate, and the source's size and mtime at the moment they were read, which is how a stale table is recognised.
  • proxy/<geometry>/proxy.mp4: one downscaled, frame-aligned decode proxy, reused across runs while the source's size and mtime still match its manifest.json.
  • dataset.json and classmap.txt in the run folder: the annotation index and the sorted class names.
bash — a prepared data folder
$ ls -1 /data/mouse_study/videos
mouse001.mp4
mouse001.csv                    # your labels
mouse001.mp4.vtrace/            # everything cached for this video
  ├── pts.npy                     # per-frame timestamps
  ├── pts.meta.json
  └── proxy/s224_sq_crf23_g30_v1/ # one decode proxy per geometry
        ├── proxy.mp4
        └── manifest.json         # source signature, for reuse
mouse002.mp4
mouse002.csv

Clips are virtual — no clip files are written. Each dataset.json entry records source_video plus source_frame_offset and source_pts_table, which stay authoritative for all timeline maths, and proxy_video, which only redirects which file frames decode from.

Finding out about a new release

The session asks PyPI whether there is a newer V-TRACE while it starts, and when there is, the start screen carries an update row naming the version. Typing update installs it. Nothing appears when you are on the current release, which is why the row is news rather than furniture.

The answer is cached in ~/.vtrace/update-check.json and refreshed at most once a day, so nearly every launch reads a file and costs nothing measurable. The refresh itself runs on a background thread started before the annotator's server, and the screen waits no more than a second for it — a check that misses that deadline lands in the cache and shows up the next time you start. A machine that cannot reach PyPI records the attempt like any other, so it asks once a day rather than once a launch, and never shows an error for a question nobody asked.

Watching a long preparation

An overnight recording is millions of frames, and making its decode copy is the longest single wait the tool has. So it is shown rather than left blank: a bar with the share done, the time still to go, and the encoder that is doing it. The last of those is worth reading — GPU NVENC means the card took the encode, CPU x264 means it did not, either because there is no usable card or because the proxy is smaller than NVENC's minimum frame size.

vtrace — preparing a long recording
Preparing the train dataset  2 videos
  2/2  reading timestamps  2026-06-13 17-06-54-day9
  2/2  making a smaller copy  ━━━━━━╸━━━━━━━━━   28%  eta 1h 48m  GPU NVENC 12.4x

The bar is redrawn in place and wiped when it ends, so it leaves no scrollback behind; an encode that ran for more than a minute leaves one line saying how long it took and on what. Where there is no terminal to redraw into — the annotator's job log, a captured run — the same figures are written out as an ordinary line every couple of minutes instead.

dataset.json — one entry
{ "database": {
    "mouse001": {
      "frame": 7200,
      "duration": 240.033,
      "subset": "training",
      "source_video": "/data/…/mouse001.mp4",
      "source_frame_offset": 0,
      "source_pts_table": "/data/…/mouse001.mp4.vtrace/pts.npy",
      "proxy_video": "/data/…/proxy.mp4",
      "annotations": [
        { "label": "investigation",
          "segment": [0.95, 2.4],
          "timestamp_sec": [0.95, 2.4],
          "frame_segment": [29, 71] }, …
      ]
    }
} }

Nothing is split. Every video a command names is training data, and evaluation videos are a corpus of their own that you name separately. Holding a slice of the training recordings back would be the same decision made worse, since it would be made without looking at them.

Training

Training takes the pairs, optionally where the run folder goes, and optionally the videos that will score it. Without --output the run lands in runs/ under the working directory — never beside the videos: a corpus is an input many runs read, and none of them should write its checkpoints there just because nobody said otherwise.

bash — vtrace train
$ vtrace train --model maev2b \
    --output /data/calms21/runs \
    --pairs /data/calms21/train/mouse001.mp4=/data/calms21/train/mouse001.csv \
            /data/calms21/train/mouse002.mp4=/data/calms21/train/mouse002.csv \
    --eval-pairs /data/calms21/test/mouse071.mp4=/data/calms21/test/mouse071.csv

Preparing 2 videos …
  mouse001.mp4   242 bouts over 712.1s (kept whole)
  mouse002.mp4   198 bouts over 654.9s (kept whole)
Classes: attack, investigation, mount
Run folder: /data/calms21/runs/model_20260827_014500
epoch  2/10  loss 0.641  mAP 0.703
epoch  6/10  loss 0.284  mAP 0.884
epoch  9/10  loss 0.183  mAP 0.912  ← best.pth
FlagDefaultWhat it does
--pairsrequiredThe training videos and their CSVs.
--outputruns/ under the working directoryWhere the model_YYYYMMDD_HHMMSS/ folder is created.
--eval-pairsnoneVideos the loop scores each epoch against. Their score is what writes best.pth.
--modelmaev2bModel preset. --config overrides it with a config file.
--epochsthe config's ownTotal epochs. The shipped presets run 10, warming up over 2.
--val-start-epochthe config's ownFirst epoch scored against the evaluation videos.
--val-intervalthe config's ownEpochs between evaluation passes.
--input-resolutionthe config's own112 to 256. Also sets the decode-proxy geometry, so the proxies match what training will read.
--resource-profilethe config's ownlow, balanced, high, or auto to benchmark the dataloader first.
--resumenoneContinue from a checkpoint.
--cfg-optionsnoneRaw key=value config overrides.

The epoch and resource flags all default to leaving the config alone. A number emitted here that nobody chose would silently replace the one the config author did choose.

bash — three variations
# four GPUs, a longer schedule
$ vtrace train --epochs 20 --output /data/calms21/runs \
    --pairs /data/calms21/train/*.mp4=…

# the heavier preset, at a larger input resolution
$ vtrace train --model vjepa2 --input-resolution 224 \
    --output /data/calms21/runs --pairs /data/calms21/train/mouse001.mp4=/data/calms21/train/mouse001.csv

# let the dataloader benchmark itself first
$ vtrace train --resource-profile auto --output /data/calms21/runs \
    --pairs /data/calms21/train/mouse001.mp4=/data/calms21/train/mouse001.csv

What decides best.pth

Every epoch is checkpointed to checkpoint/epoch_N.pth. With evaluation videos, the loop scores each pass and the highest scoring epoch is published as best.pth. Without them nothing measured any epoch, so the run publishes the last one as last.pth instead. Either way the run folder is complete and loadable.

Evaluation videos have to be named before training, not after. A score computed on a finished model can report a number, but it can no longer choose which epoch to keep.

Evaluation

vtrace eval scores a finished model. Given --pairs it prepares those videos and scores on them. Given none, it re-scores the run on the same corpus it validated against during training; a run trained without evaluation videos has no such corpus and says so rather than failing later.

bash — vtrace eval
# re-score on the corpus the run already validated against
$ vtrace eval --model-dir /data/calms21/runs/model_20260827_014500

# or on a held-out corpus of your own
$ vtrace eval --model-dir /data/calms21/runs/model_20260827_014500 \
    --pairs /data/calms21/test/mouse072.mp4=/data/calms21/test/mouse072.csv

mAP (all frames, background-aware)   0.9118
  attack           0.883      mount    0.874
  investigation    0.978
precision / recall / F1 @ 0.31       0.887 / 0.902 / 0.894
Results: …/model_20260827_014500/eval_20260827_020000/

Results land in an eval_YYYYMMDD_HHMMSS/ folder inside the model directory:

  • metrics.json: the scored summary.
  • recommended_thresholds.json: cutoffs tuned on the validation split, so a later held-out evaluation can report precision, recall and F1 at a threshold chosen without looking at the held-out data.
  • result_detection.json: the raw per-frame scores, for anyone computing their own metrics.
recommended_thresholds.json
{
  "subset": "validation",
  "global": 0.31,
  "per_class": {
    "attack":        0.22,
    "investigation": 0.41,
    "mount":         0.35
  }
}

vtrace predict reads this file back and applies the per-class cutoffs when --threshold is omitted, so the value a prediction is filtered at is one that was tuned somewhere other than the data being predicted.

Prediction

vtrace predict takes a model folder and either one video or a directory of them. No annotations are needed.

bash — vtrace predict
$ vtrace predict --model-dir /data/calms21/runs/model_20260827_014500 \
    --input /data/new_recordings \
    --include-stems mouse071 mouse072 \
    --threshold 0.25

Prediction CSV: /data/new_recordings/mouse071.pred.csv (214 bouts)
Prediction CSV: /data/new_recordings/mouse072.pred.csv (188 bouts)

Each video gets one <stem>.pred.csv written beside it. Re-running replaces that file rather than accumulating timestamped folders, so a video has exactly one current prediction. --output can redirect them, at the cost of the arrangement the review step depends on.

mouse071.pred.csv
# trace-meta: {"trace_prediction_version": 1,
#              "video": "mouse071.mp4",
#              "class_map": ["attack", "investigation", "mount"],
#              "threshold": 0.25}
labelId,        timestamp,  endTimestamp,   score,  predictionId
investigation,      12.433,        15.900,  0.9412,  mouse071::0
attack,             20.167,        23.033,  0.8815,  mouse071::1
mount,              41.200,        48.767,  0.7604,  mouse071::2
investigation,      63.500,        64.133,  0.3110,  mouse071::3
labelId
timestamp
endTimestamp
The annotation format, unchanged — which is what lets a prediction file import straight back into the annotator.
score
The model's confidence in the bout. --threshold is the minimum needed to reach the file at all; it defaults to the run's tuned cutoffs, or 0 when there are none.
predictionId
<stem>::<n>. Stable within a file, so a review decision has something to name.
# trace-meta
Which video, which classes, which cutoff. Skipped by every reader, so the provenance rides along without becoming a column.

--include-stems restricts a directory input to named videos. Once the file is reviewed in the annotator it is saved back with source and review columns filled in, and is then an ordinary training annotation file.

Chaining steps

Steps chain with the bare word then. Splitting happens before any parsing, so a chain reads the same typed at the vtrace > prompt and pasted into a shell.

bash — train, then eval, then predict
$ vtrace train --output /data/calms21/runs \
      --pairs /data/calms21/train/mouse001.mp4=/data/calms21/train/mouse001.csv \
    then eval --pairs /data/calms21/test/mouse071.mp4=/data/calms21/test/mouse071.csv \
    then predict --input /data/new_recordings

A later step reuses the model the training step produced, so its timestamped folder never has to be typed. Passing --model-dir explicitly overrides that.

train ... then eval ... is one run, not two. The eval step names the videos the training loop scores each epoch against, which is what picks best.pth, so it has to be known before training starts. An eval that follows anything else keeps its ordinary meaning: score a model that already exists.

then is a bare word rather than the next verb itself because --pairs takes one or more values and would otherwise swallow a following eval as a filename.

What a run writes

A training run creates one self-contained folder. Everything needed to rebuild and reuse the model is inside it, so --model-dir is the only thing evaluation or prediction ever needs to be told.

/data/calms21/runs/model_20260827_014500
model_20260827_014500/
├── best.pth              # or last.pth, when no epoch was scored
├── classmap.txt          # the class names, in training order
├── config_resolved.py    # the configuration the run actually used
├── config.txt            # the config file it started from
├── dataset.json          # the training corpus index
├── prep.log              # per-video detail from indexing
├── checkpoint/           # epoch_0.pth, epoch_1.pth, ...
├── eval_data/            # the evaluation corpus, when the run had one
│   └── dataset.json
└── eval_20260827_020000/
    ├── metrics.json
    ├── recommended_thresholds.json
    └── result_detection.json
best.pth
The highest-scoring epoch. Only exists when the run had evaluation videos.
last.pth
Stands in when it did not: nothing measured any epoch, so none is called best. Evaluation and prediction read whichever is there.
classmap.txt
One class name per line, sorted. A line's index is the class index the head emits.
config_resolved.py
Bases merged, every override applied, class count as detected from the data.
config.txt
The path of the config the run started from. Kept for provenance, not for loading.
dataset.json
The training corpus index. eval_data/dataset.json is the evaluation corpus, separately — the training one labels every video train, so an evaluation pass cannot read it.
prep.log
What indexing found in each video: frame count, measured rate, resolution, bouts read, and which decode proxy the frames will come from. Indexing 89 videos prints one line each; this is the rest of it.

Evaluation and prediction load config_resolved.py rather than config.txt, so editing the config a run started from cannot change how a finished model is rebuilt.

Models and weights

Three presets ship with the package. Each names its starting weights by filename rather than bundling them.

PresetBackboneTraining
maev2bVideoMAE V2 ViT-B/16ViT frozen, per-block adapters trained. The default.
maev2b-distilledthe same ViT-BAs above, but the adapters start from a V-JEPA 2 distillation instead of random init.
vjepa2V-JEPA 2 ViT-LUpper half of the encoder fine-tuned. Heavier in every direction.

None of them names a dataset. Preparation supplies the paths and the class count per run, so the same preset fits any corpus.

bash — fetching weights
$ vtrace prepare --weights all
vit_b_k710_dl_from_giant.pth   ✓ cached
vjepa2_vitl_fpc64_256.pth      ↓ 1.2 GB …
  sha256 ok~/.vtrace/pretrained/

# put the cache somewhere else
$ export TRACE_WEIGHTS_DIR=/scratch/vtrace-weights

# or fetch only what one preset needs
$ vtrace prepare --weights maev2b

A registered weight file is fetched from the project's GitHub release on first use, checked against its SHA256, and cached under ~/.vtrace/pretrained so later runs and other configs reuse the same copy. Set TRACE_WEIGHTS_DIR to put the cache elsewhere. No checkpoint is bundled in the repository or the wheel.

The CalMS21 demo

The demo is the complete CalMS21 Task-1 mouse social-behavior benchmark in its official split: 70 training videos and 19 test videos. It is a real run on a published benchmark, not a smoke test.

vtrace > — the three demo steps
vtrace > demo predict     # label a held-out video, write its predictions
Predicting on mouse083_task1_annotator1.mp4

22 bouts across 2 behaviorsmouse083_task1_annotator1.pred.csv
  investigation   17 bouts   39.8s   24.4%   best 0.88
  mount            5 bouts   18.3s   11.2%   best 0.92
  58.1s of 163s labelled (36% of the recording)

  annotated   █████████━━━███━━━━━━━━━━━━━━━━━███━━━━━━━━━━█████━━━━━━━
  predicted   █████████━━━━██━━━███━━━━━━━━━━━━█████━━━━━━━━━━████━━━━━━━━━━━━
              0s               41s              82s              122s        163s
 investigation    mount

  annotated (32)                predicted (22)
  investigation 0.9–16.7s       investigation 0.6–16.0s
  investigation 22.8–25.3s      investigation 22.5–25.4s
  investigation 25.7–26.3s      investigation 30.3–32.0s
  … 24 more

Against the CalMS21 annotation for this video (4881 frames, threshold tuned per class)
                   threshold   precision    recall      F1
  investigation        0.64        0.80      0.81    0.80
  mount                0.12        0.91      0.84    0.87
  macro F1                                            0.84
  thresholds tuned on this video, so this is the best it can do here

Review them in the annotator
  2. Open Video Folder~/.vtrace/demo/videos/test

vtrace > demo download    # fetch the videos (train ~19 GB, test ~11 GB)
Fetching 70 train videos (18.83 GB) from https://data.caltech.edu/…
  into ~/.vtrace/demo/videos/train
  ctrl-c stops it.
  all 53          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  67% 8.6/12.8 GB 6.7 MB/s 10:23
  9/53 mouse018….mp4 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  42% 104.9/248.4 MB 6.7 MB/s 00:22

vtrace > demo train       # prep and train on the 70 training videos
Training on 55 CalMS21 videos
  15 more, split out of the same 70 by behavior, score each epoch
  the benchmark's 19-video test split stays held out
324 iterations/epoch × 10 epochs — about 3 h on one modern GPU, ~23 GB VRAM.

V-TRACE re-hosts none of the video. The annotation CSVs, the class map and the video manifest ship inside the package at about 450 KB; the recordings come from the official CalMS21 release, one member at a time over HTTP range requests. Nothing pulls the full 28 GB archive.

Where it goes, and stopping it

The demo tree is ~/.vtrace/demo, with the videos under videos/train and videos/test. Set TRACE_DEMO_DIR to put it somewhere else — worth doing when $HOME is a small system disk, since the full split is about 30 GB. A source checkout that already carries data/calms21_demo/ is used ahead of the cache.

Ctrl-C is safe at any point. Each video is written to a .part file and moved into place only once it is complete, so an interrupt can never leave a half-file that the next run would mistake for a finished download. Re-running skips every video already on disk and picks up at the ones it was working on.

Four videos are fetched at once by default, each on its own connection — a ZIP handle keeps a single file position, so workers cannot share one. The cost is one extra small ranged read of the archive's tail per worker for the whole run, not per file.

Resuming is per video, not per byte. The archive's members are DEFLATE-compressed, and a deflate stream has no restart point — reaching byte N of a member means inflating everything before it, so the video that was in flight has to be fetched again from its first byte. The partial file is deleted on interrupt rather than kept, since no later run could read it. The finished videos around it cost nothing to re-check and are skipped.

  • --split train or --split test limits what is pulled.
  • --from /path/to/task1_videos_mp4.zip reads a copy you already have.
  • --verify CRC-checks the videos on disk rather than trusting their size.
  • --jobs N sets how many videos are fetched at once. The default is 4.
bash — the download variations
$ vtrace demo download --split test
$ vtrace demo download --from /mnt/share/task1_videos_mp4.zip
$ vtrace demo download --verify

vtrace demo predict labels all 19 test videos with the released checkpoint and writes a .pred.csv beside each; --video STEM narrows it to one, --model-dir DIR swaps in a run you trained. vtrace demo train is 324 iterations per epoch over ten epochs, roughly three hours on one modern GPU and about 23 GB of VRAM. It cuts a validation set out of the 70 training videos — 55 to train on, 15 to score each epoch, split so that each behavior lands on both sides in proportion — and touches the 19 test videos exactly once, after training, when it labels them with the finished checkpoint through the same path as demo predict — the same per-video table, precision-recall curves and frame mAP. That mAP is the run's benchmark figure: those videos took no part in training or epoch selection.

If you use the demo data, cite CalMS21: Sun et al., The Multi-Agent Behavior Dataset: Mouse Dyadic Social Interactions, NeurIPS 2021 Datasets & Benchmarks (doi:10.22002/D1.1991).