Skip to content

releasing 0.30.3 [rebase & merge] - #2572

Merged
Borda merged 22 commits into
release/latestfrom
releasing/0.30.3
Sep 14, 2026
Merged

Borda merged 22 commits into
release/latestfrom
releasing/0.30.3

Conversation

@Borda

@Borda Borda commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

0.30.3: Pose, VLM, and video/CSV crash and correctness fixes

📋 Summary

supervision 0.30.3 is a bug-fix release closing crash and silent-correctness gaps across pose estimation, VLM parsing, video/CSV output, and geometry utilities. Non-finite key points — how pose estimators report an undetected joint — no longer produce duplicate poses that survive sv.KeyPoints.with_nms, or crash the key point annotators outright. sv.Detections.from_vlm now orders backwards box corners, closing a bug where such a box scored a false 0.0 IoU and both survived NMS as a duplicate and counted as a total miss in mAP. sv.TraceAnnotator and sv.CSVSink no longer crash or silently drop columns on the first frame with no detections — a case every non-ByteTrack tracker pipeline hits. sv.process_video no longer hangs forever when max_frames exceeds the video length. Continuing 0.30.2's numeric-correctness theme, sv.pad_boxes and sv.scale_boxes are fixed against integer overflow. No breaking API changes, no new public API.

✨ Spotlights / highlights

Non-finite key points no longer produce duplicate poses or crash annotators

sv.KeyPoints.with_nms tested key point validity with xy == 0 alone, and NaN — how pose estimators report an undetected joint — is not 0. The stale joint stayed in the NMS box, so a duplicate skeleton scored False on every IoU comparison against it and survived suppression. The key point annotators (sv.VertexAnnotator, sv.EdgeAnnotator, sv.VertexLabelAnnotator, the sv.VertexEllipse*Annotator family) had the matching crash: a single undetected joint raised ValueError: cannot convert float NaN to integer for the whole frame. Both now skip non-finite coordinates, matching sv.KeyPoints.as_detections.

keypoints = sv.KeyPoints(xy=xy, confidence=confidence)
keypoints.with_nms(
    threshold=0.5
)  # duplicate skeletons with a NaN joint are now suppressed

sv.Detections.from_vlm no longer scores a false IoU miss on backwards box corners

A VLM that emits a corner pair backwards produced an xyxy row with x_min > x_max. Nothing downstream caught it: sv.box_iou_batch clamps intersection width at zero, so the box scored 0.0 IoU against itself — surviving NMS as a duplicate and counting as a total miss in mAP — while box_area still reported a plausible positive value. Every VLM parser now orders each box's corners before returning it.

sv.TraceAnnotator and sv.CSVSink no longer crash or silently corrupt output on an empty-detections frame

sv.TraceAnnotator.annotate raised ValueError: The tracker_id field is missing on the first frame with no detections, for every tracker except sv.ByteTrack. Such a frame now draws nothing and still advances the frame counter, so trace_length stays a window over elapsed frames rather than only over populated ones. sv.CSVSink had a quieter failure: an empty batch fixed the CSV header without the data/custom_data columns, and every later row was silently truncated to that schema — dropping fields like class_name for the whole file. The header is now fixed by the first batch that actually carries detections.

sv.process_video no longer hangs forever when max_frames exceeds the video length

The reader thread failed on the out-of-range end before enqueuing its sentinel, leaving the main loop blocked on the read queue indefinitely. max_frames is now capped at the video length, and any reader-thread error surfaces as RuntimeError("Reader thread raised: ...") instead of stalling the call.

Integer-coordinate overflow fixed in sv.pad_boxes and sv.scale_boxes

Both computed intermediate values that could overflow or silently wrap for large integer coordinates (e.g. large int32/uint16/int64 boxes). Both now use overflow-safe arithmetic.

xyxy = np.array([[10, 20, 30, 40]], dtype=np.int64)
sv.pad_boxes(xyxy=xyxy, px=5, py=10)  # int64 output, no wraparound

pad_boxes changes return dtype for integer input — see the migration guide below.

sv.scale_image and sv.resize_image(keep_aspect_ratio=True) no longer crash on an extreme aspect ratio or tiny scale factor

A small enough factor — or an aspect ratio too extreme for the target box — could round an output axis down to 0, and cv2.resize raised an assertion naming nothing the caller passed. Each axis now keeps at least one pixel. Two callers inherit the fix: sv.letterbox_image could not fill the resolution it was asked for, and sv.CropAnnotator with scale_factor < 1 aborted the whole frame as soon as one detection box was a few pixels across.

🔄 Migration guide

No breaking API changes. One fix changes return dtype for integer input:

  • sv.pad_boxes — integer xyxy now returns int64 (or float64 if a padded coordinate exceeds the int64 range), instead of the input's original integer dtype, which could silently overflow or wrap for small dtypes like int16/uint8.

If your code assumes pad_boxes preserves the input's exact dtype (e.g. reusing the result as an int16 array), cast explicitly: sv.pad_boxes(...).astype(np.int16).

sv.scale_boxes also fixes an integer-overflow bug, but its return dtype was already float64 for integer input before this release — unaffected.

📝 Notable changes

🔧 Fixed

🏆 Contributors

  • kevin (@kevin9327) — fixed KeyPoints.with_nms/key point annotators crashing on non-finite key points, tint_image image aliasing, LineZone generator exhaustion, and the scale_image/resize_image zero-target crash
  • Durgamani Sasikumar (@tedo001, LinkedIn) — fixed PolygonZone/from_vlm box-corner ordering and the TraceAnnotator/CSVSink empty-frame crash
  • S B Pranay (@pranaysb, LinkedIn) — fixed integer overflow in scale_boxes and dtype loss in filter_polygons_by_area/approximate_polygon
  • JiantaoPeng (@PengJianT) — fixed from_ultralytics masks-only class ID sizing
  • trueoneplusone (@trueoneplusone) — fixed integer overflow in pad_boxes
  • Andrew Barnes (@Bortlesboat, LinkedIn) — fixed classification export filename collisions
  • Abhijith Neil Abraham (@abhijithneilabraham, LinkedIn) — fixed process_video hanging when max_frames exceeds the video length
  • Jirka Borovec (@Borda, LinkedIn) — fixed the release-day outdated-docs banner

Full changelog: 0.30.2...0.30.3

Borda and others added 20 commits September 14, 2026 13:22
- Banner markup, stylesheet, and offset-script injection now skip the highest-numbered version directory: that tree holds the current release `/latest/` serves, so warning its readers they are reading an older version is wrong.
- New `unpatch_newest_version` reverts a banner an earlier dispatch injected into that tree, restoring the whitespace-only div a build emits so the next release patches it again through the normal path; only our own marker-bounded injection is reverted, never a genuine Material build.
- Version directories are ordered with `packaging.version.Version` instead of a name sort, so `0.9.0` sorts below `0.10.0` and a pre-release below the release it precedes; a name PEP 440 cannot parse sorts lowest rather than raising.
- The backfill workflow installs `packaging` into a throwaway venv before running the script, since the runner's system Python is externally managed and refuses a plain `pip install`.
- `packaging` added to the dev dependency group: the `.github` tests import the script directly, so it can no longer rely on a transitive install.
- Tests cover the skipped current release across all three passes, the revert and re-patch cycle, and version ordering including pre-releases and unparsable names.

---
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…2535)

Bumps the uv group with 1 update in the / directory: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.7.6 to 9.7.7
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](squidfunk/mkdocs-material@9.7.6...9.7.7)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.7.7
  dependency-type: indirect
  dependency-group: uv
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…lows

publish-docs.yml and docs-backfill.yml both push to gh-pages under
separate concurrency groups, so an overlapping run of either lost the
git "fetch first" race and failed. Both real (non-dry-run) runs now
share concurrency group "gh-pages" with cancel-in-progress: false, so
overlapping pushes queue instead of racing or getting cancelled
mid-push.
* stop banner-ing a release's own docs as outdated
* auto-archive the previously-latest release after a new one ships

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
* fix(detection): prevent integer overflow in scale_boxes (#2540)
* fix(utils): preserve exact integer box scaling

---------

Co-authored-by: jirka <6035284+borda@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
Updates the requirements on [build](https://github.com/pypa/build) to permit the latest version.
- [Release notes](https://github.com/pypa/build/releases)
- [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst)
- [Commits](pypa/build@1.0.0...1.6.0)

---
updated-dependencies:
- dependency-name: build
  dependency-version: 1.6.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* fix(csv_sink): stop empty detection batches from fixing the CSV header
* fix(trace): stop empty frames from crashing TraceAnnotator
* docs(trace): qualify fresh-trail claim, document dtype contract, add CENTER_OF_MASS/gap coverage
* fix(csv_sink): first-wins deferred schema, leak-safe header write, test coverage

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: jirka <6035284+borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…ties (#2542)

* fix(detection): support float64 and arbitrary dtypes in polygon utilities
* fix(utils): preserve large-origin polygon geometry

---------

Co-authored-by: jirka <6035284+borda@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
…e video length (#2546)

* fix(video): stop `process_video` hanging when `max_frames` exceeds the video length
* test(video): re-raise process_video errors from the deadline helper
* chore(pre_commit): ⬆ pre_commit autoupdate

updates:
- [github.com/tox-dev/pyproject-fmt: v2.27.0 → v2.29.3](tox-dev/pyproject-fmt@v2.27.0...v2.29.3)
- [github.com/abravalheri/validate-pyproject: v0.25 → 0.26](abravalheri/validate-pyproject@v0.25...0.26)
- [github.com/astral-sh/ruff-pre-commit: v0.16.2 → v0.16.6](astral-sh/ruff-pre-commit@v0.16.2...v0.16.6)
- [github.com/pre-commit/mirrors-mypy: v2.3.0 → v2.3.1](pre-commit/mirrors-mypy@v2.3.0...v2.3.1)

* fix(pre_commit): 🎨 auto format pre-commit hooks

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
* fix: reject classification export filename collisions
* Change annotation to dictionary for multiple paths
* fix(utils): clarify export collision guidance

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
…ners (#2554)

* fix(polygon_zone): reject polygons that enclose no area
* fix(vlm): order parsed box corners so VLM boxes stay valid
* test: signed commit
* fix(models): preserve Gemini masks for reversed boxes

---------

Co-authored-by: durgamani <durgamani.d.e.c.e.50@gmail.com>
Co-authored-by: Codex <codex@openai.com>
…tors (#2560)

Pose estimators report an undetected or occluded keypoint as NaN rather
than dropping it, and KeyPoints accepts those values. Every keypoint
annotator converted coordinates to int without checking, so a single
missing joint raised "ValueError: cannot convert float NaN to integer"
and aborted the whole frame.

KeyPoints.as_detections already excludes non-finite coordinates as
missing. Route the annotators through the same rule via a shared
_is_drawable helper so they skip the affected keypoint, edge or label
and still draw the rest of the skeleton.

---------

Co-authored-by: jirka <6035284+borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
…ed (#2561)

* fix(detection): keep LineZone triggering_anchors from being exhausted
* tests: add LineZone empty-anchors coverage; generalize single-pass anchors test
* docs: note LineZone anchors copy semantics; fix PolygonZone docstring wording

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
`tint_image` passed its `image` argument as OpenCV's `dst`, so the blend was written back into the caller's array. A NumPy input came back tinted; a Pillow input did not, because the decorator's ndarray conversion made a copy first. The same call therefore aliased differently depending on the input type, and `ensure_cv2_image_for_standalone_function` documents the opposite: "Assumes the annotators do NOT modify the scene in-place."

Blend into a fresh buffer so both input types are left untouched, matching `resize_image`, `letterbox_image`, `scale_image` and `grayscale_image`.

Co-authored-by: kevin9327 <kevin9327@users.noreply.github.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
…te (#2563)

`KeyPoints.with_nms` derives a box per skeleton from the key points it
considers valid, but the validity test was `xy == 0` alone. `NaN` is not
`0`, so an undetected joint — which pose estimators report as `NaN`
rather than dropping — stayed in the box, `np.min`/`np.max` propagated it
into all four corners, and every IoU comparison against that all-`NaN`
box was `False`. Duplicate skeletons therefore survived NMS.

Exclude non-finite coordinates, matching `KeyPoints.as_detections` and
the key point annotators, and give a skeleton the filter empties a
zero-area box rather than the `±inf` reduction sentinels.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
)

`scale_image` and `resize_image(keep_aspect_ratio=True)` compute the
output size from the input and truncate it with `int()`, so a small
enough factor — or an aspect ratio too extreme for the target box —
rounded an axis down to `0` and `cv2.resize` aborted with
`(-215:Assertion failed) inv_scale_x > 0`, an assertion that names
nothing the caller passed.

Clamp each derived axis to one pixel. This also reaches `letterbox_image`,
which could not fill the resolution it was asked for, and `CropAnnotator`
with a `scale_factor` below `1`, which aborted the whole frame as soon as
one detection box was a few pixels across.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
…count (#2566)

`Detections.from_ultralytics` built the placeholder `class_id` of a masks-only
result with `np.arange(len(ultralytics_results))`. `Results.__len__` returns the
number of images in the result, not the number of masks, so a single image that
carries three masks produced a `class_id` of length one:

    ValueError: class_id must be a 1D np.ndarray with shape (3,), but got shape (1,)

The placeholder now follows `mask`, so a result with N masks yields N
detections, each carrying the class `0` of the image it came from. Results that
do carry boxes still take their real class ids from `boxes.cls` and are
unaffected.

The existing test masked this by passing `length=3` to its fake results, which
conflated the image count with the mask count. It now passes `length=1`, the
value a real single-image `Results` reports, and asserts one class-zero entry
per mask.

---------

Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com>
Co-authored-by: Codex <codex@openai.com>
@Borda Borda added the enhancement New feature or request label Sep 14, 2026
@socket-security

socket-security Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedmkdocs-material@​9.7.6 ⏵ 9.7.789100 +2100100100

View full report

@Borda Borda changed the title rleasing 0.30.3 releasing 0.30.3 [rebase & merge] Sep 14, 2026
@Borda
Borda requested a lite review from Copilot September 14, 2026 12:41
@Borda
Borda marked this pull request as ready for review September 14, 2026 12:41
@Borda
Borda requested a review from SkalskiP as a code owner September 14, 2026 12:41

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.

🟡 Changes recommended

Unresolved moderate findings remain in release metadata, workflows, geometry, keypoint annotators, and regression coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This pull request releases Supervision 0.30.3 with fixes across keypoints, VLM parsing, video/CSV handling, geometry, dataset export, and documentation workflows.

Changes:

  • Harden non-finite keypoints, reversed boxes, empty frames, video limits, and image sizing.
  • Prevent integer overflow and dataset export issues.
  • Update release metadata, documentation workflows, and regression tests.
File summaries
File Summary
uv.lock Dependency lockfile reviewed.
tests/utils/test_video.py Tests video termination and reader errors.
tests/utils/test_image.py Tests image scaling, resizing, and tinting.
tests/key_points/test_core.py Tests non-finite keypoint NMS.
tests/key_points/test_annotators.py Tests keypoint annotator robustness.
tests/detection/utils/test_polygons.py Tests large-coordinate polygon precision.
tests/detection/utils/test_boxes.py Tests overflow-safe box arithmetic.
tests/detection/test_vlm.py Tests VLM box-corner normalization.
tests/detection/test_polygonzone.py Tests polygon validation.
tests/detection/test_line_counter.py Tests iterable anchor handling.
tests/detection/test_from_adapters.py Tests masks-only adapter conversion. Moderate (3 votes): the fixture does not reproduce the one-image/three-mask regression.
tests/detection/test_csv.py Tests CSV handling after empty batches.
tests/dataset/test_core.py Tests classification export collisions.
tests/annotators/test_utils.py Tests trace history across empty frames.
tests/annotators/test_core.py Tests trace and crop annotation behavior.
src/supervision/utils/video.py Fixes frame-limit hangs and reader errors.
src/supervision/utils/image.py Prevents zero-sized resize targets and input mutation.
src/supervision/key_points/core.py Handles non-finite keypoints during NMS.
src/supervision/key_points/annotators.py Skips non-finite keypoints. Moderate (1 vote each): two loops still unpack exactly two values and crash on three-component keypoint rows.
src/supervision/detection/vlm.py Normalizes parsed VLM box corners.
src/supervision/detection/utils/polygons.py Preserves large-origin polygon geometry. Moderate (1 vote): full-range signed int64 spans may overflow before translation.
src/supervision/detection/utils/boxes.py Adds overflow-safe box arithmetic.
src/supervision/detection/tools/polygon_zone.py Validates polygon vertex counts.
src/supervision/detection/tools/csv_sink.py Defers CSV schema selection until populated data.
src/supervision/detection/line_zone.py Preserves iterable triggering anchors.
src/supervision/detection/core.py Fixes masks-only adapter class IDs.
src/supervision/dataset/utils.py Supports export collision validation.
src/supervision/dataset/core.py Prevents classification export collisions.
src/supervision/annotators/utils.py Advances trace state across empty frames.
src/supervision/annotators/core.py Handles empty trace frames.
README.md Updates release documentation references.
pyproject.toml Bumps the package to 0.30.3. Moderate (1 vote): uv.lock still records version 0.31.0.dev0.
mkdocs.yml Adds release-version configuration.
docs/theme/main.html Controls version banner rendering.
docs/changelog.md Documents the release. Nit (1 vote): the keypoint behavior claim overstates support for three-component rows.
.pre-commit-config.yaml Updates quality-tool configuration.
.github/workflows/publish-docs.yml Updates documentation deployment. Moderate (1 vote): alpha and beta prereleases may be treated as stable releases.
.github/workflows/docs-backfill.yml Serializes documentation backfills.
.github/scripts/inject_outdated_banner.py Cleans outdated banners. Moderate (1 vote): generated styles, scripts, and tags can remain in the current tree.
.github/scripts/compute_is_latest_release.py Determines the latest stable release.
.github/_tests/test_docs_theme.py Tests version banner rendering.
.github/_tests/test_docs_publish_workflow.py Tests documentation publishing conditions.
.github/_tests/test_docs_backfill_workflow.py Tests documentation backfill behavior.
.github/_tests/test_compute_is_latest_release.py Tests release-version comparison.
Review details

Suppressed comments (7)

.github/scripts/inject_outdated_banner.py:405

  • This cleanup removes only the injected HTML marker. If an earlier backfill run patched the newest tree, its generated extra.css marker block, version-banner.js, and script tags remain because both patch_stylesheets/patch_scripts now skip the newest directory and --banner-only does not remove them. The documented invariant that the current tree gets neither the injected styling nor script is therefore not restored; add narrowly scoped cleanup for those generated assets or revise the invariant.
def unpatch_newest_version(root: Path) -> list[Path]:
    """Strip an injected banner from the current release tree under `root`.

    A run made before the newest release was excluded left an "older version"
    banner on the docs `latest` serves. The div is restored to the whitespace-only
    interior a build emits, so the next release — which demotes this tree to
    archived — patches it again through the normal path.

.github/workflows/publish-docs.yml:100

  • The release workflow only recognizes rc suffixes here, so a published alpha/beta tag is treated as a stable release: compute_is_latest_release includes it in the stable comparison and the deploy/archive conditions below allow it to write gh-pages. The package workflow explicitly excludes every github.event.release.prerelease; use that event flag (or a shared all-prerelease guard) for both release-doc deployment and latest-tree archival, not just RC naming.
            if [[ "$release_tag_lower" =~ (^|[._-])rc[0-9]+$ ]] || [[ "$release_tag_lower" =~ [0-9]rc[0-9]+$ ]]; then
              is_rc=true
            fi
            # An rc never gates the outdated banner (it is never deployed below), and
            # the comparison needs full tag history — see actions/checkout fetch-depth: 0
            # at the top of this job.
            if [[ "$is_rc" != "true" ]]; then

docs/changelog.md:25

  • This release note overstates the new behavior for 3-component keypoint rows: VertexAnnotator and the ellipse annotators still unpack each point as (x, y) in their loops, so xy.shape == (N, K, 3) raises before the finite-coordinate guard runs. Either limit this sentence to annotators that accept such rows or update those loops to inspect the full point while drawing its first two coordinates.
- The key point annotators — `sv.VertexAnnotator`, `sv.EdgeAnnotator`, `sv.VertexLabelAnnotator` and the `sv.VertexEllipse*Annotator` family — now skip key points whose coordinates are not finite instead of raising `ValueError: cannot convert float NaN to integer`. Pose estimators commonly report an undetected or occluded key point as `NaN` rather than dropping it, so a single missing joint aborted the whole frame. `sv.KeyPoints.as_detections` already treats non-finite coordinates as missing; the annotators now apply the same rule, drawing every other key point in the skeleton. For 3-component key points (`xy` shape `(N, K, 3)`), the check applies to the full row, so a key point with a finite, drawable `(x, y)` but a non-finite third component is skipped too, for parity with `sv.KeyPoints.as_detections`.

pyproject.toml:7

  • This version bump leaves the lockfile inconsistent: uv.lock still records the editable supervision package as 0.31.0.dev0, while this file declares 0.30.3. Any lock freshness check (and the committed lock metadata used by CI/build tooling) will therefore describe a different project version. Regenerate uv.lock after changing the project version so its package entry matches 0.30.3.
version = "0.30.3"

src/supervision/detection/utils/polygons.py:160

  • For a signed int64 polygon whose local span exceeds int64.max (for example, coordinates near both -2**62 and 2**62), this casts the local float32 candidate to int64 before adding the negative origin. That conversion is out of range and can wrap/produce invalid coordinates even though the translated absolute points are representable; perform the translation before narrowing, or retain a safe floating result for this case, and add a regression for the full-range span.
        if np.issubdtype(original_dtype, np.integer):
            approximated_points = np.asarray(
                candidate.astype(original_dtype).astype(object) + origin.astype(object),
                dtype=original_dtype,

src/supervision/key_points/annotators.py:118

  • The new finite-coordinate guard is reached only after destructuring each point into exactly two values. KeyPoints instances with a third per-point component (the new tests exercise this for the other annotators) still raise ValueError: too many values to unpack here, so a non-finite third component cannot be skipped as documented. Iterate over the full point, validate it, and use only point[0]/point[1] for rasterization.
            for point_index, (x, y) in enumerate(xy):
                if np.allclose((x, y), 0) or not _has_finite_coordinates((x, y)):

src/supervision/key_points/annotators.py:370

  • This loop has the same exact-two-value destructuring, so a (N, K, 3) keypoint array still crashes before _has_finite_coordinates can reject a non-finite third component. The annotator docs/changelog state that finiteness is checked on the full keypoint row; iterate over each point and use its first two coordinates for the ellipse center instead.
            for point_index, (x, y) in enumerate(xy):
                if np.allclose((x, y), 0) or not _has_finite_coordinates((x, y)):
  • Files reviewed: 43/44 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/detection/test_from_adapters.py
Copilot AI and others added 2 commits September 14, 2026 15:01
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
@Borda
Borda merged commit 98396bd into release/latest Sep 14, 2026
7 checks passed
@Borda
Borda deleted the releasing/0.30.3 branch September 14, 2026 13:05

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.

🟡 Changes recommended

Unresolved critical keypoint annotation handling and moderate polygon and pre-release documentation issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (5)

.github/scripts/compute_is_latest_release.py:59

  • Version(normalized) accepts alpha, beta, dev, and RC versions, but only the RC suffix regex is excluded here. If a future stable tag is compared while a higher a/b/dev tag exists, that pre-release becomes the maximum candidate and the stable release is incorrectly treated as non-latest. After parsing, also skip version.is_prerelease so the helper's 'stable version' contract matches its implementation.
    if _RC_SUFFIX_RE.search(normalized.lower()):
        return None
    try:
        return Version(normalized)
    except InvalidVersion:

.github/scripts/inject_outdated_banner.py:167

  • _numeric_version_dirs includes pre-release directories, so a leftover 0.31.0rc1 becomes _newest_version_dir when 0.31.0 has not been deployed. Since publish-docs.yml skips RC deployments, latest can still serve a stable tree while this routine treats the RC as current, leaves it unpatched, and incorrectly archives the stable tree. Exclude Version.is_prerelease directories before choosing the newest tree.
    dirs = (d for d in root.iterdir() if d.is_dir() and d.name[:1].isdigit())
    return sorted(dirs, key=lambda d: _version_key(d.name))

.github/workflows/publish-docs.yml:95

  • This gate only recognizes rc suffixes, but this repository also treats a and b tags as valid pre-releases (.github/workflows/publish-pre-release.yml:6-11). Publishing a GitHub release such as 1.0.0b1 would therefore enter the stable docs deploy, root-file notification, and latest-release/archive paths. Gate this on github.event.release.prerelease (or otherwise detect every PEP 440 pre-release), rather than only release candidates.
            # Match RC suffixes with separators (1.0-rc1, 1.0.rc1) or compact form (1.0rc1).
            if [[ "$release_tag_lower" =~ (^|[._-])rc[0-9]+$ ]] || [[ "$release_tag_lower" =~ [0-9]rc[0-9]+$ ]]; then
              is_rc=true

src/supervision/detection/utils/polygons.py:160

  • candidate is in the translated coordinate system, but it is narrowed to original_dtype before origin is restored. For a signed integer polygon whose local span exceeds that dtype's range (for example an int64 polygon spanning -2**63 to 2**63 - 1), this conversion can wrap or fail before reconstruction and corrupt the returned vertices, undermining the large-coordinate correctness fix. Reconstruct in a wider/exact representation (or map approximated vertices back to the original integer points) and narrow only after restoring the origin.
        if np.issubdtype(original_dtype, np.integer):
            approximated_points = np.asarray(
                candidate.astype(original_dtype).astype(object) + origin.astype(object),
                dtype=original_dtype,

src/supervision/key_points/annotators.py:371

  • The ellipse path has the same two-coordinate destructuring, so 3-component keypoints cannot reach the new full-row finiteness check and instead fail with ValueError: too many values to unpack. Iterate over each point, validate the full row, and use its first two values as the ellipse center, matching KeyPoints.as_detections and the other annotators.
            for point_index, (x, y) in enumerate(xy):
                if np.allclose((x, y), 0) or not _has_finite_coordinates((x, y)):
                    continue
  • Files reviewed: 43/44 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines 117 to 119
for point_index, (x, y) in enumerate(xy):
if np.allclose((x, y), 0):
if np.allclose((x, y), 0) or not _has_finite_coordinates((x, y)):
continue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants