from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Tuple
from loguru import logger
from ..data_structure.constants import Matrix, PathLike, Vector
from ..object.object_base import ObjectBase
from ..object.object_utils import ObjectUtilsBase
from ..utils import Validator
[docs]
class CameraBase(ABC, ObjectBase):
"""Base camera class."""
_object_utils = ObjectUtilsBase
@classmethod
def from_param(cls, param):
...
[docs]
@classmethod
def spawn(
cls,
camera_name: str = None,
location: Vector = (0, 0, 0),
rotation: Vector = (0, 0, 0),
fov: float = 90.0,
) -> 'CameraBase':
"""Spawn a camera in the engine.
Args:
name (str, optional): name of the camera
location (Vector, optional): location of the camera. Defaults to (0, 0, 0).
rotation (Vector, optional): rotation of the camera. Defaults to (0, 0, 0).
fov (float, optional): field of view of the camera. Defaults to 90.0.
Returns:
CameraBase: New camera
"""
if camera_name is None:
camera_name = cls._object_utils.generate_obj_name(obj_type='camera')
cls._object_utils.validate_new_name(camera_name)
Validator.validate_vector(location, 3)
Validator.validate_vector(rotation, 3)
Validator.validate_argument_type(fov, [float, int])
cls._spawn_in_engine(camera_name=camera_name, location=location, rotation=rotation, fov=fov)
logger.info(f'[cyan]Spawned[/cyan] camera "{camera_name}"')
return cls(camera_name)
@property
def fov(self) -> float:
"""Field of view of the camera lens, in degrees."""
return self._get_fov_in_engine(self._name)
@fov.setter
def fov(self, value):
Validator.validate_argument_type(value, [float, int])
self._set_camera_fov_in_engine(self._name, value)
[docs]
def get_KRT(self) -> Tuple[List, List, Vector]:
"""Get the intrinsic and extrinsic parameters of the camera.
Returns:
Tuple[Matrix, Matrix, Vector]: K, R, T
"""
return self._get_KRT_in_engine(self._name)
[docs]
def set_KRT(self, K: List, R: List, T: Vector) -> None:
"""Set the intrinsic and extrinsic parameters of the camera.
Args:
K (Matrix): 3x3 intrinsic matrix
R (Matrix): 3x3 rotation matrix
T (Vector): 3x1 translation vector
"""
self._set_KRT_in_engine(self._name, K, R, T)
[docs]
def dump_params(self, output_path: PathLike) -> None:
"""Dump the intrinsic and extrinsic parameters of the camera to a file.
Args:
output_path (PathLike): path to the camera parameter file
"""
from .camera_parameter import CameraParameter
# mkdir
output_path = Path(output_path).resolve()
output_path.parent.mkdir(parents=True, exist_ok=True)
# dump
K, R, T = self.get_KRT()
camera_param = CameraParameter(K=K, R=R, T=T, world2cam=True)
camera_param.dump(output_path.as_posix())
logger.debug(f'Camera parameters dumped to "{output_path.as_posix()}"')
[docs]
def look_at(self, target: Vector) -> None:
"""Set the camera to look at the target.
Args:
target (Vector): [x, y, z] coordinates of the target, in units of meters.
"""
# TODO: get rid of _look_at_in_engine, calculate the rotation matrix from the direction vector
# direction = np.array(target) - np.array(self.location)
# self.rotation = self._object_utils.direction_to_euler(direction)
self._look_at_in_engine(self._name, target)
def __repr__(self) -> str:
return f'<Camera "{self._name}">'
#################################
#### RPC METHODS (Private) ####
#################################
# ----- Getter ----- #
@staticmethod
@abstractmethod
def _get_KRT_in_engine(name: str):
pass
@staticmethod
@abstractmethod
def _get_fov_in_engine(name: str) -> float:
pass
# ----- Setter ----- #
@staticmethod
@abstractmethod
def _set_KRT_in_engine(name, K, R, T):
pass
@staticmethod
@abstractmethod
def _set_camera_fov_in_engine(name: str, value: float):
pass
@staticmethod
@abstractmethod
def _spawn_in_engine(camera_name, location, rotation, fov):
pass
@staticmethod
@abstractmethod
def _look_at_in_engine(name, target):
pass