Skip to content
Snippets Groups Projects
09_eeg_use_case.ipynb 1.43 MiB
Newer Older
{
 "cells": [
  {
   "cell_type": "code",
Franziska Oschmann's avatar
Franziska Oschmann committed
   "execution_count": 1,
   "metadata": {},
Franziska Oschmann's avatar
Franziska Oschmann committed
   "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",
Franziska Oschmann's avatar
Franziska Oschmann committed
       "</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"
Franziska Oschmann's avatar
Franziska Oschmann committed
      ],
      "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": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "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",
    "</figure>\n",
    "</center>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This data contains EEG recordings of one subject performing **grasp-and-lift (GAL)** trials. \n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "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",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "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": [
Franziska Oschmann's avatar
Franziska Oschmann committed
    "## Load data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
Franziska Oschmann's avatar
Franziska Oschmann committed
    "The data can be found in: `/data/eeg_use_case` and contains:\n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "\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",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "\n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "- combine all events in one array (size: (total number of time series, number of different arm movement))\n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "\n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "- pay attention to the order of the series"
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-warning\">\n",
    "    <i class=\"fa fa-info-circle\"></i>&nbsp; <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>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "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",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "        df = pd.read_csv(path + f).drop('id', axis = 1)\n",
    "        dfs.append(df)\n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "    return dfs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
Franziska Oschmann's avatar
Franziska Oschmann committed
    "# 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",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "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)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
Franziska Oschmann's avatar
Franziska Oschmann committed
    "# 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": [
    "## Visualization"
Franziska Oschmann's avatar
Franziska Oschmann committed
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise section\n",
    "\n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "Visualize the EEG-data and events and pay attention to:\n",
Franziska Oschmann's avatar
Franziska Oschmann committed
    "- the EEG traces (plt.plot())\n",
    "- the number of detected arm movements (plt.hist())\n",
    "\n",
    "What do you observe?"
Franziska Oschmann's avatar
Franziska Oschmann committed
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "tags": [
     "solution"
    ]
   },
   "outputs": [
    {
     "data": {
Franziska Oschmann's avatar
Franziska Oschmann committed
      "image/png": "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
Loading
Loading full blame...