Source code for eradiate.scenes.measure._perspective

from __future__ import annotations

import attrs
import mitsuba as mi
import numpy as np
import pint
import pinttr

from ._core import Measure
from ... import validators
from ...attrs import documented, parse_docs
from ...units import symbol
from ...units import unit_context_config as ucc
from ...units import unit_context_kernel as uck
from ...units import unit_registry as ureg


[docs] @parse_docs @attrs.define(eq=False, slots=False) class PerspectiveCameraMeasure(Measure): """ Perspective camera scene element [``perspective``]. This scene element is a thin wrapper around the ``perspective`` sensor kernel plugin. It positions a perspective camera based on a set of vectors, specifying the origin, viewing direction and 'up' direction of the camera. """ # -------------------------------------------------------------------------- # Fields and properties # -------------------------------------------------------------------------- spp: int = documented( attrs.field(default=32, converter=int, validator=validators.is_positive), doc="Number of samples per pixel.", type="int", default="32", ) _film_resolution: tuple[int, int] = documented( attrs.field( default=(32, 32), converter=tuple, validator=attrs.validators.deep_iterable( member_validator=attrs.validators.instance_of(int), iterable_validator=validators.has_len(2), ), ), doc="Film resolution as a (width, height) 2-tuple.", type="tuple of int", init_type="array-like", default="(32, 32)", ) @property def film_resolution(self) -> tuple[int, int]: return self._film_resolution origin: pint.Quantity = documented( pinttr.field( factory=lambda: [1, 1, 1] * ureg.m, validator=[validators.has_len(3), pinttr.validators.has_compatible_units], units=ucc.deferred("length"), ), doc="A 3-vector specifying the position of the camera.\n" "\n" "Unit-enabled field (default: ucc['length']).", type="quantity", init_type="array-like", default="[1, 1, 1] m", ) target: pint.Quantity = documented( pinttr.field( factory=lambda: [0, 0, 0] * ureg.m, validator=[validators.has_len(3), pinttr.validators.has_compatible_units], units=ucc.deferred("length"), ), doc="Point location targeted by the camera.\n" "\n" "Unit-enabled field (default: ucc['length']).", type="quantity", init_type="array-like", default="[0, 0, 0] m", ) @target.validator @origin.validator def _target_origin_validator(self, attribute, value): if np.allclose(self.target, self.origin): raise ValueError( f"While initializing {attribute}: " f"Origin and target must not be equal, " f"got target = {self.target}, origin = {self.origin}" ) up: np.ndarray = documented( attrs.field( factory=lambda: [0, 0, 1], converter=np.array, validator=validators.has_len(3), ), doc="A 3-vector specifying the up direction of the camera.\n" "This vector must be different from the camera's viewing direction,\n" "which is given by ``target - origin``.", type="array", default="[0, 0, 1]", ) @up.validator def _up_validator(self, attribute, value): direction = self.target - self.origin if np.allclose(np.cross(direction, value), 0): raise ValueError( f"While initializing '{attribute.name}': " f"up direction must not be colinear with viewing direction, " f"got up = {self.up}, direction = {direction}" ) far_clip: pint.Quantity = documented( pinttr.field( default=1e4 * ureg.km, units=ucc.deferred("length"), ), doc="Distance to the far clip plane.\n" "\n" "Unit-enabled field (default: ucc[length]).", type="quantity", init_type="quantity of float", default="10 000 km", ) fov: pint.Quantity = documented( pinttr.field(default=50.0 * ureg.deg, units=ureg.deg), doc="Camera field of view.\n\nUnit-enabled field (default: degree).", type="quantity", init_type="quantity or float", default="50°", ) # -------------------------------------------------------------------------- # Kernel dictionary generation # -------------------------------------------------------------------------- @property def kernel_type(self) -> str: # Inherit docstring return "perspective" @property def template(self) -> dict: # Inherit docstring result = super().template result["far_clip"] = self.far_clip.m_as(uck.get("length")) result["fov"] = self.fov.m_as(ureg.deg) target = self.target.m_as(uck.get("length")) origin = self.origin.m_as(uck.get("length")) result["to_world"] = mi.ScalarTransform4f.look_at( origin=origin, target=target, up=self.up ) return result # -------------------------------------------------------------------------- # Post-processing information # -------------------------------------------------------------------------- @property def var(self) -> tuple[str, dict]: return "radiance", { "standard_name": "radiance", "long_name": "radiance", "units": symbol(uck.get("radiance")), }