Source code for mani_skill.utils.structs.render_camera

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Union

from mani_skill.utils.geometry.rotation_conversions import quaternion_to_matrix

if TYPE_CHECKING:
    from mani_skill.envs.scene import ManiSkillScene

import numpy as np
import sapien
import sapien.physx as physx
import sapien.render
import torch

from mani_skill.render import SAPIEN_RENDER_SYSTEM
from mani_skill.utils import common
from mani_skill.utils.structs.actor import Actor
from mani_skill.utils.structs.link import Link
from mani_skill.utils.structs.pose import Pose

if SAPIEN_RENDER_SYSTEM == "3.1":
    sapien.render.RenderCameraGroup = "oldtype"  # type: ignore

# NOTE (stao): commented out functions are functions that are not confirmed to be working in the wrapped class but the original class has


@dataclass
[docs]class RenderCamera: """ Wrapper around sapien.render.RenderCameraComponent """
[docs] _render_cameras: list[sapien.render.RenderCameraComponent]
[docs] name: str
# NOTE (stao): I cannot seem to use ManiSkillScene as a type here, it complains it is undefined despite using TYPE_CHECKING variable. Without typchecking there is a ciruclar import error
[docs] scene: Any
[docs] camera_group: sapien.render.RenderCameraGroup = None
[docs] mount: Union[Actor, Link] = None
# we cache model and extrinsic matrices since the code here supports computing these when the camera is mounted and these are always changing
[docs] _cached_model_matrix: torch.Tensor = None
[docs] _cached_extrinsic_matrix: torch.Tensor = None
# NOTE (stao): default @cache from functools seems to cause CPU memory leaks in dataclasses, so we cache ourselves here
[docs] _cached_intrinsic_matrix: torch.Tensor = None
[docs] _cached_local_pose: Pose = None
@classmethod
[docs] def create( cls, render_cameras: list[sapien.render.RenderCameraComponent], scene: Any, mount: Union[Actor, Link] = None, ): w, h = ( render_cameras[0].width, render_cameras[0].height, ) # Currently camera groups must have all the same shape shared_name = "_".join(render_cameras[0].name.split("_")[1:]) for render_camera in render_cameras: assert (render_camera.width, render_camera.height) == ( w, h, ), "all passed in render cameras must have the same width and height" return cls( _render_cameras=render_cameras, scene=scene, name=shared_name, mount=mount )
[docs] def get_name(self) -> str: return self.name
[docs] def __hash__(self): return self._render_cameras[0].__hash__()
# -------------------------------------------------------------------------- # # Functions from RenderCameraComponent # -------------------------------------------------------------------------- #
[docs] def get_extrinsic_matrix(self): if self.scene.gpu_sim_enabled: if self._cached_extrinsic_matrix is not None: return self._cached_extrinsic_matrix ros2opencv = torch.tensor( [[0, 0, 1, 0], [-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 0, 1]], device=self.scene.device, dtype=torch.float32, ).T res = ( ros2opencv @ self.get_global_pose().inv().to_transformation_matrix() )[:, :3, :4] if self.mount is None: self._cached_extrinsic_matrix = res return res else: return common.to_tensor(self._render_cameras[0].get_extrinsic_matrix())[ None, : ]
[docs] def get_far(self) -> float: return self._render_cameras[0].get_far()
[docs] def get_global_pose(self) -> Pose: return self.global_pose
[docs] def get_height(self) -> int: return self._render_cameras[0].get_height()
[docs] def get_intrinsic_matrix(self): if self._cached_intrinsic_matrix is None: self._cached_intrinsic_matrix = common.to_tensor( np.array([cam.get_intrinsic_matrix() for cam in self._render_cameras]), device=self.scene.device, ) return self._cached_intrinsic_matrix
[docs] def get_local_pose(self) -> Pose: if self._cached_local_pose is None: if self.scene.gpu_sim_enabled: ps = np.array( [ np.concatenate([cam.get_local_pose().p, cam.get_local_pose().q]) for cam in self._render_cameras ] ) self._cached_local_pose = Pose.create( common.to_tensor(ps), device=self.scene.device ) else: self._cached_local_pose = Pose.create_from_pq( self._render_cameras[0].get_local_pose().p, self._render_cameras[0].get_local_pose().q, ) return self._cached_local_pose
[docs] def get_model_matrix(self): if self.scene.gpu_sim_enabled: if self._cached_model_matrix is not None: return self._cached_model_matrix # NOTE (stao): This code is based on SAPIEN. It cannot expose GPU buffers of this data of all cameras directly so # we have to compute it here based on how SAPIEN does it: POSE_GL_TO_ROS = Pose.create_from_pq(p=[0, 0, 0], q=[-0.5, -0.5, 0.5, 0.5]) pose = self.get_global_pose() * POSE_GL_TO_ROS b = len(pose.raw_pose) qmat = torch.zeros((b, 4, 4), device=self.scene.device) qmat[:, :3, :3] = quaternion_to_matrix(pose.q) qmat[:, -1, -1] = 1 pmat = torch.eye(4, device=self.scene.device)[None, ...].repeat( len(qmat), 1, 1 ) pmat[:, :3, 3] = pose.p res = pmat @ qmat if self.mount is None: self._cached_model_matrix = res return res else: return common.to_tensor(self._render_cameras[0].get_model_matrix())[None, :]
[docs] def get_near(self) -> float: return self._render_cameras[0].get_near()
[docs] def get_picture(self, names: Union[str, list[str]]) -> list[torch.Tensor]: if isinstance(names, str): names = [names] if self.scene.gpu_sim_enabled and not self.scene.parallel_in_single_scene: if SAPIEN_RENDER_SYSTEM == "3.0": return [ self.camera_group.get_picture_cuda(name).torch() for name in names ] elif SAPIEN_RENDER_SYSTEM == "3.1": return [x.torch() for x in self.camera_group.get_cuda_pictures(names)] else: if self.scene.backend.render_backend == "sapien_cuda": return [ self._render_cameras[0].get_picture_cuda(name).torch()[None, ...] for name in names ] else: return [ common.to_tensor(self._render_cameras[0].get_picture(name))[ None, ... ] for name in names ]
# def get_picture_cuda(self, name: str): # return self._render_cameras[0].get_picture_cuda(name) # def get_picture_names(self) -> list[str]: # return self._render_cameras[0].get_picture_names()
[docs] def get_projection_matrix(self): return self._render_cameras[0].get_projection_matrix()
[docs] def get_skew(self) -> float: return self._render_cameras[0].get_skew()
[docs] def get_width(self) -> int: return self._render_cameras[0].get_width()
[docs] def set_far(self, far: float) -> None: for obj in self._render_cameras: obj.set_far(far)
[docs] def set_focal_lengths(self, fx: float, fy: float) -> None: for obj in self._render_cameras: obj.set_focal_lengths(fx, fy)
[docs] def set_fovx(self, fov: float, compute_y: bool = True) -> None: for obj in self._render_cameras: obj.set_fovx(fov, compute_y)
[docs] def set_fovy(self, fov: float, compute_x: bool = True) -> None: for obj in self._render_cameras: obj.set_fovy(fov, compute_x)
[docs] def set_gpu_pose_batch_index(self, arg0: int) -> None: for obj in self._render_cameras: obj.set_gpu_pose_batch_index(arg0)
[docs] def set_local_pose(self, arg0: sapien.Pose) -> None: for obj in self._render_cameras: obj.set_local_pose(arg0) self._cached_local_pose = None
[docs] def set_near(self, near: float) -> None: for obj in self._render_cameras: obj.set_near(near)
[docs] def set_perspective_parameters( self, near: float, far: float, fx: float, fy: float, cx: float, cy: float, skew: float, ) -> None: for obj in self._render_cameras: obj.set_perspective_parameters(near, far, fx, fy, cx, cy, skew)
[docs] def set_principal_point(self, cx: float, cy: float) -> None: for obj in self._render_cameras: obj.set_principal_point(cx, cy)
# @typing.overload # def set_property(self, name: str, value: float) -> None: # self._render_cameras[0].set_property(name, value)
[docs] def set_property(self, name: str, value: Any) -> None: """change properties of the camera. This is not well documented at the moment and is a heavily overloaded function. At the moment you can do this: - set_property("toneMapper", value) where value is 0 (gamma), 1 (sRGB), 2 (filmic) change the color management used. Default is 0 (gamma) - set_property("exposure", value) where value is the exposure. Default is 1.0 """ self._render_cameras[0].set_property(name, value)
[docs] def set_skew(self, skew: float) -> None: for obj in self._render_cameras: obj.set_skew(skew)
# def set_texture(self, name: str, texture: RenderTexture) -> None: # self._render_cameras[0].set_texture(name, texture) # def set_texture_array(self, name: str, textures: list[RenderTexture]) -> None: # self._render_cameras[0].set_texture_array(name, textures)
[docs] def take_picture(self) -> None: if self.scene.gpu_sim_enabled: self.camera_group.take_picture() else: self._render_cameras[0].take_picture()
@property
[docs] def _cuda_buffer(self): return self._render_cameras[0]._cuda_buffer
@property
[docs] def cx(self) -> float: return self._render_cameras[0].cx
@property
[docs] def cy(self) -> float: return self._render_cameras[0].cy
@property
[docs] def far(self) -> float: return self._render_cameras[0].far
@far.setter def far(self, arg1: float) -> None: for obj in self._render_cameras: obj.far = arg1 @property
[docs] def fovx(self) -> float: return self._render_cameras[0].fovx
@property
[docs] def fovy(self) -> float: return self._render_cameras[0].fovy
@property
[docs] def fx(self) -> float: return self._render_cameras[0].fx
@property
[docs] def fy(self) -> float: return self._render_cameras[0].fy
@property
[docs] def global_pose(self) -> sapien.Pose: if self.scene.gpu_sim_enabled: if self.mount is not None: return self.mount.pose * self.get_local_pose() return self.get_local_pose() else: return Pose.create_from_pq( self._render_cameras[0].get_global_pose().p, self._render_cameras[0].get_global_pose().q, )
# TODO (stao): These properties should be torch tensors in the future @property
[docs] def height(self) -> int: return self._render_cameras[0].height
@property
[docs] def local_pose(self) -> sapien.Pose: return self._render_cameras[0].local_pose
@local_pose.setter def local_pose(self, arg1: sapien.Pose) -> None: for obj in self._render_cameras: obj.local_pose = arg1 @property
[docs] def near(self) -> float: return self._render_cameras[0].near
@near.setter def near(self, arg1: float) -> None: for obj in self._render_cameras: obj.near = arg1 @property
[docs] def skew(self) -> float: return self._render_cameras[0].skew
@skew.setter def skew(self, arg1: float) -> None: for obj in self._render_cameras: obj.skew = arg1 @property
[docs] def width(self) -> int: return self._render_cameras[0].width