from __future__ import annotations import logging from math import isfinite # configure module logger (app sets handlers/level) logger = logging.getLogger("PTZTracker") # ---------- fast json ---------- try: import orjson def json_dumps(obj) -> str: return orjson.dumps(obj).decode("utf-8") json_loads = orjson.loads except Exception: import json def json_dumps(obj) -> str: return json.dumps(obj, separators=(",", ":"), ensure_ascii=False) json_loads = json.loads # ---------- math helpers ---------- def clipf(v: float, lo: float, hi: float) -> float: return lo if v < lo else hi if v > hi else v def quantf(v: float, q: float) -> float: return round(v / q) * q # ---------- angle / compass helpers ---------- def wrap_deg(a: float) -> float: """[-180, +180)""" return (a + 180.0) % 360.0 - 180.0 def norm360(a: float) -> float: """[0, 360)""" return a % 360.0 def shortest_delta_deg(a: float, b: float) -> float: """(b - a) в [-180, +180)""" return wrap_deg(b - a) # --- компас 8 румбов (как на картинке) --- def deg_to_compass(deg: float) -> str: labels_ru_8 = ["С", "СВ", "В", "ЮВ", "Ю", "ЮЗ", "З", "СЗ"] idx = int((norm360(deg) + 22.5) // 45) % 8 return labels_ru_8[idx] def format_bearing_ru(deg: float) -> str: return f"{deg_to_compass(deg)} ({norm360(deg):.1f}°)" # ---------- optics helpers ---------- def hfov_ptz_from_abs_zoom(abs_zoom: float, zmin: float = 0.0, zmax: float = 100.0, hfov_wide: float = 59.0, hfov_tele: float = 2.0) -> float: """ Аппроксимация HFOV от абсолютного зума (экспоненциальная интерполяция). abs_zoom приводим к [0..1]. Под реальные значения можно подогнать константы. """ if not isfinite(abs_zoom): return hfov_wide if zmax == zmin: return hfov_wide t = clipf((abs_zoom - zmin) / (zmax - zmin), 0.0, 1.0) r = hfov_tele / hfov_wide return hfov_wide * (r ** t) def bearing_from_xcenter(x_center_norm: float, hfov_deg: float, base_axis_deg: float) -> float: """ Преобразует x-норм. центр бокса (0..1) в глобальный азимут: bearing = base_axis_deg + (x-0.5)*HFOV где base_axis_deg — азимут оптической оси (с учётом севера). """ delta = (x_center_norm - 0.5) * hfov_deg return norm360(base_axis_deg + delta)