[Feature]: Joint angles and JointAngleAnnotator for KeyPoints
Maintainers usually reply within 1 day
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
- Domain
- computer-vision
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 smallJointAngle(name, a, b, c)dataclass insupervision.key_points.angles, wherebis the vertex.sv.JointAngleAnnotator: draws the arc and the value at each joint, built onBaseKeyPointAnnotatorwith 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:
- Indices are zero-based positions in
KeyPoints.xy, documented next to the dataclass as distinct from the one-basedSkeletonedge convention. - The result is NaN, never an invented value, when any of the three points is missing, non-finite, at
[0, 0], hidden byvisible, or when a segment has zero length. The annotator skips NaN joints. - 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). - Only
sv.joint_anglesandsv.JointAngleAnnotatorare added to the top-level namespace;JointAngleis imported fromsupervision.key_points.angles. Happy to export it at top level too if you prefer. - 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
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from roboflow/supervision
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
roboflow/supervision#2971 ·
Maintainers usually reply within 1 day
-
[Bug]: DetectionDataset.split loses a train item when len(images) * split_ratio is a whole number (90 images at 0.7 give 62/28, not 63/27)Possibly taken @avanish-garg claimed this today. Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 30/100
roboflow/supervision#2968 ·
Maintainers usually reply within 1 day
-
[Bug]: MeanAverageRecall returns 0.0 instead of -1 when there are no targets (whole run or empty size bucket)Possibly taken @MaiAditya claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 20/100
roboflow/supervision#2964 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 38/100
roboflow/supervision#2950 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 56/100
roboflow/supervision#2938 · 2 comments ·
Maintainers usually reply within 1 day
All issues in roboflow/supervision
Similar issues
-
area:core-platform effort:S priority:P3 risk:night-safe status:ready type:test
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
randomparity/kdive#3620 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
FinanceFlash/unvibecode#760 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
google-research/google-research#3470 ·
Maintainers usually reply within 21 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
mrvautin/ha-workouts#11 ·