`Pose`
The per-frame output of the pose engine. 33 landmarks in normalised image space with optional 3D world-space coordinates.
class Pose { bool get hasWorldLandmarks; Map<String, int> get indexByName; PoseSourceType? get source; int? get timestampUs; int? get trackId; int get length; // always 33
// Zero-copy accessors (hot path) double x(int i); // [0, 1] double y(int i); // [0, 1] double visibility(int i); // [0, 1] Offset point(int i);
// Lazy lists (allocated on first access) List<Offset> get points; List<double> get visibilityList;
// World-space accessors (when hasWorldLandmarks) double worldX(int i); double worldY(int i); double worldZ(int i);}Construction
You mostly receive Pose from the pose engine, NativePoseSource
on native, the web worker pipeline in the browser. For tests +
replay:
final pose = Pose.fromPoints( points, // List<Offset>, up to 33 entries visibility, // List<double>, same length indexByName: { 'nose': 0, 'left_shoulder': 11, 'right_shoulder': 12, ... }, worldX: [...], // optional, length 33 worldY: [...], worldZ: [...],);Coordinate spaces
Image-space (always available)
x(i),y(i)are normalised to[0, 1].x = 0is the LEFT edge of the image;x = 1is the RIGHT.y = 0is the TOP;y = 1is the BOTTOM.- For a SELFIE / mirror display (the PoseFlow default on the front camera), the pose engine emits image-space coordinates flipped along x so anatomical-left lands at low x. This matches the user’s expectation of a mirror.
World-space (only when hasWorldLandmarks)
worldX(i),worldY(i),worldZ(i)in metric units (approximately metres) with origin at the hip midpoint.+x= subject’s right;-x= subject’s left.+y= down;-y= up.+z= away from camera;-z= toward the camera.
When the subject is facing the camera squarely, both shoulders
have similar z (≈ 0). When the subject rotates to their right,
their right shoulder’s z decreases (closer to camera). The
camera-angle detector uses this directly for unambiguous sign
detection, see camera buckets.
The web worker pipeline currently doesn’t populate world
landmarks. Native PoseFlow’s native runtime does. Gate world-coord-dependent code
on pose.hasWorldLandmarks.
Landmark index map
indexByName maps anatomical landmark names → 0..32 indices.
The pose landmark layout:
| Name | Index | Name | Index |
|---|---|---|---|
nose | 0 | left_pinky | 17 |
left_eye_inner | 1 | right_pinky | 18 |
left_eye | 2 | left_index | 19 |
left_eye_outer | 3 | right_index | 20 |
right_eye_inner | 4 | left_thumb | 21 |
right_eye | 5 | right_thumb | 22 |
right_eye_outer | 6 | left_hip | 23 |
left_ear | 7 | right_hip | 24 |
right_ear | 8 | left_knee | 25 |
mouth_left | 9 | right_knee | 26 |
mouth_right | 10 | left_ankle | 27 |
left_shoulder | 11 | right_ankle | 28 |
right_shoulder | 12 | left_heel | 29 |
left_elbow | 13 | right_heel | 30 |
right_elbow | 14 | left_foot_index | 31 |
left_wrist | 15 | right_foot_index | 32 |
right_wrist | 16 |
The labels are anatomical, left_shoulder is always the
subject’s anatomical left shoulder, regardless of which side of
the image it appears on.
Visibility
visibility(i) is a confidence score in [0, 1] from the pose
detector, roughly “how sure are we this landmark is in frame +
not heavily occluded”. The camera-angle detector uses
left/right visibility asymmetry as a sign signal: when one
ear has visibility 1.0 and the other 0.1, the high-visibility
side is the side facing the camera.
Most analysis code thresholds visibility at 0.5 for “trust this landmark”; the bucket detector uses continuous values for weighted voting.
Working with landmarks
final idx = pose.indexByName;final leftShoulder = pose.point(idx['left_shoulder']!);final rightShoulder = pose.point(idx['right_shoulder']!);
final shoulderSpan = (leftShoulder.dx - rightShoulder.dx).abs();For higher-level processing (joint angles, distances, ratios), use
AngleExtractor and the channel
definitions in AngleDefinitions, DistanceDefinitions, etc. ,
they have built-in handling for visibility, side-flipping, and
view-invariance.
Converting to Landmark[]
Some legacy APIs work with List<Landmark> instead of Pose:
final landmarks = LandmarkAdapter.fromPose(pose);final back = LandmarkAdapter.toPose(landmarks, indexByName: idx);LandmarkAdapter is a zero-copy bridge, no allocation if both
sides are 33 landmarks.
Performance notes
- Use
x(i),y(i),point(i)on the hot path (per-frame). They’re zero-allocation. - Use
points(the lazy list) only when you need an iterable , it allocates on first access. - Don’t hold references to
Poseinstances across frames; they can carry pinned buffers from the native plugin.
Read next
PoseCameraView, the widget that emits poses.MovementTracker.processFrame, the runtime consumer.AngleExtractor, joint-angle helpers.- Camera buckets, how the bucket detector reads
Pose.