github roboflow/supervision 0.31.7
supervision-0.31.7

one hour ago

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 contourArea and convertScaleAbs fallbacks 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 small

Misread 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: MeanAveragePrecision sizes a prediction by its geometry and ignores its data["area"], and reads target data["area"] as float64, so small, medium and large mAP can move. (#2939) Oriented-box mAP on iscrowd targets can rise, never fall, because crowd overlaps are ignored. (#2941) as_coco writes box area, not 0, for empty mask placeholders. (#2942) Oversized labels with smart_position=True stop at the left and top frame edges. (#2940)
  • New errors: CSVSink raises ValueError for extra fields named like a built-in column. (#2934) KeyPoints selection raises IndexError for a nonempty boolean mask of the wrong length. (#2943)
  • Without OpenCV: the contourArea fallback can flip filter_polygons_by_area at a boundary (#2936), and the convertScaleAbs fallback rounds some uint8 results one level differently, up or down (#2935). Regenerate stored references made with the NumPy fallback.

📝 Notable changes

🔧 Fixed

Metrics

  • MeanAveragePrecision puts objects in the small, medium and large buckets pycocotools gives them: prediction area comes from its own geometry (box, mask or oriented-box polygon), and target data["area"] is read as float64. (#2939)
  • MeanAveragePrecision with MetricTarget.ORIENTED_BOUNDING_BOXES uses the COCO crowd IoU for targets with data["iscrowd"] set, so predictions inside a crowd region are ignored instead of counted as false positives. (#2941)

Key points, zones and sinks

  • PolygonZone preserves mask dimensions when integer polygon coordinates reach their dtype limit, instead of overflowing. (#2945)
  • KeyPoints rejects nonempty one-dimensional boolean selection masks whose length does not match the number of skeletons or keypoints; keypoint selection on KeyPoints.empty() stays empty. (#2943)
  • KeyPoints accepts a zero-dimensional NumPy integer row index combined with a list or boolean mask of keypoints, preserving coordinates and aligned fields instead of raising ValueError. (#2930)
  • CSVSink raises ValueError when additional data uses a built-in column name such as confidence or x_min, instead of writing duplicate columns. (#2934)

Annotators and the NumPy fallback without OpenCV

  • LabelAnnotator and RichLabelAnnotator with smart_position=True keep an oversized label at the frame's left and top edges; shaped right-to-left text in RichLabelAnnotator retains its leading characters at the visible right edge. (#2940)
  • The contourArea fallback returns exactly what cv2.contourArea returns, so filter_polygons_by_area agrees across backends at its boundaries. (#2936)
  • The convertScaleAbs fallback scales in float32 with a single-rounding multiply-add, like OpenCV's fused float32 path. (#2935)

Dataset exports

  • DetectionDataset.as_yolo and as_pascal_voc retry a failed mask-hole seam search with traced contours when simplification removes usable vertices; as_yolo and as_pascal_voc still raise if no seam is found, and as_coco also tries this for iscrowd=0 annotations. (#2933)
  • DetectionDataset.as_coco uses 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 develop branch. (#2944)
  • The SAHI notebook shows the corrected tile size output. (#2937)
  • The SAHI notebook calls InferenceSlicer with overlap_wh and no longer passes the removed overlap_ratio_wh, so its cells run again, and outputs from the old 2560-pixel image were cleared. (#2938)
  • The CSVSink how-to now says that extra fields named like a built-in column raise ValueError, and the snap_boxes and MeanAveragePrecisionResult docstrings 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 MeanAveragePrecision size buckets and oriented-box crowd IoU to pycocotools.
  • 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 CSVSink extra 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 PolygonZone masks intact at integer coordinate limits.
  • kevin (@kevin9327) — made the OpenCV-free contourArea and convertScaleAbs fallbacks 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 develop branch.
  • 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.

Don't miss a new supervision release

NewReleases is sending notifications on new releases.