Skip to main content

Expressions

Irregular • File: Expressions.csv • Writer: LaboExpressionsDataWriter.cs

At a glance

Expressions captures discrete detection events for configured facial / hand expressions — one row per detection, stamped at trigger time. Each row records the expression name, left and right similarity scores, the threshold in effect, and which sides met the threshold. Children of a single trigger are flattened — if one detection produces multiple child observations, each gets its own row.

For continuous per-frame facial pose (jaw-open over time, blendshape time series), use the regular Face stream instead. Expressions is for "when did a detectable expression cross the threshold?" questions.

When it writes

Whenever the expression detector calls LaboDataCapture.Instance.ExpressionEntry(observation). Detection logic is driven by configured expression definitions and thresholds; whenever the detector observes that a configured expression has crossed its threshold, it fires.

File & location

  • Default: Expressions.csv in the run directory.
  • Writer: LaboExpressionsDataWriter.cs.
  • Observation: Runtime/DataCapture/Observations/ExpressionObservation.cs.

Configuration

ExpressionsDataCapture SO (subclass of DataCaptureConfig). Fields:

FieldWhat it does
enabledLoggingMaster enable/disable.
fileNameOverride the default name.
flushEveryNRowsBatched flush cadence. Default 30.
writeHeaderWrite the header row on first emission. Default true.

Expression definitions and thresholds (which expressions to detect, similarity thresholds) live on the experience's expression-detection components — this SO only controls how the writer emits rows.

Not auto-created on experience open — assign manually if missing.

Columns

Shared prefix

See Column conventions — The shared prefix.

Domain columns

ColumnTypeUnits / valuesDescription
Expression_NamestringName of the detected expression (e.g. Smile, BrowRaise, Frown).
Expression_StatestringDetected / Partial / EndedDetection state at this moment.
Expression_Thresholdfloat[0, 1]Similarity threshold that was active.
Expression_Left_Similarityfloat[0, 1]Similarity score on the left side.
Expression_Right_Similarityfloat[0, 1]Similarity score on the right side.
Expression_Left_MetboolWhether the left-side score met the threshold.
Expression_Right_MetboolWhether the right-side score met the threshold.
Expression_Both_MetboolWhether both sides met the threshold (full bilateral match).

Sample rows

...shared...,Expression_Name,Expression_State,Expression_Threshold,Expression_Left_Similarity,Expression_Right_Similarity,Expression_Left_Met,Expression_Right_Met,Expression_Both_Met
...,Smile,Detected,0.75,0.82,0.79,True,True,True
...,BrowRaise,Partial,0.80,0.84,0.62,True,False,False

Join with other streams

Typical pattern — attach per-frame context to each detection, then filter:

import pandas as pd
exp = pd.read_csv("ExperienceState.csv")
expr = pd.read_csv("Expressions.csv")

enriched = expr.merge(exp, on="FrameNumber", how="left", suffixes=("_expr", "_frame"))

# Every full-smile detection during the Stimulus epoch:
smiles = enriched[(enriched["Expression_Name"] == "Smile")
& (enriched["Expression_Both_Met"] == True)
& (enriched["EpochName_frame"] == "Stimulus")]

Or pair with stimulus-onset events from Events to do a "smile latency after stimulus" analysis:

events = pd.read_csv("EventPoints.csv")
stim_onsets = events[events["Event_Description"] == "Stimulus flash"]

# For each onset, find the first Smile detection after it:
for _, onset in stim_onsets.iterrows():
after = smiles[smiles["MonotonicExecutionTime_frame"] > onset["MonotonicExecutionTime"]]
if not after.empty:
latency = after.iloc[0]["MonotonicExecutionTime_frame"] - onset["MonotonicExecutionTime"]
# ... use latency

Gotchas

  • Children are flattened. Same pattern as Events — a single trigger may produce multiple child observations, each as its own row.
  • Partial vs. Detected vs. Ended. A single expression can fire multiple rows with different states as it ramps up and decays. For "peak detection" analyses, filter on Expression_State == "Detected"; for duration, pair Detected with the matching Ended.
  • Similarity is not blendshape weight. These scores come from the expression-definition matcher, not directly from raw tracking blendshapes. If you want blendshape weights as a continuous series, use Face.
  • Unilateral expressions can be asymmetric. Expression_Left_Met = True, Expression_Right_Met = False is a real state — e.g. a raised eyebrow on one side. Use Expression_Both_Met only when you want the symmetric-only case.
  • Threshold is per-row, not per-experience. If you change the threshold mid-run, different rows may have different Expression_Threshold values. Filter on this column if you need threshold-homogeneous analysis.

Analysis recipes

  • Reaction time — for expression-based responses (e.g. "time from stimulus to smile"), pair an Events onset with the first matching Expressions detection.
  • Stimulus-locked averaging — bin detections relative to stimulus-onset events.