136 lines
6.0 KiB
Python
136 lines
6.0 KiB
Python
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)
|