EDTH_1 / scripts /triangulation_3d.py
MarcAltabella's picture
Add four receptor triangulation artifacts
8bef75d
Raw
History Blame Contribute Delete
3.39 kB
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
@dataclass(frozen=True)
class TriangulationResult:
module_a: np.ndarray
module_b: np.ndarray
direction_a: np.ndarray
direction_b: np.ndarray
closest_a: np.ndarray
closest_b: np.ndarray
estimated_position: np.ndarray
residual_m: float
range_a_m: float
range_b_m: float
def direction_from_az_el(azimuth_deg: float, elevation_deg: float) -> np.ndarray:
"""Convert ENU azimuth/elevation angles to a unit direction vector."""
az = np.deg2rad(azimuth_deg)
el = np.deg2rad(elevation_deg)
direction = np.array(
[
np.cos(el) * np.sin(az),
np.cos(el) * np.cos(az),
np.sin(el),
],
dtype=np.float64,
)
return direction / np.linalg.norm(direction)
def az_el_from_points(origin: np.ndarray, target: np.ndarray) -> tuple[float, float]:
"""Return ENU azimuth/elevation from origin to target."""
delta = np.asarray(target, dtype=np.float64) - np.asarray(origin, dtype=np.float64)
horizontal = np.hypot(delta[0], delta[1])
azimuth = (np.rad2deg(np.arctan2(delta[0], delta[1])) + 360.0) % 360.0
elevation = np.rad2deg(np.arctan2(delta[2], horizontal))
return float(azimuth), float(elevation)
def closest_points_between_rays(
origin_a: np.ndarray,
direction_a: np.ndarray,
origin_b: np.ndarray,
direction_b: np.ndarray,
) -> tuple[np.ndarray, np.ndarray, float, float]:
"""Find the closest points between two 3D rays."""
p = np.asarray(origin_a, dtype=np.float64)
q = np.asarray(origin_b, dtype=np.float64)
u = np.asarray(direction_a, dtype=np.float64)
v = np.asarray(direction_b, dtype=np.float64)
u = u / np.linalg.norm(u)
v = v / np.linalg.norm(v)
w = p - q
a = float(np.dot(u, u))
b = float(np.dot(u, v))
c = float(np.dot(v, v))
d = float(np.dot(u, w))
e = float(np.dot(v, w))
denom = a * c - b * b
if abs(denom) < 1e-9:
t = 0.0
s = max(0.0, e / c)
else:
t = (b * e - c * d) / denom
s = (a * e - b * d) / denom
t = max(0.0, t)
s = max(0.0, s)
closest_a = p + t * u
closest_b = q + s * v
return closest_a, closest_b, float(t), float(s)
def triangulate_from_az_el(
module_a: np.ndarray,
azimuth_a_deg: float,
elevation_a_deg: float,
module_b: np.ndarray,
azimuth_b_deg: float,
elevation_b_deg: float,
) -> TriangulationResult:
"""Triangulate a 3D position from two module rays."""
direction_a = direction_from_az_el(azimuth_a_deg, elevation_a_deg)
direction_b = direction_from_az_el(azimuth_b_deg, elevation_b_deg)
closest_a, closest_b, range_a_m, range_b_m = closest_points_between_rays(
module_a,
direction_a,
module_b,
direction_b,
)
estimated = (closest_a + closest_b) / 2.0
residual = float(np.linalg.norm(closest_a - closest_b))
return TriangulationResult(
module_a=np.asarray(module_a, dtype=np.float64),
module_b=np.asarray(module_b, dtype=np.float64),
direction_a=direction_a,
direction_b=direction_b,
closest_a=closest_a,
closest_b=closest_b,
estimated_position=estimated,
residual_m=residual,
range_a_m=range_a_m,
range_b_m=range_b_m,
)