Skip to main content

Hand

Regular • Files: LeftHandTracking.csv and/or RightHandTracking.csv • Writer: HandTrackingDataWriter.cs

Media

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 initialized flag is true.

Each hand is evaluated independently — one side can be writing while the other is gated out.

File & location

  • LeftHandTracking.csv and RightHandTracking.csv in 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:

ColumnTypeUnitsDescription
<Bone>_PosX, <Bone>_PosY, <Bone>_PosZfloatmetresWorld-space position of the bone.
<Bone>_RotX, <Bone>_RotY, <Bone>_RotZ, <Bone>_RotWfloatunit quaternionWorld-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.
  • initialized flag matters. The writer waits until the skeleton's initialized = true before emitting rows. Early in a run, there may be a few frames with no Hand row while the skeleton warms up. Use how="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.