Column conventions
Every LABO CSV follows the same conventions for column naming, coordinate systems, units, and placeholders. Once you know them, every stream page can skip re-explaining — you'll know what to expect.
The shared prefix
Every CSV — regular or irregular — begins with the same columns, in the same order, from LaboExperienceState:
| Column | Type | Units | Description |
|---|---|---|---|
ObservationTime | double | seconds | Unity-side wall-like time at the moment the row is written. May drift between writers in the same frame unless frame-cached. Prefer MonotonicExecutionTime for analysis. |
MonotonicExecutionTime | double | seconds | Drift-free reference clock, seconds from the first sample of the session. Use this for all analysis and for cross-device alignment. |
FixedIntervalNumber | long | count | Monotonic physics-tick counter (FixedUpdate). |
FrameNumber | long | count | Monotonic render-frame counter (Time.frameCount). Primary join key within a session. |
EventNumber | long | count | Index of the most recent event-point that fired. Used to segment rows by experimental event. |
EpochNumber | long | count | Index of the current epoch. |
EpochName | string | — | Human-readable name of the current epoch (e.g. "Baseline", "Stimulus", "Response"). |
FrameTime | double | seconds | Time.unscaledTimeAsDouble at frame start. Identical across all rows written in the same render frame. |
FixedIntervalTime | double | seconds | Time.fixedUnscaledTimeAsDouble at FixedUpdate. Identical across all rows written in the same FixedUpdate. |
When USING_LSL is compiled in, three additional columns appear at the end of the shared prefix: FrameStartTimeLSL, FrameEndTimeLSL, FixedIntervalTimeLSL — sampled from liblsl for cross-process stream alignment.
After this prefix, each stream appends its own domain columns. See each stream page for the specifics.
See Timing for when each time column is stamped and how they relate.
Auto-generated reference
The shared prefix below is generated directly from the LABO source enum (LaboExperienceState.cs → DataOrder) — if the code changes, regenerate with node scripts/generate-columns.mjs and this table updates. The order matches the on-disk CSV exactly; the manual table above adds type / units / description that the auto-generator doesn't yet capture.
Shared prefix (auto-generated)
| Column | Type | Units | Description |
|---|---|---|---|
ObservationTime | — | — | — |
MonotonicExecutionTime | — | — | — |
FixedIntervalNumber | — | — | — |
FrameNumber | — | — | — |
EventNumber | — | — | — |
EpochNumber | — | — | — |
EpochName | — | — | — |
FrameTime | — | — | — |
FixedIntervalTime | — | — | — |
FrameStartTimeLSL | — | — | — |
FrameEndTimeLSL | — | — | — |
FixedIntervalTimeLSL | — | — | — |
Auto-generated from LaboExperienceState.cs → DataOrder. Regenerate with node scripts/generate-columns.mjs.
Coordinate system
- One Unity unit = one metre.
- All positions are relative to the environment origin — the world-space
(0, 0, 0)— not to the participant. - Axes follow Unity's left-handed convention:
+Xright,+Yup,+Zforward.
Aligning the environment
At the start of an experience, the participant spawns at the position set on the Participant object. For positional data to be easy to reason about, set the environment's root GameObject to (0, 0, 0). Then every position in every CSV is measured from the environment center.
Participant's right palm reports PosX = 0.43. That means the palm is 43 centimetres to the right of the environment center, not 43 cm from the participant.
If the environment root is at (1, 1, 0) instead of origin, you have to subtract (1, 1, 0) from every reported position before the numbers mean anything in environment-local coordinates. Fixable, but a foot-gun.
Importing a GameObject from an asset library usually sets its position to (0, 0, 0) automatically. Verify in the Inspector before creating an experience. When building environments from scratch, always ensure the final environment root is at the origin.
You can intentionally build an experience with the participant and environment misaligned — just account for it in analysis.
Rotation
- Quaternion
(X, Y, Z, W)by default. Unit quaternion;Wis the scalar component. - Euler
(X, Y, Z)in degrees when the Participant Tracking SO hasUse Euler Anglesenabled.
Column suffixes are _RotX / _RotY / _RotZ / _RotW for quaternions, _RotX / _RotY / _RotZ for Euler.
A unit quaternion q and -q represent the same rotation, but CSV writers don't normalise sign between rows. A pose that barely moves can show large numeric jumps in RotW (e.g. 0.99 → -0.99) that are not physical. For continuous analysis (velocity, angular distance), either convert to a canonical hemisphere (q if q.w >= 0 else -q) or convert to axis-angle before differencing.
Velocity
Participant controller velocity is reported in metres per second, in the environment's coordinate frame.
Units by column type
| Column type | Unit |
|---|---|
| Position / distance | metres |
| Rotation (quaternion) | unit quaternion (dimensionless) |
| Rotation (Euler) | degrees |
| Velocity | metres per second |
| Time | seconds (double precision) |
| Angular velocity | degrees per second |
| Blend-shape weight / confidence | [0, 1] dimensionless |
Frame intervals (deltaTime, fixedDeltaTime) | seconds |
Frame rate (fps) | Hz |
State units explicitly in every column table on stream pages — researchers will need to convert.
Placeholder conventions
Several stream pages reference columns with a bracketed placeholder in their documentation:
| Placeholder | Expanded to | Example |
|---|---|---|
Eye_[eye]_* | Center, Left, Right | Eye_Center_DirX, Eye_Left_PosY, Eye_Right_DirZ |
[blendshape] | One of ~63 Meta blendshape names | JawOpen, EyesLookLeftL, MouthSmileL (see Meta's reference) |
[bone] | A bone name from the active skeleton | Hips, Spine, Head, LeftHand, etc. |
When reading a stream page, expect to expand these placeholders into their real column names based on the active skeleton / device.
Column-name casing
LABO columns use a mix of styles that reflect their history:
- Shared prefix columns use PascalCase (
FrameNumber,MonotonicExecutionTime). - Participant tracking and most per-bone domain columns use camelCase with underscores separating bone + field (
cameraPosX,Hips_PosX). - Event/Variable/Expression/Agent domain columns use PascalCase with underscores (
Event_Type,Variable_Name).
Grep-friendly; just case-sensitive. When writing read_csv(names=...) or SQL CREATE TABLE, copy the header line verbatim from a real CSV output rather than retyping from a stream page's column table.
Value encoding
- Commas inside string values (e.g. list elements in
Variables.csv) are replaced with semicolons to preserve CSV structure. - Missing / not-applicable values appear as literal
NaNin numeric columns and as empty strings in text columns. - Booleans appear as
True/Falseor1/0depending on the writer — check the stream page.
Further reading
- Timing — the shared time columns in depth.
- Join keys — which shared columns to use when aligning CSVs.
- File layout — where files are written on disk.