Source code for darsia.signals.models.linearmodel

"""Module containing a linear (affine) conversion from signals to data.

"""

from __future__ import annotations

from typing import Literal, Optional, Union

import cv2
import numpy as np

import darsia


[docs] class ScalingModel(darsia.Model): """Linear model (plain scaling).""" def __init__( self, key: str = "", **kwargs, ) -> None: self._scaling = kwargs.get(key + "scaling", 1.0) self.num_parameters = 1 self.volumes = None
[docs] def update( self, scaling: Optional[float] = None, ) -> None: """ Update of internal parameters. Args: scaling (float, optional): slope """ if scaling is not None: self._scaling = scaling
[docs] def update_model_parameters( self, parameters: np.ndarray, dofs: Optional[Union[list[Literal["scaling"]], Literal["all"]]] = None, ) -> None: """ Short cut to update scaling and offset parameters using a general function signature. The main use is the model calibration. Do not update the offset. Args: parameters (np.ndarray): 2-array containing scaling and offset values. """ if dofs is None or dofs == "all" or set(dofs) == set(["scaling"]): self.update(scaling=parameters[0]) else: raise ValueError(f"Unknown dof {dofs}.")
def __call__(self, img: np.ndarray) -> np.ndarray: """ Application of linear model. Args: img (np.ndarray): image Returns: np.ndarray: converted signal """ if np.isclose(self._scaling, 1.0): return img else: return self._scaling * img
[docs] class LinearModel(darsia.Model): """Linear model, applying an affine conversion for signals to data.""" def __init__( self, key: str = "", **kwargs, ) -> None: self._scaling = kwargs.get(key + "scaling", 1.0) self._offset = kwargs.get(key + "offset", 0.0) self.num_parameters = 2 self.volumes = None
[docs] def update( self, scaling: Optional[float] = None, offset: Optional[float] = None, ) -> None: """ Update of internal parameters. Args: scaling (float, optional): slope offset (float, optional): offset """ if scaling is not None: self._scaling = scaling if offset is not None: self._offset = offset
[docs] def update_model_parameters( self, parameters: np.ndarray, dofs: Optional[ Union[list[Literal["scaling", "offset"]], Literal["all"]] ] = None, ) -> None: """ Short cut to update scaling and offset parameters using a general function signature. The main use is the model calibration. Do not update the offset. Args: parameters (np.ndarray): 2-array containing scaling and offset values. """ if dofs is None or dofs == ["all"] or set(dofs) == set(["scaling", "offset"]): self.update(scaling=parameters[0], offset=parameters[1]) elif set(dofs) == set(["scaling"]): self.update(scaling=parameters[0]) elif set(dofs) == set(["offset"]): self.update(offset=parameters[0]) else: raise ValueError(f"Unknown dof {dofs}.")
def __call__(self, img: np.ndarray) -> np.ndarray: """ Application of linear model. Args: img (np.ndarray): image Returns: np.ndarray: converted signal """ return self._scaling * img + self._offset
[docs] class HeterogeneousLinearModel(darsia.Model): """ Linear model, applying an affine conversion for signals to data. """ def __init__( self, labels: np.ndarray, key: str = "", **kwargs, ) -> None: # Organize label info self.labels = labels self.unique_labels = np.unique(labels) self.num_labels = len(self.unique_labels) self.cached_labels = self.labels.copy() # Read model parameters from options self._scaling = kwargs.get( key + "scaling", np.ones(self.num_labels, dtype=float) ) self._offset = kwargs.get( key + "offset", np.zeros(self.num_labels, dtype=float) ) self.num_parameters = 2 * self.num_labels # Convert to arrays if isinstance(self._scaling, list): self._scaling = np.array(self._scaling) if isinstance(self._offset, list): self._offset = np.array(self._offset) # Check compatibility self._compatibility() def _compatibility(self) -> None: # Convert from scalar to vectors if isinstance(self._scaling, float): self._scaling = self._scaling * np.ones(self.num_labels, dtype=float) if isinstance(self._offset, float): self._offset = self._offset * np.ones(self.num_labels, dtype=float) # Compatibility checks assert ( isinstance(self._scaling, np.ndarray) and len(self._scaling) == self.num_labels ) assert ( isinstance(self._offset, np.ndarray) and len(self._offset) == self.num_labels ) self.volumes = None
[docs] def update( self, scaling: Optional[np.ndarray] = None, offset: Optional[np.ndarray] = None, ) -> None: """ Update of internal parameters. Args: scaling (np.ndarray, optional): slope offset (np.ndarray, optional): offset """ if scaling is not None: self._scaling = scaling if offset is not None: self._offset = offset self._compatibility()
[docs] def update_model_parameters( self, parameters: np.ndarray, dofs: Optional[ Union[list[Literal["scaling", "offset"]], Literal["all"]] ] = None, ) -> None: """ Short cut to update scaling and offset parameters using a general function signature. Args: parameters (np.ndarray): 2-array containing scaling and offset values. """ if dofs is None or dofs == "all" or set(dofs) == set(["scaling", "offset"]): self.update( scaling=parameters[: self.num_labels], offset=parameters[self.num_labels :], ) elif set(dofs) == set(["scaling"]): self.update(scaling=parameters[: self.num_labels]) elif set(dofs) == set(["offset"]): self.update(offset=parameters[: self.num_labels])
def __call__(self, img: np.ndarray) -> np.ndarray: """ Application of linear model. Args: img (np.ndarray): image Returns: np.ndarray: converted signal """ # Potentially need to resize labels. if img.shape != self.cached_labels: self.cached_labels = cv2.resize( self.labels, tuple(reversed(img.shape[:2])), interpolation=cv2.INTER_NEAREST, ) # Initialize result result = np.zeros_like(img, dtype=img.dtype) for l_counter, label in enumerate(self.unique_labels): tmp = self._scaling[l_counter] * img + self._offset[l_counter] mask = self.cached_labels == label result[mask] = tmp[mask] return result