Switch to unified view

a b/landmark_extraction/utils/general.py
1
# YOLOR general utils
2
3
import glob
4
import logging
5
import math
6
import os
7
import platform
8
import random
9
import re
10
import subprocess
11
import time
12
from pathlib import Path
13
14
import cv2
15
import numpy as np
16
import pandas as pd
17
import torch
18
import torchvision
19
import yaml
20
21
from utils.google_utils import gsutil_getsize
22
from utils.metrics import fitness
23
from utils.torch_utils import init_torch_seeds
24
25
# Settings
26
torch.set_printoptions(linewidth=320, precision=5, profile='long')
27
np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format})  # format short g, %precision=5
28
pd.options.display.max_columns = 10
29
cv2.setNumThreads(0)  # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)
30
os.environ['NUMEXPR_MAX_THREADS'] = str(min(os.cpu_count(), 8))  # NumExpr max threads
31
32
33
def set_logging(rank=-1):
34
    logging.basicConfig(
35
        format="%(message)s",
36
        level=logging.INFO if rank in [-1, 0] else logging.WARN)
37
38
39
def init_seeds(seed=0):
40
    # Initialize random number generator (RNG) seeds
41
    random.seed(seed)
42
    np.random.seed(seed)
43
    init_torch_seeds(seed)
44
45
46
def get_latest_run(search_dir='.'):
47
    # Return path to most recent 'last.pt' in /runs (i.e. to --resume from)
48
    last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True)
49
    return max(last_list, key=os.path.getctime) if last_list else ''
50
51
52
def isdocker():
53
    # Is environment a Docker container
54
    return Path('/workspace').exists()  # or Path('/.dockerenv').exists()
55
56
57
def emojis(str=''):
58
    # Return platform-dependent emoji-safe version of string
59
    return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str
60
61
62
def check_online():
63
    # Check internet connectivity
64
    import socket
65
    try:
66
        socket.create_connection(("1.1.1.1", 443), 5)  # check host accesability
67
        return True
68
    except OSError:
69
        return False
70
71
72
def check_git_status():
73
    # Recommend 'git pull' if code is out of date
74
    print(colorstr('github: '), end='')
75
    try:
76
        assert Path('.git').exists(), 'skipping check (not a git repository)'
77
        assert not isdocker(), 'skipping check (Docker image)'
78
        assert check_online(), 'skipping check (offline)'
79
80
        cmd = 'git fetch && git config --get remote.origin.url'
81
        url = subprocess.check_output(cmd, shell=True).decode().strip().rstrip('.git')  # github repo url
82
        branch = subprocess.check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip()  # checked out
83
        n = int(subprocess.check_output(f'git rev-list {branch}..origin/master --count', shell=True))  # commits behind
84
        if n > 0:
85
            s = f"⚠️ WARNING: code is out of date by {n} commit{'s' * (n > 1)}. " \
86
                f"Use 'git pull' to update or 'git clone {url}' to download latest."
87
        else:
88
            s = f'up to date with {url} ✅'
89
        print(emojis(s))  # emoji-safe
90
    except Exception as e:
91
        print(e)
92
93
94
def check_requirements(requirements='requirements.txt', exclude=()):
95
    # Check installed dependencies meet requirements (pass *.txt file or list of packages)
96
    import pkg_resources as pkg
97
    prefix = colorstr('red', 'bold', 'requirements:')
98
    if isinstance(requirements, (str, Path)):  # requirements.txt file
99
        file = Path(requirements)
100
        if not file.exists():
101
            print(f"{prefix} {file.resolve()} not found, check failed.")
102
            return
103
        requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(file.open()) if x.name not in exclude]
104
    else:  # list or tuple of packages
105
        requirements = [x for x in requirements if x not in exclude]
106
107
    n = 0  # number of packages updates
108
    for r in requirements:
109
        try:
110
            pkg.require(r)
111
        except Exception as e:  # DistributionNotFound or VersionConflict if requirements not met
112
            n += 1
113
            print(f"{prefix} {e.req} not found and is required by YOLOR, attempting auto-update...")
114
            print(subprocess.check_output(f"pip install '{e.req}'", shell=True).decode())
115
116
    if n:  # if packages updated
117
        source = file.resolve() if 'file' in locals() else requirements
118
        s = f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n" \
119
            f"{prefix} ⚠️ {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n"
120
        print(emojis(s))  # emoji-safe
121
122
123
def check_img_size(img_size, s=32):
124
    # Verify img_size is a multiple of stride s
125
    new_size = make_divisible(img_size, int(s))  # ceil gs-multiple
126
    if new_size != img_size:
127
        print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size))
128
    return new_size
129
130
131
def check_imshow():
132
    # Check if environment supports image displays
133
    try:
134
        assert not isdocker(), 'cv2.imshow() is disabled in Docker environments'
135
        cv2.imshow('test', np.zeros((1, 1, 3)))
136
        cv2.waitKey(1)
137
        cv2.destroyAllWindows()
138
        cv2.waitKey(1)
139
        return True
140
    except Exception as e:
141
        print(f'WARNING: Environment does not support cv2.imshow() or PIL Image.show() image displays\n{e}')
142
        return False
143
144
145
def check_file(file):
146
    # Search for file if not found
147
    if Path(file).is_file() or file == '':
148
        return file
149
    else:
150
        files = glob.glob('./**/' + file, recursive=True)  # find file
151
        assert len(files), f'File Not Found: {file}'  # assert file was found
152
        assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}"  # assert unique
153
        return files[0]  # return file
154
155
156
def check_dataset(dict):
157
    # Download dataset if not found locally
158
    val, s = dict.get('val'), dict.get('download')
159
    if val and len(val):
160
        val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])]  # val path
161
        if not all(x.exists() for x in val):
162
            print('\nWARNING: Dataset not found, nonexistent paths: %s' % [str(x) for x in val if not x.exists()])
163
            if s and len(s):  # download script
164
                print('Downloading %s ...' % s)
165
                if s.startswith('http') and s.endswith('.zip'):  # URL
166
                    f = Path(s).name  # filename
167
                    torch.hub.download_url_to_file(s, f)
168
                    r = os.system('unzip -q %s -d ../ && rm %s' % (f, f))  # unzip
169
                else:  # bash script
170
                    r = os.system(s)
171
                print('Dataset autodownload %s\n' % ('success' if r == 0 else 'failure'))  # analyze return value
172
            else:
173
                raise Exception('Dataset not found.')
174
175
176
def make_divisible(x, divisor):
177
    # Returns x evenly divisible by divisor
178
    return math.ceil(x / divisor) * divisor
179
180
181
def clean_str(s):
182
    # Cleans a string by replacing special characters with underscore _
183
    return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)
184
185
186
def one_cycle(y1=0.0, y2=1.0, steps=100):
187
    # lambda function for sinusoidal ramp from y1 to y2
188
    return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1
189
190
191
def colorstr(*input):
192
    # Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e.  colorstr('blue', 'hello world')
193
    *args, string = input if len(input) > 1 else ('blue', 'bold', input[0])  # color arguments, string
194
    colors = {'black': '\033[30m',  # basic colors
195
              'red': '\033[31m',
196
              'green': '\033[32m',
197
              'yellow': '\033[33m',
198
              'blue': '\033[34m',
199
              'magenta': '\033[35m',
200
              'cyan': '\033[36m',
201
              'white': '\033[37m',
202
              'bright_black': '\033[90m',  # bright colors
203
              'bright_red': '\033[91m',
204
              'bright_green': '\033[92m',
205
              'bright_yellow': '\033[93m',
206
              'bright_blue': '\033[94m',
207
              'bright_magenta': '\033[95m',
208
              'bright_cyan': '\033[96m',
209
              'bright_white': '\033[97m',
210
              'end': '\033[0m',  # misc
211
              'bold': '\033[1m',
212
              'underline': '\033[4m'}
213
    return ''.join(colors[x] for x in args) + f'{string}' + colors['end']
214
215
216
def labels_to_class_weights(labels, nc=80):
217
    # Get class weights (inverse frequency) from training labels
218
    if labels[0] is None:  # no labels loaded
219
        return torch.Tensor()
220
221
    labels = np.concatenate(labels, 0)  # labels.shape = (866643, 5) for COCO
222
    classes = labels[:, 0].astype(np.int)  # labels = [class xywh]
223
    weights = np.bincount(classes, minlength=nc)  # occurrences per class
224
225
    # Prepend gridpoint count (for uCE training)
226
    # gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum()  # gridpoints per image
227
    # weights = np.hstack([gpi * len(labels)  - weights.sum() * 9, weights * 9]) ** 0.5  # prepend gridpoints to start
228
229
    weights[weights == 0] = 1  # replace empty bins with 1
230
    weights = 1 / weights  # number of targets per class
231
    weights /= weights.sum()  # normalize
232
    return torch.from_numpy(weights)
233
234
235
def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):
236
    # Produces image weights based on class_weights and image contents
237
    class_counts = np.array([np.bincount(x[:, 0].astype(np.int), minlength=nc) for x in labels])
238
    image_weights = (class_weights.reshape(1, nc) * class_counts).sum(1)
239
    # index = random.choices(range(n), weights=image_weights, k=1)  # weight image sample
240
    return image_weights
241
242
243
def coco80_to_coco91_class():  # converts 80-index (val2014) to 91-index (paper)
244
    # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/
245
    # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')
246
    # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
247
    # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)]  # darknet to coco
248
    # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)]  # coco to darknet
249
    x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34,
250
         35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
251
         64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
252
    return x
253
254
255
def xyxy2xywh(x):
256
    # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right
257
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
258
    y[:, 0] = (x[:, 0] + x[:, 2]) / 2  # x center
259
    y[:, 1] = (x[:, 1] + x[:, 3]) / 2  # y center
260
    y[:, 2] = x[:, 2] - x[:, 0]  # width
261
    y[:, 3] = x[:, 3] - x[:, 1]  # height
262
    return y
263
264
265
def xywh2xyxy(x):
266
    # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
267
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
268
    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
269
    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
270
    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
271
    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
272
    return y
273
274
275
def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
276
    # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
277
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
278
    y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw  # top left x
279
    y[:, 1] = h * (x[:, 1] - x[:, 3] / 2) + padh  # top left y
280
    y[:, 2] = w * (x[:, 0] + x[:, 2] / 2) + padw  # bottom right x
281
    y[:, 3] = h * (x[:, 1] + x[:, 3] / 2) + padh  # bottom right y
282
    return y
283
284
285
def xyn2xy(x, w=640, h=640, padw=0, padh=0):
286
    # Convert normalized segments into pixel segments, shape (n,2)
287
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
288
    y[:, 0] = w * x[:, 0] + padw  # top left x
289
    y[:, 1] = h * x[:, 1] + padh  # top left y
290
    return y
291
292
293
def segment2box(segment, width=640, height=640):
294
    # Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy)
295
    x, y = segment.T  # segment xy
296
    inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)
297
    x, y, = x[inside], y[inside]
298
    return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4))  # xyxy
299
300
301
def segments2boxes(segments):
302
    # Convert segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh)
303
    boxes = []
304
    for s in segments:
305
        x, y = s.T  # segment xy
306
        boxes.append([x.min(), y.min(), x.max(), y.max()])  # cls, xyxy
307
    return xyxy2xywh(np.array(boxes))  # cls, xywh
308
309
310
def resample_segments(segments, n=1000):
311
    # Up-sample an (n,2) segment
312
    for i, s in enumerate(segments):
313
        x = np.linspace(0, len(s) - 1, n)
314
        xp = np.arange(len(s))
315
        segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T  # segment xy
316
    return segments
317
318
319
def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
320
    # Rescale coords (xyxy) from img1_shape to img0_shape
321
    if ratio_pad is None:  # calculate from img0_shape
322
        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain  = old / new
323
        pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2  # wh padding
324
    else:
325
        gain = ratio_pad[0][0]
326
        pad = ratio_pad[1]
327
328
    coords[:, [0, 2]] -= pad[0]  # x padding
329
    coords[:, [1, 3]] -= pad[1]  # y padding
330
    coords[:, :4] /= gain
331
    clip_coords(coords, img0_shape)
332
    return coords
333
334
335
def clip_coords(boxes, img_shape):
336
    # Clip bounding xyxy bounding boxes to image shape (height, width)
337
    boxes[:, 0].clamp_(0, img_shape[1])  # x1
338
    boxes[:, 1].clamp_(0, img_shape[0])  # y1
339
    boxes[:, 2].clamp_(0, img_shape[1])  # x2
340
    boxes[:, 3].clamp_(0, img_shape[0])  # y2
341
342
343
def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):
344
    # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4
345
    box2 = box2.T
346
347
    # Get the coordinates of bounding boxes
348
    if x1y1x2y2:  # x1, y1, x2, y2 = box1
349
        b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
350
        b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
351
    else:  # transform from xywh to xyxy
352
        b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2
353
        b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2
354
        b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2
355
        b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2
356
357
    # Intersection area
358
    inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
359
            (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
360
361
    # Union Area
362
    w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
363
    w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
364
    union = w1 * h1 + w2 * h2 - inter + eps
365
366
    iou = inter / union
367
368
    if GIoU or DIoU or CIoU:
369
        cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)  # convex (smallest enclosing box) width
370
        ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)  # convex height
371
        if CIoU or DIoU:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
372
            c2 = cw ** 2 + ch ** 2 + eps  # convex diagonal squared
373
            rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 +
374
                    (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4  # center distance squared
375
            if DIoU:
376
                return iou - rho2 / c2  # DIoU
377
            elif CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
378
                v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / (h2 + eps)) - torch.atan(w1 / (h1 + eps)), 2)
379
                with torch.no_grad():
380
                    alpha = v / (v - iou + (1 + eps))
381
                return iou - (rho2 / c2 + v * alpha)  # CIoU
382
        else:  # GIoU https://arxiv.org/pdf/1902.09630.pdf
383
            c_area = cw * ch + eps  # convex area
384
            return iou - (c_area - union) / c_area  # GIoU
385
    else:
386
        return iou  # IoU
387
388
389
390
391
def bbox_alpha_iou(box1, box2, x1y1x2y2=False, GIoU=False, DIoU=False, CIoU=False, alpha=2, eps=1e-9):
392
    # Returns tsqrt_he IoU of box1 to box2. box1 is 4, box2 is nx4
393
    box2 = box2.T
394
395
    # Get the coordinates of bounding boxes
396
    if x1y1x2y2:  # x1, y1, x2, y2 = box1
397
        b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
398
        b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
399
    else:  # transform from xywh to xyxy
400
        b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2
401
        b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2
402
        b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2
403
        b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2
404
405
    # Intersection area
406
    inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \
407
            (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)
408
409
    # Union Area
410
    w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps
411
    w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps
412
    union = w1 * h1 + w2 * h2 - inter + eps
413
414
    # change iou into pow(iou+eps)
415
    # iou = inter / union
416
    iou = torch.pow(inter/union + eps, alpha)
417
    # beta = 2 * alpha
418
    if GIoU or DIoU or CIoU:
419
        cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)  # convex (smallest enclosing box) width
420
        ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)  # convex height
421
        if CIoU or DIoU:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
422
            c2 = (cw ** 2 + ch ** 2) ** alpha + eps  # convex diagonal
423
            rho_x = torch.abs(b2_x1 + b2_x2 - b1_x1 - b1_x2)
424
            rho_y = torch.abs(b2_y1 + b2_y2 - b1_y1 - b1_y2)
425
            rho2 = ((rho_x ** 2 + rho_y ** 2) / 4) ** alpha  # center distance
426
            if DIoU:
427
                return iou - rho2 / c2  # DIoU
428
            elif CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
429
                v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)
430
                with torch.no_grad():
431
                    alpha_ciou = v / ((1 + eps) - inter / union + v)
432
                # return iou - (rho2 / c2 + v * alpha_ciou)  # CIoU
433
                return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha))  # CIoU
434
        else:  # GIoU https://arxiv.org/pdf/1902.09630.pdf
435
            # c_area = cw * ch + eps  # convex area
436
            # return iou - (c_area - union) / c_area  # GIoU
437
            c_area = torch.max(cw * ch + eps, union) # convex area
438
            return iou - torch.pow((c_area - union) / c_area + eps, alpha)  # GIoU
439
    else:
440
        return iou # torch.log(iou+eps) or iou
441
442
443
def box_iou(box1, box2):
444
    # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
445
    """
446
    Return intersection-over-union (Jaccard index) of boxes.
447
    Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
448
    Arguments:
449
        box1 (Tensor[N, 4])
450
        box2 (Tensor[M, 4])
451
    Returns:
452
        iou (Tensor[N, M]): the NxM matrix containing the pairwise
453
            IoU values for every element in boxes1 and boxes2
454
    """
455
456
    def box_area(box):
457
        # box = 4xn
458
        return (box[2] - box[0]) * (box[3] - box[1])
459
460
    area1 = box_area(box1.T)
461
    area2 = box_area(box2.T)
462
463
    # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
464
    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
465
    return inter / (area1[:, None] + area2 - inter)  # iou = inter / (area1 + area2 - inter)
466
467
468
def wh_iou(wh1, wh2):
469
    # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2
470
    wh1 = wh1[:, None]  # [N,1,2]
471
    wh2 = wh2[None]  # [1,M,2]
472
    inter = torch.min(wh1, wh2).prod(2)  # [N,M]
473
    return inter / (wh1.prod(2) + wh2.prod(2) - inter)  # iou = inter / (area1 + area2 - inter)
474
475
476
def box_giou(box1, box2):
477
    """
478
    Return generalized intersection-over-union (Jaccard index) between two sets of boxes.
479
    Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with
480
    ``0 <= x1 < x2`` and ``0 <= y1 < y2``.
481
    Args:
482
        boxes1 (Tensor[N, 4]): first set of boxes
483
        boxes2 (Tensor[M, 4]): second set of boxes
484
    Returns:
485
        Tensor[N, M]: the NxM matrix containing the pairwise generalized IoU values
486
        for every element in boxes1 and boxes2
487
    """
488
489
    def box_area(box):
490
        # box = 4xn
491
        return (box[2] - box[0]) * (box[3] - box[1])
492
493
    area1 = box_area(box1.T)
494
    area2 = box_area(box2.T)
495
    
496
    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
497
    union = (area1[:, None] + area2 - inter)
498
499
    iou = inter / union
500
501
    lti = torch.min(box1[:, None, :2], box2[:, :2])
502
    rbi = torch.max(box1[:, None, 2:], box2[:, 2:])
503
504
    whi = (rbi - lti).clamp(min=0)  # [N,M,2]
505
    areai = whi[:, :, 0] * whi[:, :, 1]
506
507
    return iou - (areai - union) / areai
508
509
510
def box_ciou(box1, box2, eps: float = 1e-7):
511
    """
512
    Return complete intersection-over-union (Jaccard index) between two sets of boxes.
513
    Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with
514
    ``0 <= x1 < x2`` and ``0 <= y1 < y2``.
515
    Args:
516
        boxes1 (Tensor[N, 4]): first set of boxes
517
        boxes2 (Tensor[M, 4]): second set of boxes
518
        eps (float, optional): small number to prevent division by zero. Default: 1e-7
519
    Returns:
520
        Tensor[N, M]: the NxM matrix containing the pairwise complete IoU values
521
        for every element in boxes1 and boxes2
522
    """
523
524
    def box_area(box):
525
        # box = 4xn
526
        return (box[2] - box[0]) * (box[3] - box[1])
527
528
    area1 = box_area(box1.T)
529
    area2 = box_area(box2.T)
530
    
531
    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
532
    union = (area1[:, None] + area2 - inter)
533
534
    iou = inter / union
535
536
    lti = torch.min(box1[:, None, :2], box2[:, :2])
537
    rbi = torch.max(box1[:, None, 2:], box2[:, 2:])
538
539
    whi = (rbi - lti).clamp(min=0)  # [N,M,2]
540
    diagonal_distance_squared = (whi[:, :, 0] ** 2) + (whi[:, :, 1] ** 2) + eps
541
542
    # centers of boxes
543
    x_p = (box1[:, None, 0] + box1[:, None, 2]) / 2
544
    y_p = (box1[:, None, 1] + box1[:, None, 3]) / 2
545
    x_g = (box2[:, 0] + box2[:, 2]) / 2
546
    y_g = (box2[:, 1] + box2[:, 3]) / 2
547
    # The distance between boxes' centers squared.
548
    centers_distance_squared = (x_p - x_g) ** 2 + (y_p - y_g) ** 2
549
550
    w_pred = box1[:, None, 2] - box1[:, None, 0]
551
    h_pred = box1[:, None, 3] - box1[:, None, 1]
552
553
    w_gt = box2[:, 2] - box2[:, 0]
554
    h_gt = box2[:, 3] - box2[:, 1]
555
556
    v = (4 / (torch.pi ** 2)) * torch.pow((torch.atan(w_gt / h_gt) - torch.atan(w_pred / h_pred)), 2)
557
    with torch.no_grad():
558
        alpha = v / (1 - iou + v + eps)
559
    return iou - (centers_distance_squared / diagonal_distance_squared) - alpha * v
560
561
562
def box_diou(box1, box2, eps: float = 1e-7):
563
    """
564
    Return distance intersection-over-union (Jaccard index) between two sets of boxes.
565
    Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with
566
    ``0 <= x1 < x2`` and ``0 <= y1 < y2``.
567
    Args:
568
        boxes1 (Tensor[N, 4]): first set of boxes
569
        boxes2 (Tensor[M, 4]): second set of boxes
570
        eps (float, optional): small number to prevent division by zero. Default: 1e-7
571
    Returns:
572
        Tensor[N, M]: the NxM matrix containing the pairwise distance IoU values
573
        for every element in boxes1 and boxes2
574
    """
575
576
    def box_area(box):
577
        # box = 4xn
578
        return (box[2] - box[0]) * (box[3] - box[1])
579
580
    area1 = box_area(box1.T)
581
    area2 = box_area(box2.T)
582
    
583
    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
584
    union = (area1[:, None] + area2 - inter)
585
586
    iou = inter / union
587
588
    lti = torch.min(box1[:, None, :2], box2[:, :2])
589
    rbi = torch.max(box1[:, None, 2:], box2[:, 2:])
590
591
    whi = (rbi - lti).clamp(min=0)  # [N,M,2]
592
    diagonal_distance_squared = (whi[:, :, 0] ** 2) + (whi[:, :, 1] ** 2) + eps
593
594
    # centers of boxes
595
    x_p = (box1[:, None, 0] + box1[:, None, 2]) / 2
596
    y_p = (box1[:, None, 1] + box1[:, None, 3]) / 2
597
    x_g = (box2[:, 0] + box2[:, 2]) / 2
598
    y_g = (box2[:, 1] + box2[:, 3]) / 2
599
    # The distance between boxes' centers squared.
600
    centers_distance_squared = (x_p - x_g) ** 2 + (y_p - y_g) ** 2
601
602
    # The distance IoU is the IoU penalized by a normalized
603
    # distance between boxes' centers squared.
604
    return iou - (centers_distance_squared / diagonal_distance_squared)
605
606
607
def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,
608
                        labels=()):
609
    """Runs Non-Maximum Suppression (NMS) on inference results
610
611
    Returns:
612
         list of detections, on (n,6) tensor per image [xyxy, conf, cls]
613
    """
614
615
    nc = prediction.shape[2] - 5  # number of classes
616
    xc = prediction[..., 4] > conf_thres  # candidates
617
618
    # Settings
619
    min_wh, max_wh = 2, 4096  # (pixels) minimum and maximum box width and height
620
    max_det = 300  # maximum number of detections per image
621
    max_nms = 30000  # maximum number of boxes into torchvision.ops.nms()
622
    time_limit = 10.0  # seconds to quit after
623
    redundant = True  # require redundant detections
624
    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)
625
    merge = False  # use merge-NMS
626
627
    t = time.time()
628
    output = [torch.zeros((0, 6), device=prediction.device)] * prediction.shape[0]
629
    for xi, x in enumerate(prediction):  # image index, image inference
630
        # Apply constraints
631
        # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0  # width-height
632
        x = x[xc[xi]]  # confidence
633
634
        # Cat apriori labels if autolabelling
635
        if labels and len(labels[xi]):
636
            l = labels[xi]
637
            v = torch.zeros((len(l), nc + 5), device=x.device)
638
            v[:, :4] = l[:, 1:5]  # box
639
            v[:, 4] = 1.0  # conf
640
            v[range(len(l)), l[:, 0].long() + 5] = 1.0  # cls
641
            x = torch.cat((x, v), 0)
642
643
        # If none remain process next image
644
        if not x.shape[0]:
645
            continue
646
647
        # Compute conf
648
        x[:, 5:] *= x[:, 4:5]  # conf = obj_conf * cls_conf
649
650
        # Box (center x, center y, width, height) to (x1, y1, x2, y2)
651
        box = xywh2xyxy(x[:, :4])
652
653
        # Detections matrix nx6 (xyxy, conf, cls)
654
        if multi_label:
655
            i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
656
            x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
657
        else:  # best class only
658
            conf, j = x[:, 5:].max(1, keepdim=True)
659
            x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]
660
661
        # Filter by class
662
        if classes is not None:
663
            x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
664
665
        # Apply finite constraint
666
        # if not torch.isfinite(x).all():
667
        #     x = x[torch.isfinite(x).all(1)]
668
669
        # Check shape
670
        n = x.shape[0]  # number of boxes
671
        if not n:  # no boxes
672
            continue
673
        elif n > max_nms:  # excess boxes
674
            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence
675
676
        # Batched NMS
677
        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes
678
        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores
679
        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS
680
        if i.shape[0] > max_det:  # limit detections
681
            i = i[:max_det]
682
        if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)
683
            # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
684
            iou = box_iou(boxes[i], boxes) > iou_thres  # iou matrix
685
            weights = iou * scores[None]  # box weights
686
            x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes
687
            if redundant:
688
                i = i[iou.sum(1) > 1]  # require redundancy
689
690
        output[xi] = x[i]
691
        if (time.time() - t) > time_limit:
692
            print(f'WARNING: NMS time limit {time_limit}s exceeded')
693
            break  # time limit exceeded
694
695
    return output
696
697
698
def non_max_suppression_kpt(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,
699
                        labels=(), kpt_label=False, nc=None, nkpt=None):
700
    """Runs Non-Maximum Suppression (NMS) on inference results
701
702
    Returns:
703
         list of detections, on (n,6) tensor per image [xyxy, conf, cls]
704
    """
705
    if nc is None:
706
        nc = prediction.shape[2] - 5  if not kpt_label else prediction.shape[2] - 56 # number of classes
707
    xc = prediction[..., 4] > conf_thres  # candidates
708
709
    # Settings
710
    min_wh, max_wh = 2, 4096  # (pixels) minimum and maximum box width and height
711
    max_det = 300  # maximum number of detections per image
712
    max_nms = 30000  # maximum number of boxes into torchvision.ops.nms()
713
    time_limit = 10.0  # seconds to quit after
714
    redundant = True  # require redundant detections
715
    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)
716
    merge = False  # use merge-NMS
717
718
    t = time.time()
719
    output = [torch.zeros((0,6), device=prediction.device)] * prediction.shape[0]
720
    for xi, x in enumerate(prediction):  # image index, image inference
721
        # Apply constraints
722
        # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0  # width-height
723
        x = x[xc[xi]]  # confidence
724
725
        # Cat apriori labels if autolabelling
726
        if labels and len(labels[xi]):
727
            l = labels[xi]
728
            v = torch.zeros((len(l), nc + 5), device=x.device)
729
            v[:, :4] = l[:, 1:5]  # box
730
            v[:, 4] = 1.0  # conf
731
            v[range(len(l)), l[:, 0].long() + 5] = 1.0  # cls
732
            x = torch.cat((x, v), 0)
733
734
        # If none remain process next image
735
        if not x.shape[0]:
736
            continue
737
738
        # Compute conf
739
        x[:, 5:5+nc] *= x[:, 4:5]  # conf = obj_conf * cls_conf
740
741
        # Box (center x, center y, width, height) to (x1, y1, x2, y2)
742
        box = xywh2xyxy(x[:, :4])
743
744
        # Detections matrix nx6 (xyxy, conf, cls)
745
        if multi_label:
746
            i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
747
            x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
748
        else:  # best class only
749
            if not kpt_label:
750
                conf, j = x[:, 5:].max(1, keepdim=True)
751
                x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]
752
            else:
753
                kpts = x[:, 6:]
754
                conf, j = x[:, 5:6].max(1, keepdim=True)
755
                x = torch.cat((box, conf, j.float(), kpts), 1)[conf.view(-1) > conf_thres]
756
757
758
        # Filter by class
759
        if classes is not None:
760
            x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
761
762
        # Apply finite constraint
763
        # if not torch.isfinite(x).all():
764
        #     x = x[torch.isfinite(x).all(1)]
765
766
        # Check shape
767
        n = x.shape[0]  # number of boxes
768
        if not n:  # no boxes
769
            continue
770
        elif n > max_nms:  # excess boxes
771
            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence
772
773
        # Batched NMS
774
        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes
775
        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores
776
        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS
777
        if i.shape[0] > max_det:  # limit detections
778
            i = i[:max_det]
779
        if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)
780
            # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
781
            iou = box_iou(boxes[i], boxes) > iou_thres  # iou matrix
782
            weights = iou * scores[None]  # box weights
783
            x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes
784
            if redundant:
785
                i = i[iou.sum(1) > 1]  # require redundancy
786
787
        output[xi] = x[i]
788
        if (time.time() - t) > time_limit:
789
            print(f'WARNING: NMS time limit {time_limit}s exceeded')
790
            break  # time limit exceeded
791
792
    return output
793
794
795
def strip_optimizer(device='cpu',f='yolov7-w6-pose.pt', s=''):  # from utils.general import *; strip_optimizer()
796
    # Strip optimizer from 'f' to finalize training, optionally save as 's'
797
    x = torch.load(f, map_location=torch.device(device))
798
    if x.get('ema'):
799
        x['model'] = x['ema']  # replace model with ema
800
    for k in 'optimizer', 'training_results', 'wandb_id', 'ema', 'updates':  # keys
801
        x[k] = None
802
    x['epoch'] = -1
803
    if device!='cpu':
804
        x['model'].half()  # to FP16
805
    else:
806
        x['model'].float()
807
    for p in x['model'].parameters():
808
        p.requires_grad = False
809
    torch.save(x, s or f)
810
    mb = os.path.getsize(s or f) / 1E6  # filesize
811
    print(f"Optimizer stripped from {f},{(' saved as %s,' % s) if s else ''} {mb:.1f}MB")
812
813
814
def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):
815
    # Print mutation results to evolve.txt (for use with train.py --evolve)
816
    a = '%10s' * len(hyp) % tuple(hyp.keys())  # hyperparam keys
817
    b = '%10.3g' * len(hyp) % tuple(hyp.values())  # hyperparam values
818
    c = '%10.4g' * len(results) % results  # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)
819
    print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c))
820
821
    if bucket:
822
        url = 'gs://%s/evolve.txt' % bucket
823
        if gsutil_getsize(url) > (os.path.getsize('evolve.txt') if os.path.exists('evolve.txt') else 0):
824
            os.system('gsutil cp %s .' % url)  # download evolve.txt if larger than local
825
826
    with open('evolve.txt', 'a') as f:  # append result
827
        f.write(c + b + '\n')
828
    x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0)  # load unique rows
829
    x = x[np.argsort(-fitness(x))]  # sort
830
    np.savetxt('evolve.txt', x, '%10.3g')  # save sort by fitness
831
832
    # Save yaml
833
    for i, k in enumerate(hyp.keys()):
834
        hyp[k] = float(x[0, i + 7])
835
    with open(yaml_file, 'w') as f:
836
        results = tuple(x[0, :7])
837
        c = '%10.4g' * len(results) % results  # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)
838
        f.write('# Hyperparameter Evolution Results\n# Generations: %g\n# Metrics: ' % len(x) + c + '\n\n')
839
        yaml.dump(hyp, f, sort_keys=False)
840
841
    if bucket:
842
        os.system('gsutil cp evolve.txt %s gs://%s' % (yaml_file, bucket))  # upload
843
844
845
def apply_classifier(x, model, img, im0):
846
    # applies a second stage classifier to yolo outputs
847
    im0 = [im0] if isinstance(im0, np.ndarray) else im0
848
    for i, d in enumerate(x):  # per image
849
        if d is not None and len(d):
850
            d = d.clone()
851
852
            # Reshape and pad cutouts
853
            b = xyxy2xywh(d[:, :4])  # boxes
854
            b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1)  # rectangle to square
855
            b[:, 2:] = b[:, 2:] * 1.3 + 30  # pad
856
            d[:, :4] = xywh2xyxy(b).long()
857
858
            # Rescale boxes from img_size to im0 size
859
            scale_coords(img.shape[2:], d[:, :4], im0[i].shape)
860
861
            # Classes
862
            pred_cls1 = d[:, 5].long()
863
            ims = []
864
            for j, a in enumerate(d):  # per item
865
                cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]
866
                im = cv2.resize(cutout, (224, 224))  # BGR
867
                # cv2.imwrite('test%i.jpg' % j, cutout)
868
869
                im = im[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
870
                im = np.ascontiguousarray(im, dtype=np.float32)  # uint8 to float32
871
                im /= 255.0  # 0 - 255 to 0.0 - 1.0
872
                ims.append(im)
873
874
            pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1)  # classifier prediction
875
            x[i] = x[i][pred_cls1 == pred_cls2]  # retain matching class detections
876
877
    return x
878
879
880
def increment_path(path, exist_ok=True, sep=''):
881
    # Increment path, i.e. runs/exp --> runs/exp{sep}0, runs/exp{sep}1 etc.
882
    path = Path(path)  # os-agnostic
883
    if (path.exists() and exist_ok) or (not path.exists()):
884
        return str(path)
885
    else:
886
        dirs = glob.glob(f"{path}{sep}*")  # similar paths
887
        matches = [re.search(rf"%s{sep}(\d+)" % path.stem, d) for d in dirs]
888
        i = [int(m.groups()[0]) for m in matches if m]  # indices
889
        n = max(i) + 1 if i else 2  # increment number
890
        return f"{path}{sep}{n}"  # update path