Newer
Older
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"outputs": [
{
"data": {
"text/html": [
"<style>\n",
" \n",
" @import url('http://fonts.googleapis.com/css?family=Source+Code+Pro');\n",
" \n",
" @import url('http://fonts.googleapis.com/css?family=Kameron');\n",
" @import url('http://fonts.googleapis.com/css?family=Crimson+Text');\n",
" \n",
" @import url('http://fonts.googleapis.com/css?family=Lato');\n",
" @import url('http://fonts.googleapis.com/css?family=Source+Sans+Pro');\n",
" \n",
" @import url('http://fonts.googleapis.com/css?family=Lora'); \n",
"\n",
" \n",
" body {\n",
" font-family: 'Lora', Consolas, sans-serif;\n",
" \n",
" -webkit-print-color-adjust: exact important !;\n",
" \n",
" \n",
" \n",
" }\n",
" \n",
" .alert-block {\n",
" width: 95%;\n",
" margin: auto;\n",
" }\n",
" \n",
" .rendered_html code\n",
" {\n",
" color: black;\n",
" background: #eaf0ff;\n",
" background: #f5f5f5; \n",
" padding: 1pt;\n",
" font-family: 'Source Code Pro', Consolas, monocco, monospace;\n",
" }\n",
" \n",
" p {\n",
" line-height: 140%;\n",
" }\n",
" \n",
" strong code {\n",
" background: red;\n",
" }\n",
" \n",
" .rendered_html strong code\n",
" {\n",
" background: #f5f5f5;\n",
" }\n",
" \n",
" .CodeMirror pre {\n",
" font-family: 'Source Code Pro', monocco, Consolas, monocco, monospace;\n",
" }\n",
" \n",
" .cm-s-ipython span.cm-keyword {\n",
" font-weight: normal;\n",
" }\n",
" \n",
" strong {\n",
" background: #f5f5f5;\n",
" margin-top: 4pt;\n",
" margin-bottom: 4pt;\n",
" padding: 2pt;\n",
" border: 0.5px solid #a0a0a0;\n",
" font-weight: bold;\n",
" color: darkred;\n",
" }\n",
" \n",
" \n",
" div #notebook {\n",
" # font-size: 10pt; \n",
" line-height: 145%;\n",
" }\n",
" \n",
" li {\n",
" line-height: 145%;\n",
" }\n",
"\n",
" div.output_area pre {\n",
" background: #fff9d8 !important;\n",
" padding: 5pt;\n",
" \n",
" -webkit-print-color-adjust: exact; \n",
" \n",
" }\n",
" \n",
" \n",
" \n",
" h1, h2, h3, h4 {\n",
" font-family: Kameron, arial;\n",
"\n",
" }\n",
" \n",
" div#maintoolbar {display: none !important;}\n",
"\n",
" div#site { \n",
" border-top: 20px solid #1F407A; \n",
" border-right: 20px solid #1F407A; \n",
" margin-bottom: 0;\n",
" padding-bottom: 0;\n",
" }\n",
" div#toc-wrapper { \n",
" border-left: 20px solid #1F407A; \n",
" border-top: 20px solid #1F407A; \n",
"\n",
" }\n",
"\n",
" body {\n",
" margin-botton:10px;\n",
" }\n",
"\n",
"</style>\n",
" <script>\n",
"IPython.OutputArea.prototype._should_scroll = function(lines) {\n",
" return false;\n",
"}\n",
" </script>\n",
"\n",
"\n",
"<footer id=\"attribution\" style=\"float:left; color:#1F407A; background:#fff; font-family: helvetica;\">\n",
" Copyright (C) 2019 Scientific IT Services of ETH Zurich,\n",
" <p>\n",
" Contributing Authors:\n",
" Dr. Tarun Chadha,\n",
" Dr. Franziska Oschmann,\n",
" Dr. Mikolaj Rybinski,\n",
" Dr. Uwe Schmitt.\n",
" </p<\n",
"</footer>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# IGNORE THIS CELL WHICH CUSTOMIZES LAYOUT AND STYLING OF THE NOTEBOOK !\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline\n",
"%config InlineBackend.figure_format = 'retina'\n",
"import warnings\n",
"warnings.filterwarnings('ignore', category=FutureWarning)\n",
"warnings.filterwarnings = lambda *a, **kw: None\n",
"from IPython.core.display import HTML; HTML(open(\"custom.html\", \"r\").read())"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Chapter 9: Use case - prediction of arm movements\n",
"\n",
"<center>\n",
"<figure>\n",
"<table><tr>\n",
"<td> <img src=\"./images/eeg_cap.png\" style=\"width: 400px;\"/> </td>\n",
"<td> <img src=\"./images/arm_movement.png\" style=\"width: 400px;\"/> </td>\n",
"</tr></table>\n",
"<figcaption>Setup of an EEG-experiment.</figcaption>\n",
"</figure>\n",
"</center>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Background"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<center>\n",
"<figure>\n",
" <img src=\"./images/eeg_electrode_numbering.jpg\" width=35%/> \n",
" <figcaption>Arrangement of electrodes on head.</figcaption>\n",
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This data contains EEG recordings of one subject performing **grasp-and-lift (GAL)** trials. \n",
"There is **1 subject** in total, **8 series** of trials for this subject, and approximately **30 trials** within each series. The number of trials varies for each series.\n",
"\n",
"For each **GAL**, you are tasked to detect 6 events:\n",
"\n",
"- HandStart\n",
"- FirstDigitTouch\n",
"- BothStartLoadPhase\n",
"- LiftOff\n",
"- Replace\n",
"- BothReleased\n",
"\n",
"These events always occur in the same order. In this dataset, there are two files for the subject + series combination:\n",
"the ***_data.csv** files contain the raw 32 channels EEG data (sampling rate 500Hz)\n",
"the ***_events.csv** files contains the ground truth frame-wise labels for all events\n",
"\n",
"\n",
"Detailed information about the data can be found here:\n",
"Luciw MD, Jarocka E, Edin BB (2014) Multi-channel EEG recordings during 3,936 grasp and lift trials with varying weight and friction. Scientific Data 1:140047. www.nature.com/articles/sdata201447\n",
"\n",
"*Description from https://www.kaggle.com/c/grasp-and-lift-eeg-detection/data*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<center>\n",
"<figure>\n",
" <img src=\"./images/eeg_signal_preprocessing.png\" title=\"made at imgflip.com\" width=75%/> \n",
" <figcaption>Preprocessing steps for EEG-signals.</figcaption>\n",
"</figure>\n",
"</center>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The data can be found in: `/data/eeg_use_case` and contains:\n",
"\n",
"- 8 series of recorded EEG data\n",
"\n",
"- 8 series of events of arm movements\n",
"\n",
"Load the EEG data and the events:\n",
"- combine all EEG series in one array (size: (total number of time series, number of channels))\n",
"- combine all events in one array (size: (total number of time series, number of different arm movement))\n",
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-warning\">\n",
" <i class=\"fa fa-info-circle\"></i> <strong>Filter strings with the lambda-operator</strong> \n",
" The lambda-operator allows to build hidden functions, which are basically functions without a name. These hidden functions have any number of parameters, execute an expression and return the value of this expression. The lambda operator can be applied in the following way to filter the filenames:\n",
" \n",
" all_data_files = list(filter(lambda x: '_data' in x, os.listdir(path)))\n",
"</div>"
]
},
"metadata": {},
"outputs": [],
"source": [
"def load_data(file_names, path):\n",
" # read the csv file and drop the id column\n",
" dfs = []\n",
" for f in file_names:\n",
" df = pd.read_csv(path + f).drop('id', axis = 1)\n",
"metadata": {},
"outputs": [],
"source": [
"# define path and list of all data and event files\n",
"import os\n",
"import pandas as pd\n",
"\n",
"path = 'data/eeg_use_case/' \n",
"\n",
"all_data_files = list(filter(lambda x: '_data' in x, os.listdir(path)))\n",
"all_event_files = list(filter(lambda x: '_events' in x, os.listdir(path)))\n",
"\n",
"all_data_sort = np.sort(all_data_files)\n",
"all_event_sort = np.sort(all_event_files)"
"metadata": {},
"outputs": [],
"source": [
"# load all data and event files\n",
"all_data = np.concatenate(load_data(all_data_sort, path))\n",
"all_events = np.concatenate(load_data(all_event_sort, path))"
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Visualize the EEG-data and events and pay attention to:\n",
"- the EEG traces (plt.plot())\n",
"- the number of detected arm movements (plt.hist())\n",
"execution_count": 6,
"metadata": {
"tags": [
"solution"
]
},
"image/png": "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
Loading
Loading full blame...