Source code for openwfs.processors.hdrcamera

from concurrent.futures import Future
from typing import Sequence, Union

import numpy as np

from ..core import Detector


[docs] class HDRCamera(Detector): """Wrapper to convert a camera to a high dynamic range (HDR) camera. This wrapper forwards all method calls and attribute acces to the camera object, except for the trigger method, which is replaced by a method that triggers the camera multiple times with different exposure times. todo: find out why the HDR camera is so slow. Args: camera: The camera object to wrap. background: The background value to subtract from each image before scaling. If a sequence is passed, each value corresponds to the background for a different exposure factor. saturation_threshold: The threshold value to consider a pixel saturated, in which case it will not be taken into account in the computation of the result. exposure_factors: A sequence of exposure factors to use for HDR imaging. """ def __init__( self, camera, background: Union[Sequence[float], float], saturation_threshold: int, exposure_factors: Sequence[float] = (1.0, 0.1, 0.01), ): self._camera = camera # object.__setattr__(self, "_camera", camera) self._exposure_factors = np.asarray(exposure_factors, dtype=np.float32) self._saturation_threshold = saturation_threshold self._background = (background,) * len(exposure_factors) if np.isscalar(background) else background super().__init__( data_shape=camera.data_shape, pixel_size=camera.pixel_size, duration=camera.duration, latency=camera.latency, multi_threaded=False, )
[docs] def trigger(self, *args, out=None, immediate=False, **kwargs) -> Future: """Trigger the camera multiple times with different exposure times. todo: at the moment, this method is always blocking, the immediate flag is ignored. Args: *args: Positional arguments to pass to the camera's trigger method. out: Optional output buffer. immediate: If True, trigger the camera immediately. **kwargs: Keyword arguments to pass to the camera's trigger method. Returns: A Future object representing the result of the trigger operation. """ exposure = self._camera.exposure data = None weight = None for factor, background in zip(self._exposure_factors, self._background): self._camera.exposure = exposure * factor frame = (self._camera.trigger(*args, out=out, immediate=True, **kwargs).result()).astype( np.float32 ) - background mask = frame < self._saturation_threshold - background frame[~mask] = 0 if data is None: data = frame weight = mask * factor else: data += frame weight += mask * factor # finally divide by zero, special treatment if there were zeros in the mask mask = weight == 0 weight[mask] = 1.0 data[mask] = self._saturation_threshold data /= weight if out is not None: out[...] = data # Set the exposure back to the original value self._camera.exposure = exposure result = Future() result.set_result(data) return result
def _fetch(self, *args, **kwargs) -> np.ndarray: """Fetch method required by the Detector interface. This method is not implemented for HDRCamera because the trigger method directly returns the result without using the fetch mechanism. Args: *args: Variable length argument list. **kwargs: Arbitrary keyword arguments. Raises: NotImplementedError: Always raised because this method is not implemented. """ raise NotImplementedError("Fetch is not implemented for HDRCamera") def __getattr__(self, name): """Forward attribute access to the wrapped camera object. This method is called when an attribute is not found in the HDRCamera instance. It forwards the attribute access to the wrapped camera object, except for attributes starting with an underscore. Args: name: The name of the attribute to access. Returns: The attribute value from the wrapped camera object. """ # Forward attribute access to the target if name.startswith("_"): return super().__getattr__(name) else: return getattr(self._camera, name) def __setattr__(self, name, value): """Forward attribute setting to the wrapped camera object. This method is called when an attribute is set on the HDRCamera instance. It forwards the attribute setting to the wrapped camera object, except for attributes starting with an underscore. Args: name: The name of the attribute to set. value: The value to set the attribute to. """ # Forward attribute setting to the target if name.startswith("_"): return super().__setattr__(name, value) else: setattr(self._camera, name, value)