ultralytics 8.1.28__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 +527 -67
  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 +44 -37
  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 +84 -56
  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.28.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.28.dist-info/METADATA +0 -373
  244. ultralytics-8.1.28.dist-info/RECORD +0 -197
  245. {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/LICENSE +0 -0
  246. {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/entry_points.txt +0 -0
  247. {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # Ultralytics YOLO 🚀, AGPL-3.0 license
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
3
  import itertools
4
4
  from glob import glob
@@ -13,21 +13,29 @@ from tqdm import tqdm
13
13
  from ultralytics.data.utils import exif_size, img2label_paths
14
14
  from ultralytics.utils.checks import check_requirements
15
15
 
16
- check_requirements("shapely")
17
- from shapely.geometry import Polygon
18
-
19
16
 
20
17
  def bbox_iof(polygon1, bbox2, eps=1e-6):
21
18
  """
22
- Calculate iofs between bbox1 and bbox2.
19
+ Calculate Intersection over Foreground (IoF) between polygons and bounding boxes.
23
20
 
24
21
  Args:
25
- polygon1 (np.ndarray): Polygon coordinates, (n, 8).
26
- bbox2 (np.ndarray): Bounding boxes, (n ,4).
22
+ polygon1 (np.ndarray): Polygon coordinates, shape (n, 8).
23
+ bbox2 (np.ndarray): Bounding boxes, shape (n, 4).
24
+ eps (float, optional): Small value to prevent division by zero. Defaults to 1e-6.
25
+
26
+ Returns:
27
+ (np.ndarray): IoF scores, shape (n, 1) or (n, m) if bbox2 is (m, 4).
28
+
29
+ Note:
30
+ Polygon format: [x1, y1, x2, y2, x3, y3, x4, y4].
31
+ Bounding box format: [x_min, y_min, x_max, y_max].
27
32
  """
33
+ check_requirements("shapely")
34
+ from shapely.geometry import Polygon
35
+
28
36
  polygon1 = polygon1.reshape(-1, 4, 2)
29
- lt_point = np.min(polygon1, axis=-2)
30
- rb_point = np.max(polygon1, axis=-2)
37
+ lt_point = np.min(polygon1, axis=-2) # left-top
38
+ rb_point = np.max(polygon1, axis=-2) # right-bottom
31
39
  bbox1 = np.concatenate([lt_point, rb_point], axis=-1)
32
40
 
33
41
  lt = np.maximum(bbox1[:, None, :2], bbox2[..., :2])
@@ -35,8 +43,8 @@ def bbox_iof(polygon1, bbox2, eps=1e-6):
35
43
  wh = np.clip(rb - lt, 0, np.inf)
36
44
  h_overlaps = wh[..., 0] * wh[..., 1]
37
45
 
38
- l, t, r, b = (bbox2[..., i] for i in range(4))
39
- polygon2 = np.stack([l, t, r, t, r, b, l, b], axis=-1).reshape(-1, 4, 2)
46
+ left, top, right, bottom = (bbox2[..., i] for i in range(4))
47
+ polygon2 = np.stack([left, top, right, top, right, bottom, left, bottom], axis=-1).reshape(-1, 4, 2)
40
48
 
41
49
  sg_polys1 = [Polygon(p) for p in polygon1]
42
50
  sg_polys2 = [Polygon(p) for p in polygon2]
@@ -59,7 +67,7 @@ def load_yolo_dota(data_root, split="train"):
59
67
 
60
68
  Args:
61
69
  data_root (str): Data root.
62
- split (str): The split data set, could be train or val.
70
+ split (str): The split data set, could be `train` or `val`.
63
71
 
64
72
  Notes:
65
73
  The directory structure assumed for the DOTA dataset:
@@ -71,7 +79,7 @@ def load_yolo_dota(data_root, split="train"):
71
79
  - train
72
80
  - val
73
81
  """
74
- assert split in ["train", "val"]
82
+ assert split in {"train", "val"}, f"Split must be 'train' or 'val', not {split}."
75
83
  im_dir = Path(data_root) / "images" / split
76
84
  assert im_dir.exists(), f"Can't find {im_dir}, please check your data root."
77
85
  im_files = glob(str(Path(data_root) / "images" / split / "*"))
@@ -86,7 +94,7 @@ def load_yolo_dota(data_root, split="train"):
86
94
  return annos
87
95
 
88
96
 
89
- def get_windows(im_size, crop_sizes=[1024], gaps=[200], im_rate_thr=0.6, eps=0.01):
97
+ def get_windows(im_size, crop_sizes=(1024,), gaps=(200,), im_rate_thr=0.6, eps=0.01):
90
98
  """
91
99
  Get the coordinates of windows.
92
100
 
@@ -95,6 +103,7 @@ def get_windows(im_size, crop_sizes=[1024], gaps=[200], im_rate_thr=0.6, eps=0.0
95
103
  crop_sizes (List(int)): Crop size of windows.
96
104
  gaps (List(int)): Gap between crops.
97
105
  im_rate_thr (float): Threshold of windows areas divided by image ares.
106
+ eps (float): Epsilon value for math operations.
98
107
  """
99
108
  h, w = im_size
100
109
  windows = []
@@ -143,7 +152,7 @@ def get_window_obj(anno, windows, iof_thr=0.7):
143
152
  return [np.zeros((0, 9), dtype=np.float32) for _ in range(len(windows))] # window_anns
144
153
 
145
154
 
146
- def crop_and_save(anno, windows, window_objs, im_dir, lb_dir):
155
+ def crop_and_save(anno, windows, window_objs, im_dir, lb_dir, allow_background_images=True):
147
156
  """
148
157
  Crop images and save new labels.
149
158
 
@@ -153,6 +162,7 @@ def crop_and_save(anno, windows, window_objs, im_dir, lb_dir):
153
162
  window_objs (list): A list of labels inside each window.
154
163
  im_dir (str): The output directory path of images.
155
164
  lb_dir (str): The output directory path of labels.
165
+ allow_background_images (bool): Whether to include background images without labels.
156
166
 
157
167
  Notes:
158
168
  The directory structure assumed for the DOTA dataset:
@@ -172,22 +182,22 @@ def crop_and_save(anno, windows, window_objs, im_dir, lb_dir):
172
182
  patch_im = im[y_start:y_stop, x_start:x_stop]
173
183
  ph, pw = patch_im.shape[:2]
174
184
 
175
- cv2.imwrite(str(Path(im_dir) / f"{new_name}.jpg"), patch_im)
176
185
  label = window_objs[i]
177
- if len(label) == 0:
178
- continue
179
- label[:, 1::2] -= x_start
180
- label[:, 2::2] -= y_start
181
- label[:, 1::2] /= pw
182
- label[:, 2::2] /= ph
186
+ if len(label) or allow_background_images:
187
+ cv2.imwrite(str(Path(im_dir) / f"{new_name}.jpg"), patch_im)
188
+ if len(label):
189
+ label[:, 1::2] -= x_start
190
+ label[:, 2::2] -= y_start
191
+ label[:, 1::2] /= pw
192
+ label[:, 2::2] /= ph
183
193
 
184
- with open(Path(lb_dir) / f"{new_name}.txt", "w") as f:
185
- for lb in label:
186
- formatted_coords = ["{:.6g}".format(coord) for coord in lb[1:]]
187
- f.write(f"{int(lb[0])} {' '.join(formatted_coords)}\n")
194
+ with open(Path(lb_dir) / f"{new_name}.txt", "w") as f:
195
+ for lb in label:
196
+ formatted_coords = [f"{coord:.6g}" for coord in lb[1:]]
197
+ f.write(f"{int(lb[0])} {' '.join(formatted_coords)}\n")
188
198
 
189
199
 
190
- def split_images_and_labels(data_root, save_dir, split="train", crop_sizes=[1024], gaps=[200]):
200
+ def split_images_and_labels(data_root, save_dir, split="train", crop_sizes=(1024,), gaps=(200,)):
191
201
  """
192
202
  Split both images and labels.
193
203
 
@@ -217,7 +227,7 @@ def split_images_and_labels(data_root, save_dir, split="train", crop_sizes=[1024
217
227
  crop_and_save(anno, windows, window_objs, str(im_dir), str(lb_dir))
218
228
 
219
229
 
220
- def split_trainval(data_root, save_dir, crop_size=1024, gap=200, rates=[1.0]):
230
+ def split_trainval(data_root, save_dir, crop_size=1024, gap=200, rates=(1.0,)):
221
231
  """
222
232
  Split train and val set of DOTA.
223
233
 
@@ -247,7 +257,7 @@ def split_trainval(data_root, save_dir, crop_size=1024, gap=200, rates=[1.0]):
247
257
  split_images_and_labels(data_root, save_dir, split, crop_sizes, gaps)
248
258
 
249
259
 
250
- def split_test(data_root, save_dir, crop_size=1024, gap=200, rates=[1.0]):
260
+ def split_test(data_root, save_dir, crop_size=1024, gap=200, rates=(1.0,)):
251
261
  """
252
262
  Split test set of DOTA, labels are not included within this set.
253
263
 
ultralytics/data/utils.py CHANGED
@@ -1,6 +1,5 @@
1
- # Ultralytics YOLO 🚀, AGPL-3.0 license
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
- import contextlib
4
3
  import hashlib
5
4
  import json
6
5
  import os
@@ -22,11 +21,12 @@ from ultralytics.utils import (
22
21
  LOGGER,
23
22
  NUM_THREADS,
24
23
  ROOT,
25
- SETTINGS_YAML,
24
+ SETTINGS_FILE,
26
25
  TQDM,
27
26
  clean_url,
28
27
  colorstr,
29
28
  emojis,
29
+ is_dir_writeable,
30
30
  yaml_load,
31
31
  yaml_save,
32
32
  )
@@ -34,10 +34,11 @@ from ultralytics.utils.checks import check_file, check_font, is_ascii
34
34
  from ultralytics.utils.downloads import download, safe_download, unzip_file
35
35
  from ultralytics.utils.ops import segments2boxes
36
36
 
37
- HELP_URL = "See https://docs.ultralytics.com/datasets/detect for dataset formatting guidance."
38
- IMG_FORMATS = "bmp", "dng", "jpeg", "jpg", "mpo", "png", "tif", "tiff", "webp", "pfm" # image suffixes
39
- VID_FORMATS = "asf", "avi", "gif", "m4v", "mkv", "mov", "mp4", "mpeg", "mpg", "ts", "wmv", "webm" # video suffixes
37
+ HELP_URL = "See https://docs.ultralytics.com/datasets for dataset formatting guidance."
38
+ IMG_FORMATS = {"bmp", "dng", "jpeg", "jpg", "mpo", "png", "tif", "tiff", "webp", "pfm", "heic"} # image suffixes
39
+ VID_FORMATS = {"asf", "avi", "gif", "m4v", "mkv", "mov", "mp4", "mpeg", "mpg", "ts", "wmv", "webm"} # video suffixes
40
40
  PIN_MEMORY = str(os.getenv("PIN_MEMORY", True)).lower() == "true" # global pin_memory for dataloaders
41
+ FORMATS_HELP_MSG = f"Supported formats are:\nimages: {IMG_FORMATS}\nvideos: {VID_FORMATS}"
41
42
 
42
43
 
43
44
  def img2label_paths(img_paths):
@@ -58,12 +59,13 @@ def exif_size(img: Image.Image):
58
59
  """Returns exif-corrected PIL size."""
59
60
  s = img.size # (width, height)
60
61
  if img.format == "JPEG": # only support JPEG images
61
- with contextlib.suppress(Exception):
62
- exif = img.getexif()
63
- if exif:
62
+ try:
63
+ if exif := img.getexif():
64
64
  rotation = exif.get(274, None) # the EXIF key for the orientation tag is 274
65
- if rotation in [6, 8]: # rotation 270 or 90
65
+ if rotation in {6, 8}: # rotation 270 or 90
66
66
  s = s[1], s[0]
67
+ except Exception:
68
+ pass
67
69
  return s
68
70
 
69
71
 
@@ -78,8 +80,8 @@ def verify_image(args):
78
80
  shape = exif_size(im) # image size
79
81
  shape = (shape[1], shape[0]) # hw
80
82
  assert (shape[0] > 9) & (shape[1] > 9), f"image size {shape} <10 pixels"
81
- assert im.format.lower() in IMG_FORMATS, f"invalid image format {im.format}"
82
- if im.format.lower() in ("jpg", "jpeg"):
83
+ assert im.format.lower() in IMG_FORMATS, f"Invalid image format {im.format}. {FORMATS_HELP_MSG}"
84
+ if im.format.lower() in {"jpg", "jpeg"}:
83
85
  with open(im_file, "rb") as f:
84
86
  f.seek(-2, 2)
85
87
  if f.read() != b"\xff\xd9": # corrupt JPEG
@@ -104,8 +106,8 @@ def verify_image_label(args):
104
106
  shape = exif_size(im) # image size
105
107
  shape = (shape[1], shape[0]) # hw
106
108
  assert (shape[0] > 9) & (shape[1] > 9), f"image size {shape} <10 pixels"
107
- assert im.format.lower() in IMG_FORMATS, f"invalid image format {im.format}"
108
- if im.format.lower() in ("jpg", "jpeg"):
109
+ assert im.format.lower() in IMG_FORMATS, f"invalid image format {im.format}. {FORMATS_HELP_MSG}"
110
+ if im.format.lower() in {"jpg", "jpeg"}:
109
111
  with open(im_file, "rb") as f:
110
112
  f.seek(-2, 2)
111
113
  if f.read() != b"\xff\xd9": # corrupt JPEG
@@ -122,8 +124,7 @@ def verify_image_label(args):
122
124
  segments = [np.array(x[1:], dtype=np.float32).reshape(-1, 2) for x in lb] # (cls, xy1...)
123
125
  lb = np.concatenate((classes.reshape(-1, 1), segments2boxes(segments)), 1) # (cls, xywh)
124
126
  lb = np.array(lb, dtype=np.float32)
125
- nl = len(lb)
126
- if nl:
127
+ if nl := len(lb):
127
128
  if keypoint:
128
129
  assert lb.shape[1] == (5 + nkpt * ndim), f"labels require {(5 + nkpt * ndim)} columns each"
129
130
  points = lb[:, 5:].reshape(-1, ndim)[:, :2]
@@ -164,6 +165,55 @@ def verify_image_label(args):
164
165
  return [None, None, None, None, None, nm, nf, ne, nc, msg]
165
166
 
166
167
 
168
+ def visualize_image_annotations(image_path, txt_path, label_map):
169
+ """
170
+ Visualizes YOLO annotations (bounding boxes and class labels) on an image.
171
+
172
+ This function reads an image and its corresponding annotation file in YOLO format, then
173
+ draws bounding boxes around detected objects and labels them with their respective class names.
174
+ The bounding box colors are assigned based on the class ID, and the text color is dynamically
175
+ adjusted for readability, depending on the background color's luminance.
176
+
177
+ Args:
178
+ image_path (str): The path to the image file to annotate, and it can be in formats supported by PIL (e.g., .jpg, .png).
179
+ txt_path (str): The path to the annotation file in YOLO format, that should contain one line per object with:
180
+ - class_id (int): The class index.
181
+ - x_center (float): The X center of the bounding box (relative to image width).
182
+ - y_center (float): The Y center of the bounding box (relative to image height).
183
+ - width (float): The width of the bounding box (relative to image width).
184
+ - height (float): The height of the bounding box (relative to image height).
185
+ label_map (dict): A dictionary that maps class IDs (integers) to class labels (strings).
186
+
187
+ Example:
188
+ >>> label_map = {0: "cat", 1: "dog", 2: "bird"} # It should include all annotated classes details
189
+ >>> visualize_image_annotations("path/to/image.jpg", "path/to/annotations.txt", label_map)
190
+ """
191
+ import matplotlib.pyplot as plt
192
+
193
+ from ultralytics.utils.plotting import colors
194
+
195
+ img = np.array(Image.open(image_path))
196
+ img_height, img_width = img.shape[:2]
197
+ annotations = []
198
+ with open(txt_path) as file:
199
+ for line in file:
200
+ class_id, x_center, y_center, width, height = map(float, line.split())
201
+ x = (x_center - width / 2) * img_width
202
+ y = (y_center - height / 2) * img_height
203
+ w = width * img_width
204
+ h = height * img_height
205
+ annotations.append((x, y, w, h, int(class_id)))
206
+ fig, ax = plt.subplots(1) # Plot the image and annotations
207
+ for x, y, w, h, label in annotations:
208
+ color = tuple(c / 255 for c in colors(label, True)) # Get and normalize the RGB color
209
+ rect = plt.Rectangle((x, y), w, h, linewidth=2, edgecolor=color, facecolor="none") # Create a rectangle
210
+ ax.add_patch(rect)
211
+ luminance = 0.2126 * color[0] + 0.7152 * color[1] + 0.0722 * color[2] # Formula for luminance
212
+ ax.text(x, y - 5, label_map[label], color="white" if luminance < 0.5 else "black", backgroundcolor=color)
213
+ ax.imshow(img)
214
+ plt.show()
215
+
216
+
167
217
  def polygon2mask(imgsz, polygons, color=1, downsample_ratio=1):
168
218
  """
169
219
  Convert a list of polygons to a binary mask of the specified image size.
@@ -214,7 +264,7 @@ def polygons2masks_overlap(imgsz, segments, downsample_ratio=1):
214
264
  ms = []
215
265
  for si in range(len(segments)):
216
266
  mask = polygon2mask(imgsz, [segments[si].reshape(-1)], downsample_ratio=downsample_ratio, color=1)
217
- ms.append(mask)
267
+ ms.append(mask.astype(masks.dtype))
218
268
  areas.append(mask.sum())
219
269
  areas = np.asarray(areas)
220
270
  index = np.argsort(-areas)
@@ -263,7 +313,6 @@ def check_det_dataset(dataset, autodownload=True):
263
313
  Returns:
264
314
  (dict): Parsed dataset information and paths.
265
315
  """
266
-
267
316
  file = check_file(dataset)
268
317
 
269
318
  # Download (optional)
@@ -303,7 +352,7 @@ def check_det_dataset(dataset, autodownload=True):
303
352
 
304
353
  # Set paths
305
354
  data["path"] = path # download scripts
306
- for k in "train", "val", "test":
355
+ for k in "train", "val", "test", "minival":
307
356
  if data.get(k): # prepend path
308
357
  if isinstance(data[k], str):
309
358
  x = (path / data[k]).resolve()
@@ -323,7 +372,7 @@ def check_det_dataset(dataset, autodownload=True):
323
372
  if s and autodownload:
324
373
  LOGGER.warning(m)
325
374
  else:
326
- m += f"\nNote dataset download directory is '{DATASETS_DIR}'. You can update this in '{SETTINGS_YAML}'"
375
+ m += f"\nNote dataset download directory is '{DATASETS_DIR}'. You can update this in '{SETTINGS_FILE}'"
327
376
  raise FileNotFoundError(m)
328
377
  t = time.time()
329
378
  r = None # success
@@ -335,7 +384,7 @@ def check_det_dataset(dataset, autodownload=True):
335
384
  else: # python script
336
385
  exec(s, {"yaml": data})
337
386
  dt = f"({round(time.time() - t, 1)}s)"
338
- s = f"success ✅ {dt}, saved to {colorstr('bold', DATASETS_DIR)}" if r in (0, None) else f"failure {dt} ❌"
387
+ s = f"success ✅ {dt}, saved to {colorstr('bold', DATASETS_DIR)}" if r in {0, None} else f"failure {dt} ❌"
339
388
  LOGGER.info(f"Dataset download {s}\n")
340
389
  check_font("Arial.ttf" if is_ascii(data["names"]) else "Arial.Unicode.ttf") # download fonts
341
390
 
@@ -361,11 +410,10 @@ def check_cls_dataset(dataset, split=""):
361
410
  - 'nc' (int): The number of classes in the dataset.
362
411
  - 'names' (dict): A dictionary of class names in the dataset.
363
412
  """
364
-
365
413
  # Download (optional if dataset=https://file.zip is passed directly)
366
414
  if str(dataset).startswith(("http:/", "https:/")):
367
415
  dataset = safe_download(dataset, dir=DATASETS_DIR, unzip=True, delete=False)
368
- elif Path(dataset).suffix in (".zip", ".tar", ".gz"):
416
+ elif Path(dataset).suffix in {".zip", ".tar", ".gz"}:
369
417
  file = check_file(dataset)
370
418
  dataset = safe_download(file, dir=DATASETS_DIR, unzip=True, delete=False)
371
419
 
@@ -377,7 +425,7 @@ def check_cls_dataset(dataset, split=""):
377
425
  if str(dataset) == "imagenet":
378
426
  subprocess.run(f"bash {ROOT / 'data/scripts/get_imagenet.sh'}", shell=True, check=True)
379
427
  else:
380
- url = f"https://github.com/ultralytics/yolov5/releases/download/v1.0/{dataset}.zip"
428
+ url = f"https://github.com/ultralytics/assets/releases/download/v0.0.0/{dataset}.zip"
381
429
  download(url, dir=data_dir.parent)
382
430
  s = f"Dataset download success ✅ ({time.time() - t:.1f}s), saved to {colorstr('bold', data_dir)}\n"
383
431
  LOGGER.info(s)
@@ -401,7 +449,7 @@ def check_cls_dataset(dataset, split=""):
401
449
 
402
450
  # Print to console
403
451
  for k, v in {"train": train_set, "val": val_set, "test": test_set}.items():
404
- prefix = f'{colorstr(f"{k}:")} {v}...'
452
+ prefix = f"{colorstr(f'{k}:')} {v}..."
405
453
  if v is None:
406
454
  LOGGER.info(prefix)
407
455
  else:
@@ -436,10 +484,11 @@ class HUBDatasetStats:
436
484
  ```python
437
485
  from ultralytics.data.utils import HUBDatasetStats
438
486
 
439
- stats = HUBDatasetStats('path/to/coco8.zip', task='detect') # detect dataset
440
- stats = HUBDatasetStats('path/to/coco8-seg.zip', task='segment') # segment dataset
441
- stats = HUBDatasetStats('path/to/coco8-pose.zip', task='pose') # pose dataset
442
- stats = HUBDatasetStats('path/to/imagenet10.zip', task='classify') # classification dataset
487
+ stats = HUBDatasetStats("path/to/coco8.zip", task="detect") # detect dataset
488
+ stats = HUBDatasetStats("path/to/coco8-seg.zip", task="segment") # segment dataset
489
+ stats = HUBDatasetStats("path/to/coco8-pose.zip", task="pose") # pose dataset
490
+ stats = HUBDatasetStats("path/to/dota8.zip", task="obb") # OBB dataset
491
+ stats = HUBDatasetStats("path/to/imagenet10.zip", task="classify") # classification dataset
443
492
 
444
493
  stats.get_json(save=True)
445
494
  stats.process_images()
@@ -451,12 +500,12 @@ class HUBDatasetStats:
451
500
  path = Path(path).resolve()
452
501
  LOGGER.info(f"Starting HUB dataset checks for {path}....")
453
502
 
454
- self.task = task # detect, segment, pose, classify
503
+ self.task = task # detect, segment, pose, classify, obb
455
504
  if self.task == "classify":
456
505
  unzip_dir = unzip_file(path)
457
506
  data = check_cls_dataset(unzip_dir)
458
507
  data["path"] = unzip_dir
459
- else: # detect, segment, pose
508
+ else: # detect, segment, pose, obb
460
509
  _, data_dir, yaml_path = self._unzip(Path(path))
461
510
  try:
462
511
  # Load YAML with checks
@@ -468,7 +517,7 @@ class HUBDatasetStats:
468
517
  except Exception as e:
469
518
  raise Exception("error/HUB/dataset_stats/init") from e
470
519
 
471
- self.hub_dir = Path(f'{data["path"]}-hub')
520
+ self.hub_dir = Path(f"{data['path']}-hub")
472
521
  self.im_dir = self.hub_dir / "images"
473
522
  self.stats = {"nc": len(data["names"]), "names": list(data["names"].values())} # statistics dictionary
474
523
  self.data = data
@@ -480,7 +529,7 @@ class HUBDatasetStats:
480
529
  return False, None, path
481
530
  unzip_dir = unzip_file(path, path=path.parent)
482
531
  assert unzip_dir.is_dir(), (
483
- f"Error unzipping {path}, {unzip_dir} not found. " f"path/to/abc.zip MUST unzip to path/to/abc/"
532
+ f"Error unzipping {path}, {unzip_dir} not found. path/to/abc.zip MUST unzip to path/to/abc/"
484
533
  )
485
534
  return True, str(unzip_dir), find_dataset_yaml(unzip_dir) # zipped, data_dir, yaml_path
486
535
 
@@ -495,13 +544,13 @@ class HUBDatasetStats:
495
544
  """Update labels to integer class and 4 decimal place floats."""
496
545
  if self.task == "detect":
497
546
  coordinates = labels["bboxes"]
498
- elif self.task == "segment":
547
+ elif self.task in {"segment", "obb"}: # Segment and OBB use segments. OBB segments are normalized xyxyxyxy
499
548
  coordinates = [x.flatten() for x in labels["segments"]]
500
549
  elif self.task == "pose":
501
- n = labels["keypoints"].shape[0]
502
- coordinates = np.concatenate((labels["bboxes"], labels["keypoints"].reshape(n, -1)), 1)
550
+ n, nk, nd = labels["keypoints"].shape
551
+ coordinates = np.concatenate((labels["bboxes"], labels["keypoints"].reshape(n, nk * nd)), 1)
503
552
  else:
504
- raise ValueError("Undefined dataset task.")
553
+ raise ValueError(f"Undefined dataset task={self.task}.")
505
554
  zipped = zip(labels["cls"], coordinates)
506
555
  return [[int(c[0]), *(round(float(x), 4) for x in points)] for c, points in zipped]
507
556
 
@@ -595,11 +644,10 @@ def compress_one_image(f, f_new=None, max_dim=1920, quality=50):
595
644
  from pathlib import Path
596
645
  from ultralytics.data.utils import compress_one_image
597
646
 
598
- for f in Path('path/to/dataset').rglob('*.jpg'):
647
+ for f in Path("path/to/dataset").rglob("*.jpg"):
599
648
  compress_one_image(f)
600
649
  ```
601
650
  """
602
-
603
651
  try: # use PIL
604
652
  im = Image.open(f)
605
653
  r = max_dim / max(im.height, im.width) # ratio
@@ -632,7 +680,6 @@ def autosplit(path=DATASETS_DIR / "coco8/images", weights=(0.9, 0.1, 0.0), annot
632
680
  autosplit()
633
681
  ```
634
682
  """
635
-
636
683
  path = Path(path) # images dir
637
684
  files = sorted(x for x in path.rglob("*.*") if x.suffix[1:].lower() in IMG_FORMATS) # image files only
638
685
  n = len(files) # number of files
@@ -649,3 +696,26 @@ def autosplit(path=DATASETS_DIR / "coco8/images", weights=(0.9, 0.1, 0.0), annot
649
696
  if not annotated_only or Path(img2label_paths([str(img)])[0]).exists(): # check label
650
697
  with open(path.parent / txt[i], "a") as f:
651
698
  f.write(f"./{img.relative_to(path.parent).as_posix()}" + "\n") # add image to txt file
699
+
700
+
701
+ def load_dataset_cache_file(path):
702
+ """Load an Ultralytics *.cache dictionary from path."""
703
+ import gc
704
+
705
+ gc.disable() # reduce pickle load time https://github.com/ultralytics/ultralytics/pull/1585
706
+ cache = np.load(str(path), allow_pickle=True).item() # load dict
707
+ gc.enable()
708
+ return cache
709
+
710
+
711
+ def save_dataset_cache_file(prefix, path, x, version):
712
+ """Save an Ultralytics dataset *.cache dictionary x to path."""
713
+ x["version"] = version # add cache version
714
+ if is_dir_writeable(path.parent):
715
+ if path.exists():
716
+ path.unlink() # remove *.cache file if exists
717
+ np.save(str(path), x) # save cache for next time
718
+ path.with_suffix(".cache.npy").rename(path) # remove .npy suffix
719
+ LOGGER.info(f"{prefix}New cache created: {path}")
720
+ else:
721
+ LOGGER.warning(f"{prefix}WARNING ⚠️ Cache directory {path.parent} is not writeable, cache not saved.")
@@ -1 +1 @@
1
- # Ultralytics YOLO 🚀, AGPL-3.0 license
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license