12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025 |
- # 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="")
|