Source code for synpivimage.camera

import pathlib
from typing import Tuple, Union, Optional

import numpy as np
from ontolutils.namespacelib import QUDT_UNIT, QUDT_KIND
from pivmetalib import PIVMETA
from pivmetalib.m4i import NumericalVariable
from pydantic import BaseModel
from ssnolib.ssno import StandardName
from typing_extensions import Annotated

from . import noise
from .component import Component, load_jsonld
from .particles import Particles, model_image_particles
from .validation import PositiveInt, PositiveFloat, ValueRange

Efficiency = Annotated[float, ValueRange(0, 1)]
FillRatio = Annotated[float, ValueRange(0, 1)]


[docs]class Camera(BaseModel, Component): """Camera Model""" nx: PositiveInt ny: PositiveInt bit_depth: PositiveInt qe: Efficiency sensitivity: Efficiency baseline_noise: float dark_noise: float shot_noise: bool fill_ratio_x: FillRatio fill_ratio_y: FillRatio particle_image_diameter: PositiveFloat seed: Optional[int] = None @property def size(self) -> int: """Size of the sensor in pixels (nx x ny)""" return int(self.nx * self.ny) @property def max_count(self): """Max count of the sensor, which is computed from the bit depth `b` of the sensor. .. math c_{max} = 2**b -1 """ return int(2 ** self.bit_depth - 1) def _quantize(self, electrons) -> Tuple[np.ndarray, int]: """Quantize the electrons to the bit depth Parameters ---------- electrons : np.ndarray The number of electrons Returns ------- np.ndarray The quantized image int The number of saturated pixels """ max_adu = self.max_count adu = electrons * self.sensitivity _saturated_pixels = adu > max_adu n_saturated_pixels = np.sum(_saturated_pixels) adu[adu > max_adu] = max_adu # model saturation if self.bit_depth == 8: adu = adu.astype(np.uint8) elif self.bit_depth == 16: adu = adu.astype(np.uint16) else: raise ValueError(f"Bit depth {self.bit_depth} not supported") return np.asarray(adu), int(n_saturated_pixels) def _capture(self, irrad_photons): """Capture the image and add noise""" electrons = noise.add_noise(irrad_photons, self.shot_noise, self.baseline_noise, self.dark_noise, self.qe, rs=np.random.RandomState(self.seed) ) return electrons def take_image(self, particles: Particles) -> Tuple[np.ndarray, int]: """capture and quantize the image. .. note:: The definition of the image particle diameter is the diameter of the particle image in pixels, where the normalized gaussian is equal to $e^{-2}$, which is a full width of $4 \sigma$. Returns image and number of saturated pixels. """ # active = particles.active active = particles.in_fov irrad_photons, particles.max_image_photons[active] = model_image_particles( particles[active], nx=self.nx, ny=self.ny, sigmax=self.particle_image_diameter / 4, sigmay=self.particle_image_diameter / 4, fill_ratio_x=self.fill_ratio_x, fill_ratio_y=self.fill_ratio_y ) electrons = self._capture(irrad_photons) particles.image_electrons[active] = self._capture(particles.max_image_photons[active]) particles.image_quantized_electrons[active] = self._quantize(particles.image_electrons[active])[0] return self._quantize(electrons) def model_dump_jsonld(self) -> str: """Return JSON-LD str""" from pivmetalib import pivmeta from pivmetalib import m4i from .codemeta import get_package_meta def _build_variable(value, standard_name=None, unit=None, qkind=None, label=None, description=None): kwargs = {'hasNumericalValue': value} if label: kwargs['label'] = label if standard_name: kwargs['hasStandardName'] = standard_name if unit: kwargs['hasUnit'] = unit if qkind: kwargs['hasKindOfQuantity'] = qkind if description: kwargs['hasVariableDescription'] = description return NumericalVariable( **kwargs ) sn_dict = { 'nx': StandardName(standardName='sensor_pixel_width', unit=QUDT_UNIT.PIXEL), 'ny': StandardName(standardName='sensor_pixel_height', unit=QUDT_UNIT.PIXEL), 'bit_depth': StandardName(standardName='sensor_bit_depth', unit=QUDT_UNIT.BIT), 'fill_ratio_x': StandardName(standardName='sensor_pixel_width_fill_factor', unit=QUDT_UNIT.UNITLESS), 'fill_ratio_y': StandardName(standardName='sensor_pixel_height_fill_factor', unit=QUDT_UNIT.UNITLESS), 'particle_image_diameter': StandardName(standardName='image_particle_diameter', unit=QUDT_UNIT.M) } descr_dict = { 'qe': 'quantum efficiency', 'dark_noise': 'Dark noise is the standard deviation of a gaussian noise model', 'baseline_noise': 'Dark noise is the mean value of a gaussian noise model' } unit_dict = { 'nx': QUDT_UNIT.UNITLESS, 'ny': QUDT_UNIT.UNITLESS, 'bit_depth': QUDT_UNIT.BIT, # 'http://qudt.org/vocab/unit/BIT', } qkind_dict = { 'nx': QUDT_KIND.Dimensionless, 'ny': QUDT_KIND.Dimensionless, 'bit_depth': QUDT_KIND.InformationEntropy # 'http://qudt.org/schema/qudt/CountingUnit' } hasParameter = [] field_dict = self.model_dump(exclude_none=True) shot_noise = field_dict.pop('shot_noise') for key, value in field_dict.items(): hasParameter.append( _build_variable( label=key, value=value, unit=unit_dict.get(key, None), qkind=qkind_dict.get(key, None), standard_name=sn_dict.get(key, None), description=descr_dict.get(key, None) ) ) shot_noise_txt_value = 'true' if shot_noise else 'false' hasParameter.append( m4i.TextVariable(label='shot_noise', hasStringValue=shot_noise_txt_value) ) camera = pivmeta.VirtualCamera( hasSourceCode=get_package_meta(), hasParameter=hasParameter ) return camera.model_dump_jsonld( context={ "@import": 'https://raw.githubusercontent.com/matthiasprobst/pivmeta/main/pivmeta_context.jsonld', # "codemeta": 'https://codemeta.github.io/terms/' } )
[docs] def save_jsonld(self, filename: Union[str, pathlib.Path]): """Save the component to JSON""" filename = pathlib.Path(filename) # .with_suffix('.jsonld') with open(filename, 'w') as f: f.write( self.model_dump_jsonld() ) return filename
[docs] @classmethod def load_jsonld(cls, filename: Union[str, pathlib.Path]): """Load the camera from a JSON-LD file .. note:: This function will return a list of Camera objects. This may be confusing, but there might be multiple lasers in the JSON file. Parameters ---------- filename : Union[str, pathlib.Path] The filename to load the component from Returns ------- List[Camera] List of camera objects """ return load_jsonld(cls, 'pivmeta:VirtualCamera', filename)