from __future__ import annotations import asyncio import hmac import math import queue import threading from dataclasses import dataclass from typing import Any import numpy as np def valid_bearer(header: str | None, token: str) -> bool: return bool(header and header.startswith("Bearer ") and hmac.compare_digest(header[7:], token)) def finite01(value: float) -> float: value = float(value) return min(1.0, max(0.0, value)) if math.isfinite(value) else 0.0 def normalize_bbox(bbox: list[float] | np.ndarray, width: int, height: int) -> list[float]: return [finite01(bbox[0] / width), finite01(bbox[1] / height), finite01(bbox[2] / width), finite01(bbox[3] / height)] def normalize_keypoints(points: np.ndarray, width: int, height: int, count: int) -> list[list[float]]: out = [[finite01(p[0] / width), finite01(p[1] / height), finite01(p[2])] for p in points[:count]] out.extend([[0.0, 0.0, 0.0] for _ in range(count - len(out))]) return out def inverse_letterbox(points: np.ndarray, scale: float, pad_x: float, pad_y: float) -> np.ndarray: out = points.copy().astype(float) out[..., 0] = (out[..., 0] - pad_x) / scale out[..., 1] = (out[..., 1] - pad_y) / scale return out class LatestQueue: def __init__(self) -> None: self._q: queue.Queue[Any] = queue.Queue(maxsize=1) self._dropped = 0 self._lock = threading.Lock() def put(self, item: Any) -> None: try: self._q.put_nowait(item) except queue.Full: try: self._q.get_nowait() with self._lock: self._dropped += 1 except queue.Empty: pass self._q.put_nowait(item) def get(self, timeout: float | None = None) -> tuple[Any, int]: item = self._q.get(timeout=timeout) with self._lock: dropped, self._dropped = self._dropped, 0 return item, dropped def qsize(self) -> int: return self._q.qsize() class BroadcastHub: def __init__(self) -> None: self._clients: set[asyncio.Queue] = set() self._lock = threading.Lock() def add(self) -> asyncio.Queue: q: asyncio.Queue = asyncio.Queue(maxsize=1) with self._lock: self._clients.add(q) return q def remove(self, q: asyncio.Queue) -> None: with self._lock: self._clients.discard(q) def publish_on_loop(self, loop: asyncio.AbstractEventLoop, message: str) -> None: def publish() -> None: with self._lock: clients = list(self._clients) for q in clients: if q.full(): try: q.get_nowait() except asyncio.QueueEmpty: pass q.put_nowait(message) loop.call_soon_threadsafe(publish) @dataclass class Frame: pixels: np.ndarray received_ns: int decode_ms: float