Source code for neurokit2.signal.signal_power

# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from .signal_psd import signal_psd


[docs] def signal_power( signal, frequency_band, sampling_rate=1000, continuous=False, show=False, normalize=True, **kwargs, ): """**Compute the power of a signal in a given frequency band** Parameters ---------- signal : Union[list, np.array, pd.Series] The signal (i.e., a time series) in the form of a vector of values. frequency_band :tuple or list Tuple or list of tuples indicating the range of frequencies to compute the power in. sampling_rate : int The sampling frequency of the signal (in Hz, i.e., samples/second). continuous : bool Compute instant frequency, or continuous power. show : bool If ``True``, will return a Poincaré plot. Defaults to ``False``. normalize : bool Normalization of power by maximum PSD value. Default to ``True``. Normalization allows comparison between different PSD methods. **kwargs Keyword arguments to be passed to :func:`.signal_psd`. See Also -------- signal_filter, signal_psd Returns ------- pd.DataFrame A DataFrame containing the Power Spectrum values and a plot if ``show`` is ``True``. Examples -------- .. ipython:: python import neurokit2 as nk import numpy as np # Instant power signal = nk.signal_simulate(duration=60, frequency=[10, 15, 20], amplitude = [1, 2, 3], noise = 2) @savefig p_signal_power1.png scale=100% power_plot = nk.signal_power(signal, frequency_band=[(8, 12), (18, 22)], method="welch", show=True) @suppress plt.close() ..ipython:: python # Continuous (simulated signal) signal = np.concatenate((nk.ecg_simulate(duration=30, heart_rate=75), nk.ecg_simulate(duration=30, heart_rate=85))) power = nk.signal_power(signal, frequency_band=[(72/60, 78/60), (82/60, 88/60)], continuous=True) processed, _ = nk.ecg_process(signal) power["ECG_Rate"] = processed["ECG_Rate"] @savefig p_signal_power2.png scale=100% nk.signal_plot(power, standardize=True) @suppress plt.close() .. ipython:: python # Continuous (real signal) signal = nk.data("bio_eventrelated_100hz")["ECG"] power = nk.signal_power(signal, sampling_rate=100, frequency_band=[(0.12, 0.15), (0.15, 0.4)], continuous=True) processed, _ = nk.ecg_process(signal, sampling_rate=100) power["ECG_Rate"] = processed["ECG_Rate"] @savefig p_signal_power3.png scale=100% nk.signal_plot(power, standardize=True) @suppress plt.close() """ if continuous is False: out = _signal_power_instant( signal, frequency_band, sampling_rate=sampling_rate, show=show, normalize=normalize, **kwargs, ) else: out = _signal_power_continuous(signal, frequency_band, sampling_rate=sampling_rate) out = pd.DataFrame.from_dict(out, orient="index").T return out
# ============================================================================= # Instant # ============================================================================= def _signal_power_instant( signal, frequency_band, sampling_rate=1000, show=False, normalize=True, order_criteria="KIC", **kwargs, ): # Sanitize frequency band if isinstance(frequency_band[0], (int, float)): frequency_band = [frequency_band] # put in list to iterate on # Get min-max frequency min_freq = min([band[0] for band in frequency_band]) max_freq = max([band[1] for band in frequency_band]) # Get PSD psd = signal_psd( signal, sampling_rate=sampling_rate, show=False, normalize=normalize, order_criteria=order_criteria, **kwargs, ) psd = psd[(psd["Frequency"] >= min_freq) & (psd["Frequency"] <= max_freq)] out = {} for band in frequency_band: power = _signal_power_instant_compute(psd, band) out[f"Hz_{band[0]}_{band[1]}"] = power if show: _signal_power_instant_plot(psd, out, frequency_band) return out def _signal_power_instant_compute(psd, band): """Also used in other instances""" where = (psd["Frequency"] >= band[0]) & (psd["Frequency"] < band[1]) power = np.trapz(y=psd["Power"][where], x=psd["Frequency"][where]) return np.nan if power == 0.0 else power def _signal_power_instant_plot(psd, out, frequency_band, ax=None): if ax is None: fig, ax = plt.subplots() else: fig = None # Sanitize signal if isinstance(frequency_band[0], int): if len(frequency_band) > 2: print( "NeuroKit error: signal_power(): The `frequency_band` argument must be a list of tuples" " or a tuple of 2 integers" ) else: frequency_band = [tuple(i for i in frequency_band)] freq = np.array(psd["Frequency"]) power = np.array(psd["Power"]) # Get indexes for different frequency band frequency_band_index = [] for band in frequency_band: indexes = np.logical_and( psd["Frequency"] >= band[0], psd["Frequency"] < band[1] ) # pylint: disable=E1111 frequency_band_index.append(np.array(indexes)) labels = list(out.keys()) # Reformat labels if of the pattern "Hz_X_Y" if len(labels[0].split("_")) == 3: labels = [i.split("_") for i in labels] labels = [f"{i[1]}-{i[2]} Hz" for i in labels] # Get cmap cmap = plt.get_cmap("Set1") colors = cmap.colors colors = ( colors[3], colors[1], colors[2], colors[4], colors[0], colors[5], colors[6], colors[7], colors[8], ) # manually rearrange colors colors = colors[0 : len(frequency_band_index)] # Plot ax.set_title("Power Spectral Density (PSD) for Frequency Domains") ax.set_xlabel("Frequency (Hz)") ax.set_ylabel("Spectrum (ms2/Hz)") ax.fill_between(freq, 0, power, color="lightgrey") for band_index, label, i in zip(frequency_band_index, labels, colors): ax.fill_between(freq[band_index], 0, power[band_index], label=label, color=i) ax.legend(prop={"size": 10}, loc="best") return fig # ============================================================================= # Continuous # ============================================================================= def _signal_power_continuous(signal, frequency_band, sampling_rate=1000): out = {} if isinstance(frequency_band[0], (list, tuple)): for band in frequency_band: out.update(_signal_power_continuous_get(signal, band, sampling_rate)) else: out.update(_signal_power_continuous_get(signal, frequency_band, sampling_rate)) return out def _signal_power_continuous_get(signal, frequency_band, sampling_rate=1000, precision=20): try: import mne except ImportError as e: raise ImportError( "NeuroKit error: signal_power(): the 'mne'", "module is required. ", "Please install it first (`pip install mne`).", ) from e # explicitly raise error from ImportError exception out = mne.time_frequency.tfr_array_morlet( [[signal]], sfreq=sampling_rate, freqs=np.linspace(frequency_band[0], frequency_band[1], precision), zero_mean=False, output="power", ) power = np.mean(out[0][0], axis=0) out = {} out[f"{frequency_band[0]:.2f}-{frequency_band[1]:.2f}Hz"] = power # use literal string format return out