dgenerate-ultralytics-headless 8.3.196__py3-none-any.whl → 8.3.248__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 (243) hide show
  1. {dgenerate_ultralytics_headless-8.3.196.dist-info → dgenerate_ultralytics_headless-8.3.248.dist-info}/METADATA +33 -34
  2. dgenerate_ultralytics_headless-8.3.248.dist-info/RECORD +298 -0
  3. tests/__init__.py +5 -7
  4. tests/conftest.py +8 -15
  5. tests/test_cli.py +8 -10
  6. tests/test_cuda.py +9 -10
  7. tests/test_engine.py +29 -2
  8. tests/test_exports.py +69 -21
  9. tests/test_integrations.py +8 -11
  10. tests/test_python.py +109 -71
  11. tests/test_solutions.py +170 -159
  12. ultralytics/__init__.py +27 -9
  13. ultralytics/cfg/__init__.py +57 -64
  14. ultralytics/cfg/datasets/Argoverse.yaml +7 -6
  15. ultralytics/cfg/datasets/DOTAv1.5.yaml +1 -1
  16. ultralytics/cfg/datasets/DOTAv1.yaml +1 -1
  17. ultralytics/cfg/datasets/ImageNet.yaml +1 -1
  18. ultralytics/cfg/datasets/Objects365.yaml +19 -15
  19. ultralytics/cfg/datasets/SKU-110K.yaml +1 -1
  20. ultralytics/cfg/datasets/VOC.yaml +19 -21
  21. ultralytics/cfg/datasets/VisDrone.yaml +5 -5
  22. ultralytics/cfg/datasets/african-wildlife.yaml +1 -1
  23. ultralytics/cfg/datasets/coco-pose.yaml +24 -2
  24. ultralytics/cfg/datasets/coco.yaml +2 -2
  25. ultralytics/cfg/datasets/coco128-seg.yaml +1 -1
  26. ultralytics/cfg/datasets/coco8-pose.yaml +21 -0
  27. ultralytics/cfg/datasets/construction-ppe.yaml +32 -0
  28. ultralytics/cfg/datasets/dog-pose.yaml +28 -0
  29. ultralytics/cfg/datasets/dota8-multispectral.yaml +1 -1
  30. ultralytics/cfg/datasets/dota8.yaml +2 -2
  31. ultralytics/cfg/datasets/hand-keypoints.yaml +26 -2
  32. ultralytics/cfg/datasets/kitti.yaml +27 -0
  33. ultralytics/cfg/datasets/lvis.yaml +7 -7
  34. ultralytics/cfg/datasets/open-images-v7.yaml +1 -1
  35. ultralytics/cfg/datasets/tiger-pose.yaml +16 -0
  36. ultralytics/cfg/datasets/xView.yaml +16 -16
  37. ultralytics/cfg/default.yaml +96 -94
  38. ultralytics/cfg/models/11/yolo11-pose.yaml +1 -1
  39. ultralytics/cfg/models/11/yoloe-11-seg.yaml +2 -2
  40. ultralytics/cfg/models/11/yoloe-11.yaml +2 -2
  41. ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +1 -1
  42. ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +1 -1
  43. ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +1 -1
  44. ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +1 -1
  45. ultralytics/cfg/models/v10/yolov10b.yaml +2 -2
  46. ultralytics/cfg/models/v10/yolov10l.yaml +2 -2
  47. ultralytics/cfg/models/v10/yolov10m.yaml +2 -2
  48. ultralytics/cfg/models/v10/yolov10n.yaml +2 -2
  49. ultralytics/cfg/models/v10/yolov10s.yaml +2 -2
  50. ultralytics/cfg/models/v10/yolov10x.yaml +2 -2
  51. ultralytics/cfg/models/v3/yolov3-tiny.yaml +1 -1
  52. ultralytics/cfg/models/v6/yolov6.yaml +1 -1
  53. ultralytics/cfg/models/v8/yoloe-v8-seg.yaml +9 -6
  54. ultralytics/cfg/models/v8/yoloe-v8.yaml +9 -6
  55. ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +1 -1
  56. ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +1 -1
  57. ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +2 -2
  58. ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +2 -2
  59. ultralytics/cfg/models/v8/yolov8-ghost.yaml +2 -2
  60. ultralytics/cfg/models/v8/yolov8-obb.yaml +1 -1
  61. ultralytics/cfg/models/v8/yolov8-p2.yaml +1 -1
  62. ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +1 -1
  63. ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +1 -1
  64. ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +1 -1
  65. ultralytics/cfg/models/v8/yolov8-world.yaml +1 -1
  66. ultralytics/cfg/models/v8/yolov8-worldv2.yaml +6 -6
  67. ultralytics/cfg/models/v9/yolov9s.yaml +1 -1
  68. ultralytics/cfg/trackers/botsort.yaml +16 -17
  69. ultralytics/cfg/trackers/bytetrack.yaml +9 -11
  70. ultralytics/data/__init__.py +4 -4
  71. ultralytics/data/annotator.py +3 -4
  72. ultralytics/data/augment.py +286 -476
  73. ultralytics/data/base.py +18 -26
  74. ultralytics/data/build.py +151 -26
  75. ultralytics/data/converter.py +38 -50
  76. ultralytics/data/dataset.py +47 -75
  77. ultralytics/data/loaders.py +42 -49
  78. ultralytics/data/split.py +5 -6
  79. ultralytics/data/split_dota.py +8 -15
  80. ultralytics/data/utils.py +41 -45
  81. ultralytics/engine/exporter.py +462 -462
  82. ultralytics/engine/model.py +150 -191
  83. ultralytics/engine/predictor.py +30 -40
  84. ultralytics/engine/results.py +177 -311
  85. ultralytics/engine/trainer.py +193 -120
  86. ultralytics/engine/tuner.py +77 -63
  87. ultralytics/engine/validator.py +39 -22
  88. ultralytics/hub/__init__.py +16 -19
  89. ultralytics/hub/auth.py +6 -12
  90. ultralytics/hub/google/__init__.py +7 -10
  91. ultralytics/hub/session.py +15 -25
  92. ultralytics/hub/utils.py +5 -8
  93. ultralytics/models/__init__.py +1 -1
  94. ultralytics/models/fastsam/__init__.py +1 -1
  95. ultralytics/models/fastsam/model.py +8 -10
  96. ultralytics/models/fastsam/predict.py +19 -30
  97. ultralytics/models/fastsam/utils.py +1 -2
  98. ultralytics/models/fastsam/val.py +5 -7
  99. ultralytics/models/nas/__init__.py +1 -1
  100. ultralytics/models/nas/model.py +5 -8
  101. ultralytics/models/nas/predict.py +7 -9
  102. ultralytics/models/nas/val.py +1 -2
  103. ultralytics/models/rtdetr/__init__.py +1 -1
  104. ultralytics/models/rtdetr/model.py +7 -8
  105. ultralytics/models/rtdetr/predict.py +15 -19
  106. ultralytics/models/rtdetr/train.py +10 -13
  107. ultralytics/models/rtdetr/val.py +21 -23
  108. ultralytics/models/sam/__init__.py +15 -2
  109. ultralytics/models/sam/amg.py +14 -20
  110. ultralytics/models/sam/build.py +26 -19
  111. ultralytics/models/sam/build_sam3.py +377 -0
  112. ultralytics/models/sam/model.py +29 -32
  113. ultralytics/models/sam/modules/blocks.py +83 -144
  114. ultralytics/models/sam/modules/decoders.py +22 -40
  115. ultralytics/models/sam/modules/encoders.py +44 -101
  116. ultralytics/models/sam/modules/memory_attention.py +16 -30
  117. ultralytics/models/sam/modules/sam.py +206 -79
  118. ultralytics/models/sam/modules/tiny_encoder.py +64 -83
  119. ultralytics/models/sam/modules/transformer.py +18 -28
  120. ultralytics/models/sam/modules/utils.py +174 -50
  121. ultralytics/models/sam/predict.py +2268 -366
  122. ultralytics/models/sam/sam3/__init__.py +3 -0
  123. ultralytics/models/sam/sam3/decoder.py +546 -0
  124. ultralytics/models/sam/sam3/encoder.py +529 -0
  125. ultralytics/models/sam/sam3/geometry_encoders.py +415 -0
  126. ultralytics/models/sam/sam3/maskformer_segmentation.py +286 -0
  127. ultralytics/models/sam/sam3/model_misc.py +199 -0
  128. ultralytics/models/sam/sam3/necks.py +129 -0
  129. ultralytics/models/sam/sam3/sam3_image.py +339 -0
  130. ultralytics/models/sam/sam3/text_encoder_ve.py +307 -0
  131. ultralytics/models/sam/sam3/vitdet.py +547 -0
  132. ultralytics/models/sam/sam3/vl_combiner.py +160 -0
  133. ultralytics/models/utils/loss.py +14 -26
  134. ultralytics/models/utils/ops.py +13 -17
  135. ultralytics/models/yolo/__init__.py +1 -1
  136. ultralytics/models/yolo/classify/predict.py +9 -12
  137. ultralytics/models/yolo/classify/train.py +15 -41
  138. ultralytics/models/yolo/classify/val.py +34 -32
  139. ultralytics/models/yolo/detect/predict.py +8 -11
  140. ultralytics/models/yolo/detect/train.py +13 -32
  141. ultralytics/models/yolo/detect/val.py +75 -63
  142. ultralytics/models/yolo/model.py +37 -53
  143. ultralytics/models/yolo/obb/predict.py +5 -14
  144. ultralytics/models/yolo/obb/train.py +11 -14
  145. ultralytics/models/yolo/obb/val.py +42 -39
  146. ultralytics/models/yolo/pose/__init__.py +1 -1
  147. ultralytics/models/yolo/pose/predict.py +7 -22
  148. ultralytics/models/yolo/pose/train.py +10 -22
  149. ultralytics/models/yolo/pose/val.py +40 -59
  150. ultralytics/models/yolo/segment/predict.py +16 -20
  151. ultralytics/models/yolo/segment/train.py +3 -12
  152. ultralytics/models/yolo/segment/val.py +106 -56
  153. ultralytics/models/yolo/world/train.py +12 -16
  154. ultralytics/models/yolo/world/train_world.py +11 -34
  155. ultralytics/models/yolo/yoloe/__init__.py +7 -7
  156. ultralytics/models/yolo/yoloe/predict.py +16 -23
  157. ultralytics/models/yolo/yoloe/train.py +31 -56
  158. ultralytics/models/yolo/yoloe/train_seg.py +5 -10
  159. ultralytics/models/yolo/yoloe/val.py +16 -21
  160. ultralytics/nn/__init__.py +7 -7
  161. ultralytics/nn/autobackend.py +152 -80
  162. ultralytics/nn/modules/__init__.py +60 -60
  163. ultralytics/nn/modules/activation.py +4 -6
  164. ultralytics/nn/modules/block.py +133 -217
  165. ultralytics/nn/modules/conv.py +52 -97
  166. ultralytics/nn/modules/head.py +64 -116
  167. ultralytics/nn/modules/transformer.py +79 -89
  168. ultralytics/nn/modules/utils.py +16 -21
  169. ultralytics/nn/tasks.py +111 -156
  170. ultralytics/nn/text_model.py +40 -67
  171. ultralytics/solutions/__init__.py +12 -12
  172. ultralytics/solutions/ai_gym.py +11 -17
  173. ultralytics/solutions/analytics.py +15 -16
  174. ultralytics/solutions/config.py +5 -6
  175. ultralytics/solutions/distance_calculation.py +10 -13
  176. ultralytics/solutions/heatmap.py +7 -13
  177. ultralytics/solutions/instance_segmentation.py +5 -8
  178. ultralytics/solutions/object_blurrer.py +7 -10
  179. ultralytics/solutions/object_counter.py +12 -19
  180. ultralytics/solutions/object_cropper.py +8 -14
  181. ultralytics/solutions/parking_management.py +33 -31
  182. ultralytics/solutions/queue_management.py +10 -12
  183. ultralytics/solutions/region_counter.py +9 -12
  184. ultralytics/solutions/security_alarm.py +15 -20
  185. ultralytics/solutions/similarity_search.py +13 -17
  186. ultralytics/solutions/solutions.py +75 -74
  187. ultralytics/solutions/speed_estimation.py +7 -10
  188. ultralytics/solutions/streamlit_inference.py +4 -7
  189. ultralytics/solutions/templates/similarity-search.html +7 -18
  190. ultralytics/solutions/trackzone.py +7 -10
  191. ultralytics/solutions/vision_eye.py +5 -8
  192. ultralytics/trackers/__init__.py +1 -1
  193. ultralytics/trackers/basetrack.py +3 -5
  194. ultralytics/trackers/bot_sort.py +10 -27
  195. ultralytics/trackers/byte_tracker.py +14 -30
  196. ultralytics/trackers/track.py +3 -6
  197. ultralytics/trackers/utils/gmc.py +11 -22
  198. ultralytics/trackers/utils/kalman_filter.py +37 -48
  199. ultralytics/trackers/utils/matching.py +12 -15
  200. ultralytics/utils/__init__.py +116 -116
  201. ultralytics/utils/autobatch.py +2 -4
  202. ultralytics/utils/autodevice.py +17 -18
  203. ultralytics/utils/benchmarks.py +70 -70
  204. ultralytics/utils/callbacks/base.py +8 -10
  205. ultralytics/utils/callbacks/clearml.py +5 -13
  206. ultralytics/utils/callbacks/comet.py +32 -46
  207. ultralytics/utils/callbacks/dvc.py +13 -18
  208. ultralytics/utils/callbacks/mlflow.py +4 -5
  209. ultralytics/utils/callbacks/neptune.py +7 -15
  210. ultralytics/utils/callbacks/platform.py +314 -38
  211. ultralytics/utils/callbacks/raytune.py +3 -4
  212. ultralytics/utils/callbacks/tensorboard.py +23 -31
  213. ultralytics/utils/callbacks/wb.py +10 -13
  214. ultralytics/utils/checks.py +151 -87
  215. ultralytics/utils/cpu.py +3 -8
  216. ultralytics/utils/dist.py +19 -15
  217. ultralytics/utils/downloads.py +29 -41
  218. ultralytics/utils/errors.py +6 -14
  219. ultralytics/utils/events.py +2 -4
  220. ultralytics/utils/export/__init__.py +7 -0
  221. ultralytics/utils/{export.py → export/engine.py} +16 -16
  222. ultralytics/utils/export/imx.py +325 -0
  223. ultralytics/utils/export/tensorflow.py +231 -0
  224. ultralytics/utils/files.py +24 -28
  225. ultralytics/utils/git.py +9 -11
  226. ultralytics/utils/instance.py +30 -51
  227. ultralytics/utils/logger.py +212 -114
  228. ultralytics/utils/loss.py +15 -24
  229. ultralytics/utils/metrics.py +131 -160
  230. ultralytics/utils/nms.py +21 -30
  231. ultralytics/utils/ops.py +107 -165
  232. ultralytics/utils/patches.py +33 -21
  233. ultralytics/utils/plotting.py +122 -119
  234. ultralytics/utils/tal.py +28 -44
  235. ultralytics/utils/torch_utils.py +70 -187
  236. ultralytics/utils/tqdm.py +20 -20
  237. ultralytics/utils/triton.py +13 -19
  238. ultralytics/utils/tuner.py +17 -5
  239. dgenerate_ultralytics_headless-8.3.196.dist-info/RECORD +0 -281
  240. {dgenerate_ultralytics_headless-8.3.196.dist-info → dgenerate_ultralytics_headless-8.3.248.dist-info}/WHEEL +0 -0
  241. {dgenerate_ultralytics_headless-8.3.196.dist-info → dgenerate_ultralytics_headless-8.3.248.dist-info}/entry_points.txt +0 -0
  242. {dgenerate_ultralytics_headless-8.3.196.dist-info → dgenerate_ultralytics_headless-8.3.248.dist-info}/licenses/LICENSE +0 -0
  243. {dgenerate_ultralytics_headless-8.3.196.dist-info → dgenerate_ultralytics_headless-8.3.248.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ that yield the best model performance. This is particularly crucial in deep lear
8
8
  where small changes in hyperparameters can lead to significant differences in model accuracy and efficiency.
9
9
 
10
10
  Examples:
11
- Tune hyperparameters for YOLO11n on COCO8 at imgsz=640 and epochs=30 for 300 tuning iterations.
11
+ Tune hyperparameters for YOLO11n on COCO8 at imgsz=640 and epochs=10 for 300 tuning iterations.
12
12
  >>> from ultralytics import YOLO
13
13
  >>> model = YOLO("yolo11n.pt")
14
14
  >>> model.tune(data="coco8.yaml", epochs=10, iterations=300, optimizer="AdamW", plots=False, save=False, val=False)
@@ -16,6 +16,7 @@ Examples:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
+ import gc
19
20
  import random
20
21
  import shutil
21
22
  import subprocess
@@ -23,6 +24,7 @@ import time
23
24
  from datetime import datetime
24
25
 
25
26
  import numpy as np
27
+ import torch
26
28
 
27
29
  from ultralytics.cfg import get_cfg, get_save_dir
28
30
  from ultralytics.utils import DEFAULT_CFG, LOGGER, YAML, callbacks, colorstr, remove_colorstr
@@ -32,12 +34,11 @@ from ultralytics.utils.plotting import plot_tune_results
32
34
 
33
35
 
34
36
  class Tuner:
35
- """
36
- A class for hyperparameter tuning of YOLO models.
37
+ """A class for hyperparameter tuning of YOLO models.
37
38
 
38
39
  The class evolves YOLO model hyperparameters over a given number of iterations by mutating them according to the
39
- search space and retraining the model to evaluate their performance. Supports both local CSV storage and
40
- distributed MongoDB Atlas coordination for multi-machine hyperparameter optimization.
40
+ search space and retraining the model to evaluate their performance. Supports both local CSV storage and distributed
41
+ MongoDB Atlas coordination for multi-machine hyperparameter optimization.
41
42
 
42
43
  Attributes:
43
44
  space (dict[str, tuple]): Hyperparameter search space containing bounds and scaling factors for mutation.
@@ -54,7 +55,7 @@ class Tuner:
54
55
  __call__: Execute the hyperparameter evolution across multiple iterations.
55
56
 
56
57
  Examples:
57
- Tune hyperparameters for YOLO11n on COCO8 at imgsz=640 and epochs=30 for 300 tuning iterations.
58
+ Tune hyperparameters for YOLO11n on COCO8 at imgsz=640 and epochs=10 for 300 tuning iterations.
58
59
  >>> from ultralytics import YOLO
59
60
  >>> model = YOLO("yolo11n.pt")
60
61
  >>> model.tune(
@@ -81,8 +82,7 @@ class Tuner:
81
82
  """
82
83
 
83
84
  def __init__(self, args=DEFAULT_CFG, _callbacks: list | None = None):
84
- """
85
- Initialize the Tuner with configurations.
85
+ """Initialize the Tuner with configurations.
86
86
 
87
87
  Args:
88
88
  args (dict): Configuration for hyperparameter evolution.
@@ -97,7 +97,7 @@ class Tuner:
97
97
  "warmup_epochs": (0.0, 5.0), # warmup epochs (fractions ok)
98
98
  "warmup_momentum": (0.0, 0.95), # warmup initial momentum
99
99
  "box": (1.0, 20.0), # box loss gain
100
- "cls": (0.2, 4.0), # cls loss gain (scale with pixels)
100
+ "cls": (0.1, 4.0), # cls loss gain (scale with pixels)
101
101
  "dfl": (0.4, 6.0), # dfl loss gain
102
102
  "hsv_h": (0.0, 0.1), # image HSV-Hue augmentation (fraction)
103
103
  "hsv_s": (0.0, 0.9), # image HSV-Saturation augmentation (fraction)
@@ -114,6 +114,7 @@ class Tuner:
114
114
  "mixup": (0.0, 1.0), # image mixup (probability)
115
115
  "cutmix": (0.0, 1.0), # image cutmix (probability)
116
116
  "copy_paste": (0.0, 1.0), # segment copy-paste (probability)
117
+ "close_mosaic": (0.0, 10.0), # close dataloader mosaic (epochs)
117
118
  }
118
119
  mongodb_uri = args.pop("mongodb_uri", None)
119
120
  mongodb_db = args.pop("mongodb_db", "ultralytics")
@@ -139,8 +140,7 @@ class Tuner:
139
140
  )
140
141
 
141
142
  def _connect(self, uri: str = "mongodb+srv://username:password@cluster.mongodb.net/", max_retries: int = 3):
142
- """
143
- Create MongoDB client with exponential backoff retry on connection failures.
143
+ """Create MongoDB client with exponential backoff retry on connection failures.
144
144
 
145
145
  Args:
146
146
  uri (str): MongoDB connection string with credentials and cluster information.
@@ -180,12 +180,10 @@ class Tuner:
180
180
  time.sleep(wait_time)
181
181
 
182
182
  def _init_mongodb(self, mongodb_uri="", mongodb_db="", mongodb_collection=""):
183
- """
184
- Initialize MongoDB connection for distributed tuning.
183
+ """Initialize MongoDB connection for distributed tuning.
185
184
 
186
- Connects to MongoDB Atlas for distributed hyperparameter optimization across multiple machines.
187
- Each worker saves results to a shared collection and reads the latest best hyperparameters
188
- from all workers for evolution.
185
+ Connects to MongoDB Atlas for distributed hyperparameter optimization across multiple machines. Each worker
186
+ saves results to a shared collection and reads the latest best hyperparameters from all workers for evolution.
189
187
 
190
188
  Args:
191
189
  mongodb_uri (str): MongoDB connection string, e.g. 'mongodb+srv://username:password@cluster.mongodb.net/'.
@@ -203,8 +201,7 @@ class Tuner:
203
201
  LOGGER.info(f"{self.prefix}Using MongoDB Atlas for distributed tuning")
204
202
 
205
203
  def _get_mongodb_results(self, n: int = 5) -> list:
206
- """
207
- Get top N results from MongoDB sorted by fitness.
204
+ """Get top N results from MongoDB sorted by fitness.
208
205
 
209
206
  Args:
210
207
  n (int): Number of top results to retrieve.
@@ -218,8 +215,7 @@ class Tuner:
218
215
  return []
219
216
 
220
217
  def _save_to_mongodb(self, fitness: float, hyperparameters: dict[str, float], metrics: dict, iteration: int):
221
- """
222
- Save results to MongoDB with proper type conversion.
218
+ """Save results to MongoDB with proper type conversion.
223
219
 
224
220
  Args:
225
221
  fitness (float): Fitness score achieved with these hyperparameters.
@@ -230,7 +226,7 @@ class Tuner:
230
226
  try:
231
227
  self.collection.insert_one(
232
228
  {
233
- "fitness": float(fitness),
229
+ "fitness": fitness,
234
230
  "hyperparameters": {k: (v.item() if hasattr(v, "item") else v) for k, v in hyperparameters.items()},
235
231
  "metrics": metrics,
236
232
  "timestamp": datetime.now(),
@@ -241,8 +237,7 @@ class Tuner:
241
237
  LOGGER.warning(f"{self.prefix}MongoDB save failed: {e}")
242
238
 
243
239
  def _sync_mongodb_to_csv(self):
244
- """
245
- Sync MongoDB results to CSV for plotting compatibility.
240
+ """Sync MongoDB results to CSV for plotting compatibility.
246
241
 
247
242
  Downloads all results from MongoDB and writes them to the local CSV file in chronological order. This enables
248
243
  the existing plotting functions to work seamlessly with distributed MongoDB data.
@@ -254,31 +249,42 @@ class Tuner:
254
249
  return
255
250
 
256
251
  # Write to CSV
257
- headers = ",".join(["fitness"] + list(self.space.keys())) + "\n"
252
+ headers = ",".join(["fitness", *list(self.space.keys())]) + "\n"
258
253
  with open(self.tune_csv, "w", encoding="utf-8") as f:
259
254
  f.write(headers)
260
255
  for result in all_results:
261
256
  fitness = result["fitness"]
262
257
  hyp_values = [result["hyperparameters"][k] for k in self.space.keys()]
263
- log_row = [round(fitness, 5)] + hyp_values
258
+ log_row = [round(fitness, 5), *hyp_values]
264
259
  f.write(",".join(map(str, log_row)) + "\n")
265
260
 
266
261
  except Exception as e:
267
262
  LOGGER.warning(f"{self.prefix}MongoDB to CSV sync failed: {e}")
268
263
 
264
+ @staticmethod
265
+ def _crossover(x: np.ndarray, alpha: float = 0.2, k: int = 9) -> np.ndarray:
266
+ """BLX-α crossover from up to top-k parents (x[:,0]=fitness, rest=genes)."""
267
+ k = min(k, len(x))
268
+ # fitness weights (shifted to >0); fallback to uniform if degenerate
269
+ weights = x[:, 0] - x[:, 0].min() + 1e-6
270
+ if not np.isfinite(weights).all() or weights.sum() == 0:
271
+ weights = np.ones_like(weights)
272
+ idxs = random.choices(range(len(x)), weights=weights, k=k)
273
+ parents_mat = np.stack([x[i][1:] for i in idxs], 0) # (k, ng) strip fitness
274
+ lo, hi = parents_mat.min(0), parents_mat.max(0)
275
+ span = hi - lo
276
+ return np.random.uniform(lo - alpha * span, hi + alpha * span)
277
+
269
278
  def _mutate(
270
279
  self,
271
- parent: str = "single",
272
- n: int = 5,
273
- mutation: float = 0.8,
280
+ n: int = 9,
281
+ mutation: float = 0.5,
274
282
  sigma: float = 0.2,
275
283
  ) -> dict[str, float]:
276
- """
277
- Mutate hyperparameters based on bounds and scaling factors specified in `self.space`.
284
+ """Mutate hyperparameters based on bounds and scaling factors specified in `self.space`.
278
285
 
279
286
  Args:
280
- parent (str): Parent selection method: 'single' or 'weighted'.
281
- n (int): Number of parents to consider.
287
+ n (int): Number of top parents to consider.
282
288
  mutation (float): Probability of a parameter mutation in any given iteration.
283
289
  sigma (float): Standard deviation for Gaussian random number generator.
284
290
 
@@ -289,51 +295,51 @@ class Tuner:
289
295
 
290
296
  # Try MongoDB first if available
291
297
  if self.mongodb:
292
- results = self._get_mongodb_results(n)
293
- if results:
298
+ if results := self._get_mongodb_results(n):
294
299
  # MongoDB already sorted by fitness DESC, so results[0] is best
295
300
  x = np.array([[r["fitness"]] + [r["hyperparameters"][k] for k in self.space.keys()] for r in results])
296
- n = min(n, len(x))
301
+ elif self.collection.name in self.collection.database.list_collection_names(): # Tuner started elsewhere
302
+ x = np.array([[0.0] + [getattr(self.args, k) for k in self.space.keys()]])
297
303
 
298
304
  # Fall back to CSV if MongoDB unavailable or empty
299
305
  if x is None and self.tune_csv.exists():
300
306
  csv_data = np.loadtxt(self.tune_csv, ndmin=2, delimiter=",", skiprows=1)
301
307
  if len(csv_data) > 0:
302
308
  fitness = csv_data[:, 0] # first column
303
- n = min(n, len(csv_data))
304
- x = csv_data[np.argsort(-fitness)][:n] # top n sorted by fitness DESC
309
+ order = np.argsort(-fitness)
310
+ x = csv_data[order][:n] # top-n sorted by fitness DESC
305
311
 
306
312
  # Mutate if we have data, otherwise use defaults
307
313
  if x is not None:
308
- w = x[:, 0] - x[:, 0].min() + 1e-6 # weights (sum > 0)
309
- if parent == "single" or len(x) <= 1:
310
- x = x[random.choices(range(n), weights=w)[0]] # weighted selection
311
- elif parent == "weighted":
312
- x = (x * w.reshape(n, 1)).sum(0) / w.sum() # weighted combination
313
-
314
- # Mutate
315
- r = np.random
316
- r.seed(int(time.time()))
317
- g = np.array([v[2] if len(v) == 3 else 1.0 for v in self.space.values()]) # gains 0-1
314
+ np.random.seed(int(time.time()))
318
315
  ng = len(self.space)
319
- v = np.ones(ng)
320
- while all(v == 1): # mutate until a change occurs (prevent duplicates)
321
- v = (g * (r.random(ng) < mutation) * r.randn(ng) * r.random() * sigma + 1).clip(0.3, 3.0)
322
- hyp = {k: float(x[i + 1] * v[i]) for i, k in enumerate(self.space.keys())}
316
+
317
+ # Crossover
318
+ genes = self._crossover(x)
319
+
320
+ # Mutation
321
+ gains = np.array([v[2] if len(v) == 3 else 1.0 for v in self.space.values()]) # gains 0-1
322
+ factors = np.ones(ng)
323
+ while np.all(factors == 1): # mutate until a change occurs (prevent duplicates)
324
+ mask = np.random.random(ng) < mutation
325
+ step = np.random.randn(ng) * (sigma * gains)
326
+ factors = np.where(mask, np.exp(step), 1.0).clip(0.25, 4.0)
327
+ hyp = {k: float(genes[i] * factors[i]) for i, k in enumerate(self.space.keys())}
323
328
  else:
324
329
  hyp = {k: getattr(self.args, k) for k in self.space.keys()}
325
330
 
326
331
  # Constrain to limits
327
332
  for k, bounds in self.space.items():
328
- hyp[k] = max(hyp[k], bounds[0]) # lower limit
329
- hyp[k] = min(hyp[k], bounds[1]) # upper limit
330
- hyp[k] = round(hyp[k], 5) # significant digits
333
+ hyp[k] = round(min(max(hyp[k], bounds[0]), bounds[1]), 5)
334
+
335
+ # Update types
336
+ if "close_mosaic" in hyp:
337
+ hyp["close_mosaic"] = round(hyp["close_mosaic"])
331
338
 
332
339
  return hyp
333
340
 
334
341
  def __call__(self, model=None, iterations: int = 10, cleanup: bool = True):
335
- """
336
- Execute the hyperparameter evolution process when the Tuner instance is called.
342
+ """Execute the hyperparameter evolution process when the Tuner instance is called.
337
343
 
338
344
  This method iterates through the specified number of iterations, performing the following steps:
339
345
  1. Sync MongoDB results to CSV (if using distributed mode)
@@ -361,8 +367,12 @@ class Tuner:
361
367
  start = x.shape[0]
362
368
  LOGGER.info(f"{self.prefix}Resuming tuning run {self.tune_dir} from iteration {start + 1}...")
363
369
  for i in range(start, iterations):
370
+ # Linearly decay sigma from 0.2 → 0.1 over first 300 iterations
371
+ frac = min(i / 300.0, 1.0)
372
+ sigma_i = 0.2 - 0.1 * frac
373
+
364
374
  # Mutate hyperparameters
365
- mutated_hyp = self._mutate()
375
+ mutated_hyp = self._mutate(sigma=sigma_i)
366
376
  LOGGER.info(f"{self.prefix}Starting iteration {i + 1}/{iterations} with hyperparameters: {mutated_hyp}")
367
377
 
368
378
  metrics = {}
@@ -378,6 +388,11 @@ class Tuner:
378
388
  metrics = torch_load(ckpt_file)["train_metrics"]
379
389
  assert return_code == 0, "training failed"
380
390
 
391
+ # Cleanup
392
+ time.sleep(1)
393
+ gc.collect()
394
+ torch.cuda.empty_cache()
395
+
381
396
  except Exception as e:
382
397
  LOGGER.error(f"training failure for hyperparameter tuning iteration {i + 1}\n{e}")
383
398
 
@@ -395,7 +410,7 @@ class Tuner:
395
410
  else:
396
411
  # Save to CSV only if no MongoDB
397
412
  log_row = [round(fitness, 5)] + [mutated_hyp[k] for k in self.space.keys()]
398
- headers = "" if self.tune_csv.exists() else (",".join(["fitness"] + list(self.space.keys())) + "\n")
413
+ headers = "" if self.tune_csv.exists() else (",".join(["fitness", *list(self.space.keys())]) + "\n")
399
414
  with open(self.tune_csv, "a", encoding="utf-8") as f:
400
415
  f.write(headers + ",".join(map(str, log_row)) + "\n")
401
416
 
@@ -405,12 +420,12 @@ class Tuner:
405
420
  best_idx = fitness.argmax()
406
421
  best_is_current = best_idx == i
407
422
  if best_is_current:
408
- best_save_dir = save_dir
423
+ best_save_dir = str(save_dir)
409
424
  best_metrics = {k: round(v, 5) for k, v in metrics.items()}
410
425
  for ckpt in weights_dir.glob("*.pt"):
411
426
  shutil.copy2(ckpt, self.tune_dir / "weights")
412
- elif cleanup:
413
- shutil.rmtree(weights_dir, ignore_errors=True) # remove iteration weights/ dir to reduce storage space
427
+ elif cleanup and best_save_dir:
428
+ shutil.rmtree(best_save_dir, ignore_errors=True) # remove iteration dirs to reduce storage space
414
429
 
415
430
  # Plot tune results
416
431
  plot_tune_results(str(self.tune_csv))
@@ -421,8 +436,7 @@ class Tuner:
421
436
  f"{self.prefix}Results saved to {colorstr('bold', self.tune_dir)}\n"
422
437
  f"{self.prefix}Best fitness={fitness[best_idx]} observed at iteration {best_idx + 1}\n"
423
438
  f"{self.prefix}Best fitness metrics are {best_metrics}\n"
424
- f"{self.prefix}Best fitness model is {best_save_dir}\n"
425
- f"{self.prefix}Best fitness hyperparameters are printed below.\n"
439
+ f"{self.prefix}Best fitness model is {best_save_dir}"
426
440
  )
427
441
  LOGGER.info("\n" + header)
428
442
  data = {k: float(x[best_idx, i + 1]) for i, k in enumerate(self.space.keys())}
@@ -29,26 +29,26 @@ from pathlib import Path
29
29
 
30
30
  import numpy as np
31
31
  import torch
32
+ import torch.distributed as dist
32
33
 
33
34
  from ultralytics.cfg import get_cfg, get_save_dir
34
35
  from ultralytics.data.utils import check_cls_dataset, check_det_dataset
35
36
  from ultralytics.nn.autobackend import AutoBackend
36
- from ultralytics.utils import LOGGER, TQDM, callbacks, colorstr, emojis
37
+ from ultralytics.utils import LOGGER, RANK, TQDM, callbacks, colorstr, emojis
37
38
  from ultralytics.utils.checks import check_imgsz
38
39
  from ultralytics.utils.ops import Profile
39
40
  from ultralytics.utils.torch_utils import attempt_compile, select_device, smart_inference_mode, unwrap_model
40
41
 
41
42
 
42
43
  class BaseValidator:
43
- """
44
- A base class for creating validators.
44
+ """A base class for creating validators.
45
45
 
46
46
  This class provides the foundation for validation processes, including model evaluation, metric computation, and
47
47
  result visualization.
48
48
 
49
49
  Attributes:
50
50
  args (SimpleNamespace): Configuration for the validator.
51
- dataloader (DataLoader): Dataloader to use for validation.
51
+ dataloader (DataLoader): DataLoader to use for validation.
52
52
  model (nn.Module): Model to validate.
53
53
  data (dict): Data dictionary containing dataset information.
54
54
  device (torch.device): Device to use for validation.
@@ -61,8 +61,8 @@ class BaseValidator:
61
61
  nc (int): Number of classes.
62
62
  iouv (torch.Tensor): IoU thresholds from 0.50 to 0.95 in spaces of 0.05.
63
63
  jdict (list): List to store JSON validation results.
64
- speed (dict): Dictionary with keys 'preprocess', 'inference', 'loss', 'postprocess' and their respective
65
- batch processing times in milliseconds.
64
+ speed (dict): Dictionary with keys 'preprocess', 'inference', 'loss', 'postprocess' and their respective batch
65
+ processing times in milliseconds.
66
66
  save_dir (Path): Directory to save results.
67
67
  plots (dict): Dictionary to store plots for visualization.
68
68
  callbacks (dict): Dictionary to store various callback functions.
@@ -92,11 +92,10 @@ class BaseValidator:
92
92
  """
93
93
 
94
94
  def __init__(self, dataloader=None, save_dir=None, args=None, _callbacks=None):
95
- """
96
- Initialize a BaseValidator instance.
95
+ """Initialize a BaseValidator instance.
97
96
 
98
97
  Args:
99
- dataloader (torch.utils.data.DataLoader, optional): Dataloader to be used for validation.
98
+ dataloader (torch.utils.data.DataLoader, optional): DataLoader to be used for validation.
100
99
  save_dir (Path, optional): Directory to save results.
101
100
  args (SimpleNamespace, optional): Configuration for the validator.
102
101
  _callbacks (dict, optional): Dictionary to store various callback functions.
@@ -130,8 +129,7 @@ class BaseValidator:
130
129
 
131
130
  @smart_inference_mode()
132
131
  def __call__(self, trainer=None, model=None):
133
- """
134
- Execute validation process, running inference on dataloader and computing performance metrics.
132
+ """Execute validation process, running inference on dataloader and computing performance metrics.
135
133
 
136
134
  Args:
137
135
  trainer (object, optional): Trainer object that contains the model to validate.
@@ -160,7 +158,7 @@ class BaseValidator:
160
158
  callbacks.add_integration_callbacks(self)
161
159
  model = AutoBackend(
162
160
  model=model or self.args.model,
163
- device=select_device(self.args.device, self.args.batch),
161
+ device=select_device(self.args.device) if RANK == -1 else torch.device("cuda", RANK),
164
162
  dnn=self.args.dnn,
165
163
  data=self.args.data,
166
164
  fp16=self.args.half,
@@ -223,21 +221,34 @@ class BaseValidator:
223
221
  preds = self.postprocess(preds)
224
222
 
225
223
  self.update_metrics(preds, batch)
226
- if self.args.plots and batch_i < 3:
224
+ if self.args.plots and batch_i < 3 and RANK in {-1, 0}:
227
225
  self.plot_val_samples(batch, batch_i)
228
226
  self.plot_predictions(batch, preds, batch_i)
229
227
 
230
228
  self.run_callbacks("on_val_batch_end")
231
- stats = self.get_stats()
232
- self.speed = dict(zip(self.speed.keys(), (x.t / len(self.dataloader.dataset) * 1e3 for x in dt)))
233
- self.finalize_metrics()
234
- self.print_results()
235
- self.run_callbacks("on_val_end")
229
+
230
+ stats = {}
231
+ self.gather_stats()
232
+ if RANK in {-1, 0}:
233
+ stats = self.get_stats()
234
+ self.speed = dict(zip(self.speed.keys(), (x.t / len(self.dataloader.dataset) * 1e3 for x in dt)))
235
+ self.finalize_metrics()
236
+ self.print_results()
237
+ self.run_callbacks("on_val_end")
238
+
236
239
  if self.training:
237
240
  model.float()
238
- results = {**stats, **trainer.label_loss_items(self.loss.cpu() / len(self.dataloader), prefix="val")}
241
+ # Reduce loss across all GPUs
242
+ loss = self.loss.clone().detach()
243
+ if trainer.world_size > 1:
244
+ dist.reduce(loss, dst=0, op=dist.ReduceOp.AVG)
245
+ if RANK > 0:
246
+ return
247
+ results = {**stats, **trainer.label_loss_items(loss.cpu() / len(self.dataloader), prefix="val")}
239
248
  return {k: round(float(v), 5) for k, v in results.items()} # return results as 5 decimal place floats
240
249
  else:
250
+ if RANK > 0:
251
+ return stats
241
252
  LOGGER.info(
242
253
  "Speed: {:.1f}ms preprocess, {:.1f}ms inference, {:.1f}ms loss, {:.1f}ms postprocess per image".format(
243
254
  *tuple(self.speed.values())
@@ -255,8 +266,7 @@ class BaseValidator:
255
266
  def match_predictions(
256
267
  self, pred_classes: torch.Tensor, true_classes: torch.Tensor, iou: torch.Tensor, use_scipy: bool = False
257
268
  ) -> torch.Tensor:
258
- """
259
- Match predictions to ground truth objects using IoU.
269
+ """Match predictions to ground truth objects using IoU.
260
270
 
261
271
  Args:
262
272
  pred_classes (torch.Tensor): Predicted class indices of shape (N,).
@@ -336,6 +346,10 @@ class BaseValidator:
336
346
  """Return statistics about the model's performance."""
337
347
  return {}
338
348
 
349
+ def gather_stats(self):
350
+ """Gather statistics from all the GPUs during DDP training to GPU 0."""
351
+ pass
352
+
339
353
  def print_results(self):
340
354
  """Print the results of the model's predictions."""
341
355
  pass
@@ -350,7 +364,10 @@ class BaseValidator:
350
364
  return []
351
365
 
352
366
  def on_plot(self, name, data=None):
353
- """Register plots for visualization."""
367
+ """Register plots for visualization, deduplicating by type."""
368
+ plot_type = data.get("type") if data else None
369
+ if plot_type and any((v.get("data") or {}).get("type") == plot_type for v in self.plots.values()):
370
+ return # Skip duplicate plot types
354
371
  self.plots[Path(name)] = {"data": data, "timestamp": time.time()}
355
372
 
356
373
  def plot_val_samples(self, batch, ni):
@@ -1,5 +1,7 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from ultralytics.data.utils import HUBDatasetStats
4
6
  from ultralytics.hub.auth import Auth
5
7
  from ultralytics.hub.session import HUBTrainingSession
@@ -7,29 +9,28 @@ from ultralytics.hub.utils import HUB_API_ROOT, HUB_WEB_ROOT, PREFIX
7
9
  from ultralytics.utils import LOGGER, SETTINGS, checks
8
10
 
9
11
  __all__ = (
10
- "PREFIX",
11
12
  "HUB_WEB_ROOT",
13
+ "PREFIX",
12
14
  "HUBTrainingSession",
13
- "login",
14
- "logout",
15
- "reset_model",
15
+ "check_dataset",
16
16
  "export_fmts_hub",
17
17
  "export_model",
18
18
  "get_export",
19
- "check_dataset",
19
+ "login",
20
+ "logout",
21
+ "reset_model",
20
22
  )
21
23
 
22
24
 
23
- def login(api_key: str = None, save: bool = True) -> bool:
24
- """
25
- Log in to the Ultralytics HUB API using the provided API key.
25
+ def login(api_key: str | None = None, save: bool = True) -> bool:
26
+ """Log in to the Ultralytics HUB API using the provided API key.
26
27
 
27
28
  The session is not stored; a new session is created when needed using the saved SETTINGS or the HUB_API_KEY
28
29
  environment variable if successfully authenticated.
29
30
 
30
31
  Args:
31
- api_key (str, optional): API key to use for authentication. If not provided, it will be retrieved from
32
- SETTINGS or HUB_API_KEY environment variable.
32
+ api_key (str, optional): API key to use for authentication. If not provided, it will be retrieved from SETTINGS
33
+ or HUB_API_KEY environment variable.
33
34
  save (bool, optional): Whether to save the API key to SETTINGS if authentication is successful.
34
35
 
35
36
  Returns:
@@ -85,12 +86,11 @@ def export_fmts_hub():
85
86
  """Return a list of HUB-supported export formats."""
86
87
  from ultralytics.engine.exporter import export_formats
87
88
 
88
- return list(export_formats()["Argument"][1:]) + ["ultralytics_tflite", "ultralytics_coreml"]
89
+ return [*list(export_formats()["Argument"][1:]), "ultralytics_tflite", "ultralytics_coreml"]
89
90
 
90
91
 
91
92
  def export_model(model_id: str = "", format: str = "torchscript"):
92
- """
93
- Export a model to a specified format for deployment via the Ultralytics HUB API.
93
+ """Export a model to a specified format for deployment via the Ultralytics HUB API.
94
94
 
95
95
  Args:
96
96
  model_id (str): The ID of the model to export. An empty string will use the default model.
@@ -115,13 +115,11 @@ def export_model(model_id: str = "", format: str = "torchscript"):
115
115
 
116
116
 
117
117
  def get_export(model_id: str = "", format: str = "torchscript"):
118
- """
119
- Retrieve an exported model in the specified format from Ultralytics HUB using the model ID.
118
+ """Retrieve an exported model in the specified format from Ultralytics HUB using the model ID.
120
119
 
121
120
  Args:
122
121
  model_id (str): The ID of the model to retrieve from Ultralytics HUB.
123
- format (str): The export format to retrieve. Must be one of the supported formats returned by
124
- export_fmts_hub().
122
+ format (str): The export format to retrieve. Must be one of the supported formats returned by export_fmts_hub().
125
123
 
126
124
  Returns:
127
125
  (dict): JSON response containing the exported model information.
@@ -146,8 +144,7 @@ def get_export(model_id: str = "", format: str = "torchscript"):
146
144
 
147
145
 
148
146
  def check_dataset(path: str, task: str) -> None:
149
- """
150
- Check HUB dataset Zip file for errors before upload.
147
+ """Check HUB dataset Zip file for errors before upload.
151
148
 
152
149
  Args:
153
150
  path (str): Path to data.zip (with data.yaml inside data.zip).
ultralytics/hub/auth.py CHANGED
@@ -7,8 +7,7 @@ API_KEY_URL = f"{HUB_WEB_ROOT}/settings?tab=api+keys"
7
7
 
8
8
 
9
9
  class Auth:
10
- """
11
- Manages authentication processes including API key handling, cookie-based authentication, and header generation.
10
+ """Manages authentication processes including API key handling, cookie-based authentication, and header generation.
12
11
 
13
12
  The class supports different methods of authentication:
14
13
  1. Directly using an API key.
@@ -37,8 +36,7 @@ class Auth:
37
36
  id_token = api_key = model_key = False
38
37
 
39
38
  def __init__(self, api_key: str = "", verbose: bool = False):
40
- """
41
- Initialize Auth class and authenticate user.
39
+ """Initialize Auth class and authenticate user.
42
40
 
43
41
  Handles API key validation, Google Colab authentication, and new key requests. Updates SETTINGS upon successful
44
42
  authentication.
@@ -82,8 +80,7 @@ class Auth:
82
80
  LOGGER.info(f"{PREFIX}Get API key from {API_KEY_URL} and then run 'yolo login API_KEY'")
83
81
 
84
82
  def request_api_key(self, max_attempts: int = 3) -> bool:
85
- """
86
- Prompt the user to input their API key.
83
+ """Prompt the user to input their API key.
87
84
 
88
85
  Args:
89
86
  max_attempts (int): Maximum number of authentication attempts.
@@ -102,8 +99,7 @@ class Auth:
102
99
  raise ConnectionError(emojis(f"{PREFIX}Failed to authenticate ❌"))
103
100
 
104
101
  def authenticate(self) -> bool:
105
- """
106
- Attempt to authenticate with the server using either id_token or API key.
102
+ """Attempt to authenticate with the server using either id_token or API key.
107
103
 
108
104
  Returns:
109
105
  (bool): True if authentication is successful, False otherwise.
@@ -123,8 +119,7 @@ class Auth:
123
119
  return False
124
120
 
125
121
  def auth_with_cookies(self) -> bool:
126
- """
127
- Attempt to fetch authentication via cookies and set id_token.
122
+ """Attempt to fetch authentication via cookies and set id_token.
128
123
 
129
124
  User must be logged in to HUB and running in a supported browser.
130
125
 
@@ -145,8 +140,7 @@ class Auth:
145
140
  return False
146
141
 
147
142
  def get_auth_header(self):
148
- """
149
- Get the authentication header for making API requests.
143
+ """Get the authentication header for making API requests.
150
144
 
151
145
  Returns:
152
146
  (dict | None): The authentication header if id_token or API key is set, None otherwise.
@@ -8,11 +8,10 @@ import time
8
8
 
9
9
 
10
10
  class GCPRegions:
11
- """
12
- A class for managing and analyzing Google Cloud Platform (GCP) regions.
11
+ """A class for managing and analyzing Google Cloud Platform (GCP) regions.
13
12
 
14
- This class provides functionality to initialize, categorize, and analyze GCP regions based on their
15
- geographical location, tier classification, and network latency.
13
+ This class provides functionality to initialize, categorize, and analyze GCP regions based on their geographical
14
+ location, tier classification, and network latency.
16
15
 
17
16
  Attributes:
18
17
  regions (dict[str, tuple[int, str, str]]): A dictionary of GCP regions with their tier, city, and country.
@@ -82,8 +81,7 @@ class GCPRegions:
82
81
 
83
82
  @staticmethod
84
83
  def _ping_region(region: str, attempts: int = 1) -> tuple[str, float, float, float, float]:
85
- """
86
- Ping a specified GCP region and measure network latency statistics.
84
+ """Ping a specified GCP region and measure network latency statistics.
87
85
 
88
86
  Args:
89
87
  region (str): The GCP region identifier to ping (e.g., 'us-central1').
@@ -126,8 +124,7 @@ class GCPRegions:
126
124
  tier: int | None = None,
127
125
  attempts: int = 1,
128
126
  ) -> list[tuple[str, float, float, float, float]]:
129
- """
130
- Determine the GCP regions with the lowest latency based on ping tests.
127
+ """Determine the GCP regions with the lowest latency based on ping tests.
131
128
 
132
129
  Args:
133
130
  top (int, optional): Number of top regions to return.
@@ -136,8 +133,8 @@ class GCPRegions:
136
133
  attempts (int, optional): Number of ping attempts per region.
137
134
 
138
135
  Returns:
139
- (list[tuple[str, float, float, float, float]]): List of tuples containing region information and
140
- latency statistics. Each tuple contains (region, mean_latency, std_dev, min_latency, max_latency).
136
+ (list[tuple[str, float, float, float, float]]): List of tuples containing region information and latency
137
+ statistics. Each tuple contains (region, mean_latency, std_dev, min_latency, max_latency).
141
138
 
142
139
  Examples:
143
140
  >>> regions = GCPRegions()