ultralytics 8.3.123__py3-none-any.whl → 8.3.125__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/test_python.py +5 -8
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +7 -14
- ultralytics/cfg/default.yaml +2 -2
- ultralytics/data/base.py +1 -2
- ultralytics/data/loaders.py +3 -4
- ultralytics/data/utils.py +8 -9
- ultralytics/engine/exporter.py +7 -7
- ultralytics/engine/model.py +7 -4
- ultralytics/engine/trainer.py +2 -2
- ultralytics/engine/tuner.py +3 -3
- ultralytics/hub/session.py +1 -1
- ultralytics/models/sam/model.py +2 -1
- ultralytics/models/sam/modules/tiny_encoder.py +2 -3
- ultralytics/models/sam/predict.py +4 -1
- ultralytics/models/yolo/model.py +3 -3
- ultralytics/nn/autobackend.py +4 -4
- ultralytics/nn/tasks.py +7 -7
- ultralytics/solutions/analytics.py +9 -8
- ultralytics/solutions/config.py +104 -0
- ultralytics/solutions/heatmap.py +1 -1
- ultralytics/solutions/object_blurrer.py +1 -1
- ultralytics/solutions/object_cropper.py +2 -2
- ultralytics/solutions/parking_management.py +2 -2
- ultralytics/solutions/security_alarm.py +1 -1
- ultralytics/solutions/solutions.py +6 -9
- ultralytics/solutions/speed_estimation.py +4 -4
- ultralytics/solutions/trackzone.py +1 -1
- ultralytics/solutions/vision_eye.py +1 -1
- ultralytics/trackers/track.py +2 -2
- ultralytics/utils/__init__.py +115 -59
- ultralytics/utils/benchmarks.py +4 -8
- ultralytics/utils/checks.py +4 -3
- ultralytics/utils/dist.py +2 -1
- ultralytics/utils/downloads.py +6 -1
- ultralytics/utils/metrics.py +6 -2
- ultralytics/utils/plotting.py +11 -5
- ultralytics/utils/torch_utils.py +10 -5
- {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/METADATA +1 -1
- {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/RECORD +44 -44
- {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/WHEEL +1 -1
- ultralytics/cfg/solutions/default.yaml +0 -24
- {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/licenses/LICENSE +0 -0
- {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/top_level.txt +0 -0
@@ -3,10 +3,7 @@
|
|
3
3
|
from itertools import cycle
|
4
4
|
|
5
5
|
import cv2
|
6
|
-
import matplotlib.pyplot as plt
|
7
6
|
import numpy as np
|
8
|
-
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
9
|
-
from matplotlib.figure import Figure
|
10
7
|
|
11
8
|
from ultralytics.solutions.solutions import BaseSolution, SolutionResults # Import a parent class
|
12
9
|
|
@@ -32,7 +29,7 @@ class Analytics(BaseSolution):
|
|
32
29
|
clswise_count (Dict[str, int]): Dictionary for class-wise object counts.
|
33
30
|
fig (Figure): Matplotlib figure object for the chart.
|
34
31
|
ax (Axes): Matplotlib axes object for the chart.
|
35
|
-
canvas (
|
32
|
+
canvas (FigureCanvasAgg): Canvas for rendering the chart.
|
36
33
|
lines (dict): Dictionary to store line objects for area charts.
|
37
34
|
color_mapping (Dict[str, str]): Dictionary mapping class labels to colors for consistent visualization.
|
38
35
|
|
@@ -51,7 +48,11 @@ class Analytics(BaseSolution):
|
|
51
48
|
"""Initialize Analytics class with various chart types for visual data representation."""
|
52
49
|
super().__init__(**kwargs)
|
53
50
|
|
54
|
-
|
51
|
+
import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
|
52
|
+
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
53
|
+
from matplotlib.figure import Figure
|
54
|
+
|
55
|
+
self.type = self.CFG["analytics_type"] # type of analytics i.e "line", "pie", "bar" or "area" charts.
|
55
56
|
self.x_label = "Classes" if self.type in {"bar", "pie"} else "Frame#"
|
56
57
|
self.y_label = "Total Counts"
|
57
58
|
|
@@ -61,7 +62,7 @@ class Analytics(BaseSolution):
|
|
61
62
|
self.title = "Ultralytics Solutions" # window name
|
62
63
|
self.max_points = 45 # maximum points to be drawn on window
|
63
64
|
self.fontsize = 25 # text font size for display
|
64
|
-
figsize = (12.8, 7.2)
|
65
|
+
figsize = self.CFG["figsize"] # set output image size i.e (12.8, 7.2) -> w = 1280, h = 720
|
65
66
|
self.color_cycle = cycle(["#DD00BA", "#042AFF", "#FF4447", "#7D24FF", "#BD00FF"])
|
66
67
|
|
67
68
|
self.total_counts = 0 # count variable for storing total counts i.e. for line
|
@@ -71,14 +72,14 @@ class Analytics(BaseSolution):
|
|
71
72
|
if self.type in {"line", "area"}:
|
72
73
|
self.lines = {}
|
73
74
|
self.fig = Figure(facecolor=self.bg_color, figsize=figsize)
|
74
|
-
self.canvas =
|
75
|
+
self.canvas = FigureCanvasAgg(self.fig) # Set common axis properties
|
75
76
|
self.ax = self.fig.add_subplot(111, facecolor=self.bg_color)
|
76
77
|
if self.type == "line":
|
77
78
|
(self.line,) = self.ax.plot([], [], color="cyan", linewidth=self.line_width)
|
78
79
|
elif self.type in {"bar", "pie"}:
|
79
80
|
# Initialize bar or pie plot
|
80
81
|
self.fig, self.ax = plt.subplots(figsize=figsize, facecolor=self.bg_color)
|
81
|
-
self.canvas =
|
82
|
+
self.canvas = FigureCanvasAgg(self.fig) # Set common axis properties
|
82
83
|
self.ax.set_facecolor(self.bg_color)
|
83
84
|
self.color_mapping = {}
|
84
85
|
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import List, Optional, Tuple
|
5
|
+
|
6
|
+
import cv2
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass
|
10
|
+
class SolutionConfig:
|
11
|
+
"""
|
12
|
+
Manages configuration parameters for Ultralytics Vision AI solutions.
|
13
|
+
|
14
|
+
The SolutionConfig class serves as a centralized configuration container for all the
|
15
|
+
Ultralytics solution modules: https://docs.ultralytics.com/solutions/#solutions.
|
16
|
+
It leverages Python `dataclass` for clear, type-safe, and maintainable parameter definitions.
|
17
|
+
|
18
|
+
Attributes:
|
19
|
+
source (Optional[str]): Path to the input source (video, RTSP, etc.). Only usable with Solutions CLI.
|
20
|
+
model (Optional[str]): Path to the Ultralytics YOLO model to be used for inference.
|
21
|
+
classes (Optional[List[int]]): List of class indices to filter detections.
|
22
|
+
show_conf (bool): Whether to show confidence scores on the visual output.
|
23
|
+
show_labels (bool): Whether to display class labels on visual output.
|
24
|
+
region (Optional[List[Tuple[int, int]]]): Polygonal region or line for object counting.
|
25
|
+
colormap (Optional[int]): OpenCV colormap constant for visual overlays (e.g., cv2.COLORMAP_JET).
|
26
|
+
show_in (bool): Whether to display count number for objects entering the region.
|
27
|
+
show_out (bool): Whether to display count number for objects leaving the region.
|
28
|
+
up_angle (float): Upper angle threshold used in pose-based workouts monitoring.
|
29
|
+
down_angle (int): Lower angle threshold used in pose-based workouts monitoring.
|
30
|
+
kpts (List[int]): Keypoint indices to monitor, e.g., for pose analytics.
|
31
|
+
analytics_type (str): Type of analytics to perform ("line", "area", "bar", "pie", etc.).
|
32
|
+
figsize (Optional[Tuple[int, int]]): Size of the matplotlib figure used for analytical plots (width, height).
|
33
|
+
blur_ratio (float): Ratio used to blur objects in the video frames (0.0 to 1.0).
|
34
|
+
vision_point (Tuple[int, int]): Reference point for directional tracking or perspective drawing.
|
35
|
+
crop_dir (str): Directory path to save cropped detection images.
|
36
|
+
json_file (str): Path to a JSON file containing data for parking areas.
|
37
|
+
line_width (int): Width for visual display i.e. bounding boxes, keypoints, counts.
|
38
|
+
records (int): Number of detection records to send email alerts.
|
39
|
+
fps (float): Frame rate (Frames Per Second) for speed estimation calculation.
|
40
|
+
max_hist (int): Maximum number of historical points or states stored per tracked object for speed estimation.
|
41
|
+
meter_per_pixel (float): Scale for real-world measurement, used in speed or distance calculations.
|
42
|
+
max_speed (int): Maximum speed limit (e.g., km/h or mph) used in visual alerts or constraints.
|
43
|
+
show (bool): Whether to display the visual output on screen.
|
44
|
+
iou (float): Intersection-over-Union threshold for detection filtering.
|
45
|
+
conf (float): Confidence threshold for keeping predictions.
|
46
|
+
device (Optional[str]): Device to run inference on (e.g., 'cpu', '0' for CUDA GPU).
|
47
|
+
max_det (int): Maximum number of detections allowed per video frame.
|
48
|
+
half (bool): Whether to use FP16 precision (requires a supported CUDA device).
|
49
|
+
tracker (str): Path to tracking configuration YAML file (e.g., 'botsort.yaml').
|
50
|
+
verbose (bool): Enable verbose logging output for debugging or diagnostics.
|
51
|
+
|
52
|
+
Methods:
|
53
|
+
update: Update the configuration with user-defined keyword arguments and raise error on invalid keys.
|
54
|
+
|
55
|
+
Examples:
|
56
|
+
>>> from ultralytics.solutions.config import SolutionConfig
|
57
|
+
>>> cfg = SolutionConfig(model="yolo11n.pt", region=[(0, 0), (100, 0), (100, 100), (0, 100)])
|
58
|
+
>>> cfg.update(show=False, conf=0.3)
|
59
|
+
>>> print(cfg.model)
|
60
|
+
"""
|
61
|
+
|
62
|
+
source: Optional[str] = None
|
63
|
+
model: Optional[str] = None
|
64
|
+
classes: Optional[List[int]] = None
|
65
|
+
show_conf: bool = True
|
66
|
+
show_labels: bool = True
|
67
|
+
region: Optional[List[Tuple[int, int]]] = None
|
68
|
+
colormap: Optional[int] = cv2.COLORMAP_DEEPGREEN
|
69
|
+
show_in: bool = True
|
70
|
+
show_out: bool = True
|
71
|
+
up_angle: float = 145.0
|
72
|
+
down_angle: int = 90
|
73
|
+
kpts: List[int] = field(default_factory=lambda: [6, 8, 10])
|
74
|
+
analytics_type: str = "line"
|
75
|
+
figsize: Optional[Tuple[int, int]] = (12.8, 7.2)
|
76
|
+
blur_ratio: float = 0.5
|
77
|
+
vision_point: Tuple[int, int] = (20, 20)
|
78
|
+
crop_dir: str = "cropped-detections"
|
79
|
+
json_file: str = None
|
80
|
+
line_width: int = 2
|
81
|
+
records: int = 5
|
82
|
+
fps: float = 30.0
|
83
|
+
max_hist: int = 5
|
84
|
+
meter_per_pixel: float = 0.05
|
85
|
+
max_speed: int = 120
|
86
|
+
show: bool = False
|
87
|
+
iou: float = 0.7
|
88
|
+
conf: float = 0.25
|
89
|
+
device: Optional[str] = None
|
90
|
+
max_det: int = 300
|
91
|
+
half: bool = False
|
92
|
+
tracker: str = "botsort.yaml"
|
93
|
+
verbose: bool = True
|
94
|
+
|
95
|
+
def update(self, **kwargs):
|
96
|
+
"""Update configuration parameters with new values provided as keyword arguments."""
|
97
|
+
for key, value in kwargs.items():
|
98
|
+
if hasattr(self, key):
|
99
|
+
setattr(self, key, value)
|
100
|
+
else:
|
101
|
+
raise ValueError(
|
102
|
+
f"❌ {key} is not a valid solution argument, available arguments here: https://docs.ultralytics.com/solutions/#solutions-arguments"
|
103
|
+
)
|
104
|
+
return self
|
ultralytics/solutions/heatmap.py
CHANGED
@@ -45,7 +45,7 @@ class Heatmap(ObjectCounter):
|
|
45
45
|
self.initialize_region()
|
46
46
|
|
47
47
|
# Store colormap
|
48
|
-
self.colormap =
|
48
|
+
self.colormap = self.CFG["colormap"]
|
49
49
|
self.heatmap = None
|
50
50
|
|
51
51
|
def heatmap_effect(self, box):
|
@@ -41,7 +41,7 @@ class ObjectBlurrer(BaseSolution):
|
|
41
41
|
blur_ratio (float): Intensity of the blur effect (0.1-1.0, default=0.5).
|
42
42
|
"""
|
43
43
|
super().__init__(**kwargs)
|
44
|
-
blur_ratio =
|
44
|
+
blur_ratio = self.CFG["blur_ratio"]
|
45
45
|
if blur_ratio < 0.1:
|
46
46
|
LOGGER.warning("blur ratio cannot be less than 0.1, updating it to default value 0.5")
|
47
47
|
blur_ratio = 0.5
|
@@ -40,7 +40,7 @@ class ObjectCropper(BaseSolution):
|
|
40
40
|
"""
|
41
41
|
super().__init__(**kwargs)
|
42
42
|
|
43
|
-
self.crop_dir =
|
43
|
+
self.crop_dir = self.CFG["crop_dir"] # Directory for storing cropped detections
|
44
44
|
if not os.path.exists(self.crop_dir):
|
45
45
|
os.mkdir(self.crop_dir) # Create directory if it does not exist
|
46
46
|
if self.CFG["show"]:
|
@@ -49,7 +49,7 @@ class ObjectCropper(BaseSolution):
|
|
49
49
|
)
|
50
50
|
self.crop_idx = 0 # Initialize counter for total cropped objects
|
51
51
|
self.iou = self.CFG["iou"]
|
52
|
-
self.conf = self.CFG["conf"]
|
52
|
+
self.conf = self.CFG["conf"]
|
53
53
|
|
54
54
|
def process(self, im0):
|
55
55
|
"""
|
@@ -201,9 +201,9 @@ class ParkingManagement(BaseSolution):
|
|
201
201
|
"""Initialize the parking management system with a YOLO model and visualization settings."""
|
202
202
|
super().__init__(**kwargs)
|
203
203
|
|
204
|
-
self.json_file = self.CFG["json_file"] # Load JSON data
|
204
|
+
self.json_file = self.CFG["json_file"] # Load parking regions JSON data
|
205
205
|
if self.json_file is None:
|
206
|
-
LOGGER.warning("
|
206
|
+
LOGGER.warning("json_file argument missing. Parking region details required.")
|
207
207
|
raise ValueError("❌ Json file path can not be empty")
|
208
208
|
|
209
209
|
with open(self.json_file) as f:
|
@@ -143,7 +143,7 @@ class SecurityAlarm(BaseSolution):
|
|
143
143
|
annotator.box_label(box, label=self.names[cls], color=colors(cls, True))
|
144
144
|
|
145
145
|
total_det = len(self.clss)
|
146
|
-
if total_det
|
146
|
+
if total_det >= self.records and not self.email_sent: # Only send email if not sent before
|
147
147
|
self.send_email(im0, total_det)
|
148
148
|
self.email_sent = True
|
149
149
|
|
@@ -7,7 +7,8 @@ import cv2
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
9
|
from ultralytics import YOLO
|
10
|
-
from ultralytics.
|
10
|
+
from ultralytics.solutions.config import SolutionConfig
|
11
|
+
from ultralytics.utils import ASSETS_URL, LOGGER
|
11
12
|
from ultralytics.utils.checks import check_imshow, check_requirements
|
12
13
|
from ultralytics.utils.plotting import Annotator
|
13
14
|
|
@@ -72,15 +73,11 @@ class BaseSolution:
|
|
72
73
|
self.r_s = None
|
73
74
|
|
74
75
|
self.LOGGER = LOGGER # Store logger object to be used in multiple solution classes
|
75
|
-
|
76
|
-
|
77
|
-
DEFAULT_SOL_DICT.update(kwargs)
|
78
|
-
DEFAULT_CFG_DICT.update(kwargs)
|
79
|
-
self.CFG = {**DEFAULT_SOL_DICT, **DEFAULT_CFG_DICT}
|
80
|
-
self.LOGGER.info(f"Ultralytics Solutions: ✅ {DEFAULT_SOL_DICT}")
|
76
|
+
self.CFG = vars(SolutionConfig().update(**kwargs))
|
77
|
+
self.LOGGER.info(f"Ultralytics Solutions: ✅ {self.CFG}")
|
81
78
|
|
82
79
|
self.region = self.CFG["region"] # Store region data for other classes usage
|
83
|
-
self.line_width = self.CFG["line_width"]
|
80
|
+
self.line_width = self.CFG["line_width"]
|
84
81
|
|
85
82
|
# Load Model and store additional information (classes, show_conf, show_label)
|
86
83
|
if self.CFG["model"] is None:
|
@@ -178,7 +175,7 @@ class BaseSolution:
|
|
178
175
|
def initialize_region(self):
|
179
176
|
"""Initialize the counting region and line segment based on configuration settings."""
|
180
177
|
if self.region is None:
|
181
|
-
self.region = [(
|
178
|
+
self.region = [(10, 200), (540, 200), (540, 180), (10, 180)]
|
182
179
|
self.r_s = (
|
183
180
|
self.Polygon(self.region) if len(self.region) >= 3 else self.LineString(self.region)
|
184
181
|
) # region or line
|
@@ -44,15 +44,15 @@ class SpeedEstimator(BaseSolution):
|
|
44
44
|
"""
|
45
45
|
super().__init__(**kwargs)
|
46
46
|
|
47
|
-
self.fps =
|
47
|
+
self.fps = self.CFG["fps"] # assumed video FPS
|
48
48
|
self.frame_count = 0 # global frame count
|
49
49
|
self.trk_frame_ids = {} # Track ID → first frame index
|
50
50
|
self.spd = {} # Final speed per object (km/h), once locked
|
51
51
|
self.trk_hist = {} # Track ID → deque of (time, position)
|
52
52
|
self.locked_ids = set() # Track IDs whose speed has been finalized
|
53
|
-
self.max_hist =
|
54
|
-
self.meter_per_pixel =
|
55
|
-
self.max_speed =
|
53
|
+
self.max_hist = self.CFG["max_hist"] # Required frame history before computing speed
|
54
|
+
self.meter_per_pixel = self.CFG["meter_per_pixel"] # Scene scale, depends on camera details
|
55
|
+
self.max_speed = self.CFG["max_speed"] # max_speed adjustment
|
56
56
|
|
57
57
|
def process(self, im0):
|
58
58
|
"""
|
@@ -42,7 +42,7 @@ class TrackZone(BaseSolution):
|
|
42
42
|
**kwargs (Any): Additional keyword arguments passed to the parent class.
|
43
43
|
"""
|
44
44
|
super().__init__(**kwargs)
|
45
|
-
default_region = [(
|
45
|
+
default_region = [(75, 75), (565, 75), (565, 285), (75, 285)]
|
46
46
|
self.region = cv2.convexHull(np.array(self.region or default_region, dtype=np.int32))
|
47
47
|
|
48
48
|
def process(self, im0):
|
@@ -34,7 +34,7 @@ class VisionEye(BaseSolution):
|
|
34
34
|
"""
|
35
35
|
super().__init__(**kwargs)
|
36
36
|
# Set the vision point where the system will view objects and draw tracks
|
37
|
-
self.vision_point =
|
37
|
+
self.vision_point = self.CFG["vision_point"]
|
38
38
|
|
39
39
|
def process(self, im0):
|
40
40
|
"""
|
ultralytics/trackers/track.py
CHANGED
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
5
5
|
|
6
6
|
import torch
|
7
7
|
|
8
|
-
from ultralytics.utils import
|
8
|
+
from ultralytics.utils import YAML, IterableSimpleNamespace
|
9
9
|
from ultralytics.utils.checks import check_yaml
|
10
10
|
|
11
11
|
from .bot_sort import BOTSORT
|
@@ -39,7 +39,7 @@ def on_predict_start(predictor: object, persist: bool = False) -> None:
|
|
39
39
|
return
|
40
40
|
|
41
41
|
tracker = check_yaml(predictor.args.tracker)
|
42
|
-
cfg = IterableSimpleNamespace(**
|
42
|
+
cfg = IterableSimpleNamespace(**YAML.load(tracker))
|
43
43
|
|
44
44
|
if cfg.tracker_type not in {"bytetrack", "botsort"}:
|
45
45
|
raise AssertionError(f"Only 'bytetrack' and 'botsort' are supported for now, but got '{cfg.tracker_type}'")
|
ultralytics/utils/__init__.py
CHANGED
@@ -12,7 +12,6 @@ import subprocess
|
|
12
12
|
import sys
|
13
13
|
import threading
|
14
14
|
import time
|
15
|
-
import uuid
|
16
15
|
import warnings
|
17
16
|
from pathlib import Path
|
18
17
|
from threading import Lock
|
@@ -21,11 +20,9 @@ from typing import Union
|
|
21
20
|
from urllib.parse import unquote
|
22
21
|
|
23
22
|
import cv2
|
24
|
-
import matplotlib.pyplot as plt
|
25
23
|
import numpy as np
|
26
24
|
import torch
|
27
25
|
import tqdm
|
28
|
-
import yaml
|
29
26
|
|
30
27
|
from ultralytics import __version__
|
31
28
|
from ultralytics.utils.patches import imread, imshow, imwrite, torch_load, torch_save # for patches
|
@@ -41,7 +38,6 @@ ROOT = FILE.parents[1] # YOLO
|
|
41
38
|
ASSETS = ROOT / "assets" # default images
|
42
39
|
ASSETS_URL = "https://github.com/ultralytics/assets/releases/download/v0.0.0" # assets GitHub URL
|
43
40
|
DEFAULT_CFG_PATH = ROOT / "cfg/default.yaml"
|
44
|
-
DEFAULT_SOL_CFG_PATH = ROOT / "cfg/solutions/default.yaml" # Ultralytics solutions yaml path
|
45
41
|
NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLO multiprocessing threads
|
46
42
|
AUTOINSTALL = str(os.getenv("YOLO_AUTOINSTALL", True)).lower() == "true" # global auto-install mode
|
47
43
|
VERBOSE = str(os.getenv("YOLO_VERBOSE", True)).lower() == "true" # global verbose mode
|
@@ -332,6 +328,8 @@ def plt_settings(rcparams=None, backend="Agg"):
|
|
332
328
|
|
333
329
|
def wrapper(*args, **kwargs):
|
334
330
|
"""Sets rc parameters and backend, calls the original function, and restores the settings."""
|
331
|
+
import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
|
332
|
+
|
335
333
|
original_backend = plt.get_backend()
|
336
334
|
switch = backend.lower() != original_backend.lower()
|
337
335
|
if switch:
|
@@ -470,85 +468,142 @@ class ThreadingLocked:
|
|
470
468
|
return decorated
|
471
469
|
|
472
470
|
|
473
|
-
|
471
|
+
class YAML:
|
474
472
|
"""
|
475
|
-
|
473
|
+
YAML utility class for efficient file operations with automatic C-implementation detection.
|
476
474
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
475
|
+
This class provides optimized YAML loading and saving operations using PyYAML's fastest available implementation
|
476
|
+
(C-based when possible). It implements a singleton pattern with lazy initialization, allowing direct class method
|
477
|
+
usage without explicit instantiation. The class handles file path creation, validation, and character encoding
|
478
|
+
issues automatically.
|
481
479
|
|
482
|
-
|
483
|
-
|
480
|
+
The implementation prioritizes performance through:
|
481
|
+
- Automatic C-based loader/dumper selection when available
|
482
|
+
- Singleton pattern to reuse the same instance
|
483
|
+
- Lazy initialization to defer import costs until needed
|
484
|
+
- Fallback mechanisms for handling problematic YAML content
|
485
|
+
|
486
|
+
Attributes:
|
487
|
+
_instance: Internal singleton instance storage.
|
488
|
+
yaml: Reference to the PyYAML module.
|
489
|
+
SafeLoader: Best available YAML loader (CSafeLoader if available).
|
490
|
+
SafeDumper: Best available YAML dumper (CSafeDumper if available).
|
491
|
+
|
492
|
+
Examples:
|
493
|
+
>>> data = YAML.load("config.yaml")
|
494
|
+
>>> data["new_value"] = 123
|
495
|
+
>>> YAML.save("updated_config.yaml", data)
|
496
|
+
>>> YAML.print(data)
|
484
497
|
"""
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
498
|
+
|
499
|
+
_instance = None
|
500
|
+
|
501
|
+
@classmethod
|
502
|
+
def _get_instance(cls):
|
503
|
+
"""Initialize singleton instance on first use."""
|
504
|
+
if cls._instance is None:
|
505
|
+
cls._instance = cls()
|
506
|
+
return cls._instance
|
507
|
+
|
508
|
+
def __init__(self):
|
509
|
+
"""Initialize with optimal YAML implementation (C-based when available)."""
|
510
|
+
import yaml
|
511
|
+
|
512
|
+
self.yaml = yaml
|
513
|
+
# Use C-based implementation if available for better performance
|
514
|
+
try:
|
515
|
+
self.SafeLoader = yaml.CSafeLoader
|
516
|
+
self.SafeDumper = yaml.CSafeDumper
|
517
|
+
except (AttributeError, ImportError):
|
518
|
+
self.SafeLoader = yaml.SafeLoader
|
519
|
+
self.SafeDumper = yaml.SafeDumper
|
520
|
+
|
521
|
+
@classmethod
|
522
|
+
def save(cls, file="data.yaml", data=None, header=""):
|
523
|
+
"""
|
524
|
+
Save Python object as YAML file.
|
525
|
+
|
526
|
+
Args:
|
527
|
+
file (str | Path): Path to save YAML file.
|
528
|
+
data (dict | None): Dict or compatible object to save.
|
529
|
+
header (str): Optional string to add at file beginning.
|
530
|
+
"""
|
531
|
+
instance = cls._get_instance()
|
532
|
+
if data is None:
|
533
|
+
data = {}
|
534
|
+
|
535
|
+
# Create parent directories if needed
|
536
|
+
file = Path(file)
|
490
537
|
file.parent.mkdir(parents=True, exist_ok=True)
|
491
538
|
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
539
|
+
# Convert non-serializable objects to strings
|
540
|
+
valid_types = int, float, str, bool, list, tuple, dict, type(None)
|
541
|
+
for k, v in data.items():
|
542
|
+
if not isinstance(v, valid_types):
|
543
|
+
data[k] = str(v)
|
497
544
|
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
545
|
+
# Write YAML file
|
546
|
+
with open(file, "w", errors="ignore", encoding="utf-8") as f:
|
547
|
+
if header:
|
548
|
+
f.write(header)
|
549
|
+
instance.yaml.dump(data, f, sort_keys=False, allow_unicode=True, Dumper=instance.SafeDumper)
|
503
550
|
|
551
|
+
@classmethod
|
552
|
+
def load(cls, file="data.yaml", append_filename=False):
|
553
|
+
"""
|
554
|
+
Load YAML file to Python object with robust error handling.
|
504
555
|
|
505
|
-
|
506
|
-
|
507
|
-
|
556
|
+
Args:
|
557
|
+
file (str | Path): Path to YAML file.
|
558
|
+
append_filename (bool): Whether to add filename to returned dict.
|
508
559
|
|
509
|
-
|
510
|
-
|
511
|
-
|
560
|
+
Returns:
|
561
|
+
(dict): Loaded YAML content.
|
562
|
+
"""
|
563
|
+
instance = cls._get_instance()
|
564
|
+
assert str(file).endswith((".yaml", ".yml")), f"Not a YAML file: {file}"
|
512
565
|
|
513
|
-
|
514
|
-
(
|
515
|
-
|
516
|
-
assert Path(file).suffix in {".yaml", ".yml"}, f"Attempting to load non-YAML file {file} with yaml_load()"
|
517
|
-
with open(file, errors="ignore", encoding="utf-8") as f:
|
518
|
-
s = f.read() # string
|
566
|
+
# Read file content
|
567
|
+
with open(file, errors="ignore", encoding="utf-8") as f:
|
568
|
+
s = f.read()
|
519
569
|
|
520
|
-
#
|
521
|
-
|
570
|
+
# Try loading YAML with fallback for problematic characters
|
571
|
+
try:
|
572
|
+
data = instance.yaml.load(s, Loader=instance.SafeLoader) or {}
|
573
|
+
except Exception:
|
574
|
+
# Remove problematic characters and retry
|
522
575
|
s = re.sub(r"[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]+", "", s)
|
576
|
+
data = instance.yaml.load(s, Loader=instance.SafeLoader) or {}
|
577
|
+
|
578
|
+
# Check for accidental user-error None strings (should be 'null' in YAML)
|
579
|
+
if "None" in data.values():
|
580
|
+
data = {k: None if v == "None" else v for k, v in data.items()}
|
523
581
|
|
524
|
-
# Add YAML filename to dict and return
|
525
|
-
data = yaml.safe_load(s) or {} # always return a dict (yaml.safe_load() may return None for empty files)
|
526
582
|
if append_filename:
|
527
583
|
data["yaml_file"] = str(file)
|
528
584
|
return data
|
529
585
|
|
586
|
+
@classmethod
|
587
|
+
def print(cls, yaml_file):
|
588
|
+
"""
|
589
|
+
Pretty print YAML file or object to console.
|
530
590
|
|
531
|
-
|
532
|
-
|
533
|
-
|
591
|
+
Args:
|
592
|
+
yaml_file (str | Path | dict): Path to YAML file or dict to print.
|
593
|
+
"""
|
594
|
+
instance = cls._get_instance()
|
534
595
|
|
535
|
-
|
536
|
-
|
596
|
+
# Load file if path provided
|
597
|
+
yaml_dict = cls.load(yaml_file) if isinstance(yaml_file, (str, Path)) else yaml_file
|
537
598
|
|
538
|
-
|
539
|
-
(
|
540
|
-
|
541
|
-
|
542
|
-
dump = yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True, width=float("inf"))
|
543
|
-
LOGGER.info(f"Printing '{colorstr('bold', 'black', yaml_file)}'\n\n{dump}")
|
599
|
+
# Use -1 for unlimited width in C implementation
|
600
|
+
dump = instance.yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True, width=-1, Dumper=instance.SafeDumper)
|
601
|
+
|
602
|
+
LOGGER.info(f"Printing '{colorstr('bold', 'black', yaml_file)}'\n\n{dump}")
|
544
603
|
|
545
604
|
|
546
605
|
# Default configuration
|
547
|
-
DEFAULT_CFG_DICT =
|
548
|
-
DEFAULT_SOL_DICT = yaml_load(DEFAULT_SOL_CFG_PATH) # Ultralytics solutions configuration
|
549
|
-
for k, v in DEFAULT_CFG_DICT.items():
|
550
|
-
if isinstance(v, str) and v.lower() == "none":
|
551
|
-
DEFAULT_CFG_DICT[k] = None
|
606
|
+
DEFAULT_CFG_DICT = YAML.load(DEFAULT_CFG_PATH)
|
552
607
|
DEFAULT_CFG_KEYS = DEFAULT_CFG_DICT.keys()
|
553
608
|
DEFAULT_CFG = IterableSimpleNamespace(**DEFAULT_CFG_DICT)
|
554
609
|
|
@@ -1226,6 +1281,7 @@ class SettingsManager(JSONDict):
|
|
1226
1281
|
def __init__(self, file=SETTINGS_FILE, version="0.0.6"):
|
1227
1282
|
"""Initializes the SettingsManager with default settings and loads user settings."""
|
1228
1283
|
import hashlib
|
1284
|
+
import uuid
|
1229
1285
|
|
1230
1286
|
from ultralytics.utils.torch_utils import torch_distributed_zero_first
|
1231
1287
|
|
ultralytics/utils/benchmarks.py
CHANGED
@@ -36,12 +36,11 @@ from pathlib import Path
|
|
36
36
|
|
37
37
|
import numpy as np
|
38
38
|
import torch.cuda
|
39
|
-
import yaml
|
40
39
|
|
41
40
|
from ultralytics import YOLO, YOLOWorld
|
42
41
|
from ultralytics.cfg import TASK2DATA, TASK2METRIC
|
43
42
|
from ultralytics.engine.exporter import export_formats
|
44
|
-
from ultralytics.utils import ARM64, ASSETS, LINUX, LOGGER, MACOS, TQDM, WEIGHTS_DIR
|
43
|
+
from ultralytics.utils import ARM64, ASSETS, LINUX, LOGGER, MACOS, TQDM, WEIGHTS_DIR, YAML
|
45
44
|
from ultralytics.utils.checks import IS_PYTHON_3_13, check_imgsz, check_requirements, check_yolo, is_rockchip
|
46
45
|
from ultralytics.utils.downloads import safe_download
|
47
46
|
from ultralytics.utils.files import file_size
|
@@ -283,12 +282,10 @@ class RF100Benchmark:
|
|
283
282
|
@staticmethod
|
284
283
|
def fix_yaml(path):
|
285
284
|
"""Fix the train and validation paths in a given YAML file."""
|
286
|
-
|
287
|
-
yaml_data = yaml.safe_load(file)
|
285
|
+
yaml_data = YAML.load(path)
|
288
286
|
yaml_data["train"] = "train/images"
|
289
287
|
yaml_data["val"] = "valid/images"
|
290
|
-
|
291
|
-
yaml.safe_dump(yaml_data, file)
|
288
|
+
YAML.dump(yaml_data, path)
|
292
289
|
|
293
290
|
def evaluate(self, yaml_path, val_log_file, eval_log_file, list_ind):
|
294
291
|
"""
|
@@ -309,8 +306,7 @@ class RF100Benchmark:
|
|
309
306
|
>>> benchmark.evaluate("path/to/data.yaml", "path/to/val_log.txt", "path/to/eval_log.txt", 0)
|
310
307
|
"""
|
311
308
|
skip_symbols = ["🚀", "⚠️", "💡", "❌"]
|
312
|
-
|
313
|
-
class_names = yaml.safe_load(stream)["names"]
|
309
|
+
class_names = YAML.load(yaml_path)["names"]
|
314
310
|
with open(val_log_file, encoding="utf-8") as f:
|
315
311
|
lines = f.readlines()
|
316
312
|
eval_lines = []
|
ultralytics/utils/checks.py
CHANGED
@@ -16,7 +16,6 @@ from typing import Optional
|
|
16
16
|
|
17
17
|
import cv2
|
18
18
|
import numpy as np
|
19
|
-
import requests
|
20
19
|
import torch
|
21
20
|
|
22
21
|
from ultralytics.utils import (
|
@@ -261,6 +260,8 @@ def check_latest_pypi_version(package_name="ultralytics"):
|
|
261
260
|
Returns:
|
262
261
|
(str): The latest version of the package.
|
263
262
|
"""
|
263
|
+
import requests # slow import
|
264
|
+
|
264
265
|
try:
|
265
266
|
requests.packages.urllib3.disable_warnings() # Disable the InsecureRequestWarning
|
266
267
|
response = requests.get(f"https://pypi.org/pypi/{package_name}/json", timeout=3)
|
@@ -304,7 +305,7 @@ def check_font(font="Arial.ttf"):
|
|
304
305
|
Returns:
|
305
306
|
(Path): Resolved font file path.
|
306
307
|
"""
|
307
|
-
from matplotlib import font_manager
|
308
|
+
from matplotlib import font_manager # scope for faster 'import ultralytics'
|
308
309
|
|
309
310
|
# Check USER_CONFIG_DIR
|
310
311
|
name = Path(font).name
|
@@ -453,7 +454,7 @@ def check_suffix(file="yolo11n.pt", suffix=".pt", msg=""):
|
|
453
454
|
"""
|
454
455
|
if file and suffix:
|
455
456
|
if isinstance(suffix, str):
|
456
|
-
suffix =
|
457
|
+
suffix = {suffix}
|
457
458
|
for f in file if isinstance(file, (list, tuple)) else [file]:
|
458
459
|
s = Path(f).suffix.lower().strip() # file suffix
|
459
460
|
if len(s):
|