Skip to content

`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 = 0 is the LEFT edge of the image; x = 1 is the RIGHT.
  • y = 0 is the TOP; y = 1 is 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:

NameIndexNameIndex
nose0left_pinky17
left_eye_inner1right_pinky18
left_eye2left_index19
left_eye_outer3right_index20
right_eye_inner4left_thumb21
right_eye5right_thumb22
right_eye_outer6left_hip23
left_ear7right_hip24
right_ear8left_knee25
mouth_left9right_knee26
mouth_right10left_ankle27
left_shoulder11right_ankle28
right_shoulder12left_heel29
left_elbow13right_heel30
right_elbow14left_foot_index31
left_wrist15right_foot_index32
right_wrist16

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 Pose instances across frames; they can carry pinned buffers from the native plugin.