Skip to main content

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:

ColumnTypeUnitsDescription
ObservationTimedoublesecondsUnity-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.
MonotonicExecutionTimedoublesecondsDrift-free reference clock, seconds from the first sample of the session. Use this for all analysis and for cross-device alignment.
FixedIntervalNumberlongcountMonotonic physics-tick counter (FixedUpdate).
FrameNumberlongcountMonotonic render-frame counter (Time.frameCount). Primary join key within a session.
EventNumberlongcountIndex of the most recent event-point that fired. Used to segment rows by experimental event.
EpochNumberlongcountIndex of the current epoch.
EpochNamestringHuman-readable name of the current epoch (e.g. "Baseline", "Stimulus", "Response").
FrameTimedoublesecondsTime.unscaledTimeAsDouble at frame start. Identical across all rows written in the same render frame.
FixedIntervalTimedoublesecondsTime.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)

ColumnTypeUnitsDescription
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: +X right, +Y up, +Z forward.

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.

In action

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.

Best practice

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; W is the scalar component.
  • Euler (X, Y, Z) in degrees when the Participant Tracking SO has Use Euler Angles enabled.

Column suffixes are _RotX / _RotY / _RotZ / _RotW for quaternions, _RotX / _RotY / _RotZ for Euler.

Quaternion sign flips

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 typeUnit
Position / distancemetres
Rotation (quaternion)unit quaternion (dimensionless)
Rotation (Euler)degrees
Velocitymetres per second
Timeseconds (double precision)
Angular velocitydegrees 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:

PlaceholderExpanded toExample
Eye_[eye]_*Center, Left, RightEye_Center_DirX, Eye_Left_PosY, Eye_Right_DirZ
[blendshape]One of ~63 Meta blendshape namesJawOpen, EyesLookLeftL, MouthSmileL (see Meta's reference)
[bone]A bone name from the active skeletonHips, 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 NaN in numeric columns and as empty strings in text columns.
  • Booleans appear as True / False or 1 / 0 depending 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.