Source code for mne_denoise.progress
"""Structured progress events and callback helpers."""
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, cast
__all__ = ["ProgressEvent", "TqdmProgress"]
[docs]
@dataclass(frozen=True, slots=True)
class ProgressEvent:
"""Immutable description of one completed progress unit.
Parameters
----------
method : str
Algorithm identifier. The value is an open string.
stage : str
Semantic phase identifier. The value is an open string.
current : int or None, default=None
Completed-unit count, normally 1-based.
total : int or None, default=None
Planned unit count when known.
component : int or None, default=None
Optional 1-based component index.
metric : float or None, default=None
Optional method- and stage-specific diagnostic.
Notes
-----
Events are emitted after work and its metric complete. Callback return values
are ignored; callback exceptions propagate unchanged.
"""
method: str
stage: str
current: int | None = None
total: int | None = None
component: int | None = None
metric: float | None = None
def _load_tqdm() -> Any:
"""Load the optional tqdm factory on demand."""
try:
from tqdm.auto import tqdm
except ImportError as error:
raise ImportError(
"TqdmProgress requires the optional 'tqdm' dependency. "
'Install it with `pip install "mne-denoise[progress]"`.'
) from error
return tqdm
[docs]
class TqdmProgress:
"""Presentation adapter that renders ProgressEvent objects with tqdm.
Parameters
----------
leave : bool, default=False
Keep completed bars visible.
**tqdm_kwargs : dict
Additional keyword arguments passed to tqdm. total and initial are controlled
by the adapter.
"""
def __init__(self, *, leave: bool = False, **tqdm_kwargs: Any) -> None:
if "total" in tqdm_kwargs or "initial" in tqdm_kwargs:
raise TypeError("TqdmProgress controls tqdm's total and initial")
self._tqdm = _load_tqdm()
self._tqdm_kwargs = {**tqdm_kwargs, "leave": leave}
self._bar: Any | None = None
self._previous_event: ProgressEvent | None = None
def __call__(self, event: ProgressEvent) -> None:
"""Render one completed progress event."""
if self._starts_new_stream(event):
self.close()
bar_kwargs = dict(self._tqdm_kwargs)
bar_kwargs["total"] = event.total
bar_kwargs["initial"] = event.current if event.current is not None else 0
bar_kwargs.setdefault("desc", f"{event.method}: {event.stage}")
self._bar = self._tqdm(**bar_kwargs)
if event.current is None:
self._bar.update(1)
else:
delta = event.current - self._bar.n
if delta > 0:
self._bar.update(delta)
self._previous_event = event
def _starts_new_stream(self, event: ProgressEvent) -> bool:
"""Return whether ``event`` starts a new semantic stream."""
if self._bar is None or self._previous_event is None:
return True
previous = self._previous_event
if event.method != previous.method or event.stage != previous.stage:
return True
return (
event.current is not None
and previous.current is not None
and event.current <= previous.current
)
def __enter__(self) -> "TqdmProgress":
"""Return this adapter as a callback."""
return self
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> bool:
"""Close the active bar and propagate exceptions."""
self.close()
return False
[docs]
def close(self) -> None:
"""Close the active progress bar and clear its state."""
if self._bar is not None:
self._bar.close()
self._bar = None
self._previous_event = None
_ProgressCallback = Callable[[ProgressEvent], object]
def _validate_callback(callback: object) -> _ProgressCallback | None:
"""Validate a callback without invoking it."""
if callback is None:
return None
if not callable(callback):
raise TypeError(
f"callback must be callable or None, got {type(callback).__name__}."
)
return cast(_ProgressCallback, callback)
def _emit_progress(
callback: _ProgressCallback | None,
*,
method: str,
stage: str,
current: int | None = None,
total: int | None = None,
component: int | None = None,
metric: float | None = None,
) -> None:
"""Emit one ProgressEvent when callback is provided."""
if callback is None:
return
callback(
ProgressEvent(
method=method,
stage=stage,
current=current,
total=total,
component=component,
metric=metric,
)
)