0.30.3: Pose, VLM, and video/CSV crash and correctness fixes
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 suppressedsv.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 wraparoundpad_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— 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_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
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. (#2566)sv.pad_boxesnow computes integer-coordinate padding without overflow or unsigned casting errors. (#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. (#2564)sv.KeyPoints.with_nmsno longer stops suppressing duplicate skeletons as soon as a key point is non-finite. (#2563)sv.tint_imageno longer tints the caller's own image array in place. (#2562)sv.LineZoneno longer consumes thetriggering_anchorsiterable during validation, so a generator ormappassed in is no longer exhausted before the firsttrigger()call. (#2561)- The key point annotators now skip key points whose coordinates are not finite instead of raising
ValueError. (#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. (#2554)sv.ClassificationDataset.as_folder_structurenow rejects images that would overwrite the same class-relative filename before writing any files. (#2551)sv.process_videono longer hangs forever whenmax_framesis larger than the number of frames in the video. (#2546)sv.filter_polygons_by_areaandsv.approximate_polygonnow preserve local geometry for large-origin integer andfloat64polygons. (#2542)sv.TraceAnnotator.annotateno longer raises on an empty-detections frame;sv.CSVSinkno longer lets an empty batch fix the CSV header. (#2539)sv.scale_boxesnow preserves exact integer intermediates, preventing overflow and scaled-corner rounding errors for large integer-coordinate boxes. (#2541)- A release's own version-pinned docs no longer show the outdated-version banner on the day it ships, and the docs-publish and canonical-backfill workflows now share one
gh-pageswrite lock instead of racing each other. (#2536)
🏆 Contributors
- kevin (@kevin9327) — fixed
KeyPoints.with_nms/key point annotators crashing on non-finite key points,tint_imageimage aliasing,LineZonegenerator exhaustion, and thescale_image/resize_imagezero-target crash - Durgamani Sasikumar (@tedo001, LinkedIn) — fixed
PolygonZone/from_vlmbox-corner ordering and theTraceAnnotator/CSVSinkempty-frame crash - S B Pranay (@pranaysb, LinkedIn) — fixed integer overflow in
scale_boxesand dtype loss infilter_polygons_by_area/approximate_polygon - JiantaoPeng (@PengJianT) — fixed
from_ultralyticsmasks-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_videohanging whenmax_framesexceeds the video length - Jirka Borovec (@Borda, LinkedIn) — fixed the release-day outdated-docs banner
Full changelog: 0.30.2...0.30.3