From 871edfa4cf03b0cf2c8e67125d0ec7642660993c Mon Sep 17 00:00:00 2001 From: Quarto GHA Workflow Runner Date: Tue, 8 Oct 2024 09:34:47 +0000 Subject: [PATCH] Built site for gh-pages --- .nojekyll | 2 +- bibliography.html | 269 +++++- course.html | 266 +++++- exercises/ex10_decoding.html | 272 +++++- exercises/ex11_sourceSpace.html | 274 +++++- exercises/ex1_overview.html | 272 +++++- exercises/ex2_erpComponents.html | 270 +++++- exercises/ex3_filter.html | 302 +++++- exercises/ex4_cleaning.html | 272 +++++- exercises/ex5_ICA.html | 272 +++++- exercises/ex6_linearModels.html | 303 ++++++- exercises/ex7_encoding.html | 301 +++++- exercises/ex8_clusterPerm.html | 301 +++++- exercises/ex9_tf.html | 272 +++++- exercises/exercises.html | 268 +++++- exercises/solutions/ex10_decoding.html | 606 +++++++++---- ...l-11-output-2.png => cell-11-output-1.png} | Bin ...l-22-output-2.png => cell-22-output-1.png} | Bin ...l-24-output-2.png => cell-24-output-1.png} | Bin ...l-25-output-2.png => cell-25-output-1.png} | Bin ...l-27-output-2.png => cell-27-output-1.png} | Bin exercises/solutions/ex11_sourceSpace.html | 662 +++++++++----- .../figure-html/cell-20-output-3.png | Bin 30245 -> 0 bytes .../figure-html/cell-20-output-4.png | Bin 9727 -> 30245 bytes .../figure-html/cell-20-output-5.png | Bin 0 -> 9727 bytes ...25-output-10.png => cell-25-output-13.png} | Bin ...27-output-10.png => cell-27-output-13.png} | Bin exercises/solutions/ex1_overview.html | 403 ++++++-- exercises/solutions/ex2_filter.html | 527 ++++++++--- exercises/solutions/ex3_cleaning.html | 320 +++++-- exercises/solutions/ex4_ICA.html | 455 +++++++--- exercises/solutions/ex6_linearModels.html | 591 ++++++++---- ...l-16-output-2.png => cell-16-output-1.png} | Bin ...l-22-output-2.png => cell-22-output-1.png} | Bin ...l-23-output-2.png => cell-23-output-1.png} | Bin ...l-26-output-2.png => cell-26-output-1.png} | Bin ...l-30-output-2.png => cell-30-output-1.png} | Bin exercises/solutions/ex7_encoding.html | 850 +++++++++++------ ...l-10-output-2.png => cell-10-output-1.png} | Bin ...l-13-output-3.png => cell-13-output-2.png} | Bin ...l-15-output-3.png => cell-15-output-2.png} | Bin ...l-19-output-2.png => cell-19-output-1.png} | Bin exercises/solutions/ex8_clusterPerm.html | 771 +++++++++++----- ...ell-3-output-2.svg => cell-3-output-1.svg} | 0 ...ell-5-output-2.svg => cell-5-output-1.svg} | 0 ...ell-8-output-2.svg => cell-8-output-1.svg} | 0 exercises/solutions/ex9_tf.html | 496 +++++++--- ...l-15-output-3.png => cell-15-output-2.png} | Bin exercises_overview.html | 268 +++++- grading.html | 266 +++++- index.html | 269 +++++- milestones.html | 271 +++++- robots.txt | 2 +- schedule.html | 273 +++++- search.json | 857 ++++++++++-------- semesterproject.html | 268 +++++- site_libs/bootstrap/bootstrap-icons.css | 148 ++- site_libs/bootstrap/bootstrap-icons.woff | Bin 164168 -> 176200 bytes site_libs/bootstrap/bootstrap.min.css | 12 +- site_libs/bootstrap/bootstrap.min.js | 6 +- site_libs/quarto-html/anchor.min.js | 6 +- site_libs/quarto-html/popper.min.js | 4 +- .../quarto-syntax-highlighting.css | 2 + site_libs/quarto-html/quarto.js | 44 +- site_libs/quarto-nav/quarto-nav.js | 58 +- site_libs/quarto-search/autocomplete.umd.js | 4 +- site_libs/quarto-search/quarto-search.js | 230 ++++- sitemap.xml | 124 +-- software.html | 266 +++++- 69 files changed, 10041 insertions(+), 2934 deletions(-) rename exercises/solutions/ex10_decoding_files/figure-html/{cell-11-output-2.png => cell-11-output-1.png} (100%) rename exercises/solutions/ex10_decoding_files/figure-html/{cell-22-output-2.png => cell-22-output-1.png} (100%) rename exercises/solutions/ex10_decoding_files/figure-html/{cell-24-output-2.png => cell-24-output-1.png} (100%) rename exercises/solutions/ex10_decoding_files/figure-html/{cell-25-output-2.png => cell-25-output-1.png} (100%) rename exercises/solutions/ex10_decoding_files/figure-html/{cell-27-output-2.png => cell-27-output-1.png} (100%) delete mode 100644 exercises/solutions/ex11_sourceSpace_files/figure-html/cell-20-output-3.png create mode 100644 exercises/solutions/ex11_sourceSpace_files/figure-html/cell-20-output-5.png rename exercises/solutions/ex11_sourceSpace_files/figure-html/{cell-25-output-10.png => cell-25-output-13.png} (100%) rename exercises/solutions/ex11_sourceSpace_files/figure-html/{cell-27-output-10.png => cell-27-output-13.png} (100%) rename exercises/solutions/ex6_linearModels_files/figure-html/{cell-16-output-2.png => cell-16-output-1.png} (100%) rename exercises/solutions/ex6_linearModels_files/figure-html/{cell-22-output-2.png => cell-22-output-1.png} (100%) rename exercises/solutions/ex6_linearModels_files/figure-html/{cell-23-output-2.png => cell-23-output-1.png} (100%) rename exercises/solutions/ex6_linearModels_files/figure-html/{cell-26-output-2.png => cell-26-output-1.png} (100%) rename exercises/solutions/ex6_linearModels_files/figure-html/{cell-30-output-2.png => cell-30-output-1.png} (100%) rename exercises/solutions/ex7_encoding_files/figure-html/{cell-10-output-2.png => cell-10-output-1.png} (100%) rename exercises/solutions/ex7_encoding_files/figure-html/{cell-13-output-3.png => cell-13-output-2.png} (100%) rename exercises/solutions/ex7_encoding_files/figure-html/{cell-15-output-3.png => cell-15-output-2.png} (100%) rename exercises/solutions/ex7_encoding_files/figure-html/{cell-19-output-2.png => cell-19-output-1.png} (100%) rename exercises/solutions/ex8_clusterPerm_files/figure-html/{cell-3-output-2.svg => cell-3-output-1.svg} (100%) rename exercises/solutions/ex8_clusterPerm_files/figure-html/{cell-5-output-2.svg => cell-5-output-1.svg} (100%) rename exercises/solutions/ex8_clusterPerm_files/figure-html/{cell-8-output-2.svg => cell-8-output-1.svg} (100%) rename exercises/solutions/ex9_tf_files/figure-html/{cell-15-output-3.png => cell-15-output-2.png} (100%) diff --git a/.nojekyll b/.nojekyll index 9360aa8..d860834 100644 --- a/.nojekyll +++ b/.nojekyll @@ -1 +1 @@ -0ccd2a25 \ No newline at end of file +4554831e \ No newline at end of file diff --git a/bibliography.html b/bibliography.html index e65debc..a13e931 100644 --- a/bibliography.html +++ b/bibliography.html @@ -2,12 +2,12 @@ - + -🧠 EEG-2023 - Bibliography +Bibliography – 🧠 EEG-2024 - + @@ -79,7 +79,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -88,13 +94,14 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", "search-submit-button-title": "Submit", "search-label": "Search" } } - + @@ -108,15 +115,15 @@
-
+ +
%load_ext autoreload
 %autoreload 2
@@ -288,7 +297,7 @@

The EEG Motor Movement/Imagery Dataset

%reload_ext autoreload
-
+
import numpy as np
 import scipy
 
@@ -308,7 +317,7 @@ 

The EEG Motor Movement/Imagery Dataset

import ccs_eeg_utils
-
+
epochs = ccs_eeg_utils.get_classification_dataset(typeInt=4)
loading subject 1 with runs [6, 10, 14]
@@ -350,11 +359,11 @@ 

The EEG Motor Movement/Imagery Dataset

0 bad epochs dropped
-
+
epochs
- +
@@ -397,7 +406,7 @@

Let’s get started

Our training data should exclude the evoked response, because it will not be sustained throughout the trial. We will make a copy of the epochs and crop it between 1 and 2 seconds (epochs_train.crop(tmin=1.,tmax=2.))

T: To get a first look at the training data, 1) average the epochs_train over time and plot the channels C3 and C4 as x/y axis in a scatter plot. Color the datapoints according to the labels. Data you can get using .get_data(picks=["C3","C4"]), labels via .events

Q: Would this be easy or difficult to separate with a linear classifier?

-
+
epochs_train = epochs.copy().crop(tmin=1., tmax=2.)
 
 data = epochs_train.get_data(picks=['C3','C4']).mean(axis=2)
@@ -409,7 +418,11 @@ 

Let’s get started

3 2 3 3 2 3 2 3]
-

+
+
+

+
+
@@ -419,7 +432,7 @@

CSP Feature Selectio

To fit the CSP we have to give it the data and the labels. For now we just want to look at the CSP, not run a classifier for it, so we dont worry about any overfit etc.

T: csp.fit_transform(epochs_data, labels) will fit it and csp.plot_filters(epochs.info) and csp.plot_patterns(epochs.info) will plot filter and activation.

Q: Let’s say you invite a subject again, but are allowed to only measure 5 channels not the 64. Which of the two plots would you choose to inform which channels to measure? In other words, does any of the plot tell you where the information to decode is strongest?

-
+
csp = mne.decoding.CSP(n_components=2)
 csp.fit_transform(epochs.get_data(), labels)
 csp
@@ -445,7 +458,7 @@

CSP Feature Selectio transform_into='average_power')

-
+
csp = mne.decoding.CSP(n_components=2)
 csp.fit_transform(epochs.get_data(), labels)
 csp.plot_filters(epochs.info);
@@ -469,35 +482,44 @@ 

CSP Feature Selectio Done.

-

+
+
+

+
+
-

+
+
+

+
+

T: Finally, let’s weight/transform the data to the fitted CSP components using csp_data = csp.transform(data). Note that because csp(...,transform_into="average_power") is set by default, we will get data without a time-dimension, thus already aggregated. Plot the datapoints against each other, how easy is it now to define a classifier?

-
+
epochs.get_data().shape
(45, 64, 801)
-
+
csp_data = csp.transform(epochs.get_data())
 csp_data.shape
(45, 2)
-
+
csp_data = csp.transform(epochs.get_data())
 
 plt.scatter(csp_data[:,0],csp_data[:,1],color=np.array(["red","green"])[labels-2])
-
-
<matplotlib.collections.PathCollection at 0x7fb687357ee0>
-
-

+
+
+

+
+
@@ -505,9 +527,9 @@

CSP Feature Selectio

LDA Classification

Let’s use the LDA classifier (you could use any other if you fancy, up to you - LDA is a fast and simple one).

You need to: 1. Construct the classifier via

-
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
-
-lda = LinearDiscriminantAnalysis()
+
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
+
+lda = LinearDiscriminantAnalysis()
  1. Reshape the csp_data to conform to labels x features (2D instead of 3D) e.g. using flattenData = csp_data.reshape(csp_data.shape[0],-1)

  2. Fit the LDA using lda.fit(data,labels)

  3. @@ -515,20 +537,20 @@

    LDA Classification

Note: we are doing a cardinal sin: Training and testing on the same data. How to fix that is next.

T: How well can you classify?

-
-
csp_data.reshape(csp_data.shape[0],-1).shape
+
+
csp_data.reshape(csp_data.shape[0],-1).shape
(45, 2)
-
-
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
-
-lda = LinearDiscriminantAnalysis()
-# flattenData = csp_data.reshape(csp_data.shape[0],-1)
-
-lda.fit(csp_data, labels) # fit on training
-print(lda.score(csp_data,labels))
+
+
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
+
+lda = LinearDiscriminantAnalysis()
+# flattenData = csp_data.reshape(csp_data.shape[0],-1)
+
+lda.fit(csp_data, labels) # fit on training
+print(lda.score(csp_data,labels))
0.9777777777777777
@@ -537,25 +559,25 @@

LDA Classification

Crossvalidation

To work against overfitting, we use crossvalidate and use some functions from sklearn for it. We will use a stratifiedShuffleSplit, which will split our data in training and test sets, but in a stratified way. Thus we dont have to worry about under/oversampling between our classes for now.

-
cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
-cv_split = cv.split(epochs_train.get_data(),labels)
+
cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
+cv_split = cv.split(epochs_train.get_data(),labels)

Next we can walk though each test/train, fit CSP, fit LDA and evaluate. I will give you the skeleton and you only have to fill in the XXX to speed up programming :).

-
score_list = []
-for train_idx, test_idx in cv_split:
-    y_train, y_test = labels[train_idx], labels[test_idx]
-
-    csp.fit_transform(epochs_train.get_data()[XXX_idx], y_XXX)
-    X_train = csp.transform(epochs_train.get_data()[XXX_idx])
-    X_test  = csp.transform(epochs_train.get_data()[XXX_idx])
-
-    lda.fit(X_XXX, y_XXX) 
-    score = lda.score(X_XXX,y_XXX)
-    score_list.append(score)
-
-
### Crossvalidation
-cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
-cv_split = cv.split(epochs_train.get_data(),labels)
-next(cv_split)
+
score_list = []
+for train_idx, test_idx in cv_split:
+    y_train, y_test = labels[train_idx], labels[test_idx]
+
+    csp.fit_transform(epochs_train.get_data()[XXX_idx], y_XXX)
+    X_train = csp.transform(epochs_train.get_data()[XXX_idx])
+    X_test  = csp.transform(epochs_train.get_data()[XXX_idx])
+
+    lda.fit(X_XXX, y_XXX) 
+    score = lda.score(X_XXX,y_XXX)
+    score_list.append(score)
+
+
### Crossvalidation
+cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
+cv_split = cv.split(epochs_train.get_data(),labels)
+next(cv_split)
(array([ 1, 19, 37, 28, 27, 14, 26, 18, 17, 25, 34, 20, 11, 35,  5, 44,  9,
         32, 30, 15, 29, 33,  6,  7,  4, 24,  8, 13, 10, 36, 40, 16, 38, 43,
@@ -563,26 +585,26 @@ 

Crossvalidation

array([42, 41, 31, 21, 3, 39, 0, 22, 12]))
-
-

-### Crossvalidation
-cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
-cv_split = cv.split(epochs_train.get_data(),labels)
-
-
-score_list = []
-for train_idx, test_idx in cv_split:
-    y_train, y_test = labels[train_idx], labels[test_idx]
-
-    csp.fit_transform(epochs_train.get_data()[train_idx], y_train)
-    X_train = csp.transform(epochs_train.get_data()[train_idx])
-    
-    X_test = csp.transform(epochs_train.get_data()[test_idx])
-
-    lda.fit(X_train, y_train) # fit on training
-    score = lda.score(X_test,y_test)
-    score_list.append(score)
-print(np.mean(score_list))
+
+

+### Crossvalidation
+cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
+cv_split = cv.split(epochs_train.get_data(),labels)
+
+
+score_list = []
+for train_idx, test_idx in cv_split:
+    y_train, y_test = labels[train_idx], labels[test_idx]
+
+    csp.fit_transform(epochs_train.get_data()[train_idx], y_train)
+    X_train = csp.transform(epochs_train.get_data()[train_idx])
+    
+    X_test = csp.transform(epochs_train.get_data()[test_idx])
+
+    lda.fit(X_train, y_train) # fit on training
+    score = lda.score(X_test,y_test)
+    score_list.append(score)
+print(np.mean(score_list))
Computing rank from data with rank=None
     Using tolerance 0.00012 (2.2e-16 eps * 64 dim * 8.2e+09  max singular value)
@@ -741,26 +763,26 @@ 

Implementation

  1. CSP and LDA & pipeline
-
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
-import mne.decoding
-lda = LinearDiscriminantAnalysis()
-csp = mne.decoding.CSP(n_components=2) # default is 4, typically youd like to do a nested cross-val hyperparam search. 2 is likely too low
-% and the pipeline simply:
-pipe = sklearn.pipeline.Pipeline([('CSP', csp), ('LDA', lda)])
+
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
+import mne.decoding
+lda = LinearDiscriminantAnalysis()
+csp = mne.decoding.CSP(n_components=2) # default is 4, typically youd like to do a nested cross-val hyperparam search. 2 is likely too low
+% and the pipeline simply:
+pipe = sklearn.pipeline.Pipeline([('CSP', csp), ('LDA', lda)])
  1. The sklearn Cross-validation pipeline is run like this
-
import sklearn.model_selection
-cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=0)
-scores = sklearn.model_selection.cross_val_score(pipe, trainingData, labels, cv=cv, n_jobs=1)
-
-
lda = LinearDiscriminantAnalysis()
-csp = mne.decoding.CSP(n_components=2)
-
-
-
pipe = sklearn.pipeline.Pipeline([('CSP', csp), ('LDA', lda)])
-cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
-scores = sklearn.model_selection.cross_val_score(pipe, epochs_train.get_data(), labels, cv=cv, n_jobs=1)
+
import sklearn.model_selection
+cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=0)
+scores = sklearn.model_selection.cross_val_score(pipe, trainingData, labels, cv=cv, n_jobs=1)
+
+
lda = LinearDiscriminantAnalysis()
+csp = mne.decoding.CSP(n_components=2)
+
+
+
pipe = sklearn.pipeline.Pipeline([('CSP', csp), ('LDA', lda)])
+cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=42)
+scores = sklearn.model_selection.cross_val_score(pipe, epochs_train.get_data(), labels, cv=cv, n_jobs=1)
Computing rank from data with rank=None
     Using tolerance 0.00012 (2.2e-16 eps * 64 dim * 8.2e+09  max singular value)
@@ -904,10 +926,10 @@ 

Implementation

Done.
-
-
print(np.mean(scores))
-print(scores)
-print(score_list)
+
+
print(np.mean(scores))
+print(scores)
+print(score_list)
0.9111111111111111
 [1.         1.         0.88888889 0.88888889 0.77777778 0.88888889
@@ -916,9 +938,9 @@ 

Implementation

Q: What is the accuracy of the classifier? What is the chance level?

-
-
print("Accuracy: {:.1f}%, ".format(scores.mean()*100))
-# chance level should be 50% because we used stratified cross validation
+
+
print("Accuracy: {:.1f}%, ".format(scores.mean()*100))
+# chance level should be 50% because we used stratified cross validation
Accuracy: 91.1%, 
@@ -933,11 +955,11 @@

Extensions

Remove CSP

Instead of applying a feature selection, maybe we can learn from the β€œraw” data? T: Replace the β€œcsd” in the pipeline with mne.decoding.Vectorizer(), this will ensure the reshaping of the features we performed earlier.

Q: What is the accuracy now?

-
-
pipe_simple = sklearn.pipeline.Pipeline([('vector',mne.decoding.Vectorizer()),('LDA', lda)])
-cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=0)
-scores = sklearn.model_selection.cross_val_score(pipe_simple, epochs_train.get_data(), labels, cv=cv, n_jobs=1)
-print("Accuracy: {:.1f}%, ".format(scores.mean()*100))
+
+
pipe_simple = sklearn.pipeline.Pipeline([('vector',mne.decoding.Vectorizer()),('LDA', lda)])
+cv = sklearn.model_selection.StratifiedShuffleSplit(10, test_size=0.2, random_state=0)
+scores = sklearn.model_selection.cross_val_score(pipe_simple, epochs_train.get_data(), labels, cv=cv, n_jobs=1)
+print("Accuracy: {:.1f}%, ".format(scores.mean()*100))
Accuracy: 51.1%, 
@@ -949,9 +971,9 @@

Classify over time

Because we will get multiple scores per cross-val, we also have to switch our scorer to mne.decoding.cross_val_multiscore(timeDecode,...).

T: Plot the performance against time

Bonus T: You can also use mne.decoding.GeneralizingEstimator(...) to get the temporal decoding matrix (increased runtime warning)

-
-
timeDecode = mne.decoding.SlidingEstimator(pipe_simple)
-scores = mne.decoding.cross_val_multiscore(timeDecode, epochs.copy().resample(20).get_data(), labels, cv=cv,n_jobs=1)
+
+
timeDecode = mne.decoding.SlidingEstimator(pipe_simple)
+scores = mne.decoding.cross_val_multiscore(timeDecode, epochs.copy().resample(20).get_data(), labels, cv=cv,n_jobs=1)
-
-
plt.plot(epochs.copy().resample(20).times,scores.mean(axis=0))
-plt.hlines(0.5,-1,4,'k')
-
-
<matplotlib.collections.LineCollection at 0x7fb67badead0>
-
+
+
plt.plot(epochs.copy().resample(20).times,scores.mean(axis=0))
+plt.hlines(0.5,-1,4,'k')
-

+
+
+

+
+
-
-
timeDecode = mne.decoding.GeneralizingEstimator(pipe_simple)
-scores = mne.decoding.cross_val_multiscore(timeDecode, epochs.copy().resample(20).get_data(), labels, cv=2,n_jobs=1) # resample speeds it up a lot! 
+
+
timeDecode = mne.decoding.GeneralizingEstimator(pipe_simple)
+scores = mne.decoding.cross_val_multiscore(timeDecode, epochs.copy().resample(20).get_data(), labels, cv=2,n_jobs=1) # resample speeds it up a lot! 
+ @@ -79,7 +79,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -88,13 +94,14 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", "search-submit-button-title": "Submit", "search-label": "Search" } } - + @@ -108,15 +115,15 @@
-
+ +
# packages: ; pysurfer
 %load_ext autoreload
 %autoreload 2
@@ -298,7 +307,7 @@ 

General remarks

-
MNE Flow
+
MNE Flow

We will skip these steps completly and start with an already-segment β€œdefault” MRI (called β€˜fsaverage’).

@@ -332,7 +341,7 @@

Actual start of e trans = 'fsaverage' # MNE has a built-in fsaverage transformation src = op.join(fs_dir, 'bem', 'fsaverage-ico-5-src.fif') bem = op.join(fs_dir, 'bem', 'fsaverage-5120-5120-5120-bem-sol.fif')

-
+

 import os.path as op
 
@@ -358,7 +367,7 @@ 

Actual start of e
from mne.datasets.limo import load_data
 epochs = load_data(subject=3,path='../local/limo') #
 epochs.set_eeg_reference(projection=True)  # needed for inverse modeling
-
+
from mne.datasets.limo import load_data
 epochs = load_data(subject=3,path='../local/limo') #
 epochs.set_eeg_reference(projection=True)  # needed for inverse modeling
@@ -374,7 +383,7 @@

Actual start of e

-

+
@@ -416,7 +425,7 @@

Alignment p

Hint: You can close the 3D plots using p.plotter.close() and later plots using b.close(). At least on my machine I had many crashes if I kept them floating around…

-
+
# Check that the locations of EEG electrodes is correct with respect to MRI
 
 mne.viz.set_3d_backend("notebook")
@@ -451,7 +460,7 @@ 

Alignment

The forward model

The forward model translates source-activity to sensor-activity. We have to provide the sensor locations (epochs.info), the transformations of sensorlocations to BEM model (trans) and the actual physical spheres (bem). The default conductivities for the BEM model are already saved in the pre-computed standard BEM model.

-
+
fwd = mne.make_forward_solution(epochs.info, trans=trans, src=src, bem=bem, eeg=True, mindist=5.0)
Source space          : /home/ehinger/mne_data/MNE-fsaverage-data/fsaverage/bem/fsaverage-ico-5-src.fif
@@ -509,13 +518,13 @@ 

The forward model

Finished.
-
+
fwd["sol"]["data"].shape
(128, 61452)
-
+
%matplotlib inline
 
 mne.viz.plot_topomap(fwd["sol"]["data"][:,2],epochs.info)
@@ -523,10 +532,18 @@ 

The forward model

plt.figure() mne.viz.plot_topomap(fwd["sol"]["data"][:,8],epochs.info)
-

+
+
+

+
+
-

+
+
+

+
+
(<matplotlib.image.AxesImage at 0x7f4293e5b2e0>,
@@ -537,7 +554,7 @@ 

The forward model

T: What is the shape of the leadfield and what do the dimension refer to?

T: select a random source-point and plot its respective sensor-topoplot (mne.viz.plot_topomap(vector,epochs.info))

T: Next plot three following source-points. Be sure to start with 0,3,6,9… (e.g. multiply your random β€œstarting”-index by 3).What do you observe and what does it tell you about the structure of the forward model? (hint: you could also inspect fwd["source_nn"]) - plot_topomap has an β€œaxes” option to specify subplot-axes, be sure to put show=False if you want to use that.

-
+
import mne.viz
 print(fwd["sol"]["data"].shape)
 #mne.viz.plot_topomap(fwd["sol"]["data"][:,1],epochs.info);
@@ -553,7 +570,11 @@ 

The forward model

<Figure size 432x288 with 0 Axes>
-

+
+
+

+
+

Now we will plot the reverse, choose one electrode and see which sources are most sensitive to that electrode. Note that you only need every third sample, i.e. we are looking for the activity at the cortex in only one dimension. It is a bit more involved to collapse over all source-orientations.

@@ -575,7 +596,7 @@

The forward model

b.add_data(x[0:nvert],vertices=v,smoothing_steps='nearest') ´´´ -::: {.cell execution_count=14} +::: {#cell-16 .cell execution_count=14} ``` {.python .cell-code} from mne.viz import Brain @@ -603,7 +624,7 @@

The forward model

10242

:::

-
+
b.close()
@@ -616,20 +637,24 @@

My first MNE

T: Use from scipy.linalg import pinv on one orientation of the Leadfield (fwd["sol"]["data"][:,::3]) and @-multiply it on your epochs.copy().crop(tmin=0.15,tmax=0.15).average().interpolate_bads().data[:,0] - you need to interpolate the bad channels, else they will project noise to your source-space

T: Plot a histogram of your source values. Do negative values make sense (solution below, don’t peak ;))?

Bonus: instead of cropping to around 150ms, you can also calculate all timepoints and later select a timepoint, but it is a bit more cumbersome to handle everything

-
+
epochs.average().plot();
-

+
+
+

+
-
+
+
L = fwd["sol"]["data"][:,::3]
 L.shape
(128, 20484)
-
+
d = epochs.copy().crop(tmin=0.150,tmax=0.153).average().interpolate_bads().data
 d.shape
 from scipy.linalg import pinv
@@ -647,7 +672,7 @@ 

My first MNE

(20484, 128)
-
+
from scipy.linalg import pinv
 
 L = fwd["sol"]["data"][:,::3]
@@ -663,10 +688,14 @@ 

My first MNE

Interpolating 7 sensors
-
+
plt.hist(s,100);
-

+
+
+

+
+

Once we have the vector of source-activities, we want to plot it

@@ -689,14 +718,14 @@

My first MNE

b.scale_data_colormap(fmin=lim[0], fmid=lim[1], fmax=lim[2], transparent=True)

Note the usage of np.abs around the source activity. It is generally not really possible to interpret positive / negative source activity. if you are on one side of a gyrus, the activity might be positive, on the other side it might be negative - simply because of dipole orientation. We therefore often just look at the absolute value

Bonus: If you wanted to plot all time-points, you can use b.set_time(200) to set time, or from surfer import TimeViewer and viewer = TimeViewer(b) to get a rudimentairy GUI

-
+
v = fwd["src"][0]['vertno']
 v
array([    0,     1,     2, ..., 10239, 10240, 10241])
-
+
%gui qt
 from surfer import Brain
 import numpy as np
@@ -712,7 +741,7 @@ 

My first MNE

10242
-
+
b.close()
@@ -734,12 +763,18 @@

Covariance Matrices

noise_cov = mne.compute_covariance(epochs, tmax=0)
 noise_cov.plot(epochs.info)

Q: What does the 2d-image plot tell you?

-
+
noise_cov = mne.compute_covariance(epochs, tmax=0)
 noise_cov.plot(epochs.info)
-
Computing rank from data with rank=None
-    Using tolerance 1.5e-10 (2.2e-16 eps * 121 dim * 5.5e+03  max singular value)
+
Computing rank from data with rank=None
+
+
+
/tmp/ipykernel_2058897/3142076790.py:1: RuntimeWarning: Epochs are not baseline corrected, covariance matrix may be inaccurate
+  noise_cov = mne.compute_covariance(epochs, tmax=0)
+
+
+
    Using tolerance 1.5e-10 (2.2e-16 eps * 121 dim * 5.5e+03  max singular value)
     Estimated rank (eeg): 120
     EEG: rank 120 computed from 121 data channels with 1 projector
     Created an SSP operator (subspace dimension = 1)
@@ -754,18 +789,19 @@ 

Covariance Matrices

Estimated rank (eeg): 120 EEG: rank 120 computed from 121 data channels with 0 projectors
-
-
/tmp/ipykernel_2058897/3142076790.py:1: RuntimeWarning: Epochs are not baseline corrected, covariance matrix may be inaccurate
-  noise_cov = mne.compute_covariance(epochs, tmax=0)
-
-

+
+
+

+
+
-

+
+
+

+
-
-
(<Figure size 273.6x266.4 with 2 Axes>, <Figure size 273.6x266.4 with 1 Axes>)
@@ -775,7 +811,7 @@

Inverse Operator

from mne.minimum_norm import make_inverse_operator, apply_inverse
 inv_default = make_inverse_operator(epochs.info, fwd, noise_cov, loose=0.2, depth=0.8)

T: Generate three more operator, one with loose=1, allowing for all dipole orientations, one with loose=0, enforcing strict orthogonal orientation (don’t do that) and one with loose=0.2, but depth=0. - deactivating depth weighting.

-
+
from mne.minimum_norm import make_inverse_operator, apply_inverse
 
 inv_default= make_inverse_operator(epochs.info, fwd, noise_cov, loose=0.2, depth=0.8)
@@ -882,7 +918,7 @@ 

Applying the inverse<
snr = 10.0
 lambda2 = 1.0 / snr ** 2

T: calculate the inverse solution and get the stc (source time course) for the inv_default - choose the MNE algorithm in the function mne.minimum_norm.apply_inverse T: Plot it! brain = stc.plot(time_viewer=True,hemi="both")

-
+
snr = 10.0
 lambda2 = 1.0 / snr ** 2
 stc = apply_inverse(epochs.average(), inv_default, lambda2, 'MNE')#, pick_ori='vector')
@@ -902,7 +938,7 @@

Applying the inverse< [done]

-
+
brain = stc.plot(time_viewer=True,hemi="both")
Using control points [8.26314785e-12 9.82639882e-12 2.66900633e-11]
@@ -1891,7 +1927,7 @@

Applying the inverse<

Bonus: You can also plot the brain activity on a β€œinflated” or β€œflat” brain using surface = "flat" (or inflated respectively). This can be very helpful in comparing conditions etc. - but especially the flat map will be very hard to read for everyone who is not a brain-anatomy-expert

-
+
stc.plot(surface="flat",time_viewer=True)
 #stc.plot(surface="flat",time_viewer=True)
@@ -2916,7 +2952,7 @@

# add the png to a subplot ax = plt.subplot(2,2,ix+1).imshow(img) plt.axis('off')

-
+
import sys
 sys.path.insert(0,"..")
 
@@ -2961,8 +2997,20 @@ 

+ +

+
+ +
+
+
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
     Created the regularized inverter
     Created an SSP operator (subspace dimension = 1)
@@ -2974,8 +3022,20 @@ 

+ +

+
+ +
+
+
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
     Created the regularized inverter
     Created an SSP operator (subspace dimension = 1)
@@ -2988,8 +3048,20 @@ 

+ +

+
+ +
+
+
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
     Created the regularized inverter
     Created an SSP operator (subspace dimension = 1)
@@ -3006,36 +3078,6 @@ 

-

-
- -
-
- -
-
- -
-
- -
-
- -
-
-
@@ -3045,38 +3087,42 @@

-

+
+
+

+
+

Bonus-Q: Why did MNE suddenly decide to plot pos/neg for the loose=0 (fixed orientation) instead of abs?

Comparing algorithms

Next we will compare the four different minimum-norm algorithms implemented in MNE: ['MNE','sLORETA','eLORETA','dSPM'] adapt the previous script to use inv_default for all of them and plot the solutions again.

-
-
# plotting parameters
-brain_kwargs = dict(views="lateral",hemi='split', size=(800, 400), initial_time=0.15,time_viewer=False,time_unit='s',show_traces=False)
-
-# getting larger figures
-plt.rcParams['figure.dpi'] = 100 
-plt.rcParams["figure.figsize"] = (20,15)
-
-# a list of all our inverse operators
-
-#loop over inverse operators
-for (ix,method) in enumerate(['MNE','sLORETA','eLORETA','dSPM']):
-
-    # calculate the stc
-    s = apply_inverse(epochs.average(), inv_default, lambda2, method, verbose=True)#, pick_ori='vector')
-    
-    # plot it
-    h = s.plot(**brain_kwargs)
-
-    # grab a png from it
-    img = ccs_eeg_utils.stc_plot2img(h,closeAfterwards=False)
-
-    # add the png to a subplot
-    ax = plt.subplot(2,2,ix+1).imshow(img)
-    plt.axis('off')
+
+
# plotting parameters
+brain_kwargs = dict(views="lateral",hemi='split', size=(800, 400), initial_time=0.15,time_viewer=False,time_unit='s',show_traces=False)
+
+# getting larger figures
+plt.rcParams['figure.dpi'] = 100 
+plt.rcParams["figure.figsize"] = (20,15)
+
+# a list of all our inverse operators
+
+#loop over inverse operators
+for (ix,method) in enumerate(['MNE','sLORETA','eLORETA','dSPM']):
+
+    # calculate the stc
+    s = apply_inverse(epochs.average(), inv_default, lambda2, method, verbose=True)#, pick_ori='vector')
+    
+    # plot it
+    h = s.plot(**brain_kwargs)
+
+    # grab a png from it
+    img = ccs_eeg_utils.stc_plot2img(h,closeAfterwards=False)
+
+    # add the png to a subplot
+    ax = plt.subplot(2,2,ix+1).imshow(img)
+    plt.axis('off')
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
@@ -3091,8 +3137,20 @@ 

Comparing algorithms< Explained 46.4% variance Combining the current components... [done] -Using control points [8.26314785e-12 9.82639882e-12 2.66900633e-11] -Preparing the inverse operator for use... +Using control points [8.26314785e-12 9.82639882e-12 2.66900633e-11]

+
+
+ +
+
+ +
+
+
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
     Created the regularized inverter
     Created an SSP operator (subspace dimension = 1)
@@ -3108,8 +3166,20 @@ 

Comparing algorithms< Combining the current components... sLORETA... [done] -Using control points [18.43577167 21.42066689 46.66754233] -Preparing the inverse operator for use... +Using control points [18.43577167 21.42066689 46.66754233]

+
+
+ +
+
+ +
+
+
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
     Created the regularized inverter
     Created an SSP operator (subspace dimension = 1)
@@ -3128,8 +3198,20 @@ 

Comparing algorithms< Explained 45.4% variance Combining the current components... [done] -Using control points [7.39223674e-12 8.42494906e-12 1.71655980e-11] -Preparing the inverse operator for use... +Using control points [7.39223674e-12 8.42494906e-12 1.71655980e-11]

+
+
+ +
+
+ +
+
+
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
     Created the regularized inverter
     Created an SSP operator (subspace dimension = 1)
@@ -3149,36 +3231,6 @@ 

Comparing algorithms<

-
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
@@ -3188,39 +3240,43 @@

Comparing algorithms<

-

+
+
+

+
+

Final exploration: Different regularisation

Adapt the above script once more, and apply different SNRs (e.g. [0.1,2,10,50]). What do you observe? Make note of the MNE text-output, it tells you how much % of your actual observed data is explained by the solution. Do they roughly match your expectations?

-
-
# plotting parameters
-brain_kwargs = dict(views="lateral",hemi='split', size=(800, 400), initial_time=0.15,time_viewer=False,time_unit='s',show_traces=False)
-
-# getting larger figures
-plt.rcParams['figure.dpi'] = 100 
-plt.rcParams["figure.figsize"] = (20,15)
-
-# a list of all our inverse operators
-
-#loop over inverse operators
-for (ix,SNR) in enumerate([0.1,2,10,50]):
-    
-    lamb = 1.0 / SNR ** 2
-    # calculate the stc
-    s = apply_inverse(epochs.average(), inv_default, lamb, "MNE", verbose=True)#, pick_ori='vector')
-    
-    # plot it
-    h = s.plot(**brain_kwargs)
-
-    # grab a png from it
-    img = ccs_eeg_utils.stc_plot2img(h,closeAfterwards=False)
-
-    # add the png to a subplot
-    ax = plt.subplot(2,2,ix+1).imshow(img)
-    plt.axis('off')
+
+
# plotting parameters
+brain_kwargs = dict(views="lateral",hemi='split', size=(800, 400), initial_time=0.15,time_viewer=False,time_unit='s',show_traces=False)
+
+# getting larger figures
+plt.rcParams['figure.dpi'] = 100 
+plt.rcParams["figure.figsize"] = (20,15)
+
+# a list of all our inverse operators
+
+#loop over inverse operators
+for (ix,SNR) in enumerate([0.1,2,10,50]):
+    
+    lamb = 1.0 / SNR ** 2
+    # calculate the stc
+    s = apply_inverse(epochs.average(), inv_default, lamb, "MNE", verbose=True)#, pick_ori='vector')
+    
+    # plot it
+    h = s.plot(**brain_kwargs)
+
+    # grab a png from it
+    img = ccs_eeg_utils.stc_plot2img(h,closeAfterwards=False)
+
+    # add the png to a subplot
+    ax = plt.subplot(2,2,ix+1).imshow(img)
+    plt.axis('off')
Preparing the inverse operator for use...
     Scaled noise and source covariance from nave = 1 to nave = 1072
@@ -3280,7 +3336,11 @@ 

-

+
+
+

+
+
@@ -3336,18 +3396,7 @@

Some further notes:

Some further notes:

{ + return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); + } + // Inspect non-navigation links and adorn them if external + var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); + for (var i=0; iSome further notes:Some further notes: { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + container.appendChild(note.children[0].cloneNode(true)); + for (let i = 1; i < note.children.length; i++) { + const child = note.children[i]; + if (child.tagName === "P" && child.innerText === "") { + continue; + } else { + container.appendChild(child.cloneNode(true)); + break; + } + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(container); + } + return container.innerHTML + } else { + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + // TODO in 1.5, we should make sure this works without a callout special case + if (note.classList.contains("callout")) { + return note.outerHTML; + } else { + return note.innerHTML; + } + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { }); } let selectedAnnoteEl; @@ -3450,6 +3670,7 @@

Some further notes:

Some further notes: { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -3538,4 +3785,5 @@

Some further notes:

\ No newline at end of file diff --git a/exercises/solutions/ex11_sourceSpace_files/figure-html/cell-20-output-3.png b/exercises/solutions/ex11_sourceSpace_files/figure-html/cell-20-output-3.png deleted file mode 100644 index 96fa0b9c53e4b539f7698fa68e04db712b803e0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30245 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 - + -🧠 EEG-2023 – ex1_overview +ex1_overview – 🧠 EEG-2024 - + @@ -79,7 +79,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -88,13 +94,14 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", "search-submit-button-title": "Submit", "search-label": "Search" } } - + @@ -107,15 +114,15 @@
@@ -765,36 +774,44 @@

T: Extract a single channel and plot the whole timeseries. You can directly interact with the raw object, e.g. raw[1:10,1:2000] extracts the first 10 channels and 2000 samples.

-
-
from matplotlib import pyplot as plt
-plt.plot(raw[10,:][0].T)
+
+
from matplotlib import pyplot as plt
+plt.plot(raw[10,:][0].T)
-

+
+
+

+
-
-
raw.info
+
+
+
raw.info

Q: What is the unit/scale of the data? 0.02V = 20000 Β΅V

T: Have a look at raw.info and note down what the sampling frequency is (how many EEG-samples per second): 1024Hz

T: We will epoch the data now. Formost we will cut the raw data to one channel using raw.pick_channels(["Cz"]) - not that this will permanently change the β€œraw” object and β€œdeletes” alle other channels from memory. If you want rather a copy you could use raw_subselect = raw.copy().pick_channels(["Cz"])).

-
-
raw.info
+
+
raw.info
-
-
raw.pick_channels(["Cz"])
-plt.plot(raw[:,:][0].T)
+
+
raw.pick_channels(["Cz"])
+plt.plot(raw[:,:][0].T)
NOTE: pick_channels() is a legacy function. New code should use inst.pick(...).
-

+
+
+

+
-
-
evts,evts_dict = mne.events_from_annotations(raw)
-wanted_keys = [e for e in evts_dict.keys() if "stimulus" in e]
-wanted_keys
+
+
+
evts,evts_dict = mne.events_from_annotations(raw)
+wanted_keys = [e for e in evts_dict.keys() if "stimulus" in e]
+wanted_keys
Used Annotations descriptions: ['response:201', 'response:202', 'stimulus:11', 'stimulus:12', 'stimulus:13', 'stimulus:14', 'stimulus:15', 'stimulus:21', 'stimulus:22', 'stimulus:23', 'stimulus:24', 'stimulus:25', 'stimulus:31', 'stimulus:32', 'stimulus:33', 'stimulus:34', 'stimulus:35', 'stimulus:41', 'stimulus:42', 'stimulus:43', 'stimulus:44', 'stimulus:45', 'stimulus:51', 'stimulus:52', 'stimulus:53', 'stimulus:54', 'stimulus:55']
@@ -826,9 +843,9 @@ 'stimulus:55']
-
-
evts_dict_stim=dict((k, evts_dict[k]) for k in wanted_keys if k in evts_dict)
-evts_dict_stim
+
+
evts_dict_stim=dict((k, evts_dict[k]) for k in wanted_keys if k in evts_dict)
+evts_dict_stim
{'stimulus:11': 3,
  'stimulus:12': 4,
@@ -858,12 +875,12 @@
 

T: MNE-speciality: We have to convert annotations to events

-
-
import mne
-evts,evts_dict = mne.events_from_annotations(raw)
-wanted_keys = [e for e in evts_dict.keys() if "stimulus" in e]
-evts_dict_stim=dict((k, evts_dict[k]) for k in wanted_keys if k in evts_dict)
-epochs = mne.Epochs(raw,evts,evts_dict_stim,tmin=-0.1,tmax=1)
+
+
import mne
+evts,evts_dict = mne.events_from_annotations(raw)
+wanted_keys = [e for e in evts_dict.keys() if "stimulus" in e]
+evts_dict_stim=dict((k, evts_dict[k]) for k in wanted_keys if k in evts_dict)
+epochs = mne.Epochs(raw,evts,evts_dict_stim,tmin=-0.1,tmax=1)
Used Annotations descriptions: ['response:201', 'response:202', 'stimulus:11', 'stimulus:12', 'stimulus:13', 'stimulus:14', 'stimulus:15', 'stimulus:21', 'stimulus:22', 'stimulus:23', 'stimulus:24', 'stimulus:25', 'stimulus:31', 'stimulus:32', 'stimulus:33', 'stimulus:34', 'stimulus:35', 'stimulus:41', 'stimulus:42', 'stimulus:43', 'stimulus:44', 'stimulus:45', 'stimulus:51', 'stimulus:52', 'stimulus:53', 'stimulus:54', 'stimulus:55']
 Not setting metadata
@@ -873,11 +890,11 @@
 0 projection items activated
-
-
epochs
+
+
epochs
-
Measurement date
+
@@ -928,8 +945,8 @@ -
-
epochs.average().get_data().shape
+
+
epochs.average().get_data().shape
NOTE: pick_channels() is a legacy function. New code should use inst.pick(...).
@@ -937,35 +954,43 @@
(1, 1127)
-
-
epochs.average().plot();
+
+
epochs.average().plot();
NOTE: pick_channels() is a legacy function. New code should use inst.pick(...).
 Need more than one channel to make topography for eeg. Disabling interactivity.
-

+
+
+

+
+

T Now that we have the epochs we should plot them. Plot all trials β€˜manually’, without using mne’s functionality (epochs.get_data()).

Q What is the unit/scale of the data now?: 0.0001 V = 100Β΅V

-
-
import numpy as np
-plt.plot(np.squeeze(epochs.get_data()[:,0,:].T))
-plt.plot(np.squeeze(epochs.get_data()[:,0,:].T).mean(axis=1),color="black")
+
+
import numpy as np
+plt.plot(np.squeeze(epochs.get_data()[:,0,:].T))
+plt.plot(np.squeeze(epochs.get_data()[:,0,:].T).mean(axis=1),color="black")
Using data from preloaded Raw for 200 events and 1127 original time points ...
 Using data from preloaded Raw for 200 events and 1127 original time points ...
-

+
+
+

+
+

Now index the epochs evoked = epochs[index].average() and average them. You can then plot them either via evoked.plot() or with mne.viz.plot_compare_evokeds([evokedA,evokedB]).

-
-
target = epochs[["stimulus:{}{}".format(k,k) for k in [1,2,3,4,5]]].average()
-distractor = epochs[["stimulus:{}{}".format(k,j) for k in [1,2,3,4,5] for j in [1,2,3,4,5] if k!=j]].average()
-mne.viz.plot_compare_evokeds([target,distractor]);
+
+
target = epochs[["stimulus:{}{}".format(k,k) for k in [1,2,3,4,5]]].average()
+distractor = epochs[["stimulus:{}{}".format(k,j) for k in [1,2,3,4,5] for j in [1,2,3,4,5] if k!=j]].average()
+mne.viz.plot_compare_evokeds([target,distractor]);
NOTE: pick_channels() is a legacy function. New code should use inst.pick(...).
 NOTE: pick_channels() is a legacy function. New code should use inst.pick(...).
@@ -975,7 +1000,11 @@ mne.viz.plot_compare_evokeds([target,distractor]);
-

+
+
+

+
+
                                                                                            **Q** What is the unit/scale of the data now? Set it into context to the other two scales you reported (**Q**'s higher up). 
@@ -1019,18 +1048,7 @@ } return false; } - const clipboard = new window.ClipboardJS('.code-copy-button', { - text: function(trigger) { - const codeEl = trigger.previousElementSibling.cloneNode(true); - for (const childEl of codeEl.children) { - if (isCodeAnnotation(childEl)) { - childEl.remove(); - } - } - return codeEl.innerText; - } - }); - clipboard.on('success', function(e) { + const onCopySuccess = function(e) { // button target const button = e.trigger; // don't keep focus @@ -1062,11 +1080,50 @@ }, 1000); // clear code selection e.clearSelection(); + } + const getTextToCopy = function(trigger) { + const codeEl = trigger.previousElementSibling.cloneNode(true); + for (const childEl of codeEl.children) { + if (isCodeAnnotation(childEl)) { + childEl.remove(); + } + } + return codeEl.innerText; + } + const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { + text: getTextToCopy }); - function tippyHover(el, contentFn) { + clipboard.on('success', onCopySuccess); + if (window.document.getElementById('quarto-embedded-source-code-modal')) { + // For code content inside modals, clipBoardJS needs to be initialized with a container option + // TODO: Check when it could be a function (https://github.com/zenorocha/clipboard.js/issues/860) + const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { + text: getTextToCopy, + container: window.document.getElementById('quarto-embedded-source-code-modal') + }); + clipboardModal.on('success', onCopySuccess); + } + var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); + var mailtoRegex = new RegExp(/^mailto:/); + var filterRegex = new RegExp("https:\/\/s-ccs\.github\.io\/course_EEG\/"); + var isInternal = (href) => { + return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); + } + // Inspect non-navigation links and adorn them if external + var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); + for (var i=0; i { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + container.appendChild(note.children[0].cloneNode(true)); + for (let i = 1; i < note.children.length; i++) { + const child = note.children[i]; + if (child.tagName === "P" && child.innerText === "") { + continue; + } else { + container.appendChild(child.cloneNode(true)); + break; + } + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(container); + } + return container.innerHTML + } else { + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + // TODO in 1.5, we should make sure this works without a callout special case + if (note.classList.contains("callout")) { + return note.outerHTML; + } else { + return note.innerHTML; + } + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { }); } let selectedAnnoteEl; @@ -1133,6 +1322,7 @@ } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -1158,6 +1348,32 @@ }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1221,4 +1437,5 @@ + \ No newline at end of file diff --git a/exercises/solutions/ex2_filter.html b/exercises/solutions/ex2_filter.html index 53eb497..3dd6261 100644 --- a/exercises/solutions/ex2_filter.html +++ b/exercises/solutions/ex2_filter.html @@ -2,12 +2,12 @@ - + -🧠 EEG-2023 - Highpass instead of lowpass +ex2_filter – 🧠 EEG-2024 - + @@ -79,7 +79,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -88,18 +94,46 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", "search-submit-button-title": "Submit", "search-label": "Search" } } - + - + + + @@ -109,15 +143,15 @@
@@ -966,18 +1055,22 @@

-
#%matplotlib qt
-raw.pick(["Pz"])#["Pz","Fz","Cz"])
-plt.plot(raw[:,:][0].T)
+
+
#%matplotlib qt
+raw.pick(["Pz"])#["Pz","Fz","Cz"])
+plt.plot(raw[:,:][0].T)
-

+
+
+

+
+

T: Plot the fourier space using raw.plot_psd

-
-
%matplotlib inline
-raw.plot_psd(area_mode='range', tmax=10.0, average=False,xscale="linear",);
+
+
%matplotlib inline
+raw.plot_psd(area_mode='range', tmax=10.0, average=False,xscale="linear",);
NOTE: plot_psd() is a legacy function. New code should use .compute_psd().plot().
 Effective window size : 2.000 (s)
@@ -990,14 +1083,18 @@ 

-

+
+
+

+
+

T: Now we filter using raw.filter(), specify a highpass of 0.5Hz and a lowpass of 50Hz. Plot the fourier spectrum again.

T: Plot the channel again, did the filter work as indented?

-
-
raw_f = raw.copy().filter(0.5,50, fir_design='firwin')
-raw_f.plot_psd(area_mode='range', tmax=10.0, average=False,xscale="log");
+
+
raw_f = raw.copy().filter(0.5,50, fir_design='firwin')
+raw_f.plot_psd(area_mode='range', tmax=10.0, average=False,xscale="log");
Filtering raw data in 1 contiguous segment
 Setting up band-pass filter from 0.5 - 50 Hz
@@ -1024,23 +1121,35 @@ 

-

+
+
+

+
+

-
-
plt.plot(raw[:,0:1000][0].T)
-plt.plot(raw_f[:,0:1000][0].T)
+
+
plt.plot(raw[:,0:1000][0].T)
+plt.plot(raw_f[:,0:1000][0].T)
-

+
+
+

+
+

Due to a huge DC-offset, the signals cannot be compared. We have to remove e.g. the median from each signal

-
-
plt.plot(raw[:,0:10000][0].T-np.median(raw[:,0:1000][0].T))
-plt.plot(raw_f[:,0:10000][0].T-np.median(raw_f[:,0:1000][0].T))
-#plt.plot(raw_f[:,0:1000][0].T-np.mean(raw_f[:,0:1000][0].T))
+
+
plt.plot(raw[:,0:10000][0].T-np.median(raw[:,0:1000][0].T))
+plt.plot(raw_f[:,0:10000][0].T-np.median(raw_f[:,0:1000][0].T))
+#plt.plot(raw_f[:,0:1000][0].T-np.mean(raw_f[:,0:1000][0].T))
-

+
+
+

+
+
@@ -1083,18 +1192,7 @@

{ + return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); + } + // Inspect non-navigation links and adorn them if external + var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); + for (var i=0; i { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + container.appendChild(note.children[0].cloneNode(true)); + for (let i = 1; i < note.children.length; i++) { + const child = note.children[i]; + if (child.tagName === "P" && child.innerText === "") { + continue; + } else { + container.appendChild(child.cloneNode(true)); + break; + } + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(container); + } + return container.innerHTML + } else { + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + // TODO in 1.5, we should make sure this works without a callout special case + if (note.classList.contains("callout")) { + return note.outerHTML; + } else { + return note.innerHTML; + } + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { }); } let selectedAnnoteEl; @@ -1197,6 +1466,7 @@

{ + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1285,4 +1581,5 @@

- + -🧠 EEG-2023 - Bonus Tasks! +ex3_cleaning – 🧠 EEG-2024 - + @@ -79,7 +79,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -88,13 +94,14 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", "search-submit-button-title": "Submit", "search-label": "Search" } } - + @@ -107,15 +114,15 @@

Measurement date
@@ -1038,10 +1079,10 @@
Measurement date
-
-
    ica = mne.preprocessing.ICA(method="picard") # picard is fast and supposedly good; infomax is slow but good
-    ica.fit(raw,verbose=True)
-    
+
+
    ica = mne.preprocessing.ICA(method="picard") # picard is fast and supposedly good; infomax is slow but good
+    ica.fit(raw,verbose=True)
+    
Fitting ICA to data using 30 channels (please be patient, this may take a while)
 Selecting by non-zero PCA components: 30 components
@@ -1049,7 +1090,7 @@
 
- +
@@ -1084,22 +1125,30 @@ -
-
ica.plot_components(range(20));
+
+
ica.plot_components(range(20));
-

+
+
+

+
-
-
icaact = ica.get_sources(raw)
-plt.plot(icaact[1,0:20000][0].T);
-#plt.plot(raw[0,0:20000][0].T)
+
+
+
icaact = ica.get_sources(raw)
+plt.plot(icaact[1,0:20000][0].T);
+#plt.plot(raw[0,0:20000][0].T)
-

+
+
+

+
+
-
-
ica.plot_properties(raw,picks=[0,1,7],psd_args={'fmax': 35.},reject=None);
+
+
ica.plot_properties(raw,picks=[0,1,7],psd_args={'fmax': 35.},reject=None);
    Using multitaper spectrum estimation with 7 DPSS windows
 Not setting metadata
@@ -1109,26 +1158,32 @@
 0 bad epochs dropped
-

+
+
+

+
+
-
-
ica_infomax = mne.preprocessing.ICA(method="infomax") # picard is fast and supposedly good; infomax is slow but good
-ica_infomax.fit(raw,verbose=True)
-    
+
+
ica_infomax = mne.preprocessing.ICA(method="infomax") # picard is fast and supposedly good; infomax is slow but good
+ica_infomax.fit(raw,verbose=True)
+    
Fitting ICA to data using 30 channels (please be patient, this may take a while)
 Selecting by non-zero PCA components: 30 components
- 
-Fitting ICA took 281.0s.
+
/home/ehinger/miniconda3/envs/eegCourse/lib/python3.10/site-packages/mne/preprocessing/infomax_.py:192: RuntimeWarning: overflow encountered in exp
   y = 1.0 / (1.0 + np.exp(-u))
+
+
Fitting ICA took 281.0s.
+
-
Method
+
@@ -1163,19 +1218,19 @@ -
-
import mne
-evts,evts_dict = mne.events_from_annotations(raw)
-wanted_keys = [e for e in evts_dict.keys() if "response" in e]
-evts_dict_stim=dict((k, evts_dict[k]) for k in wanted_keys if k in evts_dict)
+
+
import mne
+evts,evts_dict = mne.events_from_annotations(raw)
+wanted_keys = [e for e in evts_dict.keys() if "response" in e]
+evts_dict_stim=dict((k, evts_dict[k]) for k in wanted_keys if k in evts_dict)
-
-
reconst_raw = raw.copy()
-#ica.apply(reconst_raw,exclude=[1,8,9])
-
-#raw.plot()
-#reconst_raw.plot()  
-ica.plot_overlay(raw,exclude=[1,8,9]);
+
+
reconst_raw = raw.copy()
+#ica.apply(reconst_raw,exclude=[1,8,9])
+
+#raw.plot()
+#reconst_raw.plot()  
+ica.plot_overlay(raw,exclude=[1,8,9]);

whiteX = whiten(x)

w = np.eye(x.shape[0])

@@ -1244,18 +1299,7 @@ } return false; } - const clipboard = new window.ClipboardJS('.code-copy-button', { - text: function(trigger) { - const codeEl = trigger.previousElementSibling.cloneNode(true); - for (const childEl of codeEl.children) { - if (isCodeAnnotation(childEl)) { - childEl.remove(); - } - } - return codeEl.innerText; - } - }); - clipboard.on('success', function(e) { + const onCopySuccess = function(e) { // button target const button = e.trigger; // don't keep focus @@ -1287,11 +1331,50 @@ }, 1000); // clear code selection e.clearSelection(); + } + const getTextToCopy = function(trigger) { + const codeEl = trigger.previousElementSibling.cloneNode(true); + for (const childEl of codeEl.children) { + if (isCodeAnnotation(childEl)) { + childEl.remove(); + } + } + return codeEl.innerText; + } + const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { + text: getTextToCopy }); - function tippyHover(el, contentFn) { + clipboard.on('success', onCopySuccess); + if (window.document.getElementById('quarto-embedded-source-code-modal')) { + // For code content inside modals, clipBoardJS needs to be initialized with a container option + // TODO: Check when it could be a function (https://github.com/zenorocha/clipboard.js/issues/860) + const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { + text: getTextToCopy, + container: window.document.getElementById('quarto-embedded-source-code-modal') + }); + clipboardModal.on('success', onCopySuccess); + } + var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); + var mailtoRegex = new RegExp(/^mailto:/); + var filterRegex = new RegExp("https:\/\/s-ccs\.github\.io\/course_EEG\/"); + var isInternal = (href) => { + return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); + } + // Inspect non-navigation links and adorn them if external + var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); + for (var i=0; i { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + container.appendChild(note.children[0].cloneNode(true)); + for (let i = 1; i < note.children.length; i++) { + const child = note.children[i]; + if (child.tagName === "P" && child.innerText === "") { + continue; + } else { + container.appendChild(child.cloneNode(true)); + break; + } + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(container); + } + return container.innerHTML + } else { + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + // TODO in 1.5, we should make sure this works without a callout special case + if (note.classList.contains("callout")) { + return note.outerHTML; + } else { + return note.innerHTML; + } + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { }); } let selectedAnnoteEl; @@ -1358,6 +1573,7 @@ } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -1383,6 +1599,32 @@ }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1446,4 +1688,5 @@ + \ No newline at end of file diff --git a/exercises/solutions/ex6_linearModels.html b/exercises/solutions/ex6_linearModels.html index 888e41e..4260ad8 100644 --- a/exercises/solutions/ex6_linearModels.html +++ b/exercises/solutions/ex6_linearModels.html @@ -2,12 +2,12 @@ - + -🧠 EEG-2023 - Statistical Analysis of N170 area using Linear Regression +Statistical Analysis of N170 area using Linear Regression – 🧠 EEG-2024 - + @@ -79,7 +79,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -88,18 +94,46 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", "search-submit-button-title": "Submit", "search-label": "Search" } } - + - + + + @@ -109,15 +143,15 @@
+

T Load the data into a pandas dataframe. A very useful concept here is the concept of a tidy dataframe. The idea is, that every observation is one row of a table, where columns are potential features / descriptors + the independent variable (the average activity at PO8 here).

-
+
import pandas as pd
 import seaborn as sns
 import numpy as numpy
 d = pd.read_csv("../ex6_N170.csv",delimiter=",")
-
+
d
-
Method
+
@@ -398,10 +434,14 @@

Statistical Analysis of N170 area using Linear Regression

T: Use a plottinglibrary of your choice to visualise the simple scatter plot between some/all variables. I recommend packages seaborn e.g. pairplot) or plotnine for this. They make it easy to split up plots of continuous variables (e.g. baseline vs. PO8) by a categorical variable, e.g. cond (scrambled/intact).

Q: Can you already guess what the relationships between the variables are, solely based on the plots?

-
+
sns.pairplot(d,hue="cond")
-

+
+
+

+
+
@@ -415,7 +455,7 @@

Do-It-Yoursel

Because we will need it quite often.

Q: What does the resulting value mean? Note: The result is often called a β€œbeta”

Note: While the pseudoinverse is great because it is easy to understand what is going on, typically we would not directly use it but rather go over a Cholesky-Decomposition first. This will greatly increase numeric stability. But right now we do not care much about that

-
+
import numpy as  np
 
 def solve(*args):
@@ -423,7 +463,7 @@ 

Do-It-Yoursel #print(X) return np.linalg.inv(X.T @ X)@X.T@d.PO8.values

-
+
const = np.ones(d.shape[0])
 
 print("Const-only{},mean:{}".format(solve(const),np.mean(d.PO8)))
@@ -431,8 +471,8 @@

Do-It-Yoursel
Const-only[-3.91109151],mean:-3.911091509146645

-

\(y_i = \beta_0*X_1 + e_i\) with \(X_i = 1\)

-
+

\(y_i = \beta_0*X_1 + e_i\) with \(X_i = 1\)

+
from matplotlib import pyplot as plt
 plt.hist(d.PO8)
@@ -443,10 +483,14 @@

Do-It-Yoursel <BarContainer object of 10 artists>)

-

+
+
+

+
+
-
+
solve(const)
array([-3.91109151])
@@ -454,7 +498,7 @@

Do-It-Yoursel

T: Now we add the condition cond to the designmarix (thus PO8 ~ intercept + cond) which differentiates between intact and scrambled images. Because this is a categorical variable we have to encode it first. For that, we need a β€œ1” when images are intact and a β€œ0” if they were scrambled. Fit the model

Q: What do the outputs mean now?

-
+
cond = 1.*(d.cond.values == 'intact')
 
 cond
@@ -476,23 +520,27 @@

Do-It-Yoursel 0., 1., 1., 1., 0., 1., 0.])

-
+
print(solve(const,cond))
[-0.97665794 -5.75148979]

\(\hat{y} = \beta_0 + \beta_1 * X_{cond}\)

-
+
plt.plot([0,1],[d.PO8[d.cond=="scrambled"].mean(),d.PO8[d.cond=="intact"].mean()])
-

+
+
+

+
+

T: Next add a predictor for stimulus (PO8 ~ intercept + cond + stim) and fit it.

Q: What changed to the previous modelfit - Bonus: An idea why the size of the change is the size it is? Hint: It has to do with correlations

\(\hat{y} = \beta_0 + \beta_1 * X_{cond} + \beta_2 * X{stim}\)

-
+
stim = 1.*(d.stim.values == 'face')
 print(solve(const,cond))
 # intact, face
@@ -502,14 +550,18 @@ 

Do-It-Yoursel [-0.29596347 -5.63868899 -1.458631 ]

-
+
plt.plot([0,1],[d.PO8[(d.cond=="scrambled") & (d.stim=="face")].mean(),d.PO8[(d.cond=="scrambled") & (d.stim=="car")].mean()])
 plt.plot([0,1],[d.PO8[(d.cond=="intact") & (d.stim=="face")].mean(),d.PO8[(d.cond=="intact") & (d.stim=="car")].mean()])                  
-

+
+
+

+
+
-
+
# investigate correlation between cond and interaction of cond*stim
 np.corrcoef(cond,stim*cond)
@@ -519,7 +571,7 @@

Do-It-Yoursel

T: And an interaction (column of stimulus * column of cond)

Q: What do the betas/results/parameters mean now? (You might want to do the plotting of the next step to help your interpretation)

-
+
print(solve(const,cond))
 print(solve(const,cond,stim))
 print(solve(const,cond,stim,cond*stim))
@@ -531,8 +583,8 @@

Do-It-Yoursel

Now we have what we call a 2x2 design. Two categorical factors (often only β€œfactor”) with two levels each + the interaction.

T: It is about time to plot our results! Put cond on the x, and use color for stimulus. We will reconstruct our original data first:

-

You should reconstruct 4 values, one for intact faces, one for scrambled faces, one for intact cars and one for scrambled cars. It migt be helpful to explicitly write down the models (\(y = 1*\beta_0 + ...\) )

-
+

You should reconstruct 4 values, one for intact faces, one for scrambled faces, one for intact cars and one for scrambled cars. It migt be helpful to explicitly write down the models (\(y = 1*\beta_0 + ...\) )

+
est = solve(const,cond,stim,cond*stim)
 #est = solve(const,cond,stim)
 #est = np.append(est,0)
@@ -544,11 +596,12 @@ 

Do-It-Yoursel 1*est[0]+0*est[1] + 1*est[2] + 0*1*est[3], 1*est[0]+1*est[1] + 1*est[2] + 1*1*est[3]]}) sns.lineplot(x=res.stim,y=res.est,hue=res.cond)

-
-
<AxesSubplot:xlabel='stim', ylabel='est'>
-
-

+
+
+

+
+

\(\beta_0 *1 + \beta_1 * X_1(1,0) + \beta_2 * X_2(1,0)\)

@@ -561,8 +614,8 @@

Changing Bases

We discussed in the lecture that it is possible to change the bases. We will go back to the simple example of of Intercept + cond. But in addition of dummy coding (intact == 1, scrambled == 0), we will fit a second model with effect-coding (intact == 0.5, scrambled = -0.5).

Q: How do the betas compare of the two models? How does the interpretation change?

Hint If you need more you can read two of my tutorial-blogposts on this topic here and here :)

-
-
cond-0.5
+
+
cond-0.5
array([-0.5,  0.5, -0.5, -0.5,  0.5,  0.5,  0.5, -0.5, -0.5,  0.5, -0.5,
        -0.5, -0.5, -0.5, -0.5,  0.5, -0.5, -0.5,  0.5, -0.5, -0.5,  0.5,
@@ -589,21 +642,21 @@ 

Changing Bases

-0.5, 0.5, -0.5])
-
-
(np.mean(d.PO8[(d.cond=="scrambled")]) + np.mean(d.PO8[(d.cond=="intact")]))/2
+
+
(np.mean(d.PO8[(d.cond=="scrambled")]) + np.mean(d.PO8[(d.cond=="intact")]))/2
-3.8524028378068995
-
-
np.mean(d.PO8)
+
+
np.mean(d.PO8)
-3.911091509146645
-
-
print(solve(const,cond))
-print(solve(const,cond-0.5))
+
+
print(solve(const,cond))
+print(solve(const,cond-0.5))
[-0.97665794 -5.75148979]
 [-3.85240284 -5.75148979]
@@ -611,9 +664,9 @@

Changing Bases

T: Now we run the full 2x2 model once with dummy and once with effect-coding. The interaction of an effect-coded model is still just the designmatrix columns multiplied with eachother

Q: Can you put the results together, why do they have the results they have?

-
-
print(solve(const,cond-0.5,stim-0.5,(cond-0.5)*(stim-0.5)))
-print(solve(const,cond,stim,cond*stim)) # dummy cding / treatment coding
+
+
print(solve(const,cond-0.5,stim-0.5,(cond-0.5)*(stim-0.5)))
+print(solve(const,cond,stim,cond*stim)) # dummy cding / treatment coding
[-3.73857326 -5.61334487 -1.40707547 -5.4995781 ]
 [-1.60325761 -2.86355582  1.34271358 -5.4995781 ]
@@ -625,29 +678,31 @@

Continuous Regressor

In theory baseline corrections are not needed, the baseline (i.e. what happens before stimulus onset, thus β€œnegative” time in an ERP) should be flat / noise around 0, because stimulus order is random. But in practice, we only have limited number of trials and limited randomization. Thus it might be, that we have a bias with in one condition more residual drift / low-frequency activity than in the other. This will β€œmove” the whole ERP curve up/down and bias results later in the epoch.

Classically, baselines are simply subtracted. Thus every point of an ERP recieves the β€œsame” baseline correction. This is equivalent to adding a known parameter to our model: y ~ b0 * constant + b1 * cond + stim +1*BSL. What we will do instead is the 2020-version, we regress the baseline. This allows us to remove less of the baseline activity (or more, but rarely happens).

T: Plot the PO8 actiity against the baseline (you might have done this plot at the beginning of the exercise). Split it up by cond & stim

-
-
sns.scatterplot(x=d.bsl,y=d.PO8,hue=d.cond)
-
-
<AxesSubplot:xlabel='bsl', ylabel='PO8'>
-
+
+
sns.scatterplot(x=d.bsl,y=d.PO8,hue=d.cond)
-

+
+
+

+
-
-
sns.scatterplot(x=d.bsl,y=d.PO8,hue=d.stim)
-
-
<AxesSubplot:xlabel='bsl', ylabel='PO8'>
+
+
sns.scatterplot(x=d.bsl,y=d.PO8,hue=d.stim)
-

+
+
+

+
+

T: Add the baseline as a predictor to your 2x2 model.

Q: What is the resulting beta?

-
-
print(solve(const,cond,stim,cond*stim,d.bsl))
-print(solve(const,cond,stim,cond*stim))
+
+
print(solve(const,cond,stim,cond*stim,d.bsl))
+print(solve(const,cond,stim,cond*stim))
[-1.23950777 -3.80187398  1.27907238 -5.53859036  0.32611814]
 [-1.60325761 -2.86355582  1.34271358 -5.4995781 ]
@@ -656,9 +711,9 @@

Continuous Regressor

Typically we do not only remove the overall baseline, but condition-indivudal baselines.

T: Thus we have to generate interactions with all predictors & interaction of the interactions too

Q: What is your interpretation of the resulting betas? For which conditions do we really need a baseline correction and how strong should it be?

-
-
print(solve(const,cond,stim,cond*stim,d.bsl))
-print(solve(const,cond,stim,cond*stim,d.bsl,d.bsl*cond,d.bsl*stim,d.bsl*cond*stim))
+
+
print(solve(const,cond,stim,cond*stim,d.bsl))
+print(solve(const,cond,stim,cond*stim,d.bsl,d.bsl*cond,d.bsl*stim,d.bsl*cond*stim))
[-1.23950777 -3.80187398  1.27907238 -5.53859036  0.32611814]
 [-1.59620965 -4.05240262  1.58967869 -5.42777959  0.00631882  0.66445615
@@ -668,33 +723,34 @@ 

Continuous Regressor

\(y - BSL = \beta_0 \dots\)

\(y = \beta_0 \dots + BSL\)

\(y = \beta_0 \dots + \gamma_0 * BSL\)

-
-
est = solve(const,cond,stim,cond*stim,d.bsl,d.bsl*cond,d.bsl*stim,d.bsl*cond*stim)
-#est = solve(const,cond,stim,cond*stim,d.bsl)
-
-res = pd.DataFrame({"stim":["car","car","face","face"],
-              "cond":["scrambled","intact","scrambled","intact"],
-              "est": [
-                    1*est[0]+0*est[1] + 0*est[2] + 0*0*est[3],
-                    1*est[0]+1*est[1] + 0*est[2] + 1*0*est[3],
-                    1*est[0]+0*est[1] + 1*est[2] + 0*1*est[3],
-                    1*est[0]+1*est[1] + 1*est[2] + 1*1*est[3]]})
-sns.lineplot(x=res.cond,y=res.est,hue=res.stim)
-
-est = solve(const,cond,stim,cond*stim)
-res = pd.DataFrame({"stim":["car","car","face","face"],
-              "cond":["scrambled","intact","scrambled","intact"],
-              "est": [
-                    1*est[0]+0*est[1] + 0*est[2] + 0*0*est[3],
-                    1*est[0]+1*est[1] + 0*est[2] + 1*0*est[3],
-                    1*est[0]+0*est[1] + 1*est[2] + 0*1*est[3],
-                    1*est[0]+1*est[1] + 1*est[2] + 1*1*est[3]]})
-sns.lineplot(x=res.cond,y=res.est,hue=res.stim)
-
-
<AxesSubplot:xlabel='cond', ylabel='est'>
-
+
+
est = solve(const,cond,stim,cond*stim,d.bsl,d.bsl*cond,d.bsl*stim,d.bsl*cond*stim)
+#est = solve(const,cond,stim,cond*stim,d.bsl)
+
+res = pd.DataFrame({"stim":["car","car","face","face"],
+              "cond":["scrambled","intact","scrambled","intact"],
+              "est": [
+                    1*est[0]+0*est[1] + 0*est[2] + 0*0*est[3],
+                    1*est[0]+1*est[1] + 0*est[2] + 1*0*est[3],
+                    1*est[0]+0*est[1] + 1*est[2] + 0*1*est[3],
+                    1*est[0]+1*est[1] + 1*est[2] + 1*1*est[3]]})
+sns.lineplot(x=res.cond,y=res.est,hue=res.stim)
+
+est = solve(const,cond,stim,cond*stim)
+res = pd.DataFrame({"stim":["car","car","face","face"],
+              "cond":["scrambled","intact","scrambled","intact"],
+              "est": [
+                    1*est[0]+0*est[1] + 0*est[2] + 0*0*est[3],
+                    1*est[0]+1*est[1] + 0*est[2] + 1*0*est[3],
+                    1*est[0]+0*est[1] + 1*est[2] + 0*1*est[3],
+                    1*est[0]+1*est[1] + 1*est[2] + 1*1*est[3]]})
+sns.lineplot(x=res.cond,y=res.est,hue=res.stim)
-

+
+
+

+
+

@@ -707,22 +763,22 @@

Bonus: U

T: Implement this formula. The sqrt of the diagonal elements are your Standard-Errors, which we can use as error-bars. The \(\sigma^2\) is the variance of the residuals

\(Y_i = X\beta + e_i\)

\(Y_i - X\beta = e_i\)

-
-
import numpy as  np
-
-def se(*args):
-    X = np.stack(args).T
-    b = np.linalg.inv(X.T @ X)@X.T@d.PO8
-    e = d.PO8-X@b
-    s = np.var(e)
-    return np.sqrt(np.diag(s * np.linalg.inv(X.T @ X)))
-
-se = (se(const,cond,stim,cond*stim))    
-es = (solve(const,cond,stim,cond*stim))    
-print(es)
-print(se)
-
-print(es/se)
+
+
import numpy as  np
+
+def se(*args):
+    X = np.stack(args).T
+    b = np.linalg.inv(X.T @ X)@X.T@d.PO8
+    e = d.PO8-X@b
+    s = np.var(e)
+    return np.sqrt(np.diag(s * np.linalg.inv(X.T @ X)))
+
+se = (se(const,cond,stim,cond*stim))    
+es = (solve(const,cond,stim,cond*stim))    
+print(es)
+print(se)
+
+print(es/se)
[-1.60325761 -2.86355582  1.34271358 -5.4995781 ]
 [0.73685984 1.07359417 1.07865236 1.51134258]
@@ -731,29 +787,29 @@ 

Bonus: U

Now we have to apply it including our contrast vectors e.g. c = [1 0 0 0*0], or c=[1 0 1 1*0]

\[se_c = \sigma^2 c^T (X^TX)^{-1} c\]

-
-
def se_contrast(*args,c=[1,0,0,0]):
-    c = np.array(c)
-    X = np.stack(args).T
-    b = np.linalg.inv(X.T @ X)@X.T@d.PO8
-    e = d.PO8-X@b
-    s = np.var(e)
-    #print(np.sqrt(c.T @ np.linalg.inv(X.T @ X) @c))
-    return np.sqrt(s * c.T @ np.linalg.inv(X.T @ X) @c)
-
-se_A = se_contrast(const,cond,stim,cond*stim,c = [1,1,0,0])
-se_A
+
+
def se_contrast(*args,c=[1,0,0,0]):
+    c = np.array(c)
+    X = np.stack(args).T
+    b = np.linalg.inv(X.T @ X)@X.T@d.PO8
+    e = d.PO8-X@b
+    s = np.var(e)
+    #print(np.sqrt(c.T @ np.linalg.inv(X.T @ X) @c))
+    return np.sqrt(s * c.T @ np.linalg.inv(X.T @ X) @c)
+
+se_A = se_contrast(const,cond,stim,cond*stim,c = [1,1,0,0])
+se_A
0.7807957629392348
-
-
res
+
+
res
-

+
@@ -793,27 +849,28 @@

Bonus: U -
-
from matplotlib import pyplot as plt
-est = solve(const,cond,stim,cond*stim)
-res = pd.DataFrame({"stim":["car","car","face","face"],
-              "cond":["scrambled","intact","scrambled","intact"],
-              "est": [
-                    est@np.array([1,0 ,0,0*0]),
-                    est@np.array([1,1 ,0,1*0]),
-                    est@np.array([1,0 ,1,0*1]),
-                    est@np.array([1,1 ,1,1*1])],
-               "se": [se_contrast(const,cond,stim,cond*stim,c = [1,0,0,0]),
-                     se_contrast(const,cond,stim,cond*stim,c = [1,1,0,0]),
-                     se_contrast(const,cond,stim,cond*stim,c = [1,0,1,0]),
-                     se_contrast(const,cond,stim,cond*stim,c = [1,1,1,1])]})
-sns.lineplot(x=res.cond,y=res.est,hue=res.stim)
-plt.errorbar(x=res.cond,y=res.est,yerr=res.se,fmt="none",c = 'k')
-
-
<ErrorbarContainer object of 3 artists>
-
+
+
from matplotlib import pyplot as plt
+est = solve(const,cond,stim,cond*stim)
+res = pd.DataFrame({"stim":["car","car","face","face"],
+              "cond":["scrambled","intact","scrambled","intact"],
+              "est": [
+                    est@np.array([1,0 ,0,0*0]),
+                    est@np.array([1,1 ,0,1*0]),
+                    est@np.array([1,0 ,1,0*1]),
+                    est@np.array([1,1 ,1,1*1])],
+               "se": [se_contrast(const,cond,stim,cond*stim,c = [1,0,0,0]),
+                     se_contrast(const,cond,stim,cond*stim,c = [1,1,0,0]),
+                     se_contrast(const,cond,stim,cond*stim,c = [1,0,1,0]),
+                     se_contrast(const,cond,stim,cond*stim,c = [1,1,1,1])]})
+sns.lineplot(x=res.cond,y=res.est,hue=res.stim)
+plt.errorbar(x=res.cond,y=res.est,yerr=res.se,fmt="none",c = 'k')
-

+
+
+

+
+
@@ -856,18 +913,7 @@

Bonus: U } return false; } - const clipboard = new window.ClipboardJS('.code-copy-button', { - text: function(trigger) { - const codeEl = trigger.previousElementSibling.cloneNode(true); - for (const childEl of codeEl.children) { - if (isCodeAnnotation(childEl)) { - childEl.remove(); - } - } - return codeEl.innerText; - } - }); - clipboard.on('success', function(e) { + const onCopySuccess = function(e) { // button target const button = e.trigger; // don't keep focus @@ -899,11 +945,50 @@

Bonus: U }, 1000); // clear code selection e.clearSelection(); + } + const getTextToCopy = function(trigger) { + const codeEl = trigger.previousElementSibling.cloneNode(true); + for (const childEl of codeEl.children) { + if (isCodeAnnotation(childEl)) { + childEl.remove(); + } + } + return codeEl.innerText; + } + const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { + text: getTextToCopy }); - function tippyHover(el, contentFn) { + clipboard.on('success', onCopySuccess); + if (window.document.getElementById('quarto-embedded-source-code-modal')) { + // For code content inside modals, clipBoardJS needs to be initialized with a container option + // TODO: Check when it could be a function (https://github.com/zenorocha/clipboard.js/issues/860) + const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { + text: getTextToCopy, + container: window.document.getElementById('quarto-embedded-source-code-modal') + }); + clipboardModal.on('success', onCopySuccess); + } + var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); + var mailtoRegex = new RegExp(/^mailto:/); + var filterRegex = new RegExp("https:\/\/s-ccs\.github\.io\/course_EEG\/"); + var isInternal = (href) => { + return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); + } + // Inspect non-navigation links and adorn them if external + var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); + for (var i=0; iBonus: U interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -926,7 +1020,130 @@

Bonus: U try { href = new URL(href).hash; } catch {} const id = href.replace(/^#\/?/, ""); const note = window.document.getElementById(id); - return note.innerHTML; + if (note) { + return note.innerHTML; + } else { + return ""; + } + }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + container.appendChild(note.children[0].cloneNode(true)); + for (let i = 1; i < note.children.length; i++) { + const child = note.children[i]; + if (child.tagName === "P" && child.innerText === "") { + continue; + } else { + container.appendChild(child.cloneNode(true)); + break; + } + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(container); + } + return container.innerHTML + } else { + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + // TODO in 1.5, we should make sure this works without a callout special case + if (note.classList.contains("callout")) { + return note.outerHTML; + } else { + return note.innerHTML; + } + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { }); } let selectedAnnoteEl; @@ -970,6 +1187,7 @@

Bonus: U } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -995,6 +1213,32 @@

Bonus: U }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1058,4 +1302,5 @@

Bonus: U + \ No newline at end of file diff --git a/exercises/solutions/ex6_linearModels_files/figure-html/cell-16-output-2.png b/exercises/solutions/ex6_linearModels_files/figure-html/cell-16-output-1.png similarity index 100% rename from exercises/solutions/ex6_linearModels_files/figure-html/cell-16-output-2.png rename to exercises/solutions/ex6_linearModels_files/figure-html/cell-16-output-1.png diff --git a/exercises/solutions/ex6_linearModels_files/figure-html/cell-22-output-2.png b/exercises/solutions/ex6_linearModels_files/figure-html/cell-22-output-1.png similarity index 100% rename from exercises/solutions/ex6_linearModels_files/figure-html/cell-22-output-2.png rename to exercises/solutions/ex6_linearModels_files/figure-html/cell-22-output-1.png diff --git a/exercises/solutions/ex6_linearModels_files/figure-html/cell-23-output-2.png b/exercises/solutions/ex6_linearModels_files/figure-html/cell-23-output-1.png similarity index 100% rename from exercises/solutions/ex6_linearModels_files/figure-html/cell-23-output-2.png rename to exercises/solutions/ex6_linearModels_files/figure-html/cell-23-output-1.png diff --git a/exercises/solutions/ex6_linearModels_files/figure-html/cell-26-output-2.png b/exercises/solutions/ex6_linearModels_files/figure-html/cell-26-output-1.png similarity index 100% rename from exercises/solutions/ex6_linearModels_files/figure-html/cell-26-output-2.png rename to exercises/solutions/ex6_linearModels_files/figure-html/cell-26-output-1.png diff --git a/exercises/solutions/ex6_linearModels_files/figure-html/cell-30-output-2.png b/exercises/solutions/ex6_linearModels_files/figure-html/cell-30-output-1.png similarity index 100% rename from exercises/solutions/ex6_linearModels_files/figure-html/cell-30-output-2.png rename to exercises/solutions/ex6_linearModels_files/figure-html/cell-30-output-1.png diff --git a/exercises/solutions/ex7_encoding.html b/exercises/solutions/ex7_encoding.html index 9b66101..9f8f811 100644 --- a/exercises/solutions/ex7_encoding.html +++ b/exercises/solutions/ex7_encoding.html @@ -2,12 +2,12 @@ - + -🧠 EEG-2023 - Compare it to binned data +ex7_encoding – 🧠 EEG-2024 - + @@ -79,7 +79,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -88,18 +94,46 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", "search-submit-button-title": "Submit", "search-label": "Search" } } - + - + + + @@ -109,15 +143,15 @@

+
@@ -737,72 +809,96 @@

Fitting a model in
  • You can access the terms using res[β€˜Intercept’], and plot e.g. the betas using res['Intecept'].beta.plot()
-
-
reg['intercept']
+
+
reg['intercept']
lm(beta=<Evoked | '' (average, N=1), -0.29994 – 0.49991 sec, baseline off, 132 ch, ~381 kB>, stderr=<Evoked | '' (average, N=1), -0.29994 – 0.49991 sec, baseline off, 132 ch, ~381 kB>, t_val=<Evoked | '' (average, N=1), -0.29994 – 0.49991 sec, baseline off, 132 ch, ~381 kB>, p_val=<Evoked | '' (average, N=1), -0.29994 – 0.49991 sec, baseline off, 132 ch, ~381 kB>, mlog10_p_val=<Evoked | '' (average, N=1), -0.29994 – 0.49991 sec, baseline off, 132 ch, ~381 kB>)
-
-
reg['intercept'].beta.plot();
-reg['phase-coherence'].beta.plot();
+
+
reg['intercept'].beta.plot();
+reg['phase-coherence'].beta.plot();
-

+
+
+

+
+
-

+
+
+

+
+
  • Because we have all channels now, we can also plot topoplots. Think about how you could visualize the splines as topoplots (note: this is a hard question)
-
-
reg['intercept'].beta.plot_topomap(times = np.arange(-0.3,0.5,0.1))
-reg['phase-coherence'].beta.plot_topomap(times = np.arange(-0.3,0.5,0.1));
+
+
reg['intercept'].beta.plot_topomap(times = np.arange(-0.3,0.5,0.1))
+reg['phase-coherence'].beta.plot_topomap(times = np.arange(-0.3,0.5,0.1));
-

+
+
+

+
+
-

+
+
+

+
+
  • You can also plot the p-values using res[β€˜phase_coherence’].p_val.plot(). Do they follow your intuition?
-
-
reg['phase-coherence'].p_val.plot();
+
+
reg['phase-coherence'].p_val.plot();
-

+
+
+

+
+
  • Bonus: What does the distribution (histogram) of p-values of phase-coherence look like during the baseline? (note, bad channels are not automatically removed in MNE, thus you might want to increase your bin-width to spot them
-
-
pH0 = reg['phase-coherence'].p_val.copy().crop(tmax=0);
+
+
pH0 = reg['phase-coherence'].p_val.copy().crop(tmax=0);
-
-
pH0.data.shape
+
+
pH0.data.shape
(132, 76)
-
-
plData = pH0.data[np.where(np.sum(pH0.data > 0.99,axis=1) < 25),:]
-plt.hist(plData.reshape(np.prod(plData.shape)),bins=100);
+
+
plData = pH0.data[np.where(np.sum(pH0.data > 0.99,axis=1) < 25),:]
+plt.hist(plData.reshape(np.prod(plData.shape)),bins=100);
-

+
+
+

+
+
-
-
np.mean(plData.reshape(np.prod(plData.shape)) < 0.05)
+
+
np.mean(plData.reshape(np.prod(plData.shape)) < 0.05)
0.040736842105263155
-
-
# the channels with the many 1's are the bad channels
-np.where(np.sum(pH0.data > 0.99,axis=1) > 25)
+
+
# the channels with the many 1's are the bad channels
+np.where(np.sum(pH0.data > 0.99,axis=1) > 25)
(array([ 12,  26,  40,  71,  80,  85, 116]),)
@@ -847,18 +943,7 @@

Fitting a model in } return false; } - const clipboard = new window.ClipboardJS('.code-copy-button', { - text: function(trigger) { - const codeEl = trigger.previousElementSibling.cloneNode(true); - for (const childEl of codeEl.children) { - if (isCodeAnnotation(childEl)) { - childEl.remove(); - } - } - return codeEl.innerText; - } - }); - clipboard.on('success', function(e) { + const onCopySuccess = function(e) { // button target const button = e.trigger; // don't keep focus @@ -890,11 +975,50 @@

Fitting a model in }, 1000); // clear code selection e.clearSelection(); + } + const getTextToCopy = function(trigger) { + const codeEl = trigger.previousElementSibling.cloneNode(true); + for (const childEl of codeEl.children) { + if (isCodeAnnotation(childEl)) { + childEl.remove(); + } + } + return codeEl.innerText; + } + const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { + text: getTextToCopy }); - function tippyHover(el, contentFn) { + clipboard.on('success', onCopySuccess); + if (window.document.getElementById('quarto-embedded-source-code-modal')) { + // For code content inside modals, clipBoardJS needs to be initialized with a container option + // TODO: Check when it could be a function (https://github.com/zenorocha/clipboard.js/issues/860) + const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { + text: getTextToCopy, + container: window.document.getElementById('quarto-embedded-source-code-modal') + }); + clipboardModal.on('success', onCopySuccess); + } + var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); + var mailtoRegex = new RegExp(/^mailto:/); + var filterRegex = new RegExp("https:\/\/s-ccs\.github\.io\/course_EEG\/"); + var isInternal = (href) => { + return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); + } + // Inspect non-navigation links and adorn them if external + var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); + for (var i=0; iFitting a model in interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -917,7 +1050,130 @@

Fitting a model in try { href = new URL(href).hash; } catch {} const id = href.replace(/^#\/?/, ""); const note = window.document.getElementById(id); - return note.innerHTML; + if (note) { + return note.innerHTML; + } else { + return ""; + } + }); + } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + container.appendChild(note.children[0].cloneNode(true)); + for (let i = 1; i < note.children.length; i++) { + const child = note.children[i]; + if (child.tagName === "P" && child.innerText === "") { + continue; + } else { + container.appendChild(child.cloneNode(true)); + break; + } + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(container); + } + return container.innerHTML + } else { + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + // TODO in 1.5, we should make sure this works without a callout special case + if (note.classList.contains("callout")) { + return note.outerHTML; + } else { + return note.innerHTML; + } + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { }); } let selectedAnnoteEl; @@ -961,6 +1217,7 @@

Fitting a model in } div.style.top = top - 2 + "px"; div.style.height = height + 4 + "px"; + div.style.left = 0; let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); if (gutterDiv === null) { gutterDiv = window.document.createElement("div"); @@ -986,6 +1243,32 @@

Fitting a model in }); selectedAnnoteEl = undefined; }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } // Attach click handler to the DT const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); for (const annoteDlNode of annoteDls) { @@ -1049,4 +1332,5 @@

Fitting a model in + \ No newline at end of file diff --git a/exercises/solutions/ex7_encoding_files/figure-html/cell-10-output-2.png b/exercises/solutions/ex7_encoding_files/figure-html/cell-10-output-1.png similarity index 100% rename from exercises/solutions/ex7_encoding_files/figure-html/cell-10-output-2.png rename to exercises/solutions/ex7_encoding_files/figure-html/cell-10-output-1.png diff --git a/exercises/solutions/ex7_encoding_files/figure-html/cell-13-output-3.png b/exercises/solutions/ex7_encoding_files/figure-html/cell-13-output-2.png similarity index 100% rename from exercises/solutions/ex7_encoding_files/figure-html/cell-13-output-3.png rename to exercises/solutions/ex7_encoding_files/figure-html/cell-13-output-2.png diff --git a/exercises/solutions/ex7_encoding_files/figure-html/cell-15-output-3.png b/exercises/solutions/ex7_encoding_files/figure-html/cell-15-output-2.png similarity index 100% rename from exercises/solutions/ex7_encoding_files/figure-html/cell-15-output-3.png rename to exercises/solutions/ex7_encoding_files/figure-html/cell-15-output-2.png diff --git a/exercises/solutions/ex7_encoding_files/figure-html/cell-19-output-2.png b/exercises/solutions/ex7_encoding_files/figure-html/cell-19-output-1.png similarity index 100% rename from exercises/solutions/ex7_encoding_files/figure-html/cell-19-output-2.png rename to exercises/solutions/ex7_encoding_files/figure-html/cell-19-output-1.png diff --git a/exercises/solutions/ex8_clusterPerm.html b/exercises/solutions/ex8_clusterPerm.html index f706f49..743074d 100644 --- a/exercises/solutions/ex8_clusterPerm.html +++ b/exercises/solutions/ex8_clusterPerm.html @@ -2,12 +2,12 @@ - + -🧠 EEG-2023 - Multiple Comparison Corrections +Multiple Comparison Corrections – 🧠 EEG-2024