Utils API Reference
oriented_det.utils
Utility helpers for configuration management and visualization.
FrozenConfig
dataclass
Bases: Mapping[str, Any]
Immutable configuration wrapper with dotted-key access.
Source code in oriented_det/utils/config.py
| @dataclass(frozen=True)
class FrozenConfig(Mapping[str, Any]):
"""Immutable configuration wrapper with dotted-key access."""
_data: Mapping[str, Any]
def __getitem__(self, key: str) -> Any:
return self._data[key]
def __iter__(self) -> Iterator[str]:
return iter(self._data)
def __len__(self) -> int:
return len(self._data)
def get(self, dotted_key: str, default: Any = None) -> Any:
cursor: Any = self
for part in dotted_key.split("."):
if isinstance(cursor, Mapping) and part in cursor:
cursor = cursor[part]
else:
return default
return cursor
def to_dict(self) -> Dict[str, Any]:
def thaw(value: Any) -> Any:
if isinstance(value, FrozenConfig):
return {k: thaw(v) for k, v in value.items()}
if isinstance(value, tuple):
return [thaw(v) for v in value]
return value
return {k: thaw(v) for k, v in self.items()}
|
TraceLogger
Configurable logger for tracing function execution.
Source code in oriented_det/utils/logging.py
| class TraceLogger:
"""Configurable logger for tracing function execution."""
_enabled: bool = False
_indent_level: int = 0
_indent_str: str = " "
@classmethod
def enable(cls) -> None:
"""Enable tracing/logging."""
cls._enabled = True
@classmethod
def disable(cls) -> None:
"""Disable tracing/logging."""
cls._enabled = False
@classmethod
def is_enabled(cls) -> bool:
"""Check if tracing is enabled."""
return cls._enabled
@classmethod
def trace(cls, message: str, *args, **kwargs) -> None:
"""Print a trace message if logging is enabled."""
if cls._enabled:
indent = cls._indent_str * cls._indent_level
formatted_msg = message.format(*args, **kwargs) if args or kwargs else message
print(f"{indent}{formatted_msg}")
@classmethod
@contextmanager
def trace_block(cls, message: str, *args, **kwargs):
"""Context manager for tracing a code block with timing."""
if cls._enabled:
formatted_msg = message.format(*args, **kwargs) if args or kwargs else message
cls.trace(f"→ {formatted_msg}")
cls._indent_level += 1
start_time = time.time()
try:
yield
finally:
elapsed = time.time() - start_time
cls._indent_level -= 1
cls.trace(f"✅ {formatted_msg} ({elapsed:.3f}s)")
else:
yield
@classmethod
def trace_timing(cls, message: str, elapsed: float, *args, **kwargs) -> None:
"""Trace a message with timing information."""
if cls._enabled:
formatted_msg = message.format(*args, **kwargs) if args or kwargs else message
cls.trace(f"{formatted_msg} ({elapsed:.3f}s)")
@classmethod
def trace_value(cls, name: str, value: Any, unit: Optional[str] = None) -> None:
"""Trace a value with optional unit."""
if cls._enabled:
if unit:
cls.trace(f"{name}: {value} {unit}")
else:
cls.trace(f"{name}: {value}")
|
disable()
classmethod
Disable tracing/logging.
Source code in oriented_det/utils/logging.py
| @classmethod
def disable(cls) -> None:
"""Disable tracing/logging."""
cls._enabled = False
|
enable()
classmethod
Enable tracing/logging.
Source code in oriented_det/utils/logging.py
| @classmethod
def enable(cls) -> None:
"""Enable tracing/logging."""
cls._enabled = True
|
is_enabled()
classmethod
Check if tracing is enabled.
Source code in oriented_det/utils/logging.py
| @classmethod
def is_enabled(cls) -> bool:
"""Check if tracing is enabled."""
return cls._enabled
|
trace(message, *args, **kwargs)
classmethod
Print a trace message if logging is enabled.
Source code in oriented_det/utils/logging.py
| @classmethod
def trace(cls, message: str, *args, **kwargs) -> None:
"""Print a trace message if logging is enabled."""
if cls._enabled:
indent = cls._indent_str * cls._indent_level
formatted_msg = message.format(*args, **kwargs) if args or kwargs else message
print(f"{indent}{formatted_msg}")
|
trace_block(message, *args, **kwargs)
classmethod
Context manager for tracing a code block with timing.
Source code in oriented_det/utils/logging.py
| @classmethod
@contextmanager
def trace_block(cls, message: str, *args, **kwargs):
"""Context manager for tracing a code block with timing."""
if cls._enabled:
formatted_msg = message.format(*args, **kwargs) if args or kwargs else message
cls.trace(f"→ {formatted_msg}")
cls._indent_level += 1
start_time = time.time()
try:
yield
finally:
elapsed = time.time() - start_time
cls._indent_level -= 1
cls.trace(f"✅ {formatted_msg} ({elapsed:.3f}s)")
else:
yield
|
trace_timing(message, elapsed, *args, **kwargs)
classmethod
Trace a message with timing information.
Source code in oriented_det/utils/logging.py
| @classmethod
def trace_timing(cls, message: str, elapsed: float, *args, **kwargs) -> None:
"""Trace a message with timing information."""
if cls._enabled:
formatted_msg = message.format(*args, **kwargs) if args or kwargs else message
cls.trace(f"{formatted_msg} ({elapsed:.3f}s)")
|
trace_value(name, value, unit=None)
classmethod
Trace a value with optional unit.
Source code in oriented_det/utils/logging.py
| @classmethod
def trace_value(cls, name: str, value: Any, unit: Optional[str] = None) -> None:
"""Trace a value with optional unit."""
if cls._enabled:
if unit:
cls.trace(f"{name}: {value} {unit}")
else:
cls.trace(f"{name}: {value}")
|
disable_tracing()
Disable tracing globally.
Source code in oriented_det/utils/logging.py
| def disable_tracing() -> None:
"""Disable tracing globally."""
logger.disable()
|
enable_tracing()
Enable tracing globally.
Source code in oriented_det/utils/logging.py
| def enable_tracing() -> None:
"""Enable tracing globally."""
logger.enable()
|
get_device(prefer_cuda=True)
Return the best available device: CUDA > MPS (Apple Silicon) > CPU.
Use this when you want training or inference to use GPU when possible,
including on macOS with M1/M2/M3 (MPS).
Source code in oriented_det/utils/device.py
| def get_device(prefer_cuda: bool = True) -> "torch.device":
"""Return the best available device: CUDA > MPS (Apple Silicon) > CPU.
Use this when you want training or inference to use GPU when possible,
including on macOS with M1/M2/M3 (MPS).
"""
if torch is None:
raise RuntimeError("PyTorch is required for device selection.")
if prefer_cuda and torch.cuda.is_available():
return torch.device("cuda")
if getattr(torch.backends, "mps", None) and torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
|
is_gpu_device(device)
Return True if the device is a GPU (CUDA or MPS).
Source code in oriented_det/utils/device.py
| def is_gpu_device(device: "torch.device") -> bool:
"""Return True if the device is a GPU (CUDA or MPS)."""
if device is None:
return False
device_type = device.type if hasattr(device, "type") else str(device).split(":")[0]
if device_type == "cuda":
return torch is not None and torch.cuda.is_available()
if device_type == "mps":
return torch is not None and getattr(torch.backends, "mps", None) is not None and torch.backends.mps.is_available()
return False
|
is_tracing_enabled()
Check if tracing is enabled.
Source code in oriented_det/utils/logging.py
| def is_tracing_enabled() -> bool:
"""Check if tracing is enabled."""
return logger.is_enabled()
|
load_config(source, *, overrides=None)
Load a configuration file or dict and apply optional overrides.
Supports MMRotate-style nested config inheritance via base field.
Parameters:
| Name |
Type |
Description |
Default |
source
|
str | PathLike[str] | Mapping[str, Any]
|
Path to config file or dict
|
required
|
overrides
|
Sequence[str] | Mapping[str, Any] | None
|
Optional overrides to apply after loading
|
None
|
Returns:
| Type |
Description |
FrozenConfig
|
FrozenConfig instance with merged configuration
|
Examples:
>>> # Simple config
>>> cfg = load_config("config.json")
>>> # Config with base inheritance
>>> # config.json: {"_base_": "base.json", "lr": 0.01}
>>> cfg = load_config("config.json")
>>> # With overrides
>>> cfg = load_config("config.json", overrides=["training.lr=0.001"])
Source code in oriented_det/utils/config.py
| def load_config(
source: str | os.PathLike[str] | Mapping[str, Any],
*,
overrides: Sequence[str] | Mapping[str, Any] | None = None,
) -> FrozenConfig:
"""Load a configuration file or dict and apply optional overrides.
Supports MMRotate-style nested config inheritance via _base_ field.
Args:
source: Path to config file or dict
overrides: Optional overrides to apply after loading
Returns:
FrozenConfig instance with merged configuration
Examples:
>>> # Simple config
>>> cfg = load_config("config.json")
>>> # Config with base inheritance
>>> # config.json: {"_base_": "base.json", "lr": 0.01}
>>> cfg = load_config("config.json")
>>> # With overrides
>>> cfg = load_config("config.json", overrides=["training.lr=0.001"])
"""
if isinstance(source, Mapping):
cfg_dict = _deep_clone(_ensure_mapping(source))
_strip_muted_keys(cfg_dict)
else:
path = Path(source)
if not path.exists():
raise FileNotFoundError(path)
# Load with base inheritance support
cfg_dict = _load_config_with_base(path)
# Strip _muted_* keys (keeps alternative values visible without affecting behavior)
_strip_muted_keys(cfg_dict)
# Handle deletion syntax (must be done before overrides)
_delete_keys(cfg_dict, cfg_dict)
if overrides:
cfg_dict = apply_overrides(cfg_dict, overrides)
return _deep_freeze(cfg_dict)
|
tqdm_progress_stream()
File object for tqdm(..., file=...), aligned with :func:tools.train / progress_stream on the training loop.
- If
sys.stderr is a TTY, use it (normal interactive run).
- If not (shell redirect /
| tee), try /dev/tty (POSIX) or CON (Windows) so the bar
still updates on the user’s terminal while other output is captured in a file.
- If none of that works, use
os.devnull so logs are not flooded with control characters.
Source code in oriented_det/utils/progress.py
| def tqdm_progress_stream() -> Any:
"""File object for ``tqdm(..., file=...)``, aligned with :func:`tools.train` / ``progress_stream`` on the training loop.
* If ``sys.stderr`` is a TTY, use it (normal interactive run).
* If not (shell redirect / ``| tee``), try ``/dev/tty`` (POSIX) or ``CON`` (Windows) so the bar
still updates on the user’s terminal while other output is captured in a file.
* If none of that works, use ``os.devnull`` so logs are not flooded with control characters.
"""
global _tqdm_tty, _tqdm_tty_open_failed
if sys.stderr.isatty():
return sys.stderr
if _tqdm_tty is not None:
return _tqdm_tty
if not _tqdm_tty_open_failed:
if os.name == "posix":
try:
# Still works when e.g. ``python ... 2>&1 | tee x.log`` (stderr is a pipe, not a TTY).
_tqdm_tty = open("/dev/tty", "w", encoding="utf-8", buffering=1)
atexit.register(_close_tqdm_tty)
return _tqdm_tty
except OSError:
_tqdm_tty_open_failed = True
elif os.name == "nt":
try:
_tqdm_tty = open("CON", "w", encoding="utf-8", errors="replace", buffering=1)
atexit.register(_close_tqdm_tty)
return _tqdm_tty
except OSError:
_tqdm_tty_open_failed = True
return _get_devnull()
|
trace_function(func_name=None)
Decorator to trace function execution.
Parameters:
| Name |
Type |
Description |
Default |
func_name
|
Optional[str]
|
Optional custom name for the function in logs.
If None, uses the actual function name.
|
None
|
Example
@trace_function()
def my_function(x, y):
return x + y
@trace_function("custom_name")
def another_function():
pass
Source code in oriented_det/utils/logging.py
| def trace_function(func_name: Optional[str] = None):
"""Decorator to trace function execution.
Args:
func_name: Optional custom name for the function in logs.
If None, uses the actual function name.
Example:
@trace_function()
def my_function(x, y):
return x + y
@trace_function("custom_name")
def another_function():
pass
"""
def decorator(func: Callable) -> Callable:
name = func_name or func.__name__
@functools.wraps(func)
def wrapper(*args, **kwargs):
if logger.is_enabled():
# Format arguments for display
args_str = ", ".join([str(arg)[:50] for arg in args[:3]])
if len(args) > 3:
args_str += f", ... (+{len(args)-3} more)"
kwargs_str = ", ".join([f"{k}={str(v)[:30]}" for k, v in list(kwargs.items())[:2]])
if len(kwargs) > 2:
kwargs_str += f", ... (+{len(kwargs)-2} more)"
call_str = f"{name}({args_str}"
if kwargs_str:
call_str += f", {kwargs_str}"
call_str += ")"
logger.trace(f"→ {call_str}")
logger._indent_level += 1
start_time = time.time()
try:
result = func(*args, **kwargs)
elapsed = time.time() - start_time
logger._indent_level -= 1
logger.trace(f"✅ {name} returned ({elapsed:.3f}s)")
return result
except Exception as e:
elapsed = time.time() - start_time
logger._indent_level -= 1
logger.trace(f"❌ {name} raised {type(e).__name__}: {e} ({elapsed:.3f}s)")
raise
else:
return func(*args, **kwargs)
return wrapper
return decorator
|
Important Notes
Configuration Management
The oriented_det.utils package provides configuration helpers:
load_config(path_or_dict, overrides=...) reads JSON/YAML configs, applies dotted-key overrides, and returns an immutable FrozenConfig
- Nested config inheritance via
_base_ field (MMRotate-style)
- Supports single or multiple base configs
- Recursive loading with circular dependency detection
- Relative path resolution (relative to config file's directory)
- Deep merging of nested dictionaries
- Configs are immutable to prevent accidental modifications
- Supports nested access with dot notation
Visualization
The visualization utilities provide lightweight helpers for debugging:
viz.draw_boxes(image, boxes, specs=...) overlays polygons, QBox, or RBox detections onto a Pillow image or NumPy array
- Pillow is optional; when it is not installed the helper raises a clear error
- Supports custom colors, line widths, and drawing specs
Examples
Configuration Loading
from oriented_det.utils import load_config
# From file
cfg = load_config("configs/retinanet.yaml", overrides=["trainer.epochs=24"])
# From dict
cfg = load_config({"model": {"num_classes": 15}})
# Access with dot notation
print(cfg.model.num_classes) # 15
# With nested config inheritance
# config.json:
# {
# "_base_": [
# "../_base_/models/oriented_rcnn_r50.json",
# "../_base_/schedules/1x.json"
# ],
# "training": {"learning_rate": 0.01}
# }
cfg = load_config("config.json")
# Automatically loads and merges base configs, then applies overrides
Visualization
from oriented_det.utils import viz
from oriented_det.geometry import RBox
from PIL import Image
image = Image.open("image.jpg")
# Draw RBoxes
rboxes = [RBox(100, 200, 50, 30, 0.5)]
result = viz.draw_boxes(image, rboxes)
# Save
result.save("output.jpg")
Custom Drawing Specs
from oriented_det.utils import viz, DrawingSpec
# Custom drawing specs
specs = [
DrawingSpec(outline=(255, 0, 0), width=3), # Red, thick
DrawingSpec(outline=(0, 255, 0), width=2), # Green, medium
]
result = viz.draw_boxes(image, rboxes, specs=specs)