Adding New Features#

import py_neuromodulation as nm
import numpy as np
from typing import Iterable

In this example we will demonstrate how a new feature can be added to the existing feature pipeline. This can be done by creating a new feature class that implements the protocol class NMFeature and registering it with the AddCustomFeature() function.

Let’s create a new feature class called ChannelMean that calculates the mean signal for each channel. We can optinally make it inherit from NMFeature but as long as it has an adequate constructor and a calc_feature method with the appropriate signatures it will work. The __init__() method should take the settings, channel names and sampling frequency as arguments. The calc_feature method should take the data and a dictionary of features as arguments and return the updated dictionary.

class ChannelMean:
    def __init__(
        self, settings: nm.NMSettings, ch_names: Iterable[str], sfreq: float
    ) -> None:
        # If required for feature calculation, store the settings,
        # channel names and sampling frequency (optional)
        self.settings = settings
        self.ch_names = ch_names
        self.sfreq = sfreq

        # Here you can add any additional initialization code
        # For example, you could store parameters for the functions\
        # used in the calc_feature method

        self.feature_name = "channel_mean"

    def calc_feature(self, data: np.ndarray) -> dict:
        # First, create an empty dictionary to store the calculated features
        feature_results = {}

        # Here you can add any feature calculation code
        # This example simply calculates the mean signal for each channel
        ch_means = np.mean(data, axis=1)

        # Store the calculated features in the feature_results dictionary
        # Be careful to use a unique keyfor each channel and metric you compute
        for ch_idx, ch in enumerate(self.ch_names):
            feature_results[f"{self.feature_name}_{ch}"] = ch_means[ch_idx]

        # Return the updated feature_results dictionary to the stream
        return feature_results


nm.add_custom_feature("channel_mean", ChannelMean)

Now we can instantiate settings and observe that the new feature has been added to the list of features

settings = nm.NMSettings() # Get default settings

settings.features
{'bandpass_filter': False,
 'bispectrum': False,
 'bursts': True,
 'channel_mean': True,
 'coherence': False,
 'fft': True,
 'fooof': False,
 'linelength': True,
 'mne_connectivity': False,
 'nolds': False,
 'raw_hjorth': True,
 'return_raw': True,
 'sharpwave_analysis': True,
 'stft': False,
 'welch': True}

Let’s create some artificial data to demonstrate the feature calculation.

N_CHANNELS = 5
N_SAMPLES = 10000 # 10 seconds of random data at 1000 Hz sampling frequency

data = np.random.random([N_CHANNELS, N_SAMPLES])
stream = nm.Stream(
    sfreq=1000,
    data=data,
    settings = settings,
    sampling_rate_features_hz=10,
    verbose=False,
)

feature_df = stream.run()
columns = [col for col in feature_df.columns if "channel_mean" in col]

feature_df[columns]
channel_mean_ch0_avgref channel_mean_ch1_avgref channel_mean_ch2_avgref channel_mean_ch3_avgref channel_mean_ch4_avgref
0 -0.003151 0.003845 0.009986 -0.008624 -0.002056
1 -1.000000 -1.000000 1.000000 1.000000 -1.000000
2 -1.183055 0.067441 0.127422 -1.388348 1.386479
3 0.971766 0.420605 -1.230613 -0.665998 0.684274
4 1.602812 -1.861613 1.262754 -1.847107 1.170723
... ... ... ... ... ...
86 -0.223974 0.650751 0.052932 -0.559971 0.342396
87 -0.469088 0.560148 -0.411298 0.418785 0.369015
88 -0.284326 0.633788 -0.878672 1.154053 -0.013363
89 -1.228100 0.222809 0.037242 1.437820 -0.544140
90 -0.826018 -0.044380 0.340241 0.805401 -0.586588

91 rows × 5 columns



Remove feature so that it does not interfere with other examples

nm.remove_custom_feature("channel_mean")

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

Gallery generated by Sphinx-Gallery