diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 947bd24..a6d60d0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,17 +46,14 @@ jobs: - name: Test HUB training shell: python env: - APIKEY: ${{ secrets.ULTRALYTICS_HUB_APIKEY }} + API_KEY: ${{ secrets.ULTRALYTICS_HUB_API_KEY }} + MODEL_ID: ${{ secrets.ULTRALYTICS_HUB_MODEL_ID }} run: | import os - from pathlib import Path from ultralytics import YOLO, hub - from ultralytics.yolo.utils import USER_CONFIG_DIR - Path(USER_CONFIG_DIR / 'settings.yaml').unlink() - key = os.environ['APIKEY'] - hub.reset_model(key) - key, model_id = key.split('_') - hub.login(key) + api_key, model_id = os.environ['API_KEY'], os.environ['MODEL_ID'] + hub.login(api_key) + hub.reset_model(model_id) model = YOLO('https://hub.ultralytics.com/models/' + model_id) model.train() diff --git a/ultralytics/__init__.py b/ultralytics/__init__.py index 83bc5c1..08a183e 100644 --- a/ultralytics/__init__.py +++ b/ultralytics/__init__.py @@ -1,6 +1,6 @@ # Ultralytics YOLO 🚀, GPL-3.0 license -__version__ = '8.0.68' +__version__ = '8.0.69' from ultralytics.hub import start from ultralytics.yolo.engine.model import YOLO diff --git a/ultralytics/hub/__init__.py b/ultralytics/hub/__init__.py index 40ea503..a907ad7 100644 --- a/ultralytics/hub/__init__.py +++ b/ultralytics/hub/__init__.py @@ -2,7 +2,8 @@ import requests -from ultralytics.hub.utils import PREFIX, split_key +from ultralytics.hub.auth import Auth +from ultralytics.hub.utils import PREFIX from ultralytics.yolo.utils import LOGGER, SETTINGS, USER_CONFIG_DIR, yaml_save @@ -17,7 +18,6 @@ def login(api_key=''): from ultralytics import hub hub.login('API_KEY') """ - from ultralytics.hub.auth import Auth Auth(api_key) @@ -42,20 +42,20 @@ def start(key=''): key (str, optional): A string containing either the API key and model ID combination (apikey_modelid), or the full model URL (https://hub.ultralytics.com/models/apikey_modelid). """ + api_key, model_id = key.split('_') LOGGER.warning(f""" -WARNING ⚠️ ultralytics.start() is deprecated in 8.0.60. Updated usage to train your Ultralytics HUB model is below: +WARNING ⚠️ ultralytics.start() is deprecated after 8.0.60. Updated usage to train Ultralytics HUB models is: -from ultralytics import YOLO +from ultralytics import YOLO, hub -model = YOLO('https://hub.ultralytics.com/models/{key}') +hub.login('{api_key}') +model = YOLO('https://hub.ultralytics.com/models/{model_id}') model.train()""") -def reset_model(key=''): +def reset_model(model_id=''): # Reset a trained model to an untrained state - api_key, model_id = split_key(key) - r = requests.post('https://api.ultralytics.com/model-reset', json={'apiKey': api_key, 'modelId': model_id}) - + r = requests.post('https://api.ultralytics.com/model-reset', json={'apiKey': Auth().api_key, 'modelId': model_id}) if r.status_code == 200: LOGGER.info(f'{PREFIX}Model reset successfully') return @@ -68,26 +68,24 @@ def export_fmts_hub(): return list(export_formats()['Argument'][1:]) + ['ultralytics_tflite', 'ultralytics_coreml'] -def export_model(key='', format='torchscript'): +def export_model(model_id='', format='torchscript'): # Export a model to all formats assert format in export_fmts_hub(), f"Unsupported export format '{format}', valid formats are {export_fmts_hub()}" - api_key, model_id = split_key(key) r = requests.post('https://api.ultralytics.com/export', json={ - 'apiKey': api_key, + 'apiKey': Auth().api_key, 'modelId': model_id, 'format': format}) assert r.status_code == 200, f'{PREFIX}{format} export failure {r.status_code} {r.reason}' LOGGER.info(f'{PREFIX}{format} export started ✅') -def get_export(key='', format='torchscript'): +def get_export(model_id='', format='torchscript'): # Get an exported model dictionary with download URL assert format in export_fmts_hub, f"Unsupported export format '{format}', valid formats are {export_fmts_hub}" - api_key, model_id = split_key(key) r = requests.post('https://api.ultralytics.com/get-export', json={ - 'apiKey': api_key, + 'apiKey': Auth().api_key, 'modelId': model_id, 'format': format}) assert r.status_code == 200, f'{PREFIX}{format} get_export failure {r.status_code} {r.reason}' diff --git a/ultralytics/hub/utils.py b/ultralytics/hub/utils.py index 41e3770..a6c0eac 100644 --- a/ultralytics/hub/utils.py +++ b/ultralytics/hub/utils.py @@ -13,7 +13,7 @@ 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, + TryExcept, __version__, colorstr, get_git_origin_url, is_colab, is_git_dir, is_pip_package) PREFIX = colorstr('Ultralytics HUB: ') @@ -80,29 +80,6 @@ def request_with_credentials(url: str) -> any: 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. diff --git a/ultralytics/yolo/utils/callbacks/clearml.py b/ultralytics/yolo/utils/callbacks/clearml.py index bc73a12..ea3067f 100644 --- a/ultralytics/yolo/utils/callbacks/clearml.py +++ b/ultralytics/yolo/utils/callbacks/clearml.py @@ -27,14 +27,16 @@ def _log_debug_samples(files, title='Debug Samples'): files (List(PosixPath)) a list of file paths in PosixPath format title (str) A title that groups together images with the same values """ - for f in files: - if f.exists(): - it = re.search(r'_batch(\d+)', f.name) - iteration = int(it.groups()[0]) if it else 0 - Task.current_task().get_logger().report_image(title=title, - series=f.name.replace(it.group(), ''), - local_path=str(f), - iteration=iteration) + task = Task.current_task() + if task: + for f in files: + if f.exists(): + it = re.search(r'_batch(\d+)', f.name) + iteration = int(it.groups()[0]) if it else 0 + task.get_logger().report_image(title=title, + series=f.name.replace(it.group(), ''), + local_path=str(f), + iteration=iteration) def _log_plot(title, plot_path): @@ -54,11 +56,9 @@ def _log_plot(title, plot_path): def on_pretrain_routine_start(trainer): - # TODO: reuse existing task try: - if Task.current_task(): - task = Task.current_task() - + task = Task.current_task() + if task: # Make sure the automatic pytorch and matplotlib bindings are disabled! # We are logging these plots and model files manually in the integration PatchPyTorchModelIO.update_current_task(None) @@ -80,43 +80,46 @@ def on_pretrain_routine_start(trainer): def on_train_epoch_end(trainer): - if trainer.epoch == 1: + if trainer.epoch == 1 and Task.current_task(): _log_debug_samples(sorted(trainer.save_dir.glob('train_batch*.jpg')), 'Mosaic') def on_fit_epoch_end(trainer): - # You should have access to the validation bboxes under jdict - Task.current_task().get_logger().report_scalar(title='Epoch Time', - series='Epoch Time', - value=trainer.epoch_time, - iteration=trainer.epoch) - if trainer.epoch == 0: - model_info = { - 'model/parameters': get_num_params(trainer.model), - 'model/GFLOPs': round(get_flops(trainer.model), 3), - 'model/speed(ms)': round(trainer.validator.speed['inference'], 3)} - for k, v in model_info.items(): - Task.current_task().get_logger().report_single_value(k, v) + task = Task.current_task() + if task: + # You should have access to the validation bboxes under jdict + task.get_logger().report_scalar(title='Epoch Time', + series='Epoch Time', + value=trainer.epoch_time, + iteration=trainer.epoch) + if trainer.epoch == 0: + model_info = { + 'model/parameters': get_num_params(trainer.model), + 'model/GFLOPs': round(get_flops(trainer.model), 3), + 'model/speed(ms)': round(trainer.validator.speed['inference'], 3)} + for k, v in model_info.items(): + task.get_logger().report_single_value(k, v) def on_val_end(validator): - # Log val_labels and val_pred - _log_debug_samples(sorted(validator.save_dir.glob('val*.jpg')), 'Validation') + if Task.current_task(): + # Log val_labels and val_pred + _log_debug_samples(sorted(validator.save_dir.glob('val*.jpg')), 'Validation') def on_train_end(trainer): - # Log final results, CM matrix + PR plots - files = ['results.png', 'confusion_matrix.png', *(f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R'))] - files = [(trainer.save_dir / f) for f in files if (trainer.save_dir / f).exists()] # filter - for f in files: - _log_plot(title=f.stem, plot_path=f) - # Report final metrics - for k, v in trainer.validator.metrics.results_dict.items(): - Task.current_task().get_logger().report_single_value(k, v) - # Log the final model - Task.current_task().update_output_model(model_path=str(trainer.best), - model_name=trainer.args.name, - auto_delete_file=False) + task = Task.current_task() + if task: + # Log final results, CM matrix + PR plots + files = ['results.png', 'confusion_matrix.png', *(f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R'))] + files = [(trainer.save_dir / f) for f in files if (trainer.save_dir / f).exists()] # filter + for f in files: + _log_plot(title=f.stem, plot_path=f) + # Report final metrics + for k, v in trainer.validator.metrics.results_dict.items(): + task.get_logger().report_single_value(k, v) + # Log the final model + task.update_output_model(model_path=str(trainer.best), model_name=trainer.args.name, auto_delete_file=False) callbacks = { diff --git a/ultralytics/yolo/utils/checks.py b/ultralytics/yolo/utils/checks.py index c566daf..0198090 100644 --- a/ultralytics/yolo/utils/checks.py +++ b/ultralytics/yolo/utils/checks.py @@ -337,6 +337,10 @@ def git_describe(path=ROOT): # path must be a directory def print_args(args: Optional[dict] = None, show_file=True, show_func=False): # Print function arguments (optional args dict) + def strip_auth(v): + # Clean longer Ultralytics HUB URLs by stripping potential authentication information + return clean_url(v) if (isinstance(v, str) and v.startswith('http') and len(v) > 100) else v + x = inspect.currentframe().f_back # previous frame file, _, func, _, _ = inspect.getframeinfo(x) if args is None: # get args automatically @@ -347,4 +351,4 @@ def print_args(args: Optional[dict] = None, show_file=True, show_func=False): 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())) + LOGGER.info(colorstr(s) + ', '.join(f'{k}={strip_auth(v)}' for k, v in args.items()))