anomaly-detection-material-parameters-calibration

Sionna param calibration (research proj)
git clone https://git.ea.contact/anomaly-detection-material-parameters-calibration
Log | Files | Refs | README

camera.py (8087B)


      1 #
      2 # SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
      3 # SPDX-License-Identifier: Apache-2.0
      4 #
      5 """
      6 Implements a camera for rendering of the scene.
      7 A camera defines a viewpoint for rendering.
      8 """
      9 
     10 from .object import Object
     11 import mitsuba as mi
     12 import numpy as np
     13 
     14 
     15 class Camera(Object):
     16     # pylint: disable=line-too-long
     17     r"""Camera(name, position, orientation=[0.,0.,0.], look_at=None)
     18 
     19     A camera defines a position and view direction for rendering the scene.
     20 
     21     In its local coordinate system, a camera looks toward the positive X-axis
     22     with the positive Z-axis being the upward direction.
     23 
     24     Input
     25     ------
     26     name : str
     27         Name.
     28         Cannot be `"preview"`, as it is reserved for the viewpoint of the
     29         interactive viewer.
     30 
     31     position : [3], float
     32         Position :math:`(x,y,z)` [m] as three-dimensional vector
     33 
     34     orientation : [3], float
     35         Orientation :math:`(\alpha, \beta, \gamma)` specified
     36         through three angles corresponding to a 3D rotation
     37         as defined in :eq:`rotation`.
     38         This parameter is ignored if ``look_at`` is not `None`.
     39         Defaults to `[0,0,0]`.
     40 
     41     look_at : [3], float | :class:`~sionna.rt.Transmitter` | :class:`~sionna.rt.Receiver` | :class:`~sionna.rt.RIS` | :class:`~sionna.rt.Camera` | None
     42         A position or the instance of a :class:`~sionna.rt.Transmitter`,
     43         :class:`~sionna.rt.Receiver`, :class:`~sionna.rt.RIS`, or :class:`~sionna.rt.Camera` to look at.
     44         If set to `None`, then ``orientation`` is used to orientate the device.
     45     """
     46 
     47     # The convention of Mitsuba for camera is Y as up and look toward Z+.
     48     # However, Sionna uses Z as up and looks toward X+, for consistency
     49     # with radio devices.
     50     # The following transform peforms a rotation to ensure Sionna's
     51     # convention.
     52     # Note: Mitsuba uses degrees
     53     mi_2_sionna = ( mi.ScalarTransform4f.rotate([0,0,1], 90.0)
     54                 @ mi.ScalarTransform4f.rotate([1,0,0], 90.0) )
     55 
     56     def __init__(self, name, position, orientation=(0.,0.,0.), look_at=None):
     57 
     58         # Keep track of the "to world" transform.
     59         # Initialized to identity.
     60         self._to_world = mi.ScalarTransform4f()
     61 
     62         # Position and orientation are set through this call
     63         super().__init__(name, position, orientation, look_at)
     64 
     65     @property
     66     def position(self):
     67         """
     68         [3], float : Get/set the position :math:`(x,y,z)` as three-dimensional
     69             vector
     70         """
     71         return Camera.world_to_position(self._to_world)
     72 
     73     @position.setter
     74     def position(self, new_position):
     75         new_position = np.array(new_position)
     76         if not (new_position.ndim == 1 and new_position.shape[0] == 3):
     77             msg = "Position must be shaped as [x,y,z] (rank=1 and shape=[3])"
     78             raise ValueError(msg)
     79         # Update transform
     80         to_world = self._to_world.matrix.numpy()
     81         to_world[:3,3] = new_position
     82         self._to_world = mi.ScalarTransform4f(to_world)
     83 
     84     @property
     85     def orientation(self):
     86         r"""
     87         [3], float : Get/set the orientation :math:`(\alpha, \beta, \gamma)`
     88             specified through three angles corresponding to a 3D rotation
     89             as defined in :eq:`rotation`.
     90         """
     91         return Camera.world_to_angles(self._to_world)
     92 
     93     @orientation.setter
     94     def orientation(self, new_orientation):
     95         new_orientation = np.array(new_orientation)
     96         if not (new_orientation.ndim == 1 and new_orientation.shape[0] == 3):
     97             msg = "Orientation must be shaped as [a,b,c] (rank=1 and shape=[3])"
     98             raise ValueError(msg)
     99 
    100         # Mitsuba transform
    101         # Note: Mitsuba uses degrees
    102         new_orientation = new_orientation*180.0/np.pi
    103         rot_x = mi.ScalarTransform4f.rotate([1,0,0], new_orientation[2])
    104         rot_y = mi.ScalarTransform4f.rotate([0,1,0], new_orientation[1])
    105         rot_z = mi.ScalarTransform4f.rotate([0,0,1], new_orientation[0])
    106         rot_mat = rot_z@rot_y@rot_x@Camera.mi_2_sionna
    107         # Translation to keep the current position
    108         trs = mi.ScalarTransform4f.translate(self.position)
    109         to_world = trs@rot_mat
    110         # Update in Mitsuba
    111         self._to_world = to_world
    112 
    113     def look_at(self, target):
    114         r"""
    115         Sets the orientation so that the camera looks at a position, radio
    116         device, or another camera.
    117 
    118         Given a point :math:`\mathbf{x}\in\mathbb{R}^3` with spherical angles
    119         :math:`\theta` and :math:`\varphi`, the orientation of the camera
    120         will be set equal to :math:`(\varphi, \frac{\pi}{2}-\theta, 0.0)`.
    121 
    122         Input
    123         -----
    124         target : [3], float | :class:`~sionna.rt.Transmitter` | :class:`~sionna.rt.Receiver` | :class:`~sionna.rt.Camera` | str
    125             A position or the name or instance of a
    126             :class:`~sionna.rt.Transmitter`, :class:`~sionna.rt.Receiver`, or
    127             :class:`~sionna.rt.Camera` in the scene to look at.
    128         """
    129         # Get position to look at
    130         if isinstance(target, str):
    131             if self.scene is None:
    132                 msg = f"Cannot look for radio device '{target}' as the camera"\
    133                        " is not part of the scene"
    134                 raise ValueError(msg)
    135             item = self.scene.get(target)
    136             if not isinstance(item, Object):
    137                 msg = f"No radio device or camera named '{target}' found."
    138                 raise ValueError(msg)
    139             else:
    140                 target = item.position.numpy()
    141         else:
    142             target = np.array(target).astype(float)
    143             if not ( (target.ndim == 1) and (target.shape[0] == 3) ):
    144                 raise ValueError("`x` must be a three-element vector)")
    145 
    146         # If the position and the target are on a line that is parallel to z,
    147         # then the look-at transform is ill-defined as z is up.
    148         # In this case, we add a small epsilon to x to avoid this.
    149         if np.allclose(self.position[:2], target[:2]):
    150             target[0] = target[0] + 1e-3
    151         # Look-at transform
    152         trf = mi.ScalarTransform4f.look_at(self.position, target,
    153                                            [0.0, 0.0, 1.0]) # Sionna uses Z-up
    154         # Set the rotation matrix of the Mitsuba sensor
    155         self._to_world = trf
    156 
    157     ##############################################
    158     # Internal methods and class functions.
    159     # Should not be appear in the end user
    160     # documentation
    161     ##############################################
    162 
    163     @property
    164     def world_transform(self):
    165         return self._to_world
    166 
    167     @staticmethod
    168     def world_to_angles(to_world):
    169         """
    170         Extract the orientation angles corresponding to a ``to_world`` transform
    171 
    172         Input
    173         ------
    174         to_world : :class:`~mitsuba.ScalarTransform4f`
    175             Transform.
    176 
    177         Output
    178         -------
    179         : [3], float
    180             Orientation angles `[a,b,c]`.
    181         """
    182 
    183         # Undo the rotation to switch from Mitsuba to Sionna convention
    184         to_world = to_world@Camera.mi_2_sionna.inverse()
    185 
    186         # Extract the rotation matrix
    187         to_world = to_world.matrix.numpy()
    188         if to_world.ndim == 3:
    189             to_world = to_world[0]
    190         r_mat = to_world[:3,:3]
    191 
    192         # Compute angles
    193         x_ang = np.arctan2(r_mat[2,1], r_mat[2,2])
    194         y_ang = np.arctan2(-r_mat[2,0],
    195                         np.sqrt(np.square(r_mat[2,1]) + np.square(r_mat[2,2])))
    196         z_ang = np.arctan2(r_mat[1,0], r_mat[0,0])
    197 
    198         return np.array([z_ang, y_ang, x_ang])
    199 
    200     @staticmethod
    201     def world_to_position(to_world):
    202         """
    203         Extract the position corresponding to a ``to_world`` transform
    204 
    205         Input
    206         ------
    207         to_world : :class:`~mitsuba.ScalarTransform4f`
    208             Transform.
    209 
    210         Output
    211         -------
    212         : [3], float
    213             Position `[x,y,z]`.
    214         """
    215 
    216         to_world = to_world.matrix.numpy()
    217         if to_world.ndim == 3:
    218             to_world = to_world[0]
    219         position = to_world[:3,3]
    220         return position