Skip to content

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)