ultralytics 8.1.29__py3-none-any.whl → 8.3.63__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 +37 -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 +111 -41
  102. ultralytics/engine/__init__.py +1 -1
  103. ultralytics/engine/exporter.py +579 -244
  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 +191 -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 +226 -82
  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 +172 -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 +305 -112
  232. ultralytics/utils/triton.py +2 -1
  233. ultralytics/utils/tuner.py +21 -12
  234. ultralytics-8.3.63.dist-info/METADATA +370 -0
  235. ultralytics-8.3.63.dist-info/RECORD +241 -0
  236. {ultralytics-8.1.29.dist-info → ultralytics-8.3.63.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.63.dist-info}/LICENSE +0 -0
  246. {ultralytics-8.1.29.dist-info → ultralytics-8.3.63.dist-info}/entry_points.txt +0 -0
  247. {ultralytics-8.1.29.dist-info → ultralytics-8.3.63.dist-info}/top_level.txt +0 -0
ultralytics/utils/ops.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 contextlib
4
4
  import math
@@ -9,7 +9,6 @@ import cv2
9
9
  import numpy as np
10
10
  import torch
11
11
  import torch.nn.functional as F
12
- import torchvision
13
12
 
14
13
  from ultralytics.utils import LOGGER
15
14
  from ultralytics.utils.metrics import batch_probiou
@@ -76,6 +75,10 @@ def segment2box(segment, width=640, height=640):
76
75
  (np.ndarray): the minimum and maximum x and y values of the segment.
77
76
  """
78
77
  x, y = segment.T # segment xy
78
+ # any 3 out of 4 sides are outside the image, clip coordinates first, https://github.com/ultralytics/ultralytics/pull/18294
79
+ if np.array([x.min() < 0, y.min() < 0, x.max() > width, y.max() > height]).sum() >= 3:
80
+ x = x.clip(0, width)
81
+ y = y.clip(0, height)
79
82
  inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height)
80
83
  x = x[inside]
81
84
  y = y[inside]
@@ -142,14 +145,15 @@ def make_divisible(x, divisor):
142
145
 
143
146
  def nms_rotated(boxes, scores, threshold=0.45):
144
147
  """
145
- NMS for obbs, powered by probiou and fast-nms.
148
+ NMS for oriented bounding boxes using probiou and fast-nms.
146
149
 
147
150
  Args:
148
- boxes (torch.Tensor): (N, 5), xywhr.
149
- scores (torch.Tensor): (N, ).
150
- threshold (float): IoU threshold.
151
+ boxes (torch.Tensor): Rotated bounding boxes, shape (N, 5), format xywhr.
152
+ scores (torch.Tensor): Confidence scores, shape (N,).
153
+ threshold (float, optional): IoU threshold. Defaults to 0.45.
151
154
 
152
155
  Returns:
156
+ (torch.Tensor): Indices of boxes to keep after NMS.
153
157
  """
154
158
  if len(boxes) == 0:
155
159
  return np.empty((0,), dtype=np.int8)
@@ -200,22 +204,32 @@ def non_max_suppression(
200
204
  max_nms (int): The maximum number of boxes into torchvision.ops.nms().
201
205
  max_wh (int): The maximum box width and height in pixels.
202
206
  in_place (bool): If True, the input prediction tensor will be modified in place.
207
+ rotated (bool): If Oriented Bounding Boxes (OBB) are being passed for NMS.
203
208
 
204
209
  Returns:
205
210
  (List[torch.Tensor]): A list of length batch_size, where each element is a tensor of
206
211
  shape (num_boxes, 6 + num_masks) containing the kept boxes, with columns
207
212
  (x1, y1, x2, y2, confidence, class, mask1, mask2, ...).
208
213
  """
214
+ import torchvision # scope for faster 'import ultralytics'
209
215
 
210
216
  # Checks
211
217
  assert 0 <= conf_thres <= 1, f"Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0"
212
218
  assert 0 <= iou_thres <= 1, f"Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0"
213
219
  if isinstance(prediction, (list, tuple)): # YOLOv8 model in validation model, output = (inference_out, loss_out)
214
220
  prediction = prediction[0] # select only inference output
221
+ if classes is not None:
222
+ classes = torch.tensor(classes, device=prediction.device)
215
223
 
216
- bs = prediction.shape[0] # batch size
224
+ if prediction.shape[-1] == 6: # end-to-end model (BNC, i.e. 1,300,6)
225
+ output = [pred[pred[:, 4] > conf_thres][:max_det] for pred in prediction]
226
+ if classes is not None:
227
+ output = [pred[(pred[:, 5:6] == classes).any(1)] for pred in output]
228
+ return output
229
+
230
+ bs = prediction.shape[0] # batch size (BCN, i.e. 1,84,6300)
217
231
  nc = nc or (prediction.shape[1] - 4) # number of classes
218
- nm = prediction.shape[1] - nc - 4
232
+ nm = prediction.shape[1] - nc - 4 # number of masks
219
233
  mi = 4 + nc # mask start index
220
234
  xc = prediction[:, 4:mi].amax(1) > conf_thres # candidates
221
235
 
@@ -262,7 +276,7 @@ def non_max_suppression(
262
276
 
263
277
  # Filter by class
264
278
  if classes is not None:
265
- x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]
279
+ x = x[(x[:, 5:6] == classes).any(1)]
266
280
 
267
281
  # Check shape
268
282
  n = x.shape[0] # number of boxes
@@ -307,11 +321,11 @@ def clip_boxes(boxes, shape):
307
321
  Takes a list of bounding boxes and a shape (height, width) and clips the bounding boxes to the shape.
308
322
 
309
323
  Args:
310
- boxes (torch.Tensor): the bounding boxes to clip
311
- shape (tuple): the shape of the image
324
+ boxes (torch.Tensor): The bounding boxes to clip.
325
+ shape (tuple): The shape of the image.
312
326
 
313
327
  Returns:
314
- (torch.Tensor | numpy.ndarray): Clipped boxes
328
+ (torch.Tensor | numpy.ndarray): The clipped boxes.
315
329
  """
316
330
  if isinstance(boxes, torch.Tensor): # faster individually (WARNING: inplace .clamp_() Apple MPS bug)
317
331
  boxes[..., 0] = boxes[..., 0].clamp(0, shape[1]) # x1
@@ -349,12 +363,12 @@ def scale_image(masks, im0_shape, ratio_pad=None):
349
363
  Takes a mask, and resizes it to the original image size.
350
364
 
351
365
  Args:
352
- masks (np.ndarray): resized and padded masks/images, [h, w, num]/[h, w, 3].
353
- im0_shape (tuple): the original image shape
354
- ratio_pad (tuple): the ratio of the padding to the original image.
366
+ masks (np.ndarray): Resized and padded masks/images, [h, w, num]/[h, w, 3].
367
+ im0_shape (tuple): The original image shape.
368
+ ratio_pad (tuple): The ratio of the padding to the original image.
355
369
 
356
370
  Returns:
357
- masks (torch.Tensor): The masks that are being returned.
371
+ masks (np.ndarray): The masks that are being returned with shape [h, w, num].
358
372
  """
359
373
  # Rescale coordinates (xyxy) from im1_shape to im0_shape
360
374
  im1_shape = masks.shape
@@ -391,7 +405,7 @@ def xyxy2xywh(x):
391
405
  y (np.ndarray | torch.Tensor): The bounding box coordinates in (x, y, width, height) format.
392
406
  """
393
407
  assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
394
- y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x) # faster than clone/copy
408
+ y = empty_like(x) # faster than clone/copy
395
409
  y[..., 0] = (x[..., 0] + x[..., 2]) / 2 # x center
396
410
  y[..., 1] = (x[..., 1] + x[..., 3]) / 2 # y center
397
411
  y[..., 2] = x[..., 2] - x[..., 0] # width
@@ -402,7 +416,7 @@ def xyxy2xywh(x):
402
416
  def xywh2xyxy(x):
403
417
  """
404
418
  Convert bounding box coordinates from (x, y, width, height) format to (x1, y1, x2, y2) format where (x1, y1) is the
405
- top-left corner and (x2, y2) is the bottom-right corner.
419
+ top-left corner and (x2, y2) is the bottom-right corner. Note: ops per 2 channels faster than per channel.
406
420
 
407
421
  Args:
408
422
  x (np.ndarray | torch.Tensor): The input bounding box coordinates in (x, y, width, height) format.
@@ -411,13 +425,11 @@ def xywh2xyxy(x):
411
425
  y (np.ndarray | torch.Tensor): The bounding box coordinates in (x1, y1, x2, y2) format.
412
426
  """
413
427
  assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
414
- y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x) # faster than clone/copy
415
- dw = x[..., 2] / 2 # half-width
416
- dh = x[..., 3] / 2 # half-height
417
- y[..., 0] = x[..., 0] - dw # top left x
418
- y[..., 1] = x[..., 1] - dh # top left y
419
- y[..., 2] = x[..., 0] + dw # bottom right x
420
- y[..., 3] = x[..., 1] + dh # bottom right y
428
+ y = empty_like(x) # faster than clone/copy
429
+ xy = x[..., :2] # centers
430
+ wh = x[..., 2:] / 2 # half width-height
431
+ y[..., :2] = xy - wh # top left xy
432
+ y[..., 2:] = xy + wh # bottom right xy
421
433
  return y
422
434
 
423
435
 
@@ -436,7 +448,7 @@ def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
436
448
  x1,y1 is the top-left corner, x2,y2 is the bottom-right corner of the bounding box.
437
449
  """
438
450
  assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
439
- y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x) # faster than clone/copy
451
+ y = empty_like(x) # faster than clone/copy
440
452
  y[..., 0] = w * (x[..., 0] - x[..., 2] / 2) + padw # top left x
441
453
  y[..., 1] = h * (x[..., 1] - x[..., 3] / 2) + padh # top left y
442
454
  y[..., 2] = w * (x[..., 0] + x[..., 2] / 2) + padw # bottom right x
@@ -462,7 +474,7 @@ def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0):
462
474
  if clip:
463
475
  x = clip_boxes(x, (h - eps, w - eps))
464
476
  assert x.shape[-1] == 4, f"input shape last dimension expected 4 but input shape is {x.shape}"
465
- y = torch.empty_like(x) if isinstance(x, torch.Tensor) else np.empty_like(x) # faster than clone/copy
477
+ y = empty_like(x) # faster than clone/copy
466
478
  y[..., 0] = ((x[..., 0] + x[..., 2]) / 2) / w # x center
467
479
  y[..., 1] = ((x[..., 1] + x[..., 3]) / 2) / h # y center
468
480
  y[..., 2] = (x[..., 2] - x[..., 0]) / w # width
@@ -518,59 +530,58 @@ def ltwh2xywh(x):
518
530
  return y
519
531
 
520
532
 
521
- def xyxyxyxy2xywhr(corners):
533
+ def xyxyxyxy2xywhr(x):
522
534
  """
523
535
  Convert batched Oriented Bounding Boxes (OBB) from [xy1, xy2, xy3, xy4] to [xywh, rotation]. Rotation values are
524
- expected in degrees from 0 to 90.
536
+ returned in radians from 0 to pi/2.
525
537
 
526
538
  Args:
527
- corners (numpy.ndarray | torch.Tensor): Input corners of shape (n, 8).
539
+ x (numpy.ndarray | torch.Tensor): Input box corners [xy1, xy2, xy3, xy4] of shape (n, 8).
528
540
 
529
541
  Returns:
530
542
  (numpy.ndarray | torch.Tensor): Converted data in [cx, cy, w, h, rotation] format of shape (n, 5).
531
543
  """
532
- is_torch = isinstance(corners, torch.Tensor)
533
- points = corners.cpu().numpy() if is_torch else corners
534
- points = points.reshape(len(corners), -1, 2)
544
+ is_torch = isinstance(x, torch.Tensor)
545
+ points = x.cpu().numpy() if is_torch else x
546
+ points = points.reshape(len(x), -1, 2)
535
547
  rboxes = []
536
548
  for pts in points:
537
549
  # NOTE: Use cv2.minAreaRect to get accurate xywhr,
538
550
  # especially some objects are cut off by augmentations in dataloader.
539
- (x, y), (w, h), angle = cv2.minAreaRect(pts)
540
- rboxes.append([x, y, w, h, angle / 180 * np.pi])
541
- return (
542
- torch.tensor(rboxes, device=corners.device, dtype=corners.dtype)
543
- if is_torch
544
- else np.asarray(rboxes, dtype=points.dtype)
545
- ) # rboxes
551
+ (cx, cy), (w, h), angle = cv2.minAreaRect(pts)
552
+ rboxes.append([cx, cy, w, h, angle / 180 * np.pi])
553
+ return torch.tensor(rboxes, device=x.device, dtype=x.dtype) if is_torch else np.asarray(rboxes)
546
554
 
547
555
 
548
- def xywhr2xyxyxyxy(rboxes):
556
+ def xywhr2xyxyxyxy(x):
549
557
  """
550
558
  Convert batched Oriented Bounding Boxes (OBB) from [xywh, rotation] to [xy1, xy2, xy3, xy4]. Rotation values should
551
- be in degrees from 0 to 90.
559
+ be in radians from 0 to pi/2.
552
560
 
553
561
  Args:
554
- rboxes (numpy.ndarray | torch.Tensor): Boxes in [cx, cy, w, h, rotation] format of shape (n, 5) or (b, n, 5).
562
+ x (numpy.ndarray | torch.Tensor): Boxes in [cx, cy, w, h, rotation] format of shape (n, 5) or (b, n, 5).
555
563
 
556
564
  Returns:
557
565
  (numpy.ndarray | torch.Tensor): Converted corner points of shape (n, 4, 2) or (b, n, 4, 2).
558
566
  """
559
- is_numpy = isinstance(rboxes, np.ndarray)
560
- cos, sin = (np.cos, np.sin) if is_numpy else (torch.cos, torch.sin)
567
+ cos, sin, cat, stack = (
568
+ (torch.cos, torch.sin, torch.cat, torch.stack)
569
+ if isinstance(x, torch.Tensor)
570
+ else (np.cos, np.sin, np.concatenate, np.stack)
571
+ )
561
572
 
562
- ctr = rboxes[..., :2]
563
- w, h, angle = (rboxes[..., i : i + 1] for i in range(2, 5))
573
+ ctr = x[..., :2]
574
+ w, h, angle = (x[..., i : i + 1] for i in range(2, 5))
564
575
  cos_value, sin_value = cos(angle), sin(angle)
565
576
  vec1 = [w / 2 * cos_value, w / 2 * sin_value]
566
577
  vec2 = [-h / 2 * sin_value, h / 2 * cos_value]
567
- vec1 = np.concatenate(vec1, axis=-1) if is_numpy else torch.cat(vec1, dim=-1)
568
- vec2 = np.concatenate(vec2, axis=-1) if is_numpy else torch.cat(vec2, dim=-1)
578
+ vec1 = cat(vec1, -1)
579
+ vec2 = cat(vec2, -1)
569
580
  pt1 = ctr + vec1 + vec2
570
581
  pt2 = ctr + vec1 - vec2
571
582
  pt3 = ctr - vec1 - vec2
572
583
  pt4 = ctr - vec1 + vec2
573
- return np.stack([pt1, pt2, pt3, pt4], axis=-2) if is_numpy else torch.stack([pt1, pt2, pt3, pt4], dim=-2)
584
+ return stack([pt1, pt2, pt3, pt4], -2)
574
585
 
575
586
 
576
587
  def ltwh2xyxy(x):
@@ -591,7 +602,7 @@ def ltwh2xyxy(x):
591
602
 
592
603
  def segments2boxes(segments):
593
604
  """
594
- It converts segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh)
605
+ It converts segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh).
595
606
 
596
607
  Args:
597
608
  segments (list): list of segments, each segment is a list of points, each point is a list of x, y coordinates
@@ -618,9 +629,12 @@ def resample_segments(segments, n=1000):
618
629
  segments (list): the resampled segments.
619
630
  """
620
631
  for i, s in enumerate(segments):
632
+ if len(s) == n:
633
+ continue
621
634
  s = np.concatenate((s, s[0:1, :]), axis=0)
622
- x = np.linspace(0, len(s) - 1, n)
635
+ x = np.linspace(0, len(s) - 1, n - len(s) if len(s) < n else n)
623
636
  xp = np.arange(len(s))
637
+ x = np.insert(x, np.searchsorted(x, xp), xp) if len(s) < n else x
624
638
  segments[i] = (
625
639
  np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)], dtype=np.float32).reshape(2, -1).T
626
640
  ) # segment xy
@@ -646,27 +660,6 @@ def crop_mask(masks, boxes):
646
660
  return masks * ((r >= x1) * (r < x2) * (c >= y1) * (c < y2))
647
661
 
648
662
 
649
- def process_mask_upsample(protos, masks_in, bboxes, shape):
650
- """
651
- Takes the output of the mask head, and applies the mask to the bounding boxes. This produces masks of higher quality
652
- but is slower.
653
-
654
- Args:
655
- protos (torch.Tensor): [mask_dim, mask_h, mask_w]
656
- masks_in (torch.Tensor): [n, mask_dim], n is number of masks after nms
657
- bboxes (torch.Tensor): [n, 4], n is number of masks after nms
658
- shape (tuple): the size of the input image (h,w)
659
-
660
- Returns:
661
- (torch.Tensor): The upsampled masks.
662
- """
663
- c, mh, mw = protos.shape # CHW
664
- masks = (masks_in @ protos.float().view(c, -1)).sigmoid().view(-1, mh, mw)
665
- masks = F.interpolate(masks[None], shape, mode="bilinear", align_corners=False)[0] # CHW
666
- masks = crop_mask(masks, bboxes) # CHW
667
- return masks.gt_(0.5)
668
-
669
-
670
663
  def process_mask(protos, masks_in, bboxes, shape, upsample=False):
671
664
  """
672
665
  Apply masks to bounding boxes using the output of the mask head.
@@ -682,10 +675,9 @@ def process_mask(protos, masks_in, bboxes, shape, upsample=False):
682
675
  (torch.Tensor): A binary mask tensor of shape [n, h, w], where n is the number of masks after NMS, and h and w
683
676
  are the height and width of the input image. The mask is applied to the bounding boxes.
684
677
  """
685
-
686
678
  c, mh, mw = protos.shape # CHW
687
679
  ih, iw = shape
688
- masks = (masks_in @ protos.float().view(c, -1)).sigmoid().view(-1, mh, mw) # CHW
680
+ masks = (masks_in @ protos.float().view(c, -1)).view(-1, mh, mw) # CHW
689
681
  width_ratio = mw / iw
690
682
  height_ratio = mh / ih
691
683
 
@@ -698,7 +690,7 @@ def process_mask(protos, masks_in, bboxes, shape, upsample=False):
698
690
  masks = crop_mask(masks, downsampled_bboxes) # CHW
699
691
  if upsample:
700
692
  masks = F.interpolate(masks[None], shape, mode="bilinear", align_corners=False)[0] # CHW
701
- return masks.gt_(0.5)
693
+ return masks.gt_(0.0)
702
694
 
703
695
 
704
696
  def process_mask_native(protos, masks_in, bboxes, shape):
@@ -707,18 +699,18 @@ def process_mask_native(protos, masks_in, bboxes, shape):
707
699
 
708
700
  Args:
709
701
  protos (torch.Tensor): [mask_dim, mask_h, mask_w]
710
- masks_in (torch.Tensor): [n, mask_dim], n is number of masks after nms
711
- bboxes (torch.Tensor): [n, 4], n is number of masks after nms
712
- shape (tuple): the size of the input image (h,w)
702
+ masks_in (torch.Tensor): [n, mask_dim], n is number of masks after nms.
703
+ bboxes (torch.Tensor): [n, 4], n is number of masks after nms.
704
+ shape (tuple): The size of the input image (h,w).
713
705
 
714
706
  Returns:
715
- masks (torch.Tensor): The returned masks with dimensions [h, w, n]
707
+ masks (torch.Tensor): The returned masks with dimensions [h, w, n].
716
708
  """
717
709
  c, mh, mw = protos.shape # CHW
718
- masks = (masks_in @ protos.float().view(c, -1)).sigmoid().view(-1, mh, mw)
710
+ masks = (masks_in @ protos.float().view(c, -1)).view(-1, mh, mw)
719
711
  masks = scale_masks(masks[None], shape)[0] # CHW
720
712
  masks = crop_mask(masks, bboxes) # CHW
721
- return masks.gt_(0.5)
713
+ return masks.gt_(0.0)
722
714
 
723
715
 
724
716
  def scale_masks(masks, shape, padding=True):
@@ -785,7 +777,7 @@ def regularize_rboxes(rboxes):
785
777
  Regularize rotated boxes in range [0, pi/2].
786
778
 
787
779
  Args:
788
- rboxes (torch.Tensor): (N, 5), xywhr.
780
+ rboxes (torch.Tensor): Input boxes of shape(N, 5) in xywhr format.
789
781
 
790
782
  Returns:
791
783
  (torch.Tensor): The regularized boxes.
@@ -798,23 +790,29 @@ def regularize_rboxes(rboxes):
798
790
  return torch.stack([x, y, w_, h_, t], dim=-1) # regularized boxes
799
791
 
800
792
 
801
- def masks2segments(masks, strategy="largest"):
793
+ def masks2segments(masks, strategy="all"):
802
794
  """
803
- It takes a list of masks(n,h,w) and returns a list of segments(n,xy)
795
+ It takes a list of masks(n,h,w) and returns a list of segments(n,xy).
804
796
 
805
797
  Args:
806
798
  masks (torch.Tensor): the output of the model, which is a tensor of shape (batch_size, 160, 160)
807
- strategy (str): 'concat' or 'largest'. Defaults to largest
799
+ strategy (str): 'all' or 'largest'. Defaults to all
808
800
 
809
801
  Returns:
810
802
  segments (List): list of segment masks
811
803
  """
804
+ from ultralytics.data.converter import merge_multi_segment
805
+
812
806
  segments = []
813
807
  for x in masks.int().cpu().numpy().astype("uint8"):
814
808
  c = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
815
809
  if c:
816
- if strategy == "concat": # concatenate all segments
817
- c = np.concatenate([x.reshape(-1, 2) for x in c])
810
+ if strategy == "all": # merge and concatenate all segments
811
+ c = (
812
+ np.concatenate(merge_multi_segment([x.reshape(-1, 2) for x in c]))
813
+ if len(c) > 1
814
+ else c[0].reshape(-1, 2)
815
+ )
818
816
  elif strategy == "largest": # select largest segment
819
817
  c = np.array(c[np.array([len(x) for x in c]).argmax()]).reshape(-1, 2)
820
818
  else:
@@ -838,7 +836,7 @@ def convert_torch2numpy_batch(batch: torch.Tensor) -> np.ndarray:
838
836
 
839
837
  def clean_str(s):
840
838
  """
841
- Cleans a string by replacing special characters with underscore _
839
+ Cleans a string by replacing special characters with '_' character.
842
840
 
843
841
  Args:
844
842
  s (str): a string needing special characters replaced
@@ -847,3 +845,10 @@ def clean_str(s):
847
845
  (str): a string with special characters replaced by an underscore _
848
846
  """
849
847
  return re.sub(pattern="[|@#!¡·$€%&()=?¿^*;:,¨´><+]", repl="_", string=s)
848
+
849
+
850
+ def empty_like(x):
851
+ """Creates empty torch.Tensor or np.ndarray with same shape as input and float32 dtype."""
852
+ return (
853
+ torch.empty_like(x, dtype=torch.float32) if isinstance(x, torch.Tensor) else np.empty_like(x, dtype=np.float32)
854
+ )
@@ -1,4 +1,4 @@
1
- # Ultralytics YOLO 🚀, AGPL-3.0 license
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
  """Monkey patches to update/extend functionality of existing functions."""
3
3
 
4
4
  import time
@@ -57,28 +57,44 @@ def imshow(winname: str, mat: np.ndarray):
57
57
 
58
58
 
59
59
  # PyTorch functions ----------------------------------------------------------------------------------------------------
60
- _torch_save = torch.save # copy to avoid recursion errors
60
+ _torch_load = torch.load # copy to avoid recursion errors
61
+ _torch_save = torch.save
61
62
 
62
63
 
63
- def torch_save(*args, use_dill=True, **kwargs):
64
+ def torch_load(*args, **kwargs):
65
+ """
66
+ Load a PyTorch model with updated arguments to avoid warnings.
67
+
68
+ This function wraps torch.load and adds the 'weights_only' argument for PyTorch 1.13.0+ to prevent warnings.
69
+
70
+ Args:
71
+ *args (Any): Variable length argument list to pass to torch.load.
72
+ **kwargs (Any): Arbitrary keyword arguments to pass to torch.load.
73
+
74
+ Returns:
75
+ (Any): The loaded PyTorch object.
76
+
77
+ Note:
78
+ For PyTorch versions 2.0 and above, this function automatically sets 'weights_only=False'
79
+ if the argument is not provided, to avoid deprecation warnings.
80
+ """
81
+ from ultralytics.utils.torch_utils import TORCH_1_13
82
+
83
+ if TORCH_1_13 and "weights_only" not in kwargs:
84
+ kwargs["weights_only"] = False
85
+
86
+ return _torch_load(*args, **kwargs)
87
+
88
+
89
+ def torch_save(*args, **kwargs):
64
90
  """
65
91
  Optionally use dill to serialize lambda functions where pickle does not, adding robustness with 3 retries and
66
92
  exponential standoff in case of save failure.
67
93
 
68
94
  Args:
69
95
  *args (tuple): Positional arguments to pass to torch.save.
70
- use_dill (bool): Whether to try using dill for serialization if available. Defaults to True.
71
- **kwargs (any): Keyword arguments to pass to torch.save.
96
+ **kwargs (Any): Keyword arguments to pass to torch.save.
72
97
  """
73
- try:
74
- assert use_dill
75
- import dill as pickle
76
- except (AssertionError, ImportError):
77
- import pickle
78
-
79
- if "pickle_module" not in kwargs:
80
- kwargs["pickle_module"] = pickle
81
-
82
98
  for i in range(4): # 3 retries
83
99
  try:
84
100
  return _torch_save(*args, **kwargs)