First Demo#

This Demo will showcase the feature estimation and exemplar analysis using simulated data.

import numpy as np
from matplotlib import pyplot as plt

import py_neuromodulation as nm

Data Simulation#

We will now generate some exemplar data with 10 second duration for 6 channels with a sample rate of 1 kHz.

def generate_random_walk(NUM_CHANNELS, TIME_DATA_SAMPLES):
    # from https://towardsdatascience.com/random-walks-with-python-8420981bc4bc
    dims = NUM_CHANNELS
    step_n = TIME_DATA_SAMPLES - 1
    step_set = [-1, 0, 1]
    origin = (np.random.random([1, dims]) - 0.5) * 1  # Simulate steps in 1D
    step_shape = (step_n, dims)
    steps = np.random.choice(a=step_set, size=step_shape)
    path = np.concatenate([origin, steps]).cumsum(0)
    return path.T


NUM_CHANNELS = 6
sfreq = 1000
TIME_DATA_SAMPLES = 10 * sfreq
data = generate_random_walk(NUM_CHANNELS, TIME_DATA_SAMPLES)
time = np.arange(0, TIME_DATA_SAMPLES / sfreq, 1 / sfreq)

plt.figure(figsize=(8, 4), dpi=100)
for ch_idx in range(data.shape[0]):
    plt.plot(time, data[ch_idx, :])
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.title("Example random walk data")
Example random walk data
Text(0.5, 1.0, 'Example random walk data')

Now let’s define the necessary setup files we will be using for data preprocessing and feature estimation. Py_neuromodualtion is based on two parametrization files: the channels.tsv and the default_settings.json.

nm_channels#

The nm_channel dataframe. This dataframe contains the columns

Column name

Description

name

name of the channel

rereference

different channel name for bipolar re-referencing, or average for common average re-referencing

used

0 or 1, channel selection

target

0 or 1, for some decoding applications we can define target channels, e.g. EMG channels

type

channel type according to the mne-python toolbox

e.g. ecog, eeg, ecg, emg, dbs, seeg etc.

status

good or bad, used for channel quality indication

new_name

this keyword can be specified to indicate for example the used rereferncing scheme

The nm_stream_abc can either be created as a .tsv text file, or as a pandas DataFrame. There are some helper functions that let you create the nm_channels without much effort:

nm_channels = nm.utils.get_default_channels_from_data(data, car_rereferencing=True)

nm_channels
name rereference used target type status new_name
0 ch0 average 1 0 ecog good ch0_avgref
1 ch1 average 1 0 ecog good ch1_avgref
2 ch2 average 1 0 ecog good ch2_avgref
3 ch3 average 1 0 ecog good ch3_avgref
4 ch4 average 1 0 ecog good ch4_avgref
5 ch5 average 1 0 ecog good ch5_avgref


Using this function default channel names and a common average re-referencing scheme is specified. Alternatively the define_nmchannels.set_channels function can be used to pass each column values.

nm_settings#

Next, we will initialize the nm_settings dictionary and use the default settings, reset them, and enable a subset of features:

settings = nm.NMSettings.get_fast_compute()

The setting itself is a .json file which contains the parametrization for preprocessing, feature estimation, postprocessing and definition with which sampling rate features are being calculated. In this example sampling_rate_features_hz is specified to be 10 Hz, so every 100ms a new set of features is calculated.

For many features the segment_length_features_ms specifies the time dimension of the raw signal being used for feature calculation. Here it is specified to be 1000 ms.

We will now enable the features:

  • fft

  • bursts

  • sharpwave

and stay with the default preprcessing methods:

  • notch_filter

  • re_referencing

and use z-score postprocessing normalization.

We are now ready to go to instantiate the Stream and call the run method for feature estimation:

stream = nm.Stream(
    settings=settings,
    channels=nm_channels,
    verbose=True,
    sfreq=sfreq,
    line_noise=50,
)

features = stream.run(data, save_csv=True)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/fooof/core/funcs.py:62: RuntimeWarning: invalid value encountered in log10
  ys = offset - np.log10(knee + xs**exp)

Feature Analysis#

There is a lot of output, which we could omit by verbose being False, but let’s have a look what was being computed. We will therefore use the nm_analysis class to showcase some functions. For multi-run -or subject analysis we will pass here the feature_file “sub” as default directory:

analyzer = nm.FeatureReader(
    feature_dir=stream.out_dir_root, feature_file=stream.experiment_name
)

Let’s have a look at the resulting “feature_arr” DataFrame:

ch0_avgref_fft_theta_mean ch1_avgref_fft_theta_mean ch2_avgref_fft_theta_mean ch3_avgref_fft_theta_mean ch4_avgref_fft_theta_mean ch5_avgref_fft_theta_mean ch0_avgref_fft_alpha_mean
0 3.173335 2.599706 2.717248 3.058939 2.982307 2.943506 2.837150
1 -1.000000 1.000000 1.000000 -1.000000 -1.000000 -1.000000 1.000000
2 -1.346376 -0.503323 -1.382572 -1.033234 0.067698 0.884309 -1.404595
3 -0.826819 0.393170 -0.938964 1.091164 -0.872061 0.277494 -1.452470
4 -1.460201 1.524250 -0.405598 0.348553 1.648792 -1.002571 -1.272433
5 -0.455732 0.924325 -1.610885 1.489735 1.546969 0.614548 -0.491364
6 0.862754 0.031796 1.833354 0.553278 0.175344 0.366158 -1.083103
7 0.979889 -0.922244 -1.538273 -1.483128 1.360713 0.343133 1.145800
8 0.897504 -0.130931 -0.693593 -0.919179 1.166493 -0.721426 1.590803
9 1.243882 0.574644 0.040646 -0.870387 -0.247241 -0.098972 1.599101


Seems like a lot of features were calculated. The time column tells us about each row time index. For the 6 specified channels, it is each 31 features. We can now use some in-built plotting functions for visualization.

Note

Due to the nature of simulated data, some of the features have constant values, which are not displayed through the image normalization.

analyzer.plot_all_features(ch_used="ch1")
Feature Plot sub
nm.analysis.plot_corr_matrix(
    figsize=(25, 25),
    show_plot=True,
    feature=analyzer.feature_arr,
)
Correlation matrix
<Axes: title={'center': 'Correlation matrix'}>

The upper correlation matrix shows the correlation of every feature of every channel to every other. This notebook demonstrated a first demo how features can quickly be generated. For further feature modalities and decoding applications check out the next notebooks.

Total running time of the script: (0 minutes 5.492 seconds)

Gallery generated by Sphinx-Gallery