init commit

This commit is contained in:
2025-11-08 19:15:39 +01:00
parent ecffcb08e8
commit c7adacf53b
470 changed files with 73751 additions and 0 deletions

440
ultralytics/utils/tqdm.py Normal file
View File

@@ -0,0 +1,440 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
from __future__ import annotations
import os
import sys
import time
from functools import lru_cache
from typing import IO, Any
@lru_cache(maxsize=1)
def is_noninteractive_console() -> bool:
"""Check for known non-interactive console environments."""
return "GITHUB_ACTIONS" in os.environ or "RUNPOD_POD_ID" in os.environ
class TQDM:
"""
Lightweight zero-dependency progress bar for Ultralytics.
Provides clean, rich-style progress bars suitable for various environments including Weights & Biases,
console outputs, and other logging systems. Features zero external dependencies, clean single-line output,
rich-style progress bars with Unicode block characters, context manager support, iterator protocol support,
and dynamic description updates.
Attributes:
iterable (object): Iterable to wrap with progress bar.
desc (str): Prefix description for the progress bar.
total (int): Expected number of iterations.
disable (bool): Whether to disable the progress bar.
unit (str): String for units of iteration.
unit_scale (bool): Auto-scale units flag.
unit_divisor (int): Divisor for unit scaling.
leave (bool): Whether to leave the progress bar after completion.
mininterval (float): Minimum time interval between updates.
initial (int): Initial counter value.
n (int): Current iteration count.
closed (bool): Whether the progress bar is closed.
bar_format (str): Custom bar format string.
file (object): Output file stream.
Methods:
update: Update progress by n steps.
set_description: Set or update the description.
set_postfix: Set postfix for the progress bar.
close: Close the progress bar and clean up.
refresh: Refresh the progress bar display.
clear: Clear the progress bar from display.
write: Write a message without breaking the progress bar.
Examples:
Basic usage with iterator:
>>> for i in TQDM(range(100)):
... time.sleep(0.01)
With custom description:
>>> pbar = TQDM(range(100), desc="Processing")
>>> for i in pbar:
... pbar.set_description(f"Processing item {i}")
Context manager usage:
>>> with TQDM(total=100, unit="B", unit_scale=True) as pbar:
... for i in range(100):
... pbar.update(1)
Manual updates:
>>> pbar = TQDM(total=100, desc="Training")
>>> for epoch in range(100):
... # Do work
... pbar.update(1)
>>> pbar.close()
"""
# Constants
MIN_RATE_CALC_INTERVAL = 0.01 # Minimum time interval for rate calculation
RATE_SMOOTHING_FACTOR = 0.3 # Factor for exponential smoothing of rates
MAX_SMOOTHED_RATE = 1000000 # Maximum rate to apply smoothing to
NONINTERACTIVE_MIN_INTERVAL = 60.0 # Minimum interval for non-interactive environments
def __init__(
self,
iterable: Any = None,
desc: str | None = None,
total: int | None = None,
leave: bool = True,
file: IO[str] | None = None,
mininterval: float = 0.1,
disable: bool | None = None,
unit: str = "it",
unit_scale: bool = True,
unit_divisor: int = 1000,
bar_format: str | None = None, # kept for API compatibility; not used for formatting
initial: int = 0,
**kwargs,
) -> None:
"""
Initialize the TQDM progress bar with specified configuration options.
Args:
iterable (object, optional): Iterable to wrap with progress bar.
desc (str, optional): Prefix description for the progress bar.
total (int, optional): Expected number of iterations.
leave (bool, optional): Whether to leave the progress bar after completion.
file (object, optional): Output file stream for progress display.
mininterval (float, optional): Minimum time interval between updates (default 0.1s, 60s in GitHub Actions).
disable (bool, optional): Whether to disable the progress bar. Auto-detected if None.
unit (str, optional): String for units of iteration (default "it" for items).
unit_scale (bool, optional): Auto-scale units for bytes/data units.
unit_divisor (int, optional): Divisor for unit scaling (default 1000).
bar_format (str, optional): Custom bar format string.
initial (int, optional): Initial counter value.
**kwargs (Any): Additional keyword arguments for compatibility (ignored).
Examples:
>>> pbar = TQDM(range(100), desc="Processing")
>>> with TQDM(total=1000, unit="B", unit_scale=True) as pbar:
... pbar.update(1024) # Updates by 1KB
"""
# Disable if not verbose
if disable is None:
try:
from ultralytics.utils import LOGGER, VERBOSE
disable = not VERBOSE or LOGGER.getEffectiveLevel() > 20
except ImportError:
disable = False
self.iterable = iterable
self.desc = desc or ""
self.total = total or (len(iterable) if hasattr(iterable, "__len__") else None) or None # prevent total=0
self.disable = disable
self.unit = unit
self.unit_scale = unit_scale
self.unit_divisor = unit_divisor
self.leave = leave
self.noninteractive = is_noninteractive_console()
self.mininterval = max(mininterval, self.NONINTERACTIVE_MIN_INTERVAL) if self.noninteractive else mininterval
self.initial = initial
# Kept for API compatibility (unused for f-string formatting)
self.bar_format = bar_format
self.file = file or sys.stdout
# Internal state
self.n = self.initial
self.last_print_n = self.initial
self.last_print_t = time.time()
self.start_t = time.time()
self.last_rate = 0.0
self.closed = False
self.is_bytes = unit_scale and unit in ("B", "bytes")
self.scales = (
[(1073741824, "GB/s"), (1048576, "MB/s"), (1024, "KB/s")]
if self.is_bytes
else [(1e9, f"G{self.unit}/s"), (1e6, f"M{self.unit}/s"), (1e3, f"K{self.unit}/s")]
)
if not self.disable and self.total and not self.noninteractive:
self._display()
def _format_rate(self, rate: float) -> str:
"""Format rate with units."""
if rate <= 0:
return ""
fallback = f"{rate:.1f}B/s" if self.is_bytes else f"{rate:.1f}{self.unit}/s"
return next((f"{rate / t:.1f}{u}" for t, u in self.scales if rate >= t), fallback)
def _format_num(self, num: int | float) -> str:
"""Format number with optional unit scaling."""
if not self.unit_scale or not self.is_bytes:
return str(num)
for unit in ("", "K", "M", "G", "T"):
if abs(num) < self.unit_divisor:
return f"{num:3.1f}{unit}B" if unit else f"{num:.0f}B"
num /= self.unit_divisor
return f"{num:.1f}PB"
def _format_time(self, seconds: float) -> str:
"""Format time duration."""
if seconds < 60:
return f"{seconds:.1f}s"
elif seconds < 3600:
return f"{int(seconds // 60)}:{seconds % 60:02.0f}"
else:
h, m = int(seconds // 3600), int((seconds % 3600) // 60)
return f"{h}:{m:02d}:{seconds % 60:02.0f}"
def _generate_bar(self, width: int = 12) -> str:
"""Generate progress bar."""
if self.total is None:
return "" * width if self.closed else "" * width
frac = min(1.0, self.n / self.total)
filled = int(frac * width)
bar = "" * filled + "" * (width - filled)
if filled < width and frac * width - filled > 0.5:
bar = f"{bar[:filled]}{bar[filled + 1 :]}"
return bar
def _should_update(self, dt: float, dn: int) -> bool:
"""Check if display should update."""
if self.noninteractive:
return False
return (self.total is not None and self.n >= self.total) or (dt >= self.mininterval)
def _display(self, final: bool = False) -> None:
"""Display progress bar."""
if self.disable or (self.closed and not final):
return
current_time = time.time()
dt = current_time - self.last_print_t
dn = self.n - self.last_print_n
if not final and not self._should_update(dt, dn):
return
# Calculate rate (avoid crazy numbers)
if dt > self.MIN_RATE_CALC_INTERVAL:
rate = dn / dt if dt else 0.0
# Smooth rate for reasonable values, use raw rate for very high values
if rate < self.MAX_SMOOTHED_RATE:
self.last_rate = self.RATE_SMOOTHING_FACTOR * rate + (1 - self.RATE_SMOOTHING_FACTOR) * self.last_rate
rate = self.last_rate
else:
rate = self.last_rate
# At completion, use overall rate
if self.total and self.n >= self.total:
overall_elapsed = current_time - self.start_t
if overall_elapsed > 0:
rate = self.n / overall_elapsed
# Update counters
self.last_print_n = self.n
self.last_print_t = current_time
elapsed = current_time - self.start_t
# Remaining time
remaining_str = ""
if self.total and 0 < self.n < self.total and elapsed > 0:
est_rate = rate or (self.n / elapsed)
remaining_str = f"<{self._format_time((self.total - self.n) / est_rate)}"
# Numbers and percent
if self.total:
percent = (self.n / self.total) * 100
n_str = self._format_num(self.n)
t_str = self._format_num(self.total)
if self.is_bytes:
# Collapse suffix only when identical (e.g. "5.4/5.4MB")
if n_str[-2] == t_str[-2]:
n_str = n_str.rstrip("KMGTPB") # Remove unit suffix from current if different than total
else:
percent = 0.0
n_str, t_str = self._format_num(self.n), "?"
elapsed_str = self._format_time(elapsed)
rate_str = self._format_rate(rate) or (self._format_rate(self.n / elapsed) if elapsed > 0 else "")
bar = self._generate_bar()
# Compose progress line via f-strings (two shapes: with/without total)
if self.total:
if self.is_bytes and self.n >= self.total:
# Completed bytes: show only final size
progress_str = f"{self.desc}: {percent:.0f}% {bar} {t_str} {rate_str} {elapsed_str}"
else:
progress_str = (
f"{self.desc}: {percent:.0f}% {bar} {n_str}/{t_str} {rate_str} {elapsed_str}{remaining_str}"
)
else:
progress_str = f"{self.desc}: {bar} {n_str} {rate_str} {elapsed_str}"
# Write to output
try:
if self.noninteractive:
# In non-interactive environments, avoid carriage return which creates empty lines
self.file.write(progress_str)
else:
# In interactive terminals, use carriage return and clear line for updating display
self.file.write(f"\r\033[K{progress_str}")
self.file.flush()
except Exception:
pass
def update(self, n: int = 1) -> None:
"""Update progress by n steps."""
if not self.disable and not self.closed:
self.n += n
self._display()
def set_description(self, desc: str | None) -> None:
"""Set description."""
self.desc = desc or ""
if not self.disable:
self._display()
def set_postfix(self, **kwargs: Any) -> None:
"""Set postfix (appends to description)."""
if kwargs:
postfix = ", ".join(f"{k}={v}" for k, v in kwargs.items())
base_desc = self.desc.split(" | ")[0] if " | " in self.desc else self.desc
self.set_description(f"{base_desc} | {postfix}")
def close(self) -> None:
"""Close progress bar."""
if self.closed:
return
self.closed = True
if not self.disable:
# Final display
if self.total and self.n >= self.total:
self.n = self.total
self._display(final=True)
# Cleanup
if self.leave:
self.file.write("\n")
else:
self.file.write("\r\033[K")
try:
self.file.flush()
except Exception:
pass
def __enter__(self) -> TQDM:
"""Enter context manager."""
return self
def __exit__(self, *args: Any) -> None:
"""Exit context manager and close progress bar."""
self.close()
def __iter__(self) -> Any:
"""Iterate over the wrapped iterable with progress updates."""
if self.iterable is None:
raise TypeError("'NoneType' object is not iterable")
try:
for item in self.iterable:
yield item
self.update(1)
finally:
self.close()
def __del__(self) -> None:
"""Destructor to ensure cleanup."""
try:
self.close()
except Exception:
pass
def refresh(self) -> None:
"""Refresh display."""
if not self.disable:
self._display()
def clear(self) -> None:
"""Clear progress bar."""
if not self.disable:
try:
self.file.write("\r\033[K")
self.file.flush()
except Exception:
pass
@staticmethod
def write(s: str, file: IO[str] | None = None, end: str = "\n") -> None:
"""Static method to write without breaking progress bar."""
file = file or sys.stdout
try:
file.write(s + end)
file.flush()
except Exception:
pass
if __name__ == "__main__":
import time
print("1. Basic progress bar with known total:")
for i in TQDM(range(3), desc="Known total"):
time.sleep(0.05)
print("\n2. Manual updates with known total:")
pbar = TQDM(total=300, desc="Manual updates", unit="files")
for i in range(300):
time.sleep(0.03)
pbar.update(1)
if i % 10 == 9:
pbar.set_description(f"Processing batch {i // 10 + 1}")
pbar.close()
print("\n3. Progress bar with unknown total:")
pbar = TQDM(desc="Unknown total", unit="items")
for i in range(25):
time.sleep(0.08)
pbar.update(1)
if i % 5 == 4:
pbar.set_postfix(processed=i + 1, status="OK")
pbar.close()
print("\n4. Context manager with unknown total:")
with TQDM(desc="Processing stream", unit="B", unit_scale=True, unit_divisor=1024) as pbar:
for i in range(30):
time.sleep(0.1)
pbar.update(1024 * 1024 * i) # Simulate processing MB of data
print("\n5. Iterator with unknown length:")
def data_stream():
"""Simulate a data stream of unknown length."""
import random
for i in range(random.randint(10, 20)):
yield f"data_chunk_{i}"
for chunk in TQDM(data_stream(), desc="Stream processing", unit="chunks"):
time.sleep(0.1)
print("\n6. File processing simulation (unknown size):")
def process_files():
"""Simulate processing files of unknown count."""
return [f"file_{i}.txt" for i in range(18)]
pbar = TQDM(desc="Scanning files", unit="files")
files = process_files()
for i, filename in enumerate(files):
time.sleep(0.06)
pbar.update(1)
pbar.set_description(f"Processing {filename}")
pbar.close()