init commit: загрузка и предобработка данных

This commit is contained in:
xausssr committed 2024-05-11 12:29:10 +03:00
commit 0a55543ae2
22 files changed
+1986

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
Записи/OpenBCI GUI
Записи/OpenVibe Signals
__pycache__
.ipynb_checkpoints
.pytest_cache
runs
+50
View File
@@ -0,0 +1,50 @@
g_offset = nil
g_duration = nil
-- this function is called when the box is initialized
function initialize(box)
dofile(box:get_config("${Path_Data}") .. "/plugins/stimulation/lua-stimulator-stim-codes.lua")
g_offset = box:get_setting(2)
g_duration = box:get_setting(3)
end
-- this function is called when the box is uninitialized
function uninitialize(box)
end
function wait_until(box, time)
while box:get_current_time() < time do
box:sleep()
end
end
function wait_for(box, duration)
wait_until(box, box:get_current_time() + duration)
end
function process(box)
-- loops on every received stimulation for a given input
while box:keep_processing() do
for stimulation = 1, box:get_stimulation_count(1) do
-- gets the received stimulation
identifier, date, duration = box:get_stimulation(1, 1)
-- discards it
box:remove_stimulation(1, 1)
-- delay the OVTK_GDF_Left and Right
if identifier == OVTK_GDF_Left or identifier == OVTK_GDF_Right then
box:send_stimulation(1, OVTK_GDF_Correct, date+g_offset, 0)
box:send_stimulation(1, OVTK_GDF_Incorrect, date+g_offset+g_duration, 0)
end
end
box:sleep()
end
end
+471
View File
@@ -0,0 +1,471 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import pandas as pd\n",
"import numpy as np\n",
"import torch\n",
"\n",
"from pathlib import Path\n",
"\n",
"from matplotlib import pylab as plt\n",
"\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.metrics import balanced_accuracy_score\n",
"\n",
"import pytorch_lightning as pl\n",
"from pytorch_lightning.callbacks import ModelCheckpoint\n",
"\n",
"from core.io import filter_events, get_ranges, split_epochs_binary\n",
"from core.signal_processing import filter_signal, __create_constants, CSPFilter\n",
"from core.nn.provider import NetProvider\n",
"from core.nn.data import TableDataset\n",
"\n",
"%matplotlib inline\n",
"plt.style.use('bmh')\n",
"plt.rcParams.update({'font.size': 14})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Необходимо проверить точность классификации с помощью полносвязной сети:\n",
"\n",
"1. Подобрать архитектуру (поиск по сетке) - возможно полносвязные слои заменить на свёртку\n",
"2. Выбрать электроды\n",
"3. Исследовать влияние фильтрации\n",
"4. Исследовать влияние использования CSP-фильтра\n",
"\n",
"> По числовым значениям - **обсудить с Андреем Николаевичем**\n",
"\n",
"> **Окружение TacticENV**\n",
"\n",
"> **Сохранять все результаты - ребятам понадабятся графики/таблицы в статье, необзодимые визуализации обсудить с Андреем Николаевичем**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Примеры использования библеотеки `core`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Данные"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Загрузка"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"openvibe_config = json.load(open(\"./config/openVibe.json\", \"r\"))\n",
"raw_data = pd.read_csv(\"./Записи/OpenVibe signals/motor-imagery-csp-1-acquisition-[2024.04.18-10.37.21].csv\")\n",
"data = filter_events(raw_data, openvibe_config)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Фильтрация"
]
},
{
"cell_type": "code",
"execution_count": 3,
"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": [
"### Получение отрезков и разметки"
]
},
{
"cell_type": "code",
"execution_count": 4,
"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=50000, # берем только 1 отрезок на действие (обсудить с А.Н.), подробнее см. доку \n",
" map_labels={\"left\": 0, \"right\": 1},\n",
" cols=[x for x in data.columns if \"Channel\" in x]\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### CSP-фильтр"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(40, 1000)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"csp_filter = CSPFilter(4)\n",
"csp_filter.fit(x, y)\n",
"csp_filter.transform(x).shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Нейронки\n",
"\n",
"> Используем `pytorch-lightning`, если будешь добавлять - придерживайся стиля 🤖\n",
"\n",
"Ниже пример полного пайплайна обучения (без фильтрации, без сколбзящего окна, без CSP-фильтра) для 3 случайных электродов - `Channel 1`, `Channel 2`, `Channel 3`."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(40, 3000) (40, 2)\n"
]
}
],
"source": [
"raw_data = pd.read_csv(\"./Записи/OpenVibe signals/motor-imagery-csp-1-acquisition-[2024.04.18-10.37.21].csv\")\n",
"data = filter_events(raw_data, openvibe_config)\n",
"x, y = split_epochs_binary(\n",
" df=data, \n",
" epoch_duration=4 * 250, \n",
" init_pause=int(0.5 * 250), \n",
" final_pause=0, \n",
" step=50000, # берем только 1 отрезок на действие (обсудить с А.Н.), подробнее см. доку \n",
" map_labels={\"left\": 0, \"right\": 1},\n",
" cols=[\"Channel 1\", \"Channel 2\", \"Channel 3\"]\n",
")\n",
"\n",
"# Делаем сигналы плоскими, лейблы в OneHot\n",
"x = x.reshape((-1, 3 * 1000))\n",
"y = np.eye(2)[y]\n",
"print(x.shape, y.shape)\n",
"\n",
"# бъем train/test\n",
"X_train, X_test, y_train, y_test = train_test_split(x, y, train_size=0.8)\n",
"\n",
"# конвертим в объекты торча\n",
"train_loader = torch.utils.data.DataLoader(TableDataset(X_train, y_train), batch_size=32)\n",
"val_loader = torch.utils.data.DataLoader(TableDataset(X_test, y_test), batch_size=8)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"net = torch.nn.Sequential(\n",
" torch.nn.Linear(3000, 1024),\n",
" torch.nn.Sigmoid(),\n",
" torch.nn.Linear(1024, 2),\n",
" torch.nn.Softmax(dim=1),\n",
")\n",
"\n",
"wrapped_net = NetProvider(\n",
" net=net, \n",
" lr=1e-4, \n",
" criteria=torch.nn.BCELoss(), \n",
" metrics=[\n",
" lambda pred, labels: balanced_accuracy_score(np.argmax(labels, axis=1), np.argmax(pred, axis=1))\n",
" ],\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"GPU available: True (cuda), used: True\n",
"TPU available: False, using: 0 TPU cores\n",
"IPU available: False, using: 0 IPUs\n",
"HPU available: False, using: 0 HPUs\n",
"Missing logger folder: runs\\example\\lightning_logs\n",
"LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]\n",
"\n",
" | Name | Type | Params\n",
"----------------------------------------\n",
"0 | criteria | BCELoss | 0 \n",
"1 | net | Sequential | 3.1 M \n",
"----------------------------------------\n",
"3.1 M Trainable params\n",
"0 Non-trainable params\n",
"3.1 M Total params\n",
"12.300 Total estimated model params size (MB)\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "dc2a3deccc5b4690a005b7309e3f9322",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Sanity Checking: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"c:\\Users\\user\\anaconda3\\envs\\TacticENV\\lib\\site-packages\\pytorch_lightning\\trainer\\connectors\\data_connector.py:441: The 'val_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=11` in the `DataLoader` to improve performance.\n",
"c:\\Users\\user\\anaconda3\\envs\\TacticENV\\lib\\site-packages\\pytorch_lightning\\trainer\\connectors\\data_connector.py:441: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=11` in the `DataLoader` to improve performance.\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "97215ced575c46d99b8e3cd20ab809e9",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Training: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "2d57332f35bf49ab8ff89e3f4215180b",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "64ab7eb3058c4e518478349348fcb950",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "7c621ba6368341d68765f570aaa2b8db",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "4fc7906c46b44fc8814452719f69a6cb",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "dcdd8be5bf65421db1af4cf9303da835",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "cb3f5bc1991b4addb12cd99b9213a57b",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "734438becdbc443e87942898e64199a4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "0baf18f624a341efba714be21cf42c5f",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d39784d032f84f5cbc06b1694beb1acc",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "b1998c91254e493893112c63a0e0b680",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Validation: | | 0/? [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"`Trainer.fit` stopped: `max_epochs=10` reached.\n"
]
}
],
"source": [
"torch.set_float32_matmul_precision('medium')\n",
"trainer = pl.Trainer(\n",
" default_root_dir=\"./runs/example\",\n",
" accelerator=\"auto\",\n",
" max_epochs=10,\n",
" callbacks=[\n",
" ModelCheckpoint(save_weights_only=True, monitor=\"val/loss\", mode=\"min\"),\n",
" ],\n",
" log_every_n_steps=1,\n",
")\n",
"\n",
"trainer.fit(wrapped_net, train_loader, val_loader)\n",
"__best_path = list(Path(\"./runs/example/lightning_logs/version_0/checkpoints/\").glob(\"*\"))[0]\n",
"wrapped_net.load_state_dict(torch.load(str(__best_path))['state_dict'])\n",
"del __best_path"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+25
View File
@@ -0,0 +1,25 @@
{
"map_actions": {
"769": "OVTK_GDF_Left",
"768": "OVTK_GDF_Start_Of_Trial",
"33281": "OVTK_StimulationId_Train",
"786": "OVTK_GDF_Cross_On_Screen",
"32769": "OVTK_StimulationId_ExperimentStart",
"32775": "OVTK_StimulationId_BaselineStart",
"33553": "OVTK_StimulationId_AddedSamplesBegin",
"32776": "OVTK_StimulationId_BaselineStop",
"1010": "OVTK_GDF_End_Of_Session",
"770": "OVTK_GDF_Right",
"781": "OVTK_GDF_Feedback_Continuous",
"33282": "OVTK_StimulationId_Beep",
"800": "OVTK_GDF_End_Of_Trial",
"33552": "OVTK_StimulationId_RemovedSamples",
"32770": "OVTK_StimulationId_ExperimentStop",
"33554": "OVTK_StimulationId_AddedSamplesEnd"
},
"main_actions": {
"left": "769",
"right": "770",
"stop": "800"
}
}
View File
Whitespace-only changes.
+135
View File
@@ -0,0 +1,135 @@
import numpy as np
import pandas as pd
def filter_events(df: pd.DataFrame, config: dict[str, any]) -> pd.DataFrame:
"""Получение табличной нотации для экспериментов из файлов OpenVibe (.csv)
Args:
df (pd.DataFrame): сырой файл .ov сконвертированный в .csv и загруженный в pandas
config (dict[str, any]): словарь с расшифровкой кодов событий OpenVibe
Returns:
pd.DataFrame: фрейм (таблица) в нашей собственной нотации
"""
events = []
__temp_event = {}
new_frame = df.copy()
__ov_events = df[~df["Event Id"].isna()][["Event Id"]]
for idx in range(__ov_events.shape[0]):
__row_events = __ov_events.iloc[idx, 0].split(":")
if config["main_actions"]["left"] in __row_events or config["main_actions"]["right"] in __row_events:
__temp_event = {"start": __ov_events.index[idx]}
if config["main_actions"]["left"] in __row_events:
__temp_event["action"] = "left"
if config["main_actions"]["right"] in __row_events:
__temp_event["action"] = "right"
# В записи stop повторяется дважды (как и любые другие действия)
if config["main_actions"]["stop"] in __row_events and len(__temp_event) > 0:
__temp_event["stop"] = __ov_events.index[idx + 1]
events.append(__temp_event.copy())
__temp_event = {}
new_frame = new_frame.drop(columns=["Epoch", "Event Id", "Event Date", "Event Duration"])
new_frame["action"] = None
for event in events:
new_frame.loc[event["start"] : event["stop"], "action"] = event["action"]
new_frame["action"] = new_frame["action"].fillna("relax")
return new_frame
def get_ranges(labels: np.ndarray) -> list[list[int, int, int]]:
"""Преобразование сырых лейблов временного ряда в отрезки для визуализации
Args:
labels (np.ndarray): лейблы
Returns:
list[list[int, int, int]]: список отрезков для визуализации, каждый отрезок определен как
тройка "начало", "конец", "тип действия"
"""
actions = []
__temp_action = [0]
__prev_action = labels[0]
idx = 1
while True:
if idx + 1 > len(labels):
__temp_action.extend([idx, __prev_action])
actions.append(__temp_action)
break
if labels[idx] != __prev_action:
__temp_action.extend([idx - 1, __prev_action])
actions.append(__temp_action)
__temp_action = [idx]
__prev_action = labels[idx]
idx += 1
return actions
def split_epochs_binary(
df: pd.DataFrame,
epoch_duration: int,
init_pause: int,
final_pause: int,
step: int,
map_labels: dict[str, int],
cols: list[str],
) -> tuple[np.ndarray, np.ndarray]:
"""Формирование бинарного датасета (из отрезков, когда есть действие!)
Notes:
Если нужно использовать только один отрезок на действие - установть step > [отсчеты в отрезке]
Например, если отрезок действия 4 с (1000 отсчётов для частоты дискретизации 250 Гц), то поставив step=1001
будет сформирован только 1 обучающий (валидационный) объект из данного действия.
Args:
df (pd.DataFrame): фрейм с данными
epoch_duration (int): длительность эпохи (в отсчетах сигнала, t*f)
init_pause (int): начальная пауза - сколько отсчетов выбросить с момента предъявления стимула
(в отсчетах сигнала, t*f)
final_pause (int): конечная пауза - сколько отсчетов выкинуть в конце (в отсчетах сигнала, t*f)
step (int): шаг смещения внутри времени прдъявления стимула (в отсчетах сигнала, t*f)
map_labels (dict[str, int]): словарь соотносящий действие и его числовое значение,
например, {'left': 0, 'right': 1}
cols (list[str]): имена колонок, которые будут добавлены в датасет,
например: ['Channel 1', 'Channel 2', 'Channel 3']
Returns:
tuple[np.ndarray, np.ndarray]: кортеж с 2 массивами:
* обучающие данные, размерность [количество образцов X длительность эпохи X количество каналов]
* метки классов (в соответсвии с map_labels), размерность [количество образцов X 1]
Raise:
ValueError: словарь map_labels содержит информацию не о 2х классах
"""
if len(map_labels) != 2:
raise ValueError(f"Словарь map_labels должен содержать 2 класса, получено: {len(map_labels)}!")
actions = get_ranges(df["action"])
x = []
y = []
for action in actions:
if action[2] in map_labels.keys():
__low_bound = action[0] + init_pause
__upper_bound = action[1] - final_pause - epoch_duration + 1
for inner_idx in range(__low_bound, __upper_bound, step):
x.append(df.loc[inner_idx : inner_idx + epoch_duration - 1, cols].values)
y.append(map_labels[action[2]])
return np.array(x), np.array(y)
View File
Whitespace-only changes.
+23
View File
@@ -0,0 +1,23 @@
import numpy as np
import torch
class TableDataset(torch.utils.data.Dataset):
def __init__(self, x: np.ndarray, y: np.ndarray) -> None:
"""Простой датасет из объектов numpy
Args:
x (np.ndarray): обучающие объекты
y (np.ndarray): метки
"""
super().__init__()
self.map_labels = None
self.x = x
self.y = y
def __len__(self):
return len(self.x)
def __getitem__(self, idx) -> tuple[torch.Tensor, torch.Tensor]:
return torch.FloatTensor(self.x[idx]), torch.FloatTensor(self.y[idx])
+53
View File
@@ -0,0 +1,53 @@
import pytorch_lightning as pl
import torch
class NetProvider(pl.LightningModule):
def __init__(self, net: torch.nn.Module, lr: float, criteria: torch.nn.Module, metrics: list[callable]) -> None:
"""Базовая обертка для сетей
Args:
net (torch.nn.Module): сеть - модуль pyTorch
lr (float): темп обучения
criteria (torch.nn.Module): функция ошибки
metrics (list[callable]): список метрик - функций, принимающих (preds: np.ndarray, true_labels: np.ndarray)
"""
super().__init__()
self.lr = lr
self.criteria = criteria
self.net = net
self.metrics = metrics
def forward(self, x):
return self.net(x)
def configure_optimizers(self):
return torch.optim.AdamW(self.parameters(), lr=self.lr)
def training_step(self, batch, batch_idx):
data, labels = batch
preds = self.forward(data)
loss = self.criteria(preds, labels)
self.log("train/loss", loss, on_step=True, on_epoch=True)
for metric in self.metrics:
self.log(
f"train/{metric.__name__}",
metric(preds.cpu().detach().numpy(), labels.cpu().detach().numpy()),
on_step=True,
on_epoch=True,
)
return loss
def validation_step(self, batch, batch_idx):
data, labels = batch
preds = self.forward(data)
loss = self.criteria(preds, labels)
self.log("val/loss", loss, on_step=True, on_epoch=True)
for metric in self.metrics:
self.log(
f"val/{metric.__name__}",
metric(preds.cpu().detach().numpy(), labels.cpu().detach().numpy()),
on_step=True,
on_epoch=True,
)
return loss
+134
View File
@@ -0,0 +1,134 @@
import numpy as np
import pandas as pd
import scipy
from scipy.signal import butter, lfilter
def __create_constants(low: float, high: float, sample_rate: float, order: int) -> tuple[float, float]:
"""Вычисление констан полосового фильтра (Баттерворта)
Args:
low (float): нижняя чатсота среза
high (float): верхняя частота среза
sample_rate (float): частота дискретизации
order (int): порядок фильтра
Returns:
tuple[float, float]: константы фильтра, необходимые для вычислений
"""
return butter(order, [low, high], fs=sample_rate, btype="band")
def filter_signal(df: pd.DataFrame, cols: list[str], constants: tuple[float, float]) -> pd.DataFrame:
"""Фильтрация датасета (сигналов с датчиков)
Args:
df (pd.DataFrame): датафрейм с данными из OpenVibe
cols (list[str]): список колонок, данные в которых нужно отфильтровать
constants (tuple[float, float]): константы для вычисления фильтра (см. __create_constants)
Returns:
pd.DataFrame: входной фрейм с данными, с примененным фильтром
"""
new_df = df.copy()
for col in cols:
new_df[col] = lfilter(*constants, df[col].values)
return new_df
class CSPFilter:
"""
Реализация CSP фильтра в стиле sklearn.
Основано на реализации github.co/Hiroaki-K4/total_perspective_vortex/main/srcs/csp.py
"""
def __init__(self, n_components: int) -> None:
self.n_components = n_components
self.classes = None
self.filters = None
self.patterns = None
self.mean = None
self.std = None
def __get_covariate(self, x: np.ndarray, y: np.ndarray, cls: any) -> np.ndarray:
"""Получение ковариаций
Args:
x (np.ndarray): отсчеты сигнала
y (np.ndarray): метки класса
cls (any): текущий класс
Returns:
np.ndarray: матрица ковариаций
"""
x_class = x[y == cls]
_, _, n_channels = x_class.shape
# x_class = np.transpose(x_class, [1, 0, 2])
x_class = x_class.reshape(n_channels, -1)
cov = np.dot(x_class, x_class.T)
return cov
def fit(self, x: np.ndarray, y: np.ndarray) -> None:
"""Обучение фильтра
Args:
x (np.ndarray): отсчеты сигнала (числа)
y (np.ndarray): метки класса (любые данные)
"""
self.classes = np.unique(y)
n_classes = len(self.classes)
if n_classes != 2:
raise ValueError(f"Количество классов для CSP должно быть = 2 (получено {n_classes})")
cov_neg = self.__get_covariate(x, y, self.classes[0])
cov_pos = self.__get_covariate(x, y, self.classes[1])
eig_vals, eig_vecs = scipy.linalg.eigh(cov_neg, cov_pos)
for i in range(len(eig_vecs)):
eig_vecs[i] = eig_vecs[i] / np.linalg.norm(eig_vecs[i])
sorted_vals = np.argsort(eig_vals)
idxs = np.empty_like(sorted_vals)
idxs[0::2] = sorted_vals[len(sorted_vals) // 2 :][::-1]
idxs[1::2] = sorted_vals[: len(sorted_vals) // 2]
eig_vecs = eig_vecs[:, idxs]
self.filters = eig_vecs.T
self.patterns = np.linalg.inv(eig_vecs)
pick_filters = self.filters[:, self.n_components]
x = np.asarray([np.dot(pick_filters, epoch.T) for epoch in x])
x = (x**2).mean(axis=1)
self.mean = x.mean(axis=0)
self.std = x.std(axis=0)
def transform(self, x: np.ndarray) -> np.ndarray:
"""Применение фильтра
Args:
x (np.ndarray): отсчеты сигнала (числа)
Returns:
np.ndarray: вторичные параметры (фичи)
"""
pick_filters = self.filters[: self.n_components]
x = np.asarray([np.dot(pick_filters, epoch.T) for epoch in x])
x = (x**2).mean(axis=1)
x -= self.mean
x /= self.std
return x
def fit_transform(self, x: np.ndarray, y: np.ndarray) -> np.ndarray:
"""Обучение с последующим применением ко входным данным
Args:
x (np.ndarray): отсчеты сигнала (числа)
y (np.ndarray): метки класса (любые данные)
Returns:
np.ndarray: вторичные параметры (фичи)
"""
self.fit(x, y)
return self.transform(x)
+34
View File
@@ -0,0 +1,34 @@
import numpy as np
def get_ranges(labels: np.ndarray) -> list[list[int, int, int]]:
"""Преобразование сырых лейблов временного ряда в отрезки для визуализации
Args:
labels (np.ndarray): лейблы
Returns:
list[list[int, int, int]]: список отрезков для визуализации, каждый отрезок определен как
тройка "начало", "конец", "тип действия"
"""
actions = []
__temp_action = [0]
__prev_action = labels[0]
idx = 1
while True:
if idx + 1 > len(labels):
__temp_action.extend([idx, __prev_action])
actions.append(__temp_action)
break
if labels[idx] != __prev_action:
__temp_action.extend([idx - 1, __prev_action])
actions.append(__temp_action)
__temp_action = [idx]
__prev_action = labels[idx]
idx += 1
return actions
+911
View File
@@ -0,0 +1,911 @@
{
"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": "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 truncated
"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": "iVBORw0KGgoAAAANSUhEUgAABN4AAAHnCAYAAABnvJDkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAACa1UlEQVR4nOzdd3hUZfr/8c+kTXojhZCeUJKAIhZUEEJRUbF30RVQRNwVO6KCAq6su7afIKIICn5ZWdcVUawgSBFEUUFWhISWhJIQCJDeM+f3R66cJaRPMoTA+3Vdc0HmtPs8c84zM/c8xWIYhiEAAAAAAAAAbcqpvQMAAAAAAAAATkck3gAAAAAAAAAHIPEGAAAAAAAAOACJNwAAAAAAAMABSLwBAAAAAAAADkDiDQAAAAAAAHAAEm8AAAAAAACAA5B4AwAAAAAAAByAxBsAAAAAAADgACTeAAAAAADN9vnnn+uvf/2rqqqq6ix7/fXXtXDhwnaICgBOTSTeAHRIq1evlsViqffh4eGhyMhIXX311Zo3b55KS0vbO1wAAIDTxs8//6znnntO48ePV3p6uioqKnTw4EH99a9/1aOPPqqVK1e2d4gAcMqwGIZhtHcQANBSq1ev1uDBg5u1bo8ePfTZZ5+pR48eDo4KAADg9Ldjxw6dffbZKisrq7PMYrHou+++06BBg05+YABwCqLFG4AO74EHHtDvv/9uPn766SfNmTNHiYmJkqTU1FRdeeWVKikpaedIAQAAOr7u3btrxYoVuvDCC2s9n5SUpE8//ZSkGwAcx6W9AwCA1goJCVGvXr1qPde3b1/96U9/0qBBg7Rx40alpaXp3Xff1YMPPthOUQIAAJw+LrnkEv34448qKSlRZmamQkJC5OPj095hAcAphxZvAE5bHh4emj59uvn3119/3Y7RAAAAnH48PDwUHx9P0g0AGkDiDcBp7aKLLjL/n5GRUWf5jz/+qMmTJ2vQoEHq3Lmz3Nzc5Ovrq6SkJD3wwAPatm1bs4+1YsUKjRw5Ut26dZOPj498fHyUmJioG2+8Uf/85z+Vn59fa/3GJoho7FGfmmVTp06VJH333Xe64YYbFB4eLnd3d0VHR+u+++7T7t27m3UuWVlZmjx5ss4//3wFBgbKarUqPDxcN954o5YuXdrsMomJiWnWOS1YsKDJfa1atUojR45UfHy8PD095ePjo6SkJD300EPas2dPs+IZNWpUs+JpqovMrl279Nhjj+nss8+Wv7+/WcYjRozQ2rVrG9wuPT292ee8YMECc9309PQ6y2vKdtSoUQ3uY/ny5bXOa/Xq1Y0e89NPP9Wtt96qqKgoubu7y8/PT3369NEzzzyj7OzsRrdtSkNl7+zsrMDAQPXr10//+Mc/VFhYWO/2x98vq1evlmEYevfddzVgwAAFBQXJ09NTPXv21HPPPaeCgoJmxbRp0yaNGzdOCQkJ8vHxkaenp7p27aoxY8Zoy5YtDW534r173333NXqcrKwsubq6Nvv6as39d2Jd0JxzqO+6GDRoUJOxbtu2rdZ5Nec+bkpz79HGrvuW7quxc8zOztbkyZPVt29fBQQEyNnZ2e54GosxJiamwXWmTJliHufPf/5zneU2m03fffednnjiCfXv319BQUFydXWVv7+/zjnnHD3xxBPau3dvg/tPS0tT586dZbFYdPbZZysvL6/e9Zqqc7Zs2SJfX19ZLBYlJyfXO/ZXjePrt8YejZVLjR9//FGjRo1SXFycPD095evrq169eumxxx5r9LyPr4+belx//fX17qOiokLvvPOOLr30UoWGhsrNzU0hISEaOnSo3n77bVVUVDQZf3NNnTq1Td6/JMfXMe+8806tmOp7D6tRUVGh+fPn67rrrlNkZKTc3d0VFBSkc845R2PGjNGyZctUWVkpqWWv2YmP4+NtbD81ScS77rpL69atazDutq4jt2zZovvvv189e/aUj4+PnJyc7P68BOAUZABAB7Rq1SpDkiHJmDJlSoPrlZaWmuslJCTUWjZ//nxzWUMPZ2dn480332w0lmPHjhlXXXVVk/s6Mc7jz6Elj/ocf4znn3/esFgs9W7r4eFhfPbZZ42ez4IFCwxPT89GY7juuuuMwsLCRvdjGIYRHR3drHOaP39+g/soLi42brnllka3d3NzM959990m4xk5cmSz4klOTm5wHy+++KLh6ura6Pbjxo0zKisr62yblpbWrHM2jNrXZ1paWp3lNWU7cuTIerevqKgwkpKSasW1atWqetc9fPiwkZyc3Og5+fr6Gl999VWjMTemuWUfHx9v7N+/v872x98vy5YtM6688soG9xEdHW3s2rWrwVgqKyuN8ePHN3ifSDIsFovx/PPP17v9ifeul5eXkZub2+Dxnn/++WZfX629/xqq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 truncated
"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": "iVBORw0KGgoAAAANSUhEUgAABQMAAAIICAYAAADaJ/gXAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd3QU1dsH8O/2zab3kJAECF1A/EkVpCnSRBAVpAhEQaXYkKKANGkK0lQERYqAr9IUEOliCb1XgQDpjfS2Lbs77x/rDrvJ9uxmUp7POTmE7MydZ+7O3Jm5cwuPYRgGhBBCCCGEEEIIIYSQWo/PdQCEEEIIIYQQQgghhJCqQZWBhBBCCCGEEEIIIYTUEVQZSAghhBBCCCGEEEJIHUGVgYQQQgghhBBCCCGE1BFUGUgIIYQQQgghhBBCSB1BlYGEEEIIIYQQQgghhNQRVBlICCGEEEIIIYQQQkgdQZWBhBBCCCGEEEIIIYTUEVQZSAghhBBCCCGEEEJIHUGVgYQQQgghhBBCCCGE1BFUGUgIIaTa+PPPP8Hj8Sz+eHp6IiYmBsOGDcP+/fu5DpcQQggh1YhKpcKZM2fw5Zdf4rXXXkOzZs3A5/PZ+whCCCF6PIZhGK6DIIQQQgB9ZWDPnj3tXr5fv37YtWsXZDKZG6MihBBCSE0QGxuLzZs3W/ycHn0JIUSPWgYSQgipliZMmIDr16+zP9euXcM///yDlStXokGDBgCAgwcP4u233+Y2UEIIIYRUC8aVfd7e3ujevTvCwsI4jIgQQqonIdcBEEIIIeaEhISgVatWFf7etWtXDBs2DG3atEFOTg62bduGpUuXIjw8nIMoCSGEEFJd9OvXDz169ED79u3RokUL8Pl89OjRA5mZmVyHRggh1Qq1DCSEEFLj1KtXD2PGjAGgbwVw4cIFjiMihBBCCNeGDRuGsWPH4rHHHgOfT4+6hBBiCZWQhBBCaqSGDRuyv6tUqgqfG09G8ueff1pMZ8SIEexyhu7HxlJTU7FgwQIMHjwYzZs3R2BgIEQiEfz9/dGxY0fMmzcPOTk5FdYbMmQIeDwe/P39oVQqbe7PY489Bh6PhzZt2lT47MyZM5g9ezZ69OiBsLAwiMVi+Pj4oGXLlpgwYQJu3bplM32DBg0aWJ2kxfAzb968CuuOHTvWYj4ZZGdnw8/Pz2o6R44cwXvvvYdu3bqhQYMG8PLygkQiQf369TFw4ED89NNP0Ol0FrdR2fywZz8MDPk1duxYp9OyJ08MHjx4gA8++ABt27aFr68vBAKB3d+PPXQ6Hf744w9MnToVXbp0QVBQEEQiEfz8/NC2bVtMnToVycnJVtPo0aMHeDweevToYfZzhmEQGxvLxvr555+zn82bN8+u48/cT2Jiotl0LFGpVGjUqBG7nLnvcPPmzWa3xefz4e3tjdatW+P999832XZ59n4n9pZJeXl5mDdvHtq1awd/f39IpVJERkbilVdewe+//251GwYMw2D37t0YNmwYGjRoAJlMBj8/P7Rq1QojRozAnj17TMolZ7+T8nlqT158++23Fr9XR1iKSSKRICoqCkOGDMFvv/3mVNrlOZqf5d27dw9TpkxBmzZt4OfnB6lUiujoaIwYMQJ///23XTHk5eVh8eLF6NatG0JDQyEWixEREYFOnTrh448/xqVLl1wav/G5Ye076tKlC7ucuTLBVddiQgghLsYQQggh1cSJEycYAAwAZu7cuVaXnTp1KrvsmTNnrKZ14sQJs2mcPHmSXQYAEx0dXWGZ/fv3myxj7icgIIA5deqUyXq///47+/n27dut7svp06fZZVetWmXy2aZNm2xuXyAQMF9//bXVbRhER0fbTM9S/o8ZM8ZiPhmMHz/eZjovvfSSze0/88wzjFwur7CuK/LDnv0on19jxoxxOi178oRhGObgwYOMVCp1+vuxx9y5c22mLZPJmD179lhMo3v37gwApnv37mY/f/fdd9m0Zs2a5fD2Lf0kJCSYTceSRYsWmaxv7ju053gy5Mnx48fNbsfe78SeMumPP/5g/P39rcby0ksvMQqFwuJ2UlJSmA4dOtjcp02bNlXYB0d/yueprbwoKChggoODLX6vjrA3xrfeesup9A2cyU9jS5YsYUQikdV13377bUaj0ViMYefOnYyPj4/NGFwZv/G5Yek72r59u0ka5soEV12LK8NQZlkrLwghpK6hMQMJIYTUOFlZWfjhhx8AAJGRkfjf//7ncBoMw+C9996zuZyvry+GDBmCDh06oGXLlggMDIRYLEZaWhp2796Nbdu2IS8vD6+++ioePHgAgUAAAOjTpw+ioqKQnJyMjRs3YsSIERa3sXHjRgCAWCzGqFGjTD7TaDTw9/fHCy+8gO7du6NJkybw9PREeno6Ll26hDVr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 truncated
"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
}
Binary file not shown.
+70
View File
@@ -0,0 +1,70 @@
=================================================
Îïèñàíèå ôîðìàòà csv ôàéëà
http://openvibe.inria.fr/csv-file-format-description/
Êîäû ñîáûòèé:
http://openvibe.inria.fr/stimulation-codes/
Ôîðìàò âûâîäà:
Time:250Hz,
Epoch,
Channel 1,
Channel 2,
Channel 3,
Channel 4,
Channel 5,
Channel 6,
Channel 7,
Channel 8,
Channel 9,
Channel 10,
Channel 11,
Channel 12,
Channel 13,
Channel 14,
Channel 15,
Channel 16,
Event Id,
Event Date,
Ïðèìåðû ôðàãìåíòîâ çàïèñè îòíîñÿùèåñÿ ê ñîáûòèÿì ïîêàçà ñòðåëîê:
Ëåâî
32.9839999469,257,175321.6875000000,161476.5781250000,157384.8906250000,165382.3125000000,182353.1093750000,163875.0781250000,160708.5000000000,162043.2968750000,150507.6562500000,177546.5781250000,156140.7187500000,132061.4531250000,152687.8437500000,153717.4062500000,163318.2500000000,154924.4218750000,
770:770,
32.9874541578:32.9876184834,
0.0000000000:0.0000000000
32.9879999468,257,175311.5468750000,161454.5156250000,157379.3906250000,165363.1093750000,182351.3593750000,163863.5000000000,160706.2656250000,162040.2187500000,150504.3281250000,177536.3281250000,156103.6406250000,132053.7187500000,152655.0156250000,153707.7500000000,163314.8906250000,154920.7187500000,,,
32.9919999468,257,175305.4843750000,161444.4375000000,157375.6718750000,165352.2968750000,182349.0781250000,163856.1250000000,160703.7343750000,162037.6875000000,150501.0468750000,177530.1250000000,156083.9531250000,132048.3281250000,152644.7812500000,153701.3906250000,163310.8281250000,154917.0468750000,,,
32.9959999467,257,175315.4062500000,161463.2812500000,157381.4062500000,165367.3437500000,182351.8906250000,163865.5000000000,160707.1562500000,162041.9375000000,150504.0156250000,177539.1562500000,156111.0312500000,132055.3593750000,152673.0468750000,153708.9375000000,163314.3750000000,154920.6718750000,
770,
32.9962524336,
0.0000000000
Ïðàâî
60.9639999014,476,175538.3437500000,162268.3906250000,157672.1093750000,165778.6562500000,182674.5156250000,165550.2656250000,161696.2656250000,162263.4062500000,151121.2812500000,178085.1250000000,156655.3437500000,132842.8750000000,153574.0000000000,154055.8593750000,163721.6718750000,154574.9375000000,
769:769,
60.9675073745:60.9675452565,
0.0000000000:0.0000000000
60.9679999014,476,175526.0312500000,162246.7031250000,157664.8125000000,165758.0937500000,182672.2187500000,165538.8906250000,161693.2500000000,162259.3750000000,151118.2187500000,178074.6406250000,156616.5000000000,132834.6093750000,153542.0781250000,154045.5781250000,163716.9375000000,154570.5625000000,
769,
60.9707477945,
0.0000000000
Binary file not shown.
Binary file not shown.
Binary file not shown.
+70
View File
@@ -0,0 +1,70 @@
=================================================
Îïèñàíèå ôîðìàòà csv ôàéëà
http://openvibe.inria.fr/csv-file-format-description/
Êîäû ñîáûòèé:
http://openvibe.inria.fr/stimulation-codes/
Ôîðìàò âûâîäà:
Time:250Hz,
Epoch,
Channel 1,
Channel 2,
Channel 3,
Channel 4,
Channel 5,
Channel 6,
Channel 7,
Channel 8,
Channel 9,
Channel 10,
Channel 11,
Channel 12,
Channel 13,
Channel 14,
Channel 15,
Channel 16,
Event Id,
Event Date,
Ïðèìåðû ôðàãìåíòîâ çàïèñè îòíîñÿùèåñÿ ê ñîáûòèÿì ïîêàçà ñòðåëîê:
Ëåâî
32.9839999469,257,175321.6875000000,161476.5781250000,157384.8906250000,165382.3125000000,182353.1093750000,163875.0781250000,160708.5000000000,162043.2968750000,150507.6562500000,177546.5781250000,156140.7187500000,132061.4531250000,152687.8437500000,153717.4062500000,163318.2500000000,154924.4218750000,
770:770,
32.9874541578:32.9876184834,
0.0000000000:0.0000000000
32.9879999468,257,175311.5468750000,161454.5156250000,157379.3906250000,165363.1093750000,182351.3593750000,163863.5000000000,160706.2656250000,162040.2187500000,150504.3281250000,177536.3281250000,156103.6406250000,132053.7187500000,152655.0156250000,153707.7500000000,163314.8906250000,154920.7187500000,,,
32.9919999468,257,175305.4843750000,161444.4375000000,157375.6718750000,165352.2968750000,182349.0781250000,163856.1250000000,160703.7343750000,162037.6875000000,150501.0468750000,177530.1250000000,156083.9531250000,132048.3281250000,152644.7812500000,153701.3906250000,163310.8281250000,154917.0468750000,,,
32.9959999467,257,175315.4062500000,161463.2812500000,157381.4062500000,165367.3437500000,182351.8906250000,163865.5000000000,160707.1562500000,162041.9375000000,150504.0156250000,177539.1562500000,156111.0312500000,132055.3593750000,152673.0468750000,153708.9375000000,163314.3750000000,154920.6718750000,
770,
32.9962524336,
0.0000000000
Ïðàâî
60.9639999014,476,175538.3437500000,162268.3906250000,157672.1093750000,165778.6562500000,182674.5156250000,165550.2656250000,161696.2656250000,162263.4062500000,151121.2812500000,178085.1250000000,156655.3437500000,132842.8750000000,153574.0000000000,154055.8593750000,163721.6718750000,154574.9375000000,
769:769,
60.9675073745:60.9675452565,
0.0000000000:0.0000000000
60.9679999014,476,175526.0312500000,162246.7031250000,157664.8125000000,165758.0937500000,182672.2187500000,165538.8906250000,161693.2500000000,162259.3750000000,151118.2187500000,178074.6406250000,156616.5000000000,132834.6093750000,153542.0781250000,154045.5781250000,163716.9375000000,154570.5625000000,
769,
60.9707477945,
0.0000000000
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
1. В данных существуюет небольшие выбросы по времени предъявления стимула (лево/право) см. pdf. В программе есть вариации по длительности предъявления или это ошибка чтения? >90% стимулов - 5 с. однако, 1-3 стимула (в среднем) длиннее - до 7 с. (см. pdf).
2. Какие электроды берем для эксперемента с 3мя? Номера в нотации 'Channel #'
3. Если эпохи по 4 с после предъявления сигнала (с учётом паузы в 0,5 с), то через какой шаг смещаемся внутри времени предъявления стимула? Через каждый отсчет - не будет онлайна (не успеем вычислить фильтры и сеть со скоростью 1/250с). Пока сделал через 0,5 с (принцип скользящего окна - когда праый край касается границы предъявления стимула прекращаем скольжение, см. pdf).
4. Реализация CSP может отличаться.