Source code for skcomponents.processing.noise
import numpy as np
from skcomponents.processing.processor import SignalProcessor
from typing import Union
[docs]class GaussianNoise(SignalProcessor):
"""
Add gaussian noise to a signal
Parameters
----------
noise_level : float
noise level in fraction of signal (1% = 0.01)
"""
def __init__(self, noise_level: float = 0.01, relative=True):
self.noise_level = noise_level
self._relative = relative
[docs] def process_signal(self, signal: np.ndarray) -> np.ndarray:
if self._relative:
noise = signal * self.noise_level * np.random.standard_normal(signal.shape)
else:
noise = self.noise_level * np.random.standard_normal(signal.shape)
return signal + noise
[docs] def noise_estimate(self, signal: np.ndarray) -> np.ndarray:
if self._relative:
return self.noise_level * signal
else:
return self.noise_level * np.ones_like(signal)
[docs]class PoissonCounting(SignalProcessor):
"""
Add poisson counting error to the signal
"""
[docs] def process_signal(self, signal: np.ndarray) -> np.ndarray:
try:
mask = np.isnan(signal)
if np.any(mask):
signal[mask] = 0
return np.random.poisson(signal)
except ValueError as e:
print(e)
[docs] def noise_estimate(self, signal: np.ndarray) -> np.ndarray:
return np.sqrt(signal)
[docs]class SimpleDarkCurrent(SignalProcessor):
def __init__(self, dark_current: Union[float, np.ndarray],
exposure_time: float = 1.0,
temperature: np.ndarray = None,
sensor_temperature: float = None,
noisy: bool = True):
"""
Parameters
----------
dark_current : float
dark current in Amperes. If a single number is provided it is assumed constant for all temperatures. If
an array is provided a correspending `temperature` of the same size should be given.
exposure_time : float
exposure time of the image
temperature :
array of temperatures at which dark current is defined
sensor_temperature :
temperature of the sensor. Used only if an array of `dark_current`, `temperature` is provided.
noisy : bool
if True the dark current signal is assumed to be have a Poisson distribution. Otherwise a constant value is
added.
"""
self.dark_current = dark_current
self.temperature = temperature
self.sensor_temperature = sensor_temperature
self.exposure_time = exposure_time
self.noisy = noisy
@property
def electrons_per_second(self):
if self.temperature is not None:
# interpolate in log(DC), inverse temperature space
tidx = np.argsort(1 / self.temperature)
temp = 1 / self.temperature[tidx]
dc_array = np.log(self.dark_current[tidx])
dc = np.exp(np.interp(1 / self.sensor_temperature, temp, dc_array,
left=dc_array[0], right=dc_array[-1]))
else:
dc = self.dark_current
return dc * 6.241509074e18
[docs] def process_signal(self, signal: np.ndarray) -> np.ndarray:
if self.noisy:
if type(self.exposure_time) is np.ndarray:
if signal.shape[-1] == self.exposure_time.shape[0]:
# return signal * self.exposure_time
dc = PoissonCounting().process_signal(np.ones_like(signal) * (self.electrons_per_second * self.exposure_time))
elif signal.shape[0] == self.exposure_time.shape[0]:
# return signal * self.exposure_time[:, np.newaxis, np.newaxis]
dc = PoissonCounting().process_signal(np.ones_like(signal) * (self.electrons_per_second * self.exposure_time)[:, np.newaxis, np.newaxis])
else:
raise ValueError('Cannot broadcast signal and exposure_time, '
'wavelength should be the first or last dimension')
else:
dc = PoissonCounting().process_signal(np.ones_like(signal) * (self.electrons_per_second * self.exposure_time))
else:
dc = self.electrons_per_second * self.exposure_time
return signal + dc
[docs] def noise_estimate(self, signal: np.ndarray) -> np.ndarray:
return np.ones_like(signal) * np.sqrt(self.electrons_per_second * self.exposure_time)
[docs]class DarkCurrent(SignalProcessor):
"""
Add dark current to a signal
Parameters
----------
exposure_time : float
exposure time of the measurement [s]
temperature : float
ccd temperature [K]
tref : float
reference temperature of the dark current parameters [K]
e_pixel_s : float
electrons per
"""
def __init__(self, exposure_time: float = None,
temperature: float = 273.15,
tref: float = 273.15,
e_pixel_s: float = 200,
c1: float = 1.14e6,
c2: float = 9080,
ratio_exp: float = 3.0):
self.exposure_time = exposure_time
self.readout_time = 0
self.temperature = temperature
self.tref = tref
self.e_pixel_s = e_pixel_s
self.c1 = c1
self.c2 = c2
self.ratio_exp = ratio_exp
[docs] def process_signal(self, signal: np.ndarray, temperature: float = None, exposure_time=None) -> np.ndarray:
if exposure_time is None:
exposure_time = self.exposure_time
if exposure_time is None:
raise ValueError('exposure not set')
dark_current = self.dark_signal_rate(temperature) * (self.readout_time + self.exposure_time)
return signal + dark_current
[docs] def dark_signal_rate(self, ccd_temperature: float = None):
if ccd_temperature is None:
ccd_temperature = self.temperature
# Modified Doug's equation
dark_signal_ratio_at_Tref = self.c1 * (self.tref ** self.ratio_exp) * np.exp(-self.c2 / self.tref)
dark_signal_ratio_TCCD = self.c1 * (ccd_temperature ** self.ratio_exp) * np.exp(-self.c2 / ccd_temperature)
dark_signal_ratio_TCCD /= dark_signal_ratio_at_Tref
dark_signal_rate_TCCD = dark_signal_ratio_TCCD * self.e_pixel_s
return dark_signal_rate_TCCD