You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

76 lines
2.6 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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)