{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.10.0"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "id": "md80953854",
      "metadata": {},
      "source": "# \ud83e\udde0 Tutorial 1 \u2014 Python Basics & EEG Data Loading\n### \u00b7 Biomedical Signals & Applications \u00b7 EEG Python Workshop\n\n---\n\n**Goal of this tutorial:** Get comfortable with Python and load real EEG data for the first time.\n\n**By the end you will be able to:**\n- Use Python variables, lists, dictionaries, loops, and functions\n- Create and manipulate NumPy arrays (the backbone of all scientific Python)\n- Load a real EEG recording into Python using MNE\n- Understand what is stored in a Raw EEG object\n- Plot a raw EEG trace, the electrode montage, and the power spectrum\n\n**Time:** approximately 60 minutes\n\n---\n> \ud83d\udca1 **Before running any cell:** make sure you have installed the required libraries:\n> ```\n> pip install mne numpy matplotlib scipy\n> ```\n> Run cells one by one from top to bottom using **Shift + Enter**."
    },
    {
      "cell_type": "markdown",
      "id": "md72945060",
      "metadata": {},
      "source": "---\n## Section 0 \u2014 Python Crash Course\n\nIf you have never used Python before, this section gives you the minimum you need for EEG analysis. Work through each sub-section \u2014 it only takes about 10 minutes."
    },
    {
      "cell_type": "markdown",
      "id": "md88520920",
      "metadata": {},
      "source": "### 0.1 Variables and Basic Types\n\nPython stores values in **variables**. The type of a variable (number, text, true/false) is determined automatically."
    },
    {
      "cell_type": "code",
      "id": "cd46461798",
      "metadata": {},
      "source": "name    = \"EEG Workshop\"   # string (text)\nyear    = 2025             # integer (whole number)\nfs      = 256.0            # float (decimal number \u2014 sampling frequency in Hz)\nis_demo = True             # boolean (True or False)\n\nprint(name, year, fs, is_demo)",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md56560764",
      "metadata": {},
      "source": "### 0.2 Lists & Indexing\n\nA **list** is an ordered collection of items. Python uses **0-based indexing** \u2014 the first element is at position 0, not 1."
    },
    {
      "cell_type": "code",
      "id": "cd67850261",
      "metadata": {},
      "source": "channels = [\"Fp1\", \"Fp2\", \"F3\", \"F4\", \"C3\", \"C4\", \"P3\", \"P4\", \"O1\", \"O2\"]\n\nprint(\"First channel :\", channels[0])   # position 0 = first\nprint(\"Last channel  :\", channels[-1])  # -1 = last element\nprint(\"First three   :\", channels[:3])  # slice: from start up to (not including) index 3",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md16163718",
      "metadata": {},
      "source": "### 0.3 Dictionaries\n\nA **dictionary** stores key\u2013value pairs. Perfect for metadata like subject information."
    },
    {
      "cell_type": "code",
      "id": "cd23543510",
      "metadata": {},
      "source": "subject_info = {\n    \"id\"   : \"S01\",\n    \"age\"  : 22,\n    \"sex\"  : \"M\",\n    \"task\" : \"motor imagery\",\n}\nprint(\"Task:\", subject_info[\"task\"])\nprint(\"Age :\", subject_info[\"age\"])",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md48176212",
      "metadata": {},
      "source": "### 0.4 Loops\n\nLoops let you repeat an action for every item in a list, or a fixed number of times."
    },
    {
      "cell_type": "code",
      "id": "cd42470841",
      "metadata": {},
      "source": "# Loop over a list\nfor ch in channels[:3]:\n    print(\"Channel:\", ch)\n\n# Loop over a range of numbers (0, 1, 2, 3, 4)\nfor i in range(5):\n    print(i, end=\" \")\nprint()  # newline",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md84909346",
      "metadata": {},
      "source": "### 0.5 Functions\n\nA **function** packages a reusable block of code. You define it once with `def`, then call it as many times as you need."
    },
    {
      "cell_type": "code",
      "id": "cd87073426",
      "metadata": {},
      "source": "def band_name(low, high):\n    \"\"\"Return the EEG frequency band name for a given range.\"\"\"\n    if high <= 4:\n        return \"Delta\"\n    elif high <= 8:\n        return \"Theta\"\n    elif high <= 13:\n        return \"Alpha\"\n    elif high <= 30:\n        return \"Beta\"\n    else:\n        return \"Gamma\"\n\nprint(band_name(8, 13))   # \u2192 Alpha\nprint(band_name(1,  4))   # \u2192 Delta\nprint(band_name(13, 30))  # \u2192 Beta",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md95499417",
      "metadata": {},
      "source": "### 0.6 NumPy \u2014 The Backbone of Scientific Python\n\n**NumPy** is the library that makes Python fast for numerical work. Instead of looping over thousands of numbers one by one, NumPy operates on whole arrays at once.\n\n> \ud83d\udfe2 **Analogy:** Think of a NumPy array as a spreadsheet column. Instead of writing a formula for each cell, you write one formula and it applies to all cells simultaneously."
    },
    {
      "cell_type": "code",
      "id": "cd40041745",
      "metadata": {},
      "source": "import numpy as np\n\n# Create a time axis: 1 second sampled at 256 Hz = 256 points\nt = np.linspace(0, 1, 256)      # from 0 to 1 second, 256 equally spaced points\nf = 10.0                          # 10 Hz = alpha band frequency\n\n# Generate a pure sine wave (what a single EEG oscillation looks like)\nsignal = np.sin(2 * np.pi * f * t)\n\nprint(\"Array shape :\", signal.shape)             # (256,) = 256 numbers\nprint(\"Mean        :\", round(np.mean(signal), 4)) # should be \u2248 0\nprint(\"Std         :\", round(np.std(signal), 4))  # should be \u2248 0.707\nprint(\"Min / Max   :\", round(signal.min(), 4), \"/\", round(signal.max(), 4))",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md77368603",
      "metadata": {},
      "source": "### 0.7 Quick Matplotlib Plot\n\nLet's visualise the sine wave we just created. This is how every EEG waveform will be displayed."
    },
    {
      "cell_type": "code",
      "id": "cd20553041",
      "metadata": {},
      "source": "import matplotlib.pyplot as plt\n\nsignal_uV = signal * 50.0   # scale to \u00b150 \u00b5V (realistic EEG amplitude)\n\nfig, ax = plt.subplots(figsize=(10, 3))\nax.plot(t, signal_uV, color=\"steelblue\", linewidth=1.5)\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Amplitude (\u00b5V)\")\nax.set_title(\"Synthetic 10 Hz Sine Wave \u2014 This is what the Alpha rhythm looks like\")\nax.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.show()",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md29354575",
      "metadata": {},
      "source": "---\n## Section 1 \u2014 Introduction to MNE-Python\n\n**MNE-Python** is the gold-standard open-source library for EEG and MEG analysis. It handles:\n- Loading EEG from virtually any file format (EDF, BDF, FIF, BrainVision, ...)\n- Preprocessing (filtering, ICA, epoching)\n- Visualisation (raw traces, topographic maps, time-frequency plots)\n- Analysis (ERPs, connectivity, decoding)\n\nWe will use MNE for everything from this point forward."
    },
    {
      "cell_type": "code",
      "id": "cd19107530",
      "metadata": {},
      "source": "import mne\n\nprint(\"MNE version:\", mne.__version__)\nmne.set_log_level(\"WARNING\")   # turn off verbose output during the tutorial",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md31841357",
      "metadata": {},
      "source": "---\n## Section 2 \u2014 Loading Real EEG Data\n\nWe will use the **PhysioNet EEG Motor Movement/Imagery Dataset** \u2014 a publicly available dataset where subjects imagined moving their left or right hand. MNE can download it automatically.\n\n- **Subject:** 1\n- **Runs:** 6 and 10 (these contain motor imagery trials: left hand = T1, right hand = T2)\n- **Format:** EDF (European Data Format \u2014 the standard for clinical EEG)\n- **Channels:** 64 electrodes\n- **Sampling rate:** 160 Hz\n\n> \ud83d\udfe2 **Analogy:** Loading the raw EEG is like opening a very long spreadsheet \u2014 64 columns (channels) and ~20,000 rows (time samples). MNE reads the file and packages everything into a single convenient object called `raw`."
    },
    {
      "cell_type": "code",
      "id": "cd43304397",
      "metadata": {},
      "source": "from mne.datasets import eegbci\nfrom mne.io import concatenate_raws, read_raw_edf\n\nsubject = 1\nruns    = [6, 10]   # motor imagery: left hand (T1) and right hand (T2)\n\n# Download data if not already cached, then load\nraw_files = eegbci.load_data(subject, runs, verbose=False)\nraw = concatenate_raws([read_raw_edf(f, preload=True, verbose=False)\n                        for f in raw_files])\n\n# Standardise channel names (PhysioNet uses non-standard capitalisations)\neegbci.standardize(raw)\n\n# Attach electrode positions (needed for topographic maps later)\nmontage = mne.channels.make_standard_montage(\"standard_1005\")\nraw.set_montage(montage, verbose=False)\n\nprint(\"--- Raw object ---\")\nprint(raw)",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md10036253",
      "metadata": {},
      "source": "> \ud83d\udc41 **What to check:**\n> - `n_channels = 64` \u2014 we have 64 EEG electrodes\n> - `sfreq = 160.0 Hz` \u2014 160 samples per second per channel\n> - Duration should be about 2 minutes (two runs of ~62 seconds each)"
    },
    {
      "cell_type": "markdown",
      "id": "md58449486",
      "metadata": {},
      "source": "---\n## Section 3 \u2014 Exploring Raw EEG Data\n\n### 3.1 Accessing the Data as a NumPy Array\n\nThe raw EEG data is stored internally as a 2D NumPy array:\n- **Rows** = channels (64 electrodes)\n- **Columns** = time samples (~19,840 samples at 160 Hz \u00d7 ~124 seconds)"
    },
    {
      "cell_type": "code",
      "id": "cd21684792",
      "metadata": {},
      "source": "data, times = raw.get_data(return_times=True)\n\nprint(\"Data shape (channels \u00d7 samples) :\", data.shape)\nprint(\"Time axis shape                  :\", times.shape)\nprint(f\"Time from {times[0]:.2f} s to {times[-1]:.2f} s\")\n\n# Look at one channel\nch0 = data[0]\nprint(f\"\\nChannel '{raw.ch_names[0]}':\")\nprint(f\"  Mean : {ch0.mean()*1e6:.2f} \u00b5V\")   # convert V \u2192 \u00b5V by \u00d71e6\nprint(f\"  Std  : {ch0.std()*1e6:.2f} \u00b5V\")",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md64498848",
      "metadata": {},
      "source": "### 3.2 Plot a Few Seconds of Raw EEG\n\nThis shows the raw voltage traces for 10 channels over 5 seconds. Notice:\n- The signals are messy \u2014 lots of noise and artefacts on top of the brain signal\n- Large spikes at frontal channels (Fp1, Fp2) are eye blinks\n- The amplitude is in \u00b5V \u2014 very small numbers!"
    },
    {
      "cell_type": "code",
      "id": "cd73079559",
      "metadata": {},
      "source": "fig = raw.plot(\n    duration=5,\n    n_channels=15,\n    scalings=\"auto\",\n    title=\"Raw EEG \u2014 Motor Imagery (first 5 seconds)\",\n    show=True,\n    block=False,\n)\nplt.show()",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md37582071",
      "metadata": {},
      "source": "### 3.3 Plot the Electrode Montage\n\nThis shows the positions of all 64 electrodes on a 2D projection of the scalp (viewed from above). Key electrodes for motor imagery:\n- **C3** (left): over the left motor cortex \u2192 active during RIGHT hand imagery\n- **Cz** (centre): over the central motor cortex\n- **C4** (right): over the right motor cortex \u2192 active during LEFT hand imagery"
    },
    {
      "cell_type": "code",
      "id": "cd66470178",
      "metadata": {},
      "source": "fig = raw.plot_sensors(show_names=True, show=True)\nplt.show()",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md36386662",
      "metadata": {},
      "source": "### 3.4 Power Spectral Density \u2014 First Look at Frequencies\n\nThe **Power Spectral Density (PSD)** shows how much signal energy is present at each frequency. This is our first look at the frequency content of the EEG.\n\n> \ud83d\udfe2 **Analogy:** The PSD is like a music equaliser showing the volume at each frequency band. We can see whether there is more bass (low frequencies) or treble (high frequencies) in the signal.\n\n**What to look for:**\n- The signal decreases as frequency increases (this is normal \u2014 called 1/f or \"pink noise\")\n- There may be a bump at 8\u201312 Hz (the **alpha peak** \u2014 the idle rhythm of the brain)\n- There will be a sharp spike at **60 Hz** (US power-line interference \u2014 we will remove this in Tutorial 2)"
    },
    {
      "cell_type": "code",
      "id": "cd70536969",
      "metadata": {},
      "source": "psd = raw.compute_psd(fmax=80)\nfig = psd.plot(show=True)\nplt.show()",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md48141727",
      "metadata": {},
      "source": "---\n## Section 4 \u2014 The MNE Info Object\n\n`raw.info` is a dictionary-like container with all the metadata about the recording. You will use this constantly in MNE."
    },
    {
      "cell_type": "code",
      "id": "cd25774078",
      "metadata": {},
      "source": "print(\"Sampling frequency  :\", raw.info[\"sfreq\"], \"Hz\")\nprint(\"Number of channels  :\", raw.info[\"nchan\"])\nprint(\"First 5 channels    :\", raw.info[\"ch_names\"][:5])\nprint(\"Marked bad channels :\", raw.info[\"bads\"])   # empty for now\nprint(\"\\nFull info object:\")\nprint(raw.info)",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md46854521",
      "metadata": {},
      "source": "---\n## Section 5 \u2014 Events and Annotations\n\nDuring recording, **event markers** (also called annotations) were saved at the exact moment each experimental event occurred:\n- **T0** = rest period begins\n- **T1** = left hand imagery cue appeared\n- **T2** = right hand imagery cue appeared\n\n`mne.events_from_annotations()` extracts these time stamps as an array with 3 columns:\n1. Sample number (when the event happened)\n2. Previous event ID (usually 0)\n3. Event ID (1=T0, 2=T1, 3=T2)"
    },
    {
      "cell_type": "code",
      "id": "cd34341218",
      "metadata": {},
      "source": "events, event_id = mne.events_from_annotations(raw, verbose=False)\n\nprint(\"Event ID dictionary:\", event_id)\nprint(\"Events array shape :\", events.shape)   # (n_events, 3)\nprint(\"\\nFirst 10 events (sample, prev_id, event_id):\")\nprint(events[:10])",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "code",
      "id": "cd88851657",
      "metadata": {},
      "source": "# Visualise event timing on a timeline\nfig = mne.viz.plot_events(\n    events, event_id=event_id, sfreq=raw.info[\"sfreq\"],\n    first_samp=raw.first_samp\n)\nplt.title(\"Event markers in the recording\")\nplt.show()",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md34920375",
      "metadata": {},
      "source": "> \ud83d\udc41 **What to check:**\n> - T1 (left hand) and T2 (right hand) should alternate regularly\n> - Each trial lasts about 4 seconds (you can check the sample numbers)\n> - This structure will be used in Tutorial 2 to cut the recording into epochs"
    },
    {
      "cell_type": "markdown",
      "id": "md87998727",
      "metadata": {},
      "source": "---\n## Section 6 \u2014 Manual Plot of One Channel\n\nLet's plot a short segment of channel C3 manually using matplotlib, to understand exactly what we are working with.\n\n**C3** sits over the LEFT motor cortex \u2014 it is one of the two most important channels for motor imagery classification."
    },
    {
      "cell_type": "code",
      "id": "cd98232110",
      "metadata": {},
      "source": "idx_c3 = raw.ch_names.index(\"C3\")\ndata_c3 = data[idx_c3, :800] * 1e6   # first 5 seconds, converted to \u00b5V\ntime_c3 = times[:800]\n\nfig, ax = plt.subplots(figsize=(12, 3))\nax.plot(time_c3, data_c3, color=\"steelblue\", lw=0.8)\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Amplitude (\u00b5V)\")\nax.set_title(\"Channel C3 \u2014 Raw EEG (first 5 seconds, before preprocessing)\")\nax.axhline(0, color=\"k\", lw=0.5, linestyle=\"--\")\nax.grid(True, alpha=0.2)\nplt.tight_layout()\nplt.show()\n\nprint(f\"C3 amplitude range: {data_c3.min():.1f} to {data_c3.max():.1f} \u00b5V\")",
      "outputs": [],
      "execution_count": null
    },
    {
      "cell_type": "markdown",
      "id": "md62872907",
      "metadata": {},
      "source": "---\n## \u2705 Tutorial 1 \u2014 Summary\n\nYou have completed Tutorial 1. Here is what you learned:\n\n| Concept | Python / MNE command |\n|---|---|\n| NumPy array creation | `np.linspace()`, `np.sin()` |\n| Array statistics | `np.mean()`, `np.std()` |\n| Basic plotting | `plt.plot()`, `plt.show()` |\n| Load EEG from file | `read_raw_edf()`, `concatenate_raws()` |\n| Attach electrode positions | `raw.set_montage()` |\n| Access raw data | `raw.get_data()` |\n| Inspect metadata | `raw.info` |\n| Extract event markers | `mne.events_from_annotations()` |\n| Quick spectrum plot | `raw.compute_psd().plot()` |\n\n---\n\n### \u279c Next: Tutorial 2 \u2014 EEG Preprocessing\n\nIn Tutorial 2 we will clean this raw data:\n1. Re-reference to average\n2. Filter (remove power-line noise and slow drifts)\n3. Detect and interpolate bad channels\n4. Remove eye-blink and muscle artefacts with ICA\n5. Cut into epochs around the motor imagery cues\n6. Apply baseline correction and reject noisy trials"
    }
  ]
}
