Skip to content

Commit 5cdae4e

Browse files
committed
Cache loaded fonts
1 parent 6cd5ce5 commit 5cdae4e

File tree

7 files changed

+42
-10
lines changed

7 files changed

+42
-10
lines changed

src/model_api/visualizer/primitive/bounding_box.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
from __future__ import annotations
77

8-
from PIL import Image, ImageDraw, ImageFont
8+
from PIL import Image, ImageDraw
99

1010
from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_OUTLINE_WIDTH
11+
from model_api.visualizer.utils import default_font
1112

1213
from .primitive import Primitive
1314

@@ -49,7 +50,7 @@ def __init__(
4950
self.color = color
5051
self.outline_width = outline_width
5152
self.font_size = font_size
52-
self.font = ImageFont.load_default(size=self.font_size)
53+
self.font = default_font(size=self.font_size)
5354
self.y_buffer = max(3, font_size // 3) # Text at the bottom of the text box is clipped. This prevents that.
5455

5556
def compute(self, image: Image) -> Image:

src/model_api/visualizer/primitive/keypoints.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from typing import Union
77

88
import numpy as np
9-
from PIL import Image, ImageDraw, ImageFont
9+
from PIL import Image, ImageDraw
1010

1111
from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_KEYPOINT_SIZE
12+
from model_api.visualizer.utils import default_font
1213

1314
from .primitive import Primitive
1415

@@ -51,7 +52,7 @@ def compute(self, image: Image) -> Image:
5152
)
5253

5354
if self.scores is not None:
54-
font = ImageFont.load_default(size=self.font_size)
55+
font = default_font(size=self.font_size)
5556
for score, keypoint in zip(self.scores, self.keypoints):
5657
textbox = draw.textbbox((0, 0), f"{score:.2f}", font=font)
5758
draw.text(

src/model_api/visualizer/primitive/label.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from io import BytesIO
77
from typing import Union
88

9-
from PIL import Image, ImageDraw, ImageFont
9+
from PIL import Image, ImageDraw
1010

1111
from model_api.visualizer.defaults import DEFAULT_FONT_SIZE
12+
from model_api.visualizer.utils import default_font, truetype_font
1213

1314
from .primitive import Primitive
1415

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

5859
def compute(self, image: Image, buffer_y: int = 5) -> Image:
5960
"""Generate label on top of the image.

src/model_api/visualizer/primitive/overlay.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
import numpy as np
1111
import PIL
12-
from PIL import ImageFont
1312

1413
from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_OPACITY
14+
from model_api.visualizer.utils import default_font
1515

1616
from .primitive import Primitive
1717

@@ -66,7 +66,7 @@ def overlay_labels(
6666
"""
6767
if labels is not None:
6868
labels = [labels] if isinstance(labels, str) else labels
69-
font = ImageFont.load_default(size=font_size)
69+
font = default_font(size=font_size)
7070
buffer_y = max(3, font_size // 3)
7171
dummy_image = PIL.Image.new("RGB", (1, 1))
7272
draw = PIL.ImageDraw.Draw(dummy_image)

src/model_api/visualizer/scene/detection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from model_api.visualizer.defaults import DEFAULT_FONT_SIZE, DEFAULT_OUTLINE_WIDTH
1313
from model_api.visualizer.layout import Flatten, HStack, Layout
1414
from model_api.visualizer.primitive import BoundingBox, Label, Overlay
15+
from model_api.visualizer.utils import get_label_color_mapping
1516

1617
from .scene import Scene
17-
from .utils import get_label_color_mapping
1818

1919

2020
class DetectionScene(Scene):

src/model_api/visualizer/scene/segmentation/instance_segmentation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from model_api.visualizer.layout import Flatten, HStack, Layout
1414
from model_api.visualizer.primitive import BoundingBox, Label, Overlay, Polygon
1515
from model_api.visualizer.scene import Scene
16-
from model_api.visualizer.scene.utils import get_label_color_mapping
16+
from model_api.visualizer.utils import get_label_color_mapping
1717

1818

1919
class InstanceSegmentationScene(Scene):
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
"""Visualizer utilities."""
22

3+
from functools import lru_cache
4+
5+
from PIL import ImageFont
6+
37
# Copyright (C) 2026 Intel Corporation
48
# SPDX-License-Identifier: Apache-2.0
59

@@ -38,3 +42,28 @@ def get_label_color_mapping(labels: list[str]) -> dict[str, str]:
3842
"""
3943
unique_labels = sorted(set(labels))
4044
return {label: COLOR_PALETTE[i % len(COLOR_PALETTE)] for i, label in enumerate(unique_labels)}
45+
46+
47+
@lru_cache(maxsize=5)
48+
def default_font(size: int = 10):
49+
"""Get the default font with the specified size using cache to store the object.
50+
51+
Args:
52+
size: Font size.
53+
54+
Returns:
55+
A PIL ImageFont instance with the default font and specified size.
56+
"""
57+
return ImageFont.load_default(size=size)
58+
59+
60+
@lru_cache(maxsize=5)
61+
def truetype_font(font_path: str, size: int = 10):
62+
"""Get a TrueType font from the specified path and size using cache to store the object.
63+
64+
Args:
65+
font_path: Path to the .ttf font file.
66+
size: Font size.
67+
"""
68+
69+
return ImageFont.truetype(font_path, size)

0 commit comments

Comments
 (0)