"""Utility functions for conversions between different representations.""" from typing import Optional import torch def skew_symmetric(v: torch.Tensor) -> torch.Tensor: """Create a skew-symmetric matrix from a (batched) vector of size (..., 3). Args: (torch.Tensor): Vector of size (..., 3). Returns: (torch.Tensor): Skew-symmetric matrix of size (..., 3, 3). """ z = torch.zeros_like(v[..., 0]) return torch.stack( [ z, -v[..., 2], v[..., 1], v[..., 2], z, -v[..., 0], -v[..., 1], v[..., 0], z, ], dim=-1, ).reshape(v.shape[:-1] + (3, 3)) def rad2rotmat( roll: torch.Tensor, pitch: torch.Tensor, yaw: Optional[torch.Tensor] = None ) -> torch.Tensor: """Convert (batched) roll, pitch, yaw angles (in radians) to rotation matrix. Args: roll (torch.Tensor): Roll angle in radians. pitch (torch.Tensor): Pitch angle in radians. yaw (torch.Tensor, optional): Yaw angle in radians. Defaults to None. Returns: torch.Tensor: Rotation matrix of shape (..., 3, 3). """ if yaw is None: yaw = roll.new_zeros(roll.shape) Rx = pitch.new_zeros(pitch.shape + (3, 3)) Rx[..., 0, 0] = 1 Rx[..., 1, 1] = torch.cos(pitch) Rx[..., 1, 2] = torch.sin(pitch) Rx[..., 2, 1] = -torch.sin(pitch) Rx[..., 2, 2] = torch.cos(pitch) Ry = yaw.new_zeros(yaw.shape + (3, 3)) Ry[..., 0, 0] = torch.cos(yaw) Ry[..., 0, 2] = -torch.sin(yaw) Ry[..., 1, 1] = 1 Ry[..., 2, 0] = torch.sin(yaw) Ry[..., 2, 2] = torch.cos(yaw) Rz = roll.new_zeros(roll.shape + (3, 3)) Rz[..., 0, 0] = torch.cos(roll) Rz[..., 0, 1] = torch.sin(roll) Rz[..., 1, 0] = -torch.sin(roll) Rz[..., 1, 1] = torch.cos(roll) Rz[..., 2, 2] = 1 return Rz @ Rx @ Ry def fov2focal(fov: torch.Tensor, size: torch.Tensor) -> torch.Tensor: """Compute focal length from (vertical/horizontal) field of view. Args: fov (torch.Tensor): Field of view in radians. size (torch.Tensor): Image height / width in pixels. Returns: torch.Tensor: Focal length in pixels. """ return size / 2 / torch.tan(fov / 2) def focal2fov(focal: torch.Tensor, size: torch.Tensor) -> torch.Tensor: """Compute (vertical/horizontal) field of view from focal length. Args: focal (torch.Tensor): Focal length in pixels. size (torch.Tensor): Image height / width in pixels. Returns: torch.Tensor: Field of view in radians. """ return 2 * torch.arctan(size / (2 * focal)) def pitch2rho(pitch: torch.Tensor, f: torch.Tensor, h: torch.Tensor) -> torch.Tensor: """Compute the distance from principal point to the horizon. Args: pitch (torch.Tensor): Pitch angle in radians. f (torch.Tensor): Focal length in pixels. h (torch.Tensor): Image height in pixels. Returns: torch.Tensor: Relative distance to the horizon. """ return torch.tan(pitch) * f / h def rho2pitch(rho: torch.Tensor, f: torch.Tensor, h: torch.Tensor) -> torch.Tensor: """Compute the pitch angle from the distance to the horizon. Args: rho (torch.Tensor): Relative distance to the horizon. f (torch.Tensor): Focal length in pixels. h (torch.Tensor): Image height in pixels. Returns: torch.Tensor: Pitch angle in radians. """ return torch.atan(rho * h / f) def rad2deg(rad: torch.Tensor) -> torch.Tensor: """Convert radians to degrees. Args: rad (torch.Tensor): Angle in radians. Returns: torch.Tensor: Angle in degrees. """ return rad / torch.pi * 180 def deg2rad(deg: torch.Tensor) -> torch.Tensor: """Convert degrees to radians. Args: deg (torch.Tensor): Angle in degrees. Returns: torch.Tensor: Angle in radians. """ return deg / 180 * torch.pi