Hand Tracking for Physical AI#
This example uses daft-physical-ai, a Daft extension for physical AI data pipelines. It reads a LeRobot dataset, runs hand tracking (MediaPipe) as a Daft UDF with track_hands, and shows the keypoints.
Setup#
Install with pip install "daft-physical-ai[mediapipe]" matplotlib, then import.
1 2 3 | |
Configure#
The dataset, the camera column to decode, and how many frames to run.
1 2 3 | |
Read the dataset#
The LeRobot reader gives one row per frame, decoding the camera into an image column.
1 | |
Track hands#
track_hands returns a hand-pose column: a list of detected hands per frame, each with handedness (left or right), confidence (detection score), kp2d (21 hand keypoints in image pixels), and kp3d (3D keypoints, for methods that produce them - null for MediaPipe). It's a lazy, batched Daft UDF, so nothing runs until we materialize below.
1 | |
Inspect the results#
.show() triggers execution and renders the keypoints per frame.
1 | |
| episode_index | frame_index | hands |
|---|---|---|
| 0 | 0 | [{handedness: right, confidence: 0.9790868, kp2d: [[1409.5848, 1109.521], ...], kp3d: None, }, {handedness: right, confidence: 0.7482122, kp2d: [[954.672, 1074.0482], ...], kp3d: None, }] |
| 0 | 1 | [{handedness: right, confidence: 0.98324096, kp2d: [[1406.2035, 1108.8458], ...], kp3d: None, }, {handedness: right, confidence: 0.76023126, kp2d: [[954.5771, 1071.5254], ...], kp3d: None, }] |
| 0 | 2 | [{handedness: right, confidence: 0.97795737, kp2d: [[1410.8853, 1107.5798], ...], kp3d: None, }, {handedness: right, confidence: 0.82196695, kp2d: [[953.91736, 1076.048], ...], kp3d: None, }] |
| 0 | 3 | [{handedness: right, confidence: 0.9760107, kp2d: [[1411.5732, 1105.092], ...], kp3d: None, }, {handedness: right, confidence: 0.82673466, kp2d: [[955.222, 1073.2001], ...], kp3d: None, }] |
| 0 | 4 | [{handedness: right, confidence: 0.9784236, kp2d: [[1411.9012, 1102.4144], ...], kp3d: None, }, {handedness: right, confidence: 0.79153925, kp2d: [[950.6265, 1077.1145], ...], kp3d: None, }] |
| 0 | 5 | [{handedness: right, confidence: 0.9766638, kp2d: [[1414.2744, 1107.02], ...], kp3d: None, }, {handedness: right, confidence: 0.8490106, kp2d: [[952.9871, 1075.4406], ...], kp3d: None, }] |
| 0 | 6 | [{handedness: right, confidence: 0.9718941, kp2d: [[1406.7915, 1108.6783], ...], kp3d: None, }, {handedness: right, confidence: 0.8631619, kp2d: [[953.58887, 1069.7833], ...], kp3d: None, }] |
| 0 | 7 | [{handedness: right, confidence: 0.9692566, kp2d: [[1408.7743, 1108.7031], ...], kp3d: None, }, {handedness: right, confidence: 0.8757248, kp2d: [[953.2748, 1069.3573], ...], kp3d: None, }] |
Evaluate against ground truth#
EgoDex ships per-frame GT hand poses, so we can score the predictions: project both GT hands, match the predicted hands to them, and report detect% + PCK. The matching runs as a Daft UDF (score); the summary is computed from the collected results.
EgoDex-specific (GT layout + camera intrinsics). Needs
pip install scipy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | |
1 2 | |
1 2 | |
1 2 | |
Visualize: ground truth vs predictions#
Each row is a frame; the first column is the EgoDex ground-truth hands (green), the rest are the predicted keypoints. This is the most telling view - you see where each method is right and where it misses.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
![]()