from __future__ import annotations import numpy as np import torch def quat_mul_wxyz(q1: torch.Tensor, q2: torch.Tensor) -> torch.Tensor: w1, x1, y1, z1 = q1.unbind(dim=-1) w2, x2, y2, z2 = q2.unbind(dim=-1) w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2 z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2 return torch.stack([w, x, y, z], dim=-1) def rotmat_to_quat_wxyz(Rm: torch.Tensor) -> torch.Tensor: m00, m01, m02 = Rm[0, 0], Rm[0, 1], Rm[0, 2] m10, m11, m12 = Rm[1, 0], Rm[1, 1], Rm[1, 2] m20, m21, m22 = Rm[2, 0], Rm[2, 1], Rm[2, 2] tr = m00 + m11 + m22 if tr > 0.0: s = torch.sqrt(tr + 1.0) * 2.0 w = 0.25 * s x = (m21 - m12) / s y = (m02 - m20) / s z = (m10 - m01) / s elif (m00 > m11) and (m00 > m22): s = torch.sqrt(1.0 + m00 - m11 - m22) * 2.0 w = (m21 - m12) / s x = 0.25 * s y = (m01 + m10) / s z = (m02 + m20) / s elif m11 > m22: s = torch.sqrt(1.0 + m11 - m00 - m22) * 2.0 w = (m02 - m20) / s x = (m01 + m10) / s y = 0.25 * s z = (m12 + m21) / s else: s = torch.sqrt(1.0 + m22 - m00 - m11) * 2.0 w = (m10 - m01) / s x = (m02 + m20) / s y = (m12 + m21) / s z = 0.25 * s q = torch.stack([w, x, y, z]) return q / q.norm().clamp(min=1e-8) def to_k4(k3: torch.Tensor) -> torch.Tensor: b = k3.shape[0] out = torch.eye(4, dtype=k3.dtype, device=k3.device).unsqueeze(0).repeat(b, 1, 1) out[:, :3, :3] = k3 return out def warmup_cosine_lr(step: int, warmup: int, total: int, lr0: float, lr1: float) -> float: if step <= warmup: return lr0 * float(step) / float(max(1, warmup)) t = (step - warmup) / float(max(1, total - warmup)) cos = 0.5 * (1 + np.cos(np.pi * t)) return lr1 + (lr0 - lr1) * cos @torch.no_grad() def compute_frustum_mask( depth: torch.Tensor, tgt_w2c: torch.Tensor, src_w2c: torch.Tensor, src_k3: torch.Tensor, tgt_k3: torch.Tensor, img_h: int, img_w: int, source_img_h: int | None = None, source_img_w: int | None = None, depth_min: float = 0.05, margin: float = 0.05, ) -> torch.Tensor: dev = depth.device f32 = torch.float32 src_h = int(img_h if source_img_h is None else source_img_h) src_w = int(img_w if source_img_w is None else source_img_w) d = depth[0, 0].to(f32) valid = d > depth_min vy, vx = torch.meshgrid( torch.arange(img_h, device=dev, dtype=f32), torch.arange(img_w, device=dev, dtype=f32), indexing="ij", ) fx_t = tgt_k3[0, 0, 0].to(f32) fy_t = tgt_k3[0, 1, 1].to(f32) cx_t = tgt_k3[0, 0, 2].to(f32) cy_t = tgt_k3[0, 1, 2].to(f32) X_t = (vx - cx_t) / fx_t * d Y_t = (vy - cy_t) / fy_t * d Z_t = d pts_t = torch.stack([X_t, Y_t, Z_t], dim=-1).reshape(-1, 3) c2w_t = torch.linalg.inv(tgt_w2c[0].to(f32)) pts_w = pts_t @ c2w_t[:3, :3].T + c2w_t[:3, 3][None, :] w2c_s = src_w2c[0].to(f32) pts_s = pts_w @ w2c_s[:3, :3].T + w2c_s[:3, 3][None, :] Z_s = pts_s[:, 2].clamp(min=1e-4) fx_s = src_k3[0, 0, 0].to(f32) fy_s = src_k3[0, 1, 1].to(f32) cx_s = src_k3[0, 0, 2].to(f32) cy_s = src_k3[0, 1, 2].to(f32) u_s = pts_s[:, 0] / Z_s * fx_s + cx_s v_s = pts_s[:, 1] / Z_s * fy_s + cy_s half_w = (src_w - 1) * 0.5 half_h = (src_h - 1) * 0.5 x_ndc = (u_s - half_w) / half_w y_ndc = (v_s - half_h) / half_h in_frust = ( (x_ndc.abs() <= 1.0 + margin) & (y_ndc.abs() <= 1.0 + margin) & (pts_s[:, 2] > 0) ) mask = in_frust.reshape(img_h, img_w).float() mask = mask * valid.float() return mask[None, None]