|
|
|
import glob
|
|
|
|
import inspect
|
|
|
|
import platform
|
|
|
|
import urllib
|
|
|
|
from pathlib import Path
|
|
|
|
from subprocess import check_output
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
import cv2
|
|
|
|
import numpy as np
|
|
|
|
import pkg_resources as pkg
|
|
|
|
import torch
|
|
|
|
|
|
|
|
from ultralytics.yolo.utils import (AUTOINSTALL, FONT, LOGGER, ROOT, USER_CONFIG_DIR, TryExcept, colorstr, emojis,
|
|
|
|
is_docker, is_jupyter_notebook)
|
|
|
|
from ultralytics.yolo.utils.ops import make_divisible
|
|
|
|
|
|
|
|
|
|
|
|
def is_ascii(s=''):
|
|
|
|
# Is string composed of all ASCII (no UTF) characters? (note str().isascii() introduced in python 3.7)
|
|
|
|
s = str(s) # convert list, tuple, None, etc. to str
|
|
|
|
return len(s.encode().decode('ascii', 'ignore')) == len(s)
|
|
|
|
|
|
|
|
|
|
|
|
def check_imgsz(imgsz, stride=32, min_dim=1, floor=0):
|
|
|
|
# Verify image size is a multiple of stride s in each dimension
|
|
|
|
|
|
|
|
stride = int(stride.max() if isinstance(stride, torch.Tensor) else stride)
|
|
|
|
if isinstance(imgsz, int): # integer i.e. imgsz=640
|
|
|
|
sz = max(make_divisible(imgsz, stride), floor)
|
|
|
|
else: # list i.e. imgsz=[640, 480]
|
|
|
|
imgsz = list(imgsz) # convert to list if tuple
|
|
|
|
sz = [max(make_divisible(x, stride), floor) for x in imgsz]
|
|
|
|
if sz != imgsz:
|
|
|
|
LOGGER.warning(f'WARNING ⚠️ --img-size {imgsz} must be multiple of max stride {stride}, updating to {sz}')
|
|
|
|
|
|
|
|
# Check dims
|
|
|
|
if min_dim == 2:
|
|
|
|
if isinstance(imgsz, int):
|
|
|
|
sz = [sz, sz]
|
|
|
|
elif len(sz) == 1:
|
|
|
|
sz = [sz[0], sz[0]]
|
|
|
|
|
|
|
|
return sz
|
|
|
|
|
|
|
|
|
|
|
|
def check_version(current="0.0.0", minimum="0.0.0", name="version ", pinned=False, hard=False, verbose=False):
|
|
|
|
# Check version vs. required version
|
|
|
|
current, minimum = (pkg.parse_version(x) for x in (current, minimum))
|
|
|
|
result = (current == minimum) if pinned else (current >= minimum) # bool
|
|
|
|
s = f"WARNING ⚠️ {name}{minimum} is required by YOLOv5, but {name}{current} is currently installed" # string
|
|
|
|
if hard:
|
|
|
|
assert result, emojis(s) # assert min requirements met
|
|
|
|
if verbose and not result:
|
|
|
|
LOGGER.warning(s)
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def check_font(font=FONT, progress=False):
|
|
|
|
# Download font to CONFIG_DIR if necessary
|
|
|
|
font = Path(font)
|
|
|
|
file = USER_CONFIG_DIR / font.name
|
|
|
|
if not font.exists() and not file.exists():
|
|
|
|
url = f'https://ultralytics.com/assets/{font.name}'
|
|
|
|
LOGGER.info(f'Downloading {url} to {file}...')
|
|
|
|
torch.hub.download_url_to_file(url, str(file), progress=progress)
|
|
|
|
|
|
|
|
|
|
|
|
def check_online():
|
|
|
|
# Check internet connectivity
|
|
|
|
import socket
|
|
|
|
try:
|
|
|
|
socket.create_connection(("1.1.1.1", 443), 5) # check host accessibility
|
|
|
|
return True
|
|
|
|
except OSError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def check_python(minimum='3.7.0'):
|
|
|
|
# Check current python version vs. required python version
|
|
|
|
check_version(platform.python_version(), minimum, name='Python ', hard=True)
|
|
|
|
|
|
|
|
|
|
|
|
@TryExcept()
|
|
|
|
def check_requirements(requirements=ROOT / 'requirements.txt', exclude=(), install=True, cmds=''):
|
|
|
|
# Check installed dependencies meet YOLOv5 requirements (pass *.txt file or list of packages or single package str)
|
|
|
|
prefix = colorstr('red', 'bold', 'requirements:')
|
|
|
|
check_python() # check python version
|
|
|
|
if isinstance(requirements, Path): # requirements.txt file
|
|
|
|
file = requirements.resolve()
|
|
|
|
assert file.exists(), f"{prefix} {file} not found, check failed."
|
|
|
|
with file.open() as f:
|
|
|
|
requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(f) if x.name not in exclude]
|
|
|
|
elif isinstance(requirements, str):
|
|
|
|
requirements = [requirements]
|
|
|
|
|
|
|
|
s = ''
|
|
|
|
n = 0
|
|
|
|
for r in requirements:
|
|
|
|
try:
|
|
|
|
pkg.require(r)
|
|
|
|
except (pkg.VersionConflict, pkg.DistributionNotFound): # exception if requirements not met
|
|
|
|
s += f'"{r}" '
|
|
|
|
n += 1
|
|
|
|
|
|
|
|
if s and install and AUTOINSTALL: # check environment variable
|
|
|
|
LOGGER.info(f"{prefix} YOLOv5 requirement{'s' * (n > 1)} {s}not found, attempting AutoUpdate...")
|
|
|
|
try:
|
|
|
|
assert check_online(), "AutoUpdate skipped (offline)"
|
|
|
|
LOGGER.info(check_output(f'pip install {s} {cmds}', shell=True).decode())
|
|
|
|
source = file if 'file' in locals() else requirements
|
|
|
|
s = f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n" \
|
|
|
|
f"{prefix} ⚠️ {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n"
|
|
|
|
LOGGER.info(s)
|
|
|
|
except Exception as e:
|
|
|
|
LOGGER.warning(f'{prefix} ❌ {e}')
|
|
|
|
|
|
|
|
|
|
|
|
def check_suffix(file='yolov8n.pt', suffix=('.pt',), msg=''):
|
|
|
|
# Check file(s) for acceptable suffix
|
|
|
|
if file and suffix:
|
|
|
|
if isinstance(suffix, str):
|
|
|
|
suffix = [suffix]
|
|
|
|
for f in file if isinstance(file, (list, tuple)) else [file]:
|
|
|
|
s = Path(f).suffix.lower() # file suffix
|
|
|
|
if len(s):
|
|
|
|
assert s in suffix, f"{msg}{f} acceptable suffix is {suffix}"
|
|
|
|
|
|
|
|
|
|
|
|
def check_file(file, suffix=''):
|
|
|
|
# Search/download file (if necessary) and return path
|
|
|
|
check_suffix(file, suffix) # optional
|
|
|
|
file = str(file) # convert to str()
|
|
|
|
if Path(file).is_file() or not file: # exists
|
|
|
|
return file
|
|
|
|
elif file.startswith(('http:/', 'https:/')): # download
|
|
|
|
url = file # warning: Pathlib turns :// -> :/
|
|
|
|
file = Path(urllib.parse.unquote(file).split('?')[0]).name # '%2F' to '/', split https://url.com/file.txt?auth
|
|
|
|
if Path(file).is_file():
|
|
|
|
LOGGER.info(f'Found {url} locally at {file}') # file already exists
|
|
|
|
else:
|
|
|
|
LOGGER.info(f'Downloading {url} to {file}...')
|
|
|
|
torch.hub.download_url_to_file(url, file)
|
|
|
|
assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}' # check
|
|
|
|
return file
|
|
|
|
else: # search
|
|
|
|
files = []
|
|
|
|
for d in 'data', 'v8', 'utils': # search directories
|
|
|
|
files.extend(glob.glob(str(ROOT / "yolo" / d / '**' / file), recursive=True)) # find file
|
|
|
|
assert len(files), f'File not found: {file}' # assert file was found
|
|
|
|
assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}" # assert unique
|
|
|
|
return files[0] # return file
|
|
|
|
|
|
|
|
|
|
|
|
def check_yaml(file, suffix=('.yaml', '.yml')):
|
|
|
|
# Search/download YAML file (if necessary) and return path, checking suffix
|
|
|
|
return check_file(file, suffix)
|
|
|
|
|
|
|
|
|
|
|
|
def check_imshow(warn=False):
|
|
|
|
# Check if environment supports image displays
|
|
|
|
try:
|
|
|
|
assert not is_jupyter_notebook()
|
|
|
|
assert not is_docker()
|
|
|
|
cv2.imshow('test', np.zeros((1, 1, 3)))
|
|
|
|
cv2.waitKey(1)
|
|
|
|
cv2.destroyAllWindows()
|
|
|
|
cv2.waitKey(1)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
|
|
if warn:
|
|
|
|
LOGGER.warning(f'WARNING ⚠️ Environment does not support cv2.imshow() or PIL Image.show()\n{e}')
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def git_describe(path=ROOT): # path must be a directory
|
|
|
|
# Return human-readable git description, i.e. v5.0-5-g3e25f1e https://git-scm.com/docs/git-describe
|
|
|
|
try:
|
|
|
|
assert (Path(path) / '.git').is_dir()
|
|
|
|
return check_output(f'git -C {path} describe --tags --long --always', shell=True).decode()[:-1]
|
|
|
|
except Exception:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
|
|
|
|
def print_args(args: Optional[dict] = None, show_file=True, show_func=False):
|
|
|
|
# Print function arguments (optional args dict)
|
|
|
|
x = inspect.currentframe().f_back # previous frame
|
|
|
|
file, _, func, _, _ = inspect.getframeinfo(x)
|
|
|
|
if args is None: # get args automatically
|
|
|
|
args, _, _, frm = inspect.getargvalues(x)
|
|
|
|
args = {k: v for k, v in frm.items() if k in args}
|
|
|
|
try:
|
|
|
|
file = Path(file).resolve().relative_to(ROOT).with_suffix('')
|
|
|
|
except ValueError:
|
|
|
|
file = Path(file).stem
|
|
|
|
s = (f'{file}: ' if show_file else '') + (f'{func}: ' if show_func else '')
|
|
|
|
LOGGER.info(colorstr(s) + ', '.join(f'{k}={v}' for k, v in args.items()))
|