segment-everything 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- segment_everything/__init__.py +5 -0
- segment_everything/augmentation/albumentations_helper.py +0 -0
- segment_everything/detect_and_segment.py +131 -0
- segment_everything/napari_helper.py +15 -0
- segment_everything/prompt_generator.py +188 -0
- segment_everything/py.typed +5 -0
- segment_everything/stacked_label_dataset.py +113 -0
- segment_everything/stacked_labels.py +428 -0
- segment_everything/vendored/PromptGuidedDecoder/Prompt_guided_Mask_Decoder.pt +0 -0
- segment_everything/vendored/__init__.py +5 -0
- segment_everything/vendored/dice.py +158 -0
- segment_everything/vendored/efficientvit/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/data_provider/__init__.py +7 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/bbox.py +30 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/color_aug.py +78 -0
- segment_everything/vendored/efficientvit/apps/data_provider/base.py +254 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_loader.py +1538 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_worker.py +357 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/controller.py +100 -0
- segment_everything/vendored/efficientvit/apps/setup.py +150 -0
- segment_everything/vendored/efficientvit/apps/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/trainer/base.py +318 -0
- segment_everything/vendored/efficientvit/apps/trainer/run_config.py +129 -0
- segment_everything/vendored/efficientvit/apps/utils/__init__.py +12 -0
- segment_everything/vendored/efficientvit/apps/utils/dist.py +32 -0
- segment_everything/vendored/efficientvit/apps/utils/ema.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/export.py +45 -0
- segment_everything/vendored/efficientvit/apps/utils/init.py +66 -0
- segment_everything/vendored/efficientvit/apps/utils/lr.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/metric.py +43 -0
- segment_everything/vendored/efficientvit/apps/utils/misc.py +101 -0
- segment_everything/vendored/efficientvit/apps/utils/opt.py +28 -0
- segment_everything/vendored/efficientvit/cls_model_zoo.py +79 -0
- segment_everything/vendored/efficientvit/clscore/__init__.py +0 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/__init__.py +5 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/imagenet.py +142 -0
- segment_everything/vendored/efficientvit/clscore/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_run_config.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_trainer.py +265 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/label_smooth.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/metric.py +23 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/mixup.py +67 -0
- segment_everything/vendored/efficientvit/models/__init__.py +0 -0
- segment_everything/vendored/efficientvit/models/efficientvit/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/efficientvit/backbone.py +380 -0
- segment_everything/vendored/efficientvit/models/efficientvit/cls.py +188 -0
- segment_everything/vendored/efficientvit/models/efficientvit/sam.py +181 -0
- segment_everything/vendored/efficientvit/models/efficientvit/seg.py +373 -0
- segment_everything/vendored/efficientvit/models/nn/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/nn/act.py +30 -0
- segment_everything/vendored/efficientvit/models/nn/drop.py +104 -0
- segment_everything/vendored/efficientvit/models/nn/norm.py +164 -0
- segment_everything/vendored/efficientvit/models/nn/ops.py +597 -0
- segment_everything/vendored/efficientvit/models/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/models/utils/list.py +53 -0
- segment_everything/vendored/efficientvit/models/utils/network.py +73 -0
- segment_everything/vendored/efficientvit/models/utils/random.py +65 -0
- segment_everything/vendored/efficientvit/sam_model_zoo.py +45 -0
- segment_everything/vendored/efficientvit/seg_model_zoo.py +70 -0
- segment_everything/vendored/get_object_aware.py +26 -0
- segment_everything/vendored/mobilesamv2/__init__.py +16 -0
- segment_everything/vendored/mobilesamv2/automatic_mask_generator.py +415 -0
- segment_everything/vendored/mobilesamv2/build_sam.py +246 -0
- segment_everything/vendored/mobilesamv2/modeling/__init__.py +11 -0
- segment_everything/vendored/mobilesamv2/modeling/common.py +43 -0
- segment_everything/vendored/mobilesamv2/modeling/image_encoder.py +394 -0
- segment_everything/vendored/mobilesamv2/modeling/mask_decoder.py +213 -0
- segment_everything/vendored/mobilesamv2/modeling/prompt_encoder.py +217 -0
- segment_everything/vendored/mobilesamv2/modeling/sam.py +203 -0
- segment_everything/vendored/mobilesamv2/modeling/transformer.py +240 -0
- segment_everything/vendored/mobilesamv2/predictor.py +384 -0
- segment_everything/vendored/mobilesamv2/utils/__init__.py +5 -0
- segment_everything/vendored/mobilesamv2/utils/amg.py +347 -0
- segment_everything/vendored/mobilesamv2/utils/onnx.py +144 -0
- segment_everything/vendored/mobilesamv2/utils/transforms.py +103 -0
- segment_everything/vendored/object_detection/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/nn/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autobackend.py +658 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autoshape.py +397 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/__init__.py +110 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/block.py +304 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/conv.py +297 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/head.py +468 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/transformer.py +378 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/utils.py +78 -0
- segment_everything/vendored/object_detection/ultralytics/nn/tasks.py +1049 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/__init__.py +6 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/model.py +104 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/predict.py +95 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/__init__.py +588 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/default.yaml +117 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/annotator.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/augment.py +899 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/base.py +286 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/build.py +213 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/converter.py +358 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/stream_loaders.py +459 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset.py +274 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset_wrappers.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/download_weights.sh +18 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco.sh +60 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco128.sh +17 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_imagenet.sh +51 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/utils.py +716 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/exporter.py +1214 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/model.py +641 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/predictor.py +461 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/results.py +741 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/__init__.py +893 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/autobatch.py +108 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/base.py +212 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/checks.py +547 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/dist.py +67 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/downloads.py +353 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/errors.py +12 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/files.py +100 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/instance.py +391 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/loss.py +579 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/metrics.py +1189 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/ops.py +870 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/patches.py +45 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/plotting.py +767 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tal.py +276 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/torch_utils.py +684 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tuner.py +54 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/predict.py +69 -0
- segment_everything/vendored/tinyvit/__init__.py +2 -0
- segment_everything/vendored/tinyvit/tiny_vit.py +867 -0
- segment_everything/weights_helper.py +124 -0
- segment_everything-0.1.0.dist-info/METADATA +53 -0
- segment_everything-0.1.0.dist-info/RECORD +145 -0
- segment_everything-0.1.0.dist-info/WHEEL +4 -0
- segment_everything-0.1.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import math
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import cv2
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
10
|
+
import torch
|
|
11
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
12
|
+
from PIL import __version__ as pil_version
|
|
13
|
+
from scipy.ndimage import gaussian_filter1d
|
|
14
|
+
|
|
15
|
+
from ..utils import LOGGER, TryExcept, plt_settings, threaded
|
|
16
|
+
|
|
17
|
+
from .checks import check_font, check_version, is_ascii
|
|
18
|
+
from .files import increment_path
|
|
19
|
+
from .ops import clip_boxes, scale_image, xywh2xyxy, xyxy2xywh
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Colors:
|
|
23
|
+
# Ultralytics color palette https://ultralytics.com/
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initialize colors as hex = matplotlib.colors.TABLEAU_COLORS.values()."""
|
|
26
|
+
hexs = (
|
|
27
|
+
"FF3838",
|
|
28
|
+
"FF9D97",
|
|
29
|
+
"FF701F",
|
|
30
|
+
"FFB21D",
|
|
31
|
+
"CFD231",
|
|
32
|
+
"48F90A",
|
|
33
|
+
"92CC17",
|
|
34
|
+
"3DDB86",
|
|
35
|
+
"1A9334",
|
|
36
|
+
"00D4BB",
|
|
37
|
+
"2C99A8",
|
|
38
|
+
"00C2FF",
|
|
39
|
+
"344593",
|
|
40
|
+
"6473FF",
|
|
41
|
+
"0018EC",
|
|
42
|
+
"8438FF",
|
|
43
|
+
"520085",
|
|
44
|
+
"CB38FF",
|
|
45
|
+
"FF95C8",
|
|
46
|
+
"FF37C7",
|
|
47
|
+
)
|
|
48
|
+
self.palette = [self.hex2rgb(f"#{c}") for c in hexs]
|
|
49
|
+
self.n = len(self.palette)
|
|
50
|
+
self.pose_palette = np.array(
|
|
51
|
+
[
|
|
52
|
+
[255, 128, 0],
|
|
53
|
+
[255, 153, 51],
|
|
54
|
+
[255, 178, 102],
|
|
55
|
+
[230, 230, 0],
|
|
56
|
+
[255, 153, 255],
|
|
57
|
+
[153, 204, 255],
|
|
58
|
+
[255, 102, 255],
|
|
59
|
+
[255, 51, 255],
|
|
60
|
+
[102, 178, 255],
|
|
61
|
+
[51, 153, 255],
|
|
62
|
+
[255, 153, 153],
|
|
63
|
+
[255, 102, 102],
|
|
64
|
+
[255, 51, 51],
|
|
65
|
+
[153, 255, 153],
|
|
66
|
+
[102, 255, 102],
|
|
67
|
+
[51, 255, 51],
|
|
68
|
+
[0, 255, 0],
|
|
69
|
+
[0, 0, 255],
|
|
70
|
+
[255, 0, 0],
|
|
71
|
+
[255, 255, 255],
|
|
72
|
+
],
|
|
73
|
+
dtype=np.uint8,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def __call__(self, i, bgr=False):
|
|
77
|
+
"""Converts hex color codes to rgb values."""
|
|
78
|
+
c = self.palette[int(i) % self.n]
|
|
79
|
+
return (c[2], c[1], c[0]) if bgr else c
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def hex2rgb(h): # rgb order (PIL)
|
|
83
|
+
return tuple(int(h[1 + i : 1 + i + 2], 16) for i in (0, 2, 4))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
colors = Colors() # create instance for 'from utils.plots import colors'
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Annotator:
|
|
90
|
+
# YOLOv8 Annotator for train/val mosaics and jpgs and detect/hub inference annotations
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
im,
|
|
94
|
+
line_width=None,
|
|
95
|
+
font_size=None,
|
|
96
|
+
font="Arial.ttf",
|
|
97
|
+
pil=False,
|
|
98
|
+
example="abc",
|
|
99
|
+
):
|
|
100
|
+
"""Initialize the Annotator class with image and line width along with color palette for keypoints and limbs."""
|
|
101
|
+
assert (
|
|
102
|
+
im.data.contiguous
|
|
103
|
+
), "Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images."
|
|
104
|
+
non_ascii = not is_ascii(
|
|
105
|
+
example
|
|
106
|
+
) # non-latin labels, i.e. asian, arabic, cyrillic
|
|
107
|
+
self.pil = pil or non_ascii
|
|
108
|
+
if self.pil: # use PIL
|
|
109
|
+
self.im = (
|
|
110
|
+
im if isinstance(im, Image.Image) else Image.fromarray(im)
|
|
111
|
+
)
|
|
112
|
+
self.draw = ImageDraw.Draw(self.im)
|
|
113
|
+
try:
|
|
114
|
+
font = check_font("Arial.Unicode.ttf" if non_ascii else font)
|
|
115
|
+
size = font_size or max(
|
|
116
|
+
round(sum(self.im.size) / 2 * 0.035), 12
|
|
117
|
+
)
|
|
118
|
+
self.font = ImageFont.truetype(str(font), size)
|
|
119
|
+
except Exception:
|
|
120
|
+
self.font = ImageFont.load_default()
|
|
121
|
+
# Deprecation fix for w, h = getsize(string) -> _, _, w, h = getbox(string)
|
|
122
|
+
if check_version(pil_version, "9.2.0"):
|
|
123
|
+
self.font.getsize = lambda x: self.font.getbbox(x)[
|
|
124
|
+
2:4
|
|
125
|
+
] # text width, height
|
|
126
|
+
else: # use cv2
|
|
127
|
+
self.im = im
|
|
128
|
+
self.lw = line_width or max(
|
|
129
|
+
round(sum(im.shape) / 2 * 0.003), 2
|
|
130
|
+
) # line width
|
|
131
|
+
# Pose
|
|
132
|
+
self.skeleton = [
|
|
133
|
+
[16, 14],
|
|
134
|
+
[14, 12],
|
|
135
|
+
[17, 15],
|
|
136
|
+
[15, 13],
|
|
137
|
+
[12, 13],
|
|
138
|
+
[6, 12],
|
|
139
|
+
[7, 13],
|
|
140
|
+
[6, 7],
|
|
141
|
+
[6, 8],
|
|
142
|
+
[7, 9],
|
|
143
|
+
[8, 10],
|
|
144
|
+
[9, 11],
|
|
145
|
+
[2, 3],
|
|
146
|
+
[1, 2],
|
|
147
|
+
[1, 3],
|
|
148
|
+
[2, 4],
|
|
149
|
+
[3, 5],
|
|
150
|
+
[4, 6],
|
|
151
|
+
[5, 7],
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
self.limb_color = colors.pose_palette[
|
|
155
|
+
[9, 9, 9, 9, 7, 7, 7, 0, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16]
|
|
156
|
+
]
|
|
157
|
+
self.kpt_color = colors.pose_palette[
|
|
158
|
+
[16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9]
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
def box_label(
|
|
162
|
+
self, box, label="", color=(128, 128, 128), txt_color=(255, 255, 255)
|
|
163
|
+
):
|
|
164
|
+
"""Add one xyxy box to image with label."""
|
|
165
|
+
if isinstance(box, torch.Tensor):
|
|
166
|
+
box = box.tolist()
|
|
167
|
+
if self.pil or not is_ascii(label):
|
|
168
|
+
self.draw.rectangle(box, width=self.lw, outline=color) # box
|
|
169
|
+
if label:
|
|
170
|
+
w, h = self.font.getsize(label) # text width, height
|
|
171
|
+
outside = box[1] - h >= 0 # label fits outside box
|
|
172
|
+
self.draw.rectangle(
|
|
173
|
+
(
|
|
174
|
+
box[0],
|
|
175
|
+
box[1] - h if outside else box[1],
|
|
176
|
+
box[0] + w + 1,
|
|
177
|
+
box[1] + 1 if outside else box[1] + h + 1,
|
|
178
|
+
),
|
|
179
|
+
fill=color,
|
|
180
|
+
)
|
|
181
|
+
# self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0
|
|
182
|
+
self.draw.text(
|
|
183
|
+
(box[0], box[1] - h if outside else box[1]),
|
|
184
|
+
label,
|
|
185
|
+
fill=txt_color,
|
|
186
|
+
font=self.font,
|
|
187
|
+
)
|
|
188
|
+
else: # cv2
|
|
189
|
+
p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
|
|
190
|
+
cv2.rectangle(
|
|
191
|
+
self.im, p1, p2, color, thickness=self.lw, lineType=cv2.LINE_AA
|
|
192
|
+
)
|
|
193
|
+
if label:
|
|
194
|
+
tf = max(self.lw - 1, 1) # font thickness
|
|
195
|
+
w, h = cv2.getTextSize(
|
|
196
|
+
label, 0, fontScale=self.lw / 3, thickness=tf
|
|
197
|
+
)[
|
|
198
|
+
0
|
|
199
|
+
] # text width, height
|
|
200
|
+
outside = p1[1] - h >= 3
|
|
201
|
+
p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
|
|
202
|
+
cv2.rectangle(
|
|
203
|
+
self.im, p1, p2, color, -1, cv2.LINE_AA
|
|
204
|
+
) # filled
|
|
205
|
+
cv2.putText(
|
|
206
|
+
self.im,
|
|
207
|
+
label,
|
|
208
|
+
(p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
|
|
209
|
+
0,
|
|
210
|
+
self.lw / 3,
|
|
211
|
+
txt_color,
|
|
212
|
+
thickness=tf,
|
|
213
|
+
lineType=cv2.LINE_AA,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def masks(self, masks, colors, im_gpu, alpha=0.5, retina_masks=False):
|
|
217
|
+
"""Plot masks at once.
|
|
218
|
+
Args:
|
|
219
|
+
masks (tensor): predicted masks on cuda, shape: [n, h, w]
|
|
220
|
+
colors (List[List[Int]]): colors for predicted masks, [[r, g, b] * n]
|
|
221
|
+
im_gpu (tensor): img is in cuda, shape: [3, h, w], range: [0, 1]
|
|
222
|
+
alpha (float): mask transparency: 0.0 fully transparent, 1.0 opaque
|
|
223
|
+
"""
|
|
224
|
+
if self.pil:
|
|
225
|
+
# Convert to numpy first
|
|
226
|
+
self.im = np.asarray(self.im).copy()
|
|
227
|
+
if len(masks) == 0:
|
|
228
|
+
self.im[:] = (
|
|
229
|
+
im_gpu.permute(1, 2, 0).contiguous().cpu().numpy() * 255
|
|
230
|
+
)
|
|
231
|
+
if im_gpu.device != masks.device:
|
|
232
|
+
im_gpu = im_gpu.to(masks.device)
|
|
233
|
+
colors = (
|
|
234
|
+
torch.tensor(colors, device=masks.device, dtype=torch.float32)
|
|
235
|
+
/ 255.0
|
|
236
|
+
) # shape(n,3)
|
|
237
|
+
colors = colors[:, None, None] # shape(n,1,1,3)
|
|
238
|
+
masks = masks.unsqueeze(3) # shape(n,h,w,1)
|
|
239
|
+
masks_color = masks * (colors * alpha) # shape(n,h,w,3)
|
|
240
|
+
|
|
241
|
+
inv_alph_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1)
|
|
242
|
+
mcs = masks_color.max(dim=0).values # shape(n,h,w,3)
|
|
243
|
+
|
|
244
|
+
im_gpu = im_gpu.flip(dims=[0]) # flip channel
|
|
245
|
+
im_gpu = im_gpu.permute(1, 2, 0).contiguous() # shape(h,w,3)
|
|
246
|
+
im_gpu = im_gpu * inv_alph_masks[-1] + mcs
|
|
247
|
+
im_mask = im_gpu * 255
|
|
248
|
+
im_mask_np = im_mask.byte().cpu().numpy()
|
|
249
|
+
self.im[:] = (
|
|
250
|
+
im_mask_np
|
|
251
|
+
if retina_masks
|
|
252
|
+
else scale_image(im_mask_np, self.im.shape)
|
|
253
|
+
)
|
|
254
|
+
if self.pil:
|
|
255
|
+
# Convert im back to PIL and update draw
|
|
256
|
+
self.fromarray(self.im)
|
|
257
|
+
|
|
258
|
+
def kpts(self, kpts, shape=(640, 640), radius=5, kpt_line=True):
|
|
259
|
+
"""Plot keypoints on the image.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
kpts (tensor): Predicted keypoints with shape [17, 3]. Each keypoint has (x, y, confidence).
|
|
263
|
+
shape (tuple): Image shape as a tuple (h, w), where h is the height and w is the width.
|
|
264
|
+
radius (int, optional): Radius of the drawn keypoints. Default is 5.
|
|
265
|
+
kpt_line (bool, optional): If True, the function will draw lines connecting keypoints
|
|
266
|
+
for human pose. Default is True.
|
|
267
|
+
|
|
268
|
+
Note: `kpt_line=True` currently only supports human pose plotting.
|
|
269
|
+
"""
|
|
270
|
+
if self.pil:
|
|
271
|
+
# Convert to numpy first
|
|
272
|
+
self.im = np.asarray(self.im).copy()
|
|
273
|
+
nkpt, ndim = kpts.shape
|
|
274
|
+
is_pose = nkpt == 17 and ndim == 3
|
|
275
|
+
kpt_line &= is_pose # `kpt_line=True` for now only supports human pose plotting
|
|
276
|
+
for i, k in enumerate(kpts):
|
|
277
|
+
color_k = (
|
|
278
|
+
[int(x) for x in self.kpt_color[i]] if is_pose else colors(i)
|
|
279
|
+
)
|
|
280
|
+
x_coord, y_coord = k[0], k[1]
|
|
281
|
+
if x_coord % shape[1] != 0 and y_coord % shape[0] != 0:
|
|
282
|
+
if len(k) == 3:
|
|
283
|
+
conf = k[2]
|
|
284
|
+
if conf < 0.5:
|
|
285
|
+
continue
|
|
286
|
+
cv2.circle(
|
|
287
|
+
self.im,
|
|
288
|
+
(int(x_coord), int(y_coord)),
|
|
289
|
+
radius,
|
|
290
|
+
color_k,
|
|
291
|
+
-1,
|
|
292
|
+
lineType=cv2.LINE_AA,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if kpt_line:
|
|
296
|
+
ndim = kpts.shape[-1]
|
|
297
|
+
for i, sk in enumerate(self.skeleton):
|
|
298
|
+
pos1 = (int(kpts[(sk[0] - 1), 0]), int(kpts[(sk[0] - 1), 1]))
|
|
299
|
+
pos2 = (int(kpts[(sk[1] - 1), 0]), int(kpts[(sk[1] - 1), 1]))
|
|
300
|
+
if ndim == 3:
|
|
301
|
+
conf1 = kpts[(sk[0] - 1), 2]
|
|
302
|
+
conf2 = kpts[(sk[1] - 1), 2]
|
|
303
|
+
if conf1 < 0.5 or conf2 < 0.5:
|
|
304
|
+
continue
|
|
305
|
+
if (
|
|
306
|
+
pos1[0] % shape[1] == 0
|
|
307
|
+
or pos1[1] % shape[0] == 0
|
|
308
|
+
or pos1[0] < 0
|
|
309
|
+
or pos1[1] < 0
|
|
310
|
+
):
|
|
311
|
+
continue
|
|
312
|
+
if (
|
|
313
|
+
pos2[0] % shape[1] == 0
|
|
314
|
+
or pos2[1] % shape[0] == 0
|
|
315
|
+
or pos2[0] < 0
|
|
316
|
+
or pos2[1] < 0
|
|
317
|
+
):
|
|
318
|
+
continue
|
|
319
|
+
cv2.line(
|
|
320
|
+
self.im,
|
|
321
|
+
pos1,
|
|
322
|
+
pos2,
|
|
323
|
+
[int(x) for x in self.limb_color[i]],
|
|
324
|
+
thickness=2,
|
|
325
|
+
lineType=cv2.LINE_AA,
|
|
326
|
+
)
|
|
327
|
+
if self.pil:
|
|
328
|
+
# Convert im back to PIL and update draw
|
|
329
|
+
self.fromarray(self.im)
|
|
330
|
+
|
|
331
|
+
def rectangle(self, xy, fill=None, outline=None, width=1):
|
|
332
|
+
"""Add rectangle to image (PIL-only)."""
|
|
333
|
+
self.draw.rectangle(xy, fill, outline, width)
|
|
334
|
+
|
|
335
|
+
def text(
|
|
336
|
+
self,
|
|
337
|
+
xy,
|
|
338
|
+
text,
|
|
339
|
+
txt_color=(255, 255, 255),
|
|
340
|
+
anchor="top",
|
|
341
|
+
box_style=False,
|
|
342
|
+
):
|
|
343
|
+
"""Adds text to an image using PIL or cv2."""
|
|
344
|
+
if anchor == "bottom": # start y from font bottom
|
|
345
|
+
w, h = self.font.getsize(text) # text width, height
|
|
346
|
+
xy[1] += 1 - h
|
|
347
|
+
if self.pil:
|
|
348
|
+
if box_style:
|
|
349
|
+
w, h = self.font.getsize(text)
|
|
350
|
+
self.draw.rectangle(
|
|
351
|
+
(xy[0], xy[1], xy[0] + w + 1, xy[1] + h + 1),
|
|
352
|
+
fill=txt_color,
|
|
353
|
+
)
|
|
354
|
+
# Using `txt_color` for background and draw fg with white color
|
|
355
|
+
txt_color = (255, 255, 255)
|
|
356
|
+
self.draw.text(xy, text, fill=txt_color, font=self.font)
|
|
357
|
+
else:
|
|
358
|
+
if box_style:
|
|
359
|
+
tf = max(self.lw - 1, 1) # font thickness
|
|
360
|
+
w, h = cv2.getTextSize(
|
|
361
|
+
text, 0, fontScale=self.lw / 3, thickness=tf
|
|
362
|
+
)[
|
|
363
|
+
0
|
|
364
|
+
] # text width, height
|
|
365
|
+
outside = xy[1] - h >= 3
|
|
366
|
+
p2 = xy[0] + w, xy[1] - h - 3 if outside else xy[1] + h + 3
|
|
367
|
+
cv2.rectangle(
|
|
368
|
+
self.im, xy, p2, txt_color, -1, cv2.LINE_AA
|
|
369
|
+
) # filled
|
|
370
|
+
# Using `txt_color` for background and draw fg with white color
|
|
371
|
+
txt_color = (255, 255, 255)
|
|
372
|
+
tf = max(self.lw - 1, 1) # font thickness
|
|
373
|
+
cv2.putText(
|
|
374
|
+
self.im,
|
|
375
|
+
text,
|
|
376
|
+
xy,
|
|
377
|
+
0,
|
|
378
|
+
self.lw / 3,
|
|
379
|
+
txt_color,
|
|
380
|
+
thickness=tf,
|
|
381
|
+
lineType=cv2.LINE_AA,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def fromarray(self, im):
|
|
385
|
+
"""Update self.im from a numpy array."""
|
|
386
|
+
self.im = im if isinstance(im, Image.Image) else Image.fromarray(im)
|
|
387
|
+
self.draw = ImageDraw.Draw(self.im)
|
|
388
|
+
|
|
389
|
+
def result(self):
|
|
390
|
+
"""Return annotated image as array."""
|
|
391
|
+
return np.asarray(self.im)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@TryExcept() # known issue https://github.com/ultralytics/yolov5/issues/5395
|
|
395
|
+
@plt_settings()
|
|
396
|
+
def plot_labels(boxes, cls, names=(), save_dir=Path(""), on_plot=None):
|
|
397
|
+
"""Save and plot image with no axis or spines."""
|
|
398
|
+
import pandas as pd
|
|
399
|
+
import seaborn as sn
|
|
400
|
+
|
|
401
|
+
# Plot dataset labels
|
|
402
|
+
LOGGER.info(f"Plotting labels to {save_dir / 'labels.jpg'}... ")
|
|
403
|
+
b = boxes.transpose() # classes, boxes
|
|
404
|
+
nc = int(cls.max() + 1) # number of classes
|
|
405
|
+
x = pd.DataFrame(b.transpose(), columns=["x", "y", "width", "height"])
|
|
406
|
+
|
|
407
|
+
# Seaborn correlogram
|
|
408
|
+
sn.pairplot(
|
|
409
|
+
x,
|
|
410
|
+
corner=True,
|
|
411
|
+
diag_kind="auto",
|
|
412
|
+
kind="hist",
|
|
413
|
+
diag_kws=dict(bins=50),
|
|
414
|
+
plot_kws=dict(pmax=0.9),
|
|
415
|
+
)
|
|
416
|
+
plt.savefig(save_dir / "labels_correlogram.jpg", dpi=200)
|
|
417
|
+
plt.close()
|
|
418
|
+
|
|
419
|
+
# Matplotlib labels
|
|
420
|
+
ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)[1].ravel()
|
|
421
|
+
y = ax[0].hist(cls, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8)
|
|
422
|
+
with contextlib.suppress(Exception): # color histogram bars by class
|
|
423
|
+
[
|
|
424
|
+
y[2].patches[i].set_color([x / 255 for x in colors(i)])
|
|
425
|
+
for i in range(nc)
|
|
426
|
+
] # known issue #3195
|
|
427
|
+
ax[0].set_ylabel("instances")
|
|
428
|
+
if 0 < len(names) < 30:
|
|
429
|
+
ax[0].set_xticks(range(len(names)))
|
|
430
|
+
ax[0].set_xticklabels(list(names.values()), rotation=90, fontsize=10)
|
|
431
|
+
else:
|
|
432
|
+
ax[0].set_xlabel("classes")
|
|
433
|
+
sn.histplot(x, x="x", y="y", ax=ax[2], bins=50, pmax=0.9)
|
|
434
|
+
sn.histplot(x, x="width", y="height", ax=ax[3], bins=50, pmax=0.9)
|
|
435
|
+
|
|
436
|
+
# Rectangles
|
|
437
|
+
boxes[:, 0:2] = 0.5 # center
|
|
438
|
+
boxes = xywh2xyxy(boxes) * 1000
|
|
439
|
+
img = Image.fromarray(np.ones((1000, 1000, 3), dtype=np.uint8) * 255)
|
|
440
|
+
for cls, box in zip(cls[:500], boxes[:500]):
|
|
441
|
+
ImageDraw.Draw(img).rectangle(
|
|
442
|
+
box, width=1, outline=colors(cls)
|
|
443
|
+
) # plot
|
|
444
|
+
ax[1].imshow(img)
|
|
445
|
+
ax[1].axis("off")
|
|
446
|
+
|
|
447
|
+
for a in [0, 1, 2, 3]:
|
|
448
|
+
for s in ["top", "right", "left", "bottom"]:
|
|
449
|
+
ax[a].spines[s].set_visible(False)
|
|
450
|
+
|
|
451
|
+
fname = save_dir / "labels.jpg"
|
|
452
|
+
plt.savefig(fname, dpi=200)
|
|
453
|
+
plt.close()
|
|
454
|
+
if on_plot:
|
|
455
|
+
on_plot(fname)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def save_one_box(
|
|
459
|
+
xyxy,
|
|
460
|
+
im,
|
|
461
|
+
file=Path("im.jpg"),
|
|
462
|
+
gain=1.02,
|
|
463
|
+
pad=10,
|
|
464
|
+
square=False,
|
|
465
|
+
BGR=False,
|
|
466
|
+
save=True,
|
|
467
|
+
):
|
|
468
|
+
"""Save image crop as {file} with crop size multiple {gain} and {pad} pixels. Save and/or return crop."""
|
|
469
|
+
b = xyxy2xywh(xyxy.view(-1, 4)) # boxes
|
|
470
|
+
if square:
|
|
471
|
+
b[:, 2:] = (
|
|
472
|
+
b[:, 2:].max(1)[0].unsqueeze(1)
|
|
473
|
+
) # attempt rectangle to square
|
|
474
|
+
b[:, 2:] = b[:, 2:] * gain + pad # box wh * gain + pad
|
|
475
|
+
xyxy = xywh2xyxy(b).long()
|
|
476
|
+
clip_boxes(xyxy, im.shape)
|
|
477
|
+
crop = im[
|
|
478
|
+
int(xyxy[0, 1]) : int(xyxy[0, 3]),
|
|
479
|
+
int(xyxy[0, 0]) : int(xyxy[0, 2]),
|
|
480
|
+
:: (1 if BGR else -1),
|
|
481
|
+
]
|
|
482
|
+
if save:
|
|
483
|
+
file.parent.mkdir(parents=True, exist_ok=True) # make directory
|
|
484
|
+
f = str(increment_path(file).with_suffix(".jpg"))
|
|
485
|
+
# cv2.imwrite(f, crop) # save BGR, https://github.com/ultralytics/yolov5/issues/7007 chroma subsampling issue
|
|
486
|
+
Image.fromarray(crop[..., ::-1]).save(
|
|
487
|
+
f, quality=95, subsampling=0
|
|
488
|
+
) # save RGB
|
|
489
|
+
return crop
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@threaded
|
|
493
|
+
def plot_images(
|
|
494
|
+
images,
|
|
495
|
+
batch_idx,
|
|
496
|
+
cls,
|
|
497
|
+
bboxes=np.zeros(0, dtype=np.float32),
|
|
498
|
+
masks=np.zeros(0, dtype=np.uint8),
|
|
499
|
+
kpts=np.zeros((0, 51), dtype=np.float32),
|
|
500
|
+
paths=None,
|
|
501
|
+
fname="images.jpg",
|
|
502
|
+
names=None,
|
|
503
|
+
on_plot=None,
|
|
504
|
+
):
|
|
505
|
+
# Plot image grid with labels
|
|
506
|
+
if isinstance(images, torch.Tensor):
|
|
507
|
+
images = images.cpu().float().numpy()
|
|
508
|
+
if isinstance(cls, torch.Tensor):
|
|
509
|
+
cls = cls.cpu().numpy()
|
|
510
|
+
if isinstance(bboxes, torch.Tensor):
|
|
511
|
+
bboxes = bboxes.cpu().numpy()
|
|
512
|
+
if isinstance(masks, torch.Tensor):
|
|
513
|
+
masks = masks.cpu().numpy().astype(int)
|
|
514
|
+
if isinstance(kpts, torch.Tensor):
|
|
515
|
+
kpts = kpts.cpu().numpy()
|
|
516
|
+
if isinstance(batch_idx, torch.Tensor):
|
|
517
|
+
batch_idx = batch_idx.cpu().numpy()
|
|
518
|
+
|
|
519
|
+
max_size = 1920 # max image size
|
|
520
|
+
max_subplots = 16 # max image subplots, i.e. 4x4
|
|
521
|
+
bs, _, h, w = images.shape # batch size, _, height, width
|
|
522
|
+
bs = min(bs, max_subplots) # limit plot images
|
|
523
|
+
ns = np.ceil(bs**0.5) # number of subplots (square)
|
|
524
|
+
if np.max(images[0]) <= 1:
|
|
525
|
+
images *= 255 # de-normalise (optional)
|
|
526
|
+
|
|
527
|
+
# Build Image
|
|
528
|
+
mosaic = np.full(
|
|
529
|
+
(int(ns * h), int(ns * w), 3), 255, dtype=np.uint8
|
|
530
|
+
) # init
|
|
531
|
+
for i, im in enumerate(images):
|
|
532
|
+
if i == max_subplots: # if last batch has fewer images than we expect
|
|
533
|
+
break
|
|
534
|
+
x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin
|
|
535
|
+
im = im.transpose(1, 2, 0)
|
|
536
|
+
mosaic[y : y + h, x : x + w, :] = im
|
|
537
|
+
|
|
538
|
+
# Resize (optional)
|
|
539
|
+
scale = max_size / ns / max(h, w)
|
|
540
|
+
if scale < 1:
|
|
541
|
+
h = math.ceil(scale * h)
|
|
542
|
+
w = math.ceil(scale * w)
|
|
543
|
+
mosaic = cv2.resize(mosaic, tuple(int(x * ns) for x in (w, h)))
|
|
544
|
+
|
|
545
|
+
# Annotate
|
|
546
|
+
fs = int((h + w) * ns * 0.01) # font size
|
|
547
|
+
annotator = Annotator(
|
|
548
|
+
mosaic,
|
|
549
|
+
line_width=round(fs / 10),
|
|
550
|
+
font_size=fs,
|
|
551
|
+
pil=True,
|
|
552
|
+
example=names,
|
|
553
|
+
)
|
|
554
|
+
for i in range(i + 1):
|
|
555
|
+
x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin
|
|
556
|
+
annotator.rectangle(
|
|
557
|
+
[x, y, x + w, y + h], None, (255, 255, 255), width=2
|
|
558
|
+
) # borders
|
|
559
|
+
if paths:
|
|
560
|
+
annotator.text(
|
|
561
|
+
(x + 5, y + 5),
|
|
562
|
+
text=Path(paths[i]).name[:40],
|
|
563
|
+
txt_color=(220, 220, 220),
|
|
564
|
+
) # filenames
|
|
565
|
+
if len(cls) > 0:
|
|
566
|
+
idx = batch_idx == i
|
|
567
|
+
classes = cls[idx].astype("int")
|
|
568
|
+
|
|
569
|
+
if len(bboxes):
|
|
570
|
+
boxes = xywh2xyxy(bboxes[idx, :4]).T
|
|
571
|
+
labels = bboxes.shape[1] == 4 # labels if no conf column
|
|
572
|
+
conf = (
|
|
573
|
+
None if labels else bboxes[idx, 4]
|
|
574
|
+
) # check for confidence presence (label vs pred)
|
|
575
|
+
|
|
576
|
+
if boxes.shape[1]:
|
|
577
|
+
if (
|
|
578
|
+
boxes.max() <= 1.01
|
|
579
|
+
): # if normalized with tolerance 0.01
|
|
580
|
+
boxes[[0, 2]] *= w # scale to pixels
|
|
581
|
+
boxes[[1, 3]] *= h
|
|
582
|
+
elif (
|
|
583
|
+
scale < 1
|
|
584
|
+
): # absolute coords need scale if image scales
|
|
585
|
+
boxes *= scale
|
|
586
|
+
boxes[[0, 2]] += x
|
|
587
|
+
boxes[[1, 3]] += y
|
|
588
|
+
for j, box in enumerate(boxes.T.tolist()):
|
|
589
|
+
c = classes[j]
|
|
590
|
+
color = colors(c)
|
|
591
|
+
c = names.get(c, c) if names else c
|
|
592
|
+
if labels or conf[j] > 0.25: # 0.25 conf thresh
|
|
593
|
+
label = f"{c}" if labels else f"{c} {conf[j]:.1f}"
|
|
594
|
+
annotator.box_label(box, label, color=color)
|
|
595
|
+
elif len(classes):
|
|
596
|
+
for c in classes:
|
|
597
|
+
color = colors(c)
|
|
598
|
+
c = names.get(c, c) if names else c
|
|
599
|
+
annotator.text(
|
|
600
|
+
(x, y), f"{c}", txt_color=color, box_style=True
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# Plot keypoints
|
|
604
|
+
if len(kpts):
|
|
605
|
+
kpts_ = kpts[idx].copy()
|
|
606
|
+
if len(kpts_):
|
|
607
|
+
if (
|
|
608
|
+
kpts_[..., 0].max() <= 1.01
|
|
609
|
+
or kpts_[..., 1].max() <= 1.01
|
|
610
|
+
): # if normalized with tolerance .01
|
|
611
|
+
kpts_[..., 0] *= w # scale to pixels
|
|
612
|
+
kpts_[..., 1] *= h
|
|
613
|
+
elif (
|
|
614
|
+
scale < 1
|
|
615
|
+
): # absolute coords need scale if image scales
|
|
616
|
+
kpts_ *= scale
|
|
617
|
+
kpts_[..., 0] += x
|
|
618
|
+
kpts_[..., 1] += y
|
|
619
|
+
for j in range(len(kpts_)):
|
|
620
|
+
if labels or conf[j] > 0.25: # 0.25 conf thresh
|
|
621
|
+
annotator.kpts(kpts_[j])
|
|
622
|
+
|
|
623
|
+
# Plot masks
|
|
624
|
+
if len(masks):
|
|
625
|
+
if idx.shape[0] == masks.shape[0]: # overlap_masks=False
|
|
626
|
+
image_masks = masks[idx]
|
|
627
|
+
else: # overlap_masks=True
|
|
628
|
+
image_masks = masks[[i]] # (1, 640, 640)
|
|
629
|
+
nl = idx.sum()
|
|
630
|
+
index = np.arange(nl).reshape((nl, 1, 1)) + 1
|
|
631
|
+
image_masks = np.repeat(image_masks, nl, axis=0)
|
|
632
|
+
image_masks = np.where(image_masks == index, 1.0, 0.0)
|
|
633
|
+
|
|
634
|
+
im = np.asarray(annotator.im).copy()
|
|
635
|
+
for j, box in enumerate(boxes.T.tolist()):
|
|
636
|
+
if labels or conf[j] > 0.25: # 0.25 conf thresh
|
|
637
|
+
color = colors(classes[j])
|
|
638
|
+
mh, mw = image_masks[j].shape
|
|
639
|
+
if mh != h or mw != w:
|
|
640
|
+
mask = image_masks[j].astype(np.uint8)
|
|
641
|
+
mask = cv2.resize(mask, (w, h))
|
|
642
|
+
mask = mask.astype(bool)
|
|
643
|
+
else:
|
|
644
|
+
mask = image_masks[j].astype(bool)
|
|
645
|
+
with contextlib.suppress(Exception):
|
|
646
|
+
im[y : y + h, x : x + w, :][mask] = (
|
|
647
|
+
im[y : y + h, x : x + w, :][mask] * 0.4
|
|
648
|
+
+ np.array(color) * 0.6
|
|
649
|
+
)
|
|
650
|
+
annotator.fromarray(im)
|
|
651
|
+
annotator.im.save(fname) # save
|
|
652
|
+
if on_plot:
|
|
653
|
+
on_plot(fname)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@plt_settings()
|
|
657
|
+
def plot_results(
|
|
658
|
+
file="path/to/results.csv",
|
|
659
|
+
dir="",
|
|
660
|
+
segment=False,
|
|
661
|
+
pose=False,
|
|
662
|
+
classify=False,
|
|
663
|
+
on_plot=None,
|
|
664
|
+
):
|
|
665
|
+
"""Plot training results.csv. Usage: from utils.plots import *; plot_results('path/to/results.csv')."""
|
|
666
|
+
import pandas as pd
|
|
667
|
+
|
|
668
|
+
save_dir = Path(file).parent if file else Path(dir)
|
|
669
|
+
if classify:
|
|
670
|
+
fig, ax = plt.subplots(2, 2, figsize=(6, 6), tight_layout=True)
|
|
671
|
+
index = [1, 4, 2, 3]
|
|
672
|
+
elif segment:
|
|
673
|
+
fig, ax = plt.subplots(2, 8, figsize=(18, 6), tight_layout=True)
|
|
674
|
+
index = [1, 2, 3, 4, 5, 6, 9, 10, 13, 14, 15, 16, 7, 8, 11, 12]
|
|
675
|
+
elif pose:
|
|
676
|
+
fig, ax = plt.subplots(2, 9, figsize=(21, 6), tight_layout=True)
|
|
677
|
+
index = [1, 2, 3, 4, 5, 6, 7, 10, 11, 14, 15, 16, 17, 18, 8, 9, 12, 13]
|
|
678
|
+
else:
|
|
679
|
+
fig, ax = plt.subplots(2, 5, figsize=(12, 6), tight_layout=True)
|
|
680
|
+
index = [1, 2, 3, 4, 5, 8, 9, 10, 6, 7]
|
|
681
|
+
ax = ax.ravel()
|
|
682
|
+
files = list(save_dir.glob("results*.csv"))
|
|
683
|
+
assert len(
|
|
684
|
+
files
|
|
685
|
+
), f"No results.csv files found in {save_dir.resolve()}, nothing to plot."
|
|
686
|
+
for f in files:
|
|
687
|
+
try:
|
|
688
|
+
data = pd.read_csv(f)
|
|
689
|
+
s = [x.strip() for x in data.columns]
|
|
690
|
+
x = data.values[:, 0]
|
|
691
|
+
for i, j in enumerate(index):
|
|
692
|
+
y = data.values[:, j].astype("float")
|
|
693
|
+
# y[y == 0] = np.nan # don't show zero values
|
|
694
|
+
ax[i].plot(
|
|
695
|
+
x, y, marker=".", label=f.stem, linewidth=2, markersize=8
|
|
696
|
+
) # actual results
|
|
697
|
+
ax[i].plot(
|
|
698
|
+
x,
|
|
699
|
+
gaussian_filter1d(y, sigma=3),
|
|
700
|
+
":",
|
|
701
|
+
label="smooth",
|
|
702
|
+
linewidth=2,
|
|
703
|
+
) # smoothing line
|
|
704
|
+
ax[i].set_title(s[j], fontsize=12)
|
|
705
|
+
# if j in [8, 9, 10]: # share train and val loss y axes
|
|
706
|
+
# ax[i].get_shared_y_axes().join(ax[i], ax[i - 5])
|
|
707
|
+
except Exception as e:
|
|
708
|
+
LOGGER.warning(f"WARNING: Plotting error for {f}: {e}")
|
|
709
|
+
ax[1].legend()
|
|
710
|
+
fname = save_dir / "results.png"
|
|
711
|
+
fig.savefig(fname, dpi=200)
|
|
712
|
+
plt.close()
|
|
713
|
+
if on_plot:
|
|
714
|
+
on_plot(fname)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def output_to_target(output, max_det=300):
|
|
718
|
+
"""Convert model output to target format [batch_id, class_id, x, y, w, h, conf] for plotting."""
|
|
719
|
+
targets = []
|
|
720
|
+
for i, o in enumerate(output):
|
|
721
|
+
box, conf, cls = o[:max_det, :6].cpu().split((4, 1, 1), 1)
|
|
722
|
+
j = torch.full((conf.shape[0], 1), i)
|
|
723
|
+
targets.append(torch.cat((j, cls, xyxy2xywh(box), conf), 1))
|
|
724
|
+
targets = torch.cat(targets, 0).numpy()
|
|
725
|
+
return targets[:, 0], targets[:, 1], targets[:, 2:]
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def feature_visualization(
|
|
729
|
+
x, module_type, stage, n=32, save_dir=Path("runs/detect/exp")
|
|
730
|
+
):
|
|
731
|
+
"""
|
|
732
|
+
Visualize feature maps of a given model module during inference.
|
|
733
|
+
|
|
734
|
+
Args:
|
|
735
|
+
x (torch.Tensor): Features to be visualized.
|
|
736
|
+
module_type (str): Module type.
|
|
737
|
+
stage (int): Module stage within the model.
|
|
738
|
+
n (int, optional): Maximum number of feature maps to plot. Defaults to 32.
|
|
739
|
+
save_dir (Path, optional): Directory to save results. Defaults to Path('runs/detect/exp').
|
|
740
|
+
"""
|
|
741
|
+
for m in ["Detect", "Pose", "Segment"]:
|
|
742
|
+
if m in module_type:
|
|
743
|
+
return
|
|
744
|
+
batch, channels, height, width = x.shape # batch, channels, height, width
|
|
745
|
+
if height > 1 and width > 1:
|
|
746
|
+
f = (
|
|
747
|
+
save_dir
|
|
748
|
+
/ f"stage{stage}_{module_type.split('.')[-1]}_features.png"
|
|
749
|
+
) # filename
|
|
750
|
+
|
|
751
|
+
blocks = torch.chunk(
|
|
752
|
+
x[0].cpu(), channels, dim=0
|
|
753
|
+
) # select batch index 0, block by channels
|
|
754
|
+
n = min(n, channels) # number of plots
|
|
755
|
+
fig, ax = plt.subplots(
|
|
756
|
+
math.ceil(n / 8), 8, tight_layout=True
|
|
757
|
+
) # 8 rows x n/8 cols
|
|
758
|
+
ax = ax.ravel()
|
|
759
|
+
plt.subplots_adjust(wspace=0.05, hspace=0.05)
|
|
760
|
+
for i in range(n):
|
|
761
|
+
ax[i].imshow(blocks[i].squeeze()) # cmap='gray'
|
|
762
|
+
ax[i].axis("off")
|
|
763
|
+
|
|
764
|
+
LOGGER.info(f"Saving {f}... ({n}/{channels})")
|
|
765
|
+
plt.savefig(f, dpi=300, bbox_inches="tight")
|
|
766
|
+
plt.close()
|
|
767
|
+
np.save(str(f.with_suffix(".npy")), x[0].cpu().numpy()) # npy save
|