Restructure annotator docs tabs to avoid Material’s 20-tab limit (#2257)

* Initial plan
* fix: split annotator docs tabs into categories
* test: harden annotator docs regression test
* docs: fix blur example assignment
* docs(annotators): restructure tabs, harden regression tests
* docs(annotators): remove broken oriented box preview image

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
Copilot 2026-05-22 19:54:33 +02:00 committed by GitHub
parent 2c13b269e6
commit 6461d3fbac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 589 additions and 424 deletions

View File

@ -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(...)
<div class="result" markdown>
![bounding-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/bounding-box-annotator-example-purple.png){ align=center width="800" }
</div>
=== "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,
)
```
<div class="result" markdown>
![round-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/round-box-annotator-example-purple.png){ align=center width="800" }
</div>
=== "BoxCorner"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
corner_annotator = sv.BoxCornerAnnotator()
annotated_frame = corner_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![box-corner-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-corner-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Color"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
color_annotator = sv.ColorAnnotator()
annotated_frame = color_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![box-mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Circle"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
circle_annotator = sv.CircleAnnotator()
annotated_frame = circle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![circle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/circle-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Dot"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
dot_annotator = sv.DotAnnotator()
annotated_frame = dot_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![dot-annotator-example](https://media.roboflow.com/supervision-annotator-examples/dot-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Triangle"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
triangle_annotator = sv.TriangleAnnotator()
annotated_frame = triangle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![triangle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/triangle-annotator-example.png){ align=center width="800" }
</div>
=== "Ellipse"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
ellipse_annotator = sv.EllipseAnnotator()
annotated_frame = ellipse_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![ellipse-annotator-example](https://media.roboflow.com/supervision-annotator-examples/ellipse-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Halo"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
halo_annotator = sv.HaloAnnotator()
annotated_frame = halo_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![halo-annotator-example](https://media.roboflow.com/supervision-annotator-examples/halo-annotator-example-purple.png){ align=center width="800" }
</div>
=== "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,
)
```
<div class="result" markdown>
![percentage-bar-annotator-example](https://media.roboflow.com/supervision-annotator-examples/percentage-bar-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Mask"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
mask_annotator = sv.MaskAnnotator()
annotated_frame = mask_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Polygon"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
polygon_annotator = sv.PolygonAnnotator()
annotated_frame = polygon_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![polygon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/polygon-annotator-example-purple.png){ align=center width="800" }
</div>
=== "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
)
```
<div class="result" markdown>
<div class="result" markdown>
![bounding-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/bounding-box-annotator-example-purple.png){ align=center width="800" }
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
</div>
</div>
=== "RoundBox"
=== "RichLabel"
```python
import supervision as sv
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
image = ...
detections = sv.Detections(...)
labels = [
f"{class_name} {confidence:.2f}"
for class_name, confidence in zip(
detections["class_name"],
detections.confidence,
round_box_annotator = sv.RoundBoxAnnotator()
annotated_frame = round_box_annotator.annotate(
scene=image.copy(),
detections=detections,
)
]
```
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,
)
```
<div class="result" markdown>
<div class="result" markdown>
![round-box-annotator-example](https://media.roboflow.com/supervision-annotator-examples/round-box-annotator-example-purple.png){ align=center width="800" }
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
</div>
</div>
=== "BoxCorner"
=== "Icon"
```python
import supervision as sv
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
image = ...
detections = sv.Detections(...)
corner_annotator = sv.BoxCornerAnnotator()
annotated_frame = corner_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
icon_paths = ["<ICON_PATH>" for _ in detections]
<div class="result" markdown>
icon_annotator = sv.IconAnnotator()
annotated_frame = icon_annotator.annotate(
scene=image.copy(),
detections=detections,
icon_path=icon_paths,
)
```
![box-corner-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-corner-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![icon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/icon-annotator-example.png){ align=center width="800" }
=== "Circle"
</div>
```python
import supervision as sv
<!-- === "Crop"
image = ...
detections = sv.Detections(...)
```python
import supervision as sv
circle_annotator = sv.CircleAnnotator()
annotated_frame = circle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
image = ...
detections = sv.Detections(...)
<div class="result" markdown>
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
![circle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/circle-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![crop-annotator-example](https://media.roboflow.com/supervision-annotator-examples/crop-annotator-example.png){ align=center width="800" }
=== "Ellipse"
</div>
```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
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![ellipse-annotator-example](https://media.roboflow.com/supervision-annotator-examples/ellipse-annotator-example-purple.png){ align=center width="800" }
blur_annotator = sv.BlurAnnotator()
annotated_frame = (blur_annotator.annotate(scene=image.copy(), detections=detections),)
```
</div>
<div class="result" markdown>
=== "Polygon"
![blur-annotator-example](https://media.roboflow.com/supervision-annotator-examples/blur-annotator-example-purple.png){ align=center width="800" }
```python
import supervision as sv
</div>
image = ...
detections = sv.Detections(...)
=== "Pixelate"
polygon_annotator = sv.PolygonAnnotator()
annotated_frame = polygon_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
```python
import supervision as sv
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![polygon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/polygon-annotator-example-purple.png){ align=center width="800" }
pixelate_annotator = sv.PixelateAnnotator()
annotated_frame = pixelate_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
</div>
<div class="result" markdown>
=== "Shading"
![pixelate-annotator-example](https://media.roboflow.com/supervision-annotator-examples/pixelate-annotator-example-10.png){ align=center width="800" }
=== "Color"
</div>
```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")
<div class="result" markdown>
trace_annotator = sv.TraceAnnotator()
![box-mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/box-mask-annotator-example-purple.png){ 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()
</div>
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,
)
```
<div class="result" markdown>
![halo-annotator-example](https://media.roboflow.com/supervision-annotator-examples/halo-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Mask"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
mask_annotator = sv.MaskAnnotator()
annotated_frame = mask_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![mask-annotator-example](https://media.roboflow.com/supervision-annotator-examples/mask-annotator-example-purple.png){ align=center width="800" }
</div>
=== "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,
)
```
<div class="result" markdown>
![dot-annotator-example](https://media.roboflow.com/supervision-annotator-examples/dot-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Triangle"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
triangle_annotator = sv.TriangleAnnotator()
annotated_frame = triangle_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![triangle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/triangle-annotator-example.png){ align=center width="800" }
</div>
=== "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)
```
]
<div class="result" markdown>
label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER)
annotated_frame = label_annotator.annotate(
scene=image.copy(), detections=detections, labels=labels
)
```
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/trace-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
=== "HeatMap"
</div>
```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)
```
]
<div class="result" markdown>
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,
)
```
![heat-map-annotator-example](https://media.roboflow.com/supervision-annotator-examples/heat-map-annotator-example-purple.png){ align=center width="800" }
<div class="result" markdown>
</div>
![label-annotator-example](https://media.roboflow.com/supervision-annotator-examples/label-annotator-example-purple.png){ align=center width="800" }
=== "Background Color"
</div>
```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
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![background-overlay-annotator-example](https://media.roboflow.com/supervision-annotator-examples/background-color-annotator-example-purple.png){ align=center width="800" }
blur_annotator = sv.BlurAnnotator()
annotated_frame = blur_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
</div>
<div class="result" markdown>
=== "Comparison"
![blur-annotator-example](https://media.roboflow.com/supervision-annotator-examples/blur-annotator-example-purple.png){ align=center width="800" }
```python
import supervision as sv
</div>
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
<div class="result" markdown>
image = ...
detections = sv.Detections(...)
![comparison-annotator-example](https://media.roboflow.com/supervision-annotator-examples/comparison-annotator-example.png){ align=center width="800" }
pixelate_annotator = sv.PixelateAnnotator()
annotated_frame = pixelate_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
</div>
<div class="result" markdown>
![pixelate-annotator-example](https://media.roboflow.com/supervision-annotator-examples/pixelate-annotator-example-10.png){ align=center width="800" }
</div>
<!-- === "Crop"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
crop_annotator = sv.CropAnnotator()
annotated_frame = crop_annotator.annotate(
scene=image.copy(),
detections=detections,
)
```
<div class="result" markdown>
![crop-annotator-example](https://media.roboflow.com/supervision-annotator-examples/crop-annotator-example.png){ align=center width="800" }
</div>
-->
=== "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)
```
<div class="result" markdown>
![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/trace-annotator-example-purple.png){ align=center width="800" }
</div>
=== "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)
```
<div class="result" markdown>
![heat-map-annotator-example](https://media.roboflow.com/supervision-annotator-examples/heat-map-annotator-example-purple.png){ align=center width="800" }
</div>
=== "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,
)
```
<div class="result" markdown>
![percentage-bar-annotator-example](https://media.roboflow.com/supervision-annotator-examples/percentage-bar-annotator-example-purple.png){ align=center width="800" }
</div>
=== "Icon"
```python
import supervision as sv
image = ...
detections = sv.Detections(...)
icon_paths = ["<ICON_PATH>" for _ in detections]
icon_annotator = sv.IconAnnotator()
annotated_frame = icon_annotator.annotate(
scene=image.copy(),
detections=detections,
icon_path=icon_paths,
)
```
<div class="result" markdown>
![icon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/icon-annotator-example.png){ align=center width="800" }
</div>
=== "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,
)
```
<div class="result" markdown>
![background-overlay-annotator-example](https://media.roboflow.com/supervision-annotator-examples/background-color-annotator-example-purple.png){ align=center width="800" }
</div>
=== "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,
)
```
<div class="result" markdown>
![comparison-annotator-example](https://media.roboflow.com/supervision-annotator-examples/comparison-annotator-example.png){ align=center width="800" }
</div>
<div class="md-typeset">
<h2>Try Supervision Annotators on your own image</h2>

View File

@ -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<indent>\s*)=== "(?P<label>[^"]+)"\s*$', line):
indent = 1 if match.group("indent") else 0
label = match.group("label")
if indent == 0:
current_group = label
groups[current_group] = []
elif indent > 0 and current_group:
groups[current_group].append(label)
return groups
def test_all_expected_annotators_have_tab_entries() -> None:
"""Assert every annotator in EXPECTED_ANNOTATOR_TAB_GROUPS has a tab in the docs.
Tests flat membership only does not enforce which category each annotator
belongs to. Update EXPECTED_ANNOTATOR_TAB_GROUPS when adding or removing
annotator tab entries.
"""
tab_groups = _extract_annotator_tab_groups()
actual_tabs = {tab for group in tab_groups.values() for tab in group}
expected_tabs = {
tab for group in EXPECTED_ANNOTATOR_TAB_GROUPS.values() for tab in group
}
assert actual_tabs == expected_tabs, (
f"Tab set mismatch. "
f"Missing from docs: {expected_tabs - actual_tabs!r}. "
f"Extra in docs (add to EXPECTED_ANNOTATOR_TAB_GROUPS): "
f"{actual_tabs - expected_tabs!r}."
)
def test_annotator_example_tab_groups_stay_within_material_limit() -> None:
"""Assert no tab group exceeds MkDocs Material's 20-tab rendering limit."""
tab_groups = _extract_annotator_tab_groups()
# Outer-tab guard is defense-in-depth; the original failure was strictly an
# inner-tab overflow (22 inner tabs in a single flat group).
assert len(tab_groups) <= 20
assert all(len(group) <= 20 for group in tab_groups.values())
def test_annotator_code_examples_have_no_tuple_assignment() -> None:
"""Assert no annotated_frame assignment wraps the call in a tuple.
Guards against the paren-comma typo: ``annotated_frame = (call(),)``
which creates a 1-tuple instead of the annotated image.
"""
docs_path = REPO_ROOT / "docs" / "detection" / "annotators.md"
content = docs_path.read_text(encoding="utf-8")
fenced_blocks = re.findall(r"```python\n(.*?)```", content, re.DOTALL)
for block_idx, block in enumerate(fenced_blocks, 1):
try:
tree = ast.parse(block)
except SyntaxError:
continue
for node in ast.walk(tree):
if (
isinstance(node, ast.Assign)
and any(
isinstance(t, ast.Name) and t.id == "annotated_frame"
for t in node.targets
)
and isinstance(node.value, ast.Tuple)
):
pytest.fail(
f"Block {block_idx}: 'annotated_frame' assigned a tuple "
"(paren-comma typo). "
"Use 'annotated_frame = call()' not 'annotated_frame = (call(),)'."
)