Source code for neurokit2.signal.signal_distort

# -*- coding: utf-8 -*-
from warnings import warn

import numpy as np

from ..misc import NeuroKitWarning, check_random_state, listify
from .signal_resample import signal_resample
from .signal_simulate import signal_simulate


[docs] def signal_distort( signal, sampling_rate=1000, noise_shape="laplace", noise_amplitude=0, noise_frequency=100, powerline_amplitude=0, powerline_frequency=50, artifacts_amplitude=0, artifacts_frequency=100, artifacts_number=5, linear_drift=False, random_state=None, silent=False, ): """**Signal distortion** Add noise of a given frequency, amplitude and shape to a signal. Parameters ---------- signal : Union[list, np.array, pd.Series] The signal (i.e., a time series) in the form of a vector of values. sampling_rate : int The sampling frequency of the signal (in Hz, i.e., samples/second). noise_shape : str The shape of the noise. Can be one of ``"laplace"`` (default) or ``"gaussian"``. noise_amplitude : float The amplitude of the noise (the scale of the random function, relative to the standard deviation of the signal). noise_frequency : float The frequency of the noise (in Hz, i.e., samples/second). powerline_amplitude : float The amplitude of the powerline noise (relative to the standard deviation of the signal). powerline_frequency : float The frequency of the powerline noise (in Hz, i.e., samples/second). artifacts_amplitude : float The amplitude of the artifacts (relative to the standard deviation of the signal). artifacts_frequency : int The frequency of the artifacts (in Hz, i.e., samples/second). artifacts_number : int The number of artifact bursts. The bursts have a random duration between 1 and 10% of the signal duration. linear_drift : bool Whether or not to add linear drift to the signal. random_state : None, int, numpy.random.RandomState or numpy.random.Generator Seed for the random number generator. See for ``misc.check_random_state`` for further information. silent : bool Whether or not to display warning messages. Returns ------- array Vector containing the distorted signal. Examples -------- .. ipython:: python import numpy as np import pandas as pd import neurokit2 as nk signal = nk.signal_simulate(duration=10, frequency=0.5) # Noise @savefig p_signal_distort1.png scale=100% noise = pd.DataFrame({"Freq100": nk.signal_distort(signal, noise_frequency=200), "Freq50": nk.signal_distort(signal, noise_frequency=50), "Freq10": nk.signal_distort(signal, noise_frequency=10), "Freq5": nk.signal_distort(signal, noise_frequency=5), "Raw": signal}).plot() @suppress plt.close() .. ipython:: python # Artifacts @savefig p_signal_distort2.png scale=100% artifacts = pd.DataFrame({"1Hz": nk.signal_distort(signal, noise_amplitude=0, artifacts_frequency=1, artifacts_amplitude=0.5), "5Hz": nk.signal_distort(signal, noise_amplitude=0, artifacts_frequency=5, artifacts_amplitude=0.2), "Raw": signal}).plot() @suppress plt.close() """ # Seed the random generator for reproducible results. rng = check_random_state(random_state) # Make sure that noise_amplitude is a list. if isinstance(noise_amplitude, (int, float)): noise_amplitude = [noise_amplitude] signal_sd = np.std(signal, ddof=1) if signal_sd == 0: signal_sd = None noise = 0 # Basic noise. if min(noise_amplitude) > 0: noise += _signal_distort_noise_multifrequency( signal, signal_sd=signal_sd, sampling_rate=sampling_rate, noise_amplitude=noise_amplitude, noise_frequency=noise_frequency, noise_shape=noise_shape, silent=silent, rng=rng, ) # Powerline noise. if powerline_amplitude > 0: noise += _signal_distort_powerline( signal, signal_sd=signal_sd, sampling_rate=sampling_rate, powerline_frequency=powerline_frequency, powerline_amplitude=powerline_amplitude, silent=silent, ) # Artifacts. if artifacts_amplitude > 0: noise += _signal_distort_artifacts( signal, signal_sd=signal_sd, sampling_rate=sampling_rate, artifacts_frequency=artifacts_frequency, artifacts_amplitude=artifacts_amplitude, artifacts_number=artifacts_number, silent=silent, rng=rng, ) if linear_drift: noise += _signal_linear_drift(signal) distorted = signal + noise return distorted
# =========================================================================== # Types of Noise # =========================================================================== def _signal_linear_drift(signal): n_samples = len(signal) linear_drift = np.arange(n_samples) * (1 / n_samples) return linear_drift def _signal_distort_artifacts( signal, signal_sd=None, sampling_rate=1000, artifacts_frequency=0, artifacts_amplitude=0.1, artifacts_number=5, artifacts_shape="laplace", silent=False, rng=None, ): # Generate artifact burst with random onset and random duration. artifacts = _signal_distort_noise( len(signal), sampling_rate=sampling_rate, noise_frequency=artifacts_frequency, noise_amplitude=artifacts_amplitude, noise_shape=artifacts_shape, silent=silent, rng=rng, ) if artifacts.sum() == 0: return artifacts min_duration = int(np.rint(len(artifacts) * 0.001)) max_duration = int(np.rint(len(artifacts) * 0.01)) artifact_durations = rng.choice(range(min_duration, max_duration), size=artifacts_number) artifact_onsets = rng.choice(len(artifacts) - max_duration, size=artifacts_number) artifact_offsets = artifact_onsets + artifact_durations artifact_idcs = np.array([False] * len(artifacts)) for i in range(artifacts_number): artifact_idcs[artifact_onsets[i] : artifact_offsets[i]] = True artifacts[~artifact_idcs] = 0 # Scale amplitude by the signal's standard deviation. if signal_sd is not None: artifacts_amplitude *= signal_sd artifacts *= artifacts_amplitude return artifacts def _signal_distort_powerline( signal, signal_sd=None, sampling_rate=1000, powerline_frequency=50, powerline_amplitude=0.1, silent=False, ): duration = len(signal) / sampling_rate powerline_noise = signal_simulate( duration=duration, sampling_rate=sampling_rate, frequency=powerline_frequency, amplitude=1, silent=silent, ) if signal_sd is not None: powerline_amplitude *= signal_sd powerline_noise *= powerline_amplitude return powerline_noise def _signal_distort_noise_multifrequency( signal, signal_sd=None, sampling_rate=1000, noise_amplitude=0.1, noise_frequency=100, noise_shape="laplace", silent=False, rng=None, ): base_noise = np.zeros(len(signal)) params = listify( noise_amplitude=noise_amplitude, noise_frequency=noise_frequency, noise_shape=noise_shape ) for i in range(len(params["noise_amplitude"])): freq = params["noise_frequency"][i] amp = params["noise_amplitude"][i] shape = params["noise_shape"][i] if signal_sd is not None: amp *= signal_sd # Make some noise! _base_noise = _signal_distort_noise( len(signal), sampling_rate=sampling_rate, noise_frequency=freq, noise_amplitude=amp, noise_shape=shape, silent=silent, rng=rng, ) base_noise += _base_noise return base_noise def _signal_distort_noise( n_samples, sampling_rate=1000, noise_frequency=100, noise_amplitude=0.1, noise_shape="laplace", silent=False, rng=None, ): _noise = np.zeros(n_samples) # Apply a very conservative Nyquist criterion in order to ensure # sufficiently sampled signals. nyquist = sampling_rate * 0.1 if noise_frequency > nyquist: if not silent: warn( f"Skipping requested noise frequency " f" of {noise_frequency} Hz since it cannot be resolved at " f" the sampling rate of {sampling_rate} Hz. Please increase " f" sampling rate to {noise_frequency * 10} Hz or choose " f" frequencies smaller than or equal to {nyquist} Hz.", category=NeuroKitWarning, ) return _noise # Also make sure that at least one period of the frequency can be # captured over the duration of the signal. duration = n_samples / sampling_rate if (1 / noise_frequency) > duration: if not silent: warn( f"Skipping requested noise frequency " f" of {noise_frequency} Hz since its period of {1 / noise_frequency} " f" seconds exceeds the signal duration of {duration} seconds. " f" Please choose noise frequencies larger than " f" {1 / duration} Hz or increase the duration of the " f" signal above {1 / noise_frequency} seconds.", category=NeuroKitWarning, ) return _noise noise_duration = int(duration * noise_frequency) if noise_shape in ["normal", "gaussian"]: _noise = rng.normal(0, noise_amplitude, noise_duration) elif noise_shape == "laplace": _noise = rng.laplace(0, noise_amplitude, noise_duration) else: raise ValueError( "NeuroKit error: signal_distort(): 'noise_shape' should be one of 'gaussian' or 'laplace'." ) if len(_noise) != n_samples: _noise = signal_resample(_noise, desired_length=n_samples, method="interpolation") return _noise