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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. tests/__init__.py +22 -0
  2. tests/conftest.py +83 -0
  3. tests/test_cli.py +122 -0
  4. tests/test_cuda.py +155 -0
  5. tests/test_engine.py +131 -0
  6. tests/test_exports.py +216 -0
  7. tests/test_integrations.py +150 -0
  8. tests/test_python.py +615 -0
  9. tests/test_solutions.py +94 -0
  10. ultralytics/__init__.py +11 -8
  11. ultralytics/cfg/__init__.py +569 -131
  12. ultralytics/cfg/datasets/Argoverse.yaml +2 -1
  13. ultralytics/cfg/datasets/DOTAv1.5.yaml +3 -2
  14. ultralytics/cfg/datasets/DOTAv1.yaml +3 -2
  15. ultralytics/cfg/datasets/GlobalWheat2020.yaml +3 -2
  16. ultralytics/cfg/datasets/ImageNet.yaml +2 -1
  17. ultralytics/cfg/datasets/Objects365.yaml +5 -4
  18. ultralytics/cfg/datasets/SKU-110K.yaml +2 -1
  19. ultralytics/cfg/datasets/VOC.yaml +3 -2
  20. ultralytics/cfg/datasets/VisDrone.yaml +6 -5
  21. ultralytics/cfg/datasets/african-wildlife.yaml +25 -0
  22. ultralytics/cfg/datasets/brain-tumor.yaml +23 -0
  23. ultralytics/cfg/datasets/carparts-seg.yaml +3 -2
  24. ultralytics/cfg/datasets/coco-pose.yaml +7 -6
  25. ultralytics/cfg/datasets/coco.yaml +3 -2
  26. ultralytics/cfg/datasets/coco128-seg.yaml +4 -3
  27. ultralytics/cfg/datasets/coco128.yaml +4 -3
  28. ultralytics/cfg/datasets/coco8-pose.yaml +3 -2
  29. ultralytics/cfg/datasets/coco8-seg.yaml +3 -2
  30. ultralytics/cfg/datasets/coco8.yaml +3 -2
  31. ultralytics/cfg/datasets/crack-seg.yaml +3 -2
  32. ultralytics/cfg/datasets/dog-pose.yaml +24 -0
  33. ultralytics/cfg/datasets/dota8.yaml +3 -2
  34. ultralytics/cfg/datasets/hand-keypoints.yaml +26 -0
  35. ultralytics/cfg/datasets/lvis.yaml +1236 -0
  36. ultralytics/cfg/datasets/medical-pills.yaml +22 -0
  37. ultralytics/cfg/datasets/open-images-v7.yaml +2 -1
  38. ultralytics/cfg/datasets/package-seg.yaml +5 -4
  39. ultralytics/cfg/datasets/signature.yaml +21 -0
  40. ultralytics/cfg/datasets/tiger-pose.yaml +3 -2
  41. ultralytics/cfg/datasets/xView.yaml +2 -1
  42. ultralytics/cfg/default.yaml +14 -11
  43. ultralytics/cfg/models/11/yolo11-cls-resnet18.yaml +24 -0
  44. ultralytics/cfg/models/11/yolo11-cls.yaml +33 -0
  45. ultralytics/cfg/models/11/yolo11-obb.yaml +50 -0
  46. ultralytics/cfg/models/11/yolo11-pose.yaml +51 -0
  47. ultralytics/cfg/models/11/yolo11-seg.yaml +50 -0
  48. ultralytics/cfg/models/11/yolo11.yaml +50 -0
  49. ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +5 -2
  50. ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +5 -2
  51. ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +5 -2
  52. ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +5 -2
  53. ultralytics/cfg/models/v10/yolov10b.yaml +45 -0
  54. ultralytics/cfg/models/v10/yolov10l.yaml +45 -0
  55. ultralytics/cfg/models/v10/yolov10m.yaml +45 -0
  56. ultralytics/cfg/models/v10/yolov10n.yaml +45 -0
  57. ultralytics/cfg/models/v10/yolov10s.yaml +45 -0
  58. ultralytics/cfg/models/v10/yolov10x.yaml +45 -0
  59. ultralytics/cfg/models/v3/yolov3-spp.yaml +5 -2
  60. ultralytics/cfg/models/v3/yolov3-tiny.yaml +5 -2
  61. ultralytics/cfg/models/v3/yolov3.yaml +5 -2
  62. ultralytics/cfg/models/v5/yolov5-p6.yaml +5 -2
  63. ultralytics/cfg/models/v5/yolov5.yaml +5 -2
  64. ultralytics/cfg/models/v6/yolov6.yaml +5 -2
  65. ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +5 -2
  66. ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +5 -2
  67. ultralytics/cfg/models/v8/yolov8-cls.yaml +5 -2
  68. ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +6 -2
  69. ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +6 -2
  70. ultralytics/cfg/models/v8/yolov8-ghost.yaml +5 -2
  71. ultralytics/cfg/models/v8/yolov8-obb.yaml +5 -2
  72. ultralytics/cfg/models/v8/yolov8-p2.yaml +5 -2
  73. ultralytics/cfg/models/v8/yolov8-p6.yaml +10 -7
  74. ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +5 -2
  75. ultralytics/cfg/models/v8/yolov8-pose.yaml +5 -2
  76. ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +5 -2
  77. ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +5 -2
  78. ultralytics/cfg/models/v8/yolov8-seg.yaml +5 -2
  79. ultralytics/cfg/models/v8/yolov8-world.yaml +5 -2
  80. ultralytics/cfg/models/v8/yolov8-worldv2.yaml +5 -2
  81. ultralytics/cfg/models/v8/yolov8.yaml +5 -2
  82. ultralytics/cfg/models/v9/yolov9c-seg.yaml +41 -0
  83. ultralytics/cfg/models/v9/yolov9c.yaml +30 -25
  84. ultralytics/cfg/models/v9/yolov9e-seg.yaml +64 -0
  85. ultralytics/cfg/models/v9/yolov9e.yaml +46 -42
  86. ultralytics/cfg/models/v9/yolov9m.yaml +41 -0
  87. ultralytics/cfg/models/v9/yolov9s.yaml +41 -0
  88. ultralytics/cfg/models/v9/yolov9t.yaml +41 -0
  89. ultralytics/cfg/solutions/default.yaml +24 -0
  90. ultralytics/cfg/trackers/botsort.yaml +8 -5
  91. ultralytics/cfg/trackers/bytetrack.yaml +8 -5
  92. ultralytics/data/__init__.py +14 -3
  93. ultralytics/data/annotator.py +37 -15
  94. ultralytics/data/augment.py +1783 -289
  95. ultralytics/data/base.py +62 -27
  96. ultralytics/data/build.py +36 -8
  97. ultralytics/data/converter.py +196 -36
  98. ultralytics/data/dataset.py +233 -94
  99. ultralytics/data/loaders.py +199 -96
  100. ultralytics/data/split_dota.py +39 -29
  101. ultralytics/data/utils.py +110 -40
  102. ultralytics/engine/__init__.py +1 -1
  103. ultralytics/engine/exporter.py +569 -242
  104. ultralytics/engine/model.py +604 -252
  105. ultralytics/engine/predictor.py +22 -11
  106. ultralytics/engine/results.py +1228 -218
  107. ultralytics/engine/trainer.py +190 -129
  108. ultralytics/engine/tuner.py +18 -18
  109. ultralytics/engine/validator.py +18 -15
  110. ultralytics/hub/__init__.py +31 -13
  111. ultralytics/hub/auth.py +11 -7
  112. ultralytics/hub/google/__init__.py +159 -0
  113. ultralytics/hub/session.py +128 -94
  114. ultralytics/hub/utils.py +20 -21
  115. ultralytics/models/__init__.py +4 -2
  116. ultralytics/models/fastsam/__init__.py +2 -3
  117. ultralytics/models/fastsam/model.py +26 -4
  118. ultralytics/models/fastsam/predict.py +127 -63
  119. ultralytics/models/fastsam/utils.py +1 -44
  120. ultralytics/models/fastsam/val.py +1 -1
  121. ultralytics/models/nas/__init__.py +1 -1
  122. ultralytics/models/nas/model.py +21 -10
  123. ultralytics/models/nas/predict.py +3 -6
  124. ultralytics/models/nas/val.py +4 -4
  125. ultralytics/models/rtdetr/__init__.py +1 -1
  126. ultralytics/models/rtdetr/model.py +1 -1
  127. ultralytics/models/rtdetr/predict.py +6 -8
  128. ultralytics/models/rtdetr/train.py +6 -2
  129. ultralytics/models/rtdetr/val.py +3 -3
  130. ultralytics/models/sam/__init__.py +3 -3
  131. ultralytics/models/sam/amg.py +29 -23
  132. ultralytics/models/sam/build.py +211 -13
  133. ultralytics/models/sam/model.py +91 -30
  134. ultralytics/models/sam/modules/__init__.py +1 -1
  135. ultralytics/models/sam/modules/blocks.py +1129 -0
  136. ultralytics/models/sam/modules/decoders.py +381 -53
  137. ultralytics/models/sam/modules/encoders.py +515 -324
  138. ultralytics/models/sam/modules/memory_attention.py +237 -0
  139. ultralytics/models/sam/modules/sam.py +969 -21
  140. ultralytics/models/sam/modules/tiny_encoder.py +425 -154
  141. ultralytics/models/sam/modules/transformer.py +159 -60
  142. ultralytics/models/sam/modules/utils.py +293 -0
  143. ultralytics/models/sam/predict.py +1263 -132
  144. ultralytics/models/utils/__init__.py +1 -1
  145. ultralytics/models/utils/loss.py +36 -24
  146. ultralytics/models/utils/ops.py +3 -7
  147. ultralytics/models/yolo/__init__.py +3 -3
  148. ultralytics/models/yolo/classify/__init__.py +1 -1
  149. ultralytics/models/yolo/classify/predict.py +7 -8
  150. ultralytics/models/yolo/classify/train.py +17 -22
  151. ultralytics/models/yolo/classify/val.py +8 -4
  152. ultralytics/models/yolo/detect/__init__.py +1 -1
  153. ultralytics/models/yolo/detect/predict.py +3 -5
  154. ultralytics/models/yolo/detect/train.py +11 -4
  155. ultralytics/models/yolo/detect/val.py +90 -52
  156. ultralytics/models/yolo/model.py +14 -9
  157. ultralytics/models/yolo/obb/__init__.py +1 -1
  158. ultralytics/models/yolo/obb/predict.py +2 -2
  159. ultralytics/models/yolo/obb/train.py +5 -3
  160. ultralytics/models/yolo/obb/val.py +41 -23
  161. ultralytics/models/yolo/pose/__init__.py +1 -1
  162. ultralytics/models/yolo/pose/predict.py +3 -5
  163. ultralytics/models/yolo/pose/train.py +2 -2
  164. ultralytics/models/yolo/pose/val.py +51 -17
  165. ultralytics/models/yolo/segment/__init__.py +1 -1
  166. ultralytics/models/yolo/segment/predict.py +3 -5
  167. ultralytics/models/yolo/segment/train.py +2 -2
  168. ultralytics/models/yolo/segment/val.py +60 -19
  169. ultralytics/models/yolo/world/__init__.py +5 -0
  170. ultralytics/models/yolo/world/train.py +92 -0
  171. ultralytics/models/yolo/world/train_world.py +109 -0
  172. ultralytics/nn/__init__.py +1 -1
  173. ultralytics/nn/autobackend.py +228 -93
  174. ultralytics/nn/modules/__init__.py +39 -14
  175. ultralytics/nn/modules/activation.py +21 -0
  176. ultralytics/nn/modules/block.py +526 -66
  177. ultralytics/nn/modules/conv.py +24 -7
  178. ultralytics/nn/modules/head.py +177 -34
  179. ultralytics/nn/modules/transformer.py +6 -5
  180. ultralytics/nn/modules/utils.py +1 -2
  181. ultralytics/nn/tasks.py +225 -77
  182. ultralytics/solutions/__init__.py +30 -1
  183. ultralytics/solutions/ai_gym.py +96 -143
  184. ultralytics/solutions/analytics.py +247 -0
  185. ultralytics/solutions/distance_calculation.py +78 -135
  186. ultralytics/solutions/heatmap.py +93 -247
  187. ultralytics/solutions/object_counter.py +184 -259
  188. ultralytics/solutions/parking_management.py +246 -0
  189. ultralytics/solutions/queue_management.py +112 -0
  190. ultralytics/solutions/region_counter.py +116 -0
  191. ultralytics/solutions/security_alarm.py +144 -0
  192. ultralytics/solutions/solutions.py +178 -0
  193. ultralytics/solutions/speed_estimation.py +86 -174
  194. ultralytics/solutions/streamlit_inference.py +190 -0
  195. ultralytics/solutions/trackzone.py +68 -0
  196. ultralytics/trackers/__init__.py +1 -1
  197. ultralytics/trackers/basetrack.py +32 -13
  198. ultralytics/trackers/bot_sort.py +61 -28
  199. ultralytics/trackers/byte_tracker.py +83 -51
  200. ultralytics/trackers/track.py +21 -6
  201. ultralytics/trackers/utils/__init__.py +1 -1
  202. ultralytics/trackers/utils/gmc.py +62 -48
  203. ultralytics/trackers/utils/kalman_filter.py +166 -35
  204. ultralytics/trackers/utils/matching.py +40 -21
  205. ultralytics/utils/__init__.py +511 -239
  206. ultralytics/utils/autobatch.py +40 -22
  207. ultralytics/utils/benchmarks.py +266 -85
  208. ultralytics/utils/callbacks/__init__.py +1 -1
  209. ultralytics/utils/callbacks/base.py +1 -3
  210. ultralytics/utils/callbacks/clearml.py +7 -6
  211. ultralytics/utils/callbacks/comet.py +39 -17
  212. ultralytics/utils/callbacks/dvc.py +1 -1
  213. ultralytics/utils/callbacks/hub.py +16 -16
  214. ultralytics/utils/callbacks/mlflow.py +28 -24
  215. ultralytics/utils/callbacks/neptune.py +6 -2
  216. ultralytics/utils/callbacks/raytune.py +3 -4
  217. ultralytics/utils/callbacks/tensorboard.py +18 -18
  218. ultralytics/utils/callbacks/wb.py +27 -20
  219. ultralytics/utils/checks.py +160 -100
  220. ultralytics/utils/dist.py +2 -1
  221. ultralytics/utils/downloads.py +40 -34
  222. ultralytics/utils/errors.py +1 -1
  223. ultralytics/utils/files.py +72 -38
  224. ultralytics/utils/instance.py +41 -19
  225. ultralytics/utils/loss.py +83 -55
  226. ultralytics/utils/metrics.py +61 -56
  227. ultralytics/utils/ops.py +94 -89
  228. ultralytics/utils/patches.py +30 -14
  229. ultralytics/utils/plotting.py +600 -269
  230. ultralytics/utils/tal.py +67 -26
  231. ultralytics/utils/torch_utils.py +302 -102
  232. ultralytics/utils/triton.py +2 -1
  233. ultralytics/utils/tuner.py +21 -12
  234. ultralytics-8.3.62.dist-info/METADATA +370 -0
  235. ultralytics-8.3.62.dist-info/RECORD +241 -0
  236. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/WHEEL +1 -1
  237. ultralytics/data/explorer/__init__.py +0 -5
  238. ultralytics/data/explorer/explorer.py +0 -472
  239. ultralytics/data/explorer/gui/__init__.py +0 -1
  240. ultralytics/data/explorer/gui/dash.py +0 -268
  241. ultralytics/data/explorer/utils.py +0 -166
  242. ultralytics/models/fastsam/prompt.py +0 -357
  243. ultralytics-8.1.29.dist-info/METADATA +0 -373
  244. ultralytics-8.1.29.dist-info/RECORD +0 -197
  245. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/LICENSE +0 -0
  246. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/entry_points.txt +0 -0
  247. {ultralytics-8.1.29.dist-info → ultralytics-8.3.62.dist-info}/top_level.txt +0 -0
ultralytics/data/base.py CHANGED
@@ -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 glob
4
4
  import math
@@ -14,8 +14,8 @@ import numpy as np
14
14
  import psutil
15
15
  from torch.utils.data import Dataset
16
16
 
17
+ from ultralytics.data.utils import FORMATS_HELP_MSG, HELP_URL, IMG_FORMATS
17
18
  from ultralytics.utils import DEFAULT_CFG, LOCAL_RANK, LOGGER, NUM_THREADS, TQDM
18
- from .utils import HELP_URL, IMG_FORMATS
19
19
 
20
20
 
21
21
  class BaseDataset(Dataset):
@@ -86,13 +86,19 @@ class BaseDataset(Dataset):
86
86
  self.buffer = [] # buffer size = batch size
87
87
  self.max_buffer_length = min((self.ni, self.batch_size * 8, 1000)) if self.augment else 0
88
88
 
89
- # Cache images
90
- if cache == "ram" and not self.check_cache_ram():
91
- cache = False
89
+ # Cache images (options are cache = True, False, None, "ram", "disk")
92
90
  self.ims, self.im_hw0, self.im_hw = [None] * self.ni, [None] * self.ni, [None] * self.ni
93
91
  self.npy_files = [Path(f).with_suffix(".npy") for f in self.im_files]
94
- if cache:
95
- self.cache_images(cache)
92
+ self.cache = cache.lower() if isinstance(cache, str) else "ram" if cache is True else None
93
+ if self.cache == "ram" and self.check_cache_ram():
94
+ if hyp.deterministic:
95
+ LOGGER.warning(
96
+ "WARNING ⚠️ cache='ram' may produce non-deterministic training results. "
97
+ "Consider cache='disk' as a deterministic alternative if your disk space allows."
98
+ )
99
+ self.cache_images()
100
+ elif self.cache == "disk" and self.check_cache_disk():
101
+ self.cache_images()
96
102
 
97
103
  # Transforms
98
104
  self.transforms = self.build_transforms(hyp=hyp)
@@ -116,13 +122,11 @@ class BaseDataset(Dataset):
116
122
  raise FileNotFoundError(f"{self.prefix}{p} does not exist")
117
123
  im_files = sorted(x.replace("/", os.sep) for x in f if x.split(".")[-1].lower() in IMG_FORMATS)
118
124
  # self.img_files = sorted([x for x in f if x.suffix[1:].lower() in IMG_FORMATS]) # pathlib
119
- assert im_files, f"{self.prefix}No images found in {img_path}"
125
+ assert im_files, f"{self.prefix}No images found in {img_path}. {FORMATS_HELP_MSG}"
120
126
  except Exception as e:
121
127
  raise FileNotFoundError(f"{self.prefix}Error loading data from {img_path}\n{HELP_URL}") from e
122
128
  if self.fraction < 1:
123
- # im_files = im_files[: round(len(im_files) * self.fraction)]
124
- num_elements_to_select = round(len(im_files) * self.fraction)
125
- im_files = random.sample(im_files, num_elements_to_select)
129
+ im_files = im_files[: round(len(im_files) * self.fraction)] # retain a fraction of the dataset
126
130
  return im_files
127
131
 
128
132
  def update_labels(self, include_class: Optional[list]):
@@ -173,28 +177,29 @@ class BaseDataset(Dataset):
173
177
  if self.augment:
174
178
  self.ims[i], self.im_hw0[i], self.im_hw[i] = im, (h0, w0), im.shape[:2] # im, hw_original, hw_resized
175
179
  self.buffer.append(i)
176
- if len(self.buffer) >= self.max_buffer_length:
180
+ if 1 < len(self.buffer) >= self.max_buffer_length: # prevent empty buffer
177
181
  j = self.buffer.pop(0)
178
- self.ims[j], self.im_hw0[j], self.im_hw[j] = None, None, None
182
+ if self.cache != "ram":
183
+ self.ims[j], self.im_hw0[j], self.im_hw[j] = None, None, None
179
184
 
180
185
  return im, (h0, w0), im.shape[:2]
181
186
 
182
187
  return self.ims[i], self.im_hw0[i], self.im_hw[i]
183
188
 
184
- def cache_images(self, cache):
189
+ def cache_images(self):
185
190
  """Cache images to memory or disk."""
186
191
  b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
187
- fcn = self.cache_images_to_disk if cache == "disk" else self.load_image
192
+ fcn, storage = (self.cache_images_to_disk, "Disk") if self.cache == "disk" else (self.load_image, "RAM")
188
193
  with ThreadPool(NUM_THREADS) as pool:
189
194
  results = pool.imap(fcn, range(self.ni))
190
195
  pbar = TQDM(enumerate(results), total=self.ni, disable=LOCAL_RANK > 0)
191
196
  for i, x in pbar:
192
- if cache == "disk":
197
+ if self.cache == "disk":
193
198
  b += self.npy_files[i].stat().st_size
194
199
  else: # 'ram'
195
200
  self.ims[i], self.im_hw0[i], self.im_hw[i] = x # im, hw_orig, hw_resized = load_image(self, i)
196
201
  b += self.ims[i].nbytes
197
- pbar.desc = f"{self.prefix}Caching images ({b / gb:.1f}GB {cache})"
202
+ pbar.desc = f"{self.prefix}Caching images ({b / gb:.1f}GB {storage})"
198
203
  pbar.close()
199
204
 
200
205
  def cache_images_to_disk(self, i):
@@ -203,25 +208,55 @@ class BaseDataset(Dataset):
203
208
  if not f.exists():
204
209
  np.save(f.as_posix(), cv2.imread(self.im_files[i]), allow_pickle=False)
205
210
 
211
+ def check_cache_disk(self, safety_margin=0.5):
212
+ """Check image caching requirements vs available disk space."""
213
+ import shutil
214
+
215
+ b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
216
+ n = min(self.ni, 30) # extrapolate from 30 random images
217
+ for _ in range(n):
218
+ im_file = random.choice(self.im_files)
219
+ im = cv2.imread(im_file)
220
+ if im is None:
221
+ continue
222
+ b += im.nbytes
223
+ if not os.access(Path(im_file).parent, os.W_OK):
224
+ self.cache = None
225
+ LOGGER.info(f"{self.prefix}Skipping caching images to disk, directory not writeable ⚠️")
226
+ return False
227
+ disk_required = b * self.ni / n * (1 + safety_margin) # bytes required to cache dataset to disk
228
+ total, used, free = shutil.disk_usage(Path(self.im_files[0]).parent)
229
+ if disk_required > free:
230
+ self.cache = None
231
+ LOGGER.info(
232
+ f"{self.prefix}{disk_required / gb:.1f}GB disk space required, "
233
+ f"with {int(safety_margin * 100)}% safety margin but only "
234
+ f"{free / gb:.1f}/{total / gb:.1f}GB free, not caching images to disk ⚠️"
235
+ )
236
+ return False
237
+ return True
238
+
206
239
  def check_cache_ram(self, safety_margin=0.5):
207
240
  """Check image caching requirements vs available memory."""
208
241
  b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
209
242
  n = min(self.ni, 30) # extrapolate from 30 random images
210
243
  for _ in range(n):
211
244
  im = cv2.imread(random.choice(self.im_files)) # sample image
245
+ if im is None:
246
+ continue
212
247
  ratio = self.imgsz / max(im.shape[0], im.shape[1]) # max(h, w) # ratio
213
248
  b += im.nbytes * ratio**2
214
249
  mem_required = b * self.ni / n * (1 + safety_margin) # GB required to cache dataset into RAM
215
250
  mem = psutil.virtual_memory()
216
- cache = mem_required < mem.available # to cache or not to cache, that is the question
217
- if not cache:
251
+ if mem_required > mem.available:
252
+ self.cache = None
218
253
  LOGGER.info(
219
- f'{self.prefix}{mem_required / gb:.1f}GB RAM required to cache images '
220
- f'with {int(safety_margin * 100)}% safety margin but only '
221
- f'{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, '
222
- f"{'caching images ✅' if cache else 'not caching images ⚠️'}"
254
+ f"{self.prefix}{mem_required / gb:.1f}GB RAM required to cache images "
255
+ f"with {int(safety_margin * 100)}% safety margin but only "
256
+ f"{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, not caching images ⚠️"
223
257
  )
224
- return cache
258
+ return False
259
+ return True
225
260
 
226
261
  def set_rectangle(self):
227
262
  """Sets the shape of bounding boxes for YOLO detections as rectangles."""
@@ -300,10 +335,10 @@ class BaseDataset(Dataset):
300
335
  im_file=im_file,
301
336
  shape=shape, # format: (height, width)
302
337
  cls=cls,
303
- bboxes=bboxes, # xywh
338
+ bboxes=bboxes, # xywh
304
339
  segments=segments, # xy
305
- keypoints=keypoints, # xy
306
- normalized=True, # or False
340
+ keypoints=keypoints, # xy
341
+ normalized=True, # or False
307
342
  bbox_format="xyxy", # or xywh, ltwh
308
343
  )
309
344
  ```
ultralytics/data/build.py CHANGED
@@ -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 os
4
4
  import random
@@ -9,6 +9,7 @@ import torch
9
9
  from PIL import Image
10
10
  from torch.utils.data import dataloader, distributed
11
11
 
12
+ from ultralytics.data.dataset import GroundingDataset, YOLODataset, YOLOMultiModalDataset
12
13
  from ultralytics.data.loaders import (
13
14
  LOADERS,
14
15
  LoadImagesAndVideos,
@@ -19,11 +20,9 @@ from ultralytics.data.loaders import (
19
20
  SourceTypes,
20
21
  autocast_list,
21
22
  )
22
- from ultralytics.data.utils import IMG_FORMATS, VID_FORMATS
23
+ from ultralytics.data.utils import IMG_FORMATS, PIN_MEMORY, VID_FORMATS
23
24
  from ultralytics.utils import RANK, colorstr
24
25
  from ultralytics.utils.checks import check_file
25
- from .dataset import YOLODataset
26
- from .utils import PIN_MEMORY
27
26
 
28
27
 
29
28
  class InfiniteDataLoader(dataloader.DataLoader):
@@ -48,6 +47,13 @@ class InfiniteDataLoader(dataloader.DataLoader):
48
47
  for _ in range(len(self)):
49
48
  yield next(self.iterator)
50
49
 
50
+ def __del__(self):
51
+ """Ensure that workers are terminated."""
52
+ for w in self.iterator._workers: # force terminate
53
+ if w.is_alive():
54
+ w.terminate()
55
+ self.iterator._shutdown_workers() # cleanup
56
+
51
57
  def reset(self):
52
58
  """
53
59
  Reset iterator.
@@ -82,9 +88,10 @@ def seed_worker(worker_id): # noqa
82
88
  random.seed(worker_seed)
83
89
 
84
90
 
85
- def build_yolo_dataset(cfg, img_path, batch, data, mode="train", rect=False, stride=32):
91
+ def build_yolo_dataset(cfg, img_path, batch, data, mode="train", rect=False, stride=32, multi_modal=False):
86
92
  """Build YOLO Dataset."""
87
- return YOLODataset(
93
+ dataset = YOLOMultiModalDataset if multi_modal else YOLODataset
94
+ return dataset(
88
95
  img_path=img_path,
89
96
  imgsz=cfg.imgsz,
90
97
  batch_size=batch,
@@ -103,11 +110,32 @@ def build_yolo_dataset(cfg, img_path, batch, data, mode="train", rect=False, str
103
110
  )
104
111
 
105
112
 
113
+ def build_grounding(cfg, img_path, json_file, batch, mode="train", rect=False, stride=32):
114
+ """Build YOLO Dataset."""
115
+ return GroundingDataset(
116
+ img_path=img_path,
117
+ json_file=json_file,
118
+ imgsz=cfg.imgsz,
119
+ batch_size=batch,
120
+ augment=mode == "train", # augmentation
121
+ hyp=cfg, # TODO: probably add a get_hyps_from_cfg function
122
+ rect=cfg.rect or rect, # rectangular batches
123
+ cache=cfg.cache or None,
124
+ single_cls=cfg.single_cls or False,
125
+ stride=int(stride),
126
+ pad=0.0 if mode == "train" else 0.5,
127
+ prefix=colorstr(f"{mode}: "),
128
+ task=cfg.task,
129
+ classes=cfg.classes,
130
+ fraction=cfg.fraction if mode == "train" else 1.0,
131
+ )
132
+
133
+
106
134
  def build_dataloader(dataset, batch, workers, shuffle=True, rank=-1):
107
135
  """Return an InfiniteDataLoader or DataLoader for training or validation set."""
108
136
  batch = min(batch, len(dataset))
109
137
  nd = torch.cuda.device_count() # number of CUDA devices
110
- nw = min([os.cpu_count() // max(nd, 1), workers]) # number of workers
138
+ nw = min(os.cpu_count() // max(nd, 1), workers) # number of workers
111
139
  sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle)
112
140
  generator = torch.Generator()
113
141
  generator.manual_seed(6148914691236517205 + RANK)
@@ -129,7 +157,7 @@ def check_source(source):
129
157
  webcam, screenshot, from_img, in_memory, tensor = False, False, False, False, False
130
158
  if isinstance(source, (str, int, Path)): # int for local usb camera
131
159
  source = str(source)
132
- is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
160
+ is_file = Path(source).suffix[1:] in (IMG_FORMATS | VID_FORMATS)
133
161
  is_url = source.lower().startswith(("https://", "http://", "rtsp://", "rtmp://", "tcp://"))
134
162
  webcam = source.isnumeric() or source.endswith(".streams") or (is_url and not is_file)
135
163
  screenshot = source.lower() == "screen"
@@ -1,13 +1,18 @@
1
- # Ultralytics YOLO 🚀, AGPL-3.0 license
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
3
  import json
4
+ import random
5
+ import shutil
4
6
  from collections import defaultdict
7
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
8
  from pathlib import Path
6
9
 
7
10
  import cv2
8
11
  import numpy as np
12
+ from PIL import Image
9
13
 
10
- from ultralytics.utils import LOGGER, TQDM
14
+ from ultralytics.utils import DATASETS_DIR, LOGGER, NUM_THREADS, TQDM
15
+ from ultralytics.utils.downloads import download
11
16
  from ultralytics.utils.files import increment_path
12
17
 
13
18
 
@@ -115,7 +120,7 @@ def coco91_to_coco80_class():
115
120
 
116
121
 
117
122
  def coco80_to_coco91_class():
118
- """
123
+ r"""
119
124
  Converts 80-index (val2014) to 91-index (paper).
120
125
  For details see https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/.
121
126
 
@@ -123,8 +128,8 @@ def coco80_to_coco91_class():
123
128
  ```python
124
129
  import numpy as np
125
130
 
126
- a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n')
127
- b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
131
+ a = np.loadtxt("data/coco.names", dtype="str", delimiter="\n")
132
+ b = np.loadtxt("data/coco_paper.names", dtype="str", delimiter="\n")
128
133
  x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco
129
134
  x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet
130
135
  ```
@@ -219,6 +224,7 @@ def convert_coco(
219
224
  use_segments=False,
220
225
  use_keypoints=False,
221
226
  cls91to80=True,
227
+ lvis=False,
222
228
  ):
223
229
  """
224
230
  Converts COCO dataset annotations to a YOLO annotation format suitable for training YOLO models.
@@ -229,18 +235,21 @@ def convert_coco(
229
235
  use_segments (bool, optional): Whether to include segmentation masks in the output.
230
236
  use_keypoints (bool, optional): Whether to include keypoint annotations in the output.
231
237
  cls91to80 (bool, optional): Whether to map 91 COCO class IDs to the corresponding 80 COCO class IDs.
238
+ lvis (bool, optional): Whether to convert data in lvis dataset way.
232
239
 
233
240
  Example:
234
241
  ```python
235
242
  from ultralytics.data.converter import convert_coco
236
243
 
237
- convert_coco('../datasets/coco/annotations/', use_segments=True, use_keypoints=False, cls91to80=True)
244
+ convert_coco("../datasets/coco/annotations/", use_segments=True, use_keypoints=False, cls91to80=False)
245
+ convert_coco(
246
+ "../datasets/lvis/annotations/", use_segments=True, use_keypoints=False, cls91to80=False, lvis=True
247
+ )
238
248
  ```
239
249
 
240
250
  Output:
241
251
  Generates output files in the specified output directory.
242
252
  """
243
-
244
253
  # Create dataset directory
245
254
  save_dir = increment_path(save_dir) # increment if save directory already exists
246
255
  for p in save_dir / "labels", save_dir / "images":
@@ -251,28 +260,38 @@ def convert_coco(
251
260
 
252
261
  # Import json
253
262
  for json_file in sorted(Path(labels_dir).resolve().glob("*.json")):
254
- fn = Path(save_dir) / "labels" / json_file.stem.replace("instances_", "") # folder name
263
+ lname = "" if lvis else json_file.stem.replace("instances_", "")
264
+ fn = Path(save_dir) / "labels" / lname # folder name
255
265
  fn.mkdir(parents=True, exist_ok=True)
256
- with open(json_file) as f:
266
+ if lvis:
267
+ # NOTE: create folders for both train and val in advance,
268
+ # since LVIS val set contains images from COCO 2017 train in addition to the COCO 2017 val split.
269
+ (fn / "train2017").mkdir(parents=True, exist_ok=True)
270
+ (fn / "val2017").mkdir(parents=True, exist_ok=True)
271
+ with open(json_file, encoding="utf-8") as f:
257
272
  data = json.load(f)
258
273
 
259
274
  # Create image dict
260
- images = {f'{x["id"]:d}': x for x in data["images"]}
275
+ images = {f"{x['id']:d}": x for x in data["images"]}
261
276
  # Create image-annotations dict
262
277
  imgToAnns = defaultdict(list)
263
278
  for ann in data["annotations"]:
264
279
  imgToAnns[ann["image_id"]].append(ann)
265
280
 
281
+ image_txt = []
266
282
  # Write labels file
267
283
  for img_id, anns in TQDM(imgToAnns.items(), desc=f"Annotations {json_file}"):
268
284
  img = images[f"{img_id:d}"]
269
- h, w, f = img["height"], img["width"], img["file_name"]
285
+ h, w = img["height"], img["width"]
286
+ f = str(Path(img["coco_url"]).relative_to("http://images.cocodataset.org")) if lvis else img["file_name"]
287
+ if lvis:
288
+ image_txt.append(str(Path("./images") / f))
270
289
 
271
290
  bboxes = []
272
291
  segments = []
273
292
  keypoints = []
274
293
  for ann in anns:
275
- if ann["iscrowd"]:
294
+ if ann.get("iscrowd", False):
276
295
  continue
277
296
  # The COCO box format is [top left x, top left y, width, height]
278
297
  box = np.array(ann["bbox"], dtype=np.float64)
@@ -314,7 +333,89 @@ def convert_coco(
314
333
  ) # cls, box or segments
315
334
  file.write(("%g " * len(line)).rstrip() % line + "\n")
316
335
 
317
- LOGGER.info(f"COCO data converted successfully.\nResults saved to {save_dir.resolve()}")
336
+ if lvis:
337
+ with open((Path(save_dir) / json_file.name.replace("lvis_v1_", "").replace(".json", ".txt")), "a") as f:
338
+ f.writelines(f"{line}\n" for line in image_txt)
339
+
340
+ LOGGER.info(f"{'LVIS' if lvis else 'COCO'} data converted successfully.\nResults saved to {save_dir.resolve()}")
341
+
342
+
343
+ def convert_segment_masks_to_yolo_seg(masks_dir, output_dir, classes):
344
+ """
345
+ Converts a dataset of segmentation mask images to the YOLO segmentation format.
346
+
347
+ This function takes the directory containing the binary format mask images and converts them into YOLO segmentation format.
348
+ The converted masks are saved in the specified output directory.
349
+
350
+ Args:
351
+ masks_dir (str): The path to the directory where all mask images (png, jpg) are stored.
352
+ output_dir (str): The path to the directory where the converted YOLO segmentation masks will be stored.
353
+ classes (int): Total classes in the dataset i.e. for COCO classes=80
354
+
355
+ Example:
356
+ ```python
357
+ from ultralytics.data.converter import convert_segment_masks_to_yolo_seg
358
+
359
+ # The classes here is the total classes in the dataset, for COCO dataset we have 80 classes
360
+ convert_segment_masks_to_yolo_seg("path/to/masks_directory", "path/to/output/directory", classes=80)
361
+ ```
362
+
363
+ Notes:
364
+ The expected directory structure for the masks is:
365
+
366
+ - masks
367
+ ├─ mask_image_01.png or mask_image_01.jpg
368
+ ├─ mask_image_02.png or mask_image_02.jpg
369
+ ├─ mask_image_03.png or mask_image_03.jpg
370
+ └─ mask_image_04.png or mask_image_04.jpg
371
+
372
+ After execution, the labels will be organized in the following structure:
373
+
374
+ - output_dir
375
+ ├─ mask_yolo_01.txt
376
+ ├─ mask_yolo_02.txt
377
+ ├─ mask_yolo_03.txt
378
+ └─ mask_yolo_04.txt
379
+ """
380
+ pixel_to_class_mapping = {i + 1: i for i in range(classes)}
381
+ for mask_path in Path(masks_dir).iterdir():
382
+ if mask_path.suffix in {".png", ".jpg"}:
383
+ mask = cv2.imread(str(mask_path), cv2.IMREAD_GRAYSCALE) # Read the mask image in grayscale
384
+ img_height, img_width = mask.shape # Get image dimensions
385
+ LOGGER.info(f"Processing {mask_path} imgsz = {img_height} x {img_width}")
386
+
387
+ unique_values = np.unique(mask) # Get unique pixel values representing different classes
388
+ yolo_format_data = []
389
+
390
+ for value in unique_values:
391
+ if value == 0:
392
+ continue # Skip background
393
+ class_index = pixel_to_class_mapping.get(value, -1)
394
+ if class_index == -1:
395
+ LOGGER.warning(f"Unknown class for pixel value {value} in file {mask_path}, skipping.")
396
+ continue
397
+
398
+ # Create a binary mask for the current class and find contours
399
+ contours, _ = cv2.findContours(
400
+ (mask == value).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
401
+ ) # Find contours
402
+
403
+ for contour in contours:
404
+ if len(contour) >= 3: # YOLO requires at least 3 points for a valid segmentation
405
+ contour = contour.squeeze() # Remove single-dimensional entries
406
+ yolo_format = [class_index]
407
+ for point in contour:
408
+ # Normalize the coordinates
409
+ yolo_format.append(round(point[0] / img_width, 6)) # Rounding to 6 decimal places
410
+ yolo_format.append(round(point[1] / img_height, 6))
411
+ yolo_format_data.append(yolo_format)
412
+ # Save Ultralytics YOLO format data to file
413
+ output_path = Path(output_dir) / f"{mask_path.stem}.txt"
414
+ with open(output_path, "w") as file:
415
+ for item in yolo_format_data:
416
+ line = " ".join(map(str, item))
417
+ file.write(line + "\n")
418
+ LOGGER.info(f"Processed and stored at {output_path} imgsz = {img_height} x {img_width}")
318
419
 
319
420
 
320
421
  def convert_dota_to_yolo_obb(dota_root_path: str):
@@ -331,7 +432,7 @@ def convert_dota_to_yolo_obb(dota_root_path: str):
331
432
  ```python
332
433
  from ultralytics.data.converter import convert_dota_to_yolo_obb
333
434
 
334
- convert_dota_to_yolo_obb('path/to/DOTA')
435
+ convert_dota_to_yolo_obb("path/to/DOTA")
335
436
  ```
336
437
 
337
438
  Notes:
@@ -393,7 +494,7 @@ def convert_dota_to_yolo_obb(dota_root_path: str):
393
494
  normalized_coords = [
394
495
  coords[i] / image_width if i % 2 == 0 else coords[i] / image_height for i in range(8)
395
496
  ]
396
- formatted_coords = ["{:.6g}".format(coord) for coord in normalized_coords]
497
+ formatted_coords = [f"{coord:.6g}" for coord in normalized_coords]
397
498
  g.write(f"{class_idx} {' '.join(formatted_coords)}\n")
398
499
 
399
500
  for phase in ["train", "val"]:
@@ -463,7 +564,7 @@ def merge_multi_segment(segments):
463
564
  segments[i] = np.roll(segments[i], -idx[0], axis=0)
464
565
  segments[i] = np.concatenate([segments[i], segments[i][:1]])
465
566
  # Deal with the first segment and the last one
466
- if i in [0, len(idx_list) - 1]:
567
+ if i in {0, len(idx_list) - 1}:
467
568
  s.append(segments[i])
468
569
  else:
469
570
  idx = [0, idx[1] - idx[0]]
@@ -471,14 +572,14 @@ def merge_multi_segment(segments):
471
572
 
472
573
  else:
473
574
  for i in range(len(idx_list) - 1, -1, -1):
474
- if i not in [0, len(idx_list) - 1]:
575
+ if i not in {0, len(idx_list) - 1}:
475
576
  idx = idx_list[i]
476
577
  nidx = abs(idx[1] - idx[0])
477
578
  s.append(segments[i][nidx:])
478
579
  return s
479
580
 
480
581
 
481
- def yolo_bbox2segment(im_dir, save_dir=None, sam_model="sam_b.pt"):
582
+ def yolo_bbox2segment(im_dir, save_dir=None, sam_model="sam_b.pt", device=None):
482
583
  """
483
584
  Converts existing object detection dataset (bounding boxes) to segmentation dataset or oriented bounding box (OBB)
484
585
  in YOLO format. Generates segmentation data using SAM auto-annotator as needed.
@@ -488,24 +589,24 @@ def yolo_bbox2segment(im_dir, save_dir=None, sam_model="sam_b.pt"):
488
589
  save_dir (str | Path): Path to save the generated labels, labels will be saved
489
590
  into `labels-segment` in the same directory level of `im_dir` if save_dir is None. Default: None.
490
591
  sam_model (str): Segmentation model to use for intermediate segmentation data; optional.
592
+ device (int | str): The specific device to run SAM models. Default: None.
491
593
 
492
594
  Notes:
493
595
  The input directory structure assumed for dataset:
494
596
 
495
597
  - im_dir
496
598
  ├─ 001.jpg
497
- ├─ ..
599
+ ├─ ...
498
600
  └─ NNN.jpg
499
601
  - labels
500
602
  ├─ 001.txt
501
- ├─ ..
603
+ ├─ ...
502
604
  └─ NNN.txt
503
605
  """
606
+ from ultralytics import SAM
504
607
  from ultralytics.data import YOLODataset
505
- from ultralytics.utils.ops import xywh2xyxy
506
608
  from ultralytics.utils import LOGGER
507
- from ultralytics import SAM
508
- from tqdm import tqdm
609
+ from ultralytics.utils.ops import xywh2xyxy
509
610
 
510
611
  # NOTE: add placeholder to pass class index check
511
612
  dataset = YOLODataset(im_dir, data=dict(names=list(range(1000))))
@@ -515,28 +616,87 @@ def yolo_bbox2segment(im_dir, save_dir=None, sam_model="sam_b.pt"):
515
616
 
516
617
  LOGGER.info("Detection labels detected, generating segment labels by SAM model!")
517
618
  sam_model = SAM(sam_model)
518
- for l in tqdm(dataset.labels, total=len(dataset.labels), desc="Generating segment labels"):
519
- h, w = l["shape"]
520
- boxes = l["bboxes"]
619
+ for label in TQDM(dataset.labels, total=len(dataset.labels), desc="Generating segment labels"):
620
+ h, w = label["shape"]
621
+ boxes = label["bboxes"]
521
622
  if len(boxes) == 0: # skip empty labels
522
623
  continue
523
624
  boxes[:, [0, 2]] *= w
524
625
  boxes[:, [1, 3]] *= h
525
- im = cv2.imread(l["im_file"])
526
- sam_results = sam_model(im, bboxes=xywh2xyxy(boxes), verbose=False, save=False)
527
- l["segments"] = sam_results[0].masks.xyn
626
+ im = cv2.imread(label["im_file"])
627
+ sam_results = sam_model(im, bboxes=xywh2xyxy(boxes), verbose=False, save=False, device=device)
628
+ label["segments"] = sam_results[0].masks.xyn
528
629
 
529
630
  save_dir = Path(save_dir) if save_dir else Path(im_dir).parent / "labels-segment"
530
631
  save_dir.mkdir(parents=True, exist_ok=True)
531
- for l in dataset.labels:
632
+ for label in dataset.labels:
532
633
  texts = []
533
- lb_name = Path(l["im_file"]).with_suffix(".txt").name
634
+ lb_name = Path(label["im_file"]).with_suffix(".txt").name
534
635
  txt_file = save_dir / lb_name
535
- cls = l["cls"]
536
- for i, s in enumerate(l["segments"]):
636
+ cls = label["cls"]
637
+ for i, s in enumerate(label["segments"]):
638
+ if len(s) == 0:
639
+ continue
537
640
  line = (int(cls[i]), *s.reshape(-1))
538
641
  texts.append(("%g " * len(line)).rstrip() % line)
539
- if texts:
540
- with open(txt_file, "a") as f:
541
- f.writelines(text + "\n" for text in texts)
642
+ with open(txt_file, "a") as f:
643
+ f.writelines(text + "\n" for text in texts)
542
644
  LOGGER.info(f"Generated segment labels saved in {save_dir}")
645
+
646
+
647
+ def create_synthetic_coco_dataset():
648
+ """
649
+ Creates a synthetic COCO dataset with random images based on filenames from label lists.
650
+
651
+ This function downloads COCO labels, reads image filenames from label list files,
652
+ creates synthetic images for train2017 and val2017 subsets, and organizes
653
+ them in the COCO dataset structure. It uses multithreading to generate images efficiently.
654
+
655
+ Examples:
656
+ >>> from ultralytics.data.converter import create_synthetic_coco_dataset
657
+ >>> create_synthetic_coco_dataset()
658
+
659
+ Notes:
660
+ - Requires internet connection to download label files.
661
+ - Generates random RGB images of varying sizes (480x480 to 640x640 pixels).
662
+ - Existing test2017 directory is removed as it's not needed.
663
+ - Reads image filenames from train2017.txt and val2017.txt files.
664
+ """
665
+
666
+ def create_synthetic_image(image_file):
667
+ """Generates synthetic images with random sizes and colors for dataset augmentation or testing purposes."""
668
+ if not image_file.exists():
669
+ size = (random.randint(480, 640), random.randint(480, 640))
670
+ Image.new(
671
+ "RGB",
672
+ size=size,
673
+ color=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)),
674
+ ).save(image_file)
675
+
676
+ # Download labels
677
+ dir = DATASETS_DIR / "coco"
678
+ url = "https://github.com/ultralytics/assets/releases/download/v0.0.0/"
679
+ label_zip = "coco2017labels-segments.zip"
680
+ download([url + label_zip], dir=dir.parent)
681
+
682
+ # Create synthetic images
683
+ shutil.rmtree(dir / "labels" / "test2017", ignore_errors=True) # Remove test2017 directory as not needed
684
+ with ThreadPoolExecutor(max_workers=NUM_THREADS) as executor:
685
+ for subset in ["train2017", "val2017"]:
686
+ subset_dir = dir / "images" / subset
687
+ subset_dir.mkdir(parents=True, exist_ok=True)
688
+
689
+ # Read image filenames from label list file
690
+ label_list_file = dir / f"{subset}.txt"
691
+ if label_list_file.exists():
692
+ with open(label_list_file) as f:
693
+ image_files = [dir / line.strip() for line in f]
694
+
695
+ # Submit all tasks
696
+ futures = [executor.submit(create_synthetic_image, image_file) for image_file in image_files]
697
+ for _ in TQDM(as_completed(futures), total=len(futures), desc=f"Generating images for {subset}"):
698
+ pass # The actual work is done in the background
699
+ else:
700
+ print(f"Warning: Labels file {label_list_file} does not exist. Skipping image creation for {subset}.")
701
+
702
+ print("Synthetic COCO dataset created successfully.")