segment-everything 0.1.0__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.
- segment_everything/__init__.py +5 -0
- segment_everything/augmentation/albumentations_helper.py +0 -0
- segment_everything/detect_and_segment.py +131 -0
- segment_everything/napari_helper.py +15 -0
- segment_everything/prompt_generator.py +188 -0
- segment_everything/py.typed +5 -0
- segment_everything/stacked_label_dataset.py +113 -0
- segment_everything/stacked_labels.py +428 -0
- segment_everything/vendored/PromptGuidedDecoder/Prompt_guided_Mask_Decoder.pt +0 -0
- segment_everything/vendored/__init__.py +5 -0
- segment_everything/vendored/dice.py +158 -0
- segment_everything/vendored/efficientvit/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/data_provider/__init__.py +7 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/bbox.py +30 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/color_aug.py +78 -0
- segment_everything/vendored/efficientvit/apps/data_provider/base.py +254 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_loader.py +1538 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_worker.py +357 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/controller.py +100 -0
- segment_everything/vendored/efficientvit/apps/setup.py +150 -0
- segment_everything/vendored/efficientvit/apps/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/trainer/base.py +318 -0
- segment_everything/vendored/efficientvit/apps/trainer/run_config.py +129 -0
- segment_everything/vendored/efficientvit/apps/utils/__init__.py +12 -0
- segment_everything/vendored/efficientvit/apps/utils/dist.py +32 -0
- segment_everything/vendored/efficientvit/apps/utils/ema.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/export.py +45 -0
- segment_everything/vendored/efficientvit/apps/utils/init.py +66 -0
- segment_everything/vendored/efficientvit/apps/utils/lr.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/metric.py +43 -0
- segment_everything/vendored/efficientvit/apps/utils/misc.py +101 -0
- segment_everything/vendored/efficientvit/apps/utils/opt.py +28 -0
- segment_everything/vendored/efficientvit/cls_model_zoo.py +79 -0
- segment_everything/vendored/efficientvit/clscore/__init__.py +0 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/__init__.py +5 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/imagenet.py +142 -0
- segment_everything/vendored/efficientvit/clscore/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_run_config.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_trainer.py +265 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/label_smooth.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/metric.py +23 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/mixup.py +67 -0
- segment_everything/vendored/efficientvit/models/__init__.py +0 -0
- segment_everything/vendored/efficientvit/models/efficientvit/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/efficientvit/backbone.py +380 -0
- segment_everything/vendored/efficientvit/models/efficientvit/cls.py +188 -0
- segment_everything/vendored/efficientvit/models/efficientvit/sam.py +181 -0
- segment_everything/vendored/efficientvit/models/efficientvit/seg.py +373 -0
- segment_everything/vendored/efficientvit/models/nn/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/nn/act.py +30 -0
- segment_everything/vendored/efficientvit/models/nn/drop.py +104 -0
- segment_everything/vendored/efficientvit/models/nn/norm.py +164 -0
- segment_everything/vendored/efficientvit/models/nn/ops.py +597 -0
- segment_everything/vendored/efficientvit/models/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/models/utils/list.py +53 -0
- segment_everything/vendored/efficientvit/models/utils/network.py +73 -0
- segment_everything/vendored/efficientvit/models/utils/random.py +65 -0
- segment_everything/vendored/efficientvit/sam_model_zoo.py +45 -0
- segment_everything/vendored/efficientvit/seg_model_zoo.py +70 -0
- segment_everything/vendored/get_object_aware.py +26 -0
- segment_everything/vendored/mobilesamv2/__init__.py +16 -0
- segment_everything/vendored/mobilesamv2/automatic_mask_generator.py +415 -0
- segment_everything/vendored/mobilesamv2/build_sam.py +246 -0
- segment_everything/vendored/mobilesamv2/modeling/__init__.py +11 -0
- segment_everything/vendored/mobilesamv2/modeling/common.py +43 -0
- segment_everything/vendored/mobilesamv2/modeling/image_encoder.py +394 -0
- segment_everything/vendored/mobilesamv2/modeling/mask_decoder.py +213 -0
- segment_everything/vendored/mobilesamv2/modeling/prompt_encoder.py +217 -0
- segment_everything/vendored/mobilesamv2/modeling/sam.py +203 -0
- segment_everything/vendored/mobilesamv2/modeling/transformer.py +240 -0
- segment_everything/vendored/mobilesamv2/predictor.py +384 -0
- segment_everything/vendored/mobilesamv2/utils/__init__.py +5 -0
- segment_everything/vendored/mobilesamv2/utils/amg.py +347 -0
- segment_everything/vendored/mobilesamv2/utils/onnx.py +144 -0
- segment_everything/vendored/mobilesamv2/utils/transforms.py +103 -0
- segment_everything/vendored/object_detection/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/nn/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autobackend.py +658 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autoshape.py +397 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/__init__.py +110 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/block.py +304 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/conv.py +297 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/head.py +468 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/transformer.py +378 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/utils.py +78 -0
- segment_everything/vendored/object_detection/ultralytics/nn/tasks.py +1049 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/__init__.py +6 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/model.py +104 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/predict.py +95 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/__init__.py +588 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/default.yaml +117 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/annotator.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/augment.py +899 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/base.py +286 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/build.py +213 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/converter.py +358 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/stream_loaders.py +459 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset.py +274 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset_wrappers.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/download_weights.sh +18 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco.sh +60 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco128.sh +17 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_imagenet.sh +51 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/utils.py +716 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/exporter.py +1214 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/model.py +641 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/predictor.py +461 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/results.py +741 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/__init__.py +893 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/autobatch.py +108 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/base.py +212 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/checks.py +547 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/dist.py +67 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/downloads.py +353 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/errors.py +12 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/files.py +100 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/instance.py +391 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/loss.py +579 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/metrics.py +1189 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/ops.py +870 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/patches.py +45 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/plotting.py +767 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tal.py +276 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/torch_utils.py +684 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tuner.py +54 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/predict.py +69 -0
- segment_everything/vendored/tinyvit/__init__.py +2 -0
- segment_everything/vendored/tinyvit/tiny_vit.py +867 -0
- segment_everything/weights_helper.py +124 -0
- segment_everything-0.1.0.dist-info/METADATA +53 -0
- segment_everything-0.1.0.dist-info/RECORD +145 -0
- segment_everything-0.1.0.dist-info/WHEEL +4 -0
- segment_everything-0.1.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
import zipfile
|
|
10
|
+
from multiprocessing.pool import ThreadPool
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from tarfile import is_tarfile
|
|
13
|
+
|
|
14
|
+
import cv2
|
|
15
|
+
import numpy as np
|
|
16
|
+
from PIL import ExifTags, Image, ImageOps
|
|
17
|
+
from tqdm import tqdm
|
|
18
|
+
|
|
19
|
+
from ...nn.autobackend import check_class_names
|
|
20
|
+
from ..utils import (
|
|
21
|
+
DATASETS_DIR,
|
|
22
|
+
LOGGER,
|
|
23
|
+
NUM_THREADS,
|
|
24
|
+
ROOT,
|
|
25
|
+
SETTINGS_YAML,
|
|
26
|
+
clean_url,
|
|
27
|
+
colorstr,
|
|
28
|
+
emojis,
|
|
29
|
+
yaml_load,
|
|
30
|
+
)
|
|
31
|
+
from ..utils.checks import check_file, check_font, is_ascii
|
|
32
|
+
from ..utils.downloads import download, safe_download, unzip_file
|
|
33
|
+
from ..utils.ops import segments2boxes
|
|
34
|
+
|
|
35
|
+
HELP_URL = (
|
|
36
|
+
"See https://docs.ultralytics.com/yolov5/tutorials/train_custom_data"
|
|
37
|
+
)
|
|
38
|
+
IMG_FORMATS = (
|
|
39
|
+
"bmp",
|
|
40
|
+
"dng",
|
|
41
|
+
"jpeg",
|
|
42
|
+
"jpg",
|
|
43
|
+
"mpo",
|
|
44
|
+
"png",
|
|
45
|
+
"tif",
|
|
46
|
+
"tiff",
|
|
47
|
+
"webp",
|
|
48
|
+
"pfm",
|
|
49
|
+
) # image suffixes
|
|
50
|
+
VID_FORMATS = (
|
|
51
|
+
"asf",
|
|
52
|
+
"avi",
|
|
53
|
+
"gif",
|
|
54
|
+
"m4v",
|
|
55
|
+
"mkv",
|
|
56
|
+
"mov",
|
|
57
|
+
"mp4",
|
|
58
|
+
"mpeg",
|
|
59
|
+
"mpg",
|
|
60
|
+
"ts",
|
|
61
|
+
"wmv",
|
|
62
|
+
"webm",
|
|
63
|
+
) # video suffixes
|
|
64
|
+
PIN_MEMORY = (
|
|
65
|
+
str(os.getenv("PIN_MEMORY", True)).lower() == "true"
|
|
66
|
+
) # global pin_memory for dataloaders
|
|
67
|
+
IMAGENET_MEAN = 0.485, 0.456, 0.406 # RGB mean
|
|
68
|
+
IMAGENET_STD = 0.229, 0.224, 0.225 # RGB standard deviation
|
|
69
|
+
|
|
70
|
+
# Get orientation exif tag
|
|
71
|
+
for orientation in ExifTags.TAGS.keys():
|
|
72
|
+
if ExifTags.TAGS[orientation] == "Orientation":
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def img2label_paths(img_paths):
|
|
77
|
+
"""Define label paths as a function of image paths."""
|
|
78
|
+
sa, sb = (
|
|
79
|
+
f"{os.sep}images{os.sep}",
|
|
80
|
+
f"{os.sep}labels{os.sep}",
|
|
81
|
+
) # /images/, /labels/ substrings
|
|
82
|
+
return [
|
|
83
|
+
sb.join(x.rsplit(sa, 1)).rsplit(".", 1)[0] + ".txt" for x in img_paths
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_hash(paths):
|
|
88
|
+
"""Returns a single hash value of a list of paths (files or dirs)."""
|
|
89
|
+
size = sum(os.path.getsize(p) for p in paths if os.path.exists(p)) # sizes
|
|
90
|
+
h = hashlib.sha256(str(size).encode()) # hash sizes
|
|
91
|
+
h.update("".join(paths).encode()) # hash paths
|
|
92
|
+
return h.hexdigest() # return hash
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def exif_size(img):
|
|
96
|
+
"""Returns exif-corrected PIL size."""
|
|
97
|
+
s = img.size # (width, height)
|
|
98
|
+
with contextlib.suppress(Exception):
|
|
99
|
+
rotation = dict(img._getexif().items())[orientation]
|
|
100
|
+
if rotation in [6, 8]: # rotation 270 or 90
|
|
101
|
+
s = (s[1], s[0])
|
|
102
|
+
return s
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def verify_image_label(args):
|
|
106
|
+
"""Verify one image-label pair."""
|
|
107
|
+
im_file, lb_file, prefix, keypoint, num_cls, nkpt, ndim = args
|
|
108
|
+
# Number (missing, found, empty, corrupt), message, segments, keypoints
|
|
109
|
+
nm, nf, ne, nc, msg, segments, keypoints = 0, 0, 0, 0, "", [], None
|
|
110
|
+
try:
|
|
111
|
+
# Verify images
|
|
112
|
+
im = Image.open(im_file)
|
|
113
|
+
im.verify() # PIL verify
|
|
114
|
+
shape = exif_size(im) # image size
|
|
115
|
+
shape = (shape[1], shape[0]) # hw
|
|
116
|
+
assert (shape[0] > 9) & (
|
|
117
|
+
shape[1] > 9
|
|
118
|
+
), f"image size {shape} <10 pixels"
|
|
119
|
+
assert (
|
|
120
|
+
im.format.lower() in IMG_FORMATS
|
|
121
|
+
), f"invalid image format {im.format}"
|
|
122
|
+
if im.format.lower() in ("jpg", "jpeg"):
|
|
123
|
+
with open(im_file, "rb") as f:
|
|
124
|
+
f.seek(-2, 2)
|
|
125
|
+
if f.read() != b"\xff\xd9": # corrupt JPEG
|
|
126
|
+
ImageOps.exif_transpose(Image.open(im_file)).save(
|
|
127
|
+
im_file, "JPEG", subsampling=0, quality=100
|
|
128
|
+
)
|
|
129
|
+
msg = f"{prefix}WARNING ⚠️ {im_file}: corrupt JPEG restored and saved"
|
|
130
|
+
|
|
131
|
+
# Verify labels
|
|
132
|
+
if os.path.isfile(lb_file):
|
|
133
|
+
nf = 1 # label found
|
|
134
|
+
with open(lb_file) as f:
|
|
135
|
+
lb = [
|
|
136
|
+
x.split() for x in f.read().strip().splitlines() if len(x)
|
|
137
|
+
]
|
|
138
|
+
if any(len(x) > 6 for x in lb) and (
|
|
139
|
+
not keypoint
|
|
140
|
+
): # is segment
|
|
141
|
+
classes = np.array([x[0] for x in lb], dtype=np.float32)
|
|
142
|
+
segments = [
|
|
143
|
+
np.array(x[1:], dtype=np.float32).reshape(-1, 2)
|
|
144
|
+
for x in lb
|
|
145
|
+
] # (cls, xy1...)
|
|
146
|
+
lb = np.concatenate(
|
|
147
|
+
(classes.reshape(-1, 1), segments2boxes(segments)), 1
|
|
148
|
+
) # (cls, xywh)
|
|
149
|
+
lb = np.array(lb, dtype=np.float32)
|
|
150
|
+
nl = len(lb)
|
|
151
|
+
if nl:
|
|
152
|
+
if keypoint:
|
|
153
|
+
assert lb.shape[1] == (
|
|
154
|
+
5 + nkpt * ndim
|
|
155
|
+
), f"labels require {(5 + nkpt * ndim)} columns each"
|
|
156
|
+
assert (
|
|
157
|
+
lb[:, 5::ndim] <= 1
|
|
158
|
+
).all(), (
|
|
159
|
+
"non-normalized or out of bounds coordinate labels"
|
|
160
|
+
)
|
|
161
|
+
assert (
|
|
162
|
+
lb[:, 6::ndim] <= 1
|
|
163
|
+
).all(), (
|
|
164
|
+
"non-normalized or out of bounds coordinate labels"
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
assert (
|
|
168
|
+
lb.shape[1] == 5
|
|
169
|
+
), f"labels require 5 columns, {lb.shape[1]} columns detected"
|
|
170
|
+
assert (
|
|
171
|
+
lb[:, 1:] <= 1
|
|
172
|
+
).all(), f"non-normalized or out of bounds coordinates {lb[:, 1:][lb[:, 1:] > 1]}"
|
|
173
|
+
assert (
|
|
174
|
+
lb >= 0
|
|
175
|
+
).all(), f"negative label values {lb[lb < 0]}"
|
|
176
|
+
# All labels
|
|
177
|
+
max_cls = int(lb[:, 0].max()) # max label count
|
|
178
|
+
assert max_cls <= num_cls, (
|
|
179
|
+
f"Label class {max_cls} exceeds dataset class count {num_cls}. "
|
|
180
|
+
f"Possible class labels are 0-{num_cls - 1}"
|
|
181
|
+
)
|
|
182
|
+
_, i = np.unique(lb, axis=0, return_index=True)
|
|
183
|
+
if len(i) < nl: # duplicate row check
|
|
184
|
+
lb = lb[i] # remove duplicates
|
|
185
|
+
if segments:
|
|
186
|
+
segments = [segments[x] for x in i]
|
|
187
|
+
msg = f"{prefix}WARNING ⚠️ {im_file}: {nl - len(i)} duplicate labels removed"
|
|
188
|
+
else:
|
|
189
|
+
ne = 1 # label empty
|
|
190
|
+
lb = (
|
|
191
|
+
np.zeros((0, (5 + nkpt * ndim)), dtype=np.float32)
|
|
192
|
+
if keypoint
|
|
193
|
+
else np.zeros((0, 5), dtype=np.float32)
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
nm = 1 # label missing
|
|
197
|
+
lb = (
|
|
198
|
+
np.zeros((0, (5 + nkpt * ndim)), dtype=np.float32)
|
|
199
|
+
if keypoint
|
|
200
|
+
else np.zeros((0, 5), dtype=np.float32)
|
|
201
|
+
)
|
|
202
|
+
if keypoint:
|
|
203
|
+
keypoints = lb[:, 5:].reshape(-1, nkpt, ndim)
|
|
204
|
+
if ndim == 2:
|
|
205
|
+
kpt_mask = np.ones(keypoints.shape[:2], dtype=np.float32)
|
|
206
|
+
kpt_mask = np.where(keypoints[..., 0] < 0, 0.0, kpt_mask)
|
|
207
|
+
kpt_mask = np.where(keypoints[..., 1] < 0, 0.0, kpt_mask)
|
|
208
|
+
keypoints = np.concatenate(
|
|
209
|
+
[keypoints, kpt_mask[..., None]], axis=-1
|
|
210
|
+
) # (nl, nkpt, 3)
|
|
211
|
+
lb = lb[:, :5]
|
|
212
|
+
return im_file, lb, shape, segments, keypoints, nm, nf, ne, nc, msg
|
|
213
|
+
except Exception as e:
|
|
214
|
+
nc = 1
|
|
215
|
+
msg = f"{prefix}WARNING ⚠️ {im_file}: ignoring corrupt image/label: {e}"
|
|
216
|
+
return [None, None, None, None, None, nm, nf, ne, nc, msg]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def polygon2mask(imgsz, polygons, color=1, downsample_ratio=1):
|
|
220
|
+
"""
|
|
221
|
+
Args:
|
|
222
|
+
imgsz (tuple): The image size.
|
|
223
|
+
polygons (list[np.ndarray]): [N, M], N is the number of polygons, M is the number of points(Be divided by 2).
|
|
224
|
+
color (int): color
|
|
225
|
+
downsample_ratio (int): downsample ratio
|
|
226
|
+
"""
|
|
227
|
+
mask = np.zeros(imgsz, dtype=np.uint8)
|
|
228
|
+
polygons = np.asarray(polygons)
|
|
229
|
+
polygons = polygons.astype(np.int32)
|
|
230
|
+
shape = polygons.shape
|
|
231
|
+
polygons = polygons.reshape(shape[0], -1, 2)
|
|
232
|
+
cv2.fillPoly(mask, polygons, color=color)
|
|
233
|
+
nh, nw = (imgsz[0] // downsample_ratio, imgsz[1] // downsample_ratio)
|
|
234
|
+
# NOTE: fillPoly firstly then resize is trying the keep the same way
|
|
235
|
+
# of loss calculation when mask-ratio=1.
|
|
236
|
+
mask = cv2.resize(mask, (nw, nh))
|
|
237
|
+
return mask
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def polygons2masks(imgsz, polygons, color, downsample_ratio=1):
|
|
241
|
+
"""
|
|
242
|
+
Args:
|
|
243
|
+
imgsz (tuple): The image size.
|
|
244
|
+
polygons (list[np.ndarray]): each polygon is [N, M], N is number of polygons, M is number of points (M % 2 = 0)
|
|
245
|
+
color (int): color
|
|
246
|
+
downsample_ratio (int): downsample ratio
|
|
247
|
+
"""
|
|
248
|
+
masks = []
|
|
249
|
+
for si in range(len(polygons)):
|
|
250
|
+
mask = polygon2mask(
|
|
251
|
+
imgsz, [polygons[si].reshape(-1)], color, downsample_ratio
|
|
252
|
+
)
|
|
253
|
+
masks.append(mask)
|
|
254
|
+
return np.array(masks)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def polygons2masks_overlap(imgsz, segments, downsample_ratio=1):
|
|
258
|
+
"""Return a (640, 640) overlap mask."""
|
|
259
|
+
masks = np.zeros(
|
|
260
|
+
(imgsz[0] // downsample_ratio, imgsz[1] // downsample_ratio),
|
|
261
|
+
dtype=np.int32 if len(segments) > 255 else np.uint8,
|
|
262
|
+
)
|
|
263
|
+
areas = []
|
|
264
|
+
ms = []
|
|
265
|
+
for si in range(len(segments)):
|
|
266
|
+
mask = polygon2mask(
|
|
267
|
+
imgsz,
|
|
268
|
+
[segments[si].reshape(-1)],
|
|
269
|
+
downsample_ratio=downsample_ratio,
|
|
270
|
+
color=1,
|
|
271
|
+
)
|
|
272
|
+
ms.append(mask)
|
|
273
|
+
areas.append(mask.sum())
|
|
274
|
+
areas = np.asarray(areas)
|
|
275
|
+
index = np.argsort(-areas)
|
|
276
|
+
ms = np.array(ms)[index]
|
|
277
|
+
for i in range(len(segments)):
|
|
278
|
+
mask = ms[i] * (i + 1)
|
|
279
|
+
masks = masks + mask
|
|
280
|
+
masks = np.clip(masks, a_min=0, a_max=i + 1)
|
|
281
|
+
return masks, index
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def check_det_dataset(dataset, autodownload=True):
|
|
285
|
+
"""Download, check and/or unzip dataset if not found locally."""
|
|
286
|
+
data = check_file(dataset)
|
|
287
|
+
|
|
288
|
+
# Download (optional)
|
|
289
|
+
extract_dir = ""
|
|
290
|
+
if isinstance(data, (str, Path)) and (
|
|
291
|
+
zipfile.is_zipfile(data) or is_tarfile(data)
|
|
292
|
+
):
|
|
293
|
+
new_dir = safe_download(
|
|
294
|
+
data, dir=DATASETS_DIR, unzip=True, delete=False, curl=False
|
|
295
|
+
)
|
|
296
|
+
data = next((DATASETS_DIR / new_dir).rglob("*.yaml"))
|
|
297
|
+
extract_dir, autodownload = data.parent, False
|
|
298
|
+
|
|
299
|
+
# Read yaml (optional)
|
|
300
|
+
if isinstance(data, (str, Path)):
|
|
301
|
+
data = yaml_load(data, append_filename=True) # dictionary
|
|
302
|
+
|
|
303
|
+
# Checks
|
|
304
|
+
for k in "train", "val":
|
|
305
|
+
if k not in data:
|
|
306
|
+
raise SyntaxError(
|
|
307
|
+
emojis(
|
|
308
|
+
f"{dataset} '{k}:' key missing ❌.\n'train' and 'val' are required in all data YAMLs."
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
if "names" not in data and "nc" not in data:
|
|
312
|
+
raise SyntaxError(
|
|
313
|
+
emojis(
|
|
314
|
+
f"{dataset} key missing ❌.\n either 'names' or 'nc' are required in all data YAMLs."
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
if "names" in data and "nc" in data and len(data["names"]) != data["nc"]:
|
|
318
|
+
raise SyntaxError(
|
|
319
|
+
emojis(
|
|
320
|
+
f"{dataset} 'names' length {len(data['names'])} and 'nc: {data['nc']}' must match."
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
if "names" not in data:
|
|
324
|
+
data["names"] = [f"class_{i}" for i in range(data["nc"])]
|
|
325
|
+
else:
|
|
326
|
+
data["nc"] = len(data["names"])
|
|
327
|
+
|
|
328
|
+
data["names"] = check_class_names(data["names"])
|
|
329
|
+
|
|
330
|
+
# Resolve paths
|
|
331
|
+
path = Path(
|
|
332
|
+
extract_dir
|
|
333
|
+
or data.get("path")
|
|
334
|
+
or Path(data.get("yaml_file", "")).parent
|
|
335
|
+
) # dataset root
|
|
336
|
+
|
|
337
|
+
if not path.is_absolute():
|
|
338
|
+
path = (DATASETS_DIR / path).resolve()
|
|
339
|
+
data["path"] = path # download scripts
|
|
340
|
+
for k in "train", "val", "test":
|
|
341
|
+
if data.get(k): # prepend path
|
|
342
|
+
if isinstance(data[k], str):
|
|
343
|
+
x = (path / data[k]).resolve()
|
|
344
|
+
if not x.exists() and data[k].startswith("../"):
|
|
345
|
+
x = (path / data[k][3:]).resolve()
|
|
346
|
+
data[k] = str(x)
|
|
347
|
+
else:
|
|
348
|
+
data[k] = [str((path / x).resolve()) for x in data[k]]
|
|
349
|
+
|
|
350
|
+
# Parse yaml
|
|
351
|
+
train, val, test, s = (
|
|
352
|
+
data.get(x) for x in ("train", "val", "test", "download")
|
|
353
|
+
)
|
|
354
|
+
if val:
|
|
355
|
+
val = [
|
|
356
|
+
Path(x).resolve()
|
|
357
|
+
for x in (val if isinstance(val, list) else [val])
|
|
358
|
+
] # val path
|
|
359
|
+
if not all(x.exists() for x in val):
|
|
360
|
+
name = clean_url(dataset) # dataset name with URL auth stripped
|
|
361
|
+
m = f"\nDataset '{name}' images not found ⚠️, missing paths %s" % [
|
|
362
|
+
str(x) for x in val if not x.exists()
|
|
363
|
+
]
|
|
364
|
+
if s and autodownload:
|
|
365
|
+
LOGGER.warning(m)
|
|
366
|
+
else:
|
|
367
|
+
m += f"\nNote dataset download directory is '{DATASETS_DIR}'. You can update this in '{SETTINGS_YAML}'"
|
|
368
|
+
raise FileNotFoundError(m)
|
|
369
|
+
t = time.time()
|
|
370
|
+
if s.startswith("http") and s.endswith(".zip"): # URL
|
|
371
|
+
safe_download(url=s, dir=DATASETS_DIR, delete=True)
|
|
372
|
+
r = None # success
|
|
373
|
+
elif s.startswith("bash "): # bash script
|
|
374
|
+
LOGGER.info(f"Running {s} ...")
|
|
375
|
+
r = os.system(s)
|
|
376
|
+
else: # python script
|
|
377
|
+
r = exec(s, {"yaml": data}) # return None
|
|
378
|
+
dt = f"({round(time.time() - t, 1)}s)"
|
|
379
|
+
s = (
|
|
380
|
+
f"success ✅ {dt}, saved to {colorstr('bold', DATASETS_DIR)}"
|
|
381
|
+
if r in (0, None)
|
|
382
|
+
else f"failure {dt} ❌"
|
|
383
|
+
)
|
|
384
|
+
LOGGER.info(f"Dataset download {s}\n")
|
|
385
|
+
check_font(
|
|
386
|
+
"Arial.ttf" if is_ascii(data["names"]) else "Arial.Unicode.ttf"
|
|
387
|
+
) # download fonts
|
|
388
|
+
|
|
389
|
+
return data # dictionary
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def check_cls_dataset(dataset: str, split=""):
|
|
393
|
+
"""
|
|
394
|
+
Check a classification dataset such as Imagenet.
|
|
395
|
+
|
|
396
|
+
This function takes a `dataset` name as input and returns a dictionary containing information about the dataset.
|
|
397
|
+
If the dataset is not found, it attempts to download the dataset from the internet and save it locally.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
dataset (str): Name of the dataset.
|
|
401
|
+
split (str, optional): Dataset split, either 'val', 'test', or ''. Defaults to ''.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
data (dict): A dictionary containing the following keys and values:
|
|
405
|
+
'train': Path object for the directory containing the training set of the dataset
|
|
406
|
+
'val': Path object for the directory containing the validation set of the dataset
|
|
407
|
+
'test': Path object for the directory containing the test set of the dataset
|
|
408
|
+
'nc': Number of classes in the dataset
|
|
409
|
+
'names': List of class names in the dataset
|
|
410
|
+
"""
|
|
411
|
+
data_dir = (DATASETS_DIR / dataset).resolve()
|
|
412
|
+
if not data_dir.is_dir():
|
|
413
|
+
LOGGER.info(
|
|
414
|
+
f"\nDataset not found ⚠️, missing path {data_dir}, attempting download..."
|
|
415
|
+
)
|
|
416
|
+
t = time.time()
|
|
417
|
+
if dataset == "imagenet":
|
|
418
|
+
subprocess.run(
|
|
419
|
+
f"bash {ROOT / 'yolo/data/scripts/get_imagenet.sh'}",
|
|
420
|
+
shell=True,
|
|
421
|
+
check=True,
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
url = f"https://github.com/ultralytics/yolov5/releases/download/v1.0/{dataset}.zip"
|
|
425
|
+
download(url, dir=data_dir.parent)
|
|
426
|
+
s = f"Dataset download success ✅ ({time.time() - t:.1f}s), saved to {colorstr('bold', data_dir)}\n"
|
|
427
|
+
LOGGER.info(s)
|
|
428
|
+
train_set = data_dir / "train"
|
|
429
|
+
val_set = (
|
|
430
|
+
data_dir / "val" if (data_dir / "val").exists() else None
|
|
431
|
+
) # data/test or data/val
|
|
432
|
+
test_set = (
|
|
433
|
+
data_dir / "test" if (data_dir / "test").exists() else None
|
|
434
|
+
) # data/val or data/test
|
|
435
|
+
if split == "val" and not val_set:
|
|
436
|
+
LOGGER.info(
|
|
437
|
+
"WARNING ⚠️ Dataset 'split=val' not found, using 'split=test' instead."
|
|
438
|
+
)
|
|
439
|
+
elif split == "test" and not test_set:
|
|
440
|
+
LOGGER.info(
|
|
441
|
+
"WARNING ⚠️ Dataset 'split=test' not found, using 'split=val' instead."
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
nc = len(
|
|
445
|
+
[x for x in (data_dir / "train").glob("*") if x.is_dir()]
|
|
446
|
+
) # number of classes
|
|
447
|
+
names = [
|
|
448
|
+
x.name for x in (data_dir / "train").iterdir() if x.is_dir()
|
|
449
|
+
] # class names list
|
|
450
|
+
names = dict(enumerate(sorted(names)))
|
|
451
|
+
return {
|
|
452
|
+
"train": train_set,
|
|
453
|
+
"val": val_set or test_set,
|
|
454
|
+
"test": test_set or val_set,
|
|
455
|
+
"nc": nc,
|
|
456
|
+
"names": names,
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class HUBDatasetStats:
|
|
461
|
+
"""
|
|
462
|
+
Class for generating HUB dataset JSON and `-hub` dataset directory
|
|
463
|
+
|
|
464
|
+
Arguments
|
|
465
|
+
path: Path to data.yaml or data.zip (with data.yaml inside data.zip)
|
|
466
|
+
task: Dataset task. Options are 'detect', 'segment', 'pose', 'classify'.
|
|
467
|
+
autodownload: Attempt to download dataset if not found locally
|
|
468
|
+
|
|
469
|
+
Usage
|
|
470
|
+
from ultralytics.yolo.data.utils import HUBDatasetStats
|
|
471
|
+
stats = HUBDatasetStats('/Users/glennjocher/Downloads/coco8.zip', task='detect') # detect dataset
|
|
472
|
+
stats = HUBDatasetStats('/Users/glennjocher/Downloads/coco8-seg.zip', task='segment') # segment dataset
|
|
473
|
+
stats = HUBDatasetStats('/Users/glennjocher/Downloads/coco8-pose.zip', task='pose') # pose dataset
|
|
474
|
+
stats.get_json(save=False)
|
|
475
|
+
stats.process_images()
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
def __init__(self, path="coco128.yaml", task="detect", autodownload=False):
|
|
479
|
+
"""Initialize class."""
|
|
480
|
+
LOGGER.info(f"Starting HUB dataset checks for {path}....")
|
|
481
|
+
zipped, data_dir, yaml_path = self._unzip(Path(path))
|
|
482
|
+
try:
|
|
483
|
+
# data = yaml_load(check_yaml(yaml_path)) # data dict
|
|
484
|
+
data = check_det_dataset(yaml_path, autodownload) # data dict
|
|
485
|
+
if zipped:
|
|
486
|
+
data["path"] = data_dir
|
|
487
|
+
except Exception as e:
|
|
488
|
+
raise Exception("error/HUB/dataset_stats/yaml_load") from e
|
|
489
|
+
|
|
490
|
+
self.hub_dir = Path(str(data["path"]) + "-hub")
|
|
491
|
+
self.im_dir = self.hub_dir / "images"
|
|
492
|
+
self.im_dir.mkdir(parents=True, exist_ok=True) # makes /images
|
|
493
|
+
self.stats = {
|
|
494
|
+
"nc": len(data["names"]),
|
|
495
|
+
"names": list(data["names"].values()),
|
|
496
|
+
} # statistics dictionary
|
|
497
|
+
self.data = data
|
|
498
|
+
self.task = task # detect, segment, pose, classify
|
|
499
|
+
|
|
500
|
+
@staticmethod
|
|
501
|
+
def _find_yaml(dir):
|
|
502
|
+
"""Return data.yaml file."""
|
|
503
|
+
files = list(dir.glob("*.yaml")) or list(
|
|
504
|
+
dir.rglob("*.yaml")
|
|
505
|
+
) # try root level first and then recursive
|
|
506
|
+
assert files, f"No *.yaml file found in {dir}"
|
|
507
|
+
if len(files) > 1:
|
|
508
|
+
files = [
|
|
509
|
+
f for f in files if f.stem == dir.stem
|
|
510
|
+
] # prefer *.yaml files that match dir name
|
|
511
|
+
assert (
|
|
512
|
+
files
|
|
513
|
+
), f"Multiple *.yaml files found in {dir}, only 1 *.yaml file allowed"
|
|
514
|
+
assert (
|
|
515
|
+
len(files) == 1
|
|
516
|
+
), f"Multiple *.yaml files found: {files}, only 1 *.yaml file allowed in {dir}"
|
|
517
|
+
return files[0]
|
|
518
|
+
|
|
519
|
+
def _unzip(self, path):
|
|
520
|
+
"""Unzip data.zip."""
|
|
521
|
+
if not str(path).endswith(".zip"): # path is data.yaml
|
|
522
|
+
return False, None, path
|
|
523
|
+
unzip_dir = unzip_file(path, path=path.parent)
|
|
524
|
+
assert unzip_dir.is_dir(), (
|
|
525
|
+
f"Error unzipping {path}, {unzip_dir} not found. "
|
|
526
|
+
f"path/to/abc.zip MUST unzip to path/to/abc/"
|
|
527
|
+
)
|
|
528
|
+
return (
|
|
529
|
+
True,
|
|
530
|
+
str(unzip_dir),
|
|
531
|
+
self._find_yaml(unzip_dir),
|
|
532
|
+
) # zipped, data_dir, yaml_path
|
|
533
|
+
|
|
534
|
+
def _hub_ops(self, f):
|
|
535
|
+
"""Saves a compressed image for HUB previews."""
|
|
536
|
+
compress_one_image(
|
|
537
|
+
f, self.im_dir / Path(f).name
|
|
538
|
+
) # save to dataset-hub
|
|
539
|
+
|
|
540
|
+
def get_json(self, save=False, verbose=False):
|
|
541
|
+
"""Return dataset JSON for Ultralytics HUB."""
|
|
542
|
+
from ultralytics.yolo.data import YOLODataset # ClassificationDataset
|
|
543
|
+
|
|
544
|
+
def _round(labels):
|
|
545
|
+
"""Update labels to integer class and 4 decimal place floats."""
|
|
546
|
+
if self.task == "detect":
|
|
547
|
+
coordinates = labels["bboxes"]
|
|
548
|
+
elif self.task == "segment":
|
|
549
|
+
coordinates = [x.flatten() for x in labels["segments"]]
|
|
550
|
+
elif self.task == "pose":
|
|
551
|
+
n = labels["keypoints"].shape[0]
|
|
552
|
+
coordinates = np.concatenate(
|
|
553
|
+
(labels["bboxes"], labels["keypoints"].reshape(n, -1)), 1
|
|
554
|
+
)
|
|
555
|
+
else:
|
|
556
|
+
raise ValueError("Undefined dataset task.")
|
|
557
|
+
zipped = zip(labels["cls"], coordinates)
|
|
558
|
+
return [
|
|
559
|
+
[int(c), *(round(float(x), 4) for x in points)]
|
|
560
|
+
for c, points in zipped
|
|
561
|
+
]
|
|
562
|
+
|
|
563
|
+
for split in "train", "val", "test":
|
|
564
|
+
if self.data.get(split) is None:
|
|
565
|
+
self.stats[split] = None # i.e. no test set
|
|
566
|
+
continue
|
|
567
|
+
|
|
568
|
+
dataset = YOLODataset(
|
|
569
|
+
img_path=self.data[split],
|
|
570
|
+
data=self.data,
|
|
571
|
+
use_segments=self.task == "segment",
|
|
572
|
+
use_keypoints=self.task == "pose",
|
|
573
|
+
)
|
|
574
|
+
x = np.array(
|
|
575
|
+
[
|
|
576
|
+
np.bincount(
|
|
577
|
+
label["cls"].astype(int).flatten(),
|
|
578
|
+
minlength=self.data["nc"],
|
|
579
|
+
)
|
|
580
|
+
for label in tqdm(
|
|
581
|
+
dataset.labels, total=len(dataset), desc="Statistics"
|
|
582
|
+
)
|
|
583
|
+
]
|
|
584
|
+
) # shape(128x80)
|
|
585
|
+
self.stats[split] = {
|
|
586
|
+
"instance_stats": {
|
|
587
|
+
"total": int(x.sum()),
|
|
588
|
+
"per_class": x.sum(0).tolist(),
|
|
589
|
+
},
|
|
590
|
+
"image_stats": {
|
|
591
|
+
"total": len(dataset),
|
|
592
|
+
"unlabelled": int(np.all(x == 0, 1).sum()),
|
|
593
|
+
"per_class": (x > 0).sum(0).tolist(),
|
|
594
|
+
},
|
|
595
|
+
"labels": [
|
|
596
|
+
{Path(k).name: _round(v)}
|
|
597
|
+
for k, v in zip(dataset.im_files, dataset.labels)
|
|
598
|
+
],
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
# Save, print and return
|
|
602
|
+
if save:
|
|
603
|
+
stats_path = self.hub_dir / "stats.json"
|
|
604
|
+
LOGGER.info(f"Saving {stats_path.resolve()}...")
|
|
605
|
+
with open(stats_path, "w") as f:
|
|
606
|
+
json.dump(self.stats, f) # save stats.json
|
|
607
|
+
if verbose:
|
|
608
|
+
LOGGER.info(json.dumps(self.stats, indent=2, sort_keys=False))
|
|
609
|
+
return self.stats
|
|
610
|
+
|
|
611
|
+
def process_images(self):
|
|
612
|
+
"""Compress images for Ultralytics HUB."""
|
|
613
|
+
from ultralytics.yolo.data import YOLODataset # ClassificationDataset
|
|
614
|
+
|
|
615
|
+
for split in "train", "val", "test":
|
|
616
|
+
if self.data.get(split) is None:
|
|
617
|
+
continue
|
|
618
|
+
dataset = YOLODataset(img_path=self.data[split], data=self.data)
|
|
619
|
+
with ThreadPool(NUM_THREADS) as pool:
|
|
620
|
+
for _ in tqdm(
|
|
621
|
+
pool.imap(self._hub_ops, dataset.im_files),
|
|
622
|
+
total=len(dataset),
|
|
623
|
+
desc=f"{split} images",
|
|
624
|
+
):
|
|
625
|
+
pass
|
|
626
|
+
LOGGER.info(f"Done. All images saved to {self.im_dir}")
|
|
627
|
+
return self.im_dir
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def compress_one_image(f, f_new=None, max_dim=1920, quality=50):
|
|
631
|
+
"""
|
|
632
|
+
Compresses a single image file to reduced size while preserving its aspect ratio and quality using either the
|
|
633
|
+
Python Imaging Library (PIL) or OpenCV library. If the input image is smaller than the maximum dimension, it will
|
|
634
|
+
not be resized.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
f (str): The path to the input image file.
|
|
638
|
+
f_new (str, optional): The path to the output image file. If not specified, the input file will be overwritten.
|
|
639
|
+
max_dim (int, optional): The maximum dimension (width or height) of the output image. Default is 1920 pixels.
|
|
640
|
+
quality (int, optional): The image compression quality as a percentage. Default is 50%.
|
|
641
|
+
|
|
642
|
+
Usage:
|
|
643
|
+
from pathlib import Path
|
|
644
|
+
from ultralytics.yolo.data.utils import compress_one_image
|
|
645
|
+
for f in Path('/Users/glennjocher/Downloads/dataset').rglob('*.jpg'):
|
|
646
|
+
compress_one_image(f)
|
|
647
|
+
"""
|
|
648
|
+
try: # use PIL
|
|
649
|
+
im = Image.open(f)
|
|
650
|
+
r = max_dim / max(im.height, im.width) # ratio
|
|
651
|
+
if r < 1.0: # image too large
|
|
652
|
+
im = im.resize((int(im.width * r), int(im.height * r)))
|
|
653
|
+
im.save(f_new or f, "JPEG", quality=quality, optimize=True) # save
|
|
654
|
+
except Exception as e: # use OpenCV
|
|
655
|
+
LOGGER.info(f"WARNING ⚠️ HUB ops PIL failure {f}: {e}")
|
|
656
|
+
im = cv2.imread(f)
|
|
657
|
+
im_height, im_width = im.shape[:2]
|
|
658
|
+
r = max_dim / max(im_height, im_width) # ratio
|
|
659
|
+
if r < 1.0: # image too large
|
|
660
|
+
im = cv2.resize(
|
|
661
|
+
im,
|
|
662
|
+
(int(im_width * r), int(im_height * r)),
|
|
663
|
+
interpolation=cv2.INTER_AREA,
|
|
664
|
+
)
|
|
665
|
+
cv2.imwrite(str(f_new or f), im)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def delete_dsstore(path):
|
|
669
|
+
"""
|
|
670
|
+
Deletes all ".DS_store" files under a specified directory.
|
|
671
|
+
|
|
672
|
+
Args:
|
|
673
|
+
path (str, optional): The directory path where the ".DS_store" files should be deleted.
|
|
674
|
+
|
|
675
|
+
Usage:
|
|
676
|
+
from ultralytics.yolo.data.utils import delete_dsstore
|
|
677
|
+
delete_dsstore('/Users/glennjocher/Downloads/dataset')
|
|
678
|
+
|
|
679
|
+
Note:
|
|
680
|
+
".DS_store" files are created by the Apple operating system and contain metadata about folders and files. They
|
|
681
|
+
are hidden system files and can cause issues when transferring files between different operating systems.
|
|
682
|
+
"""
|
|
683
|
+
# Delete Apple .DS_store files
|
|
684
|
+
files = list(Path(path).rglob(".DS_store"))
|
|
685
|
+
LOGGER.info(f"Deleting *.DS_store files: {files}")
|
|
686
|
+
for f in files:
|
|
687
|
+
f.unlink()
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def zip_directory(dir, use_zipfile_library=True):
|
|
691
|
+
"""
|
|
692
|
+
Zips a directory and saves the archive to the specified output path.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
dir (str): The path to the directory to be zipped.
|
|
696
|
+
use_zipfile_library (bool): Whether to use zipfile library or shutil for zipping.
|
|
697
|
+
|
|
698
|
+
Usage:
|
|
699
|
+
from ultralytics.yolo.data.utils import zip_directory
|
|
700
|
+
zip_directory('/Users/glennjocher/Downloads/playground')
|
|
701
|
+
|
|
702
|
+
zip -r coco8-pose.zip coco8-pose
|
|
703
|
+
"""
|
|
704
|
+
delete_dsstore(dir)
|
|
705
|
+
if use_zipfile_library:
|
|
706
|
+
dir = Path(dir)
|
|
707
|
+
with zipfile.ZipFile(
|
|
708
|
+
dir.with_suffix(".zip"), "w", zipfile.ZIP_DEFLATED
|
|
709
|
+
) as zip_file:
|
|
710
|
+
for file_path in dir.glob("**/*"):
|
|
711
|
+
if file_path.is_file():
|
|
712
|
+
zip_file.write(file_path, file_path.relative_to(dir))
|
|
713
|
+
else:
|
|
714
|
+
import shutil
|
|
715
|
+
|
|
716
|
+
shutil.make_archive(dir, "zip", dir)
|