ultralytics 8.1.29__py3-none-any.whl → 8.3.62__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. tests/__init__.py +22 -0
  2. tests/conftest.py +83 -0
  3. tests/test_cli.py +122 -0
  4. tests/test_cuda.py +155 -0
  5. tests/test_engine.py +131 -0
  6. tests/test_exports.py +216 -0
  7. tests/test_integrations.py +150 -0
  8. tests/test_python.py +615 -0
  9. tests/test_solutions.py +94 -0
  10. ultralytics/__init__.py +11 -8
  11. ultralytics/cfg/__init__.py +569 -131
  12. ultralytics/cfg/datasets/Argoverse.yaml +2 -1
  13. ultralytics/cfg/datasets/DOTAv1.5.yaml +3 -2
  14. ultralytics/cfg/datasets/DOTAv1.yaml +3 -2
  15. ultralytics/cfg/datasets/GlobalWheat2020.yaml +3 -2
  16. ultralytics/cfg/datasets/ImageNet.yaml +2 -1
  17. ultralytics/cfg/datasets/Objects365.yaml +5 -4
  18. ultralytics/cfg/datasets/SKU-110K.yaml +2 -1
  19. ultralytics/cfg/datasets/VOC.yaml +3 -2
  20. ultralytics/cfg/datasets/VisDrone.yaml +6 -5
  21. ultralytics/cfg/datasets/african-wildlife.yaml +25 -0
  22. ultralytics/cfg/datasets/brain-tumor.yaml +23 -0
  23. ultralytics/cfg/datasets/carparts-seg.yaml +3 -2
  24. ultralytics/cfg/datasets/coco-pose.yaml +7 -6
  25. ultralytics/cfg/datasets/coco.yaml +3 -2
  26. ultralytics/cfg/datasets/coco128-seg.yaml +4 -3
  27. ultralytics/cfg/datasets/coco128.yaml +4 -3
  28. ultralytics/cfg/datasets/coco8-pose.yaml +3 -2
  29. ultralytics/cfg/datasets/coco8-seg.yaml +3 -2
  30. ultralytics/cfg/datasets/coco8.yaml +3 -2
  31. ultralytics/cfg/datasets/crack-seg.yaml +3 -2
  32. ultralytics/cfg/datasets/dog-pose.yaml +24 -0
  33. ultralytics/cfg/datasets/dota8.yaml +3 -2
  34. ultralytics/cfg/datasets/hand-keypoints.yaml +26 -0
  35. ultralytics/cfg/datasets/lvis.yaml +1236 -0
  36. ultralytics/cfg/datasets/medical-pills.yaml +22 -0
  37. ultralytics/cfg/datasets/open-images-v7.yaml +2 -1
  38. ultralytics/cfg/datasets/package-seg.yaml +5 -4
  39. ultralytics/cfg/datasets/signature.yaml +21 -0
  40. ultralytics/cfg/datasets/tiger-pose.yaml +3 -2
  41. ultralytics/cfg/datasets/xView.yaml +2 -1
  42. ultralytics/cfg/default.yaml +14 -11
  43. ultralytics/cfg/models/11/yolo11-cls-resnet18.yaml +24 -0
  44. ultralytics/cfg/models/11/yolo11-cls.yaml +33 -0
  45. ultralytics/cfg/models/11/yolo11-obb.yaml +50 -0
  46. ultralytics/cfg/models/11/yolo11-pose.yaml +51 -0
  47. ultralytics/cfg/models/11/yolo11-seg.yaml +50 -0
  48. ultralytics/cfg/models/11/yolo11.yaml +50 -0
  49. ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +5 -2
  50. ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +5 -2
  51. ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +5 -2
  52. ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +5 -2
  53. ultralytics/cfg/models/v10/yolov10b.yaml +45 -0
  54. ultralytics/cfg/models/v10/yolov10l.yaml +45 -0
  55. ultralytics/cfg/models/v10/yolov10m.yaml +45 -0
  56. ultralytics/cfg/models/v10/yolov10n.yaml +45 -0
  57. ultralytics/cfg/models/v10/yolov10s.yaml +45 -0
  58. ultralytics/cfg/models/v10/yolov10x.yaml +45 -0
  59. ultralytics/cfg/models/v3/yolov3-spp.yaml +5 -2
  60. ultralytics/cfg/models/v3/yolov3-tiny.yaml +5 -2
  61. ultralytics/cfg/models/v3/yolov3.yaml +5 -2
  62. ultralytics/cfg/models/v5/yolov5-p6.yaml +5 -2
  63. ultralytics/cfg/models/v5/yolov5.yaml +5 -2
  64. ultralytics/cfg/models/v6/yolov6.yaml +5 -2
  65. ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +5 -2
  66. ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +5 -2
  67. ultralytics/cfg/models/v8/yolov8-cls.yaml +5 -2
  68. ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +6 -2
  69. ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +6 -2
  70. ultralytics/cfg/models/v8/yolov8-ghost.yaml +5 -2
  71. ultralytics/cfg/models/v8/yolov8-obb.yaml +5 -2
  72. ultralytics/cfg/models/v8/yolov8-p2.yaml +5 -2
  73. ultralytics/cfg/models/v8/yolov8-p6.yaml +10 -7
  74. ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +5 -2
  75. ultralytics/cfg/models/v8/yolov8-pose.yaml +5 -2
  76. ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +5 -2
  77. ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +5 -2
  78. ultralytics/cfg/models/v8/yolov8-seg.yaml +5 -2
  79. ultralytics/cfg/models/v8/yolov8-world.yaml +5 -2
  80. ultralytics/cfg/models/v8/yolov8-worldv2.yaml +5 -2
  81. ultralytics/cfg/models/v8/yolov8.yaml +5 -2
  82. ultralytics/cfg/models/v9/yolov9c-seg.yaml +41 -0
  83. ultralytics/cfg/models/v9/yolov9c.yaml +30 -25
  84. ultralytics/cfg/models/v9/yolov9e-seg.yaml +64 -0
  85. ultralytics/cfg/models/v9/yolov9e.yaml +46 -42
  86. ultralytics/cfg/models/v9/yolov9m.yaml +41 -0
  87. ultralytics/cfg/models/v9/yolov9s.yaml +41 -0
  88. ultralytics/cfg/models/v9/yolov9t.yaml +41 -0
  89. ultralytics/cfg/solutions/default.yaml +24 -0
  90. ultralytics/cfg/trackers/botsort.yaml +8 -5
  91. ultralytics/cfg/trackers/bytetrack.yaml +8 -5
  92. ultralytics/data/__init__.py +14 -3
  93. ultralytics/data/annotator.py +37 -15
  94. ultralytics/data/augment.py +1783 -289
  95. ultralytics/data/base.py +62 -27
  96. ultralytics/data/build.py +36 -8
  97. ultralytics/data/converter.py +196 -36
  98. ultralytics/data/dataset.py +233 -94
  99. ultralytics/data/loaders.py +199 -96
  100. ultralytics/data/split_dota.py +39 -29
  101. ultralytics/data/utils.py +110 -40
  102. ultralytics/engine/__init__.py +1 -1
  103. ultralytics/engine/exporter.py +569 -242
  104. ultralytics/engine/model.py +604 -252
  105. ultralytics/engine/predictor.py +22 -11
  106. ultralytics/engine/results.py +1228 -218
  107. ultralytics/engine/trainer.py +190 -129
  108. ultralytics/engine/tuner.py +18 -18
  109. ultralytics/engine/validator.py +18 -15
  110. ultralytics/hub/__init__.py +31 -13
  111. ultralytics/hub/auth.py +11 -7
  112. ultralytics/hub/google/__init__.py +159 -0
  113. ultralytics/hub/session.py +128 -94
  114. ultralytics/hub/utils.py +20 -21
  115. ultralytics/models/__init__.py +4 -2
  116. ultralytics/models/fastsam/__init__.py +2 -3
  117. ultralytics/models/fastsam/model.py +26 -4
  118. ultralytics/models/fastsam/predict.py +127 -63
  119. ultralytics/models/fastsam/utils.py +1 -44
  120. ultralytics/models/fastsam/val.py +1 -1
  121. ultralytics/models/nas/__init__.py +1 -1
  122. ultralytics/models/nas/model.py +21 -10
  123. ultralytics/models/nas/predict.py +3 -6
  124. ultralytics/models/nas/val.py +4 -4
  125. ultralytics/models/rtdetr/__init__.py +1 -1
  126. ultralytics/models/rtdetr/model.py +1 -1
  127. ultralytics/models/rtdetr/predict.py +6 -8
  128. ultralytics/models/rtdetr/train.py +6 -2
  129. ultralytics/models/rtdetr/val.py +3 -3
  130. ultralytics/models/sam/__init__.py +3 -3
  131. ultralytics/models/sam/amg.py +29 -23
  132. ultralytics/models/sam/build.py +211 -13
  133. ultralytics/models/sam/model.py +91 -30
  134. ultralytics/models/sam/modules/__init__.py +1 -1
  135. ultralytics/models/sam/modules/blocks.py +1129 -0
  136. ultralytics/models/sam/modules/decoders.py +381 -53
  137. ultralytics/models/sam/modules/encoders.py +515 -324
  138. ultralytics/models/sam/modules/memory_attention.py +237 -0
  139. ultralytics/models/sam/modules/sam.py +969 -21
  140. ultralytics/models/sam/modules/tiny_encoder.py +425 -154
  141. ultralytics/models/sam/modules/transformer.py +159 -60
  142. ultralytics/models/sam/modules/utils.py +293 -0
  143. ultralytics/models/sam/predict.py +1263 -132
  144. ultralytics/models/utils/__init__.py +1 -1
  145. ultralytics/models/utils/loss.py +36 -24
  146. ultralytics/models/utils/ops.py +3 -7
  147. ultralytics/models/yolo/__init__.py +3 -3
  148. ultralytics/models/yolo/classify/__init__.py +1 -1
  149. ultralytics/models/yolo/classify/predict.py +7 -8
  150. ultralytics/models/yolo/classify/train.py +17 -22
  151. ultralytics/models/yolo/classify/val.py +8 -4
  152. ultralytics/models/yolo/detect/__init__.py +1 -1
  153. ultralytics/models/yolo/detect/predict.py +3 -5
  154. ultralytics/models/yolo/detect/train.py +11 -4
  155. ultralytics/models/yolo/detect/val.py +90 -52
  156. ultralytics/models/yolo/model.py +14 -9
  157. ultralytics/models/yolo/obb/__init__.py +1 -1
  158. ultralytics/models/yolo/obb/predict.py +2 -2
  159. ultralytics/models/yolo/obb/train.py +5 -3
  160. ultralytics/models/yolo/obb/val.py +41 -23
  161. ultralytics/models/yolo/pose/__init__.py +1 -1
  162. ultralytics/models/yolo/pose/predict.py +3 -5
  163. ultralytics/models/yolo/pose/train.py +2 -2
  164. ultralytics/models/yolo/pose/val.py +51 -17
  165. ultralytics/models/yolo/segment/__init__.py +1 -1
  166. ultralytics/models/yolo/segment/predict.py +3 -5
  167. ultralytics/models/yolo/segment/train.py +2 -2
  168. ultralytics/models/yolo/segment/val.py +60 -19
  169. ultralytics/models/yolo/world/__init__.py +5 -0
  170. ultralytics/models/yolo/world/train.py +92 -0
  171. ultralytics/models/yolo/world/train_world.py +109 -0
  172. ultralytics/nn/__init__.py +1 -1
  173. ultralytics/nn/autobackend.py +228 -93
  174. ultralytics/nn/modules/__init__.py +39 -14
  175. ultralytics/nn/modules/activation.py +21 -0
  176. ultralytics/nn/modules/block.py +526 -66
  177. ultralytics/nn/modules/conv.py +24 -7
  178. ultralytics/nn/modules/head.py +177 -34
  179. ultralytics/nn/modules/transformer.py +6 -5
  180. ultralytics/nn/modules/utils.py +1 -2
  181. ultralytics/nn/tasks.py +225 -77
  182. ultralytics/solutions/__init__.py +30 -1
  183. ultralytics/solutions/ai_gym.py +96 -143
  184. ultralytics/solutions/analytics.py +247 -0
  185. ultralytics/solutions/distance_calculation.py +78 -135
  186. ultralytics/solutions/heatmap.py +93 -247
  187. ultralytics/solutions/object_counter.py +184 -259
  188. ultralytics/solutions/parking_management.py +246 -0
  189. ultralytics/solutions/queue_management.py +112 -0
  190. ultralytics/solutions/region_counter.py +116 -0
  191. ultralytics/solutions/security_alarm.py +144 -0
  192. ultralytics/solutions/solutions.py +178 -0
  193. ultralytics/solutions/speed_estimation.py +86 -174
  194. ultralytics/solutions/streamlit_inference.py +190 -0
  195. ultralytics/solutions/trackzone.py +68 -0
  196. ultralytics/trackers/__init__.py +1 -1
  197. ultralytics/trackers/basetrack.py +32 -13
  198. ultralytics/trackers/bot_sort.py +61 -28
  199. ultralytics/trackers/byte_tracker.py +83 -51
  200. ultralytics/trackers/track.py +21 -6
  201. ultralytics/trackers/utils/__init__.py +1 -1
  202. ultralytics/trackers/utils/gmc.py +62 -48
  203. ultralytics/trackers/utils/kalman_filter.py +166 -35
  204. ultralytics/trackers/utils/matching.py +40 -21
  205. ultralytics/utils/__init__.py +511 -239
  206. ultralytics/utils/autobatch.py +40 -22
  207. ultralytics/utils/benchmarks.py +266 -85
  208. ultralytics/utils/callbacks/__init__.py +1 -1
  209. ultralytics/utils/callbacks/base.py +1 -3
  210. ultralytics/utils/callbacks/clearml.py +7 -6
  211. ultralytics/utils/callbacks/comet.py +39 -17
  212. ultralytics/utils/callbacks/dvc.py +1 -1
  213. ultralytics/utils/callbacks/hub.py +16 -16
  214. ultralytics/utils/callbacks/mlflow.py +28 -24
  215. ultralytics/utils/callbacks/neptune.py +6 -2
  216. ultralytics/utils/callbacks/raytune.py +3 -4
  217. ultralytics/utils/callbacks/tensorboard.py +18 -18
  218. ultralytics/utils/callbacks/wb.py +27 -20
  219. ultralytics/utils/checks.py +160 -100
  220. ultralytics/utils/dist.py +2 -1
  221. ultralytics/utils/downloads.py +40 -34
  222. ultralytics/utils/errors.py +1 -1
  223. ultralytics/utils/files.py +72 -38
  224. ultralytics/utils/instance.py +41 -19
  225. ultralytics/utils/loss.py +83 -55
  226. ultralytics/utils/metrics.py +61 -56
  227. ultralytics/utils/ops.py +94 -89
  228. ultralytics/utils/patches.py +30 -14
  229. ultralytics/utils/plotting.py +600 -269
  230. ultralytics/utils/tal.py +67 -26
  231. ultralytics/utils/torch_utils.py +302 -102
  232. ultralytics/utils/triton.py +2 -1
  233. ultralytics/utils/tuner.py +21 -12
  234. ultralytics-8.3.62.dist-info/METADATA +370 -0
  235. ultralytics-8.3.62.dist-info/RECORD +241 -0
  236. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/WHEEL +1 -1
  237. ultralytics/data/explorer/__init__.py +0 -5
  238. ultralytics/data/explorer/explorer.py +0 -472
  239. ultralytics/data/explorer/gui/__init__.py +0 -1
  240. ultralytics/data/explorer/gui/dash.py +0 -268
  241. ultralytics/data/explorer/utils.py +0 -166
  242. ultralytics/models/fastsam/prompt.py +0 -357
  243. ultralytics-8.1.29.dist-info/METADATA +0 -373
  244. ultralytics-8.1.29.dist-info/RECORD +0 -197
  245. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/LICENSE +0 -0
  246. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/entry_points.txt +0 -0
  247. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
- # Ultralytics YOLO 🚀, AGPL-3.0 license
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
3
  import contextlib
4
+ import importlib.metadata
4
5
  import inspect
6
+ import json
5
7
  import logging.config
6
8
  import os
7
9
  import platform
@@ -10,11 +12,12 @@ import subprocess
10
12
  import sys
11
13
  import threading
12
14
  import time
13
- import urllib
14
15
  import uuid
15
16
  from pathlib import Path
17
+ from threading import Lock
16
18
  from types import SimpleNamespace
17
19
  from typing import Union
20
+ from urllib.parse import unquote
18
21
 
19
22
  import cv2
20
23
  import matplotlib.pyplot as plt
@@ -30,19 +33,26 @@ RANK = int(os.getenv("RANK", -1))
30
33
  LOCAL_RANK = int(os.getenv("LOCAL_RANK", -1)) # https://pytorch.org/docs/stable/elastic/run.html
31
34
 
32
35
  # Other Constants
36
+ ARGV = sys.argv or ["", ""] # sometimes sys.argv = []
33
37
  FILE = Path(__file__).resolve()
34
38
  ROOT = FILE.parents[1] # YOLO
35
39
  ASSETS = ROOT / "assets" # default images
40
+ ASSETS_URL = "https://github.com/ultralytics/assets/releases/download/v0.0.0" # assets GitHub URL
36
41
  DEFAULT_CFG_PATH = ROOT / "cfg/default.yaml"
37
- NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLOv5 multiprocessing threads
42
+ DEFAULT_SOL_CFG_PATH = ROOT / "cfg/solutions/default.yaml" # Ultralytics solutions yaml path
43
+ NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLO multiprocessing threads
38
44
  AUTOINSTALL = str(os.getenv("YOLO_AUTOINSTALL", True)).lower() == "true" # global auto-install mode
39
45
  VERBOSE = str(os.getenv("YOLO_VERBOSE", True)).lower() == "true" # global verbose mode
40
46
  TQDM_BAR_FORMAT = "{l_bar}{bar:10}{r_bar}" if VERBOSE else None # tqdm bar format
41
47
  LOGGING_NAME = "ultralytics"
42
48
  MACOS, LINUX, WINDOWS = (platform.system() == x for x in ["Darwin", "Linux", "Windows"]) # environment booleans
43
- ARM64 = platform.machine() in ("arm64", "aarch64") # ARM64 booleans
49
+ ARM64 = platform.machine() in {"arm64", "aarch64"} # ARM64 booleans
50
+ PYTHON_VERSION = platform.python_version()
51
+ TORCH_VERSION = torch.__version__
52
+ TORCHVISION_VERSION = importlib.metadata.version("torchvision") # faster than importing torchvision
53
+ IS_VSCODE = os.environ.get("TERM_PROGRAM", False) == "vscode"
44
54
  HELP_MSG = """
45
- Usage examples for running YOLOv8:
55
+ Examples for running Ultralytics:
46
56
 
47
57
  1. Install the ultralytics package:
48
58
 
@@ -53,37 +63,37 @@ HELP_MSG = """
53
63
  from ultralytics import YOLO
54
64
 
55
65
  # Load a model
56
- model = YOLO('yolov8n.yaml') # build a new model from scratch
57
- model = YOLO("yolov8n.pt") # load a pretrained model (recommended for training)
66
+ model = YOLO("yolo11n.yaml") # build a new model from scratch
67
+ model = YOLO("yolo11n.pt") # load a pretrained model (recommended for training)
58
68
 
59
69
  # Use the model
60
- results = model.train(data="coco128.yaml", epochs=3) # train the model
70
+ results = model.train(data="coco8.yaml", epochs=3) # train the model
61
71
  results = model.val() # evaluate model performance on the validation set
62
- results = model('https://ultralytics.com/images/bus.jpg') # predict on an image
63
- success = model.export(format='onnx') # export the model to ONNX format
72
+ results = model("https://ultralytics.com/images/bus.jpg") # predict on an image
73
+ success = model.export(format="onnx") # export the model to ONNX format
64
74
 
65
75
  3. Use the command line interface (CLI):
66
76
 
67
- YOLOv8 'yolo' CLI commands use the following syntax:
77
+ Ultralytics 'yolo' CLI commands use the following syntax:
68
78
 
69
79
  yolo TASK MODE ARGS
70
80
 
71
- Where TASK (optional) is one of [detect, segment, classify]
72
- MODE (required) is one of [train, val, predict, export]
73
- ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults.
74
- See all ARGS at https://docs.ultralytics.com/usage/cfg or with 'yolo cfg'
81
+ Where TASK (optional) is one of [detect, segment, classify, pose, obb]
82
+ MODE (required) is one of [train, val, predict, export, track, benchmark]
83
+ ARGS (optional) are any number of custom "arg=value" pairs like "imgsz=320" that override defaults.
84
+ See all ARGS at https://docs.ultralytics.com/usage/cfg or with "yolo cfg"
75
85
 
76
86
  - Train a detection model for 10 epochs with an initial learning_rate of 0.01
77
- yolo detect train data=coco128.yaml model=yolov8n.pt epochs=10 lr0=0.01
87
+ yolo detect train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01
78
88
 
79
89
  - Predict a YouTube video using a pretrained segmentation model at image size 320:
80
- yolo segment predict model=yolov8n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320
90
+ yolo segment predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320
81
91
 
82
92
  - Val a pretrained detection model at batch-size 1 and image size 640:
83
- yolo detect val model=yolov8n.pt data=coco128.yaml batch=1 imgsz=640
93
+ yolo detect val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640
84
94
 
85
- - Export a YOLOv8n classification model to ONNX format at image size 224 by 128 (no TASK required)
86
- yolo export model=yolov8n-cls.pt format=onnx imgsz=224,128
95
+ - Export a YOLO11n classification model to ONNX format at image size 224 by 128 (no TASK required)
96
+ yolo export model=yolo11n-cls.pt format=onnx imgsz=224,128
87
97
 
88
98
  - Run special commands:
89
99
  yolo help
@@ -98,29 +108,59 @@ HELP_MSG = """
98
108
  GitHub: https://github.com/ultralytics/ultralytics
99
109
  """
100
110
 
101
- # Settings
111
+ # Settings and Environment Variables
102
112
  torch.set_printoptions(linewidth=320, precision=4, profile="default")
103
113
  np.set_printoptions(linewidth=320, formatter={"float_kind": "{:11.5g}".format}) # format short g, %precision=5
104
114
  cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)
105
115
  os.environ["NUMEXPR_MAX_THREADS"] = str(NUM_THREADS) # NumExpr max threads
106
- os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" # for deterministic training
107
- os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" # suppress verbose TF compiler warnings in Colab
116
+ os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" # for deterministic training to avoid CUDA warning
117
+ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" # suppress verbose TF compiler warnings in Colab
118
+ os.environ["TORCH_CPP_LOG_LEVEL"] = "ERROR" # suppress "NNPACK.cpp could not initialize NNPACK" warnings
119
+ os.environ["KINETO_LOG_LEVEL"] = "5" # suppress verbose PyTorch profiler output when computing FLOPs
108
120
 
109
121
 
110
122
  class TQDM(tqdm_original):
111
123
  """
112
- Custom Ultralytics tqdm class with different default arguments.
124
+ A custom TQDM progress bar class that extends the original tqdm functionality.
113
125
 
114
- Args:
115
- *args (list): Positional arguments passed to original tqdm.
116
- **kwargs (any): Keyword arguments, with custom defaults applied.
126
+ This class modifies the behavior of the original tqdm progress bar based on global settings and provides
127
+ additional customization options.
128
+
129
+ Attributes:
130
+ disable (bool): Whether to disable the progress bar. Determined by the global VERBOSE setting and
131
+ any passed 'disable' argument.
132
+ bar_format (str): The format string for the progress bar. Uses the global TQDM_BAR_FORMAT if not
133
+ explicitly set.
134
+
135
+ Methods:
136
+ __init__: Initializes the TQDM object with custom settings.
137
+
138
+ Examples:
139
+ >>> from ultralytics.utils import TQDM
140
+ >>> for i in TQDM(range(100)):
141
+ ... # Your processing code here
142
+ ... pass
117
143
  """
118
144
 
119
145
  def __init__(self, *args, **kwargs):
120
146
  """
121
- Initialize custom Ultralytics tqdm class with different default arguments.
147
+ Initializes a custom TQDM progress bar.
148
+
149
+ This class extends the original tqdm class to provide customized behavior for Ultralytics projects.
122
150
 
123
- Note these can still be overridden when calling TQDM.
151
+ Args:
152
+ *args (Any): Variable length argument list to be passed to the original tqdm constructor.
153
+ **kwargs (Any): Arbitrary keyword arguments to be passed to the original tqdm constructor.
154
+
155
+ Notes:
156
+ - The progress bar is disabled if VERBOSE is False or if 'disable' is explicitly set to True in kwargs.
157
+ - The default bar format is set to TQDM_BAR_FORMAT unless overridden in kwargs.
158
+
159
+ Examples:
160
+ >>> from ultralytics.utils import TQDM
161
+ >>> for i in TQDM(range(100)):
162
+ ... # Your code here
163
+ ... pass
124
164
  """
125
165
  kwargs["disable"] = not VERBOSE or kwargs.get("disable", False) # logical 'and' with default value if passed
126
166
  kwargs.setdefault("bar_format", TQDM_BAR_FORMAT) # override default value if passed
@@ -128,8 +168,33 @@ class TQDM(tqdm_original):
128
168
 
129
169
 
130
170
  class SimpleClass:
131
- """Ultralytics SimpleClass is a base class providing helpful string representation, error reporting, and attribute
132
- access methods for easier debugging and usage.
171
+ """
172
+ A simple base class for creating objects with string representations of their attributes.
173
+
174
+ This class provides a foundation for creating objects that can be easily printed or represented as strings,
175
+ showing all their non-callable attributes. It's useful for debugging and introspection of object states.
176
+
177
+ Methods:
178
+ __str__: Returns a human-readable string representation of the object.
179
+ __repr__: Returns a machine-readable string representation of the object.
180
+ __getattr__: Provides a custom attribute access error message with helpful information.
181
+
182
+ Examples:
183
+ >>> class MyClass(SimpleClass):
184
+ ... def __init__(self):
185
+ ... self.x = 10
186
+ ... self.y = "hello"
187
+ >>> obj = MyClass()
188
+ >>> print(obj)
189
+ __main__.MyClass object with attributes:
190
+
191
+ x: 10
192
+ y: 'hello'
193
+
194
+ Notes:
195
+ - This class is designed to be subclassed. It provides a convenient way to inspect object attributes.
196
+ - The string representation includes the module and class name of the object.
197
+ - Callable attributes and attributes starting with an underscore are excluded from the string representation.
133
198
  """
134
199
 
135
200
  def __str__(self):
@@ -157,8 +222,38 @@ class SimpleClass:
157
222
 
158
223
 
159
224
  class IterableSimpleNamespace(SimpleNamespace):
160
- """Ultralytics IterableSimpleNamespace is an extension class of SimpleNamespace that adds iterable functionality and
161
- enables usage with dict() and for loops.
225
+ """
226
+ An iterable SimpleNamespace class that provides enhanced functionality for attribute access and iteration.
227
+
228
+ This class extends the SimpleNamespace class with additional methods for iteration, string representation,
229
+ and attribute access. It is designed to be used as a convenient container for storing and accessing
230
+ configuration parameters.
231
+
232
+ Methods:
233
+ __iter__: Returns an iterator of key-value pairs from the namespace's attributes.
234
+ __str__: Returns a human-readable string representation of the object.
235
+ __getattr__: Provides a custom attribute access error message with helpful information.
236
+ get: Retrieves the value of a specified key, or a default value if the key doesn't exist.
237
+
238
+ Examples:
239
+ >>> cfg = IterableSimpleNamespace(a=1, b=2, c=3)
240
+ >>> for k, v in cfg:
241
+ ... print(f"{k}: {v}")
242
+ a: 1
243
+ b: 2
244
+ c: 3
245
+ >>> print(cfg)
246
+ a=1
247
+ b=2
248
+ c=3
249
+ >>> cfg.get("b")
250
+ 2
251
+ >>> cfg.get("d", "default")
252
+ 'default'
253
+
254
+ Notes:
255
+ This class is particularly useful for storing configuration parameters in a more accessible
256
+ and iterable format compared to a standard dictionary.
162
257
  """
163
258
 
164
259
  def __iter__(self):
@@ -202,7 +297,6 @@ def plt_settings(rcparams=None, backend="Agg"):
202
297
  (Callable): Decorated function with temporarily set rc parameters and backend. This decorator can be
203
298
  applied to any function that needs to have specific matplotlib rc parameters and backend for its execution.
204
299
  """
205
-
206
300
  if rcparams is None:
207
301
  rcparams = {"font.size": 11}
208
302
 
@@ -212,16 +306,19 @@ def plt_settings(rcparams=None, backend="Agg"):
212
306
  def wrapper(*args, **kwargs):
213
307
  """Sets rc parameters and backend, calls the original function, and restores the settings."""
214
308
  original_backend = plt.get_backend()
215
- if backend.lower() != original_backend.lower():
309
+ switch = backend.lower() != original_backend.lower()
310
+ if switch:
216
311
  plt.close("all") # auto-close()ing of figures upon backend switching is deprecated since 3.8
217
312
  plt.switch_backend(backend)
218
313
 
219
- with plt.rc_context(rcparams):
220
- result = func(*args, **kwargs)
221
-
222
- if backend != original_backend:
223
- plt.close("all")
224
- plt.switch_backend(original_backend)
314
+ # Plot with backend and always revert to original backend
315
+ try:
316
+ with plt.rc_context(rcparams):
317
+ result = func(*args, **kwargs)
318
+ finally:
319
+ if switch:
320
+ plt.close("all")
321
+ plt.switch_backend(original_backend)
225
322
  return result
226
323
 
227
324
  return wrapper
@@ -229,37 +326,61 @@ def plt_settings(rcparams=None, backend="Agg"):
229
326
  return decorator
230
327
 
231
328
 
232
- def set_logging(name=LOGGING_NAME, verbose=True):
233
- """Sets up logging for the given name with UTF-8 encoding support."""
329
+ def set_logging(name="LOGGING_NAME", verbose=True):
330
+ """
331
+ Sets up logging with UTF-8 encoding and configurable verbosity.
332
+
333
+ This function configures logging for the Ultralytics library, setting the appropriate logging level and
334
+ formatter based on the verbosity flag and the current process rank. It handles special cases for Windows
335
+ environments where UTF-8 encoding might not be the default.
336
+
337
+ Args:
338
+ name (str): Name of the logger. Defaults to "LOGGING_NAME".
339
+ verbose (bool): Flag to set logging level to INFO if True, ERROR otherwise. Defaults to True.
340
+
341
+ Examples:
342
+ >>> set_logging(name="ultralytics", verbose=True)
343
+ >>> logger = logging.getLogger("ultralytics")
344
+ >>> logger.info("This is an info message")
345
+
346
+ Notes:
347
+ - On Windows, this function attempts to reconfigure stdout to use UTF-8 encoding if possible.
348
+ - If reconfiguration is not possible, it falls back to a custom formatter that handles non-UTF-8 environments.
349
+ - The function sets up a StreamHandler with the appropriate formatter and level.
350
+ - The logger's propagate flag is set to False to prevent duplicate logging in parent loggers.
351
+ """
234
352
  level = logging.INFO if verbose and RANK in {-1, 0} else logging.ERROR # rank in world for Multi-GPU trainings
235
353
 
236
- # Configure the console (stdout) encoding to UTF-8
354
+ # Configure the console (stdout) encoding to UTF-8, with checks for compatibility
237
355
  formatter = logging.Formatter("%(message)s") # Default formatter
238
- if WINDOWS and sys.stdout.encoding != "utf-8":
356
+ if WINDOWS and hasattr(sys.stdout, "encoding") and sys.stdout.encoding != "utf-8":
357
+
358
+ class CustomFormatter(logging.Formatter):
359
+ def format(self, record):
360
+ """Sets up logging with UTF-8 encoding and configurable verbosity."""
361
+ return emojis(super().format(record))
362
+
239
363
  try:
364
+ # Attempt to reconfigure stdout to use UTF-8 encoding if possible
240
365
  if hasattr(sys.stdout, "reconfigure"):
241
366
  sys.stdout.reconfigure(encoding="utf-8")
367
+ # For environments where reconfigure is not available, wrap stdout in a TextIOWrapper
242
368
  elif hasattr(sys.stdout, "buffer"):
243
369
  import io
244
370
 
245
371
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
246
372
  else:
247
- sys.stdout.encoding = "utf-8"
373
+ formatter = CustomFormatter("%(message)s")
248
374
  except Exception as e:
249
375
  print(f"Creating custom formatter for non UTF-8 environments due to {e}")
376
+ formatter = CustomFormatter("%(message)s")
250
377
 
251
- class CustomFormatter(logging.Formatter):
252
- def format(self, record):
253
- """Sets up logging with UTF-8 encoding and configurable verbosity."""
254
- return emojis(super().format(record))
255
-
256
- formatter = CustomFormatter("%(message)s") # Use CustomFormatter to eliminate UTF-8 output as last recourse
257
-
258
- # Create and configure the StreamHandler
378
+ # Create and configure the StreamHandler with the appropriate formatter and level
259
379
  stream_handler = logging.StreamHandler(sys.stdout)
260
380
  stream_handler.setFormatter(formatter)
261
381
  stream_handler.setLevel(level)
262
382
 
383
+ # Set up the logger
263
384
  logger = logging.getLogger(name)
264
385
  logger.setLevel(level)
265
386
  logger.addHandler(stream_handler)
@@ -294,7 +415,6 @@ class ThreadingLocked:
294
415
  @ThreadingLocked()
295
416
  def my_function():
296
417
  # Your code here
297
- pass
298
418
  ```
299
419
  """
300
420
 
@@ -358,7 +478,7 @@ def yaml_load(file="data.yaml", append_filename=False):
358
478
  Returns:
359
479
  (dict): YAML data and file name.
360
480
  """
361
- assert Path(file).suffix in (".yaml", ".yml"), f"Attempting to load non-YAML file {file} with yaml_load()"
481
+ assert Path(file).suffix in {".yaml", ".yml"}, f"Attempting to load non-YAML file {file} with yaml_load()"
362
482
  with open(file, errors="ignore", encoding="utf-8") as f:
363
483
  s = f.read() # string
364
484
 
@@ -384,12 +504,13 @@ def yaml_print(yaml_file: Union[str, Path, dict]) -> None:
384
504
  (None)
385
505
  """
386
506
  yaml_dict = yaml_load(yaml_file) if isinstance(yaml_file, (str, Path)) else yaml_file
387
- dump = yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True)
507
+ dump = yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True, width=float("inf"))
388
508
  LOGGER.info(f"Printing '{colorstr('bold', 'black', yaml_file)}'\n\n{dump}")
389
509
 
390
510
 
391
511
  # Default configuration
392
512
  DEFAULT_CFG_DICT = yaml_load(DEFAULT_CFG_PATH)
513
+ DEFAULT_SOL_DICT = yaml_load(DEFAULT_SOL_CFG_PATH) # Ultralytics solutions configuration
393
514
  for k, v in DEFAULT_CFG_DICT.items():
394
515
  if isinstance(v, str) and v.lower() == "none":
395
516
  DEFAULT_CFG_DICT[k] = None
@@ -397,6 +518,17 @@ DEFAULT_CFG_KEYS = DEFAULT_CFG_DICT.keys()
397
518
  DEFAULT_CFG = IterableSimpleNamespace(**DEFAULT_CFG_DICT)
398
519
 
399
520
 
521
+ def read_device_model() -> str:
522
+ """
523
+ Reads the device model information from the system and caches it for quick access. Used by is_jetson() and
524
+ is_raspberrypi().
525
+
526
+ Returns:
527
+ (str): Kernel release information.
528
+ """
529
+ return platform.release().lower()
530
+
531
+
400
532
  def is_ubuntu() -> bool:
401
533
  """
402
534
  Check if the OS is Ubuntu.
@@ -404,10 +536,11 @@ def is_ubuntu() -> bool:
404
536
  Returns:
405
537
  (bool): True if OS is Ubuntu, False otherwise.
406
538
  """
407
- with contextlib.suppress(FileNotFoundError):
539
+ try:
408
540
  with open("/etc/os-release") as f:
409
541
  return "ID=ubuntu" in f.read()
410
- return False
542
+ except FileNotFoundError:
543
+ return False
411
544
 
412
545
 
413
546
  def is_colab():
@@ -432,16 +565,16 @@ def is_kaggle():
432
565
 
433
566
  def is_jupyter():
434
567
  """
435
- Check if the current script is running inside a Jupyter Notebook. Verified on Colab, Jupyterlab, Kaggle, Paperspace.
568
+ Check if the current script is running inside a Jupyter Notebook.
436
569
 
437
570
  Returns:
438
571
  (bool): True if running inside a Jupyter Notebook, False otherwise.
439
- """
440
- with contextlib.suppress(Exception):
441
- from IPython import get_ipython
442
572
 
443
- return get_ipython() is not None
444
- return False
573
+ Note:
574
+ - Only works on Colab and Kaggle, other environments like Jupyterlab and Paperspace are not reliably detectable.
575
+ - "get_ipython" in globals() method suffers false positives when IPython package installed manually.
576
+ """
577
+ return IS_COLAB or IS_KAGGLE
445
578
 
446
579
 
447
580
  def is_docker() -> bool:
@@ -451,14 +584,33 @@ def is_docker() -> bool:
451
584
  Returns:
452
585
  (bool): True if the script is running inside a Docker container, False otherwise.
453
586
  """
454
- file = Path("/proc/self/cgroup")
455
- if file.exists():
456
- with open(file) as f:
587
+ try:
588
+ with open("/proc/self/cgroup") as f:
457
589
  return "docker" in f.read()
458
- else:
590
+ except Exception:
459
591
  return False
460
592
 
461
593
 
594
+ def is_raspberrypi() -> bool:
595
+ """
596
+ Determines if the Python environment is running on a Raspberry Pi by checking the device model information.
597
+
598
+ Returns:
599
+ (bool): True if running on a Raspberry Pi, False otherwise.
600
+ """
601
+ return "rpi" in DEVICE_MODEL
602
+
603
+
604
+ def is_jetson() -> bool:
605
+ """
606
+ Determines if the Python environment is running on an NVIDIA Jetson device by checking the device model information.
607
+
608
+ Returns:
609
+ (bool): True if running on an NVIDIA Jetson device, False otherwise.
610
+ """
611
+ return "tegra" in DEVICE_MODEL
612
+
613
+
462
614
  def is_online() -> bool:
463
615
  """
464
616
  Check internet connectivity by attempting to connect to a known online host.
@@ -466,21 +618,15 @@ def is_online() -> bool:
466
618
  Returns:
467
619
  (bool): True if connection is successful, False otherwise.
468
620
  """
469
- import socket
621
+ try:
622
+ assert str(os.getenv("YOLO_OFFLINE", "")).lower() != "true" # check if ENV var YOLO_OFFLINE="True"
623
+ import socket
470
624
 
471
- for host in "1.1.1.1", "8.8.8.8", "223.5.5.5": # Cloudflare, Google, AliDNS:
472
- try:
473
- test_connection = socket.create_connection(address=(host, 53), timeout=2)
474
- except (socket.timeout, socket.gaierror, OSError):
475
- continue
476
- else:
477
- # If the connection was successful, close it to avoid a ResourceWarning
478
- test_connection.close()
625
+ for dns in ("1.1.1.1", "8.8.8.8"): # check Cloudflare and Google DNS
626
+ socket.create_connection(address=(dns, 80), timeout=2.0).close()
479
627
  return True
480
- return False
481
-
482
-
483
- ONLINE = is_online()
628
+ except Exception:
629
+ return False
484
630
 
485
631
 
486
632
  def is_pip_package(filepath: str = __name__) -> bool:
@@ -522,7 +668,7 @@ def is_pytest_running():
522
668
  Returns:
523
669
  (bool): True if pytest is running, False otherwise.
524
670
  """
525
- return ("PYTEST_CURRENT_TEST" in os.environ) or ("pytest" in sys.modules) or ("pytest" in Path(sys.argv[0]).stem)
671
+ return ("PYTEST_CURRENT_TEST" in os.environ) or ("pytest" in sys.modules) or ("pytest" in Path(ARGV[0]).stem)
526
672
 
527
673
 
528
674
  def is_github_action_running() -> bool:
@@ -535,17 +681,6 @@ def is_github_action_running() -> bool:
535
681
  return "GITHUB_ACTIONS" in os.environ and "GITHUB_WORKFLOW" in os.environ and "RUNNER_OS" in os.environ
536
682
 
537
683
 
538
- def is_git_dir():
539
- """
540
- Determines whether the current file is part of a git repository. If the current file is not part of a git
541
- repository, returns None.
542
-
543
- Returns:
544
- (bool): True if current file is part of a git repository.
545
- """
546
- return get_git_dir() is not None
547
-
548
-
549
684
  def get_git_dir():
550
685
  """
551
686
  Determines whether the current file is part of a git repository and if so, returns the repository root directory. If
@@ -559,6 +694,17 @@ def get_git_dir():
559
694
  return d
560
695
 
561
696
 
697
+ def is_git_dir():
698
+ """
699
+ Determines whether the current file is part of a git repository. If the current file is not part of a git
700
+ repository, returns None.
701
+
702
+ Returns:
703
+ (bool): True if current file is part of a git repository.
704
+ """
705
+ return GIT_DIR is not None
706
+
707
+
562
708
  def get_git_origin_url():
563
709
  """
564
710
  Retrieves the origin URL of a git repository.
@@ -566,10 +712,12 @@ def get_git_origin_url():
566
712
  Returns:
567
713
  (str | None): The origin URL of the git repository or None if not git directory.
568
714
  """
569
- if is_git_dir():
570
- with contextlib.suppress(subprocess.CalledProcessError):
715
+ if IS_GIT_DIR:
716
+ try:
571
717
  origin = subprocess.check_output(["git", "config", "--get", "remote.origin.url"])
572
718
  return origin.decode().strip()
719
+ except subprocess.CalledProcessError:
720
+ return None
573
721
 
574
722
 
575
723
  def get_git_branch():
@@ -579,10 +727,12 @@ def get_git_branch():
579
727
  Returns:
580
728
  (str | None): The current git branch name or None if not a git directory.
581
729
  """
582
- if is_git_dir():
583
- with contextlib.suppress(subprocess.CalledProcessError):
730
+ if IS_GIT_DIR:
731
+ try:
584
732
  origin = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
585
733
  return origin.decode().strip()
734
+ except subprocess.CalledProcessError:
735
+ return None
586
736
 
587
737
 
588
738
  def get_default_args(func):
@@ -607,9 +757,11 @@ def get_ubuntu_version():
607
757
  (str): Ubuntu version or None if not an Ubuntu OS.
608
758
  """
609
759
  if is_ubuntu():
610
- with contextlib.suppress(FileNotFoundError, AttributeError):
760
+ try:
611
761
  with open("/etc/os-release") as f:
612
762
  return re.search(r'VERSION_ID="(\d+\.\d+)"', f.read())[1]
763
+ except (FileNotFoundError, AttributeError):
764
+ return None
613
765
 
614
766
 
615
767
  def get_user_config_dir(sub_dir="Ultralytics"):
@@ -645,12 +797,24 @@ def get_user_config_dir(sub_dir="Ultralytics"):
645
797
  return path
646
798
 
647
799
 
800
+ # Define constants (required below)
801
+ DEVICE_MODEL = read_device_model() # is_jetson() and is_raspberrypi() depend on this constant
802
+ ONLINE = is_online()
803
+ IS_COLAB = is_colab()
804
+ IS_KAGGLE = is_kaggle()
805
+ IS_DOCKER = is_docker()
806
+ IS_JETSON = is_jetson()
807
+ IS_JUPYTER = is_jupyter()
808
+ IS_PIP_PACKAGE = is_pip_package()
809
+ IS_RASPBERRYPI = is_raspberrypi()
810
+ GIT_DIR = get_git_dir()
811
+ IS_GIT_DIR = is_git_dir()
648
812
  USER_CONFIG_DIR = Path(os.getenv("YOLO_CONFIG_DIR") or get_user_config_dir()) # Ultralytics settings dir
649
- SETTINGS_YAML = USER_CONFIG_DIR / "settings.yaml"
813
+ SETTINGS_FILE = USER_CONFIG_DIR / "settings.json"
650
814
 
651
815
 
652
816
  def colorstr(*input):
653
- """
817
+ r"""
654
818
  Colors a string based on the provided color and style arguments. Utilizes ANSI escape codes.
655
819
  See https://en.wikipedia.org/wiki/ANSI_escape_code for more details.
656
820
 
@@ -661,7 +825,7 @@ def colorstr(*input):
661
825
  In the second form, 'blue' and 'bold' will be applied by default.
662
826
 
663
827
  Args:
664
- *input (str): A sequence of strings where the first n-1 strings are color and style arguments,
828
+ *input (str | Path): A sequence of strings where the first n-1 strings are color and style arguments,
665
829
  and the last string is the one to be colored.
666
830
 
667
831
  Supported Colors and Styles:
@@ -674,8 +838,8 @@ def colorstr(*input):
674
838
  (str): The input string wrapped with ANSI escape codes for the specified color and style.
675
839
 
676
840
  Examples:
677
- >>> colorstr('blue', 'bold', 'hello world')
678
- >>> '\033[34m\033[1mhello world\033[0m'
841
+ >>> colorstr("blue", "bold", "hello world")
842
+ >>> "\033[34m\033[1mhello world\033[0m"
679
843
  """
680
844
  *args, string = input if len(input) > 1 else ("blue", "bold", input[0]) # color arguments, string
681
845
  colors = {
@@ -713,8 +877,8 @@ def remove_colorstr(input_string):
713
877
  (str): A new string with all ANSI escape codes removed.
714
878
 
715
879
  Examples:
716
- >>> remove_colorstr(colorstr('blue', 'bold', 'hello world'))
717
- >>> 'hello world'
880
+ >>> remove_colorstr(colorstr("blue", "bold", "hello world"))
881
+ >>> "hello world"
718
882
  """
719
883
  ansi_escape = re.compile(r"\x1B\[[0-9;]*[A-Za-z]")
720
884
  return ansi_escape.sub("", input_string)
@@ -728,12 +892,12 @@ class TryExcept(contextlib.ContextDecorator):
728
892
  As a decorator:
729
893
  >>> @TryExcept(msg="Error occurred in func", verbose=True)
730
894
  >>> def func():
731
- >>> # Function logic here
895
+ >>> # Function logic here
732
896
  >>> pass
733
897
 
734
898
  As a context manager:
735
899
  >>> with TryExcept(msg="Error occurred in block", verbose=True):
736
- >>> # Code block here
900
+ >>> # Code block here
737
901
  >>> pass
738
902
  """
739
903
 
@@ -757,20 +921,15 @@ class Retry(contextlib.ContextDecorator):
757
921
  """
758
922
  Retry class for function execution with exponential backoff.
759
923
 
760
- Can be used as a decorator or a context manager to retry a function or block of code on exceptions, up to a
761
- specified number of times with an exponentially increasing delay between retries.
924
+ Can be used as a decorator to retry a function on exceptions, up to a specified number of times with an
925
+ exponentially increasing delay between retries.
762
926
 
763
927
  Examples:
764
928
  Example usage as a decorator:
765
929
  >>> @Retry(times=3, delay=2)
766
930
  >>> def test_func():
767
- >>> # Replace with function logic that may raise exceptions
931
+ >>> # Replace with function logic that may raise exceptions
768
932
  >>> return True
769
-
770
- Example usage as a context manager:
771
- >>> with Retry(times=3, delay=2):
772
- >>> # Replace with code block that may raise exceptions
773
- >>> pass
774
933
  """
775
934
 
776
935
  def __init__(self, times=3, delay=2):
@@ -797,20 +956,6 @@ class Retry(contextlib.ContextDecorator):
797
956
 
798
957
  return wrapped_func
799
958
 
800
- def __enter__(self):
801
- """Enter the runtime context related to this object."""
802
- self._attempts = 0
803
-
804
- def __exit__(self, exc_type, exc_value, traceback):
805
- """Exit the runtime context related to this object with exponential backoff."""
806
- if exc_type is not None:
807
- self._attempts += 1
808
- if self._attempts < self.times:
809
- print(f"Retry {self._attempts}/{self.times} failed: {exc_value}")
810
- time.sleep(self.delay * (2**self._attempts)) # exponential backoff delay
811
- return True # Suppresses the exception and retries
812
- return False # Re-raises the exception if retries are exhausted
813
-
814
959
 
815
960
  def threaded(func):
816
961
  """
@@ -834,7 +979,7 @@ def threaded(func):
834
979
  def set_sentry():
835
980
  """
836
981
  Initialize the Sentry SDK for error tracking and reporting. Only used if sentry_sdk package is installed and
837
- sync=True in settings. Run 'yolo settings' to see and update settings YAML file.
982
+ sync=True in settings. Run 'yolo settings' to see and update settings.
838
983
 
839
984
  Conditions required to send errors (ALL conditions must be met or no errors will be reported):
840
985
  - sentry_sdk package is installed
@@ -846,11 +991,26 @@ def set_sentry():
846
991
  - online environment
847
992
  - CLI used to run package (checked with 'yolo' as the name of the main CLI command)
848
993
 
849
- The function also configures Sentry SDK to ignore KeyboardInterrupt and FileNotFoundError
850
- exceptions and to exclude events with 'out of memory' in their exception message.
994
+ The function also configures Sentry SDK to ignore KeyboardInterrupt and FileNotFoundError exceptions and to exclude
995
+ events with 'out of memory' in their exception message.
851
996
 
852
997
  Additionally, the function sets custom tags and user information for Sentry events.
853
998
  """
999
+ if (
1000
+ not SETTINGS["sync"]
1001
+ or RANK not in {-1, 0}
1002
+ or Path(ARGV[0]).name != "yolo"
1003
+ or TESTS_RUNNING
1004
+ or not ONLINE
1005
+ or not IS_PIP_PACKAGE
1006
+ or IS_GIT_DIR
1007
+ ):
1008
+ return
1009
+ # If sentry_sdk package is not installed then return and do not use Sentry
1010
+ try:
1011
+ import sentry_sdk # noqa
1012
+ except ImportError:
1013
+ return
854
1014
 
855
1015
  def before_send(event, hint):
856
1016
  """
@@ -864,153 +1024,254 @@ def set_sentry():
864
1024
  dict: The modified event or None if the event should not be sent to Sentry.
865
1025
  """
866
1026
  if "exc_info" in hint:
867
- exc_type, exc_value, tb = hint["exc_info"]
868
- if exc_type in (KeyboardInterrupt, FileNotFoundError) or "out of memory" in str(exc_value):
1027
+ exc_type, exc_value, _ = hint["exc_info"]
1028
+ if exc_type in {KeyboardInterrupt, FileNotFoundError} or "out of memory" in str(exc_value):
869
1029
  return None # do not send event
870
1030
 
871
1031
  event["tags"] = {
872
- "sys_argv": sys.argv[0],
873
- "sys_argv_name": Path(sys.argv[0]).name,
874
- "install": "git" if is_git_dir() else "pip" if is_pip_package() else "other",
1032
+ "sys_argv": ARGV[0],
1033
+ "sys_argv_name": Path(ARGV[0]).name,
1034
+ "install": "git" if IS_GIT_DIR else "pip" if IS_PIP_PACKAGE else "other",
875
1035
  "os": ENVIRONMENT,
876
1036
  }
877
1037
  return event
878
1038
 
879
- if (
880
- SETTINGS["sync"]
881
- and RANK in (-1, 0)
882
- and Path(sys.argv[0]).name == "yolo"
883
- and not TESTS_RUNNING
884
- and ONLINE
885
- and is_pip_package()
886
- and not is_git_dir()
887
- ):
888
- # If sentry_sdk package is not installed then return and do not use Sentry
1039
+ sentry_sdk.init(
1040
+ dsn="https://888e5a0778212e1d0314c37d4b9aae5d@o4504521589325824.ingest.us.sentry.io/4504521592406016",
1041
+ debug=False,
1042
+ auto_enabling_integrations=False,
1043
+ traces_sample_rate=1.0,
1044
+ release=__version__,
1045
+ environment="production", # 'dev' or 'production'
1046
+ before_send=before_send,
1047
+ ignore_errors=[KeyboardInterrupt, FileNotFoundError],
1048
+ )
1049
+ sentry_sdk.set_user({"id": SETTINGS["uuid"]}) # SHA-256 anonymized UUID hash
1050
+
1051
+
1052
+ class JSONDict(dict):
1053
+ """
1054
+ A dictionary-like class that provides JSON persistence for its contents.
1055
+
1056
+ This class extends the built-in dictionary to automatically save its contents to a JSON file whenever they are
1057
+ modified. It ensures thread-safe operations using a lock.
1058
+
1059
+ Attributes:
1060
+ file_path (Path): The path to the JSON file used for persistence.
1061
+ lock (threading.Lock): A lock object to ensure thread-safe operations.
1062
+
1063
+ Methods:
1064
+ _load: Loads the data from the JSON file into the dictionary.
1065
+ _save: Saves the current state of the dictionary to the JSON file.
1066
+ __setitem__: Stores a key-value pair and persists it to disk.
1067
+ __delitem__: Removes an item and updates the persistent storage.
1068
+ update: Updates the dictionary and persists changes.
1069
+ clear: Clears all entries and updates the persistent storage.
1070
+
1071
+ Examples:
1072
+ >>> json_dict = JSONDict("data.json")
1073
+ >>> json_dict["key"] = "value"
1074
+ >>> print(json_dict["key"])
1075
+ value
1076
+ >>> del json_dict["key"]
1077
+ >>> json_dict.update({"new_key": "new_value"})
1078
+ >>> json_dict.clear()
1079
+ """
1080
+
1081
+ def __init__(self, file_path: Union[str, Path] = "data.json"):
1082
+ """Initialize a JSONDict object with a specified file path for JSON persistence."""
1083
+ super().__init__()
1084
+ self.file_path = Path(file_path)
1085
+ self.lock = Lock()
1086
+ self._load()
1087
+
1088
+ def _load(self):
1089
+ """Load the data from the JSON file into the dictionary."""
889
1090
  try:
890
- import sentry_sdk # noqa
891
- except ImportError:
892
- return
893
-
894
- sentry_sdk.init(
895
- dsn="https://5ff1556b71594bfea135ff0203a0d290@o4504521589325824.ingest.sentry.io/4504521592406016",
896
- debug=False,
897
- traces_sample_rate=1.0,
898
- release=__version__,
899
- environment="production", # 'dev' or 'production'
900
- before_send=before_send,
901
- ignore_errors=[KeyboardInterrupt, FileNotFoundError],
902
- )
903
- sentry_sdk.set_user({"id": SETTINGS["uuid"]}) # SHA-256 anonymized UUID hash
1091
+ if self.file_path.exists():
1092
+ with open(self.file_path) as f:
1093
+ self.update(json.load(f))
1094
+ except json.JSONDecodeError:
1095
+ print(f"Error decoding JSON from {self.file_path}. Starting with an empty dictionary.")
1096
+ except Exception as e:
1097
+ print(f"Error reading from {self.file_path}: {e}")
904
1098
 
1099
+ def _save(self):
1100
+ """Save the current state of the dictionary to the JSON file."""
1101
+ try:
1102
+ self.file_path.parent.mkdir(parents=True, exist_ok=True)
1103
+ with open(self.file_path, "w") as f:
1104
+ json.dump(dict(self), f, indent=2, default=self._json_default)
1105
+ except Exception as e:
1106
+ print(f"Error writing to {self.file_path}: {e}")
1107
+
1108
+ @staticmethod
1109
+ def _json_default(obj):
1110
+ """Handle JSON serialization of Path objects."""
1111
+ if isinstance(obj, Path):
1112
+ return str(obj)
1113
+ raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
1114
+
1115
+ def __setitem__(self, key, value):
1116
+ """Store a key-value pair and persist to disk."""
1117
+ with self.lock:
1118
+ super().__setitem__(key, value)
1119
+ self._save()
1120
+
1121
+ def __delitem__(self, key):
1122
+ """Remove an item and update the persistent storage."""
1123
+ with self.lock:
1124
+ super().__delitem__(key)
1125
+ self._save()
905
1126
 
906
- class SettingsManager(dict):
1127
+ def __str__(self):
1128
+ """Return a pretty-printed JSON string representation of the dictionary."""
1129
+ contents = json.dumps(dict(self), indent=2, ensure_ascii=False, default=self._json_default)
1130
+ return f'JSONDict("{self.file_path}"):\n{contents}'
1131
+
1132
+ def update(self, *args, **kwargs):
1133
+ """Update the dictionary and persist changes."""
1134
+ with self.lock:
1135
+ super().update(*args, **kwargs)
1136
+ self._save()
1137
+
1138
+ def clear(self):
1139
+ """Clear all entries and update the persistent storage."""
1140
+ with self.lock:
1141
+ super().clear()
1142
+ self._save()
1143
+
1144
+
1145
+ class SettingsManager(JSONDict):
907
1146
  """
908
- Manages Ultralytics settings stored in a YAML file.
1147
+ SettingsManager class for managing and persisting Ultralytics settings.
909
1148
 
910
- Args:
911
- file (str | Path): Path to the Ultralytics settings YAML file. Default is USER_CONFIG_DIR / 'settings.yaml'.
912
- version (str): Settings version. In case of local version mismatch, new default settings will be saved.
1149
+ This class extends JSONDict to provide JSON persistence for settings, ensuring thread-safe operations and default
1150
+ values. It validates settings on initialization and provides methods to update or reset settings.
1151
+
1152
+ Attributes:
1153
+ file (Path): The path to the JSON file used for persistence.
1154
+ version (str): The version of the settings schema.
1155
+ defaults (Dict): A dictionary containing default settings.
1156
+ help_msg (str): A help message for users on how to view and update settings.
1157
+
1158
+ Methods:
1159
+ _validate_settings: Validates the current settings and resets if necessary.
1160
+ update: Updates settings, validating keys and types.
1161
+ reset: Resets the settings to default and saves them.
1162
+
1163
+ Examples:
1164
+ Initialize and update settings:
1165
+ >>> settings = SettingsManager()
1166
+ >>> settings.update(runs_dir="/new/runs/dir")
1167
+ >>> print(settings["runs_dir"])
1168
+ /new/runs/dir
913
1169
  """
914
1170
 
915
- def __init__(self, file=SETTINGS_YAML, version="0.0.4"):
916
- """Initialize the SettingsManager with default settings, load and validate current settings from the YAML
917
- file.
918
- """
919
- import copy
1171
+ def __init__(self, file=SETTINGS_FILE, version="0.0.6"):
1172
+ """Initializes the SettingsManager with default settings and loads user settings."""
920
1173
  import hashlib
921
1174
 
922
- from ultralytics.utils.checks import check_version
923
1175
  from ultralytics.utils.torch_utils import torch_distributed_zero_first
924
1176
 
925
- git_dir = get_git_dir()
926
- root = git_dir or Path()
927
- datasets_root = (root.parent if git_dir and is_dir_writeable(root.parent) else root).resolve()
1177
+ root = GIT_DIR or Path()
1178
+ datasets_root = (root.parent if GIT_DIR and is_dir_writeable(root.parent) else root).resolve()
928
1179
 
929
1180
  self.file = Path(file)
930
1181
  self.version = version
931
1182
  self.defaults = {
932
- "settings_version": version,
933
- "datasets_dir": str(datasets_root / "datasets"),
934
- "weights_dir": str(root / "weights"),
935
- "runs_dir": str(root / "runs"),
936
- "uuid": hashlib.sha256(str(uuid.getnode()).encode()).hexdigest(),
937
- "sync": True,
938
- "api_key": "",
939
- "openai_api_key": "",
940
- "clearml": True, # integrations
941
- "comet": True,
942
- "dvc": True,
943
- "hub": True,
944
- "mlflow": True,
945
- "neptune": True,
946
- "raytune": True,
947
- "tensorboard": True,
948
- "wandb": True,
1183
+ "settings_version": version, # Settings schema version
1184
+ "datasets_dir": str(datasets_root / "datasets"), # Datasets directory
1185
+ "weights_dir": str(root / "weights"), # Model weights directory
1186
+ "runs_dir": str(root / "runs"), # Experiment runs directory
1187
+ "uuid": hashlib.sha256(str(uuid.getnode()).encode()).hexdigest(), # SHA-256 anonymized UUID hash
1188
+ "sync": True, # Enable synchronization
1189
+ "api_key": "", # Ultralytics API Key
1190
+ "openai_api_key": "", # OpenAI API Key
1191
+ "clearml": True, # ClearML integration
1192
+ "comet": True, # Comet integration
1193
+ "dvc": True, # DVC integration
1194
+ "hub": True, # Ultralytics HUB integration
1195
+ "mlflow": True, # MLflow integration
1196
+ "neptune": True, # Neptune integration
1197
+ "raytune": True, # Ray Tune integration
1198
+ "tensorboard": True, # TensorBoard logging
1199
+ "wandb": False, # Weights & Biases logging
1200
+ "vscode_msg": True, # VSCode messaging
949
1201
  }
950
1202
 
951
- super().__init__(copy.deepcopy(self.defaults))
1203
+ self.help_msg = (
1204
+ f"\nView Ultralytics Settings with 'yolo settings' or at '{self.file}'"
1205
+ "\nUpdate Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. "
1206
+ "For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings."
1207
+ )
952
1208
 
953
1209
  with torch_distributed_zero_first(RANK):
954
- if not self.file.exists():
955
- self.save()
956
-
957
- self.load()
958
- correct_keys = self.keys() == self.defaults.keys()
959
- correct_types = all(type(a) is type(b) for a, b in zip(self.values(), self.defaults.values()))
960
- correct_version = check_version(self["settings_version"], self.version)
961
- help_msg = (
962
- f"\nView settings with 'yolo settings' or at '{self.file}'"
963
- "\nUpdate settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. "
964
- "For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings."
965
- )
966
- if not (correct_keys and correct_types and correct_version):
967
- LOGGER.warning(
968
- "WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
969
- f"with your settings or a recent ultralytics package update. {help_msg}"
970
- )
1210
+ super().__init__(self.file)
1211
+
1212
+ if not self.file.exists() or not self: # Check if file doesn't exist or is empty
1213
+ LOGGER.info(f"Creating new Ultralytics Settings v{version} file ✅ {self.help_msg}")
971
1214
  self.reset()
972
1215
 
973
- if self.get("datasets_dir") == self.get("runs_dir"):
974
- LOGGER.warning(
975
- f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
976
- f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
977
- f"Please change one to avoid possible issues during training. {help_msg}"
978
- )
1216
+ self._validate_settings()
979
1217
 
980
- def load(self):
981
- """Loads settings from the YAML file."""
982
- super().update(yaml_load(self.file))
1218
+ def _validate_settings(self):
1219
+ """Validate the current settings and reset if necessary."""
1220
+ correct_keys = set(self.keys()) == set(self.defaults.keys())
1221
+ correct_types = all(isinstance(self.get(k), type(v)) for k, v in self.defaults.items())
1222
+ correct_version = self.get("settings_version", "") == self.version
983
1223
 
984
- def save(self):
985
- """Saves the current settings to the YAML file."""
986
- yaml_save(self.file, dict(self))
1224
+ if not (correct_keys and correct_types and correct_version):
1225
+ LOGGER.warning(
1226
+ "WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
1227
+ f"with your settings or a recent ultralytics package update. {self.help_msg}"
1228
+ )
1229
+ self.reset()
1230
+
1231
+ if self.get("datasets_dir") == self.get("runs_dir"):
1232
+ LOGGER.warning(
1233
+ f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
1234
+ f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
1235
+ f"Please change one to avoid possible issues during training. {self.help_msg}"
1236
+ )
1237
+
1238
+ def __setitem__(self, key, value):
1239
+ """Updates one key: value pair."""
1240
+ self.update({key: value})
987
1241
 
988
1242
  def update(self, *args, **kwargs):
989
- """Updates a setting value in the current settings."""
1243
+ """Updates settings, validating keys and types."""
1244
+ for arg in args:
1245
+ if isinstance(arg, dict):
1246
+ kwargs.update(arg)
1247
+ for k, v in kwargs.items():
1248
+ if k not in self.defaults:
1249
+ raise KeyError(f"No Ultralytics setting '{k}'. {self.help_msg}")
1250
+ t = type(self.defaults[k])
1251
+ if not isinstance(v, t):
1252
+ raise TypeError(
1253
+ f"Ultralytics setting '{k}' must be '{t.__name__}' type, not '{type(v).__name__}'. {self.help_msg}"
1254
+ )
990
1255
  super().update(*args, **kwargs)
991
- self.save()
992
1256
 
993
1257
  def reset(self):
994
1258
  """Resets the settings to default and saves them."""
995
1259
  self.clear()
996
1260
  self.update(self.defaults)
997
- self.save()
998
1261
 
999
1262
 
1000
- def deprecation_warn(arg, new_arg, version=None):
1263
+ def deprecation_warn(arg, new_arg=None):
1001
1264
  """Issue a deprecation warning when a deprecated argument is used, suggesting an updated argument."""
1002
- if not version:
1003
- version = float(__version__[:3]) + 0.2 # deprecate after 2nd major release
1004
- LOGGER.warning(
1005
- f"WARNING ⚠️ '{arg}' is deprecated and will be removed in 'ultralytics {version}' in the future. "
1006
- f"Please use '{new_arg}' instead."
1007
- )
1265
+ msg = f"WARNING ⚠️ '{arg}' is deprecated and will be removed in in the future."
1266
+ if new_arg is not None:
1267
+ msg += f" Use '{new_arg}' instead."
1268
+ LOGGER.warning(msg)
1008
1269
 
1009
1270
 
1010
1271
  def clean_url(url):
1011
1272
  """Strip auth from URL, i.e. https://url.com/file.txt?auth -> https://url.com/file.txt."""
1012
1273
  url = Path(url).as_posix().replace(":/", "://") # Pathlib turns :// -> :/, as_posix() for Windows
1013
- return urllib.parse.unquote(url).split("?")[0] # '%2F' to '/', split https://url.com/file.txt?auth
1274
+ return unquote(url).split("?")[0] # '%2F' to '/', split https://url.com/file.txt?auth
1014
1275
 
1015
1276
 
1016
1277
  def url2file(url):
@@ -1018,31 +1279,42 @@ def url2file(url):
1018
1279
  return Path(clean_url(url)).name
1019
1280
 
1020
1281
 
1282
+ def vscode_msg(ext="ultralytics.ultralytics-snippets") -> str:
1283
+ """Display a message to install Ultralytics-Snippets for VS Code if not already installed."""
1284
+ path = (USER_CONFIG_DIR.parents[2] if WINDOWS else USER_CONFIG_DIR.parents[1]) / ".vscode/extensions"
1285
+ obs_file = path / ".obsolete" # file tracks uninstalled extensions, while source directory remains
1286
+ installed = any(path.glob(f"{ext}*")) and ext not in (obs_file.read_text("utf-8") if obs_file.exists() else "")
1287
+ url = "https://docs.ultralytics.com/integrations/vscode"
1288
+ return "" if installed else f"{colorstr('VS Code:')} view Ultralytics VS Code Extension ⚡ at {url}"
1289
+
1290
+
1021
1291
  # Run below code on utils init ------------------------------------------------------------------------------------
1022
1292
 
1023
1293
  # Check first-install steps
1024
1294
  PREFIX = colorstr("Ultralytics: ")
1025
1295
  SETTINGS = SettingsManager() # initialize settings
1296
+ PERSISTENT_CACHE = JSONDict(USER_CONFIG_DIR / "persistent_cache.json") # initialize persistent cache
1026
1297
  DATASETS_DIR = Path(SETTINGS["datasets_dir"]) # global datasets directory
1027
1298
  WEIGHTS_DIR = Path(SETTINGS["weights_dir"]) # global weights directory
1028
1299
  RUNS_DIR = Path(SETTINGS["runs_dir"]) # global runs directory
1029
1300
  ENVIRONMENT = (
1030
1301
  "Colab"
1031
- if is_colab()
1302
+ if IS_COLAB
1032
1303
  else "Kaggle"
1033
- if is_kaggle()
1304
+ if IS_KAGGLE
1034
1305
  else "Jupyter"
1035
- if is_jupyter()
1306
+ if IS_JUPYTER
1036
1307
  else "Docker"
1037
- if is_docker()
1308
+ if IS_DOCKER
1038
1309
  else platform.system()
1039
1310
  )
1040
1311
  TESTS_RUNNING = is_pytest_running() or is_github_action_running()
1041
1312
  set_sentry()
1042
1313
 
1043
1314
  # Apply monkey patches
1044
- from .patches import imread, imshow, imwrite, torch_save
1315
+ from ultralytics.utils.patches import imread, imshow, imwrite, torch_load, torch_save
1045
1316
 
1317
+ torch.load = torch_load
1046
1318
  torch.save = torch_save
1047
1319
  if WINDOWS:
1048
1320
  # Apply cv2 patches for non-ASCII and non-UTF characters in image paths