Spaces:
Sleeping
Sleeping
# Ultralytics YOLO π, GPL-3.0 license | |
import os | |
import platform | |
import shutil | |
import sys | |
import threading | |
import time | |
from pathlib import Path | |
from random import random | |
import requests | |
from tqdm import tqdm | |
from ultralytics.yolo.utils import (ENVIRONMENT, LOGGER, ONLINE, RANK, SETTINGS, TESTS_RUNNING, TQDM_BAR_FORMAT, | |
TryExcept, __version__, colorstr, emojis, get_git_origin_url, is_colab, is_git_dir, | |
is_pip_package) | |
PREFIX = colorstr('Ultralytics HUB: ') | |
HELP_MSG = 'If this issue persists please visit https://github.com/ultralytics/hub/issues for assistance.' | |
HUB_API_ROOT = os.environ.get('ULTRALYTICS_HUB_API', 'https://api.ultralytics.com') | |
def check_dataset_disk_space(url='https://ultralytics.com/assets/coco128.zip', sf=2.0): | |
""" | |
Check if there is sufficient disk space to download and store a dataset. | |
Args: | |
url (str, optional): The URL to the dataset file. Defaults to 'https://ultralytics.com/assets/coco128.zip'. | |
sf (float, optional): Safety factor, the multiplier for the required free space. Defaults to 2.0. | |
Returns: | |
bool: True if there is sufficient disk space, False otherwise. | |
""" | |
gib = 1 << 30 # bytes per GiB | |
data = int(requests.head(url).headers['Content-Length']) / gib # dataset size (GB) | |
total, used, free = (x / gib for x in shutil.disk_usage('/')) # bytes | |
LOGGER.info(f'{PREFIX}{data:.3f} GB dataset, {free:.1f}/{total:.1f} GB free disk space') | |
if data * sf < free: | |
return True # sufficient space | |
LOGGER.warning(f'{PREFIX}WARNING: Insufficient free disk space {free:.1f} GB < {data * sf:.3f} GB required, ' | |
f'training cancelled β. Please free {data * sf - free:.1f} GB additional disk space and try again.') | |
return False # insufficient space | |
def request_with_credentials(url: str) -> any: | |
""" | |
Make an AJAX request with cookies attached in a Google Colab environment. | |
Args: | |
url (str): The URL to make the request to. | |
Returns: | |
any: The response data from the AJAX request. | |
Raises: | |
OSError: If the function is not run in a Google Colab environment. | |
""" | |
if not is_colab(): | |
raise OSError('request_with_credentials() must run in a Colab environment') | |
from google.colab import output # noqa | |
from IPython import display # noqa | |
display.display( | |
display.Javascript(""" | |
window._hub_tmp = new Promise((resolve, reject) => { | |
const timeout = setTimeout(() => reject("Failed authenticating existing browser session"), 5000) | |
fetch("%s", { | |
method: 'POST', | |
credentials: 'include' | |
}) | |
.then((response) => resolve(response.json())) | |
.then((json) => { | |
clearTimeout(timeout); | |
}).catch((err) => { | |
clearTimeout(timeout); | |
reject(err); | |
}); | |
}); | |
""" % url)) | |
return output.eval_js('_hub_tmp') | |
def split_key(key=''): | |
""" | |
Verify and split a 'api_key[sep]model_id' string, sep is one of '.' or '_' | |
Args: | |
key (str): The model key to split. If not provided, the user will be prompted to enter it. | |
Returns: | |
Tuple[str, str]: A tuple containing the API key and model ID. | |
""" | |
import getpass | |
error_string = emojis(f'{PREFIX}Invalid API key β οΈ\n') # error string | |
if not key: | |
key = getpass.getpass('Enter model key: ') | |
sep = '_' if '_' in key else None # separator | |
assert sep, error_string | |
api_key, model_id = key.split(sep) | |
assert len(api_key) and len(model_id), error_string | |
return api_key, model_id | |
def requests_with_progress(method, url, **kwargs): | |
""" | |
Make an HTTP request using the specified method and URL, with an optional progress bar. | |
Args: | |
method (str): The HTTP method to use (e.g. 'GET', 'POST'). | |
url (str): The URL to send the request to. | |
progress (bool, optional): Whether to display a progress bar. Defaults to False. | |
**kwargs: Additional keyword arguments to pass to the underlying `requests.request` function. | |
Returns: | |
requests.Response: The response from the HTTP request. | |
""" | |
progress = kwargs.pop('progress', False) | |
if not progress: | |
return requests.request(method, url, **kwargs) | |
response = requests.request(method, url, stream=True, **kwargs) | |
total = int(response.headers.get('content-length', 0)) # total size | |
pbar = tqdm(total=total, unit='B', unit_scale=True, unit_divisor=1024, bar_format=TQDM_BAR_FORMAT) | |
for data in response.iter_content(chunk_size=1024): | |
pbar.update(len(data)) | |
pbar.close() | |
return response | |
def smart_request(method, url, retry=3, timeout=30, thread=True, code=-1, verbose=True, progress=False, **kwargs): | |
""" | |
Makes an HTTP request using the 'requests' library, with exponential backoff retries up to a specified timeout. | |
Args: | |
method (str): The HTTP method to use for the request. Choices are 'post' and 'get'. | |
url (str): The URL to make the request to. | |
retry (int, optional): Number of retries to attempt before giving up. Default is 3. | |
timeout (int, optional): Timeout in seconds after which the function will give up retrying. Default is 30. | |
thread (bool, optional): Whether to execute the request in a separate daemon thread. Default is True. | |
code (int, optional): An identifier for the request, used for logging purposes. Default is -1. | |
verbose (bool, optional): A flag to determine whether to print out to console or not. Default is True. | |
progress (bool, optional): Whether to show a progress bar during the request. Default is False. | |
**kwargs: Keyword arguments to be passed to the requests function specified in method. | |
Returns: | |
requests.Response: The HTTP response object. If the request is executed in a separate thread, returns None. | |
""" | |
retry_codes = (408, 500) # retry only these codes | |
def func(func_method, func_url, **func_kwargs): | |
r = None # response | |
t0 = time.time() # initial time for timer | |
for i in range(retry + 1): | |
if (time.time() - t0) > timeout: | |
break | |
r = requests_with_progress(func_method, func_url, **func_kwargs) # i.e. get(url, data, json, files) | |
if r.status_code == 200: | |
break | |
try: | |
m = r.json().get('message', 'No JSON message.') | |
except AttributeError: | |
m = 'Unable to read JSON.' | |
if i == 0: | |
if r.status_code in retry_codes: | |
m += f' Retrying {retry}x for {timeout}s.' if retry else '' | |
elif r.status_code == 429: # rate limit | |
h = r.headers # response headers | |
m = f"Rate limit reached ({h['X-RateLimit-Remaining']}/{h['X-RateLimit-Limit']}). " \ | |
f"Please retry after {h['Retry-After']}s." | |
if verbose: | |
LOGGER.warning(f'{PREFIX}{m} {HELP_MSG} ({r.status_code} #{code})') | |
if r.status_code not in retry_codes: | |
return r | |
time.sleep(2 ** i) # exponential standoff | |
return r | |
args = method, url | |
kwargs['progress'] = progress | |
if thread: | |
threading.Thread(target=func, args=args, kwargs=kwargs, daemon=True).start() | |
else: | |
return func(*args, **kwargs) | |
class Traces: | |
def __init__(self): | |
""" | |
Initialize Traces for error tracking and reporting if tests are not currently running. | |
Sets the rate limit, timer, and metadata attributes, and determines whether Traces are enabled. | |
""" | |
self.rate_limit = 60.0 # rate limit (seconds) | |
self.t = 0.0 # rate limit timer (seconds) | |
self.metadata = { | |
'sys_argv_name': Path(sys.argv[0]).name, | |
'install': 'git' if is_git_dir() else 'pip' if is_pip_package() else 'other', | |
'python': platform.python_version(), | |
'release': __version__, | |
'environment': ENVIRONMENT} | |
self.enabled = \ | |
SETTINGS['sync'] and \ | |
RANK in (-1, 0) and \ | |
not TESTS_RUNNING and \ | |
ONLINE and \ | |
(is_pip_package() or get_git_origin_url() == 'https://github.com/ultralytics/ultralytics.git') | |
self._reset_usage() | |
def __call__(self, cfg, all_keys=False, traces_sample_rate=1.0): | |
""" | |
Sync traces data if enabled in the global settings. | |
Args: | |
cfg (IterableSimpleNamespace): Configuration for the task and mode. | |
all_keys (bool): Sync all items, not just non-default values. | |
traces_sample_rate (float): Fraction of traces captured from 0.0 to 1.0. | |
""" | |
# Increment usage | |
self.usage['modes'][cfg.mode] = self.usage['modes'].get(cfg.mode, 0) + 1 | |
self.usage['tasks'][cfg.task] = self.usage['tasks'].get(cfg.task, 0) + 1 | |
t = time.time() # current time | |
if not self.enabled or random() > traces_sample_rate: | |
# Traces disabled or not randomly selected, do nothing | |
return | |
elif (t - self.t) < self.rate_limit: | |
# Time is under rate limiter, do nothing | |
return | |
else: | |
# Time is over rate limiter, send trace now | |
trace = {'uuid': SETTINGS['uuid'], 'usage': self.usage.copy(), 'metadata': self.metadata} | |
# Send a request to the HUB API to sync analytics | |
smart_request('post', f'{HUB_API_ROOT}/v1/usage/anonymous', json=trace, code=3, retry=0, verbose=False) | |
# Reset usage and rate limit timer | |
self._reset_usage() | |
self.t = t | |
def _reset_usage(self): | |
"""Reset the usage dictionary by initializing keys for each task and mode with a value of 0.""" | |
from ultralytics.yolo.cfg import MODES, TASKS | |
self.usage = {'tasks': {k: 0 for k in TASKS}, 'modes': {k: 0 for k in MODES}} | |
# Run below code on hub/utils init ------------------------------------------------------------------------------------- | |
traces = Traces() | |