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.
- tests/__init__.py +22 -0
- tests/conftest.py +83 -0
- tests/test_cli.py +122 -0
- tests/test_cuda.py +155 -0
- tests/test_engine.py +131 -0
- tests/test_exports.py +216 -0
- tests/test_integrations.py +150 -0
- tests/test_python.py +615 -0
- tests/test_solutions.py +94 -0
- ultralytics/__init__.py +11 -8
- ultralytics/cfg/__init__.py +569 -131
- ultralytics/cfg/datasets/Argoverse.yaml +2 -1
- ultralytics/cfg/datasets/DOTAv1.5.yaml +3 -2
- ultralytics/cfg/datasets/DOTAv1.yaml +3 -2
- ultralytics/cfg/datasets/GlobalWheat2020.yaml +3 -2
- ultralytics/cfg/datasets/ImageNet.yaml +2 -1
- ultralytics/cfg/datasets/Objects365.yaml +5 -4
- ultralytics/cfg/datasets/SKU-110K.yaml +2 -1
- ultralytics/cfg/datasets/VOC.yaml +3 -2
- ultralytics/cfg/datasets/VisDrone.yaml +6 -5
- ultralytics/cfg/datasets/african-wildlife.yaml +25 -0
- ultralytics/cfg/datasets/brain-tumor.yaml +23 -0
- ultralytics/cfg/datasets/carparts-seg.yaml +3 -2
- ultralytics/cfg/datasets/coco-pose.yaml +7 -6
- ultralytics/cfg/datasets/coco.yaml +3 -2
- ultralytics/cfg/datasets/coco128-seg.yaml +4 -3
- ultralytics/cfg/datasets/coco128.yaml +4 -3
- ultralytics/cfg/datasets/coco8-pose.yaml +3 -2
- ultralytics/cfg/datasets/coco8-seg.yaml +3 -2
- ultralytics/cfg/datasets/coco8.yaml +3 -2
- ultralytics/cfg/datasets/crack-seg.yaml +3 -2
- ultralytics/cfg/datasets/dog-pose.yaml +24 -0
- ultralytics/cfg/datasets/dota8.yaml +3 -2
- ultralytics/cfg/datasets/hand-keypoints.yaml +26 -0
- ultralytics/cfg/datasets/lvis.yaml +1236 -0
- ultralytics/cfg/datasets/medical-pills.yaml +22 -0
- ultralytics/cfg/datasets/open-images-v7.yaml +2 -1
- ultralytics/cfg/datasets/package-seg.yaml +5 -4
- ultralytics/cfg/datasets/signature.yaml +21 -0
- ultralytics/cfg/datasets/tiger-pose.yaml +3 -2
- ultralytics/cfg/datasets/xView.yaml +2 -1
- ultralytics/cfg/default.yaml +14 -11
- ultralytics/cfg/models/11/yolo11-cls-resnet18.yaml +24 -0
- ultralytics/cfg/models/11/yolo11-cls.yaml +33 -0
- ultralytics/cfg/models/11/yolo11-obb.yaml +50 -0
- ultralytics/cfg/models/11/yolo11-pose.yaml +51 -0
- ultralytics/cfg/models/11/yolo11-seg.yaml +50 -0
- ultralytics/cfg/models/11/yolo11.yaml +50 -0
- ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +5 -2
- ultralytics/cfg/models/v10/yolov10b.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10l.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10m.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10n.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10s.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10x.yaml +45 -0
- ultralytics/cfg/models/v3/yolov3-spp.yaml +5 -2
- ultralytics/cfg/models/v3/yolov3-tiny.yaml +5 -2
- ultralytics/cfg/models/v3/yolov3.yaml +5 -2
- ultralytics/cfg/models/v5/yolov5-p6.yaml +5 -2
- ultralytics/cfg/models/v5/yolov5.yaml +5 -2
- ultralytics/cfg/models/v6/yolov6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +6 -2
- ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +6 -2
- ultralytics/cfg/models/v8/yolov8-ghost.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-obb.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-p2.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-p6.yaml +10 -7
- ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-pose.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-seg.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-world.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-worldv2.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8.yaml +5 -2
- ultralytics/cfg/models/v9/yolov9c-seg.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9c.yaml +30 -25
- ultralytics/cfg/models/v9/yolov9e-seg.yaml +64 -0
- ultralytics/cfg/models/v9/yolov9e.yaml +46 -42
- ultralytics/cfg/models/v9/yolov9m.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9s.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9t.yaml +41 -0
- ultralytics/cfg/solutions/default.yaml +24 -0
- ultralytics/cfg/trackers/botsort.yaml +8 -5
- ultralytics/cfg/trackers/bytetrack.yaml +8 -5
- ultralytics/data/__init__.py +14 -3
- ultralytics/data/annotator.py +37 -15
- ultralytics/data/augment.py +1783 -289
- ultralytics/data/base.py +62 -27
- ultralytics/data/build.py +36 -8
- ultralytics/data/converter.py +196 -36
- ultralytics/data/dataset.py +233 -94
- ultralytics/data/loaders.py +199 -96
- ultralytics/data/split_dota.py +39 -29
- ultralytics/data/utils.py +110 -40
- ultralytics/engine/__init__.py +1 -1
- ultralytics/engine/exporter.py +569 -242
- ultralytics/engine/model.py +604 -252
- ultralytics/engine/predictor.py +22 -11
- ultralytics/engine/results.py +1228 -218
- ultralytics/engine/trainer.py +190 -129
- ultralytics/engine/tuner.py +18 -18
- ultralytics/engine/validator.py +18 -15
- ultralytics/hub/__init__.py +31 -13
- ultralytics/hub/auth.py +11 -7
- ultralytics/hub/google/__init__.py +159 -0
- ultralytics/hub/session.py +128 -94
- ultralytics/hub/utils.py +20 -21
- ultralytics/models/__init__.py +4 -2
- ultralytics/models/fastsam/__init__.py +2 -3
- ultralytics/models/fastsam/model.py +26 -4
- ultralytics/models/fastsam/predict.py +127 -63
- ultralytics/models/fastsam/utils.py +1 -44
- ultralytics/models/fastsam/val.py +1 -1
- ultralytics/models/nas/__init__.py +1 -1
- ultralytics/models/nas/model.py +21 -10
- ultralytics/models/nas/predict.py +3 -6
- ultralytics/models/nas/val.py +4 -4
- ultralytics/models/rtdetr/__init__.py +1 -1
- ultralytics/models/rtdetr/model.py +1 -1
- ultralytics/models/rtdetr/predict.py +6 -8
- ultralytics/models/rtdetr/train.py +6 -2
- ultralytics/models/rtdetr/val.py +3 -3
- ultralytics/models/sam/__init__.py +3 -3
- ultralytics/models/sam/amg.py +29 -23
- ultralytics/models/sam/build.py +211 -13
- ultralytics/models/sam/model.py +91 -30
- ultralytics/models/sam/modules/__init__.py +1 -1
- ultralytics/models/sam/modules/blocks.py +1129 -0
- ultralytics/models/sam/modules/decoders.py +381 -53
- ultralytics/models/sam/modules/encoders.py +515 -324
- ultralytics/models/sam/modules/memory_attention.py +237 -0
- ultralytics/models/sam/modules/sam.py +969 -21
- ultralytics/models/sam/modules/tiny_encoder.py +425 -154
- ultralytics/models/sam/modules/transformer.py +159 -60
- ultralytics/models/sam/modules/utils.py +293 -0
- ultralytics/models/sam/predict.py +1263 -132
- ultralytics/models/utils/__init__.py +1 -1
- ultralytics/models/utils/loss.py +36 -24
- ultralytics/models/utils/ops.py +3 -7
- ultralytics/models/yolo/__init__.py +3 -3
- ultralytics/models/yolo/classify/__init__.py +1 -1
- ultralytics/models/yolo/classify/predict.py +7 -8
- ultralytics/models/yolo/classify/train.py +17 -22
- ultralytics/models/yolo/classify/val.py +8 -4
- ultralytics/models/yolo/detect/__init__.py +1 -1
- ultralytics/models/yolo/detect/predict.py +3 -5
- ultralytics/models/yolo/detect/train.py +11 -4
- ultralytics/models/yolo/detect/val.py +90 -52
- ultralytics/models/yolo/model.py +14 -9
- ultralytics/models/yolo/obb/__init__.py +1 -1
- ultralytics/models/yolo/obb/predict.py +2 -2
- ultralytics/models/yolo/obb/train.py +5 -3
- ultralytics/models/yolo/obb/val.py +41 -23
- ultralytics/models/yolo/pose/__init__.py +1 -1
- ultralytics/models/yolo/pose/predict.py +3 -5
- ultralytics/models/yolo/pose/train.py +2 -2
- ultralytics/models/yolo/pose/val.py +51 -17
- ultralytics/models/yolo/segment/__init__.py +1 -1
- ultralytics/models/yolo/segment/predict.py +3 -5
- ultralytics/models/yolo/segment/train.py +2 -2
- ultralytics/models/yolo/segment/val.py +60 -19
- ultralytics/models/yolo/world/__init__.py +5 -0
- ultralytics/models/yolo/world/train.py +92 -0
- ultralytics/models/yolo/world/train_world.py +109 -0
- ultralytics/nn/__init__.py +1 -1
- ultralytics/nn/autobackend.py +228 -93
- ultralytics/nn/modules/__init__.py +39 -14
- ultralytics/nn/modules/activation.py +21 -0
- ultralytics/nn/modules/block.py +527 -67
- ultralytics/nn/modules/conv.py +24 -7
- ultralytics/nn/modules/head.py +177 -34
- ultralytics/nn/modules/transformer.py +6 -5
- ultralytics/nn/modules/utils.py +1 -2
- ultralytics/nn/tasks.py +225 -77
- ultralytics/solutions/__init__.py +30 -1
- ultralytics/solutions/ai_gym.py +96 -143
- ultralytics/solutions/analytics.py +247 -0
- ultralytics/solutions/distance_calculation.py +78 -135
- ultralytics/solutions/heatmap.py +93 -247
- ultralytics/solutions/object_counter.py +184 -259
- ultralytics/solutions/parking_management.py +246 -0
- ultralytics/solutions/queue_management.py +112 -0
- ultralytics/solutions/region_counter.py +116 -0
- ultralytics/solutions/security_alarm.py +144 -0
- ultralytics/solutions/solutions.py +178 -0
- ultralytics/solutions/speed_estimation.py +86 -174
- ultralytics/solutions/streamlit_inference.py +190 -0
- ultralytics/solutions/trackzone.py +68 -0
- ultralytics/trackers/__init__.py +1 -1
- ultralytics/trackers/basetrack.py +32 -13
- ultralytics/trackers/bot_sort.py +61 -28
- ultralytics/trackers/byte_tracker.py +83 -51
- ultralytics/trackers/track.py +21 -6
- ultralytics/trackers/utils/__init__.py +1 -1
- ultralytics/trackers/utils/gmc.py +62 -48
- ultralytics/trackers/utils/kalman_filter.py +166 -35
- ultralytics/trackers/utils/matching.py +40 -21
- ultralytics/utils/__init__.py +511 -239
- ultralytics/utils/autobatch.py +40 -22
- ultralytics/utils/benchmarks.py +266 -85
- ultralytics/utils/callbacks/__init__.py +1 -1
- ultralytics/utils/callbacks/base.py +1 -3
- ultralytics/utils/callbacks/clearml.py +7 -6
- ultralytics/utils/callbacks/comet.py +39 -17
- ultralytics/utils/callbacks/dvc.py +1 -1
- ultralytics/utils/callbacks/hub.py +16 -16
- ultralytics/utils/callbacks/mlflow.py +28 -24
- ultralytics/utils/callbacks/neptune.py +6 -2
- ultralytics/utils/callbacks/raytune.py +3 -4
- ultralytics/utils/callbacks/tensorboard.py +18 -18
- ultralytics/utils/callbacks/wb.py +27 -20
- ultralytics/utils/checks.py +160 -100
- ultralytics/utils/dist.py +2 -1
- ultralytics/utils/downloads.py +44 -37
- ultralytics/utils/errors.py +1 -1
- ultralytics/utils/files.py +72 -38
- ultralytics/utils/instance.py +41 -19
- ultralytics/utils/loss.py +84 -56
- ultralytics/utils/metrics.py +61 -56
- ultralytics/utils/ops.py +94 -89
- ultralytics/utils/patches.py +30 -14
- ultralytics/utils/plotting.py +600 -269
- ultralytics/utils/tal.py +67 -26
- ultralytics/utils/torch_utils.py +302 -102
- ultralytics/utils/triton.py +2 -1
- ultralytics/utils/tuner.py +21 -12
- ultralytics-8.3.62.dist-info/METADATA +370 -0
- ultralytics-8.3.62.dist-info/RECORD +241 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/WHEEL +1 -1
- ultralytics/data/explorer/__init__.py +0 -5
- ultralytics/data/explorer/explorer.py +0 -472
- ultralytics/data/explorer/gui/__init__.py +0 -1
- ultralytics/data/explorer/gui/dash.py +0 -268
- ultralytics/data/explorer/utils.py +0 -166
- ultralytics/models/fastsam/prompt.py +0 -357
- ultralytics-8.1.28.dist-info/METADATA +0 -373
- ultralytics-8.1.28.dist-info/RECORD +0 -197
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/LICENSE +0 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/top_level.txt +0 -0
ultralytics/data/split_dota.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Ultralytics
|
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
|
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
|
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
|
-
|
39
|
-
polygon2 = np.stack([
|
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
|
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=
|
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)
|
178
|
-
|
179
|
-
label
|
180
|
-
|
181
|
-
|
182
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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=
|
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=
|
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=
|
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
|
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
|
-
|
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
|
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
|
-
|
62
|
-
exif
|
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
|
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"
|
82
|
-
if im.format.lower() in
|
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
|
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
|
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 '{
|
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
|
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
|
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/
|
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
|
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(
|
440
|
-
stats = HUBDatasetStats(
|
441
|
-
stats = HUBDatasetStats(
|
442
|
-
stats = HUBDatasetStats(
|
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
|
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.
|
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
|
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
|
502
|
-
coordinates = np.concatenate((labels["bboxes"], labels["keypoints"].reshape(n,
|
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(
|
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.")
|
ultralytics/engine/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
# Ultralytics
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|