ultralytics 8.0.238__py3-none-any.whl → 8.0.239__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.
Potentially problematic release.
This version of ultralytics might be problematic. Click here for more details.
- ultralytics/__init__.py +2 -2
- ultralytics/cfg/__init__.py +241 -138
- ultralytics/data/__init__.py +9 -2
- ultralytics/data/annotator.py +4 -4
- ultralytics/data/augment.py +186 -169
- ultralytics/data/base.py +54 -48
- ultralytics/data/build.py +34 -23
- ultralytics/data/converter.py +242 -70
- ultralytics/data/dataset.py +117 -95
- ultralytics/data/explorer/__init__.py +3 -1
- ultralytics/data/explorer/explorer.py +120 -100
- ultralytics/data/explorer/gui/__init__.py +1 -0
- ultralytics/data/explorer/gui/dash.py +123 -89
- ultralytics/data/explorer/utils.py +37 -39
- ultralytics/data/loaders.py +75 -62
- ultralytics/data/split_dota.py +44 -36
- ultralytics/data/utils.py +160 -142
- ultralytics/engine/exporter.py +348 -292
- ultralytics/engine/model.py +102 -66
- ultralytics/engine/predictor.py +74 -55
- ultralytics/engine/results.py +61 -41
- ultralytics/engine/trainer.py +192 -144
- ultralytics/engine/tuner.py +66 -59
- ultralytics/engine/validator.py +31 -26
- ultralytics/hub/__init__.py +54 -31
- ultralytics/hub/auth.py +28 -25
- ultralytics/hub/session.py +282 -133
- ultralytics/hub/utils.py +64 -42
- ultralytics/models/__init__.py +1 -1
- ultralytics/models/fastsam/__init__.py +1 -1
- ultralytics/models/fastsam/model.py +6 -6
- ultralytics/models/fastsam/predict.py +3 -2
- ultralytics/models/fastsam/prompt.py +55 -48
- ultralytics/models/fastsam/val.py +1 -1
- ultralytics/models/nas/__init__.py +1 -1
- ultralytics/models/nas/model.py +9 -8
- ultralytics/models/nas/predict.py +8 -6
- ultralytics/models/nas/val.py +11 -9
- ultralytics/models/rtdetr/__init__.py +1 -1
- ultralytics/models/rtdetr/model.py +11 -9
- ultralytics/models/rtdetr/train.py +18 -16
- ultralytics/models/rtdetr/val.py +25 -19
- ultralytics/models/sam/__init__.py +1 -1
- ultralytics/models/sam/amg.py +13 -14
- ultralytics/models/sam/build.py +44 -42
- ultralytics/models/sam/model.py +6 -6
- ultralytics/models/sam/modules/decoders.py +6 -4
- ultralytics/models/sam/modules/encoders.py +37 -35
- ultralytics/models/sam/modules/sam.py +5 -4
- ultralytics/models/sam/modules/tiny_encoder.py +95 -73
- ultralytics/models/sam/modules/transformer.py +3 -2
- ultralytics/models/sam/predict.py +39 -27
- ultralytics/models/utils/loss.py +99 -95
- ultralytics/models/utils/ops.py +34 -31
- ultralytics/models/yolo/__init__.py +1 -1
- ultralytics/models/yolo/classify/__init__.py +1 -1
- ultralytics/models/yolo/classify/predict.py +8 -6
- ultralytics/models/yolo/classify/train.py +37 -31
- ultralytics/models/yolo/classify/val.py +26 -24
- ultralytics/models/yolo/detect/__init__.py +1 -1
- ultralytics/models/yolo/detect/predict.py +8 -6
- ultralytics/models/yolo/detect/train.py +47 -37
- ultralytics/models/yolo/detect/val.py +100 -82
- ultralytics/models/yolo/model.py +31 -25
- ultralytics/models/yolo/obb/__init__.py +1 -1
- ultralytics/models/yolo/obb/predict.py +13 -11
- ultralytics/models/yolo/obb/train.py +3 -3
- ultralytics/models/yolo/obb/val.py +70 -59
- ultralytics/models/yolo/pose/__init__.py +1 -1
- ultralytics/models/yolo/pose/predict.py +17 -12
- ultralytics/models/yolo/pose/train.py +28 -25
- ultralytics/models/yolo/pose/val.py +91 -64
- ultralytics/models/yolo/segment/__init__.py +1 -1
- ultralytics/models/yolo/segment/predict.py +10 -8
- ultralytics/models/yolo/segment/train.py +16 -15
- ultralytics/models/yolo/segment/val.py +90 -68
- ultralytics/nn/__init__.py +26 -6
- ultralytics/nn/autobackend.py +144 -112
- ultralytics/nn/modules/__init__.py +96 -13
- ultralytics/nn/modules/block.py +28 -7
- ultralytics/nn/modules/conv.py +41 -23
- ultralytics/nn/modules/head.py +60 -52
- ultralytics/nn/modules/transformer.py +49 -32
- ultralytics/nn/modules/utils.py +20 -15
- ultralytics/nn/tasks.py +215 -141
- ultralytics/solutions/ai_gym.py +59 -47
- ultralytics/solutions/distance_calculation.py +17 -14
- ultralytics/solutions/heatmap.py +57 -55
- ultralytics/solutions/object_counter.py +46 -39
- ultralytics/solutions/speed_estimation.py +13 -16
- ultralytics/trackers/__init__.py +1 -1
- ultralytics/trackers/basetrack.py +1 -0
- ultralytics/trackers/bot_sort.py +2 -1
- ultralytics/trackers/byte_tracker.py +10 -7
- ultralytics/trackers/track.py +7 -7
- ultralytics/trackers/utils/gmc.py +25 -25
- ultralytics/trackers/utils/kalman_filter.py +85 -42
- ultralytics/trackers/utils/matching.py +8 -7
- ultralytics/utils/__init__.py +173 -152
- ultralytics/utils/autobatch.py +10 -10
- ultralytics/utils/benchmarks.py +76 -86
- ultralytics/utils/callbacks/__init__.py +1 -1
- ultralytics/utils/callbacks/base.py +29 -29
- ultralytics/utils/callbacks/clearml.py +51 -43
- ultralytics/utils/callbacks/comet.py +81 -66
- ultralytics/utils/callbacks/dvc.py +33 -26
- ultralytics/utils/callbacks/hub.py +44 -26
- ultralytics/utils/callbacks/mlflow.py +31 -24
- ultralytics/utils/callbacks/neptune.py +35 -25
- ultralytics/utils/callbacks/raytune.py +9 -4
- ultralytics/utils/callbacks/tensorboard.py +16 -11
- ultralytics/utils/callbacks/wb.py +39 -33
- ultralytics/utils/checks.py +189 -141
- ultralytics/utils/dist.py +15 -12
- ultralytics/utils/downloads.py +112 -96
- ultralytics/utils/errors.py +1 -1
- ultralytics/utils/files.py +11 -11
- ultralytics/utils/instance.py +22 -22
- ultralytics/utils/loss.py +117 -67
- ultralytics/utils/metrics.py +224 -158
- ultralytics/utils/ops.py +38 -28
- ultralytics/utils/patches.py +3 -3
- ultralytics/utils/plotting.py +217 -120
- ultralytics/utils/tal.py +19 -13
- ultralytics/utils/torch_utils.py +138 -109
- ultralytics/utils/triton.py +12 -10
- ultralytics/utils/tuner.py +49 -47
- {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/METADATA +2 -1
- ultralytics-8.0.239.dist-info/RECORD +188 -0
- ultralytics-8.0.238.dist-info/RECORD +0 -188
- {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/LICENSE +0 -0
- {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/WHEEL +0 -0
- {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/top_level.txt +0 -0
ultralytics/utils/__init__.py
CHANGED
|
@@ -25,23 +25,22 @@ from tqdm import tqdm as tqdm_original
|
|
|
25
25
|
from ultralytics import __version__
|
|
26
26
|
|
|
27
27
|
# PyTorch Multi-GPU DDP Constants
|
|
28
|
-
RANK = int(os.getenv(
|
|
29
|
-
LOCAL_RANK = int(os.getenv(
|
|
28
|
+
RANK = int(os.getenv("RANK", -1))
|
|
29
|
+
LOCAL_RANK = int(os.getenv("LOCAL_RANK", -1)) # https://pytorch.org/docs/stable/elastic/run.html
|
|
30
30
|
|
|
31
31
|
# Other Constants
|
|
32
32
|
FILE = Path(__file__).resolve()
|
|
33
33
|
ROOT = FILE.parents[1] # YOLO
|
|
34
|
-
ASSETS = ROOT /
|
|
35
|
-
DEFAULT_CFG_PATH = ROOT /
|
|
34
|
+
ASSETS = ROOT / "assets" # default images
|
|
35
|
+
DEFAULT_CFG_PATH = ROOT / "cfg/default.yaml"
|
|
36
36
|
NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLOv5 multiprocessing threads
|
|
37
|
-
AUTOINSTALL = str(os.getenv(
|
|
38
|
-
VERBOSE = str(os.getenv(
|
|
39
|
-
TQDM_BAR_FORMAT =
|
|
40
|
-
LOGGING_NAME =
|
|
41
|
-
MACOS, LINUX, WINDOWS = (platform.system() == x for x in [
|
|
42
|
-
ARM64 = platform.machine() in (
|
|
43
|
-
HELP_MSG =
|
|
44
|
-
"""
|
|
37
|
+
AUTOINSTALL = str(os.getenv("YOLO_AUTOINSTALL", True)).lower() == "true" # global auto-install mode
|
|
38
|
+
VERBOSE = str(os.getenv("YOLO_VERBOSE", True)).lower() == "true" # global verbose mode
|
|
39
|
+
TQDM_BAR_FORMAT = "{l_bar}{bar:10}{r_bar}" if VERBOSE else None # tqdm bar format
|
|
40
|
+
LOGGING_NAME = "ultralytics"
|
|
41
|
+
MACOS, LINUX, WINDOWS = (platform.system() == x for x in ["Darwin", "Linux", "Windows"]) # environment booleans
|
|
42
|
+
ARM64 = platform.machine() in ("arm64", "aarch64") # ARM64 booleans
|
|
43
|
+
HELP_MSG = """
|
|
45
44
|
Usage examples for running YOLOv8:
|
|
46
45
|
|
|
47
46
|
1. Install the ultralytics package:
|
|
@@ -99,12 +98,12 @@ HELP_MSG = \
|
|
|
99
98
|
"""
|
|
100
99
|
|
|
101
100
|
# Settings
|
|
102
|
-
torch.set_printoptions(linewidth=320, precision=4, profile=
|
|
103
|
-
np.set_printoptions(linewidth=320, formatter={
|
|
101
|
+
torch.set_printoptions(linewidth=320, precision=4, profile="default")
|
|
102
|
+
np.set_printoptions(linewidth=320, formatter={"float_kind": "{:11.5g}".format}) # format short g, %precision=5
|
|
104
103
|
cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)
|
|
105
|
-
os.environ[
|
|
106
|
-
os.environ[
|
|
107
|
-
os.environ[
|
|
104
|
+
os.environ["NUMEXPR_MAX_THREADS"] = str(NUM_THREADS) # NumExpr max threads
|
|
105
|
+
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" # for deterministic training
|
|
106
|
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" # suppress verbose TF compiler warnings in Colab
|
|
108
107
|
|
|
109
108
|
|
|
110
109
|
class TQDM(tqdm_original):
|
|
@@ -119,8 +118,8 @@ class TQDM(tqdm_original):
|
|
|
119
118
|
def __init__(self, *args, **kwargs):
|
|
120
119
|
"""Initialize custom Ultralytics tqdm class with different default arguments."""
|
|
121
120
|
# Set new default values (these can still be overridden when calling TQDM)
|
|
122
|
-
kwargs[
|
|
123
|
-
kwargs.setdefault(
|
|
121
|
+
kwargs["disable"] = not VERBOSE or kwargs.get("disable", False) # logical 'and' with default value if passed
|
|
122
|
+
kwargs.setdefault("bar_format", TQDM_BAR_FORMAT) # override default value if passed
|
|
124
123
|
super().__init__(*args, **kwargs)
|
|
125
124
|
|
|
126
125
|
|
|
@@ -134,14 +133,14 @@ class SimpleClass:
|
|
|
134
133
|
attr = []
|
|
135
134
|
for a in dir(self):
|
|
136
135
|
v = getattr(self, a)
|
|
137
|
-
if not callable(v) and not a.startswith(
|
|
136
|
+
if not callable(v) and not a.startswith("_"):
|
|
138
137
|
if isinstance(v, SimpleClass):
|
|
139
138
|
# Display only the module and class name for subclasses
|
|
140
|
-
s = f
|
|
139
|
+
s = f"{a}: {v.__module__}.{v.__class__.__name__} object"
|
|
141
140
|
else:
|
|
142
|
-
s = f
|
|
141
|
+
s = f"{a}: {repr(v)}"
|
|
143
142
|
attr.append(s)
|
|
144
|
-
return f
|
|
143
|
+
return f"{self.__module__}.{self.__class__.__name__} object with attributes:\n\n" + "\n".join(attr)
|
|
145
144
|
|
|
146
145
|
def __repr__(self):
|
|
147
146
|
"""Return a machine-readable string representation of the object."""
|
|
@@ -164,24 +163,26 @@ class IterableSimpleNamespace(SimpleNamespace):
|
|
|
164
163
|
|
|
165
164
|
def __str__(self):
|
|
166
165
|
"""Return a human-readable string representation of the object."""
|
|
167
|
-
return
|
|
166
|
+
return "\n".join(f"{k}={v}" for k, v in vars(self).items())
|
|
168
167
|
|
|
169
168
|
def __getattr__(self, attr):
|
|
170
169
|
"""Custom attribute access error message with helpful information."""
|
|
171
170
|
name = self.__class__.__name__
|
|
172
|
-
raise AttributeError(
|
|
171
|
+
raise AttributeError(
|
|
172
|
+
f"""
|
|
173
173
|
'{name}' object has no attribute '{attr}'. This may be caused by a modified or out of date ultralytics
|
|
174
174
|
'default.yaml' file.\nPlease update your code with 'pip install -U ultralytics' and if necessary replace
|
|
175
175
|
{DEFAULT_CFG_PATH} with the latest version from
|
|
176
176
|
https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/default.yaml
|
|
177
|
-
"""
|
|
177
|
+
"""
|
|
178
|
+
)
|
|
178
179
|
|
|
179
180
|
def get(self, key, default=None):
|
|
180
181
|
"""Return the value of the specified key if it exists; otherwise, return the default value."""
|
|
181
182
|
return getattr(self, key, default)
|
|
182
183
|
|
|
183
184
|
|
|
184
|
-
def plt_settings(rcparams=None, backend=
|
|
185
|
+
def plt_settings(rcparams=None, backend="Agg"):
|
|
185
186
|
"""
|
|
186
187
|
Decorator to temporarily set rc parameters and the backend for a plotting function.
|
|
187
188
|
|
|
@@ -199,7 +200,7 @@ def plt_settings(rcparams=None, backend='Agg'):
|
|
|
199
200
|
"""
|
|
200
201
|
|
|
201
202
|
if rcparams is None:
|
|
202
|
-
rcparams = {
|
|
203
|
+
rcparams = {"font.size": 11}
|
|
203
204
|
|
|
204
205
|
def decorator(func):
|
|
205
206
|
"""Decorator to apply temporary rc parameters and backend to a function."""
|
|
@@ -208,14 +209,14 @@ def plt_settings(rcparams=None, backend='Agg'):
|
|
|
208
209
|
"""Sets rc parameters and backend, calls the original function, and restores the settings."""
|
|
209
210
|
original_backend = plt.get_backend()
|
|
210
211
|
if backend != original_backend:
|
|
211
|
-
plt.close(
|
|
212
|
+
plt.close("all") # auto-close()ing of figures upon backend switching is deprecated since 3.8
|
|
212
213
|
plt.switch_backend(backend)
|
|
213
214
|
|
|
214
215
|
with plt.rc_context(rcparams):
|
|
215
216
|
result = func(*args, **kwargs)
|
|
216
217
|
|
|
217
218
|
if backend != original_backend:
|
|
218
|
-
plt.close(
|
|
219
|
+
plt.close("all")
|
|
219
220
|
plt.switch_backend(original_backend)
|
|
220
221
|
return result
|
|
221
222
|
|
|
@@ -229,25 +230,26 @@ def set_logging(name=LOGGING_NAME, verbose=True):
|
|
|
229
230
|
level = logging.INFO if verbose and RANK in {-1, 0} else logging.ERROR # rank in world for Multi-GPU trainings
|
|
230
231
|
|
|
231
232
|
# Configure the console (stdout) encoding to UTF-8
|
|
232
|
-
formatter = logging.Formatter(
|
|
233
|
-
if WINDOWS and sys.stdout.encoding !=
|
|
233
|
+
formatter = logging.Formatter("%(message)s") # Default formatter
|
|
234
|
+
if WINDOWS and sys.stdout.encoding != "utf-8":
|
|
234
235
|
try:
|
|
235
|
-
if hasattr(sys.stdout,
|
|
236
|
-
sys.stdout.reconfigure(encoding=
|
|
237
|
-
elif hasattr(sys.stdout,
|
|
236
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
237
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
238
|
+
elif hasattr(sys.stdout, "buffer"):
|
|
238
239
|
import io
|
|
239
|
-
|
|
240
|
+
|
|
241
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
|
240
242
|
else:
|
|
241
|
-
sys.stdout.encoding =
|
|
243
|
+
sys.stdout.encoding = "utf-8"
|
|
242
244
|
except Exception as e:
|
|
243
|
-
print(f
|
|
245
|
+
print(f"Creating custom formatter for non UTF-8 environments due to {e}")
|
|
244
246
|
|
|
245
247
|
class CustomFormatter(logging.Formatter):
|
|
246
|
-
|
|
247
248
|
def format(self, record):
|
|
249
|
+
"""Sets up logging with UTF-8 encoding and configurable verbosity."""
|
|
248
250
|
return emojis(super().format(record))
|
|
249
251
|
|
|
250
|
-
formatter = CustomFormatter(
|
|
252
|
+
formatter = CustomFormatter("%(message)s") # Use CustomFormatter to eliminate UTF-8 output as last recourse
|
|
251
253
|
|
|
252
254
|
# Create and configure the StreamHandler
|
|
253
255
|
stream_handler = logging.StreamHandler(sys.stdout)
|
|
@@ -263,13 +265,13 @@ def set_logging(name=LOGGING_NAME, verbose=True):
|
|
|
263
265
|
|
|
264
266
|
# Set logger
|
|
265
267
|
LOGGER = set_logging(LOGGING_NAME, verbose=VERBOSE) # define globally (used in train.py, val.py, predict.py, etc.)
|
|
266
|
-
for logger in
|
|
268
|
+
for logger in "sentry_sdk", "urllib3.connectionpool":
|
|
267
269
|
logging.getLogger(logger).setLevel(logging.CRITICAL + 1)
|
|
268
270
|
|
|
269
271
|
|
|
270
|
-
def emojis(string=
|
|
272
|
+
def emojis(string=""):
|
|
271
273
|
"""Return platform-dependent emoji-safe version of string."""
|
|
272
|
-
return string.encode().decode(
|
|
274
|
+
return string.encode().decode("ascii", "ignore") if WINDOWS else string
|
|
273
275
|
|
|
274
276
|
|
|
275
277
|
class ThreadingLocked:
|
|
@@ -309,7 +311,7 @@ class ThreadingLocked:
|
|
|
309
311
|
return decorated
|
|
310
312
|
|
|
311
313
|
|
|
312
|
-
def yaml_save(file=
|
|
314
|
+
def yaml_save(file="data.yaml", data=None, header=""):
|
|
313
315
|
"""
|
|
314
316
|
Save YAML data to a file.
|
|
315
317
|
|
|
@@ -335,13 +337,13 @@ def yaml_save(file='data.yaml', data=None, header=''):
|
|
|
335
337
|
data[k] = str(v)
|
|
336
338
|
|
|
337
339
|
# Dump data to file in YAML format
|
|
338
|
-
with open(file,
|
|
340
|
+
with open(file, "w", errors="ignore", encoding="utf-8") as f:
|
|
339
341
|
if header:
|
|
340
342
|
f.write(header)
|
|
341
343
|
yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True)
|
|
342
344
|
|
|
343
345
|
|
|
344
|
-
def yaml_load(file=
|
|
346
|
+
def yaml_load(file="data.yaml", append_filename=False):
|
|
345
347
|
"""
|
|
346
348
|
Load YAML data from a file.
|
|
347
349
|
|
|
@@ -352,18 +354,18 @@ def yaml_load(file='data.yaml', append_filename=False):
|
|
|
352
354
|
Returns:
|
|
353
355
|
(dict): YAML data and file name.
|
|
354
356
|
"""
|
|
355
|
-
assert Path(file).suffix in (
|
|
356
|
-
with open(file, errors=
|
|
357
|
+
assert Path(file).suffix in (".yaml", ".yml"), f"Attempting to load non-YAML file {file} with yaml_load()"
|
|
358
|
+
with open(file, errors="ignore", encoding="utf-8") as f:
|
|
357
359
|
s = f.read() # string
|
|
358
360
|
|
|
359
361
|
# Remove special characters
|
|
360
362
|
if not s.isprintable():
|
|
361
|
-
s = re.sub(r
|
|
363
|
+
s = re.sub(r"[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]+", "", s)
|
|
362
364
|
|
|
363
365
|
# Add YAML filename to dict and return
|
|
364
366
|
data = yaml.safe_load(s) or {} # always return a dict (yaml.safe_load() may return None for empty files)
|
|
365
367
|
if append_filename:
|
|
366
|
-
data[
|
|
368
|
+
data["yaml_file"] = str(file)
|
|
367
369
|
return data
|
|
368
370
|
|
|
369
371
|
|
|
@@ -385,7 +387,7 @@ def yaml_print(yaml_file: Union[str, Path, dict]) -> None:
|
|
|
385
387
|
# Default configuration
|
|
386
388
|
DEFAULT_CFG_DICT = yaml_load(DEFAULT_CFG_PATH)
|
|
387
389
|
for k, v in DEFAULT_CFG_DICT.items():
|
|
388
|
-
if isinstance(v, str) and v.lower() ==
|
|
390
|
+
if isinstance(v, str) and v.lower() == "none":
|
|
389
391
|
DEFAULT_CFG_DICT[k] = None
|
|
390
392
|
DEFAULT_CFG_KEYS = DEFAULT_CFG_DICT.keys()
|
|
391
393
|
DEFAULT_CFG = IterableSimpleNamespace(**DEFAULT_CFG_DICT)
|
|
@@ -399,8 +401,8 @@ def is_ubuntu() -> bool:
|
|
|
399
401
|
(bool): True if OS is Ubuntu, False otherwise.
|
|
400
402
|
"""
|
|
401
403
|
with contextlib.suppress(FileNotFoundError):
|
|
402
|
-
with open(
|
|
403
|
-
return
|
|
404
|
+
with open("/etc/os-release") as f:
|
|
405
|
+
return "ID=ubuntu" in f.read()
|
|
404
406
|
return False
|
|
405
407
|
|
|
406
408
|
|
|
@@ -411,7 +413,7 @@ def is_colab():
|
|
|
411
413
|
Returns:
|
|
412
414
|
(bool): True if running inside a Colab notebook, False otherwise.
|
|
413
415
|
"""
|
|
414
|
-
return
|
|
416
|
+
return "COLAB_RELEASE_TAG" in os.environ or "COLAB_BACKEND_VERSION" in os.environ
|
|
415
417
|
|
|
416
418
|
|
|
417
419
|
def is_kaggle():
|
|
@@ -421,7 +423,7 @@ def is_kaggle():
|
|
|
421
423
|
Returns:
|
|
422
424
|
(bool): True if running inside a Kaggle kernel, False otherwise.
|
|
423
425
|
"""
|
|
424
|
-
return os.environ.get(
|
|
426
|
+
return os.environ.get("PWD") == "/kaggle/working" and os.environ.get("KAGGLE_URL_BASE") == "https://www.kaggle.com"
|
|
425
427
|
|
|
426
428
|
|
|
427
429
|
def is_jupyter():
|
|
@@ -433,6 +435,7 @@ def is_jupyter():
|
|
|
433
435
|
"""
|
|
434
436
|
with contextlib.suppress(Exception):
|
|
435
437
|
from IPython import get_ipython
|
|
438
|
+
|
|
436
439
|
return get_ipython() is not None
|
|
437
440
|
return False
|
|
438
441
|
|
|
@@ -444,10 +447,10 @@ def is_docker() -> bool:
|
|
|
444
447
|
Returns:
|
|
445
448
|
(bool): True if the script is running inside a Docker container, False otherwise.
|
|
446
449
|
"""
|
|
447
|
-
file = Path(
|
|
450
|
+
file = Path("/proc/self/cgroup")
|
|
448
451
|
if file.exists():
|
|
449
452
|
with open(file) as f:
|
|
450
|
-
return
|
|
453
|
+
return "docker" in f.read()
|
|
451
454
|
else:
|
|
452
455
|
return False
|
|
453
456
|
|
|
@@ -461,7 +464,7 @@ def is_online() -> bool:
|
|
|
461
464
|
"""
|
|
462
465
|
import socket
|
|
463
466
|
|
|
464
|
-
for host in
|
|
467
|
+
for host in "1.1.1.1", "8.8.8.8", "223.5.5.5": # Cloudflare, Google, AliDNS:
|
|
465
468
|
try:
|
|
466
469
|
test_connection = socket.create_connection(address=(host, 53), timeout=2)
|
|
467
470
|
except (socket.timeout, socket.gaierror, OSError):
|
|
@@ -515,7 +518,7 @@ def is_pytest_running():
|
|
|
515
518
|
Returns:
|
|
516
519
|
(bool): True if pytest is running, False otherwise.
|
|
517
520
|
"""
|
|
518
|
-
return (
|
|
521
|
+
return ("PYTEST_CURRENT_TEST" in os.environ) or ("pytest" in sys.modules) or ("pytest" in Path(sys.argv[0]).stem)
|
|
519
522
|
|
|
520
523
|
|
|
521
524
|
def is_github_action_running() -> bool:
|
|
@@ -525,7 +528,7 @@ def is_github_action_running() -> bool:
|
|
|
525
528
|
Returns:
|
|
526
529
|
(bool): True if the current environment is a GitHub Actions runner, False otherwise.
|
|
527
530
|
"""
|
|
528
|
-
return
|
|
531
|
+
return "GITHUB_ACTIONS" in os.environ and "GITHUB_WORKFLOW" in os.environ and "RUNNER_OS" in os.environ
|
|
529
532
|
|
|
530
533
|
|
|
531
534
|
def is_git_dir():
|
|
@@ -548,7 +551,7 @@ def get_git_dir():
|
|
|
548
551
|
(Path | None): Git root directory if found or None if not found.
|
|
549
552
|
"""
|
|
550
553
|
for d in Path(__file__).parents:
|
|
551
|
-
if (d /
|
|
554
|
+
if (d / ".git").is_dir():
|
|
552
555
|
return d
|
|
553
556
|
|
|
554
557
|
|
|
@@ -561,7 +564,7 @@ def get_git_origin_url():
|
|
|
561
564
|
"""
|
|
562
565
|
if is_git_dir():
|
|
563
566
|
with contextlib.suppress(subprocess.CalledProcessError):
|
|
564
|
-
origin = subprocess.check_output([
|
|
567
|
+
origin = subprocess.check_output(["git", "config", "--get", "remote.origin.url"])
|
|
565
568
|
return origin.decode().strip()
|
|
566
569
|
|
|
567
570
|
|
|
@@ -574,7 +577,7 @@ def get_git_branch():
|
|
|
574
577
|
"""
|
|
575
578
|
if is_git_dir():
|
|
576
579
|
with contextlib.suppress(subprocess.CalledProcessError):
|
|
577
|
-
origin = subprocess.check_output([
|
|
580
|
+
origin = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
|
578
581
|
return origin.decode().strip()
|
|
579
582
|
|
|
580
583
|
|
|
@@ -601,11 +604,11 @@ def get_ubuntu_version():
|
|
|
601
604
|
"""
|
|
602
605
|
if is_ubuntu():
|
|
603
606
|
with contextlib.suppress(FileNotFoundError, AttributeError):
|
|
604
|
-
with open(
|
|
607
|
+
with open("/etc/os-release") as f:
|
|
605
608
|
return re.search(r'VERSION_ID="(\d+\.\d+)"', f.read())[1]
|
|
606
609
|
|
|
607
610
|
|
|
608
|
-
def get_user_config_dir(sub_dir=
|
|
611
|
+
def get_user_config_dir(sub_dir="Ultralytics"):
|
|
609
612
|
"""
|
|
610
613
|
Get the user config directory.
|
|
611
614
|
|
|
@@ -617,19 +620,21 @@ def get_user_config_dir(sub_dir='Ultralytics'):
|
|
|
617
620
|
"""
|
|
618
621
|
# Return the appropriate config directory for each operating system
|
|
619
622
|
if WINDOWS:
|
|
620
|
-
path = Path.home() /
|
|
623
|
+
path = Path.home() / "AppData" / "Roaming" / sub_dir
|
|
621
624
|
elif MACOS: # macOS
|
|
622
|
-
path = Path.home() /
|
|
625
|
+
path = Path.home() / "Library" / "Application Support" / sub_dir
|
|
623
626
|
elif LINUX:
|
|
624
|
-
path = Path.home() /
|
|
627
|
+
path = Path.home() / ".config" / sub_dir
|
|
625
628
|
else:
|
|
626
|
-
raise ValueError(f
|
|
629
|
+
raise ValueError(f"Unsupported operating system: {platform.system()}")
|
|
627
630
|
|
|
628
631
|
# GCP and AWS lambda fix, only /tmp is writeable
|
|
629
632
|
if not is_dir_writeable(path.parent):
|
|
630
|
-
LOGGER.warning(
|
|
631
|
-
|
|
632
|
-
|
|
633
|
+
LOGGER.warning(
|
|
634
|
+
f"WARNING ⚠️ user config directory '{path}' is not writeable, defaulting to '/tmp' or CWD."
|
|
635
|
+
"Alternatively you can define a YOLO_CONFIG_DIR environment variable for this path."
|
|
636
|
+
)
|
|
637
|
+
path = Path("/tmp") / sub_dir if is_dir_writeable("/tmp") else Path().cwd() / sub_dir
|
|
633
638
|
|
|
634
639
|
# Create the subdirectory if it does not exist
|
|
635
640
|
path.mkdir(parents=True, exist_ok=True)
|
|
@@ -637,8 +642,8 @@ def get_user_config_dir(sub_dir='Ultralytics'):
|
|
|
637
642
|
return path
|
|
638
643
|
|
|
639
644
|
|
|
640
|
-
USER_CONFIG_DIR = Path(os.getenv(
|
|
641
|
-
SETTINGS_YAML = USER_CONFIG_DIR /
|
|
645
|
+
USER_CONFIG_DIR = Path(os.getenv("YOLO_CONFIG_DIR") or get_user_config_dir()) # Ultralytics settings dir
|
|
646
|
+
SETTINGS_YAML = USER_CONFIG_DIR / "settings.yaml"
|
|
642
647
|
|
|
643
648
|
|
|
644
649
|
def colorstr(*input):
|
|
@@ -669,28 +674,29 @@ def colorstr(*input):
|
|
|
669
674
|
>>> colorstr('blue', 'bold', 'hello world')
|
|
670
675
|
>>> '\033[34m\033[1mhello world\033[0m'
|
|
671
676
|
"""
|
|
672
|
-
*args, string = input if len(input) > 1 else (
|
|
677
|
+
*args, string = input if len(input) > 1 else ("blue", "bold", input[0]) # color arguments, string
|
|
673
678
|
colors = {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
679
|
+
"black": "\033[30m", # basic colors
|
|
680
|
+
"red": "\033[31m",
|
|
681
|
+
"green": "\033[32m",
|
|
682
|
+
"yellow": "\033[33m",
|
|
683
|
+
"blue": "\033[34m",
|
|
684
|
+
"magenta": "\033[35m",
|
|
685
|
+
"cyan": "\033[36m",
|
|
686
|
+
"white": "\033[37m",
|
|
687
|
+
"bright_black": "\033[90m", # bright colors
|
|
688
|
+
"bright_red": "\033[91m",
|
|
689
|
+
"bright_green": "\033[92m",
|
|
690
|
+
"bright_yellow": "\033[93m",
|
|
691
|
+
"bright_blue": "\033[94m",
|
|
692
|
+
"bright_magenta": "\033[95m",
|
|
693
|
+
"bright_cyan": "\033[96m",
|
|
694
|
+
"bright_white": "\033[97m",
|
|
695
|
+
"end": "\033[0m", # misc
|
|
696
|
+
"bold": "\033[1m",
|
|
697
|
+
"underline": "\033[4m",
|
|
698
|
+
}
|
|
699
|
+
return "".join(colors[x] for x in args) + f"{string}" + colors["end"]
|
|
694
700
|
|
|
695
701
|
|
|
696
702
|
def remove_colorstr(input_string):
|
|
@@ -707,8 +713,8 @@ def remove_colorstr(input_string):
|
|
|
707
713
|
>>> remove_colorstr(colorstr('blue', 'bold', 'hello world'))
|
|
708
714
|
>>> 'hello world'
|
|
709
715
|
"""
|
|
710
|
-
ansi_escape = re.compile(r
|
|
711
|
-
return ansi_escape.sub(
|
|
716
|
+
ansi_escape = re.compile(r"\x1B\[[0-9;]*[A-Za-z]")
|
|
717
|
+
return ansi_escape.sub("", input_string)
|
|
712
718
|
|
|
713
719
|
|
|
714
720
|
class TryExcept(contextlib.ContextDecorator):
|
|
@@ -718,7 +724,7 @@ class TryExcept(contextlib.ContextDecorator):
|
|
|
718
724
|
Use as @TryExcept() decorator or 'with TryExcept():' context manager.
|
|
719
725
|
"""
|
|
720
726
|
|
|
721
|
-
def __init__(self, msg=
|
|
727
|
+
def __init__(self, msg="", verbose=True):
|
|
722
728
|
"""Initialize TryExcept class with optional message and verbosity settings."""
|
|
723
729
|
self.msg = msg
|
|
724
730
|
self.verbose = verbose
|
|
@@ -743,7 +749,7 @@ def threaded(func):
|
|
|
743
749
|
|
|
744
750
|
def wrapper(*args, **kwargs):
|
|
745
751
|
"""Multi-threads a given function based on 'threaded' kwarg and returns the thread or function result."""
|
|
746
|
-
if kwargs.pop(
|
|
752
|
+
if kwargs.pop("threaded", True): # run in thread
|
|
747
753
|
thread = threading.Thread(target=func, args=args, kwargs=kwargs, daemon=True)
|
|
748
754
|
thread.start()
|
|
749
755
|
return thread
|
|
@@ -785,27 +791,28 @@ def set_sentry():
|
|
|
785
791
|
Returns:
|
|
786
792
|
dict: The modified event or None if the event should not be sent to Sentry.
|
|
787
793
|
"""
|
|
788
|
-
if
|
|
789
|
-
exc_type, exc_value, tb = hint[
|
|
790
|
-
if exc_type in (KeyboardInterrupt, FileNotFoundError)
|
|
791
|
-
or 'out of memory' in str(exc_value):
|
|
794
|
+
if "exc_info" in hint:
|
|
795
|
+
exc_type, exc_value, tb = hint["exc_info"]
|
|
796
|
+
if exc_type in (KeyboardInterrupt, FileNotFoundError) or "out of memory" in str(exc_value):
|
|
792
797
|
return None # do not send event
|
|
793
798
|
|
|
794
|
-
event[
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
+
event["tags"] = {
|
|
800
|
+
"sys_argv": sys.argv[0],
|
|
801
|
+
"sys_argv_name": Path(sys.argv[0]).name,
|
|
802
|
+
"install": "git" if is_git_dir() else "pip" if is_pip_package() else "other",
|
|
803
|
+
"os": ENVIRONMENT,
|
|
804
|
+
}
|
|
799
805
|
return event
|
|
800
806
|
|
|
801
|
-
if
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
807
|
+
if (
|
|
808
|
+
SETTINGS["sync"]
|
|
809
|
+
and RANK in (-1, 0)
|
|
810
|
+
and Path(sys.argv[0]).name == "yolo"
|
|
811
|
+
and not TESTS_RUNNING
|
|
812
|
+
and ONLINE
|
|
813
|
+
and is_pip_package()
|
|
814
|
+
and not is_git_dir()
|
|
815
|
+
):
|
|
809
816
|
# If sentry_sdk package is not installed then return and do not use Sentry
|
|
810
817
|
try:
|
|
811
818
|
import sentry_sdk # noqa
|
|
@@ -813,14 +820,15 @@ def set_sentry():
|
|
|
813
820
|
return
|
|
814
821
|
|
|
815
822
|
sentry_sdk.init(
|
|
816
|
-
dsn=
|
|
823
|
+
dsn="https://5ff1556b71594bfea135ff0203a0d290@o4504521589325824.ingest.sentry.io/4504521592406016",
|
|
817
824
|
debug=False,
|
|
818
825
|
traces_sample_rate=1.0,
|
|
819
826
|
release=__version__,
|
|
820
|
-
environment=
|
|
827
|
+
environment="production", # 'dev' or 'production'
|
|
821
828
|
before_send=before_send,
|
|
822
|
-
ignore_errors=[KeyboardInterrupt, FileNotFoundError]
|
|
823
|
-
|
|
829
|
+
ignore_errors=[KeyboardInterrupt, FileNotFoundError],
|
|
830
|
+
)
|
|
831
|
+
sentry_sdk.set_user({"id": SETTINGS["uuid"]}) # SHA-256 anonymized UUID hash
|
|
824
832
|
|
|
825
833
|
|
|
826
834
|
class SettingsManager(dict):
|
|
@@ -832,7 +840,7 @@ class SettingsManager(dict):
|
|
|
832
840
|
version (str): Settings version. In case of local version mismatch, new default settings will be saved.
|
|
833
841
|
"""
|
|
834
842
|
|
|
835
|
-
def __init__(self, file=SETTINGS_YAML, version=
|
|
843
|
+
def __init__(self, file=SETTINGS_YAML, version="0.0.4"):
|
|
836
844
|
"""Initialize the SettingsManager with default settings, load and validate current settings from the YAML
|
|
837
845
|
file.
|
|
838
846
|
"""
|
|
@@ -849,23 +857,24 @@ class SettingsManager(dict):
|
|
|
849
857
|
self.file = Path(file)
|
|
850
858
|
self.version = version
|
|
851
859
|
self.defaults = {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
860
|
+
"settings_version": version,
|
|
861
|
+
"datasets_dir": str(datasets_root / "datasets"),
|
|
862
|
+
"weights_dir": str(root / "weights"),
|
|
863
|
+
"runs_dir": str(root / "runs"),
|
|
864
|
+
"uuid": hashlib.sha256(str(uuid.getnode()).encode()).hexdigest(),
|
|
865
|
+
"sync": True,
|
|
866
|
+
"api_key": "",
|
|
867
|
+
"openai_api_key": "",
|
|
868
|
+
"clearml": True, # integrations
|
|
869
|
+
"comet": True,
|
|
870
|
+
"dvc": True,
|
|
871
|
+
"hub": True,
|
|
872
|
+
"mlflow": True,
|
|
873
|
+
"neptune": True,
|
|
874
|
+
"raytune": True,
|
|
875
|
+
"tensorboard": True,
|
|
876
|
+
"wandb": True,
|
|
877
|
+
}
|
|
869
878
|
|
|
870
879
|
super().__init__(copy.deepcopy(self.defaults))
|
|
871
880
|
|
|
@@ -876,13 +885,14 @@ class SettingsManager(dict):
|
|
|
876
885
|
self.load()
|
|
877
886
|
correct_keys = self.keys() == self.defaults.keys()
|
|
878
887
|
correct_types = all(type(a) is type(b) for a, b in zip(self.values(), self.defaults.values()))
|
|
879
|
-
correct_version = check_version(self[
|
|
888
|
+
correct_version = check_version(self["settings_version"], self.version)
|
|
880
889
|
if not (correct_keys and correct_types and correct_version):
|
|
881
890
|
LOGGER.warning(
|
|
882
|
-
|
|
883
|
-
|
|
891
|
+
"WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
|
|
892
|
+
"with your settings or a recent ultralytics package update. "
|
|
884
893
|
f"\nView settings with 'yolo settings' or at '{self.file}'"
|
|
885
|
-
"\nUpdate settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'."
|
|
894
|
+
"\nUpdate settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'."
|
|
895
|
+
)
|
|
886
896
|
self.reset()
|
|
887
897
|
|
|
888
898
|
def load(self):
|
|
@@ -909,14 +919,16 @@ def deprecation_warn(arg, new_arg, version=None):
|
|
|
909
919
|
"""Issue a deprecation warning when a deprecated argument is used, suggesting an updated argument."""
|
|
910
920
|
if not version:
|
|
911
921
|
version = float(__version__[:3]) + 0.2 # deprecate after 2nd major release
|
|
912
|
-
LOGGER.warning(
|
|
913
|
-
|
|
922
|
+
LOGGER.warning(
|
|
923
|
+
f"WARNING ⚠️ '{arg}' is deprecated and will be removed in 'ultralytics {version}' in the future. "
|
|
924
|
+
f"Please use '{new_arg}' instead."
|
|
925
|
+
)
|
|
914
926
|
|
|
915
927
|
|
|
916
928
|
def clean_url(url):
|
|
917
929
|
"""Strip auth from URL, i.e. https://url.com/file.txt?auth -> https://url.com/file.txt."""
|
|
918
|
-
url = Path(url).as_posix().replace(
|
|
919
|
-
return urllib.parse.unquote(url).split(
|
|
930
|
+
url = Path(url).as_posix().replace(":/", "://") # Pathlib turns :// -> :/, as_posix() for Windows
|
|
931
|
+
return urllib.parse.unquote(url).split("?")[0] # '%2F' to '/', split https://url.com/file.txt?auth
|
|
920
932
|
|
|
921
933
|
|
|
922
934
|
def url2file(url):
|
|
@@ -927,13 +939,22 @@ def url2file(url):
|
|
|
927
939
|
# Run below code on utils init ------------------------------------------------------------------------------------
|
|
928
940
|
|
|
929
941
|
# Check first-install steps
|
|
930
|
-
PREFIX = colorstr(
|
|
942
|
+
PREFIX = colorstr("Ultralytics: ")
|
|
931
943
|
SETTINGS = SettingsManager() # initialize settings
|
|
932
|
-
DATASETS_DIR = Path(SETTINGS[
|
|
933
|
-
WEIGHTS_DIR = Path(SETTINGS[
|
|
934
|
-
RUNS_DIR = Path(SETTINGS[
|
|
935
|
-
ENVIRONMENT =
|
|
936
|
-
|
|
944
|
+
DATASETS_DIR = Path(SETTINGS["datasets_dir"]) # global datasets directory
|
|
945
|
+
WEIGHTS_DIR = Path(SETTINGS["weights_dir"]) # global weights directory
|
|
946
|
+
RUNS_DIR = Path(SETTINGS["runs_dir"]) # global runs directory
|
|
947
|
+
ENVIRONMENT = (
|
|
948
|
+
"Colab"
|
|
949
|
+
if is_colab()
|
|
950
|
+
else "Kaggle"
|
|
951
|
+
if is_kaggle()
|
|
952
|
+
else "Jupyter"
|
|
953
|
+
if is_jupyter()
|
|
954
|
+
else "Docker"
|
|
955
|
+
if is_docker()
|
|
956
|
+
else platform.system()
|
|
957
|
+
)
|
|
937
958
|
TESTS_RUNNING = is_pytest_running() or is_github_action_running()
|
|
938
959
|
set_sentry()
|
|
939
960
|
|