v0.31.7: pycocotools-matching mAP, stricter key point and CSV inputs, and edge-case fixes for zones and dataset exports
A fixes-only patch: mAP now matches pycocotools more closely, and several zone, key point and export edge cases stop failing.
- MeanAveragePrecision uses pycocotools size buckets and the COCO crowd IoU for oriented-box crowd targets.
- PolygonZone keeps its full mask when polygon coordinates reach their integer limit.
- Key point selection and CSVSink now raise on inputs they used to silently misread.
- DetectionDataset exports keep box area for empty COCO masks and, with simplification on, bridge more mask holes.
- The OpenCV-free
contourAreaandconvertScaleAbsfallbacks agree with OpenCV in more cases.
Drop-in upgrade, but check the Migration guide if you compare mAP values, pass extra CSV fields or select key points with boolean masks.
✨ Spotlights / highlights
MeanAveragePrecision matches pycocotools more closely
sv.metrics.MeanAveragePrecision now puts objects in the small, medium and large buckets that pycocotools gives them: a prediction's own geometry (box, mask or oriented-box polygon) decides, and its data["area"] is ignored. Target data["area"] is read as float64, so an area such as 9216.0004 is no longer scored as both medium and large. With MetricTarget.ORIENTED_BOUNDING_BOXES, targets with data["iscrowd"] set use the COCO crowd IoU, so a prediction inside a crowd region is ignored instead of counted as a false positive. (#2939, #2941)
import numpy as np
import supervision as sv
from supervision.metrics import MeanAveragePrecision, MetricTarget
# A small target with a perfect prediction, plus a higher-scored medium false positive
# whose area metadata claims it is small.
masks = np.zeros((2, 128, 128), dtype=bool)
masks[0, 0:20, 0:20] = True
masks[1, 60:100, 60:100] = True
predictions = sv.Detections(
xyxy=np.array([[0, 0, 20, 20], [60, 60, 100, 100]], dtype=float),
mask=masks,
class_id=np.array([0, 0]),
confidence=np.array([0.5, 0.9]),
data={"area": np.array([400.0, 300.0])},
)
targets = sv.Detections(
xyxy=np.array([[0, 0, 20, 20]], dtype=float),
mask=masks[:1],
class_id=np.array([0]),
)
result = (
MeanAveragePrecision(metric_target=MetricTarget.MASKS)
.update(predictions, targets)
.compute()
)
result.small_objects.map50_95 # 1.0: the medium false positive no longer counts as smallMisread inputs raise, and scalar key point indexing works
sv.CSVSink raises ValueError when additional data uses a built-in column name such as confidence or x_min, instead of writing a duplicate column that shadows the detection value. sv.KeyPoints selection rejects a nonempty one-dimensional boolean mask whose length does not match the number of skeletons or keypoints, instead of silently selecting a subset, and a zero-dimensional NumPy integer row index combined with a keypoint list or mask now works. (#2934, #2943, #2930)
import numpy as np
import supervision as sv
detections = sv.Detections(
xyxy=np.array([[0.0, 0.0, 10.0, 10.0]]), confidence=np.array([0.9])
)
with sv.CSVSink("detections.csv") as sink:
sink.append(
detections, {"confidence": 0.1}
) # ValueError: Additional data uses reserved CSV column names confidence.PolygonZone survives integer coordinate limits
sv.PolygonZone computes its mask bounds as Python integers, so a polygon whose coordinates reach their dtype limit, for example uint8 255, keeps its full mask instead of overflowing, raising an error or silently creating a smaller zone. (#2945)
import numpy as np
import supervision as sv
polygon = np.array([[0, 0], [255, 0], [255, 3], [0, 3]], dtype=np.uint8)
sv.PolygonZone(polygon).mask.shape # (5, 257)The OpenCV-free fallbacks agree with OpenCV in more cases
The contourArea fallback adds the shoelace terms in OpenCV's order and reads integer points as float32, so sv.filter_polygons_by_area no longer flips at a min_area or max_area boundary between backends. The convertScaleAbs fallback scales in float32 with a single-rounding multiply-add, so values on a .5 tie such as 95 * 1.3 round to the same uint8 as OpenCV. (#2936, #2935)
Labels and dataset exports
sv.LabelAnnotator and sv.RichLabelAnnotator with smart_position=True keep an oversized label at the frame's left and top edges, and shaped right-to-left text in RichLabelAnnotator keeps its leading characters at the visible right edge. DetectionDataset.as_yolo and as_pascal_voc retry a failed mask-hole seam search with traced contours, so more masks export when you simplify them with approximation_percentage, and as_coco uses box area for empty mask placeholders that have no finite stored area. (#2940, #2933, #2942)
🔄 Migration guide
Nothing was removed, renamed or deprecated, but a few fixes change what existing code returns or accepts:
- Different values:
MeanAveragePrecisionsizes a prediction by its geometry and ignores itsdata["area"], and reads targetdata["area"]as float64, so small, medium and large mAP can move. (#2939) Oriented-box mAP oniscrowdtargets can rise, never fall, because crowd overlaps are ignored. (#2941)as_cocowrites box area, not0, for empty mask placeholders. (#2942) Oversized labels withsmart_position=Truestop at the left and top frame edges. (#2940) - New errors:
CSVSinkraisesValueErrorfor extra fields named like a built-in column. (#2934)KeyPointsselection raisesIndexErrorfor a nonempty boolean mask of the wrong length. (#2943) - Without OpenCV: the
contourAreafallback can flipfilter_polygons_by_areaat a boundary (#2936), and theconvertScaleAbsfallback rounds someuint8results one level differently, up or down (#2935). Regenerate stored references made with the NumPy fallback.
📝 Notable changes
🔧 Fixed
Metrics
MeanAveragePrecisionputs objects in the small, medium and large bucketspycocotoolsgives them: prediction area comes from its own geometry (box, mask or oriented-box polygon), and targetdata["area"]is read as float64. (#2939)MeanAveragePrecisionwithMetricTarget.ORIENTED_BOUNDING_BOXESuses the COCO crowd IoU for targets withdata["iscrowd"]set, so predictions inside a crowd region are ignored instead of counted as false positives. (#2941)
Key points, zones and sinks
PolygonZonepreserves mask dimensions when integer polygon coordinates reach their dtype limit, instead of overflowing. (#2945)KeyPointsrejects nonempty one-dimensional boolean selection masks whose length does not match the number of skeletons or keypoints; keypoint selection onKeyPoints.empty()stays empty. (#2943)KeyPointsaccepts a zero-dimensional NumPy integer row index combined with a list or boolean mask of keypoints, preserving coordinates and aligned fields instead of raisingValueError. (#2930)CSVSinkraisesValueErrorwhen additional data uses a built-in column name such asconfidenceorx_min, instead of writing duplicate columns. (#2934)
Annotators and the NumPy fallback without OpenCV
LabelAnnotatorandRichLabelAnnotatorwithsmart_position=Truekeep an oversized label at the frame's left and top edges; shaped right-to-left text inRichLabelAnnotatorretains its leading characters at the visible right edge. (#2940)- The
contourAreafallback returns exactly whatcv2.contourAreareturns, sofilter_polygons_by_areaagrees across backends at its boundaries. (#2936) - The
convertScaleAbsfallback scales in float32 with a single-rounding multiply-add, like OpenCV's fused float32 path. (#2935)
Dataset exports
DetectionDataset.as_yoloandas_pascal_vocretry a failed mask-hole seam search with traced contours when simplification removes usable vertices;as_yoloandas_pascal_vocstill raise if no seam is found, andas_cocoalso tries this foriscrowd=0annotations. (#2933)DetectionDataset.as_cocouses bounding-box area for empty mask placeholders when no finite stored area is available and no segmentation is exported. (#2942)
📚 Docs
- The "Edit this page" links now point at the
developbranch. (#2944) - The SAHI notebook shows the corrected tile size output. (#2937)
- The SAHI notebook calls
InferenceSlicerwithoverlap_whand no longer passes the removedoverlap_ratio_wh, so its cells run again, and outputs from the old 2560-pixel image were cleared. (#2938) - The
CSVSinkhow-to now says that extra fields named like a built-in column raiseValueError, and thesnap_boxesandMeanAveragePrecisionResultdocstrings describe oversized boxes and the inclusive size limits.
🏆 Contributors
Welcome to our first-time contributors Pranjal Mishra, Chris Censullo and viwe-monai!
- Pranjal Mishra (@pmxlr8, LinkedIn) — matched
MeanAveragePrecisionsize buckets and oriented-box crowd IoU topycocotools. - Mohammad Sohail Shaikh (@sohailshk, LinkedIn) — kept oversized labels at the frame's left edge and bridged mask holes in dataset exports.
- Vlad Voropaev (@voropaevv, LinkedIn) — rejected
CSVSinkextra fields that shadow built-in columns and kept box area for empty COCO mask placeholders. - Ahmet Mert Özacar (@AhmetMert01) — validated boolean key point selection masks and accepted scalar NumPy row indices.
- A Aswanth Raj (@aswanth-07, LinkedIn) — kept
PolygonZonemasks intact at integer coordinate limits. - kevin (@kevin9327) — made the OpenCV-free
contourAreaandconvertScaleAbsfallbacks match OpenCV. - Chris Censullo (@censullo) — corrected the tile size output in the SAHI notebook.
- viwe-monai (@viwe-monai) — pointed the "Edit this page" links at the
developbranch. - Jirka Borovec (@Borda, LinkedIn) — updated dependency pins and prepared the release.
Automated contributions: pre-commit.ci, claude, Codex
Full changelog: 0.31.6...0.31.7
Co-authored with /oss:release.