ultralytics 8.3.111__py3-none-any.whl → 8.3.112__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.
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +14 -16
- ultralytics/cfg/datasets/coco8-multispectral.yaml +104 -0
- ultralytics/data/augment.py +16 -6
- ultralytics/data/base.py +24 -26
- ultralytics/data/converter.py +52 -3
- ultralytics/data/dataset.py +5 -5
- ultralytics/data/loaders.py +7 -9
- ultralytics/data/split.py +123 -0
- ultralytics/data/utils.py +34 -52
- ultralytics/engine/exporter.py +22 -24
- ultralytics/engine/model.py +3 -6
- ultralytics/engine/predictor.py +5 -3
- ultralytics/engine/results.py +7 -7
- ultralytics/engine/trainer.py +4 -5
- ultralytics/engine/tuner.py +1 -1
- ultralytics/engine/validator.py +4 -4
- ultralytics/hub/auth.py +1 -1
- ultralytics/hub/session.py +3 -3
- ultralytics/models/rtdetr/train.py +1 -22
- ultralytics/models/sam/modules/sam.py +2 -1
- ultralytics/models/yolo/classify/train.py +1 -1
- ultralytics/models/yolo/detect/train.py +2 -2
- ultralytics/models/yolo/detect/val.py +1 -1
- ultralytics/models/yolo/obb/train.py +1 -1
- ultralytics/models/yolo/pose/predict.py +1 -1
- ultralytics/models/yolo/pose/train.py +4 -2
- ultralytics/models/yolo/pose/val.py +1 -1
- ultralytics/models/yolo/segment/train.py +1 -1
- ultralytics/models/yolo/segment/val.py +1 -1
- ultralytics/models/yolo/world/train.py +1 -1
- ultralytics/models/yolo/world/train_world.py +1 -0
- ultralytics/models/yolo/yoloe/train.py +2 -2
- ultralytics/models/yolo/yoloe/train_seg.py +2 -2
- ultralytics/nn/autobackend.py +7 -4
- ultralytics/nn/tasks.py +11 -11
- ultralytics/solutions/instance_segmentation.py +1 -1
- ultralytics/solutions/object_blurrer.py +1 -1
- ultralytics/solutions/object_cropper.py +2 -2
- ultralytics/solutions/parking_management.py +1 -1
- ultralytics/solutions/security_alarm.py +1 -1
- ultralytics/solutions/solutions.py +3 -6
- ultralytics/trackers/byte_tracker.py +1 -1
- ultralytics/trackers/utils/gmc.py +4 -4
- ultralytics/utils/__init__.py +28 -21
- ultralytics/utils/autobatch.py +4 -4
- ultralytics/utils/benchmarks.py +8 -8
- ultralytics/utils/callbacks/clearml.py +1 -1
- ultralytics/utils/callbacks/comet.py +5 -5
- ultralytics/utils/callbacks/dvc.py +1 -1
- ultralytics/utils/callbacks/mlflow.py +2 -1
- ultralytics/utils/callbacks/neptune.py +1 -1
- ultralytics/utils/callbacks/tensorboard.py +7 -9
- ultralytics/utils/checks.py +20 -26
- ultralytics/utils/downloads.py +4 -4
- ultralytics/utils/export.py +1 -1
- ultralytics/utils/metrics.py +1 -1
- ultralytics/utils/ops.py +1 -1
- ultralytics/utils/patches.py +8 -1
- ultralytics/utils/plotting.py +27 -29
- ultralytics/utils/tal.py +1 -1
- ultralytics/utils/torch_utils.py +4 -4
- ultralytics/utils/tuner.py +2 -2
- {ultralytics-8.3.111.dist-info → ultralytics-8.3.112.dist-info}/METADATA +1 -1
- {ultralytics-8.3.111.dist-info → ultralytics-8.3.112.dist-info}/RECORD +69 -67
- {ultralytics-8.3.111.dist-info → ultralytics-8.3.112.dist-info}/WHEEL +1 -1
- {ultralytics-8.3.111.dist-info → ultralytics-8.3.112.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.3.111.dist-info → ultralytics-8.3.112.dist-info}/licenses/LICENSE +0 -0
- {ultralytics-8.3.111.dist-info → ultralytics-8.3.112.dist-info}/top_level.txt +0 -0
ultralytics/__init__.py
CHANGED
ultralytics/cfg/__init__.py
CHANGED
@@ -315,7 +315,7 @@ def get_cfg(cfg: Union[str, Path, Dict, SimpleNamespace] = DEFAULT_CFG_DICT, ove
|
|
315
315
|
cfg[k] = str(cfg[k])
|
316
316
|
if cfg.get("name") == "model": # assign model to 'name' arg
|
317
317
|
cfg["name"] = str(cfg.get("model", "")).split(".")[0]
|
318
|
-
LOGGER.warning(f"
|
318
|
+
LOGGER.warning(f"'name=model' automatically updated to 'name={cfg['name']}'.")
|
319
319
|
|
320
320
|
# Type and Value checks
|
321
321
|
check_cfg(cfg)
|
@@ -570,9 +570,7 @@ def handle_yolo_hub(args: List[str]) -> None:
|
|
570
570
|
or 'logout'. For 'login', an optional second argument can be the API key.
|
571
571
|
|
572
572
|
Examples:
|
573
|
-
|
574
|
-
yolo login YOUR_API_KEY
|
575
|
-
```
|
573
|
+
$ yolo login YOUR_API_KEY
|
576
574
|
|
577
575
|
Notes:
|
578
576
|
- The function imports the 'hub' module from ultralytics to perform login and logout operations.
|
@@ -625,10 +623,10 @@ def handle_yolo_settings(args: List[str]) -> None:
|
|
625
623
|
check_dict_alignment(SETTINGS, new)
|
626
624
|
SETTINGS.update(new)
|
627
625
|
|
628
|
-
|
626
|
+
LOGGER.info(SETTINGS) # print the current settings
|
629
627
|
LOGGER.info(f"💡 Learn more about Ultralytics Settings at {url}")
|
630
628
|
except Exception as e:
|
631
|
-
LOGGER.warning(f"
|
629
|
+
LOGGER.warning(f"settings error: '{e}'. Please see {url} for help.")
|
632
630
|
|
633
631
|
|
634
632
|
def handle_yolo_solutions(args: List[str]) -> None:
|
@@ -687,7 +685,7 @@ def handle_yolo_solutions(args: List[str]) -> None:
|
|
687
685
|
|
688
686
|
# Get solution name
|
689
687
|
if not args:
|
690
|
-
LOGGER.warning("
|
688
|
+
LOGGER.warning("No solution name provided. i.e `yolo solutions count`. Defaulting to 'count'.")
|
691
689
|
args = ["count"]
|
692
690
|
if args[0] == "help":
|
693
691
|
LOGGER.info(SOLUTIONS_HELP_MSG)
|
@@ -877,10 +875,10 @@ def entrypoint(debug: str = "") -> None:
|
|
877
875
|
overrides = {} # basic overrides, i.e. imgsz=320
|
878
876
|
for a in merge_equals_args(args): # merge spaces around '=' sign
|
879
877
|
if a.startswith("--"):
|
880
|
-
LOGGER.warning(f"
|
878
|
+
LOGGER.warning(f"argument '{a}' does not require leading dashes '--', updating to '{a[2:]}'.")
|
881
879
|
a = a[2:]
|
882
880
|
if a.endswith(","):
|
883
|
-
LOGGER.warning(f"
|
881
|
+
LOGGER.warning(f"argument '{a}' does not require trailing comma ',', updating to '{a[:-1]}'.")
|
884
882
|
a = a[:-1]
|
885
883
|
if "=" in a:
|
886
884
|
try:
|
@@ -917,7 +915,7 @@ def entrypoint(debug: str = "") -> None:
|
|
917
915
|
mode = overrides.get("mode")
|
918
916
|
if mode is None:
|
919
917
|
mode = DEFAULT_CFG.mode or "predict"
|
920
|
-
LOGGER.warning(f"
|
918
|
+
LOGGER.warning(f"'mode' argument is missing. Valid modes are {MODES}. Using default 'mode={mode}'.")
|
921
919
|
elif mode not in MODES:
|
922
920
|
raise ValueError(f"Invalid 'mode={mode}'. Valid modes are {MODES}.\n{CLI_HELP_MSG}")
|
923
921
|
|
@@ -927,7 +925,7 @@ def entrypoint(debug: str = "") -> None:
|
|
927
925
|
if task not in TASKS:
|
928
926
|
if task == "track":
|
929
927
|
LOGGER.warning(
|
930
|
-
"
|
928
|
+
"invalid 'task=track', setting 'task=detect' and 'mode=track'. Valid tasks are {TASKS}.\n{CLI_HELP_MSG}."
|
931
929
|
)
|
932
930
|
task, mode = "detect", "track"
|
933
931
|
else:
|
@@ -939,7 +937,7 @@ def entrypoint(debug: str = "") -> None:
|
|
939
937
|
model = overrides.pop("model", DEFAULT_CFG.model)
|
940
938
|
if model is None:
|
941
939
|
model = "yolo11n.pt"
|
942
|
-
LOGGER.warning(f"
|
940
|
+
LOGGER.warning(f"'model' argument is missing. Using default 'model={model}'.")
|
943
941
|
overrides["model"] = model
|
944
942
|
stem = Path(model).stem.lower()
|
945
943
|
if "rtdetr" in stem: # guess architecture
|
@@ -965,7 +963,7 @@ def entrypoint(debug: str = "") -> None:
|
|
965
963
|
if task != model.task:
|
966
964
|
if task:
|
967
965
|
LOGGER.warning(
|
968
|
-
f"
|
966
|
+
f"conflicting 'task={task}' passed with 'task={model.task}' model. "
|
969
967
|
f"Ignoring 'task={task}' and updating to 'task={model.task}' to match model."
|
970
968
|
)
|
971
969
|
task = model.task
|
@@ -975,15 +973,15 @@ def entrypoint(debug: str = "") -> None:
|
|
975
973
|
overrides["source"] = (
|
976
974
|
"https://ultralytics.com/images/boats.jpg" if task == "obb" else DEFAULT_CFG.source or ASSETS
|
977
975
|
)
|
978
|
-
LOGGER.warning(f"
|
976
|
+
LOGGER.warning(f"'source' argument is missing. Using default 'source={overrides['source']}'.")
|
979
977
|
elif mode in {"train", "val"}:
|
980
978
|
if "data" not in overrides and "resume" not in overrides:
|
981
979
|
overrides["data"] = DEFAULT_CFG.data or TASK2DATA.get(task or DEFAULT_CFG.task, DEFAULT_CFG.data)
|
982
|
-
LOGGER.warning(f"
|
980
|
+
LOGGER.warning(f"'data' argument is missing. Using default 'data={overrides['data']}'.")
|
983
981
|
elif mode == "export":
|
984
982
|
if "format" not in overrides:
|
985
983
|
overrides["format"] = DEFAULT_CFG.format or "torchscript"
|
986
|
-
LOGGER.warning(f"
|
984
|
+
LOGGER.warning(f"'format' argument is missing. Using default 'format={overrides['format']}'.")
|
987
985
|
|
988
986
|
# Run command in python
|
989
987
|
getattr(model, mode)(**overrides) # default args from model
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
|
+
|
3
|
+
# COCO8-Multispectral dataset (COCO8 images interpolated across 10 channels in the visual spectrum) by Ultralytics
|
4
|
+
# Documentation: https://docs.ultralytics.com/datasets/detect/coco8-multispectral/
|
5
|
+
# Example usage: yolo train data=coco8-multispectral.yaml
|
6
|
+
# parent
|
7
|
+
# ├── ultralytics
|
8
|
+
# └── datasets
|
9
|
+
# └── coco8-multispectral ← downloads here (20.2 MB)
|
10
|
+
|
11
|
+
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
|
12
|
+
path: ../datasets/coco8-multispectral # dataset root dir
|
13
|
+
train: images/train # train images (relative to 'path') 4 images
|
14
|
+
val: images/val # val images (relative to 'path') 4 images
|
15
|
+
test: # test images (optional)
|
16
|
+
|
17
|
+
# Number of multispectral image channels
|
18
|
+
channels: 10
|
19
|
+
|
20
|
+
# Classes
|
21
|
+
names:
|
22
|
+
0: person
|
23
|
+
1: bicycle
|
24
|
+
2: car
|
25
|
+
3: motorcycle
|
26
|
+
4: airplane
|
27
|
+
5: bus
|
28
|
+
6: train
|
29
|
+
7: truck
|
30
|
+
8: boat
|
31
|
+
9: traffic light
|
32
|
+
10: fire hydrant
|
33
|
+
11: stop sign
|
34
|
+
12: parking meter
|
35
|
+
13: bench
|
36
|
+
14: bird
|
37
|
+
15: cat
|
38
|
+
16: dog
|
39
|
+
17: horse
|
40
|
+
18: sheep
|
41
|
+
19: cow
|
42
|
+
20: elephant
|
43
|
+
21: bear
|
44
|
+
22: zebra
|
45
|
+
23: giraffe
|
46
|
+
24: backpack
|
47
|
+
25: umbrella
|
48
|
+
26: handbag
|
49
|
+
27: tie
|
50
|
+
28: suitcase
|
51
|
+
29: frisbee
|
52
|
+
30: skis
|
53
|
+
31: snowboard
|
54
|
+
32: sports ball
|
55
|
+
33: kite
|
56
|
+
34: baseball bat
|
57
|
+
35: baseball glove
|
58
|
+
36: skateboard
|
59
|
+
37: surfboard
|
60
|
+
38: tennis racket
|
61
|
+
39: bottle
|
62
|
+
40: wine glass
|
63
|
+
41: cup
|
64
|
+
42: fork
|
65
|
+
43: knife
|
66
|
+
44: spoon
|
67
|
+
45: bowl
|
68
|
+
46: banana
|
69
|
+
47: apple
|
70
|
+
48: sandwich
|
71
|
+
49: orange
|
72
|
+
50: broccoli
|
73
|
+
51: carrot
|
74
|
+
52: hot dog
|
75
|
+
53: pizza
|
76
|
+
54: donut
|
77
|
+
55: cake
|
78
|
+
56: chair
|
79
|
+
57: couch
|
80
|
+
58: potted plant
|
81
|
+
59: bed
|
82
|
+
60: dining table
|
83
|
+
61: toilet
|
84
|
+
62: tv
|
85
|
+
63: laptop
|
86
|
+
64: mouse
|
87
|
+
65: remote
|
88
|
+
66: keyboard
|
89
|
+
67: cell phone
|
90
|
+
68: microwave
|
91
|
+
69: oven
|
92
|
+
70: toaster
|
93
|
+
71: sink
|
94
|
+
72: refrigerator
|
95
|
+
73: book
|
96
|
+
74: clock
|
97
|
+
75: vase
|
98
|
+
76: scissors
|
99
|
+
77: teddy bear
|
100
|
+
78: hair drier
|
101
|
+
79: toothbrush
|
102
|
+
|
103
|
+
# Download script/URL (optional)
|
104
|
+
download: https://github.com/ultralytics/assets/releases/download/v0.0.0/coco8-multispectral.zip
|
ultralytics/data/augment.py
CHANGED
@@ -1364,8 +1364,10 @@ class RandomHSV:
|
|
1364
1364
|
>>> hsv_augmenter(labels)
|
1365
1365
|
>>> augmented_img = labels["img"]
|
1366
1366
|
"""
|
1367
|
+
img = labels["img"]
|
1368
|
+
if img.shape[-1] != 3: # only apply to RGB images
|
1369
|
+
return labels
|
1367
1370
|
if self.hgain or self.sgain or self.vgain:
|
1368
|
-
img = labels["img"]
|
1369
1371
|
dtype = img.dtype # uint8
|
1370
1372
|
|
1371
1373
|
r = np.random.uniform(-1, 1, 3) * [self.hgain, self.sgain, self.vgain] # random gains
|
@@ -1588,9 +1590,14 @@ class LetterBox:
|
|
1588
1590
|
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
|
1589
1591
|
top, bottom = int(round(dh - 0.1)) if self.center else 0, int(round(dh + 0.1))
|
1590
1592
|
left, right = int(round(dw - 0.1)) if self.center else 0, int(round(dw + 0.1))
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1593
|
+
h, w, c = img.shape
|
1594
|
+
if c == 3:
|
1595
|
+
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
|
1596
|
+
else: # multispectral
|
1597
|
+
pad_img = np.full((h + top + bottom, w + left + right, c), fill_value=114, dtype=img.dtype)
|
1598
|
+
pad_img[top : top + h, left : left + w] = img
|
1599
|
+
img = pad_img
|
1600
|
+
|
1594
1601
|
if labels.get("ratio_pad"):
|
1595
1602
|
labels["ratio_pad"] = (labels["ratio_pad"], (left, top)) # for evaluation
|
1596
1603
|
|
@@ -1908,10 +1915,13 @@ class Albumentations:
|
|
1908
1915
|
if self.transform is None or random.random() > self.p:
|
1909
1916
|
return labels
|
1910
1917
|
|
1918
|
+
im = labels["img"]
|
1919
|
+
if im.shape[2] != 3: # Only apply Albumentation on 3-channel images
|
1920
|
+
return labels
|
1921
|
+
|
1911
1922
|
if self.contains_spatial:
|
1912
1923
|
cls = labels["cls"]
|
1913
1924
|
if len(cls):
|
1914
|
-
im = labels["img"]
|
1915
1925
|
labels["instances"].convert_bbox("xywh")
|
1916
1926
|
labels["instances"].normalize(*im.shape[:2][::-1])
|
1917
1927
|
bboxes = labels["instances"].bboxes
|
@@ -2426,7 +2436,7 @@ def v8_transforms(dataset, imgsz, hyp, stretch=False):
|
|
2426
2436
|
kpt_shape = dataset.data.get("kpt_shape", None)
|
2427
2437
|
if len(flip_idx) == 0 and hyp.fliplr > 0.0:
|
2428
2438
|
hyp.fliplr = 0.0
|
2429
|
-
LOGGER.warning("
|
2439
|
+
LOGGER.warning("No 'flip_idx' array defined in data.yaml, setting augmentation 'fliplr=0.0'")
|
2430
2440
|
elif flip_idx and (len(flip_idx) != kpt_shape[0]):
|
2431
2441
|
raise ValueError(f"data.yaml flip_idx={flip_idx} length must be equal to kpt_shape[0]={kpt_shape[0]}")
|
2432
2442
|
|
ultralytics/data/base.py
CHANGED
@@ -15,7 +15,7 @@ import psutil
|
|
15
15
|
from torch.utils.data import Dataset
|
16
16
|
|
17
17
|
from ultralytics.data.utils import FORMATS_HELP_MSG, HELP_URL, IMG_FORMATS, check_file_speeds
|
18
|
-
from ultralytics.utils import DEFAULT_CFG, LOCAL_RANK, LOGGER, NUM_THREADS, TQDM
|
18
|
+
from ultralytics.utils import DEFAULT_CFG, LOCAL_RANK, LOGGER, NUM_THREADS, TQDM, imread
|
19
19
|
|
20
20
|
|
21
21
|
class BaseDataset(Dataset):
|
@@ -127,7 +127,7 @@ class BaseDataset(Dataset):
|
|
127
127
|
if self.cache == "ram" and self.check_cache_ram():
|
128
128
|
if hyp.deterministic:
|
129
129
|
LOGGER.warning(
|
130
|
-
"
|
130
|
+
"cache='ram' may produce non-deterministic training results. "
|
131
131
|
"Consider cache='disk' as a deterministic alternative if your disk space allows."
|
132
132
|
)
|
133
133
|
self.cache_images()
|
@@ -221,11 +221,11 @@ class BaseDataset(Dataset):
|
|
221
221
|
try:
|
222
222
|
im = np.load(fn)
|
223
223
|
except Exception as e:
|
224
|
-
LOGGER.warning(f"{self.prefix}
|
224
|
+
LOGGER.warning(f"{self.prefix}Removing corrupt *.npy image file {fn} due to: {e}")
|
225
225
|
Path(fn).unlink(missing_ok=True)
|
226
|
-
im =
|
226
|
+
im = imread(f) # BGR
|
227
227
|
else: # read image
|
228
|
-
im =
|
228
|
+
im = imread(f) # BGR
|
229
229
|
if im is None:
|
230
230
|
raise FileNotFoundError(f"Image Not Found {f}")
|
231
231
|
|
@@ -271,7 +271,7 @@ class BaseDataset(Dataset):
|
|
271
271
|
"""Save an image as an *.npy file for faster loading."""
|
272
272
|
f = self.npy_files[i]
|
273
273
|
if not f.exists():
|
274
|
-
np.save(f.as_posix(),
|
274
|
+
np.save(f.as_posix(), imread(self.im_files[i]), allow_pickle=False)
|
275
275
|
|
276
276
|
def check_cache_disk(self, safety_margin=0.5):
|
277
277
|
"""
|
@@ -289,22 +289,22 @@ class BaseDataset(Dataset):
|
|
289
289
|
n = min(self.ni, 30) # extrapolate from 30 random images
|
290
290
|
for _ in range(n):
|
291
291
|
im_file = random.choice(self.im_files)
|
292
|
-
im =
|
292
|
+
im = imread(im_file)
|
293
293
|
if im is None:
|
294
294
|
continue
|
295
295
|
b += im.nbytes
|
296
296
|
if not os.access(Path(im_file).parent, os.W_OK):
|
297
297
|
self.cache = None
|
298
|
-
LOGGER.
|
298
|
+
LOGGER.warning(f"{self.prefix}Skipping caching images to disk, directory not writeable")
|
299
299
|
return False
|
300
300
|
disk_required = b * self.ni / n * (1 + safety_margin) # bytes required to cache dataset to disk
|
301
301
|
total, used, free = shutil.disk_usage(Path(self.im_files[0]).parent)
|
302
302
|
if disk_required > free:
|
303
303
|
self.cache = None
|
304
|
-
LOGGER.
|
304
|
+
LOGGER.warning(
|
305
305
|
f"{self.prefix}{disk_required / gb:.1f}GB disk space required, "
|
306
306
|
f"with {int(safety_margin * 100)}% safety margin but only "
|
307
|
-
f"{free / gb:.1f}/{total / gb:.1f}GB free, not caching images to disk
|
307
|
+
f"{free / gb:.1f}/{total / gb:.1f}GB free, not caching images to disk"
|
308
308
|
)
|
309
309
|
return False
|
310
310
|
return True
|
@@ -322,7 +322,7 @@ class BaseDataset(Dataset):
|
|
322
322
|
b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
|
323
323
|
n = min(self.ni, 30) # extrapolate from 30 random images
|
324
324
|
for _ in range(n):
|
325
|
-
im =
|
325
|
+
im = imread(random.choice(self.im_files)) # sample image
|
326
326
|
if im is None:
|
327
327
|
continue
|
328
328
|
ratio = self.imgsz / max(im.shape[0], im.shape[1]) # max(h, w) # ratio
|
@@ -331,10 +331,10 @@ class BaseDataset(Dataset):
|
|
331
331
|
mem = psutil.virtual_memory()
|
332
332
|
if mem_required > mem.available:
|
333
333
|
self.cache = None
|
334
|
-
LOGGER.
|
334
|
+
LOGGER.warning(
|
335
335
|
f"{self.prefix}{mem_required / gb:.1f}GB RAM required to cache images "
|
336
336
|
f"with {int(safety_margin * 100)}% safety margin but only "
|
337
|
-
f"{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, not caching images
|
337
|
+
f"{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, not caching images"
|
338
338
|
)
|
339
339
|
return False
|
340
340
|
return True
|
@@ -415,19 +415,17 @@ class BaseDataset(Dataset):
|
|
415
415
|
"""
|
416
416
|
Users can customize their own format here.
|
417
417
|
|
418
|
-
|
418
|
+
Examples:
|
419
419
|
Ensure output is a dictionary with the following keys:
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
)
|
431
|
-
```
|
420
|
+
>>> dict(
|
421
|
+
... im_file=im_file,
|
422
|
+
... shape=shape, # format: (height, width)
|
423
|
+
... cls=cls,
|
424
|
+
... bboxes=bboxes, # xywh
|
425
|
+
... segments=segments, # xy
|
426
|
+
... keypoints=keypoints, # xy
|
427
|
+
... normalized=True, # or False
|
428
|
+
... bbox_format="xyxy", # or xywh, ltwh
|
429
|
+
... )
|
432
430
|
"""
|
433
431
|
raise NotImplementedError
|
ultralytics/data/converter.py
CHANGED
@@ -12,7 +12,7 @@ import numpy as np
|
|
12
12
|
from PIL import Image
|
13
13
|
|
14
14
|
from ultralytics.utils import DATASETS_DIR, LOGGER, NUM_THREADS, TQDM
|
15
|
-
from ultralytics.utils.downloads import download
|
15
|
+
from ultralytics.utils.downloads import download, zip_directory
|
16
16
|
from ultralytics.utils.files import increment_path
|
17
17
|
|
18
18
|
|
@@ -698,6 +698,55 @@ def create_synthetic_coco_dataset():
|
|
698
698
|
for _ in TQDM(as_completed(futures), total=len(futures), desc=f"Generating images for {subset}"):
|
699
699
|
pass # The actual work is done in the background
|
700
700
|
else:
|
701
|
-
|
701
|
+
LOGGER.warning(f"Labels file {label_list_file} does not exist. Skipping image creation for {subset}.")
|
702
702
|
|
703
|
-
|
703
|
+
LOGGER.info("Synthetic COCO dataset created successfully.")
|
704
|
+
|
705
|
+
|
706
|
+
def convert_to_multispectral(path, n_channels=10, replace=False, zip=False):
|
707
|
+
"""
|
708
|
+
Convert RGB images to multispectral images by interpolating across wavelength bands.
|
709
|
+
|
710
|
+
This function takes RGB images and interpolates them to create multispectral images with a specified number
|
711
|
+
of channels. It can process either a single image or a directory of images.
|
712
|
+
|
713
|
+
Args:
|
714
|
+
path (str | Path): Path to an image file or directory containing images to convert.
|
715
|
+
n_channels (int): Number of spectral channels to generate in the output image.
|
716
|
+
replace (bool): Whether to replace the original image file with the converted one.
|
717
|
+
zip (bool): Whether to zip the converted images into a zip file.
|
718
|
+
|
719
|
+
Examples:
|
720
|
+
>>> # Convert a single image
|
721
|
+
>>> convert_to_multispectral("path/to/image.jpg", n_channels=12)
|
722
|
+
"""
|
723
|
+
from scipy.interpolate import interp1d
|
724
|
+
|
725
|
+
from ultralytics.data.utils import IMG_FORMATS
|
726
|
+
|
727
|
+
path = Path(path)
|
728
|
+
if path.is_dir():
|
729
|
+
# Process directory
|
730
|
+
im_files = sum([list(path.rglob(f"*.{ext}")) for ext in (IMG_FORMATS - {"tif", "tiff"})], [])
|
731
|
+
for im_path in im_files:
|
732
|
+
try:
|
733
|
+
convert_to_multispectral(im_path, n_channels)
|
734
|
+
if replace:
|
735
|
+
im_path.unlink()
|
736
|
+
except Exception as e:
|
737
|
+
LOGGER.info(f"Error converting {im_path}: {e}")
|
738
|
+
|
739
|
+
if zip:
|
740
|
+
zip_directory(path)
|
741
|
+
else:
|
742
|
+
# Process a single image
|
743
|
+
output_path = path.with_suffix(".tiff")
|
744
|
+
img = cv2.cvtColor(cv2.imread(str(path)), cv2.COLOR_BGR2RGB)
|
745
|
+
|
746
|
+
# Interpolate all pixels at once
|
747
|
+
rgb_wavelengths = np.array([650, 510, 475]) # R, G, B wavelengths (nm)
|
748
|
+
target_wavelengths = np.linspace(450, 700, n_channels)
|
749
|
+
f = interp1d(rgb_wavelengths.T, img, kind="linear", bounds_error=False, fill_value="extrapolate")
|
750
|
+
multispectral = f(target_wavelengths)
|
751
|
+
cv2.imwritemulti(str(output_path), np.clip(multispectral, 0, 255).astype(np.uint8).transpose(2, 0, 1))
|
752
|
+
LOGGER.info(f"Converted {output_path}")
|
ultralytics/data/dataset.py
CHANGED
@@ -148,7 +148,7 @@ class YOLODataset(BaseDataset):
|
|
148
148
|
if msgs:
|
149
149
|
LOGGER.info("\n".join(msgs))
|
150
150
|
if nf == 0:
|
151
|
-
LOGGER.warning(f"{self.prefix}
|
151
|
+
LOGGER.warning(f"{self.prefix}No labels found in {path}. {HELP_URL}")
|
152
152
|
x["hash"] = get_hash(self.label_files + self.im_files)
|
153
153
|
x["results"] = nf, nm, ne, nc, len(self.im_files)
|
154
154
|
x["msgs"] = msgs # warnings
|
@@ -185,7 +185,7 @@ class YOLODataset(BaseDataset):
|
|
185
185
|
[cache.pop(k) for k in ("hash", "version", "msgs")] # remove items
|
186
186
|
labels = cache["labels"]
|
187
187
|
if not labels:
|
188
|
-
LOGGER.warning(f"
|
188
|
+
LOGGER.warning(f"No images found in {cache_path}, training may not work correctly. {HELP_URL}")
|
189
189
|
self.im_files = [lb["im_file"] for lb in labels] # update im_files
|
190
190
|
|
191
191
|
# Check if the dataset is all boxes or all segments
|
@@ -193,14 +193,14 @@ class YOLODataset(BaseDataset):
|
|
193
193
|
len_cls, len_boxes, len_segments = (sum(x) for x in zip(*lengths))
|
194
194
|
if len_segments and len_boxes != len_segments:
|
195
195
|
LOGGER.warning(
|
196
|
-
f"
|
196
|
+
f"Box and segment counts should be equal, but got len(segments) = {len_segments}, "
|
197
197
|
f"len(boxes) = {len_boxes}. To resolve this only boxes will be used and all segments will be removed. "
|
198
198
|
"To avoid this please supply either a detect or segment dataset, not a detect-segment mixed dataset."
|
199
199
|
)
|
200
200
|
for lb in labels:
|
201
201
|
lb["segments"] = []
|
202
202
|
if len_cls == 0:
|
203
|
-
LOGGER.warning(f"
|
203
|
+
LOGGER.warning(f"No labels found in {cache_path}, training may not work correctly. {HELP_URL}")
|
204
204
|
return labels
|
205
205
|
|
206
206
|
def build_transforms(self, hyp=None):
|
@@ -731,7 +731,7 @@ class ClassificationDataset:
|
|
731
731
|
self.cache_ram = args.cache is True or str(args.cache).lower() == "ram" # cache images into RAM
|
732
732
|
if self.cache_ram:
|
733
733
|
LOGGER.warning(
|
734
|
-
"
|
734
|
+
"Classification `cache_ram` training has known memory leak in "
|
735
735
|
"https://github.com/ultralytics/ultralytics/issues/9824, setting `cache_ram=False`."
|
736
736
|
)
|
737
737
|
self.cache_ram = False
|
ultralytics/data/loaders.py
CHANGED
@@ -16,9 +16,8 @@ import torch
|
|
16
16
|
from PIL import Image
|
17
17
|
|
18
18
|
from ultralytics.data.utils import FORMATS_HELP_MSG, IMG_FORMATS, VID_FORMATS
|
19
|
-
from ultralytics.utils import IS_COLAB, IS_KAGGLE, LOGGER, ops
|
19
|
+
from ultralytics.utils import IS_COLAB, IS_KAGGLE, LOGGER, imread, ops
|
20
20
|
from ultralytics.utils.checks import check_requirements
|
21
|
-
from ultralytics.utils.patches import imread
|
22
21
|
|
23
22
|
|
24
23
|
@dataclass
|
@@ -152,7 +151,7 @@ class LoadStreams:
|
|
152
151
|
success, im = cap.retrieve()
|
153
152
|
if not success:
|
154
153
|
im = np.zeros(self.shape[i], dtype=np.uint8)
|
155
|
-
LOGGER.warning("
|
154
|
+
LOGGER.warning("Video stream unresponsive, please check your IP camera connection.")
|
156
155
|
cap.open(stream) # re-open stream if signal was lost
|
157
156
|
if self.buffer:
|
158
157
|
self.imgs[i].append(im)
|
@@ -171,7 +170,7 @@ class LoadStreams:
|
|
171
170
|
try:
|
172
171
|
cap.release() # release video capture
|
173
172
|
except Exception as e:
|
174
|
-
LOGGER.warning(f"
|
173
|
+
LOGGER.warning(f"Could not release VideoCapture object: {e}")
|
175
174
|
cv2.destroyAllWindows()
|
176
175
|
|
177
176
|
def __iter__(self):
|
@@ -193,7 +192,7 @@ class LoadStreams:
|
|
193
192
|
time.sleep(1 / min(self.fps))
|
194
193
|
x = self.imgs[i]
|
195
194
|
if not x:
|
196
|
-
LOGGER.warning(f"
|
195
|
+
LOGGER.warning(f"Waiting for stream {i}")
|
197
196
|
|
198
197
|
# Get and remove the first frame from imgs buffer
|
199
198
|
if self.buffer:
|
@@ -424,7 +423,7 @@ class LoadImagesAndVideos:
|
|
424
423
|
else:
|
425
424
|
im0 = imread(path) # BGR
|
426
425
|
if im0 is None:
|
427
|
-
LOGGER.warning(f"
|
426
|
+
LOGGER.warning(f"Image Read Error {path}")
|
428
427
|
else:
|
429
428
|
paths.append(path)
|
430
429
|
imgs.append(im0)
|
@@ -549,7 +548,7 @@ class LoadTensor:
|
|
549
548
|
def _single_check(im, stride=32):
|
550
549
|
"""Validates and formats a single image tensor, ensuring correct shape and normalization."""
|
551
550
|
s = (
|
552
|
-
f"
|
551
|
+
f"torch.Tensor inputs should be BCHW i.e. shape(1, 3, 640, 640) "
|
553
552
|
f"divisible by stride {stride}. Input shape{tuple(im.shape)} is incompatible."
|
554
553
|
)
|
555
554
|
if len(im.shape) != 4:
|
@@ -561,8 +560,7 @@ class LoadTensor:
|
|
561
560
|
raise ValueError(s)
|
562
561
|
if im.max() > 1.0 + torch.finfo(im.dtype).eps: # torch.float32 eps is 1.2e-07
|
563
562
|
LOGGER.warning(
|
564
|
-
f"
|
565
|
-
f"Dividing input by 255."
|
563
|
+
f"torch.Tensor inputs should be normalized 0.0-1.0 but max value is {im.max()}. Dividing input by 255."
|
566
564
|
)
|
567
565
|
im = im.float() / 255.0
|
568
566
|
|