Source code for cuvis_ai.transformation.reflectance

from ..node.base import BaseTransformation
from ..node import Node
import numpy as np


[docs] class Reflectance(Node, BaseTransformation): """Generic reflectance calculus: (data - dark) / (white - dark) Requires "Dark" and "White" references to be set in Metadata. Parameters ---------- lower_bound : float, optional Threshold for the smallest allowed value, everything lower will be clamped. Set to None to allow any value. Default: 0.0 upper_bound : float, optional Threshold for the largest allowed value, everything higher will be clamped. Set to None to allow any value. Default: 2.0 """
[docs] def __init__(self, lower_bound: float = 0.0, upper_bound: float = 2.0): super().__init__() self.lower_bound = lower_bound self.upper_bound = upper_bound self.input_size = None self.output_size = None self.set_forward_meta_request( references__Dark=True, references__White=True)
[docs] def forward(self, X: np.ndarray, references__White: np.ndarray, references__Dark: np.ndarray): """Apply reflectance calculus to the data. Returns the data as percentage values between the "Dark" and "White" references set in the meta-data. e.g. A pixel value of 1.0 means that the pixel is as bright as the white reference at this pixel, 1.5 -> 50% brighter, 0.0 -> as bright as the dark reference, -0.2 -> 20% darker than the dark reference. The output values can be clamped by setting :attr:`lower_bond` and :attr:`upper_bound`. Parameters ---------- X : np.ndarray The input data array for which reflectance is computed. Must have the same shape as `references__White` and `references__Dark`. references__White : np.ndarray The white reference array. Defines the maximum intensity for each pixel. references__Dark : np.ndarray The dark reference array. Defines the minimum intensity for each pixel. Returns ------- np.ndarray An array of reflectance values with the same shape as the input `X`. The reflectance values are computed as `(X - references__Dark) / (references__White - references__Dark)` and optionally clamped between the lower and upper bounds. """ numerator = X - references__Dark denominator = references__White - references__Dark # Avoid division by zero reflectance = np.divide(numerator, denominator, where=denominator != 0) reflectance = np.nan_to_num(reflectance) # Clamp values if bounds are set if self.lower_bound is not None or self.upper_bound is not None: lower_bound = self.lower_bound if self.lower_bound is not None else -np.inf upper_bound = self.upper_bound if self.upper_bound is not None else np.inf reflectance = np.clip(reflectance, lower_bound, upper_bound) return reflectance
@Node.output_dim.getter def output_dim(self) -> tuple[int, int, int]: return (-1, -1, -1) @Node.input_dim.getter def input_dim(self) -> tuple[int, int, int]: return (-1, -1, -1)
[docs] def serialize(self, serial_dir: str) -> dict: """Serialize this node.""" data = { "lower": self.lower_bound, "upper": self.upper_bound, } return data
[docs] def load(self, params: dict, serial_dir: str) -> None: """Load this node from a serialized graph.""" try: self.lower_bound = float(params["lower"]) except: self.lower_bound = None try: self.upper_bound = float(params["upper"]) except: self.upper_bound = None