# Supervision User Guide

Supervision is an open-source Python library by **Roboflow** that gives you the
reusable building blocks for computer vision applications. It takes the raw output
of *any* detection, segmentation, keypoint, or classification model and turns it
into a single, consistent object you can **annotate, filter, track, count, export,
and benchmark** — without rewriting glue code for every model you try.

This guide is task-oriented. Start with the Quick Start, install the package, learn
the one object every workflow revolves around (`sv.Detections`), then jump to the
task you need: drawing boxes and masks, filtering predictions, tracking objects in
video, counting in zones, processing datasets, or measuring model accuracy.

> [!NOTE]
> Supervision is **model-agnostic**. It does not run inference itself — you bring a
> model (Ultralytics YOLO, Roboflow Inference, Hugging Face Transformers, SAM,
> Detectron2, MMDetection, a VLM, and more) and Supervision handles everything that
> happens to the predictions afterward.

---

## 1. Quick Start

If you already have a detection model, here is the complete loop: run inference,
convert to Supervision's format, and draw the results.

```python
import cv2
import supervision as sv
from ultralytics import YOLO

# 1. Run any model
model = YOLO("yolov8n.pt")
image = cv2.imread("dog.jpeg")
results = model(image)[0]

# 2. Convert the output into a unified Detections object
detections = sv.Detections.from_ultralytics(results)

# 3. Draw the predictions
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()

annotated = box_annotator.annotate(scene=image.copy(), detections=detections)
annotated = label_annotator.annotate(scene=annotated, detections=detections)

sv.plot_image(annotated)
```

That is the core pattern for *every* model Supervision supports. Only line 2 changes:
swap `from_ultralytics` for `from_inference`, `from_transformers`, `from_sam`, and so
on. Everything downstream — annotators, filters, trackers, zones, metrics — works the
same regardless of which model produced the predictions.

> [!TIP]
> Don't have a test image? Download the one used throughout the Roboflow tutorials:
> ```bash
> wget https://media.roboflow.com/notebooks/examples/dog.jpeg
> ```

---

## 2. Installation

Supervision runs on **Python 3.9 or newer** (3.9–3.14 are officially supported) on
macOS, Windows, and Linux.

=== "pip (recommended)"

    ```bash
    pip install supervision
    ```

=== "uv"

    ```bash
    uv pip install supervision
    ```

    For a uv-managed project:

    ```bash
    uv add supervision
    ```

=== "poetry"

    ```bash
    poetry add supervision
    ```

=== "conda / mamba"

    ```bash
    conda install -c conda-forge supervision
    ```

### Optional extras

Some features pull in extra dependencies. Install them only when you need them:

| Extra | Command | Adds |
|-------|---------|------|
| Metrics | `pip install "supervision[metrics]"` | `pandas`, required only for tabular metric output |

> [!NOTE]
> The legacy `headless` and `desktop` install targets were **removed**. `pip install
> supervision[headless]` still works — it installs the base library and harmlessly
> warns that the extra no longer exists. The base package is already headless-friendly.

### Install from source (development)

```bash
git clone --depth 1 -b develop https://github.com/roboflow/supervision.git
cd supervision

python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip

pip install -e "."
```

### Verify the installation

```python
import supervision as sv
print(sv.__version__)
```

```text
0.29.0
```

> [!IMPORTANT]
> Supervision installs **CPU** dependencies only (NumPy, OpenCV, Pillow, SciPy,
> Matplotlib). It does **not** install PyTorch, Ultralytics, or any model framework —
> you choose and install those yourself based on the model you want to run.

---

## 3. The `Detections` Object (Core Concept)

Almost everything in Supervision flows through one class: `sv.Detections`. It is a
container that normalizes the output of any supported model into the same set of
NumPy arrays.

| Attribute | Shape | Meaning |
|-----------|-------|---------|
| `xyxy` | `(n, 4)` | Bounding boxes as `[x1, y1, x2, y2]` in pixels |
| `mask` | `(n, H, W)` or `None` | Boolean segmentation masks, when available |
| `confidence` | `(n,)` or `None` | Per-detection confidence scores |
| `class_id` | `(n,)` or `None` | Integer class IDs |
| `tracker_id` | `(n,)` or `None` | Persistent IDs assigned by a tracker |
| `data` | `dict` | Extra per-detection fields, e.g. `data["class_name"]` |
| `metadata` | `dict` | Collection-level info (video name, timestamp, …) |

You rarely build a `Detections` by hand. Instead you use a `from_*` connector for
your model.

### Connectors — load predictions from any model

| Method | Source framework |
|--------|------------------|
| `Detections.from_inference(result)` | Roboflow [Inference](https://github.com/roboflow/inference) |
| `Detections.from_ultralytics(result)` | Ultralytics YOLO (detect, segment, OBB) |
| `Detections.from_transformers(result, id2label=...)` | Hugging Face Transformers |
| `Detections.from_sam(result)` | Segment Anything Model |
| `Detections.from_sam3(result, resolution_wh=...)` | SAM 3 |
| `Detections.from_detectron2(result)` | Detectron2 |
| `Detections.from_mmdetection(result)` | MMDetection / MMYOLO |
| `Detections.from_yolo_nas(result)` | YOLO-NAS (super-gradients) |
| `Detections.from_yolov5(result)` | YOLOv5 |
| `Detections.from_deepsparse(result)` | Neural Magic DeepSparse |
| `Detections.from_paddledet(result)` | PaddleDetection |
| `Detections.from_tensorflow(result, resolution_wh=...)` | TensorFlow Hub |
| `Detections.from_ncnn(result)` | NCNN |
| `Detections.from_easyocr(result)` | EasyOCR |
| `Detections.from_azure_analyze_image(result)` | Azure AI Vision 4.0 |
| `Detections.from_vlm(vlm=..., result=..., ...)` | Vision-language models (see §11) |

> [!TIP]
> Every connector returns the same `Detections` type. This is the whole point of
> Supervision: write your annotation, tracking, and counting logic once, then change
> models freely by changing only the connector line.

### Useful properties and methods

| Member | Returns | Notes |
|--------|---------|-------|
| `len(detections)` | `int` | Number of detections |
| `detections.area` | `(n,)` | Mask area if masks present, else box area |
| `detections.box_area` | `(n,)` | Bounding-box area in pixels |
| `detections.box_aspect_ratio` | `(n,)` | Width / height of each box |
| `detections.get_anchors_coordinates(anchor)` | `(n, 2)` | Anchor points (see `Position`) |
| `detections.with_nms(threshold=0.5)` | `Detections` | Non-maximum suppression |
| `detections.with_nmm(threshold=0.5)` | `Detections` | Non-maximum *merge* |
| `Detections.merge([d1, d2, ...])` | `Detections` | Concatenate multiple objects |
| `Detections.empty()` | `Detections` | An empty object |
| `detections.is_empty()` | `bool` | True when there are no detections |
| `detections["class_name"]` | array | Access a `data` field directly |

---

## 4. Detect and Annotate

Supervision ships a rich catalog of **annotators** — composable objects that each
draw one kind of visual overlay. They all share the same call shape:
`annotator.annotate(scene=image, detections=detections)`, and each returns the
modified image so you can chain them.

### Boxes and labels

=== "Ultralytics"

    ```python
    import cv2
    import supervision as sv
    from ultralytics import YOLO

    model = YOLO("yolov8n.pt")
    image = cv2.imread("dog.jpeg")
    results = model(image)[0]
    detections = sv.Detections.from_ultralytics(results)

    box_annotator = sv.BoxAnnotator()
    label_annotator = sv.LabelAnnotator()

    annotated = box_annotator.annotate(scene=image, detections=detections)
    annotated = label_annotator.annotate(scene=annotated, detections=detections)
    ```

=== "Inference"

    ```python
    import cv2
    import supervision as sv
    from inference import get_model

    model = get_model(model_id="yolov8n-640")
    image = cv2.imread("dog.jpeg")
    results = model.infer(image)[0]
    detections = sv.Detections.from_inference(results)

    box_annotator = sv.BoxAnnotator()
    label_annotator = sv.LabelAnnotator()

    annotated = box_annotator.annotate(scene=image, detections=detections)
    annotated = label_annotator.annotate(scene=annotated, detections=detections)
    ```

### Custom labels

By default `LabelAnnotator` prints each detection's class name (falling back to class
ID, then index). Pass your own `labels` list to override:

```python
labels = [
    f"{class_name} {confidence:.2f}"
    for class_name, confidence
    in zip(detections["class_name"], detections.confidence)
]

annotated = box_annotator.annotate(scene=image, detections=detections)
annotated = label_annotator.annotate(
    scene=annotated, detections=detections, labels=labels)
```

### Segmentation masks

For a segmentation model, swap `BoxAnnotator` for `MaskAnnotator` — it is a drop-in
replacement.

```python
mask_annotator = sv.MaskAnnotator()
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER_OF_MASS)

annotated = mask_annotator.annotate(scene=image.copy(), detections=detections)
annotated = label_annotator.annotate(scene=annotated, detections=detections)
```

### The annotator catalog

| Annotator | Draws |
|-----------|-------|
| `BoxAnnotator` | Bounding-box rectangles |
| `RoundBoxAnnotator` | Boxes with rounded corners |
| `BoxCornerAnnotator` | Corner brackets only |
| `OrientedBoxAnnotator` | Rotated (OBB) boxes |
| `ColorAnnotator` | Semi-transparent filled boxes |
| `MaskAnnotator` | Segmentation masks |
| `PolygonAnnotator` | Mask outlines as polygons |
| `HaloAnnotator` | Soft glow around masks |
| `EllipseAnnotator` | Ellipse under each object (sports-style) |
| `CircleAnnotator` | Circle around each object |
| `DotAnnotator` | A dot at an anchor point |
| `TriangleAnnotator` | A triangle marker above each object |
| `LabelAnnotator` | Text labels with background |
| `RichLabelAnnotator` | Labels with custom (e.g. non-Latin) fonts |
| `IconAnnotator` | A custom PNG icon per detection |
| `BlurAnnotator` | Blurs the detected regions |
| `PixelateAnnotator` | Pixelates the detected regions |
| `HeatMapAnnotator` | Accumulated heatmap over a video |
| `TraceAnnotator` | Motion trails for tracked objects |
| `PercentageBarAnnotator` | A progress-style bar per detection |
| `CropAnnotator` | Magnified crops of each detection |
| `BackgroundOverlayAnnotator` | Dims everything outside the detections |
| `ComparisonAnnotator` | Side-by-side comparison of two detection sets |

### Common annotator options

Most annotators accept the same core constructor arguments:

| Argument | Default | Meaning |
|----------|---------|---------|
| `color` | `ColorPalette.DEFAULT` | A single `Color`, a `ColorPalette`, or a hex string |
| `thickness` | `2` | Line thickness in pixels |
| `color_lookup` | `ColorLookup.CLASS` | How colors map to objects: `INDEX`, `CLASS`, or `TRACK` |

`MaskAnnotator` and `ColorAnnotator` add `opacity` (default `0.5`). `LabelAnnotator`
adds `text_color`, `text_scale`, `text_padding`, `text_position`, `border_radius`,
and `smart_position` (spreads overlapping labels apart).

> [!TIP]
> Set `color_lookup=sv.ColorLookup.TRACK` once you are tracking objects, so every
> object keeps the *same* color across all frames of a video.

---

## 5. Filter Detections

`Detections` supports NumPy-style boolean indexing. This is the fastest, most
expressive way to refine predictions — no helper methods to memorize.

```python
import numpy as np
import supervision as sv

# Keep only class 0
detections = detections[detections.class_id == 0]

# Keep a set of classes
detections = detections[np.isin(detections.class_id, [0, 2, 3])]

# Confidence threshold
detections = detections[detections.confidence > 0.5]

# Minimum pixel area
detections = detections[detections.area > 1000]

# Relative area (drop objects larger than 80% of the frame)
h, w, _ = image.shape
detections = detections[(detections.area / (h * w)) < 0.8]

# Box dimensions
box_w = detections.xyxy[:, 2] - detections.xyxy[:, 0]
box_h = detections.xyxy[:, 3] - detections.xyxy[:, 1]
detections = detections[(box_w > 200) & (box_h > 200)]
```

Combine any conditions with `&` (and) / `|` (or):

```python
detections = detections[(detections.confidence > 0.7) & (detections.class_id == 2)]
```

### Remove duplicates with NMS

```python
detections = detections.with_nms(threshold=0.5)
```

> [!NOTE]
> `detections.area` returns **mask** area when masks are present and **box** area
> otherwise. Use `detections.box_area` when you always want bounding-box area, and
> `detections.box_aspect_ratio` to filter by shape.

---

## 6. Track Objects in Video

Tracking assigns a **persistent ID** to each object so you can follow it across
frames. Supervision processes video through `sv.process_video`, which calls your
per-frame `callback`.

```python
import numpy as np
import supervision as sv
from ultralytics import YOLO

model = YOLO("yolov8n.pt")
tracker = sv.ByteTrack()
box_annotator = sv.BoxAnnotator()
label_annotator = sv.LabelAnnotator()
trace_annotator = sv.TraceAnnotator()

def callback(frame: np.ndarray, index: int) -> np.ndarray:
    results = model(frame)[0]
    detections = sv.Detections.from_ultralytics(results)
    detections = tracker.update_with_detections(detections)

    labels = [
        f"#{tracker_id} {class_name}"
        for class_name, tracker_id
        in zip(detections["class_name"], detections.tracker_id)
    ]

    annotated = box_annotator.annotate(frame.copy(), detections=detections)
    annotated = label_annotator.annotate(annotated, detections=detections, labels=labels)
    return trace_annotator.annotate(annotated, detections=detections)

sv.process_video(
    source_path="people-walking.mp4",
    target_path="result.mp4",
    callback=callback,
)
```

`TraceAnnotator` overlays each tracked object's recent path, making movement patterns
easy to see.

> [!WARNING]
> `sv.ByteTrack` is **deprecated** since `supervision-0.28.0` and will be removed in
> `supervision-0.30.0`. The recommended replacement is `ByteTrackTracker` from the
> separate [`trackers`](https://pypi.org/project/trackers/) package
> (`pip install trackers`). In the new package the update method is named `update()`
> instead of `update_with_detections()`.

### ByteTrack tuning

| Parameter | Default | Effect |
|-----------|---------|--------|
| `track_activation_threshold` | `0.25` | Min confidence to start a track |
| `lost_track_buffer` | `30` | Frames to keep a lost track alive (occlusion handling) |
| `minimum_matching_threshold` | `0.8` | Track-to-detection matching strictness |
| `frame_rate` | `30` | Video FPS (used to scale the lost-track buffer) |
| `minimum_consecutive_frames` | `1` | Frames an object must persist to become a valid track |

### Smoothing shaky boxes

Wrap the tracker output in `DetectionsSmoother` to average box coordinates over the
last few frames:

```python
smoother = sv.DetectionsSmoother()      # default length = 5 frames

detections = tracker.update_with_detections(detections)
detections = smoother.update_with_detections(detections)
```

> [!CAUTION]
> `DetectionsSmoother` requires `tracker_id` (run a tracker first) and is **not**
> compatible with segmentation masks.

---

## 7. Count Objects in Zones

### Polygon zones

A `PolygonZone` tests which detections fall inside an arbitrary region. Its
`trigger()` method returns a boolean mask you can use to filter or count.

```python
import numpy as np
import supervision as sv

polygon = np.array([[100, 200], [200, 100], [300, 200], [200, 300]])
zone = sv.PolygonZone(polygon=polygon)

mask = zone.trigger(detections=detections)   # boolean array, one per detection
detections_in_zone = detections[mask]
count = zone.current_count                   # number currently inside
```

Draw the zone and its live count with `PolygonZoneAnnotator`:

```python
zone_annotator = sv.PolygonZoneAnnotator(
    zone=zone,
    color=sv.Color.RED,
    thickness=4,
)
frame = zone_annotator.annotate(scene=frame)
```

> [!TIP]
> Generate polygon coordinates visually with the free
> [PolygonZone web tool](https://roboflow.github.io/polygonzone/) — upload a frame,
> click the region, and copy the NumPy array it produces.

> [!NOTE]
> By default a detection counts as "inside" when its **bottom-center** point is in the
> polygon. Change which anchor(s) are tested with
> `PolygonZone(polygon=..., triggering_anchors=(sv.Position.CENTER,))`.

### Line zones (crossing counts)

A `LineZone` counts objects that cross a line, separately tallying each direction.
Line crossing needs `tracker_id`, so run a tracker first.

```python
start = sv.Point(x=0, y=500)
end = sv.Point(x=1280, y=500)
line_zone = sv.LineZone(start=start, end=end)

# inside the per-frame callback, after tracking:
crossed_in, crossed_out = line_zone.trigger(detections)

print(line_zone.in_count, line_zone.out_count)
```

`LineZone` also exposes per-class tallies via `in_count_per_class` and
`out_count_per_class`. Render the line and counts with `sv.LineZoneAnnotator`
(or `LineZoneAnnotatorMulticlass` for per-class display).

---

## 8. Detect Small Objects (Inference Slicing)

Small objects in high-resolution images are easy for a model to miss. `InferenceSlicer`
applies the **SAHI** technique: it cuts the image into overlapping tiles, runs your
model on each tile, and merges the results.

```python
import cv2
import numpy as np
import supervision as sv
from ultralytics import YOLO

model = YOLO("yolov8x.pt")
image = cv2.imread("high_res.jpg")

def callback(image_slice: np.ndarray) -> sv.Detections:
    result = model(image_slice)[0]
    return sv.Detections.from_ultralytics(result)

slicer = sv.InferenceSlicer(callback=callback)
detections = slicer(image)
```

| Parameter | Default | Meaning |
|-----------|---------|---------|
| `slice_wh` | `640` | Tile size `(w, h)`; an int sets both |
| `overlap_wh` | `100` | Overlap between tiles **in pixels** |
| `overlap_filter` | `NON_MAX_SUPPRESSION` | How to merge overlaps: `NON_MAX_SUPPRESSION`, `NON_MAX_MERGE`, or `NONE` |
| `iou_threshold` | `0.5` | IoU used when filtering merged overlaps |
| `thread_workers` | `1` | Parallel tiles (see caution below) |
| `compact_masks` | `False` | Keep masks run-length encoded to avoid OOM on huge images |

The same slicer works for segmentation — just return masks from your callback and use
`MaskAnnotator` to draw them.

> [!WARNING]
> `overlap_wh` is measured in **pixels**, not as a percentage. Increase it when
> objects are large relative to the tile or often land on tile borders; decrease it
> for speed.

> [!CAUTION]
> When using oriented bounding boxes (OBB), Supervision automatically falls back to
> sequential slice processing even if `thread_workers > 1`, because many OBB backends
> are not thread-safe. You will see a one-time warning when this happens.

---

## 9. Process Datasets

`sv.DetectionDataset` loads, converts, splits, merges, and saves object-detection
datasets in **YOLO**, **COCO**, and **Pascal VOC** formats.

### Load

=== "COCO"

    ```python
    ds = sv.DetectionDataset.from_coco(
        images_directory_path="dataset/train",
        annotations_path="dataset/train/_annotations.coco.json",
    )
    ```

=== "YOLO"

    ```python
    ds = sv.DetectionDataset.from_yolo(
        images_directory_path="dataset/train/images",
        annotations_directory_path="dataset/train/labels",
        data_yaml_path="dataset/data.yaml",
    )
    ```

=== "Pascal VOC"

    ```python
    ds = sv.DetectionDataset.from_pascal_voc(
        images_directory_path="dataset/train/images",
        annotations_directory_path="dataset/train/labels",
    )
    ```

### Iterate (images load lazily)

```python
for image_path, image, annotations in ds:
    ...  # annotations is a Detections object

print(ds.classes)   # ['person', 'bicycle', 'car', ...]
print(len(ds))
```

### Split, merge, and convert

```python
# Split — returns exactly TWO datasets
train_ds, test_ds = ds.split(split_ratio=0.8, shuffle=True)

# Merge several into one
combined = sv.DetectionDataset.merge([ds_a, ds_b, ds_c])

# Convert format by loading one and saving as another
sv.DetectionDataset.from_yolo(...).as_pascal_voc(
    images_directory_path="out/images",
    annotations_directory_path="out/labels",
)
```

Export with `ds.as_coco(...)`, `ds.as_yolo(...)`, or `ds.as_pascal_voc(...)`.

> [!NOTE]
> `split()` returns **two** datasets (train and test). To get a validation set as
> well, split one of the results again, e.g.
> `valid_ds, test_ds = test_ds.split(split_ratio=0.5)`.

> [!TIP]
> Supervision has no built-in augmentation pipeline. Pair it with
> [Albumentations](https://albumentations.ai/): feed `annotations.xyxy` as
> `pascal_voc`-format boxes, then rebuild the annotation with
> `dataclasses.replace(annotations, xyxy=...)`.

---

## 10. Benchmark a Model

Supervision computes the standard detection metrics so you can compare models
numerically, not just visually.

### Mean Average Precision (mAP)

```python
from supervision.metrics import MeanAveragePrecision, MetricTarget

map_metric = MeanAveragePrecision(metric_target=MetricTarget.BOXES)
map_result = map_metric.update(predictions_list, targets_list).compute()

print(map_result)          # mAP @ 50:95, @ 50, @ 75, per class, and by object size
map_result.plot()
```

`MetricTarget` selects what to evaluate: `BOXES`, `MASKS`, or `ORIENTED_BOUNDING_BOXES`.
mAP is reported at IoU 0.50:0.95 (the headline number), plus mAP@50 and mAP@75, and is
broken down by small / medium / large objects.

### F1, Precision, Recall, mAR

The same `update(...).compute()` pattern applies to `F1Score`, `Precision`, `Recall`,
and `MeanAverageRecall`, all importable from `supervision.metrics`.

```python
from supervision.metrics import F1Score, MetricTarget

f1_result = F1Score(metric_target=MetricTarget.BOXES) \
    .update(predictions_list, targets_list).compute()
print(f1_result)
```

### Confusion matrix

```python
import supervision as sv

cm = sv.ConfusionMatrix.from_detections(
    predictions=predictions_list,
    targets=targets_list,
    classes=dataset.classes,
)
cm.plot()
```

> [!TIP]
> Benchmark on a **test** set the model never saw during training. Using the training
> or validation set produces unrealistically optimistic numbers.

---

## 11. Keypoints, Classification, and VLMs

### Keypoint detection

`sv.KeyPoints` is the keypoint analog of `Detections`, with its own connectors:
`from_ultralytics`, `from_inference`, `from_mediapipe`, `from_yolo_nas`,
`from_detectron2`, `from_transformers`, and `from_rfdetr`.

```python
import supervision as sv
from ultralytics import YOLO

model = YOLO("yolov8m-pose.pt")
results = model(image)[0]
key_points = sv.KeyPoints.from_ultralytics(results)

edge_annotator = sv.EdgeAnnotator()
vertex_annotator = sv.VertexAnnotator()

annotated = edge_annotator.annotate(image.copy(), key_points=key_points)
annotated = vertex_annotator.annotate(annotated, key_points=key_points)
```

To track a pose model, convert keypoints to boxes with `key_points.as_detections()`,
then feed those into `ByteTrack` as usual.

### Classification

`sv.Classifications` wraps whole-image classifier output, with connectors
`from_clip`, `from_ultralytics`, and `from_timm`, plus `get_top_k(k)` to retrieve the
highest-scoring classes.

### Vision-language models (VLMs)

Parse bounding boxes out of VLM text/JSON output with `Detections.from_vlm`:

```python
import supervision as sv

paligemma_result = "<loc0256><loc0256><loc0768><loc0768> cat"
detections = sv.Detections.from_vlm(
    sv.VLM.PALIGEMMA,
    paligemma_result,
    resolution_wh=(1000, 1000),
    classes=["cat", "dog"],
)
```

Supported `sv.VLM` values: `PALIGEMMA`, `FLORENCE_2`, `QWEN_2_5_VL`, `QWEN_3_VL`,
`GOOGLE_GEMINI_2_0`, `GOOGLE_GEMINI_2_5`, `MOONDREAM`, `DEEPSEEK_VL_2`.

> [!NOTE]
> The older `sv.LMM` enum and `Detections.from_lmm` are deprecated (removal planned for
> `supervision-0.31.0`). Use `sv.VLM` and `Detections.from_vlm` instead.

---

## 12. Save and Export Detections

Stream detections to CSV or JSON for offline analysis. Both sinks are context managers
— open once, append per frame, and the file is finalized on exit.

=== "CSV"

    ```python
    with sv.CSVSink("output.csv") as sink:
        for frame_index, frame in enumerate(sv.get_video_frames_generator("video.mp4")):
            results = model(frame)[0]
            detections = sv.Detections.from_ultralytics(results)
            sink.append(detections, {"frame_index": frame_index})
    ```

=== "JSON"

    ```python
    with sv.JSONSink("output.json") as sink:
        for frame_index, frame in enumerate(sv.get_video_frames_generator("video.mp4")):
            results = model(frame)[0]
            detections = sv.Detections.from_ultralytics(results)
            sink.append(detections, {"frame_index": frame_index})
    ```

The output includes box coordinates, `class_id`, `confidence`, `tracker_id`,
`class_name`, plus any keys you pass in the second `custom_data` dictionary. Filter
before appending to export only what you need:

```python
sink.append(detections[detections.confidence > 0.7], {})
```

---

## 13. Working with Images and Video

Supervision bundles the file and frame utilities most CV pipelines need.

| Helper | Purpose |
|--------|---------|
| `sv.VideoInfo.from_video_path(path)` | Read width, height, fps, frame count |
| `sv.get_video_frames_generator(path)` | Iterate frames lazily |
| `sv.process_video(source_path, target_path, callback)` | Run a callback over a whole video and write the result |
| `sv.VideoSink(path, video_info)` | Context manager to write frames |
| `sv.FPSMonitor()` | Measure real-time throughput (`.fps`) |
| `sv.crop_image(image, xyxy)` | Crop a region |
| `sv.resize_image` / `sv.letterbox_image` / `sv.scale_image` | Resize helpers |
| `sv.plot_image(image)` | Show an image in a notebook |
| `sv.plot_images_grid(images, grid_size=(r, c))` | Show a grid of images |

Sample assets for tutorials are available without extra installs:

```python
from supervision.assets import download_assets, VideoAssets

download_assets(VideoAssets.PEOPLE_WALKING)
```

---

## 14. Colors

`sv.Color` and `sv.ColorPalette` control every annotator's appearance.

```python
red = sv.Color.RED                       # constants: WHITE, BLACK, GREY, RED, GREEN,
purple = sv.Color.from_hex("#A351FB")    #            BLUE, YELLOW, ROBOFLOW
purple.as_bgr()                          # (251, 81, 163)  — OpenCV order

palette = sv.ColorPalette.DEFAULT        # also ROBOFLOW, LEGACY
palette = sv.ColorPalette.from_hex(["#ff0000", "#00ff00", "#0000ff"])
palette = sv.ColorPalette.from_matplotlib("viridis", 10)
palette.by_idx(3)                        # the 4th color
```

> [!CAUTION]
> Older code used method-style colors such as `sv.Color.white()` and
> `sv.ColorPalette.default()`. These were **removed** in `supervision-0.22.0`. Use the
> constants instead: `sv.Color.WHITE`, `sv.ColorPalette.DEFAULT`.

---

## 15. Troubleshooting

### Error: `Detections confidence must be provided for tracking.`

**Cause:** You passed a `Detections` object with `confidence=None` into
`ByteTrack.update_with_detections()`. Trackers need confidence scores to associate
objects.

**Solution:** Use a model output that includes confidence (all detection connectors
do) and do not strip the `confidence` array before tracking.

**Prevention:** Track *before* any filtering step that might drop confidence.

### Error: `Triggering anchors cannot be empty.`

**Cause:** You constructed `PolygonZone(..., triggering_anchors=())` with an empty
iterable.

**Solution:** Provide at least one `sv.Position`, e.g.
`triggering_anchors=(sv.Position.BOTTOM_CENTER,)`.

### Warning: `Smoothing skipped. DetectionsSmoother requires tracker_id.`

**Cause:** `DetectionsSmoother.update_with_detections()` received detections without
`tracker_id`.

**Solution:** Run a tracker first; the smoother averages boxes per track ID.

**Prevention:** Order your pipeline as detect → track → smooth → annotate.

### Warning: detections have coordinates outside the slice bounds

**Cause:** Inside an `InferenceSlicer` callback, your model ran on the full image
instead of the `image_slice` argument it was given.

**Solution:** Make the callback run inference on its `image_slice` parameter only, and
return detections in slice-local coordinates — Supervision shifts them back to full-
image space for you.

### `TypeError`/`unexpected keyword argument 'frame_resolution_wh'`

**Cause:** Following an old tutorial that calls
`PolygonZone(polygon=..., frame_resolution_wh=...)`. That parameter was **removed** in
`supervision-0.24.0`.

**Solution:** Drop it — `sv.PolygonZone(polygon=polygon)` is all you need now.

### Masks look empty after `DetectionDataset.as_coco()`

**Cause:** Known limitation tracked in issue
[#2285](https://github.com/roboflow/supervision/issues/2285) — segmentation masks may
not be written to COCO output in some versions.

**Solution:** Verify your installed version against the changelog and keep masks in
memory (`detections.mask`) as a fallback until your version includes the fix.

### `AttributeError: ... has no attribute 'keypoint'` / deprecation warnings

**Cause:** Importing from the old `supervision.keypoint` module.

**Solution:** Use `supervision.key_points` (with an underscore). The old spelling is
deprecated and slated for removal in `supervision-0.30.0`.

> [!TIP]
> To silence Supervision's deprecation warnings in a known-good pipeline, set the
> environment variable `SUPERVISON_DEPRECATION_WARNING=0` before importing the library.
> (Note the original spelling of the variable.)

---

## 16. Frequently Asked Questions

**Q: Does Supervision run my model for me?**
No. Supervision is model-agnostic and never performs inference. You run any model you
like and pass its output to a `Detections.from_*` connector.

**Q: Which models are supported?**
Object detection, segmentation, and keypoint outputs from Ultralytics YOLO, Roboflow
Inference, Hugging Face Transformers, SAM and SAM 3, Detectron2, MMDetection, YOLO-NAS,
YOLOv5, DeepSparse, PaddleDetection, TensorFlow Hub, NCNN, EasyOCR, Azure AI Vision,
and VLMs (Florence-2, PaliGemma, Qwen-VL, Gemini, Moondream, DeepSeek-VL2).

**Q: How do I filter detections by class?**
Use boolean indexing: `detections[detections.class_id == 0]`. Combine conditions with
`&` and `|`.

**Q: How do I track objects across frames?**
Call `tracker.update_with_detections(detections)` each frame with `sv.ByteTrack` (note
it is being superseded by the external `trackers` package). The tracker fills in
`detections.tracker_id`.

**Q: How do I count objects crossing a line?**
Use `sv.LineZone` with a start and end `sv.Point`. `trigger()` returns
`(crossed_in, crossed_out)`. It requires `tracker_id`, so track first.

**Q: How do I count objects inside a region?**
Use `sv.PolygonZone`. `trigger()` returns a boolean mask, and `zone.current_count`
gives the live count.

**Q: How do I improve detection of tiny objects?**
Use `sv.InferenceSlicer` to run your model on overlapping tiles and merge the results.

**Q: What dataset formats can I load and save?**
YOLO, COCO JSON, and Pascal VOC via `DetectionDataset.from_*` and `.as_*` methods.

**Q: How do I compute mAP?**
`MeanAveragePrecision().update(predictions, targets).compute()` from
`supervision.metrics`. It reports mAP@50:95, @50, @75, per class, and by object size.

**Q: Can I use my own colors and fonts?**
Yes. Pass `sv.Color`/`sv.ColorPalette` to any annotator, and use `RichLabelAnnotator`
for custom (including non-Latin) fonts.

**Q: Is Supervision free?**
Yes. It is open source under the **MIT** license.

**Q: Why am I seeing a `ByteTrack` deprecation warning?**
The built-in `ByteTrack` is being phased out in favor of `ByteTrackTracker` from the
`trackers` package (`pip install trackers`); the update method there is `update()`.

**Q: Where can I get help?**
The [documentation](https://supervision.roboflow.com/), the
[Discord community](https://discord.gg/GbfgXGJ8Bk), and
[GitHub issues](https://github.com/roboflow/supervision/issues).

---

## 17. Quick Reference

### The universal pipeline

```python
detections = sv.Detections.from_<model>(result)   # 1. connect
detections = detections[detections.confidence > 0.5]   # 2. filter
detections = tracker.update_with_detections(detections)   # 3. track (video)
mask = zone.trigger(detections)                    # 4. zone count
annotated = annotator.annotate(scene, detections)  # 5. visualize
```

### Position anchors

`CENTER`, `CENTER_LEFT`, `CENTER_RIGHT`, `TOP_CENTER`, `TOP_LEFT`, `TOP_RIGHT`,
`BOTTOM_LEFT`, `BOTTOM_CENTER`, `BOTTOM_RIGHT`, `CENTER_OF_MASS`.

### Color lookup strategies

`ColorLookup.INDEX` (by order), `ColorLookup.CLASS` (by class — default),
`ColorLookup.TRACK` (by tracker ID — stable per object across frames).

### Key imports

```python
import supervision as sv
from supervision.metrics import MeanAveragePrecision, F1Score, MetricTarget
from supervision.assets import download_assets, VideoAssets
```

---

_Generated June 2026 from roboflow/supervision at b18a30e. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
