Narrative Feature Extraction
Computational annotation of movies and stories for cognitive science and naturalistic neuroimaging.
This site is the entry point for browsing every part of the project: the Phase 1 scoping review, the annotation format specification and pipeline, the how-to guides for growing the corpus, the draft review paper with its figures, and the interactive segment browser.
What the project does
A human supplies a movie or an audio/text story; the pipeline returns a hierarchical, semantically organized, second-by-second set of annotations — visual, audio, language, social, situational, and affective — produced by a curated set of best-in-class models. Features that do not apply to a stimulus (e.g. visual features for an audio-only story) are returned as explicit nulls, so every annotation shares one constant shape and the corpus stacks into rectangular matrices for analysis.
At a glance
- 83 stimuli annotated (~7.8 h): 53 audiovisual, 29 audio-only, 1 text-only.
- 95 channels per stimulus across six feature classes, on a common 1 Hz grid.
- Local-first models on Apple-Silicon GPU/CPU; HDF5 + JSON output with a MATLAB reader.
How this book is organized
| Part | Contents |
|---|---|
| Overview | Project overview and the full contents & user guide. |
| Phase 1 — Scoping review | Survey of computational annotation tools per feature class, the semantic hierarchy, redundancy analysis, and best-in-class recommendations. |
| Phase 2 — Pipeline & format | The annotation format spec, the frozen feature set, and the extractors that run. |
| Phase 3 — Corpus | How the corpus was assembled and how to add your own movies/stories. |
| Phase 4 — Analysis & dissemination | Corpus analysis and tools, the review paper, and the interactive browser. |
Start here
- Review paper (draft) — the models/algorithms behind each annotation and the empirical structure of the annotation space across the corpus.
- Contents & user guide — the full map of tools, datasets, derivatives, and how to load / view / inspect them (with MATLAB recipes).
- Interactive segment browser — rank stimulus segments by any combination of features.
The source repository holds the pipeline (
src/nfe/), the MATLAB reader (matlab/), and the annotated corpus derivatives. This documentation book is generated from the canonical Markdown indocs/bytools/build_book.py.
Narrative Feature Extraction
📖 Browse the documentation book →
The full documentation — scoping review, annotation format, pipeline, corpus, the review
paper with figures, and the interactive segment browser — is published as a book at
canlab.github.io/narrative_feature_annotations.
It is the recommended entry point. (Build/deploy notes: docs/DEPLOYING.md.)
🗺️ Feature map — all 95 annotation channels at a glance
A map of every feature and its hierarchical organization. The source is an editable
SVG (open/recolor in PowerPoint, Illustrator, or Inkscape);
regenerate with python3 tools/build_feature_map.py.
Infrastructure for producing second-by-second computational annotations of movies and audio stories across the full range of stimulus, perceptual, linguistic, social, situational, and affective features — for use in cognitive science and naturalistic-neuroimaging research.
A human supplies a movie or story; the pipeline returns a hierarchical, semantically organized, time-resolved set of annotations from a curated set of best-in-class models. Annotations that do not apply to a given stimulus (e.g. visual features for an audio-only story) are returned as explicit nulls (NaN). The output reserves space for later human annotations and ships with a lightweight MATLAB reader.
Project phases
| Phase | Goal | Status |
|---|---|---|
| 1. Scoping review | Survey computational annotation tools across all feature classes; organize in a semantic hierarchy; recommend best-in-class vs redundant features. | ✅ Draft complete — see docs/scoping_review/ |
| 2. Pipeline build | Select final feature/model set (with human input); deploy models locally or via API; produce hierarchical second-by-second annotations + MATLAB reader. | ✅ 23 extractors across all classes; constant-shape output; see docs/design/PHASE2_STATUS.md |
| 3. Corpus annotation | Assemble movies/stories (user-supplied + sourced); run annotations across the whole corpus. | ✅ manifest + resumable batch runner; 83 stimuli annotated (movies, films, 29 spoken stories, a text story). docs/design/PHASE3_CORPUS.md |
| 4. Analysis & dissemination | MATLAB analysis across corpus; review paper; interactive web search interface; experimental-design / stimulus-selection tool. | ✅ corpus reader, synced viewer, correlation/PCA/network, design tool, web search, draft review paper. docs/design/PHASE4_ANALYSIS.md |
Repository layout
docs/
scoping_review/ # Phase 1 output: the hierarchical scoping review
design/ # plans, schema design, deployment feasibility
schema/ # annotation output format (spec + JSON schema + example)
src/nfe/ # the pipeline package
base.py # types, common-grid TimeGrid, resampler
ingest.py # PyAV decode + audio extraction
emit.py # HDF5 + JSON-sidecar writer
pipeline.py, run.py # orchestration + CLI
extractors/ # one module per feature pass
matlab/ # MATLAB reader (readAnnotations / getFeature / featuresToTimetable)
data/
movies/ # input movies (spacetop/ = lab stimuli; open/ = CC-BY test films)
stories/ # input audio stories
annotations/
corpus/<id>/ # corpus derivatives: <id>.h5 + <id>.manifest.json per stimulus
# + corpus_index.csv (batch status)
analysis/
figures/ # generated analysis figures
corpus_stats.json # corpus summary numbers the docs cite
web/ # interactive segment-search app
tools/ # helper scripts (manifest, template, search index, refresh)
Quickstart (Phase 2 pipeline)
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
PYTHONPATH=src .venv/bin/python -m nfe.run data/movies/open/BigBuckBunny.mp4 --rate 1.0
See docs/design/PHASE2_STATUS.md for what runs today and what’s next.
Design principles
- Common time grid. Every feature has a native rate (frame, sub-second, per-second, per-utterance); all are resampled onto a configurable common grid (default 1 Hz) with native-rate metadata preserved.
- Explicit nulls. Inapplicable annotations are
NaN, never silently omitted, so downstream analysis can distinguish “not measured” from “measured zero.” - Hierarchical + readable. Output is organized by the semantic hierarchy in
docs/scoping_review/so a human can read it and a program can traverse it. - Local-first. Prefer open, locally deployable models; fall back to hosted large models only where they are clearly superior and irreplaceable.
- Reproducible provenance. Each feature records the model name, version, and parameters that produced it.
Start here
docs/CONTENTS.md— full guide to the folder, tools, datasets, the extracted derivatives, and how to load / view / inspect them.docs/walkthrough.m— runnable MATLAB tour of the common operations.docs/REVIEW_PAPER.md— draft review: the models/algorithms and the empirical structure of the annotation space across the corpus.docs/ADDING_MOVIES.md— how to add movies/stories and refresh the whole dataset (annotations, search index, figures, and the corpus-specific doc numbers).
Key documents
- Scoping review (Phase 1 output):
docs/scoping_review/— 146 tools across 15 feature subclasses, the semantic hierarchy, redundancy analysis, and best-in-class recommendations. - Annotation output format:
docs/design/ANNOTATION_FORMAT.md— authoritative v0.2 spec (HDF5 canonical + readable JSON sidecar; common grid; explicit nulls; reserved human-annotation slots; MATLAB reader interface).schema/annotation_schema.jsonis the v0.1 pure-JSON profile used for small/demo files, andmatlab/*.mis the reference reader for that profile. - Phase 2 core-set freeze sheet:
docs/design/PHASE2_CORE_SET_PROPOSAL.md— fillable decision doc; sign-off freezes the Phase 2 build scope. - Deployment feasibility:
docs/design/DEPLOYMENT_FEASIBILITY.md. - Full plan:
docs/design/IMPLEMENTATION_PLAN.md.
Feature map
A visual map of all 95 annotation channels and their hierarchical organization — the six feature classes, each grouped by subclass, with one chip per channel.
Editable graphic
The map is a plain, flat SVG built for editing:
analysis/figures/feature_map.svg.
- Open it in PowerPoint, Illustrator, Inkscape, or any browser. In PowerPoint, Insert → Pictures, then right-click → Convert to Shape to recolor, move, or relabel individual cards, chips, and text.
- It uses real text elements, per-card groups, and inline colors (no CSS or filters), so it survives import cleanly and recolors easily.
- Regenerate it whenever the channel set changes:
python3 tools/build_feature_map.py # reads schema/channel_template.json
Related
- The designed semantic hierarchy behind these channels (the taxonomy the pipeline is organized around): feature hierarchy.
- What each pass actually computes: pipeline status.
- The on-disk layout of these channels in each
.h5: annotation format.
Project Contents & User Guide
A map of everything in this repository: the tools, datasets, and extracted
derivatives, what is included so far, and how to load, view, and inspect them.
For a hands-on tour run docs/walkthrough.m in MATLAB.
What this project is. Infrastructure that turns a movie or audio story into second-by-second, hierarchical, computational annotations (visual, audio, language, social, situational, affective), stored in a constant-shape format that loads into MATLAB, plus tools to analyze the annotation structure across a corpus and to design high-variance / low-redundancy stimulus sets.
Status at a glance
| Phase | What | State |
|---|---|---|
| 1 Scoping review | best-in-class tools per feature class | ✅ docs/scoping_review/ |
| 2 Pipeline | 23 extractors → constant-shape annotations + MATLAB reader | ✅ src/nfe/, matlab/ |
| 3 Corpus | manifest + batch runner; 83 stimuli annotated (53 audiovisual + 29 audio stories + 1 text) | ✅ annotations/corpus/ |
| 4 Analysis | corpus reader, viewer, correlation/PCA/network, design tool, web search, draft paper | ✅ matlab/, analysis/web/, REVIEW_PAPER.md |
Folder map
README.md project overview + quickstart
requirements.txt Python deps (pipeline)
data/
manifest.csv / .json stimulus catalog (83 stimuli) — tools/build_manifest.py
movies/spacetop/ lab fMRI stimulus clips (49) [internal]
movies/open/ CC-BY Blender films (3) + SOURCES.md
lexicons/ optional psycholinguistic norm CSVs (README.md)
stories/narratives/ 29 Narratives spoken-story audio clips (+ SOURCES.md)
stories/samples/ pure-text sample story (.txt)
movies/hcp/, movies/camcan/ placeholders for credentialed stimuli (see EXTERNAL_STIMULI.md)
schema/
channel_template.json the 95-channel constant-shape template
annotation_schema.json v0.1 pure-JSON profile schema
example_annotation.json tiny worked example
src/nfe/ the Python annotation pipeline (see "Tools")
matlab/ the MATLAB readers + analysis (see "Tools")
annotations/
corpus/<id>/<id>.h5 DERIVATIVES: one annotation per stimulus (+ .manifest.json)
corpus/corpus_index.csv batch status/index
analysis/figures/ generated analysis figures (PNG)
analysis/corpus_stats.json corpus summary numbers the docs cite (refreshAnalysis)
analysis/web/ interactive segment search (index.html + segments.json + README)
docs/
CONTENTS.md ← this file
walkthrough.m runnable MATLAB tour
scoping_review/ Phase 1 review (hierarchy, recommendations, ...)
design/ format spec, plans, per-phase status
tools/ helper scripts (manifest, template, review assembly)
Datasets
- Corpus manifest: 83 stimuli (
data/manifest.csv) — 49spacetopaudiovisual clips + 4 short films (3 CC-BY Blender open films + Kung Fury) + 29 Narratives spoken-story audio clips (data/stories/narratives/, ~5.3 h; OpenNeuro ds002345, Nastase et al.) + 1 pure-text sample story. 53 audiovisual, 29 audio-only, 1 text-only. Add media underdata/movies/<source>/(movies/audio) ordata/stories/<source>/(audio/text stories) and re-runtools/build_manifest.py. SeeADDING_MOVIES.md; credentialed sets (HCP, CamCAN) inEXTERNAL_STIMULI.md. All 83 are annotated; corpus-wide analysis focuses on the audio/language channels shared across modalities, with visual/social/ affective structure on the 53-stimulus audiovisual subset (seeREVIEW_PAPER.md§5–6). - Lexicons (optional): drop
data/lexicons/<field>.csv(valence, arousal, dominance, concreteness, aoa) to light up those per-word channels; absent → NaN.
Extracted derivatives
One annotation per stimulus at annotations/corpus/<id>/:
<id>.h5— canonical HDF5. Hierarchical groups mirror the feature taxonomy:/time/ common 1 Hz grid (rate_hz, t_start_sec, n_samples, time_sec) /stimulus/ id, title, modality, duration, source, sha256 /features/ visual/ audio/ language/ social/ situation/ affect/ → each leaf = one channel dataset [n_samples (× dim)] + attrs (dtype, applicable, units, model, version, native_rate_hz, resample) /human/ reserved, empty — slots for later human annotation /provenance/ per-channel model registry<id>.manifest.json— readable sidecar: same hierarchy + metadata, no bulk arrays.
Constant shape. Every file has the same 95 channels (the template). Channels
not produced for a stimulus (a class that doesn’t apply to the modality, or a pass not
run) are present with applicable=false and all-NaN, so the corpus stacks into
rectangular matrices. Full spec: design/ANNOTATION_FORMAT.md.
The 95 channels span: visual (37 — low-level, semantic SigLIP2/DINOv2, motion,
depth, action, faces, pose, saliency), audio (21 — low-level, AudioSet/CLAP,
speech), language (14 — lexical, syntax, surprisal), affect (14 — text emotion/
sentiment, vocal, EmoNet image emotion, facial affect, VLM depicted), situation
(5 — incl. GSBS event boundaries), social (4). What each pass computes:
design/PHASE2_STATUS.md.
Tools
Python pipeline — src/nfe/ (annotate media)
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
# annotate ONE movie (CPU passes always on; add MPS/VLM passes as flags):
PYTHONPATH=src .venv/bin/python -m nfe.run data/movies/open/BigBuckBunny.mp4 \
--vision --audio-hl --events --template schema/channel_template.json
# annotate the WHOLE corpus (resumable, crash-safe):
PYTHONPATH=src .venv/bin/python -m nfe.batch --manifest data/manifest.csv \
--out annotations/corpus --template schema/channel_template.json \
--vision --audio-hl --events
Pass flags: --vision (SigLIP2/DINOv2/RAFT/depth/VideoMAE/faces/pose/saliency),
--audio-hl (AST/CLAP/vocal-affect/text-emotion/surprisal), --reason (Qwen2.5-VL —
slow), --events (GSBS). Modules: ingest (PyAV decode), extractors/ (the 20
passes), emit (HDF5+JSON), pipeline/run/batch, schema_registry (skeleton
fill). Helpers in tools/: build_manifest.py, build_channel_template.py,
build_search_index.py (segment index for the web search interface).
Web — analysis/web/ (interactive segment search)
Browser tool to rank segments by any combination of features and play the matching moment. Serve from the project root, then open the page:
python3 tools/serve.py # from the project root (Range-enabled, so video seeking works)
# open http://localhost:8000/analysis/web/index.html
Rebuild the index after annotating more stimuli:
PYTHONPATH=src .venv/bin/python tools/build_search_index.py --seglen 5.
Details: ../analysis/web/app_readme.md.
MATLAB — matlab/ (load, view, analyze)
| Function | Purpose |
|---|---|
readAnnotations(path) | load one .h5/folder/JSON → struct (stimulus, time_sec, features) |
getFeature(ann, "audio/low_level/mfcc") | one channel + metadata by hierarchical path |
featuresToTimetable(ann) | scalar channels → timetable on the common grid |
readAnnotationCorpus(folder) | stack the whole corpus → C.X [timepoints × channels] |
annotationMovieViewer(movie, ann) | play movie with synced annotation time series + marker |
analyzeCorpus(C) | correlation heatmap, PCA, channel + class network graphs |
selectStimulusSet(C) | D-optimal high-variance / low-redundancy segment selection |
How to load, view, inspect (MATLAB quick recipes)
addpath matlab
% 1) Inspect ONE stimulus
ann = readAnnotations("annotations/corpus/ses-01_run-01_order-04_content-parkour");
tt = featuresToTimetable(ann); % scalars as a timetable
stackedplot(tt(:, ["visual__low_level_static__luminance","audio__low_level__rms"]))
mf = getFeature(ann, "audio/low_level/mfcc"); % a vector channel [n × 13]
% 2) WATCH a movie with its annotations scrolling underneath
m = "data/movies/spacetop/videos/ses-01/ses-01_run-01_order-04_content-parkour.mp4";
annotationMovieViewer(m, "annotations/corpus/ses-01_run-01_order-04_content-parkour")
% 3) Analyze the whole CORPUS
C = readAnnotationCorpus("annotations/corpus");
res = analyzeCorpus(C); % figures → analysis/figures/
sel = selectStimulusSet(C, "K", 20); % design a stimulus set; sel.table
% Python inspection (alternative): h5py / pandas on <id>.h5 and corpus_index.csv
# 4) SEARCH segments by feature in the browser (serve from project root)
python3 tools/serve.py # then open http://localhost:8000/analysis/web/index.html
See walkthrough.m for the same steps, runnable section by section.
Documentation index
- Feature map (all 95 channels):
FEATURE_MAP.md— a graphical map of the feature hierarchy; editable SVG atanalysis/figures/feature_map.svg - Adding movies/stories:
ADDING_MOVIES.md— how to grow the corpus and refresh all derivatives + corpus-specific doc numbers - External stimuli (HCP, CamCAN):
EXTERNAL_STIMULI.md— how to obtain the credentialed/copyrighted neuroimaging movie stimuli and integrate them - Review paper (draft):
REVIEW_PAPER.md— models/algorithms + the empirical structure of the annotation space (correlation, dimensionality, networks, contingency on stimulus type, design tool) - Scoping review (Phase 1):
scoping_review/ - Format spec:
design/ANNOTATION_FORMAT.md - Plan & per-phase status:
design/IMPLEMENTATION_PLAN.md,PHASE2_STATUS.md,PHASE3_CORPUS.md,PHASE4_ANALYSIS.md - Frozen feature set:
design/PHASE2_CORE_SET_PROPOSAL.md - Deployment:
design/DEPLOYMENT_FEASIBILITY.md
Scoping Review — Computational Tools for Second-by-Second Annotation of Movies and Stories
Generated by a 21-agent research workflow. Sections:
- Overview & Executive Summary
- Semantic Feature Hierarchy
- Visual features
- Audio features
- Language features
- Social features
- Situation features
- Affect features
- Redundancy & Parsimony Analysis
- Recommendations — Best-in-Class Feature Set
Companion design docs: annotation output format, deployment feasibility, implementation plan.
Catalog covers 146 tools across 15 feature subclasses.
Scoping Review — Overview & Executive Summary
Computational Tools for Second-by-Second Annotation of Movies and Stories
A Scoping Review
Executive Summary
Naturalistic stimuli — feature films, television, and spoken narratives — have become central to cognitive and affective neuroscience because they engage perception, language, social cognition, and emotion as they actually unfold in the world. But their analytic value depends on a prerequisite that is rarely trivial: turning a continuous audiovisual stream into a dense, time-locked set of features that can be aligned to brain data on a common clock (per second, per frame, or per fMRI repetition time, TR). This review maps the computational tools that produce those features and offers a tractable, best-in-class set a small lab can actually deploy.
We organize the entire annotation space into a six-class semantic hierarchy. Three classes are defined by signal source — Visual (pixels), Audio (waveform), and Language (the time-aligned transcript) — and within each, features run from low-level signal statistics through mid-level structure to high-level semantics. Three further classes — Social, Situation, and Affect — are defined by what is represented rather than by any single sensor; they are intrinsically cross-modal and consume outputs from the first three. This crossing of modality with representational level is the review’s backbone and lets a reader walk any modality from “what the sensor measures” to “what it means.”
Our headline finding is that the field has consolidated. For most subclasses a handful of mature, open-source, locally deployable tools dominate, and a single consumer GPU (often just a CPU) suffices. The recommended core: classic computer-vision libraries (scikit-image, OpenCV) plus vision-science toolboxes (pyrtools, SHINE) for low-level visual statistics; SEA-RAFT, pymoten, and TransNetV2 for motion and shot structure; vision-language and self-supervised encoders (SigLIP 2, DINOv2/v3) plus a video-LLM (Qwen2.5/3-VL) for visual semantics and actions; OpenFace 3.0, InsightFace, and MMPose for faces, identity, gaze, and body pose. On the audio side, librosa, openSMILE, and Parselmouth cover acoustics and voice quality; BEATs/CLAP handle sound events; faster-whisper/WhisperX and pyannote produce transcripts and speaker turns. For language, spaCy plus established norm databases (SUBTLEX, Brysbaert, Warriner, NRC) supply lexical features, while instruction-tuned LLM embeddings (Qwen3-Embedding) and hidden states anchor discourse, coherence, and the neuroimaging encoding-model standard. The cross-modal classes lean on the same primitives fused by a video-LLM, with depicted affect drawn from per-modality models (audeering wav2vec2, HSEmotion, GoEmotions) and event boundaries from GSBS or BrainIAK HMMs.
Two design cautions recur throughout. First, depicted versus elicited affect (what a character expresses versus what a viewer feels) must be logged as separate streams with different ground truth. Second, temporal unit (frame, window, utterance, event, shot) must be recorded explicitly, because every feature ultimately resamples to one shared per-second/per-TR movie clock. These two fields are the most likely to be silently conflated downstream and should be required, not optional.
Introduction
Motivation
For decades, cognitive neuroscience favored tightly controlled, minimal stimuli — isolated words, gratings, static faces — because they made the mapping from stimulus to neural response interpretable. That control came at a cost: it stripped away the temporal continuity, multimodal richness, and social-emotional context in which cognition normally operates. The shift toward naturalistic paradigms — watching films, listening to stories — restores that ecological validity and has proven remarkably productive, enabling work on event segmentation, narrative comprehension, social inference, and shared neural responses across individuals.
The methodological catch is that a movie or a story is not, by itself, an independent variable. To relate brain activity to the stimulus, the stimulus must first be annotated: decomposed into quantitative feature timelines — luminance and motion energy, speaker identity and word surprisal, depicted emotion and event boundaries — each sampled densely enough to track second-by-second variation and aligned to the same clock as the neural recording. Historically this was done by hand, which is slow, subjective, and impossible to scale to feature films. The explosion of computer-vision, audio, and language models over the past decade has made automated second-by-second annotation feasible, but it has also produced a bewildering and fast-moving toolscape. A researcher who wants, say, a per-TR regressor for “social interaction intensity” or “visual clutter” faces dozens of candidate models with unclear redundancy, maturity, and hardware demands. This review exists to make that landscape navigable.
The feature taxonomy at a glance
We classify annotation features along two axes. The first is modality, the signal source: the visual stream (1), the audio waveform (2), and the time-aligned linguistic transcript (3). The second is representational level, the climb from raw signal to meaning: low-level perceptual statistics, mid-level structure, and high-level semantics — with a static-versus-dynamic split preserved within vision and audio. Layered on top are three cross-modal classes organized by what they represent: Social (4), depicted interpersonal content; Situation (5), settings, schemas, scripts, and event structure; and Affect (6), emotion in both its depicted and elicited senses. Each leaf in this tree is a feature group with a stable dotted address (for example visual.lowlevel_static.color or affect.depicted.vocal), a modality and level tag, a temporal unit, onset/duration, values, and provenance. Crucially, cross-modal classes do not copy values; they store references to the contributing leaves, making lineage explicit. A language subclass acts as a deliberate seam — speech-to-language tools (ASR, diarization, prosody) are computed from the waveform but produce the linguistic substrate, so they sit at the head of the Language class while referencing, not duplicating, the acoustic layer.
Methodology of this review
We conducted a scoping review (rather than a systematic effectiveness review) because the goal is breadth of coverage and practical orientation, not pooled effect sizes. For each leaf of the hierarchy we surveyed the current open-source and API tool ecosystem, prioritizing tools that (a) emit time-resolved, timeline-alignable output, (b) are documented and maintained, and (c) are deployable by a small lab on modest hardware. Each tool is characterized by its concrete output (what value it produces per temporal unit), its compute profile (CPU/GPU/API), its native temporal granularity, and a recommendation tier (include / include-if-resources / alternative / drop). For every subclass we then identify a best-in-class default set and flag likely-redundant tools — overlapping options where running more than one buys little. This redundancy analysis is deliberate: it converts an overwhelming menu into a defensible minimal pipeline.
Headline recommendations
Our central recommendation is to resist the temptation to run everything. For each subclass, a small overlapping set of mature tools captures nearly all the available signal; additional models are mostly redundant variants. The tractable best-in-class pipeline named in the Executive Summary lets a single lab annotate a full feature film across all six classes on one consumer GPU. The recurring decision points are practical, not exotic: which norm databases and LLM to license, what window stride sets temporal granularity, and how to resample heterogeneous units onto a shared per-second clock. Finally, we stress two non-negotiable schema fields — temporal_unit and a depicted_vs_elicited flag — as the safeguards most likely to prevent silent misinterpretation of these feature streams downstream.
Semantic Feature Hierarchy
This semantic hierarchy organizes all annotation features into a coherent tree. The natural organizing axes are MODALITY (visual / audio / language / multimodal) crossed with LEVEL (low-level signal → mid → high-level semantic/social/situational/affective).
Semantic Hierarchy for Naturalistic-Stimulus Annotation Features
The tree is organized first by modality (the signal source: visual pixels, audio waveform, linguistic transcript) and second by representational level (low-level perceptual signal → mid-level structure → high-level semantic/social/situational meaning). Cross-modal classes (Social, Situation, Affect) sit at the top level because they are defined by what is represented, not by a single signal source, and they consume features from multiple modalities.
ANNOTATION FEATURES
│
├── 1. VISUAL — features derived from the image/video stream
│ │
│ ├── 1.1 Low-level static (per-frame signal statistics)
│ │ ├── 1.1.1 Luminance & contrast (mean luminance, RMS/Michelson contrast, luminance histogram)
│ │ ├── 1.1.2 Color (RGB/HSV/CIELAB means+SDs, colorfulness, warmth, saturation, dominant hue)
│ │ ├── 1.1.3 Spatial frequency & orientation energy (FFT power spectrum/slope, steerable-pyramid subband energy, Gabor banks)
│ │ ├── 1.1.4 Texture & edges (edge density, GLCM, LBP, Laplacian sharpness/focus)
│ │ ├── 1.1.5 Complexity & clutter (Shannon entropy, Feature-Congestion, Subband-Entropy)
│ │ └── 1.1.6 Holistic spatial envelope (GIST descriptor — bridges to mid-level)
│ │
│ ├── 1.2 Low-level dynamic (per-frame-pair motion signal)
│ │ ├── 1.2.1 Optical flow (dense u,v field; mean/median magnitude, orientation histogram, divergence/curl)
│ │ ├── 1.2.2 Camera vs object motion (global-motion estimate + residual object-motion magnitude)
│ │ ├── 1.2.3 Motion energy (pymoten spatiotemporal Gabor-channel energies for fMRI encoding)
│ │ └── 1.2.4 Shot/cut boundaries & visual change (cut probabilities, scene segments, frame-to-frame novelty score)
│ │
│ ├── 1.3 Mid-level perceptual attention & geometry
│ │ ├── 1.3.1 Visual saliency (static & spatiotemporal fixation maps; concentration, entropy, peak, salient-area)
│ │ ├── 1.3.2 Monocular depth (relative/metric depth maps; mean/range depth, foreground fraction, layering)
│ │ └── 1.3.3 Image aesthetics & quality (aesthetic score, technical IQA, memorability)
│ │
│ ├── 1.4 High-level static semantics (what is depicted, per frame)
│ │ ├── 1.4.1 Vision-language embeddings & probes (CLIP/SigLIP image embeddings; open-vocab object/scene/attribute scores)
│ │ ├── 1.4.2 Self-supervised dense embeddings (DINOv2/v3 CLS + patch features for RSA / linear probes)
│ │ ├── 1.4.3 Objects (localized) (open-vocab detection boxes, labels, counts, positions)
│ │ ├── 1.4.4 Scenes/places/attributes (scene-category posteriors, indoor/outdoor, scene attributes)
│ │ ├── 1.4.5 Scene composition (panoptic) (per-class area fractions, instance masks/counts, segmentation tracks)
│ │ └── 1.4.6 Frame description (VLM) (free-text captions + structured VQA fields per keyframe)
│ │
│ ├── 1.5 High-level dynamic semantics (what happens, over time)
│ │ ├── 1.5.1 Action recognition (Kinetics/SSv2 posteriors + pooled spatiotemporal embeddings, sliding window)
│ │ ├── 1.5.2 Zero-shot open-vocab actions (X-CLIP/InternVideo2 similarity to custom action phrases)
│ │ ├── 1.5.3 Temporal action localization (start–end action segments with confidence)
│ │ └── 1.5.4 Dense event description (video-LLM) (timestamped open-vocabulary event captions / temporal grounding)
│ │
│ └── 1.6 Person-centric visual (faces, bodies, gaze, expression)
│ ├── 1.6.1 Face detection & identity (bboxes, landmarks, ArcFace identity embeddings, age/sex)
│ ├── 1.6.2 Head pose & gaze (yaw/pitch/roll, gaze vectors, gaze targets)
│ ├── 1.6.3 Facial expression (FACS action units, discrete emotion, valence/arousal — depicted)
│ └── 1.6.4 Body & hand pose (2D/3D/whole-body keypoints → posture, gesture, orientation)
│
├── 2. AUDIO — features derived from the soundtrack/waveform
│ │
│ ├── 2.1 Low-level acoustic (per-frame signal descriptors)
│ │ ├── 2.1.1 Loudness & energy (RMS, loudness in sones, intensity dB, zero-crossing rate)
│ │ ├── 2.1.2 Spectral descriptors (centroid, rolloff, flux, flatness, bandwidth, contrast, MFCC, mel-spectrogram)
│ │ ├── 2.1.3 Pitch / F0 (F0 contour, voiced flags, salience — CREPE/pYIN/Praat)
│ │ ├── 2.1.4 Harmonic & psychoacoustic (harmonicity/HNR, inharmonicity, roughness/dissonance, sharpness, tonality)
│ │ └── 2.1.5 Voice quality (jitter, shimmer, formants F1–F4, spectral tilt — segment-level)
│ │
│ ├── 2.2 Mid-level musical structure
│ │ ├── 2.2.1 Tonal/harmonic content (chroma, tonnetz, key/mode + strength, chords)
│ │ └── 2.2.2 Rhythm & meter (onsets, tempo/BPM, beat & downbeat times, time signature)
│ │
│ └── 2.3 High-level audio semantics
│ ├── 2.3.1 Sound events / tagging (527-class AudioSet posteriors, framewise SED, embeddings)
│ ├── 2.3.2 Acoustic scene (scene classification, ambience)
│ ├── 2.3.3 Speech/music/noise segmentation (time-stamped speech/music/noise; VAD)
│ ├── 2.3.4 Open-vocab audio probes (CLAP) (per-prompt audio-text similarity time series)
│ └── 2.3.5 Music semantics (MIR) (genre, mood/theme, instrument tags, danceability, music valence/arousal)
│
├── 3. LANGUAGE — features derived from the (time-aligned) transcript
│ │
│ ├── 3.1 Speech-to-language interface (paralinguistic bridge from audio)
│ │ ├── 3.1.1 ASR transcript & timing (word/segment text + timestamps, confidence, speaking rate)
│ │ ├── 3.1.2 Speaker diarization & turns (who-spoke-when, speaker count, overlap, turn transitions)
│ │ ├── 3.1.3 Prosody contours (per-second F0/intensity/HNR resampled from acoustic layer)
│ │ └── 3.1.4 Vocal affect (dimensional A/V/D + categorical emotion from voice — depicted)
│ │
│ ├── 3.2 Low-level lexical (per-word psycholinguistic features)
│ │ ├── 3.2.1 Token annotations (lemma, POS, NER, stop/punct flags)
│ │ ├── 3.2.2 Frequency & length (Zipf/log frequency, contextual diversity, AoA, word length)
│ │ ├── 3.2.3 Semantic norms (concreteness, imageability, valence/arousal/dominance per word)
│ │ ├── 3.2.4 Emotion lexicons (NRC EmoLex 8-emotion flags, LIWC affect categories)
│ │ └── 3.2.5 Surprisal & entropy (per-word LLM surprisal in bits, next-word entropy)
│ │
│ ├── 3.3 Mid-level syntactic structure (per-utterance)
│ │ ├── 3.3.1 Dependency & morphology (dep relations+heads, UD morph feats: tense/aspect/mood)
│ │ ├── 3.3.2 Constituency trees (PTB phrase-structure trees, nesting depth)
│ │ ├── 3.3.3 Syntactic complexity (tree depth, Yngve/Frazier, L2SCA clause/T-unit indices, readability)
│ │ ├── 3.3.4 Coreference (mention clusters; who/what each pronoun refers to)
│ │ └── 3.3.5 Dialogue acts (per-utterance speech-act categories)
│ │
│ └── 3.4 High-level semantics, discourse & narrative
│ ├── 3.4.1 Passage embeddings (dense sentence/window embeddings — the semantic backbone)
│ ├── 3.4.2 Contextual LLM hidden states (per-word last-layer states — fMRI encoding-model standard)
│ ├── 3.4.3 Coherence/drift/novelty (sliding-window cosine coherence, drift, segmentation troughs)
│ ├── 3.4.4 Topic structure (topic assignment + topics-over-time prevalence)
│ ├── 3.4.5 Discourse relations (RST/EDU segmentation, relation labels, nuclearity)
│ └── 3.4.6 Narrative structure (LLM) (narrative stage, turning points, structured tags, summaries)
│
├── 4. SOCIAL — depicted interpersonal content (cross-modal: vision + audio + language)
│ │
│ ├── 4.1 Social primitives (deterministic, per-second tracks)
│ │ ├── 4.1.1 Agent presence & identity (character count, identity tracks, co-presence matrix)
│ │ ├── 4.1.2 Active speaker / addressee (who-talks-on-screen, speaker↔face binding, listener roles)
│ │ ├── 4.1.3 Gaze & joint attention (mutual gaze, shared-target / joint-attention signals)
│ │ └── 4.1.4 Proximity, orientation & touch (pairwise distance, facing toward/away, contact, approach/retreat)
│ │
│ └── 4.2 Social semantics (VLM/LLM-inferred, per-shot/scene)
│ ├── 4.2.1 Interaction type (cooperation/conflict, dialogue, joint action categories)
│ ├── 4.2.2 Dominance & affiliation (relational ratings, social-network edges)
│ └── 4.2.3 Theory-of-mind & intention (mental-state, goal, and intention inferences)
│
├── 5. SITUATION — setting, schemas, scripts & event structure (cross-modal)
│ │
│ ├── 5.1 Event segmentation (data-driven boundaries on feature timelines: HMM/GSBS; + shot priors)
│ ├── 5.2 Setting & location (scene type, indoor/outdoor, time-of-day, spatial layout)
│ ├── 5.3 Scripts & schemas (recognized situational scripts / schema labels)
│ └── 5.4 Event-indexing dimensions (space, time, causation, intention, protagonist per event/segment)
│
└── 6. AFFECT — emotion & affect (cross-modal; depicted vs elicited kept distinct)
│
├── 6.1 Depicted affect (expressed by characters, by source modality)
│ ├── 6.1.1 Facial affect (face valence/arousal + categorical — from 1.6.3)
│ ├── 6.1.2 Vocal affect (voice A/V/D + categorical — from 3.1.4)
│ ├── 6.1.3 Textual/dialogue affect (utterance emotion + VAD mapping — from 3.2.3/3.4)
│ └── 6.1.4 Music/soundtrack affect (soundtrack valence/arousal, mood — from 2.3.5)
│
├── 6.2 Multimodal fused affect (MLLM window-level categorical + V/A/D + reasoned justification)
│
└── 6.3 Elicited (viewer) affect (induced V/A timeseries, emotional-impact/fear flags — SEPARATE stream)
Node-by-node rationale (design notes)
Top-level split (1–3 modality, 4–6 cross-modal). Classes 1–3 are pure signal-source modalities: every feature traces to pixels, the waveform, or the transcript. Classes 4–6 are representational targets (social content, situational structure, affect) that are intrinsically multimodal and reuse outputs from 1–3 — so they are organized by what they represent, not by signal source. This keeps each leaf feature in exactly one home while letting cross-modal classes reference contributing leaves (the “from X.Y.Z” pointers).
Level ordering within each modality. Every modality is internally ordered low-level signal → mid-level structure → high-level semantics, and within visual/audio the static-vs-dynamic distinction is preserved as a parallel axis (1.1/1.4 static; 1.2/1.5 dynamic). This is the scoping-review backbone: a reviewer can walk any modality from “what the sensor measures” to “what it means.”
Language as a bridge modality. Subclass 3.1 (Speech-to-language interface) is the deliberate seam between Audio and Language: ASR, diarization, prosody, and vocal affect are computed from the waveform but produce the time-aligned linguistic substrate, so they live at the head of Language. Acoustic prosody itself stays in 2.1 (signal) and is referenced, not duplicated, by 3.1.3.
Depicted vs elicited affect. Class 6 explicitly separates depicted/expressed affect (6.1, organized by contributing modality) from viewer-elicited/induced affect (6.3) because they have different ground truth and must be logged as distinct feature streams — a recurring caution in the affect catalog.
Implications for the output data format
This tree maps directly onto a hierarchical schema. Each leaf is a feature group with a stable dotted path (e.g. visual.lowlevel_static.color, language.semantics.coherence_drift, affect.depicted.vocal) and a uniform record envelope:
path(semantic address),modality,level(low/mid/high),temporal_unit(frame / window / utterance / event / shot),onset,duration,value(s), plus provenance (extractor,model,version,recommendation_tier).- Cross-modal classes (4–6) store references to the contributing leaf paths rather than copying values, encoding the “from X.Y.Z” pointers as first-class lineage.
- The
levelandstatic/dynamicaxes become filterable facets — the same metadata that structures the scoping review structures query and resampling (everything resamples to a common per-second / per-TR movie clock).
Paths worth flagging for the format spec: temporal_unit and a depicted_vs_elicited flag (on class 6) are load-bearing — they are the two fields most likely to be conflated downstream and should be required, not optional.
Visual features
Low-level static visual properties
Per-frame low-level image statistics (luminance, RMS contrast, color in RGB/HSV/CIELAB, colorfulness, spatial-frequency power spectra, edge/orientation energy, entropy, clutter) are a mature, well-understood domain that is best served by classic computer-vision libraries and a handful of vision-science toolboxes rather than learned models. The recommended core is scikit-image + OpenCV + NumPy for the bulk of pixel/color/edge/frequency statistics, a steerable-pyramid library (pyrtools / plenoptic) for biologically motivated orientation-energy and spatial-frequency banks, SHINE for canonical luminance/contrast/Fourier-amplitude measures aligned with the vision-science literature, and the Rosenholtz Feature-Congestion/Subband-Entropy code for visual clutter. GIST provides a compact holistic spatial-envelope descriptor that bridges low- and mid-level. Learned descriptors add little over these analytic features for this specific subclass; the main “modern” gain is wrapping the classic operators in a per-frame pipeline (e.g. pliers) and running them at 1-25 fps over a video. All of these run on CPU and are trivially feasible for a small lab; the practical work is timeline alignment and per-frame batching, not compute.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| scikit-image (skimage) 0.24/0.25 + NumPy | General-purpose per-frame pixel statistics: luminance/intensity, RMS contrast (std of luminance), color-space conversions (rgb2hsv, rgb2lab, rgb2gray), Shannon entropy (skimage.measure.shannon_entropy and local entropy filter), edge maps (Sobel, Scharr, Canny, edge density), texture (GLCM via greycomatrix/graycoprops, local binary patterns), and frequency content via skimage.filters and FFT helpers. | Per-frame scalars and maps: mean luminance, RMS contrast (Michelson/Weber/std-based), per-channel R/G/B and H/S/V and L*/a*/b* means and stds, global/local Shannon entropy (bits), edge-pixel fraction (edge density), GLCM contrast/energy/homogeneity/correlation, LBP histograms. All reducible to 1 value/frame. | video-frame | frame | yes-cpu | best-in-class | include |
| OpenCV (cv2) 4.x | Fast per-frame image statistics and primitives: color-space conversion (cvtColor to HSV/Lab/YCrCb/Gray), histograms, Sobel/Laplacian/Canny edges, Gabor kernels (getGaborKernel) for orientation/spatial-frequency energy, DFT/magnitude spectra, Laplacian-variance sharpness/blur, and efficient video frame I/O (VideoCapture) for timeline-locked extraction. | Per-frame: mean/std luminance, per-channel color means in any space, color histograms, edge density, Laplacian variance (focus/sharpness scalar), Gabor-bank response energy per orientation/frequency, FFT radial power-spectrum slope. Plus the frame-grabbing layer that timestamps every frame. | video-frame | frame | yes-cpu | best-in-class | include |
| SHINE toolbox (+ SHINE_color fork) | Canonical vision-science measurement and control of low-level image properties: mean luminance, RMS contrast, luminance histogram, and the rotational (orientation-averaged) Fourier amplitude spectrum / energy at each spatial frequency. Primarily a control/equalization tool but its measurement functions are the field-standard definitions. | Per-frame (per-image): mean luminance, RMS contrast, full luminance histogram, 1-D rotationally averaged Fourier amplitude spectrum (energy vs spatial frequency) and amplitude-spectrum slope. SHINE_color extends to color channels. | video-frame | frame | yes-cpu | strong | include |
| pyrtools (LabForComputationalVision) / matlabPyrTools | Multi-scale, multi-orientation steerable-pyramid and Laplacian/QMF/wavelet decompositions. Provides biologically motivated orientation-energy and spatial-frequency subband responses (the V1-like Gabor/steerable filter bank) at multiple scales and orientations. | Per-frame: per-subband (scale x orientation) coefficient energy/variance, total energy per orientation band (orientation-energy histogram), energy per spatial-frequency scale, oriented-edge energy. Yields a compact vector of orientation x scale energies per frame. | video-frame | frame | yes-cpu | strong | include |
| Rosenholtz Feature Congestion + Subband Entropy clutter measures | Two complementary models of visual clutter. Feature Congestion captures local variability of color, luminance-contrast, and orientation (via Gaussian-pyramid covariance of feature maps); Subband Entropy captures clutter as the entropy/information in wavelet luminance and chrominance subbands. | Per-frame: a global Feature-Congestion clutter scalar (and a dense per-pixel clutter map), and a global Subband-Entropy clutter scalar; optionally split into color, contrast, and orientation clutter sub-components. | video-frame | frame | yes-cpu | strong | include |
| Hasler & Susstrunk colorfulness metric (+ warmth from CIELAB) | Perceptual colorfulness of a natural image from opponent-color statistics, validated against human colorfulness ratings (>90% correlation). Trivially paired with CIELAB-based ‘warmth’ (a* red-green / b* yellow-blue means or hue distribution) and saturation from HSV. | Per-frame scalars: colorfulness index M = sqrt(std_rg^2 + std_yb^2) + 0.3sqrt(mean_rg^2 + mean_yb^2); plus mean a/b* (warmth/coolness), mean HSV saturation, dominant-hue summaries. | video-frame | frame | yes-cpu | strong | include |
| GIST / spatial envelope descriptor (lear-gist-python, pyGist, LMgist) | Holistic low-to-mid-level scene descriptor: orientation x spatial-frequency energy (Gabor filter bank, typically 8 orientations x 4 scales) pooled over a 4x4 spatial grid, summarizing global scene structure (openness, roughness, naturalness-related dimensions). | Per-frame fixed-length vector (e.g. 512-d for 8 orientations x 4 scales x 4x4 grid) of windowed orientation-energy values; a compact global texture/structure signature usable directly as a per-frame feature vector. | video-frame | frame | yes-cpu | usable | include-if-resources |
| pliers (PsychoinformaticsLab) low-level image extractors | Orchestration framework that runs low-level image extractors (BrightnessExtractor, SharpnessExtractor, VibranceExtractor, SaliencyExtractor, plus wrappers to scikit-image/OpenCV) over video frames with automatic timestamping and a tidy long-format output, instead of hand-rolling the per-frame loop. | Per-frame, timestamped: brightness (mean value/luminance), sharpness (Laplacian-variance), vibrance (variance across RGB channels), saliency summary; emits an onset/duration-tagged dataframe ready for fMRI/timeline alignment. | video-frame | frame | yes-cpu | usable | include |
Recommended (best-in-class): scikit-image + OpenCV + NumPy as the core per-frame engine (luminance, RMS contrast, RGB/HSV/CIELAB color, entropy, edges, FFT power spectrum); pyrtools / plenoptic steerable pyramid for orientation-energy and spatial-frequency banks; SHINE for canonical, literature-matched luminance/contrast/Fourier-amplitude-spectrum definitions; Rosenholtz Feature-Congestion + Subband-Entropy for visual clutter; Hasler-Susstrunk colorfulness formula (implemented directly) for perceptual color; pliers as the timestamping/orchestration layer feeding the movie timeline
Likely redundant:
- OpenCV Gabor-kernel bank is superseded by pyrtools/GIST for principled orientation-energy (keep OpenCV mainly for speed and video decoding)
- Global Shannon entropy and raw edge density are weaker clutter proxies superseded by Rosenholtz Feature-Congestion / Subband-Entropy
- GIST overlaps heavily with pyrtools orientation-energy; only one is needed unless a compact fixed-length holistic vector is specifically wanted
- Learned low-level descriptors / CNN early-layer activations add little over analytic luminance/contrast/color/frequency features for this subclass and are better reserved for mid/high-level visual subclasses
- pliers built-in extractors duplicate scikit-image/OpenCV computations; it is an orchestration convenience, not an independent feature source
- scikit-image and OpenCV themselves overlap substantially in color/edge/FFT ops
References
- scikit-image (skimage) 0.24/0.25 + NumPy — scikit-image.org; van der Walt et al. 2014 PeerJ
- OpenCV (cv2) 4.x — opencv.org; pyimagesearch colorfulness/blur tutorials
- SHINE toolbox (+ SHINE_color fork) — Willenbockel et al. 2010 Behav Res Methods 42:671; mapageweb.umontreal.ca/gosselif/shine
- pyrtools (LabForComputationalVision) / matlabPyrTools — github.com/LabForComputationalVision/pyrtools; Simoncelli & Freeman 1995; plenoptic (Duong et al.)
- Rosenholtz Feature Congestion + Subband Entropy clutter measures — Rosenholtz, Li & Nakano 2007 J Vision; persci.mit.edu/research/clutter
- Hasler & Susstrunk colorfulness metric (+ warmth from CIELAB) — Hasler & Susstrunk 2003, SPIE Human Vision; pyimagesearch colorfulness post
- GIST / spatial envelope descriptor (lear-gist-python, pyGist, LMgist) — Oliva & Torralba 2001 IJCV; github.com/whitphx/lear-gist-python; davidmwatson/pyTools pyGist
- pliers (PsychoinformaticsLab) low-level image extractors — McNamara et al. 2017; github.com/PsychoinformaticsLab/pliers; Neuroscout (de la Vega 2022 eLife)
High-level static visual: objects, scenes, places, attributes
For per-frame movie/audio-story annotation of objects, scenes, places, and attributes, the field has consolidated around a few foundation-model families. Two complementary backbones cover most needs: contrastive vision-language encoders (OpenCLIP, SigLIP 2) that give open-vocabulary probe scores and zero-shot scene/place/object/attribute labels via text prompts, and self-supervised encoders (DINOv2/DINOv3) that give label-free dense semantic embeddings. For explicit localized labels, open-vocabulary detectors (Grounding DINO 1.5/1.6, YOLO-World) and promptable/panoptic segmenters (SAM 2, Mask2Former) emit per-object boxes/masks. For rich natural-language scene descriptions and attribute/relation reasoning, open VLMs (Qwen2.5-VL, InternVL3, LLaVA-OneVision) caption frames or answer structured VQA. A small lab can run most of these locally on a single 24-48GB GPU; the main tradeoff is fixed-vocabulary classifiers (Places365, ImageNet backbones) being largely superseded by open-vocabulary CLIP-family probing, except where a frozen, reproducible, interpretable category vector is explicitly wanted.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| OpenCLIP (ViT-L/14, ViT-H/14, ViT-bigG/14; LAION-2B / DataComp checkpoints) | Open-vocabulary semantic content of a frame: similarity between the image and arbitrary text prompts for objects, scenes, places, attributes, actions. | Per-frame: (a) 768-1280 dim L2-normalized image embedding; (b) cosine-similarity / softmax scores against any user-defined label set (e.g. zero-shot Places365 categories, ImageNet-1k classes, attribute prompts). Continuous probe scores per concept per frame. | video-frame | frame (run on sampled frames, e.g. 1-4 fps, aligned to timeline) | yes-gpu | best-in-class | include |
| SigLIP 2 (google/siglip2 ViT-B/L/So400m, NaFlex variants) | Open-vocabulary image-text alignment (same role as CLIP) with sigmoid loss, stronger zero-shot classification, localization, multilingual (109 languages) and improved dense features. | Per-frame: image embedding (768-1152 dim) plus per-prompt sigmoid/logit scores for object/scene/place/attribute label sets; supports native-resolution (NaFlex) and better patch-level dense features for localized attribute probing. | video-frame | frame | yes-gpu | best-in-class | include |
| DINOv2 / DINOv3 (ViT-S/B/L/g, self-supervised) | Label-free general-purpose visual semantics and structure; strong dense (patch-level) features for objects/parts/scenes without needing any text vocabulary. | Per-frame: global CLS embedding (384-1536 dim) and per-patch dense feature maps. No human-readable labels by default; used as representational similarity (RSA) features, for linear-probe classifiers, or as the backbone for downstream attribute/scene probes. DINOv3 adds Gram-anchored high-resolution dense maps. | video-frame | frame | yes-gpu | best-in-class | include |
| Grounding DINO 1.5 / 1.6 (Pro and Edge) | Open-vocabulary object DETECTION: localizes arbitrary objects/categories given free-text prompts (category names or referring expressions). | Per-frame: list of detected objects with bounding boxes, free-text class label (from the prompt vocabulary), and confidence score. Yields per-frame object presence/counts/positions for any concept list. | video-frame | frame | yes-gpu | best-in-class | include |
| YOLO-World (v2, S/M/L/X) | Real-time open-vocabulary object detection (CLIP-text-conditioned YOLO). | Per-frame: bounding boxes + open-vocabulary class labels + confidence scores; very fast, so practical to run densely (every frame) across long films. | video-frame | frame | yes-gpu | strong | alternative |
| Qwen2.5-VL (3B / 7B / 72B) | Natural-language scene description, structured VQA over objects/scenes/attributes/relations, grounded captioning with coordinates; the workhorse open VLM for semantic frame annotation. | Per-frame (or per-shot keyframe): free-text caption; structured/JSON answers to fixed question schemas (e.g. setting=indoor/outdoor, place type, salient objects list, attributes, people count); optional grounded boxes for referenced objects. Convert to categorical/ordinal time series. | video-frame | frame / per-shot (run on sampled keyframes; also natively handles short clips) | yes-gpu | best-in-class | include |
| InternVL3 (1B-78B; InternVL3-8B/14B common) | Same scene-description / VQA / grounded-captioning role as Qwen2.5-VL; among the strongest open MLLMs on perception and reasoning benchmarks. | Per-frame: free-text captions and structured VQA outputs (scene/place classification, object inventory, attributes, spatial relations); supports grounding. | video-frame | frame / per-shot | yes-gpu | strong | alternative |
| Places365 CNN (ResNet-50-places365) and ImageNet backbones (ConvNeXt-V2, EVA-02, BEiT-3) | Closed-vocabulary scene/place recognition (365 scene categories) and object classification (ImageNet-1k/-21k) with fixed, reproducible, interpretable label posteriors. | Per-frame: 365-dim scene-category probability vector (Places365) and/or 1000/21841-dim ImageNet class posteriors; also scene attributes (SUN-attribute head) and indoor/outdoor + naturalness. Fully interpretable per-category time series. | video-frame | frame | yes-cpu | usable | include-if-resources |
| Mask2Former / OneFormer (panoptic, COCO/ADE20K/Cityscapes) | Panoptic / semantic / instance segmentation: pixel-level labeling of every region into ‘thing’ (objects) and ‘stuff’ (sky, road, wall, vegetation) categories. | Per-frame: per-pixel class map plus per-instance masks with class labels; derive per-frame area fractions per category (e.g. % sky, % person, % building), object counts, scene composition vectors. | video-frame | frame | yes-gpu | strong | include |
| SAM 2 (Segment Anything Model 2, hiera-tiny/small/base/large) | Class-agnostic promptable segmentation of objects in images and video; produces masks but NOT semantic labels by itself. | Per-frame: high-quality instance masks (from points/boxes/auto ‘everything’ mode), with video temporal mask propagation/tracking. Pair with CLIP/Grounding DINO to attach labels (Grounded-SAM); yields per-object segmented regions, sizes, counts, and consistent track IDs. | video-clip | frame (with temporal mask propagation across the clip) | yes-gpu | strong | include-if-resources |
Recommended (best-in-class): SigLIP 2 (or OpenCLIP ViT-L) as the primary open-vocabulary text-probe encoder for object/scene/place/attribute scores; DINOv2/DINOv3 as the complementary label-free dense embedding for RSA and linear probes; Qwen2.5-VL-7B as the primary captioning/VQA VLM for structured scene descriptions; Grounding DINO (1.6 Pro via API for accuracy, or open 1.0 / YOLO-World locally) for open-vocabulary per-object detection; Mask2Former/OneFormer panoptic for scene-composition (thing+stuff area fractions)
Likely redundant:
- OpenCLIP and SigLIP 2 fill the same text-probe role - run one as primary (SigLIP 2 generally stronger), not both
- Qwen2.5-VL, InternVL3, and LLaVA-OneVision are interchangeable captioning/VQA VLMs - pick one primary after benchmarking your prompt schema
- YOLO-World is superseded on accuracy by Grounding DINO 1.6 Pro; keep only when per-frame throughput/density is the constraint
- Places365 CNN and ImageNet-trained backbones are largely superseded by zero-shot CLIP/SigLIP2 probing of the same label sets - keep only for a frozen, reproducible fixed-taxonomy probability vector
- SAM 2 alone produces no labels and overlaps Mask2Former for static panoptic needs; only adds value via video-consistent tracking or as Grounded-SAM
Open questions:
- Frame sampling rate to hit true second-by-second alignment vs compute budget (1 fps probing vs shot-level keyframes for heavy VLMs)
- Whether to derive scene/place labels via zero-shot CLIP/SigLIP2 prompting or keep a frozen Places365 head for reproducibility/citation in fMRI work
- DINOv3 licensing (research/non-commercial terms) may restrict use vs Apache-2.0 DINOv2
- Grounding DINO 1.6 Pro best accuracy is API-only - acceptable for a small lab, or fall back to open 1.0/YOLO-World?
- Need for an aggregation/parsing layer to convert free-text VLM captions into consistent categorical per-second feature vectors (schema + JSON-constrained decoding)
References
- OpenCLIP (ViT-L/14, ViT-H/14, ViT-bigG/14; LAION-2B / DataComp checkpoints) — github.com/mlfoundations/open_clip; LAION-2B / DataComp-1B; Radford et al. 2021 CLIP
- SigLIP 2 (google/siglip2 ViT-B/L/So400m, NaFlex variants) — Tschannen et al. 2025 arXiv:2502.14786; hf.co/google (siglip2)
- DINOv2 / DINOv3 (ViT-S/B/L/g, self-supervised) — Oquab et al. 2023 DINOv2; DINOv3 2025 arXiv:2508.10104
- Grounding DINO 1.5 / 1.6 (Pro and Edge) — Liu et al. 2023; Ren et al. 2024 Grounding DINO 1.5 arXiv:2405.10300; IDEA-Research/GroundingDINO
- YOLO-World (v2, S/M/L/X) — Cheng et al. 2024 arXiv:2401.17270; docs.ultralytics.com/models/yolo-world
- Qwen2.5-VL (3B / 7B / 72B) — Qwen2.5-VL Technical Report 2025; hf.co/Qwen
- InternVL3 (1B-78B; InternVL3-8B/14B common) — InternVL3 2025 arXiv:2504.10479; hf.co/OpenGVLab
- Places365 CNN (ResNet-50-places365) and ImageNet backbones (ConvNeXt-V2, EVA-02, BEiT-3) — Zhou et al. 2017 Places365; github.com/CSAILVision/places365; huggingface timm
- Mask2Former / OneFormer (panoptic, COCO/ADE20K/Cityscapes) — Cheng et al. 2022 Mask2Former; Jain et al. 2023 OneFormer
- SAM 2 (Segment Anything Model 2, hiera-tiny/small/base/large) — Ravi et al. 2024 SAM 2 arXiv:2408.00714; Grounded-SAM (IDEA-Research)
Faces, bodies, gaze, and facial expression
This subclass covers per-frame extraction of where faces and bodies are, who they are, where they look, and what they express. The mature open-source stack is: a detector/tracker (RetinaFace/InsightFace, MediaPipe) feeding identity (ArcFace), landmarks, head pose and gaze (OpenFace 3.0, L2CS-Net), facial action units and discrete/continuous emotion (Py-Feat, OpenFace 3.0, HSEmotion/EmotiEffLib, EmoNet), and body/hand keypoints (MMPose’s RTMPose/RTMW, ViTPose, MediaPipe Holistic). As of 2025, OpenFace 3.0 (arXiv 2506.02891) consolidates landmarks+AUs+gaze+emotion into one lightweight CPU-capable multitask model, and MMPose RTMPose/RTMW dominate real-time 2D/whole-body pose; these are the recommended defaults for a small lab. All major components run on a single consumer GPU (often CPU), making second-by-second annotation of long films feasible.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| InsightFace (RetinaFace detector + ArcFace ‘buffalo_l’ pack, ResNet50@WebFace600K) | Face detection, 5-point + 2D-106/3D-68 landmark alignment, and 512-d face identity embeddings for recognition/tracking across frames. | Per detected face per frame: bounding box (x,y,w,h) + detection score; 5 or 106 2D landmark coords; 68 3D landmark coords; coarse head pose (yaw/pitch/roll from 3D fit); age estimate; binary sex; 512-dim L2-normalized ArcFace embedding (cosine-comparable for identity clustering/tracking). | video-frame | per-frame (per detected face) | yes-gpu | best-in-class | include |
| RetinaFace (standalone, serengil/retinaface or InsightFace detector) | Single-stage dense face detection with facial landmark localization; the detection backbone underneath most modern pipelines. | Per frame: face bounding boxes + confidence; 5 facial landmarks (eyes, nose, mouth corners) per face. No identity/AUs. | video-frame | per-frame | yes-cpu | strong | alternative |
| MediaPipe Tasks (Face Detector, Face Landmarker w/ blendshapes, Pose Landmarker, Hand Landmarker; legacy Holistic) | Lightweight on-device face, body, and hand keypoints plus 52 ARKit-style blendshape expression coefficients and approximate head pose. | Per frame: 478 3D face mesh landmarks; 52 blendshape coefficients (0-1) covering brow/eye/mouth/jaw expression primitives; 4x4 facial transform matrix (head pose); 33 3D body pose landmarks with visibility; 21 3D hand landmarks per hand. Emits normalized + world coords. | video-frame | per-frame | yes-cpu | strong | include |
| OpenFace 3.0 (lightweight multitask, RetinaFace MobileNet-0.25 frontend) | Unified per-frame facial behavior: landmarks, facial action units, eye-gaze, and discrete emotion from a single shared-backbone model. | Per face per frame: dense 2D facial landmarks; AU detection (presence/intensity, FACS-style set); eye-gaze direction (gaze vectors / yaw-pitch); head pose; discrete facial emotion class + probabilities. Time-series CSV per video. | video-frame | per-frame | yes-cpu | best-in-class | include |
| OpenFace 2.0 (TadasBaltrusaitis/OpenFace) | Classic CLM/CE-CLM facial landmark, head pose, gaze, and AU intensity/presence regression; long the field standard for FACS-based AU time series. | Per frame: 68 2D+3D landmarks; head pose (Tx,Ty,Tz, pitch/yaw/roll); eye-gaze vectors (gaze_0, gaze_1) + gaze angle; 17 AU intensities (AU01-AU45, 0-5) + 18 AU presence binaries; per-frame confidence and success flag. Outputs tidy CSV. | video-frame | per-frame | yes-cpu | usable | alternative |
| Py-Feat (cosanlab/py-feat) | Modular Python facial expression pipeline: face detection, landmarks, head pose, AUs, and 7-class emotion, with built-in visualization and analysis (Fex data class). | Per face per frame: bbox + score; 68 landmarks; head pose (pitch/yaw/roll); ~20 AU probabilities/intensities (selectable detectors, e.g. XGBoost/SVM on landmark+HOG); 7 emotion probabilities (anger, disgust, fear, happy, sad, surprise, neutral); optional identity embedding. Returns a pandas-like Fex time-series. | video-frame | per-frame | yes-gpu | strong | include |
| L2CS-Net (Ahmednull/L2CS-Net) | Appearance-based 3D gaze direction estimation in unconstrained, full-360 settings from the face crop (no eye-tracker hardware). | Per face per frame: gaze yaw and pitch angles (radians/degrees) -> 3D gaze direction vector. SOTA 3.92 deg (MPIIGaze), 10.41 deg (Gaze360). | video-frame | per-frame | yes-gpu | strong | include-if-resources |
| MMPose: RTMPose / RTMW / RTMW3D (open-mmlab/mmpose) | Real-time top-down multi-person 2D body, whole-body (body+face+hands+feet), and 3D whole-body keypoint estimation. | Per detected person per frame: 17 COCO body keypoints (RTMPose) OR 133 COCO-WholeBody keypoints (RTMW: 23 body+foot, 68 face, 42 hand), each with (x,y) + confidence; RTMW3D adds 3D (x,y,z). Yields per-person skeletons -> derived posture, hand gesture, head orientation signals. | video-frame | per-frame (per person) | yes-gpu | best-in-class | include |
| ViTPose / ViTPose++ (ViTAE-Transformer/ViTPose, in MMPose) | Vision-transformer top-down human pose estimation; highest-accuracy 2D body keypoints when accuracy outranks speed. | Per person per frame: 17 (COCO) or up to 133 (whole-body via ViTPose+) keypoints with (x,y)+confidence heatmap-derived coords. ViTPose-G reaches >80 AP on COCO. | video-frame | per-frame (per person) | yes-heavy-gpu | strong | alternative |
| HSEmotion / EmotiEffLib (EmotiEffNet, av-savchenko / sb-ai-lab) | Fast EfficientNet/MobileViT-based facial expression classification, valence-arousal regression, and AU prediction from face crops; tuned for video. | Per face per frame: 8-class emotion probabilities (anger, contempt, disgust, fear, happy, neutral, sad, surprise); continuous valence and arousal scalars (-1..1); AU activations; plus reusable face embedding features for downstream temporal models. | video-frame | per-frame (aggregable per-utterance/clip) | yes-cpu | strong | include |
| EmoNet (face-analysis/emonet) | Continuous valence-arousal plus discrete emotion estimation from faces in naturalistic conditions (AffectNet-trained). | Per face per frame: continuous valence and arousal (-1..1); discrete emotion class (5- or 8-class model) + probabilities; facial landmarks. Includes demo_video.py for video face-detect + emotion. | video-frame | per-frame | yes-gpu | usable | alternative |
Recommended (best-in-class): OpenFace 3.0 (landmarks + AUs + gaze + emotion in one CPU-capable multitask model) as the primary face-behavior extractor; InsightFace buffalo_l (RetinaFace + ArcFace) for face detection, tracking, and identity embeddings; MMPose RTMPose / RTMW for real-time 2D and whole-body (body+face+hands+feet) keypoints; MediaPipe Tasks for cheap CPU-only body/hand/face landmarks + blendshapes as a fallback/redundancy layer; HSEmotion/EmotiEffLib for fast continuous valence-arousal and expression, and L2CS-Net when high-accuracy 360-degree gaze is a primary signal
Likely redundant:
- OpenFace 2.0 -> superseded by OpenFace 3.0 (faster, multitask, better accuracy); keep only for reproducing legacy pipelines or its validated AU intensity scale
- Standalone RetinaFace -> redundant with InsightFace, which bundles it; and with detectors inside OpenFace 3.0 / Py-Feat
- ViTPose -> redundant with MMPose RTMPose/RTMW for typical lab use; only worth it for max-accuracy keypoints on a strong GPU
- EmoNet -> valence-arousal largely superseded by HSEmotion (faster, newer, more outputs); keep as a citable cross-check
- L2CS-Net gaze and Py-Feat/HSEmotion emotion overlap OpenFace 3.0’s bundled gaze/emotion -> run separately only when those specific signals need best-in-class accuracy
- MediaPipe body/hand keypoints overlap MMPose (lower accuracy, single-best-effort) -> use MediaPipe for cheap CPU-only or redundancy, MMPose for accuracy/multi-person
References
- InsightFace (RetinaFace detector + ArcFace ‘buffalo_l’ pack, ResNet50@WebFace600K) — github.com/deepinsight/insightface; RetinaFace (arXiv 1905.00641); ArcFace (arXiv 1801.07698)
- RetinaFace (standalone, serengil/retinaface or InsightFace detector) — arXiv 1905.00641; github.com/serengil/retinaface
- MediaPipe Tasks (Face Detector, Face Landmarker w/ blendshapes, Pose Landmarker, Hand Landmarker; legacy Holistic) — ai.google.dev/edge/mediapipe; developers.google.com/mediapipe
- OpenFace 3.0 (lightweight multitask, RetinaFace MobileNet-0.25 frontend) — arXiv 2506.02891; huggingface.co/papers/2506.02891
- OpenFace 2.0 (TadasBaltrusaitis/OpenFace) — github.com/TadasBaltrusaitis/OpenFace; OpenFace 2.0 (FG 2018)
- Py-Feat (cosanlab/py-feat) — Cheong et al., Behavior Research Methods 2023; github.com/cosanlab/py-feat
- L2CS-Net (Ahmednull/L2CS-Net) — arXiv 2203.03339; github.com/Ahmednull/L2CS-Net
- MMPose: RTMPose / RTMW / RTMW3D (open-mmlab/mmpose) — RTMPose arXiv 2303.07399; RTMW arXiv 2407.08634; github.com/open-mmlab/mmpose
- ViTPose / ViTPose++ (ViTAE-Transformer/ViTPose, in MMPose) — ViTPose arXiv 2204.12484; ViTPose++ arXiv 2212.04246
- HSEmotion / EmotiEffLib (EmotiEffNet, av-savchenko / sb-ai-lab) — Savchenko, HSEmotion (CVPR 2023 workshops; arXiv 2403.11590); github.com/av-savchenko/hsemotion; github.com/sb-ai-lab/EmotiEffLib
- EmoNet (face-analysis/emonet) — Toisoul et al., Nature Machine Intelligence 2021; github.com/face-analysis/emonet
Dynamic visual: motion, optical flow, motion energy
This subclass covers low-level dynamic visual signal: dense optical flow (per-pixel motion vectors), aggregate motion energy from spatiotemporal filter banks, camera-vs-object motion separation, shot/cut boundaries, and frame-to-frame visual change/novelty. These are deliberately pre-semantic features (how much/which-direction things move, when the camera cuts) distinct from semantic action recognition. The field is mature and largely open-source: RAFT and its 2024 successor SEA-RAFT dominate dense flow, MemFlow/VideoFlow add multi-frame temporal smoothing, pymoten provides the canonical neuroscience motion-energy pyramid, and TransNetV2 plus PySceneDetect cover shot detection. For a small lab, a practical default pipeline is: PySceneDetect/TransNetV2 for cuts, pymoten for a brain-aligned motion-energy regressor, and SEA-RAFT (or RAFT) for dense flow from which you derive global/mean flow magnitude (proxy for camera motion) and residual flow (object motion).
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| SEA-RAFT (Princeton-VL, ECCV 2024) | Dense per-pixel optical flow (2D motion vector field) between consecutive frames; current best accuracy/speed tradeoff among open flow models | Per-pixel (u,v) displacement field at full resolution; derivable per-frame scalars: mean/median flow-vector magnitude, flow-orientation histogram, divergence/curl, global-motion (camera) estimate via robust fit and residual (object) motion magnitude | video-frame | frame (per consecutive frame pair) | yes-gpu | best-in-class | include |
| RAFT (Teed & Deng, ECCV 2020) | Dense per-pixel optical flow via recurrent all-pairs correlation; the long-standing reliable baseline, widely wrapped (torchvision, ptlflow) | Per-pixel (u,v) flow field; same derived per-frame scalars as SEA-RAFT (mean magnitude, orientation entropy, camera/object decomposition) | video-frame | frame | yes-gpu | strong | alternative |
| MemFlow (Dong et al., CVPR 2024) | Multi-frame optical flow with a memory module that aggregates historical motion, giving temporally smoother flow and large-motion robustness; real-time | Per-pixel (u,v) flow field, temporally coherent across the clip; same derived scalars, with less frame-to-frame jitter useful for second-by-second time series | video-clip | frame (with temporal context) | yes-gpu | strong | include-if-resources |
| VideoFlow (Shi et al., ICCV 2023) | Multi-frame (tri-frame TROF + propagation) optical flow exploiting temporal cues; high accuracy on Sintel/KITTI | Bi-directional per-pixel (u,v) flow across multiple frames; derived per-frame motion magnitude/orientation series | video-clip | frame (multi-frame context) | yes-gpu | usable | drop |
| pymoten (Gallant Lab; Nunez-Elizalde et al. 2021) | Motion energy from a pyramid of spatiotemporal Gabor quadrature filters at multiple spatial/temporal frequencies, directions, positions, sizes — the canonical low-level motion model for fMRI encoding (Nishimoto & Gallant 2011) | Per-frame high-dimensional (hundreds-to-thousands-dim) vector of filter activation energies, one value per Gabor channel (spatial freq x temporal freq x direction x location). Directly usable as a voxelwise encoding-model regressor or reducible to summary motion-energy scalars | video-frame | frame (configurable; commonly downsampled to per-second or TR) | yes-cpu | best-in-class | include |
| TransNetV2 (Soucek & Lokoc 2020) | Shot/cut boundary detection including hard cuts and gradual transitions (dissolves, fades, wipes) via a deep dilated-3D-CNN; current open SOTA for shot boundaries | Per-frame cut probability (0-1); thresholded list of shot boundary frame indices and shot segments; handles gradual transitions, flashes, motion blur | video-clip | per-frame probability yielding per-shot segmentation | yes-gpu | best-in-class | include |
| PySceneDetect (v0.6+) | Shot/cut detection via content-aware HSV histogram change (ContentDetector) or adaptive/threshold detectors; lightweight CPU tool | List of cut frame/timecode boundaries and scene segments; per-frame content-change score time series (the raw HSV-delta signal is itself a usable visual-change/novelty feature) | video-frame | per-frame change score; per-shot segmentation | yes-cpu | strong | include |
| AutoShot (Zhu et al., CVPRW 2023) | Shot boundary detection trained on the SHOT/short-video dataset; reports SOTA on short-form/social video where TransNetV2 was trained on different distribution | Per-frame shot-boundary scores and shot segments | video-clip | per-frame / per-shot | yes-gpu | usable | alternative |
| OpenCV Farneback / DIS dense optical flow | Classical (non-deep) dense optical flow — Farneback polynomial expansion or DISOpticalFlow; CPU-only fallback when no GPU is available | Per-pixel (u,v) flow field; derived per-frame mean magnitude and orientation histograms | video-frame | frame | yes-cpu | usable | alternative |
Recommended (best-in-class): SEA-RAFT (dense optical flow; derive global camera-motion vs residual object-motion and mean flow magnitude); pymoten (neuroscience-standard motion-energy pyramid feature for direct fMRI encoding); TransNetV2 (shot/cut boundary detection incl. gradual transitions); PySceneDetect (fast CPU cut detection + continuous per-frame visual-change/novelty score)
Likely redundant:
- RAFT -> superseded by SEA-RAFT (better accuracy and ~2.3x faster); keep only as easy torchvision fallback
- VideoFlow -> superseded by MemFlow (faster, fewer params, higher accuracy for multi-frame flow)
- AutoShot -> overlaps TransNetV2; only preferable for short-form/social video stimuli
- OpenCV Farneback/DIS -> superseded by SEA-RAFT/RAFT in accuracy; keep only as no-GPU fallback
- GMFlow/FlowFormer family -> covered in spirit by SEA-RAFT as the chosen single dense-flow model; no need to run multiple deep flow nets
Open questions:
- Camera-vs-object motion separation is not a turnkey tool: it must be derived from a dense-flow model (e.g. robust homography/affine global-motion fit on the flow field, then residual = object motion). Decide whether to implement this or adopt a SLAM/egomotion package.
- Choose the temporal aggregation strategy: flow and motion-energy are per-frame, so define how to bin/downsample to per-second or per-TR (mean, RMS, or HRF-convolved) for the neuroimaging timeline.
- For long narrative films, decide between per-pair flow (SEA-RAFT) vs temporally-smoothed multi-frame flow (MemFlow) depending on whether jitter in the second-by-second series is a problem.
- Confirm SEA-RAFT vs MemFlow generalization on your actual stimulus domain (animation, archival film, low light) since flow models are trained mostly on Sintel/KITTI/Spring.
References
- SEA-RAFT (Princeton-VL, ECCV 2024) — github.com/princeton-vl/SEA-RAFT ; arXiv 2405.14793 ; Spring benchmark EPE 3.69
- RAFT (Teed & Deng, ECCV 2020) — github.com/princeton-vl/RAFT ; pytorch.org/vision optical_flow
- MemFlow (Dong et al., CVPR 2024) — github.com/dqiaole/MemFlow ; CVPR 2024 ; dqiaole.github.io/MemFlow
- VideoFlow (Shi et al., ICCV 2023) — arXiv 2303.08340 ; github.com/XiaoyuShi97/VideoFlow
- pymoten (Gallant Lab; Nunez-Elizalde et al. 2021) — github.com/gallantlab/pymoten ; Nishimoto & Gallant 2011 J Neurosci ; gallantlab.org/voxelwise_tutorials
- TransNetV2 (Soucek & Lokoc 2020) — github.com/soCzech/TransNetV2 ; arXiv 2008.04838
- PySceneDetect (v0.6+) — github.com/Breakthrough/PySceneDetect ; scenedetect.com
- AutoShot (Zhu et al., CVPRW 2023) — arXiv 2304.06116 ; github.com/wentaoyu/AutoShot
- OpenCV Farneback / DIS dense optical flow — docs.opencv.org optical flow tutorial; Kroeger et al. DIS ECCV 2016
Action and activity recognition (video understanding)
Computational action/activity recognition for a movie/audio-story timeline splits into three usable layers: (1) clip-level recognition backbones that emit Kinetics/Something-Something class posteriors and/or pooled spatiotemporal embeddings over short sliding windows (VideoMAE V2, InternVideo2, V-JEPA 2, TimeSformer, VideoSwin, SlowFast), best run at a fixed window stride (e.g. 16 frames / 1-2 s) to produce per-second features; (2) temporal action localization (TAL) heads (ActionFormer / OpenTAD, typically fed InternVideo2 or VideoMAE features) that turn those per-window embeddings into start-end action segments with confidence; and (3) video-LLMs (Qwen3-VL, VideoLLaMA3, InternVL3, Video-LLaVA) that produce open-vocabulary dense event descriptions with timestamps, valuable for naturalistic film where a fixed label set is too restrictive. For a small lab the most leverage comes from: VideoMAE V2 / InternVideo2 as frozen feature extractors on a sliding window, X-CLIP for zero-shot open-vocabulary action probabilities, and one video-LLM for dense captioning. The pure clip classifiers (TimeSformer/VideoSwin/SlowFast) are now largely superseded as standalone choices but remain valuable as the standard, well-documented MMAction2/PySlowFast backbones and as TAL feature sources.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| VideoMAE V2 (OpenGVLab, CVPR 2023; ViT-B/L/g, UnlabeledHybrid-1M pretrain, K400/K710 finetune) | Self-supervised masked-autoencoder video backbone; clip-level human action class and a strong pooled spatiotemporal embedding over a short window | Per-window (16 frames) 400-dim Kinetics-400 (or 710) action softmax posteriors AND/OR a frozen pooled embedding (768-dim ViT-B, 1024-dim ViT-L, 1408-dim ViT-g) used as the de facto feature for downstream TAL | video-clip | per-clip (sliding 16-frame window; emit per-second by striding) | yes-gpu | best-in-class | include |
| InternVideo2 (OpenGVLab, ECCV 2024; Stage2 1B & 6B, video-text contrastive) | Large multimodal video foundation model; SOTA clip-level action recognition, zero-shot/open-vocabulary action classification via video-text matching, and the strongest published TAL feature extractor | Per-window video embedding (aligned to a text embedding space) giving zero-shot similarity scores to an arbitrary action-label vocabulary; supervised K400/SSv2 class posteriors; pooled features (1B/6B) feeding ActionFormer-style TAL | multimodal | per-clip (sliding window; emit per-second by striding) | yes-heavy-gpu | best-in-class | include |
| V-JEPA 2 (Meta AI, June 2025; ViT-L/H/g, facebook/vjepa2-vitl-fpc64-256) | Self-supervised (non-generative, latent-prediction) video world model; motion-centric spatiotemporal representations strong on temporal/motion understanding and action anticipation | Frozen per-window spatiotemporal embedding (attentive-probe gives action class posteriors, e.g. SSv2/EK100); supports action-anticipation (recall@5) outputs; embeddings usable as motion-rich timeline features | video-clip | per-clip (16-/64-frame window; stride for per-second) | yes-gpu | strong | include-if-resources |
| X-CLIP (Microsoft, ECCV 2022; microsoft/xclip-base-patch16/32, zero-shot variant) | CLIP extended to video with cross-frame attention; open-vocabulary / zero-shot clip-level action classification via video-text similarity | Per-window similarity score (softmax) over an ARBITRARY user-supplied list of action phrases; or 512-dim joint video embedding for retrieval | multimodal | per-clip (8-frame window; stride for per-second) | yes-gpu | strong | include |
| ActionFormer + OpenTAD (ECCV 2022 / CVPR-W 2025; TAL on InternVideo2/VideoMAE features) | Temporal action localization: converts per-window backbone features into time-stamped action segments with class and confidence (single-shot, anchor-free, multiscale local attention) | List of (start_time, end_time, action_class, confidence) segments per video; densely covers the timeline, directly alignable to a movie clock | video-clip | per-shot / per-segment (boundaries at sub-second precision) | yes-gpu | best-in-class | include |
| Qwen3-VL (Alibaba, 2025; 4B/8B/30B/235B, video; predecessor Qwen2.5-VL) | Video-LLM for open-vocabulary dense event description and temporal grounding; describes what people are doing and localizes it in time via text-timestamp alignment | Free-text dense captions and event segmentations with explicit start/end timestamps (Text-Timestamp Alignment); answers ‘what action happens between t1-t2’; temporal grounding outputs (Charades-STA style) | multimodal | per-utterance / per-event (model-emitted timestamps, ~second precision) | yes-gpu | best-in-class | include |
| VideoLLaMA3 (DAMO/Alibaba, 2025; 2B/7B) | Unified image+video LLM specialized for temporal grounding and dense video captioning | Dense captions with start-end token timestamps; temporal grounding on ActivityNet/YouCook2/Charades-STA/Ego4D/COIN; open-vocabulary action/event descriptions per timestamped span | multimodal | per-event / per-utterance (start-end token timestamps) | yes-gpu | strong | alternative |
| InternVL3 (video) (OpenGVLab/Shanghai AI Lab, 2025; 1B-78B) | General multimodal LLM with competitive video understanding; open-vocabulary event/action description and video QA over sampled frames | Free-text descriptions of actions/activities for a queried window; video QA answers; can be prompted for timestamped event lists (weaker native timestamp grounding than Qwen3-VL) | multimodal | per-clip / per-event (prompt-dependent) | yes-gpu | strong | alternative |
| TimeSformer / VideoSwin / SlowFast (via MMAction2 + PySlowFast) | Classic supervised clip-level action classifiers (divided space-time attention / hierarchical 3D-window transformer / dual-pathway 3D-CNN); emit Kinetics/SSv2 action posteriors | Per-window action softmax posteriors (e.g. 400-dim K400, 174-dim Something-Something v2) and pooled clip embeddings; SlowFast features were the long-time standard TAL input before VideoMAE/InternVideo2 | video-clip | per-clip (8-32 frame window; stride for per-second) | yes-gpu | usable | include-if-resources |
Recommended (best-in-class): VideoMAE V2 (ViT-L/g) or InternVideo2-1B as the frozen sliding-window feature extractor + Kinetics classifier; X-CLIP for lightweight zero-shot open-vocabulary action probabilities over a custom film vocabulary; ActionFormer/OpenTAD over InternVideo2/VideoMAE features for timestamped action segments; Qwen3-VL (8B) as the primary video-LLM for open-vocabulary dense, timestamped event description
Likely redundant:
- TimeSformer / VideoSwin / SlowFast as standalone recognizers -> superseded by VideoMAE V2 and InternVideo2 (keep only for the convenient MMAction2/PySlowFast pipeline or as legacy TAL features)
- InternVL3 (video) and VideoLLaMA3 -> overlap with Qwen3-VL for dense captioning/temporal grounding; pick one video-LLM as primary (Qwen3-VL has the strongest native timestamp grounding)
- X-CLIP zero-shot vs InternVideo2 zero-shot -> same open-vocabulary role; InternVideo2 is more accurate but far heavier, X-CLIP is the cheap default
- V-JEPA 2 vs VideoMAE V2 -> both self-supervised backbones; V-JEPA 2 is the more motion/temporal-biased complement, not a strict duplicate
Open questions:
- Licensing: VideoMAE V2 and InternVideo2 weights are non-commercial/research only; confirm this is acceptable for the project’s use and publication plans.
- Window stride and temporal resolution: clip backbones natively output per-16-frame (~0.5-1 s) features; the target second-by-second cadence requires choosing stride/overlap and a pooling rule that needs to be standardized across features.
- Label taxonomy: Kinetics-400/700 and Something-Something classes are everyday/atomic actions and may map poorly onto narrative-film events; decide whether to rely on TAL with a custom taxonomy vs open-vocabulary X-CLIP/video-LLM outputs.
- Video-LLM timestamp reliability: Qwen3-VL/VideoLLaMA3 emit timestamps but their second-level accuracy on long naturalistic films is not well validated; may need calibration against TAL or manual spot-checks.
- Compute budget: InternVideo2-6B and Qwen3-VL-30B+ exceed a single-GPU small-lab setup; confirm whether 1B/8B-class models suffice for the required annotation quality.
References
- VideoMAE V2 (OpenGVLab, CVPR 2023; ViT-B/L/g, UnlabeledHybrid-1M pretrain, K400/K710 finetune) — arXiv:2303.16727; HF OpenGVLab/VideoMAEv2-giant; github.com/OpenGVLab/VideoMAEv2
- InternVideo2 (OpenGVLab, ECCV 2024; Stage2 1B & 6B, video-text contrastive) — arXiv:2403.15377; ECCV 2024; HF collection OpenGVLab/InternVideo2
- V-JEPA 2 (Meta AI, June 2025; ViT-L/H/g, facebook/vjepa2-vitl-fpc64-256) — arXiv:2506.09985; ai.meta.com/blog/v-jepa-2-world-model-benchmarks; HF facebook/vjepa2-vitl-fpc64-256
- X-CLIP (Microsoft, ECCV 2022; microsoft/xclip-base-patch16/32, zero-shot variant) — arXiv:2208.02816; HF docs/transformers model_doc/xclip
- ActionFormer + OpenTAD (ECCV 2022 / CVPR-W 2025; TAL on InternVideo2/VideoMAE features) — arXiv:2202.07925; OpenTAD arXiv:2502.20361; github.com/OpenGVLab/OpenTAD
- Qwen3-VL (Alibaba, 2025; 4B/8B/30B/235B, video; predecessor Qwen2.5-VL) — Qwen3-VL Technical Report arXiv:2511.21631; github.com/QwenLM/Qwen3-VL
- VideoLLaMA3 (DAMO/Alibaba, 2025; 2B/7B) — arXiv:2501.13106; HF DAMO-NLP-SG/VideoLLaMA3
- InternVL3 (video) (OpenGVLab/Shanghai AI Lab, 2025; 1B-78B) — arXiv:2504.10479; HF OpenGVLab/InternVL3
- TimeSformer / VideoSwin / SlowFast (via MMAction2 + PySlowFast) — TimeSformer arXiv:2102.05095; Video Swin arXiv:2106.13230; SlowFast arXiv:1812.03982; github.com/open-mmlab/mmaction2
Visual saliency, attention, aesthetics, depth
This subclass produces per-frame attention/complexity proxies: predicted human fixation/saliency maps (static and video), monocular depth maps, and scalar image-level scores for aesthetics, technical quality, and memorability. For a movie/audio-story timeline, the most timeline-relevant outputs are spatiotemporal video-saliency maps (one map per frame, summarizable to scalars like saliency concentration/entropy and number-of-salient-regions) and depth maps (mean/range/foreground-fraction per frame). The field is mature and overwhelmingly open-source: DeepGaze IIE/III for static fixation, ViNet/TASED-Net/UNISAL for video saliency, Depth-Anything-V2 (and Apple Depth Pro for metric) for depth, and CLIP/LMM-based scorers (Q-Align, LAION-Aesthetics, ResMem) for aesthetics/quality/memorability. All run on a single consumer GPU; most also run on CPU slowly. Recommend one video-saliency model + one depth model + one aesthetics/quality scorer + memorability as the core, with static-image saliency only as an alternative.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| DeepGaze IIE (Linardos et al., ICCV 2021) | Static per-image predicted human fixation density (free-viewing saliency), well-calibrated in/out-of-domain | Single-channel probabilistic saliency/fixation-density map per frame (log-density, normalizes to a spatial probability distribution); derive scalars: saliency entropy/dispersion, peak saliency, center-of-mass, top-k salient-region count | video-frame | frame (applied independently per frame) | yes-gpu | strong | alternative |
| TASED-Net (Min & Corso, ICCV 2019) | Video spatiotemporal saliency: predicted fixation map for the current frame given a clip, capturing motion-driven attention | Per-frame single-channel saliency map (0-1); scalars: saliency concentration/entropy, peak location & value, salient-area fraction, frame-to-frame saliency-shift (proxy for attention reorienting) | video-clip | frame (sliding window of ~32 frames -> one output map per frame) | yes-gpu | strong | alternative |
| ViNet / ViNet-S+ViNet-A ensemble (Jain et al., IROS 2021; updated) | Video (and audio-visual) saliency: predicted dynamic fixation maps, current SOTA on multiple DHF1K/Hollywood2/AViMoS-style benchmarks | Per-frame single-channel saliency map; scalars as for TASED-Net (concentration/entropy, peak, salient-area fraction, temporal saliency shift). Audio-visual variant (ViNet-A) integrates the soundtrack | video-clip | frame (clip-based, one map per frame) | yes-gpu | best-in-class | include |
| UNISAL (Droste et al., ECCV 2020) | Unified image+video saliency with one model; predicted fixation maps for both stills and clips | Per-frame saliency map (0-1) plus same derived scalars; handles static frames and video in one network | video-clip | frame (video) / per-image (stills) | yes-gpu | strong | alternative |
| Depth-Anything-V2 (Yang et al., NeurIPS 2024) | Monocular (relative, optionally metric) depth per pixel; foundation MDE model, robust across scenes | Dense per-pixel depth/inverse-depth map per frame; scalars: mean/median depth, depth range, foreground-fraction (near-camera pixels), depth entropy/layering, depth gradient (spatial complexity). Metric checkpoints give meters | video-frame | frame (per frame independently) | yes-gpu | best-in-class | include |
| Apple Depth Pro (Bochkovskii et al., 2024) | Sharp zero-shot metric (absolute, in meters) monocular depth with high-frequency boundary detail, no camera intrinsics needed | Per-pixel metric depth map (meters) per frame; same scalars as Depth-Anything plus true-scale foreground distance and absolute depth-range in meters | video-frame | frame | yes-gpu | strong | include-if-resources |
| Q-Align / OneAlign (Wu et al., ICML 2024) | Image (and video) technical quality (IQA) and aesthetic quality (IAA) via an LMM scoring against discrete text-defined levels; SOTA unified scorer | Per-frame scalar quality score and scalar aesthetic score (continuous, derived from rating-level probabilities); VQA variant gives per-clip quality | video-frame | frame (per frame) / per-clip (VQA mode) | yes-heavy-gpu | best-in-class | include |
| LAION-Aesthetics Predictor V2 (improved-aesthetic-predictor) | Image aesthetic appeal as a single scalar (the score used to filter LAION/Stable-Diffusion training data) | Per-frame scalar aesthetic score (~1-10); cheap aesthetics proxy over a timeline | video-frame | frame | yes-cpu | usable | alternative |
| CLIP-IQA / QualiCLIP | No-reference technical image quality (and perceptual attributes) via CLIP text-image alignment, opinion-unaware | Per-frame scalar quality score (0-1); antonym-prompt variants also yield attribute scores (sharp/blurry, bright/dark, natural) | video-frame | frame | yes-gpu | usable | alternative |
| ResMem (Needell & Bainbridge, 2022) | Intrinsic image memorability: predicted probability an image will be remembered | Per-frame scalar memorability score (0-1, LaMem-calibrated) | video-frame | frame | yes-cpu | strong | include |
| IQA-PyTorch (toolkit, chaofengc) | Not a single model: a unified library exposing 40+ no-reference IQA/IAA metrics (CLIP-IQA, MUSIQ, NIMA, DBCNN, LAION-AES, MANIQA, etc.) under one API | Per-frame scalar quality/aesthetic scores from whichever metric is selected; convenient for emitting several quality scalars at once | video-frame | frame | yes-gpu | strong | include-if-resources |
Recommended (best-in-class): ViNet (or ViNet-A for audio-visual) for per-frame video saliency maps -> saliency concentration/entropy, peak, salient-area, temporal shift; Depth-Anything-V2 for per-frame relative depth (add Apple Depth Pro only if metric/meters depth is needed) -> mean/range depth, foreground-fraction, depth complexity; Q-Align/OneAlign for combined per-frame aesthetic + technical-quality scalars (subsample to ~1 fps), with IQA-PyTorch (MUSIQ/NIMA/LAION-AES) as a lightweight fallback; ResMem for per-frame image-memorability scalar
Likely redundant:
- MiDaS / DPT / ZoeDepth — superseded by Depth-Anything-V2 (relative) and Apple Depth Pro (metric)
- TASED-Net and UNISAL — same video-saliency output as ViNet; pick one (ViNet best accuracy, UNISAL lightest)
- DeepGaze IIE/III (static fixation) — for video timelines superseded by motion-aware video-saliency models; keep only as a pure static-image attention proxy
- LAION-Aesthetics V2 and CLIP-IQA/QualiCLIP — subsumed by Q-Align’s IAA/IQA branches (use them only as lightweight substitutes when the 7B LMM is too heavy)
- MemNet / ResMem-Net — older or less-standardized memorability predictors superseded by the maintained, pip-installable ResMem
References
- DeepGaze IIE (Linardos et al., ICCV 2021) — arXiv:2105.12441; github.com/matthias-k/DeepGaze
- TASED-Net (Min & Corso, ICCV 2019) — arXiv:1908.05786; github.com/MichiganCOG/TASED-Net
- ViNet / ViNet-S+ViNet-A ensemble (Jain et al., IROS 2021; updated) — arXiv:2012.06170; github.com/samyak0210/ViNet; AIM 2024 Video Saliency Challenge (ECCV-W)
- UNISAL (Droste et al., ECCV 2020) — arXiv:2003.05477; github.com/rdroste/unisal
- Depth-Anything-V2 (Yang et al., NeurIPS 2024) — arXiv:2406.09414; github.com/DepthAnything/Depth-Anything-V2
- Apple Depth Pro (Bochkovskii et al., 2024) — arXiv:2410.02073; github.com/apple/ml-depth-pro
- Q-Align / OneAlign (Wu et al., ICML 2024) — arXiv:2312.17090; github.com/Q-Future/Q-Align; q-align.github.io
- LAION-Aesthetics Predictor V2 (improved-aesthetic-predictor) — github.com/christophschuhmann/improved-aesthetic-predictor; LAION-5B
- CLIP-IQA / QualiCLIP — CLIP-IQA arXiv:2207.12396; QualiCLIP github.com/miccunifi/QualiCLIP; IQA-PyTorch
- ResMem (Needell & Bainbridge, 2022) — Needell & Bainbridge 2022; github.com/Brain-Bridge-Lab/resmem; LaMem dataset
- IQA-PyTorch (toolkit, chaofengc) — github.com/chaofengc/IQA-PyTorch (pyiqa)
Audio features
Low-level acoustic features
Per-frame low-level acoustic descriptors (loudness/RMS, F0/pitch, spectral centroid/rolloff/flux, MFCCs, chroma, onsets, tempo/beat, harmonicity, roughness, voice-quality jitter/shimmer/HNR) are produced by a mature, mostly open-source ecosystem. The core trio is librosa (most flexible general MIR/DSP in pure Python), openSMILE (the standardized affective-computing feature sets eGeMAPS/ComParE used heavily in speech/emotion neuroscience), and Praat via Parselmouth (the reference for voice-quality and F0). Essentia adds a fast C++ engine with a richer set of music/psychoacoustic descriptors (dissonance/roughness, beat tracking) and torchaudio provides GPU-batched, differentiable versions of standard transforms. For pitch specifically, neural CREPE and probabilistic pYIN exceed classic autocorrelation/cepstral trackers in accuracy. All are CPU-friendly and trivially deployable in a small lab; only CREPE benefits from (but does not require) a GPU. For a movie/audio-timeline pipeline, a small overlapping set (librosa + openSMILE eGeMAPS + Parselmouth, optionally Essentia for music-rich roughness/beat and CREPE for high-accuracy F0) covers the entire subclass with minimal redundancy.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| librosa (0.10.x / 1.0, 2024) | General-purpose DSP/MIR feature extraction: spectral shape, energy, pitch, harmony, rhythm. The default Swiss-army-knife for time-aligned low-level audio descriptors. | Per-frame (hop-based) numeric vectors: RMS energy; spectral_centroid, spectral_bandwidth, spectral_rolloff, spectral_flatness, spectral_contrast (7-band), zero_crossing_rate; MFCC (n_mfcc, default 20) + delta/delta2; mel-spectrogram; chroma_stft/chroma_cqt/chroma_cens (12-dim); tonnetz (6-dim); onset_strength envelope and onset event times; tempo (global + dynamic) and beat frame times via beat_track; F0 contour + voiced flags + voiced probabilities via pyin (probabilistic YIN); harmonic/percussive separation. | audio | frame (configurable hop, e.g. 512 samples ~= 23 ms at 22.05 kHz; resampleable to per-second) plus per-clip globals (tempo) | yes-cpu | best-in-class | include |
| openSMILE 3.0 + opensmile-python (audEERING, 2023-2024) | Standardized, paper-citable acoustic feature sets for speech/affective computing (eGeMAPS, GeMAPS, ComParE 2016, emobase). The lingua franca for emotion/voice modeling. | Two levels. Low-Level Descriptors (LLDs) per frame (~10 ms): F0 (semitone/Hz), loudness, jitter (local), shimmer (local dB), HNR/log-HNR, spectral flux, alpha-ratio, Hammarberg index, spectral slope (0-500/500-1500 Hz), MFCC 1-4, formant 1-3 frequencies/bandwidths/amplitudes, harmonic ratios (H1-H2, H1-A3). Functionals: eGeMAPSv02 = 88 summary features over a window; ComParE_2016 = 6373 features (65 LLD + deltas x statistical functionals). Configurable to emit per-frame LLD CSV aligned to timeline. | audio | frame (~10 ms LLDs) for time-resolved use; per-segment/per-window for functionals | yes-cpu | best-in-class | include |
| Parselmouth (Praat 6.x in Python, 2023-2024) | Reference-grade voice-quality and phonetic acoustic analysis — the gold standard for F0, formants, and perturbation measures in speech science. | Per-frame time series: F0/pitch contour (Praat autocorrelation/cc, with voiced/unvoiced), intensity (dB) contour, formant tracks F1-F4 (frequency + bandwidth) via Burg. Per-segment voice-quality: jitter (local, localabsolute, rap, ppq5, ddp), shimmer (local, localdB, apq3, apq5, apq11, dda), HNR, glottal pulses, voice breaks, fraction unvoiced. Spectral moments (centroid, sd, skew, kurtosis) and cepstral peak prominence available via Praat objects. | audio | frame (pitch/intensity/formant, ~6.25-10 ms step) for contours; per-utterance/per-segment for jitter/shimmer/HNR | yes-cpu | best-in-class | include |
| Essentia 2.1 (MTG-UPF, 2023-2024) | Fast C++ MIR/audio-analysis engine with the broadest low-level descriptor set, including psychoacoustic descriptors (roughness/dissonance) and robust rhythm analysis not in librosa. | Per-frame: spectral centroid, rolloff, flux, spread, skewness, kurtosis, flatness, decrease, energy bands, MFCC, GFCC, HPCP/chroma, spectral contrast, spectral complexity, HFC, dissonance (sensory roughness), inharmonicity, pitch (PitchYin, PitchYinFFT, PitchMelodia, PredominantPitchMelodia + confidence), tristimulus, odd-to-even harmonic ratio, dynamic complexity, loudness/replay-gain. Rhythm: onset detection, BPM/tempo, beat positions (multifeature/degara/loudness beat tracking), beat loudness, danceability. | audio | frame (configurable) for descriptors; per-event for onsets/beats; per-clip for tempo | yes-cpu | strong | include-if-resources |
| torchaudio 2.x (PyTorch, 2024-2025) | GPU-accelerated, batched, differentiable implementations of standard spectral/cepstral transforms for ML pipelines (not a high-level MIR toolkit). | Per-frame tensors: Spectrogram/MelSpectrogram, MFCC (librosa-consistent), LFCC, spectral_centroid, amplitude/power, deltas (compute_deltas); pitch via detect_pitch_frequency (autocorrelation F0). Resampling, time/frequency masking. No native chroma/beat/roughness/jitter. | audio | frame (configurable hop) | yes-gpu | usable | alternative |
| CREPE (marl/crepe, ICASSP 2018; maintained) | State-of-the-art monophonic fundamental-frequency (F0) estimation via a deep CNN on the raw waveform; higher accuracy and noise-robustness than DSP trackers. | Per-frame F0 in Hz, voicing confidence (0-1), and a 360-bin pitch salience activation matrix. Default 10 ms hop; outputs a time/frequency/confidence CSV directly alignable to a timeline. | audio | frame (default 10 ms; configurable) | yes-gpu | strong | include-if-resources |
| MIRtoolbox / mosqito (psychoacoustic roughness & loudness) | Psychoacoustic descriptors aligned to perception — Zwicker loudness, sharpness, and sensory roughness — that general MIR libraries do not compute by standard. | Per-frame/time-varying: loudness in sones (Zwicker/ISO 532-1), specific loudness per Bark band, sharpness (acum), roughness (asper), fluctuation strength, tonality. (mosqito = maintained Python; MIRtoolbox = MATLAB equivalent for roughness/loudness/centroid/F0.) | audio | frame / per-second time-varying | yes-cpu | usable | include-if-resources |
Recommended (best-in-class): librosa (primary general spectral/MFCC/chroma/onset/tempo + pyin F0); openSMILE eGeMAPS/ComParE via opensmile-python (standardized speech/affect LLDs, timeline-indexed); Parselmouth/Praat (gold-standard F0, formants, jitter/shimmer/HNR voice quality); Essentia (add for music-rich roughness/dissonance, inharmonicity, robust beat tracking); CREPE/torchcrepe (add for high-accuracy monophonic F0 on speech/vocals)
Likely redundant:
- torchaudio — spectral/MFCC fully superseded by librosa for a small lab; keep only if GPU-batched or differentiable front-ends are needed
- torchaudio.detect_pitch_frequency and librosa.pyin F0 — superseded by CREPE/torchcrepe (accuracy) or Praat (voice science) for the F0 feature
- openSMILE jitter/shimmer/HNR/formant LLDs overlap Parselmouth — run both only for cross-validation; otherwise Parselmouth is canonical and openSMILE provides the standardized 88-dim summary
- Essentia spectral/MFCC/chroma/onset overlap librosa — choose one as the primary MIR engine (librosa for ease, Essentia for speed/roughness), not both for the same descriptors
- Essentia ‘dissonance’ vs mosqito roughness — overlapping roughness measures; mosqito is the standards-based (asper) choice if perceptual fidelity matters
Open questions:
- Will the pipeline analyze the full mixed soundtrack (speech+music+SFX) or source-separated stems? Monophonic F0 tools (CREPE, Praat) and voice-quality measures degrade badly on mixes — may need Demucs/source separation upstream.
- Target output rate: true per-frame (~10-23 ms) vs aggregated per-second for fMRI/timeline alignment? This sets hop sizes and whether to keep LLDs or functionals.
- Is openSMILE’s source-available (non-OSI) license and Essentia’s AGPL acceptable for the project’s distribution/sharing model?
- Are standardized perceptual units (sones/asper for loudness/roughness via mosqito) required, or are relative librosa/Essentia descriptors sufficient?
- Sample rate harmonization: openSMILE/Praat expect specific rates; movie audio is often 48 kHz — confirm resampling strategy to keep features comparable across tools.
References
- librosa (0.10.x / 1.0, 2024) — github.com/librosa/librosa; librosa.org; McFee et al. 2015 SciPy
- openSMILE 3.0 + opensmile-python (audEERING, 2023-2024) — github.com/audeering/opensmile-python; Eyben et al. 2016 (eGeMAPS, IEEE TAC); audeering.github.io/opensmile-python
- Parselmouth (Praat 6.x in Python, 2023-2024) — github.com/YannickJadoul/Parselmouth; Jadoul et al. 2018 J. Phonetics; parselmouth.readthedocs.io
- Essentia 2.1 (MTG-UPF, 2023-2024) — github.com/MTG/essentia; essentia.upf.edu; Bogdanov et al. 2013 ISMIR
- torchaudio 2.x (PyTorch, 2024-2025) — docs.pytorch.org/audio; Yang et al. 2021 (TorchAudio) arXiv:2110.15018
- CREPE (marl/crepe, ICASSP 2018; maintained) — github.com/marl/crepe; github.com/maxrmorrison/torchcrepe; Kim et al. 2018 arXiv:1802.06182
- MIRtoolbox / mosqito (psychoacoustic roughness & loudness) — github.com/Eomys/MoSQITo; MIRtoolbox (Lartillot & Toiviainen 2007 ISMIR)
High-level audio: events, scenes, music, speech/non-speech
This subclass covers time-resolved semantic audio annotation: (1) audio tagging / sound event detection over the 527-class AudioSet ontology, (2) acoustic scene classification, (3) speech vs music vs noise segmentation, and (4) music information retrieval (tempo, key/mode, beat/downbeat, instrumentation, mood/emotion). The field is mature and largely open-source. For a movie/audio-story timeline, the recommended stack is a transformer audio tagger (BEATs or PaSST/AST) run on a sliding window for AudioSet event posteriors, CLAP for open-vocabulary/zero-shot event labels and audio-text similarity, inaSpeechSegmenter or pyannote VAD for speech/music/noise segmentation, and Essentia + madmom (or Beat This!) for MIR descriptors. Most tools run comfortably on a single consumer GPU or even CPU; nothing here requires heavy multi-GPU compute. Key design choice: window size sets temporal granularity (AudioSet models are trained on ~10 s clips but can be run on ~1 s hops for per-second annotation, with the caveat that very short windows degrade accuracy on slow-onset events).
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| BEATs (Microsoft, iter3+ / AS2M checkpoint, 2022-2023) | Audio tagging over the 527-class AudioSet ontology via a self-supervised acoustic-tokenizer ViT encoder fine-tuned on AudioSet-2M; also a strong general-purpose audio embedding backbone. | 527-dim AudioSet class probability vector per analysis window (e.g. speech, music, vehicle, dog, laughter, gunshot, applause, etc.); 768-dim frame/patch embeddings usable for downstream probing or as features. | audio | per-clip on ~10 s windows; sliding-window to per-second or sub-second (degrades for short windows) | yes-gpu | best-in-class | include |
| PaSST (Patchout faSt Spectrogram Transformer, kkoutini, 2022) | AudioSet audio tagging and acoustic scene classification using a DeiT-style spectrogram transformer with patchout regularization; designed to train/infer efficiently on a single GPU. | 527-dim AudioSet class logits/probabilities per window; intermediate transformer embeddings (used widely as audio features, e.g. in CLAP-like and captioning systems). | audio | per-clip on ~10 s windows; sliding-window to per-second | yes-gpu | strong | include |
| AST (Audio Spectrogram Transformer, MIT, 2021) | AudioSet audio tagging with a pure ViT over log-mel spectrograms; the canonical HF-native transformer tagger. | 527-dim AudioSet class probabilities per ~10 s window; CLS/patch embeddings. | audio | per-clip on ~10 s windows; sliding-window to per-second | yes-gpu | strong | alternative |
| PANNs (CNN14, Kong et al., 2020) | CNN-based AudioSet audio tagging and sound event detection; long-standing reference baseline with framewise SED variants. | 527-dim AudioSet clipwise probabilities; CNN14 also exposes framewise (segment-level) predictions enabling onset/offset localization; 2048-dim embedding. | audio | clipwise plus framewise SED (~tens of ms frames) | yes-gpu | usable | alternative |
| YAMNet (Google, MobileNet-v1) | Lightweight AudioSet audio event tagging over a 521-class subset; classic fast/edge baseline. | 521-dim AudioSet-class scores per 0.96 s frame (hop 0.48 s); 1024-dim embedding per frame. | audio | per-frame (~0.48 s hop, 0.96 s window) | yes-cpu | usable | alternative |
| CLAP (LAION clap-htsat-fused / larger_clap_general / larger_clap_music_and_speech, 2022-2023) | Contrastive language-audio embedding enabling OPEN-VOCABULARY zero-shot audio classification and audio-text similarity beyond the fixed AudioSet ontology; also Microsoft CLAP variant. | 512-dim joint audio and text embeddings; per-window cosine similarity to arbitrary text prompts (e.g. ‘a crowd cheering’, ‘tense orchestral music’, ‘rain on a window’) yielding a custom per-prompt time series; softmax over a prompt set for zero-shot labels. | multimodal | per-clip on the windowed segment (sliding-window to per-second); music-domain variant for MIR-style tags | yes-gpu | best-in-class | include |
| inaSpeechSegmenter (INA, smn engine) | Segments a continuous audio stream into speech / music / noise homogeneous zones, with speech further split by speaker gender (male/female); purpose-built for broadcast media. | Time-stamped segment labels {speech, music, noise} with start/end; optional male/female tag on speech segments (singing voice -> music, speech-over-music/noise -> speech). | audio | per-segment with onset/offset boundaries (frame-level decisions ~0.1 s) | yes-cpu | best-in-class | include |
| pyannote.audio (segmentation 3.x / VAD pipeline) | Voice activity detection, speech segmentation, overlapped-speech detection and speaker diarization; the strongest open VAD/diarization stack for foreground speech. | Per-frame speech-activity probability (~16-20 ms resolution) and time-stamped speech/non-speech and per-speaker turn segments; overlap flags. | audio | per-frame (~16-20 ms) and per-segment | yes-gpu | strong | include-if-resources |
| Essentia + TensorFlow models (MTG-UPF) | Full classical + deep MIR: tempo, key/scale (mode), tonal features, plus pretrained TF models for music tagging, genre, mood/emotion (valence-arousal), danceability, instrumentation, voice/instrumental, and reusable embeddings (MusiCNN, VGGish, Discogs-EffNet, MSD). | Scalar/categorical descriptors: BPM/tempo, key+scale (major/minor) with strength, loudness; and per-tag probabilities for genre (MTG-Jamendo/Discogs taxonomies), mood/theme, valence and arousal regressors, danceability, acoustic/electronic, instrument tags; 200-1280-dim embeddings. | audio | whole-track or windowed (frame-level low-level features; segment/window for ML tags) | yes-cpu | best-in-class | include |
| madmom (beat/downbeat/tempo/chords) and ‘Beat This!’ (2024) | Rhythm/meter MIR: onset detection, beat and downbeat tracking, tempo estimation, meter, and chord recognition. ‘Beat This!’ is a current transformer-based beat/downbeat tracker. | Beat timestamps, downbeat timestamps (bar positions), per-beat tempo (BPM) curve, time signature/meter, and (madmom) chord labels with timing; yields a per-beat or per-second rhythmic feature track. | audio | per-beat / per-event timestamps (continuous tempo curve) | yes-cpu | strong | include-if-resources |
Recommended (best-in-class): BEATs (or PaSST) for AudioSet event/scene tagging on a sliding window; CLAP (laion/larger_clap_general + music_and_speech) for open-vocabulary zero-shot audio dimensions and audio-text similarity time series; inaSpeechSegmenter for clean speech/music/noise segmentation (add pyannote VAD/diarization if speaker turns needed); Essentia + TF models for MIR scalars and music mood/genre/instrument tags; madmom or Beat This! for beat/downbeat/tempo structure
Likely redundant:
- AST is largely superseded by BEATs/PaSST on AudioSet mAP (keep only for its trivial HF deployment)
- PANNs CNN14 superseded by transformer taggers, but retain for framewise SED and panns_inference simplicity
- YAMNet superseded on accuracy by all transformer taggers; keep only for CPU/real-time/edge constraints
- Choose ONE primary AudioSet tagger (BEATs or PaSST) rather than running AST+PANNs+YAMNet+BEATs together, since their 527-class outputs are highly correlated
- Essentia tempo estimator is redundant with madmom/Beat This! beat tracking (prefer the latter when precise beat/downbeat timing matters)
- CLAP music tags partially overlap Essentia genre/mood models; use CLAP for novel/custom prompts and Essentia for calibrated valence-arousal/tempo/key
References
- BEATs (Microsoft, iter3+ / AS2M checkpoint, 2022-2023) — arXiv:2212.09058; github.com/microsoft/unilm/tree/master/beats
- PaSST (Patchout faSt Spectrogram Transformer, kkoutini, 2022) — arXiv:2110.05069; github.com/kkoutini/PaSST
- AST (Audio Spectrogram Transformer, MIT, 2021) — arXiv:2104.01778; huggingface.co/MIT/ast-finetuned-audioset-10-10-0.4593
- PANNs (CNN14, Kong et al., 2020) — arXiv:1912.10211; github.com/qiuqiangkong/audioset_tagging_cnn
- YAMNet (Google, MobileNet-v1) — tfhub.dev/google/yamnet; github.com/tensorflow/models/tree/master/research/audioset/yamnet
- CLAP (LAION clap-htsat-fused / larger_clap_general / larger_clap_music_and_speech, 2022-2023) — arXiv:2211.06687; huggingface.co/laion/clap-htsat-fused; github.com/microsoft/CLAP
- inaSpeechSegmenter (INA, smn engine) — github.com/ina-foss/inaSpeechSegmenter; Doukhan et al. ICASSP 2018
- pyannote.audio (segmentation 3.x / VAD pipeline) — github.com/pyannote/pyannote-audio; Bredin et al. (Interspeech)
- Essentia + TensorFlow models (MTG-UPF) — essentia.upf.edu; mtg.github.io/essentia-labs; Alonso-Jimenez et al.
- madmom (beat/downbeat/tempo/chords) and ‘Beat This!’ (2024) — github.com/CPJKU/madmom; arXiv:1605.07008; github.com/CPJKU/beat_this (Foscarin et al. 2024)
Speech: ASR, diarization, prosody, vocal affect
This subclass converts the speech audio of movies/stories into a time-aligned linguistic and paralinguistic layer: word/segment-level transcripts with timestamps (ASR + forced alignment), who-spoke-when (diarization + VAD), low-level prosody (pitch/intensity/voice-quality contours), and emotion-from-voice (categorical and dimensional A/V/D). The open ecosystem is mature and almost entirely locally deployable on a single consumer GPU. A practical pipeline is faster-whisper/WhisperX for ASR+word timestamps, pyannote (community-1) for diarization, Parselmouth/openSMILE for prosody, and an audEERING wav2vec2-dim or emotion2vec model for vocal affect; most other tools are redundant variants of these. Key design choice is per-word vs per-second resampling, since ASR/SER are utterance-level while prosody is natively frame-level (10 ms).
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| faster-whisper (CTranslate2 Whisper large-v3 / distil-large-v3) | Automatic speech recognition: speech-to-text with segment- and word-level timestamps and language ID; backbone for most pipelines. | Per-segment text + start/end times; optional per-word start/end + token confidence/avg_logprob; no_speech_prob per segment; detected language posterior; 99 languages. Speaking-rate derivable (words or syllables per second from word timestamps). | audio | per-utterance/segment, optional per-word | yes-gpu | best-in-class | include |
| WhisperX (faster-whisper + wav2vec2 forced alignment + pyannote) | End-to-end pipeline: ASR with accurate word-level timestamps via phoneme forced alignment, plus optional speaker assignment per word. The most direct path to a word-aligned, speaker-attributed transcript. | Per-word text + tightened start/end times (wav2vec2 CTC alignment, far better than Whisper’s internal DTW timestamps); per-segment text; speaker label per segment/word (when diarization enabled); VAD-based chunking removes Whisper hallucination on silence. Enables precise speaking-rate and per-second word-presence binning. | audio | per-word, per-segment | yes-gpu | best-in-class | include |
| pyannote.audio speaker-diarization-community-1 (pyannote 4.x) | Speaker diarization (who spoke when) plus the underlying VAD and overlapped-speech detection; assigns speaker identities across the whole timeline. | Time-stamped speaker-turn segments with speaker labels (SPEAKER_00..N); per-frame speech-activity and overlapped-speech probabilities; estimated number of speakers; speaker embeddings for cross-segment/cross-file identity. Yields per-second active-speaker and speaker-change features. | audio | per-segment / per-frame (segmentation at ~16-60 ms) | yes-gpu | best-in-class | include |
| Silero VAD v5 | Standalone voice activity detection: frame-level probability of speech vs non-speech (music/noise/silence), for gating and speech-presence timelines. | Per-chunk (~30 ms) speech probability 0-1; derived speech/non-speech segment boundaries; total speech time. Useful as a lightweight per-second ‘speech present’ regressor and to mask non-speech before ASR/SER. | audio | sub-second (~30 ms frames) | yes-cpu | strong | include |
| Parselmouth (Praat) / praat-parselmouth | Classic low-level prosody and voice-quality acoustics computed directly from the waveform: pitch, loudness, and perturbation measures. The reference for interpretable prosodic contours. | Per-frame F0/pitch contour (Hz, with voiced/unvoiced flag), intensity/loudness (dB), formants F1-F4, harmonics-to-noise ratio (HNR); jitter (local/rap/ppq5) and shimmer (local/apq); spectral tilt; pause/silence structure. Speaking/articulation rate via syllable-nuclei scripts. All resamplable to per-second. | audio | frame (default ~10 ms hop, configurable) | yes-cpu | best-in-class | include |
| openSMILE (eGeMAPSv02 / ComParE feature sets) | Standardized affective-computing acoustic feature extraction: large banks of low-level descriptors (LLDs) and functionals used as inputs to emotion/paralinguistic models and as direct features. | Per-frame LLDs: F0, loudness, jitter, shimmer, HNR, MFCC 1-13, spectral flux/slope/centroid, alpha-ratio, Hammarberg index, formant freq/bandwidth/amplitude (eGeMAPS = 25 LLDs). Also utterance-level functionals (means, percentiles, slopes). Frame LLDs give per-second prosody/spectral vectors. | audio | frame (10 ms LLDs) or per-utterance functionals | yes-cpu | strong | include-if-resources |
| audEERING wav2vec2-large-robust-12-ft-emotion-msp-dim (dimensional SER) | Dimensional speech emotion recognition from voice: continuous arousal, dominance, valence regressed directly from waveform (no transcript needed). | Three scalars per utterance/window: arousal, dominance, valence each ~0-1; plus 1024-dim pooled wav2vec2 embedding. Sliding-window application (e.g. 1-4 s hop) yields per-second A/D/V trajectories aligned to the movie timeline. | audio | per-utterance or per-window (user-chosen, can approach per-second) | yes-gpu | best-in-class | include |
| emotion2vec+ (seed / base / large) and emotion2vec foundation model | Self-supervised speech-emotion foundation model: universal emotion representation plus categorical SER, robust across 10+ languages and recording conditions. | 9-class categorical emotion posteriors (angry, happy, sad, neutral, fear, disgust, surprise, etc.) per utterance; frame-level and utterance-level emotion embeddings (feature extraction mode) usable for custom regressors (e.g. to dimensional A/V). Sliding window gives per-second categorical-emotion probability trajectories. | audio | per-utterance (frame-level embeddings available) | yes-gpu | strong | alternative |
| NVIDIA NeMo ASR (Parakeet-TDT / Canary) + Sortformer diarization | Alternative high-accuracy ASR (with timestamps) and end-to-end neural diarization; relevant if Whisper accuracy or throughput is insufficient, or for joint ASR+diarization. | ASR: word/segment text + timestamps, token confidences (Parakeet-TDT-1.1B/0.6B leads English Open ASR leaderboard; Canary multilingual). Sortformer/MSDD: per-frame speaker-activity probabilities and speaker-attributed segments. Same downstream features as Whisper+pyannote (word timing, speaker turns, speaking rate). | audio | per-word / per-segment (ASR); per-frame (diarization) | yes-gpu | strong | alternative |
| CREPE / torchcrepe (deep pitch tracker) | Data-driven monophonic fundamental-frequency (F0) estimation; a more robust alternative to Praat autocorrelation pitch under noise/expressive speech. | Per-frame F0 (Hz) and voicing confidence at configurable hop (e.g. 10 ms); cleaner pitch contours than Praat on noisy/creaky speech. Feeds per-second pitch mean/range/slope features. | audio | frame (10 ms typical) | yes-gpu | usable | include-if-resources |
Recommended (best-in-class): faster-whisper (large-v3) for ASR; WhisperX for word-level forced alignment + speaker-attributed transcript; pyannote speaker-diarization-community-1 for diarization/VAD/overlap; Parselmouth (Praat) for prosody and voice-quality contours; audEERING wav2vec2-large-robust-12-ft-emotion-msp-dim for dimensional vocal affect (arousal/valence/dominance)
Likely redundant:
- openai/whisper, insanely-fast-whisper, whisper.cpp -> superseded by faster-whisper (same weights, faster CTranslate2 runtime)
- pyannote speaker-diarization-3.1 -> superseded by community-1 (better speaker counting, same segmentation)
- WebRTC VAD / audiotok -> superseded by Silero VAD and pyannote VAD
- librosa pitch (pyin/piptrack) and standalone MFCC extraction -> superseded by Parselmouth (F0/voice-quality) + openSMILE (spectral LLDs)
- Generic HF wav2vec2/HuBERT IEMOCAP categorical SER checkpoints -> superseded by emotion2vec+ (categorical) and audEERING wav2vec2-dim (dimensional)
- NeMo ASR/diarization and emotion2vec+ -> overlap with the Whisper/pyannote/audEERING defaults; keep only as accuracy or multilingual/cross-domain alternatives
Open questions:
- Target temporal grid: per-word (event-locked) vs fixed per-second resampling - ASR/SER are utterance-level and need windowing/interpolation, whereas Parselmouth/openSMILE are natively 10 ms.
- Movie audio is mixed with music/SFX/overlapping speakers; do we need source separation (e.g. Demucs/Bandit) or a music-VAD gate before ASR/SER to avoid contaminating pitch/jitter/shimmer and emotion estimates?
- Vocal affect should be computed per diarized speaker segment, not on mixed audio - confirm diarization-first ordering and how to handle overlapped speech for SER/prosody.
- Licensing: openSMILE eGeMAPS and audEERING wav2vec2-dim are research-only; confirm acceptable for the study or substitute emotion2vec+/Parselmouth-only features.
- Whether to add a paralinguistics/laughter-cry/non-verbal-vocalization detector (e.g. emotion2vec vocal events or a dedicated laughter model) as a separate feature stream.
References
- faster-whisper (CTranslate2 Whisper large-v3 / distil-large-v3) — github.com/SYSTRAN/faster-whisper; huggingface.co/openai/whisper-large-v3
- WhisperX (faster-whisper + wav2vec2 forced alignment + pyannote) — github.com/m-bain/whisperX; Bain et al. Interspeech 2023
- pyannote.audio speaker-diarization-community-1 (pyannote 4.x) — huggingface.co/pyannote/speaker-diarization-community-1; pyannote.ai/blog/community-1
- Silero VAD v5 — github.com/snakers4/silero-vad
- Parselmouth (Praat) / praat-parselmouth — github.com/YannickJadoul/Parselmouth; Praat (Boersma & Weenink)
- openSMILE (eGeMAPSv02 / ComParE feature sets) — github.com/audeering/opensmile; Eyben et al. eGeMAPS 2016
- audEERING wav2vec2-large-robust-12-ft-emotion-msp-dim (dimensional SER) — huggingface.co/audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim; Wagner et al. IEEE TPAMI 2023
- emotion2vec+ (seed / base / large) and emotion2vec foundation model — github.com/ddlBoJack/emotion2vec; Ma et al. ACL Findings 2024; arXiv:2312.15185
- NVIDIA NeMo ASR (Parakeet-TDT / Canary) + Sortformer diarization — github.com/NVIDIA/NeMo; huggingface.co/nvidia/parakeet-tdt-1.1b; HF Open ASR Leaderboard
- CREPE / torchcrepe (deep pitch tracker) — Kim et al. ICASSP 2018; github.com/maxrmorrison/torchcrepe
Language features
Low-level lexical & word-level features
Per-word lexical/psycholinguistic annotation is a mature, mostly CPU-friendly pipeline problem rather than a deep-learning frontier. The backbone is a fast tokenizer/POS/NER/dependency engine (spaCy) that segments the transcript and tags each word, followed by table lookups against well-established norm databases (SUBTLEX/wordfreq for frequency; Brysbaert for concreteness; Kuperman for AoA; Warriner/NRC-VAD for valence/arousal/dominance; NRC-EmoLex/LIWC for affect categories). The one genuinely model-dependent feature is word-level surprisal/entropy, computed from an incremental LLM (GPT-2 through larger autoregressive models) via the minicons or surprisal packages. For time-alignment, all of these are per-token features that inherit timestamps from forced alignment of the transcript to the audio (e.g. WhisperX/Montreal Forced Aligner) — that alignment step is upstream and out of scope here but is the mechanism that turns per-word features into a per-second movie timeline. The main design decisions for a small lab are: (1) which norm databases to license/merge, (2) how to handle out-of-vocabulary words and lemmatization for lookups, and (3) which LLM to use for surprisal given the size/fit tradeoff.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| spaCy (3.7+, en_core_web_trf / en_core_web_lg) | Tokenization, lemmatization, part-of-speech (coarse UPOS + fine PTB tags), morphological features, dependency parse, named entities, and the anchor for all downstream per-word lookups | Per token: lemma, UPOS tag (one of ~17), fine-grained POS tag, dependency relation+head, is_stop/is_punct flags, NER label (PERSON/ORG/GPE/DATE/etc.) with BILUO span; person/pronoun reference derivable from PronType + PERSON entities | transcript | per-word (per token) | yes-cpu | best-in-class | include |
| wordfreq (Speer 2022, Python) / SUBTLEX-US norms | Word frequency / lexical familiarity, the single most predictive low-level lexical variable for reading time and neural response | Per word: Zipf frequency (log10 per-billion scale, ~1-7), raw frequency, derivable log-frequency; SUBTLEX-US adds Lg10WF, contextual diversity (Lg10CD), %known | transcript | per-word | yes-cpu | best-in-class | include |
| Brysbaert Concreteness Norms (40,000 words) | Concreteness (and serves as proxy for imageability) of each word’s referent | Per word: concreteness mean (1=abstract to 5=concrete), SD, percent-known; covers 37,058 English lemmas + 2,896 two-word expressions | transcript | per-word | yes-cpu | best-in-class | include |
| Kuperman Age-of-Acquisition Norms (30,000 words) | Estimated age at which a word is typically learned (developmental/lexical accessibility) | Per word: AoA rating in years (mean), SD, percent-known; ~30,121 English words | transcript | per-word | yes-cpu | best-in-class | include |
| Warriner VAD Norms (13,915 lemmas) + NRC-VAD lexicon (20,000 words) | Continuous affective dimensions of word meaning: valence (pleasantness), arousal (intensity), dominance (control) | Per word: valence, arousal, dominance each on ~1-9 scale (Warriner, with SDs) or 0-1 (NRC-VAD); supersedes the small 1,034-word ANEW set | transcript | per-word | yes-cpu | best-in-class | include |
| NRC Emotion Lexicon (EmoLex, ~14,000 words) | Discrete (Plutchik) emotion category membership per word — categorical affect beyond the VAD continuum | Per word: 8 binary emotion associations (anger, anticipation, disgust, fear, joy, sadness, surprise, trust) + positive/negative sentiment flags; can be summed into per-second emotion-density signals | transcript | per-word | yes-cpu | strong | include |
| LIWC-22 | Dictionary-based psychological and linguistic category membership — pronouns/person reference, affect, cognitive/social process words, function-word style | Per word (and aggregable per window): membership in up to 118 categories incl. 1st/2nd/3rd-person pronouns, positive/negative emotion, cognitive-process, social, certainty/tentativeness; summary scores (Analytic, Clout, Authentic, Tone) at the segment level | transcript | per-word (category hits) / per-segment (summary scores) | yes-cpu | strong | alternative |
| minicons (IncrementalLMScorer) — GPT-2 / Pythia / GPT-Neo surprisal | Word-level surprisal and entropy: how unexpected each word is given its left context, the core information-theoretic predictor of reading time and N400/neural response | Per token: surprisal = -log2 P(word | left context) in bits; next-word entropy; per-word log-probability; aggregates subword pieces to whole-word surprisal | transcript | per-word | yes-gpu | best-in-class | include |
| surprisal (aalok-sathe, Python) | Same as minicons — word/token surprisal — via a unified interface across neural, n-gram (KenLM), and black-box API models | Per token/word: surprisal in bits, log-probabilities; convenient .word_level aggregation and built-in plotting; can target OpenAI/Petals/HF backends | transcript | per-word | yes-gpu | strong | alternative |
| GLiNER (urchade/gliner, 2024) | Zero-shot / open-type named entity recognition when you need entity types beyond spaCy’s fixed ontology (e.g. character names, fictional places, custom referents in narratives) | Per span: entity label (user-defined types) + confidence score; maps to per-word entity flags (is_named_entity, entity_type, person-reference) | transcript | per-word (span-derived) | yes-gpu | strong | include-if-resources |
| NLTK | Tokenization, POS tagging, stemming, basic lexical utilities (legacy) | Per token: Penn-Treebank POS tag, stem/lemma (WordNet), basic chunk tags | transcript | per-word | yes-cpu | legacy/superseded | drop |
Recommended (best-in-class): spaCy (en_core_web_trf/lg) as the tokenizer/POS/NER/dependency backbone; wordfreq + SUBTLEX-US for frequency; Brysbaert concreteness + Kuperman AoA + Warriner/NRC-VAD norm joins for psycholinguistic per-word features; NRC EmoLex for discrete emotion channels; minicons (or surprisal) with GPT-2-medium/Pythia for word surprisal & entropy
Likely redundant:
- NLTK — superseded by spaCy for tokenization/POS/NER (keep only for WordNet synset access)
- surprisal package — direct overlap with minicons; pick one surprisal library
- ANEW (1,034 words) — superseded by Warriner VAD (13,915) and NRC-VAD (20,000)
- MRC Psycholinguistic Database — legacy imageability/concreteness, superseded by Brysbaert and Glasgow norms
- LIWC-22 affect categories — most per-word affect content is reproducible with free NRC EmoLex + Warriner VAD + spaCy POS; LIWC is justified mainly for its validated pronoun/person-reference and function-word category scheme
- Glasgow Norms — overlaps Brysbaert/Kuperman/Warriner on the same dimensions at smaller coverage; useful only as a single-source merge or for its imageability/familiarity/semantic-size/gender scales
- VADER — sentence/social-media-tuned sentiment, redundant with Warriner+NRC for per-word valence
Open questions:
- Time-alignment is upstream: per-word features are only as good as the forced alignment (WhisperX / Montreal Forced Aligner) that assigns each token a start/end timestamp — confirm which aligner the pipeline uses before binning to per-second.
- Lemmatization vs surface-form lookup: norm databases key on lemmas/base forms while surprisal and frequency can be surface-form; need a consistent OOV and morphology strategy (e.g. fall back lemma->surface->subword).
- Whether to merge Warriner and NRC-VAD (and with what priority) for valence/arousal/dominance, vs picking one to avoid double-counting correlated features.
- Choice of surprisal LM: smaller GPT-2/Pythia models often fit human reading-time/neural data better than the largest LLMs — decide whether the goal is best psycholinguistic fit (small model) or best raw language modeling (large model).
- Coverage/OOV handling for proper nouns and disfluencies in spoken-narrative transcripts, which are common in movies/audio stories but absent from norm databases.
- LLM-generated norm estimates (2024-2026 work) could fill OOV gaps for concreteness/valence/AoA, but require validation/fine-tuning against the human tables before use.
References
- spaCy (3.7+, en_core_web_trf / en_core_web_lg) — spacy.io; github.com/explosion/spaCy
- wordfreq (Speer 2022, Python) / SUBTLEX-US norms — github.com/rspeer/wordfreq; Brysbaert & New 2009 SUBTLEX-US
- Brysbaert Concreteness Norms (40,000 words) — Brysbaert, Warriner & Kuperman 2014, Behavior Research Methods
- Kuperman Age-of-Acquisition Norms (30,000 words) — Kuperman, Stadthagen-Gonzalez & Brysbaert 2012, Behavior Research Methods
- Warriner VAD Norms (13,915 lemmas) + NRC-VAD lexicon (20,000 words) — Warriner, Kuperman & Brysbaert 2013; Mohammad 2018 NRC-VAD
- NRC Emotion Lexicon (EmoLex, ~14,000 words) — Mohammad & Turney 2013, NRC EmoLex
- LIWC-22 — Boyd, Ashokkumar, Seraj & Pennebaker 2022, LIWC-22
- minicons (IncrementalLMScorer) — GPT-2 / Pythia / GPT-Neo surprisal — Misra 2022 (arXiv:2203.13112); github.com/kanishkamisra/minicons
- surprisal (aalok-sathe, Python) — github.com/aalok-sathe/surprisal
- GLiNER (urchade/gliner, 2024) — Zaratiana et al. 2024 (arXiv:2311.08526); github.com/urchade/GLiNER
- NLTK — nltk.org
Syntactic & grammatical structure
Sentence/utterance-level syntactic annotation for movie/audio narratives rests on a mature stack: a few NLP pipelines (spaCy, Stanza, Trankit) emit dependency parses, POS/morphology (tense/aspect/mood as UD feats), and lemmas per token; dedicated constituency parsers (benepar, supar, Stanza/CoreNLP conparse) yield phrase-structure trees from which complexity metrics (tree depth, Yngve/Frazier scores, L2SCA clause/T-unit ratios) are derived; coreference (Maverick, fastcoref, Stanza-coref) links mentions across the timeline; and lightweight libraries (textstat, readability) plus DA classifiers handle readability and dialogue acts. All core tools are open-source and CPU-runnable; transformer variants want a modest GPU. The main workflow is: parse transcript utterances time-aligned to the movie/audio timeline (via forced alignment elsewhere), then compute per-utterance/per-second syntactic features. Most complexity, readability, and Yngve/Frazier metrics are derived post-hoc from parser output rather than being separate models.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| spaCy 3.8 (en_core_web_trf / en_core_web_lg) | Full token-level syntactic pipeline: dependency parse, POS/tag, morphology, lemma, sentence segmentation, noun chunks | Per-token: dependency label (UD/CLEAR), head index, UPOS+fine POS tag, morphological feats (Tense, Aspect, Mood, Number, Person, VerbForm), lemma; per-span: noun chunks; sentence boundaries. trf model LAS ~87.9, UAS ~89.6 on OntoNotes (CLEAR scheme). | transcript | per-utterance (parses sentences; tokens map to timeline) | yes-cpu | best-in-class | include |
| Stanza 1.10 (Stanford NLP, UD pipeline) | Neural UD pipeline: tokenization, MWT, POS/UPOS+XPOS, lemma, morphological features, biaffine dependency parse; plus optional constituency parser and neural coreference | Per-token: UD dependency relation + head, UPOS/XPOS, UFeats (Tense/Aspect/Mood/Voice/VerbForm/Person/Number etc.), lemma; constituency tree (PTB-style); coref clusters. True Universal Dependencies labels (cleaner for cross-tool complexity metrics than spaCy’s CLEAR). | transcript | per-utterance | yes-gpu | best-in-class | include |
| benepar (Berkeley Neural Parser, self-attentive-parser) | Constituency (phrase-structure) parsing with a self-attentive encoder; basis for tree-depth, Yngve, Frazier, and clause-based complexity metrics | Per-sentence PTB-style constituency tree (NP/VP/S/SBAR labels, nesting depth) integrable as a spaCy component; ~95.4-96.3 F1 on revised WSJ. From the tree you derive: max/mean tree depth, # clauses, # SBAR, phrase-type counts, Yngve/Frazier scores. | transcript | per-utterance | yes-gpu | best-in-class | include |
| supar (yzhangcs/parser) | State-of-the-art biaffine/CRF dependency and constituency parsing (and semantic dependency); research-grade trees with explicit scoring algorithms | Per-token dependency arcs+labels (Biaffine, CRF, CRF2o); per-sentence constituency CRF trees; semantic dependency graphs. Exposes MST/Eisner/CKY/TreeCRF; useful when you want labeled tree probabilities/marginals for complexity weighting. | transcript | per-utterance | yes-gpu | strong | alternative |
| Trankit 1.x | Light multilingual transformer (XLM-R + adapters) UD pipeline: tokenization, sentence seg, POS, morphology, lemma, dependency parse | Per-token UD dependency relation + head, UPOS/XPOS, morphological UFeats, lemma; sentence boundaries. Reported +5 LAS / +4 sent-seg over Stanza on UD v2.5 in many languages; strongest sentence segmentation, which matters for utterance alignment. | transcript | per-utterance | yes-gpu | strong | alternative |
| Maverick (coreference resolution) | Coreference resolution: links mentions (pronouns, names, NPs) into entity clusters across the narrative for tracking referents over the timeline | Mention spans grouped into coreference clusters with cluster IDs; resolves who/what each pronoun refers to. SOTA on CoNLL-2012 (OntoNotes) with ~500M params, ~170x faster inference and far less memory than 13B generative coref models. | transcript | per-utterance (clusters span document/scene) | yes-gpu | best-in-class | include |
| fastcoref / F-coref (LingMess) | Fast, easy English coreference resolution; LingMess (accurate) and F-coref (fast) modes | Coreference clusters of mention spans per document. LingMess near-SOTA accuracy; F-coref ~25s for 2.8K OntoNotes docs on a V100 (vs 6-12 min for older systems) with modest accuracy drop. | transcript | per-utterance (document-level clusters) | yes-gpu | strong | alternative |
| L2 Syntactic Complexity Analyzer (L2SCA) + Tregex | Syntactic complexity indices derived from constituency parses: clause, T-unit, coordination, and phrasal complexity | 14 indices per text incl. mean length of clause/T-unit/sentence, clauses per T-unit, dependent clauses per clause, complex-nominals per clause, coordinate phrases per T-unit, T-units per sentence. Standard, validated set for syntactic complexity scoring. | transcript | per-utterance / per-window (aggregate over span) | yes-cpu | strong | include |
| textstat (Python) | Readability and surface complexity indices as a coarse proxy for grammatical/processing difficulty | Per-text scalars: Flesch Reading Ease, Flesch-Kincaid Grade, Gunning Fog, SMOG, Coleman-Liau, ARI, Dale-Chall, plus syllable/word/sentence counts and lexicon counts. Cheap per-second feature when computed over sliding transcript windows. | transcript | per-window / per-utterance | yes-cpu | usable | include-if-resources |
| Dialogue act classifier (DialogTag / DistilBERT-SwDA / silicone-finetuned) | Dialogue act / speech act of each utterance (function in conversation), relevant for dialogue-heavy film/audio | Per-utterance categorical DA label (SwDA 42-tag or DAMSL set: statement, yes-no-question, wh-question, backchannel, agreement, opinion, etc.) with class probabilities. Maps directly onto time-aligned dialogue turns. | transcript | per-utterance | yes-cpu | usable | include-if-resources |
Recommended (best-in-class): spaCy 3.8 (en_core_web_trf) for dependency/POS/morphology; Stanza 1.10 for UD-scheme parses + built-in constituency + coref in one tool; benepar for constituency trees feeding complexity metrics; Maverick for coreference resolution; L2SCA (+ Yngve/Frazier from trees) as the derived syntactic-complexity layer
Likely redundant:
- Trankit — superseded by spaCy/Stanza for English; keep only for multilingual or superior sentence-segmentation needs
- supar — overlaps benepar/Stanza; use only when you need joint dep+constituency or CRF marginals
- fastcoref/F-coref — superseded by Maverick on accuracy+efficiency; keep for high-throughput batch coref
- textstat — overlaps py-readability-metrics; surface-only, redundant with L2SCA’s deeper syntactic complexity
- Stanford CoreNLP constituency/coref — largely superseded by Stanza’s neural pipeline and Maverick
References
- spaCy 3.8 (en_core_web_trf / en_core_web_lg) — spacy.io; github.com/explosion/spaCy; en_core_web_trf
- Stanza 1.10 (Stanford NLP, UD pipeline) — stanfordnlp.github.io/stanza; Qi et al. 2020
- benepar (Berkeley Neural Parser, self-attentive-parser) — github.com/nikitakit/self-attentive-parser; Kitaev & Klein 2018/2019
- supar (yzhangcs/parser) — github.com/yzhangcs/parser; Zhang et al. ACL/IJCAI 2020
- Trankit 1.x — github.com/nlp-uoregon/trankit; Nguyen et al. EACL 2021
- Maverick (coreference resolution) — github.com/SapienzaNLP/maverick-coref; Martinelli et al. ACL 2024; arXiv:2407.21489
- fastcoref / F-coref (LingMess) — github.com/shon-otmazgin/fastcoref; Otmazgin et al. AACL 2022; arXiv:2209.04280
- L2 Syntactic Complexity Analyzer (L2SCA) + Tregex — Lu 2010; personal.psu.edu/xxl13 (L2SCA); en.wikipedia.org/wiki/L2_Syntactic_Complexity_Analyzer
- textstat (Python) — github.com/textstat/textstat
- Dialogue act classifier (DialogTag / DistilBERT-SwDA / silicone-finetuned) — github.com/bhavitvyas/DialogTag; SwDA/SILICONE benchmark; Raheja & Tetreault N19-1373
High-level semantics, discourse & narrative
This subclass covers meaning at the sentence/passage/scene level: dense semantic embeddings, topic structure, semantic coherence/similarity over time, discourse relations, and narrative structure (arcs, turning points, summaries, LLM-derived tags). For a movie/audio-story timeline the practical pipeline is: align a transcript (with word/utterance timestamps from ASR), window it into rolling segments (e.g. per-sentence, per-utterance, or sliding N-second windows), then emit per-segment features. The dominant, most flexible feature source in 2024-2026 is dense passage embeddings from instruction-tuned encoders (Qwen3-Embedding, E5/multilingual-e5, GTE, BGE, plus API options like OpenAI/Voyage/Gemini), from which you derive coherence (cosine similarity between adjacent windows), drift, and topic clusters. BERTopic gives interpretable topics-over-time. Instruction-tuned LLMs (Llama-3, Qwen2.5, GPT-4o-class) used as zero/few-shot feature extractors now cover the harder, more abstract signals — turning points, discourse relations, event/scene segmentation, narrative tags, and structured summaries — that no specialized supervised model does robustly. Note: in this neuroimaging context, the LAST-token/last-layer hidden states of autoregressive LLMs (GPT-2 through Llama-3) are themselves a primary per-word/per-TR feature and the current standard for naturalistic language-comprehension encoding models.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| Qwen3-Embedding (0.6B / 4B / 8B) | Instruction-tuned dense semantic embedding of a sentence/passage; supports task-specific instructions prepended to the text. Best open-weight model on MTEB-multilingual as of mid-2025 (8B = 70.58). | Per-window L2-normalized embedding vector (1024-dim for 0.6B, up to 4096-dim for 8B; Matryoshka-truncatable). Derived per-timepoint signals: cosine similarity to adjacent window (local coherence), cosine to a running mean (semantic drift/novelty), distance to topic/scene centroids, per-window similarity to a fixed semantic probe (e.g. ‘fear’, ‘social interaction’). | transcript | per-utterance / per-sentence / per-sliding-window (set by how transcript is segmented) | yes-gpu | best-in-class | include |
| multilingual-e5-large-instruct / e5-mistral-7b-instruct | General-purpose instruction-tuned passage embeddings; strong, very widely used, well-documented baseline. e5-mistral is a 7B decoder-as-embedder; multilingual-e5-large is a compact 560M encoder. | Per-window embedding (1024-dim for multilingual-e5-large; 4096-dim for e5-mistral). Same derived signals as Qwen3 (adjacent-window cosine coherence, drift, probe similarity, cluster assignment). | transcript | per-utterance / per-sentence / per-sliding-window | yes-gpu | strong | alternative |
| GTE (gte-Qwen2-7B-instruct / gte-large-en-v1.5) and BGE (bge-m3, bge-large-en-v1.5) | Two additional top-tier open embedding families. gte-Qwen2-7B-instruct is a high-MTEB instruction embedder; bge-m3 is notable for multi-functionality (dense + sparse + ColBERT multi-vector) and 8k context, useful for embedding longer passages/scenes. | Per-window dense embedding (1024-dim bge-large/gte-large; up to 3584/4096 for 7B variants). bge-m3 additionally yields sparse lexical weights and token-level multi-vectors. Derived: coherence, drift, lexical-overlap-vs-semantic divergence. | transcript | per-utterance / per-sentence / per-sliding-window / per-scene | yes-gpu | strong | alternative |
| OpenAI text-embedding-3-large / Voyage-3 / Gemini Embedding (API) | Commercial managed embeddings. Convenient, no local GPU; Gemini Embedding and Voyage-3 currently top several leaderboards. text-embedding-3-large is widely used but not updated since Jan 2024 and now matched/beaten by open models. | Per-window embedding (3072-dim openai-3-large, dimension-reducible; 1024/2048 Voyage; ~3072 Gemini). Same derived coherence/drift/probe signals. | transcript | per-utterance / per-sentence / per-sliding-window | api-only | usable | alternative |
| BERTopic (with dynamic / topics-over-time) | Neural topic modeling: clusters embeddings (UMAP+HDBSCAN) and labels clusters with class-based TF-IDF; topics_over_time recomputes topic representations per time bin without refitting, giving an interpretable topic timeline. | Per-window: hard topic assignment (topic id) and soft topic-probability distribution; per-time-bin: topic prevalence/frequency series and time-varying top keywords per topic. Yields a categorical topic label plus a continuous topic-activation vector per timepoint. | transcript | per-window (assignment) + per-time-bin (prevalence series) | yes-cpu | best-in-class | include |
| Semantic coherence / drift over time (sliding-window cosine pipeline) | Derived analysis layer (not a model): from any sentence-embedding stream, compute local semantic coherence, topical drift, novelty, and recurrence over the timeline. Mirrors classic latent-semantic coherence measures used in psycholinguistics/clinical NLP, modernized with transformer embeddings. | Per-timepoint scalars: cosine(window_t, window_{t-1}) = local coherence; cosine(window_t, running-mean) = drift/integration; 1-max-similarity-to-past = novelty; mean similarity within a lag window = coherence-at-lag; segmentation boundary score (similarity trough) for event/scene cuts. | transcript | per-window / per-second (after resampling to the stimulus timeline) | yes-cpu | best-in-class | include |
| Autoregressive LLM hidden-state extractor (GPT-2, Llama-3-8B, Qwen2.5-7B) for encoding models | Contextualized per-word/per-token representations from a causal LM as the stimulus is read word-by-word. In neuroimaging this is the standard high-level-semantic feature for naturalistic language comprehension; layer activations predict comprehension and brain responses. | Per-token hidden-state vector at a chosen layer (e.g. 768-dim GPT-2, 4096-dim Llama-3-8B); next-word surprisal/entropy (-log p); per-token contextual embedding. Aggregated to per-word then averaged within each TR/second for the stimulus timeline. | transcript | per-word (timestamp-aligned) then resampled per-second / per-TR | yes-gpu | best-in-class | include |
| Instruction-tuned LLM as zero/few-shot feature extractor (Llama-3.1-8B-Instruct / Qwen2.5-7B-Instruct local; GPT-4o / Claude API) | Prompt an instruction-tuned LLM over a transcript window to emit structured high-level semantic/narrative tags that no specialized supervised model covers robustly: scene/topic labels, narrative-stage classification, turning-point likelihood, discourse-relation between adjacent segments, character/event mentions, abstractness, and structured summaries. | Per-window structured tags (JSON): categorical narrative stage (setup/conflict/climax/resolution), turning-point flag + type, discourse relation label (cause/contrast/elaboration), valence/tension Likert score, topic/theme label, entity/event list, one-line summary. Also ‘interpretable embeddings’ = vector of yes/no answers to a fixed question battery (Benara/Gao 2024). | transcript | per-window / per-scene / per-utterance | yes-gpu | strong | include |
| DMRST / neural RST discourse parser | Supervised rhetorical-structure-theory parsing: segments text into elementary discourse units (EDUs) and builds a labeled discourse tree (nuclearity + relation labels) within a passage. The structured, theory-grounded alternative to asking an LLM for discourse relations. | Per-passage: EDU segmentation boundaries; discourse-tree edges with relation labels (Elaboration, Contrast, Cause, Background, ~18-30 relation classes) and nuclearity (nucleus/satellite). Per-timepoint derivable: discourse-boundary indicator, local tree depth, relation-type at each boundary. | transcript | per-EDU / per-sentence (within passage windows) | yes-gpu | usable | include-if-resources |
| Supervised narrative turning-point / story-arc model (TRIPOD / Papalampidi-style; sentiment-arc tooling) | Identifies screenplay/story turning points (Opportunity, Change of Plans, Point of No Return, Major Setback, Climax) and/or computes the emotional/sentiment arc (Reagan ‘six story shapes’) across the narrative. | Per-scene/per-segment: turning-point probability for each of 5 TP types; normalized story position; continuous sentiment/emotional-arc value (smoothed valence trajectory); arc-shape class. Yields a tension/arc scalar per timepoint plus sparse turning-point markers. | transcript | per-scene / per-segment (smoothed to a continuous arc) | yes-gpu | usable | alternative |
Recommended (best-in-class): Qwen3-Embedding-0.6B (lab default open encoder; scale to 4B/8B if GPU allows) as the backbone for all semantic features; Sliding-window cosine coherence/drift pipeline on top of those embeddings to produce per-second time-resolved scalars; BERTopic with topics-over-time for interpretable topic timelines (reuses the same embeddings); Autoregressive LLM hidden-state + surprisal extractor (GPT-2 / Llama-3.1-8B) for word-level / per-TR comprehension features (the neuroimaging standard); Instruction-tuned LLM (Llama-3.1-8B-Instruct / Qwen2.5-7B-Instruct local, or GPT-4o/Claude API) as a zero/few-shot extractor for narrative stage, turning points, discourse relations, and structured tags
Likely redundant:
- multilingual-e5, GTE, BGE — pick ONE primary open encoder; the others are interchangeable alternatives to Qwen3-Embedding, not separate features
- OpenAI/Voyage/Gemini embedding APIs — superseded for most needs by open encoders (Qwen3/E5); use only if a leaderboard-topping API embedding is required and off-site transcript transfer is acceptable
- Supervised RST parser (DMRST) and supervised turning-point models — largely subsumed by the instruction-tuned LLM extractor, which is more flexible on spoken transcripts; keep the supervised ones only when standardized, reproducible discourse labels are specifically required
- Standalone raw-embedding clustering — superseded by BERTopic, which wraps clustering with labeling and dynamic topics-over-time
References
- Qwen3-Embedding (0.6B / 4B / 8B) — github.com/QwenLM/Qwen3-Embedding; huggingface.co/Qwen/Qwen3-Embedding-0.6B; MTEB/MMTEB
- multilingual-e5-large-instruct / e5-mistral-7b-instruct — huggingface.co/intfloat/multilingual-e5-large-instruct; arXiv:2212.03533, 2401.00368
- GTE (gte-Qwen2-7B-instruct / gte-large-en-v1.5) and BGE (bge-m3, bge-large-en-v1.5) — huggingface.co/Alibaba-NLP/gte-Qwen2-7B-instruct; huggingface.co/BAAI/bge-m3
- OpenAI text-embedding-3-large / Voyage-3 / Gemini Embedding (API) — platform.openai.com/docs/guides/embeddings; docs.voyageai.com; ai.google.dev embeddings
- BERTopic (with dynamic / topics-over-time) — github.com/MaartenGr/BERTopic; arXiv:2203.05794; maartengr.github.io/BERTopic/getting_started/topicsovertime
- Semantic coherence / drift over time (sliding-window cosine pipeline) — Standard sliding-window cosine; cf. coherence/tangentiality NLP literature; TextTiling for segmentation
- Autoregressive LLM hidden-state extractor (GPT-2, Llama-3-8B, Qwen2.5-7B) for encoding models — Caucheteux & King 2022; Goldstein et al. 2022; ‘Narratives’ fMRI dataset (Nastase et al.); biorxiv 2021.04.20.440622
- Instruction-tuned LLM as zero/few-shot feature extractor (Llama-3.1-8B-Instruct / Qwen2.5-7B-Instruct local; GPT-4o / Claude API) — Benara/Gao ‘QA-Emb / Crafting Interpretable Embeddings by Asking LLMs Questions’ arXiv:2405.16714; Papalampidi turning-point work; Story Ribbons arXiv:2508.06772
- DMRST / neural RST discourse parser — Liu et al. DMRST (arXiv:2110.04518 / 2012.01704); eRST (Computational Linguistics 2025); GUM/RST-DT corpora
- Supervised narrative turning-point / story-arc model (TRIPOD / Papalampidi-style; sentiment-arc tooling) — Papalampidi et al. TRIPOD ‘Movie Plot Analysis via Turning Points’; Reagan et al. ‘emotional arcs of stories’; arXiv:2511.11857; culturalanalytics.org Beyond Plot
Social features
Social & interpersonal features
Depicted social content for a movie/audio timeline decomposes into (a) low-level perceptual primitives that are reliably extractable today with dedicated open models – number/identity of agents (face detection + recognition), who-speaks-when (diarization), who-is-actively-talking (audio-visual ASD), gaze targets / joint attention, and body pose for proximity/touch – and (b) high-level social semantics (interaction type, cooperation/conflict, dominance/affiliation, theory-of-mind, intentions, joint attention construal) that have no robust dedicated detector and are best realized by prompting a strong video VLM (Qwen2.5-VL, InternVL3, or a frontier API) against schemas derived from social-reasoning benchmarks (Social-IQ 2.0, Social Genome, SIV-Bench). The recommended architecture is a two-layer pipeline: run the cheap deterministic primitive extractors per-second to build a character/speaker track and proximity/gaze graph, then feed those tracks plus sampled frames into a VLM with structured prompts to label interaction type and mental-state content per shot/scene. The dedicated social-reasoning models in the literature are mostly benchmarks, not deployable annotators, so the VLM-prompting layer is unavoidable for the genuinely “social” dimensions.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| pyannote-audio 3.1 (speaker-diarization-3.1) | Who-speaks-when from the audio track: segments the soundtrack by speaker identity, the backbone for who-talks-to-whom in dialogue. | Per-segment speaker labels (anonymous SPK_00, SPK_01, …) with start/end times; derivable per-second one-hot active-speaker-id vector, speaker count, overlap/cross-talk flag, turn-taking transitions. Optional speaker embeddings (192-d) for cross-scene identity linking. | audio | per-utterance / per-segment, resampleable to per-second | yes-gpu | best-in-class | include |
| Light-ASD / LR-ASD (CVPR 2023) | Audio-visual active speaker detection: which on-screen face is speaking at each frame, linking the diarization speaker to a visible agent (essential for who-talks-to-whom in video). | Per-face, per-frame speaking probability/binary label; combined with face tracks yields per-second ‘face X is speaking’ signals, listener vs speaker roles, and addressee inference when fused with gaze. | multimodal | per-frame | yes-gpu | strong | include |
| InsightFace (SCRFD/RetinaFace detection + ArcFace/buffalo_l recognition) | Number and identity of agents present: detects faces per frame and assigns persistent character identities via face-embedding clustering across the film. | Per-frame face count + bounding boxes; 512-d ArcFace embeddings; per-track character ID (after clustering/gallery matching); per-second ‘characters present’ set; co-presence matrix (which characters share the frame) that seeds the social-network graph; head pose (yaw/pitch/roll) and age/gender estimates. | video-frame | per-frame, aggregated per-shot/per-second | yes-gpu | best-in-class | include |
| Gaze-LLE (CVPR 2025, Highlight) | Gaze target estimation and joint attention: where each person is looking in the scene, enabling looking-at-each-other (mutual gaze), looking-at-same-object (shared/joint attention), and addressee detection. | Per-person gaze heatmap over the frame + predicted gaze target point; in-frame/out-of-frame gaze flag; derivable per-second mutual-gaze (dyad looking at each other) and joint-attention (multiple people fixating same target) binary signals and gaze-direction vectors. | video-frame | per-frame (designed for VideoAttentionTarget), aggregateable per-second | yes-gpu | best-in-class | include |
| RTMPose / ViTPose (MMPose) + ByteTrack | Multi-person 2D body pose and tracking: provides inter-personal distance (proximity), orientation/facing, touch/contact candidates, and gesture cues underlying affiliation and interaction. | Per-person 17/133-keypoint skeletons per frame with track IDs; derived per-second features: pairwise inter-personal distance, body-orientation (facing toward/away), overlap/contact flags (touch proxy when keypoints of two people coincide), approach/retreat velocity, gesture/posture descriptors. | video-frame | per-frame | yes-gpu | strong | include |
| Qwen2.5-VL (7B / 72B, Instruct) | High-level social semantics via structured video prompting: interaction type (cooperation/conflict/dominance/affiliation), who-talks-to-whom narration, intentions, joint-attention construal, scene-level social description — the main engine for the genuinely social labels. | Per-shot/per-clip structured outputs you define: interaction-type categorical labels, dominance/affiliation ratings, agent-relationship descriptions, ToM/intention free-text or scalar ratings, dialogue addressee attribution, social-network edge proposals; supports temporal grounding (timestamps) for event localization. | multimodal | per-clip / per-shot (sampled frames), with timestamp grounding | yes-heavy-gpu | best-in-class | include |
| InternVL3 (8B / 38B / 78B) | Alternative high-level social-semantics VLM for the same interaction-type / ToM / relationship labeling layer; native multimodal pretraining gives strong video social-scene reasoning. | Same configurable per-clip social labels as Qwen2.5-VL: interaction categories, dominance/affiliation, intention/mental-state inferences, agent-relationship and social-network edge proposals, grounded social descriptions. | multimodal | per-clip / per-shot | yes-heavy-gpu | strong | alternative |
| Frontier multimodal APIs (Claude Opus 4.x, GPT-4o, Gemini 2.x) as social-reasoning judges | Highest-quality high-level social inference (ToM, intention, conflict/cooperation nuance, dominance/affiliation, mental-state attribution) when local VLMs are insufficient or for generating gold annotations to validate the local pipeline. | Per-shot/per-scene structured JSON: interaction-type labels, dominance/affiliation/valence ratings, ToM/mental-state inferences with rationale, addressee and relationship graphs; can produce grounded reasoning traces analogous to Social Genome. | multimodal | per-clip / per-scene (sampled frames or transcript) | api-only | strong | include-if-resources |
| Social-IQ 2.0 / Social Genome / SIV-Bench (benchmark + schema resources) | Not annotators but the definitional backbone: socially-rich video QA and grounded-reasoning datasets that define the social-interaction label taxonomy, evidence types (visual/verbal/vocal cues + external knowledge), and evaluation protocols to design and validate the VLM-prompting layer. | Provides label schemas, multiple-choice social-reasoning items, and 5,777 annotated reasoning steps (Social Genome) referencing visual/verbal/vocal evidence — used to construct prompt templates, few-shot exemplars, and held-out evaluation sets for the social-feature extractor. | multimodal | per-clip (dataset-level) | yes-cpu | usable | include |
Recommended (best-in-class): pyannote-audio 3.1 (who-speaks-when); Light-ASD/LR-ASD (active speaker / who-talks-on-screen); InsightFace SCRFD+ArcFace (agent count + character identity + co-presence); Gaze-LLE (gaze target / joint attention / mutual gaze); RTMPose or ViTPose (pose for proximity/touch/orientation); Qwen2.5-VL (primary local VLM for interaction-type, dominance/affiliation, ToM/intention labeling)
Likely redundant:
- TalkNet-ASD — superseded by Light-ASD/LR-ASD (same task, ~23x more params for equal mAP)
- OpenPose — legacy, superseded by RTMPose/ViTPose for multi-person pose
- InternVL3 — redundant with Qwen2.5-VL as the primary local social-reasoning VLM; keep at most one primary + optional agreement check
- Frontier APIs (GPT-4o/Gemini/Claude) — redundant with local VLMs for routine annotation; reserve for gold-label validation/distillation
- DeSIQ / VEGAS / Human Behavior Atlas / SIV-Bench — overlapping social benchmarks, redundant with Social-IQ 2.0 + Social Genome as the chosen schema source
- Dedicated HOI/dyadic-interaction detectors (HOTR, PRTT, ViPLO) — image-centric, no robust video social-interaction-type SOTA; the VLM layer supersedes them for movie-timeline use
References
- pyannote-audio 3.1 (speaker-diarization-3.1) — huggingface.co/pyannote/speaker-diarization-3.1; github.com/pyannote/pyannote-audio
- Light-ASD / LR-ASD (CVPR 2023) — github.com/Junhua-Liao/Light-ASD; arxiv 2303.04439 (Light-ASD); IJCV 2025 LR-ASD
- InsightFace (SCRFD/RetinaFace detection + ArcFace/buffalo_l recognition) — insightface.ai/arcface; github.com/deepinsight/insightface
- Gaze-LLE (CVPR 2025, Highlight) — arxiv 2412.09586; github.com/fkryan/gazelle
- RTMPose / ViTPose (MMPose) + ByteTrack — arxiv 2303.07399 (RTMPose); ViTPose NeurIPS 2022; github.com/open-mmlab/mmpose
- Qwen2.5-VL (7B / 72B, Instruct) — qwenlm.github.io/blog/qwen2.5-vl; HF Qwen2.5-VL
- InternVL3 (8B / 38B / 78B) — OpenGVLab InternVL3; arxiv InternVL3
- Frontier multimodal APIs (Claude Opus 4.x, GPT-4o, Gemini 2.x) as social-reasoning judges — Social Genome (arxiv 2502.15109) uses such models as graders; provider API docs
- Social-IQ 2.0 / Social Genome / SIV-Bench (benchmark + schema resources) — cmu-multicomp-lab.github.io/social-iq-2.0; cmu-multicomp-lab.github.io/social-genome; arxiv 2502.15109; arxiv 2506.05425 (SIV-Bench)
Situation features
Situations, schemas, scripts & event segmentation
This subclass spans two complementary tool families: (1) data-driven event/state boundary detection that segments a continuous feature timeline (neural data, or stimulus-derived embeddings such as visual/semantic features) into discrete events with stable patterns, and (2) situational tagging that labels each segment or timepoint with setting, location, time-of-day, scripts/schemas, and the Event-Indexing situational dimensions (space, time, causation, intention, protagonist). For boundary detection, BrainIAK’s HMM (Baldassano 2017) and Geerligs’ GSBS (statesegmentation) are the canonical, locally deployable algorithms; both run on any feature matrix, not just fMRI, making them directly usable to segment movie-derived embedding timelines. Shot/scene cut detection (PySceneDetect, TransNetV2) gives a fast low-level event-boundary prior. For situational content, scene-recognition CNNs (Places365), movie-tuned CLIP (MovieCLIP), and zero-shot CLIP cover location/setting/time-of-day, while modern open-weight video-LLMs (Qwen2.5-VL, VideoLLaMA3) and text/transcript LLMs (GPT-4-class via prompting) provide human-like event segmentation plus rich, queryable situational dimensions (goals, causes, protagonist, schema/script labels) with second-level temporal grounding. A small lab can run the algorithmic tools and 7B-class VLMs on a single modern GPU.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| BrainIAK EventSegment (HMM, Baldassano et al. 2017) | Data-driven event boundaries / segmentation of any continuous feature timeline into K discrete events with stable activity (or embedding) patterns; finds discontinuities in pattern correlation as event transitions. | Per-timepoint hard or soft (posterior probability) event-label assignment; ordered list of event-boundary timepoints; per-event mean pattern; with model comparison, the optimal number of events K (which yields event duration/granularity). Runs on stimulus-derived embeddings to segment a movie/audio timeline, not only fMRI. | multimodal | per-second (matches input TR/frame sampling); boundaries at input timestep resolution | yes-cpu | best-in-class | include |
| GSBS / statesegmentation (Geerligs et al. 2021, 2022) | Greedy State Boundary Search: iteratively places event/state boundaries that maximize within-state pattern similarity in a feature timeline; auto-selects optimal number of states via t-distance metric. | Per-timepoint state/event label; ranked boundary timepoints (greedy order gives boundary salience/strength); optimal number of states; nested hierarchical boundaries (fine-to-coarse event granularity). Applicable to any T x features matrix including movie embeddings. | multimodal | per-second / per-input-timestep | yes-cpu | best-in-class | include |
| PySceneDetect | Low-level shot/scene cut detection via frame-to-frame content change (HSV histogram / content difference) and adaptive thresholding. | List of shot-boundary timestamps and frame indices; per-frame content-change score; shot-segment start/end times. Provides a fast event-boundary prior aligned to the video timeline. | video-frame | per-frame / per-shot | yes-cpu | strong | include |
| TransNetV2 | Deep-learning shot-boundary detection (hard cuts and gradual transitions/dissolves) trained on labeled shot transitions. | Per-frame shot-transition probability (0-1); derived shot-boundary timestamps and shot segments. ~87% shot segmentation accuracy vs ~65% for histogram methods; handles gradual transitions. | video-frame | per-frame / per-shot | yes-gpu | best-in-class | include |
| Places365-CNN (ResNet50/DenseNet161, CSAILVision) | Scene/place category recognition: classifies each frame into one of 365 physical scene/location categories (e.g., restaurant, kitchen, street, forest) plus SUN scene attributes. | Per-frame 365-dim scene-category posterior (top-k location/setting labels), indoor/outdoor probability, and ~102 scene-attribute scores (e.g., ‘open area’, ‘man-made’, ‘enclosed’, ‘natural light’). Directly yields location/setting and coarse spatial-layout descriptors. | video-frame | frame (sample per second/shot) | yes-gpu | strong | include |
| MovieCLIP (visual scene recognition in movies) | Movie-tuned visual scene recognition over a 179-label movie-centric scene taxonomy curated from scripts (settings like ‘courtroom’, ‘bar’, ‘spaceship’, ‘chase’-relevant locations). | Per-shot/per-frame scores over 179 movie scene labels (background location/setting); embeddings usable for setting clustering. Tailored to cinematic settings better than ImageNet/Places taxonomies. | video-frame | per-shot / frame | yes-gpu | usable | alternative |
| Zero-shot CLIP / OpenCLIP (ViT-L/14, ViT-H/14) | Flexible zero-shot classification of arbitrary situational attributes via text prompts: location/setting, indoor/outdoor, time-of-day, weather, schema/script cues (e.g., ‘a restaurant scene’, ‘a car chase’). | Per-frame cosine-similarity / softmax scores for any user-defined prompt set (e.g., morning/afternoon/night; indoor/outdoor; named scripts). Continuous per-label scores per timepoint, fully customizable taxonomy. | video-frame | frame (sample per second/shot) | yes-gpu | strong | include |
| Qwen2.5-VL (3B/7B/72B, open weights) | Open-weight video-LLM with second-level temporal grounding: produces human-like event segmentation and free-form situational tagging (location, time of day, goals, causes, protagonist actions, schema/script identification) with timestamps. | Timestamped event/segment boundaries (second-level via absolute-time MRoPE encoding); per-segment natural-language situational descriptions queryable into structured Event-Indexing fields (space, time, causation, intention, protagonist); script/schema labels; mIoU 50.9 on Charades-STA temporal grounding (> GPT-4o). | video-clip | per-second / per-shot / per-segment (timestamped) | yes-gpu | best-in-class | include |
| VideoLLaMA3 (2B/7B, DAMO/Alibaba) | Open multimodal video foundation model for dense captioning, temporal grounding, and video QA; produces situational descriptions and event localization across a clip. | Timestamped dense captions / event segments; per-segment situational narrative (setting, actors, actions, goals) parseable into situational dimensions; temporal-grounding spans for queried events. | video-clip | per-segment / per-clip (timestamped grounding) | yes-gpu | strong | alternative |
| LLM text/transcript event segmentation (GPT-4-class via API; Llama-3 70B open) | Human-like narrative event segmentation and situational annotation from text/transcripts: zero-shot prompts mark event boundaries and tag schema/script, goals, causes, protagonist, time, and space per event. | Ordered event-boundary positions in the transcript (mappable to timestamps via word/utterance alignment); per-event structured situational fields (location, time-of-day, goal, cause, protagonist, script label). LLM boundaries align with human segmentation and are more consistent than individual humans. | transcript | per-utterance / per-event (timestamps via transcript alignment) | yes-heavy-gpu | best-in-class | include |
| Indexter (Event-Indexing computational model) | Symbolic computational implementation of Zwaan’s Event-Indexing situation model: tracks the five situational dimensions (time, space, causation, intention, protagonist) and continuity/discontinuity across events. | Per-event indices and between-event continuity scores along time, space, protagonist, causation, intention dimensions; situational-change signals predicting event boundaries. Provides the conceptual schema for structuring LLM/VLM situational outputs. | text | per-event | unclear | legacy/superseded | alternative |
Recommended (best-in-class): GSBS / statesegmentation (primary data-driven event-boundary detector on stimulus feature timelines); BrainIAK EventSegment HMM (alternative boundary detector + soft event posteriors); TransNetV2 (best shot-boundary prior for video); Qwen2.5-VL-7B (open video-LLM for second-level event segmentation + situational dimensions); Zero-shot OpenCLIP (flexible per-frame location/time-of-day/setting/script attributes); Places365-ResNet50 (calibrated fixed-taxonomy location/setting + scene attributes); GPT-4-class / Llama-3-70B LLM (transcript-based human-like event segmentation + Event-Indexing situational tagging, esp. audio stories)
Likely redundant:
- PySceneDetect -> superseded for accuracy by TransNetV2 (keep only as a fast CPU prior)
- MovieCLIP -> overlaps zero-shot CLIP/Places365; use only if its 179 movie-scene taxonomy is a better fit and checkpoints are accessible
- VideoLLaMA3 -> redundant with Qwen2.5-VL as the situational video-LLM; pick one primary
- BrainIAK HMM vs GSBS -> overlapping boundary detectors; GSBS generally preferred (auto-K, faster), keep HMM mainly for soft event posteriors / recall-reactivation modeling
- Indexter -> superseded as a runnable tool by LLM situational tagging; retain only as the target ontology/schema definition
Open questions:
- Ground-truth/validation: which event-boundary timeline (human dense-rating norms, e.g., per-second boundary agreement) will be used to validate algorithmic vs LLM/VLM boundaries on your specific movies/audio stimuli?
- Pipeline order: run shot detection (TransNetV2) first and tag shots, vs run GSBS/HMM on a continuous embedding timeline (from CLIP/VLM/text embeddings) to get semantic events independent of camera cuts? These give different granularities.
- For audio-only stories, the visual tools do not apply; need to confirm WhisperX-style word-level alignment to project transcript LLM event boundaries and situational tags onto the audio timeline.
- Structured output schema: define a fixed JSON schema for the five Event-Indexing dimensions (space/time/causation/intention/protagonist) + script/schema label so VLM/LLM outputs are consistently parseable per second/segment.
- Compute budget: is a single 24GB GPU available for 7B video-LLM inference, or should situational tagging fall back to API (GPT-4-class) for the visual stream?
- Which feature space feeds GSBS/HMM for stimulus segmentation (raw CLIP frame embeddings, VLM hidden states, semantic transcript embeddings) materially changes the events recovered and should be decided/benchmarked.
References
- BrainIAK EventSegment (HMM, Baldassano et al. 2017) — Baldassano et al. 2017 Neuron ‘Discovering Event Structure in Continuous Narrative Perception and Memory’; github.com/brainiak/brainiak; brainiak.org/examples/Event_Segmentation.html
- GSBS / statesegmentation (Geerligs et al. 2021, 2022) — Geerligs, van Gerven, Guclu 2021 NeuroImage; Geerligs et al. 2022 eLife ‘A partially nested cortical hierarchy of neural states’; pypi.org/project/statesegmentation
- PySceneDetect — github.com/Breakthrough/PySceneDetect; scenedetect.com
- TransNetV2 — Soucek & Lokoc, ‘TransNet V2: An Effective Deep Network for Fast Shot Transition Detection’; github.com/soCzech/TransNetV2
- Places365-CNN (ResNet50/DenseNet161, CSAILVision) — Zhou et al. 2018 TPAMI ‘Places: A 10 Million Image Database for Scene Recognition’; places2.csail.mit.edu
- MovieCLIP (visual scene recognition in movies) — Bose et al., WACV 2023, arXiv:2210.11065; project page from USC SAIL
- Zero-shot CLIP / OpenCLIP (ViT-L/14, ViT-H/14) — Radford et al. 2021 CLIP; github.com/mlfoundations/open_clip; MovieCLIP uses this labeling recipe
- Qwen2.5-VL (3B/7B/72B, open weights) — Bai et al. 2025 ‘Qwen2.5-VL Technical Report’ arXiv:2502.13923; huggingface.co/Qwen
- VideoLLaMA3 (2B/7B, DAMO/Alibaba) — Zhang et al. 2025 ‘VideoLLaMA 3’ arXiv:2501.13106; github.com/DAMO-NLP-SG/VideoLLaMA3
- LLM text/transcript event segmentation (GPT-4-class via API; Llama-3 70B open) — Michelmann et al. 2025 ‘LLMs segment narrative events similarly to humans’ (arXiv:2301.10297); Nature Comms Psychology 2025 s44271-025-00359-7 on LLM-enabled recall/event segmentation
- Indexter (Event-Indexing computational model) — Cardona-Rivera et al., ‘Indexter: A Computational Model of the Event-Indexing Situation Model’; Zwaan & Radvansky 1998 event-indexing framework
Affect features
Emotion & affect (multimodal)
Time-resolved emotion annotation for movies/audio splits cleanly by signal source: per-frame face models (EmoNet, HSEmotion/EmotiEffLib), per-utterance/windowed voice models (audeering wav2vec2-dim), per-window text/dialogue models (RoBERTa-GoEmotions, EmoBank-VAD regressors), per-window music models (MERT-based regressors), and clip/window multimodal fusion or MLLM reasoning systems (Emotion-LLaMA, AffectGPT, Qwen2.5-VL/GPT-4o judges). A crucial distinction: most of these models predict character-DEPICTED emotion (the expressed affect of a speaker/face), whereas viewer-ELICITED emotion requires separate models trained on induced-affect ground truth (LIRIS-ACCEDE / MediaEval continuous, MuSe/SEWA benchmarks) — the two are correlated but not interchangeable and should be logged as distinct feature streams. For a small lab, the strongest open, locally deployable defaults are audeering wav2vec2 (voice VAD), HSEmotion EfficientNet (face VA + categorical), RoBERTa-GoEmotions (dialogue), MERT regressor (music), with an MLLM (Qwen2.5-VL or GPT-4o) as a window-level fusion/captioning layer and EmoNet/Emotion-LLaMA as alternatives.
| Tool | Measures | Per-timepoint output | Modality | Granularity | Local | Rating | Rec |
|---|---|---|---|---|---|---|---|
| audeering wav2vec2-large-robust-12-ft-emotion-msp-dim | Dimensional emotion from raw speech audio (depicted/expressed affect of the speaker’s voice); trained on MSP-Podcast v1.7. | 3 continuous scalars per window: arousal, dominance, valence each in ~0..1. Also exposes pooled last-layer hidden states (1024-d) usable as a voice-affect embedding. Run on a sliding window (e.g. 0.5-2 s hop) to get per-second VAD timeseries aligned to the audio timeline. | audio | sub-second to per-second (windowed; receptive field of one inference pass) | yes-gpu | best-in-class | include |
| HSEmotion / EmotiEffLib (EfficientNet-B0/B2 + MT-MobileFaceNet, AffectNet-trained) | Per-face facial expression: categorical emotion, continuous valence/arousal, and (multi-task variants) action-unit / emotion intensity. Depicted emotion of on-screen faces. | Per detected face per frame: 7- or 8-class categorical softmax (Anger, Contempt, Disgust, Fear, Happiness, Neutral, Sadness, Surprise), continuous valence and arousal scalars (va_mtl models), plus the penultimate embedding (1280-d for B0) as a face-affect feature. Aggregate across faces per frame for a scene-level signal. | video-frame | frame (needs an upstream face detector/tracker) | yes-cpu | best-in-class | include |
| EmoNet (face-analysis/emonet, Toisoul et al. Nature Mach. Intell. 2021) | Continuous valence/arousal and categorical expression from faces in naturalistic conditions; depicted facial emotion. | Per face per frame: continuous valence and arousal (-1..1), 5- or 8-class expression probabilities, and 68 facial landmarks. Ships demo_video.py (added 2024) for face detection + emotion over video frames. | video-frame | frame | yes-gpu | strong | alternative |
| RoBERTa fine-tuned on GoEmotions (SamLowe/roberta-base-go_emotions) | Categorical emotion of dialogue/narration text (depicted emotional content of language). 28-emotion taxonomy. | Per utterance/sentence: 28 independent multi-label sigmoid scores (admiration, amusement, anger, … neutral). Map to Russell valence/arousal via a VAD lexicon (NRC-VAD / EmoBank) for a continuous proxy. Align to timeline via subtitle/ASR timestamps (per-utterance). | text | per-utterance (subtitle/ASR-segment level) | yes-cpu | strong | include |
| EmoBank / NRC-VAD dimensional text regressor (VAD lexicon + transformer) | Continuous valence-arousal-dominance of text on a graded scale (complements categorical GoEmotions). Depicted language affect. | Per utterance: continuous valence, arousal, dominance scalars (typically 1..9 or normalized). Can be produced by a transformer regressor fine-tuned on EmoBank or by lexicon lookup (NRC-VAD) averaged over tokens. | text | per-utterance | yes-cpu | usable | include-if-resources |
| MERT-based music emotion regressor (MERT-v1-95M/330M embeddings + V/A head) | Emotion conveyed by the musical score/soundtrack (depicted musical affect), distinct from speech affect. | Per audio window: continuous valence and arousal scalars (and optional categorical mood tags). MERT yields ~768/1024-d self-supervised music embeddings per ~frame; a small regression head trained on DEAM/PMEmo/EmoMusic maps to V/A timeseries. | audio | per-second (windowed) | yes-gpu | strong | include-if-resources |
| Emotion-LLaMA (ZebangCheng/Emotion-LLaMA, NeurIPS 2024) | Multimodal (audio+visual+text) emotion recognition AND free-text reasoning about the emotional state of on-screen people. Depicted emotion with explanation. | Per clip/window: categorical emotion label (+ confidence) plus a natural-language explanation of cues (facial, prosodic, lexical). Strong zero-shot transfer (e.g. DFEW). Run over fixed windows (e.g. 2-5 s shots) to get a per-shot labeled + reasoned stream. | multimodal | per-shot / per-clip (windowed) | yes-heavy-gpu | strong | include-if-resources |
| AffectGPT (zeroQiaoba/AffectGPT, ICML 2025) | Explainable multimodal emotion understanding with descriptive captions; open-vocabulary (OV-MER) emotion rather than fixed classes. Depicted emotion + description. | Per clip: open-vocabulary emotion terms and a descriptive caption of the emotional state and its multimodal evidence; can be reduced to category/intensity. Built on MER-Caption / EMER datasets (MER2024-SEMI 115k videos). | multimodal | per-clip (windowed) | yes-heavy-gpu | strong | alternative |
| General MLLM as affect annotator (GPT-4o / Qwen2.5-VL-7B/72B with structured prompting) | Window-level fused emotion annotation from frames+ASR+(audio captions): both depicted character emotion and, with appropriate prompts, an estimate of viewer-intended/elicited affect, plus rationale. | Per window (configurable, e.g. per shot or per 2-5 s): JSON with categorical emotion, valence/arousal/dominance estimates (1-5 or -1..1), intensity, confidence, and a free-text justification. Flexible schema; can separately ask for ‘character feels’ vs ‘viewer likely feels’. | multimodal | per-shot / per-window (configurable) | api-only | best-in-class | include |
| Continuous induced-affect regressors (LIRIS-ACCEDE / MediaEval Emotional Impact; AttendAffectNet) | Viewer-ELICITED valence/arousal evoked by a movie segment (not the character’s expressed emotion) — the affect a typical audience feels, trained on continuous self-report + GSR ground truth. | Per second: continuous induced valence and arousal (-1..1) along the movie timeline; some pipelines also predict a fear/emotional-impact flag. AttendAffectNet fuses audio+visual (+deep) features with self-attention to regress continuous V/A. | video-clip | per-second | yes-gpu | usable | include |
Recommended (best-in-class): audeering wav2vec2-large-robust-12-ft-emotion-msp-dim (voice VAD); HSEmotion / EmotiEffLib EfficientNet (face valence/arousal + categorical); RoBERTa-GoEmotions + NRC-VAD mapping (dialogue affect); MERT-based V/A regressor (soundtrack/music affect, if relevant); Qwen2.5-VL-7B or GPT-4o as window-level multimodal fusion/annotator; LIRIS-ACCEDE/MuSe-trained continuous regressor for viewer-ELICITED valence/arousal (kept as a separate stream from all depicted-emotion models)
Likely redundant:
- EmoNet is redundant with HSEmotion for face valence/arousal+categorical — keep HSEmotion as primary (faster, lighter, more permissive license, active ABAW results), EmoNet for cross-validation only
- AffectGPT is redundant with Emotion-LLaMA as the open MLLM fusion/reasoning layer — pick one (AffectGPT for open-vocabulary/descriptive output, Emotion-LLaMA for turnkey benchmarked labels)
- Emotion-LLaMA/AffectGPT overlap with a general MLLM (GPT-4o/Qwen2.5-VL) — the specialized MLLMs add benchmarked depicted-emotion accuracy, the general MLLM adds schema flexibility and elicited-vs-depicted prompting; running all three is wasteful
- EmoBank/NRC-VAD dimensional text regressor partially duplicates RoBERTa-GoEmotions (same text, dimensional vs categorical view) — include both only if cross-modal VAD alignment is needed
- Per-modality stack (wav2vec2 + HSEmotion + GoEmotions) overlaps the internal branches of any fusion MLLM; the interpretable per-modality signals are generally preferable as primary features, with the MLLM as an added fused/reasoned layer rather than a replacement
Open questions:
- Does the project need depicted (character-expressed) emotion, viewer-elicited (induced) emotion, or both? This determines whether the LIRIS-ACCEDE/MuSe-style regressor is mandatory (it is the only viewer-elicited source) versus the depicted-emotion stack.
- Target temporal resolution: true per-second requires sliding-window inference and decisions on hop size and label smoothing; MLLM-per-shot annotation is cheaper but coarser and costlier per second of film.
- Licensing for downstream use: audeering wav2vec2 (CC-BY-NC-SA), EmoNet, and the MLLM-specialized models are non-commercial/research only; if any commercial redistribution is planned, prefer Apache/MIT options (HSEmotion, GoEmotions, Qwen2.5-VL).
- Soundtrack handling: do you need source separation to split musical-score affect (MERT) from speech affect (wav2vec2) when they are mixed in the film audio?
- Ground-truth/validation plan: which human-rated benchmark (SEND, SEWA, MuSe, LIRIS-ACCEDE) will be used to calibrate and cross-check the automated streams, and how will face/voice/text streams be fused (late fusion vs MLLM)?
- Face pipeline dependency: HSEmotion/EmoNet need an upstream detector+tracker (RetinaFace/MediaPipe) and a policy for multi-face scenes (which/aggregate) before producing a scene-level signal.
References
- audeering wav2vec2-large-robust-12-ft-emotion-msp-dim — Wagner et al. 2023 IEEE TPAMI ‘Dawn of Transformer Era in SER’; HF audeering/wav2vec2-large-robust-12-ft-emotion-msp-dim
- HSEmotion / EmotiEffLib (EfficientNet-B0/B2 + MT-MobileFaceNet, AffectNet-trained) — Savchenko, HSEmotion (Software Impacts 2022); ABAW 6/7/8 reports arXiv:2403.11590, 2407.13184; github.com/sb-ai-lab/EmotiEffLib
- EmoNet (face-analysis/emonet, Toisoul et al. Nature Mach. Intell. 2021) — Toisoul et al. 2021 Nat. Mach. Intell.; github.com/face-analysis/emonet
- RoBERTa fine-tuned on GoEmotions (SamLowe/roberta-base-go_emotions) — Demszky et al. GoEmotions ACL 2020; HF SamLowe/roberta-base-go_emotions
- EmoBank / NRC-VAD dimensional text regressor (VAD lexicon + transformer) — Buechel & Hahn EmoBank EACL 2017; Mohammad NRC-VAD ACL 2018
- MERT-based music emotion regressor (MERT-v1-95M/330M embeddings + V/A head) — Li et al. MERT ICLR 2024 (arXiv:2306.00107); DEAM/MediaEval; ‘Unified MER’ arXiv:2502.03979
- Emotion-LLaMA (ZebangCheng/Emotion-LLaMA, NeurIPS 2024) — Cheng et al. arXiv:2406.11161 (NeurIPS 2024); github.com/ZebangCheng/Emotion-LLaMA
- AffectGPT (zeroQiaoba/AffectGPT, ICML 2025) — Lian et al. arXiv:2501.16566 / 2407.07653 (ICML 2025); github.com/zeroQiaoba/AffectGPT
- General MLLM as affect annotator (GPT-4o / Qwen2.5-VL-7B/72B with structured prompting) — Qwen2.5-VL arXiv:2502.13923; EmoBench-M arXiv:2502.04424; MMAFFBen arXiv:2505.24423; GPT-4o (OpenAI)
- Continuous induced-affect regressors (LIRIS-ACCEDE / MediaEval Emotional Impact; AttendAffectNet) — Baveye et al. LIRIS-ACCEDE IEEE TAC 2015; MediaEval Emotional Impact of Movies 2016/2018; AttendAffectNet arXiv:2010.11188; MuSe/SEWA benchmarks
Redundancy & Parsimony Analysis
Redundancy & Parsimony Analysis: Computational Narrative Feature Extraction
This analysis identifies redundant or highly-correlated feature extractors across the 16 catalogs, recommends a single best-in-class option per redundancy group, flags cases where keeping two is justified by genuinely distinct information, and ends with a MINIMAL tractable set vs. a FULL set.
A. WITHIN-SUBCLASS REDUNDANCY
These are pairs/groups producing near-identical outputs. Keep one; the rest are interchangeable variants.
| # | Redundant group | Why correlated | KEEP (best-in-class) | Drop / conditional |
|---|---|---|---|---|
| 1 | scikit-image vs OpenCV (color/edge/FFT) | Same pixel-level operators | scikit-image + OpenCV together as one engine (OpenCV for decode/speed, skimage for stats) | Treat as a single tool, not two features |
| 2 | OpenCV Gabor bank vs pyrtools vs GIST | All = oriented spatial-frequency energy | pyrtools (principled steerable pyramid) | GIST only if a compact fixed-length holistic vector is wanted; drop OpenCV Gabor |
| 3 | Global entropy / edge density vs Rosenholtz clutter | Weak vs strong clutter proxies | Rosenholtz Feature-Congestion + Subband-Entropy | Entropy/edge density redundant as clutter |
| 4 | OpenCLIP vs SigLIP 2 | Same open-vocab text-probe role | SigLIP 2 | Drop OpenCLIP (run one) |
| 5 | Places365 / ImageNet CNNs vs CLIP/SigLIP probing | Same scene/object categories | SigLIP 2 zero-shot probe | Keep Places365 ONLY for a frozen, reproducible fixed-taxonomy vector |
| 6 | Qwen2.5/3-VL vs InternVL3 vs LLaVA-OneVision vs VideoLLaMA3 | Interchangeable captioning/VQA VLMs | Qwen-VL (2.5 image / 3 video) | Drop others as primary; one API judge for validation only |
| 7 | YOLO-World vs Grounding DINO | Same open-vocab detection | Grounding DINO (accuracy) | YOLO-World only when per-frame throughput is the bottleneck |
| 8 | SAM 2 vs Mask2Former (static panoptic) | Overlapping segmentation | Mask2Former for composition | SAM 2 only for video-consistent tracking / Grounded-SAM |
| 9 | OpenFace 2.0 vs OpenFace 3.0 | Superseded | OpenFace 3.0 | OpenFace 2.0 legacy/AU-scale reproduction only |
| 10 | Standalone RetinaFace vs InsightFace | InsightFace bundles RetinaFace | InsightFace buffalo_l | Drop standalone RetinaFace |
| 11 | ViTPose vs MMPose RTMPose/RTMW | Same keypoints | MMPose RTMPose/RTMW | ViTPose only for max-accuracy on strong GPU |
| 12 | EmoNet vs HSEmotion (face V/A) | Same face valence-arousal | HSEmotion/EmotiEffLib | EmoNet citable cross-check only |
| 13 | RAFT / MemFlow / VideoFlow / Farneback vs SEA-RAFT | Same dense flow | SEA-RAFT | MemFlow if temporal smoothing needed; Farneback = no-GPU fallback |
| 14 | AutoShot vs TransNetV2 | Same shot detection | TransNetV2 (+ PySceneDetect CPU prior) | Drop AutoShot |
| 15 | TimeSformer/VideoSwin/SlowFast vs VideoMAE V2 / InternVideo2 | Superseded clip classifiers | VideoMAE V2 (+ InternVideo2) | Legacy backbones for TAL features only |
| 16 | X-CLIP vs InternVideo2 zero-shot | Same open-vocab action role | X-CLIP (cheap default) | InternVideo2 zero-shot if accuracy needed |
| 17 | MiDaS/DPT/ZoeDepth vs Depth-Anything-V2 | Superseded depth | Depth-Anything-V2 | Apple Depth Pro only if metric (meters) needed |
| 18 | TASED-Net / UNISAL vs ViNet | Same video saliency | ViNet (ViNet-A for audio-visual) | UNISAL if lightest needed |
| 19 | DeepGaze (static) vs ViNet (video) | Static superseded for video | ViNet | DeepGaze only as pure static attention proxy |
| 20 | LAION-Aesthetics / CLIP-IQA vs Q-Align | Subsumed by Q-Align IAA/IQA | Q-Align/OneAlign | Lightweight fallbacks when 7B LMM too heavy |
| 21 | torchaudio / Essentia spectral/MFCC vs librosa | Same DSP descriptors | librosa (primary MIR) | Essentia for roughness/beat; torchaudio if GPU/differentiable |
| 22 | librosa pyin / torchaudio pitch vs CREPE/Praat | Weaker F0 | Praat (Parselmouth) + CREPE | Drop pyin/torchaudio F0 |
| 23 | openSMILE jitter/shimmer/HNR vs Parselmouth | Same voice quality | Parselmouth (canonical) | openSMILE for standardized 88-dim eGeMAPS summary |
| 24 | AST / PANNs / YAMNet vs BEATs / PaSST | Highly correlated 527-class | BEATs (one primary tagger) | PANNs for framewise SED; YAMNet CPU-only |
| 25 | Essentia tempo vs madmom/Beat This! | Overlapping beat tracking | madmom / Beat This! | Essentia tempo redundant |
| 26 | openai-whisper / whisper.cpp / insanely-fast vs faster-whisper | Same weights | faster-whisper + WhisperX | Drop variants |
| 27 | pyannote 3.1 vs community-1 | Superseded | pyannote community-1 (3.1 in social catalog) | Use newest |
| 28 | WebRTC VAD / audiotok vs Silero/pyannote VAD | Superseded | Silero VAD | Drop old VADs |
| 29 | NeMo ASR / emotion2vec+ vs Whisper/audEERING | Overlapping defaults | faster-whisper + audEERING wav2vec2-dim | NeMo for multilingual; emotion2vec+ for categorical |
| 30 | NLTK vs spaCy | Superseded | spaCy (trf) | NLTK for WordNet synsets only |
| 31 | surprisal pkg vs minicons | Direct overlap | minicons | Pick one |
| 32 | ANEW / MRC / Glasgow vs Warriner / Brysbaert / Kuperman | Smaller-coverage norm sets | Warriner VAD + Brysbaert + Kuperman + NRC-VAD | Glasgow for imageability/size only |
| 33 | Trankit / supar vs spaCy / Stanza / benepar | Overlapping parsers | spaCy + Stanza + benepar | Trankit for multilingual seg; supar for CRF marginals |
| 34 | fastcoref vs Maverick | Superseded on accuracy | Maverick | fastcoref for high-throughput batch |
| 35 | textstat vs L2SCA | Surface vs deep complexity | L2SCA (+ Yngve/Frazier) | textstat cheap sliding-window scalar |
| 36 | multilingual-e5 / GTE / BGE / API embeddings vs Qwen3-Embedding | Interchangeable encoders | Qwen3-Embedding | Pick one; API only if leaderboard-topping required |
| 37 | DMRST / TRIPOD turning-point vs instruction-tuned LLM | Subsumed by LLM extractor | Instruction-tuned LLM | Supervised models only when reproducible labels required |
| 38 | BrainIAK HMM vs GSBS | Overlapping boundary detectors | GSBS (auto-K, faster) | HMM for soft event posteriors / reactivation |
| 39 | MovieCLIP vs zero-shot CLIP / Places365 | Overlapping setting taxonomy | SigLIP/CLIP zero-shot | MovieCLIP if 179-cinematic taxonomy fits |
| 40 | TalkNet-ASD vs Light-ASD/LR-ASD | Same task, 23x params | Light-ASD/LR-ASD | Drop TalkNet |
| 41 | OpenPose vs RTMPose/ViTPose | Legacy | RTMPose | Drop OpenPose |
| 42 | Emotion-LLaMA vs AffectGPT vs general MLLM | Overlapping fusion/reasoning | One MLLM (Qwen-VL or GPT-4o) | Emotion-LLaMA for benchmarked labels; AffectGPT for open-vocab — not all three |
B. CROSS-SUBCLASS REDUNDANCY
These span catalogs and are the easiest savings to miss. The same underlying model or signal appears in multiple subclasses.
| # | Appears in | Redundant signal | Resolution |
|---|---|---|---|
| C1 | Faces, Affect, Social | Face emotion (Py-Feat / OpenFace 3.0 / HSEmotion / EmoNet) | Run HSEmotion once; feed its output to both the Affect and Social streams. Do not extract face emotion separately per catalog. |
| C2 | Faces, Speech, Affect | Vocal affect (HSEmotion audio? no — audEERING wav2vec2-dim) appears in Audio-Speech AND Affect | Run audEERING wav2vec2-dim once; shared by both |
| C3 | Speech, Social | Diarization (pyannote) | One pyannote run feeds ASR-speaker-attribution AND social speaker-turn graph |
| C4 | Speech, Low-level acoustic, Affect | Prosody / F0 / voice quality (Parselmouth, openSMILE) | One Parselmouth + openSMILE pass; resample for all three uses |
| C5 | Faces, Social, Dynamic visual | Body pose (MMPose RTMPose) | One pose run feeds gesture (Faces), proximity/touch (Social) |
| C6 | Faces, Social | Gaze (OpenFace 3.0 / L2CS-Net / Gaze-LLE) | L2CS-Net or Gaze-LLE once; mutual-gaze/joint-attention derived for Social |
| C7 | Faces, Social, High-level objects | Face detect + identity (InsightFace) | Single InsightFace track feeds character count/identity everywhere |
| C8 | Dynamic visual, Situation, Action | Shot/cut boundaries (TransNetV2 / PySceneDetect) | Run once; shared event-boundary prior |
| C9 | Action, Situation, Social, Affect | Video-LLM (Qwen-VL) | A single Qwen-VL pass per shot with a combined multi-field prompt schema (action + situation + social + emotion) replaces 4 separate VLM passes. This is the single biggest compute saving. |
| C10 | High-level visual, Situation | Scene/place recognition (CLIP / Places365) | One SigLIP probe with merged label set |
| C11 | Semantics, Affect (text), Lexical | Text/dialogue affect (RoBERTa-GoEmotions, NRC-VAD, Warriner) | Lexical-VAD lookups and GoEmotions shared across Language-Lexical and Affect |
| C12 | Semantics, Affect | Autoregressive LLM surprisal (minicons GPT-2/Llama) | One extractor; surprisal used as both a comprehension feature (Semantics) and arousal proxy |
| C13 | Audio high-level, Audio low-level, Affect | Music emotion / MIR (Essentia, MERT) | Essentia run once feeds tempo/key (MIR) and valence-arousal (Affect) |
| C14 | Low-level visual, High-level visual | CLIP early-layer / orientation energy | Analytic features (pyrtools) for low-level; CLIP reserved for mid/high-level — do not duplicate |
C. CASES WHERE KEEPING TWO IS JUSTIFIED (distinct information)
These are NOT redundant despite surface similarity — log as separate streams.
| Pair | Why both are needed |
|---|---|
| DINOv2/DINOv3 + SigLIP 2 | Label-free self-supervised dense embedding (RSA, no human labels) vs. text-probed interpretable concept scores — genuinely different representations |
| VideoMAE V2 + V-JEPA 2 | Both self-supervised, but V-JEPA 2 is motion/temporal-biased; complementary, not duplicate |
| pymoten (motion energy) + SEA-RAFT (optical flow) | Brain-aligned filter-bank regressor vs. interpretable camera-vs-object flow decomposition — different scientific uses |
| Action recognition (VideoMAE) + low-level motion (SEA-RAFT/pymoten) | Semantic “what action” vs. pre-semantic “how much motion” — deliberately separate layers |
| Depicted emotion (face/voice/text models) + ELICITED emotion (LIRIS-ACCEDE/MuSe regressor) | Character-expressed affect vs. viewer-induced affect — correlated but NOT interchangeable; must be distinct streams |
| Per-modality affect (wav2vec2 + HSEmotion + GoEmotions) + fusion MLLM | Interpretable unimodal signals as primary features; MLLM as an added reasoned/fused layer, not a replacement |
| CREPE + Parselmouth F0 | CREPE cleaner on noisy/creaky speech; Praat canonical for voice-science jitter/shimmer/HNR — keep Praat for voice quality, CREPE for robust pitch contour |
| openSMILE eGeMAPS + Parselmouth | openSMILE’s standardized 88-dim affective-computing summary vs. Praat’s per-frame canonical contours |
| Mask2Former (panoptic composition) + Grounding DINO (object boxes/counts) | Area-fraction scene composition vs. discrete object presence/counts/positions |
| BEATs (AudioSet events) + CLAP (open-vocab prompts) | Fixed 527-class calibrated tagging vs. arbitrary custom text-prompt similarity |
| inaSpeechSegmenter + pyannote diarization | Speech/music/noise segmentation vs. who-spoke-when speaker turns — different partitions |
| Lexical surprisal (LLM) + lexical norms (frequency/concreteness/AoA/VAD) | Contextual prediction-error vs. context-free word properties |
| spaCy (CLEAR/fast) + Stanza (true UD) | Justified only if cross-tool UD-scheme complexity metrics are needed; otherwise pick one |
| GSBS + BrainIAK HMM | GSBS for hard boundaries/auto-K; HMM only if soft posteriors / reactivation modeling is the goal |
D. RECOMMENDED FEATURE SETS
MINIMAL TRACTABLE SET (single consumer GPU + CPU; one-pass-per-modality)
The design principle: one extractor per signal, one shared VLM pass, maximal cross-subclass reuse.
Visual — low-level
- scikit-image + OpenCV core (luminance, RMS contrast, RGB/HSV/CIELAB color, entropy, edges, FFT slope, Hasler-Susstrunk colorfulness)
- pyrtools steerable pyramid (orientation × scale energy)
Visual — dynamic
- SEA-RAFT (flow magnitude, camera-vs-object decomposition)
- pymoten (brain-aligned motion energy regressor)
- TransNetV2 + PySceneDetect (shot boundaries — shared with Situation/Action)
Visual — high-level / faces / action / saliency
- SigLIP 2 (object/scene/place/attribute probes — shared with Situation)
- DINOv2 (label-free embedding for RSA)
- InsightFace (face count/identity — shared with Social)
- OpenFace 3.0 (landmarks + AUs + gaze + emotion in one model — shared with Affect/Social)
- MMPose RTMPose (pose — shared with Social)
- VideoMAE V2 (sliding-window action posteriors + embeddings) + X-CLIP (zero-shot action)
- ViNet (video saliency) + Depth-Anything-V2 (depth)
Audio
- librosa (spectral/MFCC/chroma/onset/tempo)
- openSMILE eGeMAPS (standardized 88-dim LLDs)
- Parselmouth (F0, formants, jitter/shimmer/HNR)
- BEATs (AudioSet events) + CLAP (open-vocab prompts)
- inaSpeechSegmenter (speech/music/noise)
- faster-whisper + WhisperX (ASR + word alignment)
- pyannote community-1 (diarization — shared with Social)
- audEERING wav2vec2-dim (vocal A/V/D — shared with Affect)
Language
- spaCy trf (POS/dep/NER/morph)
- wordfreq + Brysbaert + Kuperman + Warriner/NRC-VAD + NRC-EmoLex (lexical norms — shared with Affect)
- minicons GPT-2/Llama (surprisal + hidden states — shared with Semantics)
- benepar + L2SCA (syntactic complexity)
- Maverick (coreference)
- Qwen3-Embedding + sliding-window coherence/drift + BERTopic (semantics)
- RoBERTa-GoEmotions (dialogue affect — shared with Affect)
Situation / Social / Affect (mostly via shared passes)
- GSBS (data-driven event segmentation on the embedding timeline)
- Light-ASD (active speaker)
- Gaze-LLE (joint/mutual attention)
- HSEmotion (face V/A — or reuse OpenFace 3.0 emotion)
- One Qwen2.5/3-VL pass per shot with a combined JSON schema covering: dense action/event description + situational dimensions (space/time/causation/intention/protagonist) + social interaction-type/dominance/affiliation/ToM + depicted emotion. This single pass replaces four separate VLM deployments.
- LIRIS-ACCEDE/MuSe regressor (viewer-ELICITED valence/arousal — kept separate from all depicted-emotion streams)
This set covers every subclass, runs on one GPU, and eliminates all Section A/B redundancy.
FULL SET (add when resources / specific accuracy needs justify)
Add to the minimal set, only where a distinct signal or best-in-class accuracy is wanted:
- Visual low-level: SHINE (literature-matched canonical defs), Rosenholtz clutter, GIST (compact holistic vector)
- Visual high-level: Grounding DINO (object boxes/counts), Mask2Former (panoptic composition), SAM 2 (video-tracked masks/Grounded-SAM), Places365 (frozen fixed taxonomy)
- Dynamic: MemFlow (temporally smooth flow)
- Action: InternVideo2-1B (stronger zero-shot/features), V-JEPA 2 (motion-biased complement), ActionFormer/OpenTAD (timestamped TAL segments), VideoLLaMA3 (alt video-LLM)
- Saliency/depth: Q-Align (aesthetic + quality), ResMem (memorability), Apple Depth Pro (metric depth), DeepGaze IIE (static fixation)
- Faces: Py-Feat (validated AU detector set), L2CS-Net (best-in-class 360° gaze), MediaPipe (CPU redundancy layer), InsightFace age/sex
- Audio: Essentia + madmom/Beat This! (roughness/beat/MIR + calibrated mood/genre/valence-arousal), CREPE (robust F0), mosqito (standards-based psychoacoustic roughness/loudness/sharpness), MERT music-emotion regressor
- Speech: emotion2vec+ (categorical SER), NeMo Parakeet/Canary (multilingual ASR cross-check)
- Language: Stanza (true UD scheme), GLiNER (custom entity types), LIWC-22 (validated pronoun/function-word scheme), DMRST (reproducible RST discourse labels), supervised TRIPOD turning-points, textstat (cheap readability), dialogue-act classifier
- Semantics: API embeddings (OpenAI/Voyage/Gemini) if leaderboard-topping required
- Situation: BrainIAK HMM (soft event posteriors / reactivation), MovieCLIP (cinematic-taxonomy settings), Llama-3-70B/GPT-4 transcript event segmentation
- Social: InternVL3 or a frontier API as a second VLM for agreement/gold-label validation; Social-IQ 2.0 / Social Genome schemas for prompt construction
- Affect: Emotion-LLaMA OR AffectGPT (benchmarked / open-vocab depicted-emotion reasoning), EmoNet (cross-check), EmoBank-VAD text regressor, AttendAffectNet (fused induced V/A)
E. KEY TAKEAWAYS
- The single largest saving is consolidating all high-level reasoning into ONE video-LLM pass (C9). Action, situation, social, and depicted-emotion semantics are all currently realized by Qwen-VL-class models — run it once per shot with a unified multi-field prompt rather than four times.
- Cross-subclass sharing (Section B) saves more than within-subclass pruning. Face emotion, gaze, pose, diarization, prosody, shot boundaries, surprisal, and lexical-VAD each appear in 2-4 catalogs — extract once, route everywhere.
- Foundation models supersede most fixed-taxonomy CNNs (Places365, ImageNet, YAMNet, AST) — keep the latter only when a frozen, calibrated, reproducible probability vector is explicitly required.
- The one non-negotiable “keep two” is depicted vs. elicited emotion — these are different constructs and conflating them is a scientific error, not a redundancy.
Recommendations — Best-in-Class Feature Set
This section translates the feature catalogs and redundancy analysis into actionable per-class recommendations. For each feature class (and its subclasses), it names the best-in-class tool(s), the key features to extract, whether the tool runs locally, and a priority tier: core (the minimal tractable set — one extractor per signal, maximal cross-subclass reuse, single consumer GPU + CPU) or extended (added only when resources or a specific accuracy/distinct-signal need justifies it).
Recommendation table
| Class | Subclass | Recommended tool | Output features | Local? | Priority |
|---|---|---|---|---|---|
| Visual | Low-level static visual | scikit-image + OpenCV + NumPy | Mean luminance, RMS contrast, RGB/HSV/CIELAB color means+stds, Shannon entropy, edge density, FFT power-spectrum slope, GLCM/LBP texture | Yes (CPU) | core |
| Visual | Low-level static visual | Hasler-Susstrunk colorfulness (direct impl) | Colorfulness index M, mean a*/b* (warmth), HSV saturation, dominant hue | Yes (CPU) | core |
| Visual | Low-level static visual | pyrtools / plenoptic steerable pyramid | Per-subband (scale x orientation) energy; orientation-energy and spatial-frequency-band vectors | Yes (CPU) | core |
| Visual | Low-level static visual | SHINE (+ SHINE_color) | Canonical luminance, RMS contrast, 1-D rotationally averaged Fourier amplitude spectrum + slope | Yes (CPU) | extended |
| Visual | Low-level static visual | Rosenholtz Feature-Congestion + Subband-Entropy | Global clutter scalars (+ per-pixel map), color/contrast/orientation clutter sub-components | Yes (CPU) | extended |
| Visual | Low-level static visual | GIST / spatial envelope | Fixed-length holistic orientation-energy descriptor (e.g. 512-d) | Yes (CPU) | extended |
| Visual | High-level static visual (objects/scenes/places/attributes) | SigLIP 2 | Image embedding (768-1152-d) + per-prompt sigmoid scores for object/scene/place/attribute label sets | Yes (GPU) | core |
| Visual | High-level static visual | DINOv2 / DINOv3 | Label-free CLS embedding (384-1536-d) + dense patch maps for RSA / linear probes | Yes (GPU) | core |
| Visual | High-level static visual | Grounding DINO 1.5/1.6 | Per-object boxes + free-text labels + confidence (presence/counts/positions) | Yes (GPU) | extended |
| Visual | High-level static visual | Mask2Former / OneFormer (panoptic) | Per-pixel class map + instance masks; per-category area fractions, object counts, composition vector | Yes (GPU) | extended |
| Visual | High-level static visual | SAM 2 | Video-tracked instance masks (sizes, counts, track IDs); label via Grounded-SAM | Yes (GPU) | extended |
| Visual | High-level static visual | Places365 ResNet-50 | Frozen 365-d scene posterior + indoor/outdoor + scene attributes (reproducible fixed taxonomy) | Yes (CPU) | extended |
| Visual | Faces / bodies / gaze / expression | OpenFace 3.0 | Landmarks + FACS AUs + eye-gaze + head pose + discrete emotion (one multitask model) | Yes (CPU) | core |
| Visual | Faces / bodies / gaze / expression | InsightFace buffalo_l (RetinaFace + ArcFace) | Face bbox + score, 5/106/68 landmarks, head pose, age/sex, 512-d identity embedding | Yes (GPU) | core |
| Visual | Faces / bodies / gaze / expression | MMPose RTMPose / RTMW | 17 body or 133 whole-body keypoints (x,y,conf) per person -> posture, gesture, orientation | Yes (GPU) | core |
| Visual | Faces / bodies / gaze / expression | Py-Feat | Validated AU probabilities/intensities + 7 emotions + head pose (Fex time series) | Yes (GPU) | extended |
| Visual | Faces / bodies / gaze / expression | L2CS-Net | High-accuracy gaze yaw/pitch -> 3D gaze vector | Yes (GPU) | extended |
| Visual | Faces / bodies / gaze / expression | MediaPipe Tasks | 478 face-mesh landmarks, 52 blendshapes, 33 body + 21 hand landmarks (CPU redundancy layer) | Yes (CPU) | extended |
| Visual | Dynamic visual (motion/flow) | SEA-RAFT | Dense (u,v) flow -> mean flow magnitude, orientation histogram, camera-vs-object motion decomposition | Yes (GPU) | core |
| Visual | Dynamic visual | pymoten | High-dim motion-energy filter-bank vector (brain-aligned encoding regressor) | Yes (CPU) | core |
| Visual | Dynamic visual | TransNetV2 + PySceneDetect | Per-frame cut probability, shot boundaries/segments, per-frame visual-change score | Yes (GPU/CPU) | core |
| Visual | Dynamic visual | MemFlow | Temporally coherent (u,v) flow (lower frame-to-frame jitter) | Yes (GPU) | extended |
| Visual | Action / activity recognition | VideoMAE V2 | Per-window Kinetics-400/710 posteriors + pooled embedding (sliding 16-frame window) | Yes (GPU) | core |
| Visual | Action / activity recognition | X-CLIP | Per-window zero-shot similarity scores over arbitrary action-phrase vocabulary | Yes (GPU) | core |
| Visual | Action / activity recognition | InternVideo2-1B | Stronger zero-shot action similarity + features for TAL | Yes (heavy GPU) | extended |
| Visual | Action / activity recognition | V-JEPA 2 | Motion/temporal-biased self-supervised window embeddings + action posteriors | Yes (GPU) | extended |
| Visual | Action / activity recognition | ActionFormer / OpenTAD | (start, end, action_class, confidence) timestamped action segments | Yes (GPU) | extended |
| Visual | Saliency / attention / aesthetics / depth | ViNet (ViNet-A audio-visual) | Per-frame saliency map -> concentration/entropy, peak, salient-area fraction, temporal shift | Yes (GPU) | core |
| Visual | Saliency / attention / aesthetics / depth | Depth-Anything-V2 | Per-frame depth map -> mean/range depth, foreground fraction, depth entropy/gradient | Yes (GPU) | core |
| Visual | Saliency / attention / aesthetics / depth | Q-Align / OneAlign | Per-frame aesthetic + technical-quality scalars | Yes (heavy GPU) | extended |
| Visual | Saliency / attention / aesthetics / depth | ResMem | Per-frame memorability scalar (0-1) | Yes (CPU) | extended |
| Visual | Saliency / attention / aesthetics / depth | Apple Depth Pro | Per-frame metric (meters) depth map + true-scale foreground distance | Yes (GPU) | extended |
| Audio | Low-level acoustic | librosa | RMS, spectral centroid/bandwidth/rolloff/flatness/contrast, ZCR, MFCC+deltas, chroma, tonnetz, onset envelope, tempo/beats, pYIN F0, HPSS | Yes (CPU) | core |
| Audio | Low-level acoustic | openSMILE eGeMAPS (opensmile-python) | ~10 ms LLDs (F0, loudness, jitter, shimmer, HNR, spectral, MFCC1-4, formants) + 88-d eGeMAPS functionals | Yes (CPU) | core |
| Audio | Low-level acoustic | Parselmouth (Praat) | F0/intensity/formant contours; jitter/shimmer/HNR voice quality; spectral moments, CPP | Yes (CPU) | core |
| Audio | Low-level acoustic | Essentia | Dissonance/roughness, inharmonicity, HPCP, spectral complexity, robust beat tracking | Yes (CPU) | extended |
| Audio | Low-level acoustic | CREPE / torchcrepe | High-accuracy per-frame F0 + voicing confidence + salience matrix | Yes (GPU) | extended |
| Audio | Low-level acoustic | mosqito | Standards-based loudness (sones), sharpness (acum), roughness (asper), fluctuation strength | Yes (CPU) | extended |
| Audio | High-level audio (events/scenes/music/speech) | BEATs | 527-d AudioSet event/scene probabilities per window + 768-d embeddings | Yes (GPU) | core |
| Audio | High-level audio | CLAP (general + music_and_speech) | 512-d joint audio/text embeddings; per-prompt cosine-similarity time series; zero-shot labels | Yes (GPU) | core |
| Audio | High-level audio | inaSpeechSegmenter | Timestamped speech/music/noise segments (+ optional speaker sex) | Yes (CPU) | core |
| Audio | High-level audio | Essentia + TF models | BPM/tempo, key+scale, genre/mood tags, valence-arousal regressors, instrument tags | Yes (CPU) | extended |
| Audio | High-level audio | madmom / Beat This! | Beat + downbeat timestamps, per-beat tempo curve, meter, chord labels | Yes (CPU) | extended |
| Audio | High-level audio | PANNs CNN14 | Framewise (tens-of-ms) SED for onset/offset localization | Yes (GPU) | extended |
| Audio | Speech (ASR/diarization/prosody/affect) | faster-whisper (large-v3) + WhisperX | Segment + word-level text and timestamps, confidences, language, speaking rate | Yes (GPU) | core |
| Audio | Speech | pyannote diarization (community-1 / 3.1) | Speaker-turn segments, per-frame speech-activity, overlap flags, speaker embeddings | Yes (GPU) | core |
| Audio | Speech | Parselmouth (Praat) | Per-frame F0, intensity, formants F1-F4, HNR, jitter, shimmer; pause structure | Yes (CPU) | core |
| Audio | Speech | audEERING wav2vec2-large-robust-12-ft-emotion-msp-dim | Per-window arousal/valence/dominance scalars + 1024-d voice-affect embedding | Yes (GPU) | core |
| Audio | Speech | Silero VAD v5 | ~30 ms speech probability; speech/non-speech boundaries; total speech time | Yes (CPU) | core |
| Audio | Speech | emotion2vec+ | 9-class categorical emotion posteriors + emotion embeddings | Yes (GPU) | extended |
| Audio | Speech | NeMo Parakeet-TDT / Canary + Sortformer | Word/segment ASR + timestamps; per-frame speaker activity (multilingual cross-check) | Yes (GPU) | extended |
| Audio | Speech | CREPE / torchcrepe | Robust per-frame F0 + voicing on noisy/creaky speech | Yes (GPU) | extended |
| Language | Low-level lexical / word-level | spaCy (en_core_web_trf) | Per-token lemma, UPOS + fine POS, dependency, NER, is_stop/punct flags | Yes (CPU) | core |
| Language | Low-level lexical | wordfreq + SUBTLEX-US | Zipf/log frequency, contextual diversity, %known | Yes (CPU) | core |
| Language | Low-level lexical | Brysbaert concreteness + Kuperman AoA + Warriner/NRC-VAD | Per-word concreteness, AoA, valence/arousal/dominance | Yes (CPU) | core |
| Language | Low-level lexical | NRC EmoLex | 8 binary emotion associations + pos/neg sentiment per word | Yes (CPU) | core |
| Language | Low-level lexical | minicons (GPT-2-medium / Pythia) | Per-token surprisal (bits), next-word entropy, log-prob (whole-word aggregated) | Yes (GPU) | core |
| Language | Low-level lexical | LIWC-22 | Validated pronoun/person-reference + function-word categories; segment summary scores | Yes (CPU) | extended |
| Language | Low-level lexical | GLiNER | User-defined entity-type spans + confidence | Yes (GPU) | extended |
| Language | Syntactic / grammatical structure | spaCy (en_core_web_trf) | Dependency labels+heads, POS, morphology (tense/aspect/mood), noun chunks | Yes (CPU) | core |
| Language | Syntactic structure | benepar | PTB constituency trees -> tree depth, clause counts, phrase-type counts | Yes (GPU) | core |
| Language | Syntactic structure | L2SCA (+ Yngve/Frazier from trees) | 14 complexity indices (clause/T-unit ratios, dependent clauses, complex nominals) | Yes (CPU) | core |
| Language | Syntactic structure | Maverick | Coreference clusters (who/what each mention refers to) | Yes (GPU) | core |
| Language | Syntactic structure | Stanza | True UD-scheme dependencies + UFeats + constituency (cross-tool complexity metrics) | Yes (GPU) | extended |
| Language | Syntactic structure | textstat | Flesch-Kincaid/Gunning Fog/SMOG readability over sliding windows | Yes (CPU) | extended |
| Language | Syntactic structure | Dialogue-act classifier (DialogTag/SwDA) | Per-utterance DA label (statement/question/backchannel/…) + probabilities | Yes (CPU) | extended |
| Language | High-level semantics / discourse / narrative | Qwen3-Embedding (0.6B; scale to 4B/8B) | Per-window L2-normalized embedding (1024-4096-d, Matryoshka-truncatable) | Yes (GPU) | core |
| Language | Semantics / discourse / narrative | Sliding-window cosine pipeline | Per-timepoint local coherence, semantic drift, novelty, segmentation-boundary score | Yes (CPU) | core |
| Language | Semantics / discourse / narrative | BERTopic (topics-over-time) | Per-window topic id + soft topic distribution; per-bin topic prevalence series | Yes (CPU) | core |
| Language | Semantics / discourse / narrative | Autoregressive LLM hidden-state extractor (GPT-2 / Llama-3.1-8B) | Per-token hidden-state vector + surprisal/entropy, resampled per TR (neuroimaging standard) | Yes (GPU) | core |
| Language | Semantics / discourse / narrative | Instruction-tuned LLM (Llama-3.1-8B / Qwen2.5-7B-Instruct) | Structured JSON: narrative stage, turning points, discourse relations, tension, summary | Yes (GPU) | core |
| Language | Semantics / discourse / narrative | DMRST (neural RST parser) | EDU boundaries + discourse-tree relations/nuclearity (reproducible labels) | Yes (GPU) | extended |
| Language | Semantics / discourse / narrative | Supervised turning-point model (TRIPOD-style) | 5 TP-type probabilities, normalized story position, sentiment-arc value | Yes (GPU) | extended |
| Language | Semantics / discourse / narrative | API embeddings (OpenAI/Voyage/Gemini) | Leaderboard-topping per-window embedding (only if off-site transfer acceptable) | API-only | extended |
| Social | Social & interpersonal | pyannote diarization (3.1 / community-1) | Speaker turns, active-speaker id, speaker count, overlap, turn-taking transitions | Yes (GPU) | core |
| Social | Social & interpersonal | Light-ASD / LR-ASD | Per-face per-frame speaking probability -> active speaker, speaker-vs-listener roles | Yes (GPU) | core |
| Social | Social & interpersonal | InsightFace (SCRFD + ArcFace) | Face count, identity track, characters-present set, co-presence matrix (social-network seed) | Yes (GPU) | core |
| Social | Social & interpersonal | Gaze-LLE | Per-person gaze target + heatmap -> mutual-gaze and joint-attention signals | Yes (GPU) | core |
| Social | Social & interpersonal | MMPose RTMPose + ByteTrack | Tracked skeletons -> inter-personal distance, body orientation, contact/touch proxy, approach/retreat | Yes (GPU) | core |
| Social | Social & interpersonal | Qwen2.5-VL (7B) | Per-shot interaction-type, dominance/affiliation, ToM/intention, addressee, social-network edges | Yes (heavy GPU) | core |
| Social | Social & interpersonal | Social-IQ 2.0 / Social Genome (schema) | Label schemas, few-shot exemplars, held-out evaluation items for the social extractor | Yes (CPU) | core |
| Social | Social & interpersonal | InternVL3 or frontier API judge | Second VLM for inter-rater agreement / gold-label validation | GPU / API | extended |
| Situation | Situations / schemas / event segmentation | GSBS / statesegmentation | Per-timepoint state label, ranked boundaries, optimal K, hierarchical boundaries | Yes (CPU) | core |
| Situation | Situations / event segmentation | TransNetV2 + PySceneDetect | Shot-boundary timestamps + per-frame change score (event-boundary prior) | Yes (GPU/CPU) | core |
| Situation | Situations / event segmentation | SigLIP 2 / OpenCLIP zero-shot | Per-frame location/time-of-day/setting/script attribute scores (custom taxonomy) | Yes (GPU) | core |
| Situation | Situations / event segmentation | Qwen2.5-VL (7B) | Timestamped event boundaries + Event-Indexing fields (space/time/causation/intention/protagonist), script labels | Yes (GPU) | core |
| Situation | Situations / event segmentation | Instruction-tuned / API LLM (transcript) | Transcript event boundaries + per-event situational fields (esp. audio stories) | Yes (heavy GPU) / API | core |
| Situation | Situations / event segmentation | Places365 ResNet-50 | Calibrated fixed-taxonomy location/setting + scene attributes | Yes (GPU) | extended |
| Situation | Situations / event segmentation | BrainIAK EventSegment HMM | Soft event posteriors / reactivation modeling | Yes (CPU) | extended |
| Affect | Emotion & affect (multimodal) | audEERING wav2vec2-dim | Per-window voice arousal/valence/dominance + 1024-d embedding | Yes (GPU) | core |
| Affect | Emotion & affect | HSEmotion / EmotiEffLib | Per-face categorical emotion + continuous valence/arousal + 1280-d embedding | Yes (CPU) | core |
| Affect | Emotion & affect | RoBERTa-GoEmotions (+ NRC-VAD mapping) | Per-utterance 28 emotion scores -> valence/arousal proxy | Yes (CPU) | core |
| Affect | Emotion & affect | Qwen2.5-VL (7B) | Per-window JSON: categorical emotion, V/A/D, intensity, confidence, justification (depicted vs viewer) | Yes (GPU) | core |
| Affect | Emotion & affect | LIRIS-ACCEDE / MuSe-trained continuous regressor | Per-second viewer-ELICITED valence/arousal (kept as a SEPARATE stream) | Yes (GPU) | core |
| Affect | Emotion & affect | MERT-based V/A regressor | Per-window soundtrack/music valence/arousal + mood tags | Yes (GPU) | extended |
| Affect | Emotion & affect | Emotion-LLaMA or AffectGPT | Per-clip benchmarked / open-vocabulary depicted-emotion label + multimodal-cue explanation | Yes (heavy GPU) | extended |
| Affect | Emotion & affect | EmoNet | Per-face valence/arousal + expression (citable cross-check) | Yes (GPU) | extended |
Tradeoffs
Cross-subclass reuse dominates within-subclass pruning. Several signals appear in multiple classes and must be extracted once and routed everywhere, not re-run per catalog. The clearest cases: face detection/identity (InsightFace) feeds Faces, Social, and High-level visual; face emotion (HSEmotion/OpenFace 3.0) feeds Faces, Social, and Affect; body pose (MMPose RTMPose) feeds Faces, Social, and Dynamic visual; diarization (pyannote) feeds Speech and Social; prosody (Parselmouth + openSMILE) feeds Speech, Low-level acoustic, and Affect; shot boundaries (TransNetV2/PySceneDetect) feed Dynamic visual, Situation, and Action; vocal affect (audEERING wav2vec2-dim) feeds Speech and Affect; LLM surprisal and hidden states (minicons) feed Lexical and Semantics; and lexical-VAD/GoEmotions feed Lexical and Affect. Wiring the pipeline around shared passes, rather than per-class extractors, is what makes the whole set tractable on a single consumer GPU.
The single largest compute saving is consolidating high-level reasoning into one video-LLM pass. Action description, situational dimensions (space/time/causation/intention/protagonist), social interaction-type/dominance/ToM, and depicted emotion are all realized by Qwen2.5/3-VL-class models. Running one Qwen-VL pass per shot with a unified multi-field JSON schema replaces four separate VLM deployments. InternVL3, VideoLLaMA3, LLaVA-OneVision, Emotion-LLaMA, and AffectGPT are interchangeable for this role; keep at most one primary plus, optionally, one frontier-API judge reserved for gold-label validation rather than routine annotation.
Foundation models supersede most fixed-taxonomy CNNs, but not always. SigLIP 2 zero-shot probing covers the object/scene/place/attribute role that Places365 and ImageNet backbones (and YAMNet/AST in audio) used to fill. Keep the fixed-taxonomy models only when a frozen, calibrated, reproducible probability vector is explicitly required for interpretability. Likewise, classic flow (RAFT/Farneback), depth (MiDaS/ZoeDepth), saliency (TASED-Net/UNISAL), and shot detection (AutoShot) are superseded by SEA-RAFT, Depth-Anything-V2, ViNet, and TransNetV2 respectively; the older tools survive only as no-GPU fallbacks.
Some surface-similar pairs are genuinely distinct and must both be kept. DINOv2 (label-free dense embedding for RSA) versus SigLIP 2 (text-probed interpretable scores) are different representations. pymoten (brain-aligned motion-energy regressor) versus SEA-RAFT (interpretable camera-vs-object flow), and semantic action recognition (VideoMAE) versus pre-semantic motion (SEA-RAFT/pymoten), are deliberately separate layers. BEATs (calibrated 527-class) versus CLAP (arbitrary open-vocabulary prompts), inaSpeechSegmenter (speech/music/noise partition) versus pyannote (who-spoke-when), Mask2Former (area-fraction composition) versus Grounding DINO (object counts/positions), and CREPE (robust pitch contour) versus Parselmouth (canonical voice-quality) all carry complementary information.
The one non-negotiable “keep two” is depicted versus elicited emotion. Face/voice/text emotion models predict character-expressed (depicted) affect, whereas LIRIS-ACCEDE/MuSe-trained regressors predict viewer-induced (elicited) affect. These are correlated but not interchangeable; logging them as a single stream would be a scientific error, not a parsimony gain. Similarly, interpretable per-modality affect signals (wav2vec2 + HSEmotion + GoEmotions) should remain the primary features, with any fusion MLLM as an added reasoned layer rather than a replacement.
Local-first is feasible throughout. Every core recommendation runs locally; most low-level audio and lexical tools are CPU-only, and the heaviest core components (Qwen-VL, VideoMAE, SigLIP 2, DINOv2, pyannote, Whisper) fit on a single 24-48 GB GPU. API embeddings and frontier-API VLM judges are the only API-only entries and are confined to the extended tier, used only when leaderboard-topping accuracy is required and off-site transfer of transcripts/frames is acceptable.
Annotation Output Data Format
Status: v0.2 design (supersedes the schema/annotation_schema.json v0.1 draft; reconciles it
with the Phase 1 catalogs).
Scope: the on-disk format the Phase 2 pipeline emits for one stimulus (one movie or one audio
story), and the MATLAB reader interface that loads it.
This document defines: (1) the container format and why; (2) the timeline / alignment convention;
(3) the hierarchical feature layout; (4) per-feature (per-channel) metadata; (5) how nulls and
reserved human-annotation slots are represented; (6) a worked 2-timepoint example; (7) the MATLAB
reader interface. It is the authoritative spec; schema/annotation_schema.json is the machine-checkable
encoding of it, and matlab/*.m is the reference reader.
1. Format recommendation: HDF5 canonical + JSON sidecar manifest
1.1 The requirement, sized
A single feature-rich movie produces, per second:
- ~hundreds of scalar channels (luminance, RMS contrast, F0, flow magnitude, valence, surprisal …);
- several high-dimensional vector channels — AudioSet tags (527-dim), CLIP/SigLIP probe sets (tens–hundreds of prompts), Places365 (365-dim), DINO/CLIP/LLM embeddings (384–4096-dim), pymoten motion energy (hundreds–thousands-dim), pose keypoints (17–133 × 3);
- categorical / label channels (scene category, speaker id, topic id, dialogue act, narrative stage);
- sparse-event channels (cuts, beats, turning points) carried as both a per-bin flag and an onset list.
A 2-hour movie at 1 Hz is 7200 timepoints. With a few thousand embedding dimensions per timepoint across several encoders, the dense payload is tens of MB to low GB per stimulus. Inline-array JSON (the v0.1 draft) is unworkable at that size: it is ~3–5× larger than binary, parses slowly, holds everything in memory, and has no native typed n-D array. So JSON stays the readable metadata layer, not the bulk container.
1.2 Decision
Canonical container: HDF5 (
.h5), one file per stimulus. Sidecar: a human-readable JSON manifest (.manifest.json) holding the same hierarchy and all metadata but no bulk numeric arrays — pointers (data_ref) into the.h5instead.
Rationale, against the requirements:
| Requirement | How HDF5 + JSON sidecar meets it |
|---|---|
| Second-by-second, configurable rate | One shared time axis dataset; every channel is [n_samples × …] aligned to it. |
| Semantically readable, coherent, hierarchical | HDF5 groups are a filesystem-like hierarchy; the JSON sidecar mirrors it and is the human-readable view. |
| Irrelevant features → null/NaN | Float datasets use NaN; an applicable attribute + a fill value make “not measured” explicit and self-describing. |
| Reserved human-annotation slots | A top-level /human/ group, pre-created empty with the same shape contract; never written by machines. |
| Loadable into MATLAB | HDF5 is a first-class MATLAB format (h5read, h5info, h5readatt) with no toolbox required; the JSON sidecar loads via jsondecode. |
| Reproducible provenance | Per-channel attributes (model, version, native rate, units, resample op) live next to the data; global provenance in /. |
| Scales to dense embeddings | Chunked + gzip-compressed datasets; lazy/partial reads; no whole-file parse. |
Parquet is a strong alternative for the scalar/tabular slice (columnar, great with
pandas/Arrow), but it is awkward for ragged high-dimensional per-timepoint vectors and variable-length
event lists, and MATLAB support (parquetread) is newer and tabular-only. We therefore choose HDF5 as
the single canonical container and optionally emit a flat *_scalars.parquet projection of just
the scalar channels as a convenience export (see §3.5).
Pure-JSON profile (small/demo only). For tiny clips, tests, and documentation, the entire payload
may be inlined into the JSON file (the v0.1 layout). The reader auto-detects: if a channel has inline
value, it uses it; if it has data_ref only, it reads the .h5. This keeps the v0.1 example valid
while making the dense case feasible.
1.3 On-disk artifacts per stimulus
annotations/output/<stimulus_id>/
<stimulus_id>.h5 # canonical: time axis + all feature datasets + attributes
<stimulus_id>.manifest.json # readable hierarchy + metadata + data_ref pointers (no bulk arrays)
<stimulus_id>_scalars.parquet # OPTIONAL flat export of scalar channels (convenience)
Self-contained alternative: a single .h5 is sufficient on its own (it carries all metadata as
attributes); the sidecar JSON exists for grep/diff/readability and for the pure-JSON profile.
2. Timeline & alignment convention
2.1 The common grid
There is exactly one common time grid per file, defined by three numbers and materialized as one dataset:
/time/rate_hz scalar e.g. 1.0 (configurable; default 1 Hz)
/time/t_start_sec scalar e.g. 0.0 (time of the *center* of sample 0; see §2.2)
/time/n_samples scalar e.g. 7200
/time/time_sec [n_samples] float64 center timestamp of each grid bin (always written, even
though derivable, so MATLAB/Python never recompute it)
Invariant: time_sec[i] == t_start_sec + i / rate_hz. Every feature dataset has leading dimension
n_samples and shares this axis — that is what makes the whole file a single aligned matrix family.
A file may also declare an alternate grid (e.g. a 0.5 Hz coarse grid, or a per-TR grid for a specific
fMRI study) under /time/alt/<name>/…; channels resampled to an alternate grid point at it via a
grid attribute. Default and common case: a single grid.
2.2 Bin convention (center-referenced, half-open)
Grid bin i covers the continuous interval
[ t_start_sec + (i - 0.5)/rate_hz , t_start_sec + (i + 0.5)/rate_hz )
and time_sec[i] is its center. All resampling (§2.4) maps native samples into these bins. Center-
referencing (not left-edge) keeps a feature’s grid time aligned to the middle of the interval it
summarizes, which is the convention most neuroimaging analyses expect and avoids a systematic half-bin
lag. The choice is recorded once in /time/bin_reference = "center".
2.3 Native rates feeding the grid
Each extractor has a native rate (from the Phase 1 catalogs), one of:
| Native rate | Examples | Typical native step |
|---|---|---|
frame | luminance, optical flow, CLIP/DINO probes, depth, saliency, pose | 1/fps (e.g. 25 Hz, or the analysis 2 fps) |
subsecond | librosa/openSMILE LLDs, Praat F0, CREPE, audio taggers on short hops | ~10–100 Hz |
second | windowed VLM/affect/action windows | 1 Hz |
window | sliding clip models (VideoMAE 16-frame, CLAP 10 s) | window-dependent |
utterance | ASR segments, dialogue acts, per-utterance affect | irregular |
word | surprisal, lexical norms, POS, LLM hidden states | irregular (from forced alignment) |
shot / scene | per-shot VLM captions, scene labels, situational tags | irregular |
event | cuts, beats, turning points, event-segment boundaries | irregular/sparse |
The native rate is preserved as metadata on every channel (native_rate_hz, which may be a number
or one of the strings above), and the raw pre-resampling output is cached (per DEPLOYMENT_FEASIBILITY
§2.6) so the grid can be regenerated at a different rate without re-running models.
2.4 Resampling onto the grid (per dtype)
One resampling rule per channel, chosen by dtype and recorded in the resample attribute. These match
DEPLOYMENT_FEASIBILITY §2.7:
| Channel kind | Default resample op (resample) | Notes |
|---|---|---|
| Continuous scalar (luminance, F0, flow, valence) | mean | Anti-aliased / area average of native samples within the bin. Optionally also emit _std, _max companion channels. |
| Probability / score vector (AudioSet 527, CLIP probes, emotion posteriors) | mean | Element-wise mean-pool within bin. |
| Embedding vector (DINO/CLIP/LLM/Qwen3) | mean | Mean-pool of native vectors in bin (record embed_pool="mean"). |
Categorical / label (scene, speaker, topic, dialogue act, narrative stage) | mode | Most-frequent native label in bin; ties broken by longest dwell. Companion *_change boundary flag emitted. |
bool / presence (speech present, face present) | any or frac | any = ≥1 native true in bin; or frac = fraction of bin true (record which). |
Sparse event (cut, beat, turning point) | count + onset list | Per-bin integer count/flag and an exact onset-time list (§5.3). |
Per-word feature (surprisal, concreteness, hidden state) | mean | Assign by word timestamp to its bin, average within bin; companion word_rate/word_onset channels. |
A native sample with no bin (out of range) is dropped; a bin with no native sample is NaN (continuous)
/ empty-label (categorical) / 0 (count) — see §5.
2.5 Why a single grid (not per-feature native arrays in the file)
Keeping every channel on one shared, regular grid is what makes the file a coherent design matrix:
MATLAB loads it straight into a timetable, cross-feature correlations and PCA (Phase 4) need no
re-interpolation, and “inapplicable = NaN of the right length” gives a constant output shape across
all stimuli (the README’s core principle). Native-rate arrays still exist — in the cache, and
optionally archived under /native/<channel>/… for power users — but the grid is the contract.
3. Hierarchical feature layout
3.1 Top-level structure (HDF5 groups == hierarchy)
/ (root; global attributes = schema_version, ids, provenance)
├── time/ the common grid (§2)
├── stimulus/ (attrs: id, title, modality, duration_sec, source, media_file, sha256)
├── features/ MACHINE annotations, mirroring the Phase 1 semantic hierarchy
│ ├── visual/
│ │ ├── low_level_static/ luminance, rms_contrast, colorfulness, edge_density, fft_slope, clutter…
│ │ ├── high_level_static/ scene_category, object_presence, clip_probe…, places365, panoptic_fractions
│ │ ├── faces_bodies_gaze/ n_faces, au_intensity, gaze_yaw/pitch, head_pose, pose_keypoints, expression
│ │ ├── dynamic_motion/ flow_magnitude, camera_motion, residual_motion, motion_energy, cut (event)
│ │ ├── action/ action_posteriors, action_probe, action_segment (event)
│ │ └── saliency_aesthetics_depth/ saliency_entropy, depth_mean, fg_fraction, aesthetic, quality, memorability
│ ├── audio/
│ │ ├── low_level/ rms, loudness, spectral_centroid, mfcc, chroma, f0, onset_strength, tempo
│ │ ├── high_level/ audioset_tags, clap_probe, scene, speech_music_noise, key, beat (event)
│ │ └── speech/ asr_text, speaker_id, vad, speech_rate, prosody_f0, ser_arousal/valence/dominance
│ ├── language/
│ │ ├── lexical/ word, lemma, pos, freq_zipf, concreteness, aoa, valence_norm, emolex_*, surprisal
│ │ ├── syntax/ dep_depth, clauses_per_tunit, tree_depth, coref_chain_len, dialogue_act, readability
│ │ └── semantics_discourse/ embedding, coherence, drift, novelty, topic_id, topic_vector, narrative_stage, turning_point (event)
│ ├── social/ n_agents, characters_present, active_speaker, mutual_gaze, joint_attention,
│ │ proximity, interaction_type, dominance, affiliation, tom_intention
│ ├── situation/ location, time_of_day, indoor_outdoor, script_label, event_id, event_boundary (event),
│ │ space/time/causation/intention/protagonist (Event-Indexing dims)
│ └── affect/
│ ├── depicted/ face_valence/arousal, voice_v/a/d, text_valence, music_v/a, categorical_emotion
│ └── elicited/ induced_valence, induced_arousal, fear_flag (separate stream from depicted)
├── human/ RESERVED for later human annotation (§5.4); same shape contract; empty at emit
│ ├── visual/ … affect/ (mirrors features/ subgroups; created empty)
│ └── _free/ free-form human channels not in the machine taxonomy
└── provenance/ per-class model registry, env locks, params hashes (§4.3)
The top-level classes and subclasses are exactly the Phase 1 hierarchy (Visual / Audio / Language / Social / Situation / Affect). A program traverses groups; a human reads the JSON sidecar, which has the identical tree.
3.2 A leaf = one channel = one HDF5 dataset + attributes
Each leaf feature is one dataset named by the channel, shaped [n_samples] (scalar/label/bool/event)
or [n_samples × D] (vector/embedding) or [n_samples × K × C] (e.g. pose K keypoints × C coords),
with metadata carried as HDF5 attributes on that dataset (§4). The dataset’s HDF5 path is its
hierarchical path, e.g. /features/visual/low_level_static/luminance.
3.3 Dtype encodings
dtype | HDF5 storage | Null encoding |
|---|---|---|
scalar | float64 [n] | NaN |
vector | float32/float64 [n × D] | whole row NaN (not measured) |
bool | int8 [n] (0/1) | -1 (or float NaN if stored as float) |
categorical | int32 [n] code + categories attr (string array) | code -1 ↔ <undefined> |
label | variable-length UTF-8 string [n] | empty string "" ↔ not measured |
event | see §5.3 (per-bin count int32 [n] + onset list group) | count 0 |
text (e.g. ASR words) | variable-length UTF-8 string [n] | "" |
Categorical channels store integer codes + a categories attribute (the label vocabulary) so MATLAB
reconstructs a categorical array directly and analyses stay numeric; label/text channels store
strings directly for the free-vocabulary cases (ASR words, LLM free-text tags).
3.4 Vector channels carry a component axis
A [n × D] vector dataset gets a components attribute: a length-D string array naming each column
(e.g. AudioSet class names, CLIP prompt strings, MFCC indices, keypoint names). Embeddings whose
dimensions are not individually meaningful set components = [] and rely on dim/model metadata. This
makes every vector self-describing without an external codebook.
3.5 Optional flat scalar export
For quick tabular work, the pipeline may also emit <id>_scalars.parquet: one row per timepoint, one
column per scalar channel, column names = the slash-path with /→__
(visual__low_level_static__luminance), plus a time_sec column. This is a lossy convenience
projection (no vectors/labels/events); the .h5 remains authoritative.
4. Per-feature (per-channel) metadata
4.1 Required + optional attributes on every channel
Carried as HDF5 attributes on the dataset (and as JSON keys in the sidecar). Required marked R.
| Attribute | Type | Meaning |
|---|---|---|
dtype R | string | one of §3.3. |
applicable R | bool | false ⇒ feature does not apply to this stimulus modality; value is all-null (§5.2). |
units | string | e.g. "0-1", "Hz", "dB", "bits", "1-9 (Warriner)", "-1..1", "deg". ""/absent for unitless or categorical. |
model R | string | producing tool/model, e.g. "scikit-image", "SigLIP2-so400m", "faster-whisper-large-v3". |
version R | string | model checkpoint + code version, e.g. "0.24.0", "siglip2-so400m-patch14-384". |
native_rate_hz R | number or string | numeric Hz, or one of `frame |
resample R | string | op used to map native→grid (§2.4): `mean |
components | string[] | column names for vector dtypes (§3.4); empty for opaque embeddings. |
categories | string[] | label vocabulary for categorical (code↔label map); index = code. |
dim | int | D for vector/embedding channels. |
grid | string | "default" or an alternate-grid name (§2.1). |
tier | string | provenance: `cpu |
params_hash | string | content hash of model params/config that produced this channel. |
notes | string | free text (e.g. “null where no speech present”). |
confidence_ref | string | optional path to a companion [n] confidence channel. |
4.2 Companion channels (uncertainty & dispersion)
Where a model exposes it, a channel X may have siblings:
X_conf— per-timepoint confidence/probability (e.g. ASR avg_logprob, detection score, voicing prob);X_std,X_max— within-bin dispersion when many native samples collapsed into one grid bin;X_change— boundary/onset flag for categorical/label channels.
These are ordinary channels with their own metadata; the parent points to them via confidence_ref.
4.3 Global provenance (/provenance + root attributes)
/ (root attributes)
schema_version = "0.2"
pipeline_version = "<git-describe>"
generated_utc = "2026-06-13T12:00:00Z"
common_grid_rate_hz = 1.0
/provenance/
models/ attr-per-model: name → {version, tier, env, params_hash, citation}
env_locks/ names of the locked envs used (core-cv, torch-vision, speech, llm, …)
stimulus_sha256
transcript_source = "whisperx" | "supplied"
This satisfies the README’s “reproducible provenance”: every channel ties to a model entry, which ties to a locked environment and a params hash.
5. Nulls, applicability, and reserved human slots
5.1 Three distinct “missing” states — never conflated
| State | Meaning | Encoding |
|---|---|---|
| Not applicable | feature class can’t apply to this modality (visual on audio-only story) | applicable=false attr and value all-NaN/""/-1 for full length |
| Not measured here | applicable, but no native sample fell in this bin (e.g. silence → no word, no speech) | per-element NaN/""/code=-1/count=0; applicable=true |
| Measured zero / absent | the model ran and reports zero/absence (e.g. 0 faces detected, loudness ≈ 0) | the actual numeric value (0, low float) — not null |
The applicable flag plus the element-wise fill value lets downstream analysis distinguish “not
measured” from “measured zero” — the README’s explicit requirement. Float channels use IEEE NaN
(reads into MATLAB as NaN natively); categorical uses code -1; labels/text use ""; events use 0.
5.2 Inapplicable features are still present, full-length, all-null
When a stimulus is audio-only, every visual/* channel is still emitted with the correct
[n_samples × …] shape, filled with NaN/""/-1, and applicable=false. This guarantees a
constant output shape and hierarchy across the whole corpus, so cross-stimulus matrices, PCA, and
the Phase-4 design tool never have to special-case which classes a given stimulus has.
Modality → applicable-class gate (from IMPLEMENTATION_PLAN “applicability rule”):
| Stimulus modality | Applicable top-level classes |
|---|---|
audiovisual | visual, audio, language, social, situation, affect (all) |
video-only | visual, situation, affect.depicted, social (no speech-derived language/affect) |
audio-only | audio, language, social*, situation, affect (no visual) |
text-only | language, situation, social*, affect (text branch only) |
(*social/situation degrade to the subset derivable from the available modalities; per-channel
applicable flags carry the exact truth.)
5.3 Sparse events
An event channel is stored as a per-bin count dataset plus an onset group holding exact times:
/features/visual/dynamic_motion/cut int32 [n] per-bin cut count (resample="count")
/features/visual/dynamic_motion/cut__onsets/
time_sec [m] float64 exact onset times (m = total events)
value [m] (optional) per-event payload (e.g. transition type, confidence)
So an event is both griddable (the [n] count/flag, usable as a regressor) and exact (the onset list,
for precise timing). Beats, turning points, scene boundaries, action segments follow the same pattern;
segment-type events add a paired __offsets list.
5.4 Reserved human-annotation slots (/human/)
A top-level /human/ group mirrors the /features/ subgroup tree but is created empty at machine
emit time — no datasets, just the group skeleton — plus a /human/_free/ group for human channels that
don’t fit the machine taxonomy. Properties:
- Same channel contract. A later human (or human-in-the-loop tool) writes a channel into
/human/<class>/<sub>/<name>using the identical schema:[n_samples]aligned to/time, withdtype,applicable,model="human",version=<rater id / protocol>,resample,notes. - Never auto-populated. Machine runs only (re)create the empty skeleton; they never write under
/human/, so re-running the pipeline never clobbers human work. (Writers should append, and the pipeline must not overwrite an existing.h5’s/human/group — emit a new file or merge.) - Discoverable.
/humanhas an attributepopulated = falseuntil a human channel is added; the reader exposesann.humanas an (initially empty) struct of the same shape asann.features. - Provenance for humans. A human channel records rater id, instructions/protocol version, and date
in its attributes exactly as a model channel records
model/version, so human and machine annotations are first-class and equally traceable.
In the JSON sidecar / pure-JSON profile, this is the human_annotations object: empty {} at emit,
later filled with the same hierarchical shape as features.
6. Worked example (~2 timepoints)
Below is the JSON sidecar view (the readable layer) for an audiovisual clip at 1 Hz, showing two
grid timepoints and a representative channel of each kind. Bulk vectors point into the .h5 via
data_ref; small channels inline value for readability. A null at t=0 for a language feature shows
“applicable but not measured” (no speech yet); the whole affect/elicited example shows a separate
stream from depicted affect.
{
"schema_version": "0.2",
"stimulus": {
"id": "demo_movie_001", "title": "Demo clip", "modality": "audiovisual",
"duration_sec": 2.0, "source": "user-supplied",
"media_file": "data/movies/demo_movie_001.mp4", "sha256": "ab12…"
},
"time": {
"rate_hz": 1.0, "t_start_sec": 0.0, "n_samples": 2,
"bin_reference": "center", "time_sec": [0.0, 1.0]
},
"features": {
"visual": {
"low_level_static": {
"luminance": {
"dtype": "scalar", "applicable": true, "units": "0-1",
"model": "scikit-image", "version": "0.24.0",
"native_rate_hz": 25, "resample": "mean",
"value": [0.41, 0.43]
}
},
"high_level_static": {
"scene_category": {
"dtype": "categorical", "applicable": true,
"model": "SigLIP2-so400m", "version": "siglip2-so400m-patch14-384",
"native_rate_hz": "frame", "resample": "mode",
"categories": ["kitchen", "hallway", "street"],
"value": [0, 1] // codes -> "kitchen", "hallway"
},
"clip_probe": {
"dtype": "vector", "applicable": true, "dim": 3,
"model": "SigLIP2-so400m", "version": "siglip2-so400m-patch14-384",
"native_rate_hz": "frame", "resample": "mean",
"components": ["a kitchen", "a person cooking", "an empty hallway"],
"data_ref": "/features/visual/high_level_static/clip_probe" // [2 x 3] in .h5
}
},
"dynamic_motion": {
"cut": {
"dtype": "event", "applicable": true, "units": "count",
"model": "TransNetV2", "version": "1.0",
"native_rate_hz": "frame", "resample": "count",
"value": [0, 1], // one cut landed in bin t=1
"onsets": { "time_sec": [1.04], "value": ["hard"] }
}
}
},
"audio": {
"low_level": {
"loudness": {
"dtype": "scalar", "applicable": true, "units": "LUFS-rel",
"model": "librosa", "version": "0.10.2",
"native_rate_hz": 100, "resample": "mean",
"value": [0.62, 0.71]
}
},
"high_level": {
"audioset_tags": {
"dtype": "vector", "applicable": true, "dim": 527,
"model": "BEATs", "version": "iter3+",
"native_rate_hz": "window", "resample": "mean",
"components": ["Speech", "Music", "..."], // 527 names (truncated)
"data_ref": "/features/audio/high_level/audioset_tags" // [2 x 527] in .h5
}
}
},
"language": {
"lexical": {
"valence_norm": {
"dtype": "scalar", "applicable": true, "units": "1-9 (Warriner)",
"model": "Warriner-norms", "version": "2013",
"native_rate_hz": "word", "resample": "mean",
"value": [null, 6.2], // t=0 NaN: no speech in that bin (not measured)
"notes": "null where no word onset falls in the bin"
}
},
"semantics_discourse": {
"embedding": {
"dtype": "vector", "applicable": true, "dim": 1024,
"model": "Qwen3-Embedding-0.6B", "version": "0.6B",
"native_rate_hz": "utterance", "resample": "mean",
"components": [], // opaque embedding dims
"data_ref": "/features/language/semantics_discourse/embedding" // [2 x 1024]
},
"narrative_stage": {
"dtype": "categorical", "applicable": true,
"model": "Llama-3.1-8B-Instruct", "version": "4bit",
"native_rate_hz": "scene", "resample": "mode",
"categories": ["setup", "conflict", "climax", "resolution"],
"value": [0, 0] // both bins "setup"
}
}
},
"social": {
"n_agents": {
"dtype": "scalar", "applicable": true, "units": "count",
"model": "InsightFace-buffalo_l", "version": "0.7",
"native_rate_hz": "frame", "resample": "mean",
"value": [2.0, 2.0] // measured 2 (not null)
}
},
"affect": {
"depicted": {
"face_valence": {
"dtype": "scalar", "applicable": true, "units": "-1..1",
"model": "HSEmotion-EfficientNet-B2", "version": "va_mtl",
"native_rate_hz": "frame", "resample": "mean",
"value": [0.10, 0.30]
}
},
"elicited": {
"induced_valence": {
"dtype": "scalar", "applicable": true, "units": "-1..1",
"model": "LIRIS-ACCEDE-regressor", "version": "1.0",
"native_rate_hz": "second", "resample": "mean",
"notes": "viewer-elicited; distinct stream from affect.depicted",
"value": [0.05, 0.12]
}
}
}
},
"human_annotations": {}, // empty skeleton mirrors features/ (see §5.4)
"provenance": {
"pipeline_version": "0.2.0", "generated_utc": "2026-06-13T12:00:00Z",
"models": {
"SigLIP2-so400m": { "version": "siglip2-so400m-patch14-384", "tier": "gpu", "env": "torch-vision" },
"BEATs": { "version": "iter3+", "tier": "gpu", "env": "audio-tag" }
/* … */
}
}
}
For an audio-only story, the entire features.visual subtree is still present and full-length, with
every channel applicable=false and value all-NaN (scalars) / -1 codes (categoricals) / ""
(labels). Shape is identical to the audiovisual file; only applicable and the fill values differ.
7. MATLAB reader interface
MATLAB reads HDF5 natively (h5read, h5info, h5readatt) and JSON via jsondecode — no toolbox
required for the core reader. The reference implementation lives in matlab/.
Implementation status. IMPLEMENTED and verified:
readAnnotations(.h5, folder, or JSON),getFeature,featuresToTimetable(ann)(no name-value options; scalar/bool/event channels only, not-applicable channels as NaN), plusreadAnnotationCorpus,analyzeCorpus,selectStimulusSet,refreshAnalysis,annotationMovieViewer. The signatures below marked (planned) —listFeatures,getFeatureMatrix,writeHumanChannel, the"Lazy"option, and featuresToTimetable’s name-value options — are design targets, not yet implemented.
High-level signatures:
% --- Load -----------------------------------------------------------------
ann = readAnnotations(path)
% READANNOTATIONS Load one annotation into a MATLAB struct.
% PATH may be the .h5, the .manifest.json, or the stimulus folder.
% If given JSON-with-data_ref, reads bulk arrays from the sibling .h5;
% if given pure-JSON, uses inline values; if given the .h5, reads everything
% from it (metadata from attributes). Returns:
% ann.stimulus struct (id, title, modality, duration_sec, source, sha256)
% ann.time struct (rate_hz, t_start_sec, n_samples, bin_reference)
% ann.time_sec double[n] column (the common grid; always materialized)
% ann.features struct nested groups mirroring the hierarchy; each leaf is
% a channel struct (see below)
% ann.human struct same shape as features (empty until humans populate)
% ann.provenance struct
% JSON null / HDF5 fill -> NaN (numeric), "" (label/text), <undefined> (categorical).
% A leaf channel struct:
% ch.value numeric [n] | [n x D] | categorical [n] | string [n] | int [n]
% ch.dtype, ch.applicable, ch.units, ch.model, ch.version,
% ch.native_rate_hz, ch.resample, ch.components (string[]), ch.categories,
% ch.notes, ch.onsets (struct .time_sec/.value for event dtypes)
% --- Navigate / select ----------------------------------------------------
ch = getFeature(ann, "visual/low_level_static/luminance")
% GETFEATURE Retrieve one channel by hierarchical (slash) path. Errors if the
% path is not a leaf channel. Works for features/ and human/ paths.
paths = listFeatures(ann, namevalue)
% LISTFEATURES Return the slash-paths of all channels, filterable by:
% "Class","visual" | "Dtype","scalar" | "Applicable",true |
% "Modality",... | "Pattern","*valence*"
% Use to discover what a file contains without manual traversal.
% --- Reshape for analysis -------------------------------------------------
tt = featuresToTimetable(ann, namevalue)
% FEATURESTOTIMETABLE Collect channels into a MATLAB timetable on the common
% grid (RowTimes = seconds(ann.time_sec)). Options:
% "Dtypes",["scalar","bool"] which kinds to include (default scalars)
% "Class","audio" restrict to one branch
% "ExpandVectors",true explode [n x D] vectors into D named columns
% "IncludeNaNApplicable",false drop all-NaN inapplicable channels
% Variable names are the slash-path with "/"->"__". Vector/label/event channels
% are skipped unless ExpandVectors / explicit Dtypes request them.
M = getFeatureMatrix(ann, paths)
% GETFEATUREMATRIX Stack a list of channel paths into one [n x P] numeric
% matrix (scalars) for PCA / correlation / the Phase-4 design tool; returns the
% column->path map and propagates NaN.
% --- Corpus level (Phase 4) ----------------------------------------------
C = readAnnotationCorpus(folder, namevalue)
% READANNOTATIONCORPUS Load many annotation files into a struct array (or a
% stacked timetable keyed by stimulus_id), enforcing the constant-shape
% contract so cross-stimulus matrices align. Options mirror featuresToTimetable.
% --- Write-back (human-in-the-loop) --------------------------------------
writeHumanChannel(path, "social/interaction_type", values, namevalue)
% WRITEHUMANCHANNEL Append a human annotation channel under /human/ in the .h5
% (and update the JSON sidecar), validating length == n_samples and stamping
% model="human", version=<RaterId>, plus protocol/date attributes. Never
% touches /features/.
Design notes for the reader:
- No toolbox dependency for read:
h5read/h5info/h5readatt+jsondecodeare base MATLAB.timetable/categoricalare base MATLAB. (Parquet export read needs no extra toolbox viaparquetread, which is base since R2019a.) - Lazy option.
readAnnotations(...,"Lazy",true)reads only metadata + the time axis and defers bulk dataset reads untilgetFeature/getFeatureMatrixtouches them — important for the multi-GB embedding channels. - Categorical round-trips. Integer-code +
categoriesattribute reconstructs a MATLABcategoricaldirectly, so code-1becomes<undefined>and grid math stays numeric. - Constant shape guarantee is what lets
readAnnotationCorpusstack heterogeneous stimuli without per-file branching: inapplicable channels are present andNaN, so the corpus matrix is rectangular.
8. Summary of decisions
- HDF5 canonical, one file per stimulus, with a readable JSON sidecar manifest; optional flat
*_scalars.parquetexport; a pure-JSON profile for small/demo files. All load into MATLAB with no toolbox. - One shared, configurable common grid (default 1 Hz), center-referenced half-open bins,
materialized
time_sec; every channel shares then_samplesleading axis. Native rates preserved as metadata; raw native outputs cached for re-gridding. - Hierarchical groups mirroring the Phase 1 semantic hierarchy (visual / audio / language / social / situation / affect, with subclasses); each leaf is one dataset + self-describing attributes.
- Per-channel metadata: model, version, native rate, resample op, units, components/categories, tier, params hash; global provenance ties channels to locked environments.
- Three explicit missing-states — not-applicable (
applicable=false, all-null), not-measured (elementNaN/""/-1/0), measured-zero (real value) — never conflated. - Reserved
/human/group mirroring the machine tree, empty at emit, never auto-clobbered, with identical channel contract and human-rater provenance. - MATLAB reader (
readAnnotations,getFeature,listFeatures,featuresToTimetable,getFeatureMatrix,readAnnotationCorpus,writeHumanChannel) loading the file into structs and timetables on the common grid.
Phase 2 Core-Set Proposal — freeze sheet
Purpose: the minimal, tractable, best-in-class feature set to build in Phase 2. Derived from
docs/scoping_review/09_recommendations.md. Mark each row keep ✓ / drop ✗ / → extended and edit
freely; your sign-off at the bottom freezes the Phase 2 scope. Everything here runs locally on
Tier A (CPU) + Tier B (one 24–48 GB GPU). Default answer for every row is keep.
Note: keep all i didn’t answer. x = selected (i.e., x by keep means keep it. x by change means change)
1. Global parameters
| Parameter | Proposed default | Decision |
|---|---|---|
| Common grid rate | 1 Hz, center-referenced bins | x keep ☐ change: ____ |
| Emit alternate grids | No (single grid; native rates cached for re-gridding) | x keep ☐ also emit: ____ |
| Output container | HDF5 canonical + JSON sidecar (+ optional scalar Parquet) | x keep ☐ change: ____ |
| Hosted (API) models | Off by default; local-only core | ☐ keep x allow opt-in for: OpenAI text-embedding-3-large, ChatGPT5.x and later____ |
| Depicted vs elicited emotion | Two separate streams (non-negotiable) | x keep ☐ change: ____ |
2. Core extraction passes (extract once → route to all classes)
The core set is ~18 model passes, not 146 tools, because signals are shared. Each pass below is one deployable extractor; “feeds” shows which feature classes consume it.
Visual
| Pass | Model | Tier | Feeds | Key per-timepoint outputs | Keep? |
|---|---|---|---|---|---|
| Low-level image stats | scikit-image + OpenCV + Hasler colorfulness | CPU | Visual | luminance, RMS contrast, color means (RGB/HSV/Lab), colorfulness, edge density, entropy, FFT slope | x |
| Orientation/SF bank | pyrtools / plenoptic | CPU | Visual | scale×orientation energy vector | x |
| Semantic image probe | SigLIP 2 | GPU | Visual, Situation | image embedding + per-prompt object/scene/place/attribute scores | x |
| Label-free embedding | DINOv2 | GPU | Visual | CLS embedding (for RSA / probes) | x |
| Faces + identity | InsightFace (RetinaFace+ArcFace) | GPU | Visual, Social | face count, identity tracks, landmarks, head pose, 512-d id embedding | x |
| Face AUs + expression | OpenFace 3.0 | CPU | Visual, Affect | AUs, gaze, head pose, discrete emotion | x |
| Body pose | MMPose RTMPose (+ByteTrack) | GPU | Visual, Social | 17/133 keypoints → posture, orientation, proximity, contact proxy | x |
| Optical flow | SEA-RAFT | GPU | Visual | flow magnitude, orientation hist, camera-vs-object motion | x |
| Motion energy | pymoten | CPU | Visual | brain-aligned motion-energy vector | x |
| Shot boundaries | TransNetV2 + PySceneDetect | GPU/CPU | Visual, Situation, Action | per-frame cut prob, shot segments | x |
| Action recognition | VideoMAE V2 + X-CLIP | GPU | Visual | Kinetics posteriors + zero-shot action-phrase scores | x |
| Saliency + depth | ViNet + Depth-Anything-V2 | GPU | Visual | saliency entropy/peak, depth mean/range, foreground fraction | x |
Audio / speech
| Pass | Model | Tier | Feeds | Key outputs | Keep? |
|---|---|---|---|---|---|
| Low-level acoustic | librosa + openSMILE eGeMAPS + Parselmouth | CPU | Audio, Speech, Affect | RMS, spectral, MFCC, chroma, F0, jitter/shimmer/HNR, tempo/beats | ☐ |
| Audio events/scenes | BEATs + CLAP | GPU | Audio | 527-d AudioSet probs + open-vocab prompt similarity | ☐ |
| Speech/music/noise | inaSpeechSegmenter | CPU | Audio | timestamped speech/music/noise segments | ☐ |
| ASR (the hub) | faster-whisper large-v3 + WhisperX | GPU | Speech, Language, Social, Situation, Affect | word/segment text + timestamps + speaking rate | ☐ |
| Diarization + VAD | pyannote + Silero VAD | GPU/CPU | Speech, Social | who-spoke-when, active speaker, overlap, speech activity | ☐ |
| Vocal affect | audEERING wav2vec2-dim | GPU | Speech, Affect | voice valence/arousal/dominance + embedding | ☐ |
Language (consumes the transcript)
| Pass | Model | Tier | Feeds | Key outputs | Keep? |
|---|---|---|---|---|---|
| Lexical + syntax | spaCy trf + wordfreq/SUBTLEX + concreteness/AoA/VAD norms + NRC EmoLex | CPU | Language, Affect | POS, dep, NER; freq, concreteness, AoA, valence, emotion per word | ☐ |
| Syntactic complexity | benepar + L2SCA + Maverick (coref) | GPU/CPU | Language | tree depth, clause ratios, complexity indices, coref chains | ☐ |
| LLM surprisal/hidden | minicons (GPT-2/Pythia) | GPU | Language | per-token surprisal, entropy, hidden-state vector | ☐ |
| Semantic embedding | Qwen3-Embedding + sliding-window coherence + BERTopic | GPU/CPU | Language, Situation | embedding, coherence, drift, novelty, topic series | ☐ |
High-level reasoning (one consolidated VLM/LLM pass)
| Pass | Model | Tier | Feeds | Key outputs | Keep? |
|---|---|---|---|---|---|
| Per-shot video-LLM | Qwen2.5-VL 7B (unified JSON schema) | GPU | Social, Situation, Affect, Language | interaction type, dominance/affiliation, ToM/intention, addressee; Event-Indexing fields (space/time/cause/intent/protagonist), script labels, event boundaries; depicted emotion+V/A/D; narrative stage/turning points | ☐ |
| Active-speaker | Light-ASD | GPU | Social | per-face speaking prob → speaker/listener roles | ☐ |
| Gaze target | Gaze-LLE | GPU | Social | mutual gaze, joint attention | ☐ |
Situation / affect (algorithmic + dedicated)
| Pass | Model | Tier | Feeds | Key outputs | Keep? |
|---|---|---|---|---|---|
| Event segmentation | GSBS / statesegmentation | CPU | Situation | per-timepoint state label, ranked boundaries, optimal K | ☐ |
| Dialogue-text emotion | RoBERTa-GoEmotions (+NRC-VAD) | CPU | Affect | 28 emotion scores → V/A proxy per utterance | ☐ |
| Elicited affect | LIRIS-ACCEDE / MuSe regressor | GPU | Affect (separate stream) | viewer-induced valence/arousal per second | ☐ |
3. Deliberately keep-both (distinct signals, not redundant)
DINOv2 (RSA) vs SigLIP 2 (interpretable); pymoten (brain-aligned) vs SEA-RAFT (interpretable) vs VideoMAE (semantic action); BEATs (fixed 527) vs CLAP (open-vocab); inaSpeechSegmenter (partition) vs pyannote (who-spoke); depicted vs elicited emotion. ☐ accept all ☐ edit: ____
4. Notable items left to the extended tier (off by default)
Grounding-DINO/Mask2Former/SAM2 (object boxes/masks), Places365 (calibrated taxonomy), Q-Align (aesthetics), Essentia/madmom (music key/beat/mood), DMRST (RST discourse), turning-point model, API embeddings, frontier-API VLM judge (gold-label validation). ☐ ok ☐ promote to core: ____
5. Modality applicability (auto-null rule)
Audio-only story → all visual channels applicable=false (NaN). Text-only → visual+audio null,
language/social/situation/affect run on text. ☐ accept
Sign-off
- Approved by: Tor Wager Date: 6/19/2026
- Common grid rate frozen at: 1 Hz
- Hosted models: ☐ none x enabled for: OpenAI ChatGPT models__________________
- Notes / edits: ________________________________________________
On sign-off, Phase 2 builds one Extractor (src/extractors/<class>/) per kept pass behind the
base.py interface, wired around the shared passes above.
Phase 2 — Build Status
The pipeline package lives in src/nfe/. It ingests a stimulus, runs the applicable
extractors, resamples every signal onto the common grid, and emits the canonical
HDF5 + JSON-sidecar annotation (docs/design/ANNOTATION_FORMAT.md).
Quickstart
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
PYTHONPATH=src .venv/bin/python -m nfe.run path/to/movie.mp4 --out annotations/output --rate 1.0
Output: annotations/output/<id>/<id>.h5 (+ <id>.manifest.json). Load in MATLAB:
ann = readAnnotations("annotations/output/<id>"); % folder, .h5, or .json
tt = featuresToTimetable(ann); % scalars on the common grid
ch = getFeature(ann, "audio/low_level/mfcc"); % one channel + metadata
Pipeline core — DONE & verified
base.py— types, common-gridTimeGrid(center-referenced bins),FeatureChannel, and the native-rate→grid resampler (mean/max/sum/nearest/mode/count + event onsets).ingest.py— PyAV decode (downscaled RGB frames at an analysis fps) + audio extraction via the imageio-ffmpeg static binary. No system ffmpeg, no cv2 (avoids the PyAV/OpenCV ffmpeg-dylib clash on macOS).emit.py— HDF5 writer (hierarchical groups, per-channel attrs, reserved/human/group,/provenancemodel registry) + JSON sidecar manifest withdata_refpointers.pipeline.py/run.py— orchestration + CLI, per-extractor error isolation, modality-driven applicability.- MATLAB reader extended to the canonical
.h5path (matlab/readAnnotations.m), verified end-to-end on generated output (scalars, vectors, event onsets, timetable).
Extractors
| Status | Extractor | Frozen core pick | Notes |
|---|---|---|---|
| ✅ runs (CPU) | visual_lowlevel | scikit-image + OpenCV | luminance, contrast, colorfulness, edges, entropy, color means, FFT slope |
| ✅ runs (CPU) | visual_shots | TransNetV2 + PySceneDetect | substitute: color-histogram cut detector; swap in TransNetV2 on GPU tier |
| ✅ runs (CPU) | audio_lowlevel | librosa + openSMILE + Parselmouth | RMS, spectral, MFCC, chroma, F0 (pyin), onset, tempo. openSMILE/Praat to add |
| ✅ runs (CPU) | asr (transcript hub) | faster-whisper + WhisperX | word/segment timestamps; attaches Transcript to Ingest; emits speech_present, word_rate, asr_text. Default model small (CT2 = CPU on Apple Silicon); pass --asr-model large-v3 for production. Diarization (pyannote) is a later Social pass |
| ✅ runs (CPU) | language_lexical, language_syntax | spaCy + wordfreq + norms + minicons | freq_zipf, word_length, VAD/concreteness/AoA norms (NaN unless data/lexicons/*.csv present); tree_depth, dependency distance, content/noun/verb fractions. Uses en_core_web_sm (→ trf for production); LLM surprisal (minicons) is a later torch pass |
| ✅ runs (MPS) | visual_semantic (SigLIP2 + DINOv2) | SigLIP 2 + DINOv2 | per-frame image embedding (768-d), zero-shot probe scores (16-d), DINOv2 CLS embedding (384-d). --vision. Default base/small checkpoints (→ so400m / dinov2-large for production) |
| ✅ runs (MPS) | visual_motion (RAFT), visual_depth (Depth-Anything-V2), visual_action (VideoMAE) | SEA-RAFT, Depth-Anything, VideoMAE V2 | flow magnitude / camera / residual motion; depth mean/range/fg/entropy; Kinetics-400 posteriors + top label. RAFT substitutes SEA-RAFT; VideoMAE has a benign q/v-bias key-name mismatch in transformers 5.x (outputs verified correct) |
| ✅ runs (MPS) | audio_events (AST), audio_clap (CLAP), vocal_affect (wav2vec2-dim) | BEATs + CLAP, audEERING | AudioSet-527 tags + top; open-vocab audio embedding + probes; voice arousal/dominance/valence (depicted stream). --audio-hl. AST substitutes BEATs |
| ✅ runs (CPU/MPS) | faces (MTCNN), pose (Keypoint R-CNN) | InsightFace/OpenFace, MMPose RTMPose | n_faces/present/max_face_frac/det_prob + social/n_agents; n_persons/present/kp_score + social/min_pair_distance. cv2-free substitutes (facenet-pytorch, torchvision). Identity/AUs/gaze + 133-kpt whole-body are later (OpenFace isolated; RTMPose) |
| ✅ runs (MPS) | qwen_reasoning (Qwen2.5-VL-3B) | Qwen2.5-VL-7B | one VLM pass per window → JSON populating Social/Situation/Affect: scene_description, setting, indoor/outdoor, interaction_type, dominance, depicted emotion + valence/arousal. --reason (slow). 3B default (→ 7B for production) |
| ✅ runs (MPS) | text_emotion (GoEmotions), text_sentiment (CardiffNLP), language_surprisal (GPT-2) | RoBERTa-GoEmotions, twitter-roberta-sentiment, minicons | 28-emotion vector + top; neg/neu/pos sentiment + polarity scalar (affect/depicted); per-segment surprisal + entropy (bits). All consume the transcript; in --audio-hl |
| ✅ runs (MPS) | visual_emonet (EmoNet, Kragel 2019) | EmoNet (AlexNet, Sci Adv 2019) | 20-way image emotion-schema distribution per frame → affect/depicted/emonet(+top). Vendored port (ecco-laboratory), weights from OSF. --vision |
| ✅ runs (CPU/MPS) | face_emotion (HSEmotion enet_b0_8_va_mtl) | HSEmotion / EmotiEffLib (AffectNet SOTA) | 8 facial expressions + face valence/arousal per MTCNN face, averaged per frame → affect/depicted/face_emotion(+top), face_valence, face_arousal. Needs timm==0.9.16. --vision |
| ✅ runs (CPU) | visual_saliency (spectral-residual) | ViNet | saliency mean/peak/entropy + salient-area fraction. Model-free attention proxy; in --vision (ViNet for production) |
| ✅ runs (CPU) | event_segmentation (GSBS) — post-pass | GSBS | runs after all extractors over the assembled scalar matrix → situation/event_id (state per bin) + event_boundary (onsets). --events |
| ⬜ next | speech_diarization (pyannote), elicited affect (LIRIS/MuSe) | pyannote, LIRIS-ACCEDE | pyannote needs a HF token + accepted terms; no off-the-shelf elicited-affect checkpoint |
| ⬜ | vlm_reasoning (Qwen2.5-VL) | one consolidated VLM pass | Social/Situation/Affect/narrative |
| ⬜ | event_segmentation, text_emotion, elicited_affect | GSBS, GoEmotions, LIRIS/MuSe | |
| ⬜ | hosted (opt-in) | OpenAI text-embedding-3-large, GPT-5.x | gated by allow_hosted; per sign-off |
Constant-shape contract — DONE
Every stimulus can yield an identical channel set via an auto-generated channel
template (schema/channel_template.json, built by tools/build_channel_template.py
from a real full run — no hand-maintained spec lists). Run with --template schema/channel_template.json: channels not produced (class inapplicable to the
modality, or pass disabled) are filled as applicable=false, all-NaN skeletons with
the right dtype/dim/components. Verified: a CPU-only run + template = the same 95-channel
hierarchy as the full stack (e.g. kungfury: 78 measured + 8 skeleton; a CPU-only run has
correspondingly more skeleton channels). This unblocks Phase 3/4 stacking.
Code-review fixes (2026-07)
A verified review pass fixed: event_id dtype consistency across the degenerate GSBS
branch (constant-shape); extractor failures now tracked (n_failed/failed in the
summary + status=partial in corpus_index.csv) instead of being silently relabelled
“not applicable”; faces_present/pose_present computed as any-in-bin with NaN for
unmeasured bins (were derived from the mean, erasing missingness); temp workdirs cleaned
up; zero-length media/text now errors instead of writing an empty “ok” file; the
nearest resampler returns nearest-to-bin-center; MATLAB reader always orients vectors
time-first; featuresToTimetable excludes categorical class-code channels from the
analysis matrix; analyzeCorpus tolerates constant channels; and the batch index merges
across filtered runs. Shared frame decode DONE: Ingest caches decoded frames (capped
for very long films) so the ~9 visual passes share one decode instead of re-decoding.
Second review pass (2026-07-08)
Fixed: skeleton event/bool fill values no longer leak into analyses as real data
(featuresToTimetable + the search-index builder treat applicable=false as NaN/absent);
the design tool excludes candidate segments dominated by imputed/missing cells (they could
previously win the objective because data were missing); refreshAnalysis now produces the
full documented artifact set in one command (full-corpus + AV-subset stats/figures, NaN-safe
class lookups, crash-guarded contingency split); web search fixes (rounded-zero std wipeout,
High/Low toggle ignored on check, text-only play dead-end, index-like channels excluded,
missing-feature coverage shown); viewer works for audio/text-only stimuli with clean timer
teardown; walkthrough root detection; SigLIP2/CLAP text towers precomputed (verified
bit-identical to the combined forward; visual-semantic pass ~3× faster).
Known follow-ups
- F0 (pyin) is the runtime bottleneck (~0.85× audio duration); make optional / coarser.
- CLAP is fed 22 kHz audio (loses >11 kHz); optionally extract a 48 kHz stream for it.
- Replace CPU substitutes (histogram cuts) with the frozen GPU picks.
- Planned MATLAB APIs not yet built:
listFeatures,getFeatureMatrix,writeHumanChannel, lazy reads (see ANNOTATION_FORMAT §7 status note).
Deployment Feasibility
How the recommended Phase 1 feature set is realistically deployed by a small lab. Grounded in the
scoping-review catalogs (docs/scoping_review/) and aligned with the extractor contract in
IMPLEMENTATION_PLAN.md and the output format in ANNOTATION_FORMAT.md. Design anchors: local-first,
explicit NaN nulls, one common time grid, reproducible provenance.
1. Hardware tiers
Four tiers, derived from each tool’s catalog deployability tag (yes-cpu / yes-gpu /
yes-heavy-gpu / api-only) and sanity-checked against real VRAM:
- Tier A — CPU backbone (no GPU). All low-level visual and audio statistics, lexical and syntactic norms, and event-segmentation algorithms: librosa, openSMILE, Praat/parselmouth, scikit-image/OpenCV, spaCy. Runs on any laptop/server.
- Tier B — single 24 GB GPU workhorse. SigLIP / DINOv2, SEA-RAFT (flow), Whisper (ASR), BEATs (audio tagging), Qwen3-Embedding, RTMPose, Depth-Anything, and a 7B VLM (Qwen2.5-VL-7B at 4-bit). This tier covers the great majority of high-level features.
- Tier C — 48 GB+ GPU. Heavier models (InternVideo2, ViTPose-G, Q-Align, 70B/72B LLMs). Each is paired with a Tier-B substitute that should be preferred unless the accuracy gain is needed.
- Tier D — API-only (optional). Frontier embeddings and frontier multimodal judges. None are required; each has a local default and is used as an accuracy ceiling / validation oracle.
2. Environment & orchestration
The real blocker is incompatible dependency worlds (TensorFlow vs PyTorch vs CTranslate2 vs mmcv, plus MATLAB-origin SHINE/Rosenholtz code). Prescription:
- 5–6 locked
uv/conda environments, each with a resident GPU worker behind the uniformExtractorinterface (src/nfe/base.py). - A Snakemake/Prefect DAG orchestrating per-stimulus extraction, with transcript-as-hub ordering (ASR runs early because language/social/situational models consume the timestamped transcript).
- Ingest specifics: frame sampling at ~2 fps plus per-shot keyframes; audio extracted to 16 kHz (speech) and 44.1 kHz (music/acoustic) WAVs.
- Batching rules per model family; content-addressed caching at stage boundaries for idempotency and provenance (raw native-rate output cached so the common grid can be regenerated without re-running models).
- A single common-grid resampler with per-feature-type rules (continuous / probability / categorical
/ per-word / sparse-event) emitting constant-shape,
NaN-padded output (seeANNOTATION_FORMAT.md§2.4).
3. Runtime & storage
- Per-hour-of-stimulus real-time factor (RTF): a core config runs ~0.5–1× real-time — a 2-hour movie annotates overnight on Tier A+B.
- Cost drivers: the per-shot VLM, dense per-frame depth/saliency/segmentation, pymoten motion energy, and benepar constituency parsing.
- Storage budget: low tens of GB per hour of stimulus (dominated by dense embedding/vector channels; HDF5 chunked+gzip keeps this in check).
4. Features that benefit from hosted large models
Only 3–4 classes realistically gain from large hosted models, each with a local include default:
- High-level social semantics / theory-of-mind tagging.
- Transcript-level narrative / Event-Indexing situational tagging.
- Multimodal affect with rationale.
- (Optional) top-end embeddings.
Interface guidance: a thin API client behind the same Extractor interface; JSON-schema-validated
structured output; per-shot/per-scene granularity; batch + cache + retry; an opt-in allow_hosted
privacy gate; and using APIs mainly to validate/distill into the local 7–8B model rather than as a
runtime dependency. See the claude-api skill reference for Anthropic model IDs, structured output, and
pricing if wiring a hosted judge.
5. Recommended default configs
- Core (zero-API): Tier A + Tier B only. Fully local, overnight per feature-length film, covers the recommended core feature set.
- Extended: add Tier C models where the accuracy gain is justified.
- Hosted (opt-in): enable Tier D for the 3–4 classes above, gated by
allow_hosted.
Implementation Plan — Narrative Feature Extraction
This document is the master plan. It is intentionally concrete about Phase 1–2 (buildable now) and directional about Phase 3–4 (depend on human selection, real movies, and Phase 1 outcomes).
Phase 1 — Scoping review (automated; in progress)
Deliverable: a complete scoping review of computational annotation tools, organized in a logical semantic hierarchy, with descriptions and recommendations (best-in-class vs likely-redundant).
Method: a fan-out of research agents, one per feature subclass, each grounding a structured catalog of current (2023–2026) tools in web sources, followed by cross-cutting analyses (hierarchy, redundancy, output schema, deployment feasibility) and synthesis.
Feature classes covered
- Visual
- Low-level static (luminance, contrast, color, spatial frequency, edges)
- High-level static (objects, scenes/places, attributes)
- Faces, bodies, gaze, facial expression
- Dynamic / motion (optical flow, motion energy, cut detection)
- Action & event recognition (video understanding)
- Saliency, aesthetics, depth
- Audio
- Low-level acoustic (loudness, pitch, spectral, MFCC, onsets, tempo)
- High-level audio (sound events, scenes, music vs speech, MIR)
- Speech (ASR + timestamps, diarization, prosody, vocal affect)
- Language / semantic
- Low-level lexical & word-level (frequency, concreteness, affect norms, surprisal, POS, NER)
- Syntax & grammar (parsing, complexity, coreference, dialogue acts)
- High-level semantics, discourse & narrative (embeddings, topics, coherence, story arc)
- Social / situational / affective
- Social & interpersonal (agents, who-talks-to-whom, interaction type, theory of mind)
- Situations, schemas, scripts & event segmentation (event boundaries, situation models)
- Emotion & affect (multimodal valence/arousal + categorical; depicted vs elicited)
Outputs (written under docs/scoping_review/):
00_overview.md, 01_hierarchy.md, per-class catalog sections, 08_redundancy.md, 09_recommendations.md.
Schema & deployment outputs land in schema/ and docs/design/.
Phase 2 — Pipeline build (pending Phase 1 human sign-off)
- Feature selection checkpoint — human developer reviews Phase 1 recommendations and freezes the final feature/model set (core vs extended tiers).
- Model deployment — for each selected model: download + pin locally (HF/torch), or build a thin
API client where local is infeasible. Each lives in
src/nfe/extractors/behind a uniform interface:extract(stimulus, time_grid) -> {feature: array_over_time, metadata}. - Ingest — accept a movie or story; demux video frames (configurable sampling) and audio; generate a timestamped transcript (ASR or supplied) that feeds language/social/situational models.
- Common-grid resampling — map every native-rate feature onto the configured grid (default 1 Hz), recording the native rate and resampling method per feature.
- Emit — write one hierarchical annotation file per stimulus (see
schema/), withNaNfor inapplicable features and reserved slots for later human annotations. - MATLAB reader —
matlab/readAnnotations.m+ helpers load annotations into MATLAB structs/tables.
Applicability rule: stimulus modality (video / audio / text) determines which classes run; the rest
emit NaN so the output shape is constant across stimuli.
Phase 3 — Corpus annotation
- Assemble a corpus from user-supplied media and findable/downloadable sources (respecting licensing).
- Maintain a stimulus manifest (id, source, modality, duration, rights).
- Run the Phase 2 pipeline over the corpus; cache per-model outputs; store annotations in
annotations/output/.
Phase 4 — Analysis & dissemination
- MATLAB analysis loading annotations across all stimuli: descriptive structure, cross-feature correlations conditioned on stimulus type, principal components of the annotation set, and a network graph of how feature classes relate over time, with visualizations.
- Review paper documenting the models/algorithms and the empirical relationships among measures.
- Interactive web interface to search for movie/story segments high in particular features.
- Experimental-design tool: select stimulus segments that maximize variance across the major annotation principal components while maximizing independence of feature time series — formally, an optimal-design objective (e.g. maximize the determinant / D-optimality of the leading components over the selected set), applied to the corpus to produce a high-variance, low-redundancy stimulus set.
Open decisions for the human developer (Phase 1 → 2 gate)
- Common grid rate (default 1 Hz) and whether multiple rates are emitted.
- Core vs extended feature tier membership.
- Output container format (recommendation produced in
schema/). - Budget/appetite for API-only large models vs local-only constraint.
- Licensing policy for Phase 3 corpus sourcing.
Phase 3 — Corpus Annotation
Assemble the stimulus corpus and run annotations across it, producing one constant-shape annotation file per stimulus for the Phase 4 analysis.
Corpus & manifest
tools/build_manifest.py scans data/movies/** and data/stories/** and writes
data/manifest.csv (+ .json): id, path, source, modality, duration_sec, fps, width, height, has_video, has_audio, rights. Rebuild any time media is added.
Current corpus: 83 stimuli / ~470.6 min (~7.8 h) — 49 lab spacetop clips + 4 short films
(3 CC-BY Blender open films + Kung Fury) + 29 Narratives spoken-story audio clips
(data/stories/narratives/, ~5.3 h) + 1 pure-text sample story (53 audiovisual, 29 audio-only,
1 text-only). To grow it:
drop files under data/movies/<source>/ (movies/audio) or data/stories/<source>/
(audio/text stories) and re-run the manifest (see ../ADDING_MOVIES.md;
credentialed sets in ../EXTERNAL_STIMULI.md).
Batch runner
python -m nfe.batch annotates manifest entries:
PYTHONPATH=src .venv/bin/python -m nfe.batch \
--manifest data/manifest.csv --out annotations/corpus \
--template schema/channel_template.json \
--vision --audio-hl --events --source spacetop --max-dur 65
- Constant shape:
--template schema/channel_template.json→ every file has the same 95-channel hierarchy (un-run/inapplicable channels areapplicable=falseNaN), so Phase 4 stacks them into rectangular matrices. - Resumable: skips a stimulus whose
<id>.h5already exists (--no-skipto force). - Crash-safe: rewrites
annotations/corpus/corpus_index.csv(id, status, n_measured/n_skeleton, seconds, error) after every stimulus. - Isolated: one stimulus erroring doesn’t stop the batch.
- Filters:
--source,--ids,--max-dur,--limit. Passes:--vision --audio-hl --reason --events.--fps(default 2 for batch).
Output: annotations/corpus/<id>/<id>.h5 (+ .manifest.json) per stimulus.
Runtime guidance (this M1 Ultra, MPS)
Per-pass cost (≈, at 2 fps): CPU low-level + ASR + language are cheap; pose and the
frame VLM/depth/action passes dominate; --reason (Qwen2.5-VL) is ~50 s/window and
is impractical for long films here. Recommended tiers:
| Goal | Config | Notes |
|---|---|---|
| Broad, fast corpus | --audio-hl --events (CPU+audio) | many stimuli; visual = NaN skeleton |
| Rich per-stimulus | --vision --audio-hl --events | all classes measured; ~minutes/clip |
| + high-level reasoning | add --reason | only on short clips / sampled windows here |
Scale-up: run the full corpus in the background (overnight); the index + skip-existing
make it safely resumable. For production-scale --reason, use the 7B on a CUDA GPU or
shot-sampled windows.
Adding Movies and Stories to the Dataset
How to grow the corpus and refresh everything that depends on the specific set of stimuli (annotations, search index, analysis figures, and the corpus-specific numbers in the docs). The extractor code, schema/template, scoping review, and format spec do not change when you only add media.
For the user
1. Add the media
Drop files under a source subfolder of data/movies/:
data/movies/<source>/... e.g. data/movies/myset/clip01.mp4 (movies / audio)
data/stories/<source>/... e.g. data/stories/myset/story01.txt (stories)
- Movies: any video container (
.mp4 .mkv .mov .avi .webm .m4v). Modality is detected automatically (audiovisual / video-only). - Audio stories: audio files (
.wav .mp3 .m4a .flac .aac .ogg) — detected asaudio-only; visual channels are emitted asNaN(not applicable). - Pure-text stories: plain-text files (
.txt .text .story) — detected astext-only. The text is placed on an estimated reading-rate timeline (~3 words/s) so it shares the 1 Hz grid; the language, text-emotion, surprisal, and event passes run, and visual/audio channels areNaN. (Markdown.mdis treated as documentation, not a story.) Put stories underdata/stories/<source>/(ordata/movies/— both are scanned). - Use one subfolder per source (keeps provenance/rights tidy). If the material has
licensing terms, add a
SOURCES.mdin that subfolder (seedata/movies/open/SOURCES.md). - Credentialed neuroimaging stimuli (HCP 7T movies, CamCAN Hitchcock clip): these need a
data-access request — see
EXTERNAL_STIMULI.md. Placeholder foldersdata/movies/hcp/anddata/movies/camcan/are ready for them.
2. Refresh — two options
Option A (easiest): tell Claude “I added media under data/movies/<source>/,
refresh the dataset and docs”. Claude runs the runbook below and updates everything.
Option B (do it yourself):
# 1) manifest + annotate NEW stimuli (resumable) + rebuild search index
tools/refresh_corpus.sh # add --reason for the VLM pass; --no-skip to redo all
# 2) regenerate figures + the corpus stats the docs use (in MATLAB)
matlab -batch "addpath matlab; refreshAnalysis('annotations/corpus')"
# 3) update the corpus-specific numbers in the docs from analysis/corpus_stats.json
# (ask Claude, or edit the files listed in the runbook below)
3. Verify
annotations/corpus/corpus_index.csv— one row per stimulus,status= ok/skipped/error.analysis/figures/*.pngandanalysis/corpus_stats.json— regenerated.- Web search: rebuilt automatically by step 1; reopen
analysis/web/index.html. - Spot-check a new stimulus in the viewer:
annotationMovieViewer(movie, annDir).
Notes
- Resumable: existing annotations are skipped; only new stimuli are processed. Long
films take a while (especially
--reason); run overnight if needed. - Template is stable: adding media does not change the 95-channel template. Only
rebuild it (
tools/build_channel_template.py) if you change the extractor set — then re-annotate everything with--no-skip. - Hardware/runtime guidance:
docs/design/PHASE3_CORPUS.md,design/DEPLOYMENT_FEASIBILITY.md.
Agent runbook (for Claude)
When asked to refresh after media was added:
-
Annotate + index —
bash tools/refresh_corpus.sh(forward--reason/--source/--no-skipas requested). Resumable. For many/long stimuli run it in the background and monitorannotations/corpus/corpus_index.csv(count*/*.h5, watch forstatus=error). This rebuildsdata/manifest.csvandanalysis/web/segments.json. -
Analysis + stats — in MATLAB (via MCP):
addpath matlab; cd <root>; refreshAnalysis("annotations/corpus"). Regeneratesanalysis/figures/*.pngand writesanalysis/corpus_stats.json. -
Update corpus-dependent docs from
analysis/corpus_stats.json(key → where).refreshAnalysisexports full-corpus stats underfull.*, audiovisual-subset stats underav_subset.*(the paper’s structural figures are AV-subset), and NaN for any class that drops out of the analysis — a NaN here means “not enough data”, not “update the doc to NaN”.stat key update in n_stimuli,total_minutes,by_source,by_modalityREADME.md(Phase 3 row),docs/CONTENTS.md(Datasets + folder map count),docs/REVIEW_PAPER.md§5,docs/design/PHASE3_CORPUS.mdn_timepoints,full.n_channels_analyzed,av_subset.n_channels_analyzedREVIEW_PAPER.md§5/§6full.pcs_to_80,av_subset.pcs_to_80(+pc1_5_pct)REVIEW_PAPER.md§6.2,PHASE4_ANALYSIS.mdclass_visual_social,class_audio_social,class_audio_visualREVIEW_PAPER.md§6.3,PHASE4_ANALYSIS.mdcontingency.visual_social_*,contingency.audio_social_*REVIEW_PAPER.md§6.4design_greedy,design_random,design_k,design_n_source_stimuliREVIEW_PAPER.md§7,PHASE4_ANALYSIS.mdsearchable segment/channel counts from build_search_index.pystdout →PHASE4_ANALYSIS.md -
Surface results —
SendUserFilethe regeneratedanalysis/figures/*.pngif useful. -
Memory — update the project memory with the new corpus size and date.
-
Sanity checks —
corpus_index.csvhas 0 errors;readAnnotationCorpusloads the expected N; the constant 95-channel shape still holds across all files; if a newSOURCES.md/rights are needed for the added source, create it.
Edge cases
- New modality (audio-only / text-only): supported. Confirm inapplicable classes are
present as
applicable=falseNaN skeletons (the template fill handles this). Text stories run the language/affect/event passes on an estimated reading-rate timeline (--audio-hl --eventsreaches text-emotion + surprisal; lexical/syntax are always on). - Extractor set changed (not just new media): rebuild the template
(
tools/build_channel_template.pyon a full-stack short clip) → re-annotate all with--no-skip→ then steps 2–6. - Failures: per-stimulus errors are isolated and logged in
corpus_index.csv(status=error, message); investigate individually.
Does NOT need updating when only media is added
src/nfe/**, matlab/**, schema/** (template stable), docs/scoping_review/**,
docs/design/ANNOTATION_FORMAT.md, docs/design/PHASE2_*, and this file.
Obtaining external neuroimaging stimuli (Narratives, HCP, CamCAN)
Three widely-used naturalistic-fMRI stimulus sets that enrich this corpus and enable comparison with a large body of published brain data. The Narratives spoken-story collection is openly available and has already been downloaded and integrated; the HCP 7T movie clips and the CamCAN Hitchcock clip require data-access requests. This page gives provenance/licensing for each and how to (re)obtain and integrate them.
| Stimulus | What it is | Openly downloadable? | Channel | Status |
|---|---|---|---|---|
| Narratives | 29 spoken-story audio clips (~5.3 h) | ✅ dataset CC0; stimuli shared for non-commercial research (fair use) | OpenNeuro ds002345 | integrated |
| HCP 7T movies | 4 concatenated .mp4s of CC Vimeo clips + Hollywood excerpts | ❌ (free account + data-use terms; Hollywood parts copyrighted) | ConnectomeDB | request |
| CamCAN movie | 8-min edit of Hitchcock’s Bang! You’re Dead | ❌ (application + agreement; copyrighted) | Cam-CAN data access | request |
1. Narratives spoken-story stimuli (obtained)
What it is. 29 naturalistic spoken-story audio clips (~3–56 min, ~5.3 h total) from
the “Narratives” fMRI dataset (Nastase et al., 2021), OpenNeuro ds002345 — natural stories
(Pie Man, Tunnel, Slumlord, Milky Way, Sherlock, Merlin, …), several scrambled/paraphrased
control variants, and a few short TV/film audio clips. Integrated here as audio-only
stimuli under data/stories/narratives/ (ASR transcribes them; audio/language/affect/event
passes run; visual channels are NaN).
How it was obtained (reproducible). Downloaded from OpenNeuro’s public S3 bucket:
base="https://s3.amazonaws.com/openneuro.org/ds002345/stimuli"
for s in pieman tunnel slumlordreach ... ; do curl -fsSL -o "data/stories/narratives/${s}_audio.wav" "$base/${s}_audio.wav"; done
(The full file list is on S3 under ds002345/stimuli/; word/phoneme transcripts and Gentle
alignments are also in ds002345 if you want ground-truth transcripts instead of ASR.)
License / terms. The dataset is CC0, but the audio clips are not public domain —
they are shared for non-profit, non-commercial scholarly research under “fair use” (some
are copyrighted TV/film audio); do not redistribute. Attribution + details:
data/stories/narratives/SOURCES.md. Rights holders may request removal via the dataset’s
contact.
Status. Downloaded and catalogued in the manifest; annotation runs via the standard
audio config (--audio-hl --events --template).
2. HCP 7T movie-watching stimuli
What it is. Four movie runs (MOVIE1–MOVIE4), each a ~12–14 min .mp4 concatenating
short (1–4.3 min) clips from two sources:
- Creative-Commons Vimeo independent films (filenames marked
CC) — genuinely open, - Hollywood film excerpts (the set published by Cutting et al.) — copyrighted,
plus a repeated Vimeo “validation” clip shown across runs. Metadata files
HCP_7T_Movie_Info.csv(clip origins) andHCP_7T_Movie_Clip_Timing.csv(start/stop of each clip and REST block) accompany the stimuli.
How to obtain (official).
- Register for a free account at ConnectomeDB — https://db.humanconnectome.org.
- Accept the HCP Open Access Data Use Terms.
- Open the WU-Minn HCP 1200 Subjects (S1200) project page and find the
“7T Movie Resources” — download the 7T movie stimulus zip plus
HCP_7T_Movie_Info.csvandHCP_7T_Movie_Clip_Timing.csv. Direct project link: https://db.humanconnectome.org/data/projects/HCP_1200. - Open subset (optional): if you only need the freely-licensed portions, the
CC-named clips are CC Vimeo films;HCP_7T_Movie_Info.csvlists each clip’s origin, so those can be fetched individually. (This will not reproduce the exact HCP stimulus, which also includes the copyrighted Hollywood excerpts and specific concatenation/timing.)
Licensing. The Hollywood excerpts are copyrighted; HCP shares them under its data-use
terms for research use — do not redistribute. The CC clips carry their Creative-Commons
terms (attribution).
Integrate. Put the .mp4s in data/movies/hcp/ and refresh (below).
3. CamCAN movie-watching stimulus
What it is. An 8-minute edited version of Alfred Hitchcock’s “Bang! You’re Dead” (Alfred Hitchcock Presents, S7E2, 1961), condensed from the ~25–30 min original while preserving the plot. It is copyrighted (Universal / the Hitchcock estate). The specific 8-min CamCAN edit — not just the original episode — is what matches the published data and its extensive existing annotations.
How to obtain (official).
- Apply through the Cam-CAN data access portal — https://camcan-archive.mrc-cbu.cam.ac.uk/dataaccess/.
- Provide a valid academic affiliation and a specific hypothesis, and request the movie-watching data/stimulus. Notes from Cam-CAN: they reject vague “all data” requests and student-submitted applications — a supervisor/PI must apply, selecting only the data types justified by the proposal.
- On approval you receive sftp download instructions; the Hitchcock stimulus is included.
Licensing. Copyrighted TV footage provided for research under the Cam-CAN agreement — do not redistribute. (The full original episode airs on some streaming services, e.g. Roku, but that is not the 8-min Cam-CAN edit.)
Integrate. Put the clip in data/movies/camcan/ and refresh (below).
Why these weren’t downloaded automatically
Both require a personal account and a data-use agreement that you must accept, and both contain copyrighted Hollywood/Hitchcock footage. I have no such accounts, and pulling the copyrighted content from unofficial sources would not be appropriate. Obtain them through the official channels above; then integration is one step.
After you obtain them
data/movies/hcp/ ← HCP 7T MOVIE1–4 .mp4 files
data/movies/camcan/ ← the 8-min Hitchcock clip
Then run the standard refresh (see ADDING_MOVIES.md) — or just tell
Claude “refresh the dataset”:
tools/refresh_corpus.sh # annotates the new stimuli
matlab -batch "addpath matlab; refreshAnalysis('annotations/corpus')"
The new stimuli are annotated on the same 95-channel constant-shape template and folded into all analyses and the search interface automatically. If you also have the HCP clip-timing CSV, tell me and I can align annotations to the per-clip boundaries.
Sources. HCP: 7T imaging protocol, ConnectomeDB S1200, HCP-users: movie stimuli, 7T movie clip-info wiki. CamCAN: data access portal, Cam-CAN protocol (Shafto et al. 2014, BMC Neurology), Bang! You’re Dead (Hitchcock wiki).
Phase 4 — Analysis & Dissemination
MATLAB tools that load the annotated corpus and analyze the structure of the
annotations across stimuli, plus interactive viewing. Builds on the constant-shape
corpus from Phase 3 (annotations/corpus/).
Done
readAnnotationCorpus(folder)
Loads every <id>/<id>.h5 under folder into one analysis structure:
C.ids/C.ann— stimulus ids and full annotation structsC.channels— shared scalar-channel set (constant-shape → identical across stimuli)C.X[sumT x P]— all timepoints of all stimuli stacked into one rectangular matrixC.stim,C.time_sec,C.nT— stimulus id and within-stimulus time per row ofX
Robust to partially-written files (skips + warns), so it can run while the Phase 3
batch is still filling the corpus. X is the direct input to correlation / PCA / the
design tool. Example:
C = readAnnotationCorpus("annotations/corpus");
R = corr(C.X, 'rows','pairwise'); % cross-feature correlation across the corpus
[coeff,score,~,~,expl] = pca(normalize(C.X)); % principal components of the annotation set
annotationMovieViewer(movieFile, annPath)
Plays a movie with its annotation time series synced below and a red marker on each
series tracking the playback position. Play/Pause + scrub slider; "Channels" and
"Speed" name-value options. Example:
m = "data/movies/spacetop/videos/ses-01/ses-01_run-01_order-04_content-parkour.mp4";
annotationMovieViewer(m, "annotations/corpus/ses-01_run-01_order-04_content-parkour")
analyzeCorpus(C)
Structure analysis over the stacked corpus matrix → figures in analysis/figures/:
clustered cross-feature correlation heatmap, PCA scree/cumulative variance,
a channel correlation network (nodes colored by feature class, edge |r|≥thresh),
and a feature-class network (edge = mean |r| between classes). Returns R,
cluster order, PCA coeff/score/explained, and classR. NaN-aware z-scoring before
PCA. NaN-aware. On the 83-stimulus modality-mixed corpus, 12 PCs reach 80% over the 26
audio/language channels shared across modalities; on the 53-stimulus audiovisual subset,
18 PCs reach 80%, with modest class couplings — strongest affect↔social (mean |r| 0.15), then
visual↔social and audio↔social (0.11) — roughly stable across content. (The depicted-affect,
social, and situational scalars come from the VLM reasoning pass, now run across the whole
audiovisual subset; the language class stays under-represented in the dialogue-light AV clips;
for the visual/social structure analyze the audiovisual subset — see REVIEW_PAPER §5–6.)
selectStimulusSet(C)
Experimental-design / stimulus-selection tool. Splits every stimulus into fixed-length
candidate segments and greedily maximizes log det(cov) of the concatenated
annotation time series (projected onto the leading PCs) — jointly rewarding high
variance across the major feature dimensions and independence of the feature time
series (D-optimality over the annotation space). Returns sel.table (selected
segments) and objTrace vs a random baseline. Candidate segments dominated by
missing/imputed data are excluded (they cannot win the objective by being blank). On the
83-stimulus corpus, selecting 20 × 10 s segments reaches log det(cov) 12.8 vs 2.5 for
random — well over 5× the generalized variance — drawing from 16 distinct stimuli across
modalities.
refreshAnalysis(corpusFolder)
One-command refresh matching the docs: full-corpus stats + design figure, audiovisual-subset
structural figures (Figs 1–5 of the review paper) and class couplings, the speech-rich vs
speech-sparse comparison, and analysis/corpus_stats.json with both full and AV-subset
numbers (missing classes export as NaN, never empty).
See also ../CONTENTS.md (full guide) and ../walkthrough.m.
Interactive web search — analysis/web/
Static browser app over a precomputed segment index (tools/build_search_index.py →
analysis/web/segments.json; 5684 × 5 s segments, 174 searchable channels — index-like
channels and not-applicable skeleton fills are excluded, and interpretable vector channels are
expanded into per-component columns so e.g. EmoNet’s 20 emotion categories and the 8 facial
expressions are individually searchable). Pick any combination of features,
toggle High/Low, and segments are ranked by mean z-score (with a features-covered count when
a segment lacks some selected channels); ▶ Play seeks the actual clip to that segment
(audio stories play as sound; text-only has no playback). Serve from the project root
(python3 tools/serve.py → analysis/web/index.html; the bundled server adds HTTP
Range support so video seeking works). Rankings validate well —
high flow surfaces action scenes, high word-rate the dialogue clips, EmoNet “Aesthetic
Appreciation” the beach-sunset clips, etc. Launch +
rebuild notes: ../../analysis/web/app_readme.md.
Review paper
../REVIEW_PAPER.md — draft review covering the models/algorithms
behind each annotation and the empirical structure of the annotation space (redundancy,
dimensionality: 12–18 PCs→80%, class networks — couplings are modest, strongest
affect↔social ≈ 0.15, and roughly stable across speech density — and the D-optimal design
tool). The group-conditioned figure is analysis/figures/class_coupling_by_group.png.
Phase 4 complete
Corpus reader, synced viewer, correlation/PCA/network analysis, experimental-design tool, web search interface, and the draft review paper are all in place. Future work (per the paper’s §9): swap in production models, scale the VLM reasoning pass, add elicited-affect + diarization, validate against human ratings / brain data.
Computational Annotation of Movies and Stories: Models, a Second-by-Second Annotation Pipeline, and the Structure of Naturalistic Feature Spaces
Draft review paper — Narrative Feature Extraction project. Figures referenced here are
generated by matlab/analyzeCorpus.m and matlab/selectStimulusSet.m into
analysis/figures/.
Abstract
Naturalistic stimuli — movies and spoken stories — are increasingly used to study perception, language, social cognition, and emotion under ecologically rich conditions. Their analytic value depends on dense, well-organized annotations of what the stimulus contains at each moment. We describe an infrastructure that converts an arbitrary movie or audio story into second-by-second computational annotations spanning visual, auditory, linguistic, social, situational, and affective features, produced by a curated set of 23 best-in-class (or best-available-locally) models. The annotations are emitted in a single constant-shape, hierarchical format that loads natively into MATLAB, with explicit nulls where a feature does not apply. We annotate an 83-stimulus, ~7.8-hour corpus spanning three modalities — audiovisual clips and films, spoken-story audio (the open Narratives collection), and a text story — and analyze the structure of the resulting feature space. The annotations are highly multidimensional (12 principal components explain 80% of the variance in the modality-shared audio/language channels; 18 in the audiovisual subset that adds the visual/social/situational/affective channels), organized into interpretable redundancy blocks. Cross-class couplings are modest — the strongest is affect↔social (mean |r| ≈ 0.15), followed by visual↔social and audio↔social (≈ 0.11) — and roughly stable across content; language-class scalar features are defined only by dialogue not present throughout, so that class is under-represented in the corpus-wide analysis, while the spoken stories make the corpus richly language-annotated. Finally, we use the annotation space to drive an experimental-design tool that selects high-variance, low-redundancy stimulus segments. All tools, derivatives, and an interactive viewer and web search interface are released with the project.
1. Introduction
A central methodological problem in naturalistic neuroimaging and cognitive science is annotation: to relate brain or behavioral responses to a movie or story, one needs a time-resolved description of the stimulus across many feature classes simultaneously. Hand annotation is slow, low-dimensional, and hard to standardize. Recent advances in computer vision, audio understanding, speech recognition, and language and vision-language models make it feasible to annotate naturalistic stimuli automatically across a wide range of features, from low-level image statistics to high-level social and situational content.
This project builds that infrastructure in four phases: (1) a scoping review of
computational tools per feature class (docs/scoping_review/); (2) a pipeline of
selected models producing a hierarchical, second-by-second annotation; (3) annotation
of a corpus; and (4) analysis of the annotation structure plus dissemination tools.
This paper documents the models and algorithms, the annotation format, and the
empirical structure of the annotations over the corpus.
2. Feature taxonomy and annotation hierarchy
Annotations are organized into a semantic hierarchy of six top-level classes, each with
subclasses (full tree: docs/scoping_review/01_hierarchy.md):
- Visual — low-level static (luminance, contrast, color, edges, spatial-frequency slope, entropy), high-level static (object/scene semantics), faces/bodies/gaze, dynamic motion, action, and saliency/depth.
- Audio — low-level acoustics (loudness, spectral shape, MFCC, chroma, pitch, onset, tempo), high-level audio events/scenes, and speech.
- Language — lexical/word-level, syntactic/grammatical, and high-level semantic features computed from the transcript.
- Social — agents present, interaction type, dominance/affiliation, proximity.
- Situation — setting, event boundaries, and situational dimensions.
- Affect — depicted (expressed) and elicited emotion across face, voice, text, and fusion.
Each leaf of the hierarchy is a channel — a value series sampled on a common time grid (default 1 Hz). This taxonomy is the organizing backbone of both the scoping review and the on-disk format.
3. Models and algorithms
The pipeline implements 23 extractors. For each we name the model actually deployed and, where a lighter local stand-in was used, the best-in-class model it substitutes for (production targets). Local-first deployment runs on a single Apple-Silicon GPU (Metal/ MPS) or CPU; a small number of high-level reasoning features can optionally be routed to hosted large models.
3.1 Visual
- Low-level static (scikit-image / NumPy): per-frame luminance, RMS contrast, RGB/ HSV color means, Hasler–Süsstrunk colorfulness, Canny edge density, Shannon entropy, and the slope of the radially averaged Fourier power spectrum.
- High-level semantics: SigLIP 2 (sigmoid language–image pretraining) provides a per-frame image embedding and zero-shot probe scores over a text prompt set; DINOv2 provides a label-free self-supervised embedding. These are kept as distinct representations (interpretable text-probed vs. representational-similarity).
- Motion: dense optical flow via RAFT (substituting SEA-RAFT) → flow magnitude, a global-median “camera motion” term, and a residual “object motion” term.
- Depth: Depth-Anything-V2 → per-frame relative-depth statistics (mean, range, foreground fraction, depth entropy).
- Action: VideoMAE over sliding 16-frame clips → Kinetics-400 action posteriors and the top action label.
- Faces: MTCNN (facenet-pytorch; substituting InsightFace/OpenFace) → face count, presence, largest-face size, detection confidence; also feeds Social (agents present).
- Pose: torchvision Keypoint R-CNN (substituting MMPose RTMPose) → person count, mean keypoint confidence, and nearest-pair distance (a proximity proxy feeding Social).
- Saliency: spectral-residual saliency (substituting ViNet) → saliency mean/peak/ entropy and salient-area fraction.
- Shots: a color-histogram cut detector (substituting TransNetV2 + PySceneDetect) → cut events and a running shot index, feeding Visual, Situation, and Action.
3.2 Audio and speech
- Low-level acoustics (librosa): RMS, spectral centroid/bandwidth/rolloff/flatness, zero-crossing rate, onset strength, pitch (pYIN), tempo, MFCC and chroma vectors.
- Audio events/scenes: AST (Audio Spectrogram Transformer; substituting BEATs) → AudioSet-527 tag posteriors and top tag.
- Open-vocabulary audio: CLAP → an audio embedding and zero-shot scores over a prompt set (speech / music / noise / laughter / …), kept distinct from AST’s fixed taxonomy.
- Speech (the transcript hub): faster-whisper transcribes with word- and segment-level timestamps. The resulting transcript is attached to the pipeline and consumed by all language, social, situational, and text-affect passes — speech-present, word-rate, and per-bin text are emitted directly.
- Vocal affect: the audEERING wav2vec2 dimensional model → continuous voice arousal / dominance / valence (a depicted affect stream).
3.3 Language (from the transcript)
- Lexical (spaCy + wordfreq + optional psycholinguistic norms): per-word Zipf frequency, length, and — when the standard norm tables are supplied — valence, arousal, dominance, concreteness, and age-of-acquisition.
- Syntax (spaCy): per-utterance dependency-tree depth, mean dependency distance, content-word fraction, and noun/verb fractions.
- Surprisal: a GPT-2 language model → per-segment surprisal and entropy (bits) — the standard predictability regressors.
3.4 Social, situational, and affective; event structure
- Consolidated reasoning: a single Qwen2.5-VL vision-language pass per time window emits a structured JSON that populates Social (interaction type, dominance), Situation (setting, indoor/outdoor, scene description), and Affect (depicted emotion, valence, arousal) at once — replacing several separate high-level models with one pass.
- Text emotion: RoBERTa-GoEmotions → 28-way emotion scores over the transcript, a depicted (content) affect stream distinct from voice/face/elicited affect.
- Text sentiment: CardiffNLP twitter-roberta (trained on ~124M tweets + TweetEval) → negative/neutral/positive posteriors and a signed polarity scalar over the transcript, a coarse valence-polarity complement to the fine-grained GoEmotions categories.
- Image emotion (EmoNet): the Kragel et al. (2019) AlexNet-based model → a 20-way distribution over normative emotion categories for each whole frame (“emotion schemas embedded in the visual system”), a validated image-level depicted-affect signal.
- Facial affect: HSEmotion (EfficientNet trained on AffectNet, state-of-the-art facial-expression recognition) reads each MTCNN-detected face → eight expression posteriors plus continuous facial valence and arousal, averaged over faces per frame.
- Event segmentation: GSBS (Greedy State Boundary Search) runs as a post-pass over the assembled feature matrix, yielding data-driven event states and boundaries.
Affect is thus captured from five complementary sources — image (EmoNet), face (HSEmotion), voice (audEERING), text emotion (GoEmotions) and text sentiment (CardiffNLP), plus the scene-level VLM stream. A deliberate design principle is to keep complementary signals separate rather than collapse them — e.g., interpretable vs. representational embeddings, fixed-taxonomy vs. open-vocabulary audio tagging, and depicted vs. elicited affect.
4. Pipeline and output format
Media are decoded with PyAV (frames + audio) without a system ffmpeg dependency. Each
extractor produces channels at its native rate; a common-grid resampler maps every
channel onto the shared 1 Hz grid (center-referenced bins) with a per-channel reduction
rule. Output is a single HDF5 file per stimulus with hierarchical groups mirroring
the taxonomy, per-channel metadata (model, version, native rate, units, resample op), a
reserved /human/ group for later human annotation, and a readable JSON sidecar
manifest (full spec: docs/design/ANNOTATION_FORMAT.md).
A central feature is the constant-shape contract: an auto-generated channel template
ensures every stimulus yields the same channel set; features that do not apply to a
stimulus (e.g., visual features for an audio-only story, or a pass that was not run) are
present with applicable=false and all-NaN. This makes the corpus directly stackable
into rectangular matrices for analysis, and distinguishes not-applicable from
not-measured from measured-zero. A lightweight MATLAB reader loads any annotation
into structs and timetables.
5. Corpus
The analyzed corpus comprises 83 stimuli totaling ~470.6 minutes (~7.8 hours) across
three modalities: 49 short audiovisual clips from a naturalistic-fMRI stimulus set, 4
short films (3 Creative-Commons Blender open films — Big Buck Bunny, Sintel, Tears of Steel —
and Kung Fury), 29 spoken-story audio stimuli (~5.3 h) from the openly-released
Narratives collection (Nastase et al., 2021; OpenNeuro ds002345), and 1 pure-text sample
story — i.e. 53 audiovisual, 29 audio-only, and 1 text-only stimuli. Every stimulus was
annotated under the same constant 95-channel template; classes that do not apply to a modality
(e.g. visual features for an audio story) are present as applicable=false null skeletons.
Stacked, the corpus yields 28,237 one-second timepoints, demonstrating that stimuli of very
different modalities share one common analysis format.
Because the corpus is modality-mixed and now dominated (in timepoints) by audio stories, the channels populated across all stimuli are the audio, speech, and language features; the visual/social/situational channels are — correctly — undefined for the audio and text stimuli. Corpus-wide analysis of the shared scalar channels therefore reflects this audio/language core (26 channels survive a ≤40%-missing filter), while modality-specific structure is analyzed on the 53-stimulus audiovisual subset (below; 51 channels). Adding the spoken stories also transformed the corpus from dialogue-light to language-rich: word-level surprisal is defined in 91% of story timepoints, versus sparse coverage in the audiovisual clips.
The vision-language reasoning pass was run across the entire audiovisual subset, so its depicted-affect, social-interaction, and situational scalars are populated throughout that subset and enter the cross-class analysis below. Affect is in fact the most richly sourced class — image-level (EmoNet), face-level (HSEmotion), voice (audEERING), text emotion and sentiment, and the scene-level VLM stream — but the scalar cross-class analysis below is dominated by the dense VLM affect scalars: the face-level valence/arousal and dialogue-level sentiment are only defined when a face or speech is present, so across the intermittently-peopled, dialogue-light audiovisual clips they exceed the ≤40%-missing filter and drop out of the aggregate matrix (they remain fully available per-timepoint as fMRI regressors, and EmoNet’s dense 20-way image-emotion distribution is retained as a feature vector rather than a single scalar). One class remains under-represented in the scalar analysis for the same reason: language is richly populated by the audio stories but sparse in the dialogue-light audiovisual clips, so it survives the missing-data filter in the full corpus but not in the audiovisual subset. Interpret the couplings below accordingly.
6. The structure of the annotation space
6.1 Redundancy structure
The clustered cross-feature correlation matrix (Fig. 1, audiovisual subset) shows
interpretable redundancy blocks — for example, the brightness channels (luminance, R/G/B and
value means) are nearly collinear, and groups of spectral audio descriptors co-vary. These
blocks are expected and motivate keeping a parsimonious core set plus complementary extras
(cf. docs/scoping_review/08_redundancy.md).
6.2 Dimensionality
The annotation set is highly multidimensional. Across the full 83-stimulus corpus, the 26 audio/language channels shared by all modalities require 12 principal components to reach 80% of the variance (PC1–5 ≈ 50%). On the 53-stimulus audiovisual subset (Fig. 2), which adds the visual/social/situational/affective channels, 51 channels require 18 PCs to reach 80%. Either way, naturalistic feature space has many semi-independent axes to sample — encouraging for stimulus design.
6.3 Cross-feature and cross-class networks
On the audiovisual subset, thresholding the correlation matrix (|r| ≥ 0.30) gives a feature
network whose nodes cluster by class (Fig. 3). After excluding categorical class-code
channels (which must not be treated as magnitudes), the classes with valid scalar features here
are affect, visual, audio, social, and situation (language drops out for the reason in
§5). Aggregating to the class level (mean |r| between classes; Fig. 4), the cross-class
couplings are modest: the strongest is affect ↔ social (0.15) — depicted emotion tracks
the social-interaction structure — followed by visual ↔ social and audio ↔ social (both
0.11), then audio↔visual (0.07). Affect is otherwise weakly coupled to visual, audio, and
situational features (|r| ≤ 0.06), and the situational features are nearly independent of the
others (|r| ≤ 0.04). In the full corpus the audio stories carry no visual/social/affective
channels, so the corpus-wide network is dominated by the audio↔language block — a direct
consequence of the modality mix. (The affect↔social value here comes from the vision-language
reasoning pass run across the audiovisual subset; an earlier draft reported a larger 0.26, which
a code review found was inflated by treating the text_emotion_top class-index channel as a
numeric feature — the 0.15 above uses only the model’s continuous valence/arousal scalars.)
6.4 Couplings are modest and roughly stable across content
Splitting the audiovisual subset by speech density (per-stimulus mean word rate, median split; Fig. 5) shows that the valid cross-class couplings are small and fairly stable across content — e.g. visual↔social is 0.13 in speech-rich vs 0.13 in speech-sparse stimuli. The annotation classes are thus largely non-redundant with one another regardless of stimulus type, which is favourable for stimulus design (independent axes to sample). Complementarily, adding the 29 spoken stories makes the corpus richly language-annotated (word-level surprisal defined in 91% of story timepoints) where the audiovisual clips were dialogue-light.
7. Experimental design from the annotation space
A direct application is principled stimulus selection. We split every stimulus into fixed-length candidate segments and greedily select a subset that maximizes the log-determinant of the covariance of the concatenated annotation time series projected onto the leading principal components. Maximizing log det(cov) simultaneously rewards high variance across the major feature dimensions and statistical independence (low cross-correlation) among the feature time series — a D-optimal design over the annotation space. Candidate segments dominated by missing (mean-imputed) data are excluded, so a segment cannot win the objective because its features are undefined. Over the full 83-stimulus corpus, selecting 20 ten-second segments yields a log det(cov) of 12.8 versus 2.5 for random selection (Fig. 6) — about 5× the generalized variance — while drawing segments from 16 distinct stimuli across modalities. This tool turns the corpus into an optimized stimulus set for an experiment targeting the annotation feature space.
8. Tools and reproducibility
The project ships the full pipeline (Python), a MATLAB analysis suite
(readAnnotations, readAnnotationCorpus, analyzeCorpus, selectStimulusSet), an
interactive movie-with-annotations viewer (plays a stimulus with its time series and
a synced playback marker), and an interactive web segment-search interface (rank
segments by any combination of features and play the matching moment). A runnable
walkthrough (docs/walkthrough.m) and a full contents guide (docs/CONTENTS.md)
document everyday use.
9. Limitations and future directions
- Local substitutions. Several passes use lighter local models (RAFT, MTCNN, Keypoint R-CNN, spectral-residual saliency, AST, histogram shot detection) in place of the best-in-class targets (SEA-RAFT, InsightFace/OpenFace, RTMPose, ViNet, BEATs, TransNetV2). Swapping in the production models is a configuration change.
- High-level reasoning coverage. The vision-language reasoning pass now covers the full audiovisual subset (depicted-affect, social, and situational scalars are populated throughout), but it is computationally heavy and runs at a 5 s cadence on a local model; scaling it (a larger model on dedicated hardware, finer or shot-sampled windows) would sharpen these channels and extend them to any future long-film additions.
- Elicited affect and diarization are not yet included (no off-the-shelf induced- affect model; speaker diarization needs gated model access).
- Modality balance and under-represented classes. Adding the 29 spoken stories made the corpus language-rich but also audio-heavy, so corpus-wide scalar analyses are dominated by the audio/language channels and visual/social/situational/affective structure is only defined on the audiovisual subset. One class remains under-represented in the scalar cross-class analysis: language (sparse in the dialogue-light audiovisual clips). Adding dialogue-rich audiovisual material would rebalance it. (The depicted-affect class, previously under-represented, is now populated across the audiovisual subset by the VLM pass; a code review separately removed a spurious categorical channel that had inflated the affect↔social coupling — see §6.3.)
- Validation. The annotations are validated here by face validity and internal structure; external validation against human ratings and brain data is future work.
10. Availability
All code, the channel template, the annotated corpus derivatives, analysis figures, the
viewer, and the web interface are in the project repository. Start at
docs/CONTENTS.md; the scoping review with full model citations is in
docs/scoping_review/; the format specification is in
docs/design/ANNOTATION_FORMAT.md.
Interactive segment browser
A static, in-browser tool that ranks every stimulus segment by any combination of
annotation features and plays the matching moment. It reads a precomputed index
(analysis/web/segments.json) and runs entirely client-side — no server logic.
Pick any set of features, toggle each High or Low, and segments are ranked by their mean z-score across the chosen features (with a features-covered count when a segment lacks some channels). Interpretable vector channels are expanded per component, so — for example — EmoNet’s 20 emotion categories and the eight facial expressions are individually searchable. Rankings are face-valid: high optical flow surfaces action scenes, high word-rate the dialogue clips, EmoNet “Aesthetic Appreciation” the beach-sunset clips.
Playback & media. The ranking interface works anywhere. Actually playing a clip needs the source media served from the same origin — so playback works when you serve the project locally, but not on the public GitHub Pages site, where the licensed media are not hosted. To use it fully, serve the repository root with
python3 tools/serve.py(a small Range-capable server, so video seeking works — the built-inpython -m http.servercannot seek) and openanalysis/web/index.html.
Notes from the app README
Interactive browser tool to rank movie/story segments by any combination of computational annotations and play the matching moment.
Launch
Serve from the project root with the bundled server, then open the page:
cd <project-root>
python3 tools/serve.py # http://localhost:8000 (pass a port to change it)
# then open: http://localhost:8000/analysis/web/index.html
Use tools/serve.py, not python -m http.server: the built-in server ignores HTTP
Range requests, so <video> cannot seek — the scrub bar and “play segment” won’t
jump. tools/serve.py adds byte-range support so seeking works. Serving from the root is
also what lets the player load videos from /data/movies/...; opening index.html via
file:// shows rankings but cannot play clips.
Use
- Filter/scroll the feature list (grouped by class); tick features to search on.
- Toggle each picked feature High / Low.
- Optionally filter by source and choose how many results to show.
- Segments are ranked by the mean z-score of the selected features. Each result has ▶ Segment (seeks to its start, auto-stops at its end) and ▶ Clip (plays the whole stimulus); the player repeats these for the loaded segment.
- Selected features are plotted as a time series over the whole clip, with a marker that tracks playback. Click the plot to jump the video to that time; the video scrub bar works too.
Rebuild the index
segments.json is generated from the annotated corpus. Regenerate after annotating
more stimuli (or to change the window length):
PYTHONPATH=src .venv/bin/python tools/build_search_index.py --seglen 5
Segment Search — web interface
Interactive browser tool to rank movie/story segments by any combination of computational annotations and play the matching moment.
Launch
Serve from the project root with the bundled server, then open the page:
cd <project-root>
python3 tools/serve.py # http://localhost:8000 (pass a port to change it)
# then open: http://localhost:8000/analysis/web/index.html
Use tools/serve.py, not python -m http.server: the built-in server ignores HTTP
Range requests, so <video> cannot seek — the scrub bar and “play segment” won’t
jump. tools/serve.py adds byte-range support so seeking works. Serving from the root is
also what lets the player load videos from /data/movies/...; opening index.html via
file:// shows rankings but cannot play clips.
Use
- Filter/scroll the feature list (grouped by class); tick features to search on.
- Toggle each picked feature High / Low.
- Optionally filter by source and choose how many results to show.
- Segments are ranked by the mean z-score of the selected features. Each result has ▶ Segment (seeks to its start, auto-stops at its end) and ▶ Clip (plays the whole stimulus); the player repeats these for the loaded segment.
- Selected features are plotted as a time series over the whole clip, with a marker that tracks playback. Click the plot to jump the video to that time; the video scrub bar works too.
Rebuild the index
segments.json is generated from the annotated corpus. Regenerate after annotating
more stimuli (or to change the window length):
PYTHONPATH=src .venv/bin/python tools/build_search_index.py --seglen 5
Deploying the documentation site (GitHub Pages)
The documentation book is published to GitHub Pages from this repository at
https://canlab.github.io/narrative_feature_annotations/. A GitHub Actions workflow
(.github/workflows/deploy-book.yml) rebuilds and republishes it on every push to main.
What lives in git vs. Dropbox
This project mixes small, shareable text with large, copyrighted media. The split is:
| Kept in git / GitHub | Kept in Dropbox only (git-ignored) |
|---|---|
Pipeline code (src/, matlab/, tools/) | Stimulus media — the movies and audio (data/**/*.mp4,*.wav,…, ~11 GB) |
Docs (docs/, README.md) and the book config (book/book.toml) | Annotation derivatives (annotations/, ~217 MB of .h5 + sidecars) |
Schema (schema/), figures (analysis/figures/), search index (analysis/web/) | |
Corpus metadata (data/manifest.csv, data/**/SOURCES.md, lexicon CSVs, text story) |
Why the media and annotations stay out of git:
- Media exceed GitHub’s 100 MB per-file limit and are copyrighted (the
spacetopclips, the Narratives audio, and Kung Fury are third-party IP). Only the CC-BY Blender open films are freely redistributable. - Annotations are large derivatives (and their ASR transcripts reproduce film dialogue). They are regenerable from the media, so they are kept in Dropbox and can be published separately (e.g. OSF/Zenodo with a DOI) if a citable dataset is wanted.
The published site does not need either: it is built from the docs, the figures, and the interactive search index, all of which are committed.
One-time setup
cd "<this folder>"
# 1. Initialize git and keep Dropbox out of git's internals (see "Dropbox" below).
git init
xattr -w com.dropbox.ignored 1 .git # macOS: stop Dropbox syncing the .git folder
# 2. Stage everything; .gitignore already excludes media, annotations, .venv, book build.
git add -A
git status # sanity check: no .mp4/.wav/.h5 should appear
# 3. First commit.
git commit -m "Narrative Feature Extraction: pipeline, docs, and documentation book"
# 4. Create the GitHub repo and push (org: canlab).
git branch -M main
gh repo create canlab/narrative_feature_annotations --source=. --remote=origin --push --public
# …or, if the repo already exists:
# git remote add origin https://github.com/canlab/narrative_feature_annotations.git
# git push -u origin main
Then enable Pages once: repo Settings → Pages → Build and deployment → Source: “GitHub Actions”. The next push (or a manual run of the Deploy documentation book workflow) publishes the site. GitHub Pages requires a public repo on the free plan.
Updating the site
Edit the Markdown under docs/ (or README.md), commit, and push — the workflow
regenerates the book and redeploys. To preview locally first:
brew install mdbook # one-time; or: cargo install mdbook
python3 tools/build_book.py # assemble book/src/ from docs/
mdbook serve book # live preview at http://localhost:3000
book/src/ and book/book/ are generated (git-ignored); never edit them by hand.
Regenerating the annotations (not in the repo)
Because annotations/ is Dropbox-only, a fresh git clone has the code and docs but not
the derivatives. To rebuild them you need the media (also Dropbox-only) in place, then:
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
PYTHONPATH=src .venv/bin/python -m nfe.batch --manifest data/manifest.csv \
--out annotations/corpus --template schema/channel_template.json \
--vision --audio-hl --events
# then refresh derivatives the site uses:
PYTHONPATH=src .venv/bin/python tools/build_search_index.py # analysis/web/segments.json
# (MATLAB) refreshAnalysis("annotations/corpus") # analysis/figures + stats
See ADDING_MOVIES.md for the full refresh workflow.
Dropbox + git
This working folder lives in Dropbox. That is fine — GitHub Pages does not care where the
local folder is — with one caveat: Dropbox continuously syncs the .git folder, which
across multiple machines (or if it syncs mid-commit) can create “conflicted copy” files
inside .git and corrupt the repo. Mitigations:
- Tell Dropbox to ignore
.git(thexattrcommand above), so Dropbox leaves git alone. - The arrangement is otherwise complementary: Dropbox holds the large media +
annotations; GitHub holds the code + docs. Don’t sync the repo across machines via
Dropbox — use
git clone/pull/pushfor that and let Dropbox carry only the media.