Skip to content

feature: add KeyPoints.merge() method#2412

Open
abhijithneilabraham wants to merge 8 commits into
roboflow:developfrom
abhijithneilabraham:abhijith_keypoints_merge_feat
Open

feature: add KeyPoints.merge() method#2412
abhijithneilabraham wants to merge 8 commits into
roboflow:developfrom
abhijithneilabraham:abhijith_keypoints_merge_feat

Conversation

@abhijithneilabraham

@abhijithneilabraham abhijithneilabraham commented Jul 7, 2026

Copy link
Copy Markdown
Contributor
Before submitting

Description

Adds KeyPoints.merge(), a classmethod that combines a list of KeyPoints
objects into one, mirroring the Detections.merge() contract:

  • empty KeyPoints inputs are ignored; an all-empty or empty list returns
    KeyPoints.empty()
  • xy, class_id, keypoint_confidence, detection_confidence, and visible are
    concatenated in input order
  • a field set on some inputs and None on others raises ValueError, with the
    same message convention as Detections.merge
  • data dictionaries are merged key-wise via the existing merge_data helper;
    mismatched keys raise ValueError

One KeyPoints-specific rule: all non-empty inputs must share the same number
of keypoints per skeleton (the m in xy of shape (n, m, 2)), since skeletons
from different models are not comparable. Mismatched counts raise a ValueError
naming the counts.

One implementation note versus Detections.merge: optional fields are stacked
with np.concatenate(axis=0) instead of np.hstack, because keypoint_confidence
and visible are (n, m) arrays where hstack would concatenate along the wrong
axis.

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)

Motivation and Context

As proposed in #2409: with KeyPoints.with_nms() added in #2338, the natural
workflow is merge then suppress , which means combine skeletons from multiple frames,
crops, or model passes, then dedup. The suppress half exists; the merge half
does not. Today, combining keypoints means concatenating five aligned arrays
by hand.

frames_keypoints = [sv.KeyPoints.from_ultralytics(model(frame)[0]) for frame
in batch]
all_keypoints = sv.KeyPoints.merge(frames_keypoints)
deduped = all_keypoints.with_nms(threshold=0.5)

Closes #2409

Changes Made

  • Add KeyPoints.merge() classmethod in src/supervision/key_points/core.py,
    placed alongside empty() / is_empty() / with_nms(), reusing the existing
    _validate_keypoints_fields and merge_data helpers
  • Add Google-style docstring with a runnable doctest example demonstrating
    field and data merging
  • Add 11 parametrized test cases in tests/key_points/test_core.py, following
    the same (input_list, expected_result, exception) structure as the existing
    Detections.merge tests: empty list, only-empty inputs, single input, empties
    ignored, xy-only inputs, all fields populated (including visible and data),
    partial-field mismatch for class_id / visible / detection_confidence,
    mismatched keypoint counts, and mismatched data keys
  • Add changelog entry under Unreleased

Testing

  • I have tested this code locally
  • I have added unit tests that prove my fix is effective or that my
    feature works
  • All new and existing tests pass

Full suite: 2805 passed, 1 skipped. The docstring example runs as a doctest
under the project's --doctest-modules configuration. Every line of the new
method is covered by the new tests, including all three ValueError paths. ruff
check and ruff format --check pass on both changed files; mypy reports no
errors attributed to the changed file.

Google Colab (optional)

Colab link: N/A

Screenshots/Videos (optional)

N/A

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86%. Comparing base (23a2227) to head (63b7255).

Additional details and impacted files
@@           Coverage Diff           @@
##           develop   #2412   +/-   ##
=======================================
  Coverage       86%     86%           
=======================================
  Files           70      70           
  Lines        10335   10359   +24     
=======================================
+ Hits          8878    8902   +24     
  Misses        1457    1457           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@abhijithneilabraham

Copy link
Copy Markdown
Contributor Author

@Borda @SkalskiP Hope your attention is gathered on this one

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a KeyPoints.merge() classmethod to the keypoints API, enabling users to combine multiple KeyPoints objects into one (including aligned optional fields and merged data), similar to the existing Detections.merge() workflow.

Changes:

  • Implement KeyPoints.merge() in src/supervision/key_points/core.py, including validation and keypoint-count consistency checks.
  • Add a comprehensive parametrized test suite covering happy paths and error cases for merge behavior.
  • Document the new feature in docs/changelog.md under Unreleased → Added.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/supervision/key_points/core.py Adds KeyPoints.merge() implementation and doctest documentation.
tests/key_points/test_core.py Adds parametrized unit tests validating merge behavior and error handling.
docs/changelog.md Adds an Unreleased changelog entry for KeyPoints.merge.

Comment thread src/supervision/key_points/core.py Outdated
Comment thread docs/changelog.md
@abhijithneilabraham

Copy link
Copy Markdown
Contributor Author

@Borda Resolved latest Copilot Comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: add KeyPoints.merge(), mirroring Detections.merge()

3 participants