Source code for meyelens.utils

import time

try:
    from psychopy import core as psychopy_core
except ImportError:
    psychopy_core = None


class _PerfCounterClock:
    """Minimal fallback clock with the same API as psychopy.core.Clock."""

    def __init__(self):
        self._start = time.perf_counter()

    def reset(self) -> None:
        self._start = time.perf_counter()

    def getTime(self) -> float:
        return time.perf_counter() - self._start


[docs] class CountdownTimer: """ Countdown timer based on PsychoPy's high-precision clock. This utility wraps :class:`psychopy.core.Clock` to provide a simple countdown interface for time-limited tasks (e.g., stimulus display windows, response deadlines, trial timeouts). Parameters ---------- duration : float Total countdown duration in seconds. Attributes ---------- duration : float Total countdown duration in seconds. clock : psychopy.core.Clock or _PerfCounterClock PsychoPy clock (if available) or a perf_counter-based fallback. is_running : bool ``True`` when the countdown is active. Notes ----- - If the timer is not running, :meth:`get_time_left` returns 0 (matching your current behavior and avoiding exceptions). - This class is intentionally minimal and does not use logging. """ def __init__(self, duration: float): """ Initialize the countdown timer. Parameters ---------- duration : float Countdown duration in seconds. """ self.duration = duration self.clock = psychopy_core.Clock() if psychopy_core is not None else _PerfCounterClock() self.is_running = False
[docs] def start(self) -> None: """ Start (or restart) the countdown. Resets the internal clock and marks the timer as running. Returns ------- None """ self.clock.reset() self.is_running = True
[docs] def get_time_left(self) -> float: """ Get the remaining time in the countdown. Returns ------- float Remaining time in seconds. If the timer is not running or the countdown has completed, returns 0. """ if not self.is_running: # Kept intentionally non-raising for drop-in simplicity. return 0.0 elapsed = self.clock.getTime() time_left = max(0.0, self.duration - elapsed) return time_left
[docs] def is_finished(self) -> bool: """ Check whether the countdown has completed. Returns ------- bool ``True`` if remaining time is 0, otherwise ``False``. """ return self.get_time_left() <= 0.0
[docs] def stop(self) -> None: """ Stop (pause) the countdown. This does not reset elapsed time; it simply marks the timer as inactive. With the current design, :meth:`get_time_left` will return 0 while stopped. Returns ------- None """ self.is_running = False