Skip to content

Getting results out: one table, ELAN, a methods paragraph

Each pipeline writes its own standard format (COCO JSON, WebVTT, RTTM). Those files stay as they are. For analysis, VideoAnnotator also turns them into:

  • One table of every pipeline's results, the same columns for every pipeline, as CSV (or Parquet). Read it with one read.csv in R or pandas.read_csv in Python.
  • ELAN files (.eaf), one per video, with one tier per pipeline track, to check or correct the results next to your own coding.
  • A methods paragraph with BibTeX, naming every model and its exact weights, the settings that differ from the defaults, and what to cite.

All three are built from the result files and the provenance recorded in them, so they work on any results folder: one on your computer, one a colleague sent you, or one from an older version (where something wasn't recorded, they say not recorded; they never guess).

In the viewer

On a finished job's page or a run's page, click Export:

  • Table (CSV) / Table (Parquet): downloads the table.
  • ELAN files (.eaf): one file for a job; a zip with one file per video for a run.
  • Methods paragraph: shows the paragraph and BibTeX, each with a Copy button, and a Markdown download.

A saved dataset's card has a table button too: every run made from that dataset in one table, with a run column telling them apart.

If anything was left out (a file VideoAnnotator can't read, a video whose job failed), the viewer lists it under the buttons.

From a terminal

These need no server and no network: point them at any results folder (one video's folder, a run's folder, a folder of runs, or an extracted results download).

videoannotator export table   "~/VideoAnnotator/BabyJokes wave 2 (2026-10-06)"
videoannotator export elan    "~/VideoAnnotator/BabyJokes wave 2 (2026-10-06)"
videoannotator export methods "~/VideoAnnotator/BabyJokes wave 2 (2026-10-06)" -o methods.md
  • export table writes <folder>/<folder name>.csv unless you give -o PATH (-o - prints it). --format parquet writes Parquet.
  • export elan writes <video>.eaf beside each video's results, or into -o FOLDER.
  • export methods prints Markdown (prose, model revisions, BibTeX, any VLM prompts).
  • --json prints a machine-readable summary instead (for scripts and agents).
  • Exit codes: 0 done (even if some files were skipped, which are listed), 1 nothing to export, 2 bad arguments, 3 Parquet asked for but not installed.

Over the API

GET /api/v1/jobs/{job_id}/export/table?format=csv|parquet
GET /api/v1/batches/{batch_id}/export/table?format=csv|parquet
GET /api/v1/datasets/{dataset_id}/export/table?format=csv|parquet
GET /api/v1/jobs/{job_id}/export/elan        (one .eaf)
GET /api/v1/batches/{batch_id}/export/elan   (zip, one .eaf per video)
GET /api/v1/jobs/{job_id}/methods            (JSON; ?format=markdown for a .md)
GET /api/v1/batches/{batch_id}/methods

The X-VideoAnnotator-Export-Summary response header holds what the export covered and left out, as JSON.

The table

One row per measure of one event. An event is one thing a pipeline found: a person in a frame, a face, a speech segment, a speaker turn, a scene, a VLM label. Its first row says what it is; further rows with the same event number are what was measured about it. This long layout keeps the same columns whatever pipelines you ran; turn it wide in one call (tidyr::pivot_wider, pandas.pivot_table) when you want one row per event.

Column Meaning
run the run's folder name; empty outside a run
video the video's folder name in the run (or the video's file name, without extension)
pipeline the pipeline that made the row
event numbers events within a run, video and pipeline; rows of one event share it
track who or what the pipeline followed over time (a person's id, a speaker); empty when none
start seconds from the start of the video
end seconds; empty for something seen at one moment (a frame)
frame the video frame, when the pipeline recorded it
measure what the row is (below)
label a category or text (an emotion, a transcript), or which component a value is
value a number
confidence the pipeline's own score for this row, when it gives one
x, y, width, height bounding box in pixels from the top-left, on person and face rows
model the models that made the row, from the file's provenance; not recorded without it
videoannotator_version the version that made the file; not recorded without provenance
file the result file the row came from

CSV files are UTF-8 with a header row; missing values are empty fields.

Measures, by pipeline

Pipeline measure label value confidence
person_tracking person parent, infant, … (a size heuristic: check it) detection score
face_analysis face detection score
emotion most likely expression its probability
emotion_score each expression its score
age estimated age
gender estimated gender its probability
face_openface3_embedding face detection score
action_unit AU name (AU01_Inner_Brow_Raiser) intensity
action_unit_presence AU name 1 present, 0 absent
head_pose pitch, yaw, roll degrees pose confidence
gaze direction_x, direction_y, direction_z component gaze confidence
emotion, emotion_score, valence, arousal as above
scene_detection scene scene type score
scene_score each scene type its score
speaker_diarization speaker_turn speaker (SPEAKER_00, …)
speech_recognition speech the words
vlm_annotation vlm_label the model's label
vlm_reasoning its reasoning, when different from the label

When speech and speaker turns came from audio_processing, their rows say audio_processing; the measure tells them apart.

Not in the table: pose keypoints, facial landmarks, face embeddings (one number is meaningless on its own; they are in the result files the file column names).

Reading it

library(readr)
results <- read_csv("BabyJokes wave 2 (2026-10-06).csv")
turns <- subset(results, measure == "speaker_turn")
aggregate(end - start ~ track, data = turns, FUN = sum)   # seconds each speaker talks
import pandas as pd

results = pd.read_csv("BabyJokes wave 2 (2026-10-06).csv")
turns = results[results.measure == "speaker_turn"]
(turns.end - turns.start).groupby(turns.track).sum()  # seconds each speaker talks

Two worked notebooks, one in R and one in Python, are in examples/notebooks/.

Parquet

Parquet keeps the column types and is smaller and faster for large runs. It needs an optional component on the server or computer doing the export: pip install videoannotator[parquet] (an administrator can also install it from the viewer when Parquet is first asked for). The file's metadata holds the export summary, including every source file's provenance record.

Stability

The table has a format version (1). Later versions only add columns (at the end) or measures, so code written against it keeps working.

ELAN files

One .eaf per video, linked to the video where its location is known. Tiers:

Tier Annotations
scene_detection scenes each scene, labelled with its type
speech_recognition speech each transcribed segment
speaker_diarization SPEAKER_00 (one per speaker) each turn
person_tracking <person> (<label>) (one per tracked person) in view
face_analysis faces, face_openface3_embedding faces in view (a face is detected)
vlm_annotation labels each label, until the next sample

Pipelines sample frames, so "in view" joins consecutive detections of the same person while the gap is at most twice the sampling interval, and lasts one interval past the last detection. Numbers measured per detection (emotions, action units, gaze) stay in the table: ELAN codes what happens when.

Add your own tiers in ELAN and save; the viewer still opens the file, showing both.

Methods paragraph

For example (abridged):

All 40 videos were annotated automatically with VideoAnnotator 1.6.0 [@addyman2026videoannotator]. Speech was transcribed with mobiuslabsgmbh/faster-whisper-large-v3-turbo (revision 0a363e9161cb) at float32 [@radford2023whisper]. Speaker turns were identified with pyannote/speaker-diarization-community-1 (revision 3533c8cf8e36) [@plaquet2023powerset; @pyannote2025community1; @bredin2023pyannote]. GPU deterministic mode was off. Settings not listed were the pipelines' defaults.

  • Citations are [@key] keys for the BibTeX that comes with it (R Markdown, Quarto and Pandoc resolve them; reference managers import the BibTeX).
  • Model revisions are shortened in the prose; the full ones follow in a list.
  • Videos processed the same way are described once; differences (a rerun with a newer version, a changed setting) are stated with how many videos they apply to.
  • It names no video, folder or server, since video names often carry participant codes.
  • A VLM prompt is identified by a fingerprint in the prose, with its full text alongside.
  • Settings are compared with the defaults in each pipeline's metadata; check that the listed settings are what you meant.