__init__.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025
  1. # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
  2. import shutil
  3. import subprocess
  4. import sys
  5. from pathlib import Path
  6. from types import SimpleNamespace
  7. from typing import Dict, List, Union
  8. import cv2
  9. from ultralytics.utils import (
  10. ASSETS,
  11. DEFAULT_CFG,
  12. DEFAULT_CFG_DICT,
  13. DEFAULT_CFG_PATH,
  14. DEFAULT_SOL_DICT,
  15. IS_VSCODE,
  16. LOGGER,
  17. RANK,
  18. ROOT,
  19. RUNS_DIR,
  20. SETTINGS,
  21. SETTINGS_FILE,
  22. TESTS_RUNNING,
  23. IterableSimpleNamespace,
  24. __version__,
  25. checks,
  26. colorstr,
  27. deprecation_warn,
  28. vscode_msg,
  29. yaml_load,
  30. yaml_print,
  31. )
  32. # Define valid solutions
  33. SOLUTION_MAP = {
  34. "count": ("ObjectCounter", "count"),
  35. "heatmap": ("Heatmap", "generate_heatmap"),
  36. "queue": ("QueueManager", "process_queue"),
  37. "speed": ("SpeedEstimator", "estimate_speed"),
  38. "workout": ("AIGym", "monitor"),
  39. "analytics": ("Analytics", "process_data"),
  40. "trackzone": ("TrackZone", "trackzone"),
  41. "inference": ("Inference", "inference"),
  42. "help": None,
  43. }
  44. # Define valid tasks and modes
  45. MODES = {"train", "val", "predict", "export", "track", "benchmark"}
  46. TASKS = {"detect", "segment", "classify", "pose", "obb"}
  47. TASK2DATA = {
  48. "detect": "coco8.yaml",
  49. "segment": "coco8-seg.yaml",
  50. "classify": "imagenet10",
  51. "pose": "coco8-pose.yaml",
  52. "obb": "dota8.yaml",
  53. }
  54. TASK2MODEL = {
  55. "detect": "yolo11n.pt",
  56. "segment": "yolo11n-seg.pt",
  57. "classify": "yolo11n-cls.pt",
  58. "pose": "yolo11n-pose.pt",
  59. "obb": "yolo11n-obb.pt",
  60. }
  61. TASK2METRIC = {
  62. "detect": "metrics/mAP50-95(B)",
  63. "segment": "metrics/mAP50-95(M)",
  64. "classify": "metrics/accuracy_top1",
  65. "pose": "metrics/mAP50-95(P)",
  66. "obb": "metrics/mAP50-95(B)",
  67. }
  68. MODELS = {TASK2MODEL[task] for task in TASKS}
  69. ARGV = sys.argv or ["", ""] # sometimes sys.argv = []
  70. SOLUTIONS_HELP_MSG = f"""
  71. Arguments received: {str(["yolo"] + ARGV[1:])}. Ultralytics 'yolo solutions' usage overview:
  72. yolo solutions SOLUTION ARGS
  73. Where SOLUTION (optional) is one of {list(SOLUTION_MAP.keys())[:-1]}
  74. ARGS (optional) are any number of custom 'arg=value' pairs like 'show_in=True' that override defaults
  75. at https://docs.ultralytics.com/usage/cfg
  76. 1. Call object counting solution
  77. yolo solutions count source="path/to/video/file.mp4" region=[(20, 400), (1080, 400), (1080, 360), (20, 360)]
  78. 2. Call heatmaps solution
  79. yolo solutions heatmap colormap=cv2.COLORMAP_PARULA model=yolo11n.pt
  80. 3. Call queue management solution
  81. yolo solutions queue region=[(20, 400), (1080, 400), (1080, 360), (20, 360)] model=yolo11n.pt
  82. 4. Call workouts monitoring solution for push-ups
  83. yolo solutions workout model=yolo11n-pose.pt kpts=[6, 8, 10]
  84. 5. Generate analytical graphs
  85. yolo solutions analytics analytics_type="pie"
  86. 6. Track objects within specific zones
  87. yolo solutions trackzone source="path/to/video/file.mp4" region=[(150, 150), (1130, 150), (1130, 570), (150, 570)]
  88. 7. Streamlit real-time webcam inference GUI
  89. yolo streamlit-predict
  90. """
  91. CLI_HELP_MSG = f"""
  92. Arguments received: {str(["yolo"] + ARGV[1:])}. Ultralytics 'yolo' commands use the following syntax:
  93. yolo TASK MODE ARGS
  94. Where TASK (optional) is one of {TASKS}
  95. MODE (required) is one of {MODES}
  96. ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults.
  97. See all ARGS at https://docs.ultralytics.com/usage/cfg or with 'yolo cfg'
  98. 1. Train a detection model for 10 epochs with an initial learning_rate of 0.01
  99. yolo train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01
  100. 2. Predict a YouTube video using a pretrained segmentation model at image size 320:
  101. yolo predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320
  102. 3. Val a pretrained detection model at batch-size 1 and image size 640:
  103. yolo val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640
  104. 4. Export a YOLO11n classification model to ONNX format at image size 224 by 128 (no TASK required)
  105. yolo export model=yolo11n-cls.pt format=onnx imgsz=224,128
  106. 5. Ultralytics solutions usage
  107. yolo solutions count or in {list(SOLUTION_MAP.keys())[1:-1]} source="path/to/video/file.mp4"
  108. 6. Run special commands:
  109. yolo help
  110. yolo checks
  111. yolo version
  112. yolo settings
  113. yolo copy-cfg
  114. yolo cfg
  115. yolo solutions help
  116. Docs: https://docs.ultralytics.com
  117. Solutions: https://docs.ultralytics.com/solutions/
  118. Community: https://community.ultralytics.com
  119. GitHub: https://github.com/ultralytics/ultralytics
  120. """
  121. # Define keys for arg type checks
  122. CFG_FLOAT_KEYS = { # integer or float arguments, i.e. x=2 and x=2.0
  123. "warmup_epochs",
  124. "box",
  125. "cls",
  126. "dfl",
  127. "degrees",
  128. "shear",
  129. "time",
  130. "workspace",
  131. "batch",
  132. }
  133. CFG_FRACTION_KEYS = { # fractional float arguments with 0.0<=values<=1.0
  134. "dropout",
  135. "lr0",
  136. "lrf",
  137. "momentum",
  138. "weight_decay",
  139. "warmup_momentum",
  140. "warmup_bias_lr",
  141. "hsv_h",
  142. "hsv_s",
  143. "hsv_v",
  144. "translate",
  145. "scale",
  146. "perspective",
  147. "flipud",
  148. "fliplr",
  149. "bgr",
  150. "mosaic",
  151. "mixup",
  152. "copy_paste",
  153. "conf",
  154. "iou",
  155. "fraction",
  156. }
  157. CFG_INT_KEYS = { # integer-only arguments
  158. "epochs",
  159. "patience",
  160. "workers",
  161. "seed",
  162. "close_mosaic",
  163. "mask_ratio",
  164. "max_det",
  165. "vid_stride",
  166. "line_width",
  167. "nbs",
  168. "save_period",
  169. }
  170. CFG_BOOL_KEYS = { # boolean-only arguments
  171. "save",
  172. "exist_ok",
  173. "verbose",
  174. "deterministic",
  175. "single_cls",
  176. "rect",
  177. "cos_lr",
  178. "overlap_mask",
  179. "val",
  180. "save_json",
  181. "save_hybrid",
  182. "half",
  183. "dnn",
  184. "plots",
  185. "show",
  186. "save_txt",
  187. "save_conf",
  188. "save_crop",
  189. "save_frames",
  190. "show_labels",
  191. "show_conf",
  192. "visualize",
  193. "augment",
  194. "agnostic_nms",
  195. "retina_masks",
  196. "show_boxes",
  197. "keras",
  198. "optimize",
  199. "int8",
  200. "dynamic",
  201. "simplify",
  202. "nms",
  203. "profile",
  204. "multi_scale",
  205. }
  206. def cfg2dict(cfg):
  207. """
  208. Converts a configuration object to a dictionary.
  209. Args:
  210. cfg (str | Path | Dict | SimpleNamespace): Configuration object to be converted. Can be a file path,
  211. a string, a dictionary, or a SimpleNamespace object.
  212. Returns:
  213. (Dict): Configuration object in dictionary format.
  214. Examples:
  215. Convert a YAML file path to a dictionary:
  216. >>> config_dict = cfg2dict("config.yaml")
  217. Convert a SimpleNamespace to a dictionary:
  218. >>> from types import SimpleNamespace
  219. >>> config_sn = SimpleNamespace(param1="value1", param2="value2")
  220. >>> config_dict = cfg2dict(config_sn)
  221. Pass through an already existing dictionary:
  222. >>> config_dict = cfg2dict({"param1": "value1", "param2": "value2"})
  223. Notes:
  224. - If cfg is a path or string, it's loaded as YAML and converted to a dictionary.
  225. - If cfg is a SimpleNamespace object, it's converted to a dictionary using vars().
  226. - If cfg is already a dictionary, it's returned unchanged.
  227. """
  228. if isinstance(cfg, (str, Path)):
  229. cfg = yaml_load(cfg) # load dict
  230. elif isinstance(cfg, SimpleNamespace):
  231. cfg = vars(cfg) # convert to dict
  232. return cfg
  233. def get_cfg(cfg: Union[str, Path, Dict, SimpleNamespace] = DEFAULT_CFG_DICT, overrides: Dict = None):
  234. """
  235. Load and merge configuration data from a file or dictionary, with optional overrides.
  236. Args:
  237. cfg (str | Path | Dict | SimpleNamespace): Configuration data source. Can be a file path, dictionary, or
  238. SimpleNamespace object.
  239. overrides (Dict | None): Dictionary containing key-value pairs to override the base configuration.
  240. Returns:
  241. (SimpleNamespace): Namespace containing the merged configuration arguments.
  242. Examples:
  243. >>> from ultralytics.cfg import get_cfg
  244. >>> config = get_cfg() # Load default configuration
  245. >>> config_with_overrides = get_cfg("path/to/config.yaml", overrides={"epochs": 50, "batch_size": 16})
  246. Notes:
  247. - If both `cfg` and `overrides` are provided, the values in `overrides` will take precedence.
  248. - Special handling ensures alignment and correctness of the configuration, such as converting numeric
  249. `project` and `name` to strings and validating configuration keys and values.
  250. - The function performs type and value checks on the configuration data.
  251. """
  252. cfg = cfg2dict(cfg)
  253. # Merge overrides
  254. if overrides:
  255. overrides = cfg2dict(overrides)
  256. if "save_dir" not in cfg:
  257. overrides.pop("save_dir", None) # special override keys to ignore
  258. check_dict_alignment(cfg, overrides)
  259. cfg = {**cfg, **overrides} # merge cfg and overrides dicts (prefer overrides)
  260. # Special handling for numeric project/name
  261. for k in "project", "name":
  262. if k in cfg and isinstance(cfg[k], (int, float)):
  263. cfg[k] = str(cfg[k])
  264. if cfg.get("name") == "model": # assign model to 'name' arg
  265. cfg["name"] = str(cfg.get("model", "")).split(".")[0]
  266. LOGGER.warning(f"WARNING ⚠️ 'name=model' automatically updated to 'name={cfg['name']}'.")
  267. # Type and Value checks
  268. check_cfg(cfg)
  269. # Return instance
  270. return IterableSimpleNamespace(**cfg)
  271. def check_cfg(cfg, hard=True):
  272. """
  273. Checks configuration argument types and values for the Ultralytics library.
  274. This function validates the types and values of configuration arguments, ensuring correctness and converting
  275. them if necessary. It checks for specific key types defined in global variables such as CFG_FLOAT_KEYS,
  276. CFG_FRACTION_KEYS, CFG_INT_KEYS, and CFG_BOOL_KEYS.
  277. Args:
  278. cfg (Dict): Configuration dictionary to validate.
  279. hard (bool): If True, raises exceptions for invalid types and values; if False, attempts to convert them.
  280. Examples:
  281. >>> config = {
  282. ... "epochs": 50, # valid integer
  283. ... "lr0": 0.01, # valid float
  284. ... "momentum": 1.2, # invalid float (out of 0.0-1.0 range)
  285. ... "save": "true", # invalid bool
  286. ... }
  287. >>> check_cfg(config, hard=False)
  288. >>> print(config)
  289. {'epochs': 50, 'lr0': 0.01, 'momentum': 1.2, 'save': False} # corrected 'save' key
  290. Notes:
  291. - The function modifies the input dictionary in-place.
  292. - None values are ignored as they may be from optional arguments.
  293. - Fraction keys are checked to be within the range [0.0, 1.0].
  294. """
  295. for k, v in cfg.items():
  296. if v is not None: # None values may be from optional args
  297. if k in CFG_FLOAT_KEYS and not isinstance(v, (int, float)):
  298. if hard:
  299. raise TypeError(
  300. f"'{k}={v}' is of invalid type {type(v).__name__}. "
  301. f"Valid '{k}' types are int (i.e. '{k}=0') or float (i.e. '{k}=0.5')"
  302. )
  303. cfg[k] = float(v)
  304. elif k in CFG_FRACTION_KEYS:
  305. if not isinstance(v, (int, float)):
  306. if hard:
  307. raise TypeError(
  308. f"'{k}={v}' is of invalid type {type(v).__name__}. "
  309. f"Valid '{k}' types are int (i.e. '{k}=0') or float (i.e. '{k}=0.5')"
  310. )
  311. cfg[k] = v = float(v)
  312. if not (0.0 <= v <= 1.0):
  313. raise ValueError(f"'{k}={v}' is an invalid value. Valid '{k}' values are between 0.0 and 1.0.")
  314. elif k in CFG_INT_KEYS and not isinstance(v, int):
  315. if hard:
  316. raise TypeError(
  317. f"'{k}={v}' is of invalid type {type(v).__name__}. '{k}' must be an int (i.e. '{k}=8')"
  318. )
  319. cfg[k] = int(v)
  320. elif k in CFG_BOOL_KEYS and not isinstance(v, bool):
  321. if hard:
  322. raise TypeError(
  323. f"'{k}={v}' is of invalid type {type(v).__name__}. "
  324. f"'{k}' must be a bool (i.e. '{k}=True' or '{k}=False')"
  325. )
  326. cfg[k] = bool(v)
  327. def get_save_dir(args, name=None):
  328. """
  329. Returns the directory path for saving outputs, derived from arguments or default settings.
  330. Args:
  331. args (SimpleNamespace): Namespace object containing configurations such as 'project', 'name', 'task',
  332. 'mode', and 'save_dir'.
  333. name (str | None): Optional name for the output directory. If not provided, it defaults to 'args.name'
  334. or the 'args.mode'.
  335. Returns:
  336. (Path): Directory path where outputs should be saved.
  337. Examples:
  338. >>> from types import SimpleNamespace
  339. >>> args = SimpleNamespace(project="my_project", task="detect", mode="train", exist_ok=True)
  340. >>> save_dir = get_save_dir(args)
  341. >>> print(save_dir)
  342. my_project/detect/train
  343. """
  344. if getattr(args, "save_dir", None):
  345. save_dir = args.save_dir
  346. else:
  347. from ultralytics.utils.files import increment_path
  348. project = args.project or (ROOT.parent / "tests/tmp/runs" if TESTS_RUNNING else RUNS_DIR) / args.task
  349. name = name or args.name or f"{args.mode}"
  350. save_dir = increment_path(Path(project) / name, exist_ok=args.exist_ok if RANK in {-1, 0} else True)
  351. return Path(save_dir)
  352. def _handle_deprecation(custom):
  353. """
  354. Handles deprecated configuration keys by mapping them to current equivalents with deprecation warnings.
  355. Args:
  356. custom (Dict): Configuration dictionary potentially containing deprecated keys.
  357. Examples:
  358. >>> custom_config = {"boxes": True, "hide_labels": "False", "line_thickness": 2}
  359. >>> _handle_deprecation(custom_config)
  360. >>> print(custom_config)
  361. {'show_boxes': True, 'show_labels': True, 'line_width': 2}
  362. Notes:
  363. This function modifies the input dictionary in-place, replacing deprecated keys with their current
  364. equivalents. It also handles value conversions where necessary, such as inverting boolean values for
  365. 'hide_labels' and 'hide_conf'.
  366. """
  367. for key in custom.copy().keys():
  368. if key == "boxes":
  369. deprecation_warn(key, "show_boxes")
  370. custom["show_boxes"] = custom.pop("boxes")
  371. if key == "hide_labels":
  372. deprecation_warn(key, "show_labels")
  373. custom["show_labels"] = custom.pop("hide_labels") == "False"
  374. if key == "hide_conf":
  375. deprecation_warn(key, "show_conf")
  376. custom["show_conf"] = custom.pop("hide_conf") == "False"
  377. if key == "line_thickness":
  378. deprecation_warn(key, "line_width")
  379. custom["line_width"] = custom.pop("line_thickness")
  380. if key == "label_smoothing":
  381. deprecation_warn(key)
  382. custom.pop("label_smoothing")
  383. return custom
  384. def check_dict_alignment(base: Dict, custom: Dict, e=None):
  385. """
  386. Checks alignment between custom and base configuration dictionaries, handling deprecated keys and providing error
  387. messages for mismatched keys.
  388. Args:
  389. base (Dict): The base configuration dictionary containing valid keys.
  390. custom (Dict): The custom configuration dictionary to be checked for alignment.
  391. e (Exception | None): Optional error instance passed by the calling function.
  392. Raises:
  393. SystemExit: If mismatched keys are found between the custom and base dictionaries.
  394. Examples:
  395. >>> base_cfg = {"epochs": 50, "lr0": 0.01, "batch_size": 16}
  396. >>> custom_cfg = {"epoch": 100, "lr": 0.02, "batch_size": 32}
  397. >>> try:
  398. ... check_dict_alignment(base_cfg, custom_cfg)
  399. ... except SystemExit:
  400. ... print("Mismatched keys found")
  401. Notes:
  402. - Suggests corrections for mismatched keys based on similarity to valid keys.
  403. - Automatically replaces deprecated keys in the custom configuration with updated equivalents.
  404. - Prints detailed error messages for each mismatched key to help users correct their configurations.
  405. """
  406. custom = _handle_deprecation(custom)
  407. base_keys, custom_keys = (set(x.keys()) for x in (base, custom))
  408. if mismatched := [k for k in custom_keys if k not in base_keys]:
  409. from difflib import get_close_matches
  410. string = ""
  411. for x in mismatched:
  412. matches = get_close_matches(x, base_keys) # key list
  413. matches = [f"{k}={base[k]}" if base.get(k) is not None else k for k in matches]
  414. match_str = f"Similar arguments are i.e. {matches}." if matches else ""
  415. string += f"'{colorstr('red', 'bold', x)}' is not a valid YOLO argument. {match_str}\n"
  416. raise SyntaxError(string + CLI_HELP_MSG) from e
  417. def merge_equals_args(args: List[str]) -> List[str]:
  418. """
  419. Merges arguments around isolated '=' in a list of strings and joins fragments with brackets.
  420. This function handles the following cases:
  421. 1. ['arg', '=', 'val'] becomes ['arg=val']
  422. 2. ['arg=', 'val'] becomes ['arg=val']
  423. 3. ['arg', '=val'] becomes ['arg=val']
  424. 4. Joins fragments with brackets, e.g., ['imgsz=[3,', '640,', '640]'] becomes ['imgsz=[3,640,640]']
  425. Args:
  426. args (List[str]): A list of strings where each element represents an argument or fragment.
  427. Returns:
  428. List[str]: A list of strings where the arguments around isolated '=' are merged and fragments with brackets are joined.
  429. Examples:
  430. >>> args = ["arg1", "=", "value", "arg2=", "value2", "arg3", "=value3", "imgsz=[3,", "640,", "640]"]
  431. >>> merge_and_join_args(args)
  432. ['arg1=value', 'arg2=value2', 'arg3=value3', 'imgsz=[3,640,640]']
  433. """
  434. new_args = []
  435. current = ""
  436. depth = 0
  437. i = 0
  438. while i < len(args):
  439. arg = args[i]
  440. # Handle equals sign merging
  441. if arg == "=" and 0 < i < len(args) - 1: # merge ['arg', '=', 'val']
  442. new_args[-1] += f"={args[i + 1]}"
  443. i += 2
  444. continue
  445. elif arg.endswith("=") and i < len(args) - 1 and "=" not in args[i + 1]: # merge ['arg=', 'val']
  446. new_args.append(f"{arg}{args[i + 1]}")
  447. i += 2
  448. continue
  449. elif arg.startswith("=") and i > 0: # merge ['arg', '=val']
  450. new_args[-1] += arg
  451. i += 1
  452. continue
  453. # Handle bracket joining
  454. depth += arg.count("[") - arg.count("]")
  455. current += arg
  456. if depth == 0:
  457. new_args.append(current)
  458. current = ""
  459. i += 1
  460. # Append any remaining current string
  461. if current:
  462. new_args.append(current)
  463. return new_args
  464. def handle_yolo_hub(args: List[str]) -> None:
  465. """
  466. Handles Ultralytics HUB command-line interface (CLI) commands for authentication.
  467. This function processes Ultralytics HUB CLI commands such as login and logout. It should be called when executing a
  468. script with arguments related to HUB authentication.
  469. Args:
  470. args (List[str]): A list of command line arguments. The first argument should be either 'login'
  471. or 'logout'. For 'login', an optional second argument can be the API key.
  472. Examples:
  473. ```bash
  474. yolo login YOUR_API_KEY
  475. ```
  476. Notes:
  477. - The function imports the 'hub' module from ultralytics to perform login and logout operations.
  478. - For the 'login' command, if no API key is provided, an empty string is passed to the login function.
  479. - The 'logout' command does not require any additional arguments.
  480. """
  481. from ultralytics import hub
  482. if args[0] == "login":
  483. key = args[1] if len(args) > 1 else ""
  484. # Log in to Ultralytics HUB using the provided API key
  485. hub.login(key)
  486. elif args[0] == "logout":
  487. # Log out from Ultralytics HUB
  488. hub.logout()
  489. def handle_yolo_settings(args: List[str]) -> None:
  490. """
  491. Handles YOLO settings command-line interface (CLI) commands.
  492. This function processes YOLO settings CLI commands such as reset and updating individual settings. It should be
  493. called when executing a script with arguments related to YOLO settings management.
  494. Args:
  495. args (List[str]): A list of command line arguments for YOLO settings management.
  496. Examples:
  497. >>> handle_yolo_settings(["reset"]) # Reset YOLO settings
  498. >>> handle_yolo_settings(["default_cfg_path=yolo11n.yaml"]) # Update a specific setting
  499. Notes:
  500. - If no arguments are provided, the function will display the current settings.
  501. - The 'reset' command will delete the existing settings file and create new default settings.
  502. - Other arguments are treated as key-value pairs to update specific settings.
  503. - The function will check for alignment between the provided settings and the existing ones.
  504. - After processing, the updated settings will be displayed.
  505. - For more information on handling YOLO settings, visit:
  506. https://docs.ultralytics.com/quickstart/#ultralytics-settings
  507. """
  508. url = "https://docs.ultralytics.com/quickstart/#ultralytics-settings" # help URL
  509. try:
  510. if any(args):
  511. if args[0] == "reset":
  512. SETTINGS_FILE.unlink() # delete the settings file
  513. SETTINGS.reset() # create new settings
  514. LOGGER.info("Settings reset successfully") # inform the user that settings have been reset
  515. else: # save a new setting
  516. new = dict(parse_key_value_pair(a) for a in args)
  517. check_dict_alignment(SETTINGS, new)
  518. SETTINGS.update(new)
  519. print(SETTINGS) # print the current settings
  520. LOGGER.info(f"💡 Learn more about Ultralytics Settings at {url}")
  521. except Exception as e:
  522. LOGGER.warning(f"WARNING ⚠️ settings error: '{e}'. Please see {url} for help.")
  523. def handle_yolo_solutions(args: List[str]) -> None:
  524. """
  525. Processes YOLO solutions arguments and runs the specified computer vision solutions pipeline.
  526. Args:
  527. args (List[str]): Command-line arguments for configuring and running the Ultralytics YOLO
  528. solutions: https://docs.ultralytics.com/solutions/, It can include solution name, source,
  529. and other configuration parameters.
  530. Returns:
  531. None: The function processes video frames and saves the output but doesn't return any value.
  532. Examples:
  533. Run people counting solution with default settings:
  534. >>> handle_yolo_solutions(["count"])
  535. Run analytics with custom configuration:
  536. >>> handle_yolo_solutions(["analytics", "conf=0.25", "source=path/to/video/file.mp4"])
  537. Run inference with custom configuration, requires Streamlit version 1.29.0 or higher.
  538. >>> handle_yolo_solutions(["inference", "model=yolo11n.pt"])
  539. Notes:
  540. - Default configurations are merged from DEFAULT_SOL_DICT and DEFAULT_CFG_DICT
  541. - Arguments can be provided in the format 'key=value' or as boolean flags
  542. - Available solutions are defined in SOLUTION_MAP with their respective classes and methods
  543. - If an invalid solution is provided, defaults to 'count' solution
  544. - Output videos are saved in 'runs/solution/{solution_name}' directory
  545. - For 'analytics' solution, frame numbers are tracked for generating analytical graphs
  546. - Video processing can be interrupted by pressing 'q'
  547. - Processes video frames sequentially and saves output in .avi format
  548. - If no source is specified, downloads and uses a default sample video\
  549. - The inference solution will be launched using the 'streamlit run' command.
  550. - The Streamlit app file is located in the Ultralytics package directory.
  551. """
  552. full_args_dict = {**DEFAULT_SOL_DICT, **DEFAULT_CFG_DICT} # arguments dictionary
  553. overrides = {}
  554. # check dictionary alignment
  555. for arg in merge_equals_args(args):
  556. arg = arg.lstrip("-").rstrip(",")
  557. if "=" in arg:
  558. try:
  559. k, v = parse_key_value_pair(arg)
  560. overrides[k] = v
  561. except (NameError, SyntaxError, ValueError, AssertionError) as e:
  562. check_dict_alignment(full_args_dict, {arg: ""}, e)
  563. elif arg in full_args_dict and isinstance(full_args_dict.get(arg), bool):
  564. overrides[arg] = True
  565. check_dict_alignment(full_args_dict, overrides) # dict alignment
  566. # Get solution name
  567. if args and args[0] in SOLUTION_MAP:
  568. if args[0] != "help":
  569. s_n = args.pop(0) # Extract the solution name directly
  570. else:
  571. LOGGER.info(SOLUTIONS_HELP_MSG)
  572. else:
  573. LOGGER.warning(
  574. f"⚠️ No valid solution provided. Using default 'count'. Available: {', '.join(SOLUTION_MAP.keys())}"
  575. )
  576. s_n = "count" # Default solution if none provided
  577. if args and args[0] == "help": # Add check for return if user call `yolo solutions help`
  578. return
  579. if s_n == "inference":
  580. checks.check_requirements("streamlit>=1.29.0")
  581. LOGGER.info("💡 Loading Ultralytics live inference app...")
  582. subprocess.run(
  583. [ # Run subprocess with Streamlit custom argument
  584. "streamlit",
  585. "run",
  586. str(ROOT / "solutions/streamlit_inference.py"),
  587. "--server.headless",
  588. "true",
  589. overrides.pop("model", "yolo11n.pt"),
  590. ]
  591. )
  592. else:
  593. cls, method = SOLUTION_MAP[s_n] # solution class name, method name and default source
  594. from ultralytics import solutions # import ultralytics solutions
  595. solution = getattr(solutions, cls)(IS_CLI=True, **overrides) # get solution class i.e ObjectCounter
  596. process = getattr(
  597. solution, method
  598. ) # get specific function of class for processing i.e, count from ObjectCounter
  599. cap = cv2.VideoCapture(solution.CFG["source"]) # read the video file
  600. # extract width, height and fps of the video file, create save directory and initialize video writer
  601. import os # for directory creation
  602. from pathlib import Path
  603. from ultralytics.utils.files import increment_path # for output directory path update
  604. w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS))
  605. if s_n == "analytics": # analytical graphs follow fixed shape for output i.e w=1920, h=1080
  606. w, h = 1920, 1080
  607. save_dir = increment_path(Path("runs") / "solutions" / "exp", exist_ok=False)
  608. save_dir.mkdir(parents=True, exist_ok=True) # create the output directory
  609. vw = cv2.VideoWriter(os.path.join(save_dir, "solution.avi"), cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
  610. try: # Process video frames
  611. f_n = 0 # frame number, required for analytical graphs
  612. while cap.isOpened():
  613. success, frame = cap.read()
  614. if not success:
  615. break
  616. frame = process(frame, f_n := f_n + 1) if s_n == "analytics" else process(frame)
  617. vw.write(frame)
  618. if cv2.waitKey(1) & 0xFF == ord("q"):
  619. break
  620. finally:
  621. cap.release()
  622. def parse_key_value_pair(pair: str = "key=value"):
  623. """
  624. Parses a key-value pair string into separate key and value components.
  625. Args:
  626. pair (str): A string containing a key-value pair in the format "key=value".
  627. Returns:
  628. key (str): The parsed key.
  629. value (str): The parsed value.
  630. Raises:
  631. AssertionError: If the value is missing or empty.
  632. Examples:
  633. >>> key, value = parse_key_value_pair("model=yolo11n.pt")
  634. >>> print(f"Key: {key}, Value: {value}")
  635. Key: model, Value: yolo11n.pt
  636. >>> key, value = parse_key_value_pair("epochs=100")
  637. >>> print(f"Key: {key}, Value: {value}")
  638. Key: epochs, Value: 100
  639. Notes:
  640. - The function splits the input string on the first '=' character.
  641. - Leading and trailing whitespace is removed from both key and value.
  642. - An assertion error is raised if the value is empty after stripping.
  643. """
  644. k, v = pair.split("=", 1) # split on first '=' sign
  645. k, v = k.strip(), v.strip() # remove spaces
  646. assert v, f"missing '{k}' value"
  647. return k, smart_value(v)
  648. def smart_value(v):
  649. """
  650. Converts a string representation of a value to its appropriate Python type.
  651. This function attempts to convert a given string into a Python object of the most appropriate type. It handles
  652. conversions to None, bool, int, float, and other types that can be evaluated safely.
  653. Args:
  654. v (str): The string representation of the value to be converted.
  655. Returns:
  656. (Any): The converted value. The type can be None, bool, int, float, or the original string if no conversion
  657. is applicable.
  658. Examples:
  659. >>> smart_value("42")
  660. 42
  661. >>> smart_value("3.14")
  662. 3.14
  663. >>> smart_value("True")
  664. True
  665. >>> smart_value("None")
  666. None
  667. >>> smart_value("some_string")
  668. 'some_string'
  669. Notes:
  670. - The function uses a case-insensitive comparison for boolean and None values.
  671. - For other types, it attempts to use Python's eval() function, which can be unsafe if used on untrusted input.
  672. - If no conversion is possible, the original string is returned.
  673. """
  674. v_lower = v.lower()
  675. if v_lower == "none":
  676. return None
  677. elif v_lower == "true":
  678. return True
  679. elif v_lower == "false":
  680. return False
  681. else:
  682. try:
  683. return eval(v)
  684. except Exception:
  685. return v
  686. def entrypoint(debug=""):
  687. """
  688. Ultralytics entrypoint function for parsing and executing command-line arguments.
  689. This function serves as the main entry point for the Ultralytics CLI, parsing command-line arguments and
  690. executing the corresponding tasks such as training, validation, prediction, exporting models, and more.
  691. Args:
  692. debug (str): Space-separated string of command-line arguments for debugging purposes.
  693. Examples:
  694. Train a detection model for 10 epochs with an initial learning_rate of 0.01:
  695. >>> entrypoint("train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01")
  696. Predict a YouTube video using a pretrained segmentation model at image size 320:
  697. >>> entrypoint("predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320")
  698. Validate a pretrained detection model at batch-size 1 and image size 640:
  699. >>> entrypoint("val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640")
  700. Notes:
  701. - If no arguments are passed, the function will display the usage help message.
  702. - For a list of all available commands and their arguments, see the provided help messages and the
  703. Ultralytics documentation at https://docs.ultralytics.com.
  704. """
  705. args = (debug.split(" ") if debug else ARGV)[1:]
  706. if not args: # no arguments passed
  707. LOGGER.info(CLI_HELP_MSG)
  708. return
  709. special = {
  710. "help": lambda: LOGGER.info(CLI_HELP_MSG),
  711. "checks": checks.collect_system_info,
  712. "version": lambda: LOGGER.info(__version__),
  713. "settings": lambda: handle_yolo_settings(args[1:]),
  714. "cfg": lambda: yaml_print(DEFAULT_CFG_PATH),
  715. "hub": lambda: handle_yolo_hub(args[1:]),
  716. "login": lambda: handle_yolo_hub(args),
  717. "logout": lambda: handle_yolo_hub(args),
  718. "copy-cfg": copy_default_cfg,
  719. "solutions": lambda: handle_yolo_solutions(args[1:]),
  720. }
  721. full_args_dict = {**DEFAULT_CFG_DICT, **{k: None for k in TASKS}, **{k: None for k in MODES}, **special}
  722. # Define common misuses of special commands, i.e. -h, -help, --help
  723. special.update({k[0]: v for k, v in special.items()}) # singular
  724. special.update({k[:-1]: v for k, v in special.items() if len(k) > 1 and k.endswith("s")}) # singular
  725. special = {**special, **{f"-{k}": v for k, v in special.items()}, **{f"--{k}": v for k, v in special.items()}}
  726. overrides = {} # basic overrides, i.e. imgsz=320
  727. for a in merge_equals_args(args): # merge spaces around '=' sign
  728. if a.startswith("--"):
  729. LOGGER.warning(f"WARNING ⚠️ argument '{a}' does not require leading dashes '--', updating to '{a[2:]}'.")
  730. a = a[2:]
  731. if a.endswith(","):
  732. LOGGER.warning(f"WARNING ⚠️ argument '{a}' does not require trailing comma ',', updating to '{a[:-1]}'.")
  733. a = a[:-1]
  734. if "=" in a:
  735. try:
  736. k, v = parse_key_value_pair(a)
  737. if k == "cfg" and v is not None: # custom.yaml passed
  738. LOGGER.info(f"Overriding {DEFAULT_CFG_PATH} with {v}")
  739. overrides = {k: val for k, val in yaml_load(checks.check_yaml(v)).items() if k != "cfg"}
  740. else:
  741. overrides[k] = v
  742. except (NameError, SyntaxError, ValueError, AssertionError) as e:
  743. check_dict_alignment(full_args_dict, {a: ""}, e)
  744. elif a in TASKS:
  745. overrides["task"] = a
  746. elif a in MODES:
  747. overrides["mode"] = a
  748. elif a.lower() in special:
  749. special[a.lower()]()
  750. return
  751. elif a in DEFAULT_CFG_DICT and isinstance(DEFAULT_CFG_DICT[a], bool):
  752. overrides[a] = True # auto-True for default bool args, i.e. 'yolo show' sets show=True
  753. elif a in DEFAULT_CFG_DICT:
  754. raise SyntaxError(
  755. f"'{colorstr('red', 'bold', a)}' is a valid YOLO argument but is missing an '=' sign "
  756. f"to set its value, i.e. try '{a}={DEFAULT_CFG_DICT[a]}'\n{CLI_HELP_MSG}"
  757. )
  758. else:
  759. check_dict_alignment(full_args_dict, {a: ""})
  760. # Check keys
  761. check_dict_alignment(full_args_dict, overrides)
  762. # Mode
  763. mode = overrides.get("mode")
  764. if mode is None:
  765. mode = DEFAULT_CFG.mode or "predict"
  766. LOGGER.warning(f"WARNING ⚠️ 'mode' argument is missing. Valid modes are {MODES}. Using default 'mode={mode}'.")
  767. elif mode not in MODES:
  768. raise ValueError(f"Invalid 'mode={mode}'. Valid modes are {MODES}.\n{CLI_HELP_MSG}")
  769. # Task
  770. task = overrides.pop("task", None)
  771. if task:
  772. if task == "classify" and mode == "track":
  773. raise ValueError(
  774. f"❌ Classification doesn't support 'mode=track'. Valid modes for classification are"
  775. f" {MODES - {'track'}}.\n{CLI_HELP_MSG}"
  776. )
  777. elif task not in TASKS:
  778. if task == "track":
  779. LOGGER.warning(
  780. "WARNING ⚠️ invalid 'task=track', setting 'task=detect' and 'mode=track'. Valid tasks are {TASKS}.\n{CLI_HELP_MSG}."
  781. )
  782. task, mode = "detect", "track"
  783. else:
  784. raise ValueError(f"Invalid 'task={task}'. Valid tasks are {TASKS}.\n{CLI_HELP_MSG}")
  785. if "model" not in overrides:
  786. overrides["model"] = TASK2MODEL[task]
  787. # Model
  788. model = overrides.pop("model", DEFAULT_CFG.model)
  789. if model is None:
  790. model = "yolo11n.pt"
  791. LOGGER.warning(f"WARNING ⚠️ 'model' argument is missing. Using default 'model={model}'.")
  792. overrides["model"] = model
  793. stem = Path(model).stem.lower()
  794. if "rtdetr" in stem: # guess architecture
  795. from ultralytics import RTDETR
  796. model = RTDETR(model) # no task argument
  797. elif "fastsam" in stem:
  798. from ultralytics import FastSAM
  799. model = FastSAM(model)
  800. elif "sam_" in stem or "sam2_" in stem or "sam2.1_" in stem:
  801. from ultralytics import SAM
  802. model = SAM(model)
  803. else:
  804. from ultralytics import YOLO
  805. model = YOLO(model, task=task)
  806. if isinstance(overrides.get("pretrained"), str):
  807. model.load(overrides["pretrained"])
  808. # Task Update
  809. if task != model.task:
  810. if task:
  811. LOGGER.warning(
  812. f"WARNING ⚠️ conflicting 'task={task}' passed with 'task={model.task}' model. "
  813. f"Ignoring 'task={task}' and updating to 'task={model.task}' to match model."
  814. )
  815. task = model.task
  816. # Mode
  817. if mode in {"predict", "track"} and "source" not in overrides:
  818. overrides["source"] = (
  819. "https://ultralytics.com/images/boats.jpg" if task == "obb" else DEFAULT_CFG.source or ASSETS
  820. )
  821. LOGGER.warning(f"WARNING ⚠️ 'source' argument is missing. Using default 'source={overrides['source']}'.")
  822. elif mode in {"train", "val"}:
  823. if "data" not in overrides and "resume" not in overrides:
  824. overrides["data"] = DEFAULT_CFG.data or TASK2DATA.get(task or DEFAULT_CFG.task, DEFAULT_CFG.data)
  825. LOGGER.warning(f"WARNING ⚠️ 'data' argument is missing. Using default 'data={overrides['data']}'.")
  826. elif mode == "export":
  827. if "format" not in overrides:
  828. overrides["format"] = DEFAULT_CFG.format or "torchscript"
  829. LOGGER.warning(f"WARNING ⚠️ 'format' argument is missing. Using default 'format={overrides['format']}'.")
  830. # Run command in python
  831. getattr(model, mode)(**overrides) # default args from model
  832. # Show help
  833. LOGGER.info(f"💡 Learn more at https://docs.ultralytics.com/modes/{mode}")
  834. # Recommend VS Code extension
  835. if IS_VSCODE and SETTINGS.get("vscode_msg", True):
  836. LOGGER.info(vscode_msg())
  837. # Special modes --------------------------------------------------------------------------------------------------------
  838. def copy_default_cfg():
  839. """
  840. Copies the default configuration file and creates a new one with '_copy' appended to its name.
  841. This function duplicates the existing default configuration file (DEFAULT_CFG_PATH) and saves it
  842. with '_copy' appended to its name in the current working directory. It provides a convenient way
  843. to create a custom configuration file based on the default settings.
  844. Examples:
  845. >>> copy_default_cfg()
  846. # Output: default.yaml copied to /path/to/current/directory/default_copy.yaml
  847. # Example YOLO command with this new custom cfg:
  848. # yolo cfg='/path/to/current/directory/default_copy.yaml' imgsz=320 batch=8
  849. Notes:
  850. - The new configuration file is created in the current working directory.
  851. - After copying, the function prints a message with the new file's location and an example
  852. YOLO command demonstrating how to use the new configuration file.
  853. - This function is useful for users who want to modify the default configuration without
  854. altering the original file.
  855. """
  856. new_file = Path.cwd() / DEFAULT_CFG_PATH.name.replace(".yaml", "_copy.yaml")
  857. shutil.copy2(DEFAULT_CFG_PATH, new_file)
  858. LOGGER.info(
  859. f"{DEFAULT_CFG_PATH} copied to {new_file}\n"
  860. f"Example YOLO command with this new custom cfg:\n yolo cfg='{new_file}' imgsz=320 batch=8"
  861. )
  862. if __name__ == "__main__":
  863. # Example: entrypoint(debug='yolo predict model=yolo11n.pt')
  864. entrypoint(debug="")