Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/model_api/visualizer/primitive/bounding_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

from __future__ import annotations

from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw

from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_OUTLINE_WIDTH
from model_api.visualizer.utils import default_font

from .primitive import Primitive

Expand Down Expand Up @@ -49,7 +50,7 @@ def __init__(
self.color = color
self.outline_width = outline_width
self.font_size = font_size
self.font = ImageFont.load_default(size=self.font_size)
self.font = default_font(size=self.font_size)
self.y_buffer = max(3, font_size // 3) # Text at the bottom of the text box is clipped. This prevents that.

def compute(self, image: Image) -> Image:
Expand Down
5 changes: 3 additions & 2 deletions src/model_api/visualizer/primitive/keypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from typing import Union

import numpy as np
from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw

from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_KEYPOINT_SIZE
from model_api.visualizer.utils import default_font

from .primitive import Primitive

Expand Down Expand Up @@ -51,7 +52,7 @@ def compute(self, image: Image) -> Image:
)

if self.scores is not None:
font = ImageFont.load_default(size=self.font_size)
font = default_font(size=self.font_size)
for score, keypoint in zip(self.scores, self.keypoints):
textbox = draw.textbbox((0, 0), f"{score:.2f}", font=font)
draw.text(
Expand Down
5 changes: 3 additions & 2 deletions src/model_api/visualizer/primitive/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from io import BytesIO
from typing import Union

from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw

from model_api.visualizer.defaults import DEFAULT_FONT_SIZE
from model_api.visualizer.utils import default_font, truetype_font

from .primitive import Primitive

Expand Down Expand Up @@ -53,7 +54,7 @@ def __init__(
self.label = f"{label} ({score:.2f})" if score is not None else label
self.fg_color = fg_color
self.bg_color = bg_color
self.font = ImageFont.load_default(size=size) if font_path is None else ImageFont.truetype(font_path, size)
self.font = default_font(size=size) if font_path is None else truetype_font(font_path, size)

def compute(self, image: Image, buffer_y: int = 5) -> Image:
"""Generate label on top of the image.
Expand Down
4 changes: 2 additions & 2 deletions src/model_api/visualizer/primitive/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

import numpy as np
import PIL
from PIL import ImageFont

from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_OPACITY
from model_api.visualizer.utils import default_font

from .primitive import Primitive

Expand Down Expand Up @@ -66,7 +66,7 @@ def overlay_labels(
"""
if labels is not None:
labels = [labels] if isinstance(labels, str) else labels
font = ImageFont.load_default(size=font_size)
font = default_font(size=font_size)
buffer_y = max(3, font_size // 3)
dummy_image = PIL.Image.new("RGB", (1, 1))
draw = PIL.ImageDraw.Draw(dummy_image)
Expand Down
2 changes: 1 addition & 1 deletion src/model_api/visualizer/scene/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_OUTLINE_WIDTH
from model_api.visualizer.layout import Flatten, HStack, Layout
from model_api.visualizer.primitive import BoundingBox, Label, Overlay
from model_api.visualizer.utils import get_label_color_mapping

from .scene import Scene
from .utils import get_label_color_mapping


class DetectionScene(Scene):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from model_api.visualizer.layout import Flatten, HStack, Layout
from model_api.visualizer.primitive import BoundingBox, Label, Overlay, Polygon
from model_api.visualizer.scene import Scene
from model_api.visualizer.scene.utils import get_label_color_mapping
from model_api.visualizer.utils import get_label_color_mapping


class InstanceSegmentationScene(Scene):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Visualizer utilities."""

from functools import lru_cache

from PIL import ImageFont

# Copyright (C) 2026 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -38,3 +42,28 @@ def get_label_color_mapping(labels: list[str]) -> dict[str, str]:
"""
unique_labels = sorted(set(labels))
return {label: COLOR_PALETTE[i % len(COLOR_PALETTE)] for i, label in enumerate(unique_labels)}


@lru_cache(maxsize=5)
def default_font(size: int = 10):
"""Get the default font with the specified size using cache to store the object.

Args:
size: Font size.

Returns:
A PIL ImageFont instance with the default font and specified size.
"""
return ImageFont.load_default(size=size)


@lru_cache(maxsize=5)
def truetype_font(font_path: str, size: int = 10):
"""Get a TrueType font from the specified path and size using cache to store the object.

Args:
font_path: Path to the .ttf font file.
size: Font size.
"""

return ImageFont.truetype(font_path, size)