"""
File: qumphy/data/signal_preprocessing/noise.py
Project: 22HLT01 QUMPHY
Contact: oskar.pfeffer@ptb.de
Gitlab: https://gitlab.com/qumphy
Description: Functions for adding noise to the signals.
"""
import numpy as np
import qumphy.data.signal_preprocessing.filters as filters
[docs]
def add_noise(data: np.ndarray, noise_params: dict = None):
"""
Adds noise to the given data.
Parameters
----------
data : np.ndarray
Input data.
Returns
-------
np.ndarray
Data with added noise.
"""
if noise_params is not None:
if noise_params["type"] == "gaussian":
return gaussian_noise(data, **noise_params["kwargs"])
elif noise_params["type"] == "powerline":
return powerline_noise(data, **noise_params["kwargs"])
elif noise_params["type"] == "baseline_wander":
return baseline_wander_noise(data, **noise_params["kwargs"])
else:
return data
[docs]
def baseline_wander_noise(
signal: np.ndarray,
signal_frequency: float,
snr: float,
cutoff: float = 0.5,
seed: int = None,
) -> np.ndarray:
"""
Add baseline wander noise to a signal.
Parameters
----------
signal : np.ndarray
Input array with shape (..., L), where the last dimension is the signal length
and all preceding dimensions index different signals (e.g. (batch, channels,
length))
snr : float
The signal-to-noise ratio (SNR) in decibels.
cutoff : float
The cutoff frequency of the low-pass filter.
seed : int or None, optional
Seed of the RNG.
Returns
-------
np.ndarray
The signal with added baseline wander noise.
"""
rng = np.random.default_rng(seed)
white_noise = rng.normal(0, 1, size=signal.shape)
filtered_noise = filters.lowpass_filter(white_noise, cutoff, signal_frequency)
base_power = np.mean(filtered_noise**2, axis=-1, keepdims=True)
signal_power = np.mean(signal**2, axis=-1, keepdims=True)
snr_linear = 10 ** (snr / 10)
noise_power = signal_power / snr_linear / (base_power + 1e-10)
noise = filtered_noise * np.sqrt(noise_power)
return signal + noise
[docs]
def gaussian_noise(signal: np.ndarray, snr: float, seed: int = None) -> np.ndarray:
"""
Add Gaussian noise to a signal.
Parameters
----------
signal : np.ndarray
Input array with shape (..., L), where the last dimension is the signal length
and all preceding dimensions index different signals (e.g. (batch, channels,
length))
snr : float
The signal-to-noise ratio (SNR) in decibels.
seed : int or None, optional
Seed of the RNG.
Returns
-------
np.ndarray
The signal with added Gaussian noise.
"""
rng = np.random.default_rng(seed)
signal_power = np.mean(signal**2, axis=-1, keepdims=True)
snr_linear = 10 ** (snr / 10)
noise_power = signal_power / snr_linear
noise = rng.normal(0, np.sqrt(noise_power), size=signal.shape)
return signal + noise
[docs]
def powerline_noise(
signal: np.ndarray,
signal_frequency: float,
snr: float,
noise_frequency: float = 50.0,
) -> np.ndarray:
"""
Add powerline noise to a signal.
Typically, powerline noise is a sinusoidal signal with a frequency of 50 or 60 Hz.
Parameters
----------
signal : np.ndarray
Input array with shape (..., L), where the last dimension is the signal length
and all preceding dimensions index different signals (e.g. (batch, channels,
length))
signal_frequency : float
The sampling frequency (Hz) of the signal.
snr : float
The signal-to-noise ratio (SNR) in decibels.
noise_frequency : float
The frequency (Hz) of the powerline noise.
Returns
-------
np.ndarray
The signal with added powerline noise.
"""
t = np.arange(signal.shape[-1]) / signal_frequency
noise = np.sin(2 * np.pi * noise_frequency * t)
signal_power = np.mean(signal**2, axis=-1, keepdims=True)
snr_linear = 10 ** (snr / 10)
noise_power = signal_power / snr_linear
noise = noise * np.sqrt(noise_power)
return signal + noise