|
@@ -0,0 +1,1025 @@
|
|
|
|
+# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
|
|
+
|
|
|
|
+import shutil
|
|
|
|
+import subprocess
|
|
|
|
+import sys
|
|
|
|
+from pathlib import Path
|
|
|
|
+from types import SimpleNamespace
|
|
|
|
+from typing import Dict, List, Union
|
|
|
|
+
|
|
|
|
+import cv2
|
|
|
|
+
|
|
|
|
+from ultralytics.utils import (
|
|
|
|
+ ASSETS,
|
|
|
|
+ DEFAULT_CFG,
|
|
|
|
+ DEFAULT_CFG_DICT,
|
|
|
|
+ DEFAULT_CFG_PATH,
|
|
|
|
+ DEFAULT_SOL_DICT,
|
|
|
|
+ IS_VSCODE,
|
|
|
|
+ LOGGER,
|
|
|
|
+ RANK,
|
|
|
|
+ ROOT,
|
|
|
|
+ RUNS_DIR,
|
|
|
|
+ SETTINGS,
|
|
|
|
+ SETTINGS_FILE,
|
|
|
|
+ TESTS_RUNNING,
|
|
|
|
+ IterableSimpleNamespace,
|
|
|
|
+ __version__,
|
|
|
|
+ checks,
|
|
|
|
+ colorstr,
|
|
|
|
+ deprecation_warn,
|
|
|
|
+ vscode_msg,
|
|
|
|
+ yaml_load,
|
|
|
|
+ yaml_print,
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+# Define valid solutions
|
|
|
|
+SOLUTION_MAP = {
|
|
|
|
+ "count": ("ObjectCounter", "count"),
|
|
|
|
+ "heatmap": ("Heatmap", "generate_heatmap"),
|
|
|
|
+ "queue": ("QueueManager", "process_queue"),
|
|
|
|
+ "speed": ("SpeedEstimator", "estimate_speed"),
|
|
|
|
+ "workout": ("AIGym", "monitor"),
|
|
|
|
+ "analytics": ("Analytics", "process_data"),
|
|
|
|
+ "trackzone": ("TrackZone", "trackzone"),
|
|
|
|
+ "inference": ("Inference", "inference"),
|
|
|
|
+ "help": None,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+# Define valid tasks and modes
|
|
|
|
+MODES = {"train", "val", "predict", "export", "track", "benchmark"}
|
|
|
|
+TASKS = {"detect", "segment", "classify", "pose", "obb"}
|
|
|
|
+TASK2DATA = {
|
|
|
|
+ "detect": "coco8.yaml",
|
|
|
|
+ "segment": "coco8-seg.yaml",
|
|
|
|
+ "classify": "imagenet10",
|
|
|
|
+ "pose": "coco8-pose.yaml",
|
|
|
|
+ "obb": "dota8.yaml",
|
|
|
|
+}
|
|
|
|
+TASK2MODEL = {
|
|
|
|
+ "detect": "yolo11n.pt",
|
|
|
|
+ "segment": "yolo11n-seg.pt",
|
|
|
|
+ "classify": "yolo11n-cls.pt",
|
|
|
|
+ "pose": "yolo11n-pose.pt",
|
|
|
|
+ "obb": "yolo11n-obb.pt",
|
|
|
|
+}
|
|
|
|
+TASK2METRIC = {
|
|
|
|
+ "detect": "metrics/mAP50-95(B)",
|
|
|
|
+ "segment": "metrics/mAP50-95(M)",
|
|
|
|
+ "classify": "metrics/accuracy_top1",
|
|
|
|
+ "pose": "metrics/mAP50-95(P)",
|
|
|
|
+ "obb": "metrics/mAP50-95(B)",
|
|
|
|
+}
|
|
|
|
+MODELS = {TASK2MODEL[task] for task in TASKS}
|
|
|
|
+
|
|
|
|
+ARGV = sys.argv or ["", ""] # sometimes sys.argv = []
|
|
|
|
+SOLUTIONS_HELP_MSG = f"""
|
|
|
|
+ Arguments received: {str(["yolo"] + ARGV[1:])}. Ultralytics 'yolo solutions' usage overview:
|
|
|
|
+
|
|
|
|
+ yolo solutions SOLUTION ARGS
|
|
|
|
+
|
|
|
|
+ Where SOLUTION (optional) is one of {list(SOLUTION_MAP.keys())[:-1]}
|
|
|
|
+ ARGS (optional) are any number of custom 'arg=value' pairs like 'show_in=True' that override defaults
|
|
|
|
+ at https://docs.ultralytics.com/usage/cfg
|
|
|
|
+
|
|
|
|
+ 1. Call object counting solution
|
|
|
|
+ yolo solutions count source="path/to/video/file.mp4" region=[(20, 400), (1080, 400), (1080, 360), (20, 360)]
|
|
|
|
+
|
|
|
|
+ 2. Call heatmaps solution
|
|
|
|
+ yolo solutions heatmap colormap=cv2.COLORMAP_PARULA model=yolo11n.pt
|
|
|
|
+
|
|
|
|
+ 3. Call queue management solution
|
|
|
|
+ yolo solutions queue region=[(20, 400), (1080, 400), (1080, 360), (20, 360)] model=yolo11n.pt
|
|
|
|
+
|
|
|
|
+ 4. Call workouts monitoring solution for push-ups
|
|
|
|
+ yolo solutions workout model=yolo11n-pose.pt kpts=[6, 8, 10]
|
|
|
|
+
|
|
|
|
+ 5. Generate analytical graphs
|
|
|
|
+ yolo solutions analytics analytics_type="pie"
|
|
|
|
+
|
|
|
|
+ 6. Track objects within specific zones
|
|
|
|
+ yolo solutions trackzone source="path/to/video/file.mp4" region=[(150, 150), (1130, 150), (1130, 570), (150, 570)]
|
|
|
|
+
|
|
|
|
+ 7. Streamlit real-time webcam inference GUI
|
|
|
|
+ yolo streamlit-predict
|
|
|
|
+ """
|
|
|
|
+CLI_HELP_MSG = f"""
|
|
|
|
+ Arguments received: {str(["yolo"] + ARGV[1:])}. Ultralytics 'yolo' commands use the following syntax:
|
|
|
|
+
|
|
|
|
+ yolo TASK MODE ARGS
|
|
|
|
+
|
|
|
|
+ Where TASK (optional) is one of {TASKS}
|
|
|
|
+ MODE (required) is one of {MODES}
|
|
|
|
+ ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults.
|
|
|
|
+ See all ARGS at https://docs.ultralytics.com/usage/cfg or with 'yolo cfg'
|
|
|
|
+
|
|
|
|
+ 1. Train a detection model for 10 epochs with an initial learning_rate of 0.01
|
|
|
|
+ yolo train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01
|
|
|
|
+
|
|
|
|
+ 2. Predict a YouTube video using a pretrained segmentation model at image size 320:
|
|
|
|
+ yolo predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320
|
|
|
|
+
|
|
|
|
+ 3. Val a pretrained detection model at batch-size 1 and image size 640:
|
|
|
|
+ yolo val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640
|
|
|
|
+
|
|
|
|
+ 4. Export a YOLO11n classification model to ONNX format at image size 224 by 128 (no TASK required)
|
|
|
|
+ yolo export model=yolo11n-cls.pt format=onnx imgsz=224,128
|
|
|
|
+
|
|
|
|
+ 5. Ultralytics solutions usage
|
|
|
|
+ yolo solutions count or in {list(SOLUTION_MAP.keys())[1:-1]} source="path/to/video/file.mp4"
|
|
|
|
+
|
|
|
|
+ 6. Run special commands:
|
|
|
|
+ yolo help
|
|
|
|
+ yolo checks
|
|
|
|
+ yolo version
|
|
|
|
+ yolo settings
|
|
|
|
+ yolo copy-cfg
|
|
|
|
+ yolo cfg
|
|
|
|
+ yolo solutions help
|
|
|
|
+
|
|
|
|
+ Docs: https://docs.ultralytics.com
|
|
|
|
+ Solutions: https://docs.ultralytics.com/solutions/
|
|
|
|
+ Community: https://community.ultralytics.com
|
|
|
|
+ GitHub: https://github.com/ultralytics/ultralytics
|
|
|
|
+ """
|
|
|
|
+
|
|
|
|
+# Define keys for arg type checks
|
|
|
|
+CFG_FLOAT_KEYS = { # integer or float arguments, i.e. x=2 and x=2.0
|
|
|
|
+ "warmup_epochs",
|
|
|
|
+ "box",
|
|
|
|
+ "cls",
|
|
|
|
+ "dfl",
|
|
|
|
+ "degrees",
|
|
|
|
+ "shear",
|
|
|
|
+ "time",
|
|
|
|
+ "workspace",
|
|
|
|
+ "batch",
|
|
|
|
+}
|
|
|
|
+CFG_FRACTION_KEYS = { # fractional float arguments with 0.0<=values<=1.0
|
|
|
|
+ "dropout",
|
|
|
|
+ "lr0",
|
|
|
|
+ "lrf",
|
|
|
|
+ "momentum",
|
|
|
|
+ "weight_decay",
|
|
|
|
+ "warmup_momentum",
|
|
|
|
+ "warmup_bias_lr",
|
|
|
|
+ "hsv_h",
|
|
|
|
+ "hsv_s",
|
|
|
|
+ "hsv_v",
|
|
|
|
+ "translate",
|
|
|
|
+ "scale",
|
|
|
|
+ "perspective",
|
|
|
|
+ "flipud",
|
|
|
|
+ "fliplr",
|
|
|
|
+ "bgr",
|
|
|
|
+ "mosaic",
|
|
|
|
+ "mixup",
|
|
|
|
+ "copy_paste",
|
|
|
|
+ "conf",
|
|
|
|
+ "iou",
|
|
|
|
+ "fraction",
|
|
|
|
+}
|
|
|
|
+CFG_INT_KEYS = { # integer-only arguments
|
|
|
|
+ "epochs",
|
|
|
|
+ "patience",
|
|
|
|
+ "workers",
|
|
|
|
+ "seed",
|
|
|
|
+ "close_mosaic",
|
|
|
|
+ "mask_ratio",
|
|
|
|
+ "max_det",
|
|
|
|
+ "vid_stride",
|
|
|
|
+ "line_width",
|
|
|
|
+ "nbs",
|
|
|
|
+ "save_period",
|
|
|
|
+}
|
|
|
|
+CFG_BOOL_KEYS = { # boolean-only arguments
|
|
|
|
+ "save",
|
|
|
|
+ "exist_ok",
|
|
|
|
+ "verbose",
|
|
|
|
+ "deterministic",
|
|
|
|
+ "single_cls",
|
|
|
|
+ "rect",
|
|
|
|
+ "cos_lr",
|
|
|
|
+ "overlap_mask",
|
|
|
|
+ "val",
|
|
|
|
+ "save_json",
|
|
|
|
+ "save_hybrid",
|
|
|
|
+ "half",
|
|
|
|
+ "dnn",
|
|
|
|
+ "plots",
|
|
|
|
+ "show",
|
|
|
|
+ "save_txt",
|
|
|
|
+ "save_conf",
|
|
|
|
+ "save_crop",
|
|
|
|
+ "save_frames",
|
|
|
|
+ "show_labels",
|
|
|
|
+ "show_conf",
|
|
|
|
+ "visualize",
|
|
|
|
+ "augment",
|
|
|
|
+ "agnostic_nms",
|
|
|
|
+ "retina_masks",
|
|
|
|
+ "show_boxes",
|
|
|
|
+ "keras",
|
|
|
|
+ "optimize",
|
|
|
|
+ "int8",
|
|
|
|
+ "dynamic",
|
|
|
|
+ "simplify",
|
|
|
|
+ "nms",
|
|
|
|
+ "profile",
|
|
|
|
+ "multi_scale",
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def cfg2dict(cfg):
|
|
|
|
+ """
|
|
|
|
+ Converts a configuration object to a dictionary.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ cfg (str | Path | Dict | SimpleNamespace): Configuration object to be converted. Can be a file path,
|
|
|
|
+ a string, a dictionary, or a SimpleNamespace object.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ (Dict): Configuration object in dictionary format.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ Convert a YAML file path to a dictionary:
|
|
|
|
+ >>> config_dict = cfg2dict("config.yaml")
|
|
|
|
+
|
|
|
|
+ Convert a SimpleNamespace to a dictionary:
|
|
|
|
+ >>> from types import SimpleNamespace
|
|
|
|
+ >>> config_sn = SimpleNamespace(param1="value1", param2="value2")
|
|
|
|
+ >>> config_dict = cfg2dict(config_sn)
|
|
|
|
+
|
|
|
|
+ Pass through an already existing dictionary:
|
|
|
|
+ >>> config_dict = cfg2dict({"param1": "value1", "param2": "value2"})
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - If cfg is a path or string, it's loaded as YAML and converted to a dictionary.
|
|
|
|
+ - If cfg is a SimpleNamespace object, it's converted to a dictionary using vars().
|
|
|
|
+ - If cfg is already a dictionary, it's returned unchanged.
|
|
|
|
+ """
|
|
|
|
+ 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_cfg(cfg: Union[str, Path, Dict, SimpleNamespace] = DEFAULT_CFG_DICT, overrides: Dict = None):
|
|
|
|
+ """
|
|
|
|
+ Load and merge configuration data from a file or dictionary, with optional overrides.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ cfg (str | Path | Dict | SimpleNamespace): Configuration data source. Can be a file path, dictionary, or
|
|
|
|
+ SimpleNamespace object.
|
|
|
|
+ overrides (Dict | None): Dictionary containing key-value pairs to override the base configuration.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ (SimpleNamespace): Namespace containing the merged configuration arguments.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> from ultralytics.cfg import get_cfg
|
|
|
|
+ >>> config = get_cfg() # Load default configuration
|
|
|
|
+ >>> config_with_overrides = get_cfg("path/to/config.yaml", overrides={"epochs": 50, "batch_size": 16})
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - If both `cfg` and `overrides` are provided, the values in `overrides` will take precedence.
|
|
|
|
+ - Special handling ensures alignment and correctness of the configuration, such as converting numeric
|
|
|
|
+ `project` and `name` to strings and validating configuration keys and values.
|
|
|
|
+ - The function performs type and value checks on the configuration data.
|
|
|
|
+ """
|
|
|
|
+ cfg = cfg2dict(cfg)
|
|
|
|
+
|
|
|
|
+ # Merge overrides
|
|
|
|
+ if overrides:
|
|
|
|
+ overrides = cfg2dict(overrides)
|
|
|
|
+ if "save_dir" not in cfg:
|
|
|
|
+ overrides.pop("save_dir", None) # special override keys to ignore
|
|
|
|
+ check_dict_alignment(cfg, overrides)
|
|
|
|
+ cfg = {**cfg, **overrides} # merge cfg and overrides dicts (prefer overrides)
|
|
|
|
+
|
|
|
|
+ # Special handling for numeric project/name
|
|
|
|
+ for k in "project", "name":
|
|
|
|
+ if k in cfg and isinstance(cfg[k], (int, float)):
|
|
|
|
+ cfg[k] = str(cfg[k])
|
|
|
|
+ if cfg.get("name") == "model": # assign model to 'name' arg
|
|
|
|
+ cfg["name"] = str(cfg.get("model", "")).split(".")[0]
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ 'name=model' automatically updated to 'name={cfg['name']}'.")
|
|
|
|
+
|
|
|
|
+ # Type and Value checks
|
|
|
|
+ check_cfg(cfg)
|
|
|
|
+
|
|
|
|
+ # Return instance
|
|
|
|
+ return IterableSimpleNamespace(**cfg)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def check_cfg(cfg, hard=True):
|
|
|
|
+ """
|
|
|
|
+ Checks configuration argument types and values for the Ultralytics library.
|
|
|
|
+
|
|
|
|
+ This function validates the types and values of configuration arguments, ensuring correctness and converting
|
|
|
|
+ them if necessary. It checks for specific key types defined in global variables such as CFG_FLOAT_KEYS,
|
|
|
|
+ CFG_FRACTION_KEYS, CFG_INT_KEYS, and CFG_BOOL_KEYS.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ cfg (Dict): Configuration dictionary to validate.
|
|
|
|
+ hard (bool): If True, raises exceptions for invalid types and values; if False, attempts to convert them.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> config = {
|
|
|
|
+ ... "epochs": 50, # valid integer
|
|
|
|
+ ... "lr0": 0.01, # valid float
|
|
|
|
+ ... "momentum": 1.2, # invalid float (out of 0.0-1.0 range)
|
|
|
|
+ ... "save": "true", # invalid bool
|
|
|
|
+ ... }
|
|
|
|
+ >>> check_cfg(config, hard=False)
|
|
|
|
+ >>> print(config)
|
|
|
|
+ {'epochs': 50, 'lr0': 0.01, 'momentum': 1.2, 'save': False} # corrected 'save' key
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - The function modifies the input dictionary in-place.
|
|
|
|
+ - None values are ignored as they may be from optional arguments.
|
|
|
|
+ - Fraction keys are checked to be within the range [0.0, 1.0].
|
|
|
|
+ """
|
|
|
|
+ for k, v in cfg.items():
|
|
|
|
+ if v is not None: # None values may be from optional args
|
|
|
|
+ if k in CFG_FLOAT_KEYS and not isinstance(v, (int, float)):
|
|
|
|
+ if hard:
|
|
|
|
+ raise TypeError(
|
|
|
|
+ f"'{k}={v}' is of invalid type {type(v).__name__}. "
|
|
|
|
+ f"Valid '{k}' types are int (i.e. '{k}=0') or float (i.e. '{k}=0.5')"
|
|
|
|
+ )
|
|
|
|
+ cfg[k] = float(v)
|
|
|
|
+ elif k in CFG_FRACTION_KEYS:
|
|
|
|
+ if not isinstance(v, (int, float)):
|
|
|
|
+ if hard:
|
|
|
|
+ raise TypeError(
|
|
|
|
+ f"'{k}={v}' is of invalid type {type(v).__name__}. "
|
|
|
|
+ f"Valid '{k}' types are int (i.e. '{k}=0') or float (i.e. '{k}=0.5')"
|
|
|
|
+ )
|
|
|
|
+ cfg[k] = v = float(v)
|
|
|
|
+ if not (0.0 <= v <= 1.0):
|
|
|
|
+ raise ValueError(f"'{k}={v}' is an invalid value. Valid '{k}' values are between 0.0 and 1.0.")
|
|
|
|
+ elif k in CFG_INT_KEYS and not isinstance(v, int):
|
|
|
|
+ if hard:
|
|
|
|
+ raise TypeError(
|
|
|
|
+ f"'{k}={v}' is of invalid type {type(v).__name__}. '{k}' must be an int (i.e. '{k}=8')"
|
|
|
|
+ )
|
|
|
|
+ cfg[k] = int(v)
|
|
|
|
+ elif k in CFG_BOOL_KEYS and not isinstance(v, bool):
|
|
|
|
+ if hard:
|
|
|
|
+ raise TypeError(
|
|
|
|
+ f"'{k}={v}' is of invalid type {type(v).__name__}. "
|
|
|
|
+ f"'{k}' must be a bool (i.e. '{k}=True' or '{k}=False')"
|
|
|
|
+ )
|
|
|
|
+ cfg[k] = bool(v)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def get_save_dir(args, name=None):
|
|
|
|
+ """
|
|
|
|
+ Returns the directory path for saving outputs, derived from arguments or default settings.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ args (SimpleNamespace): Namespace object containing configurations such as 'project', 'name', 'task',
|
|
|
|
+ 'mode', and 'save_dir'.
|
|
|
|
+ name (str | None): Optional name for the output directory. If not provided, it defaults to 'args.name'
|
|
|
|
+ or the 'args.mode'.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ (Path): Directory path where outputs should be saved.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> from types import SimpleNamespace
|
|
|
|
+ >>> args = SimpleNamespace(project="my_project", task="detect", mode="train", exist_ok=True)
|
|
|
|
+ >>> save_dir = get_save_dir(args)
|
|
|
|
+ >>> print(save_dir)
|
|
|
|
+ my_project/detect/train
|
|
|
|
+ """
|
|
|
|
+ if getattr(args, "save_dir", None):
|
|
|
|
+ save_dir = args.save_dir
|
|
|
|
+ else:
|
|
|
|
+ from ultralytics.utils.files import increment_path
|
|
|
|
+
|
|
|
|
+ project = args.project or (ROOT.parent / "tests/tmp/runs" if TESTS_RUNNING else RUNS_DIR) / args.task
|
|
|
|
+ name = name or args.name or f"{args.mode}"
|
|
|
|
+ save_dir = increment_path(Path(project) / name, exist_ok=args.exist_ok if RANK in {-1, 0} else True)
|
|
|
|
+
|
|
|
|
+ return Path(save_dir)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def _handle_deprecation(custom):
|
|
|
|
+ """
|
|
|
|
+ Handles deprecated configuration keys by mapping them to current equivalents with deprecation warnings.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ custom (Dict): Configuration dictionary potentially containing deprecated keys.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> custom_config = {"boxes": True, "hide_labels": "False", "line_thickness": 2}
|
|
|
|
+ >>> _handle_deprecation(custom_config)
|
|
|
|
+ >>> print(custom_config)
|
|
|
|
+ {'show_boxes': True, 'show_labels': True, 'line_width': 2}
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ This function modifies the input dictionary in-place, replacing deprecated keys with their current
|
|
|
|
+ equivalents. It also handles value conversions where necessary, such as inverting boolean values for
|
|
|
|
+ 'hide_labels' and 'hide_conf'.
|
|
|
|
+ """
|
|
|
|
+ for key in custom.copy().keys():
|
|
|
|
+ if key == "boxes":
|
|
|
|
+ deprecation_warn(key, "show_boxes")
|
|
|
|
+ custom["show_boxes"] = custom.pop("boxes")
|
|
|
|
+ if key == "hide_labels":
|
|
|
|
+ deprecation_warn(key, "show_labels")
|
|
|
|
+ custom["show_labels"] = custom.pop("hide_labels") == "False"
|
|
|
|
+ if key == "hide_conf":
|
|
|
|
+ deprecation_warn(key, "show_conf")
|
|
|
|
+ custom["show_conf"] = custom.pop("hide_conf") == "False"
|
|
|
|
+ if key == "line_thickness":
|
|
|
|
+ deprecation_warn(key, "line_width")
|
|
|
|
+ custom["line_width"] = custom.pop("line_thickness")
|
|
|
|
+ if key == "label_smoothing":
|
|
|
|
+ deprecation_warn(key)
|
|
|
|
+ custom.pop("label_smoothing")
|
|
|
|
+
|
|
|
|
+ return custom
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def check_dict_alignment(base: Dict, custom: Dict, e=None):
|
|
|
|
+ """
|
|
|
|
+ Checks alignment between custom and base configuration dictionaries, handling deprecated keys and providing error
|
|
|
|
+ messages for mismatched keys.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ base (Dict): The base configuration dictionary containing valid keys.
|
|
|
|
+ custom (Dict): The custom configuration dictionary to be checked for alignment.
|
|
|
|
+ e (Exception | None): Optional error instance passed by the calling function.
|
|
|
|
+
|
|
|
|
+ Raises:
|
|
|
|
+ SystemExit: If mismatched keys are found between the custom and base dictionaries.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> base_cfg = {"epochs": 50, "lr0": 0.01, "batch_size": 16}
|
|
|
|
+ >>> custom_cfg = {"epoch": 100, "lr": 0.02, "batch_size": 32}
|
|
|
|
+ >>> try:
|
|
|
|
+ ... check_dict_alignment(base_cfg, custom_cfg)
|
|
|
|
+ ... except SystemExit:
|
|
|
|
+ ... print("Mismatched keys found")
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - Suggests corrections for mismatched keys based on similarity to valid keys.
|
|
|
|
+ - Automatically replaces deprecated keys in the custom configuration with updated equivalents.
|
|
|
|
+ - Prints detailed error messages for each mismatched key to help users correct their configurations.
|
|
|
|
+ """
|
|
|
|
+ custom = _handle_deprecation(custom)
|
|
|
|
+ base_keys, custom_keys = (set(x.keys()) for x in (base, custom))
|
|
|
|
+ if mismatched := [k for k in custom_keys if k not in base_keys]:
|
|
|
|
+ from difflib import get_close_matches
|
|
|
|
+
|
|
|
|
+ string = ""
|
|
|
|
+ for x in mismatched:
|
|
|
|
+ matches = get_close_matches(x, base_keys) # key list
|
|
|
|
+ matches = [f"{k}={base[k]}" if base.get(k) is not None else k for k in matches]
|
|
|
|
+ match_str = f"Similar arguments are i.e. {matches}." if matches else ""
|
|
|
|
+ string += f"'{colorstr('red', 'bold', x)}' is not a valid YOLO argument. {match_str}\n"
|
|
|
|
+ raise SyntaxError(string + CLI_HELP_MSG) from e
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def merge_equals_args(args: List[str]) -> List[str]:
|
|
|
|
+ """
|
|
|
|
+ Merges arguments around isolated '=' in a list of strings and joins fragments with brackets.
|
|
|
|
+
|
|
|
|
+ This function handles the following cases:
|
|
|
|
+ 1. ['arg', '=', 'val'] becomes ['arg=val']
|
|
|
|
+ 2. ['arg=', 'val'] becomes ['arg=val']
|
|
|
|
+ 3. ['arg', '=val'] becomes ['arg=val']
|
|
|
|
+ 4. Joins fragments with brackets, e.g., ['imgsz=[3,', '640,', '640]'] becomes ['imgsz=[3,640,640]']
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ args (List[str]): A list of strings where each element represents an argument or fragment.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ List[str]: A list of strings where the arguments around isolated '=' are merged and fragments with brackets are joined.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> args = ["arg1", "=", "value", "arg2=", "value2", "arg3", "=value3", "imgsz=[3,", "640,", "640]"]
|
|
|
|
+ >>> merge_and_join_args(args)
|
|
|
|
+ ['arg1=value', 'arg2=value2', 'arg3=value3', 'imgsz=[3,640,640]']
|
|
|
|
+ """
|
|
|
|
+ new_args = []
|
|
|
|
+ current = ""
|
|
|
|
+ depth = 0
|
|
|
|
+
|
|
|
|
+ i = 0
|
|
|
|
+ while i < len(args):
|
|
|
|
+ arg = args[i]
|
|
|
|
+
|
|
|
|
+ # Handle equals sign merging
|
|
|
|
+ if arg == "=" and 0 < i < len(args) - 1: # merge ['arg', '=', 'val']
|
|
|
|
+ new_args[-1] += f"={args[i + 1]}"
|
|
|
|
+ i += 2
|
|
|
|
+ continue
|
|
|
|
+ elif arg.endswith("=") and i < len(args) - 1 and "=" not in args[i + 1]: # merge ['arg=', 'val']
|
|
|
|
+ new_args.append(f"{arg}{args[i + 1]}")
|
|
|
|
+ i += 2
|
|
|
|
+ continue
|
|
|
|
+ elif arg.startswith("=") and i > 0: # merge ['arg', '=val']
|
|
|
|
+ new_args[-1] += arg
|
|
|
|
+ i += 1
|
|
|
|
+ continue
|
|
|
|
+
|
|
|
|
+ # Handle bracket joining
|
|
|
|
+ depth += arg.count("[") - arg.count("]")
|
|
|
|
+ current += arg
|
|
|
|
+ if depth == 0:
|
|
|
|
+ new_args.append(current)
|
|
|
|
+ current = ""
|
|
|
|
+
|
|
|
|
+ i += 1
|
|
|
|
+
|
|
|
|
+ # Append any remaining current string
|
|
|
|
+ if current:
|
|
|
|
+ new_args.append(current)
|
|
|
|
+
|
|
|
|
+ return new_args
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def handle_yolo_hub(args: List[str]) -> None:
|
|
|
|
+ """
|
|
|
|
+ Handles Ultralytics HUB command-line interface (CLI) commands for authentication.
|
|
|
|
+
|
|
|
|
+ This function processes Ultralytics HUB CLI commands such as login and logout. It should be called when executing a
|
|
|
|
+ script with arguments related to HUB authentication.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ args (List[str]): A list of command line arguments. The first argument should be either 'login'
|
|
|
|
+ or 'logout'. For 'login', an optional second argument can be the API key.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ ```bash
|
|
|
|
+ yolo login YOUR_API_KEY
|
|
|
|
+ ```
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - The function imports the 'hub' module from ultralytics to perform login and logout operations.
|
|
|
|
+ - For the 'login' command, if no API key is provided, an empty string is passed to the login function.
|
|
|
|
+ - The 'logout' command does not require any additional arguments.
|
|
|
|
+ """
|
|
|
|
+ from ultralytics import hub
|
|
|
|
+
|
|
|
|
+ if args[0] == "login":
|
|
|
|
+ key = args[1] if len(args) > 1 else ""
|
|
|
|
+ # Log in to Ultralytics HUB using the provided API key
|
|
|
|
+ hub.login(key)
|
|
|
|
+ elif args[0] == "logout":
|
|
|
|
+ # Log out from Ultralytics HUB
|
|
|
|
+ hub.logout()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def handle_yolo_settings(args: List[str]) -> None:
|
|
|
|
+ """
|
|
|
|
+ Handles YOLO settings command-line interface (CLI) commands.
|
|
|
|
+
|
|
|
|
+ This function processes YOLO settings CLI commands such as reset and updating individual settings. It should be
|
|
|
|
+ called when executing a script with arguments related to YOLO settings management.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ args (List[str]): A list of command line arguments for YOLO settings management.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> handle_yolo_settings(["reset"]) # Reset YOLO settings
|
|
|
|
+ >>> handle_yolo_settings(["default_cfg_path=yolo11n.yaml"]) # Update a specific setting
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - If no arguments are provided, the function will display the current settings.
|
|
|
|
+ - The 'reset' command will delete the existing settings file and create new default settings.
|
|
|
|
+ - Other arguments are treated as key-value pairs to update specific settings.
|
|
|
|
+ - The function will check for alignment between the provided settings and the existing ones.
|
|
|
|
+ - After processing, the updated settings will be displayed.
|
|
|
|
+ - For more information on handling YOLO settings, visit:
|
|
|
|
+ https://docs.ultralytics.com/quickstart/#ultralytics-settings
|
|
|
|
+ """
|
|
|
|
+ url = "https://docs.ultralytics.com/quickstart/#ultralytics-settings" # help URL
|
|
|
|
+ try:
|
|
|
|
+ if any(args):
|
|
|
|
+ if args[0] == "reset":
|
|
|
|
+ SETTINGS_FILE.unlink() # delete the settings file
|
|
|
|
+ SETTINGS.reset() # create new settings
|
|
|
|
+ LOGGER.info("Settings reset successfully") # inform the user that settings have been reset
|
|
|
|
+ else: # save a new setting
|
|
|
|
+ new = dict(parse_key_value_pair(a) for a in args)
|
|
|
|
+ check_dict_alignment(SETTINGS, new)
|
|
|
|
+ SETTINGS.update(new)
|
|
|
|
+
|
|
|
|
+ print(SETTINGS) # print the current settings
|
|
|
|
+ LOGGER.info(f"💡 Learn more about Ultralytics Settings at {url}")
|
|
|
|
+ except Exception as e:
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ settings error: '{e}'. Please see {url} for help.")
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def handle_yolo_solutions(args: List[str]) -> None:
|
|
|
|
+ """
|
|
|
|
+ Processes YOLO solutions arguments and runs the specified computer vision solutions pipeline.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ args (List[str]): Command-line arguments for configuring and running the Ultralytics YOLO
|
|
|
|
+ solutions: https://docs.ultralytics.com/solutions/, It can include solution name, source,
|
|
|
|
+ and other configuration parameters.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ None: The function processes video frames and saves the output but doesn't return any value.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ Run people counting solution with default settings:
|
|
|
|
+ >>> handle_yolo_solutions(["count"])
|
|
|
|
+
|
|
|
|
+ Run analytics with custom configuration:
|
|
|
|
+ >>> handle_yolo_solutions(["analytics", "conf=0.25", "source=path/to/video/file.mp4"])
|
|
|
|
+
|
|
|
|
+ Run inference with custom configuration, requires Streamlit version 1.29.0 or higher.
|
|
|
|
+ >>> handle_yolo_solutions(["inference", "model=yolo11n.pt"])
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - Default configurations are merged from DEFAULT_SOL_DICT and DEFAULT_CFG_DICT
|
|
|
|
+ - Arguments can be provided in the format 'key=value' or as boolean flags
|
|
|
|
+ - Available solutions are defined in SOLUTION_MAP with their respective classes and methods
|
|
|
|
+ - If an invalid solution is provided, defaults to 'count' solution
|
|
|
|
+ - Output videos are saved in 'runs/solution/{solution_name}' directory
|
|
|
|
+ - For 'analytics' solution, frame numbers are tracked for generating analytical graphs
|
|
|
|
+ - Video processing can be interrupted by pressing 'q'
|
|
|
|
+ - Processes video frames sequentially and saves output in .avi format
|
|
|
|
+ - If no source is specified, downloads and uses a default sample video\
|
|
|
|
+ - The inference solution will be launched using the 'streamlit run' command.
|
|
|
|
+ - The Streamlit app file is located in the Ultralytics package directory.
|
|
|
|
+ """
|
|
|
|
+ full_args_dict = {**DEFAULT_SOL_DICT, **DEFAULT_CFG_DICT} # arguments dictionary
|
|
|
|
+ overrides = {}
|
|
|
|
+
|
|
|
|
+ # check dictionary alignment
|
|
|
|
+ for arg in merge_equals_args(args):
|
|
|
|
+ arg = arg.lstrip("-").rstrip(",")
|
|
|
|
+ if "=" in arg:
|
|
|
|
+ try:
|
|
|
|
+ k, v = parse_key_value_pair(arg)
|
|
|
|
+ overrides[k] = v
|
|
|
|
+ except (NameError, SyntaxError, ValueError, AssertionError) as e:
|
|
|
|
+ check_dict_alignment(full_args_dict, {arg: ""}, e)
|
|
|
|
+ elif arg in full_args_dict and isinstance(full_args_dict.get(arg), bool):
|
|
|
|
+ overrides[arg] = True
|
|
|
|
+ check_dict_alignment(full_args_dict, overrides) # dict alignment
|
|
|
|
+
|
|
|
|
+ # Get solution name
|
|
|
|
+ if args and args[0] in SOLUTION_MAP:
|
|
|
|
+ if args[0] != "help":
|
|
|
|
+ s_n = args.pop(0) # Extract the solution name directly
|
|
|
|
+ else:
|
|
|
|
+ LOGGER.info(SOLUTIONS_HELP_MSG)
|
|
|
|
+ else:
|
|
|
|
+ LOGGER.warning(
|
|
|
|
+ f"⚠️ No valid solution provided. Using default 'count'. Available: {', '.join(SOLUTION_MAP.keys())}"
|
|
|
|
+ )
|
|
|
|
+ s_n = "count" # Default solution if none provided
|
|
|
|
+
|
|
|
|
+ if args and args[0] == "help": # Add check for return if user call `yolo solutions help`
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+ if s_n == "inference":
|
|
|
|
+ checks.check_requirements("streamlit>=1.29.0")
|
|
|
|
+ LOGGER.info("💡 Loading Ultralytics live inference app...")
|
|
|
|
+ subprocess.run(
|
|
|
|
+ [ # Run subprocess with Streamlit custom argument
|
|
|
|
+ "streamlit",
|
|
|
|
+ "run",
|
|
|
|
+ str(ROOT / "solutions/streamlit_inference.py"),
|
|
|
|
+ "--server.headless",
|
|
|
|
+ "true",
|
|
|
|
+ overrides.pop("model", "yolo11n.pt"),
|
|
|
|
+ ]
|
|
|
|
+ )
|
|
|
|
+ else:
|
|
|
|
+ cls, method = SOLUTION_MAP[s_n] # solution class name, method name and default source
|
|
|
|
+
|
|
|
|
+ from ultralytics import solutions # import ultralytics solutions
|
|
|
|
+
|
|
|
|
+ solution = getattr(solutions, cls)(IS_CLI=True, **overrides) # get solution class i.e ObjectCounter
|
|
|
|
+ process = getattr(
|
|
|
|
+ solution, method
|
|
|
|
+ ) # get specific function of class for processing i.e, count from ObjectCounter
|
|
|
|
+
|
|
|
|
+ cap = cv2.VideoCapture(solution.CFG["source"]) # read the video file
|
|
|
|
+
|
|
|
|
+ # extract width, height and fps of the video file, create save directory and initialize video writer
|
|
|
|
+ import os # for directory creation
|
|
|
|
+ from pathlib import Path
|
|
|
|
+
|
|
|
|
+ from ultralytics.utils.files import increment_path # for output directory path update
|
|
|
|
+
|
|
|
|
+ w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
|
|
|
|
+ if s_n == "analytics": # analytical graphs follow fixed shape for output i.e w=1920, h=1080
|
|
|
|
+ w, h = 1920, 1080
|
|
|
|
+ save_dir = increment_path(Path("runs") / "solutions" / "exp", exist_ok=False)
|
|
|
|
+ save_dir.mkdir(parents=True, exist_ok=True) # create the output directory
|
|
|
|
+ vw = cv2.VideoWriter(os.path.join(save_dir, "solution.avi"), cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
|
|
|
|
+
|
|
|
|
+ try: # Process video frames
|
|
|
|
+ f_n = 0 # frame number, required for analytical graphs
|
|
|
|
+ while cap.isOpened():
|
|
|
|
+ success, frame = cap.read()
|
|
|
|
+ if not success:
|
|
|
|
+ break
|
|
|
|
+ frame = process(frame, f_n := f_n + 1) if s_n == "analytics" else process(frame)
|
|
|
|
+ vw.write(frame)
|
|
|
|
+ if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
|
|
+ break
|
|
|
|
+ finally:
|
|
|
|
+ cap.release()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def parse_key_value_pair(pair: str = "key=value"):
|
|
|
|
+ """
|
|
|
|
+ Parses a key-value pair string into separate key and value components.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ pair (str): A string containing a key-value pair in the format "key=value".
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ key (str): The parsed key.
|
|
|
|
+ value (str): The parsed value.
|
|
|
|
+
|
|
|
|
+ Raises:
|
|
|
|
+ AssertionError: If the value is missing or empty.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> key, value = parse_key_value_pair("model=yolo11n.pt")
|
|
|
|
+ >>> print(f"Key: {key}, Value: {value}")
|
|
|
|
+ Key: model, Value: yolo11n.pt
|
|
|
|
+
|
|
|
|
+ >>> key, value = parse_key_value_pair("epochs=100")
|
|
|
|
+ >>> print(f"Key: {key}, Value: {value}")
|
|
|
|
+ Key: epochs, Value: 100
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - The function splits the input string on the first '=' character.
|
|
|
|
+ - Leading and trailing whitespace is removed from both key and value.
|
|
|
|
+ - An assertion error is raised if the value is empty after stripping.
|
|
|
|
+ """
|
|
|
|
+ k, v = pair.split("=", 1) # split on first '=' sign
|
|
|
|
+ k, v = k.strip(), v.strip() # remove spaces
|
|
|
|
+ assert v, f"missing '{k}' value"
|
|
|
|
+ return k, smart_value(v)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def smart_value(v):
|
|
|
|
+ """
|
|
|
|
+ Converts a string representation of a value to its appropriate Python type.
|
|
|
|
+
|
|
|
|
+ This function attempts to convert a given string into a Python object of the most appropriate type. It handles
|
|
|
|
+ conversions to None, bool, int, float, and other types that can be evaluated safely.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ v (str): The string representation of the value to be converted.
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ (Any): The converted value. The type can be None, bool, int, float, or the original string if no conversion
|
|
|
|
+ is applicable.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> smart_value("42")
|
|
|
|
+ 42
|
|
|
|
+ >>> smart_value("3.14")
|
|
|
|
+ 3.14
|
|
|
|
+ >>> smart_value("True")
|
|
|
|
+ True
|
|
|
|
+ >>> smart_value("None")
|
|
|
|
+ None
|
|
|
|
+ >>> smart_value("some_string")
|
|
|
|
+ 'some_string'
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - The function uses a case-insensitive comparison for boolean and None values.
|
|
|
|
+ - For other types, it attempts to use Python's eval() function, which can be unsafe if used on untrusted input.
|
|
|
|
+ - If no conversion is possible, the original string is returned.
|
|
|
|
+ """
|
|
|
|
+ v_lower = v.lower()
|
|
|
|
+ if v_lower == "none":
|
|
|
|
+ return None
|
|
|
|
+ elif v_lower == "true":
|
|
|
|
+ return True
|
|
|
|
+ elif v_lower == "false":
|
|
|
|
+ return False
|
|
|
|
+ else:
|
|
|
|
+ try:
|
|
|
|
+ return eval(v)
|
|
|
|
+ except Exception:
|
|
|
|
+ return v
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def entrypoint(debug=""):
|
|
|
|
+ """
|
|
|
|
+ Ultralytics entrypoint function for parsing and executing command-line arguments.
|
|
|
|
+
|
|
|
|
+ This function serves as the main entry point for the Ultralytics CLI, parsing command-line arguments and
|
|
|
|
+ executing the corresponding tasks such as training, validation, prediction, exporting models, and more.
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ debug (str): Space-separated string of command-line arguments for debugging purposes.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ Train a detection model for 10 epochs with an initial learning_rate of 0.01:
|
|
|
|
+ >>> entrypoint("train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01")
|
|
|
|
+
|
|
|
|
+ Predict a YouTube video using a pretrained segmentation model at image size 320:
|
|
|
|
+ >>> entrypoint("predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320")
|
|
|
|
+
|
|
|
|
+ Validate a pretrained detection model at batch-size 1 and image size 640:
|
|
|
|
+ >>> entrypoint("val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640")
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - If no arguments are passed, the function will display the usage help message.
|
|
|
|
+ - For a list of all available commands and their arguments, see the provided help messages and the
|
|
|
|
+ Ultralytics documentation at https://docs.ultralytics.com.
|
|
|
|
+ """
|
|
|
|
+ args = (debug.split(" ") if debug else ARGV)[1:]
|
|
|
|
+ if not args: # no arguments passed
|
|
|
|
+ LOGGER.info(CLI_HELP_MSG)
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+ special = {
|
|
|
|
+ "help": lambda: LOGGER.info(CLI_HELP_MSG),
|
|
|
|
+ "checks": checks.collect_system_info,
|
|
|
|
+ "version": lambda: LOGGER.info(__version__),
|
|
|
|
+ "settings": lambda: handle_yolo_settings(args[1:]),
|
|
|
|
+ "cfg": lambda: yaml_print(DEFAULT_CFG_PATH),
|
|
|
|
+ "hub": lambda: handle_yolo_hub(args[1:]),
|
|
|
|
+ "login": lambda: handle_yolo_hub(args),
|
|
|
|
+ "logout": lambda: handle_yolo_hub(args),
|
|
|
|
+ "copy-cfg": copy_default_cfg,
|
|
|
|
+ "solutions": lambda: handle_yolo_solutions(args[1:]),
|
|
|
|
+ }
|
|
|
|
+ full_args_dict = {**DEFAULT_CFG_DICT, **{k: None for k in TASKS}, **{k: None for k in MODES}, **special}
|
|
|
|
+
|
|
|
|
+ # Define common misuses of special commands, i.e. -h, -help, --help
|
|
|
|
+ special.update({k[0]: v for k, v in special.items()}) # singular
|
|
|
|
+ special.update({k[:-1]: v for k, v in special.items() if len(k) > 1 and k.endswith("s")}) # singular
|
|
|
|
+ special = {**special, **{f"-{k}": v for k, v in special.items()}, **{f"--{k}": v for k, v in special.items()}}
|
|
|
|
+
|
|
|
|
+ overrides = {} # basic overrides, i.e. imgsz=320
|
|
|
|
+ for a in merge_equals_args(args): # merge spaces around '=' sign
|
|
|
|
+ if a.startswith("--"):
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ argument '{a}' does not require leading dashes '--', updating to '{a[2:]}'.")
|
|
|
|
+ a = a[2:]
|
|
|
|
+ if a.endswith(","):
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ argument '{a}' does not require trailing comma ',', updating to '{a[:-1]}'.")
|
|
|
|
+ a = a[:-1]
|
|
|
|
+ if "=" in a:
|
|
|
|
+ try:
|
|
|
|
+ k, v = parse_key_value_pair(a)
|
|
|
|
+ if k == "cfg" and v is not None: # custom.yaml passed
|
|
|
|
+ LOGGER.info(f"Overriding {DEFAULT_CFG_PATH} with {v}")
|
|
|
|
+ overrides = {k: val for k, val in yaml_load(checks.check_yaml(v)).items() if k != "cfg"}
|
|
|
|
+ else:
|
|
|
|
+ overrides[k] = v
|
|
|
|
+ except (NameError, SyntaxError, ValueError, AssertionError) as e:
|
|
|
|
+ check_dict_alignment(full_args_dict, {a: ""}, e)
|
|
|
|
+
|
|
|
|
+ elif a in TASKS:
|
|
|
|
+ overrides["task"] = a
|
|
|
|
+ elif a in MODES:
|
|
|
|
+ overrides["mode"] = a
|
|
|
|
+ elif a.lower() in special:
|
|
|
|
+ special[a.lower()]()
|
|
|
|
+ return
|
|
|
|
+ elif a in DEFAULT_CFG_DICT and isinstance(DEFAULT_CFG_DICT[a], bool):
|
|
|
|
+ overrides[a] = True # auto-True for default bool args, i.e. 'yolo show' sets show=True
|
|
|
|
+ elif a in DEFAULT_CFG_DICT:
|
|
|
|
+ raise SyntaxError(
|
|
|
|
+ f"'{colorstr('red', 'bold', a)}' is a valid YOLO argument but is missing an '=' sign "
|
|
|
|
+ f"to set its value, i.e. try '{a}={DEFAULT_CFG_DICT[a]}'\n{CLI_HELP_MSG}"
|
|
|
|
+ )
|
|
|
|
+ else:
|
|
|
|
+ check_dict_alignment(full_args_dict, {a: ""})
|
|
|
|
+
|
|
|
|
+ # Check keys
|
|
|
|
+ check_dict_alignment(full_args_dict, overrides)
|
|
|
|
+
|
|
|
|
+ # Mode
|
|
|
|
+ mode = overrides.get("mode")
|
|
|
|
+ if mode is None:
|
|
|
|
+ mode = DEFAULT_CFG.mode or "predict"
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ 'mode' argument is missing. Valid modes are {MODES}. Using default 'mode={mode}'.")
|
|
|
|
+ elif mode not in MODES:
|
|
|
|
+ raise ValueError(f"Invalid 'mode={mode}'. Valid modes are {MODES}.\n{CLI_HELP_MSG}")
|
|
|
|
+
|
|
|
|
+ # Task
|
|
|
|
+ task = overrides.pop("task", None)
|
|
|
|
+ if task:
|
|
|
|
+ if task == "classify" and mode == "track":
|
|
|
|
+ raise ValueError(
|
|
|
|
+ f"❌ Classification doesn't support 'mode=track'. Valid modes for classification are"
|
|
|
|
+ f" {MODES - {'track'}}.\n{CLI_HELP_MSG}"
|
|
|
|
+ )
|
|
|
|
+ elif task not in TASKS:
|
|
|
|
+ if task == "track":
|
|
|
|
+ LOGGER.warning(
|
|
|
|
+ "WARNING ⚠️ invalid 'task=track', setting 'task=detect' and 'mode=track'. Valid tasks are {TASKS}.\n{CLI_HELP_MSG}."
|
|
|
|
+ )
|
|
|
|
+ task, mode = "detect", "track"
|
|
|
|
+ else:
|
|
|
|
+ raise ValueError(f"Invalid 'task={task}'. Valid tasks are {TASKS}.\n{CLI_HELP_MSG}")
|
|
|
|
+ if "model" not in overrides:
|
|
|
|
+ overrides["model"] = TASK2MODEL[task]
|
|
|
|
+
|
|
|
|
+ # Model
|
|
|
|
+ model = overrides.pop("model", DEFAULT_CFG.model)
|
|
|
|
+ if model is None:
|
|
|
|
+ model = "yolo11n.pt"
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ 'model' argument is missing. Using default 'model={model}'.")
|
|
|
|
+ overrides["model"] = model
|
|
|
|
+ stem = Path(model).stem.lower()
|
|
|
|
+ if "rtdetr" in stem: # guess architecture
|
|
|
|
+ from ultralytics import RTDETR
|
|
|
|
+
|
|
|
|
+ model = RTDETR(model) # no task argument
|
|
|
|
+ elif "fastsam" in stem:
|
|
|
|
+ from ultralytics import FastSAM
|
|
|
|
+
|
|
|
|
+ model = FastSAM(model)
|
|
|
|
+ elif "sam_" in stem or "sam2_" in stem or "sam2.1_" in stem:
|
|
|
|
+ from ultralytics import SAM
|
|
|
|
+
|
|
|
|
+ model = SAM(model)
|
|
|
|
+ else:
|
|
|
|
+ from ultralytics import YOLO
|
|
|
|
+
|
|
|
|
+ model = YOLO(model, task=task)
|
|
|
|
+ if isinstance(overrides.get("pretrained"), str):
|
|
|
|
+ model.load(overrides["pretrained"])
|
|
|
|
+
|
|
|
|
+ # Task Update
|
|
|
|
+ if task != model.task:
|
|
|
|
+ if task:
|
|
|
|
+ LOGGER.warning(
|
|
|
|
+ f"WARNING ⚠️ conflicting 'task={task}' passed with 'task={model.task}' model. "
|
|
|
|
+ f"Ignoring 'task={task}' and updating to 'task={model.task}' to match model."
|
|
|
|
+ )
|
|
|
|
+ task = model.task
|
|
|
|
+
|
|
|
|
+ # Mode
|
|
|
|
+ if mode in {"predict", "track"} and "source" not in overrides:
|
|
|
|
+ overrides["source"] = (
|
|
|
|
+ "https://ultralytics.com/images/boats.jpg" if task == "obb" else DEFAULT_CFG.source or ASSETS
|
|
|
|
+ )
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ 'source' argument is missing. Using default 'source={overrides['source']}'.")
|
|
|
|
+ elif mode in {"train", "val"}:
|
|
|
|
+ if "data" not in overrides and "resume" not in overrides:
|
|
|
|
+ overrides["data"] = DEFAULT_CFG.data or TASK2DATA.get(task or DEFAULT_CFG.task, DEFAULT_CFG.data)
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ 'data' argument is missing. Using default 'data={overrides['data']}'.")
|
|
|
|
+ elif mode == "export":
|
|
|
|
+ if "format" not in overrides:
|
|
|
|
+ overrides["format"] = DEFAULT_CFG.format or "torchscript"
|
|
|
|
+ LOGGER.warning(f"WARNING ⚠️ 'format' argument is missing. Using default 'format={overrides['format']}'.")
|
|
|
|
+
|
|
|
|
+ # Run command in python
|
|
|
|
+ getattr(model, mode)(**overrides) # default args from model
|
|
|
|
+
|
|
|
|
+ # Show help
|
|
|
|
+ LOGGER.info(f"💡 Learn more at https://docs.ultralytics.com/modes/{mode}")
|
|
|
|
+
|
|
|
|
+ # Recommend VS Code extension
|
|
|
|
+ if IS_VSCODE and SETTINGS.get("vscode_msg", True):
|
|
|
|
+ LOGGER.info(vscode_msg())
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+# Special modes --------------------------------------------------------------------------------------------------------
|
|
|
|
+def copy_default_cfg():
|
|
|
|
+ """
|
|
|
|
+ Copies the default configuration file and creates a new one with '_copy' appended to its name.
|
|
|
|
+
|
|
|
|
+ This function duplicates the existing default configuration file (DEFAULT_CFG_PATH) and saves it
|
|
|
|
+ with '_copy' appended to its name in the current working directory. It provides a convenient way
|
|
|
|
+ to create a custom configuration file based on the default settings.
|
|
|
|
+
|
|
|
|
+ Examples:
|
|
|
|
+ >>> copy_default_cfg()
|
|
|
|
+ # Output: default.yaml copied to /path/to/current/directory/default_copy.yaml
|
|
|
|
+ # Example YOLO command with this new custom cfg:
|
|
|
|
+ # yolo cfg='/path/to/current/directory/default_copy.yaml' imgsz=320 batch=8
|
|
|
|
+
|
|
|
|
+ Notes:
|
|
|
|
+ - The new configuration file is created in the current working directory.
|
|
|
|
+ - After copying, the function prints a message with the new file's location and an example
|
|
|
|
+ YOLO command demonstrating how to use the new configuration file.
|
|
|
|
+ - This function is useful for users who want to modify the default configuration without
|
|
|
|
+ altering the original file.
|
|
|
|
+ """
|
|
|
|
+ new_file = Path.cwd() / DEFAULT_CFG_PATH.name.replace(".yaml", "_copy.yaml")
|
|
|
|
+ shutil.copy2(DEFAULT_CFG_PATH, new_file)
|
|
|
|
+ LOGGER.info(
|
|
|
|
+ f"{DEFAULT_CFG_PATH} copied to {new_file}\n"
|
|
|
|
+ f"Example YOLO command with this new custom cfg:\n yolo cfg='{new_file}' imgsz=320 batch=8"
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+if __name__ == "__main__":
|
|
|
|
+ # Example: entrypoint(debug='yolo predict model=yolo11n.pt')
|
|
|
|
+ entrypoint(debug="")
|