Face
Regular • File:
FaceTracking.csv• Writer:FaceTrackingDataWriter.cs
At a glance
Face tracking captures the facial skeletal / blend-shape-driven pose — one set of position + rotation columns per tracked face bone, produced by FaceUtility from the active SilicoSkeletonFace. On Meta Quest Pro / supported HMDs, this is driven by the ~63 Meta face-tracking blendshapes; on other devices, fewer bones may be tracked.
Use Face for:
- Expression time series (jaw open over time, brow raise, smile intensity proxies).
- Blendshape-driven analyses (compare against Meta's blendshape conventions).
For discrete expression detections above a threshold (e.g. "smile detected"), use the irregular Expressions stream instead — it emits one row per detection event rather than per frame.
When it writes
One row per LateUpdate, provided:
ExperienceUtilities.CaptureFaceDataistrue, AND- a
SilicoSkeletonFaceis assigned.
File & location
- Default:
FaceTracking.csvin the run directory. - Writer:
FaceTrackingDataWriter.cs. - Columns come from
FaceUtility.ColumnNamesSkeleton(skeleton)inRuntime/ExperienceObjects/Skeletons/Face/FaceUtility.cs.
Configuration
Gated by ExperienceUtilities.CaptureFaceData and the face-skeleton assignment — no dedicated ScriptableObject. The active SilicoSkeletonFace determines which bones are in the output.
Columns
Shared prefix
See Column conventions — The shared prefix.
Per-bone blocks
One block per face bone in the active skeleton, in skeleton-defined order. A typical block:
| Column | Type | Units | Description |
|---|---|---|---|
<Bone>_PosX, <Bone>_PosY, <Bone>_PosZ | float | metres | World-space position of the bone. |
<Bone>_RotX, <Bone>_RotY, <Bone>_RotZ, <Bone>_RotW | float | unit quaternion | World-space rotation of the bone. |
Typical face-bone names map loosely to Meta blendshapes: BrowL, BrowR, EyeL, EyeR, JawOpen, MouthSmile, MouthFrown, CheekPuff, NoseSneerL, NoseSneerR, etc. Exact set depends on the skeleton.
For the authoritative blendshape list and semantics, see Meta's face-tracking reference.
Sample rows
...shared...,BrowL_PosX,BrowL_PosY,BrowL_PosZ,BrowL_RotX,BrowL_RotY,BrowL_RotZ,BrowL_RotW,JawOpen_PosX,...
...,0.02,1.72,-2.5,0.0,0.0,0.0,1.0,0.0,...
...,0.02,1.72,-2.5,0.0,0.01,0.0,0.9999,0.005,...
Join with other streams
Standard FrameNumber left-join to ExperienceState:
import pandas as pd
exp = pd.read_csv("ExperienceState.csv")
face = pd.read_csv("FaceTracking.csv")
joined = exp.merge(face, on="FrameNumber", how="left", suffixes=("_exp", "_face"))
Gotchas
- Device support varies. Face tracking requires hardware support (Meta Quest Pro and compatible devices). On devices without face tracking, the skeleton may not produce meaningful bone data — columns will exist but values will be static or zero.
- Blendshape ↔ bone mapping is not 1:1. LABO stores bone pose, not raw blendshape weights. Some analyses care about the blendshape weight directly (0 to 1); you'll infer that from bone displacement. Link back to Meta's reference to interpret.
- Confidence is not per-column. Unlike some face-tracking APIs, Face CSV does not include a per-bone confidence score. Treat low-signal frames (head off-camera, eyes closed) as potentially invalid based on context from other streams.
- File only exists if the gate passes at run-start. Same as Body — no skeleton, no file.
- Quaternion sign flips. See Participant gotchas.
Analysis recipes
- Stimulus-locked averaging — epoch-align face-pose features (e.g.
JawOpen_PosYproxy) around stimulus onset.
For discrete expression detections above threshold, use the irregular Expressions stream — it's a much better match for "did the participant smile during stimulus X?" questions.