Hand
Regular • Files:
LeftHandTracking.csvand/orRightHandTracking.csv• Writer:HandTrackingDataWriter.cs
At a glance
Hand tracking produces two independent files, one per hand — LeftHandTracking.csv and RightHandTracking.csv. Each file is produced by its own writer instance and has the same structural shape: shared prefix, then one position + rotation block per bone in the active hand skeleton. Columns come from HandTrackingUtility.GetColumnNames(skeleton).
If only one hand has a skeleton assigned, only that one file exists.
When it writes
One row per LateUpdate, per hand, provided:
- Hand data capture is enabled in
DataCaptureSettings, AND - A hand skeleton is assigned for that side, AND
- The skeleton's
initializedflag istrue.
Each hand is evaluated independently — one side can be writing while the other is gated out.
File & location
LeftHandTracking.csvandRightHandTracking.csvin the run directory.- Filename actually comes from
skeleton.skeletonType— the "Left" / "Right" prefix is skeleton-driven, not hard-coded on the writer. - Writer:
HandTrackingDataWriter.cs. - Columns:
HandTrackingUtility.GetColumnNames(skeleton).
Configuration
Hand capture toggle lives on DataCaptureSettings. The actual hand skeleton assignment and initialized state come from the experience's hand objects at runtime — there is no dedicated ScriptableObject for hand capture per se.
Columns
Shared prefix
See Column conventions — The shared prefix.
Per-bone blocks
One block per hand 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 bone names: Wrist, Palm, Thumb0, Thumb1, Thumb2, Thumb3, Index1, Index2, Index3, Middle1, Middle2, Middle3, Ring1, Ring2, Ring3, Pinky0, Pinky1, Pinky2, Pinky3. Exact set depends on skeleton.
Sample rows
...shared...,Wrist_PosX,Wrist_PosY,Wrist_PosZ,Wrist_RotX,...,Thumb1_PosX,...,Index1_PosX,...
...,0.25,1.20,-2.4,0.0,...,0.26,...,0.27,...
...,0.26,1.21,-2.39,0.01,...,0.27,...,0.28,...
Join with other streams
Each hand file is independently joinable to ExperienceState on FrameNumber. To work with both hands at once, join them separately and then merge:
import pandas as pd
exp = pd.read_csv("ExperienceState.csv")
left = pd.read_csv("LeftHandTracking.csv")
right = pd.read_csv("RightHandTracking.csv")
frame = exp.merge(left, on="FrameNumber", how="left", suffixes=("", "_L"))
frame = frame.merge(right, on="FrameNumber", how="left", suffixes=("", "_R"))
The suffixes parameter disambiguates same-named columns between the two hands.
Gotchas
- Two files, two independent gates. Left hand may be tracking while right is not (or vice versa). Don't assume both files exist.
- Columns must match skeleton. If the skeleton changed between runs, the two runs' Hand CSVs may not have the same columns. Read the header dynamically.
initializedflag matters. The writer waits until the skeleton'sinitialized = truebefore emitting rows. Early in a run, there may be a few frames with no Hand row while the skeleton warms up. Usehow="left"on joins.- "Hand" is skeletal, not gesture. LABO's Hand stream gives you joint pose over time. Gesture recognition ("participant made a fist") is a derived computation you'd run on this data, not something LABO reports directly.
- World-space positions. To get wrist-relative finger positions (e.g. "how closed is the fist?"), subtract the
Wrist_*pose from each finger-joint pose in analysis. - Quaternion sign flips. See Participant gotchas.
Analysis recipes
- Reaction time — for gesture-based responses, differentiate fingertip position to find response onset.
- Gaze paths — joint gaze + pointing-hand analyses can combine Eye with wrist direction from Hand.