Repository navigation
releasing 0.30.3 [rebase & merge] - #2572
Conversation
- 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>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
🟡 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.cssmarker block,version-banner.js, and script tags remain because bothpatch_stylesheets/patch_scriptsnow skip the newest directory and--banner-onlydoes 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
rcsuffixes here, so a published alpha/beta tag is treated as a stable release:compute_is_latest_releaseincludes it in the stable comparison and the deploy/archive conditions below allow it to writegh-pages. The package workflow explicitly excludes everygithub.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:
VertexAnnotatorand the ellipse annotators still unpack each point as(x, y)in their loops, soxy.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.lockstill records the editablesupervisionpackage as0.31.0.dev0, while this file declares0.30.3. Any lock freshness check (and the committed lock metadata used by CI/build tooling) will therefore describe a different project version. Regenerateuv.lockafter changing the project version so its package entry matches0.30.3.
version = "0.30.3"
src/supervision/detection/utils/polygons.py:160
- For a signed
int64polygon whose local span exceedsint64.max(for example, coordinates near both-2**62and2**62), this casts the localfloat32candidate toint64before 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.
KeyPointsinstances with a third per-point component (the new tests exercise this for the other annotators) still raiseValueError: too many values to unpackhere, so a non-finite third component cannot be skipped as documented. Iterate over the full point, validate it, and use onlypoint[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_coordinatescan 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.
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
0be4c97 to
84f18c2
Compare
There was a problem hiding this comment.
🟡 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 highera/b/devtag exists, that pre-release becomes the maximum candidate and the stable release is incorrectly treated as non-latest. After parsing, also skipversion.is_prereleaseso 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_dirsincludes pre-release directories, so a leftover0.31.0rc1becomes_newest_version_dirwhen0.31.0has not been deployed. Sincepublish-docs.ymlskips RC deployments,latestcan still serve a stable tree while this routine treats the RC as current, leaves it unpatched, and incorrectly archives the stable tree. ExcludeVersion.is_prereleasedirectories 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
rcsuffixes, but this repository also treatsaandbtags as valid pre-releases (.github/workflows/publish-pre-release.yml:6-11). Publishing a GitHub release such as1.0.0b1would therefore enter the stable docs deploy, root-file notification, and latest-release/archive paths. Gate this ongithub.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
candidateis in the translated coordinate system, but it is narrowed tooriginal_dtypebeforeoriginis restored. For a signed integer polygon whose local span exceeds that dtype's range (for example anint64polygon spanning-2**63to2**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, matchingKeyPoints.as_detectionsand 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
| 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 |
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_vlmnow orders backwards box corners, closing a bug where such a box scored a false0.0IoU and both survived NMS as a duplicate and counted as a total miss in mAP.sv.TraceAnnotatorandsv.CSVSinkno longer crash or silently drop columns on the first frame with no detections — a case every non-ByteTracktracker pipeline hits.sv.process_videono longer hangs forever whenmax_framesexceeds the video length. Continuing 0.30.2's numeric-correctness theme,sv.pad_boxesandsv.scale_boxesare 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_nmstested key point validity withxy == 0alone, andNaN— how pose estimators report an undetected joint — is not0. The stale joint stayed in the NMS box, so a duplicate skeleton scoredFalseon every IoU comparison against it and survived suppression. The key point annotators (sv.VertexAnnotator,sv.EdgeAnnotator,sv.VertexLabelAnnotator, thesv.VertexEllipse*Annotatorfamily) had the matching crash: a single undetected joint raisedValueError: cannot convert float NaN to integerfor the whole frame. Both now skip non-finite coordinates, matchingsv.KeyPoints.as_detections.sv.Detections.from_vlmno longer scores a false IoU miss on backwards box cornersA VLM that emits a corner pair backwards produced an
xyxyrow withx_min > x_max. Nothing downstream caught it:sv.box_iou_batchclamps intersection width at zero, so the box scored0.0IoU against itself — surviving NMS as a duplicate and counting as a total miss in mAP — whilebox_areastill reported a plausible positive value. Every VLM parser now orders each box's corners before returning it.sv.TraceAnnotatorandsv.CSVSinkno longer crash or silently corrupt output on an empty-detections framesv.TraceAnnotator.annotateraisedValueError: The tracker_id field is missingon the first frame with no detections, for every tracker exceptsv.ByteTrack. Such a frame now draws nothing and still advances the frame counter, sotrace_lengthstays a window over elapsed frames rather than only over populated ones.sv.CSVSinkhad a quieter failure: an empty batch fixed the CSV header without thedata/custom_datacolumns, and every later row was silently truncated to that schema — dropping fields likeclass_namefor the whole file. The header is now fixed by the first batch that actually carries detections.sv.process_videono longer hangs forever whenmax_framesexceeds the video lengthThe reader thread failed on the out-of-range
endbefore enqueuing its sentinel, leaving the main loop blocked on the read queue indefinitely.max_framesis now capped at the video length, and any reader-thread error surfaces asRuntimeError("Reader thread raised: ...")instead of stalling the call.Integer-coordinate overflow fixed in
sv.pad_boxesandsv.scale_boxesBoth computed intermediate values that could overflow or silently wrap for large integer coordinates (e.g. large
int32/uint16/int64boxes). Both now use overflow-safe arithmetic.pad_boxeschanges return dtype for integer input — see the migration guide below.sv.scale_imageandsv.resize_image(keep_aspect_ratio=True)no longer crash on an extreme aspect ratio or tiny scale factorA small enough factor — or an aspect ratio too extreme for the target box — could round an output axis down to
0, andcv2.resizeraised an assertion naming nothing the caller passed. Each axis now keeps at least one pixel. Two callers inherit the fix:sv.letterbox_imagecould not fill the resolution it was asked for, andsv.CropAnnotatorwithscale_factor < 1aborted 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— integerxyxynow returnsint64(orfloat64if a padded coordinate exceeds theint64range), instead of the input's original integer dtype, which could silently overflow or wrap for small dtypes likeint16/uint8.If your code assumes
pad_boxespreserves the input's exact dtype (e.g. reusing the result as anint16array), cast explicitly:sv.pad_boxes(...).astype(np.int16).sv.scale_boxesalso fixes an integer-overflow bug, but its return dtype was alreadyfloat64for integer input before this release — unaffected.📝 Notable changes
🔧 Fixed
sv.Detections.from_ultralyticsnow assigns the placeholder class ID0to every mask in a masks-only result, instead of sequential IDs across masks that belong to the same image. (fix(detection): size masks-onlyfrom_ultralyticsclass ids by mask count #2566)sv.pad_boxesnow computes integer-coordinate padding without overflow or unsigned casting errors. (fix: prevent integer overflow inpad_boxes#2565)sv.scale_imageandsv.resize_image(keep_aspect_ratio=True)no longer derive a zero-sized target, fixing a crash reached viasv.letterbox_imageandsv.CropAnnotator. (fix(utils): stop image resizing from deriving a zero-sized target #2564)sv.KeyPoints.with_nmsno longer stops suppressing duplicate skeletons as soon as a key point is non-finite. (fix(key_points): keepwith_nmsworking when a key point is not finite #2563)sv.tint_imageno longer tints the caller's own image array in place. (fix(utils): stoptint_imagefrom tinting the caller's image #2562)sv.LineZoneno longer consumes thetriggering_anchorsiterable during validation, so a generator ormappassed in is no longer exhausted before the firsttrigger()call. (fix(detection): keepLineZonetriggering anchors from being exhausted #2561)ValueError. (fix(key_points): skip non-finite keypoints instead of crashing annotators #2560)sv.PolygonZonenow rejects a polygon with fewer than three vertices instead of building a zone that can never trigger;sv.Detections.from_vlmnow orders each parsed box's corners. (fix(detection): reject degenerate polygon zones and order VLM box corners #2554)sv.ClassificationDataset.as_folder_structurenow rejects images that would overwrite the same class-relative filename before writing any files. (fix: reject classification export filename collisions #2551)sv.process_videono longer hangs forever whenmax_framesis larger than the number of frames in the video. (fix(video): stopprocess_videohanging whenmax_framesexceeds the video length #2546)sv.filter_polygons_by_areaandsv.approximate_polygonnow preserve local geometry for large-origin integer andfloat64polygons. (fix(detection): support float64 and arbitrary dtypes in polygon utilities #2542)sv.TraceAnnotator.annotateno longer raises on an empty-detections frame;sv.CSVSinkno longer lets an empty batch fix the CSV header. (fix(trace): stop empty frames from crashing TraceAnnotator #2539)sv.scale_boxesnow preserves exact integer intermediates, preventing overflow and scaled-corner rounding errors for large integer-coordinate boxes. (fix(detection): prevent integer overflow inscale_boxes#2541)gh-pageswrite lock instead of racing each other. (fix(docs): stop banner-ing a release's own docs as outdated #2536)🏆 Contributors
KeyPoints.with_nms/key point annotators crashing on non-finite key points,tint_imageimage aliasing,LineZonegenerator exhaustion, and thescale_image/resize_imagezero-target crashPolygonZone/from_vlmbox-corner ordering and theTraceAnnotator/CSVSinkempty-frame crashscale_boxesand dtype loss infilter_polygons_by_area/approximate_polygonfrom_ultralyticsmasks-only class ID sizingpad_boxesprocess_videohanging whenmax_framesexceeds the video lengthFull changelog: 0.30.2...0.30.3