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)