CoolFace
Apppublic

k20hcmus/FishEye8K

sourceHugging Faceapache-2.0updated 2y agoView on Hugging Face
3likes
general.py1228 linesDownload Raw Back to utils
1import contextlib2import glob3import inspect4import logging5import logging.config6import math7import os8import platform9import random10import re11import signal12import sys13import time14import urllib15from copy import deepcopy16from datetime import datetime17from itertools import repeat18from multiprocessing.pool import ThreadPool19from pathlib import Path20from subprocess import check_output21from tarfile import is_tarfile22from typing import Optional23from zipfile import ZipFile, is_zipfile24 25import cv226import IPython27import numpy as np28import pandas as pd29import pkg_resources as pkg30import torch31import torchvision32import yaml33 34from utils import TryExcept, emojis35from utils.downloads import gsutil_getsize36from utils.metrics import box_iou, fitness37 38FILE = Path(__file__).resolve()39ROOT = FILE.parents[1]  # YOLO root directory40RANK = int(os.getenv('RANK', -1))41 42# Settings43NUM_THREADS = min(8, max(1, os.cpu_count() - 1))  # number of YOLOv5 multiprocessing threads44DATASETS_DIR = Path(os.getenv('YOLOv5_DATASETS_DIR', ROOT.parent / 'datasets'))  # global datasets directory45AUTOINSTALL = str(os.getenv('YOLOv5_AUTOINSTALL', True)).lower() == 'true'  # global auto-install mode46VERBOSE = str(os.getenv('YOLOv5_VERBOSE', True)).lower() == 'true'  # global verbose mode47TQDM_BAR_FORMAT = '{l_bar}{bar:10}| {n_fmt}/{total_fmt} {elapsed}'  # tqdm bar format48FONT = 'Arial.ttf'  # https://ultralytics.com/assets/Arial.ttf49 50torch.set_printoptions(linewidth=320, precision=5, profile='long')51np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format})  # format short g, %precision=552pd.options.display.max_columns = 1053cv2.setNumThreads(0)  # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)54os.environ['NUMEXPR_MAX_THREADS'] = str(NUM_THREADS)  # NumExpr max threads55os.environ['OMP_NUM_THREADS'] = '1' if platform.system() == 'darwin' else str(NUM_THREADS)  # OpenMP (PyTorch and SciPy)56 57 58def is_ascii(s=''):59    # Is string composed of all ASCII (no UTF) characters? (note str().isascii() introduced in python 3.7)60    s = str(s)  # convert list, tuple, None, etc. to str61    return len(s.encode().decode('ascii', 'ignore')) == len(s)62 63 64def is_chinese(s='人工智能'):65    # Is string composed of any Chinese characters?66    return bool(re.search('[\u4e00-\u9fff]', str(s)))67 68 69def is_colab():70    # Is environment a Google Colab instance?71    return 'google.colab' in sys.modules72 73 74def is_notebook():75    # Is environment a Jupyter notebook? Verified on Colab, Jupyterlab, Kaggle, Paperspace76    ipython_type = str(type(IPython.get_ipython()))77    return 'colab' in ipython_type or 'zmqshell' in ipython_type78 79 80def is_kaggle():81    # Is environment a Kaggle Notebook?82    return os.environ.get('PWD') == '/kaggle/working' and os.environ.get('KAGGLE_URL_BASE') == 'https://www.kaggle.com'83 84 85def is_docker() -> bool:86    """Check if the process runs inside a docker container."""87    if Path("/.dockerenv").exists():88        return True89    try:  # check if docker is in control groups90        with open("/proc/self/cgroup") as file:91            return any("docker" in line for line in file)92    except OSError:93        return False94 95 96def is_writeable(dir, test=False):97    # Return True if directory has write permissions, test opening a file with write permissions if test=True98    if not test:99        return os.access(dir, os.W_OK)  # possible issues on Windows100    file = Path(dir) / 'tmp.txt'101    try:102        with open(file, 'w'):  # open file with write permissions103            pass104        file.unlink()  # remove file105        return True106    except OSError:107        return False108 109 110LOGGING_NAME = "yolov5"111 112 113def set_logging(name=LOGGING_NAME, verbose=True):114    # sets up logging for the given name115    rank = int(os.getenv('RANK', -1))  # rank in world for Multi-GPU trainings116    level = logging.INFO if verbose and rank in {-1, 0} else logging.ERROR117    logging.config.dictConfig({118        "version": 1,119        "disable_existing_loggers": False,120        "formatters": {121            name: {122                "format": "%(message)s"}},123        "handlers": {124            name: {125                "class": "logging.StreamHandler",126                "formatter": name,127                "level": level,}},128        "loggers": {129            name: {130                "level": level,131                "handlers": [name],132                "propagate": False,}}})133 134 135set_logging(LOGGING_NAME)  # run before defining LOGGER136LOGGER = logging.getLogger(LOGGING_NAME)  # define globally (used in train.py, val.py, detect.py, etc.)137if platform.system() == 'Windows':138    for fn in LOGGER.info, LOGGER.warning:139        setattr(LOGGER, fn.__name__, lambda x: fn(emojis(x)))  # emoji safe logging140 141 142def user_config_dir(dir='Ultralytics', env_var='YOLOV5_CONFIG_DIR'):143    # Return path of user configuration directory. Prefer environment variable if exists. Make dir if required.144    env = os.getenv(env_var)145    if env:146        path = Path(env)  # use environment variable147    else:148        cfg = {'Windows': 'AppData/Roaming', 'Linux': '.config', 'Darwin': 'Library/Application Support'}  # 3 OS dirs149        path = Path.home() / cfg.get(platform.system(), '')  # OS-specific config dir150        path = (path if is_writeable(path) else Path('/tmp')) / dir  # GCP and AWS lambda fix, only /tmp is writeable151    path.mkdir(exist_ok=True)  # make if required152    return path153 154 155CONFIG_DIR = user_config_dir()  # Ultralytics settings dir156 157 158class Profile(contextlib.ContextDecorator):159    # YOLO Profile class. Usage: @Profile() decorator or 'with Profile():' context manager160    def __init__(self, t=0.0):161        self.t = t162        self.cuda = torch.cuda.is_available()163 164    def __enter__(self):165        self.start = self.time()166        return self167 168    def __exit__(self, type, value, traceback):169        self.dt = self.time() - self.start  # delta-time170        self.t += self.dt  # accumulate dt171 172    def time(self):173        if self.cuda:174            torch.cuda.synchronize()175        return time.time()176 177 178class Timeout(contextlib.ContextDecorator):179    # YOLO Timeout class. Usage: @Timeout(seconds) decorator or 'with Timeout(seconds):' context manager180    def __init__(self, seconds, *, timeout_msg='', suppress_timeout_errors=True):181        self.seconds = int(seconds)182        self.timeout_message = timeout_msg183        self.suppress = bool(suppress_timeout_errors)184 185    def _timeout_handler(self, signum, frame):186        raise TimeoutError(self.timeout_message)187 188    def __enter__(self):189        if platform.system() != 'Windows':  # not supported on Windows190            signal.signal(signal.SIGALRM, self._timeout_handler)  # Set handler for SIGALRM191            signal.alarm(self.seconds)  # start countdown for SIGALRM to be raised192 193    def __exit__(self, exc_type, exc_val, exc_tb):194        if platform.system() != 'Windows':195            signal.alarm(0)  # Cancel SIGALRM if it's scheduled196            if self.suppress and exc_type is TimeoutError:  # Suppress TimeoutError197                return True198 199 200class WorkingDirectory(contextlib.ContextDecorator):201    # Usage: @WorkingDirectory(dir) decorator or 'with WorkingDirectory(dir):' context manager202    def __init__(self, new_dir):203        self.dir = new_dir  # new dir204        self.cwd = Path.cwd().resolve()  # current dir205 206    def __enter__(self):207        os.chdir(self.dir)208 209    def __exit__(self, exc_type, exc_val, exc_tb):210        os.chdir(self.cwd)211 212 213def methods(instance):214    # Get class/instance methods215    return [f for f in dir(instance) if callable(getattr(instance, f)) and not f.startswith("__")]216 217 218def print_args(args: Optional[dict] = None, show_file=True, show_func=False):219    # Print function arguments (optional args dict)220    x = inspect.currentframe().f_back  # previous frame221    file, _, func, _, _ = inspect.getframeinfo(x)222    if args is None:  # get args automatically223        args, _, _, frm = inspect.getargvalues(x)224        args = {k: v for k, v in frm.items() if k in args}225    try:226        file = Path(file).resolve().relative_to(ROOT).with_suffix('')227    except ValueError:228        file = Path(file).stem229    s = (f'{file}: ' if show_file else '') + (f'{func}: ' if show_func else '')230    LOGGER.info(colorstr(s) + ', '.join(f'{k}={v}' for k, v in args.items()))231 232 233def init_seeds(seed=0, deterministic=False):234    # Initialize random number generator (RNG) seeds https://pytorch.org/docs/stable/notes/randomness.html235    random.seed(seed)236    np.random.seed(seed)237    torch.manual_seed(seed)238    torch.cuda.manual_seed(seed)239    torch.cuda.manual_seed_all(seed)  # for Multi-GPU, exception safe240    # torch.backends.cudnn.benchmark = True  # AutoBatch problem https://github.com/ultralytics/yolov5/issues/9287241    if deterministic and check_version(torch.__version__, '1.12.0'):  # https://github.com/ultralytics/yolov5/pull/8213242        torch.use_deterministic_algorithms(True)243        torch.backends.cudnn.deterministic = True244        os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8'245        os.environ['PYTHONHASHSEED'] = str(seed)246 247 248def intersect_dicts(da, db, exclude=()):249    # Dictionary intersection of matching keys and shapes, omitting 'exclude' keys, using da values250    return {k: v for k, v in da.items() if k in db and all(x not in k for x in exclude) and v.shape == db[k].shape}251 252 253def get_default_args(func):254    # Get func() default arguments255    signature = inspect.signature(func)256    return {k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty}257 258 259def get_latest_run(search_dir='.'):260    # Return path to most recent 'last.pt' in /runs (i.e. to --resume from)261    last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True)262    return max(last_list, key=os.path.getctime) if last_list else ''263 264 265def file_age(path=__file__):266    # Return days since last file update267    dt = (datetime.now() - datetime.fromtimestamp(Path(path).stat().st_mtime))  # delta268    return dt.days  # + dt.seconds / 86400  # fractional days269 270 271def file_date(path=__file__):272    # Return human-readable file modification date, i.e. '2021-3-26'273    t = datetime.fromtimestamp(Path(path).stat().st_mtime)274    return f'{t.year}-{t.month}-{t.day}'275 276 277def file_size(path):278    # Return file/dir size (MB)279    mb = 1 << 20  # bytes to MiB (1024 ** 2)280    path = Path(path)281    if path.is_file():282        return path.stat().st_size / mb283    elif path.is_dir():284        return sum(f.stat().st_size for f in path.glob('**/*') if f.is_file()) / mb285    else:286        return 0.0287 288 289def check_online():290    # Check internet connectivity291    import socket292 293    def run_once():294        # Check once295        try:296            socket.create_connection(("1.1.1.1", 443), 5)  # check host accessibility297            return True298        except OSError:299            return False300 301    return run_once() or run_once()  # check twice to increase robustness to intermittent connectivity issues302 303 304def git_describe(path=ROOT):  # path must be a directory305    # Return human-readable git description, i.e. v5.0-5-g3e25f1e https://git-scm.com/docs/git-describe306    try:307        assert (Path(path) / '.git').is_dir()308        return check_output(f'git -C {path} describe --tags --long --always', shell=True).decode()[:-1]309    except Exception:310        return ''311 312 313@TryExcept()314@WorkingDirectory(ROOT)315def check_git_status(repo='WongKinYiu/yolov9', branch='main'):316    # YOLO status check, recommend 'git pull' if code is out of date317    url = f'https://github.com/{repo}'318    msg = f', for updates see {url}'319    s = colorstr('github: ')  # string320    assert Path('.git').exists(), s + 'skipping check (not a git repository)' + msg321    assert check_online(), s + 'skipping check (offline)' + msg322 323    splits = re.split(pattern=r'\s', string=check_output('git remote -v', shell=True).decode())324    matches = [repo in s for s in splits]325    if any(matches):326        remote = splits[matches.index(True) - 1]327    else:328        remote = 'ultralytics'329        check_output(f'git remote add {remote} {url}', shell=True)330    check_output(f'git fetch {remote}', shell=True, timeout=5)  # git fetch331    local_branch = check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip()  # checked out332    n = int(check_output(f'git rev-list {local_branch}..{remote}/{branch} --count', shell=True))  # commits behind333    if n > 0:334        pull = 'git pull' if remote == 'origin' else f'git pull {remote} {branch}'335        s += f"⚠️ YOLO is out of date by {n} commit{'s' * (n > 1)}. Use `{pull}` or `git clone {url}` to update."336    else:337        s += f'up to date with {url} ✅'338    LOGGER.info(s)339 340 341@WorkingDirectory(ROOT)342def check_git_info(path='.'):343    # YOLO git info check, return {remote, branch, commit}344    check_requirements('gitpython')345    import git346    try:347        repo = git.Repo(path)348        remote = repo.remotes.origin.url.replace('.git', '')  # i.e. 'https://github.com/WongKinYiu/yolov9'349        commit = repo.head.commit.hexsha  # i.e. '3134699c73af83aac2a481435550b968d5792c0d'350        try:351            branch = repo.active_branch.name  # i.e. 'main'352        except TypeError:  # not on any branch353            branch = None  # i.e. 'detached HEAD' state354        return {'remote': remote, 'branch': branch, 'commit': commit}355    except git.exc.InvalidGitRepositoryError:  # path is not a git dir356        return {'remote': None, 'branch': None, 'commit': None}357 358 359def check_python(minimum='3.7.0'):360    # Check current python version vs. required python version361    check_version(platform.python_version(), minimum, name='Python ', hard=True)362 363 364def check_version(current='0.0.0', minimum='0.0.0', name='version ', pinned=False, hard=False, verbose=False):365    # Check version vs. required version366    current, minimum = (pkg.parse_version(x) for x in (current, minimum))367    result = (current == minimum) if pinned else (current >= minimum)  # bool368    s = f'WARNING ⚠️ {name}{minimum} is required by YOLO, but {name}{current} is currently installed'  # string369    if hard:370        assert result, emojis(s)  # assert min requirements met371    if verbose and not result:372        LOGGER.warning(s)373    return result374 375 376@TryExcept()377def check_requirements(requirements=ROOT / 'requirements.txt', exclude=(), install=True, cmds=''):378    # Check installed dependencies meet YOLO requirements (pass *.txt file or list of packages or single package str)379    prefix = colorstr('red', 'bold', 'requirements:')380    check_python()  # check python version381    if isinstance(requirements, Path):  # requirements.txt file382        file = requirements.resolve()383        assert file.exists(), f"{prefix} {file} not found, check failed."384        with file.open() as f:385            requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(f) if x.name not in exclude]386    elif isinstance(requirements, str):387        requirements = [requirements]388 389    s = ''390    n = 0391    for r in requirements:392        try:393            pkg.require(r)394        except (pkg.VersionConflict, pkg.DistributionNotFound):  # exception if requirements not met395            s += f'"{r}" '396            n += 1397 398    if s and install and AUTOINSTALL:  # check environment variable399        LOGGER.info(f"{prefix} YOLO requirement{'s' * (n > 1)} {s}not found, attempting AutoUpdate...")400        try:401            # assert check_online(), "AutoUpdate skipped (offline)"402            LOGGER.info(check_output(f'pip install {s} {cmds}', shell=True).decode())403            source = file if 'file' in locals() else requirements404            s = f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n" \405                f"{prefix} ⚠️ {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n"406            LOGGER.info(s)407        except Exception as e:408            LOGGER.warning(f'{prefix} ❌ {e}')409 410 411def check_img_size(imgsz, s=32, floor=0):412    # Verify image size is a multiple of stride s in each dimension413    if isinstance(imgsz, int):  # integer i.e. img_size=640414        new_size = max(make_divisible(imgsz, int(s)), floor)415    else:  # list i.e. img_size=[640, 480]416        imgsz = list(imgsz)  # convert to list if tuple417        new_size = [max(make_divisible(x, int(s)), floor) for x in imgsz]418    if new_size != imgsz:419        LOGGER.warning(f'WARNING ⚠️ --img-size {imgsz} must be multiple of max stride {s}, updating to {new_size}')420    return new_size421 422 423def check_imshow(warn=False):424    # Check if environment supports image displays425    try:426        assert not is_notebook()427        assert not is_docker()428        cv2.imshow('test', np.zeros((1, 1, 3)))429        cv2.waitKey(1)430        cv2.destroyAllWindows()431        cv2.waitKey(1)432        return True433    except Exception as e:434        if warn:435            LOGGER.warning(f'WARNING ⚠️ Environment does not support cv2.imshow() or PIL Image.show()\n{e}')436        return False437 438 439def check_suffix(file='yolo.pt', suffix=('.pt',), msg=''):440    # Check file(s) for acceptable suffix441    if file and suffix:442        if isinstance(suffix, str):443            suffix = [suffix]444        for f in file if isinstance(file, (list, tuple)) else [file]:445            s = Path(f).suffix.lower()  # file suffix446            if len(s):447                assert s in suffix, f"{msg}{f} acceptable suffix is {suffix}"448 449 450def check_yaml(file, suffix=('.yaml', '.yml')):451    # Search/download YAML file (if necessary) and return path, checking suffix452    return check_file(file, suffix)453 454 455def check_file(file, suffix=''):456    # Search/download file (if necessary) and return path457    check_suffix(file, suffix)  # optional458    file = str(file)  # convert to str()459    if os.path.isfile(file) or not file:  # exists460        return file461    elif file.startswith(('http:/', 'https:/')):  # download462        url = file  # warning: Pathlib turns :// -> :/463        file = Path(urllib.parse.unquote(file).split('?')[0]).name  # '%2F' to '/', split https://url.com/file.txt?auth464        if os.path.isfile(file):465            LOGGER.info(f'Found {url} locally at {file}')  # file already exists466        else:467            LOGGER.info(f'Downloading {url} to {file}...')468            torch.hub.download_url_to_file(url, file)469            assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}'  # check470        return file471    elif file.startswith('clearml://'):  # ClearML Dataset ID472        assert 'clearml' in sys.modules, "ClearML is not installed, so cannot use ClearML dataset. Try running 'pip install clearml'."473        return file474    else:  # search475        files = []476        for d in 'data', 'models', 'utils':  # search directories477            files.extend(glob.glob(str(ROOT / d / '**' / file), recursive=True))  # find file478        assert len(files), f'File not found: {file}'  # assert file was found479        assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}"  # assert unique480        return files[0]  # return file481 482 483def check_font(font=FONT, progress=False):484    # Download font to CONFIG_DIR if necessary485    font = Path(font)486    file = CONFIG_DIR / font.name487    if not font.exists() and not file.exists():488        url = f'https://ultralytics.com/assets/{font.name}'489        LOGGER.info(f'Downloading {url} to {file}...')490        torch.hub.download_url_to_file(url, str(file), progress=progress)491 492 493def check_dataset(data, autodownload=True):494    # Download, check and/or unzip dataset if not found locally495 496    # Download (optional)497    extract_dir = ''498    if isinstance(data, (str, Path)) and (is_zipfile(data) or is_tarfile(data)):499        download(data, dir=f'{DATASETS_DIR}/{Path(data).stem}', unzip=True, delete=False, curl=False, threads=1)500        data = next((DATASETS_DIR / Path(data).stem).rglob('*.yaml'))501        extract_dir, autodownload = data.parent, False502 503    # Read yaml (optional)504    if isinstance(data, (str, Path)):505        data = yaml_load(data)  # dictionary506 507    # Checks508    for k in 'train', 'val', 'names':509        assert k in data, emojis(f"data.yaml '{k}:' field missing ❌")510    if isinstance(data['names'], (list, tuple)):  # old array format511        data['names'] = dict(enumerate(data['names']))  # convert to dict512    assert all(isinstance(k, int) for k in data['names'].keys()), 'data.yaml names keys must be integers, i.e. 2: car'513    data['nc'] = len(data['names'])514 515    # Resolve paths516    path = Path(extract_dir or data.get('path') or '')  # optional 'path' default to '.'517    if not path.is_absolute():518        path = (ROOT / path).resolve()519        data['path'] = path  # download scripts520    for k in 'train', 'val', 'test':521        if data.get(k):  # prepend path522            if isinstance(data[k], str):523                x = (path / data[k]).resolve()524                if not x.exists() and data[k].startswith('../'):525                    x = (path / data[k][3:]).resolve()526                data[k] = str(x)527            else:528                data[k] = [str((path / x).resolve()) for x in data[k]]529 530    # Parse yaml531    train, val, test, s = (data.get(x) for x in ('train', 'val', 'test', 'download'))532    if val:533        val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])]  # val path534        if not all(x.exists() for x in val):535            LOGGER.info('\nDataset not found ⚠️, missing paths %s' % [str(x) for x in val if not x.exists()])536            if not s or not autodownload:537                raise Exception('Dataset not found ❌')538            t = time.time()539            if s.startswith('http') and s.endswith('.zip'):  # URL540                f = Path(s).name  # filename541                LOGGER.info(f'Downloading {s} to {f}...')542                torch.hub.download_url_to_file(s, f)543                Path(DATASETS_DIR).mkdir(parents=True, exist_ok=True)  # create root544                unzip_file(f, path=DATASETS_DIR)  # unzip545                Path(f).unlink()  # remove zip546                r = None  # success547            elif s.startswith('bash '):  # bash script548                LOGGER.info(f'Running {s} ...')549                r = os.system(s)550            else:  # python script551                r = exec(s, {'yaml': data})  # return None552            dt = f'({round(time.time() - t, 1)}s)'553            s = f"success ✅ {dt}, saved to {colorstr('bold', DATASETS_DIR)}" if r in (0, None) else f"failure {dt} ❌"554            LOGGER.info(f"Dataset download {s}")555    check_font('Arial.ttf' if is_ascii(data['names']) else 'Arial.Unicode.ttf', progress=True)  # download fonts556    return data  # dictionary557 558 559def check_amp(model):560    # Check PyTorch Automatic Mixed Precision (AMP) functionality. Return True on correct operation561    from models.common import AutoShape, DetectMultiBackend562 563    def amp_allclose(model, im):564        # All close FP32 vs AMP results565        m = AutoShape(model, verbose=False)  # model566        a = m(im).xywhn[0]  # FP32 inference567        m.amp = True568        b = m(im).xywhn[0]  # AMP inference569        return a.shape == b.shape and torch.allclose(a, b, atol=0.1)  # close to 10% absolute tolerance570 571    prefix = colorstr('AMP: ')572    device = next(model.parameters()).device  # get model device573    if device.type in ('cpu', 'mps'):574        return False  # AMP only used on CUDA devices575    f = ROOT / 'data' / 'images' / 'bus.jpg'  # image to check576    im = f if f.exists() else 'https://ultralytics.com/images/bus.jpg' if check_online() else np.ones((640, 640, 3))577    try:578        #assert amp_allclose(deepcopy(model), im) or amp_allclose(DetectMultiBackend('yolo.pt', device), im)579        LOGGER.info(f'{prefix}checks passed ✅')580        return True581    except Exception:582        help_url = 'https://github.com/ultralytics/yolov5/issues/7908'583        LOGGER.warning(f'{prefix}checks failed ❌, disabling Automatic Mixed Precision. See {help_url}')584        return False585 586 587def yaml_load(file='data.yaml'):588    # Single-line safe yaml loading589    with open(file, errors='ignore') as f:590        return yaml.safe_load(f)591 592 593def yaml_save(file='data.yaml', data={}):594    # Single-line safe yaml saving595    with open(file, 'w') as f:596        yaml.safe_dump({k: str(v) if isinstance(v, Path) else v for k, v in data.items()}, f, sort_keys=False)597 598 599def unzip_file(file, path=None, exclude=('.DS_Store', '__MACOSX')):600    # Unzip a *.zip file to path/, excluding files containing strings in exclude list601    if path is None:602        path = Path(file).parent  # default path603    with ZipFile(file) as zipObj:604        for f in zipObj.namelist():  # list all archived filenames in the zip605            if all(x not in f for x in exclude):606                zipObj.extract(f, path=path)607 608 609def url2file(url):610    # Convert URL to filename, i.e. https://url.com/file.txt?auth -> file.txt611    url = str(Path(url)).replace(':/', '://')  # Pathlib turns :// -> :/612    return Path(urllib.parse.unquote(url)).name.split('?')[0]  # '%2F' to '/', split https://url.com/file.txt?auth613 614 615def download(url, dir='.', unzip=True, delete=True, curl=False, threads=1, retry=3):616    # Multithreaded file download and unzip function, used in data.yaml for autodownload617    def download_one(url, dir):618        # Download 1 file619        success = True620        if os.path.isfile(url):621            f = Path(url)  # filename622        else:  # does not exist623            f = dir / Path(url).name624            LOGGER.info(f'Downloading {url} to {f}...')625            for i in range(retry + 1):626                if curl:627                    s = 'sS' if threads > 1 else ''  # silent628                    r = os.system(629                        f'curl -# -{s}L "{url}" -o "{f}" --retry 9 -C -')  # curl download with retry, continue630                    success = r == 0631                else:632                    torch.hub.download_url_to_file(url, f, progress=threads == 1)  # torch download633                    success = f.is_file()634                if success:635                    break636                elif i < retry:637                    LOGGER.warning(f'⚠️ Download failure, retrying {i + 1}/{retry} {url}...')638                else:639                    LOGGER.warning(f'❌ Failed to download {url}...')640 641        if unzip and success and (f.suffix == '.gz' or is_zipfile(f) or is_tarfile(f)):642            LOGGER.info(f'Unzipping {f}...')643            if is_zipfile(f):644                unzip_file(f, dir)  # unzip645            elif is_tarfile(f):646                os.system(f'tar xf {f} --directory {f.parent}')  # unzip647            elif f.suffix == '.gz':648                os.system(f'tar xfz {f} --directory {f.parent}')  # unzip649            if delete:650                f.unlink()  # remove zip651 652    dir = Path(dir)653    dir.mkdir(parents=True, exist_ok=True)  # make directory654    if threads > 1:655        pool = ThreadPool(threads)656        pool.imap(lambda x: download_one(*x), zip(url, repeat(dir)))  # multithreaded657        pool.close()658        pool.join()659    else:660        for u in [url] if isinstance(url, (str, Path)) else url:661            download_one(u, dir)662 663 664def make_divisible(x, divisor):665    # Returns nearest x divisible by divisor666    if isinstance(divisor, torch.Tensor):667        divisor = int(divisor.max())  # to int668    return math.ceil(x / divisor) * divisor669 670 671def clean_str(s):672    # Cleans a string by replacing special characters with underscore _673    return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)674 675 676def one_cycle(y1=0.0, y2=1.0, steps=100):677    # lambda function for sinusoidal ramp from y1 to y2 https://arxiv.org/pdf/1812.01187.pdf678    return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1679 680 681def one_flat_cycle(y1=0.0, y2=1.0, steps=100):682    # lambda function for sinusoidal ramp from y1 to y2 https://arxiv.org/pdf/1812.01187.pdf683    #return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1684    return lambda x: ((1 - math.cos((x - (steps // 2)) * math.pi / (steps // 2))) / 2) * (y2 - y1) + y1 if (x > (steps // 2)) else y1685 686 687def colorstr(*input):688    # Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e.  colorstr('blue', 'hello world')689    *args, string = input if len(input) > 1 else ('blue', 'bold', input[0])  # color arguments, string690    colors = {691        'black': '\033[30m',  # basic colors692        'red': '\033[31m',693        'green': '\033[32m',694        'yellow': '\033[33m',695        'blue': '\033[34m',696        'magenta': '\033[35m',697        'cyan': '\033[36m',698        'white': '\033[37m',699        'bright_black': '\033[90m',  # bright colors700        'bright_red': '\033[91m',701        'bright_green': '\033[92m',702        'bright_yellow': '\033[93m',703        'bright_blue': '\033[94m',704        'bright_magenta': '\033[95m',705        'bright_cyan': '\033[96m',706        'bright_white': '\033[97m',707        'end': '\033[0m',  # misc708        'bold': '\033[1m',709        'underline': '\033[4m'}710    return ''.join(colors[x] for x in args) + f'{string}' + colors['end']711 712 713def labels_to_class_weights(labels, nc=80):714    # Get class weights (inverse frequency) from training labels715    if labels[0] is None:  # no labels loaded716        return torch.Tensor()717 718    labels = np.concatenate(labels, 0)  # labels.shape = (866643, 5) for COCO719    classes = labels[:, 0].astype(int)  # labels = [class xywh]720    weights = np.bincount(classes, minlength=nc)  # occurrences per class721 722    # Prepend gridpoint count (for uCE training)723    # gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum()  # gridpoints per image724    # weights = np.hstack([gpi * len(labels)  - weights.sum() * 9, weights * 9]) ** 0.5  # prepend gridpoints to start725 726    weights[weights == 0] = 1  # replace empty bins with 1727    weights = 1 / weights  # number of targets per class728    weights /= weights.sum()  # normalize729    return torch.from_numpy(weights).float()730 731 732def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)):733    # Produces image weights based on class_weights and image contents734    # Usage: index = random.choices(range(n), weights=image_weights, k=1)  # weighted image sample735    class_counts = np.array([np.bincount(x[:, 0].astype(int), minlength=nc) for x in labels])736    return (class_weights.reshape(1, nc) * class_counts).sum(1)737 738 739def coco80_to_coco91_class():  # converts 80-index (val2014) to 91-index (paper)740    # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/741    # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')742    # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')743    # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)]  # darknet to coco744    # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)]  # coco to darknet745    return [746        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,747        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,748        64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]749 750 751def xyxy2xywh(x):752    # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right753    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)754    y[..., 0] = (x[..., 0] + x[..., 2]) / 2  # x center755    y[..., 1] = (x[..., 1] + x[..., 3]) / 2  # y center756    y[..., 2] = x[..., 2] - x[..., 0]  # width757    y[..., 3] = x[..., 3] - x[..., 1]  # height758    return y759 760 761def xywh2xyxy(x):762    # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right763    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)764    y[..., 0] = x[..., 0] - x[..., 2] / 2  # top left x765    y[..., 1] = x[..., 1] - x[..., 3] / 2  # top left y766    y[..., 2] = x[..., 0] + x[..., 2] / 2  # bottom right x767    y[..., 3] = x[..., 1] + x[..., 3] / 2  # bottom right y768    return y769 770 771def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):772    # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right773    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)774    y[..., 0] = w * (x[..., 0] - x[..., 2] / 2) + padw  # top left x775    y[..., 1] = h * (x[..., 1] - x[..., 3] / 2) + padh  # top left y776    y[..., 2] = w * (x[..., 0] + x[..., 2] / 2) + padw  # bottom right x777    y[..., 3] = h * (x[..., 1] + x[..., 3] / 2) + padh  # bottom right y778    return y779 780 781def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0):782    # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] normalized where xy1=top-left, xy2=bottom-right783    if clip:784        clip_boxes(x, (h - eps, w - eps))  # warning: inplace clip785    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)786    y[..., 0] = ((x[..., 0] + x[..., 2]) / 2) / w  # x center787    y[..., 1] = ((x[..., 1] + x[..., 3]) / 2) / h  # y center788    y[..., 2] = (x[..., 2] - x[..., 0]) / w  # width789    y[..., 3] = (x[..., 3] - x[..., 1]) / h  # height790    return y791 792 793def xyn2xy(x, w=640, h=640, padw=0, padh=0):794    # Convert normalized segments into pixel segments, shape (n,2)795    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)796    y[..., 0] = w * x[..., 0] + padw  # top left x797    y[..., 1] = h * x[..., 1] + padh  # top left y798    return y799 800 801def segment2box(segment, width=640, height=640):802    # Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy)803    x, y = segment.T  # segment xy804    inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)805    x, y, = x[inside], y[inside]806    return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4))  # xyxy807 808 809def segments2boxes(segments):810    # Convert segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh)811    boxes = []812    for s in segments:813        x, y = s.T  # segment xy814        boxes.append([x.min(), y.min(), x.max(), y.max()])  # cls, xyxy815    return xyxy2xywh(np.array(boxes))  # cls, xywh816 817 818def resample_segments(segments, n=1000):819    # Up-sample an (n,2) segment820    for i, s in enumerate(segments):821        s = np.concatenate((s, s[0:1, :]), axis=0)822        x = np.linspace(0, len(s) - 1, n)823        xp = np.arange(len(s))824        segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T  # segment xy825    return segments826 827 828def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None):829    # Rescale boxes (xyxy) from img1_shape to img0_shape830    if ratio_pad is None:  # calculate from img0_shape831        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain  = old / new832        pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2  # wh padding833    else:834        gain = ratio_pad[0][0]835        pad = ratio_pad[1]836 837    boxes[:, [0, 2]] -= pad[0]  # x padding838    boxes[:, [1, 3]] -= pad[1]  # y padding839    boxes[:, :4] /= gain840    clip_boxes(boxes, img0_shape)841    return boxes842 843 844def scale_segments(img1_shape, segments, img0_shape, ratio_pad=None, normalize=False):845    # Rescale coords (xyxy) from img1_shape to img0_shape846    if ratio_pad is None:  # calculate from img0_shape847        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain  = old / new848        pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2  # wh padding849    else:850        gain = ratio_pad[0][0]851        pad = ratio_pad[1]852 853    segments[:, 0] -= pad[0]  # x padding854    segments[:, 1] -= pad[1]  # y padding855    segments /= gain856    clip_segments(segments, img0_shape)857    if normalize:858        segments[:, 0] /= img0_shape[1]  # width859        segments[:, 1] /= img0_shape[0]  # height860    return segments861 862 863def clip_boxes(boxes, shape):864    # Clip boxes (xyxy) to image shape (height, width)865    if isinstance(boxes, torch.Tensor):  # faster individually866        boxes[:, 0].clamp_(0, shape[1])  # x1867        boxes[:, 1].clamp_(0, shape[0])  # y1868        boxes[:, 2].clamp_(0, shape[1])  # x2869        boxes[:, 3].clamp_(0, shape[0])  # y2870    else:  # np.array (faster grouped)871        boxes[:, [0, 2]] = boxes[:, [0, 2]].clip(0, shape[1])  # x1, x2872        boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, shape[0])  # y1, y2873 874 875def clip_segments(segments, shape):876    # Clip segments (xy1,xy2,...) to image shape (height, width)877    if isinstance(segments, torch.Tensor):  # faster individually878        segments[:, 0].clamp_(0, shape[1])  # x879        segments[:, 1].clamp_(0, shape[0])  # y880    else:  # np.array (faster grouped)881        segments[:, 0] = segments[:, 0].clip(0, shape[1])  # x882        segments[:, 1] = segments[:, 1].clip(0, shape[0])  # y883 884def box_iou_for_nms(box1, box2, GIoU=False, DIoU=False, CIoU=False, SIoU=False, EIou=False, eps=1e-7):885    # Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)886 887    b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)888    b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)889    w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)890    w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)891 892    # Intersection area893    inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \894            (b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0)895 896    # Union Area897    union = w1 * h1 + w2 * h2 - inter + eps898 899    # IoU900    iou = inter / union901    if CIoU or DIoU or GIoU or EIou:902        cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1)  # convex (smallest enclosing box) width903        ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1)  # convex height904        if CIoU or DIoU or EIou:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1905            c2 = cw ** 2 + ch ** 2 + eps  # convex diagonal squared906            rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4  # center dist ** 2907            if CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47908                v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)909                with torch.no_grad():910                    alpha = v / (v - iou + (1 + eps))911                return iou - (rho2 / c2 + v * alpha)  # CIoU912            elif EIou:913                rho_w2 = ((b2_x2 - b2_x1) - (b1_x2 - b1_x1)) ** 2914                rho_h2 = ((b2_y2 - b2_y1) - (b1_y2 - b1_y1)) ** 2915                cw2 = cw ** 2 + eps916                ch2 = ch ** 2 + eps917                return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2)918            return iou - rho2 / c2  # DIoU919        c_area = cw * ch + eps  # convex area920        return iou - (c_area - union) / c_area  # GIoU https://arxiv.org/pdf/1902.09630.pdf921    elif SIoU:922        cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1)  # convex (smallest enclosing box) width923        ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1)  # convex height924        # SIoU Loss https://arxiv.org/pdf/2205.12740.pdf925        s_cw = (b2_x1 + b2_x2 - b1_x1 - b1_x2) * 0.5 + eps926        s_ch = (b2_y1 + b2_y2 - b1_y1 - b1_y2) * 0.5 + eps927        sigma = torch.pow(s_cw ** 2 + s_ch ** 2, 0.5)928        sin_alpha_1 = torch.abs(s_cw) / sigma929        sin_alpha_2 = torch.abs(s_ch) / sigma930        threshold = pow(2, 0.5) / 2931        sin_alpha = torch.where(sin_alpha_1 > threshold, sin_alpha_2, sin_alpha_1)932        angle_cost = torch.cos(torch.arcsin(sin_alpha) * 2 - math.pi / 2)933        rho_x = (s_cw / cw) ** 2934        rho_y = (s_ch / ch) ** 2935        gamma = angle_cost - 2936        distance_cost = 2 - torch.exp(gamma * rho_x) - torch.exp(gamma * rho_y)937        omiga_w = torch.abs(w1 - w2) / torch.max(w1, w2)938        omiga_h = torch.abs(h1 - h2) / torch.max(h1, h2)939        shape_cost = torch.pow(1 - torch.exp(-1 * omiga_w), 4) + torch.pow(1 - torch.exp(-1 * omiga_h), 4)940        return iou - 0.5 * (distance_cost + shape_cost)941    return iou  # IoU942 943 944def soft_nms(bboxes, scores, iou_thresh=0.5,sigma=0.5,score_threshold=0.25):945    order = scores.argsort(descending=True).to(bboxes.device)946    keep = []947    948    while order.numel() > 1:949        if order.numel() == 1:950            keep.append(order[0])951            break952        else:953            i = order[0]954            keep.append(i)955        956        iou = box_iou_for_nms(bboxes[i], bboxes[order[1:]]).squeeze()957        958        idx = (iou > iou_thresh).nonzero().squeeze()959        if idx.numel() > 0: 960            iou = iou[idx] 961            newScores = torch.exp(-torch.pow(iou,2)/sigma)962            scores[order[idx+1]] *= newScores963        964        newOrder = (scores[order[1:]] > score_threshold).nonzero().squeeze() 965        if newOrder.numel() == 0: 966            break967        else:968            maxScoreIndex = torch.argmax(scores[order[newOrder+1]]) 969            if maxScoreIndex != 0: 970                newOrder[[0,maxScoreIndex],] = newOrder[[maxScoreIndex,0],]971            order = order[newOrder+1]972    973    return torch.LongTensor(keep)974 975def non_max_suppression(976        prediction,977        conf_thres=0.25,978        iou_thres=0.45,979        classes=None,980        agnostic=False,981        multi_label=False,982        labels=(),983        max_det=300,984        nm=0,  # number of masks985):986    """Non-Maximum Suppression (NMS) on inference results to reject overlapping detections987 988    Returns:989         list of detections, on (n,6) tensor per image [xyxy, conf, cls]990    """991 992    if isinstance(prediction, (list, tuple)):  # YOLO model in validation model, output = (inference_out, loss_out)993        prediction = prediction[0][0]  # select only inference output994 995    996    device = prediction.device997    mps = 'mps' in device.type  # Apple MPS998    if mps:  # MPS not fully supported yet, convert tensors to CPU before NMS999        prediction = prediction.cpu()1000    bs = prediction.shape[0]  # batch size1001    nc = prediction.shape[1] - nm - 4  # number of classes1002    mi = 4 + nc  # mask start index1003    xc = prediction[:, 4:mi].amax(1) > conf_thres  # candidates1004 1005    # Checks1006    assert 0 <= conf_thres <= 1, f'Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0'1007    assert 0 <= iou_thres <= 1, f'Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0'1008 1009    # Settings1010    # min_wh = 2  # (pixels) minimum box width and height1011    max_wh = 7680  # (pixels) maximum box width and height1012    max_nms = 30000  # maximum number of boxes into torchvision.ops.nms()1013    time_limit = 2.5 + 0.05 * bs  # seconds to quit after1014    redundant = True  # require redundant detections1015    multi_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)1016    merge = False  # use merge-NMS1017 1018    t = time.time()1019    output = [torch.zeros((0, 6 + nm), device=prediction.device)] * bs1020    for xi, x in enumerate(prediction):  # image index, image inference1021        # Apply constraints1022        # x[((x[:, 2:4] < min_wh) | (x[:, 2:4] > max_wh)).any(1), 4] = 0  # width-height1023        x = x.T[xc[xi]]  # confidence1024 1025        # Cat apriori labels if autolabelling1026        if labels and len(labels[xi]):1027            lb = labels[xi]1028            v = torch.zeros((len(lb), nc + nm + 5), device=x.device)1029            v[:, :4] = lb[:, 1:5]  # box1030            v[range(len(lb)), lb[:, 0].long() + 4] = 1.0  # cls1031            x = torch.cat((x, v), 0)1032 1033        # If none remain process next image1034        if not x.shape[0]:1035            continue1036 1037        # Detections matrix nx6 (xyxy, conf, cls)1038        box, cls, mask = x.split((4, nc, nm), 1)1039        box = xywh2xyxy(box)  # center_x, center_y, width, height) to (x1, y1, x2, y2)1040        if multi_label:1041            i, j = (cls > conf_thres).nonzero(as_tuple=False).T1042            x = torch.cat((box[i], x[i, 4 + j, None], j[:, None].float(), mask[i]), 1)1043        else:  # best class only1044            conf, j = cls.max(1, keepdim=True)1045            x = torch.cat((box, conf, j.float(), mask), 1)[conf.view(-1) > conf_thres]1046 1047        # Filter by class1048        if classes is not None:1049            x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]1050 1051        # Apply finite constraint1052        # if not torch.isfinite(x).all():1053        #     x = x[torch.isfinite(x).all(1)]1054 1055        # Check shape1056        n = x.shape[0]  # number of boxes1057        if not n:  # no boxes1058            continue1059        elif n > max_nms:  # excess boxes1060            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence1061        else:1062            x = x[x[:, 4].argsort(descending=True)]  # sort by confidence1063 1064        # Batched NMS1065        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes1066        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores1067        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS1068        # i = soft_nms(boxes, scores, iou_thres)1069        if i.shape[0] > max_det:  # limit detections1070            i = i[:max_det]1071        if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)1072            # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)1073            iou = box_iou(boxes[i], boxes) > iou_thres  # iou matrix1074            weights = iou * scores[None]  # box weights1075            x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes1076            if redundant:1077                i = i[iou.sum(1) > 1]  # require redundancy1078 1079        output[xi] = x[i]1080        if mps:1081            output[xi] = output[xi].to(device)1082        if (time.time() - t) > time_limit:1083            LOGGER.warning(f'WARNING ⚠️ NMS time limit {time_limit:.3f}s exceeded')1084            break  # time limit exceeded1085 1086    return output1087 1088 1089def strip_optimizer(f='best.pt', s=''):  # from utils.general import *; strip_optimizer()1090    # Strip optimizer from 'f' to finalize training, optionally save as 's'1091    x = torch.load(f, map_location=torch.device('cpu'))1092    if x.get('ema'):1093        x['model'] = x['ema']  # replace model with ema1094    for k in 'optimizer', 'best_fitness', 'ema', 'updates':  # keys1095        x[k] = None1096    x['epoch'] = -11097    x['model'].half()  # to FP161098    for p in x['model'].parameters():1099        p.requires_grad = False1100    torch.save(x, s or f)1101    mb = os.path.getsize(s or f) / 1E6  # filesize1102    LOGGER.info(f"Optimizer stripped from {f},{f' saved as {s},' if s else ''} {mb:.1f}MB")1103 1104 1105def print_mutation(keys, results, hyp, save_dir, bucket, prefix=colorstr('evolve: ')):1106    evolve_csv = save_dir / 'evolve.csv'1107    evolve_yaml = save_dir / 'hyp_evolve.yaml'1108    keys = tuple(keys) + tuple(hyp.keys())  # [results + hyps]1109    keys = tuple(x.strip() for x in keys)1110    vals = results + tuple(hyp.values())1111    n = len(keys)1112 1113    # Download (optional)1114    if bucket:1115        url = f'gs://{bucket}/evolve.csv'1116        if gsutil_getsize(url) > (evolve_csv.stat().st_size if evolve_csv.exists() else 0):1117            os.system(f'gsutil cp {url} {save_dir}')  # download evolve.csv if larger than local1118 1119    # Log to evolve.csv1120    s = '' if evolve_csv.exists() else (('%20s,' * n % keys).rstrip(',') + '\n')  # add header1121    with open(evolve_csv, 'a') as f:1122        f.write(s + ('%20.5g,' * n % vals).rstrip(',') + '\n')1123 1124    # Save yaml1125    with open(evolve_yaml, 'w') as f:1126        data = pd.read_csv(evolve_csv)1127        data = data.rename(columns=lambda x: x.strip())  # strip keys1128        i = np.argmax(fitness(data.values[:, :4]))  #1129        generations = len(data)1130        f.write('# YOLO Hyperparameter Evolution Results\n' + f'# Best generation: {i}\n' +1131                f'# Last generation: {generations - 1}\n' + '# ' + ', '.join(f'{x.strip():>20s}' for x in keys[:7]) +1132                '\n' + '# ' + ', '.join(f'{x:>20.5g}' for x in data.values[i, :7]) + '\n\n')1133        yaml.safe_dump(data.loc[i][7:].to_dict(), f, sort_keys=False)1134 1135    # Print to screen1136    LOGGER.info(prefix + f'{generations} generations finished, current result:\n' + prefix +1137                ', '.join(f'{x.strip():>20s}' for x in keys) + '\n' + prefix + ', '.join(f'{x:20.5g}'1138                                                                                         for x in vals) + '\n\n')1139 1140    if bucket:1141        os.system(f'gsutil cp {evolve_csv} {evolve_yaml} gs://{bucket}')  # upload1142 1143 1144def apply_classifier(x, model, img, im0):1145    # Apply a second stage classifier to YOLO outputs1146    # Example model = torchvision.models.__dict__['efficientnet_b0'](pretrained=True).to(device).eval()1147    im0 = [im0] if isinstance(im0, np.ndarray) else im01148    for i, d in enumerate(x):  # per image1149        if d is not None and len(d):1150            d = d.clone()1151 1152            # Reshape and pad cutouts1153            b = xyxy2xywh(d[:, :4])  # boxes1154            b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1)  # rectangle to square1155            b[:, 2:] = b[:, 2:] * 1.3 + 30  # pad1156            d[:, :4] = xywh2xyxy(b).long()1157 1158            # Rescale boxes from img_size to im0 size1159            scale_boxes(img.shape[2:], d[:, :4], im0[i].shape)1160 1161            # Classes1162            pred_cls1 = d[:, 5].long()1163            ims = []1164            for a in d:1165                cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])]1166                im = cv2.resize(cutout, (224, 224))  # BGR1167 1168                im = im[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x4161169                im = np.ascontiguousarray(im, dtype=np.float32)  # uint8 to float321170                im /= 255  # 0 - 255 to 0.0 - 1.01171                ims.append(im)1172 1173            pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1)  # classifier prediction1174            x[i] = x[i][pred_cls1 == pred_cls2]  # retain matching class detections1175 1176    return x1177 1178 1179def increment_path(path, exist_ok=False, sep='', mkdir=False):1180    # Increment file or directory path, i.e. runs/exp --> runs/exp{sep}2, runs/exp{sep}3, ... etc.1181    path = Path(path)  # os-agnostic1182    if path.exists() and not exist_ok:1183        path, suffix = (path.with_suffix(''), path.suffix) if path.is_file() else (path, '')1184 1185        # Method 11186        for n in range(2, 9999):1187            p = f'{path}{sep}{n}{suffix}'  # increment path1188            if not os.path.exists(p):  #1189                break1190        path = Path(p)1191 1192        # Method 2 (deprecated)1193        # dirs = glob.glob(f"{path}{sep}*")  # similar paths1194        # matches = [re.search(rf"{path.stem}{sep}(\d+)", d) for d in dirs]1195        # i = [int(m.groups()[0]) for m in matches if m]  # indices1196        # n = max(i) + 1 if i else 2  # increment number1197        # path = Path(f"{path}{sep}{n}{suffix}")  # increment path1198 1199    if mkdir:1200        path.mkdir(parents=True, exist_ok=True)  # make directory

Showing the first 1,200 of 1228 lines. Download the file for the rest.