dgenerate-ultralytics-headless 8.3.214__py3-none-any.whl → 8.4.7__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 (249) hide show
  1. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/METADATA +64 -74
  2. dgenerate_ultralytics_headless-8.4.7.dist-info/RECORD +311 -0
  3. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/WHEEL +1 -1
  4. tests/__init__.py +7 -9
  5. tests/conftest.py +8 -15
  6. tests/test_cli.py +1 -1
  7. tests/test_cuda.py +13 -10
  8. tests/test_engine.py +9 -9
  9. tests/test_exports.py +65 -13
  10. tests/test_integrations.py +13 -13
  11. tests/test_python.py +125 -69
  12. tests/test_solutions.py +161 -152
  13. ultralytics/__init__.py +1 -1
  14. ultralytics/cfg/__init__.py +86 -92
  15. ultralytics/cfg/datasets/Argoverse.yaml +7 -6
  16. ultralytics/cfg/datasets/DOTAv1.5.yaml +1 -1
  17. ultralytics/cfg/datasets/DOTAv1.yaml +1 -1
  18. ultralytics/cfg/datasets/ImageNet.yaml +1 -1
  19. ultralytics/cfg/datasets/TT100K.yaml +346 -0
  20. ultralytics/cfg/datasets/VOC.yaml +15 -16
  21. ultralytics/cfg/datasets/african-wildlife.yaml +1 -1
  22. ultralytics/cfg/datasets/coco-pose.yaml +21 -0
  23. ultralytics/cfg/datasets/coco12-formats.yaml +101 -0
  24. ultralytics/cfg/datasets/coco128-seg.yaml +1 -1
  25. ultralytics/cfg/datasets/coco8-pose.yaml +21 -0
  26. ultralytics/cfg/datasets/dog-pose.yaml +28 -0
  27. ultralytics/cfg/datasets/dota8-multispectral.yaml +1 -1
  28. ultralytics/cfg/datasets/dota8.yaml +2 -2
  29. ultralytics/cfg/datasets/hand-keypoints.yaml +26 -2
  30. ultralytics/cfg/datasets/kitti.yaml +27 -0
  31. ultralytics/cfg/datasets/lvis.yaml +5 -5
  32. ultralytics/cfg/datasets/open-images-v7.yaml +1 -1
  33. ultralytics/cfg/datasets/tiger-pose.yaml +16 -0
  34. ultralytics/cfg/datasets/xView.yaml +16 -16
  35. ultralytics/cfg/default.yaml +4 -2
  36. ultralytics/cfg/models/11/yolo11-pose.yaml +1 -1
  37. ultralytics/cfg/models/11/yoloe-11-seg.yaml +2 -2
  38. ultralytics/cfg/models/11/yoloe-11.yaml +2 -2
  39. ultralytics/cfg/models/26/yolo26-cls.yaml +33 -0
  40. ultralytics/cfg/models/26/yolo26-obb.yaml +52 -0
  41. ultralytics/cfg/models/26/yolo26-p2.yaml +60 -0
  42. ultralytics/cfg/models/26/yolo26-p6.yaml +62 -0
  43. ultralytics/cfg/models/26/yolo26-pose.yaml +53 -0
  44. ultralytics/cfg/models/26/yolo26-seg.yaml +52 -0
  45. ultralytics/cfg/models/26/yolo26.yaml +52 -0
  46. ultralytics/cfg/models/26/yoloe-26-seg.yaml +53 -0
  47. ultralytics/cfg/models/26/yoloe-26.yaml +53 -0
  48. ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +1 -1
  49. ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +1 -1
  50. ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +1 -1
  51. ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +1 -1
  52. ultralytics/cfg/models/v10/yolov10b.yaml +2 -2
  53. ultralytics/cfg/models/v10/yolov10l.yaml +2 -2
  54. ultralytics/cfg/models/v10/yolov10m.yaml +2 -2
  55. ultralytics/cfg/models/v10/yolov10n.yaml +2 -2
  56. ultralytics/cfg/models/v10/yolov10s.yaml +2 -2
  57. ultralytics/cfg/models/v10/yolov10x.yaml +2 -2
  58. ultralytics/cfg/models/v3/yolov3-tiny.yaml +1 -1
  59. ultralytics/cfg/models/v6/yolov6.yaml +1 -1
  60. ultralytics/cfg/models/v8/yoloe-v8-seg.yaml +9 -6
  61. ultralytics/cfg/models/v8/yoloe-v8.yaml +9 -6
  62. ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +1 -1
  63. ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +1 -1
  64. ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +2 -2
  65. ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +2 -2
  66. ultralytics/cfg/models/v8/yolov8-ghost.yaml +2 -2
  67. ultralytics/cfg/models/v8/yolov8-obb.yaml +1 -1
  68. ultralytics/cfg/models/v8/yolov8-p2.yaml +1 -1
  69. ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +1 -1
  70. ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +1 -1
  71. ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +1 -1
  72. ultralytics/cfg/models/v8/yolov8-world.yaml +1 -1
  73. ultralytics/cfg/models/v8/yolov8-worldv2.yaml +6 -6
  74. ultralytics/cfg/models/v9/yolov9s.yaml +1 -1
  75. ultralytics/data/__init__.py +4 -4
  76. ultralytics/data/annotator.py +5 -6
  77. ultralytics/data/augment.py +300 -475
  78. ultralytics/data/base.py +18 -26
  79. ultralytics/data/build.py +147 -25
  80. ultralytics/data/converter.py +108 -87
  81. ultralytics/data/dataset.py +47 -75
  82. ultralytics/data/loaders.py +42 -49
  83. ultralytics/data/split.py +5 -6
  84. ultralytics/data/split_dota.py +8 -15
  85. ultralytics/data/utils.py +36 -45
  86. ultralytics/engine/exporter.py +351 -263
  87. ultralytics/engine/model.py +186 -225
  88. ultralytics/engine/predictor.py +45 -54
  89. ultralytics/engine/results.py +198 -325
  90. ultralytics/engine/trainer.py +165 -106
  91. ultralytics/engine/tuner.py +41 -43
  92. ultralytics/engine/validator.py +55 -38
  93. ultralytics/hub/__init__.py +16 -19
  94. ultralytics/hub/auth.py +6 -12
  95. ultralytics/hub/google/__init__.py +7 -10
  96. ultralytics/hub/session.py +15 -25
  97. ultralytics/hub/utils.py +5 -8
  98. ultralytics/models/__init__.py +1 -1
  99. ultralytics/models/fastsam/__init__.py +1 -1
  100. ultralytics/models/fastsam/model.py +8 -10
  101. ultralytics/models/fastsam/predict.py +18 -30
  102. ultralytics/models/fastsam/utils.py +1 -2
  103. ultralytics/models/fastsam/val.py +5 -7
  104. ultralytics/models/nas/__init__.py +1 -1
  105. ultralytics/models/nas/model.py +5 -8
  106. ultralytics/models/nas/predict.py +7 -9
  107. ultralytics/models/nas/val.py +1 -2
  108. ultralytics/models/rtdetr/__init__.py +1 -1
  109. ultralytics/models/rtdetr/model.py +5 -8
  110. ultralytics/models/rtdetr/predict.py +15 -19
  111. ultralytics/models/rtdetr/train.py +10 -13
  112. ultralytics/models/rtdetr/val.py +21 -23
  113. ultralytics/models/sam/__init__.py +15 -2
  114. ultralytics/models/sam/amg.py +14 -20
  115. ultralytics/models/sam/build.py +26 -19
  116. ultralytics/models/sam/build_sam3.py +377 -0
  117. ultralytics/models/sam/model.py +29 -32
  118. ultralytics/models/sam/modules/blocks.py +83 -144
  119. ultralytics/models/sam/modules/decoders.py +19 -37
  120. ultralytics/models/sam/modules/encoders.py +44 -101
  121. ultralytics/models/sam/modules/memory_attention.py +16 -30
  122. ultralytics/models/sam/modules/sam.py +200 -73
  123. ultralytics/models/sam/modules/tiny_encoder.py +64 -83
  124. ultralytics/models/sam/modules/transformer.py +18 -28
  125. ultralytics/models/sam/modules/utils.py +174 -50
  126. ultralytics/models/sam/predict.py +2248 -350
  127. ultralytics/models/sam/sam3/__init__.py +3 -0
  128. ultralytics/models/sam/sam3/decoder.py +546 -0
  129. ultralytics/models/sam/sam3/encoder.py +529 -0
  130. ultralytics/models/sam/sam3/geometry_encoders.py +415 -0
  131. ultralytics/models/sam/sam3/maskformer_segmentation.py +286 -0
  132. ultralytics/models/sam/sam3/model_misc.py +199 -0
  133. ultralytics/models/sam/sam3/necks.py +129 -0
  134. ultralytics/models/sam/sam3/sam3_image.py +339 -0
  135. ultralytics/models/sam/sam3/text_encoder_ve.py +307 -0
  136. ultralytics/models/sam/sam3/vitdet.py +547 -0
  137. ultralytics/models/sam/sam3/vl_combiner.py +160 -0
  138. ultralytics/models/utils/loss.py +14 -26
  139. ultralytics/models/utils/ops.py +13 -17
  140. ultralytics/models/yolo/__init__.py +1 -1
  141. ultralytics/models/yolo/classify/predict.py +10 -13
  142. ultralytics/models/yolo/classify/train.py +12 -33
  143. ultralytics/models/yolo/classify/val.py +30 -29
  144. ultralytics/models/yolo/detect/predict.py +9 -12
  145. ultralytics/models/yolo/detect/train.py +17 -23
  146. ultralytics/models/yolo/detect/val.py +77 -59
  147. ultralytics/models/yolo/model.py +43 -60
  148. ultralytics/models/yolo/obb/predict.py +7 -16
  149. ultralytics/models/yolo/obb/train.py +14 -17
  150. ultralytics/models/yolo/obb/val.py +40 -37
  151. ultralytics/models/yolo/pose/__init__.py +1 -1
  152. ultralytics/models/yolo/pose/predict.py +7 -22
  153. ultralytics/models/yolo/pose/train.py +13 -16
  154. ultralytics/models/yolo/pose/val.py +39 -58
  155. ultralytics/models/yolo/segment/predict.py +17 -21
  156. ultralytics/models/yolo/segment/train.py +7 -10
  157. ultralytics/models/yolo/segment/val.py +95 -47
  158. ultralytics/models/yolo/world/train.py +8 -14
  159. ultralytics/models/yolo/world/train_world.py +11 -34
  160. ultralytics/models/yolo/yoloe/__init__.py +7 -7
  161. ultralytics/models/yolo/yoloe/predict.py +16 -23
  162. ultralytics/models/yolo/yoloe/train.py +36 -44
  163. ultralytics/models/yolo/yoloe/train_seg.py +11 -11
  164. ultralytics/models/yolo/yoloe/val.py +15 -20
  165. ultralytics/nn/__init__.py +7 -7
  166. ultralytics/nn/autobackend.py +159 -85
  167. ultralytics/nn/modules/__init__.py +68 -60
  168. ultralytics/nn/modules/activation.py +4 -6
  169. ultralytics/nn/modules/block.py +260 -224
  170. ultralytics/nn/modules/conv.py +52 -97
  171. ultralytics/nn/modules/head.py +831 -299
  172. ultralytics/nn/modules/transformer.py +76 -88
  173. ultralytics/nn/modules/utils.py +16 -21
  174. ultralytics/nn/tasks.py +180 -195
  175. ultralytics/nn/text_model.py +45 -69
  176. ultralytics/optim/__init__.py +5 -0
  177. ultralytics/optim/muon.py +338 -0
  178. ultralytics/solutions/__init__.py +12 -12
  179. ultralytics/solutions/ai_gym.py +13 -19
  180. ultralytics/solutions/analytics.py +15 -16
  181. ultralytics/solutions/config.py +6 -7
  182. ultralytics/solutions/distance_calculation.py +10 -13
  183. ultralytics/solutions/heatmap.py +8 -14
  184. ultralytics/solutions/instance_segmentation.py +6 -9
  185. ultralytics/solutions/object_blurrer.py +7 -10
  186. ultralytics/solutions/object_counter.py +12 -19
  187. ultralytics/solutions/object_cropper.py +8 -14
  188. ultralytics/solutions/parking_management.py +34 -32
  189. ultralytics/solutions/queue_management.py +10 -12
  190. ultralytics/solutions/region_counter.py +9 -12
  191. ultralytics/solutions/security_alarm.py +15 -20
  192. ultralytics/solutions/similarity_search.py +10 -15
  193. ultralytics/solutions/solutions.py +77 -76
  194. ultralytics/solutions/speed_estimation.py +7 -10
  195. ultralytics/solutions/streamlit_inference.py +2 -4
  196. ultralytics/solutions/templates/similarity-search.html +7 -18
  197. ultralytics/solutions/trackzone.py +7 -10
  198. ultralytics/solutions/vision_eye.py +5 -8
  199. ultralytics/trackers/__init__.py +1 -1
  200. ultralytics/trackers/basetrack.py +3 -5
  201. ultralytics/trackers/bot_sort.py +10 -27
  202. ultralytics/trackers/byte_tracker.py +21 -37
  203. ultralytics/trackers/track.py +4 -7
  204. ultralytics/trackers/utils/gmc.py +11 -22
  205. ultralytics/trackers/utils/kalman_filter.py +37 -48
  206. ultralytics/trackers/utils/matching.py +12 -15
  207. ultralytics/utils/__init__.py +124 -124
  208. ultralytics/utils/autobatch.py +2 -4
  209. ultralytics/utils/autodevice.py +17 -18
  210. ultralytics/utils/benchmarks.py +57 -71
  211. ultralytics/utils/callbacks/base.py +8 -10
  212. ultralytics/utils/callbacks/clearml.py +5 -13
  213. ultralytics/utils/callbacks/comet.py +32 -46
  214. ultralytics/utils/callbacks/dvc.py +13 -18
  215. ultralytics/utils/callbacks/mlflow.py +4 -5
  216. ultralytics/utils/callbacks/neptune.py +7 -15
  217. ultralytics/utils/callbacks/platform.py +423 -38
  218. ultralytics/utils/callbacks/raytune.py +3 -4
  219. ultralytics/utils/callbacks/tensorboard.py +25 -31
  220. ultralytics/utils/callbacks/wb.py +16 -14
  221. ultralytics/utils/checks.py +127 -85
  222. ultralytics/utils/cpu.py +3 -8
  223. ultralytics/utils/dist.py +9 -12
  224. ultralytics/utils/downloads.py +25 -33
  225. ultralytics/utils/errors.py +6 -14
  226. ultralytics/utils/events.py +2 -4
  227. ultralytics/utils/export/__init__.py +4 -236
  228. ultralytics/utils/export/engine.py +246 -0
  229. ultralytics/utils/export/imx.py +117 -63
  230. ultralytics/utils/export/tensorflow.py +231 -0
  231. ultralytics/utils/files.py +26 -30
  232. ultralytics/utils/git.py +9 -11
  233. ultralytics/utils/instance.py +30 -51
  234. ultralytics/utils/logger.py +212 -114
  235. ultralytics/utils/loss.py +601 -215
  236. ultralytics/utils/metrics.py +128 -156
  237. ultralytics/utils/nms.py +13 -16
  238. ultralytics/utils/ops.py +117 -166
  239. ultralytics/utils/patches.py +75 -21
  240. ultralytics/utils/plotting.py +75 -80
  241. ultralytics/utils/tal.py +125 -59
  242. ultralytics/utils/torch_utils.py +53 -79
  243. ultralytics/utils/tqdm.py +24 -21
  244. ultralytics/utils/triton.py +13 -19
  245. ultralytics/utils/tuner.py +19 -10
  246. dgenerate_ultralytics_headless-8.3.214.dist-info/RECORD +0 -283
  247. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/entry_points.txt +0 -0
  248. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/licenses/LICENSE +0 -0
  249. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/top_level.txt +0 -0
@@ -1,73 +1,458 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
- from ultralytics.utils import RANK, SETTINGS
3
+ import os
4
+ import platform
5
+ import re
6
+ import socket
7
+ import sys
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from pathlib import Path
10
+ from time import time
11
+
12
+ from ultralytics.utils import ENVIRONMENT, GIT, LOGGER, PYTHON_VERSION, RANK, SETTINGS, TESTS_RUNNING, colorstr
13
+
14
+ PREFIX = colorstr("Platform: ")
15
+
16
+ # Configurable platform URL for debugging (e.g. ULTRALYTICS_PLATFORM_URL=http://localhost:3000)
17
+ PLATFORM_URL = os.getenv("ULTRALYTICS_PLATFORM_URL", "https://platform.ultralytics.com").rstrip("/")
18
+ PLATFORM_API_URL = f"{PLATFORM_URL}/api/webhooks"
19
+
20
+
21
+ def slugify(text):
22
+ """Convert text to URL-safe slug (e.g., 'My Project 1' -> 'my-project-1')."""
23
+ if not text:
24
+ return text
25
+ return re.sub(r"-+", "-", re.sub(r"[^a-z0-9\s-]", "", str(text).lower()).replace(" ", "-")).strip("-")[:128]
26
+
27
+
28
+ try:
29
+ assert not TESTS_RUNNING # do not log pytest
30
+ assert SETTINGS.get("platform", False) is True or os.getenv("ULTRALYTICS_API_KEY") or SETTINGS.get("api_key")
31
+ _api_key = os.getenv("ULTRALYTICS_API_KEY") or SETTINGS.get("api_key")
32
+ assert _api_key # verify API key is present
33
+
34
+ import requests
35
+
36
+ from ultralytics.utils.logger import ConsoleLogger, SystemLogger
37
+ from ultralytics.utils.torch_utils import model_info_for_loggers
38
+
39
+ _executor = ThreadPoolExecutor(max_workers=10) # Bounded thread pool for async operations
40
+
41
+ except (AssertionError, ImportError):
42
+ _api_key = None
43
+
44
+
45
+ def resolve_platform_uri(uri, hard=True):
46
+ """Resolve ul:// URIs to signed URLs by authenticating with Ultralytics Platform.
47
+
48
+ Formats:
49
+ ul://username/datasets/slug -> Returns signed URL to NDJSON file
50
+ ul://username/project/model -> Returns signed URL to .pt file
51
+
52
+ Args:
53
+ uri (str): Platform URI starting with "ul://".
54
+ hard (bool): Whether to raise an error if resolution fails (FileNotFoundError only).
55
+
56
+ Returns:
57
+ (str | None): Signed URL on success, None if not found and hard=False.
58
+
59
+ Raises:
60
+ ValueError: If API key is missing/invalid or URI format is wrong.
61
+ PermissionError: If access is denied.
62
+ RuntimeError: If resource is not ready (e.g., dataset still processing).
63
+ FileNotFoundError: If resource not found and hard=True.
64
+ ConnectionError: If network request fails and hard=True.
65
+ """
66
+ import requests
67
+
68
+ path = uri[5:] # Remove "ul://"
69
+ parts = path.split("/")
70
+
71
+ api_key = os.getenv("ULTRALYTICS_API_KEY") or SETTINGS.get("api_key")
72
+ if not api_key:
73
+ raise ValueError(f"ULTRALYTICS_API_KEY required for '{uri}'. Get key at {PLATFORM_URL}/settings")
74
+
75
+ base = PLATFORM_API_URL
76
+ headers = {"Authorization": f"Bearer {api_key}"}
77
+
78
+ # ul://username/datasets/slug
79
+ if len(parts) == 3 and parts[1] == "datasets":
80
+ username, _, slug = parts
81
+ url = f"{base}/datasets/{username}/{slug}/export"
82
+
83
+ # ul://username/project/model
84
+ elif len(parts) == 3:
85
+ username, project, model = parts
86
+ url = f"{base}/models/{username}/{project}/{model}/download"
87
+
88
+ else:
89
+ raise ValueError(f"Invalid platform URI: {uri}. Use ul://user/datasets/name or ul://user/project/model")
90
+
91
+ try:
92
+ timeout = 3600 if "/datasets/" in url else 90 # NDJSON generation can be slow for large datasets
93
+ r = requests.head(url, headers=headers, allow_redirects=False, timeout=timeout)
94
+
95
+ # Handle redirect responses (301, 302, 303, 307, 308)
96
+ if 300 <= r.status_code < 400 and "location" in r.headers:
97
+ return r.headers["location"] # Return signed URL
98
+
99
+ # Handle error responses
100
+ if r.status_code == 401:
101
+ raise ValueError(f"Invalid ULTRALYTICS_API_KEY for '{uri}'")
102
+ if r.status_code == 403:
103
+ raise PermissionError(f"Access denied for '{uri}'. Check dataset/model visibility settings.")
104
+ if r.status_code == 404:
105
+ if hard:
106
+ raise FileNotFoundError(f"Not found on platform: {uri}")
107
+ LOGGER.warning(f"Not found on platform: {uri}")
108
+ return None
109
+ if r.status_code == 409:
110
+ raise RuntimeError(f"Resource not ready: {uri}. Dataset may still be processing.")
111
+
112
+ # Unexpected response
113
+ r.raise_for_status()
114
+ raise RuntimeError(f"Unexpected response from platform for '{uri}': {r.status_code}")
115
+
116
+ except requests.exceptions.RequestException as e:
117
+ if hard:
118
+ raise ConnectionError(f"Failed to resolve {uri}: {e}") from e
119
+ LOGGER.warning(f"Failed to resolve {uri}: {e}")
120
+ return None
121
+
122
+
123
+ def _interp_plot(plot, n=101):
124
+ """Interpolate plot curve data from 1000 to n points to reduce storage size."""
125
+ import numpy as np
126
+
127
+ if not plot.get("x") or not plot.get("y"):
128
+ return plot # No interpolation needed (e.g., confusion_matrix)
129
+
130
+ x, y = np.array(plot["x"]), np.array(plot["y"])
131
+ if len(x) <= n:
132
+ return plot # Already small enough
133
+
134
+ # New x values (101 points gives clean 0.01 increments: 0, 0.01, 0.02, ..., 1.0)
135
+ x_new = np.linspace(x[0], x[-1], n)
136
+
137
+ # Interpolate y values (handle both 1D and 2D arrays)
138
+ if y.ndim == 1:
139
+ y_new = np.interp(x_new, x, y)
140
+ else:
141
+ y_new = np.array([np.interp(x_new, x, yi) for yi in y])
142
+
143
+ # Also interpolate ap if present (for PR curves)
144
+ result = {**plot, "x": x_new.tolist(), "y": y_new.tolist()}
145
+ if "ap" in plot:
146
+ result["ap"] = plot["ap"] # Keep AP values as-is (per-class scalars)
147
+
148
+ return result
149
+
150
+
151
+ def _send(event, data, project, name, model_id=None):
152
+ """Send event to Platform endpoint. Returns response JSON on success."""
153
+ try:
154
+ payload = {"event": event, "project": project, "name": name, "data": data}
155
+ if model_id:
156
+ payload["modelId"] = model_id
157
+ r = requests.post(
158
+ f"{PLATFORM_API_URL}/training/metrics",
159
+ json=payload,
160
+ headers={"Authorization": f"Bearer {_api_key}"},
161
+ timeout=10,
162
+ )
163
+ r.raise_for_status()
164
+ return r.json()
165
+ except Exception as e:
166
+ LOGGER.debug(f"Platform: Failed to send {event}: {e}")
167
+ return None
168
+
169
+
170
+ def _send_async(event, data, project, name, model_id=None):
171
+ """Send event asynchronously using bounded thread pool."""
172
+ _executor.submit(_send, event, data, project, name, model_id)
173
+
174
+
175
+ def _upload_model(model_path, project, name):
176
+ """Upload model checkpoint to Platform via signed URL."""
177
+ try:
178
+ model_path = Path(model_path)
179
+ if not model_path.exists():
180
+ return None
181
+
182
+ # Get signed upload URL
183
+ response = requests.post(
184
+ f"{PLATFORM_API_URL}/models/upload",
185
+ json={"project": project, "name": name, "filename": model_path.name},
186
+ headers={"Authorization": f"Bearer {_api_key}"},
187
+ timeout=10,
188
+ )
189
+ response.raise_for_status()
190
+ data = response.json()
191
+
192
+ # Upload to GCS
193
+ with open(model_path, "rb") as f:
194
+ requests.put(
195
+ data["uploadUrl"],
196
+ data=f,
197
+ headers={"Content-Type": "application/octet-stream"},
198
+ timeout=600, # 10 min timeout for large models
199
+ ).raise_for_status()
200
+
201
+ # url = f"{PLATFORM_URL}/{project}/{name}"
202
+ # LOGGER.info(f"{PREFIX}Model uploaded to {url}")
203
+ return data.get("gcsPath")
204
+
205
+ except Exception as e:
206
+ LOGGER.debug(f"Platform: Failed to upload model: {e}")
207
+ return None
208
+
209
+
210
+ def _upload_model_async(model_path, project, name):
211
+ """Upload model asynchronously using bounded thread pool."""
212
+ _executor.submit(_upload_model, model_path, project, name)
213
+
214
+
215
+ def _get_environment_info():
216
+ """Collect comprehensive environment info using existing ultralytics utilities."""
217
+ import shutil
218
+
219
+ import psutil
220
+ import torch
221
+
222
+ from ultralytics import __version__
223
+ from ultralytics.utils.torch_utils import get_cpu_info, get_gpu_info
224
+
225
+ # Get RAM and disk totals
226
+ memory = psutil.virtual_memory()
227
+ disk_usage = shutil.disk_usage("/")
228
+
229
+ env = {
230
+ "ultralyticsVersion": __version__,
231
+ "hostname": socket.gethostname(),
232
+ "os": platform.platform(),
233
+ "environment": ENVIRONMENT,
234
+ "pythonVersion": PYTHON_VERSION,
235
+ "pythonExecutable": sys.executable,
236
+ "cpuCount": os.cpu_count() or 0,
237
+ "cpu": get_cpu_info(),
238
+ "command": " ".join(sys.argv),
239
+ "totalRamGb": round(memory.total / (1 << 30), 1), # Total RAM in GB
240
+ "totalDiskGb": round(disk_usage.total / (1 << 30), 1), # Total disk in GB
241
+ }
242
+
243
+ # Git info using cached GIT singleton (no subprocess calls)
244
+ try:
245
+ if GIT.is_repo:
246
+ if GIT.origin:
247
+ env["gitRepository"] = GIT.origin
248
+ if GIT.branch:
249
+ env["gitBranch"] = GIT.branch
250
+ if GIT.commit:
251
+ env["gitCommit"] = GIT.commit[:12] # Short hash
252
+ except Exception:
253
+ pass
254
+
255
+ # GPU info
256
+ try:
257
+ if torch.cuda.is_available():
258
+ env["gpuCount"] = torch.cuda.device_count()
259
+ env["gpuType"] = get_gpu_info(0) if torch.cuda.device_count() > 0 else None
260
+ except Exception:
261
+ pass
262
+
263
+ return env
264
+
265
+
266
+ def _get_project_name(trainer):
267
+ """Get slugified project and name from trainer args."""
268
+ raw = str(trainer.args.project)
269
+ parts = raw.split("/", 1)
270
+ project = f"{parts[0]}/{slugify(parts[1])}" if len(parts) == 2 else slugify(raw)
271
+ return project, slugify(str(trainer.args.name or "train"))
4
272
 
5
273
 
6
274
  def on_pretrain_routine_start(trainer):
7
- """Initialize and start console logging immediately at the very beginning."""
8
- if RANK in {-1, 0}:
9
- from ultralytics.utils.logger import DEFAULT_LOG_PATH, ConsoleLogger, SystemLogger
275
+ """Initialize Platform logging at training start."""
276
+ if RANK not in {-1, 0} or not trainer.args.project:
277
+ return
278
+
279
+ # Per-trainer state to isolate concurrent training runs
280
+ trainer._platform_model_id = None
281
+ trainer._platform_last_upload = time()
10
282
 
11
- trainer.system_logger = SystemLogger()
12
- trainer.console_logger = ConsoleLogger(DEFAULT_LOG_PATH)
13
- trainer.console_logger.start_capture()
283
+ project, name = _get_project_name(trainer)
284
+ url = f"{PLATFORM_URL}/{project}/{name}"
285
+ LOGGER.info(f"{PREFIX}Streaming to {url}")
14
286
 
287
+ # Create callback to send console output to Platform
288
+ def send_console_output(content, line_count, chunk_id):
289
+ """Send batched console output to Platform webhook."""
290
+ _send_async(
291
+ "console_output",
292
+ {"chunkId": chunk_id, "content": content, "lineCount": line_count},
293
+ project,
294
+ name,
295
+ getattr(trainer, "_platform_model_id", None),
296
+ )
15
297
 
16
- def on_pretrain_routine_end(trainer):
17
- """Handle pre-training routine completion event."""
18
- pass
298
+ # Start console capture with batching (5 lines or 5 seconds)
299
+ trainer._platform_console_logger = ConsoleLogger(batch_size=5, flush_interval=5.0, on_flush=send_console_output)
300
+ trainer._platform_console_logger.start_capture()
301
+
302
+ # Collect environment info (W&B-style metadata)
303
+ environment = _get_environment_info()
304
+
305
+ # Build trainArgs - callback runs before get_dataset() so args.data is still original (e.g., ul:// URIs)
306
+ # Note: model_info is sent later in on_fit_epoch_end (epoch 0) when the model is actually loaded
307
+ train_args = {k: str(v) for k, v in vars(trainer.args).items()}
308
+
309
+ # Send synchronously to get modelId for subsequent webhooks
310
+ response = _send(
311
+ "training_started",
312
+ {
313
+ "trainArgs": train_args,
314
+ "epochs": trainer.epochs,
315
+ "device": str(trainer.device),
316
+ "environment": environment,
317
+ },
318
+ project,
319
+ name,
320
+ )
321
+ if response and response.get("modelId"):
322
+ trainer._platform_model_id = response["modelId"]
19
323
 
20
324
 
21
325
  def on_fit_epoch_end(trainer):
22
- """Handle end of training epoch event and collect system metrics."""
23
- if RANK in {-1, 0} and hasattr(trainer, "system_logger"):
24
- system_metrics = trainer.system_logger.get_metrics()
25
- print(system_metrics) # for debug
326
+ """Log training and system metrics at epoch end."""
327
+ if RANK not in {-1, 0} or not trainer.args.project:
328
+ return
329
+
330
+ project, name = _get_project_name(trainer)
331
+ metrics = {**trainer.label_loss_items(trainer.tloss, prefix="train"), **trainer.metrics}
332
+
333
+ if trainer.optimizer and trainer.optimizer.param_groups:
334
+ metrics["lr"] = trainer.optimizer.param_groups[0]["lr"]
335
+
336
+ # Extract model info at epoch 0 (sent as separate field, not in metrics)
337
+ model_info = None
338
+ if trainer.epoch == 0:
339
+ try:
340
+ info = model_info_for_loggers(trainer)
341
+ model_info = {
342
+ "parameters": info.get("model/parameters", 0),
343
+ "gflops": info.get("model/GFLOPs", 0),
344
+ "speedMs": info.get("model/speed_PyTorch(ms)", 0),
345
+ }
346
+ except Exception:
347
+ pass
348
+
349
+ # Get system metrics (cache SystemLogger on trainer for efficiency)
350
+ system = {}
351
+ try:
352
+ if not hasattr(trainer, "_platform_system_logger"):
353
+ trainer._platform_system_logger = SystemLogger()
354
+ system = trainer._platform_system_logger.get_metrics(rates=True)
355
+ except Exception:
356
+ pass
357
+
358
+ payload = {
359
+ "epoch": trainer.epoch,
360
+ "metrics": metrics,
361
+ "system": system,
362
+ "fitness": trainer.fitness,
363
+ "best_fitness": trainer.best_fitness,
364
+ }
365
+ if model_info:
366
+ payload["modelInfo"] = model_info
367
+
368
+ _send_async(
369
+ "epoch_end",
370
+ payload,
371
+ project,
372
+ name,
373
+ getattr(trainer, "_platform_model_id", None),
374
+ )
26
375
 
27
376
 
28
377
  def on_model_save(trainer):
29
- """Handle model checkpoint save event."""
30
- pass
378
+ """Upload model checkpoint (rate limited to every 15 min)."""
379
+ if RANK not in {-1, 0} or not trainer.args.project:
380
+ return
31
381
 
382
+ # Rate limit to every 15 minutes (900 seconds)
383
+ if time() - getattr(trainer, "_platform_last_upload", 0) < 900:
384
+ return
32
385
 
33
- def on_train_end(trainer):
34
- """Stop console capture and finalize logs."""
35
- if logger := getattr(trainer, "console_logger", None):
36
- logger.stop_capture()
386
+ model_path = trainer.best if trainer.best and Path(trainer.best).exists() else trainer.last
387
+ if not model_path:
388
+ return
37
389
 
390
+ project, name = _get_project_name(trainer)
391
+ _upload_model_async(model_path, project, name)
392
+ trainer._platform_last_upload = time()
38
393
 
39
- def on_train_start(trainer):
40
- """Handle training start event."""
41
- pass
42
394
 
395
+ def on_train_end(trainer):
396
+ """Log final results, upload best model, and send validation plot data."""
397
+ if RANK not in {-1, 0} or not trainer.args.project:
398
+ return
399
+
400
+ project, name = _get_project_name(trainer)
43
401
 
44
- def on_val_start(validator):
45
- """Handle validation start event."""
46
- pass
402
+ # Stop console capture
403
+ if hasattr(trainer, "_platform_console_logger") and trainer._platform_console_logger:
404
+ trainer._platform_console_logger.stop_capture()
405
+ trainer._platform_console_logger = None
47
406
 
407
+ # Upload best model (blocking to ensure it completes)
408
+ model_path = None
409
+ model_size = None
410
+ if trainer.best and Path(trainer.best).exists():
411
+ model_size = Path(trainer.best).stat().st_size
412
+ model_path = _upload_model(trainer.best, project, name)
48
413
 
49
- def on_predict_start(predictor):
50
- """Handle prediction start event."""
51
- pass
414
+ # Collect plots from trainer and validator, deduplicating by type
415
+ plots_by_type = {}
416
+ for info in getattr(trainer, "plots", {}).values():
417
+ if info.get("data") and info["data"].get("type"):
418
+ plots_by_type[info["data"]["type"]] = info["data"]
419
+ for info in getattr(getattr(trainer, "validator", None), "plots", {}).values():
420
+ if info.get("data") and info["data"].get("type"):
421
+ plots_by_type.setdefault(info["data"]["type"], info["data"]) # Don't overwrite trainer plots
422
+ plots = [_interp_plot(p) for p in plots_by_type.values()] # Interpolate curves to reduce size
52
423
 
424
+ # Get class names
425
+ names = getattr(getattr(trainer, "validator", None), "names", None) or (trainer.data or {}).get("names")
426
+ class_names = list(names.values()) if isinstance(names, dict) else list(names) if names else None
53
427
 
54
- def on_export_start(exporter):
55
- """Handle model export start event."""
56
- pass
428
+ _send(
429
+ "training_complete",
430
+ {
431
+ "results": {
432
+ "metrics": {**trainer.metrics, "fitness": trainer.fitness},
433
+ "bestEpoch": getattr(trainer, "best_epoch", trainer.epoch),
434
+ "bestFitness": trainer.best_fitness,
435
+ "modelPath": model_path or (str(trainer.best) if trainer.best else None),
436
+ "modelSize": model_size,
437
+ },
438
+ "classNames": class_names,
439
+ "plots": plots,
440
+ },
441
+ project,
442
+ name,
443
+ getattr(trainer, "_platform_model_id", None),
444
+ )
445
+ url = f"{PLATFORM_URL}/{project}/{name}"
446
+ LOGGER.info(f"{PREFIX}View results at {url}")
57
447
 
58
448
 
59
449
  callbacks = (
60
450
  {
61
451
  "on_pretrain_routine_start": on_pretrain_routine_start,
62
- "on_pretrain_routine_end": on_pretrain_routine_end,
63
452
  "on_fit_epoch_end": on_fit_epoch_end,
64
453
  "on_model_save": on_model_save,
65
454
  "on_train_end": on_train_end,
66
- "on_train_start": on_train_start,
67
- "on_val_start": on_val_start,
68
- "on_predict_start": on_predict_start,
69
- "on_export_start": on_export_start,
70
455
  }
71
- if SETTINGS.get("platform", False) is True # disabled for debugging
456
+ if _api_key
72
457
  else {}
73
458
  )
@@ -13,11 +13,10 @@ except (ImportError, AssertionError):
13
13
 
14
14
 
15
15
  def on_fit_epoch_end(trainer):
16
- """
17
- Report training metrics to Ray Tune at epoch end when a Ray session is active.
16
+ """Report training metrics to Ray Tune at epoch end when a Ray session is active.
18
17
 
19
- Captures metrics from the trainer object and sends them to Ray Tune with the current epoch number,
20
- enabling hyperparameter tuning optimization. Only executes when within an active Ray Tune session.
18
+ Captures metrics from the trainer object and sends them to Ray Tune with the current epoch number, enabling
19
+ hyperparameter tuning optimization. Only executes when within an active Ray Tune session.
21
20
 
22
21
  Args:
23
22
  trainer (ultralytics.engine.trainer.BaseTrainer): The Ultralytics trainer object containing metrics and epochs.
@@ -1,6 +1,7 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
3
  from ultralytics.utils import LOGGER, SETTINGS, TESTS_RUNNING, colorstr, torch_utils
4
+ from ultralytics.utils.torch_utils import smart_inference_mode
4
5
 
5
6
  try:
6
7
  assert not TESTS_RUNNING # do not log pytest
@@ -9,7 +10,6 @@ try:
9
10
  PREFIX = colorstr("TensorBoard: ")
10
11
 
11
12
  # Imports below only required if TensorBoard enabled
12
- import warnings
13
13
  from copy import deepcopy
14
14
 
15
15
  import torch
@@ -22,8 +22,7 @@ except (ImportError, AssertionError, TypeError, AttributeError):
22
22
 
23
23
 
24
24
  def _log_scalars(scalars: dict, step: int = 0) -> None:
25
- """
26
- Log scalar values to TensorBoard.
25
+ """Log scalar values to TensorBoard.
27
26
 
28
27
  Args:
29
28
  scalars (dict): Dictionary of scalar values to log to TensorBoard. Keys are scalar names and values are the
@@ -40,17 +39,17 @@ def _log_scalars(scalars: dict, step: int = 0) -> None:
40
39
  WRITER.add_scalar(k, v, step)
41
40
 
42
41
 
42
+ @smart_inference_mode()
43
43
  def _log_tensorboard_graph(trainer) -> None:
44
- """
45
- Log model graph to TensorBoard.
44
+ """Log model graph to TensorBoard.
46
45
 
47
46
  This function attempts to visualize the model architecture in TensorBoard by tracing the model with a dummy input
48
47
  tensor. It first tries a simple method suitable for YOLO models, and if that fails, falls back to a more complex
49
48
  approach for models like RTDETR that may require special handling.
50
49
 
51
50
  Args:
52
- trainer (ultralytics.engine.trainer.BaseTrainer): The trainer object containing the model to visualize.
53
- Must have attributes model and args with imgsz.
51
+ trainer (ultralytics.engine.trainer.BaseTrainer): The trainer object containing the model to visualize. Must
52
+ have attributes model and args with imgsz.
54
53
 
55
54
  Notes:
56
55
  This function requires TensorBoard integration to be enabled and the global WRITER to be initialized.
@@ -63,32 +62,27 @@ def _log_tensorboard_graph(trainer) -> None:
63
62
  p = next(trainer.model.parameters()) # for device, type
64
63
  im = torch.zeros((1, 3, *imgsz), device=p.device, dtype=p.dtype) # input image (must be zeros, not empty)
65
64
 
66
- with warnings.catch_warnings():
67
- warnings.simplefilter("ignore", category=UserWarning) # suppress jit trace warning
68
- warnings.simplefilter("ignore", category=torch.jit.TracerWarning) # suppress jit trace warning
69
-
70
- # Try simple method first (YOLO)
65
+ # Try simple method first (YOLO)
66
+ try:
67
+ trainer.model.eval() # place in .eval() mode to avoid BatchNorm statistics changes
68
+ WRITER.add_graph(torch.jit.trace(torch_utils.unwrap_model(trainer.model), im, strict=False), [])
69
+ LOGGER.info(f"{PREFIX}model graph visualization added ✅")
70
+ return
71
+ except Exception as e1:
72
+ # Fallback to TorchScript export steps (RTDETR)
71
73
  try:
72
- trainer.model.eval() # place in .eval() mode to avoid BatchNorm statistics changes
73
- WRITER.add_graph(torch.jit.trace(torch_utils.unwrap_model(trainer.model), im, strict=False), [])
74
+ model = deepcopy(torch_utils.unwrap_model(trainer.model))
75
+ model.eval()
76
+ model = model.fuse(verbose=False)
77
+ for m in model.modules():
78
+ if hasattr(m, "export"): # Detect, RTDETRDecoder (Segment and Pose use Detect base class)
79
+ m.export = True
80
+ m.format = "torchscript"
81
+ model(im) # dry run
82
+ WRITER.add_graph(torch.jit.trace(model, im, strict=False), [])
74
83
  LOGGER.info(f"{PREFIX}model graph visualization added ✅")
75
- return
76
-
77
- except Exception:
78
- # Fallback to TorchScript export steps (RTDETR)
79
- try:
80
- model = deepcopy(torch_utils.unwrap_model(trainer.model))
81
- model.eval()
82
- model = model.fuse(verbose=False)
83
- for m in model.modules():
84
- if hasattr(m, "export"): # Detect, RTDETRDecoder (Segment and Pose use Detect base class)
85
- m.export = True
86
- m.format = "torchscript"
87
- model(im) # dry run
88
- WRITER.add_graph(torch.jit.trace(model, im, strict=False), [])
89
- LOGGER.info(f"{PREFIX}model graph visualization added ✅")
90
- except Exception as e:
91
- LOGGER.warning(f"{PREFIX}TensorBoard graph visualization failure {e}")
84
+ except Exception as e2:
85
+ LOGGER.warning(f"{PREFIX}TensorBoard graph visualization failure: {e1} -> {e2}")
92
86
 
93
87
 
94
88
  def on_pretrain_routine_start(trainer) -> None: