├── EEG ├── __init__.py └── eeg.py ├── setup.py ├── LICENSE.md └── readme.md /EEG/__init__.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, basename, isfile 2 | import glob 3 | modules = glob.glob(dirname(__file__)+"/*.py") 4 | __all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='EEG', 4 | version='0.1', 5 | description='EEG analysis utils', 6 | url='https://github.com/hadrienj/EEG', 7 | author='hadrienj', 8 | author_email='hadrienjean@gmail.com', 9 | license='MIT', 10 | packages=['EEG'], 11 | install_requires=[ 12 | 'numpy', 13 | 'matplotlib', 14 | 'pandas', 15 | 'scipy', 16 | 'mne', 17 | 'couchdb', 18 | 'h5py' 19 | ], 20 | zip_safe=False) -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 hadrienj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This repository contains a set of functions to pre-process and process electroencephalography (EEG) data. 2 | 3 | # Introduction 4 | 5 | With most recording devices, EEG data are structured as a big matrix of shape (time x electrodes). One electrode channel generaly corresponds to the trigger channel used to synchronise the participant response or the stimuli to the EEG signal. The raw EEG can be split in chunks of time according to this trigger channel. It is then possible to average EEG signal coming from same condition for instance. 6 | 7 | These functions can be used to load data, do some kind of processing, plot etc. 8 | 9 | # Special functions 10 | 11 | ## Denoising source separation 12 | 13 | This denoising method is an implementation of [this matlab toolbox]() created by Alain de Cheveigné. More details about this method can be found in the following papers: 14 | 15 | - 16 | - 17 | 18 | # API 19 | 20 | ### `addOffset(data, offset)` 21 | 22 | Plot all electrodes with an offset from t0 to t1. The stimulus channel is 23 | also ploted and red lines are used to show the events. 24 | 25 | - **`data`** `instance of pandas.core.DataFrame` 26 | 27 | Add offset to data. 28 | - **`offset`** `float` 29 | 30 | Value of the offset. 31 | 32 | Returns: 33 | 34 | - **`newData`** `instance of pandas.core.DataFrame` 35 | 36 | The data with offset applied to each electrode. 37 | 38 | ### `calculateBaseline(data, baselineDur=0.1, fs=2048.)` 39 | 40 | Calculate and return the baseline (average of each data point) of a signal. 41 | The baseline will calculated from the first `baselineDur` seconds of this 42 | signal. 43 | 44 | - **`data`** `instance of pandas.core.DataFrame` 45 | 46 | Data used to calculate the baseline. 47 | - **`baselineDur`** `float` 48 | 49 | Duration of the baseline to use for the calulation of the average in 50 | seconds. 51 | - **`fs`** `float` 52 | 53 | Sampling frequency of data in Hz. 54 | 55 | Returns: 56 | 57 | - **`baseline`** `float` 58 | 59 | The baseline value. 60 | 61 | ### `chebyBandpassFilter(data, cutoff, gstop=40, gpass=1, fs=2048.)` 62 | 63 | Design a filter with scipy functions avoiding unstable results (when using 64 | ab output and filtfilt(), lfilter()...). 65 | Cf. ()[] 66 | 67 | - **`data`** `instance of numpy.array | instance of pandas.core.DataFrame` 68 | 69 | Data to be filtered. Each column will be filtered if data is a 70 | dataframe. 71 | - **`cutoff`** `array-like of float` 72 | 73 | Pass and stop frequencies in order: 74 | - the first element is the stop limit in the lower bound 75 | - the second element is the lower bound of the pass-band 76 | - the third element is the upper bound of the pass-band 77 | - the fourth element is the stop limit in the upper bound 78 | For instance, [0.9, 1, 45, 48] will create a band-pass filter between 79 | 1 Hz and 45 Hz. 80 | - **`gstop`** `int` 81 | 82 | The minimum attenuation in the stopband (dB). 83 | - **`gpass`** `int` 84 | 85 | The maximum loss in the passband (dB). 86 | 87 | Returns: 88 | 89 | - **`filteredData`** `instance of numpy.array | instance of pandas.core.DataFrame` 90 | 91 | The filtered data. 92 | 93 | ### `checkPlots(data1, data2, fs1, fs2, start, end, electrodeNum)` 94 | 95 | Check filtering and downsampling by ploting both datasets. 96 | 97 | - **`data1`** `instance of pandas.core.DataFrame` 98 | 99 | First dataframe. 100 | - **`data2`** `instance of pandas.core.DataFrame` 101 | 102 | Second dataframe. 103 | - **`fs1`** `float` 104 | 105 | Sampling frequency of the first dataframe in Hz. 106 | - **`fs2`** `float` 107 | 108 | Sampling frequency of the second dataframe in Hz. 109 | - **`start`** `float` 110 | 111 | Start of data to plot in seconds. 112 | - **`end`** `float` 113 | 114 | End of data to plot in seconds. 115 | - **`electrodeNum`** `int` 116 | 117 | Index of the column to plot. 118 | 119 | Returns: 120 | 121 | - **`fig`** `instance of matplotlib.figure.Figure` 122 | 123 | The figure containing both dataset plots. 124 | 125 | ### `checkPlotsNP(data1, data2, fs1, fs2, start, end, electrodeNum)` 126 | 127 | Check filtering and downsampling by ploting both datasets. 128 | 129 | - **`data1`** `instance of pandas.core.DataFrame` 130 | 131 | First dataframe. 132 | - **`data2`** `instance of pandas.core.DataFrame` 133 | 134 | Second dataframe. 135 | - **`fs1`** `float` 136 | 137 | Sampling frequency of the first dataframe in Hz. 138 | - **`fs2`** `float` 139 | 140 | Sampling frequency of the second dataframe in Hz. 141 | - **`start`** `float` 142 | 143 | Start of data to plot in seconds. 144 | - **`end`** `float` 145 | 146 | End of data to plot in seconds. 147 | - **`electrodeNum`** `int` 148 | 149 | Index of the column to plot. 150 | 151 | Returns: 152 | 153 | - **`fig`** `instance of matplotlib.figure.Figure` 154 | 155 | The figure containing both dataset plots. 156 | 157 | ### `computeFFT(data, fs)` 158 | 159 | Compute the FFT of `data` and return also the axis in Hz for further plot. 160 | 161 | - **`data`** `array` 162 | 163 | First dataframe. 164 | - **`fs`** `float` 165 | 166 | Sampling frequency in Hz. 167 | 168 | Returns: 169 | 170 | - **`fAx`** `instance of numpy.array` 171 | 172 | Axis in Hz to plot the FFT. 173 | - **`fftData`** `instance of numpy.array` 174 | 175 | Value of the fft. 176 | 177 | ### `computePickEnergy(data, pickFreq, showPlot, fs)` 178 | 179 | Calculate the relative energy at the frequency `pickFreq` from the the FFT 180 | of `data`. Compare the mean around the pick with the mean of a broader zone 181 | for each column. 182 | 183 | - **`data`** `array-like` 184 | 185 | Matrix of the shape (time, electrode). 186 | - **`pickFreq`** `float` 187 | 188 | Frequency in Hz of the pick for which we want to calculate the relative energy. 189 | - **`showPlot`** `boolean` 190 | 191 | A plot of the FFT can be shown. 192 | - **`fs`** `float` 193 | 194 | Sampling frequency in Hz. 195 | 196 | Returns: 197 | 198 | - **`pickRatio`** `float` 199 | 200 | Relative energy of the pick. 201 | 202 | ### `create3DMatrix(data, trialTable, events, trialList, fs)` 203 | 204 | 205 | ### `createStimChannel(events)` 206 | 207 | Create stim channel from events. 208 | 209 | - **`events`** `instance of pandas.core.DataFrame` 210 | 211 | Dataframe containing list of events obtained with mne.find_events(raw) 212 | . 213 | 214 | Returns: 215 | 216 | - **`stim`** `instance of pandas.core.series.Series` 217 | 218 | Series containing the stimulus channel reconstructed from events. 219 | 220 | ### `discriminateEvents(events, threshold)` 221 | 222 | Discriminate triggers when different kind of events are on the same channel. 223 | A time threshold is used to determine if two events are from the same trial. 224 | 225 | - **`events`** `instance of pandas.core.DataFrame` 226 | 227 | Dataframe containing the list of events obtained with 228 | mne.find_events(raw). 229 | - **`threshold`** `float` 230 | 231 | Time threshold in milliseconds. Keeps an event if the time difference 232 | with the next one is superior than threshold. 233 | 234 | Returns: 235 | 236 | - **`newData`** `instance of pandas.series.Series` 237 | 238 | List of trial number filling the requirements. 239 | 240 | ### `downsample(data, oldFS, newFS)` 241 | 242 | Resample data from oldFS to newFS using the scipy 'resample' function. 243 | 244 | - **`data`** `instance of pandas.core.DataFrame` 245 | 246 | Data to resample. 247 | - **`oldFS`** `float` 248 | 249 | The sampling frequency of data. 250 | - **`newFS`** `float` 251 | 252 | The new sampling frequency. 253 | 254 | Returns: 255 | 256 | - **`newData`** `instance of pandas.DataFrame` 257 | 258 | The downsampled dataset. 259 | 260 | ### `downsampleEvents(events, oldFS, newFS)` 261 | 262 | Modify the timestamps of events to match a new sampling frequency. 263 | 264 | - **`events`** `instance of pandas.core.DataFrame` 265 | 266 | Dataframe containing list of events obtained with mne.find_events(raw) 267 | . 268 | - **`oldFS`** `float` 269 | 270 | The sampling frequency of the input events. 271 | - **`newFS`** `float` 272 | 273 | The sampling frequency to the output events. 274 | 275 | Returns: 276 | 277 | - **`newEvents`** `instance of pandas.DataFrame` 278 | 279 | DataFrame containing the downsampled events. 280 | 281 | ### `downsampleNP(data, oldFS, newFS)` 282 | 283 | Resample data from oldFS to newFS using the scipy 'resample' function. 284 | 285 | - **`data`** `instance of pandas.core.DataFrame` 286 | 287 | Data to resample. 288 | - **`oldFS`** `float` 289 | 290 | The sampling frequency of data. 291 | - **`newFS`** `float` 292 | 293 | The new sampling frequency. 294 | 295 | Returns: 296 | 297 | - **`newData`** `instance of pandas.DataFrame` 298 | 299 | The downsampled dataset. 300 | 301 | ### `FFTTrials(data, events, trialNumList, baselineDur, trialDur, fs, normalize` 302 | 303 | 304 | ### `getBehaviorData(dbAddress, dbName, sessionNum)` 305 | 306 | Fetch behavior data from couchdb (SOA, SNR and trial duration). 307 | 308 | - **`dbAddress`** `str` 309 | 310 | Path to the couch database. 311 | - **`dbName`** `str` 312 | 313 | Name of the database on the couch instance. 314 | - **`sessionNum`** `int` 315 | 316 | Behavior data will be fetched from this sessionNum. 317 | 318 | Returns: 319 | 320 | - **`lookupTable`** `instance of pandas.core.DataFrame` 321 | 322 | A dataframe containing trial data. 323 | 324 | ### `getEvents(raw, eventCode)` 325 | 326 | Get the events corresponding to `eventCode`. 327 | 328 | - **`raw`** `instance of mne.io.edf.edf.RawEDF` 329 | 330 | RawEDF object from the MNE library containing data from the .bdf files. 331 | - **`eventCode`** `int` 332 | 333 | Code corresponding to a specific events. For instance, with a biosemi 334 | device, the triggers are coded 65284, 65288 and 65296 respectively on 335 | the first, second and third channel. 336 | 337 | Returns: 338 | 339 | - **`startEvents`** `instance of pandas.core.DataFrame` 340 | 341 | Dataframe containing the list of timing corresponding to the event code 342 | in the first column. The second column contains the code before the event 343 | and the third the code of the selected event. 344 | 345 | ### `getTrialsAverage(data, events, trialDur=None, trialNumList=None` 346 | 347 | 348 | ### `getTrialData(data, events, trialNum=0, electrode=None, baselineDur=0.1` 349 | 350 | 351 | ### `getTrialDataNP(data, events, trialNum=0, electrode=None, baselineDur=0.1` 352 | 353 | 354 | ### `getTrialNumList(table, **kwargs)` 355 | 356 | Returns a subset of table according to SOA, SNR and/or targetFreq. This is 357 | used to select trials with specific parameters. 358 | 359 | - **`table`** `instance of pandas.core.DataFrame` 360 | 361 | DataFrame containing trial number and their parameters (SOA, SNR...). 362 | - **`kwargs`** `array-like of int | None` 363 | 364 | Array containing element from table to select. It can be `SOA`, `SNR` or 365 | `targetFreq`. 366 | 367 | Returns: 368 | 369 | - **`newData`** `instance of pandas.series.Series` 370 | 371 | List of trial number filling the requirements. 372 | 373 | ### `importH5(name, df)` 374 | 375 | 376 | ### `loadEEG(path)` 377 | 378 | Load data from .bdf files. If an array of path is provided, files will be 379 | concatenated. 380 | 381 | - **`path`** `str | array-like of str` 382 | 383 | Path to the .bdf file(s) to load. 384 | 385 | Returns: 386 | 387 | - **`raw`** `instance of mne.io.edf.edf.RawEDF` 388 | 389 | RawEDF object from the MNE library containing data from the .bdf files. 390 | 391 | ### `normalizeFromBaseline(data, baselineDur=0.1, fs=2048.)` 392 | 393 | Normalize data by subtracting the baseline to each data point. The data used 394 | to normalize has to be included at the beginning of data. For instance, to 395 | normalize a 10 seconds signal with a 0.1 second baseline, data has to be 396 | 10.1 seconds and the baseline used will be the first 0.1 second. 397 | 398 | - **`data`** `instance of pandas.core.DataFrame` 399 | 400 | Data to normalize. 401 | - **`baselineDur`** `float` 402 | 403 | Duration of the baseline to use for the normalization in seconds. 404 | - **`fs`** `float` 405 | 406 | Sampling frequency of data in Hz. 407 | 408 | Returns: 409 | 410 | - **`normalized`** `instance of pandas.core.DataFrame` 411 | 412 | The normalized data. 413 | 414 | ### `plot3DMatrix(data, picks, trialList, average, fs)` 415 | 416 | 417 | ### `plotDataSubset(data, stim, events, offset, t0=0, t1=1, fs=2048.)` 418 | 419 | Plot all electrodes with an offset from t0 to t1. The stimulus channel is 420 | also ploted and red lines are used to show the events. 421 | 422 | - **`data`** `instance of pandas.core.DataFrame` 423 | 424 | Data to plot (not epoched). Columns correspond to electrodes. 425 | - **`stim`** `instance of pandas.core.DataFrame` 426 | 427 | One column dataframe containing the event codes. Used to plot the 428 | stimulus timing along with EEG. 429 | - **`events`** `instance of pandas.core.DataFrame` 430 | 431 | Dataframe containing the list of events obtained with 432 | mne.find_events(raw). 433 | - **`offset`** `float` 434 | 435 | Offset between each electrode line on the plot. 436 | - **`t0`** `float` 437 | 438 | Start of data to plot. 439 | - **`t1`** `float` 440 | 441 | End of data to plot. 442 | - **`fs`** `float` 443 | 444 | Sampling frequency of data in Hz. 445 | 446 | Returns: 447 | 448 | - **`fig`** `instance of matplotlib.figure.Figure` 449 | 450 | The figure of the data subset in the time domain. 451 | 452 | ### `plotERPElectrodes(data, trialNumList, events, trialDur=None, fs=2048.` 453 | 454 | startOffset=0): 455 | 456 | ### `plotFFT(data, facet=False, freqMin=None, freqMax=None, yMin=None` 457 | 458 | 459 | ### `plotFFTElectrodes(data, trialNumList, events, trialDur, fs` 460 | 461 | freqMin=None, freqMax=None, yMin=None, yMax=None, startOffset=0, noiseAve=None): 462 | 463 | ### `plotFFTNP(data, average, fs)` 464 | 465 | 466 | ### `plotFilterResponse(zpk, fs)` 467 | 468 | Plot the filter frequency response. 469 | 470 | - **`zpk`** `array-like` 471 | 472 | The 3 parameters of the filter [z, p, k]. 473 | - **`fs`** `float` 474 | 475 | Sampling frequency in Hz. 476 | 477 | Returns: 478 | 479 | - **`fig`** `instance of matplotlib.figure.Figure` 480 | 481 | The figure of the filter response. 482 | 483 | ### `refToAverageNP(data)` 484 | 485 | 486 | ### `refToMastoids(data, M1, M2)` 487 | 488 | Transform each electrode of data according to the average of M1 and M2. 489 | 490 | - **`data`** `instance of pandas.core.DataFrame` 491 | 492 | First column has to contain the timing of events in frames. 493 | - **`M1`** `instance of pandas.core.series.Series` 494 | 495 | Values of mastoid 1. This Series has to be the same length as data. 496 | - **`M2`** `instance of pandas.core.series.Series` 497 | 498 | Values of mastoid 2. This Series has to be the same length as data 499 | 500 | Returns: 501 | 502 | - **`newData`** `instance of pandas.core.DataFrame` 503 | 504 | A dataframe referenced to matoids containing all electrode from which 505 | we subtract the average of M1 and M2. 506 | 507 | ### `refToMastoidsNP(data, M1, M2)` 508 | 509 | 510 | ### `compareTimeBehaviorEEG(dbAddress, dbName, events, startSound, interTrialDur` 511 | 512 | 513 | ### `preprocessing(files)` 514 | 515 | 516 | ### `getBehaviorTables(dbAddress, dbName)` 517 | 518 | 519 | ### `mergeBehaviorTables(tableHJ1, tableHJ2, tableHJ3)` 520 | 521 | 522 | 523 | # Requirements 524 | 525 | It uses some methods of the [MNE library]() and heavily depends on pandas and numpy. -------------------------------------------------------------------------------- /EEG/eeg.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | from pylab import * 5 | from matplotlib import gridspec 6 | import mne 7 | from mne import find_events 8 | from scipy.signal import resample, freqz_zpk, zpk2sos, sosfiltfilt, cheb2ord, iirdesign 9 | import couchdb 10 | import h5py 11 | import time 12 | 13 | def addOffset(data, offset): 14 | """ 15 | Plot all electrodes with an offset from t0 to t1. The stimulus channel is 16 | also ploted and red lines are used to show the events. 17 | 18 | Parameters 19 | ---------- 20 | data : instance of pandas.core.DataFrame 21 | Add offset to data. 22 | offset : float 23 | Value of the offset. 24 | 25 | Returns: 26 | 27 | newData : instance of pandas.core.DataFrame 28 | The data with offset applied to each electrode. 29 | """ 30 | 31 | newData = data + offset * np.arange(data.shape[1]-1,-1,-1) 32 | return newData 33 | 34 | def applyDSS(data, dss): 35 | """ 36 | Apply the electrodes weights obtained through the Denoising Source Separation 37 | (DSS) to the data matrix using dot product. 38 | 39 | Parameters 40 | ---------- 41 | data : array-like 42 | 2D matrix of shape (time, electrodes) or 3D matrix of shape 43 | (trials, time, electrodes). 44 | dss : array-like 45 | 2D matrix of shape (electrodes, electrodes) resulting of the DSS computation. 46 | See output of the `computeDSS()` function for more details. 47 | 48 | Returns: 49 | 50 | weightedData : array-like 51 | 2D matrix of shape (time, electrodes) or 3D matrix of shape 52 | (trials, time, electrodes) containing the input data weighted 53 | by the matrix dss. 54 | """ 55 | if (np.ndim(data)==2): 56 | weightedData = np.dot(data, dss) 57 | elif (np.ndim(data)==3): 58 | trials = data.shape[0] 59 | time = data.shape[1] 60 | electrodes = data.shape[2] 61 | 62 | # Reshape data from 3D matrix of shape (trials, time, electrodes) to 2D matrix 63 | # of shape (time, electrodes) 64 | data2D = data.reshape((trials*time), electrodes) 65 | 66 | weightedData = np.dot(data2D, dss) 67 | # Reshape to reconstruct the 3D matrix 68 | weightedData = weightedData.reshape(trials, time, electrodes) 69 | else: 70 | print('data wrong dimensions') 71 | 72 | return weightedData 73 | 74 | def calculateBaseline(data, baselineDur=0.1, fs=2048.): 75 | """ 76 | Calculate and return the baseline (average of each data point) of a signal. 77 | The baseline will be calculated from the first `baselineDur` seconds of this 78 | signal. 79 | 80 | Parameters 81 | ---------- 82 | data : instance of pandas.core.DataFrame 83 | Data used to calculate the baseline. 84 | baselineDur : float 85 | Duration of the baseline to use for the calulation of the average in 86 | seconds. 87 | fs : float 88 | Sampling frequency of data in Hz. 89 | 90 | Returns: 91 | 92 | baseline : float 93 | The baseline value. 94 | """ 95 | 96 | # duration for the baseline calculation is in seconds 97 | durSamples = int(np.round(baselineDur*fs)) 98 | subData = data[:durSamples] 99 | baseline = subData.mean() 100 | return baseline 101 | 102 | def chebyBandpassFilter(data, cutoff, gstop=40, gpass=1, fs=2048.): 103 | """ 104 | Design a filter with scipy functions avoiding unstable results (when using 105 | ab output and filtfilt(), lfilter()...). 106 | Cf. ()[] 107 | 108 | Parameters 109 | ---------- 110 | data : instance of numpy.array | instance of pandas.core.DataFrame 111 | Data to be filtered. Each column will be filtered if data is a 112 | dataframe. 113 | cutoff : array-like of float 114 | Pass and stop frequencies in order: 115 | - the first element is the stop limit in the lower bound 116 | - the second element is the lower bound of the pass-band 117 | - the third element is the upper bound of the pass-band 118 | - the fourth element is the stop limit in the upper bound 119 | For instance, [0.9, 1, 45, 48] will create a band-pass filter between 120 | 1 Hz and 45 Hz. 121 | gstop : int 122 | The minimum attenuation in the stopband (dB). 123 | gpass : int 124 | The maximum loss in the passband (dB). 125 | 126 | Returns: 127 | 128 | zpk : 129 | 130 | filteredData : instance of numpy.array | instance of pandas.core.DataFrame 131 | The filtered data. 132 | """ 133 | 134 | wp = [cutoff[1]/(fs/2), cutoff[2]/(fs/2)] 135 | ws = [cutoff[0]/(fs/2), cutoff[3]/(fs/2)] 136 | 137 | z, p, k = iirdesign(wp = wp, ws= ws, gstop=gstop, gpass=gpass, 138 | ftype='cheby2', output='zpk') 139 | zpk = [z, p, k] 140 | sos = zpk2sos(z, p, k) 141 | 142 | order, Wn = cheb2ord(wp = wp, ws= ws, gstop=gstop, gpass=gpass, analog=False) 143 | print 'Creating cheby filter of order %d...' % order 144 | 145 | if (data.ndim == 2): 146 | print 'Data contain multiple columns. Apply filter on each columns.' 147 | filteredData = np.zeros(data.shape) 148 | for electrode in range(data.shape[1]): 149 | # print 'Filtering electrode %s...' % electrode 150 | filteredData[:, electrode] = sosfiltfilt(sos, data[:, electrode]) 151 | else: 152 | # Use sosfiltfilt instead of filtfilt fixed the artifacts at the beggining 153 | # of the signal 154 | filteredData = sosfiltfilt(sos, data) 155 | return zpk, filteredData 156 | 157 | def checkPlots(data1, data2, fs1, fs2, start, end, electrodeNum): 158 | """ 159 | Check filtering and downsampling by ploting both datasets. 160 | 161 | Parameters 162 | ---------- 163 | data1 : instance of pandas.core.DataFrame 164 | First dataframe. 165 | data2 : instance of pandas.core.DataFrame 166 | Second dataframe. 167 | fs1 : float 168 | Sampling frequency of the first dataframe in Hz. 169 | fs2 : float 170 | Sampling frequency of the second dataframe in Hz. 171 | start : float 172 | Start of data to plot in seconds. 173 | end : float 174 | End of data to plot in seconds. 175 | electrodeNum : int 176 | Index of the column to plot. 177 | 178 | Returns: 179 | 180 | fig : instance of matplotlib.figure.Figure 181 | The figure containing both dataset plots. 182 | """ 183 | 184 | start1 = int(np.round(fs1*start)) 185 | start2 = int(np.round(fs2*start)) 186 | end1 = int(np.round(fs1*end)) 187 | end2 = int(np.round(fs2*end)) 188 | # Choose electrode and time to plot 189 | data1Sub = data1.iloc[start1: end1, electrodeNum] 190 | data2Sub = data2.iloc[start2: end2, electrodeNum] 191 | # x-axis in seconds 192 | x1 = np.arange(data1Sub.shape[0])/fs1 193 | x2 = np.arange(data2Sub.shape[0])/fs2 194 | 195 | plt.figure() 196 | plt.plot(x1, data1Sub) 197 | plt.plot(x2, data2Sub, alpha=0.7) 198 | plt.show() 199 | 200 | def checkPlotsNP(data1, data2, fs1, fs2, start, end, electrodeNum): 201 | """ 202 | Check filtering and downsampling by ploting both datasets. 203 | 204 | Parameters 205 | ---------- 206 | data1 : instance of pandas.core.DataFrame 207 | First dataframe. 208 | data2 : instance of pandas.core.DataFrame 209 | Second dataframe. 210 | fs1 : float 211 | Sampling frequency of the first dataframe in Hz. 212 | fs2 : float 213 | Sampling frequency of the second dataframe in Hz. 214 | start : float 215 | Start of data to plot in seconds. 216 | end : float 217 | End of data to plot in seconds. 218 | electrodeNum : int 219 | Index of the column to plot. 220 | 221 | Returns: 222 | 223 | fig : instance of matplotlib.figure.Figure 224 | The figure containing both dataset plots. 225 | """ 226 | 227 | start1 = int(np.round(fs1*start)) 228 | start2 = int(np.round(fs2*start)) 229 | end1 = int(np.round(fs1*end)) 230 | end2 = int(np.round(fs2*end)) 231 | # Choose electrode and time to plot 232 | data1Sub = data1[start1:end1, electrodeNum] 233 | data2Sub = data2[start2:end2, electrodeNum] 234 | # x-axis in seconds 235 | x1 = np.arange(data1Sub.shape[0])/fs1 236 | x2 = np.arange(data2Sub.shape[0])/fs2 237 | 238 | plt.figure() 239 | plt.plot(x1, data1Sub) 240 | plt.plot(x2, data2Sub, alpha=0.7) 241 | plt.show() 242 | 243 | def computeDSS(cov0, cov1): 244 | """ 245 | Compute the Denoising Source Separation (DSS) from unbiased (cov0) and biased (cov1) 246 | covariance matrices. 247 | 248 | Parameters 249 | ---------- 250 | cov0 : array-like 251 | Covariance matrix of unbiased data. 252 | cov1 : array-like 253 | Covariance matrix of biased data. 254 | 255 | Returns: 256 | 257 | DSS : array-like 258 | Matrix of shape identical to cov0 and cov1 containing the weights that can be 259 | applied on data. 260 | """ 261 | # cov0 is the unbiased covariance and cov1 the biased covariance 262 | P, D = PCAFromCov(cov0) 263 | D = np.abs(D) 264 | 265 | # whiten 266 | N = np.diag(np.sqrt(1./D)) 267 | c2 = N.T.dot(P.T).dot(cov1).dot(P).dot(N) 268 | 269 | Q, eigenVals2 = PCAFromCov(c2) 270 | eigenVals2 = np.abs(eigenVals2) 271 | 272 | W = P.dot(N).dot(Q) 273 | N2=np.diag(W.T.dot(cov0).dot(W)) 274 | W=W.dot(np.diag(1./sqrt(N2))) 275 | return W 276 | 277 | def computeFFT(data, fs): 278 | """ 279 | Compute the FFT of `data` and return also the axis in Hz for further plot. 280 | 281 | Parameters 282 | ---------- 283 | data : array 284 | First dataframe. 285 | fs : float 286 | Sampling frequency in Hz. 287 | 288 | Returns: 289 | 290 | fAx : instance of numpy.array 291 | Axis in Hz to plot the FFT. 292 | fftData : instance of numpy.array 293 | Value of the fft. 294 | """ 295 | N = data.shape[0] 296 | fAx = np.arange(N/2)*fs/N 297 | Y = np.abs(fftpack.fft(data, axis=0)) 298 | fftData = 2.0/N * np.abs(Y[0:N//2]) 299 | return fAx, fftData 300 | 301 | def computePickEnergy(data, pickFreq, showPlot, fs): 302 | """ 303 | Calculate the relative energy at the frequency `pickFreq` from the the FFT 304 | of `data`. Compare the mean around the pick with the mean of a broader zone 305 | for each column. 306 | 307 | Parameters 308 | ---------- 309 | data : array-like 310 | Matrix of shape (time, electrode). 311 | pickFreq : float 312 | Frequency in Hz of the pick for which we want to calculate the relative energy. 313 | showPlot : boolean 314 | A plot of the FFT can be shown. 315 | fs : float 316 | Sampling frequency in Hz. 317 | 318 | Returns: 319 | 320 | pickRatio : float 321 | Relative energy of the pick. 322 | """ 323 | pickWidth = 1 324 | N = data.shape[0] 325 | # Calculate the FFT of `data` 326 | fAx, fftData = computeFFT(data, fs) 327 | 328 | # Convert pick from Hz to bin number 329 | pickBin = int(np.round(pickFreq*(N/fs))) 330 | 331 | # Extract power at the frequency bin 332 | pickData = fftData[pickBin:pickBin+pickWidth] 333 | # Average power across time 334 | pickDataMean = pickData.mean(axis=0) 335 | 336 | # Extract power around the frequency bin 337 | nonPickBin = np.concatenate([np.arange(pickBin-5, pickBin), 338 | np.arange(pickBin+1, pickBin+5+1)]); 339 | 340 | nonPickData = fftData[nonPickBin] 341 | nonPickDataMean = nonPickData.mean(axis=0) 342 | 343 | pickRatio = pickDataMean / nonPickDataMean 344 | 345 | if (showPlot): 346 | plt.figure() 347 | plt.plot(fAx, fftData, linewidth=0.5) 348 | plt.show() 349 | 350 | return pickRatio 351 | 352 | def covUnnorm(data): 353 | """ 354 | Calculate the unnormalized covariance of the the matrix `data`. Covariance in numpy 355 | normalize by dividing the dot product by (N-1) where N is the number 356 | of samples. The sum across electrodes of the raw (not normalized) 357 | covariance is returned. 358 | 359 | Parameters 360 | ---------- 361 | data : array-like 362 | 3D matrix of shape (trials, time, electrodes). 363 | 364 | Returns: 365 | 366 | cov: array-like 367 | Covariance matrix of shape (electrodes, electrodes) 368 | """ 369 | electrodeNum = data.shape[2] 370 | trialNum = data.shape[0] 371 | cov = np.zeros((electrodeNum, electrodeNum)) 372 | for i in range(trialNum): 373 | cov += np.dot(data[i,:,:].T, data[i,:,:]) 374 | return cov 375 | 376 | def create3DMatrix(data, trialTable, events, trialList, trialDur, fs, normalize, baselineDur=0.1): 377 | """ 378 | """ 379 | trials = trialTable.copy() 380 | trials = trials[trials['trialNum'].isin(trialList)] 381 | totalTrialNum = np.max(trials['trialNum']) 382 | m = trials.shape[0] 383 | print m, totalTrialNum 384 | 385 | electrodeNumber = data.shape[1] 386 | trialSamples = int(np.round((trialDur+baselineDur)*fs)) 387 | # number of features: each sample for each electrode 388 | n = int(np.round(trialDur*fs*electrodeNumber)) 389 | # Get trial data 390 | X = np.zeros((m, trialSamples, electrodeNumber)) 391 | 392 | print 'creating matrix of shape (trials=%d, time=%ds, electrodes=%d)' % (X.shape[0], 393 | X.shape[1]/fs, 394 | X.shape[2]) 395 | count = 0 396 | for i in range(totalTrialNum+1): 397 | # Check if this trial is in our subset 398 | if (i in trialList.unique()): 399 | trial = getTrialDataNP(data.values, events=events, 400 | trialNum=i, baselineDur=baselineDur, 401 | startOffset=0, 402 | trialDur=trialDur, fs=fs) 403 | # Normalization 404 | if (normalize): 405 | trial = normalizeFromBaseline(trial, 406 | baselineDur=baselineDur, fs=fs) 407 | 408 | X[count, :, :] = trial 409 | count += 1 410 | print X.shape 411 | 412 | return X 413 | 414 | def createStimChannel(events): 415 | """ 416 | Create stim channel from events. 417 | 418 | Parameters 419 | ---------- 420 | events : instance of pandas.core.DataFrame 421 | Dataframe containing list of events obtained with mne.find_events(raw) 422 | . 423 | 424 | Returns: 425 | 426 | stim : instance of pandas.core.series.Series 427 | Series containing the stimulus channel reconstructed from events. 428 | """ 429 | 430 | newEvents = events.copy() 431 | newEvents[0] = newEvents[0].round(0).astype('int') 432 | chan = np.zeros(int(events.iloc[-1, 0])) 433 | chan[newEvents.iloc[:-2, 0]] = 8 434 | stim = pd.Series(chan, columns = ['STI 014']) 435 | return stim 436 | 437 | def crossValidate(data, dataBiased, trialTable): 438 | """ 439 | Compute DSS from all trials except one and apply it one the one. Do that 440 | with all trials as cross validation. 441 | 442 | Parameters 443 | ---------- 444 | data : array-like 445 | 3D matrix of shape (trials, time, electrodes) containing unbiased data 446 | dataBiased : array-like 447 | 3D matrix of shape (trials, time, electrodes) containing biased data 448 | (for instance band-pass filtered) 449 | 450 | Returns: 451 | 452 | allDSS : array-like 453 | Matrix of shape identical to dataBiased containing the weighted data 454 | """ 455 | trials = data.shape[0] 456 | time = data.shape[1] 457 | electrodes = data.shape[2] 458 | 459 | trials4Hz = getTrialNumList(trialTable, noise=[False], 460 | SOA=[4]).astype(int).values 461 | trials4HzNum = len(trials4Hz) 462 | 463 | allDSS = np.zeros((trials4HzNum, time, electrodes)) 464 | for i in range(trials4HzNum-1): 465 | # All 4Hz trials except ith 466 | trialsToUse4Hz = np.delete(trials4Hz, i) 467 | # All trials except ith 468 | trialsToUseAll = np.concatenate([np.arange(0,trials4Hz[i]), 469 | np.arange(trials4Hz[i]+1, trials)]) 470 | 471 | # Calculate covariance for unbiased and biased data 472 | cov0 = covUnnorm(data[trialsToUseAll,:,:]) 473 | cov1 = covUnnorm(dataBiased[trials4Hz,:,:]) 474 | 475 | DSS = computeDSS(cov0, cov1) 476 | 477 | # Test on ith trial 478 | XTest = data[trials4Hz[i],:,:] 479 | dataDSS = applyDSS(XTest, DSS) 480 | # Push ith trial 481 | allDSS[i,:,:] = dataDSS 482 | return allDSS 483 | 484 | def discriminateEvents(events, threshold): 485 | """ 486 | Discriminate triggers when different kind of events are on the same channel. 487 | A time threshold is used to determine if two events are from the same trial. 488 | 489 | Parameters 490 | ---------- 491 | events : instance of pandas.core.DataFrame 492 | Dataframe containing the list of events obtained with 493 | mne.find_events(raw). 494 | threshold : float 495 | Time threshold in milliseconds. Keeps an event if the time difference 496 | with the next one is superior than threshold. 497 | 498 | Returns: 499 | 500 | newData : instance of pandas.series.Series 501 | List of trial number filling the requirements. 502 | """ 503 | 504 | # calculate the rolling difference (between n and n+1) 505 | events['diff'] = events[0].diff() 506 | # replace the nan with the first value 507 | events['diff'].iloc[0] = events.iloc[0, 0] 508 | # select events with time distance superior to threshold 509 | events = events[events['diff']>threshold] 510 | events = events.reset_index(drop=True) 511 | del events['diff'] 512 | return events 513 | 514 | def downsample(data, oldFS, newFS): 515 | """ 516 | Resample data from oldFS to newFS using the scipy 'resample' function. 517 | 518 | Parameters 519 | ---------- 520 | data : instance of pandas.core.DataFrame 521 | Data to resample. 522 | oldFS : float 523 | The sampling frequency of data. 524 | newFS : float 525 | The new sampling frequency. 526 | 527 | Returns: 528 | 529 | newData : instance of pandas.DataFrame 530 | The downsampled dataset. 531 | """ 532 | 533 | newNumSamples = int((data.shape[0] / oldFS) * newFS) 534 | newData = pd.DataFrame(resample(data, newNumSamples)) 535 | return newData 536 | 537 | def downsampleEvents(events, oldFS, newFS): 538 | """ 539 | Modify the timestamps of events to match a new sampling frequency. 540 | 541 | Parameters 542 | ---------- 543 | events : instance of pandas.core.DataFrame 544 | Dataframe containing list of events obtained with mne.find_events(raw) 545 | . 546 | oldFS : float 547 | The sampling frequency of the input events. 548 | newFS : float 549 | The sampling frequency to the output events. 550 | 551 | Returns: 552 | 553 | newEvents : instance of pandas.DataFrame 554 | DataFrame containing the downsampled events. 555 | """ 556 | 557 | newEvents = events.copy() 558 | newEvents[0] = (events[0]/oldFS)*newFS 559 | newEvents[0] = newEvents[0].round(0).astype('int') 560 | return newEvents 561 | 562 | def downsampleNP(data, oldFS, newFS): 563 | """ 564 | Resample data from oldFS to newFS using the scipy 'resample' function. 565 | 566 | Parameters 567 | ---------- 568 | data : array-like 569 | Data to resample. 570 | oldFS : float 571 | The sampling frequency of data. 572 | newFS : float 573 | The new sampling frequency. 574 | 575 | Returns: 576 | 577 | newData : instance of pandas.DataFrame 578 | The downsampled dataset. 579 | """ 580 | 581 | newNumSamples = int((data.shape[0] / oldFS) * newFS) 582 | newData = resample(data, newNumSamples) 583 | return newData 584 | 585 | def FFTTrials(data, events, trialNumList, baselineDur, trialDur, fs, normalize, 586 | electrodes): 587 | """ 588 | """ 589 | dataElectrodes = np.zeros((5171, len(electrodes))) 590 | countEle = 0 591 | for electrode in electrodes: 592 | print 'electrode number %d' %electrode 593 | allTrials = np.zeros((5171, len(trialNumList))) 594 | count = 0 595 | for trialNum in trialNumList: 596 | trialData = getTrialDataNP(data, events=events, trialNum=trialNum, electrode=electrode, 597 | baselineDur=baselineDur, trialDur=trialDur, fs=fs) 598 | if normalize: 599 | trialData = normalizeFromBaseline(trialData, 600 | baselineDur=baselineDur, fs=fs) 601 | Y = fftpack.fft(trialData) 602 | allTrials[:, count] = pd.Series(Y.real) 603 | count += 1 604 | dataElectrodes[:, countEle] = allTrials.mean(axis=1) 605 | countEle += 1 606 | return dataElectrodes 607 | 608 | def filterAndDownsampleByChunk(raw, fs, newFS, chunkNum=10): 609 | """ 610 | Downsample data. Filters have to be used before downsampling. To be more 611 | efficient, the filters and downsampling are applied by chunk of data. 612 | 613 | Parameters 614 | ---------- 615 | raw : instance of mne.io.edf.edf.RawEDF 616 | Raw data. 617 | fs : float 618 | The sampling frequency of data. 619 | newFS : 620 | The sampling frequency of data after downsampling. 621 | chunkNum : int 622 | Number of chunk used to process data. 623 | 624 | Returns: 625 | 626 | data : array-like 627 | The filtered and downsampled data. 628 | """ 629 | # Calculate the number of sample per chunk 630 | subsetLen = int(np.round(len(raw)/float(chunkNum))) 631 | 632 | acc = {} 633 | for i in range(chunkNum): 634 | tic = time.time() 635 | print '...'*chunkNum 636 | 637 | start = subsetLen*i 638 | end = subsetLen*(i+1)-1 639 | 640 | # Take part of data 641 | eegData = raw[:, start:end][0].T 642 | 643 | zpk, eegData2Hz = chebyBandpassFilter(eegData, [1.8, 2., 30., 35.], 644 | gstop=80, gpass=1, fs=fs) 645 | eegData2HzNewFS = downsampleNP(eegData2Hz, oldFS=fs, newFS=newFS) 646 | acc[i] = eegData2HzNewFS 647 | 648 | toc = time.time() 649 | print (str(1000*(toc-tic))) 650 | 651 | # Re concatenate processed data 652 | totalSampleNum = int(np.round(len(raw)/fs*newFS)) 653 | totalChanNum = len(raw.ch_names) 654 | 655 | data = np.zeros((totalSampleNum, totalChanNum)) 656 | 657 | subset = totalSampleNum/chunkNum 658 | for i in acc: 659 | start = subset*i 660 | end = subset*(i+1) 661 | data[start:end, :] = acc[i] 662 | print i, ' done!' 663 | return data 664 | 665 | def getBehaviorData(dbAddress, dbName, sessionNum, recover=True): 666 | """ 667 | Fetch behavior data from couchdb (SOA, SNR and trial duration). 668 | 669 | Parameters 670 | ---------- 671 | dbAddress : str 672 | Path to the couch database. 673 | dbName : str 674 | Name of the database on the couch instance. 675 | sessionNum : int 676 | Behavior data will be fetched from this sessionNum. 677 | 678 | Returns: 679 | 680 | lookupTable : instance of pandas.core.DataFrame 681 | A dataframe containing trial data. 682 | """ 683 | 684 | couch = couchdb.Server(dbAddress) 685 | db = couch[dbName] 686 | lookupTable = pd.DataFrame(columns=['trialNum', 'SOA', 'SNR', 'targetRate', 687 | 'targetFreq','trialDur', 'soundStart', 'deviant', 'noise', 'target', 688 | 'score']) 689 | count = 0 690 | for docid in db.view('_all_docs'): 691 | if (docid['id'].startswith('infMask_%d' % sessionNum)): 692 | count += 1 693 | 694 | trialNum = int(docid['id'].split('_')[-1]) 695 | 696 | if (db.get(docid['id'])['toneCloudParam'] is not None and recover): 697 | doc = pd.DataFrame(db.get(docid['id'])) 698 | 699 | toneCloudLen = doc.toneCloudParam.shape[0] 700 | trialDur = doc.trialDur[0] 701 | toneCloud = pd.DataFrame(db.get(docid['id'])['toneCloudParam']) 702 | soundStart = np.min(toneCloud['time']) 703 | deviant = doc.deviant[0] 704 | recoverNoise = doc.toneCloudParam[300]['gain']!=0 705 | if ('targetSOA' in doc.columns): 706 | targetRate = 1/doc.targetSOA[0] 707 | else: 708 | targetRate = None 709 | targetFreq = doc.targetFreq[0] 710 | SNR = doc.targetdB[0] 711 | # target SOA can be infered from the number of tones in the cloud 712 | if (toneCloudLen < 700): 713 | recoverTargetSOA = 4 714 | elif ((toneCloudLen > 800) & (toneCloudLen < 1100)): 715 | recoverTargetSOA = 7 716 | elif (toneCloudLen > 1300): 717 | recoverTargetSOA = 13 718 | else: 719 | raise ValueError('check the value of toneCloudLen') 720 | 721 | else: 722 | doc = pd.Series(db.get(docid['id'])) 723 | trialDur = doc.trialDur 724 | deviant = doc.deviant 725 | targetFreq = doc.targetFreq 726 | SNR = doc.targetdB 727 | targetRate = 1/doc.targetSOA 728 | soundStart = 0 729 | recoverTargetSOA = None 730 | recoverNoise = doc.noise 731 | target=doc.target 732 | score=doc.score 733 | 734 | 735 | lookupTable.loc[count] = pd.Series({'trialNum': trialNum, 736 | 'SOA': recoverTargetSOA, 'SNR': SNR, 'targetRate': targetRate, 737 | 'targetFreq': targetFreq, 'trialDur': trialDur, 738 | 'soundStart': soundStart, 'deviant': deviant, 739 | 'noise': recoverNoise, 'target': target, 'score': score}) 740 | 741 | return lookupTable 742 | 743 | def getEvents(raw, eventCode, shortest_event=None): 744 | """ 745 | Get the events corresponding to `eventCode`. 746 | 747 | Parameters 748 | ---------- 749 | raw : instance of mne.io.edf.edf.RawEDF 750 | RawEDF object from the MNE library containing data from the .bdf files. 751 | eventCode : int 752 | Code corresponding to a specific events. For instance, with a biosemi 753 | device, the triggers are coded 65284, 65288 and 65296 respectively on 754 | the first, second and third channel. 755 | 756 | Returns: 757 | 758 | startEvents : instance of pandas.core.DataFrame 759 | Dataframe containing the list of timing corresponding to the event code 760 | in the first column. The second column contains the code before the event 761 | and the third the code of the selected event. 762 | """ 763 | if shortest_event: 764 | events = mne.find_events(raw, shortest_event=shortest_event) 765 | else: 766 | events = mne.find_events(raw) 767 | 768 | eventsDf = pd.DataFrame(events) 769 | 770 | # Keep only event corresponding to the event code 771 | startEvents = eventsDf.loc[eventsDf[2]==eventCode] 772 | 773 | startEvents = startEvents.set_index([np.arange(len(startEvents))]) 774 | startEvents.index.name = 'start' 775 | return startEvents 776 | 777 | def getTrialsAverage(data, events, trialDur=None, trialNumList=None, 778 | baselineDur=0.1, normalize=False, fs=2048., startOffset=0, noiseAve=None): 779 | """ 780 | Get the average across trials (from `trialNumList`) based on time-locking 781 | provided by `events`. 782 | 783 | Parameters 784 | ---------- 785 | data : instance of pandas.core.DataFrame 786 | Data containing values across time (not epoched). 787 | events : instance of pandas.core.DataFrame 788 | Dataframe containing the list of events obtained with 789 | mne.find_events(raw). 790 | trialDur : float | None 791 | Trial duration in seconds. 792 | trialNumList : array-like of int | None 793 | List of all trials to use. If None, all trials are taken. 794 | baselineDur : float, defaults to 0.1 795 | Duration of the baseline in seconds. If normalize is True, normalize 796 | each electrode with a baseline of duration `baselineDur`. 797 | normalize : bool, defaults to False 798 | If True data will be normalized. 799 | fs : float 800 | Sampling frequency of data in Hz. 801 | 802 | Returns: 803 | 804 | meanTrials : instance of pandas.series.Series 805 | Series containing the averaged values across trials. 806 | allTrials : instance of pandas.core.DataFrame 807 | Dataframe containing the values of each trial (1 column = 1 trial). 808 | """ 809 | 810 | # Calculate average across trials for this Series 811 | if (trialNumList is None): 812 | trialNumList = [events.shape[0]] 813 | 814 | allTrials = pd.DataFrame() 815 | for trialNum in trialNumList: 816 | trialData = getTrialData(data, events=events, trialNum=trialNum, 817 | baselineDur=baselineDur, trialDur=trialDur, fs=fs, 818 | startOffset=startOffset) 819 | if normalize: 820 | trialData = normalizeFromBaseline(trialData, 821 | baselineDur=baselineDur, fs=fs) 822 | 823 | trialData = trialData.reset_index(drop=True) 824 | 825 | if noiseAve is not None: 826 | trialData = trialData - noiseAve 827 | 828 | allTrials['trial%d' % trialNum] = pd.Series(trialData) 829 | 830 | # convert baselineDur in frames 831 | start = int(np.round(baselineDur *fs)) 832 | 833 | # Change index to have x-axis in seconds 834 | allTrials = allTrials.set_index(np.arange(-start, allTrials.shape[0]-start)/fs) 835 | 836 | meanTrials = allTrials.mean(axis=1) 837 | return meanTrials, allTrials 838 | 839 | def getTrialData(data, events, trialNum=0, electrode=None, baselineDur=0.1, 840 | trialDur=None, fs=2048., startOffset=0): 841 | """ 842 | Get the epochs from data (time series containing all epochs/trials) for the 843 | trial `trialNum`. 844 | 845 | Parameters 846 | ---------- 847 | data : instance of pandas.core.DataFrame 848 | Data containing values across time (not epoched). 849 | events : instance of pandas.core.DataFrame 850 | Dataframe containing the list of events obtained with 851 | mne.find_events(raw). 852 | trialNum : int, defaults to 0 853 | Trial number. The returned epoch corresponds to this trial number. 854 | electrode : str, default to None 855 | The epoch will returned only for this electrode. If None, all electrodes 856 | will be used. 857 | baselineDur : float, defaults to 0.1 858 | Duration of the baseline in seconds. The returned epoch contains this 859 | duration at the beginning for further use (normalization, plots...). 860 | trialDur : float | None 861 | Trial duration in seconds. If None, the whole trial duration will be 862 | used. 863 | fs : float 864 | Sampling frequency of data in Hz. 865 | 866 | Returns: 867 | 868 | dataElectrode : instance of pandas.core.DataFrame 869 | Dataframe containing 1 trial from `data` for every or 1 electrode. 870 | 871 | """ 872 | baselineDurSamples = int(np.round(baselineDur * fs)) 873 | startOffsetSamples = int(np.round(startOffset * fs)) 874 | 875 | start = events[0][trialNum]-baselineDurSamples+startOffsetSamples 876 | if (trialDur is None): 877 | if (trialNumstart and i