from __future__ import annotations from typing import Optional, Tuple import numpy as np def to_world_points(points_sensor: np.ndarray, pose: Optional[np.ndarray]) -> np.ndarray: """ Convert sensor-frame points to world frame using pose (world_T_sensor). Falls back to input points when pose is unavailable/invalid. """ if pose is None: return np.asarray(points_sensor, dtype=np.float32) try: pts = np.asarray(points_sensor, dtype=np.float32) pose_np = np.asarray(pose, dtype=np.float32) if pts.ndim != 2 or pts.shape[1] != 3 or pose_np.shape != (4, 4): return pts rot = pose_np[:3, :3] trans = pose_np[:3, 3] return (pts @ rot.T) + trans except Exception: return np.asarray(points_sensor, dtype=np.float32) def apply_transform_points(points: Optional[np.ndarray], transform: np.ndarray) -> Optional[np.ndarray]: if points is None: return None pts = np.asarray(points, dtype=np.float32) if len(pts) == 0: return pts try: tf = np.asarray(transform, dtype=np.float32) if tf.shape != (4, 4): return pts rot = tf[:3, :3] trans = tf[:3, 3] return (pts @ rot.T) + trans except Exception: return pts def tf_delta(prev_tf: np.ndarray, new_tf: np.ndarray) -> Tuple[float, float]: try: a = np.asarray(prev_tf, dtype=np.float64) b = np.asarray(new_tf, dtype=np.float64) if a.shape != (4, 4) or b.shape != (4, 4): return 0.0, 0.0 dp = b[:3, 3] - a[:3, 3] trans = float(np.linalg.norm(dp)) r = b[:3, :3] @ a[:3, :3].T ang = float(np.degrees(np.arccos(np.clip((np.trace(r) - 1.0) * 0.5, -1.0, 1.0)))) return trans, ang except Exception: return 0.0, 0.0 def blend_rigid_tf(prev_tf: np.ndarray, new_tf: np.ndarray, alpha_t: float, alpha_r: float) -> np.ndarray: try: a = np.asarray(prev_tf, dtype=np.float64) b = np.asarray(new_tf, dtype=np.float64) if a.shape != (4, 4) or b.shape != (4, 4): return np.asarray(new_tf, dtype=np.float64) at = float(np.clip(alpha_t, 0.0, 1.0)) ar = float(np.clip(alpha_r, 0.0, 1.0)) out = np.eye(4, dtype=np.float64) out[:3, 3] = ((1.0 - at) * a[:3, 3]) + (at * b[:3, 3]) rm = ((1.0 - ar) * a[:3, :3]) + (ar * b[:3, :3]) u, _, vt = np.linalg.svd(rm, full_matrices=False) r = u @ vt if np.linalg.det(r) < 0: u[:, -1] *= -1.0 r = u @ vt out[:3, :3] = r return out except Exception: return np.asarray(new_tf, dtype=np.float64) def yaw_deg_from_tf(tf: np.ndarray) -> float: try: m = np.asarray(tf, dtype=np.float64) if m.shape != (4, 4): return 0.0 yaw = np.degrees(np.arctan2(float(m[1, 0]), float(m[0, 0]))) return float(yaw) except Exception: return 0.0 def tf_from_xyzyaw(x: float, y: float, z: float, yaw_deg: float) -> np.ndarray: yaw = np.deg2rad(float(yaw_deg)) cy = float(np.cos(yaw)) sy = float(np.sin(yaw)) tf = np.eye(4, dtype=np.float64) tf[:3, :3] = np.array( [[cy, -sy, 0.0], [sy, cy, 0.0], [0.0, 0.0, 1.0]], dtype=np.float64, ) tf[:3, 3] = np.array([float(x), float(y), float(z)], dtype=np.float64) return tf