-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathdisplay.py
103 lines (86 loc) · 3.42 KB
/
display.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Visualization utilities."""
__all__ = [
"draw_bboxes",
]
from matplotlib import font_manager
import PIL
import PIL.Image
import PIL.ImageColor
import PIL.ImageDraw
import PIL.ImageFile
import PIL.ImageFont
from speciesnet.constants import Detection
from speciesnet.utils import BBox
# Color map for different detection types.
DETECTIONS_COLOR_MAP = {
Detection.ANIMAL.value: "red",
Detection.HUMAN.value: "blue",
Detection.VEHICLE.value: "magenta",
}
def draw_bboxes(img: PIL.Image.Image, detections: list[dict]) -> PIL.Image.Image:
"""Draws bounding boxes on a given image.
The detections are assumed to be in decreasing order of their confidence score. They
are drawn from the least confident to the most confident, to make sure that the
latter is always on top. The opacity of each bounding box is proportional to its
confidence score, so the least confident boxes may sometimes be barely visible,
indicating that low confidence.
Args:
img:
PIL image to draw bounding boxes on.
detections:
List of detections sorted in decreasing order of their confidence score.
Returns:
A new PIL image generated by overlaying detection bounding boxes on top of the
original PIL image.
"""
font_prop = font_manager.FontProperties(family="sans serif", weight="bold")
font = PIL.ImageFont.truetype(font_manager.findfont(font_prop), size=12)
border_size = 3
img = img.convert("RGBA")
overlay = PIL.Image.new("RGBA", img.size, (255, 255, 255, 0))
draw = PIL.ImageDraw.Draw(overlay)
# Draw bounding boxes in reversed order, from the least confident to the most
# confident, to make sure that the latter is always on top.
for detection in reversed(detections):
label = detection["label"]
score = detection["conf"]
bbox = BBox(*detection["bbox"])
x0 = bbox.xmin * img.width
y0 = bbox.ymin * img.height
x1 = (bbox.xmin + bbox.width) * img.width
y1 = (bbox.ymin + bbox.height) * img.height
rgb = PIL.ImageColor.getrgb(DETECTIONS_COLOR_MAP[label])
alpha = int(score * 255)
color = *rgb[:3], alpha
text = f"{label}: {score:.2f}"
text_rel_xy = font.getbbox(text, anchor="lb")
text_bg_xy = (
text_rel_xy[0] + x0,
text_rel_xy[1] + y0 - 2 * border_size,
text_rel_xy[2] + x0 + 2 * border_size,
text_rel_xy[3] + y0,
)
text_color = (255, 255, 255, alpha)
draw.rectangle((x0, y0, x1, y1), outline=color, width=border_size)
draw.rectangle(text_bg_xy, fill=color, width=border_size)
draw.text(
(x0 + border_size, y0 - border_size),
text,
fill=text_color,
font=font,
anchor="lb",
)
return PIL.Image.alpha_composite(img, overlay)