ultralytics 8.1.28__py3-none-any.whl → 8.3.62__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/__init__.py +22 -0
- tests/conftest.py +83 -0
- tests/test_cli.py +122 -0
- tests/test_cuda.py +155 -0
- tests/test_engine.py +131 -0
- tests/test_exports.py +216 -0
- tests/test_integrations.py +150 -0
- tests/test_python.py +615 -0
- tests/test_solutions.py +94 -0
- ultralytics/__init__.py +11 -8
- ultralytics/cfg/__init__.py +569 -131
- ultralytics/cfg/datasets/Argoverse.yaml +2 -1
- ultralytics/cfg/datasets/DOTAv1.5.yaml +3 -2
- ultralytics/cfg/datasets/DOTAv1.yaml +3 -2
- ultralytics/cfg/datasets/GlobalWheat2020.yaml +3 -2
- ultralytics/cfg/datasets/ImageNet.yaml +2 -1
- ultralytics/cfg/datasets/Objects365.yaml +5 -4
- ultralytics/cfg/datasets/SKU-110K.yaml +2 -1
- ultralytics/cfg/datasets/VOC.yaml +3 -2
- ultralytics/cfg/datasets/VisDrone.yaml +6 -5
- ultralytics/cfg/datasets/african-wildlife.yaml +25 -0
- ultralytics/cfg/datasets/brain-tumor.yaml +23 -0
- ultralytics/cfg/datasets/carparts-seg.yaml +3 -2
- ultralytics/cfg/datasets/coco-pose.yaml +7 -6
- ultralytics/cfg/datasets/coco.yaml +3 -2
- ultralytics/cfg/datasets/coco128-seg.yaml +4 -3
- ultralytics/cfg/datasets/coco128.yaml +4 -3
- ultralytics/cfg/datasets/coco8-pose.yaml +3 -2
- ultralytics/cfg/datasets/coco8-seg.yaml +3 -2
- ultralytics/cfg/datasets/coco8.yaml +3 -2
- ultralytics/cfg/datasets/crack-seg.yaml +3 -2
- ultralytics/cfg/datasets/dog-pose.yaml +24 -0
- ultralytics/cfg/datasets/dota8.yaml +3 -2
- ultralytics/cfg/datasets/hand-keypoints.yaml +26 -0
- ultralytics/cfg/datasets/lvis.yaml +1236 -0
- ultralytics/cfg/datasets/medical-pills.yaml +22 -0
- ultralytics/cfg/datasets/open-images-v7.yaml +2 -1
- ultralytics/cfg/datasets/package-seg.yaml +5 -4
- ultralytics/cfg/datasets/signature.yaml +21 -0
- ultralytics/cfg/datasets/tiger-pose.yaml +3 -2
- ultralytics/cfg/datasets/xView.yaml +2 -1
- ultralytics/cfg/default.yaml +14 -11
- ultralytics/cfg/models/11/yolo11-cls-resnet18.yaml +24 -0
- ultralytics/cfg/models/11/yolo11-cls.yaml +33 -0
- ultralytics/cfg/models/11/yolo11-obb.yaml +50 -0
- ultralytics/cfg/models/11/yolo11-pose.yaml +51 -0
- ultralytics/cfg/models/11/yolo11-seg.yaml +50 -0
- ultralytics/cfg/models/11/yolo11.yaml +50 -0
- ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +5 -2
- ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +5 -2
- ultralytics/cfg/models/v10/yolov10b.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10l.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10m.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10n.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10s.yaml +45 -0
- ultralytics/cfg/models/v10/yolov10x.yaml +45 -0
- ultralytics/cfg/models/v3/yolov3-spp.yaml +5 -2
- ultralytics/cfg/models/v3/yolov3-tiny.yaml +5 -2
- ultralytics/cfg/models/v3/yolov3.yaml +5 -2
- ultralytics/cfg/models/v5/yolov5-p6.yaml +5 -2
- ultralytics/cfg/models/v5/yolov5.yaml +5 -2
- ultralytics/cfg/models/v6/yolov6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-cls.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +6 -2
- ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +6 -2
- ultralytics/cfg/models/v8/yolov8-ghost.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-obb.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-p2.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-p6.yaml +10 -7
- ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-pose.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-seg.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-world.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8-worldv2.yaml +5 -2
- ultralytics/cfg/models/v8/yolov8.yaml +5 -2
- ultralytics/cfg/models/v9/yolov9c-seg.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9c.yaml +30 -25
- ultralytics/cfg/models/v9/yolov9e-seg.yaml +64 -0
- ultralytics/cfg/models/v9/yolov9e.yaml +46 -42
- ultralytics/cfg/models/v9/yolov9m.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9s.yaml +41 -0
- ultralytics/cfg/models/v9/yolov9t.yaml +41 -0
- ultralytics/cfg/solutions/default.yaml +24 -0
- ultralytics/cfg/trackers/botsort.yaml +8 -5
- ultralytics/cfg/trackers/bytetrack.yaml +8 -5
- ultralytics/data/__init__.py +14 -3
- ultralytics/data/annotator.py +37 -15
- ultralytics/data/augment.py +1783 -289
- ultralytics/data/base.py +62 -27
- ultralytics/data/build.py +36 -8
- ultralytics/data/converter.py +196 -36
- ultralytics/data/dataset.py +233 -94
- ultralytics/data/loaders.py +199 -96
- ultralytics/data/split_dota.py +39 -29
- ultralytics/data/utils.py +110 -40
- ultralytics/engine/__init__.py +1 -1
- ultralytics/engine/exporter.py +569 -242
- ultralytics/engine/model.py +604 -252
- ultralytics/engine/predictor.py +22 -11
- ultralytics/engine/results.py +1228 -218
- ultralytics/engine/trainer.py +190 -129
- ultralytics/engine/tuner.py +18 -18
- ultralytics/engine/validator.py +18 -15
- ultralytics/hub/__init__.py +31 -13
- ultralytics/hub/auth.py +11 -7
- ultralytics/hub/google/__init__.py +159 -0
- ultralytics/hub/session.py +128 -94
- ultralytics/hub/utils.py +20 -21
- ultralytics/models/__init__.py +4 -2
- ultralytics/models/fastsam/__init__.py +2 -3
- ultralytics/models/fastsam/model.py +26 -4
- ultralytics/models/fastsam/predict.py +127 -63
- ultralytics/models/fastsam/utils.py +1 -44
- ultralytics/models/fastsam/val.py +1 -1
- ultralytics/models/nas/__init__.py +1 -1
- ultralytics/models/nas/model.py +21 -10
- ultralytics/models/nas/predict.py +3 -6
- ultralytics/models/nas/val.py +4 -4
- ultralytics/models/rtdetr/__init__.py +1 -1
- ultralytics/models/rtdetr/model.py +1 -1
- ultralytics/models/rtdetr/predict.py +6 -8
- ultralytics/models/rtdetr/train.py +6 -2
- ultralytics/models/rtdetr/val.py +3 -3
- ultralytics/models/sam/__init__.py +3 -3
- ultralytics/models/sam/amg.py +29 -23
- ultralytics/models/sam/build.py +211 -13
- ultralytics/models/sam/model.py +91 -30
- ultralytics/models/sam/modules/__init__.py +1 -1
- ultralytics/models/sam/modules/blocks.py +1129 -0
- ultralytics/models/sam/modules/decoders.py +381 -53
- ultralytics/models/sam/modules/encoders.py +515 -324
- ultralytics/models/sam/modules/memory_attention.py +237 -0
- ultralytics/models/sam/modules/sam.py +969 -21
- ultralytics/models/sam/modules/tiny_encoder.py +425 -154
- ultralytics/models/sam/modules/transformer.py +159 -60
- ultralytics/models/sam/modules/utils.py +293 -0
- ultralytics/models/sam/predict.py +1263 -132
- ultralytics/models/utils/__init__.py +1 -1
- ultralytics/models/utils/loss.py +36 -24
- ultralytics/models/utils/ops.py +3 -7
- ultralytics/models/yolo/__init__.py +3 -3
- ultralytics/models/yolo/classify/__init__.py +1 -1
- ultralytics/models/yolo/classify/predict.py +7 -8
- ultralytics/models/yolo/classify/train.py +17 -22
- ultralytics/models/yolo/classify/val.py +8 -4
- ultralytics/models/yolo/detect/__init__.py +1 -1
- ultralytics/models/yolo/detect/predict.py +3 -5
- ultralytics/models/yolo/detect/train.py +11 -4
- ultralytics/models/yolo/detect/val.py +90 -52
- ultralytics/models/yolo/model.py +14 -9
- ultralytics/models/yolo/obb/__init__.py +1 -1
- ultralytics/models/yolo/obb/predict.py +2 -2
- ultralytics/models/yolo/obb/train.py +5 -3
- ultralytics/models/yolo/obb/val.py +41 -23
- ultralytics/models/yolo/pose/__init__.py +1 -1
- ultralytics/models/yolo/pose/predict.py +3 -5
- ultralytics/models/yolo/pose/train.py +2 -2
- ultralytics/models/yolo/pose/val.py +51 -17
- ultralytics/models/yolo/segment/__init__.py +1 -1
- ultralytics/models/yolo/segment/predict.py +3 -5
- ultralytics/models/yolo/segment/train.py +2 -2
- ultralytics/models/yolo/segment/val.py +60 -19
- ultralytics/models/yolo/world/__init__.py +5 -0
- ultralytics/models/yolo/world/train.py +92 -0
- ultralytics/models/yolo/world/train_world.py +109 -0
- ultralytics/nn/__init__.py +1 -1
- ultralytics/nn/autobackend.py +228 -93
- ultralytics/nn/modules/__init__.py +39 -14
- ultralytics/nn/modules/activation.py +21 -0
- ultralytics/nn/modules/block.py +527 -67
- ultralytics/nn/modules/conv.py +24 -7
- ultralytics/nn/modules/head.py +177 -34
- ultralytics/nn/modules/transformer.py +6 -5
- ultralytics/nn/modules/utils.py +1 -2
- ultralytics/nn/tasks.py +225 -77
- ultralytics/solutions/__init__.py +30 -1
- ultralytics/solutions/ai_gym.py +96 -143
- ultralytics/solutions/analytics.py +247 -0
- ultralytics/solutions/distance_calculation.py +78 -135
- ultralytics/solutions/heatmap.py +93 -247
- ultralytics/solutions/object_counter.py +184 -259
- ultralytics/solutions/parking_management.py +246 -0
- ultralytics/solutions/queue_management.py +112 -0
- ultralytics/solutions/region_counter.py +116 -0
- ultralytics/solutions/security_alarm.py +144 -0
- ultralytics/solutions/solutions.py +178 -0
- ultralytics/solutions/speed_estimation.py +86 -174
- ultralytics/solutions/streamlit_inference.py +190 -0
- ultralytics/solutions/trackzone.py +68 -0
- ultralytics/trackers/__init__.py +1 -1
- ultralytics/trackers/basetrack.py +32 -13
- ultralytics/trackers/bot_sort.py +61 -28
- ultralytics/trackers/byte_tracker.py +83 -51
- ultralytics/trackers/track.py +21 -6
- ultralytics/trackers/utils/__init__.py +1 -1
- ultralytics/trackers/utils/gmc.py +62 -48
- ultralytics/trackers/utils/kalman_filter.py +166 -35
- ultralytics/trackers/utils/matching.py +40 -21
- ultralytics/utils/__init__.py +511 -239
- ultralytics/utils/autobatch.py +40 -22
- ultralytics/utils/benchmarks.py +266 -85
- ultralytics/utils/callbacks/__init__.py +1 -1
- ultralytics/utils/callbacks/base.py +1 -3
- ultralytics/utils/callbacks/clearml.py +7 -6
- ultralytics/utils/callbacks/comet.py +39 -17
- ultralytics/utils/callbacks/dvc.py +1 -1
- ultralytics/utils/callbacks/hub.py +16 -16
- ultralytics/utils/callbacks/mlflow.py +28 -24
- ultralytics/utils/callbacks/neptune.py +6 -2
- ultralytics/utils/callbacks/raytune.py +3 -4
- ultralytics/utils/callbacks/tensorboard.py +18 -18
- ultralytics/utils/callbacks/wb.py +27 -20
- ultralytics/utils/checks.py +160 -100
- ultralytics/utils/dist.py +2 -1
- ultralytics/utils/downloads.py +44 -37
- ultralytics/utils/errors.py +1 -1
- ultralytics/utils/files.py +72 -38
- ultralytics/utils/instance.py +41 -19
- ultralytics/utils/loss.py +84 -56
- ultralytics/utils/metrics.py +61 -56
- ultralytics/utils/ops.py +94 -89
- ultralytics/utils/patches.py +30 -14
- ultralytics/utils/plotting.py +600 -269
- ultralytics/utils/tal.py +67 -26
- ultralytics/utils/torch_utils.py +302 -102
- ultralytics/utils/triton.py +2 -1
- ultralytics/utils/tuner.py +21 -12
- ultralytics-8.3.62.dist-info/METADATA +370 -0
- ultralytics-8.3.62.dist-info/RECORD +241 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/WHEEL +1 -1
- ultralytics/data/explorer/__init__.py +0 -5
- ultralytics/data/explorer/explorer.py +0 -472
- ultralytics/data/explorer/gui/__init__.py +0 -1
- ultralytics/data/explorer/gui/dash.py +0 -268
- ultralytics/data/explorer/utils.py +0 -166
- ultralytics/models/fastsam/prompt.py +0 -357
- ultralytics-8.1.28.dist-info/METADATA +0 -373
- ultralytics-8.1.28.dist-info/RECORD +0 -197
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/LICENSE +0 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.1.28.dist-info → ultralytics-8.3.62.dist-info}/top_level.txt +0 -0
ultralytics/utils/__init__.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
# Ultralytics
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
2
|
|
3
3
|
import contextlib
|
4
|
+
import importlib.metadata
|
4
5
|
import inspect
|
6
|
+
import json
|
5
7
|
import logging.config
|
6
8
|
import os
|
7
9
|
import platform
|
@@ -10,11 +12,12 @@ import subprocess
|
|
10
12
|
import sys
|
11
13
|
import threading
|
12
14
|
import time
|
13
|
-
import urllib
|
14
15
|
import uuid
|
15
16
|
from pathlib import Path
|
17
|
+
from threading import Lock
|
16
18
|
from types import SimpleNamespace
|
17
19
|
from typing import Union
|
20
|
+
from urllib.parse import unquote
|
18
21
|
|
19
22
|
import cv2
|
20
23
|
import matplotlib.pyplot as plt
|
@@ -30,19 +33,26 @@ RANK = int(os.getenv("RANK", -1))
|
|
30
33
|
LOCAL_RANK = int(os.getenv("LOCAL_RANK", -1)) # https://pytorch.org/docs/stable/elastic/run.html
|
31
34
|
|
32
35
|
# Other Constants
|
36
|
+
ARGV = sys.argv or ["", ""] # sometimes sys.argv = []
|
33
37
|
FILE = Path(__file__).resolve()
|
34
38
|
ROOT = FILE.parents[1] # YOLO
|
35
39
|
ASSETS = ROOT / "assets" # default images
|
40
|
+
ASSETS_URL = "https://github.com/ultralytics/assets/releases/download/v0.0.0" # assets GitHub URL
|
36
41
|
DEFAULT_CFG_PATH = ROOT / "cfg/default.yaml"
|
37
|
-
|
42
|
+
DEFAULT_SOL_CFG_PATH = ROOT / "cfg/solutions/default.yaml" # Ultralytics solutions yaml path
|
43
|
+
NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLO multiprocessing threads
|
38
44
|
AUTOINSTALL = str(os.getenv("YOLO_AUTOINSTALL", True)).lower() == "true" # global auto-install mode
|
39
45
|
VERBOSE = str(os.getenv("YOLO_VERBOSE", True)).lower() == "true" # global verbose mode
|
40
46
|
TQDM_BAR_FORMAT = "{l_bar}{bar:10}{r_bar}" if VERBOSE else None # tqdm bar format
|
41
47
|
LOGGING_NAME = "ultralytics"
|
42
48
|
MACOS, LINUX, WINDOWS = (platform.system() == x for x in ["Darwin", "Linux", "Windows"]) # environment booleans
|
43
|
-
ARM64 = platform.machine() in
|
49
|
+
ARM64 = platform.machine() in {"arm64", "aarch64"} # ARM64 booleans
|
50
|
+
PYTHON_VERSION = platform.python_version()
|
51
|
+
TORCH_VERSION = torch.__version__
|
52
|
+
TORCHVISION_VERSION = importlib.metadata.version("torchvision") # faster than importing torchvision
|
53
|
+
IS_VSCODE = os.environ.get("TERM_PROGRAM", False) == "vscode"
|
44
54
|
HELP_MSG = """
|
45
|
-
|
55
|
+
Examples for running Ultralytics:
|
46
56
|
|
47
57
|
1. Install the ultralytics package:
|
48
58
|
|
@@ -53,37 +63,37 @@ HELP_MSG = """
|
|
53
63
|
from ultralytics import YOLO
|
54
64
|
|
55
65
|
# Load a model
|
56
|
-
model = YOLO(
|
57
|
-
model = YOLO("
|
66
|
+
model = YOLO("yolo11n.yaml") # build a new model from scratch
|
67
|
+
model = YOLO("yolo11n.pt") # load a pretrained model (recommended for training)
|
58
68
|
|
59
69
|
# Use the model
|
60
|
-
results = model.train(data="
|
70
|
+
results = model.train(data="coco8.yaml", epochs=3) # train the model
|
61
71
|
results = model.val() # evaluate model performance on the validation set
|
62
|
-
results = model(
|
63
|
-
success = model.export(format=
|
72
|
+
results = model("https://ultralytics.com/images/bus.jpg") # predict on an image
|
73
|
+
success = model.export(format="onnx") # export the model to ONNX format
|
64
74
|
|
65
75
|
3. Use the command line interface (CLI):
|
66
76
|
|
67
|
-
|
77
|
+
Ultralytics 'yolo' CLI commands use the following syntax:
|
68
78
|
|
69
79
|
yolo TASK MODE ARGS
|
70
80
|
|
71
|
-
Where TASK (optional) is one of [detect, segment, classify]
|
72
|
-
MODE (required) is one of [train, val, predict, export]
|
73
|
-
ARGS (optional) are any number of custom
|
74
|
-
See all ARGS at https://docs.ultralytics.com/usage/cfg or with
|
81
|
+
Where TASK (optional) is one of [detect, segment, classify, pose, obb]
|
82
|
+
MODE (required) is one of [train, val, predict, export, track, benchmark]
|
83
|
+
ARGS (optional) are any number of custom "arg=value" pairs like "imgsz=320" that override defaults.
|
84
|
+
See all ARGS at https://docs.ultralytics.com/usage/cfg or with "yolo cfg"
|
75
85
|
|
76
86
|
- Train a detection model for 10 epochs with an initial learning_rate of 0.01
|
77
|
-
yolo detect train data=
|
87
|
+
yolo detect train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01
|
78
88
|
|
79
89
|
- Predict a YouTube video using a pretrained segmentation model at image size 320:
|
80
|
-
yolo segment predict model=
|
90
|
+
yolo segment predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320
|
81
91
|
|
82
92
|
- Val a pretrained detection model at batch-size 1 and image size 640:
|
83
|
-
yolo detect val model=
|
93
|
+
yolo detect val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640
|
84
94
|
|
85
|
-
- Export a
|
86
|
-
yolo export model=
|
95
|
+
- Export a YOLO11n classification model to ONNX format at image size 224 by 128 (no TASK required)
|
96
|
+
yolo export model=yolo11n-cls.pt format=onnx imgsz=224,128
|
87
97
|
|
88
98
|
- Run special commands:
|
89
99
|
yolo help
|
@@ -98,29 +108,59 @@ HELP_MSG = """
|
|
98
108
|
GitHub: https://github.com/ultralytics/ultralytics
|
99
109
|
"""
|
100
110
|
|
101
|
-
# Settings
|
111
|
+
# Settings and Environment Variables
|
102
112
|
torch.set_printoptions(linewidth=320, precision=4, profile="default")
|
103
113
|
np.set_printoptions(linewidth=320, formatter={"float_kind": "{:11.5g}".format}) # format short g, %precision=5
|
104
114
|
cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader)
|
105
115
|
os.environ["NUMEXPR_MAX_THREADS"] = str(NUM_THREADS) # NumExpr max threads
|
106
|
-
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" # for deterministic training
|
107
|
-
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "
|
116
|
+
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" # for deterministic training to avoid CUDA warning
|
117
|
+
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" # suppress verbose TF compiler warnings in Colab
|
118
|
+
os.environ["TORCH_CPP_LOG_LEVEL"] = "ERROR" # suppress "NNPACK.cpp could not initialize NNPACK" warnings
|
119
|
+
os.environ["KINETO_LOG_LEVEL"] = "5" # suppress verbose PyTorch profiler output when computing FLOPs
|
108
120
|
|
109
121
|
|
110
122
|
class TQDM(tqdm_original):
|
111
123
|
"""
|
112
|
-
|
124
|
+
A custom TQDM progress bar class that extends the original tqdm functionality.
|
113
125
|
|
114
|
-
|
115
|
-
|
116
|
-
|
126
|
+
This class modifies the behavior of the original tqdm progress bar based on global settings and provides
|
127
|
+
additional customization options.
|
128
|
+
|
129
|
+
Attributes:
|
130
|
+
disable (bool): Whether to disable the progress bar. Determined by the global VERBOSE setting and
|
131
|
+
any passed 'disable' argument.
|
132
|
+
bar_format (str): The format string for the progress bar. Uses the global TQDM_BAR_FORMAT if not
|
133
|
+
explicitly set.
|
134
|
+
|
135
|
+
Methods:
|
136
|
+
__init__: Initializes the TQDM object with custom settings.
|
137
|
+
|
138
|
+
Examples:
|
139
|
+
>>> from ultralytics.utils import TQDM
|
140
|
+
>>> for i in TQDM(range(100)):
|
141
|
+
... # Your processing code here
|
142
|
+
... pass
|
117
143
|
"""
|
118
144
|
|
119
145
|
def __init__(self, *args, **kwargs):
|
120
146
|
"""
|
121
|
-
|
147
|
+
Initializes a custom TQDM progress bar.
|
148
|
+
|
149
|
+
This class extends the original tqdm class to provide customized behavior for Ultralytics projects.
|
122
150
|
|
123
|
-
|
151
|
+
Args:
|
152
|
+
*args (Any): Variable length argument list to be passed to the original tqdm constructor.
|
153
|
+
**kwargs (Any): Arbitrary keyword arguments to be passed to the original tqdm constructor.
|
154
|
+
|
155
|
+
Notes:
|
156
|
+
- The progress bar is disabled if VERBOSE is False or if 'disable' is explicitly set to True in kwargs.
|
157
|
+
- The default bar format is set to TQDM_BAR_FORMAT unless overridden in kwargs.
|
158
|
+
|
159
|
+
Examples:
|
160
|
+
>>> from ultralytics.utils import TQDM
|
161
|
+
>>> for i in TQDM(range(100)):
|
162
|
+
... # Your code here
|
163
|
+
... pass
|
124
164
|
"""
|
125
165
|
kwargs["disable"] = not VERBOSE or kwargs.get("disable", False) # logical 'and' with default value if passed
|
126
166
|
kwargs.setdefault("bar_format", TQDM_BAR_FORMAT) # override default value if passed
|
@@ -128,8 +168,33 @@ class TQDM(tqdm_original):
|
|
128
168
|
|
129
169
|
|
130
170
|
class SimpleClass:
|
131
|
-
"""
|
132
|
-
|
171
|
+
"""
|
172
|
+
A simple base class for creating objects with string representations of their attributes.
|
173
|
+
|
174
|
+
This class provides a foundation for creating objects that can be easily printed or represented as strings,
|
175
|
+
showing all their non-callable attributes. It's useful for debugging and introspection of object states.
|
176
|
+
|
177
|
+
Methods:
|
178
|
+
__str__: Returns a human-readable string representation of the object.
|
179
|
+
__repr__: Returns a machine-readable string representation of the object.
|
180
|
+
__getattr__: Provides a custom attribute access error message with helpful information.
|
181
|
+
|
182
|
+
Examples:
|
183
|
+
>>> class MyClass(SimpleClass):
|
184
|
+
... def __init__(self):
|
185
|
+
... self.x = 10
|
186
|
+
... self.y = "hello"
|
187
|
+
>>> obj = MyClass()
|
188
|
+
>>> print(obj)
|
189
|
+
__main__.MyClass object with attributes:
|
190
|
+
|
191
|
+
x: 10
|
192
|
+
y: 'hello'
|
193
|
+
|
194
|
+
Notes:
|
195
|
+
- This class is designed to be subclassed. It provides a convenient way to inspect object attributes.
|
196
|
+
- The string representation includes the module and class name of the object.
|
197
|
+
- Callable attributes and attributes starting with an underscore are excluded from the string representation.
|
133
198
|
"""
|
134
199
|
|
135
200
|
def __str__(self):
|
@@ -157,8 +222,38 @@ class SimpleClass:
|
|
157
222
|
|
158
223
|
|
159
224
|
class IterableSimpleNamespace(SimpleNamespace):
|
160
|
-
"""
|
161
|
-
|
225
|
+
"""
|
226
|
+
An iterable SimpleNamespace class that provides enhanced functionality for attribute access and iteration.
|
227
|
+
|
228
|
+
This class extends the SimpleNamespace class with additional methods for iteration, string representation,
|
229
|
+
and attribute access. It is designed to be used as a convenient container for storing and accessing
|
230
|
+
configuration parameters.
|
231
|
+
|
232
|
+
Methods:
|
233
|
+
__iter__: Returns an iterator of key-value pairs from the namespace's attributes.
|
234
|
+
__str__: Returns a human-readable string representation of the object.
|
235
|
+
__getattr__: Provides a custom attribute access error message with helpful information.
|
236
|
+
get: Retrieves the value of a specified key, or a default value if the key doesn't exist.
|
237
|
+
|
238
|
+
Examples:
|
239
|
+
>>> cfg = IterableSimpleNamespace(a=1, b=2, c=3)
|
240
|
+
>>> for k, v in cfg:
|
241
|
+
... print(f"{k}: {v}")
|
242
|
+
a: 1
|
243
|
+
b: 2
|
244
|
+
c: 3
|
245
|
+
>>> print(cfg)
|
246
|
+
a=1
|
247
|
+
b=2
|
248
|
+
c=3
|
249
|
+
>>> cfg.get("b")
|
250
|
+
2
|
251
|
+
>>> cfg.get("d", "default")
|
252
|
+
'default'
|
253
|
+
|
254
|
+
Notes:
|
255
|
+
This class is particularly useful for storing configuration parameters in a more accessible
|
256
|
+
and iterable format compared to a standard dictionary.
|
162
257
|
"""
|
163
258
|
|
164
259
|
def __iter__(self):
|
@@ -202,7 +297,6 @@ def plt_settings(rcparams=None, backend="Agg"):
|
|
202
297
|
(Callable): Decorated function with temporarily set rc parameters and backend. This decorator can be
|
203
298
|
applied to any function that needs to have specific matplotlib rc parameters and backend for its execution.
|
204
299
|
"""
|
205
|
-
|
206
300
|
if rcparams is None:
|
207
301
|
rcparams = {"font.size": 11}
|
208
302
|
|
@@ -212,16 +306,19 @@ def plt_settings(rcparams=None, backend="Agg"):
|
|
212
306
|
def wrapper(*args, **kwargs):
|
213
307
|
"""Sets rc parameters and backend, calls the original function, and restores the settings."""
|
214
308
|
original_backend = plt.get_backend()
|
215
|
-
|
309
|
+
switch = backend.lower() != original_backend.lower()
|
310
|
+
if switch:
|
216
311
|
plt.close("all") # auto-close()ing of figures upon backend switching is deprecated since 3.8
|
217
312
|
plt.switch_backend(backend)
|
218
313
|
|
219
|
-
with
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
314
|
+
# Plot with backend and always revert to original backend
|
315
|
+
try:
|
316
|
+
with plt.rc_context(rcparams):
|
317
|
+
result = func(*args, **kwargs)
|
318
|
+
finally:
|
319
|
+
if switch:
|
320
|
+
plt.close("all")
|
321
|
+
plt.switch_backend(original_backend)
|
225
322
|
return result
|
226
323
|
|
227
324
|
return wrapper
|
@@ -229,37 +326,61 @@ def plt_settings(rcparams=None, backend="Agg"):
|
|
229
326
|
return decorator
|
230
327
|
|
231
328
|
|
232
|
-
def set_logging(name=LOGGING_NAME, verbose=True):
|
233
|
-
"""
|
329
|
+
def set_logging(name="LOGGING_NAME", verbose=True):
|
330
|
+
"""
|
331
|
+
Sets up logging with UTF-8 encoding and configurable verbosity.
|
332
|
+
|
333
|
+
This function configures logging for the Ultralytics library, setting the appropriate logging level and
|
334
|
+
formatter based on the verbosity flag and the current process rank. It handles special cases for Windows
|
335
|
+
environments where UTF-8 encoding might not be the default.
|
336
|
+
|
337
|
+
Args:
|
338
|
+
name (str): Name of the logger. Defaults to "LOGGING_NAME".
|
339
|
+
verbose (bool): Flag to set logging level to INFO if True, ERROR otherwise. Defaults to True.
|
340
|
+
|
341
|
+
Examples:
|
342
|
+
>>> set_logging(name="ultralytics", verbose=True)
|
343
|
+
>>> logger = logging.getLogger("ultralytics")
|
344
|
+
>>> logger.info("This is an info message")
|
345
|
+
|
346
|
+
Notes:
|
347
|
+
- On Windows, this function attempts to reconfigure stdout to use UTF-8 encoding if possible.
|
348
|
+
- If reconfiguration is not possible, it falls back to a custom formatter that handles non-UTF-8 environments.
|
349
|
+
- The function sets up a StreamHandler with the appropriate formatter and level.
|
350
|
+
- The logger's propagate flag is set to False to prevent duplicate logging in parent loggers.
|
351
|
+
"""
|
234
352
|
level = logging.INFO if verbose and RANK in {-1, 0} else logging.ERROR # rank in world for Multi-GPU trainings
|
235
353
|
|
236
|
-
# Configure the console (stdout) encoding to UTF-8
|
354
|
+
# Configure the console (stdout) encoding to UTF-8, with checks for compatibility
|
237
355
|
formatter = logging.Formatter("%(message)s") # Default formatter
|
238
|
-
if WINDOWS and sys.stdout.encoding != "utf-8":
|
356
|
+
if WINDOWS and hasattr(sys.stdout, "encoding") and sys.stdout.encoding != "utf-8":
|
357
|
+
|
358
|
+
class CustomFormatter(logging.Formatter):
|
359
|
+
def format(self, record):
|
360
|
+
"""Sets up logging with UTF-8 encoding and configurable verbosity."""
|
361
|
+
return emojis(super().format(record))
|
362
|
+
|
239
363
|
try:
|
364
|
+
# Attempt to reconfigure stdout to use UTF-8 encoding if possible
|
240
365
|
if hasattr(sys.stdout, "reconfigure"):
|
241
366
|
sys.stdout.reconfigure(encoding="utf-8")
|
367
|
+
# For environments where reconfigure is not available, wrap stdout in a TextIOWrapper
|
242
368
|
elif hasattr(sys.stdout, "buffer"):
|
243
369
|
import io
|
244
370
|
|
245
371
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
|
246
372
|
else:
|
247
|
-
|
373
|
+
formatter = CustomFormatter("%(message)s")
|
248
374
|
except Exception as e:
|
249
375
|
print(f"Creating custom formatter for non UTF-8 environments due to {e}")
|
376
|
+
formatter = CustomFormatter("%(message)s")
|
250
377
|
|
251
|
-
|
252
|
-
def format(self, record):
|
253
|
-
"""Sets up logging with UTF-8 encoding and configurable verbosity."""
|
254
|
-
return emojis(super().format(record))
|
255
|
-
|
256
|
-
formatter = CustomFormatter("%(message)s") # Use CustomFormatter to eliminate UTF-8 output as last recourse
|
257
|
-
|
258
|
-
# Create and configure the StreamHandler
|
378
|
+
# Create and configure the StreamHandler with the appropriate formatter and level
|
259
379
|
stream_handler = logging.StreamHandler(sys.stdout)
|
260
380
|
stream_handler.setFormatter(formatter)
|
261
381
|
stream_handler.setLevel(level)
|
262
382
|
|
383
|
+
# Set up the logger
|
263
384
|
logger = logging.getLogger(name)
|
264
385
|
logger.setLevel(level)
|
265
386
|
logger.addHandler(stream_handler)
|
@@ -294,7 +415,6 @@ class ThreadingLocked:
|
|
294
415
|
@ThreadingLocked()
|
295
416
|
def my_function():
|
296
417
|
# Your code here
|
297
|
-
pass
|
298
418
|
```
|
299
419
|
"""
|
300
420
|
|
@@ -358,7 +478,7 @@ def yaml_load(file="data.yaml", append_filename=False):
|
|
358
478
|
Returns:
|
359
479
|
(dict): YAML data and file name.
|
360
480
|
"""
|
361
|
-
assert Path(file).suffix in
|
481
|
+
assert Path(file).suffix in {".yaml", ".yml"}, f"Attempting to load non-YAML file {file} with yaml_load()"
|
362
482
|
with open(file, errors="ignore", encoding="utf-8") as f:
|
363
483
|
s = f.read() # string
|
364
484
|
|
@@ -384,12 +504,13 @@ def yaml_print(yaml_file: Union[str, Path, dict]) -> None:
|
|
384
504
|
(None)
|
385
505
|
"""
|
386
506
|
yaml_dict = yaml_load(yaml_file) if isinstance(yaml_file, (str, Path)) else yaml_file
|
387
|
-
dump = yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True)
|
507
|
+
dump = yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True, width=float("inf"))
|
388
508
|
LOGGER.info(f"Printing '{colorstr('bold', 'black', yaml_file)}'\n\n{dump}")
|
389
509
|
|
390
510
|
|
391
511
|
# Default configuration
|
392
512
|
DEFAULT_CFG_DICT = yaml_load(DEFAULT_CFG_PATH)
|
513
|
+
DEFAULT_SOL_DICT = yaml_load(DEFAULT_SOL_CFG_PATH) # Ultralytics solutions configuration
|
393
514
|
for k, v in DEFAULT_CFG_DICT.items():
|
394
515
|
if isinstance(v, str) and v.lower() == "none":
|
395
516
|
DEFAULT_CFG_DICT[k] = None
|
@@ -397,6 +518,17 @@ DEFAULT_CFG_KEYS = DEFAULT_CFG_DICT.keys()
|
|
397
518
|
DEFAULT_CFG = IterableSimpleNamespace(**DEFAULT_CFG_DICT)
|
398
519
|
|
399
520
|
|
521
|
+
def read_device_model() -> str:
|
522
|
+
"""
|
523
|
+
Reads the device model information from the system and caches it for quick access. Used by is_jetson() and
|
524
|
+
is_raspberrypi().
|
525
|
+
|
526
|
+
Returns:
|
527
|
+
(str): Kernel release information.
|
528
|
+
"""
|
529
|
+
return platform.release().lower()
|
530
|
+
|
531
|
+
|
400
532
|
def is_ubuntu() -> bool:
|
401
533
|
"""
|
402
534
|
Check if the OS is Ubuntu.
|
@@ -404,10 +536,11 @@ def is_ubuntu() -> bool:
|
|
404
536
|
Returns:
|
405
537
|
(bool): True if OS is Ubuntu, False otherwise.
|
406
538
|
"""
|
407
|
-
|
539
|
+
try:
|
408
540
|
with open("/etc/os-release") as f:
|
409
541
|
return "ID=ubuntu" in f.read()
|
410
|
-
|
542
|
+
except FileNotFoundError:
|
543
|
+
return False
|
411
544
|
|
412
545
|
|
413
546
|
def is_colab():
|
@@ -432,16 +565,16 @@ def is_kaggle():
|
|
432
565
|
|
433
566
|
def is_jupyter():
|
434
567
|
"""
|
435
|
-
Check if the current script is running inside a Jupyter Notebook.
|
568
|
+
Check if the current script is running inside a Jupyter Notebook.
|
436
569
|
|
437
570
|
Returns:
|
438
571
|
(bool): True if running inside a Jupyter Notebook, False otherwise.
|
439
|
-
"""
|
440
|
-
with contextlib.suppress(Exception):
|
441
|
-
from IPython import get_ipython
|
442
572
|
|
443
|
-
|
444
|
-
|
573
|
+
Note:
|
574
|
+
- Only works on Colab and Kaggle, other environments like Jupyterlab and Paperspace are not reliably detectable.
|
575
|
+
- "get_ipython" in globals() method suffers false positives when IPython package installed manually.
|
576
|
+
"""
|
577
|
+
return IS_COLAB or IS_KAGGLE
|
445
578
|
|
446
579
|
|
447
580
|
def is_docker() -> bool:
|
@@ -451,14 +584,33 @@ def is_docker() -> bool:
|
|
451
584
|
Returns:
|
452
585
|
(bool): True if the script is running inside a Docker container, False otherwise.
|
453
586
|
"""
|
454
|
-
|
455
|
-
|
456
|
-
with open(file) as f:
|
587
|
+
try:
|
588
|
+
with open("/proc/self/cgroup") as f:
|
457
589
|
return "docker" in f.read()
|
458
|
-
|
590
|
+
except Exception:
|
459
591
|
return False
|
460
592
|
|
461
593
|
|
594
|
+
def is_raspberrypi() -> bool:
|
595
|
+
"""
|
596
|
+
Determines if the Python environment is running on a Raspberry Pi by checking the device model information.
|
597
|
+
|
598
|
+
Returns:
|
599
|
+
(bool): True if running on a Raspberry Pi, False otherwise.
|
600
|
+
"""
|
601
|
+
return "rpi" in DEVICE_MODEL
|
602
|
+
|
603
|
+
|
604
|
+
def is_jetson() -> bool:
|
605
|
+
"""
|
606
|
+
Determines if the Python environment is running on an NVIDIA Jetson device by checking the device model information.
|
607
|
+
|
608
|
+
Returns:
|
609
|
+
(bool): True if running on an NVIDIA Jetson device, False otherwise.
|
610
|
+
"""
|
611
|
+
return "tegra" in DEVICE_MODEL
|
612
|
+
|
613
|
+
|
462
614
|
def is_online() -> bool:
|
463
615
|
"""
|
464
616
|
Check internet connectivity by attempting to connect to a known online host.
|
@@ -466,21 +618,15 @@ def is_online() -> bool:
|
|
466
618
|
Returns:
|
467
619
|
(bool): True if connection is successful, False otherwise.
|
468
620
|
"""
|
469
|
-
|
621
|
+
try:
|
622
|
+
assert str(os.getenv("YOLO_OFFLINE", "")).lower() != "true" # check if ENV var YOLO_OFFLINE="True"
|
623
|
+
import socket
|
470
624
|
|
471
|
-
|
472
|
-
|
473
|
-
test_connection = socket.create_connection(address=(host, 53), timeout=2)
|
474
|
-
except (socket.timeout, socket.gaierror, OSError):
|
475
|
-
continue
|
476
|
-
else:
|
477
|
-
# If the connection was successful, close it to avoid a ResourceWarning
|
478
|
-
test_connection.close()
|
625
|
+
for dns in ("1.1.1.1", "8.8.8.8"): # check Cloudflare and Google DNS
|
626
|
+
socket.create_connection(address=(dns, 80), timeout=2.0).close()
|
479
627
|
return True
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
ONLINE = is_online()
|
628
|
+
except Exception:
|
629
|
+
return False
|
484
630
|
|
485
631
|
|
486
632
|
def is_pip_package(filepath: str = __name__) -> bool:
|
@@ -522,7 +668,7 @@ def is_pytest_running():
|
|
522
668
|
Returns:
|
523
669
|
(bool): True if pytest is running, False otherwise.
|
524
670
|
"""
|
525
|
-
return ("PYTEST_CURRENT_TEST" in os.environ) or ("pytest" in sys.modules) or ("pytest" in Path(
|
671
|
+
return ("PYTEST_CURRENT_TEST" in os.environ) or ("pytest" in sys.modules) or ("pytest" in Path(ARGV[0]).stem)
|
526
672
|
|
527
673
|
|
528
674
|
def is_github_action_running() -> bool:
|
@@ -535,17 +681,6 @@ def is_github_action_running() -> bool:
|
|
535
681
|
return "GITHUB_ACTIONS" in os.environ and "GITHUB_WORKFLOW" in os.environ and "RUNNER_OS" in os.environ
|
536
682
|
|
537
683
|
|
538
|
-
def is_git_dir():
|
539
|
-
"""
|
540
|
-
Determines whether the current file is part of a git repository. If the current file is not part of a git
|
541
|
-
repository, returns None.
|
542
|
-
|
543
|
-
Returns:
|
544
|
-
(bool): True if current file is part of a git repository.
|
545
|
-
"""
|
546
|
-
return get_git_dir() is not None
|
547
|
-
|
548
|
-
|
549
684
|
def get_git_dir():
|
550
685
|
"""
|
551
686
|
Determines whether the current file is part of a git repository and if so, returns the repository root directory. If
|
@@ -559,6 +694,17 @@ def get_git_dir():
|
|
559
694
|
return d
|
560
695
|
|
561
696
|
|
697
|
+
def is_git_dir():
|
698
|
+
"""
|
699
|
+
Determines whether the current file is part of a git repository. If the current file is not part of a git
|
700
|
+
repository, returns None.
|
701
|
+
|
702
|
+
Returns:
|
703
|
+
(bool): True if current file is part of a git repository.
|
704
|
+
"""
|
705
|
+
return GIT_DIR is not None
|
706
|
+
|
707
|
+
|
562
708
|
def get_git_origin_url():
|
563
709
|
"""
|
564
710
|
Retrieves the origin URL of a git repository.
|
@@ -566,10 +712,12 @@ def get_git_origin_url():
|
|
566
712
|
Returns:
|
567
713
|
(str | None): The origin URL of the git repository or None if not git directory.
|
568
714
|
"""
|
569
|
-
if
|
570
|
-
|
715
|
+
if IS_GIT_DIR:
|
716
|
+
try:
|
571
717
|
origin = subprocess.check_output(["git", "config", "--get", "remote.origin.url"])
|
572
718
|
return origin.decode().strip()
|
719
|
+
except subprocess.CalledProcessError:
|
720
|
+
return None
|
573
721
|
|
574
722
|
|
575
723
|
def get_git_branch():
|
@@ -579,10 +727,12 @@ def get_git_branch():
|
|
579
727
|
Returns:
|
580
728
|
(str | None): The current git branch name or None if not a git directory.
|
581
729
|
"""
|
582
|
-
if
|
583
|
-
|
730
|
+
if IS_GIT_DIR:
|
731
|
+
try:
|
584
732
|
origin = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
585
733
|
return origin.decode().strip()
|
734
|
+
except subprocess.CalledProcessError:
|
735
|
+
return None
|
586
736
|
|
587
737
|
|
588
738
|
def get_default_args(func):
|
@@ -607,9 +757,11 @@ def get_ubuntu_version():
|
|
607
757
|
(str): Ubuntu version or None if not an Ubuntu OS.
|
608
758
|
"""
|
609
759
|
if is_ubuntu():
|
610
|
-
|
760
|
+
try:
|
611
761
|
with open("/etc/os-release") as f:
|
612
762
|
return re.search(r'VERSION_ID="(\d+\.\d+)"', f.read())[1]
|
763
|
+
except (FileNotFoundError, AttributeError):
|
764
|
+
return None
|
613
765
|
|
614
766
|
|
615
767
|
def get_user_config_dir(sub_dir="Ultralytics"):
|
@@ -645,12 +797,24 @@ def get_user_config_dir(sub_dir="Ultralytics"):
|
|
645
797
|
return path
|
646
798
|
|
647
799
|
|
800
|
+
# Define constants (required below)
|
801
|
+
DEVICE_MODEL = read_device_model() # is_jetson() and is_raspberrypi() depend on this constant
|
802
|
+
ONLINE = is_online()
|
803
|
+
IS_COLAB = is_colab()
|
804
|
+
IS_KAGGLE = is_kaggle()
|
805
|
+
IS_DOCKER = is_docker()
|
806
|
+
IS_JETSON = is_jetson()
|
807
|
+
IS_JUPYTER = is_jupyter()
|
808
|
+
IS_PIP_PACKAGE = is_pip_package()
|
809
|
+
IS_RASPBERRYPI = is_raspberrypi()
|
810
|
+
GIT_DIR = get_git_dir()
|
811
|
+
IS_GIT_DIR = is_git_dir()
|
648
812
|
USER_CONFIG_DIR = Path(os.getenv("YOLO_CONFIG_DIR") or get_user_config_dir()) # Ultralytics settings dir
|
649
|
-
|
813
|
+
SETTINGS_FILE = USER_CONFIG_DIR / "settings.json"
|
650
814
|
|
651
815
|
|
652
816
|
def colorstr(*input):
|
653
|
-
"""
|
817
|
+
r"""
|
654
818
|
Colors a string based on the provided color and style arguments. Utilizes ANSI escape codes.
|
655
819
|
See https://en.wikipedia.org/wiki/ANSI_escape_code for more details.
|
656
820
|
|
@@ -661,7 +825,7 @@ def colorstr(*input):
|
|
661
825
|
In the second form, 'blue' and 'bold' will be applied by default.
|
662
826
|
|
663
827
|
Args:
|
664
|
-
*input (str): A sequence of strings where the first n-1 strings are color and style arguments,
|
828
|
+
*input (str | Path): A sequence of strings where the first n-1 strings are color and style arguments,
|
665
829
|
and the last string is the one to be colored.
|
666
830
|
|
667
831
|
Supported Colors and Styles:
|
@@ -674,8 +838,8 @@ def colorstr(*input):
|
|
674
838
|
(str): The input string wrapped with ANSI escape codes for the specified color and style.
|
675
839
|
|
676
840
|
Examples:
|
677
|
-
>>> colorstr(
|
678
|
-
>>>
|
841
|
+
>>> colorstr("blue", "bold", "hello world")
|
842
|
+
>>> "\033[34m\033[1mhello world\033[0m"
|
679
843
|
"""
|
680
844
|
*args, string = input if len(input) > 1 else ("blue", "bold", input[0]) # color arguments, string
|
681
845
|
colors = {
|
@@ -713,8 +877,8 @@ def remove_colorstr(input_string):
|
|
713
877
|
(str): A new string with all ANSI escape codes removed.
|
714
878
|
|
715
879
|
Examples:
|
716
|
-
>>> remove_colorstr(colorstr(
|
717
|
-
>>>
|
880
|
+
>>> remove_colorstr(colorstr("blue", "bold", "hello world"))
|
881
|
+
>>> "hello world"
|
718
882
|
"""
|
719
883
|
ansi_escape = re.compile(r"\x1B\[[0-9;]*[A-Za-z]")
|
720
884
|
return ansi_escape.sub("", input_string)
|
@@ -728,12 +892,12 @@ class TryExcept(contextlib.ContextDecorator):
|
|
728
892
|
As a decorator:
|
729
893
|
>>> @TryExcept(msg="Error occurred in func", verbose=True)
|
730
894
|
>>> def func():
|
731
|
-
>>>
|
895
|
+
>>> # Function logic here
|
732
896
|
>>> pass
|
733
897
|
|
734
898
|
As a context manager:
|
735
899
|
>>> with TryExcept(msg="Error occurred in block", verbose=True):
|
736
|
-
>>>
|
900
|
+
>>> # Code block here
|
737
901
|
>>> pass
|
738
902
|
"""
|
739
903
|
|
@@ -757,20 +921,15 @@ class Retry(contextlib.ContextDecorator):
|
|
757
921
|
"""
|
758
922
|
Retry class for function execution with exponential backoff.
|
759
923
|
|
760
|
-
Can be used as a decorator
|
761
|
-
|
924
|
+
Can be used as a decorator to retry a function on exceptions, up to a specified number of times with an
|
925
|
+
exponentially increasing delay between retries.
|
762
926
|
|
763
927
|
Examples:
|
764
928
|
Example usage as a decorator:
|
765
929
|
>>> @Retry(times=3, delay=2)
|
766
930
|
>>> def test_func():
|
767
|
-
>>>
|
931
|
+
>>> # Replace with function logic that may raise exceptions
|
768
932
|
>>> return True
|
769
|
-
|
770
|
-
Example usage as a context manager:
|
771
|
-
>>> with Retry(times=3, delay=2):
|
772
|
-
>>> # Replace with code block that may raise exceptions
|
773
|
-
>>> pass
|
774
933
|
"""
|
775
934
|
|
776
935
|
def __init__(self, times=3, delay=2):
|
@@ -797,20 +956,6 @@ class Retry(contextlib.ContextDecorator):
|
|
797
956
|
|
798
957
|
return wrapped_func
|
799
958
|
|
800
|
-
def __enter__(self):
|
801
|
-
"""Enter the runtime context related to this object."""
|
802
|
-
self._attempts = 0
|
803
|
-
|
804
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
805
|
-
"""Exit the runtime context related to this object with exponential backoff."""
|
806
|
-
if exc_type is not None:
|
807
|
-
self._attempts += 1
|
808
|
-
if self._attempts < self.times:
|
809
|
-
print(f"Retry {self._attempts}/{self.times} failed: {exc_value}")
|
810
|
-
time.sleep(self.delay * (2**self._attempts)) # exponential backoff delay
|
811
|
-
return True # Suppresses the exception and retries
|
812
|
-
return False # Re-raises the exception if retries are exhausted
|
813
|
-
|
814
959
|
|
815
960
|
def threaded(func):
|
816
961
|
"""
|
@@ -834,7 +979,7 @@ def threaded(func):
|
|
834
979
|
def set_sentry():
|
835
980
|
"""
|
836
981
|
Initialize the Sentry SDK for error tracking and reporting. Only used if sentry_sdk package is installed and
|
837
|
-
sync=True in settings. Run 'yolo settings' to see and update settings
|
982
|
+
sync=True in settings. Run 'yolo settings' to see and update settings.
|
838
983
|
|
839
984
|
Conditions required to send errors (ALL conditions must be met or no errors will be reported):
|
840
985
|
- sentry_sdk package is installed
|
@@ -846,11 +991,26 @@ def set_sentry():
|
|
846
991
|
- online environment
|
847
992
|
- CLI used to run package (checked with 'yolo' as the name of the main CLI command)
|
848
993
|
|
849
|
-
The function also configures Sentry SDK to ignore KeyboardInterrupt and FileNotFoundError
|
850
|
-
|
994
|
+
The function also configures Sentry SDK to ignore KeyboardInterrupt and FileNotFoundError exceptions and to exclude
|
995
|
+
events with 'out of memory' in their exception message.
|
851
996
|
|
852
997
|
Additionally, the function sets custom tags and user information for Sentry events.
|
853
998
|
"""
|
999
|
+
if (
|
1000
|
+
not SETTINGS["sync"]
|
1001
|
+
or RANK not in {-1, 0}
|
1002
|
+
or Path(ARGV[0]).name != "yolo"
|
1003
|
+
or TESTS_RUNNING
|
1004
|
+
or not ONLINE
|
1005
|
+
or not IS_PIP_PACKAGE
|
1006
|
+
or IS_GIT_DIR
|
1007
|
+
):
|
1008
|
+
return
|
1009
|
+
# If sentry_sdk package is not installed then return and do not use Sentry
|
1010
|
+
try:
|
1011
|
+
import sentry_sdk # noqa
|
1012
|
+
except ImportError:
|
1013
|
+
return
|
854
1014
|
|
855
1015
|
def before_send(event, hint):
|
856
1016
|
"""
|
@@ -864,153 +1024,254 @@ def set_sentry():
|
|
864
1024
|
dict: The modified event or None if the event should not be sent to Sentry.
|
865
1025
|
"""
|
866
1026
|
if "exc_info" in hint:
|
867
|
-
exc_type, exc_value,
|
868
|
-
if exc_type in
|
1027
|
+
exc_type, exc_value, _ = hint["exc_info"]
|
1028
|
+
if exc_type in {KeyboardInterrupt, FileNotFoundError} or "out of memory" in str(exc_value):
|
869
1029
|
return None # do not send event
|
870
1030
|
|
871
1031
|
event["tags"] = {
|
872
|
-
"sys_argv":
|
873
|
-
"sys_argv_name": Path(
|
874
|
-
"install": "git" if
|
1032
|
+
"sys_argv": ARGV[0],
|
1033
|
+
"sys_argv_name": Path(ARGV[0]).name,
|
1034
|
+
"install": "git" if IS_GIT_DIR else "pip" if IS_PIP_PACKAGE else "other",
|
875
1035
|
"os": ENVIRONMENT,
|
876
1036
|
}
|
877
1037
|
return event
|
878
1038
|
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
1039
|
+
sentry_sdk.init(
|
1040
|
+
dsn="https://888e5a0778212e1d0314c37d4b9aae5d@o4504521589325824.ingest.us.sentry.io/4504521592406016",
|
1041
|
+
debug=False,
|
1042
|
+
auto_enabling_integrations=False,
|
1043
|
+
traces_sample_rate=1.0,
|
1044
|
+
release=__version__,
|
1045
|
+
environment="production", # 'dev' or 'production'
|
1046
|
+
before_send=before_send,
|
1047
|
+
ignore_errors=[KeyboardInterrupt, FileNotFoundError],
|
1048
|
+
)
|
1049
|
+
sentry_sdk.set_user({"id": SETTINGS["uuid"]}) # SHA-256 anonymized UUID hash
|
1050
|
+
|
1051
|
+
|
1052
|
+
class JSONDict(dict):
|
1053
|
+
"""
|
1054
|
+
A dictionary-like class that provides JSON persistence for its contents.
|
1055
|
+
|
1056
|
+
This class extends the built-in dictionary to automatically save its contents to a JSON file whenever they are
|
1057
|
+
modified. It ensures thread-safe operations using a lock.
|
1058
|
+
|
1059
|
+
Attributes:
|
1060
|
+
file_path (Path): The path to the JSON file used for persistence.
|
1061
|
+
lock (threading.Lock): A lock object to ensure thread-safe operations.
|
1062
|
+
|
1063
|
+
Methods:
|
1064
|
+
_load: Loads the data from the JSON file into the dictionary.
|
1065
|
+
_save: Saves the current state of the dictionary to the JSON file.
|
1066
|
+
__setitem__: Stores a key-value pair and persists it to disk.
|
1067
|
+
__delitem__: Removes an item and updates the persistent storage.
|
1068
|
+
update: Updates the dictionary and persists changes.
|
1069
|
+
clear: Clears all entries and updates the persistent storage.
|
1070
|
+
|
1071
|
+
Examples:
|
1072
|
+
>>> json_dict = JSONDict("data.json")
|
1073
|
+
>>> json_dict["key"] = "value"
|
1074
|
+
>>> print(json_dict["key"])
|
1075
|
+
value
|
1076
|
+
>>> del json_dict["key"]
|
1077
|
+
>>> json_dict.update({"new_key": "new_value"})
|
1078
|
+
>>> json_dict.clear()
|
1079
|
+
"""
|
1080
|
+
|
1081
|
+
def __init__(self, file_path: Union[str, Path] = "data.json"):
|
1082
|
+
"""Initialize a JSONDict object with a specified file path for JSON persistence."""
|
1083
|
+
super().__init__()
|
1084
|
+
self.file_path = Path(file_path)
|
1085
|
+
self.lock = Lock()
|
1086
|
+
self._load()
|
1087
|
+
|
1088
|
+
def _load(self):
|
1089
|
+
"""Load the data from the JSON file into the dictionary."""
|
889
1090
|
try:
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
traces_sample_rate=1.0,
|
898
|
-
release=__version__,
|
899
|
-
environment="production", # 'dev' or 'production'
|
900
|
-
before_send=before_send,
|
901
|
-
ignore_errors=[KeyboardInterrupt, FileNotFoundError],
|
902
|
-
)
|
903
|
-
sentry_sdk.set_user({"id": SETTINGS["uuid"]}) # SHA-256 anonymized UUID hash
|
1091
|
+
if self.file_path.exists():
|
1092
|
+
with open(self.file_path) as f:
|
1093
|
+
self.update(json.load(f))
|
1094
|
+
except json.JSONDecodeError:
|
1095
|
+
print(f"Error decoding JSON from {self.file_path}. Starting with an empty dictionary.")
|
1096
|
+
except Exception as e:
|
1097
|
+
print(f"Error reading from {self.file_path}: {e}")
|
904
1098
|
|
1099
|
+
def _save(self):
|
1100
|
+
"""Save the current state of the dictionary to the JSON file."""
|
1101
|
+
try:
|
1102
|
+
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
1103
|
+
with open(self.file_path, "w") as f:
|
1104
|
+
json.dump(dict(self), f, indent=2, default=self._json_default)
|
1105
|
+
except Exception as e:
|
1106
|
+
print(f"Error writing to {self.file_path}: {e}")
|
1107
|
+
|
1108
|
+
@staticmethod
|
1109
|
+
def _json_default(obj):
|
1110
|
+
"""Handle JSON serialization of Path objects."""
|
1111
|
+
if isinstance(obj, Path):
|
1112
|
+
return str(obj)
|
1113
|
+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
|
1114
|
+
|
1115
|
+
def __setitem__(self, key, value):
|
1116
|
+
"""Store a key-value pair and persist to disk."""
|
1117
|
+
with self.lock:
|
1118
|
+
super().__setitem__(key, value)
|
1119
|
+
self._save()
|
1120
|
+
|
1121
|
+
def __delitem__(self, key):
|
1122
|
+
"""Remove an item and update the persistent storage."""
|
1123
|
+
with self.lock:
|
1124
|
+
super().__delitem__(key)
|
1125
|
+
self._save()
|
905
1126
|
|
906
|
-
|
1127
|
+
def __str__(self):
|
1128
|
+
"""Return a pretty-printed JSON string representation of the dictionary."""
|
1129
|
+
contents = json.dumps(dict(self), indent=2, ensure_ascii=False, default=self._json_default)
|
1130
|
+
return f'JSONDict("{self.file_path}"):\n{contents}'
|
1131
|
+
|
1132
|
+
def update(self, *args, **kwargs):
|
1133
|
+
"""Update the dictionary and persist changes."""
|
1134
|
+
with self.lock:
|
1135
|
+
super().update(*args, **kwargs)
|
1136
|
+
self._save()
|
1137
|
+
|
1138
|
+
def clear(self):
|
1139
|
+
"""Clear all entries and update the persistent storage."""
|
1140
|
+
with self.lock:
|
1141
|
+
super().clear()
|
1142
|
+
self._save()
|
1143
|
+
|
1144
|
+
|
1145
|
+
class SettingsManager(JSONDict):
|
907
1146
|
"""
|
908
|
-
|
1147
|
+
SettingsManager class for managing and persisting Ultralytics settings.
|
909
1148
|
|
910
|
-
|
911
|
-
|
912
|
-
|
1149
|
+
This class extends JSONDict to provide JSON persistence for settings, ensuring thread-safe operations and default
|
1150
|
+
values. It validates settings on initialization and provides methods to update or reset settings.
|
1151
|
+
|
1152
|
+
Attributes:
|
1153
|
+
file (Path): The path to the JSON file used for persistence.
|
1154
|
+
version (str): The version of the settings schema.
|
1155
|
+
defaults (Dict): A dictionary containing default settings.
|
1156
|
+
help_msg (str): A help message for users on how to view and update settings.
|
1157
|
+
|
1158
|
+
Methods:
|
1159
|
+
_validate_settings: Validates the current settings and resets if necessary.
|
1160
|
+
update: Updates settings, validating keys and types.
|
1161
|
+
reset: Resets the settings to default and saves them.
|
1162
|
+
|
1163
|
+
Examples:
|
1164
|
+
Initialize and update settings:
|
1165
|
+
>>> settings = SettingsManager()
|
1166
|
+
>>> settings.update(runs_dir="/new/runs/dir")
|
1167
|
+
>>> print(settings["runs_dir"])
|
1168
|
+
/new/runs/dir
|
913
1169
|
"""
|
914
1170
|
|
915
|
-
def __init__(self, file=
|
916
|
-
"""
|
917
|
-
file.
|
918
|
-
"""
|
919
|
-
import copy
|
1171
|
+
def __init__(self, file=SETTINGS_FILE, version="0.0.6"):
|
1172
|
+
"""Initializes the SettingsManager with default settings and loads user settings."""
|
920
1173
|
import hashlib
|
921
1174
|
|
922
|
-
from ultralytics.utils.checks import check_version
|
923
1175
|
from ultralytics.utils.torch_utils import torch_distributed_zero_first
|
924
1176
|
|
925
|
-
|
926
|
-
|
927
|
-
datasets_root = (root.parent if git_dir and is_dir_writeable(root.parent) else root).resolve()
|
1177
|
+
root = GIT_DIR or Path()
|
1178
|
+
datasets_root = (root.parent if GIT_DIR and is_dir_writeable(root.parent) else root).resolve()
|
928
1179
|
|
929
1180
|
self.file = Path(file)
|
930
1181
|
self.version = version
|
931
1182
|
self.defaults = {
|
932
|
-
"settings_version": version,
|
933
|
-
"datasets_dir": str(datasets_root / "datasets"),
|
934
|
-
"weights_dir": str(root / "weights"),
|
935
|
-
"runs_dir": str(root / "runs"),
|
936
|
-
"uuid": hashlib.sha256(str(uuid.getnode()).encode()).hexdigest(),
|
937
|
-
"sync": True,
|
938
|
-
"api_key": "",
|
939
|
-
"openai_api_key": "",
|
940
|
-
"clearml": True, #
|
941
|
-
"comet": True,
|
942
|
-
"dvc": True,
|
943
|
-
"hub": True,
|
944
|
-
"mlflow": True,
|
945
|
-
"neptune": True,
|
946
|
-
"raytune": True,
|
947
|
-
"tensorboard": True,
|
948
|
-
"wandb":
|
1183
|
+
"settings_version": version, # Settings schema version
|
1184
|
+
"datasets_dir": str(datasets_root / "datasets"), # Datasets directory
|
1185
|
+
"weights_dir": str(root / "weights"), # Model weights directory
|
1186
|
+
"runs_dir": str(root / "runs"), # Experiment runs directory
|
1187
|
+
"uuid": hashlib.sha256(str(uuid.getnode()).encode()).hexdigest(), # SHA-256 anonymized UUID hash
|
1188
|
+
"sync": True, # Enable synchronization
|
1189
|
+
"api_key": "", # Ultralytics API Key
|
1190
|
+
"openai_api_key": "", # OpenAI API Key
|
1191
|
+
"clearml": True, # ClearML integration
|
1192
|
+
"comet": True, # Comet integration
|
1193
|
+
"dvc": True, # DVC integration
|
1194
|
+
"hub": True, # Ultralytics HUB integration
|
1195
|
+
"mlflow": True, # MLflow integration
|
1196
|
+
"neptune": True, # Neptune integration
|
1197
|
+
"raytune": True, # Ray Tune integration
|
1198
|
+
"tensorboard": True, # TensorBoard logging
|
1199
|
+
"wandb": False, # Weights & Biases logging
|
1200
|
+
"vscode_msg": True, # VSCode messaging
|
949
1201
|
}
|
950
1202
|
|
951
|
-
|
1203
|
+
self.help_msg = (
|
1204
|
+
f"\nView Ultralytics Settings with 'yolo settings' or at '{self.file}'"
|
1205
|
+
"\nUpdate Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. "
|
1206
|
+
"For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings."
|
1207
|
+
)
|
952
1208
|
|
953
1209
|
with torch_distributed_zero_first(RANK):
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
correct_keys = self.keys() == self.defaults.keys()
|
959
|
-
correct_types = all(type(a) is type(b) for a, b in zip(self.values(), self.defaults.values()))
|
960
|
-
correct_version = check_version(self["settings_version"], self.version)
|
961
|
-
help_msg = (
|
962
|
-
f"\nView settings with 'yolo settings' or at '{self.file}'"
|
963
|
-
"\nUpdate settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. "
|
964
|
-
"For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings."
|
965
|
-
)
|
966
|
-
if not (correct_keys and correct_types and correct_version):
|
967
|
-
LOGGER.warning(
|
968
|
-
"WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
|
969
|
-
f"with your settings or a recent ultralytics package update. {help_msg}"
|
970
|
-
)
|
1210
|
+
super().__init__(self.file)
|
1211
|
+
|
1212
|
+
if not self.file.exists() or not self: # Check if file doesn't exist or is empty
|
1213
|
+
LOGGER.info(f"Creating new Ultralytics Settings v{version} file ✅ {self.help_msg}")
|
971
1214
|
self.reset()
|
972
1215
|
|
973
|
-
|
974
|
-
LOGGER.warning(
|
975
|
-
f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
|
976
|
-
f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
|
977
|
-
f"Please change one to avoid possible issues during training. {help_msg}"
|
978
|
-
)
|
1216
|
+
self._validate_settings()
|
979
1217
|
|
980
|
-
def
|
981
|
-
"""
|
982
|
-
|
1218
|
+
def _validate_settings(self):
|
1219
|
+
"""Validate the current settings and reset if necessary."""
|
1220
|
+
correct_keys = set(self.keys()) == set(self.defaults.keys())
|
1221
|
+
correct_types = all(isinstance(self.get(k), type(v)) for k, v in self.defaults.items())
|
1222
|
+
correct_version = self.get("settings_version", "") == self.version
|
983
1223
|
|
984
|
-
|
985
|
-
|
986
|
-
|
1224
|
+
if not (correct_keys and correct_types and correct_version):
|
1225
|
+
LOGGER.warning(
|
1226
|
+
"WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
|
1227
|
+
f"with your settings or a recent ultralytics package update. {self.help_msg}"
|
1228
|
+
)
|
1229
|
+
self.reset()
|
1230
|
+
|
1231
|
+
if self.get("datasets_dir") == self.get("runs_dir"):
|
1232
|
+
LOGGER.warning(
|
1233
|
+
f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
|
1234
|
+
f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
|
1235
|
+
f"Please change one to avoid possible issues during training. {self.help_msg}"
|
1236
|
+
)
|
1237
|
+
|
1238
|
+
def __setitem__(self, key, value):
|
1239
|
+
"""Updates one key: value pair."""
|
1240
|
+
self.update({key: value})
|
987
1241
|
|
988
1242
|
def update(self, *args, **kwargs):
|
989
|
-
"""Updates
|
1243
|
+
"""Updates settings, validating keys and types."""
|
1244
|
+
for arg in args:
|
1245
|
+
if isinstance(arg, dict):
|
1246
|
+
kwargs.update(arg)
|
1247
|
+
for k, v in kwargs.items():
|
1248
|
+
if k not in self.defaults:
|
1249
|
+
raise KeyError(f"No Ultralytics setting '{k}'. {self.help_msg}")
|
1250
|
+
t = type(self.defaults[k])
|
1251
|
+
if not isinstance(v, t):
|
1252
|
+
raise TypeError(
|
1253
|
+
f"Ultralytics setting '{k}' must be '{t.__name__}' type, not '{type(v).__name__}'. {self.help_msg}"
|
1254
|
+
)
|
990
1255
|
super().update(*args, **kwargs)
|
991
|
-
self.save()
|
992
1256
|
|
993
1257
|
def reset(self):
|
994
1258
|
"""Resets the settings to default and saves them."""
|
995
1259
|
self.clear()
|
996
1260
|
self.update(self.defaults)
|
997
|
-
self.save()
|
998
1261
|
|
999
1262
|
|
1000
|
-
def deprecation_warn(arg, new_arg
|
1263
|
+
def deprecation_warn(arg, new_arg=None):
|
1001
1264
|
"""Issue a deprecation warning when a deprecated argument is used, suggesting an updated argument."""
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
f"Please use '{new_arg}' instead."
|
1007
|
-
)
|
1265
|
+
msg = f"WARNING ⚠️ '{arg}' is deprecated and will be removed in in the future."
|
1266
|
+
if new_arg is not None:
|
1267
|
+
msg += f" Use '{new_arg}' instead."
|
1268
|
+
LOGGER.warning(msg)
|
1008
1269
|
|
1009
1270
|
|
1010
1271
|
def clean_url(url):
|
1011
1272
|
"""Strip auth from URL, i.e. https://url.com/file.txt?auth -> https://url.com/file.txt."""
|
1012
1273
|
url = Path(url).as_posix().replace(":/", "://") # Pathlib turns :// -> :/, as_posix() for Windows
|
1013
|
-
return
|
1274
|
+
return unquote(url).split("?")[0] # '%2F' to '/', split https://url.com/file.txt?auth
|
1014
1275
|
|
1015
1276
|
|
1016
1277
|
def url2file(url):
|
@@ -1018,31 +1279,42 @@ def url2file(url):
|
|
1018
1279
|
return Path(clean_url(url)).name
|
1019
1280
|
|
1020
1281
|
|
1282
|
+
def vscode_msg(ext="ultralytics.ultralytics-snippets") -> str:
|
1283
|
+
"""Display a message to install Ultralytics-Snippets for VS Code if not already installed."""
|
1284
|
+
path = (USER_CONFIG_DIR.parents[2] if WINDOWS else USER_CONFIG_DIR.parents[1]) / ".vscode/extensions"
|
1285
|
+
obs_file = path / ".obsolete" # file tracks uninstalled extensions, while source directory remains
|
1286
|
+
installed = any(path.glob(f"{ext}*")) and ext not in (obs_file.read_text("utf-8") if obs_file.exists() else "")
|
1287
|
+
url = "https://docs.ultralytics.com/integrations/vscode"
|
1288
|
+
return "" if installed else f"{colorstr('VS Code:')} view Ultralytics VS Code Extension ⚡ at {url}"
|
1289
|
+
|
1290
|
+
|
1021
1291
|
# Run below code on utils init ------------------------------------------------------------------------------------
|
1022
1292
|
|
1023
1293
|
# Check first-install steps
|
1024
1294
|
PREFIX = colorstr("Ultralytics: ")
|
1025
1295
|
SETTINGS = SettingsManager() # initialize settings
|
1296
|
+
PERSISTENT_CACHE = JSONDict(USER_CONFIG_DIR / "persistent_cache.json") # initialize persistent cache
|
1026
1297
|
DATASETS_DIR = Path(SETTINGS["datasets_dir"]) # global datasets directory
|
1027
1298
|
WEIGHTS_DIR = Path(SETTINGS["weights_dir"]) # global weights directory
|
1028
1299
|
RUNS_DIR = Path(SETTINGS["runs_dir"]) # global runs directory
|
1029
1300
|
ENVIRONMENT = (
|
1030
1301
|
"Colab"
|
1031
|
-
if
|
1302
|
+
if IS_COLAB
|
1032
1303
|
else "Kaggle"
|
1033
|
-
if
|
1304
|
+
if IS_KAGGLE
|
1034
1305
|
else "Jupyter"
|
1035
|
-
if
|
1306
|
+
if IS_JUPYTER
|
1036
1307
|
else "Docker"
|
1037
|
-
if
|
1308
|
+
if IS_DOCKER
|
1038
1309
|
else platform.system()
|
1039
1310
|
)
|
1040
1311
|
TESTS_RUNNING = is_pytest_running() or is_github_action_running()
|
1041
1312
|
set_sentry()
|
1042
1313
|
|
1043
1314
|
# Apply monkey patches
|
1044
|
-
from .patches import imread, imshow, imwrite, torch_save
|
1315
|
+
from ultralytics.utils.patches import imread, imshow, imwrite, torch_load, torch_save
|
1045
1316
|
|
1317
|
+
torch.load = torch_load
|
1046
1318
|
torch.save = torch_save
|
1047
1319
|
if WINDOWS:
|
1048
1320
|
# Apply cv2 patches for non-ASCII and non-UTF characters in image paths
|