eegprep 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eegprep-0.0.2/LICENSE +20 -0
- eegprep-0.0.2/PKG-INFO +56 -0
- eegprep-0.0.2/README.md +14 -0
- eegprep-0.0.2/pyproject.toml +33 -0
- eegprep-0.0.2/setup.cfg +4 -0
- eegprep-0.0.2/src/eegprep/ICL_feature_extractor.py +90 -0
- eegprep-0.0.2/src/eegprep/ICL_feature_extractor_compare_helper.py +19 -0
- eegprep-0.0.2/src/eegprep/__init__.py +1 -0
- eegprep-0.0.2/src/eegprep/eeg_autocorr.py +61 -0
- eegprep-0.0.2/src/eegprep/eeg_autocorr_compare_helper.py +13 -0
- eegprep-0.0.2/src/eegprep/eeg_autocorr_fftw.py +68 -0
- eegprep-0.0.2/src/eegprep/eeg_autocorr_fftw_compare_helper.py +20 -0
- eegprep-0.0.2/src/eegprep/eeg_autocorr_welch.py +61 -0
- eegprep-0.0.2/src/eegprep/eeg_autocorr_welch_compare_helper.py +13 -0
- eegprep-0.0.2/src/eegprep/eeg_checkset.py +110 -0
- eegprep-0.0.2/src/eegprep/eeg_eeglab2mne.py +39 -0
- eegprep-0.0.2/src/eegprep/eeg_mne2eeglab.py +46 -0
- eegprep-0.0.2/src/eegprep/eeg_mne2eeglab_epochs.py +187 -0
- eegprep-0.0.2/src/eegprep/eeg_rpsd.py +65 -0
- eegprep-0.0.2/src/eegprep/eeg_rpsd_compare_helper.py +17 -0
- eegprep-0.0.2/src/eegprep/eeglabcompat.py +117 -0
- eegprep-0.0.2/src/eegprep/iclabel.py +51 -0
- eegprep-0.0.2/src/eegprep/iclabel_helper.py +26 -0
- eegprep-0.0.2/src/eegprep/iclabel_net.py +136 -0
- eegprep-0.0.2/src/eegprep/iclabel_net_load_py_measures.py +137 -0
- eegprep-0.0.2/src/eegprep/pop_loadset.py +97 -0
- eegprep-0.0.2/src/eegprep/pop_reref.py +44 -0
- eegprep-0.0.2/src/eegprep/pop_reref_compare_helper.py +31 -0
- eegprep-0.0.2/src/eegprep/pop_reref_helper.py +22 -0
- eegprep-0.0.2/src/eegprep/pop_resample.py +42 -0
- eegprep-0.0.2/src/eegprep/pop_saveset.py +201 -0
- eegprep-0.0.2/src/eegprep/save_struct_as_hdf5.py +50 -0
- eegprep-0.0.2/src/eegprep/topoplot.py +194 -0
- eegprep-0.0.2/src/eegprep/topoplot_compare_helper.py +29 -0
- eegprep-0.0.2/src/eegprep.egg-info/PKG-INFO +56 -0
- eegprep-0.0.2/src/eegprep.egg-info/SOURCES.txt +37 -0
- eegprep-0.0.2/src/eegprep.egg-info/dependency_links.txt +1 -0
- eegprep-0.0.2/src/eegprep.egg-info/requires.txt +7 -0
- eegprep-0.0.2/src/eegprep.egg-info/top_level.txt +1 -0
eegprep-0.0.2/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
GNU General Public License
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2024-2025
|
|
4
|
+
|
|
5
|
+
Young Truong, UCSD, dt.young112@gmail.com
|
|
6
|
+
Arnaud Delorme, UCSD, adelorme@ucsd.edu
|
|
7
|
+
|
|
8
|
+
This program is free software; you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU General Public License as published by
|
|
10
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
11
|
+
(at your option) any later version.
|
|
12
|
+
|
|
13
|
+
This program is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU General Public License
|
|
19
|
+
along with this program; if not, write to the Free Software
|
|
20
|
+
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1.07 USA
|
eegprep-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: eegprep
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: EEG preprocessing pipeline on Python
|
|
5
|
+
Author-email: Arnaud Delorme <adelorme@gmail.com>, Young Truong <dt.young112@gmail.com>
|
|
6
|
+
License: GNU General Public License
|
|
7
|
+
|
|
8
|
+
Copyright (C) 2024-2025
|
|
9
|
+
|
|
10
|
+
Young Truong, UCSD, dt.young112@gmail.com
|
|
11
|
+
Arnaud Delorme, UCSD, adelorme@ucsd.edu
|
|
12
|
+
|
|
13
|
+
This program is free software; you can redistribute it and/or modify
|
|
14
|
+
it under the terms of the GNU General Public License as published by
|
|
15
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
16
|
+
(at your option) any later version.
|
|
17
|
+
|
|
18
|
+
This program is distributed in the hope that it will be useful,
|
|
19
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
20
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
21
|
+
GNU General Public License for more details.
|
|
22
|
+
|
|
23
|
+
You should have received a copy of the GNU General Public License
|
|
24
|
+
along with this program; if not, write to the Free Software
|
|
25
|
+
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1.07 USA
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://github.com/sccn/eegprep
|
|
28
|
+
Project-URL: Issues, https://github.com/sccn/eegprep/issues
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Operating System :: OS Independent
|
|
32
|
+
Requires-Python: >=3.8
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
License-File: LICENSE
|
|
35
|
+
Requires-Dist: numpy>=1.21.0
|
|
36
|
+
Requires-Dist: scipy>=1.7.0
|
|
37
|
+
Requires-Dist: matplotlib>=3.4.0
|
|
38
|
+
Requires-Dist: mne>=1.0.0
|
|
39
|
+
Requires-Dist: oct2py>=5.5.0
|
|
40
|
+
Requires-Dist: torch>=1.10.0
|
|
41
|
+
Requires-Dist: h5py>=3.3.0
|
|
42
|
+
|
|
43
|
+
## how to duplicate a function
|
|
44
|
+
|
|
45
|
+
1. Get a MATLAB file to load and process an EEG file
|
|
46
|
+
|
|
47
|
+
2. Convert the code using GPT4 or when short Copilot and test in a Notebook. Once the code runs without erroring, move to 3.
|
|
48
|
+
|
|
49
|
+
3. Use the Jupyter code to create a Python file (not notebook) to load the same file as MATLAB and process it as well (in plain Python, not in a subfunction)
|
|
50
|
+
|
|
51
|
+
4. Start the debugger in both and compare. Note that it is better to use the debugger on Python file than Jupyter Notebook (could not get it to stop)
|
|
52
|
+
|
|
53
|
+
5. Once the result is the same, package the Python code in a function with the same name as MATLAB
|
|
54
|
+
|
|
55
|
+
6. Write the function to compare (see example) and the helper Python function to load the file (note that there could be a general Python helper function)
|
|
56
|
+
|
eegprep-0.0.2/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## how to duplicate a function
|
|
2
|
+
|
|
3
|
+
1. Get a MATLAB file to load and process an EEG file
|
|
4
|
+
|
|
5
|
+
2. Convert the code using GPT4 or when short Copilot and test in a Notebook. Once the code runs without erroring, move to 3.
|
|
6
|
+
|
|
7
|
+
3. Use the Jupyter code to create a Python file (not notebook) to load the same file as MATLAB and process it as well (in plain Python, not in a subfunction)
|
|
8
|
+
|
|
9
|
+
4. Start the debugger in both and compare. Note that it is better to use the debugger on Python file than Jupyter Notebook (could not get it to stop)
|
|
10
|
+
|
|
11
|
+
5. Once the result is the same, package the Python code in a function with the same name as MATLAB
|
|
12
|
+
|
|
13
|
+
6. Write the function to compare (see example) and the helper Python function to load the file (note that there could be a general Python helper function)
|
|
14
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "eegprep"
|
|
7
|
+
version = "0.0.2"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Arnaud Delorme", email="adelorme@gmail.com" },
|
|
10
|
+
{ name="Young Truong", email="dt.young112@gmail.com" },
|
|
11
|
+
]
|
|
12
|
+
description = "EEG preprocessing pipeline on Python"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
license = { file = "LICENSE" }
|
|
15
|
+
requires-python = ">=3.8"
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"numpy>=1.21.0",
|
|
23
|
+
"scipy>=1.7.0",
|
|
24
|
+
"matplotlib>=3.4.0",
|
|
25
|
+
"mne>=1.0.0",
|
|
26
|
+
"oct2py>=5.5.0",
|
|
27
|
+
"torch>=1.10.0",
|
|
28
|
+
"h5py>=3.3.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/sccn/eegprep"
|
|
33
|
+
Issues = "https://github.com/sccn/eegprep/issues"
|
eegprep-0.0.2/setup.cfg
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .topoplot import topoplot
|
|
3
|
+
from .eeg_rpsd import eeg_rpsd
|
|
4
|
+
from .eeg_autocorr_welch import eeg_autocorr_welch
|
|
5
|
+
from .eeg_autocorr import eeg_autocorr
|
|
6
|
+
from .eeg_autocorr_fftw import eeg_autocorr_fftw
|
|
7
|
+
from .pop_reref import pop_reref
|
|
8
|
+
|
|
9
|
+
def ICL_feature_extractor(EEG, flag_autocorr=False):
|
|
10
|
+
|
|
11
|
+
# Check inputs
|
|
12
|
+
ncomp = EEG['icawinv'].shape[1]
|
|
13
|
+
|
|
14
|
+
# Check for ICA key and if it is not empty
|
|
15
|
+
if not 'icawinv' in EEG.keys() or EEG['icawinv'].size == 0:
|
|
16
|
+
raise ValueError('You must have an ICA decomposition to use ICLabel')
|
|
17
|
+
|
|
18
|
+
# Assuming chanlocs are correct
|
|
19
|
+
if EEG['ref'] != 'average' and EEG['ref'] != 'averef':
|
|
20
|
+
EEG = pop_reref(EEG, []) #, exclude=list(set(range(EEG.nbchan)) - set(EEG.icachansind)))
|
|
21
|
+
# raise ValueError('Data must be rereferenced to average to use ICLabel')
|
|
22
|
+
# EEG = pop_reref(EEG, [], exclude=list(set(range(EEG.nbchan)) - set(EEG.icachansind)))
|
|
23
|
+
|
|
24
|
+
# Calculate ICA activations if missing and cast to double
|
|
25
|
+
if EEG['icaact'] is None:
|
|
26
|
+
raise ValueError('You must have ICA activations to use ICLabel')
|
|
27
|
+
# EEG['icaact'] = eeg_getica(EEG)
|
|
28
|
+
|
|
29
|
+
EEG['icaact'] = EEG['icaact'].astype(float)
|
|
30
|
+
|
|
31
|
+
# Check ICA is real
|
|
32
|
+
assert np.isreal(EEG['icaact']).all(), 'Your ICA decomposition must be real to use ICLabel'
|
|
33
|
+
|
|
34
|
+
# Calculate topo
|
|
35
|
+
topo = np.zeros((32, 32, 1, ncomp))
|
|
36
|
+
for it in range(ncomp):
|
|
37
|
+
_, temp_topo, _, _, _ = topoplot(EEG['icawinv'][:, it], EEG['chanlocs'][EEG['icachansind']], noplot='on')
|
|
38
|
+
temp_topo[np.isnan(temp_topo)] = 0
|
|
39
|
+
topo[:, :, 0, it] = temp_topo / np.max(np.abs(temp_topo))
|
|
40
|
+
|
|
41
|
+
# Cast
|
|
42
|
+
topo = topo.astype(np.float32)
|
|
43
|
+
|
|
44
|
+
# Calculate PSD
|
|
45
|
+
psd = eeg_rpsd(EEG, 100)
|
|
46
|
+
|
|
47
|
+
# Extrapolate or prune as needed
|
|
48
|
+
nfreq = psd.shape[1]
|
|
49
|
+
if nfreq < 100:
|
|
50
|
+
psd = np.hstack((psd, np.tile(psd[:, -1][:, np.newaxis], (1, 100 - nfreq))))
|
|
51
|
+
|
|
52
|
+
# Undo notch filter
|
|
53
|
+
for linenoise_ind in [50, 60]:
|
|
54
|
+
linenoise_around = [linenoise_ind - 1, linenoise_ind + 1]
|
|
55
|
+
difference = psd[:, linenoise_around] - psd[:, linenoise_ind][:, np.newaxis]
|
|
56
|
+
notch_ind = np.all(difference > 5, axis=1)
|
|
57
|
+
if np.any(notch_ind):
|
|
58
|
+
psd[notch_ind, linenoise_ind] = np.mean(psd[notch_ind][:, linenoise_around], axis=1)
|
|
59
|
+
|
|
60
|
+
# Normalize
|
|
61
|
+
psd = psd / np.max(np.abs(psd), axis=1)[:, np.newaxis]
|
|
62
|
+
|
|
63
|
+
psd = np.transpose(np.expand_dims(np.expand_dims(psd, axis=-1), axis=-1), (2, 1, 3, 0)).astype(np.float32)
|
|
64
|
+
|
|
65
|
+
# Calculate autocorrelation
|
|
66
|
+
if flag_autocorr:
|
|
67
|
+
if EEG['trials'] == 1:
|
|
68
|
+
if EEG['pnts'] / EEG['srate'] > 5:
|
|
69
|
+
autocorr = eeg_autocorr_welch(EEG)
|
|
70
|
+
else:
|
|
71
|
+
autocorr = eeg_autocorr(EEG)
|
|
72
|
+
else:
|
|
73
|
+
autocorr = eeg_autocorr_fftw(EEG)
|
|
74
|
+
|
|
75
|
+
# Reshape and cast
|
|
76
|
+
autocorr = np.transpose(np.expand_dims(np.expand_dims(autocorr, axis=-1), axis=-1), (2, 1, 3, 0)).astype(np.float32)
|
|
77
|
+
|
|
78
|
+
# Format outputs
|
|
79
|
+
if flag_autocorr:
|
|
80
|
+
features = [0.99 * topo, 0.99 * psd, 0.99 * autocorr]
|
|
81
|
+
else:
|
|
82
|
+
features = [0.99 * topo, 0.99 * psd]
|
|
83
|
+
|
|
84
|
+
return features
|
|
85
|
+
|
|
86
|
+
def test_ICL_feature_extractor():
|
|
87
|
+
flag_autocorr = True
|
|
88
|
+
EEG = EEG2
|
|
89
|
+
EEG['ref'] = 'averef'
|
|
90
|
+
EEG['chanlocs'] = np.random.randn(32, 3)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ICL_feature_extractor import ICL_feature_extractor
|
|
2
|
+
from pop_loadset import pop_loadset
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
# check if a parameter is present and if it is assign eeglab_file_path to it
|
|
6
|
+
if len(sys.argv) > 1:
|
|
7
|
+
eeglab_file_path = sys.argv[1]
|
|
8
|
+
else:
|
|
9
|
+
eeglab_file_path = './eeglab_data_with_ica_tmp.set'
|
|
10
|
+
eeglab_file_path = '/Users/arno/Downloads/STUDYstern/S01/Memorize.set'
|
|
11
|
+
EEG = pop_loadset(eeglab_file_path)
|
|
12
|
+
|
|
13
|
+
# Print the loaded data
|
|
14
|
+
res = ICL_feature_extractor(EEG, True)
|
|
15
|
+
|
|
16
|
+
# save in a matlab file
|
|
17
|
+
import scipy.io
|
|
18
|
+
scipy.io.savemat('python_temp.mat', {'grid': res})
|
|
19
|
+
print('Saved python_temp.mat')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .iclabel import iclabel
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.signal import resample_poly
|
|
3
|
+
from numpy.fft import fft, ifft
|
|
4
|
+
|
|
5
|
+
def eeg_autocorr(EEG, pct_data=None):
|
|
6
|
+
if pct_data is None:
|
|
7
|
+
pct_data = 100
|
|
8
|
+
|
|
9
|
+
# convert EEG['icaact'] to single precision
|
|
10
|
+
EEG['icaact'] = EEG['icaact'].astype(np.float32)
|
|
11
|
+
|
|
12
|
+
ncomp = EEG['icaact'].shape[0]
|
|
13
|
+
nfft = 2**np.ceil(np.log2(2 * EEG['pnts'] - 1)).astype(int)
|
|
14
|
+
|
|
15
|
+
# Calculate autocorrelation
|
|
16
|
+
c = np.zeros((ncomp, nfft))
|
|
17
|
+
for it in range(ncomp):
|
|
18
|
+
comp = EEG['icaact'][it,:].reshape(-1)
|
|
19
|
+
Xtmp = fft(comp, nfft)
|
|
20
|
+
Xtmp = Xtmp.astype(np.complex64) # matches MATLAB's single precision since python convert to double precision by default
|
|
21
|
+
X = np.abs(Xtmp)**2
|
|
22
|
+
c[it, :] = np.real(ifft(X))
|
|
23
|
+
|
|
24
|
+
# Adjust the size of the autocorrelation to match sampling rate
|
|
25
|
+
if EEG['pnts'] < EEG['srate']:
|
|
26
|
+
ac = np.hstack([c[:, :EEG['pnts']], np.zeros((ncomp, EEG['srate'] - EEG['pnts'] + 1))])
|
|
27
|
+
else:
|
|
28
|
+
ac = c[:, :EEG['srate'] + 1]
|
|
29
|
+
|
|
30
|
+
# Normalize by the 0-tap of the autocorrelation
|
|
31
|
+
ac /= ac[:, [0]]
|
|
32
|
+
|
|
33
|
+
# Resample to 1 second at 100 samples/sec
|
|
34
|
+
|
|
35
|
+
# print the size of the second dim of ac
|
|
36
|
+
ac = resample_poly(ac.T, up=100, down=EEG['srate']).T
|
|
37
|
+
ac = ac[:, 1:]
|
|
38
|
+
|
|
39
|
+
return ac
|
|
40
|
+
|
|
41
|
+
def test_eeg_autocorr():
|
|
42
|
+
EEG = {
|
|
43
|
+
'srate': 256,
|
|
44
|
+
'icaweights': np.random.randn(10, 256),
|
|
45
|
+
'pnts': 1000,
|
|
46
|
+
'trials': 5,
|
|
47
|
+
'icaact': np.random.randn(10, 1000, 5)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
psdmed = eeg_autocorr(EEG, 100)
|
|
51
|
+
|
|
52
|
+
# print information about psdmed
|
|
53
|
+
print(psdmed.shape)
|
|
54
|
+
|
|
55
|
+
#print(psdmed)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
#assert psdmed.shape == (10, 100)
|
|
59
|
+
#assert np.all(np.isfinite(psdmed))
|
|
60
|
+
|
|
61
|
+
test_eeg_autocorr()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from eeg_autocorr import eeg_autocorr
|
|
2
|
+
from pop_loadset import pop_loadset
|
|
3
|
+
|
|
4
|
+
eeglab_file_path = './eeglab_data_with_ica_tmp.set'
|
|
5
|
+
EEG = pop_loadset(eeglab_file_path)
|
|
6
|
+
|
|
7
|
+
# Print the loaded data
|
|
8
|
+
res = eeg_autocorr(EEG, 100)
|
|
9
|
+
|
|
10
|
+
# save in a matlab file
|
|
11
|
+
import scipy.io
|
|
12
|
+
scipy.io.savemat('eeg_autocorr_data.mat', {'grid': res})
|
|
13
|
+
print('Saved eeg_autocorr_data.mat')
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy import signal
|
|
3
|
+
from scipy.fft import fft, ifft, next_fast_len
|
|
4
|
+
from scipy.signal import resample_poly
|
|
5
|
+
from .pop_loadset import pop_loadset
|
|
6
|
+
from .pop_reref import pop_reref
|
|
7
|
+
|
|
8
|
+
def eeg_autocorr_fftw(EEG, pct_data=100):
|
|
9
|
+
|
|
10
|
+
# FFT length
|
|
11
|
+
nfft = next_fast_len(2 * EEG['pnts'] - 1)
|
|
12
|
+
|
|
13
|
+
# Initialize autocorrelation array
|
|
14
|
+
ncomp = EEG['icaact'].shape[0]
|
|
15
|
+
ac = np.zeros((ncomp, nfft))
|
|
16
|
+
|
|
17
|
+
# Calculate autocorrelation using FFT
|
|
18
|
+
for it in range(EEG['icaact'].shape[0]):
|
|
19
|
+
# Apply FFT
|
|
20
|
+
X = fft(EEG['icaact'][it, :, :], n=nfft, axis=0)
|
|
21
|
+
# Compute the mean of the power spectrum
|
|
22
|
+
ac[it, :] = np.mean(np.abs(X)**2, axis=1)
|
|
23
|
+
|
|
24
|
+
# Inverse FFT to get autocorrelation
|
|
25
|
+
ac = ifft(ac, axis=1)
|
|
26
|
+
|
|
27
|
+
# make sure the data is in real
|
|
28
|
+
ac = np.real(ac)
|
|
29
|
+
|
|
30
|
+
# Adjust the size of autocorrelation array
|
|
31
|
+
if EEG['pnts'] < EEG['srate']:
|
|
32
|
+
# ac = np.hstack( [ac[:, :EEG['pnts']], np.zeros((ncomp , EEG['srate'] - EEG['pnts'] + 1))])
|
|
33
|
+
ac = np.concatenate((ac[:, :EEG['pnts']], np.zeros((ac.shape[0], EEG['srate'] - EEG['pnts'] + 1))), axis=1)
|
|
34
|
+
else:
|
|
35
|
+
ac = ac[:, :EEG['srate'] + 1]
|
|
36
|
+
|
|
37
|
+
# Normalize by 0-lag autocorrelation
|
|
38
|
+
ac = ac / ac[:, 0][:, np.newaxis]
|
|
39
|
+
|
|
40
|
+
# resample to 1 second at 100 samples/sec
|
|
41
|
+
ac = resample_poly(ac.T, up=100, down=EEG['srate']).T
|
|
42
|
+
ac = ac[:, 1:101]
|
|
43
|
+
|
|
44
|
+
return ac
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_eeg_autocorr_fftw():
|
|
48
|
+
EEG = {
|
|
49
|
+
'srate': 256,
|
|
50
|
+
'icaweights': np.random.randn(10, 256),
|
|
51
|
+
'pnts': 1000,
|
|
52
|
+
'trials': 5,
|
|
53
|
+
'icaact': np.random.randn(10, 1000, 5)
|
|
54
|
+
}
|
|
55
|
+
EEG = pop_loadset('/System/Volumes/Data/data/data/STUDIES/STERN/S01/Memorize.set')
|
|
56
|
+
|
|
57
|
+
# reshape the last two dimensions of EEG['icaact']
|
|
58
|
+
# EEG['icaact'] = EEG['icaact'].reshape(EEG['icaact'].shape[0], -1)
|
|
59
|
+
|
|
60
|
+
# convert EEG['icaact'] to double precision
|
|
61
|
+
|
|
62
|
+
psdmed = eeg_autocorr_fftw(EEG, 100)
|
|
63
|
+
|
|
64
|
+
# print information about psdmed
|
|
65
|
+
print(psdmed.shape)
|
|
66
|
+
print(psdmed)
|
|
67
|
+
|
|
68
|
+
# test_eeg_autocorr_fftw()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from eeg_autocorr_fftw import eeg_autocorr_fftw
|
|
2
|
+
from pop_loadset import pop_loadset
|
|
3
|
+
from pop_reref import pop_reref
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
# check if a parameter is present and if it is assign eeglab_file_path to it
|
|
7
|
+
if len(sys.argv) > 1:
|
|
8
|
+
eeglab_file_path = sys.argv[1]
|
|
9
|
+
else:
|
|
10
|
+
eeglab_file_path = './eeglab_data_with_ica_tmp.set'
|
|
11
|
+
EEG = pop_loadset(eeglab_file_path)
|
|
12
|
+
EEG = pop_reref(EEG, [])
|
|
13
|
+
|
|
14
|
+
# Print the loaded data
|
|
15
|
+
res = eeg_autocorr_fftw(EEG, 100)
|
|
16
|
+
|
|
17
|
+
# save in a matlab file
|
|
18
|
+
import scipy.io
|
|
19
|
+
scipy.io.savemat('eeg_autocorr_data.mat', {'grid': res})
|
|
20
|
+
print('Saved eeg_autocorr_data.mat')
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.signal import resample_poly
|
|
3
|
+
import random
|
|
4
|
+
from .pop_loadset import pop_loadset
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.fft import fft, ifft
|
|
7
|
+
|
|
8
|
+
def eeg_autocorr_welch(EEG, pct_data=100):
|
|
9
|
+
# clean input cutoff freq
|
|
10
|
+
if pct_data is None or pct_data == 0:
|
|
11
|
+
pct_data = 100
|
|
12
|
+
|
|
13
|
+
# setup constants
|
|
14
|
+
ncomp = EEG['icaweights'].shape[0]
|
|
15
|
+
n_points = min(EEG['pnts'], EEG['srate'] * 3)
|
|
16
|
+
nfft = 2**(int(np.log2(n_points * 2 - 1)) + 1)
|
|
17
|
+
cutoff = (EEG['pnts'] // n_points) * n_points
|
|
18
|
+
index = np.add.outer(np.ceil(np.arange(0, cutoff - n_points + 1, n_points // 2)).astype(int), np.arange(n_points)).astype(int)
|
|
19
|
+
index = index.T
|
|
20
|
+
|
|
21
|
+
# separate data segments
|
|
22
|
+
if pct_data != 100:
|
|
23
|
+
random.seed(0)
|
|
24
|
+
n_seg = index.shape[0] * EEG['trials']
|
|
25
|
+
subset = random.sample(range(n_seg), int(np.ceil(n_seg * pct_data / 100)))
|
|
26
|
+
random.seed() # restore normal random behavior
|
|
27
|
+
temp = np.reshape(EEG['icaact'][:, index, :], (ncomp, *index.shape, EEG['trials']))
|
|
28
|
+
segments = temp[:, :, subset]
|
|
29
|
+
else:
|
|
30
|
+
segments = np.reshape(EEG['icaact'][:, index, :], (ncomp, *index.shape, EEG['trials']))
|
|
31
|
+
|
|
32
|
+
# calc autocorrelation
|
|
33
|
+
ac = np.zeros((ncomp, nfft))
|
|
34
|
+
for it in range(ncomp):
|
|
35
|
+
fftpow = np.mean(np.abs(fft(segments[it, :, :], nfft, axis=0))**2, axis=1)
|
|
36
|
+
ac[it, :] = np.real(ifft(fftpow, axis=0)).T
|
|
37
|
+
|
|
38
|
+
# normalizefft
|
|
39
|
+
if EEG['pnts'] < EEG['srate']:
|
|
40
|
+
ac = np.concatenate([ac[:, :EEG['pnts']] / (ac[:, 0][:, np.newaxis] * np.arange(n_points, 0, -1) / n_points),
|
|
41
|
+
np.zeros((ncomp, EEG['srate'] - n_points + 1))], axis=1)
|
|
42
|
+
else:
|
|
43
|
+
ac = ac[:, :EEG['srate'] + 1] / (ac[:, 0][:, np.newaxis] * np.concatenate((np.arange(n_points, n_points - EEG['srate'], -1), np.array([max(1, n_points - EEG['srate'])]))) / n_points)
|
|
44
|
+
|
|
45
|
+
# resample to 1 second at 100 samples/sec
|
|
46
|
+
ac = resample_poly(ac.T, up=100, down=EEG['srate']).T
|
|
47
|
+
ac = ac[:, 1:101]
|
|
48
|
+
|
|
49
|
+
return ac
|
|
50
|
+
|
|
51
|
+
def test_eeg_autocorr_welch():
|
|
52
|
+
eeglab_file_path = './eeglab_data_with_ica_tmp.set'
|
|
53
|
+
EEG = pop_loadset(eeglab_file_path)
|
|
54
|
+
|
|
55
|
+
psdmed = eeg_autocorr_welch(EEG, 100)
|
|
56
|
+
|
|
57
|
+
# print information about psdmed
|
|
58
|
+
print(psdmed.shape)
|
|
59
|
+
print(psdmed)
|
|
60
|
+
|
|
61
|
+
# test_eeg_autocorr_welch()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from eeg_autocorr_welch import eeg_autocorr_welch
|
|
2
|
+
from pop_loadset import pop_loadset
|
|
3
|
+
|
|
4
|
+
eeglab_file_path = './eeglab_data_with_ica_tmp.set'
|
|
5
|
+
EEG = pop_loadset(eeglab_file_path)
|
|
6
|
+
|
|
7
|
+
# Print the loaded data
|
|
8
|
+
res = eeg_autocorr_welch(EEG, 100)
|
|
9
|
+
|
|
10
|
+
# save in a matlab file
|
|
11
|
+
import scipy.io
|
|
12
|
+
scipy.io.savemat('eeg_autocorr_welch_data.mat', {'grid': res})
|
|
13
|
+
print('Saved eeg_autocorr_welch_data.mat')
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
from pop_loadset import pop_loadset
|
|
4
|
+
|
|
5
|
+
def eeg_checkset(EEG):
|
|
6
|
+
|
|
7
|
+
# Define the expected types
|
|
8
|
+
expected_types = {
|
|
9
|
+
'setname': str,
|
|
10
|
+
'filename': str,
|
|
11
|
+
'filepath': str,
|
|
12
|
+
'subject': str,
|
|
13
|
+
'group': str,
|
|
14
|
+
'condition': str,
|
|
15
|
+
'session': (str, int),
|
|
16
|
+
'comments': np.ndarray,
|
|
17
|
+
'nbchan': int,
|
|
18
|
+
'trials': int,
|
|
19
|
+
'pnts': int,
|
|
20
|
+
'srate': (float,int),
|
|
21
|
+
'xmin': float,
|
|
22
|
+
'xmax': float,
|
|
23
|
+
'times': np.ndarray, # Expecting a float numpy array
|
|
24
|
+
'data': np.ndarray, # Expecting a float numpy array
|
|
25
|
+
'icaact': np.ndarray, # Expecting a float numpy array
|
|
26
|
+
'icawinv': np.ndarray,# Expecting a float numpy array
|
|
27
|
+
'icasphere': np.ndarray, # Expecting a float numpy array
|
|
28
|
+
'icaweights': np.ndarray, # Expecting a float numpy array
|
|
29
|
+
'icachansind': np.ndarray, # Expecting an integer numpy array
|
|
30
|
+
'chanlocs': np.ndarray, # Expecting numpy array of dictionaries
|
|
31
|
+
'urchanlocs': np.ndarray, # Expecting numpy array of dictionaries
|
|
32
|
+
'chaninfo': dict,
|
|
33
|
+
'ref': str,
|
|
34
|
+
'event': np.ndarray, # Expecting numpy array of dictionaries
|
|
35
|
+
'urevent': np.ndarray, # Expecting numpy array of dictionaries
|
|
36
|
+
'eventdescription': np.ndarray, # Expecting numpy array of strings
|
|
37
|
+
'epoch': np.ndarray, # Expecting numpy array of dictionaries
|
|
38
|
+
'epochdescription': np.ndarray, # Expecting numpy array of strings
|
|
39
|
+
'reject': dict,
|
|
40
|
+
'stats': dict,
|
|
41
|
+
'specdata': dict,
|
|
42
|
+
'specicaact': dict,
|
|
43
|
+
'splinefile': str,
|
|
44
|
+
'icasplinefile': str,
|
|
45
|
+
'dipfit': dict,
|
|
46
|
+
'history': str,
|
|
47
|
+
'saved': str,
|
|
48
|
+
'etc': dict,
|
|
49
|
+
'datfile': str,
|
|
50
|
+
'run': (str, int),
|
|
51
|
+
'roi': dict,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Iterate through expected types and check input dictionary
|
|
55
|
+
for field, expected_type in expected_types.items():
|
|
56
|
+
if field not in EEG:
|
|
57
|
+
print(f"Field '{field}' is missing from the EEG dictionnary, adding it.")
|
|
58
|
+
|
|
59
|
+
# add default values
|
|
60
|
+
if expected_type == str:
|
|
61
|
+
EEG[field] = ''
|
|
62
|
+
elif expected_type == int:
|
|
63
|
+
EEG[field] = np.array([], dtype=int)
|
|
64
|
+
elif expected_type == float:
|
|
65
|
+
EEG[field] = np.array([], dtype=float)
|
|
66
|
+
elif expected_type == dict:
|
|
67
|
+
EEG[field] = {}
|
|
68
|
+
elif expected_type == np.ndarray:
|
|
69
|
+
EEG[field] = np.array([])
|
|
70
|
+
else:
|
|
71
|
+
EEG[field] = np.array([])
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
value = EEG[field]
|
|
75
|
+
|
|
76
|
+
# Special cases for numpy arrays with specific content types
|
|
77
|
+
if isinstance(expected_type, type) and expected_type == np.ndarray:
|
|
78
|
+
if not isinstance(value, np.ndarray):
|
|
79
|
+
print(f"Field '{field}' is expected to be a numpy array but is of type {type(value).__name__}.")
|
|
80
|
+
continue
|
|
81
|
+
# Further checks for numpy array content types
|
|
82
|
+
if field in ['times', 'data', 'icaact', 'icawinv', 'icasphere', 'icaweights']:
|
|
83
|
+
if not np.issubdtype(value.dtype, np.floating):
|
|
84
|
+
print(f"Field '{field}' is expected to be a numpy array of floats but has dtype {value.dtype}.")
|
|
85
|
+
elif field in ['icachansind']:
|
|
86
|
+
if not np.issubdtype(value.dtype, np.integer):
|
|
87
|
+
print(f"Field '{field}' is expected to be a numpy array of integers but has dtype {value.dtype}.")
|
|
88
|
+
elif field in ['chanlocs', 'urchanlocs', 'event', 'urevent', 'epoch']:
|
|
89
|
+
if not all(isinstance(item, dict) for item in value):
|
|
90
|
+
print(f"Field '{field}' is expected to be a numpy array of dictionaries but contains other types.")
|
|
91
|
+
# elif field in ['eventdescription', 'epochdescription']:
|
|
92
|
+
# if not all(isinstance(item, str) for item in value):
|
|
93
|
+
# print(f"Field '{field}' is expected to be a numpy array of strings but contains other types.")
|
|
94
|
+
else:
|
|
95
|
+
# General type check
|
|
96
|
+
if not isinstance(value, expected_type):
|
|
97
|
+
# check for empty Ndarray
|
|
98
|
+
if isinstance(value, np.ndarray) and value.size == 0:
|
|
99
|
+
continue
|
|
100
|
+
print(f"Field '{field}' is expected to be of type {expected_type} but is of type {type(value).__name__}.")
|
|
101
|
+
|
|
102
|
+
return EEG
|
|
103
|
+
|
|
104
|
+
def test_eeg_checkset():
|
|
105
|
+
eeglab_file_path = './eeglab_data_with_ica_tmp.set'
|
|
106
|
+
EEG = pop_loadset(eeglab_file_path)
|
|
107
|
+
EEG = eeg_checkset(EEG)
|
|
108
|
+
print('Checkset done')
|
|
109
|
+
|
|
110
|
+
test_eeg_checkset()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from eeg_autocorr import eeg_autocorr
|
|
2
|
+
from pop_loadset import pop_loadset
|
|
3
|
+
import mne
|
|
4
|
+
import tempfile
|
|
5
|
+
import os
|
|
6
|
+
from mne.export import export_raw
|
|
7
|
+
from pop_saveset import pop_saveset # in development
|
|
8
|
+
|
|
9
|
+
# write a funtion that converts a MNE raw object to an EEGLAB set file
|
|
10
|
+
def eeg_eeglab2mne(EEG):
|
|
11
|
+
|
|
12
|
+
# Generate a temporary file name
|
|
13
|
+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
|
14
|
+
temp_file_path = temp_file.name
|
|
15
|
+
|
|
16
|
+
base, _ = os.path.splitext(temp_file_path)
|
|
17
|
+
new_temp_file_path = base + ".set"
|
|
18
|
+
|
|
19
|
+
# save the raw file as a new EEGLAB .set file using MNE EEGLAB writer
|
|
20
|
+
pop_saveset(EEG, new_temp_file_path)
|
|
21
|
+
|
|
22
|
+
# load the EEGLAB set file
|
|
23
|
+
if EEG['trials'] > 1:
|
|
24
|
+
raw = mne.io.read_epochs_eeglab(new_temp_file_path)
|
|
25
|
+
else:
|
|
26
|
+
raw = mne.io.read_raw_eeglab(new_temp_file_path, preload=True)
|
|
27
|
+
|
|
28
|
+
return raw
|
|
29
|
+
|
|
30
|
+
def test_eeg_eeglab2mne():
|
|
31
|
+
eeglab_file_path = './eeglab_data_with_ica_tmp.set'
|
|
32
|
+
eeglab_file_path = '/System/Volumes/Data/data/matlab/eeglab/sample_data/eeglab_data_epochs_ica.set'
|
|
33
|
+
EEG = pop_loadset(eeglab_file_path)
|
|
34
|
+
raw = eeg_eeglab2mne(EEG)
|
|
35
|
+
|
|
36
|
+
# print the keys of the EEG dictionary
|
|
37
|
+
print(raw.info)
|
|
38
|
+
|
|
39
|
+
# test_eeg_eeglab2mne()
|