Skip to content

Commit 3e8906d

Browse files
committed
📘 docs updates for LabelAnnotator
1 parent 560a35b commit 3e8906d

File tree

3 files changed

+45
-212
lines changed

3 files changed

+45
-212
lines changed

‎docs/detection/annotate.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
## BoxCornerAnnotator
1414

1515
:::supervision.annotators.core.BoxCornerAnnotator
16+
17+
## LabelAnnotator
18+
19+
:::supervision.annotators.core.LabelAnnotator

‎supervision/__init__.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
BoundingBoxAnnotator,
1111
BoxCornerAnnotator,
1212
EllipseAnnotator,
13-
LabelAdvancedAnnotator,
1413
LabelAnnotator,
1514
MaskAnnotator,
16-
TraceAnnotator,
1715
)
1816
from supervision.classification.core import Classifications
1917
from supervision.dataset.core import (

‎supervision/annotators/core.py‎

Lines changed: 41 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray:
307307

308308

309309
class LabelAnnotator:
310+
"""
311+
A class for annotating labels on an image using provided detections.
312+
"""
310313
def __init__(
311314
self,
312315
color: Union[Color, ColorPalette] = ColorPalette.default(),
@@ -317,6 +320,19 @@ def __init__(
317320
text_position: Position = Position.TOP_LEFT,
318321
color_map: str = "class",
319322
):
323+
"""
324+
Args:
325+
color (Union[Color, ColorPalette]): The color or color palette to use for
326+
annotating the text background.
327+
text_color (Color): The color to use for the text.
328+
text_scale (float): Font scale for the text.
329+
text_thickness (int): Thickness of the text characters.
330+
text_padding (int): Padding around the text within its background box.
331+
text_position (Position): Position of the text relative to the detection.
332+
Possible values are defined in the `Position` enum.
333+
color_map (str): Strategy for mapping colors to annotations.
334+
Options are `index`, `class`, or `track`.
335+
"""
320336
self.color: Union[Color, ColorPalette] = color
321337
self.text_color: Color = text_color
322338
self.text_scale: float = text_scale
@@ -367,6 +383,31 @@ def annotate(
367383
detections: Detections,
368384
labels: List[str] = None,
369385
) -> np.ndarray:
386+
"""
387+
Annotates the given scene with labels based on the provided detections.
388+
389+
Args:
390+
scene (np.ndarray): The image where labels will be drawn.
391+
detections (Detections): Object detections to annotate.
392+
labels (List[str]): Optional. Custom labels for each detection.
393+
394+
Returns:
395+
np.ndarray: The annotated image.
396+
397+
Example:
398+
```python
399+
>>> import supervision as sv
400+
401+
>>> image = ...
402+
>>> detections = sv.Detections(...)
403+
404+
>>> label_annotator = sv.LabelAnnotator()
405+
>>> annotated_frame = label_annotator.annotate(
406+
... scene=image.copy(),
407+
... detections=detections
408+
... )
409+
```
410+
"""
370411
font = cv2.FONT_HERSHEY_SIMPLEX
371412
for detection_idx in range(len(detections)):
372413
detection_xyxy = detections.xyxy[detection_idx].astype(int)
@@ -416,213 +457,3 @@ def annotate(
416457
lineType=cv2.LINE_AA,
417458
)
418459
return scene
419-
420-
421-
class LabelAdvancedAnnotator(BaseAnnotator):
422-
def __init__(
423-
self,
424-
color: Union[Color, ColorPalette] = ColorPalette.default(),
425-
text_color: Color = Color.black(),
426-
text_padding: int = 20,
427-
color_by_track: bool = False,
428-
font: Optional[str] = None,
429-
font_size: Optional[int] = 15,
430-
):
431-
if font and os.path.exists(font):
432-
self.font = ImageFont.truetype(font, font_size)
433-
else:
434-
self.font = ImageFont.load_default()
435-
self.color: Union[Color, ColorPalette] = color
436-
self.text_color: Color = text_color
437-
self.text_padding: int = text_padding
438-
self.color_by_track = color_by_track
439-
440-
def annotate(
441-
self,
442-
scene: np.ndarray,
443-
detections: Detections,
444-
labels: Optional[List[str]] = None,
445-
) -> np.ndarray:
446-
"""
447-
Draws text on the frame using the detections provided and label.
448-
449-
Args:
450-
scene (np.ndarray): The image on which the bounding boxes will be drawn
451-
detections (Detections): The detections for which
452-
the bounding boxes will be drawn
453-
labels (Optional[List[str]]): An optional list of labels corresponding
454-
to each detection. If `labels` are not provided,
455-
corresponding `class_id` will be used as label.
456-
Returns:
457-
np.ndarray: The image with the bounding boxes drawn on it
458-
459-
Example:
460-
```python
461-
>>> import supervision as sv
462-
463-
>>> classes = ['person', ...]
464-
>>> image = ...
465-
>>> detections = sv.Detections(...)
466-
467-
>>> pil_label_annotator = sv.LabelAdvancedAnnotator()
468-
>>> labels = [
469-
... f"{classes[class_id]} {confidence:0.2f}"
470-
... for _, _, confidence, class_id, _
471-
... in detections
472-
... ]
473-
>>> annotated_frame = pil_label_annotator.annotate(
474-
... scene=image.copy(),
475-
... detections=detections,
476-
... labels=labels,
477-
... )
478-
```
479-
"""
480-
pil_image = Image.fromarray(scene)
481-
draw = ImageDraw.Draw(pil_image)
482-
text_color = "#fff"
483-
484-
for i in range(len(detections)):
485-
x1, y1, x2, y2 = detections.xyxy[i].astype(int)
486-
if self.color_by_track:
487-
tracker_id = (
488-
detections.tracker_id[i]
489-
if detections.tracker_id is not None
490-
else None
491-
)
492-
idx = tracker_id if tracker_id is not None else i
493-
else:
494-
class_id = (
495-
detections.class_id[i] if detections.class_id is not None else None
496-
)
497-
idx = class_id if class_id is not None else i
498-
499-
color = (
500-
self.color.by_idx(idx)
501-
if isinstance(self.color, ColorPalette)
502-
else self.color
503-
)
504-
505-
text = (
506-
f"{idx}"
507-
if (labels is None or len(detections) != len(labels))
508-
else labels[i]
509-
)
510-
511-
text_bbox = draw.textbbox((x1, y1), text, font=self.font)
512-
513-
text_height = text_bbox[3] - text_bbox[1]
514-
text_width = text_bbox[2] - text_bbox[0]
515-
516-
text_x = x1 + self.text_padding / 2
517-
text_y = y1 - self.text_padding / 2 - text_height
518-
519-
text_background_x1 = x1
520-
text_background_y1 = y1 - self.text_padding / 2 - text_height
521-
522-
text_background_x2 = x1 + 2 * self.text_padding / 2 + text_width
523-
text_background_y2 = y1 # correct
524-
525-
draw.rectangle(
526-
(
527-
text_background_x1,
528-
text_background_y1,
529-
text_background_x2,
530-
text_background_y2,
531-
),
532-
fill=color.as_bgr(),
533-
)
534-
draw.text((text_x, text_y), text, font=self.font, fill=text_color)
535-
536-
scene = np.asarray(pil_image)
537-
return scene
538-
539-
540-
class TraceAnnotator(BaseAnnotator):
541-
"""
542-
A class for drawing trajectory of a tracker on an image using detections provided.
543-
544-
Attributes:
545-
color (Union[Color, ColorPalette]): The color to draw the trajectory,
546-
can be a single color or a color palette
547-
color_by_track (bool): Whther to use tracker id to pick the color
548-
position (Optional[Position]): Choose position of trajectory such as
549-
center position, top left corner, etc
550-
trace_length (int): Length of the previous points
551-
thickness (int): thickness of the line
552-
"""
553-
554-
def __init__(
555-
self,
556-
color: Union[Color, ColorPalette] = ColorPalette.default(),
557-
color_by_track: bool = False,
558-
position: Optional[Position] = Position.CENTER,
559-
trace_length: int = 30,
560-
thickness: int = 2,
561-
):
562-
self.color: Union[Color, ColorPalette] = color
563-
self.color_by_track = color_by_track
564-
self.position = position
565-
self.tracker_storage = defaultdict(lambda: [])
566-
self.trace_length = trace_length
567-
self.thickness = thickness
568-
569-
def annotate(
570-
self, scene: np.ndarray, detections: Detections, **kwargs
571-
) -> np.ndarray:
572-
"""
573-
Draw the object trajectory based on history of tracked objects
574-
575-
Args:
576-
scene (np.ndarray): The image on which the trace will be drawn
577-
detections (Detections): The detections for trajectory and points
578-
579-
Returns:
580-
np.ndarray: The image with the masks overlaid
581-
Example:
582-
```python
583-
>>> import supervision as sv
584-
585-
>>> classes = ['person', ...]
586-
>>> image = ...
587-
>>> detections = sv.Detections(...)
588-
589-
>>> trace_annotator = sv.TraceAnnotator()
590-
>>> annotated_frame = trace_annotator.annotate(
591-
... scene=image.copy(),
592-
... detections=detections
593-
... )
594-
```
595-
"""
596-
if detections.tracker_id is None:
597-
return scene
598-
599-
anchor_points = detections.get_anchor_coordinates(anchor=self.position)
600-
601-
for i, tracker_id in enumerate(detections.tracker_id):
602-
track = self.tracker_storage[tracker_id]
603-
track.append((anchor_points[i][0], anchor_points[i][1]))
604-
if len(track) > self.trace_length:
605-
track.pop(0)
606-
points = np.hstack(track).astype(np.int32).reshape((-1, 1, 2))
607-
608-
if self.color_by_track:
609-
idx = tracker_id if tracker_id is not None else i
610-
else:
611-
class_id = (
612-
detections.class_id[i] if detections.class_id is not None else None
613-
)
614-
idx = class_id if class_id is not None else i
615-
color = (
616-
self.color.by_idx(idx)
617-
if isinstance(self.color, ColorPalette)
618-
else self.color
619-
)
620-
cv2.polylines(
621-
scene,
622-
[points],
623-
isClosed=False,
624-
color=color.as_bgr(),
625-
thickness=self.thickness,
626-
)
627-
628-
return scene

0 commit comments

Comments
 (0)