diff --git a/docs/detection/annotators.md b/docs/detection/annotators.md
index 90a9006c..651a6223 100644
--- a/docs/detection/annotators.md
+++ b/docs/detection/annotators.md
@@ -7,510 +7,527 @@ description: API reference for supervision's annotator classes — draw bounding
Annotators accept detections and apply box or mask visualizations to the detections. Annotators have many available styles.
-=== "Box"
+=== "Outlines"
- ```python
- import supervision as sv
+ === "Box"
- image = ...
- detections = sv.Detections(...)
+ ```python
+ import supervision as sv
- box_annotator = sv.BoxAnnotator()
- annotated_frame = box_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
+ image = ...
+ detections = sv.Detections(...)
-
-
- { align=center width="800" }
-
-
-
-=== "RoundBox"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- round_box_annotator = sv.RoundBoxAnnotator()
- annotated_frame = round_box_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "BoxCorner"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- corner_annotator = sv.BoxCornerAnnotator()
- annotated_frame = corner_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Color"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- color_annotator = sv.ColorAnnotator()
- annotated_frame = color_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Circle"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- circle_annotator = sv.CircleAnnotator()
- annotated_frame = circle_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Dot"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- dot_annotator = sv.DotAnnotator()
- annotated_frame = dot_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Triangle"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- triangle_annotator = sv.TriangleAnnotator()
- annotated_frame = triangle_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Ellipse"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- ellipse_annotator = sv.EllipseAnnotator()
- annotated_frame = ellipse_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Halo"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- halo_annotator = sv.HaloAnnotator()
- annotated_frame = halo_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "PercentageBar"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- percentage_bar_annotator = sv.PercentageBarAnnotator()
- annotated_frame = percentage_bar_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Mask"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- mask_annotator = sv.MaskAnnotator()
- annotated_frame = mask_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Polygon"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- polygon_annotator = sv.PolygonAnnotator()
- annotated_frame = polygon_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
-
-
-
- { align=center width="800" }
-
-
-
-=== "Label"
-
- ```python
- import supervision as sv
-
- image = ...
- detections = sv.Detections(...)
-
- labels = [
- f"{class_name} {confidence:.2f}"
- for class_name, confidence in zip(
- detections["class_name"],
- detections.confidence,
+ box_annotator = sv.BoxAnnotator()
+ annotated_frame = box_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
)
- ]
+ ```
- label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
- annotated_frame = label_annotator.annotate(
- scene=image.copy(), detections=detections, labels=labels
- )
- ```
+ " for _ in detections]
+
- icon_annotator = sv.IconAnnotator()
- annotated_frame = icon_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- icon_path=icon_paths,
- )
- ```
+ { align=center width="800" }
-
+
- { align=center width="800" }
+ === "Circle"
-
+ ```python
+ import supervision as sv
-
+ image = ...
+ detections = sv.Detections(...)
-=== "Blur"
+ ellipse_annotator = sv.EllipseAnnotator()
+ annotated_frame = ellipse_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
- ```python
- import supervision as sv
+
- image = ...
- detections = sv.Detections(...)
+ { align=center width="800" }
- blur_annotator = sv.BlurAnnotator()
- annotated_frame = (blur_annotator.annotate(scene=image.copy(), detections=detections),)
- ```
+
-
+ === "Polygon"
- { align=center width="800" }
+ ```python
+ import supervision as sv
-
+ image = ...
+ detections = sv.Detections(...)
-=== "Pixelate"
+ polygon_annotator = sv.PolygonAnnotator()
+ annotated_frame = polygon_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
- ```python
- import supervision as sv
+
- image = ...
- detections = sv.Detections(...)
+ { align=center width="800" }
- pixelate_annotator = sv.PixelateAnnotator()
- annotated_frame = pixelate_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
+
-
+=== "Shading"
- { align=center width="800" }
+ === "Color"
-
+ ```python
+ import supervision as sv
-=== "Trace"
+ image = ...
+ detections = sv.Detections(...)
- ```python
- import supervision as sv
- from ultralytics import YOLO
+ color_annotator = sv.ColorAnnotator()
+ annotated_frame = color_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
- model = YOLO("yolov8x.pt")
+
- trace_annotator = sv.TraceAnnotator()
+ { align=center width="800" }
- video_info = sv.VideoInfo.from_video_path(video_path="...")
- frames_generator = sv.get_video_frames_generator(source_path="...")
- tracker = sv.ByteTrack()
+
- with sv.VideoSink(target_path="...", video_info=video_info) as sink:
- for frame in frames_generator:
- result = model(frame)[0]
- detections = sv.Detections.from_ultralytics(result)
- detections = tracker.update_with_detections(detections)
- annotated_frame = trace_annotator.annotate(
- scene=frame.copy(),
- detections=detections,
+ === "Halo"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ halo_annotator = sv.HaloAnnotator()
+ annotated_frame = halo_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+ === "Mask"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ mask_annotator = sv.MaskAnnotator()
+ annotated_frame = mask_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+=== "Markers"
+
+ === "Dot"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ dot_annotator = sv.DotAnnotator()
+ annotated_frame = dot_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+ === "Triangle"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ triangle_annotator = sv.TriangleAnnotator()
+ annotated_frame = triangle_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+=== "Labels"
+
+ === "Label"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ labels = [
+ f"{class_name} {confidence:.2f}"
+ for class_name, confidence in zip(
+ detections["class_name"],
+ detections.confidence,
)
- sink.write_frame(frame=annotated_frame)
- ```
+ ]
-
+ label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
+ annotated_frame = label_annotator.annotate(
+ scene=image.copy(), detections=detections, labels=labels
+ )
+ ```
- { align=center width="800" }
+
-
+ { align=center width="800" }
-=== "HeatMap"
+
- ```python
- import supervision as sv
- from ultralytics import YOLO
+ === "RichLabel"
- model = YOLO("yolov8x.pt")
+ ```python
+ import supervision as sv
- heat_map_annotator = sv.HeatMapAnnotator()
+ image = ...
+ detections = sv.Detections(...)
- video_info = sv.VideoInfo.from_video_path(video_path="...")
- frames_generator = sv.get_video_frames_generator(source_path="...")
-
- with sv.VideoSink(target_path="...", video_info=video_info) as sink:
- for frame in frames_generator:
- result = model(frame)[0]
- detections = sv.Detections.from_ultralytics(result)
- annotated_frame = heat_map_annotator.annotate(
- scene=frame.copy(),
- detections=detections,
+ labels = [
+ f"{class_name} {confidence:.2f}"
+ for class_name, confidence in zip(
+ detections["class_name"],
+ detections.confidence,
)
- sink.write_frame(frame=annotated_frame)
- ```
+ ]
-
+ rich_label_annotator = sv.RichLabelAnnotator(
+ font_path="TTF_FONT_PATH",
+ text_position=sv.Position.CENTER,
+ )
+ annotated_frame = rich_label_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ labels=labels,
+ )
+ ```
- { align=center width="800" }
+
-
+ { align=center width="800" }
-=== "Background Color"
+
- ```python
- import supervision as sv
+=== "Transformative"
- image = ...
- detections = sv.Detections(...)
+ === "Blur"
- background_overlay_annotator = sv.BackgroundOverlayAnnotator()
- annotated_frame = background_overlay_annotator.annotate(
- scene=image.copy(),
- detections=detections,
- )
- ```
+ ```python
+ import supervision as sv
-
+ image = ...
+ detections = sv.Detections(...)
- { align=center width="800" }
+ blur_annotator = sv.BlurAnnotator()
+ annotated_frame = blur_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
-
+
-=== "Comparison"
+ { align=center width="800" }
- ```python
- import supervision as sv
+
- image = ...
- detections_1 = sv.Detections(...)
- detections_2 = sv.Detections(...)
+ === "Pixelate"
- comparison_annotator = sv.ComparisonAnnotator()
- annotated_frame = comparison_annotator.annotate(
- scene=image.copy(),
- detections_1=detections_1,
- detections_2=detections_2,
- )
- ```
+ ```python
+ import supervision as sv
-
+ image = ...
+ detections = sv.Detections(...)
- { align=center width="800" }
+ pixelate_annotator = sv.PixelateAnnotator()
+ annotated_frame = pixelate_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
-
+
+
+ { align=center width="800" }
+
+
+
+
+
+=== "Tracking & Aggregation"
+
+ === "Trace"
+
+ ```python
+ import supervision as sv
+ from ultralytics import YOLO
+
+ model = YOLO("yolov8x.pt")
+
+ trace_annotator = sv.TraceAnnotator()
+
+ video_info = sv.VideoInfo.from_video_path(video_path="...")
+ frames_generator = sv.get_video_frames_generator(source_path="...")
+ tracker = sv.ByteTrack()
+
+ with sv.VideoSink(target_path="...", video_info=video_info) as sink:
+ for frame in frames_generator:
+ result = model(frame)[0]
+ detections = sv.Detections.from_ultralytics(result)
+ detections = tracker.update_with_detections(detections)
+ annotated_frame = trace_annotator.annotate(
+ scene=frame.copy(),
+ detections=detections,
+ )
+ sink.write_frame(frame=annotated_frame)
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+ === "HeatMap"
+
+ ```python
+ import supervision as sv
+ from ultralytics import YOLO
+
+ model = YOLO("yolov8x.pt")
+
+ heat_map_annotator = sv.HeatMapAnnotator()
+
+ video_info = sv.VideoInfo.from_video_path(video_path="...")
+ frames_generator = sv.get_video_frames_generator(source_path="...")
+
+ with sv.VideoSink(target_path="...", video_info=video_info) as sink:
+ for frame in frames_generator:
+ result = model(frame)[0]
+ detections = sv.Detections.from_ultralytics(result)
+ annotated_frame = heat_map_annotator.annotate(
+ scene=frame.copy(),
+ detections=detections,
+ )
+ sink.write_frame(frame=annotated_frame)
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+=== "Others"
+
+ === "PercentageBar"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ percentage_bar_annotator = sv.PercentageBarAnnotator()
+ annotated_frame = percentage_bar_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+ === "Icon"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ icon_paths = ["" for _ in detections]
+
+ icon_annotator = sv.IconAnnotator()
+ annotated_frame = icon_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ icon_path=icon_paths,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+ === "Background Color"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections = sv.Detections(...)
+
+ background_overlay_annotator = sv.BackgroundOverlayAnnotator()
+ annotated_frame = background_overlay_annotator.annotate(
+ scene=image.copy(),
+ detections=detections,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
+
+ === "Comparison"
+
+ ```python
+ import supervision as sv
+
+ image = ...
+ detections_1 = sv.Detections(...)
+ detections_2 = sv.Detections(...)
+
+ comparison_annotator = sv.ComparisonAnnotator()
+ annotated_frame = comparison_annotator.annotate(
+ scene=image.copy(),
+ detections_1=detections_1,
+ detections_2=detections_2,
+ )
+ ```
+
+
+
+ { align=center width="800" }
+
+
Try Supervision Annotators on your own image
diff --git a/tests/annotators/test_docs.py b/tests/annotators/test_docs.py
new file mode 100644
index 00000000..f9def6ca
--- /dev/null
+++ b/tests/annotators/test_docs.py
@@ -0,0 +1,148 @@
+"""Regression tests for docs/detection/annotators.md tab structure."""
+
+import ast
+import re
+from pathlib import Path
+
+import pytest
+
+_REPO_ROOT = Path(__file__).resolve().parent
+while not (_REPO_ROOT / "pyproject.toml").exists():
+ _REPO_ROOT = _REPO_ROOT.parent
+
+REPO_ROOT = _REPO_ROOT
+
+EXPECTED_ANNOTATOR_TAB_GROUPS: dict[str, list[str]] = {
+ "Outlines": [
+ "Box",
+ "RoundBox",
+ "BoxCorner",
+ "Circle",
+ "Ellipse",
+ "Polygon",
+ ],
+ "Shading": ["Color", "Halo", "Mask"],
+ "Markers": ["Dot", "Triangle"],
+ "Labels": ["Label", "RichLabel"],
+ "Transformative": ["Blur", "Pixelate"],
+ "Tracking & Aggregation": ["Trace", "HeatMap"],
+ "Others": [
+ "PercentageBar",
+ "Icon",
+ "Background Color",
+ "Comparison",
+ ],
+}
+
+
+def _extract_annotator_tab_groups() -> dict[str, list[str]]:
+ """Parse annotator tab structure from docs/detection/annotators.md.
+
+ Returns:
+ Mapping of category name to list of annotator tab labels within that
+ category.
+ """
+ docs_path = REPO_ROOT / "docs" / "detection" / "annotators.md"
+ content = docs_path.read_text(encoding="utf-8")
+
+ start_marker = '=== "Outlines"'
+ end_marker = "Try Supervision Annotators on your own image"
+
+ start = content.find(start_marker)
+ if start == -1:
+ pytest.fail(
+ f"Could not find start marker {start_marker!r} in {docs_path} while "
+ "parsing annotator example tab groups."
+ )
+
+ end = content.find(end_marker, start)
+ if end == -1:
+ pytest.fail(
+ f"Could not find end marker {end_marker!r} in {docs_path} while "
+ "parsing annotator example tab groups."
+ )
+ if end <= start:
+ pytest.fail(
+ f"End marker {end_marker!r} must appear after start marker "
+ f"{start_marker!r} in {docs_path}."
+ )
+
+ example_section = content[start:end]
+
+ groups: dict[str, list[str]] = {}
+ current_group = None
+
+ for line in example_section.splitlines():
+ if match := re.match(r'^(?P\s*)=== "(?P