Team Ai
Apppublic

Adamfan/objectdetection

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
general.py893 linesDownload Raw Back to utils
1# YOLOR general utils2 3import glob4import logging5import math6import os7import platform8import random9import re10import subprocess11import time12from pathlib import Path13 14import cv215import numpy as np16import pandas as pd17import torch18import torchvision19import yaml20 21from utils.google_utils import gsutil_getsize22from utils.metrics import fitness23from utils.torch_utils import init_torch_seeds24 25# Settings26torch.set_printoptions(linewidth=320, precision=5, profile='long')27np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format})  # format short g, %precision=528pd.options.display.max_columns = 1029cv2.setNumThreads(0)  # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)30os.environ['NUMEXPR_MAX_THREADS'] = str(min(os.cpu_count(), 8))  # NumExpr max threads31 32 33def 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 39def init_seeds(seed=0):40    # Initialize random number generator (RNG) seeds41    random.seed(seed)42    np.random.seed(seed)43    init_torch_seeds(seed)44 45 46def 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 52def isdocker():53    # Is environment a Docker container54    return Path('/workspace').exists()  # or Path('/.dockerenv').exists()55 56 57def emojis(str=''):58    # Return platform-dependent emoji-safe version of string59    return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str60 61 62def check_online():63    # Check internet connectivity64    import socket65    try:66        socket.create_connection(("1.1.1.1", 443), 5)  # check host accesability67        return True68    except OSError:69        return False70 71 72def check_git_status():73    # Recommend 'git pull' if code is out of date74    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 url82        branch = subprocess.check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip()  # checked out83        n = int(subprocess.check_output(f'git rev-list {branch}..origin/master --count', shell=True))  # commits behind84        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-safe90    except Exception as e:91        print(e)92 93 94def check_requirements(requirements='requirements.txt', exclude=()):95    # Check installed dependencies meet requirements (pass *.txt file or list of packages)96    import pkg_resources as pkg97    prefix = colorstr('red', 'bold', 'requirements:')98    if isinstance(requirements, (str, Path)):  # requirements.txt file99        file = Path(requirements)100        if not file.exists():101            print(f"{prefix} {file.resolve()} not found, check failed.")102            return103        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 packages105        requirements = [x for x in requirements if x not in exclude]106 107    n = 0  # number of packages updates108    for r in requirements:109        try:110            pkg.require(r)111        except Exception as e:  # DistributionNotFound or VersionConflict if requirements not met112            n += 1113            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 updated117        source = file.resolve() if 'file' in locals() else requirements118        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-safe121 122 123def check_img_size(img_size, s=32):124    # Verify img_size is a multiple of stride s125    new_size = make_divisible(img_size, int(s))  # ceil gs-multiple126    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_size129 130 131def check_imshow():132    # Check if environment supports image displays133    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 True140    except Exception as e:141        print(f'WARNING: Environment does not support cv2.imshow() or PIL Image.show() image displays\n{e}')142        return False143 144 145def check_file(file):146    # Search for file if not found147    if Path(file).is_file() or file == '':148        return file149    else:150        files = glob.glob('./**/' + file, recursive=True)  # find file151        assert len(files), f'File Not Found: {file}'  # assert file was found152        assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}"  # assert unique153        return files[0]  # return file154 155 156def check_dataset(dict):157    # Download dataset if not found locally158    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 path161        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 script164                print('Downloading %s ...' % s)165                if s.startswith('http') and s.endswith('.zip'):  # URL166                    f = Path(s).name  # filename167                    torch.hub.download_url_to_file(s, f)168                    r = os.system('unzip -q %s -d ../ && rm %s' % (f, f))  # unzip169                else:  # bash script170                    r = os.system(s)171                print('Dataset autodownload %s\n' % ('success' if r == 0 else 'failure'))  # analyze return value172            else:173                raise Exception('Dataset not found.')174 175 176def make_divisible(x, divisor):177    # Returns x evenly divisible by divisor178    return math.ceil(x / divisor) * divisor179 180 181def clean_str(s):182    # Cleans a string by replacing special characters with underscore _183    return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)184 185 186def one_cycle(y1=0.0, y2=1.0, steps=100):187    # lambda function for sinusoidal ramp from y1 to y2188    return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1189 190 191def 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, string194    colors = {'black': '\033[30m',  # basic colors195              '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 colors203              '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',  # misc211              'bold': '\033[1m',212              'underline': '\033[4m'}213    return ''.join(colors[x] for x in args) + f'{string}' + colors['end']214 215 216def labels_to_class_weights(labels, nc=80):217    # Get class weights (inverse frequency) from training labels218    if labels[0] is None:  # no labels loaded219        return torch.Tensor()220 221    labels = np.concatenate(labels, 0)  # labels.shape = (866643, 5) for COCO222    classes = labels[:, 0].astype(np.int32)  # labels = [class xywh]223    weights = np.bincount(classes, minlength=nc)  # occurrences per class224 225    # Prepend gridpoint count (for uCE training)226    # gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum()  # gridpoints per image227    # weights = np.hstack([gpi * len(labels)  - weights.sum() * 9, weights * 9]) ** 0.5  # prepend gridpoints to start228 229    weights[weights == 0] = 1  # replace empty bins with 1230    weights = 1 / weights  # number of targets per class231    weights /= weights.sum()  # normalize232    return torch.from_numpy(weights)233 234 235def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):236    # Produces image weights based on class_weights and image contents237    class_counts = np.array([np.bincount(x[:, 0].astype(np.int32), 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 sample240    return image_weights241 242 243def 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 coco248    # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)]  # coco to darknet249    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 x253 254 255def xyxy2xywh(x):256    # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right257    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)258    y[:, 0] = (x[:, 0] + x[:, 2]) / 2  # x center259    y[:, 1] = (x[:, 1] + x[:, 3]) / 2  # y center260    y[:, 2] = x[:, 2] - x[:, 0]  # width261    y[:, 3] = x[:, 3] - x[:, 1]  # height262    return y263 264 265def xywh2xyxy(x):266    # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right267    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)268    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x269    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y270    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x271    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y272    return y273 274 275def 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-right277    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)278    y[:, 0] = w * (x[:, 0] - x[:, 2] / 2) + padw  # top left x279    y[:, 1] = h * (x[:, 1] - x[:, 3] / 2) + padh  # top left y280    y[:, 2] = w * (x[:, 0] + x[:, 2] / 2) + padw  # bottom right x281    y[:, 3] = h * (x[:, 1] + x[:, 3] / 2) + padh  # bottom right y282    return y283 284 285def 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 x289    y[:, 1] = h * x[:, 1] + padh  # top left y290    return y291 292 293def 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 xy296    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))  # xyxy299 300 301def 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 xy306        boxes.append([x.min(), y.min(), x.max(), y.max()])  # cls, xyxy307    return xyxy2xywh(np.array(boxes))  # cls, xywh308 309 310def resample_segments(segments, n=1000):311    # Up-sample an (n,2) segment312    for i, s in enumerate(segments):313        s = np.concatenate((s, s[0:1, :]), axis=0)314        x = np.linspace(0, len(s) - 1, n)315        xp = np.arange(len(s))316        segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T  # segment xy317    return segments318 319 320def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):321    # Rescale coords (xyxy) from img1_shape to img0_shape322    if ratio_pad is None:  # calculate from img0_shape323        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain  = old / new324        pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2  # wh padding325    else:326        gain = ratio_pad[0][0]327        pad = ratio_pad[1]328 329    coords[:, [0, 2]] -= pad[0]  # x padding330    coords[:, [1, 3]] -= pad[1]  # y padding331    coords[:, :4] /= gain332    clip_coords(coords, img0_shape)333    return coords334 335 336def clip_coords(boxes, img_shape):337    # Clip bounding xyxy bounding boxes to image shape (height, width)338    boxes[:, 0].clamp_(0, img_shape[1])  # x1339    boxes[:, 1].clamp_(0, img_shape[0])  # y1340    boxes[:, 2].clamp_(0, img_shape[1])  # x2341    boxes[:, 3].clamp_(0, img_shape[0])  # y2342 343 344def bbox_iou(box1, box2, x1y1x2y2=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7):345    # Returns the IoU of box1 to box2. box1 is 4, box2 is nx4346    box2 = box2.T347 348    # Get the coordinates of bounding boxes349    if x1y1x2y2:  # x1, y1, x2, y2 = box1350        b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]351        b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]352    else:  # transform from xywh to xyxy353        b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2354        b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2355        b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2356        b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2357 358    # Intersection area359    inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \360            (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)361 362    # Union Area363    w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps364    w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps365    union = w1 * h1 + w2 * h2 - inter + eps366 367    iou = inter / union368 369    if GIoU or DIoU or CIoU:370        cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)  # convex (smallest enclosing box) width371        ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)  # convex height372        if CIoU or DIoU:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1373            c2 = cw ** 2 + ch ** 2 + eps  # convex diagonal squared374            rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 +375                    (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4  # center distance squared376            if DIoU:377                return iou - rho2 / c2  # DIoU378            elif CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47379                v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / (h2 + eps)) - torch.atan(w1 / (h1 + eps)), 2)380                with torch.no_grad():381                    alpha = v / (v - iou + (1 + eps))382                return iou - (rho2 / c2 + v * alpha)  # CIoU383        else:  # GIoU https://arxiv.org/pdf/1902.09630.pdf384            c_area = cw * ch + eps  # convex area385            return iou - (c_area - union) / c_area  # GIoU386    else:387        return iou  # IoU388 389 390 391 392def bbox_alpha_iou(box1, box2, x1y1x2y2=False, GIoU=False, DIoU=False, CIoU=False, alpha=2, eps=1e-9):393    # Returns tsqrt_he IoU of box1 to box2. box1 is 4, box2 is nx4394    box2 = box2.T395 396    # Get the coordinates of bounding boxes397    if x1y1x2y2:  # x1, y1, x2, y2 = box1398        b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]399        b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]400    else:  # transform from xywh to xyxy401        b1_x1, b1_x2 = box1[0] - box1[2] / 2, box1[0] + box1[2] / 2402        b1_y1, b1_y2 = box1[1] - box1[3] / 2, box1[1] + box1[3] / 2403        b2_x1, b2_x2 = box2[0] - box2[2] / 2, box2[0] + box2[2] / 2404        b2_y1, b2_y2 = box2[1] - box2[3] / 2, box2[1] + box2[3] / 2405 406    # Intersection area407    inter = (torch.min(b1_x2, b2_x2) - torch.max(b1_x1, b2_x1)).clamp(0) * \408            (torch.min(b1_y2, b2_y2) - torch.max(b1_y1, b2_y1)).clamp(0)409 410    # Union Area411    w1, h1 = b1_x2 - b1_x1, b1_y2 - b1_y1 + eps412    w2, h2 = b2_x2 - b2_x1, b2_y2 - b2_y1 + eps413    union = w1 * h1 + w2 * h2 - inter + eps414 415    # change iou into pow(iou+eps)416    # iou = inter / union417    iou = torch.pow(inter/union + eps, alpha)418    # beta = 2 * alpha419    if GIoU or DIoU or CIoU:420        cw = torch.max(b1_x2, b2_x2) - torch.min(b1_x1, b2_x1)  # convex (smallest enclosing box) width421        ch = torch.max(b1_y2, b2_y2) - torch.min(b1_y1, b2_y1)  # convex height422        if CIoU or DIoU:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1423            c2 = (cw ** 2 + ch ** 2) ** alpha + eps  # convex diagonal424            rho_x = torch.abs(b2_x1 + b2_x2 - b1_x1 - b1_x2)425            rho_y = torch.abs(b2_y1 + b2_y2 - b1_y1 - b1_y2)426            rho2 = ((rho_x ** 2 + rho_y ** 2) / 4) ** alpha  # center distance427            if DIoU:428                return iou - rho2 / c2  # DIoU429            elif CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47430                v = (4 / math.pi ** 2) * torch.pow(torch.atan(w2 / h2) - torch.atan(w1 / h1), 2)431                with torch.no_grad():432                    alpha_ciou = v / ((1 + eps) - inter / union + v)433                # return iou - (rho2 / c2 + v * alpha_ciou)  # CIoU434                return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha))  # CIoU435        else:  # GIoU https://arxiv.org/pdf/1902.09630.pdf436            # c_area = cw * ch + eps  # convex area437            # return iou - (c_area - union) / c_area  # GIoU438            c_area = torch.max(cw * ch + eps, union) # convex area439            return iou - torch.pow((c_area - union) / c_area + eps, alpha)  # GIoU440    else:441        return iou # torch.log(iou+eps) or iou442 443 444def box_iou(box1, box2):445    # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py446    """447    Return intersection-over-union (Jaccard index) of boxes.448    Both sets of boxes are expected to be in (x1, y1, x2, y2) format.449    Arguments:450        box1 (Tensor[N, 4])451        box2 (Tensor[M, 4])452    Returns:453        iou (Tensor[N, M]): the NxM matrix containing the pairwise454            IoU values for every element in boxes1 and boxes2455    """456 457    def box_area(box):458        # box = 4xn459        return (box[2] - box[0]) * (box[3] - box[1])460 461    area1 = box_area(box1.T)462    area2 = box_area(box2.T)463 464    # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)465    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)466    return inter / (area1[:, None] + area2 - inter)  # iou = inter / (area1 + area2 - inter)467 468 469def wh_iou(wh1, wh2):470    # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2471    wh1 = wh1[:, None]  # [N,1,2]472    wh2 = wh2[None]  # [1,M,2]473    inter = torch.min(wh1, wh2).prod(2)  # [N,M]474    return inter / (wh1.prod(2) + wh2.prod(2) - inter)  # iou = inter / (area1 + area2 - inter)475 476 477def box_giou(box1, box2):478    """479    Return generalized intersection-over-union (Jaccard index) between two sets of boxes.480    Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with481    ``0 <= x1 < x2`` and ``0 <= y1 < y2``.482    Args:483        boxes1 (Tensor[N, 4]): first set of boxes484        boxes2 (Tensor[M, 4]): second set of boxes485    Returns:486        Tensor[N, M]: the NxM matrix containing the pairwise generalized IoU values487        for every element in boxes1 and boxes2488    """489 490    def box_area(box):491        # box = 4xn492        return (box[2] - box[0]) * (box[3] - box[1])493 494    area1 = box_area(box1.T)495    area2 = box_area(box2.T)496    497    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)498    union = (area1[:, None] + area2 - inter)499 500    iou = inter / union501 502    lti = torch.min(box1[:, None, :2], box2[:, :2])503    rbi = torch.max(box1[:, None, 2:], box2[:, 2:])504 505    whi = (rbi - lti).clamp(min=0)  # [N,M,2]506    areai = whi[:, :, 0] * whi[:, :, 1]507 508    return iou - (areai - union) / areai509 510 511def box_ciou(box1, box2, eps: float = 1e-7):512    """513    Return complete intersection-over-union (Jaccard index) between two sets of boxes.514    Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with515    ``0 <= x1 < x2`` and ``0 <= y1 < y2``.516    Args:517        boxes1 (Tensor[N, 4]): first set of boxes518        boxes2 (Tensor[M, 4]): second set of boxes519        eps (float, optional): small number to prevent division by zero. Default: 1e-7520    Returns:521        Tensor[N, M]: the NxM matrix containing the pairwise complete IoU values522        for every element in boxes1 and boxes2523    """524 525    def box_area(box):526        # box = 4xn527        return (box[2] - box[0]) * (box[3] - box[1])528 529    area1 = box_area(box1.T)530    area2 = box_area(box2.T)531    532    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)533    union = (area1[:, None] + area2 - inter)534 535    iou = inter / union536 537    lti = torch.min(box1[:, None, :2], box2[:, :2])538    rbi = torch.max(box1[:, None, 2:], box2[:, 2:])539 540    whi = (rbi - lti).clamp(min=0)  # [N,M,2]541    diagonal_distance_squared = (whi[:, :, 0] ** 2) + (whi[:, :, 1] ** 2) + eps542 543    # centers of boxes544    x_p = (box1[:, None, 0] + box1[:, None, 2]) / 2545    y_p = (box1[:, None, 1] + box1[:, None, 3]) / 2546    x_g = (box2[:, 0] + box2[:, 2]) / 2547    y_g = (box2[:, 1] + box2[:, 3]) / 2548    # The distance between boxes' centers squared.549    centers_distance_squared = (x_p - x_g) ** 2 + (y_p - y_g) ** 2550 551    w_pred = box1[:, None, 2] - box1[:, None, 0]552    h_pred = box1[:, None, 3] - box1[:, None, 1]553 554    w_gt = box2[:, 2] - box2[:, 0]555    h_gt = box2[:, 3] - box2[:, 1]556 557    v = (4 / (torch.pi ** 2)) * torch.pow((torch.atan(w_gt / h_gt) - torch.atan(w_pred / h_pred)), 2)558    with torch.no_grad():559        alpha = v / (1 - iou + v + eps)560    return iou - (centers_distance_squared / diagonal_distance_squared) - alpha * v561 562 563def box_diou(box1, box2, eps: float = 1e-7):564    """565    Return distance intersection-over-union (Jaccard index) between two sets of boxes.566    Both sets of boxes are expected to be in ``(x1, y1, x2, y2)`` format with567    ``0 <= x1 < x2`` and ``0 <= y1 < y2``.568    Args:569        boxes1 (Tensor[N, 4]): first set of boxes570        boxes2 (Tensor[M, 4]): second set of boxes571        eps (float, optional): small number to prevent division by zero. Default: 1e-7572    Returns:573        Tensor[N, M]: the NxM matrix containing the pairwise distance IoU values574        for every element in boxes1 and boxes2575    """576 577    def box_area(box):578        # box = 4xn579        return (box[2] - box[0]) * (box[3] - box[1])580 581    area1 = box_area(box1.T)582    area2 = box_area(box2.T)583    584    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)585    union = (area1[:, None] + area2 - inter)586 587    iou = inter / union588 589    lti = torch.min(box1[:, None, :2], box2[:, :2])590    rbi = torch.max(box1[:, None, 2:], box2[:, 2:])591 592    whi = (rbi - lti).clamp(min=0)  # [N,M,2]593    diagonal_distance_squared = (whi[:, :, 0] ** 2) + (whi[:, :, 1] ** 2) + eps594 595    # centers of boxes596    x_p = (box1[:, None, 0] + box1[:, None, 2]) / 2597    y_p = (box1[:, None, 1] + box1[:, None, 3]) / 2598    x_g = (box2[:, 0] + box2[:, 2]) / 2599    y_g = (box2[:, 1] + box2[:, 3]) / 2600    # The distance between boxes' centers squared.601    centers_distance_squared = (x_p - x_g) ** 2 + (y_p - y_g) ** 2602 603    # The distance IoU is the IoU penalized by a normalized604    # distance between boxes' centers squared.605    return iou - (centers_distance_squared / diagonal_distance_squared)606 607 608def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,609                        labels=()):610    """Runs Non-Maximum Suppression (NMS) on inference results611 612    Returns:613         list of detections, on (n,6) tensor per image [xyxy, conf, cls]614    """615 616    nc = prediction.shape[2] - 5  # number of classes617    xc = prediction[..., 4] > conf_thres  # candidates618 619    # Settings620    min_wh, max_wh = 2, 4096  # (pixels) minimum and maximum box width and height621    max_det = 300  # maximum number of detections per image622    max_nms = 30000  # maximum number of boxes into torchvision.ops.nms()623    time_limit = 10.0  # seconds to quit after624    redundant = True  # require redundant detections625    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)626    merge = False  # use merge-NMS627 628    t = time.time()629    output = [torch.zeros((0, 6), device=prediction.device)] * prediction.shape[0]630    for xi, x in enumerate(prediction):  # image index, image inference631        # Apply constraints632        # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0  # width-height633        x = x[xc[xi]]  # confidence634 635        # Cat apriori labels if autolabelling636        if labels and len(labels[xi]):637            l = labels[xi]638            v = torch.zeros((len(l), nc + 5), device=x.device)639            v[:, :4] = l[:, 1:5]  # box640            v[:, 4] = 1.0  # conf641            v[range(len(l)), l[:, 0].long() + 5] = 1.0  # cls642            x = torch.cat((x, v), 0)643 644        # If none remain process next image645        if not x.shape[0]:646            continue647 648        # Compute conf649        if nc == 1:650            x[:, 5:] = x[:, 4:5] # for models with one class, cls_loss is 0 and cls_conf is always 0.5,651                                 # so there is no need to multiplicate.652        else:653            x[:, 5:] *= x[:, 4:5]  # conf = obj_conf * cls_conf654 655        # Box (center x, center y, width, height) to (x1, y1, x2, y2)656        box = xywh2xyxy(x[:, :4])657 658        # Detections matrix nx6 (xyxy, conf, cls)659        if multi_label:660            i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T661            x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)662        else:  # best class only663            conf, j = x[:, 5:].max(1, keepdim=True)664            x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]665 666        # Filter by class667        if classes is not None:668            x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]669 670        # Apply finite constraint671        # if not torch.isfinite(x).all():672        #     x = x[torch.isfinite(x).all(1)]673 674        # Check shape675        n = x.shape[0]  # number of boxes676        if not n:  # no boxes677            continue678        elif n > max_nms:  # excess boxes679            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence680 681        # Batched NMS682        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes683        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores684        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS685        if i.shape[0] > max_det:  # limit detections686            i = i[:max_det]687        if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)688            # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)689            iou = box_iou(boxes[i], boxes) > iou_thres  # iou matrix690            weights = iou * scores[None]  # box weights691            x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes692            if redundant:693                i = i[iou.sum(1) > 1]  # require redundancy694 695        output[xi] = x[i]696        if (time.time() - t) > time_limit:697            print(f'WARNING: NMS time limit {time_limit}s exceeded')698            break  # time limit exceeded699 700    return output701 702 703def non_max_suppression_kpt(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,704                        labels=(), kpt_label=False, nc=None, nkpt=None):705    """Runs Non-Maximum Suppression (NMS) on inference results706 707    Returns:708         list of detections, on (n,6) tensor per image [xyxy, conf, cls]709    """710    if nc is None:711        nc = prediction.shape[2] - 5  if not kpt_label else prediction.shape[2] - 56 # number of classes712    xc = prediction[..., 4] > conf_thres  # candidates713 714    # Settings715    min_wh, max_wh = 2, 4096  # (pixels) minimum and maximum box width and height716    max_det = 300  # maximum number of detections per image717    max_nms = 30000  # maximum number of boxes into torchvision.ops.nms()718    time_limit = 10.0  # seconds to quit after719    redundant = True  # require redundant detections720    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)721    merge = False  # use merge-NMS722 723    t = time.time()724    output = [torch.zeros((0,6), device=prediction.device)] * prediction.shape[0]725    for xi, x in enumerate(prediction):  # image index, image inference726        # Apply constraints727        # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0  # width-height728        x = x[xc[xi]]  # confidence729 730        # Cat apriori labels if autolabelling731        if labels and len(labels[xi]):732            l = labels[xi]733            v = torch.zeros((len(l), nc + 5), device=x.device)734            v[:, :4] = l[:, 1:5]  # box735            v[:, 4] = 1.0  # conf736            v[range(len(l)), l[:, 0].long() + 5] = 1.0  # cls737            x = torch.cat((x, v), 0)738 739        # If none remain process next image740        if not x.shape[0]:741            continue742 743        # Compute conf744        x[:, 5:5+nc] *= x[:, 4:5]  # conf = obj_conf * cls_conf745 746        # Box (center x, center y, width, height) to (x1, y1, x2, y2)747        box = xywh2xyxy(x[:, :4])748 749        # Detections matrix nx6 (xyxy, conf, cls)750        if multi_label:751            i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T752            x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)753        else:  # best class only754            if not kpt_label:755                conf, j = x[:, 5:].max(1, keepdim=True)756                x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]757            else:758                kpts = x[:, 6:]759                conf, j = x[:, 5:6].max(1, keepdim=True)760                x = torch.cat((box, conf, j.float(), kpts), 1)[conf.view(-1) > conf_thres]761 762 763        # Filter by class764        if classes is not None:765            x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]766 767        # Apply finite constraint768        # if not torch.isfinite(x).all():769        #     x = x[torch.isfinite(x).all(1)]770 771        # Check shape772        n = x.shape[0]  # number of boxes773        if not n:  # no boxes774            continue775        elif n > max_nms:  # excess boxes776            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence777 778        # Batched NMS779        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes780        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores781        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS782        if i.shape[0] > max_det:  # limit detections783            i = i[:max_det]784        if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)785            # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)786            iou = box_iou(boxes[i], boxes) > iou_thres  # iou matrix787            weights = iou * scores[None]  # box weights788            x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes789            if redundant:790                i = i[iou.sum(1) > 1]  # require redundancy791 792        output[xi] = x[i]793        if (time.time() - t) > time_limit:794            print(f'WARNING: NMS time limit {time_limit}s exceeded')795            break  # time limit exceeded796 797    return output798 799 800def strip_optimizer(f='best.pt', s=''):  # from utils.general import *; strip_optimizer()801    # Strip optimizer from 'f' to finalize training, optionally save as 's'802    x = torch.load(f, map_location=torch.device('cpu'), weights_only=False)803    if x.get('ema'):804        x['model'] = x['ema']  # replace model with ema805    for k in 'optimizer', 'training_results', 'wandb_id', 'ema', 'updates':  # keys806        x[k] = None807    x['epoch'] = -1808    x['model'].half()  # to FP16809    for p in x['model'].parameters():810        p.requires_grad = False811    torch.save(x, s or f)812    mb = os.path.getsize(s or f) / 1E6  # filesize813    print(f"Optimizer stripped from {f},{(' saved as %s,' % s) if s else ''} {mb:.1f}MB")814 815 816def print_mutation(hyp, results, yaml_file='hyp_evolved.yaml', bucket=''):817    # Print mutation results to evolve.txt (for use with train.py --evolve)818    a = '%10s' * len(hyp) % tuple(hyp.keys())  # hyperparam keys819    b = '%10.3g' * len(hyp) % tuple(hyp.values())  # hyperparam values820    c = '%10.4g' * len(results) % results  # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)821    print('\n%s\n%s\nEvolved fitness: %s\n' % (a, b, c))822 823    if bucket:824        url = 'gs://%s/evolve.txt' % bucket825        if gsutil_getsize(url) > (os.path.getsize('evolve.txt') if os.path.exists('evolve.txt') else 0):826            os.system('gsutil cp %s .' % url)  # download evolve.txt if larger than local827 828    with open('evolve.txt', 'a') as f:  # append result829        f.write(c + b + '\n')830    x = np.unique(np.loadtxt('evolve.txt', ndmin=2), axis=0)  # load unique rows831    x = x[np.argsort(-fitness(x))]  # sort832    np.savetxt('evolve.txt', x, '%10.3g')  # save sort by fitness833 834    # Save yaml835    for i, k in enumerate(hyp.keys()):836        hyp[k] = float(x[0, i + 7])837    with open(yaml_file, 'w') as f:838        results = tuple(x[0, :7])839        c = '%10.4g' * len(results) % results  # results (P, R, mAP@0.5, mAP@0.5:0.95, val_losses x 3)840        f.write('# Hyperparameter Evolution Results\n# Generations: %g\n# Metrics: ' % len(x) + c + '\n\n')841        yaml.dump(hyp, f, sort_keys=False)842 843    if bucket:844        os.system('gsutil cp evolve.txt %s gs://%s' % (yaml_file, bucket))  # upload845 846 847def apply_classifier(x, model, img, im0):848    # applies a second stage classifier to yolo outputs849    im0 = [im0] if isinstance(im0, np.ndarray) else im0850    for i, d in enumerate(x):  # per image851        if d is not None and len(d):852            d = d.clone()853 854            # Reshape and pad cutouts855            b = xyxy2xywh(d[:, :4])  # boxes856            b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1)  # rectangle to square857            b[:, 2:] = b[:, 2:] * 1.3 + 30  # pad858            d[:, :4] = xywh2xyxy(b).long()859 860            # Rescale boxes from img_size to im0 size861            scale_coords(img.shape[2:], d[:, :4], im0[i].shape)862 863            # Classes864            pred_cls1 = d[:, 5].long()865            ims = []866            for j, a in enumerate(d):  # per item867                cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]868                im = cv2.resize(cutout, (224, 224))  # BGR869                # cv2.imwrite('test%i.jpg' % j, cutout)870 871                im = im[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416872                im = np.ascontiguousarray(im, dtype=np.float32)  # uint8 to float32873                im /= 255.0  # 0 - 255 to 0.0 - 1.0874                ims.append(im)875 876            pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1)  # classifier prediction877            x[i] = x[i][pred_cls1 == pred_cls2]  # retain matching class detections878 879    return x880 881 882def increment_path(path, exist_ok=True, sep=''):883    # Increment path, i.e. runs/exp --> runs/exp{sep}0, runs/exp{sep}1 etc.884    path = Path(path)  # os-agnostic885    if (path.exists() and exist_ok) or (not path.exists()):886        return str(path)887    else:888        dirs = glob.glob(f"{path}{sep}*")  # similar paths889        matches = [re.search(rf"%s{sep}(\d+)" % path.stem, d) for d in dirs]890        i = [int(m.groups()[0]) for m in matches if m]  # indices891        n = max(i) + 1 if i else 2  # increment number892        return f"{path}{sep}{n}"  # update path893