{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "from matplotlib import pylab as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "import json\n",
    "\n",
    "%matplotlib inline\n",
    "plt.style.use('bmh')\n",
    "plt.rcParams.update({'font.size': 14})\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Загрузка 🐱‍👤"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Описание формата csv файла:\n",
    ">http://openvibe.inria.fr/csv-file-format-description/\n",
    "\n",
    "Коды событий:\n",
    ">http://openvibe.inria.fr/stimulation-codes/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "openvibe_config = json.load(open(\"./config/openVibe.json\", \"r\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "raw_data = pd.read_csv(\"./Записи/OpenVibe signals/motor-imagery-csp-1-acquisition-[2024.04.18-10.37.21].csv\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Time:250Hz</th>\n",
       "      <th>Epoch</th>\n",
       "      <th>Channel 1</th>\n",
       "      <th>Channel 2</th>\n",
       "      <th>Channel 3</th>\n",
       "      <th>Channel 4</th>\n",
       "      <th>Channel 5</th>\n",
       "      <th>Channel 6</th>\n",
       "      <th>Channel 7</th>\n",
       "      <th>Channel 8</th>\n",
       "      <th>...</th>\n",
       "      <th>Channel 10</th>\n",
       "      <th>Channel 11</th>\n",
       "      <th>Channel 12</th>\n",
       "      <th>Channel 13</th>\n",
       "      <th>Channel 14</th>\n",
       "      <th>Channel 15</th>\n",
       "      <th>Channel 16</th>\n",
       "      <th>Event Id</th>\n",
       "      <th>Event Date</th>\n",
       "      <th>Event Duration</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0.000</td>\n",
       "      <td>0</td>\n",
       "      <td>171386.796875</td>\n",
       "      <td>165426.734375</td>\n",
       "      <td>154487.453125</td>\n",
       "      <td>162283.515625</td>\n",
       "      <td>181144.156250</td>\n",
       "      <td>166410.906250</td>\n",
       "      <td>158936.093750</td>\n",
       "      <td>149495.546875</td>\n",
       "      <td>...</td>\n",
       "      <td>179601.828125</td>\n",
       "      <td>142757.171875</td>\n",
       "      <td>123782.796875</td>\n",
       "      <td>155169.875000</td>\n",
       "      <td>146074.296875</td>\n",
       "      <td>151097.203125</td>\n",
       "      <td>132544.546875</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0.004</td>\n",
       "      <td>0</td>\n",
       "      <td>171392.593750</td>\n",
       "      <td>165444.875000</td>\n",
       "      <td>154490.781250</td>\n",
       "      <td>162296.968750</td>\n",
       "      <td>181143.578125</td>\n",
       "      <td>166415.734375</td>\n",
       "      <td>158936.906250</td>\n",
       "      <td>149496.156250</td>\n",
       "      <td>...</td>\n",
       "      <td>179604.187500</td>\n",
       "      <td>142784.218750</td>\n",
       "      <td>123787.976562</td>\n",
       "      <td>155203.531250</td>\n",
       "      <td>146079.062500</td>\n",
       "      <td>151096.406250</td>\n",
       "      <td>132547.890625</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0.008</td>\n",
       "      <td>0</td>\n",
       "      <td>171387.046875</td>\n",
       "      <td>165428.828125</td>\n",
       "      <td>154489.281250</td>\n",
       "      <td>162286.640625</td>\n",
       "      <td>181143.687500</td>\n",
       "      <td>166412.078125</td>\n",
       "      <td>158937.578125</td>\n",
       "      <td>149494.859375</td>\n",
       "      <td>...</td>\n",
       "      <td>179602.234375</td>\n",
       "      <td>142767.937500</td>\n",
       "      <td>123788.117188</td>\n",
       "      <td>155178.015625</td>\n",
       "      <td>146076.218750</td>\n",
       "      <td>151096.375000</td>\n",
       "      <td>132545.265625</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0.012</td>\n",
       "      <td>0</td>\n",
       "      <td>171377.921875</td>\n",
       "      <td>165400.750000</td>\n",
       "      <td>154485.890625</td>\n",
       "      <td>162266.593750</td>\n",
       "      <td>181143.359375</td>\n",
       "      <td>166404.328125</td>\n",
       "      <td>158937.078125</td>\n",
       "      <td>149492.484375</td>\n",
       "      <td>...</td>\n",
       "      <td>179598.078125</td>\n",
       "      <td>142729.265625</td>\n",
       "      <td>123783.914062</td>\n",
       "      <td>155126.984375</td>\n",
       "      <td>146069.140625</td>\n",
       "      <td>151094.765625</td>\n",
       "      <td>132538.375000</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0.016</td>\n",
       "      <td>0</td>\n",
       "      <td>171376.703125</td>\n",
       "      <td>165396.484375</td>\n",
       "      <td>154484.875000</td>\n",
       "      <td>162261.906250</td>\n",
       "      <td>181142.125000</td>\n",
       "      <td>166401.875000</td>\n",
       "      <td>158935.968750</td>\n",
       "      <td>149490.734375</td>\n",
       "      <td>...</td>\n",
       "      <td>179596.703125</td>\n",
       "      <td>142719.171875</td>\n",
       "      <td>123783.015625</td>\n",
       "      <td>155120.093750</td>\n",
       "      <td>146067.218750</td>\n",
       "      <td>151092.062500</td>\n",
       "      <td>132535.734375</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>0.020</td>\n",
       "      <td>0</td>\n",
       "      <td>171385.546875</td>\n",
       "      <td>165420.531250</td>\n",
       "      <td>154489.937500</td>\n",
       "      <td>162277.234375</td>\n",
       "      <td>181143.109375</td>\n",
       "      <td>166408.687500</td>\n",
       "      <td>158938.078125</td>\n",
       "      <td>149492.656250</td>\n",
       "      <td>...</td>\n",
       "      <td>179601.718750</td>\n",
       "      <td>142751.265625</td>\n",
       "      <td>123791.242188</td>\n",
       "      <td>155167.218750</td>\n",
       "      <td>146075.421875</td>\n",
       "      <td>151093.078125</td>\n",
       "      <td>132542.531250</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>6 rows × 21 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "   Time:250Hz  Epoch      Channel 1      Channel 2      Channel 3  \\\n",
       "0       0.000      0  171386.796875  165426.734375  154487.453125   \n",
       "1       0.004      0  171392.593750  165444.875000  154490.781250   \n",
       "2       0.008      0  171387.046875  165428.828125  154489.281250   \n",
       "3       0.012      0  171377.921875  165400.750000  154485.890625   \n",
       "4       0.016      0  171376.703125  165396.484375  154484.875000   \n",
       "5       0.020      0  171385.546875  165420.531250  154489.937500   \n",
       "\n",
       "       Channel 4      Channel 5      Channel 6      Channel 7      Channel 8  \\\n",
       "0  162283.515625  181144.156250  166410.906250  158936.093750  149495.546875   \n",
       "1  162296.968750  181143.578125  166415.734375  158936.906250  149496.156250   \n",
       "2  162286.640625  181143.687500  166412.078125  158937.578125  149494.859375   \n",
       "3  162266.593750  181143.359375  166404.328125  158937.078125  149492.484375   \n",
       "4  162261.906250  181142.125000  166401.875000  158935.968750  149490.734375   \n",
       "5  162277.234375  181143.109375  166408.687500  158938.078125  149492.656250   \n",
       "\n",
       "   ...     Channel 10     Channel 11     Channel 12     Channel 13  \\\n",
       "0  ...  179601.828125  142757.171875  123782.796875  155169.875000   \n",
       "1  ...  179604.187500  142784.218750  123787.976562  155203.531250   \n",
       "2  ...  179602.234375  142767.937500  123788.117188  155178.015625   \n",
       "3  ...  179598.078125  142729.265625  123783.914062  155126.984375   \n",
       "4  ...  179596.703125  142719.171875  123783.015625  155120.093750   \n",
       "5  ...  179601.718750  142751.265625  123791.242188  155167.218750   \n",
       "\n",
       "      Channel 14     Channel 15     Channel 16  Event Id Event Date  \\\n",
       "0  146074.296875  151097.203125  132544.546875       NaN        NaN   \n",
       "1  146079.062500  151096.406250  132547.890625       NaN        NaN   \n",
       "2  146076.218750  151096.375000  132545.265625       NaN        NaN   \n",
       "3  146069.140625  151094.765625  132538.375000       NaN        NaN   \n",
       "4  146067.218750  151092.062500  132535.734375       NaN        NaN   \n",
       "5  146075.421875  151093.078125  132542.531250       NaN        NaN   \n",
       "\n",
       "  Event Duration  \n",
       "0            NaN  \n",
       "1            NaN  \n",
       "2            NaN  \n",
       "3            NaN  \n",
       "4            NaN  \n",
       "5            NaN  \n",
       "\n",
       "[6 rows x 21 columns]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "raw_data.head(6)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Фильтруем события, для нас интересны стрелки (лево/право)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----------------------"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def filter_events(df: pd.DataFrame, config: dict[str, any]) -> pd.DataFrame:\n",
    "    \"\"\"Получение табличной нотации для экспериментов из файлов OpenVibe (.csv)\n",
    "\n",
    "    Args:\n",
    "        df (pd.DataFrame): сырой файл .ov сконвертированный в .csv и загруженный в pandas\n",
    "        config (dict[str, any]): словарь с расшифровкой кодов событий OpenVibe\n",
    "\n",
    "    Returns:\n",
    "        pd.DataFrame: фрейм (таблица) в нашей собственной нотации\n",
    "    \"\"\"\n",
    "\n",
    "    events = []\n",
    "    __temp_event = {}\n",
    "\n",
    "    new_frame = df.copy()\n",
    "\n",
    "    __ov_events = df[~df['Event Id'].isna()][['Event Id']]\n",
    "    for idx in range(__ov_events.shape[0]):\n",
    "        __row_events = __ov_events.iloc[idx, 0].split(\":\")\n",
    "        \n",
    "        if config['main_actions']['left'] in __row_events or config['main_actions']['right'] in __row_events:\n",
    "            __temp_event = {\"start\": __ov_events.index[idx]}\n",
    "            if config['main_actions']['left'] in __row_events:\n",
    "                __temp_event['action'] = 'left'\n",
    "            if config['main_actions']['right'] in __row_events:\n",
    "                __temp_event['action'] = 'right'\n",
    "\n",
    "        # В записи stop повторяется дважды (как и любые другие действия)\n",
    "        if config['main_actions']['stop'] in __row_events and len(__temp_event) > 0:\n",
    "            __temp_event['stop'] = __ov_events.index[idx + 1]\n",
    "            events.append(__temp_event.copy())\n",
    "            __temp_event = {}\n",
    "\n",
    "    new_frame = new_frame.drop(columns=['Epoch', 'Event Id', 'Event Date', 'Event Duration'])\n",
    "    new_frame['action'] = None\n",
    "\n",
    "    for event in events:\n",
    "        new_frame.loc[event['start']: event['stop'], 'action'] = event['action']\n",
    "\n",
    "    new_frame['action'] = new_frame['action'].fillna('relax')\n",
    "    return new_frame"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_ranges(labels: np.ndarray) -> list[list[int, int, int]]:\n",
    "    \"\"\"Преобразование сырых лейблов временного ряда в отрезки для визуализации\n",
    "\n",
    "    Args:\n",
    "        labels (np.ndarray): лейблы\n",
    "\n",
    "    Returns:\n",
    "        list[list[int, int, int]]: список отрезков для визуализации, каждый отрезок определен как\n",
    "            тройка \"начало\", \"конец\", \"тип действия\"\n",
    "    \"\"\"\n",
    "\n",
    "    actions = []\n",
    "    __temp_action = [0]\n",
    "    __prev_action = labels[0]\n",
    "    idx = 1\n",
    "\n",
    "    while True:\n",
    "        if idx + 1 > len(labels):\n",
    "            __temp_action.extend([idx, __prev_action])\n",
    "            actions.append(__temp_action)\n",
    "            break\n",
    "\n",
    "        if labels[idx] != __prev_action:\n",
    "            __temp_action.extend([idx - 1, __prev_action])\n",
    "            actions.append(__temp_action)\n",
    "            __temp_action = [idx]\n",
    "            __prev_action = labels[idx]\n",
    "\n",
    "        idx += 1\n",
    "\n",
    "    return actions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = filter_events(raw_data, openvibe_config)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 135492 characters]",
      "text/plain": [
       "<Figure size 1500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_data = data.copy()\n",
    "actions = get_ranges(plot_data['action'])\n",
    "action_colors = {\"relax\": \"#C0E986\", \"left\": \"#CA86E9\", \"right\": \"#E98B86\"}\n",
    "action_names = {\"relax\": \"отдых\", \"left\": \"левая рука\", \"right\": \"правая рука\"}\n",
    "index = 2\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(15, 5))\n",
    "__main = ax.plot(plot_data.iloc[:, index], color=\"black\", linewidth=.4, label=\"Данные\")\n",
    "ax.set_xticks(ax.get_xticks())\n",
    "ax.set_xticklabels([x * 0.004 for x in ax.get_xticks()])\n",
    "ax.set_xlim(0, 107_776)\n",
    "\n",
    "__patches = []\n",
    "for act in actions:\n",
    "    __patches.append(plt.axvspan(act[0], act[1], color=action_colors[act[2]], label=action_names[act[2]]))\n",
    "\n",
    "ax.set_title(f\"Визуализация активности в сессии 1\\nс датчика \\\"{data.columns[index]}\\\"\")\n",
    "ax.set_xlabel(\"Время, с\")\n",
    "ax.set_ylabel(\"Значение сигнала\\n(формат OpenVibe)\")\n",
    "ax.legend(loc=\"best\", handles=[__main[0]] + __patches[7:10])\n",
    "plt.show()\n",
    "\n",
    "del __main, __patches"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 53048 characters]",
      "text/plain": [
       "<Figure size 1500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(1, figsize=(15, 5))\n",
    "__temp = pd.DataFrame({'Длительность, с': [(x[1] - x[0]) / 250 for x in actions[1:-1]], 'Действие': [x[2] for x in actions[1:-1]]})\n",
    "sns.boxplot(data=__temp, x='Действие', y='Длительность, с', ax=ax)\n",
    "ax.set_title('Распределение времени для каждого действия')\n",
    "del __temp\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Что делаем с выбросами? Это так и задуманно? (вопрос 1 из сообщения)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Формирование бинарного датасета 👥"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Параметры датасета\n",
    "\n",
    "* Полосовой фильтр ✔\n",
    "    * частота $8-30 Гц$\n",
    "    * порядок $5$\n",
    "* Эпохи ✔\n",
    "    * $0,5 с$ после предъявления стимула\n",
    "    * $4 c$ - длительность эпохи\n",
    "    * Насколько смещаемся?\n",
    "* CSP-фильтр по эпохам ✔\n",
    "    * количество признаков $1024$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Полосовой фильтр"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.signal import butter, lfilter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def __create_constants(low: float, high: float, sample_rate: float, order: int) -> tuple[float, float]:\n",
    "    \"\"\"Вычисление констан полосового фильтра (Баттерворта)\n",
    "\n",
    "    Args:\n",
    "        low (float): нижняя чатсота среза\n",
    "        high (float): верхняя частота среза\n",
    "        sample_rate (float): частота дискретизации\n",
    "        order (int): порядок фильтра\n",
    "\n",
    "    Returns:\n",
    "        tuple[float, float]: константы фильтра, необходимые для вычислений\n",
    "    \"\"\"\n",
    "    return butter(order, [low, high], fs=sample_rate, btype='band')\n",
    "\n",
    "def filter_signal(df: pd.DataFrame, cols: list[str], constants: tuple[float, float]) -> pd.DataFrame:\n",
    "    \"\"\"Фильтрация датасета (сигналов с датчиков)\n",
    "\n",
    "    Args:\n",
    "        df (pd.DataFrame): датафрейм с данными из OpenVibe\n",
    "        cols (list[str]): список колонок, данные в которых нужно отфильтровать\n",
    "        constants (tuple[float, float]): константы для вычисления фильтра (см. __create_constants)\n",
    "\n",
    "    Returns:\n",
    "        pd.DataFrame: входной фрейм с данными, с примененным фильтром\n",
    "    \"\"\"\n",
    "    new_df = df.copy()\n",
    "    for col in cols:\n",
    "        new_df[col] = lfilter(*constants, df[col].values)\n",
    "\n",
    "    return new_df\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 163240 characters]",
      "text/plain": [
       "<Figure size 1500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "index = 1\n",
    "\n",
    "constants = __create_constants(8.0, 30.0, 250.0, 5) \n",
    "plot_data = filter_signal(plot_data, [data.columns[index]], constants)\n",
    "actions = get_ranges(plot_data['action'])\n",
    "action_colors = {\"relax\": \"#C0E986\", \"left\": \"#CA86E9\", \"right\": \"#E98B86\"}\n",
    "action_names = {\"relax\": \"отдых\", \"left\": \"левая рука\", \"right\": \"правая рука\"}\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(15, 5))\n",
    "__filtered = ax.plot(plot_data.iloc[1000:, index], color=\"black\", linewidth=.4, label=\"Фильтрованные данные\")\n",
    "ax.set_xticks(ax.get_xticks())\n",
    "ax.set_xticklabels([x * 0.004 for x in ax.get_xticks()])\n",
    "ax.set_xlim(0, 107_776)\n",
    "\n",
    "__patches = []\n",
    "for act in actions:\n",
    "    __patches.append(plt.axvspan(act[0], act[1], color=action_colors[act[2]], label=action_names[act[2]]))\n",
    "\n",
    "ax.set_title(f\"Визуализация активности в сессии 1\\nс датчика \\\"{data.columns[index]}\\\" (пропущена 1000 отсчетов, артефакты фильтрации)\")\n",
    "ax.set_xlabel(\"Время, с\")\n",
    "ax.set_ylabel(\"Значение сигнала\\n(формат OpenVibe)\")\n",
    "ax.legend(loc=\"best\", handles=[__filtered[0]] + __patches[7:10])\n",
    "plt.show()\n",
    "\n",
    "del __patches, __filtered"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "constants = __create_constants(low=8.0, high=30.0, sample_rate=250.0, order=5) \n",
    "filtered_data = filter_signal(df=data, cols=[x for x in data.columns if \"Channel\" in x], constants=constants)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Скользящее окно\n",
    "\n",
    "> Пояснение к вопросу 3 из сообщения\n",
    "\n",
    "Разбиение:\n",
    "\n",
    "|----------| Стимул ($5 с$)\n",
    "\n",
    "|-xxxxxxxx-| Эпоха 1 ($0,5 с$ паузы + $4 с$ данных)\n",
    "\n",
    "|--xxxxxxxx| Эпоха 2 ($0,5 с$ паузы + $0,5 с$ смещение окна + $4 с$ данных)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def split_epochs_binary(\n",
    "    df: pd.DataFrame, \n",
    "    epoch_duration: int, \n",
    "    init_pause: int, \n",
    "    final_pause: int,\n",
    "    step: int,\n",
    "    map_labels: dict[str, int],\n",
    "    cols: list[str]\n",
    ") -> tuple[np.ndarray, np.ndarray]:\n",
    "    \"\"\"Формирование бинарного датасета (из отрезков, когда есть действие!)\n",
    "\n",
    "    Notes:\n",
    "        Если нужно использовать только один отрезок на действие - установть step > [отсчеты в отрезке]\n",
    "\n",
    "        Например, если отрезок действия 4 с (1000 отсчётов для частоты дискретизации 250 Гц), то поставив step=1001\n",
    "        будет сформирован только 1 обучающий (валидационный) объект из данного действия.\n",
    "\n",
    "    Args:\n",
    "        df (pd.DataFrame): фрейм с данными \n",
    "        epoch_duration (int): длительность эпохи (в отсчетах сигнала, t*f)\n",
    "        init_pause (int): начальная пауза - сколько отсчетов выбросить с момента предъявления стимула (в отсчетах сигнала, t*f)\n",
    "        final_pause (int): конечная пауза - сколько отсчетов выкинуть в конце (в отсчетах сигнала, t*f)\n",
    "        step (int): шаг смещения внутри времени прдъявления стимула (в отсчетах сигнала, t*f)\n",
    "        map_labels (dict[str, int]): словарь соотносящий действие и его числовое значение, например, {'left': 0, 'right': 1}\n",
    "        cols (list[str]): имена колонок, которые будут добавлены в датасет, например: ['Channel 1', 'Channel 2', 'Channel 3']\n",
    "\n",
    "    Returns:\n",
    "        tuple[np.ndarray, np.ndarray]: кортеж с 2 массивами: \n",
    "            \n",
    "            * обучающие данные, размерность [количество образцов X длительность эпохи X количество каналов]\n",
    "\n",
    "            * метки классов (в соответсвии с map_labels), размерность [количество образцов X 1]\n",
    "\n",
    "    Raise:\n",
    "        ValueError: словарь map_labels содержит информацию не о 2х классах\n",
    "    \"\"\"\n",
    "\n",
    "    if len(map_labels) != 2:\n",
    "        raise ValueError(f\"Словарь map_labels должен содержать 2 класса, получено: {len(map_labels)}!\")\n",
    "    \n",
    "    actions = get_ranges(df['action'])\n",
    "    x = []\n",
    "    y = []\n",
    "    \n",
    "    for action in actions:\n",
    "        if action[2] in map_labels.keys():\n",
    "            __low_bound = action[0] + init_pause\n",
    "            __upper_bound = action[1] - final_pause - epoch_duration + 1\n",
    "            for inner_idx in range(__low_bound, __upper_bound, step):\n",
    "                x.append(df.loc[inner_idx: inner_idx + epoch_duration - 1, cols].values)\n",
    "                y.append(map_labels[action[2]])\n",
    "    return np.array(x), np.array(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "x, y = split_epochs_binary(\n",
    "    df=filtered_data, \n",
    "    epoch_duration=4 * 250, \n",
    "    init_pause=int(0.5 * 250), \n",
    "    final_pause=0, \n",
    "    step=int(5 * 250), \n",
    "    map_labels={\"left\": 0, \"right\": 1},\n",
    "    cols=[x for x in data.columns if \"Channel\" in x]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((40, 1000, 16), (40,))"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x.shape, y.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### CSP фильтр"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "import scipy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CSPFilter:\n",
    "    \"\"\"\n",
    "    Реализация CSP фильтра в стиле sklearn. \n",
    "    Основано на реализации github.co/Hiroaki-K4/total_perspective_vortex/main/srcs/csp.py\n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(self, n_components: int) -> None:\n",
    "        self.n_components = n_components\n",
    "        self.classes = None\n",
    "        self.filters = None\n",
    "        self.patterns = None\n",
    "        self.mean = None\n",
    "        self.std = None\n",
    "\n",
    "    def __get_covariate(self, x: np.ndarray, y: np.ndarray, cls: any) -> np.ndarray:\n",
    "        \"\"\"Получение ковариаций\n",
    "\n",
    "        Args:\n",
    "            x (np.ndarray): отсчеты сигнала\n",
    "            y (np.ndarray): метки класса\n",
    "            cls (any): текущий класс\n",
    "    \n",
    "        Returns:\n",
    "            np.ndarray: матрица ковариаций\n",
    "        \"\"\"\n",
    "        \n",
    "        x_class = x[y == cls]\n",
    "        _, _, n_channels = x_class.shape\n",
    "        # x_class = np.transpose(x_class, [1, 0, 2])\n",
    "        x_class = x_class.reshape(n_channels, -1)\n",
    "        cov = np.dot(x_class, x_class.T)\n",
    "        return cov\n",
    "    \n",
    "    def fit(self, x: np.ndarray, y: np.ndarray) -> None:\n",
    "        \"\"\"Обучение фильтра\n",
    "\n",
    "        Args:\n",
    "            x (np.ndarray): отсчеты сигнала (числа)\n",
    "            y (np.ndarray): метки класса (любые данные)\n",
    "        \"\"\"\n",
    "        self.classes = np.unique(y)\n",
    "        n_classes = len(self.classes)\n",
    "        if n_classes != 2:\n",
    "            raise ValueError(f\"Количество классов для CSP должно быть = 2 (получено {n_classes})\")\n",
    "\n",
    "        cov_neg = self.__get_covariate(x, y, self.classes[0])\n",
    "        cov_pos = self.__get_covariate(x, y, self.classes[1])\n",
    "\n",
    "        eig_vals, eig_vecs = scipy.linalg.eigh(cov_neg, cov_pos)\n",
    "        for i in range(len(eig_vecs)):\n",
    "            eig_vecs[i] = eig_vecs[i] / np.linalg.norm(eig_vecs[i])\n",
    "\n",
    "        sorted_vals = np.argsort(eig_vals)\n",
    "        idxs = np.empty_like(sorted_vals)\n",
    "        idxs[0::2] = sorted_vals[len(sorted_vals) // 2 :][::-1]\n",
    "        idxs[1::2] = sorted_vals[: len(sorted_vals) // 2]\n",
    "        \n",
    "        eig_vecs = eig_vecs[:, idxs]\n",
    "        self.filters = eig_vecs.T\n",
    "        self.patterns = np.linalg.inv(eig_vecs)\n",
    "        pick_filters = self.filters[:, self.n_components]\n",
    "\n",
    "        x = np.asarray([np.dot(pick_filters, epoch.T) for epoch in x])\n",
    "        x = (x ** 2).mean(axis=1)\n",
    "        self.mean = x.mean(axis=0)\n",
    "        self.std = x.std(axis=0)\n",
    "\n",
    "    def transform(self, x: np.ndarray) -> np.ndarray:\n",
    "        \"\"\"Применение фильтра\n",
    "\n",
    "        Args:\n",
    "            x (np.ndarray): отсчеты сигнала (числа)\n",
    "            \n",
    "        Returns:\n",
    "            np.ndarray: вторичные параметры (фичи)\n",
    "        \"\"\"\n",
    "        pick_filters = self.filters[: self.n_components]\n",
    "        x = np.asarray([np.dot(pick_filters, epoch.T) for epoch in x])\n",
    "        x = (x ** 2).mean(axis=1)\n",
    "        x -= self.mean\n",
    "        x /= self.std\n",
    "        return x\n",
    "\n",
    "    def fit_transform(self, x: np.ndarray, y: np.ndarray) -> np.ndarray:\n",
    "        \"\"\"Обучение с последующим применением ко входным данным\n",
    "\n",
    "        Args:\n",
    "            x (np.ndarray): отсчеты сигнала (числа)\n",
    "            y (np.ndarray): метки класса (любые данные)\n",
    "            \n",
    "        Returns:\n",
    "            np.ndarray: вторичные параметры (фичи)\n",
    "        \"\"\"\n",
    "        self.fit(x, y)\n",
    "        return self.transform(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(71, 1000)"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "csp_filter = CSPFilter(4)\n",
    "csp_filter.fit(x, y)\n",
    "csp_filter.transform(x).shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
