v0.31.2 — Pascal VOC boxes that match the devkit
A bug-fix release: Pascal VOC boxes now follow the devkit convention, and a batch of crashes on edge-case inputs is gone.
- Pascal VOC export and import use the devkit's box indices, so Detectron2 and other readers see the boxes you wrote.
- Gemini 2.5 and 3.5 masks survive boxes that leave the 0-1000 range instead of failing the whole response.
- Crop and icon annotators draw on grayscale scenes.
- Half-precision box centers stay finite instead of turning into
inf. - JSON sinks, dataset exports and the video reader stop losing data when something goes wrong.
Drop-in upgrade, with one catch: box-only Pascal VOC objects written by earlier versions now load one pixel larger on the right and bottom edges. The migration guide below shows how to fix them.
✨ Spotlights / highlights
Pascal VOC boxes follow the devkit
DetectionDataset.as_pascal_voc and from_pascal_voc now treat <bndbox> indices as 1-based and inclusive, shifting only xmin and ymin by one, which is what Detectron2 and other devkit readers expect. Export also rounds corners to the nearest pixel instead of truncating them, so a YOLO box that from_yolo loads with float noise no longer grows a pixel. Box-only objects written by 0.31.1 and earlier carry an extra +1 in xmax and ymax; objects exported from masks already used the devkit maxima. (#2748, #2780)
# `dataset` holds one box [10, 20, 40, 60]
dataset.as_pascal_voc(
images_directory_path="images", annotations_directory_path="annotations"
)
# <bndbox>: xmin=11 ymin=21 xmax=40 ymax=60 (0.31.1: xmax=41 ymax=61)
# a YOLO box loaded as [0, 0, 37.0000001, 29.0000001]: 1 1 37 29 (0.31.1: 1 1 38 30)Gemini masks survive boxes outside the 0-1000 range
Detections.from_vlm with VLM.GOOGLE_GEMINI_2_5 or VLM.GOOGLE_GEMINI_3_5 raised ValueError as soon as a box_2d coordinate went above 1000 or below 0, even by one, and you lost every detection in the response. The mask is now cropped to the part of its box inside the image, a box wholly outside gets an all-false mask, and boxes inside the image produce the same masks as before. (#2793)
import supervision as sv
# one item: {"box_2d": [500, 500, 1001, 1001], "label": "cat", "mask": "data:image/png;base64,..."}
detections = sv.Detections.from_vlm(
sv.VLM.GOOGLE_GEMINI_3_5, result, resolution_wh=(1000, 800)
)
detections.mask.shape, int(detections.mask.sum())
# ((1, 800, 1000), 200000)
# 0.31.1: ValueError: could not broadcast input array from shape (400,501) into shape (400,500)Crop and icon annotators on grayscale scenes
CropAnnotator and IconAnnotator used to raise IndexError or ValueError on scenes shaped (H, W) or (H, W, 1), or given as a Pillow "L" image. Crops now keep the scene's channel layout, and icons are drawn as their gray luminance, blended by their alpha channel. (#2788)
import numpy as np
import supervision as sv
scene = np.zeros((100, 100), dtype=np.uint8)
scene[20:60, 20:60] = 200
detections = sv.Detections(
xyxy=np.array([[20, 20, 60, 60]], dtype=float), class_id=np.array([0])
)
sv.CropAnnotator().annotate(scene.copy(), detections).shape
# (100, 100) (0.31.1: IndexError: tuple index out of range)Half-precision boxes that stay finite
sv.scale_boxes and Detections.get_anchors_coordinates now compute box centers without overflowing float16: a box from 40000 to 60000 used to report an inf center. DetectionsSmoother also compares oriented-box corner distances in float64, which stops float16 and narrow integer coordinates from overflowing; integer coordinates beyond 2**53 still lose low-order pixels in the float64 average. (#2787, #2785)
import numpy as np
import supervision as sv
detections = sv.Detections(
xyxy=np.array([[40000, 40000, 60000, 60000]], dtype=np.float16)
)
detections.get_anchors_coordinates(sv.Position.CENTER).tolist()
# [[49984.0, 49984.0]] (0.31.1: [[inf, inf]])🔄 Migration guide
One behavior change needs your attention: the Pascal VOC box convention. No public symbol was added, renamed or removed in this release, and nothing is deprecated.
Pascal VOC <bndbox> indices affects sv.DetectionDataset.as_pascal_voc and sv.DetectionDataset.from_pascal_voc. Before 0.31.2, export added 1 to all four corners of a box-only object and truncated, and import subtracted 1 from all four. From 0.31.2, both follow the devkit's 1-based, inclusive indices: only xmin and ymin are shifted by one, and export rounds to the nearest pixel. Export rounds every corner to a whole pixel and the loader returns float32 coordinates, so check precision-sensitive boxes after a round trip. Files from different versions don't round-trip. (#2748, #2780)
Box [10, 20, 40, 60]
| Written <bndbox>
| Read back by 0.31.2 |
|---|---|---|
| written by 0.31.1 | xmin=11 ymin=21 xmax=41 ymax=61
| [10, 20, 41, 61]
|
| written by 0.31.2 | xmin=11 ymin=21 xmax=40 ymax=60
| [10, 20, 40, 60]
|
- Box-only objects (no
<polygon>) in files written by 0.31.1 or earlier now load one pixel larger on the right and bottom edges. To restore the original boxes, subtract 1 from<xmax>and<ymax>of those objects only, or re-export the dataset from its source with 0.31.2. Objects exported from masks, which carry a<polygon>, already used the devkit maxima, so leave their files alone: their masks load as before, while their boxes now end one pixel further right and down. - Files exported by 0.31.2 match what Detectron2 and other devkit readers expect, so workarounds for the old extra +1 in
xmaxandymaxcan go.
📝 Notable changes
⚠️ Breaking Changes
- Pascal VOC box extents —
as_pascal_vocandfrom_pascal_vocfollow the devkit's 1-based, inclusive<bndbox>indices and export rounds corners to the nearest pixel. Box-only objects written by earlier versions load one pixel larger on the right and bottom edges, so check the migration guide before reading old VOC datasets. (#2748, #2780)
🔧 Fixed
Datasets and file I/O
- Dataset exports finished without an error even when an image could not be written, leaving image files missing; detection dataset exports and
ClassificationDataset.as_folder_structurenow raiseOSErrorwith the destination path. (#2772) JSONSink.write_and_close()raisedValueError: I/O operation on closed file.when called inside awithblock, because the block's exit calls it again; it's now a no-op once the file is closed, likeCSVSink.close(). (#2771)JSONSink.appendkept references to its input, so reusing NumPy buffers or editing nested metadata changed rows that were already appended; it now snapshots the data. (#2781)- NumPy boolean scalars are now written as JSON booleans in JSON files. (#2783)
Detections and VLM
Detections.from_vlmwith the Gemini 2.5 and 3.5 connectors failed on any item whosebox_2dleft the 0-1000 range; its mask is now cropped to the image. (#2793)Detections.select()raised an indexing error on selected rows that share a scalar value indata; scalar NumPy values are now preserved. (#2769)sv.mask_iou_batchnow treatsCompactMaskinputs like dense masks: mismatched image shapes raiseValueError, unsupported overlap metrics are rejected even when bounding-box pre-filtering finds no pair, and exact NMS thresholds make the same decision for both. (#2767)sv.scale_boxesandDetections.get_anchors_coordinatesoverflowed toinfforfloat16boxes with large coordinates. (#2787)DetectionsSmoothercould overflow or tie onfloat16and large integer coordinates when aligning oriented-box corners; integer coordinates beyond2**53still lose low-order pixels in thefloat64average. (#2785)
Annotators and video
CropAnnotatorandIconAnnotatorraisedIndexErrororValueErroron grayscale scenes. (#2788)get_video_frames_generatorleft its video capture open when range validation or the initial seek failed; it now releases the capture and logs any secondary release error without replacing the original error. (#2782)
🏆 Contributors
Welcome to our first-time contributors: 卢文彬, SHLOK SINGH, zurrll, Huashuo Lei, moyunliuyin, Wentao Tong and LEE, WEI-HONG!
- Avanish Garg (@avanish-garg) — kept Gemini 2.5 and 3.5 masks when a box leaves the 0-1000 range.
- Mohammad Hijjawi (@MohammadHijjawi97) — made
CropAnnotatorandIconAnnotatorwork on grayscale scenes. - 卢文彬 (@lu123bing) — fixed
infbox centers for half-precision boxes. - A Aswanth Raj (@aswanth-07) — fixed overflow in
DetectionsSmoothercorner alignment. - Kaushik Reddy Lingireddy (@Lingikaushikreddy) — rounded Pascal VOC corners instead of truncating them.
- moyunliuyin (@moyunliuyin) — aligned Pascal VOC export and import with the devkit's box extents.
- Junghwan (@shaun0927) — fixed
JSONSink.appendbuffer reuse and released video captures when setup fails. - Huashuo Lei (@wpyaoxueros) — fixed
Detections.select()with scalar data. - Wentao Tong (@tongwentao) — made dataset exports raise when an image cannot be written.
- kevin (@kevin9327) — fixed
JSONSink.write_and_close()after the file is closed. - Devulapalli Naga Sri Vaishnavi (@Vaishnavi220506) — aligned
CompactMaskIoU with the dense-mask contract. - @zurrll — saved NumPy booleans as JSON booleans.
- SHLOK SINGH (@buildwithshlok) — added type checking to the annotators tests.
- LEE, WEI-HONG (@AuricTW) — added a
cross_productdoctest. - Jirka Borovec (@Borda) — prepared the release.
Automated contributions: Dependabot, Copilot, Claude, pre-commit.ci
Full changelog: 0.31.1...0.31.2