k20hcmus/FishEye8K
3
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