`ultralytics 8.0.12` - Hydra removal (#506)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Pronoy Mandal <lukex9442@gmail.com> Co-authored-by: Ayush Chaurasia <ayush.chaurarsia@gmail.com>pull/530/head v8.0.12
parent
6eec39162a
commit
c5fccc3fc4
37 changed files with 398 additions and 472 deletions
@ -1,156 +0,0 @@ |
||||
# Ultralytics YOLO 🚀, GPL-3.0 license |
||||
|
||||
import argparse |
||||
import re |
||||
import shutil |
||||
import sys |
||||
from pathlib import Path |
||||
|
||||
from ultralytics import __version__, yolo |
||||
from ultralytics.yolo.utils import DEFAULT_CONFIG, LOGGER, PREFIX, checks, print_settings, yaml_load |
||||
|
||||
DIR = Path(__file__).parent |
||||
|
||||
CLI_HELP_MSG = \ |
||||
""" |
||||
YOLOv8 CLI Usage examples: |
||||
|
||||
1. Install the ultralytics package: |
||||
|
||||
pip install ultralytics |
||||
|
||||
2. Train, Val, Predict and Export using 'yolo' commands: |
||||
|
||||
yolo TASK MODE ARGS |
||||
|
||||
Where TASK (optional) is one of [detect, segment, classify] |
||||
MODE (required) is one of [train, val, predict, export] |
||||
ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults. |
||||
For a full list of available ARGS see https://docs.ultralytics.com/config. |
||||
|
||||
Train a detection model for 10 epochs with an initial learning_rate of 0.01 |
||||
yolo detect train data=coco128.yaml model=yolov8n.pt epochs=10 lr0=0.01 |
||||
|
||||
Predict a YouTube video using a pretrained segmentation model at image size 320: |
||||
yolo segment predict model=yolov8n-seg.pt source=https://youtu.be/Zgi9g1ksQHc imgsz=320 |
||||
|
||||
Validate a pretrained detection model at batch-size 1 and image size 640: |
||||
yolo detect val model=yolov8n.pt data=coco128.yaml batch=1 imgsz=640 |
||||
|
||||
Export a YOLOv8n classification model to ONNX format at image size 224 by 128 (no TASK required) |
||||
yolo export model=yolov8n-cls.pt format=onnx imgsz=224,128 |
||||
|
||||
3. Run special commands: |
||||
|
||||
yolo help |
||||
yolo checks |
||||
yolo version |
||||
yolo settings |
||||
yolo copy-config |
||||
|
||||
Docs: https://docs.ultralytics.com/cli |
||||
Community: https://community.ultralytics.com |
||||
GitHub: https://github.com/ultralytics/ultralytics |
||||
""" |
||||
|
||||
|
||||
def cli(cfg): |
||||
""" |
||||
Run a specified task and mode with the given configuration. |
||||
|
||||
Args: |
||||
cfg (DictConfig): Configuration for the task and mode. |
||||
""" |
||||
# LOGGER.info(f"{colorstr(f'Ultralytics YOLO v{ultralytics.__version__}')}") |
||||
from ultralytics.yolo.configs import get_config |
||||
|
||||
if cfg.cfg: |
||||
LOGGER.info(f"{PREFIX}Overriding default config with {cfg.cfg}") |
||||
cfg = get_config(cfg.cfg) |
||||
task, mode = cfg.task.lower(), cfg.mode.lower() |
||||
|
||||
# Mapping from task to module |
||||
tasks = {"detect": yolo.v8.detect, "segment": yolo.v8.segment, "classify": yolo.v8.classify} |
||||
module = tasks.get(task) |
||||
if not module: |
||||
raise SyntaxError(f"yolo task={task} is invalid. Valid tasks are: {', '.join(tasks.keys())}\n{CLI_HELP_MSG}") |
||||
|
||||
# Mapping from mode to function |
||||
modes = {"train": module.train, "val": module.val, "predict": module.predict, "export": yolo.engine.exporter.export} |
||||
func = modes.get(mode) |
||||
if not func: |
||||
raise SyntaxError(f"yolo mode={mode} is invalid. Valid modes are: {', '.join(modes.keys())}\n{CLI_HELP_MSG}") |
||||
|
||||
func(cfg) |
||||
|
||||
|
||||
def entrypoint(): |
||||
""" |
||||
This function is the ultralytics package entrypoint, it's responsible for parsing the command line arguments passed |
||||
to the package. It's a combination of argparse and hydra. |
||||
|
||||
This function allows for: |
||||
- passing mandatory YOLO args as a list of strings |
||||
- specifying the task to be performed, either 'detect', 'segment' or 'classify' |
||||
- specifying the mode, either 'train', 'val', 'test', or 'predict' |
||||
- running special modes like 'checks' |
||||
- passing overrides to the package's configuration |
||||
|
||||
It uses the package's default config and initializes it using the passed overrides. |
||||
Then it calls the CLI function with the composed config |
||||
""" |
||||
if len(sys.argv) == 1: # no arguments passed |
||||
LOGGER.info(CLI_HELP_MSG) |
||||
return |
||||
|
||||
parser = argparse.ArgumentParser(description='YOLO parser') |
||||
parser.add_argument('args', type=str, nargs='+', help='YOLO args') |
||||
args = parser.parse_args().args |
||||
args = re.sub(r'\s*=\s*', '=', ' '.join(args)).split(' ') # remove whitespaces around = sign |
||||
|
||||
tasks = 'detect', 'segment', 'classify' |
||||
modes = 'train', 'val', 'predict', 'export' |
||||
special_modes = { |
||||
'help': lambda: LOGGER.info(CLI_HELP_MSG), |
||||
'checks': checks.check_yolo, |
||||
'version': lambda: LOGGER.info(__version__), |
||||
'settings': print_settings, |
||||
'copy-config': copy_default_config} |
||||
|
||||
overrides = [] # basic overrides, i.e. imgsz=320 |
||||
defaults = yaml_load(DEFAULT_CONFIG) |
||||
for a in args: |
||||
if '=' in a: |
||||
overrides.append(a) |
||||
elif a in tasks: |
||||
overrides.append(f'task={a}') |
||||
elif a in modes: |
||||
overrides.append(f'mode={a}') |
||||
elif a in special_modes: |
||||
special_modes[a]() |
||||
return |
||||
elif a in defaults and defaults[a] is False: |
||||
overrides.append(f'{a}=True') # auto-True for default False args, i.e. yolo show |
||||
elif a in defaults: |
||||
raise SyntaxError(f"'{a}' is a valid YOLO argument but is missing an '=' sign to set its value, " |
||||
f"i.e. try '{a}={defaults[a]}'" |
||||
f"\n{CLI_HELP_MSG}") |
||||
else: |
||||
raise SyntaxError( |
||||
f"'{a}' is not a valid YOLO argument. For a full list of valid arguments see " |
||||
f"https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/configs/default.yaml" |
||||
f"\n{CLI_HELP_MSG}") |
||||
|
||||
from hydra import compose, initialize |
||||
|
||||
with initialize(version_base=None, config_path=str(DEFAULT_CONFIG.parent.relative_to(DIR)), job_name="YOLO"): |
||||
cfg = compose(config_name=DEFAULT_CONFIG.name, overrides=overrides) |
||||
cli(cfg) |
||||
|
||||
|
||||
# Special modes -------------------------------------------------------------------------------------------------------- |
||||
def copy_default_config(): |
||||
new_file = Path.cwd() / DEFAULT_CONFIG.name.replace('.yaml', '_copy.yaml') |
||||
shutil.copy2(DEFAULT_CONFIG, new_file) |
||||
LOGGER.info(f"{PREFIX}{DEFAULT_CONFIG} copied to {new_file}\n" |
||||
f"Usage for running YOLO with this new custom config:\nyolo cfg={new_file} args...") |
@ -1,36 +1,221 @@ |
||||
# Ultralytics YOLO 🚀, GPL-3.0 license |
||||
|
||||
import argparse |
||||
import re |
||||
import shutil |
||||
import sys |
||||
from difflib import get_close_matches |
||||
from pathlib import Path |
||||
from types import SimpleNamespace |
||||
from typing import Dict, Union |
||||
|
||||
from omegaconf import DictConfig, OmegaConf |
||||
from ultralytics import __version__, yolo |
||||
from ultralytics.yolo.utils import DEFAULT_CFG_PATH, LOGGER, PREFIX, checks, colorstr, print_settings, yaml_load |
||||
|
||||
DIR = Path(__file__).parent |
||||
|
||||
CLI_HELP_MSG = \ |
||||
""" |
||||
YOLOv8 CLI Usage examples: |
||||
|
||||
1. Install the ultralytics package: |
||||
|
||||
pip install ultralytics |
||||
|
||||
2. Train, Val, Predict and Export using 'yolo' commands: |
||||
|
||||
yolo TASK MODE ARGS |
||||
|
||||
Where TASK (optional) is one of [detect, segment, classify] |
||||
MODE (required) is one of [train, val, predict, export] |
||||
ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults. |
||||
For a full list of available ARGS see https://docs.ultralytics.com/config. |
||||
|
||||
Train a detection model for 10 epochs with an initial learning_rate of 0.01 |
||||
yolo detect train data=coco128.yaml model=yolov8n.pt epochs=10 lr0=0.01 |
||||
|
||||
Predict a YouTube video using a pretrained segmentation model at image size 320: |
||||
yolo segment predict model=yolov8n-seg.pt source=https://youtu.be/Zgi9g1ksQHc imgsz=320 |
||||
|
||||
Validate a pretrained detection model at batch-size 1 and image size 640: |
||||
yolo detect val model=yolov8n.pt data=coco128.yaml batch=1 imgsz=640 |
||||
|
||||
Export a YOLOv8n classification model to ONNX format at image size 224 by 128 (no TASK required) |
||||
yolo export model=yolov8n-cls.pt format=onnx imgsz=224,128 |
||||
|
||||
from ultralytics.yolo.configs.hydra_patch import check_config_mismatch |
||||
3. Run special commands: |
||||
|
||||
yolo help |
||||
yolo checks |
||||
yolo version |
||||
yolo settings |
||||
yolo copy-config |
||||
|
||||
def get_config(config: Union[str, Path, DictConfig], overrides: Union[str, Dict] = None): |
||||
Docs: https://docs.ultralytics.com/cli |
||||
Community: https://community.ultralytics.com |
||||
GitHub: https://github.com/ultralytics/ultralytics |
||||
""" |
||||
|
||||
|
||||
def cfg2dict(cfg): |
||||
""" |
||||
Convert a configuration object to a dictionary. |
||||
|
||||
This function converts a configuration object to a dictionary, whether it is a file path, a string, or a SimpleNamespace object. |
||||
|
||||
Inputs: |
||||
cfg (str) or (Path) or (SimpleNamespace): Configuration object to be converted to a dictionary. |
||||
|
||||
Returns: |
||||
cfg (dict): Configuration object in dictionary format. |
||||
""" |
||||
if isinstance(cfg, (str, Path)): |
||||
cfg = yaml_load(cfg) # load dict |
||||
elif isinstance(cfg, SimpleNamespace): |
||||
cfg = vars(cfg) # convert to dict |
||||
return cfg |
||||
|
||||
|
||||
def get_config(config: Union[str, Path, Dict, SimpleNamespace], overrides: Dict = None): |
||||
""" |
||||
Load and merge configuration data from a file or dictionary. |
||||
|
||||
Args: |
||||
config (str) or (Path) or (DictConfig): Configuration data in the form of a file name or a DictConfig object. |
||||
overrides (str) or(Dict), optional: Overrides in the form of a file name or a dictionary. Default is None. |
||||
config (str) or (Path) or (Dict) or (SimpleNamespace): Configuration data. |
||||
overrides (str) or (Dict), optional: Overrides in the form of a file name or a dictionary. Default is None. |
||||
|
||||
Returns: |
||||
OmegaConf.Namespace: Training arguments namespace. |
||||
""" |
||||
if overrides is None: |
||||
overrides = {} |
||||
if isinstance(config, (str, Path)): |
||||
config = OmegaConf.load(config) |
||||
elif isinstance(config, Dict): |
||||
config = OmegaConf.create(config) |
||||
# override |
||||
if isinstance(overrides, str): |
||||
overrides = OmegaConf.load(overrides) |
||||
elif isinstance(overrides, Dict): |
||||
overrides = OmegaConf.create(overrides) |
||||
|
||||
check_config_mismatch(dict(overrides).keys(), dict(config).keys()) |
||||
|
||||
return OmegaConf.merge(config, overrides) |
||||
(SimpleNamespace): Training arguments namespace. |
||||
""" |
||||
config = cfg2dict(config) |
||||
|
||||
# Merge overrides |
||||
if overrides: |
||||
overrides = cfg2dict(overrides) |
||||
check_config_mismatch(config, overrides) |
||||
config = {**config, **overrides} # merge config and overrides dicts (prefer overrides) |
||||
|
||||
# Return instance |
||||
return SimpleNamespace(**config) |
||||
|
||||
|
||||
def check_config_mismatch(base: Dict, custom: Dict): |
||||
""" |
||||
This function checks for any mismatched keys between a custom configuration list and a base configuration list. |
||||
If any mismatched keys are found, the function prints out similar keys from the base list and exits the program. |
||||
|
||||
Inputs: |
||||
- custom (Dict): a dictionary of custom configuration options |
||||
- base (Dict): a dictionary of base configuration options |
||||
""" |
||||
base, custom = (set(x.keys()) for x in (base, custom)) |
||||
mismatched = [x for x in custom if x not in base] |
||||
for option in mismatched: |
||||
LOGGER.info(f"{colorstr(option)} is not a valid key. Similar keys: {get_close_matches(option, base, 3, 0.6)}") |
||||
if mismatched: |
||||
sys.exit() |
||||
|
||||
|
||||
def entrypoint(debug=True): |
||||
""" |
||||
This function is the ultralytics package entrypoint, it's responsible for parsing the command line arguments passed |
||||
to the package. |
||||
|
||||
This function allows for: |
||||
- passing mandatory YOLO args as a list of strings |
||||
- specifying the task to be performed, either 'detect', 'segment' or 'classify' |
||||
- specifying the mode, either 'train', 'val', 'test', or 'predict' |
||||
- running special modes like 'checks' |
||||
- passing overrides to the package's configuration |
||||
|
||||
It uses the package's default config and initializes it using the passed overrides. |
||||
Then it calls the CLI function with the composed config |
||||
""" |
||||
if debug: |
||||
args = ['train', 'predict', 'model=yolov8n.pt'] # for testing |
||||
else: |
||||
if len(sys.argv) == 1: # no arguments passed |
||||
LOGGER.info(CLI_HELP_MSG) |
||||
return |
||||
|
||||
parser = argparse.ArgumentParser(description='YOLO parser') |
||||
parser.add_argument('args', type=str, nargs='+', help='YOLO args') |
||||
args = parser.parse_args().args |
||||
args = re.sub(r'\s*=\s*', '=', ' '.join(args)).split(' ') # remove whitespaces around = sign |
||||
|
||||
tasks = 'detect', 'segment', 'classify' |
||||
modes = 'train', 'val', 'predict', 'export' |
||||
special_modes = { |
||||
'help': lambda: LOGGER.info(CLI_HELP_MSG), |
||||
'checks': checks.check_yolo, |
||||
'version': lambda: LOGGER.info(__version__), |
||||
'settings': print_settings, |
||||
'copy-config': copy_default_config} |
||||
|
||||
overrides = {} # basic overrides, i.e. imgsz=320 |
||||
defaults = yaml_load(DEFAULT_CFG_PATH) |
||||
for a in args: |
||||
if '=' in a: |
||||
if a.startswith('cfg='): # custom.yaml passed |
||||
custom_config = Path(a.split('=')[-1]) |
||||
LOGGER.info(f"{PREFIX}Overriding {DEFAULT_CFG_PATH} with {custom_config}") |
||||
overrides = {k: v for k, v in yaml_load(custom_config).items() if k not in {'cfg'}} |
||||
else: |
||||
k, v = a.split('=') |
||||
try: |
||||
if k == 'device': # special DDP handling, i.e. device='0,1,2,3' |
||||
v = v.replace('[', '').replace(']', '') # handle device=[0,1,2,3] |
||||
v = v.replace(" ", "").replace('') # handle device=[0, 1, 2, 3] |
||||
v = v.replace('\\', '') # handle device=\'0,1,2,3\' |
||||
overrides[k] = v |
||||
else: |
||||
overrides[k] = eval(v) # convert strings to integers, floats, bools, etc. |
||||
except (NameError, SyntaxError): |
||||
overrides[k] = v |
||||
elif a in tasks: |
||||
overrides['task'] = a |
||||
elif a in modes: |
||||
overrides['mode'] = a |
||||
elif a in special_modes: |
||||
special_modes[a]() |
||||
return |
||||
elif a in defaults and defaults[a] is False: |
||||
overrides[a] = True # auto-True for default False args, i.e. 'yolo show' sets show=True |
||||
elif a in defaults: |
||||
raise SyntaxError(f"'{a}' is a valid YOLO argument but is missing an '=' sign to set its value, " |
||||
f"i.e. try '{a}={defaults[a]}'" |
||||
f"\n{CLI_HELP_MSG}") |
||||
else: |
||||
raise SyntaxError( |
||||
f"'{a}' is not a valid YOLO argument. For a full list of valid arguments see " |
||||
f"https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/configs/default.yaml" |
||||
f"\n{CLI_HELP_MSG}") |
||||
|
||||
cfg = get_config(defaults, overrides) # create CFG instance |
||||
|
||||
# Mapping from task to module |
||||
module = {"detect": yolo.v8.detect, "segment": yolo.v8.segment, "classify": yolo.v8.classify}.get(cfg.task) |
||||
if not module: |
||||
raise SyntaxError(f"yolo task={cfg.task} is invalid. Valid tasks are: {', '.join(tasks)}\n{CLI_HELP_MSG}") |
||||
|
||||
# Mapping from mode to function |
||||
func = { |
||||
"train": module.train, |
||||
"val": module.val, |
||||
"predict": module.predict, |
||||
"export": yolo.engine.exporter.export}.get(cfg.mode) |
||||
if not func: |
||||
raise SyntaxError(f"yolo mode={cfg.mode} is invalid. Valid modes are: {', '.join(modes)}\n{CLI_HELP_MSG}") |
||||
|
||||
func(cfg) |
||||
|
||||
|
||||
# Special modes -------------------------------------------------------------------------------------------------------- |
||||
def copy_default_config(): |
||||
new_file = Path.cwd() / DEFAULT_CFG_PATH.name.replace('.yaml', '_copy.yaml') |
||||
shutil.copy2(DEFAULT_CFG_PATH, new_file) |
||||
LOGGER.info(f"{PREFIX}{DEFAULT_CFG_PATH} copied to {new_file}\n" |
||||
f"Usage for running YOLO with this new custom config:\nyolo cfg={new_file} args...") |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
entrypoint() |
||||
|
@ -1,77 +0,0 @@ |
||||
# Ultralytics YOLO 🚀, GPL-3.0 license |
||||
|
||||
import sys |
||||
from difflib import get_close_matches |
||||
from textwrap import dedent |
||||
|
||||
import hydra |
||||
from hydra.errors import ConfigCompositionException |
||||
from omegaconf import OmegaConf, open_dict # noqa |
||||
from omegaconf.errors import ConfigAttributeError, ConfigKeyError, OmegaConfBaseException # noqa |
||||
|
||||
from ultralytics.yolo.utils import LOGGER, colorstr |
||||
|
||||
|
||||
def override_config(overrides, cfg): |
||||
override_keys = [override.key_or_group for override in overrides] |
||||
check_config_mismatch(override_keys, cfg.keys()) |
||||
for override in overrides: |
||||
if override.package is not None: |
||||
raise ConfigCompositionException(f"Override {override.input_line} looks like a config group" |
||||
f" override, but config group '{override.key_or_group}' does not exist.") |
||||
|
||||
key = override.key_or_group |
||||
value = override.value() |
||||
try: |
||||
if override.is_delete(): |
||||
config_val = OmegaConf.select(cfg, key, throw_on_missing=False) |
||||
if config_val is None: |
||||
raise ConfigCompositionException(f"Could not delete from config. '{override.key_or_group}'" |
||||
" does not exist.") |
||||
elif value is not None and value != config_val: |
||||
raise ConfigCompositionException("Could not delete from config. The value of" |
||||
f" '{override.key_or_group}' is {config_val} and not" |
||||
f" {value}.") |
||||
|
||||
last_dot = key.rfind(".") |
||||
with open_dict(cfg): |
||||
if last_dot == -1: |
||||
del cfg[key] |
||||
else: |
||||
node = OmegaConf.select(cfg, key[:last_dot]) |
||||
del node[key[last_dot + 1:]] |
||||
|
||||
elif override.is_add(): |
||||
if OmegaConf.select(cfg, key, throw_on_missing=False) is None or isinstance(value, (dict, list)): |
||||
OmegaConf.update(cfg, key, value, merge=True, force_add=True) |
||||
else: |
||||
assert override.input_line is not None |
||||
raise ConfigCompositionException( |
||||
dedent(f"""\ |
||||
Could not append to config. An item is already at '{override.key_or_group}'. |
||||
Either remove + prefix: '{override.input_line[1:]}' |
||||
Or add a second + to add or override '{override.key_or_group}': '+{override.input_line}' |
||||
""")) |
||||
elif override.is_force_add(): |
||||
OmegaConf.update(cfg, key, value, merge=True, force_add=True) |
||||
else: |
||||
try: |
||||
OmegaConf.update(cfg, key, value, merge=True) |
||||
except (ConfigAttributeError, ConfigKeyError) as ex: |
||||
raise ConfigCompositionException(f"Could not override '{override.key_or_group}'." |
||||
f"\nTo append to your config use +{override.input_line}") from ex |
||||
except OmegaConfBaseException as ex: |
||||
raise ConfigCompositionException(f"Error merging override {override.input_line}").with_traceback( |
||||
sys.exc_info()[2]) from ex |
||||
|
||||
|
||||
def check_config_mismatch(overrides, cfg): |
||||
mismatched = [option for option in overrides if option not in cfg and 'hydra.' not in option] |
||||
|
||||
for option in mismatched: |
||||
LOGGER.info(f"{colorstr(option)} is not a valid key. Similar keys: {get_close_matches(option, cfg, 3, 0.6)}") |
||||
if mismatched: |
||||
sys.exit() |
||||
|
||||
|
||||
hydra._internal.config_loader_impl.ConfigLoaderImpl._apply_overrides_to_config = override_config |
@ -1,6 +1,5 @@ |
||||
# Ultralytics YOLO 🚀, GPL-3.0 license |
||||
|
||||
from ultralytics.yolo.configs import hydra_patch # noqa (patch hydra CLI) |
||||
from ultralytics.yolo.v8 import classify, detect, segment |
||||
|
||||
__all__ = ["classify", "segment", "detect"] |
||||
|
Loading…
Reference in new issue