[Feature]: Joint angles and JointAngleAnnotator for KeyPoints

Open
#2,897 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@kari-pikkarainen is already working on this.

Since Nov 6, 2026.

  • #2899 by @kari-pikkarainen — open

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
52/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
python

Research direction

Start by reading the existing KeyPoints implementation and BaseKeyPointAnnotator to understand container visibility and image type conversion, then check their related tests and top-level exports. Implement and test the stateless angle helper and annotator against the listed missing-point, visibility, degenerate-segment, 3D-input, and empty-input cases; done means the API and tests cover those behaviors and the annotator skips NaN joints.

Written by the indexing model from the issue text.

Description

Search before asking
  • I have searched the issues and discussions and found no similar feature request.

Related history: #452 proposed angle calculations alongside the original keypoint API, and a later comment there asked for helpers such as "calculate the angle between two points"; #1658 added conversion-based tracking; #1367 redirected tracker development to trackers; and #2290/#2291 added and reverted Detections.keypoints. This request picks up the remaining angle helper and its annotator.

Feature Description

sv.KeyPoints covers loading, filtering and drawing poses, but measuring a joint still needs custom code. I'd like to add two model-agnostic pieces that follow the API design principles (annotators only render; visible is container state):

  • sv.joint_angles(key_points, angles): a stateless function that returns the interior angle at a key point for each named triplet, for example hip–knee–ankle, as an (n, k) array in degrees. Each triplet is a small JointAngle(name, a, b, c) dataclass in supervision.key_points.angles, where b is the vertex.
  • sv.JointAngleAnnotator: draws the arc and the value at each joint, built on BaseKeyPointAnnotator with the usual image-type conversion, and no confidence threshold of its own.

Unless you'd prefer otherwise, I'll open a PR with these defaults:

  1. Indices are zero-based positions in KeyPoints.xy, documented next to the dataclass as distinct from the one-based Skeleton edge convention.
  2. The result is NaN, never an invented value, when any of the three points is missing, non-finite, at [0, 0], hidden by visible, or when a segment has zero length. The annotator skips NaN joints.
  3. Three-coordinate key points are accepted; angles use xy[..., :2], and the docstring states that these are 2D image-plane angles, which differ from anatomical 3D angles under foreshortening (for example a forearm pointing at the camera).
  4. Only sv.joint_angles and sv.JointAngleAnnotator are added to the top-level namespace; JointAngle is imported from supervision.key_points.angles. Happy to export it at top level too if you prefer.
  5. Tests use exact expected angles (90°, 180°, acute, degenerate segments) and each NaN rule, plus 3D input and empty KeyPoints.

The one question I'd like your view on before the PR: is this a fit for the library, or would you rather see it as a cookbook?

Direct KeyPoints export to CSV/JSON is a separate idea; I'll raise it in its own issue rather than bundle it here.

Prototype

An external prototype implements the angle helper and the annotator with unit tests on synthetic key points of known angles. I also ran it on running, road-race and skiing clips with RF-DETR keypoint preview and ViTPose: in ten rule-selected frames, about 13/17 RF-DETR and 12/14 ViTPose knee readings were within ±15° of rough visual estimates. That was a qualitative check without ground-truth key points, not an accuracy benchmark.

Example Usage
import supervision as sv
from supervision.key_points.angles import JointAngle

# Zero-based positions in KeyPoints.xy; b is the angle's vertex.
LEFT_KNEE = JointAngle("left_knee", a=11, b=13, c=15)

key_points = sv.KeyPoints.from_ultralytics(results)
angles = sv.joint_angles(key_points, [LEFT_KNEE])  # (n, 1) degrees, NaN if a point is missing

annotated = sv.JointAngleAnnotator([LEFT_KNEE]).annotate(frame.copy(), key_points)
Are you willing to submit a PR?
  • Yes I'd like to help by submitting a PR!

Prepared with AI assistance (Claude Code); I reviewed the proposal and the prototype results.

Dominant language
Python
Stars
51.6k
Forks
5k
Avg merge
1d 17h
Merged PRs (30d)
191

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from roboflow/supervision

All issues in roboflow/supervision

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.