103 lines
2.9 KiB
Python
103 lines
2.9 KiB
Python
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
|