Repository: roboflow/supervision
File: supervision/annotators/core.py (Python)

Code before:
        detections: Detections,
        labels: Optional[List[str]] = None,
        custom_color_lookup: Optional[np.ndarray] = None,
    ) -> ImageType:
        """
        Annotates the given scene with labels based on the provided
        detections, with support for Unicode characters.

        Args:
            scene (ImageType): The image where labels will be drawn.
                `ImageType` is a flexible type, accepting either `numpy.ndarray`
                or `PIL.Image.Image`.
            detections (Detections): Object detections to annotate.
            labels (Optional[List[str]]): Custom labels for each detection.
            custom_color_lookup (Optional[np.ndarray]): Custom color lookup array.
                Allows to override the default color mapping strategy.

        Returns:
            The annotated image, matching the type of `scene` (`numpy.ndarray`
                or `PIL.Image.Image`)

        Example:
            ```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)
            ]

            rich_label_annotator = sv.RichLabelAnnotator(font_path="path/to/font.ttf")
            annotated_frame = label_annotator.annotate(
                scene=image.copy(),
                detections=detections,
                labels=labels
            )
            ```

        """
        assert isinstance(scene, Image.Image)
        self._validate_labels(labels, detections)

        draw = ImageDraw.Draw(scene)
        labels = self._get_labels_text(detections, labels)
        text_properties = self._get_text_properties(draw, labels)

        xyxy = self._calculate_label_positions(

Review comment (suggestion):
It seems to me that `_get_text_properties` and `_calculate_label_positions` should be merge into a single function that returns numpy array. You can than cut out `xyxy` portion of that numpy array and pass it to `spread_out_boxes`.

Code after:

            image = ...
            detections = sv.Detections(...)

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

            rich_label_annotator = sv.RichLabelAnnotator(font_path="path/to/font.ttf")
            annotated_frame = label_annotator.annotate(
                scene=image.copy(),
                detections=detections,
                labels=labels
            )
            ```

        """
        assert isinstance(scene, Image.Image)
        self._validate_labels(labels, detections)

        draw = ImageDraw.Draw(scene)
        labels = self._get_labels_text(detections, labels)
        label_properties = self._get_label_properties(draw, detections, labels)

        if self.smart_position:
            xyxy = label_properties[:, :4]
            xyxy = spread_out_boxes(xyxy)
            label_properties[:, :4] = xyxy

        self._draw_labels(
            draw=draw,
            labels=labels,
            label_properties=label_properties,
            detections=detections,
            custom_color_lookup=custom_color_lookup,
        )

        return scene

    def _validate_labels(self, labels: Optional[List[str]], detections: Detections):
        if labels is not None and len(labels) != len(detections):
            raise ValueError(
                f"The number of labels ({len(labels)}) does not match the "
                f"number of detections ({len(detections)}). Each detection "
                f"should have exactly 1 label."
            )

    def _get_label_properties(
        self, draw, detections: Detections, labels: List[str]