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.
Files changed (45) hide show
  1. tests/test_python.py +5 -8
  2. ultralytics/__init__.py +1 -1
  3. ultralytics/cfg/__init__.py +7 -14
  4. ultralytics/cfg/default.yaml +2 -2
  5. ultralytics/data/base.py +1 -2
  6. ultralytics/data/loaders.py +3 -4
  7. ultralytics/data/utils.py +8 -9
  8. ultralytics/engine/exporter.py +7 -7
  9. ultralytics/engine/model.py +7 -4
  10. ultralytics/engine/trainer.py +2 -2
  11. ultralytics/engine/tuner.py +3 -3
  12. ultralytics/hub/session.py +1 -1
  13. ultralytics/models/sam/model.py +2 -1
  14. ultralytics/models/sam/modules/tiny_encoder.py +2 -3
  15. ultralytics/models/sam/predict.py +4 -1
  16. ultralytics/models/yolo/model.py +3 -3
  17. ultralytics/nn/autobackend.py +4 -4
  18. ultralytics/nn/tasks.py +7 -7
  19. ultralytics/solutions/analytics.py +9 -8
  20. ultralytics/solutions/config.py +104 -0
  21. ultralytics/solutions/heatmap.py +1 -1
  22. ultralytics/solutions/object_blurrer.py +1 -1
  23. ultralytics/solutions/object_cropper.py +2 -2
  24. ultralytics/solutions/parking_management.py +2 -2
  25. ultralytics/solutions/security_alarm.py +1 -1
  26. ultralytics/solutions/solutions.py +6 -9
  27. ultralytics/solutions/speed_estimation.py +4 -4
  28. ultralytics/solutions/trackzone.py +1 -1
  29. ultralytics/solutions/vision_eye.py +1 -1
  30. ultralytics/trackers/track.py +2 -2
  31. ultralytics/utils/__init__.py +115 -59
  32. ultralytics/utils/benchmarks.py +4 -8
  33. ultralytics/utils/checks.py +4 -3
  34. ultralytics/utils/dist.py +2 -1
  35. ultralytics/utils/downloads.py +6 -1
  36. ultralytics/utils/metrics.py +6 -2
  37. ultralytics/utils/plotting.py +11 -5
  38. ultralytics/utils/torch_utils.py +10 -5
  39. {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/METADATA +1 -1
  40. {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/RECORD +44 -44
  41. {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/WHEEL +1 -1
  42. ultralytics/cfg/solutions/default.yaml +0 -24
  43. {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/entry_points.txt +0 -0
  44. {ultralytics-8.3.123.dist-info → ultralytics-8.3.125.dist-info}/licenses/LICENSE +0 -0
  45. {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 (FigureCanvas): Canvas for rendering the chart.
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
- self.type = self.CFG["analytics_type"] # extract type of analytics
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) # Set output image size 1280 * 720
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 = FigureCanvas(self.fig) # Set common axis properties
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 = FigureCanvas(self.fig) # Set common axis properties
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
@@ -45,7 +45,7 @@ class Heatmap(ObjectCounter):
45
45
  self.initialize_region()
46
46
 
47
47
  # Store colormap
48
- self.colormap = cv2.COLORMAP_PARULA if self.CFG["colormap"] is None else self.CFG["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 = kwargs.get("blur_ratio", 0.5)
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 = kwargs.get("crop_dir", "cropped-detections") # Directory for storing cropped detections
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"] if self.CFG["conf"] is not None else 0.25
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("json_file argument missing. Parking region details required.")
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 > self.records and not self.email_sent: # Only send email if not sent before
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.utils import ASSETS_URL, DEFAULT_CFG_DICT, DEFAULT_SOL_DICT, LOGGER
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
- # Load config and update with args
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"] if self.CFG["line_width"] not in (None, 0) else 2 # Store 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 = [(20, 400), (1080, 400), (1080, 360), (20, 360)]
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 = kwargs.get("fps", 30) # assumed video 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 = kwargs.get("max_hist", 5) # Required frame history before computing speed
54
- self.meter_per_pixel = kwargs.get("meter_per_pixel", 0.05) # Scene scale, depends on camera details
55
- self.max_speed = kwargs.get("max_speed", 120) # max_speed adjustment
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 = [(150, 150), (1130, 150), (1130, 570), (150, 570)]
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 = kwargs.get("vision_point", (30, 30))
37
+ self.vision_point = self.CFG["vision_point"]
38
38
 
39
39
  def process(self, im0):
40
40
  """
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
 
6
6
  import torch
7
7
 
8
- from ultralytics.utils import IterableSimpleNamespace, yaml_load
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(**yaml_load(tracker))
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}'")
@@ -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
- def yaml_save(file="data.yaml", data=None, header=""):
471
+ class YAML:
474
472
  """
475
- Save YAML data to a file.
473
+ YAML utility class for efficient file operations with automatic C-implementation detection.
476
474
 
477
- Args:
478
- file (str, optional): File name. Default is 'data.yaml'.
479
- data (dict): Data to save in YAML format.
480
- header (str, optional): YAML header to add.
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
- Returns:
483
- (None): Data is saved to the specified file.
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
- if data is None:
486
- data = {}
487
- file = Path(file)
488
- if not file.parent.exists():
489
- # Create parent directories if they don't exist
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
- # Convert Path objects to strings
493
- valid_types = int, float, str, bool, list, tuple, dict, type(None)
494
- for k, v in data.items():
495
- if not isinstance(v, valid_types):
496
- data[k] = str(v)
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
- # Dump data to file in YAML format
499
- with open(file, "w", errors="ignore", encoding="utf-8") as f:
500
- if header:
501
- f.write(header)
502
- yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True)
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
- def yaml_load(file="data.yaml", append_filename=False):
506
- """
507
- Load YAML data from a file.
556
+ Args:
557
+ file (str | Path): Path to YAML file.
558
+ append_filename (bool): Whether to add filename to returned dict.
508
559
 
509
- Args:
510
- file (str, optional): File name. Default is 'data.yaml'.
511
- append_filename (bool): Add the YAML filename to the YAML dictionary. Default is False.
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
- Returns:
514
- (dict): YAML data and file name.
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
- # Remove special characters
521
- if not s.isprintable():
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
- def yaml_print(yaml_file: Union[str, Path, dict]) -> None:
532
- """
533
- Pretty prints a YAML file or a YAML-formatted dictionary.
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
- Args:
536
- yaml_file: The file path of the YAML file or a YAML-formatted dictionary.
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
- Returns:
539
- (None)
540
- """
541
- yaml_dict = yaml_load(yaml_file) if isinstance(yaml_file, (str, Path)) else yaml_file
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 = yaml_load(DEFAULT_CFG_PATH)
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
 
@@ -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
- with open(path, encoding="utf-8") as file:
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
- with open(path, "w", encoding="utf-8") as file:
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
- with open(yaml_path, encoding="utf-8") as stream:
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 = []
@@ -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 = (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):