bci_essentials.resting_state

A module for processing resting state data.

The EEG data inputs for each function are either a single trials or a set of trials.

  • For single trials, inputs are of the shape n_channels x n_samples, where:
    • n_channels = number of channels
    • n_samples = number of samples
  • For multiple trials, inputs are of the shape n_trials x n_channels x n_samples, where:
    • n_trials = number of trials
    • n_channels = number of channels
    • n_samples = number of samples
  1"""
  2A module for processing resting state data.
  3
  4The EEG data inputs for each function are either a single trials or
  5a set of trials.
  6- For single trials, inputs are of the shape `n_channels x n_samples`, where:
  7    - n_channels = number of channels
  8    - n_samples = number of samples
  9- For multiple trials, inputs are of the shape `n_trials x n_channels x n_samples`, where:
 10    - n_trials = number of trials
 11    - n_channels = number of channels
 12    - n_samples = number of samples
 13
 14"""
 15
 16import numpy as np
 17import scipy.signal
 18
 19from matplotlib import pyplot as plt
 20
 21from .utils.logger import Logger  # Logger wrapper
 22
 23# Instantiate a logger for the module at the default level of logging.INFO
 24# Logs to bci_essentials.__module__) where __module__ is the name of the module
 25logger = Logger(name=__name__)
 26
 27
 28def get_shape(data):
 29    """Get the shape of the input data.
 30
 31    Parameters
 32    ----------
 33    data : numpy.ndarray
 34        Trial(s) of resting state EEG data.
 35        2D or 3D array containing data with `float` type.
 36
 37        shape = (`n_channels`,`n_samples`) OR
 38        (`n_trials`,`n_channels`,`n_samples`)
 39
 40    Returns
 41    -------
 42    n_trials : int
 43        Number of trials.
 44    n_channels : int
 45        Number of channels.
 46    n_samples : int
 47        Number of samples.
 48
 49    """
 50    try:
 51        n_trials, n_channels, n_samples = np.shape(data)
 52    except Exception:
 53        n_channels, n_samples = np.shape(data)
 54        n_trials = 1
 55
 56    return n_trials, n_channels, n_samples
 57
 58
 59# This function is never used at the moment, anywhere in the code. Renaming to more clear convention on it being a public function.
 60def get_bandpower(data, fs, fmin, fmax, normalization=None):
 61    """Get the bandpower of a trial of EEG.
 62
 63    Parameters
 64    ----------
 65    data : numpy.ndarray
 66        A single resting state EEG trial
 67        2D array containing data with `float` type.
 68
 69        shape = (`n_channels`,`n_samples`)
 70    fs : float
 71        EEG sampling frequency.
 72    fmin : float
 73        Lower frequency bound.
 74    fmax : float
 75        Upper frequency bound.
 76    normalization : string, *optional*
 77        Method for normalization.
 78        - `"norm"` for divide by norm.
 79        - `"sum"` for divide by sum.
 80        - Default is `None` and no normalization is done.
 81
 82    Returns
 83    -------
 84    power : numpy.ndarray
 85        Bandpower of frequency band.
 86
 87        shape = (`n_channels`)
 88
 89    """
 90    n_channels, n_samples = data.shape
 91
 92    f, Pxx = scipy.signal.welch(data, fs=fs)
 93
 94    # Normalization
 95    if normalization == "norm":
 96        Pxx = np.divide(Pxx, np.tile(np.linalg.norm(Pxx, axis=1), (len(f), 1)).T)
 97    if normalization == "sum":
 98        Pxx = Pxx / Pxx.sum(axis=1).reshape((Pxx.shape[0], 1))
 99
100    ind_local_min = np.argmax(f > fmin) - 1
101    ind_local_max = np.argmax(f > fmax) - 1
102
103    power = np.zeros([n_channels])
104    for channel in range(n_channels):
105        power[channel] = np.trapz(
106            Pxx[channel, ind_local_min:ind_local_max], f[ind_local_min:ind_local_max]
107        )
108
109    return power
110
111
112# Alpha Peak
113def get_alpha_peak(data, alpha_min=8, alpha_max=12, plot_psd=False):
114    """Get the alpha peak based on the all channel median PSD.
115
116    Parameters
117    ----------
118    data : numpy.ndarray
119        Resting state EEG trial with eyes closed.
120        3D array containing data with `float` type.
121
122        shape = (`n_trials`,`n_channels`,`n_samples`)
123    alpha_min : float, *optional*
124        Lowest possible value of alpha peak (Hz)
125        - Default is `8`.
126    alpha_max : float, *optional*
127        Highest possible value of alpha peak (Hz)
128        - Default is `12`.
129    plot_psd : bool, *optional*
130        Plot the PSD for inspection.
131        - Default is `False`.
132
133    Returns
134    -------
135    alpha_peaks : numpy.ndarray
136        The peak alpha frequency (in Hz) for each trial.
137    """
138
139    fs = 256
140
141    n_trials, n_channels, n_samples = get_shape(data)
142
143    # Create alpha_peaks of length n_trials
144    alpha_peaks = np.zeros(n_trials)
145
146    for trial in range(n_trials):
147        # Get the current trial
148        current_trial = data[trial, :, :]
149
150        # Calculate PSD using Welch's method, nfft = n_samples
151        f, Pxx = scipy.signal.welch(current_trial, fs=fs, nperseg=n_samples)
152
153        # Limit f, Pxx to the band of interest
154        ind_min = scipy.argmax(f > alpha_min) - 1
155        ind_max = scipy.argmax(f > alpha_max) - 1
156
157        f = f[ind_min:ind_max]
158        Pxx = Pxx[:, ind_min:ind_max]
159
160        alpha_peaks[trial] = f[np.argmax(np.median(Pxx, axis=0))]
161        logger.info("Alpha peak of trial %s is %s", trial, alpha_peaks[trial])
162
163        if plot_psd:
164            nrows = int(np.ceil(np.sqrt(n_channels)))
165            ncols = int(np.ceil(np.sqrt(n_channels)))
166
167            fig, axs = plt.subplots(nrows, ncols, figsize=(10, 8))
168            for r in range(nrows):
169                for c in range(ncols):
170                    ch = (ncols * r) + c
171                    axs[r, c].set_title(ch)
172                    axs[r, c].plot(f, Pxx[ch, :])
173
174            plt.show()
175
176            plt.figure()
177            plt.plot(f, np.median(Pxx, axis=0))
178
179            plt.show()
180
181    return alpha_peaks
182
183
184# Bandpower features
185def get_bandpower_features(data, fs, transition_freqs=[0, 4, 8, 12, 30]):
186    """Get bandpower features.
187
188    Parameters
189    ----------
190    data : numpy.ndarray
191        Trials of resting state EEG data.
192        3D array containing data with `float` type.
193
194        shape = (`n_trials`,`n_channels`,`n_samples`)
195    fs : float
196        Sampling frequency (Hz).
197    transition_freqs : array-like, *optional*
198        The transition frequencies of the desired power bands.
199        The first value is the lower cutoff, the last value is the
200        upper cutoff, the middle values are band transition frequencies.
201        - Default is `[0, 4, 8, 12, 30]`.
202
203    Returns
204    -------
205    abs_bandpower : numpy.ndarray
206        A numpy array of the absolute bandpower of provided bands.
207        The last item is the total bandpower. The array length is equal
208        to the length of `transition_freqs`.
209    rel_bandpower : numpy.ndarray
210        A numpy array of the relative bandpower of provided bands with
211        respect to the entire region of interest from `transition_freqs[0]`
212        to `transition_freqs[-1]`. The last item is the total relative
213        bandpower and should always equal `1`. The array length is equal to
214        the length of `transition_freqs`.
215    rel_bandpower_mat : array_like
216        A numpy array of the relative bandpower of provided bands such that
217        location `(R,C)` corresponds to the power of `R` relative to `C`.
218        The final row and column correspond to the cumulative power of
219        all bands such that `(R,-1)` is `R` relative to all bands
220
221    """
222    # Get Shape
223    n_trials, n_channels, n_samples = get_shape(data)
224
225    # Initialize
226    abs_bandpower = np.zeros((len(transition_freqs), n_trials))
227    rel_bandpower = np.zeros((len(transition_freqs), n_trials))
228    rel_bandpower_mat = np.zeros(
229        (len(transition_freqs), len(transition_freqs), n_trials)
230    )
231
232    # for each trial
233    for trial in range(n_trials):
234        # Get the current trial
235        current_trial = data[trial, :, :]
236
237        # Calculate PSD using Welch's method
238        f, Pxx = scipy.signal.welch(current_trial, fs=fs)
239
240        # Limit f, Pxx to the band of interest
241        ind_global_min = scipy.argmax(f > min(transition_freqs)) - 1
242        ind_global_max = scipy.argmax(f > max(transition_freqs)) - 1
243
244        f = f[ind_global_min:ind_global_max]
245        Pxx = Pxx[:, ind_global_min:ind_global_max]
246
247        # Calculate the absolute power of each band
248        for tf in range(len(transition_freqs)):
249            # The last item is the total
250            if tf == len(transition_freqs) - 1:
251                abs_bandpower[tf, trial] = np.sum(abs_bandpower[:tf, trial])
252                continue
253
254            fmin = transition_freqs[tf]
255            fmax = transition_freqs[tf + 1]
256
257            ind_local_min = np.argmax(f > fmin) - 1
258            ind_local_max = np.argmax(f > fmax) - 1
259
260            # Get power for each channel
261            abs_power = np.zeros([n_channels])
262            for ch in range(n_channels):
263                abs_power[ch] = np.trapz(
264                    Pxx[ch, ind_local_min:ind_local_max], f[ind_local_min:ind_local_max]
265                )
266
267            # Median across all channels
268            abs_bandpower[tf, trial] = np.median(abs_power)
269
270        rel_bandpower[:, trial] = abs_bandpower[:, trial] / abs_bandpower[-1, trial]
271
272        # Calculate the relative power of each band
273        for tf1 in range(len(transition_freqs)):
274            for tf2 in range(len(transition_freqs)):
275                rel_bandpower_mat[tf1, tf2, trial] = (
276                    abs_bandpower[tf1, trial] / abs_bandpower[tf2, trial]
277                )
278
279    return abs_bandpower, rel_bandpower, rel_bandpower_mat
def get_shape(data):
29def get_shape(data):
30    """Get the shape of the input data.
31
32    Parameters
33    ----------
34    data : numpy.ndarray
35        Trial(s) of resting state EEG data.
36        2D or 3D array containing data with `float` type.
37
38        shape = (`n_channels`,`n_samples`) OR
39        (`n_trials`,`n_channels`,`n_samples`)
40
41    Returns
42    -------
43    n_trials : int
44        Number of trials.
45    n_channels : int
46        Number of channels.
47    n_samples : int
48        Number of samples.
49
50    """
51    try:
52        n_trials, n_channels, n_samples = np.shape(data)
53    except Exception:
54        n_channels, n_samples = np.shape(data)
55        n_trials = 1
56
57    return n_trials, n_channels, n_samples

Get the shape of the input data.

Parameters
  • data (numpy.ndarray): Trial(s) of resting state EEG data. 2D or 3D array containing data with float type.

    shape = (n_channels,n_samples) OR (n_trials,n_channels,n_samples)

Returns
  • n_trials (int): Number of trials.
  • n_channels (int): Number of channels.
  • n_samples (int): Number of samples.
def get_bandpower(data, fs, fmin, fmax, normalization=None):
 61def get_bandpower(data, fs, fmin, fmax, normalization=None):
 62    """Get the bandpower of a trial of EEG.
 63
 64    Parameters
 65    ----------
 66    data : numpy.ndarray
 67        A single resting state EEG trial
 68        2D array containing data with `float` type.
 69
 70        shape = (`n_channels`,`n_samples`)
 71    fs : float
 72        EEG sampling frequency.
 73    fmin : float
 74        Lower frequency bound.
 75    fmax : float
 76        Upper frequency bound.
 77    normalization : string, *optional*
 78        Method for normalization.
 79        - `"norm"` for divide by norm.
 80        - `"sum"` for divide by sum.
 81        - Default is `None` and no normalization is done.
 82
 83    Returns
 84    -------
 85    power : numpy.ndarray
 86        Bandpower of frequency band.
 87
 88        shape = (`n_channels`)
 89
 90    """
 91    n_channels, n_samples = data.shape
 92
 93    f, Pxx = scipy.signal.welch(data, fs=fs)
 94
 95    # Normalization
 96    if normalization == "norm":
 97        Pxx = np.divide(Pxx, np.tile(np.linalg.norm(Pxx, axis=1), (len(f), 1)).T)
 98    if normalization == "sum":
 99        Pxx = Pxx / Pxx.sum(axis=1).reshape((Pxx.shape[0], 1))
100
101    ind_local_min = np.argmax(f > fmin) - 1
102    ind_local_max = np.argmax(f > fmax) - 1
103
104    power = np.zeros([n_channels])
105    for channel in range(n_channels):
106        power[channel] = np.trapz(
107            Pxx[channel, ind_local_min:ind_local_max], f[ind_local_min:ind_local_max]
108        )
109
110    return power

Get the bandpower of a trial of EEG.

Parameters
  • data (numpy.ndarray): A single resting state EEG trial 2D array containing data with float type.

    shape = (n_channels,n_samples)

  • fs (float): EEG sampling frequency.
  • fmin (float): Lower frequency bound.
  • fmax (float): Upper frequency bound.
  • normalization (string, optional): Method for normalization.
    • "norm" for divide by norm.
    • "sum" for divide by sum.
    • Default is None and no normalization is done.
Returns
  • power (numpy.ndarray): Bandpower of frequency band.

    shape = (n_channels)

def get_alpha_peak(data, alpha_min=8, alpha_max=12, plot_psd=False):
114def get_alpha_peak(data, alpha_min=8, alpha_max=12, plot_psd=False):
115    """Get the alpha peak based on the all channel median PSD.
116
117    Parameters
118    ----------
119    data : numpy.ndarray
120        Resting state EEG trial with eyes closed.
121        3D array containing data with `float` type.
122
123        shape = (`n_trials`,`n_channels`,`n_samples`)
124    alpha_min : float, *optional*
125        Lowest possible value of alpha peak (Hz)
126        - Default is `8`.
127    alpha_max : float, *optional*
128        Highest possible value of alpha peak (Hz)
129        - Default is `12`.
130    plot_psd : bool, *optional*
131        Plot the PSD for inspection.
132        - Default is `False`.
133
134    Returns
135    -------
136    alpha_peaks : numpy.ndarray
137        The peak alpha frequency (in Hz) for each trial.
138    """
139
140    fs = 256
141
142    n_trials, n_channels, n_samples = get_shape(data)
143
144    # Create alpha_peaks of length n_trials
145    alpha_peaks = np.zeros(n_trials)
146
147    for trial in range(n_trials):
148        # Get the current trial
149        current_trial = data[trial, :, :]
150
151        # Calculate PSD using Welch's method, nfft = n_samples
152        f, Pxx = scipy.signal.welch(current_trial, fs=fs, nperseg=n_samples)
153
154        # Limit f, Pxx to the band of interest
155        ind_min = scipy.argmax(f > alpha_min) - 1
156        ind_max = scipy.argmax(f > alpha_max) - 1
157
158        f = f[ind_min:ind_max]
159        Pxx = Pxx[:, ind_min:ind_max]
160
161        alpha_peaks[trial] = f[np.argmax(np.median(Pxx, axis=0))]
162        logger.info("Alpha peak of trial %s is %s", trial, alpha_peaks[trial])
163
164        if plot_psd:
165            nrows = int(np.ceil(np.sqrt(n_channels)))
166            ncols = int(np.ceil(np.sqrt(n_channels)))
167
168            fig, axs = plt.subplots(nrows, ncols, figsize=(10, 8))
169            for r in range(nrows):
170                for c in range(ncols):
171                    ch = (ncols * r) + c
172                    axs[r, c].set_title(ch)
173                    axs[r, c].plot(f, Pxx[ch, :])
174
175            plt.show()
176
177            plt.figure()
178            plt.plot(f, np.median(Pxx, axis=0))
179
180            plt.show()
181
182    return alpha_peaks

Get the alpha peak based on the all channel median PSD.

Parameters
  • data (numpy.ndarray): Resting state EEG trial with eyes closed. 3D array containing data with float type.

    shape = (n_trials,n_channels,n_samples)

  • alpha_min (float, optional): Lowest possible value of alpha peak (Hz)
    • Default is 8.
  • alpha_max (float, optional): Highest possible value of alpha peak (Hz)
    • Default is 12.
  • plot_psd (bool, optional): Plot the PSD for inspection.
    • Default is False.
Returns
  • alpha_peaks (numpy.ndarray): The peak alpha frequency (in Hz) for each trial.
def get_bandpower_features(data, fs, transition_freqs=[0, 4, 8, 12, 30]):
186def get_bandpower_features(data, fs, transition_freqs=[0, 4, 8, 12, 30]):
187    """Get bandpower features.
188
189    Parameters
190    ----------
191    data : numpy.ndarray
192        Trials of resting state EEG data.
193        3D array containing data with `float` type.
194
195        shape = (`n_trials`,`n_channels`,`n_samples`)
196    fs : float
197        Sampling frequency (Hz).
198    transition_freqs : array-like, *optional*
199        The transition frequencies of the desired power bands.
200        The first value is the lower cutoff, the last value is the
201        upper cutoff, the middle values are band transition frequencies.
202        - Default is `[0, 4, 8, 12, 30]`.
203
204    Returns
205    -------
206    abs_bandpower : numpy.ndarray
207        A numpy array of the absolute bandpower of provided bands.
208        The last item is the total bandpower. The array length is equal
209        to the length of `transition_freqs`.
210    rel_bandpower : numpy.ndarray
211        A numpy array of the relative bandpower of provided bands with
212        respect to the entire region of interest from `transition_freqs[0]`
213        to `transition_freqs[-1]`. The last item is the total relative
214        bandpower and should always equal `1`. The array length is equal to
215        the length of `transition_freqs`.
216    rel_bandpower_mat : array_like
217        A numpy array of the relative bandpower of provided bands such that
218        location `(R,C)` corresponds to the power of `R` relative to `C`.
219        The final row and column correspond to the cumulative power of
220        all bands such that `(R,-1)` is `R` relative to all bands
221
222    """
223    # Get Shape
224    n_trials, n_channels, n_samples = get_shape(data)
225
226    # Initialize
227    abs_bandpower = np.zeros((len(transition_freqs), n_trials))
228    rel_bandpower = np.zeros((len(transition_freqs), n_trials))
229    rel_bandpower_mat = np.zeros(
230        (len(transition_freqs), len(transition_freqs), n_trials)
231    )
232
233    # for each trial
234    for trial in range(n_trials):
235        # Get the current trial
236        current_trial = data[trial, :, :]
237
238        # Calculate PSD using Welch's method
239        f, Pxx = scipy.signal.welch(current_trial, fs=fs)
240
241        # Limit f, Pxx to the band of interest
242        ind_global_min = scipy.argmax(f > min(transition_freqs)) - 1
243        ind_global_max = scipy.argmax(f > max(transition_freqs)) - 1
244
245        f = f[ind_global_min:ind_global_max]
246        Pxx = Pxx[:, ind_global_min:ind_global_max]
247
248        # Calculate the absolute power of each band
249        for tf in range(len(transition_freqs)):
250            # The last item is the total
251            if tf == len(transition_freqs) - 1:
252                abs_bandpower[tf, trial] = np.sum(abs_bandpower[:tf, trial])
253                continue
254
255            fmin = transition_freqs[tf]
256            fmax = transition_freqs[tf + 1]
257
258            ind_local_min = np.argmax(f > fmin) - 1
259            ind_local_max = np.argmax(f > fmax) - 1
260
261            # Get power for each channel
262            abs_power = np.zeros([n_channels])
263            for ch in range(n_channels):
264                abs_power[ch] = np.trapz(
265                    Pxx[ch, ind_local_min:ind_local_max], f[ind_local_min:ind_local_max]
266                )
267
268            # Median across all channels
269            abs_bandpower[tf, trial] = np.median(abs_power)
270
271        rel_bandpower[:, trial] = abs_bandpower[:, trial] / abs_bandpower[-1, trial]
272
273        # Calculate the relative power of each band
274        for tf1 in range(len(transition_freqs)):
275            for tf2 in range(len(transition_freqs)):
276                rel_bandpower_mat[tf1, tf2, trial] = (
277                    abs_bandpower[tf1, trial] / abs_bandpower[tf2, trial]
278                )
279
280    return abs_bandpower, rel_bandpower, rel_bandpower_mat

Get bandpower features.

Parameters
  • data (numpy.ndarray): Trials of resting state EEG data. 3D array containing data with float type.

    shape = (n_trials,n_channels,n_samples)

  • fs (float): Sampling frequency (Hz).
  • transition_freqs (array-like, optional): The transition frequencies of the desired power bands. The first value is the lower cutoff, the last value is the upper cutoff, the middle values are band transition frequencies.
    • Default is [0, 4, 8, 12, 30].
Returns
  • abs_bandpower (numpy.ndarray): A numpy array of the absolute bandpower of provided bands. The last item is the total bandpower. The array length is equal to the length of transition_freqs.
  • rel_bandpower (numpy.ndarray): A numpy array of the relative bandpower of provided bands with respect to the entire region of interest from transition_freqs[0] to transition_freqs[-1]. The last item is the total relative bandpower and should always equal 1. The array length is equal to the length of transition_freqs.
  • rel_bandpower_mat (array_like): A numpy array of the relative bandpower of provided bands such that location (R,C) corresponds to the power of R relative to C. The final row and column correspond to the cumulative power of all bands such that (R,-1) is R relative to all bands