ultralytics 8.3.124__py3-none-any.whl → 8.3.126__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.
@@ -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,6 +48,10 @@ class Analytics(BaseSolution):
51
48
  """Initialize Analytics class with various chart types for visual data representation."""
52
49
  super().__init__(**kwargs)
53
50
 
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
+
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"
@@ -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
 
@@ -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}'")
@@ -20,11 +20,9 @@ from typing import Union
20
20
  from urllib.parse import unquote
21
21
 
22
22
  import cv2
23
- import matplotlib.pyplot as plt
24
23
  import numpy as np
25
24
  import torch
26
25
  import tqdm
27
- import yaml
28
26
 
29
27
  from ultralytics import __version__
30
28
  from ultralytics.utils.patches import imread, imshow, imwrite, torch_load, torch_save # for patches
@@ -184,6 +182,10 @@ class TQDM(rich.tqdm if TQDM_RICH else tqdm.tqdm):
184
182
  kwargs.setdefault("bar_format", TQDM_BAR_FORMAT) # override default value if passed
185
183
  super().__init__(*args, **kwargs)
186
184
 
185
+ def __iter__(self):
186
+ """Return self as iterator to satisfy Iterable interface."""
187
+ return super().__iter__()
188
+
187
189
 
188
190
  class SimpleClass:
189
191
  """
@@ -330,6 +332,8 @@ def plt_settings(rcparams=None, backend="Agg"):
330
332
 
331
333
  def wrapper(*args, **kwargs):
332
334
  """Sets rc parameters and backend, calls the original function, and restores the settings."""
335
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
336
+
333
337
  original_backend = plt.get_backend()
334
338
  switch = backend.lower() != original_backend.lower()
335
339
  if switch:
@@ -468,84 +472,142 @@ class ThreadingLocked:
468
472
  return decorated
469
473
 
470
474
 
471
- def yaml_save(file="data.yaml", data=None, header=""):
475
+ class YAML:
472
476
  """
473
- Save YAML data to a file.
477
+ YAML utility class for efficient file operations with automatic C-implementation detection.
474
478
 
475
- Args:
476
- file (str, optional): File name. Default is 'data.yaml'.
477
- data (dict): Data to save in YAML format.
478
- header (str, optional): YAML header to add.
479
+ This class provides optimized YAML loading and saving operations using PyYAML's fastest available implementation
480
+ (C-based when possible). It implements a singleton pattern with lazy initialization, allowing direct class method
481
+ usage without explicit instantiation. The class handles file path creation, validation, and character encoding
482
+ issues automatically.
479
483
 
480
- Returns:
481
- (None): Data is saved to the specified file.
484
+ The implementation prioritizes performance through:
485
+ - Automatic C-based loader/dumper selection when available
486
+ - Singleton pattern to reuse the same instance
487
+ - Lazy initialization to defer import costs until needed
488
+ - Fallback mechanisms for handling problematic YAML content
489
+
490
+ Attributes:
491
+ _instance: Internal singleton instance storage.
492
+ yaml: Reference to the PyYAML module.
493
+ SafeLoader: Best available YAML loader (CSafeLoader if available).
494
+ SafeDumper: Best available YAML dumper (CSafeDumper if available).
495
+
496
+ Examples:
497
+ >>> data = YAML.load("config.yaml")
498
+ >>> data["new_value"] = 123
499
+ >>> YAML.save("updated_config.yaml", data)
500
+ >>> YAML.print(data)
482
501
  """
483
- if data is None:
484
- data = {}
485
- file = Path(file)
486
- if not file.parent.exists():
487
- # Create parent directories if they don't exist
502
+
503
+ _instance = None
504
+
505
+ @classmethod
506
+ def _get_instance(cls):
507
+ """Initialize singleton instance on first use."""
508
+ if cls._instance is None:
509
+ cls._instance = cls()
510
+ return cls._instance
511
+
512
+ def __init__(self):
513
+ """Initialize with optimal YAML implementation (C-based when available)."""
514
+ import yaml
515
+
516
+ self.yaml = yaml
517
+ # Use C-based implementation if available for better performance
518
+ try:
519
+ self.SafeLoader = yaml.CSafeLoader
520
+ self.SafeDumper = yaml.CSafeDumper
521
+ except (AttributeError, ImportError):
522
+ self.SafeLoader = yaml.SafeLoader
523
+ self.SafeDumper = yaml.SafeDumper
524
+
525
+ @classmethod
526
+ def save(cls, file="data.yaml", data=None, header=""):
527
+ """
528
+ Save Python object as YAML file.
529
+
530
+ Args:
531
+ file (str | Path): Path to save YAML file.
532
+ data (dict | None): Dict or compatible object to save.
533
+ header (str): Optional string to add at file beginning.
534
+ """
535
+ instance = cls._get_instance()
536
+ if data is None:
537
+ data = {}
538
+
539
+ # Create parent directories if needed
540
+ file = Path(file)
488
541
  file.parent.mkdir(parents=True, exist_ok=True)
489
542
 
490
- # Convert Path objects to strings
491
- valid_types = int, float, str, bool, list, tuple, dict, type(None)
492
- for k, v in data.items():
493
- if not isinstance(v, valid_types):
494
- data[k] = str(v)
543
+ # Convert non-serializable objects to strings
544
+ valid_types = int, float, str, bool, list, tuple, dict, type(None)
545
+ for k, v in data.items():
546
+ if not isinstance(v, valid_types):
547
+ data[k] = str(v)
495
548
 
496
- # Dump data to file in YAML format
497
- with open(file, "w", errors="ignore", encoding="utf-8") as f:
498
- if header:
499
- f.write(header)
500
- yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True)
549
+ # Write YAML file
550
+ with open(file, "w", errors="ignore", encoding="utf-8") as f:
551
+ if header:
552
+ f.write(header)
553
+ instance.yaml.dump(data, f, sort_keys=False, allow_unicode=True, Dumper=instance.SafeDumper)
501
554
 
555
+ @classmethod
556
+ def load(cls, file="data.yaml", append_filename=False):
557
+ """
558
+ Load YAML file to Python object with robust error handling.
502
559
 
503
- def yaml_load(file="data.yaml", append_filename=False):
504
- """
505
- Load YAML data from a file.
560
+ Args:
561
+ file (str | Path): Path to YAML file.
562
+ append_filename (bool): Whether to add filename to returned dict.
506
563
 
507
- Args:
508
- file (str, optional): File name. Default is 'data.yaml'.
509
- append_filename (bool): Add the YAML filename to the YAML dictionary. Default is False.
564
+ Returns:
565
+ (dict): Loaded YAML content.
566
+ """
567
+ instance = cls._get_instance()
568
+ assert str(file).endswith((".yaml", ".yml")), f"Not a YAML file: {file}"
510
569
 
511
- Returns:
512
- (dict): YAML data and file name.
513
- """
514
- assert Path(file).suffix in {".yaml", ".yml"}, f"Attempting to load non-YAML file {file} with yaml_load()"
515
- with open(file, errors="ignore", encoding="utf-8") as f:
516
- s = f.read() # string
570
+ # Read file content
571
+ with open(file, errors="ignore", encoding="utf-8") as f:
572
+ s = f.read()
517
573
 
518
- # Remove special characters
519
- if not s.isprintable():
574
+ # Try loading YAML with fallback for problematic characters
575
+ try:
576
+ data = instance.yaml.load(s, Loader=instance.SafeLoader) or {}
577
+ except Exception:
578
+ # Remove problematic characters and retry
520
579
  s = re.sub(r"[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\uD7FF\uE000-\uFFFD\U00010000-\U0010ffff]+", "", s)
580
+ data = instance.yaml.load(s, Loader=instance.SafeLoader) or {}
581
+
582
+ # Check for accidental user-error None strings (should be 'null' in YAML)
583
+ if "None" in data.values():
584
+ data = {k: None if v == "None" else v for k, v in data.items()}
521
585
 
522
- # Add YAML filename to dict and return
523
- data = yaml.safe_load(s) or {} # always return a dict (yaml.safe_load() may return None for empty files)
524
586
  if append_filename:
525
587
  data["yaml_file"] = str(file)
526
588
  return data
527
589
 
590
+ @classmethod
591
+ def print(cls, yaml_file):
592
+ """
593
+ Pretty print YAML file or object to console.
528
594
 
529
- def yaml_print(yaml_file: Union[str, Path, dict]) -> None:
530
- """
531
- Pretty prints a YAML file or a YAML-formatted dictionary.
595
+ Args:
596
+ yaml_file (str | Path | dict): Path to YAML file or dict to print.
597
+ """
598
+ instance = cls._get_instance()
532
599
 
533
- Args:
534
- yaml_file: The file path of the YAML file or a YAML-formatted dictionary.
600
+ # Load file if path provided
601
+ yaml_dict = cls.load(yaml_file) if isinstance(yaml_file, (str, Path)) else yaml_file
535
602
 
536
- Returns:
537
- (None)
538
- """
539
- yaml_dict = yaml_load(yaml_file) if isinstance(yaml_file, (str, Path)) else yaml_file
540
- dump = yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True, width=float("inf"))
541
- LOGGER.info(f"Printing '{colorstr('bold', 'black', yaml_file)}'\n\n{dump}")
603
+ # Use -1 for unlimited width in C implementation
604
+ dump = instance.yaml.dump(yaml_dict, sort_keys=False, allow_unicode=True, width=-1, Dumper=instance.SafeDumper)
605
+
606
+ LOGGER.info(f"Printing '{colorstr('bold', 'black', yaml_file)}'\n\n{dump}")
542
607
 
543
608
 
544
609
  # Default configuration
545
- DEFAULT_CFG_DICT = yaml_load(DEFAULT_CFG_PATH)
546
- for k, v in DEFAULT_CFG_DICT.items():
547
- if isinstance(v, str) and v.lower() == "none":
548
- DEFAULT_CFG_DICT[k] = None
610
+ DEFAULT_CFG_DICT = YAML.load(DEFAULT_CFG_PATH)
549
611
  DEFAULT_CFG_KEYS = DEFAULT_CFG_DICT.keys()
550
612
  DEFAULT_CFG = IterableSimpleNamespace(**DEFAULT_CFG_DICT)
551
613
 
@@ -0,0 +1,175 @@
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+
3
+
4
+ from ultralytics.utils import LOGGER
5
+ from ultralytics.utils.checks import check_requirements
6
+
7
+
8
+ class GPUInfo:
9
+ """
10
+ Manages NVIDIA GPU information via pynvml with robust error handling.
11
+
12
+ Provides methods to query detailed GPU statistics (utilization, memory, temp, power) and select the most idle
13
+ GPUs based on configurable criteria. It safely handles the absence or initialization failure of the pynvml
14
+ library by logging warnings and disabling related features, preventing application crashes.
15
+
16
+ Includes fallback logic using `torch.cuda` for basic device counting if NVML is unavailable during GPU
17
+ selection. Manages NVML initialization and shutdown internally.
18
+
19
+ Attributes:
20
+ pynvml (module | None): The `pynvml` module if successfully imported and initialized, otherwise `None`.
21
+ nvml_available (bool): Indicates if `pynvml` is ready for use. True if import and `nvmlInit()` succeeded,
22
+ False otherwise.
23
+ gpu_stats (list[dict]): A list of dictionaries, each holding stats for one GPU. Populated on initialization
24
+ and by `refresh_stats()`. Keys include: 'index', 'name', 'utilization' (%), 'memory_used' (MiB),
25
+ 'memory_total' (MiB), 'memory_free' (MiB), 'temperature' (C), 'power_draw' (W),
26
+ 'power_limit' (W or 'N/A'). Empty if NVML is unavailable or queries fail.
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initializes GPUInfo, attempting to import and initialize pynvml."""
31
+ self.pynvml = None
32
+ self.nvml_available = False
33
+ self.gpu_stats = []
34
+
35
+ try:
36
+ check_requirements("pynvml>=12.0.0")
37
+ self.pynvml = __import__("pynvml")
38
+ self.pynvml.nvmlInit()
39
+ self.nvml_available = True
40
+ self.refresh_stats()
41
+ except Exception as e:
42
+ LOGGER.warning(f"Failed to initialize pynvml, GPU stats disabled: {e}")
43
+
44
+ def __del__(self):
45
+ """Ensures NVML is shut down when the object is garbage collected."""
46
+ self.shutdown()
47
+
48
+ def shutdown(self):
49
+ """Shuts down NVML if it was initialized."""
50
+ if self.nvml_available and self.pynvml:
51
+ try:
52
+ self.pynvml.nvmlShutdown()
53
+ except Exception:
54
+ pass
55
+ self.nvml_available = False
56
+
57
+ def refresh_stats(self):
58
+ """Refreshes the internal gpu_stats list by querying NVML."""
59
+ self.gpu_stats = []
60
+ if not self.nvml_available or not self.pynvml:
61
+ return
62
+
63
+ try:
64
+ device_count = self.pynvml.nvmlDeviceGetCount()
65
+ for i in range(device_count):
66
+ self.gpu_stats.append(self._get_device_stats(i))
67
+ except Exception as e:
68
+ LOGGER.warning(f"Error during device query: {e}")
69
+ self.gpu_stats = []
70
+
71
+ def _get_device_stats(self, index):
72
+ """Gets stats for a single GPU device."""
73
+ handle = self.pynvml.nvmlDeviceGetHandleByIndex(index)
74
+ memory = self.pynvml.nvmlDeviceGetMemoryInfo(handle)
75
+ util = self.pynvml.nvmlDeviceGetUtilizationRates(handle)
76
+
77
+ def safe_get(func, *args, default=-1, divisor=1):
78
+ try:
79
+ val = func(*args)
80
+ return val // divisor if divisor != 1 and isinstance(val, (int, float)) else val
81
+ except Exception:
82
+ return default
83
+
84
+ temp_type = getattr(self.pynvml, "NVML_TEMPERATURE_GPU", -1)
85
+
86
+ return {
87
+ "index": index,
88
+ "name": self.pynvml.nvmlDeviceGetName(handle),
89
+ "utilization": util.gpu if util else -1,
90
+ "memory_used": memory.used >> 20 if memory else -1,
91
+ "memory_total": memory.total >> 20 if memory else -1,
92
+ "memory_free": memory.free >> 20 if memory else -1,
93
+ "temperature": safe_get(self.pynvml.nvmlDeviceGetTemperature, handle, temp_type),
94
+ "power_draw": safe_get(self.pynvml.nvmlDeviceGetPowerUsage, handle, divisor=1000),
95
+ "power_limit": safe_get(self.pynvml.nvmlDeviceGetEnforcedPowerLimit, handle, divisor=1000),
96
+ }
97
+
98
+ def print_status(self):
99
+ """Prints GPU status in a compact table format using current stats."""
100
+ self.refresh_stats()
101
+ if not self.gpu_stats:
102
+ LOGGER.warning("No GPU stats available.")
103
+ return
104
+
105
+ stats = self.gpu_stats
106
+ name_len = max(len(gpu.get("name", "N/A")) for gpu in stats)
107
+ hdr = f"{'Idx':<3} {'Name':<{name_len}} {'Util':>6} {'Mem (MiB)':>15} {'Temp':>5} {'Pwr (W)':>10}"
108
+ LOGGER.info(f"\n--- GPU Status ---\n{hdr}\n{'-' * len(hdr)}")
109
+
110
+ for gpu in stats:
111
+ u = f"{gpu['utilization']:>5}%" if gpu["utilization"] >= 0 else " N/A "
112
+ m = f"{gpu['memory_used']:>6}/{gpu['memory_total']:<6}" if gpu["memory_used"] >= 0 else " N/A / N/A "
113
+ t = f"{gpu['temperature']}C" if gpu["temperature"] >= 0 else " N/A "
114
+ p = f"{gpu['power_draw']:>3}/{gpu['power_limit']:<3}" if gpu["power_draw"] >= 0 else " N/A "
115
+
116
+ LOGGER.info(f"{gpu.get('index'):<3d} {gpu.get('name', 'N/A'):<{name_len}} {u:>6} {m:>15} {t:>5} {p:>10}")
117
+
118
+ LOGGER.info(f"{'-' * len(hdr)}\n")
119
+
120
+ def select_idle_gpu(self, count=1, min_memory_mb=0):
121
+ """
122
+ Selects the 'count' most idle GPUs based on utilization and free memory.
123
+
124
+ Args:
125
+ count (int): The number of idle GPUs to select. Defaults to 1.
126
+ min_memory_mb (int): Minimum free memory required (MiB). Defaults to 0.
127
+
128
+ Returns:
129
+ (list[int]): Indices of the selected GPUs, sorted by idleness.
130
+
131
+ Notes:
132
+ Returns fewer than 'count' if not enough qualify or exist.
133
+ Returns basic CUDA indices if NVML fails. Empty list if no GPUs found.
134
+ """
135
+ LOGGER.info(f"Searching for {count} idle GPUs with >= {min_memory_mb} MiB free memory...")
136
+
137
+ if count <= 0:
138
+ return []
139
+
140
+ self.refresh_stats()
141
+ if not self.gpu_stats:
142
+ LOGGER.warning("NVML stats unavailable.")
143
+ return []
144
+
145
+ # Filter and sort eligible GPUs
146
+ eligible_gpus = [
147
+ gpu
148
+ for gpu in self.gpu_stats
149
+ if gpu.get("memory_free", -1) >= min_memory_mb and gpu.get("utilization", -1) != -1
150
+ ]
151
+ eligible_gpus.sort(key=lambda x: (x.get("utilization", 101), -x.get("memory_free", 0)))
152
+
153
+ # Select top 'count' indices
154
+ selected = [gpu["index"] for gpu in eligible_gpus[:count]]
155
+
156
+ if selected:
157
+ LOGGER.info(f"Selected idle CUDA devices {selected}")
158
+ else:
159
+ LOGGER.warning(f"No GPUs met criteria (Util != -1, Free Mem >= {min_memory_mb} MiB).")
160
+
161
+ return selected
162
+
163
+
164
+ if __name__ == "__main__":
165
+ required_free_mem = 2048 # Require 2GB free VRAM
166
+ num_gpus_to_select = 1
167
+
168
+ gpu_info = GPUInfo()
169
+ gpu_info.print_status()
170
+
171
+ selected = gpu_info.select_idle_gpu(count=num_gpus_to_select, min_memory_mb=required_free_mem)
172
+ if selected:
173
+ print(f"\n==> Using selected GPU indices: {selected}")
174
+ devices = [f"cuda:{idx}" for idx in selected]
175
+ print(f" Target devices: {devices}")
@@ -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 = []
@@ -403,7 +399,7 @@ class ProfileModels:
403
399
  imgsz (int): Size of the image used during profiling.
404
400
  half (bool): Flag to indicate whether to use FP16 half-precision for TensorRT profiling.
405
401
  trt (bool): Flag to indicate whether to profile using TensorRT.
406
- device (torch.device | None): Device used for profiling. If None, it is determined automatically.
402
+ device (torch.device | str | None): Device used for profiling. If None, it is determined automatically.
407
403
 
408
404
  Notes:
409
405
  FP16 'half' argument option removed for ONNX as slower on CPU than FP32.
@@ -421,7 +417,7 @@ class ProfileModels:
421
417
  self.imgsz = imgsz
422
418
  self.half = half
423
419
  self.trt = trt # run TensorRT profiling
424
- self.device = device or torch.device(0 if torch.cuda.is_available() else "cpu")
420
+ self.device = device if isinstance(device, torch.device) else select_device(device)
425
421
 
426
422
  def run(self):
427
423
  """
@@ -305,7 +305,7 @@ def check_font(font="Arial.ttf"):
305
305
  Returns:
306
306
  (Path): Resolved font file path.
307
307
  """
308
- from matplotlib import font_manager
308
+ from matplotlib import font_manager # scope for faster 'import ultralytics'
309
309
 
310
310
  # Check USER_CONFIG_DIR
311
311
  name = Path(font).name
@@ -454,7 +454,7 @@ def check_suffix(file="yolo11n.pt", suffix=".pt", msg=""):
454
454
  """
455
455
  if file and suffix:
456
456
  if isinstance(suffix, str):
457
- suffix = (suffix,)
457
+ suffix = {suffix}
458
458
  for f in file if isinstance(file, (list, tuple)) else [file]:
459
459
  s = Path(f).suffix.lower().strip() # file suffix
460
460
  if len(s):
@@ -608,7 +608,7 @@ def check_yolo(verbose=True, device=""):
608
608
 
609
609
  Args:
610
610
  verbose (bool): Whether to print verbose information.
611
- device (str): Device to use for YOLO.
611
+ device (str | torch.device): Device to use for YOLO.
612
612
  """
613
613
  import psutil
614
614
 
@@ -810,7 +810,7 @@ def print_args(args: Optional[dict] = None, show_file=True, show_func=False):
810
810
  except ValueError:
811
811
  file = Path(file).stem
812
812
  s = (f"{file}: " if show_file else "") + (f"{func}: " if show_func else "")
813
- LOGGER.info(colorstr(s) + ", ".join(f"{k}={strip_auth(v)}" for k, v in args.items()))
813
+ LOGGER.info(colorstr(s) + ", ".join(f"{k}={strip_auth(v)}" for k, v in sorted(args.items())))
814
814
 
815
815
 
816
816
  def cuda_device_count() -> int:
@@ -5,7 +5,6 @@ import math
5
5
  import warnings
6
6
  from pathlib import Path
7
7
 
8
- import matplotlib.pyplot as plt
9
8
  import numpy as np
10
9
  import torch
11
10
 
@@ -418,7 +417,8 @@ class ConfusionMatrix:
418
417
  names (tuple): Names of classes, used as labels on the plot.
419
418
  on_plot (func): An optional callback to pass plots path and data when they are rendered.
420
419
  """
421
- import seaborn # scope for faster 'import ultralytics'
420
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
421
+ import seaborn
422
422
 
423
423
  array = self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1e-9) if normalize else 1) # normalize columns
424
424
  array[array < 0.005] = np.nan # don't annotate (would appear as 0.00)
@@ -479,6 +479,8 @@ def plot_pr_curve(px, py, ap, save_dir=Path("pr_curve.png"), names={}, on_plot=N
479
479
  names (dict, optional): Dictionary mapping class indices to class names.
480
480
  on_plot (callable, optional): Function to call after plot is saved.
481
481
  """
482
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
483
+
482
484
  fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
483
485
  py = np.stack(py, axis=1)
484
486
 
@@ -515,6 +517,8 @@ def plot_mc_curve(px, py, save_dir=Path("mc_curve.png"), names={}, xlabel="Confi
515
517
  ylabel (str, optional): Y-axis label.
516
518
  on_plot (callable, optional): Function to call after plot is saved.
517
519
  """
520
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
521
+
518
522
  fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
519
523
 
520
524
  if 0 < len(names) < 21: # display per-class legend if < 21 classes
@@ -6,7 +6,6 @@ from pathlib import Path
6
6
  from typing import Callable, Dict, List, Optional, Union
7
7
 
8
8
  import cv2
9
- import matplotlib.pyplot as plt
10
9
  import numpy as np
11
10
  import torch
12
11
  from PIL import Image, ImageDraw, ImageFont
@@ -534,8 +533,9 @@ def plot_labels(boxes, cls, names=(), save_dir=Path(""), on_plot=None):
534
533
  save_dir (Path, optional): Directory to save the plot.
535
534
  on_plot (Callable, optional): Function to call after plot is saved.
536
535
  """
537
- import pandas # scope for faster 'import ultralytics'
538
- import seaborn # scope for faster 'import ultralytics'
536
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
537
+ import pandas
538
+ import seaborn
539
539
 
540
540
  # Filter matplotlib>=3.7.2 warning and Seaborn use_inf and is_categorical FutureWarnings
541
541
  warnings.filterwarnings("ignore", category=UserWarning, message="The figure layout has changed to tight")
@@ -819,7 +819,8 @@ def plot_results(file="path/to/results.csv", dir="", segment=False, pose=False,
819
819
  >>> from ultralytics.utils.plotting import plot_results
820
820
  >>> plot_results("path/to/results.csv", segment=True)
821
821
  """
822
- import pandas as pd # scope for faster 'import ultralytics'
822
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
823
+ import pandas as pd
823
824
  from scipy.ndimage import gaussian_filter1d
824
825
 
825
826
  save_dir = Path(file).parent if file else Path(dir)
@@ -878,6 +879,8 @@ def plt_color_scatter(v, f, bins=20, cmap="viridis", alpha=0.8, edgecolors="none
878
879
  >>> f = np.random.rand(100)
879
880
  >>> plt_color_scatter(v, f)
880
881
  """
882
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
883
+
881
884
  # Calculate 2D histogram and corresponding colors
882
885
  hist, xedges, yedges = np.histogram2d(v, f, bins=bins)
883
886
  colors = [
@@ -903,7 +906,8 @@ def plot_tune_results(csv_file="tune_results.csv"):
903
906
  Examples:
904
907
  >>> plot_tune_results("path/to/tune_results.csv")
905
908
  """
906
- import pandas as pd # scope for faster 'import ultralytics'
909
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
910
+ import pandas as pd
907
911
  from scipy.ndimage import gaussian_filter1d
908
912
 
909
913
  def _save_one_file(file):
@@ -980,6 +984,8 @@ def feature_visualization(x, module_type, stage, n=32, save_dir=Path("runs/detec
980
984
  n (int, optional): Maximum number of feature maps to plot.
981
985
  save_dir (Path, optional): Directory to save results.
982
986
  """
987
+ import matplotlib.pyplot as plt # scope for faster 'import ultralytics'
988
+
983
989
  for m in {"Detect", "Segment", "Pose", "Classify", "OBB", "RTDETRDecoder"}: # all model heads
984
990
  if m in module_type:
985
991
  return