├── bionic_apps ├── __init__.py ├── games │ ├── __init__.py │ └── braindriver │ │ ├── __init__.py │ │ ├── commands.py │ │ ├── networkConfig.json │ │ ├── game_paradigm.py │ │ ├── control.py │ │ ├── opponents.py │ │ ├── track.py │ │ ├── main_game_start.py │ │ └── logger.py ├── legacy │ ├── __init__.py │ ├── fbcsp.py │ ├── fbcsp_toolbox.py │ └── fbcsp_test.py ├── tests │ ├── __init__.py │ ├── utils.py │ ├── test_artefact.py │ ├── test_io.py │ └── test_bci_system.py ├── databases │ ├── eeg │ │ ├── __init__.py │ │ ├── defaults.py │ │ ├── offline.py │ │ └── online_braindriver.py │ ├── emg │ │ ├── __init__.py │ │ ├── prepare_putemg.py │ │ └── putemg_download.py │ ├── coreg_mindrove │ │ ├── __init__.py │ │ └── prepare.py │ └── __init__.py ├── artifact_filtering │ ├── __init__.py │ └── blinking_detection.py ├── external_connections │ ├── __init__.py │ ├── hpc │ │ ├── __init__.py │ │ └── example_params.py │ ├── emotiv │ │ ├── __init__.py │ │ ├── epoch.py │ │ ├── epoc_plus_lsl.py │ │ └── mne_import_xdf.py │ ├── lsl │ │ ├── __init__.py │ │ ├── ReceiveData.py │ │ ├── BCI.py │ │ └── DataSender.py │ └── brainvision │ │ ├── __init__.py │ │ ├── remote_control.py │ │ └── BrainVision_RDA.py ├── feature_extraction │ ├── time │ │ ├── __init__.py │ │ └── utils.py │ ├── time_frequency │ │ └── __init__.py │ ├── frequency │ │ ├── __init__.py │ │ └── fft_methods.py │ └── __init__.py ├── preprocess │ ├── __init__.py │ ├── data_augmentation.py │ └── channel_selection.py ├── ai │ ├── __init__.py │ ├── sklearn_classifiers.py │ ├── classifier.py │ ├── svm.py │ └── kolcs_neural_networks.py ├── handlers │ ├── __init__.py │ ├── tf.py │ ├── gui.py │ ├── pandas_res.py │ └── hdf5.py ├── validations.py ├── model_selection.py └── utils.py ├── requirements.txt ├── .gitignore ├── examples ├── mulit_svm.py └── custom_feature_classifier.py └── README.md /bionic_apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/games/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/legacy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/databases/eeg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/artifact_filtering/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/games/braindriver/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/hpc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/feature_extraction/time/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/emotiv/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/lsl/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bionic_apps/feature_extraction/time_frequency/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bionic_apps/preprocess/__init__.py: -------------------------------------------------------------------------------- 1 | from .dataset_generation import generate_db 2 | -------------------------------------------------------------------------------- /bionic_apps/ai/__init__.py: -------------------------------------------------------------------------------- 1 | from .classifier import ClassifierType, init_classifier 2 | -------------------------------------------------------------------------------- /bionic_apps/feature_extraction/frequency/__init__.py: -------------------------------------------------------------------------------- 1 | from .fft_methods import FFTCalc 2 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/brainvision/__init__.py: -------------------------------------------------------------------------------- 1 | from .remote_control import RemoteControlClient, APPLICATION_SATE, RECORDER_STATE, ACQUISITION_SATE 2 | -------------------------------------------------------------------------------- /bionic_apps/games/braindriver/commands.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ControlCommand(Enum): 5 | LEFT = 1 6 | RIGHT = 3 7 | HEADLIGHT = 2 8 | STRAIGHT = 0 9 | -------------------------------------------------------------------------------- /bionic_apps/handlers/__init__.py: -------------------------------------------------------------------------------- 1 | from .gui import show_message, select_files_in_explorer, select_folder_in_explorer, select_base_dir 2 | from .hdf5 import HDF5Dataset 3 | from .pandas_res import ResultHandler 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ###### Requirements without Version Specifiers ###### 2 | tensorflow<2.11 3 | pandas 4 | pylsl 5 | psutil 6 | 7 | ###### Requirements with Version Specifiers ###### 8 | mne[hdf5] >= 1.0 9 | scikit-learn >= 1.0 10 | -------------------------------------------------------------------------------- /bionic_apps/databases/emg/__init__.py: -------------------------------------------------------------------------------- 1 | class PutEMG: 2 | 3 | def __init__(self, config_ver=-1): 4 | self.DIR = "putemg" 5 | self.CONFIG_VER = 1.1 if config_ver == -1. else config_ver 6 | 7 | self.FILE_PATH = 'subject{subj}_raw.fif' 8 | self.SUBJECT_NUM = 87 9 | self.DROP_SUBJECTS = [43, 59, 63, 80] 10 | -------------------------------------------------------------------------------- /bionic_apps/databases/coreg_mindrove/__init__.py: -------------------------------------------------------------------------------- 1 | class MindRoveCoreg: 2 | 3 | def __init__(self, config_ver=-1): 4 | self.DIR = "MindRove-coreg" 5 | self.CONFIG_VER = 1.1 if config_ver == -1. else config_ver 6 | 7 | self.FILE_PATH = 'subject{subj}_raw.fif' 8 | self.SUBJECT_NUM = 6 9 | self.DROP_SUBJECTS = [] 10 | -------------------------------------------------------------------------------- /bionic_apps/games/braindriver/networkConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment": "net add: 10.220.255.211", 3 | "isServer": false, 4 | "networkAddress": "localhost", 5 | "networkPort": 7777, 6 | "controlAddressP1": "any", 7 | "controlAddressP2": "any", 8 | "controlAddressP3": "any", 9 | "controlAddressP4": "any", 10 | "activPlayerIndex": 1 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Please specify folders and files, which are not important to upload... 2 | 3 | # folders 4 | __pycache__/ 5 | tf_log/ 6 | .idea/ 7 | tmp/ 8 | log/ 9 | sandbox/ 10 | 11 | # code files 12 | *.log 13 | *.cfg 14 | *.[oa] 15 | 16 | # other files 17 | .directory 18 | *~ 19 | ~* 20 | *.doc 21 | *.docx 22 | *.dot 23 | *.dotx 24 | *.txt 25 | *.csv 26 | *.xls 27 | *.xlsx 28 | *trackData.json 29 | 30 | # all with 31 | *sandbox* -------------------------------------------------------------------------------- /bionic_apps/databases/eeg/defaults.py: -------------------------------------------------------------------------------- 1 | EYE_OPEN = 'eye open' 2 | EYE_CLOSED = 'eye closed' 3 | LEFT_HAND = 'left hand' 4 | RIGHT_HAND = 'right hand' 5 | BOTH_HANDS = 'both hands' 6 | LEFT_LEG = 'left leg' 7 | RIGHT_LEG = 'right leg' 8 | BOTH_LEGS = 'both legs' 9 | REST = 'rest' 10 | ACTIVE = 'active' 11 | CALM = 'calm' 12 | TONGUE = 'tongue' 13 | 14 | SUBJECT = 'subject' 15 | 16 | # Record types: 17 | IMAGINED_MOVEMENT = "imagined" 18 | REAL_MOVEMENT = "real" 19 | BASELINE = 'baseline' 20 | -------------------------------------------------------------------------------- /examples/mulit_svm.py: -------------------------------------------------------------------------------- 1 | from bionic_apps.ai import ClassifierType 2 | from bionic_apps.databases import Databases 3 | from bionic_apps.feature_extraction import eeg_bands 4 | from bionic_apps.offline_analyses import test_db_within_subject 5 | 6 | band = eeg_bands['range40'].copy() 7 | feature_type = band.pop('feature_type') 8 | filter_params = dict( # required for FASTER artefact filter 9 | order=5, l_freq=1, h_freq=45 10 | ) 11 | test_db_within_subject(Databases.PHYSIONET, feature_type, feature_kwargs=band, 12 | classifier_type=ClassifierType.VOTING_SVM, 13 | do_artefact_rejection=True, fast_load=True, 14 | filter_params=filter_params, 15 | log_file='out.csv') 16 | -------------------------------------------------------------------------------- /bionic_apps/tests/utils.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from shutil import rmtree 3 | 4 | from ..databases import Databases, get_eeg_db_name_by_filename 5 | from ..utils import init_base_config 6 | 7 | EXCLUDE_DB_LIST = [Databases.ParadigmC, Databases.EMOTIV_PAR_C] 8 | 9 | 10 | def get_available_databases(): 11 | base_dir = Path(init_base_config()) 12 | avail_dbs = set() 13 | for file in base_dir.rglob('*'): 14 | try: 15 | avail_dbs.add(get_eeg_db_name_by_filename(file.as_posix())) 16 | except ValueError: 17 | pass 18 | avail_dbs = [db_name for db_name in avail_dbs if db_name not in EXCLUDE_DB_LIST] 19 | return avail_dbs 20 | 21 | 22 | AVAILABLE_DBS = get_available_databases() 23 | 24 | 25 | def cleanup_fastload_data(path='tmp/'): 26 | path = Path(init_base_config()).joinpath(path) 27 | if path.exists(): 28 | print('Removing old files. It may take for a while...') 29 | rmtree(str(path)) 30 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/lsl/ReceiveData.py: -------------------------------------------------------------------------------- 1 | """Example program to show how to read a multi-channel time series from LSL.""" 2 | 3 | from pylsl import StreamInlet, resolve_stream 4 | 5 | 6 | def load_electrodes(info): 7 | electrodes = [] 8 | ch = info.desc().child("channels").child("channel") 9 | for _ in range(info.channel_count()): 10 | electrodes.append(ch.child_value("label")) 11 | ch = ch.next_sibling() 12 | return electrodes 13 | 14 | 15 | # first resolve an EEG stream on the lab network 16 | print("looking for an EEG stream...") 17 | streams = resolve_stream('type', 'EEG') 18 | 19 | # create a new inlet to read from the stream 20 | inlet = StreamInlet(streams[0]) 21 | 22 | # get information... 23 | info = inlet.info() 24 | print(info.as_xml()) 25 | print(info.nominal_srate()) 26 | print(load_electrodes(info)) 27 | # exit(0) 28 | timestamps = list() 29 | data = list() 30 | 31 | while True: 32 | sample, timestamp = inlet.pull_sample() 33 | # print(timestamp, sample) 34 | timestamps.append(timestamp) 35 | data.append(sample) 36 | # print(np.transpose(data).shape) 37 | print(sample) 38 | -------------------------------------------------------------------------------- /bionic_apps/validations.py: -------------------------------------------------------------------------------- 1 | from .ai.classifier import ClassifierType 2 | from .feature_extraction import FeatureType 3 | 4 | 5 | def validate_feature_classifier_pair(feature_type, classifier_type): 6 | if classifier_type in [ClassifierType.EEG_NET, ClassifierType.DEEP_CONV_NET, ClassifierType.SHALLOW_CONV_NET, 7 | ClassifierType.EEG_NET_FUSION, ClassifierType.MI_EEGNET]: 8 | feature_type = FeatureType.RAW 9 | elif classifier_type is ClassifierType.VOTING_SVM and \ 10 | feature_type in [FeatureType.AVG_FFT_POWER, FeatureType.FFT_RANGE, FeatureType.MULTI_AVG_FFT_POW]: 11 | pass 12 | elif classifier_type is ClassifierType.USER_DEFINED and \ 13 | feature_type in [FeatureType.RAW, FeatureType.USER_PIPELINE]: 14 | pass 15 | elif classifier_type in [ClassifierType.ENSEMBLE, ClassifierType.VOTING] and \ 16 | feature_type is FeatureType.HUGINES: 17 | pass 18 | else: 19 | raise ValueError(f'Feature {feature_type.name} and classifier {classifier_type.name} ' 20 | f'can not be used together.') 21 | 22 | return feature_type, classifier_type 23 | -------------------------------------------------------------------------------- /examples/custom_feature_classifier.py: -------------------------------------------------------------------------------- 1 | from sklearn.ensemble import ExtraTreesClassifier 2 | 3 | from bionic_apps.ai import ClassifierType 4 | from bionic_apps.databases import Databases 5 | from bionic_apps.feature_extraction import FeatureType, get_hugines_transfromer 6 | from bionic_apps.offline_analyses import test_db_within_subject 7 | 8 | feature_type = FeatureType.USER_PIPELINE 9 | classifier_type = ClassifierType.USER_DEFINED 10 | 11 | db_name = Databases.PHYSIONET 12 | fs = 160 13 | chs = 64 14 | 15 | filter_params = dict( 16 | order=5, l_freq=1, h_freq=45 17 | ) 18 | 19 | feature_kwargs = dict( 20 | pipeline=get_hugines_transfromer() 21 | ) 22 | 23 | classifier_kwargs = dict( 24 | classifier=ExtraTreesClassifier(n_estimators=250, n_jobs=-2) 25 | ) 26 | 27 | test_db_within_subject(db_name, feature_type, 28 | filter_params=filter_params, 29 | feature_kwargs=feature_kwargs, 30 | classifier_type=classifier_type, 31 | classifier_kwargs=classifier_kwargs, 32 | do_artefact_rejection=True, fast_load=True, 33 | log_file='out.csv') 34 | -------------------------------------------------------------------------------- /bionic_apps/games/braindriver/game_paradigm.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Pipe 2 | 3 | from bionic_apps.databases import Databases 4 | from bionic_apps.databases.eeg.standardize_database import convert_game_paradigm 5 | from bionic_apps.games.braindriver.logger import GameLogger 6 | from bionic_apps.games.braindriver.opponents import MasterPlayer 7 | from bionic_apps.handlers import show_message 8 | from bionic_apps.preprocess.io import DataLoader 9 | 10 | 11 | def run_braindriver_paradigm(db_name=Databases.PILOT_PAR_B): 12 | loader = DataLoader() 13 | loader.use_db(db_name) 14 | 15 | # rcc = RemoteControlClient(print_received_messages=False) 16 | # rcc.open_recorder() 17 | # rcc.check_impedance() 18 | 19 | print('Connecting to game...') 20 | parent_conn, child_conn = Pipe() 21 | game_log = GameLogger(data_loader=loader, connection=child_conn, annotator='bv_rcc') 22 | game_log.start() 23 | MasterPlayer(player_num=1, game_log_conn=parent_conn, reaction_time=.5).start() 24 | 25 | show_message( 26 | 'Press OK only, if you finished with the paradigm!!!' 27 | ) 28 | # del rcc 29 | 30 | convert_game_paradigm() 31 | 32 | 33 | if __name__ == '__main__': 34 | run_braindriver_paradigm() 35 | -------------------------------------------------------------------------------- /bionic_apps/preprocess/data_augmentation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def do_augmentation(ep_data, labels, n_iter=9, include_origin=True): 5 | aug_epochs, aug_labels, aug_ep_ind, orig_mask = [], [], [], [] 6 | 7 | if include_origin: 8 | aug_epochs.extend(ep_data) 9 | aug_labels.extend(labels) 10 | aug_ep_ind.extend(range(len(labels))) 11 | orig_mask.extend([True] * len(labels)) 12 | 13 | for i, ep in enumerate(ep_data): 14 | ep = ep.copy() 15 | ampl = ep.std() 16 | # 1) Setting the mean value of each channel to zero 17 | ep -= ep.mean(axis=-1, keepdims=True) 18 | 19 | for _ in range(n_iter): 20 | # 2) Amplify by a random number 21 | aug = ep * np.random.uniform(.2, 5) 22 | 23 | # 3) Polarity inversion 24 | if np.random.randint(2): 25 | aug *= -1 26 | 27 | # 4) Rotation among time dimension. HINT: not the original!!! 28 | if np.random.randint(2): 29 | aug = np.flip(aug, axis=-1) 30 | 31 | # 5) Add random noise 32 | noise = np.random.normal(0, ampl, size=aug.shape) 33 | aug += noise 34 | 35 | aug_epochs.append(aug) 36 | aug_labels.append(labels[i]) 37 | aug_ep_ind.append(i) 38 | orig_mask.append(False) 39 | 40 | return aug_epochs, aug_labels, aug_ep_ind, orig_mask 41 | -------------------------------------------------------------------------------- /bionic_apps/feature_extraction/time/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | TIME_AXIS = -1 4 | 5 | 6 | def MAV(series): 7 | return np.sum(np.abs(series), axis=TIME_AXIS) / series.shape[TIME_AXIS] 8 | 9 | 10 | # Hudgins set: RMS, WL, ZC, SSC 11 | def wave_len(series): 12 | return np.sum(np.diff(series, axis=TIME_AXIS), axis=TIME_AXIS) 13 | 14 | 15 | def zero_crossings(series): 16 | return np.sum(np.abs(np.diff(np.sign(series), axis=TIME_AXIS)), axis=TIME_AXIS) 17 | 18 | 19 | def slope_sign_change(series): 20 | return np.sum(np.abs(np.diff(np.sign(np.diff(series, axis=TIME_AXIS)), axis=TIME_AXIS)), axis=TIME_AXIS) 21 | 22 | 23 | def RMS(series): 24 | return np.sqrt(np.mean(np.power(series, 2), axis=TIME_AXIS)) 25 | 26 | # def AR6(series): 27 | # coeffs = [] 28 | # for chn in range(len(series)): 29 | # channel = series[chn, :] 30 | # model = AutoReg(channel, lags=6) 31 | # model_fit = model.fit() 32 | # coeffs.append(model_fit.params) 33 | # return coeffs 34 | # 35 | # 36 | # def Katz_fractal(series): 37 | # n = series.shape[TIME_AXIS] - 1 38 | # logn = np.log(n) 39 | # sqdiffs = np.power(np.diff(series), 2) 40 | # L = np.sum(np.sqrt(4E-6 + sqdiffs), axis=TIME_AXIS) # (1/500)^2 + sqdiff 41 | # d = np.max( 42 | # np.power((np.arange(1, series.shape[TIME_AXIS]) * 0.002), 2) + np.power(series[..., 0] - series[..., 1:], 2)) 43 | # dims = logn / (logn + np.log(d / L)) 44 | # return dims 45 | -------------------------------------------------------------------------------- /bionic_apps/handlers/tf.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | from .hdf5 import HDF5Dataset 4 | 5 | 6 | # @kolcs: move to TFRecord? - https://www.tensorflow.org/guide/data#consuming_tfrecord_data 7 | def get_tf_dataset(db, y, indexes): 8 | """Generating tensorflow dataset from hdf5 database 9 | 10 | Returns a dataset from the tensorflow dataset API. 11 | 12 | Parameters 13 | ---------- 14 | db : HDF5Dataset 15 | HDF5 database from where data can be loaded. 16 | y : ndarray 17 | Labels converted to integer numbers corresponding to the whole dataset. 18 | indexes : ndarray or list 19 | Iterable index order, which specifies the loadin order of the data 20 | 21 | Returns 22 | ------- 23 | tf.data.Dataset 24 | Tensorflow Dataset 25 | """ 26 | assert len(y) > max(indexes), \ 27 | 'Some indexes are higher than the length of the y variable. ' \ 28 | 'This error may occur when the indexes are correspond to the whole database ' \ 29 | 'and the y are filtered to a subject.' 30 | 31 | def generate_data(): 32 | for i in indexes: 33 | x = db.get_data(i) 34 | yield x, y[i] 35 | 36 | data_shape = db.get_data(indexes[0]).shape 37 | label_shape = y[indexes[0]].shape 38 | 39 | dataset = tf.data.Dataset.from_generator( 40 | generate_data, 41 | output_signature=( 42 | tf.TensorSpec(shape=data_shape, dtype=tf.float32), 43 | tf.TensorSpec(shape=label_shape, dtype=tf.int32) 44 | ) 45 | ) 46 | return dataset 47 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/hpc/example_params.py: -------------------------------------------------------------------------------- 1 | from bionic_apps.ai import ClassifierType 2 | from bionic_apps.databases import Databases 3 | from bionic_apps.feature_extraction import FeatureType 4 | from bionic_apps.offline_analyses import test_db_within_subject 5 | from bionic_apps.preprocess.io import SubjectHandle 6 | 7 | LOG_DIR = 'bci_tests/eeg_net' 8 | TEST_NAME = 'window_inv' 9 | 10 | # HPC params: 11 | partition = 'gpu' # ['cpu', 'cpu_lowpriority', 'gpu', 'gpu_long', 'gpu_lowpriority'] 12 | gpu_type = 3 # [1, 2, 3] 13 | cpu_cores = 6 14 | 15 | test_func = test_db_within_subject 16 | 17 | default_kwargs = dict( 18 | db_name=Databases.PHYSIONET, 19 | feature_type=FeatureType.RAW, 20 | epoch_tmin=0, epoch_tmax=4, 21 | window_len=2, window_step=.1, 22 | feature_kwargs=None, 23 | use_drop_subject_list=True, 24 | filter_params=dict(order=5, l_freq=1, h_freq=45), 25 | do_artefact_rejection=True, 26 | balance_data=True, 27 | subject_handle=SubjectHandle.INDEPENDENT_DAYS, 28 | n_splits=5, 29 | classifier_type=ClassifierType.EEG_NET, 30 | classifier_kwargs=dict( 31 | validation_split=.2, 32 | epochs=500, 33 | patience=15 34 | ), 35 | # db_file='tmp/database.hdf5', log_file='out.csv', base_dir='.', 36 | save_res=True, 37 | fast_load=True, subjects='all', 38 | augment_data=False, 39 | db_generation='sequential' 40 | ) 41 | 42 | # generating test params here... 43 | test_kwargs = [] 44 | 45 | _windows = [(win_len, win_step) 46 | for win_len in np.arange(.5, 4.1, .5) 47 | for win_step in [0, .01, .05] if win_len + win_step <= 4 and win_step <= win_len] 48 | 49 | for win_len, win_step in _windows: 50 | pars = dict( 51 | window_len=win_len, 52 | window_step=win_step, 53 | ) 54 | test_kwargs.append(pars) 55 | -------------------------------------------------------------------------------- /bionic_apps/tests/test_artefact.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from time import time 3 | 4 | from mne import EpochsArray, Epochs 5 | from numpy import ndarray 6 | 7 | from bionic_apps.artifact_filtering.faster import ArtefactFilter 8 | from bionic_apps.preprocess.io import DataLoader, get_epochs_from_files 9 | 10 | TTK_DB = DataLoader().use_ttk_db() 11 | 12 | 13 | class TestFaster(unittest.TestCase): 14 | 15 | @classmethod 16 | def setUpClass(cls): 17 | cls._subject = 24 18 | cls.faster = ArtefactFilter(apply_frequency_filter=False) 19 | 20 | def _check_method(self, epochs): 21 | n_epochs = len(epochs) 22 | split_ind = n_epochs - 1 23 | offline_epochs = epochs[:split_ind].copy() 24 | online_epochs = epochs[split_ind:].copy() 25 | 26 | filt_epochs = self.faster.offline_filter(offline_epochs) 27 | self.assertIn(type(filt_epochs), [Epochs, EpochsArray]) 28 | print('\n*******************************************************' 29 | '\nOnline FASTER' 30 | '\n*******************************************************') 31 | tic = time() 32 | filt_epochs = self.faster.online_filter(online_epochs.get_data()) 33 | toc = time() 34 | self.assertIsInstance(filt_epochs, ndarray) 35 | self.assertLess(toc - tic, .15, 'Online FASTER is not fast enough...') 36 | print(f'Total time spent in lsl: {toc - tic}') 37 | 38 | @unittest.skipUnless(TTK_DB.get_data_path().exists(), 39 | 'Data for TTK does not exists. Can not test it.') 40 | def test_ttk(self): 41 | file = TTK_DB.get_filenames_for_subject(self._subject) 42 | epochs = get_epochs_from_files(file, TTK_DB.get_task_dict(), epoch_tmin=0, epoch_tmax=4, 43 | event_id=TTK_DB.get_event_id(), preload=True, prefilter_signal=True, 44 | order=5, l_freq=1, h_freq=45) 45 | self._check_method(epochs) 46 | 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /bionic_apps/handlers/gui.py: -------------------------------------------------------------------------------- 1 | from tkinter import Tk, filedialog, messagebox 2 | 3 | 4 | def show_message(message, title='BCI'): 5 | root = Tk() 6 | root.withdraw() 7 | messagebox.showinfo(title=title, message=message) 8 | root.destroy() 9 | 10 | 11 | def select_files_in_explorer(init_dir='./', message='Select an EEG file!', file_type="EEG files", 12 | ext=".vhdr;*.edf;*.gdf;*.fif;*.xdf", no_file_error=True): 13 | root = Tk() 14 | root.withdraw() 15 | messagebox.showinfo(title='BCI', message=message) 16 | extension = ext if ext[0] == '.' else '.' + ext 17 | filenames = filedialog.askopenfilenames(title='Select file', 18 | initialdir=init_dir, 19 | filetypes=((file_type, "*{}".format(extension)), ("all files", "*.*"))) 20 | root.destroy() 21 | if no_file_error: 22 | assert len(filenames) > 0, 'No file were selected...' 23 | return filenames 24 | 25 | 26 | def select_folder_in_explorer(message, dialog_title, title='BCI', no_dir_error=True): 27 | root = Tk() 28 | root.withdraw() 29 | messagebox.showinfo(title=title, message=message) 30 | base_dir = filedialog.askdirectory(title=dialog_title) 31 | 32 | root.destroy() 33 | if no_dir_error: 34 | assert len(base_dir) > 0, 'Base directory is not selected. Cannot run program!' 35 | return base_dir 36 | 37 | 38 | def select_base_dir(): 39 | base_dir = select_folder_in_explorer(message='Select base directory, which contains all the database folders:\n' 40 | '- BCI_comp\n' 41 | '- Cybathlon_pilot\n' 42 | '- physionet.org\n' 43 | '- TTK', 44 | dialog_title='Select main database directory') 45 | return base_dir 46 | 47 | 48 | if __name__ == '__main__': 49 | path = select_base_dir() 50 | print(select_files_in_explorer(path)) 51 | -------------------------------------------------------------------------------- /bionic_apps/databases/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pathlib import Path 3 | 4 | from .coreg_mindrove import MindRoveCoreg 5 | from .eeg.offline import * 6 | from .eeg.online_braindriver import * 7 | from .emg import PutEMG 8 | 9 | 10 | # db selection options 11 | class Databases(Enum): 12 | PHYSIONET = 'physionet' 13 | PILOT_PAR_A = 'pilot_par_a' 14 | PILOT_PAR_B = 'pilot_par_b' 15 | TTK = 'ttk' 16 | GAME = 'game' 17 | GAME_PAR_C = 'game_par_c' 18 | GAME_PAR_D = 'game_par_d' 19 | BCI_COMP_IV_1 = 'BCICompIV1' 20 | BCI_COMP_IV_2A = 'BCICompIV2a' 21 | BCI_COMP_IV_2B = 'BCICompIV2b' 22 | ParadigmC = 'par_c' 23 | EMOTIV_PAR_C = 'emotiv_par_c' 24 | GIGA = 'giga' 25 | 26 | MINDROVE_COREG = 'mindrove' 27 | PUTEMG = 'putemg' 28 | 29 | 30 | def get_eeg_db_name_by_filename(filename): 31 | filename = Path(filename).as_posix() 32 | if Game_ParadigmC().DIR in filename: 33 | db_name = Databases.GAME_PAR_C 34 | elif Game_ParadigmD().DIR in filename: 35 | db_name = Databases.GAME_PAR_D 36 | elif PilotDB_ParadigmA().DIR in filename: 37 | db_name = Databases.PILOT_PAR_A 38 | elif PilotDB_ParadigmB().DIR in filename: 39 | db_name = Databases.PILOT_PAR_B 40 | elif Physionet().DIR in filename: 41 | db_name = Databases.PHYSIONET 42 | elif ParadigmC().DIR in filename: 43 | db_name = Databases.ParadigmC 44 | elif BciCompIV1().DIR in filename: 45 | db_name = Databases.BCI_COMP_IV_1 46 | elif BciCompIV2a().DIR in filename: 47 | db_name = Databases.BCI_COMP_IV_2A 48 | elif BciCompIV2b().DIR in filename: 49 | db_name = Databases.BCI_COMP_IV_2B 50 | elif TTK_DB().DIR in filename: 51 | db_name = Databases.TTK 52 | elif Giga().DIR in filename: 53 | db_name = Databases.GIGA 54 | elif MindRoveCoreg().DIR in filename: 55 | db_name = Databases.MINDROVE_COREG 56 | elif PutEMG().DIR in filename: 57 | db_name = Databases.PUTEMG 58 | else: 59 | raise ValueError('No database defined with path {}'.format(filename)) 60 | return db_name 61 | -------------------------------------------------------------------------------- /bionic_apps/handlers/pandas_res.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pandas as pd 4 | 5 | 6 | class ResultHandler: 7 | def __init__(self, fix_params, changing_params, to_beginning=(), filename=None): 8 | self._fix = fix_params.copy() 9 | if 'classifier' in self._fix and not isinstance(self._fix['classifier'], str): 10 | self._fix['classifier'] = type(self._fix['classifier']).__name__ 11 | self._changing = changing_params 12 | self.res = pd.DataFrame(columns=list(fix_params) + changing_params) 13 | self.filename = filename 14 | 15 | # reorder columns: 16 | cols = list(self.res.columns) 17 | for c in to_beginning: 18 | cols.remove(c) 19 | self.res = self.res[list(to_beginning) + cols] 20 | 21 | def add(self, res_dict): 22 | assert len(self._changing) == len(res_dict), f'Expected {len(self._changing)} results. ' \ 23 | f'Got {len(res_dict)} instead.' 24 | assert all(k in self._changing for k in res_dict), 'Name of parameters are not match with ' \ 25 | 'changing_params.' 26 | res_len = len(pd.DataFrame(res_dict)) 27 | if res_len == 1: 28 | res_dict.update(self._fix) 29 | else: 30 | fix = {key: [val] * res_len for key, val in self._fix.items()} 31 | res_dict.update(fix) 32 | res_dict = pd.DataFrame(res_dict) 33 | self.res = self.res.append(res_dict, ignore_index=True) 34 | 35 | def save(self, filename=None, sep=';', encoding='utf-8', index=False): 36 | assert filename is not None or self.filename is not None, 'filename must be defined!' 37 | if isinstance(filename, (str, Path)): 38 | self.filename = filename 39 | Path(self.filename).parent.mkdir(parents=True, exist_ok=True) 40 | self.res.to_csv(self.filename, sep=sep, encoding=encoding, index=index) 41 | 42 | def __len__(self): 43 | return len(self.res) 44 | 45 | def print_db_res(self, col='Avg. Acc'): 46 | print(f'\nDatabase accuracy: {self.res[col].mean():.3f} ' 47 | f'+/- {self.res[col].std():.3f} for {len(self.res)} subjects.') 48 | -------------------------------------------------------------------------------- /bionic_apps/ai/sklearn_classifiers.py: -------------------------------------------------------------------------------- 1 | from sklearn.base import ClassifierMixin 2 | from sklearn.discriminant_analysis import LinearDiscriminantAnalysis 3 | from sklearn.ensemble import StackingClassifier, ExtraTreesClassifier, RandomForestClassifier, VotingClassifier 4 | from sklearn.naive_bayes import GaussianNB 5 | from sklearn.neighbors import KNeighborsClassifier 6 | from sklearn.pipeline import make_pipeline 7 | from sklearn.preprocessing import StandardScaler, FunctionTransformer 8 | from sklearn.svm import SVC, NuSVC 9 | 10 | from .interface import ClassifierInterface 11 | 12 | 13 | def _select_fft(x, i): 14 | return x[:, i, :] 15 | 16 | 17 | class VotingSVM(ClassifierInterface, ClassifierMixin): 18 | 19 | def __init__(self, norm=StandardScaler, voting='soft'): 20 | self.norm = norm 21 | self.voting = voting 22 | self._model = None 23 | 24 | def fit(self, x, y=None, **kwargs): 25 | n_svms = x.shape[1] 26 | inner_clfs = [(f'unit{i}', make_pipeline(FunctionTransformer(_select_fft, kw_args={'i': i}), 27 | self.norm(), SVC(probability=True))) 28 | for i in range(n_svms)] 29 | self._model = VotingClassifier(inner_clfs, voting=self.voting, n_jobs=len(inner_clfs)) \ 30 | if len(inner_clfs) > 1 else inner_clfs[0][1] 31 | self._model.fit(x, y) 32 | 33 | def predict(self, x): 34 | return self._model.predict(x) 35 | 36 | 37 | def get_ensemble_clf(mode='ensemble'): 38 | level0 = [ 39 | ('SVM', SVC(C=15, gamma=.01, cache_size=512, probability=True)), 40 | ('nuSVM', NuSVC(nu=.32, gamma=.015, cache_size=512, probability=True)), 41 | ('Extra Tree', ExtraTreesClassifier(n_estimators=500, criterion='gini')), 42 | ('Random Forest', RandomForestClassifier(n_estimators=500, criterion='gini')), 43 | ('Naive Bayes', GaussianNB()), 44 | ('KNN', KNeighborsClassifier()) 45 | ] 46 | 47 | if mode == 'ensemble': 48 | level1 = LinearDiscriminantAnalysis() 49 | final_clf = StackingClassifier(level0, level1, n_jobs=len(level0)) 50 | elif mode == 'voting': 51 | final_clf = VotingClassifier(level0, voting='soft', n_jobs=len(level0)) 52 | else: 53 | raise ValueError(f'Mode {mode} is not an ensemble mode.') 54 | 55 | clf = make_pipeline( 56 | # PCA(n_components=.97), 57 | StandardScaler(), 58 | final_clf 59 | ) 60 | return clf 61 | -------------------------------------------------------------------------------- /bionic_apps/model_selection.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn.model_selection import KFold, StratifiedGroupKFold, PredefinedSplit 3 | from .utils import mask_to_ind 4 | 5 | 6 | class BalancedKFold: 7 | 8 | def __init__(self, n_splits=5, shuffle=False, random_state=None): 9 | self.n_splits = n_splits 10 | self.shuffle = shuffle 11 | self.random_state = random_state 12 | 13 | def _set_n_split(self, y, groups): 14 | if self.n_splits == 'auto': 15 | if groups is not None: 16 | self.n_splits = min([len(np.unique(groups[label == y])) for label in np.unique(y)]) 17 | else: 18 | self.n_splits = 5 19 | 20 | def split(self, x=None, y=None, groups=None): 21 | assert y is not None, 'y must be defined' 22 | self._set_n_split(y, groups) 23 | 24 | y_uniqe = np.unique(y) 25 | label_test_folds = {label: [] for label in y_uniqe} 26 | for label in y_uniqe: 27 | ind = mask_to_ind(y == label) 28 | lb_group = groups[ind] if groups is not None else None 29 | kfold = KFold if groups is None else StratifiedGroupKFold 30 | kfold = kfold(self.n_splits, shuffle=self.shuffle, random_state=self.random_state) 31 | for _, test in kfold.split(ind, y[ind], groups=lb_group): 32 | label_test_folds[label].append(ind[test]) 33 | 34 | test_fold = np.zeros_like(y) 35 | for i in range(self.n_splits): 36 | if self.shuffle: 37 | np.random.shuffle(y_uniqe) 38 | for label in y_uniqe: 39 | test_fold[label_test_folds[label][i]] = i 40 | 41 | ps = PredefinedSplit(test_fold) 42 | for train, test in ps.split(): 43 | assert not any(item in train for item in test), 'Splitting error.' 44 | yield train, test 45 | 46 | 47 | class LeavePSubjectGroupsOutSequentially: 48 | 49 | def __init__(self, n_subjects=10, add_rest=True): 50 | self._n_subj = n_subjects 51 | self._add_rest = add_rest 52 | 53 | def split(self, x=None, y=None, groups=None): 54 | assert groups is not None, 'groups must be defined!' 55 | subjs = np.unique(groups) 56 | from_ = np.arange(0, len(subjs), self._n_subj) 57 | to_ = np.arange(self._n_subj, len(subjs), self._n_subj) 58 | test_subjs = [np.array(subjs[f:t]) for f, t in zip(from_, to_)] 59 | if self._add_rest: 60 | test_subjs.append(subjs[to_[-1]:]) 61 | 62 | inds = np.arange(groups.size) 63 | for leave_out in test_subjs: 64 | test_mask = np.in1d(groups, leave_out) 65 | yield inds[~test_mask], inds[test_mask] 66 | -------------------------------------------------------------------------------- /bionic_apps/databases/emg/prepare_putemg.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pandas as pd 4 | import numpy as np 5 | 6 | import putemg_download 7 | from bionic_apps.databases.eeg.standardize_database import _create_raw 8 | 9 | FS = 5120 # Hz 10 | LABEL_CONVERTER = { 11 | -1: ['Rest'], 12 | 0: ['Idle'], 13 | 1: ['Fist'], 14 | 2: ['Flexion'], 15 | 3: ['Extension'], 16 | 6: ['pinch thumb-index'], 17 | 7: ['pinch thumb-middle'], 18 | 8: ['pinch thumb-ring'], 19 | 9: ['pinch thumb-small'] 20 | } 21 | 22 | BASE_PATH = 'Data-HDF5' 23 | FILE_PATTERN = '*repeats_long*.hdf5' 24 | LABEL_NUM = 5 25 | 26 | 27 | def get_annotated_raw(file, limit, plot=False): 28 | df = pd.read_hdf(file, sep=',', encoding='utf8') 29 | emg_cols = [col for col in df if 'EMG' in col] 30 | 31 | data = df[emg_cols].to_numpy() * 5 / 2 ** 12 * 1000 / 200 * 1e-3 32 | task_numbers = df['TRAJ_GT'].to_numpy() 33 | 34 | tr = np.ediff1d(task_numbers) 35 | tr_start = np.insert(tr, 0, 1) 36 | tr_end = np.append(tr, 1) 37 | tr_start = np.arange(len(tr_start))[tr_start > 0] 38 | tr_end = np.arange(len(tr_end))[tr_end > 0] + 1 39 | assert len(tr_start) == len(tr_end) 40 | 41 | onset = tr_start / FS 42 | duration = tr_end / FS - onset 43 | ep_labels = np.array([LABEL_CONVERTER[int(lab)] for lab in task_numbers[tr_start]]).ravel() 44 | 45 | label_limit = [np.sum(label == ep_labels) == limit for label in np.unique(ep_labels) if 46 | label not in ['Rest', 'Idle']] 47 | if not all(label_limit): 48 | print(f'\nSome of the labels in {file} does not reach the minimum label limit.\n') 49 | 50 | raw = _create_raw(data.T, 51 | ch_names=emg_cols, 52 | ch_types=['emg'] * len(emg_cols), 53 | fs=FS, onset=onset, duration=duration, 54 | description=ep_labels 55 | ) 56 | 57 | if plot: 58 | raw.plot(block=True) 59 | 60 | return raw 61 | 62 | 63 | def main(): 64 | base_dir = Path(BASE_PATH) 65 | 66 | if not base_dir.exists(): 67 | from unittest.mock import patch 68 | import sys 69 | testargs = [f"{__file__}", 'emg_gestures', str(BASE_PATH).lower()] 70 | with patch.object(sys, 'argv', testargs): 71 | putemg_download.main() 72 | 73 | files = sorted(base_dir.glob(FILE_PATTERN)) 74 | for j, file in enumerate(files): 75 | print(f'Progress: {j * 100. / len(files):.2f} %') 76 | raw = get_annotated_raw(file, LABEL_NUM, plot=False) 77 | file = str(base_dir.joinpath(f'subject{j:03d}_raw.fif')) 78 | raw.save(file, overwrite=True) 79 | 80 | 81 | if __name__ == '__main__': 82 | main() 83 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/emotiv/epoch.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mne.io import read_raw 3 | from pandas import DataFrame 4 | from pyedflib import EdfWriter, FILETYPE_EDFPLUS 5 | from scipy.io import loadmat 6 | 7 | WANTED_CHANNELS = ['AF3', 'F7', 'F3', 'FC5', 'T7', 'P7', 'O1', 'O2', 'P8', 'T8', 'FC6', 'F4', 'F8', 8 | 'AF4'] # , 'GYROX', 'GYROY', 'MARKER'] 9 | TRIGGER_LIST = 'trigger_list' 10 | FILE_APPEND = '_trigger' 11 | 12 | 13 | def add_trigger_to_edf_file(edf_file, trigger_file): 14 | """This function adds triggers to an edf file from a matlab cell array. 15 | 16 | The edf file should contain markers > 0, which are indicates time points where the 17 | triggers should be added. 18 | 19 | Parameters 20 | ---------- 21 | edf_file: str 22 | EDF file recorded from EmotivXavierTestBench 23 | trigger_file: str 24 | MATLAB .mat file, which contains all the trigger names. The parameter name in 25 | the .mat file should be equivalent with the TRIGGER_LIST variable. 26 | 27 | Returns 28 | ------- 29 | 30 | """ 31 | raw = read_raw(edf_file) 32 | fs = raw.info['sfreq'] 33 | channel_info = [ 34 | {'label': ch, 'dimension': 'V', 'sample_rate': fs, 'physical_max': 0.01, 'physical_min': 0.0, 35 | 'digital_max': 2 ** 14, 'digital_min': 0, 'transducer': '', 'prefilter': ''} for ch in 36 | raw.info['ch_names'] if ch in WANTED_CHANNELS] 37 | 38 | eeg = raw.get_data() 39 | df = DataFrame(eeg.T, columns=raw.info['ch_names']) 40 | # df = raw.to_data_frame() 41 | df2 = df[df.MARKER > 0].MARKER # triggers 42 | trigger_inds = np.array(df2.index) 43 | 44 | raw.pick_channels(WANTED_CHANNELS) 45 | eeg = raw.get_data() 46 | n_ch = raw.info['nchan'] 47 | raw.close() 48 | del raw 49 | 50 | # trg_file = select_eeg_file_in_explorer(message="Select trigger file", file_type='MATLAB', ext='.mat') 51 | mat = loadmat(trigger_file) 52 | triggers = mat[TRIGGER_LIST] 53 | triggers = [triggers[0, r][0] for r in range(len(triggers[0]))] 54 | 55 | ext = '.' + edf_file.split('.')[-1] 56 | ext_ind = edf_file.find(ext) 57 | out_file = edf_file[:ext_ind] + FILE_APPEND + edf_file[ext_ind:] 58 | 59 | edf = EdfWriter(out_file, n_ch, file_type=FILETYPE_EDFPLUS) 60 | edf.setSignalHeaders(channel_info) 61 | edf.writeSamples(eeg) 62 | for i, ind in enumerate(trigger_inds): 63 | edf.writeAnnotation(ind / fs, -1, triggers[i]) 64 | 65 | edf.close() 66 | del edf 67 | 68 | 69 | if __name__ == '__main__': 70 | from gui_handler import select_files_in_explorer 71 | 72 | edf_file = select_files_in_explorer(message='Select an EDF file!', file_type='Emotiv Epoc EDF', ext='.edf')[0] 73 | trigger_file = select_files_in_explorer(message='Select a trigger .mat file!', file_type='MATLAB', ext='.mat')[0] 74 | add_trigger_to_edf_file(edf_file, trigger_file) 75 | -------------------------------------------------------------------------------- /bionic_apps/preprocess/channel_selection.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | import numpy as np 4 | from scipy import stats 5 | 6 | 7 | class ChannelSelMode(Enum): 8 | COVARIANCE = auto() 9 | 10 | 11 | def covariance_channel_selection(epochs, Ns=10, Ntr=10): 12 | """Correlation-based channel selection. 13 | 14 | Gives a list of the best correlated channels selected from a given epoch. 15 | Based on: https://doi.org/10.1016/j.neunet.2019.07.008 16 | 17 | Parameters 18 | ---------- 19 | epochs : mne.Epochs 20 | Mne epochs, which the correlation will be calculated on. 21 | Ns : int 22 | Number of selected channels 23 | Ntr : int 24 | Number of trials 25 | 26 | Returns 27 | ------- 28 | list 29 | List of the names of the selected channels 30 | 31 | """ 32 | data = epochs.get_data() 33 | all_channel_names = np.asarray(epochs.ch_names) 34 | channel_count = np.zeros_like(epochs.ch_names, dtype=np.int) 35 | 36 | for epoch in data: 37 | z_score = stats.zscore(epoch, axis=-1) 38 | 39 | # channels -> N best correlate channel 40 | R = np.corrcoef(z_score) 41 | R_mean = R.mean(axis=0) 42 | 43 | # sort_index: original position in array (ascending order) 44 | sort_index = np.argsort(-R_mean) 45 | 46 | # Selecting the first Ns channel from an epoch 47 | channel_count[sort_index[:Ns]] += 1 48 | 49 | sort_index = np.argsort(-channel_count) 50 | selected_channels = all_channel_names[sort_index[:Ntr]] 51 | 52 | return list(selected_channels) 53 | 54 | 55 | class ChannelSelector: 56 | 57 | def __init__(self, channel_num=10, mode=ChannelSelMode.COVARIANCE): 58 | """Helper class for selecting different channel selection methods. 59 | 60 | This class stores the offline selected channel names for lsl use. 61 | 62 | Parameters 63 | ---------- 64 | channel_num : int 65 | Number of the best EEG channels in the output. 66 | mode : ChannelSelMode 67 | Enum for selecting the mode of the EEG channel selection. 68 | """ 69 | self._channel_num = channel_num 70 | self._selected_channels = list() 71 | self._mode = mode 72 | 73 | def offline_select(self, epochs, exclude_channels=()): 74 | epochs = epochs.copy().pick('eeg', exclude=exclude_channels) 75 | if self._mode == ChannelSelMode.COVARIANCE: 76 | self._selected_channels = covariance_channel_selection(epochs, self._channel_num, self._channel_num) 77 | else: 78 | raise NotImplementedError(f'{self._mode.name} channel selection mode is not implemented.') 79 | return self._selected_channels 80 | 81 | def online_select(self): 82 | assert len(self._selected_channels) > 0, 'Offline channel selection required.' 83 | return self._selected_channels 84 | 85 | @property 86 | def mode(self): 87 | return self._mode.name 88 | 89 | 90 | if __name__ == '__main__': 91 | from .io import get_epochs_from_raw_with_gui 92 | 93 | ep = get_epochs_from_raw_with_gui() 94 | ch_sel = ChannelSelector(channel_num=10, mode=ChannelSelMode.COVARIANCE) 95 | print(f'\nSelected channels: {ch_sel.offline_select(ep)}') 96 | -------------------------------------------------------------------------------- /bionic_apps/artifact_filtering/blinking_detection.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import butter, lfilter, find_peaks, sosfilt 3 | 4 | from ..databases import GameDB 5 | from ..handlers.gui import select_files_in_explorer 6 | from ..preprocess.io import get_epochs_from_files 7 | 8 | 9 | def butter_bandpass_filter(data, lowcut, highcut, fs, order=5, fmode='ba'): 10 | if fmode == 'ba': 11 | b, a = butter(order, (lowcut, highcut), btype='bandpass', fs=fs) 12 | y = lfilter(b, a, data) 13 | elif fmode == 'sos': 14 | sos = butter(order, (lowcut, highcut), btype='bandpass', output=fmode, fs=fs) 15 | y = sosfilt(sos, data) 16 | else: 17 | raise AttributeError('Filter mode {} is not defined'.format(fmode)) 18 | return y 19 | 20 | 21 | def _test_blinking_detection(filename, blink_list=None): 22 | epoch_length = 4 23 | baseline = tuple([None, 0.1]) 24 | task_dict = GameDB.TRIGGER_TASK_CONVERTER # {'Rest': 1, 'left fist/both fists': 2, 'right fist/both feet': 3} 25 | epochs, fs = get_epochs_from_files(filename, 26 | task_dict=task_dict, 27 | epoch_tmin=0, epoch_tmax=epoch_length, baseline=baseline, get_fs=True, 28 | prefilter_signal=True) 29 | epochs.load_data() 30 | ch_list = ['Fp1', 'Fp2', 'Af7', 'Af8', 'Afz'] 31 | epochs.pick_channels(ch_list) 32 | # epochs.plot(block=True) # check blinks visually here 33 | 34 | detected = list() 35 | for i, ep in enumerate(epochs): 36 | if is_there_blinking(ep, fs, threshold=4, ch_list=ch_list): 37 | print('Epoch {} contains blinking'.format(i + 1)) 38 | detected.append(i + 1) 39 | 40 | if blink_list is not None: 41 | print('\nSummary:') 42 | missed_blinks = [b for b in blink_list if b not in detected] 43 | wrongly_detected = [b for b in detected if b not in blink_list] 44 | print("Missed blinks: {}".format(missed_blinks)) 45 | print("Detected but not blink: {}".format(wrongly_detected)) 46 | epochs.plot(block=True) # check the error... 47 | 48 | 49 | def is_there_blinking(eeg, fs, threshold=4, ch_list=None): 50 | filt_data = butter_bandpass_filter(eeg, .5, 30, fs, order=5, fmode='ba') 51 | is_there_blink = False 52 | for ch_num, ch_data in enumerate(filt_data): 53 | ind, peaks = find_peaks(ch_data, height=0) 54 | avg_peak = np.mean(peaks['peak_heights']) 55 | ind_, peaks_ = find_peaks(ch_data, height=avg_peak * threshold) 56 | if len(ind_) != 0: 57 | if ch_list is not None: 58 | print("\tChannel {} contains blinking.".format(ch_list[ch_num])) 59 | is_there_blink = True 60 | 61 | return is_there_blink 62 | 63 | 64 | if __name__ == '__main__': 65 | # on: Game/mixed/subject1 66 | blink_list = [2, 4, 7, 9, 10, 11, 15, 16, 17, 19, 21, 22, 23, 25, 26, 27, 28, 29, 30, 33, 35, 37, 39, 40, 43, 44, 67 | 47, 48, 49, 50, 51, 52, 54, 55, 57, 58, 61, 62, 63, 64, 68, 70, 72, 74, 75, 77, 79, 81, 82, 84, 86, 68 | 87, 69 | 88, 89, 90, 92, 94, 95, 97, 99, 100, 101, 103, 105, 106, 107, 109, 111, 113, 115, 117, 119, 120, 121, 70 | 122, 123, 125, 127, 128, 129, 131, 133, 134, 135, 137, 138, 139, 140, 141] 71 | filename = select_files_in_explorer()[0] 72 | _test_blinking_detection(filename, blink_list) 73 | -------------------------------------------------------------------------------- /bionic_apps/feature_extraction/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from mne.decoding import Scaler 4 | from sklearn.base import TransformerMixin 5 | from sklearn.pipeline import FeatureUnion, make_pipeline, Pipeline 6 | from sklearn.preprocessing import FunctionTransformer, StandardScaler 7 | 8 | from .frequency.fft_methods import get_multi_fft_transformer, get_fft_ranges 9 | from .time.utils import * 10 | 11 | 12 | class FeatureType(Enum): 13 | RAW = 'raw' 14 | USER_PIPELINE = 'user' 15 | 16 | # time domain: 17 | HUGINES = 'hugines' 18 | 19 | # feq domain: 20 | AVG_FFT_POWER = 'avg_fft_pow' 21 | FFT_RANGE = 'fft_range' 22 | MULTI_AVG_FFT_POW = 'multi_avg_fft_pow' 23 | 24 | 25 | eeg_bands = { 26 | 'theta': dict( 27 | feature_type=FeatureType.AVG_FFT_POWER, 28 | fft_low=4, fft_high=7 29 | ), 30 | 'alpha': dict( 31 | feature_type=FeatureType.AVG_FFT_POWER, 32 | fft_low=7, fft_high=14 33 | ), 34 | 'beta': dict( 35 | feature_type=FeatureType.AVG_FFT_POWER, 36 | fft_low=14, fft_high=30 37 | ), 38 | 'gamma': dict( 39 | feature_type=FeatureType.AVG_FFT_POWER, 40 | fft_low=30, fft_high=40 41 | ), 42 | 'range30': dict( 43 | feature_type=FeatureType.FFT_RANGE, 44 | fft_low=4, fft_high=30 45 | ), 46 | 'range40': dict( 47 | feature_type=FeatureType.FFT_RANGE, 48 | fft_low=2, fft_high=40 49 | ), 50 | } 51 | 52 | 53 | def to_micro_volt(data): 54 | return data * 1e6 55 | 56 | 57 | def get_hugines_transfromer(): 58 | features = [wave_len, zero_crossings, slope_sign_change, RMS] 59 | return FeatureUnion([(fun.__name__, FunctionTransformer(fun)) for fun in features]) 60 | 61 | 62 | def get_feature_extractor(feature_type, fs=None, scale=True, norm=False, info=None, **kwargs): 63 | pipeline_steps = [] 64 | if scale: 65 | pipeline_steps.append(FunctionTransformer(to_micro_volt)) 66 | 67 | if feature_type is FeatureType.RAW: 68 | pipeline_steps.append(FunctionTransformer()) 69 | norm = info is not None and norm 70 | elif feature_type is FeatureType.HUGINES: 71 | pipeline_steps.append(get_hugines_transfromer()) 72 | elif feature_type in [FeatureType.AVG_FFT_POWER, FeatureType.FFT_RANGE, FeatureType.MULTI_AVG_FFT_POW]: 73 | assert isinstance(fs, (int, float)), 'Sampling frequency must be defined!' 74 | pipeline_steps.append(get_multi_fft_transformer(feature_type, fs, **kwargs)) 75 | else: 76 | raise NotImplementedError(f'{feature_type.name} feature extraction is not implemented.') 77 | 78 | if norm: 79 | if feature_type is FeatureType.RAW: 80 | scaler = Scaler(info, scalings='mean') 81 | else: 82 | scaler = StandardScaler() 83 | pipeline_steps.append(scaler) 84 | 85 | return make_pipeline(*pipeline_steps) 86 | 87 | 88 | def generate_features(x, fs=None, f_type=FeatureType.RAW, scale=True, norm=False, 89 | info=None, pipeline=None, **kwargs): 90 | if f_type is FeatureType.USER_PIPELINE: 91 | assert isinstance(pipeline, (Pipeline, FeatureUnion, TransformerMixin)), \ 92 | f'In case of user defined feature extractor only sklearn transformers accepted.' 93 | feature_ext = pipeline 94 | else: 95 | feature_ext = get_feature_extractor(f_type, fs, scale, norm, info=info, **kwargs) 96 | x = feature_ext.fit_transform(x) 97 | return x 98 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/lsl/BCI.py: -------------------------------------------------------------------------------- 1 | """ 2 | Online BCI 3 | 4 | @license: PPKE ITK, TTK 5 | @author: Köllőd Csaba, kollod.csaba@ikt.ppke.hu 6 | """ 7 | 8 | import numpy as np 9 | from pylsl import StreamInlet, resolve_stream 10 | from scipy import signal 11 | 12 | 13 | class SignalReceiver: 14 | 15 | def __init__(self): 16 | self._inlet = None 17 | self.fs = None # sampling frequency rate 18 | self.electrodes = list() 19 | # self.n_channels = 0 20 | self._init_inlet() 21 | 22 | def _init_inlet(self): 23 | print("looking for an EEG stream...") 24 | streams = resolve_stream('type', 'EEG') 25 | inlet = StreamInlet(streams[0]) 26 | print("EEG stream found!") 27 | self._inlet = inlet # Keep it None until inlet is ready 28 | self._load_init_info() 29 | 30 | def _load_init_info(self): 31 | self.fs = self._load_sampling_frequency() 32 | self.electrodes = self._load_electrodes() 33 | # self.n_channels = self._inlet.info.channel_count() 34 | 35 | def _load_sampling_frequency(self): 36 | return self._inlet.info().nominal_srate() # Hz 37 | 38 | def _load_electrodes(self): 39 | electrodes = [] 40 | info = self._inlet.info() 41 | ch = info.desc().child("channels").child("channel") 42 | for _ in range(info.channel_count()): 43 | electrodes.append(ch.child_value("label")) 44 | ch = ch.next_sibling() 45 | return electrodes 46 | 47 | def get_sample(self, timeout=32000000.0): 48 | return self._inlet.pull_sample(timeout=timeout) 49 | 50 | def get_chunk(self): 51 | return self._inlet.pull_chunk() 52 | 53 | 54 | class DSP(SignalReceiver): 55 | 56 | def __init__(self, use_filter=False, order=5, l_freq=1, h_freq=None, scale=1e-6): 57 | super(DSP, self).__init__() 58 | self._eeg = None 59 | self._filt_eeg = list() # change it to deque + copy()? 60 | self._timestamp = list() 61 | self._filter_signal = use_filter 62 | self._scale = scale 63 | 64 | if use_filter: 65 | if h_freq is None: 66 | self._sos = signal.butter(order, l_freq, btype='high', output='sos', fs=self.fs) 67 | elif l_freq is None: 68 | self._sos = signal.butter(order, h_freq, btype='low', output='sos', fs=self.fs) 69 | else: 70 | self._sos = signal.butter(order, [l_freq, h_freq], btype='band', output='sos', fs=self.fs) 71 | 72 | self._zi = np.array([signal.sosfilt_zi(self._sos) for _ in range(len(self.electrodes))]) 73 | self._zi = np.transpose(self._zi, (1, 2, 0)) 74 | 75 | def get_eeg_window_in_chunk(self, window_length=1.0): 76 | eeg_samples, timestamps = self.get_chunk() 77 | 78 | if len(timestamps) == 0: 79 | return None, None 80 | 81 | eeg_samples = np.array(eeg_samples) * self._scale 82 | 83 | if self._filter_signal: 84 | eeg_samples, self._zi = signal.sosfilt(self._sos, eeg_samples, axis=0, zi=self._zi) 85 | 86 | if self._eeg is None: 87 | self._eeg = eeg_samples 88 | else: 89 | self._eeg = np.vstack((self._eeg, eeg_samples)) 90 | # self._eeg.vstack(eeg_samples) 91 | self._timestamp.extend(timestamps) 92 | win = int(self.fs * window_length) 93 | timestamp = self._timestamp[-win:] 94 | eeg = self._eeg[-win:] 95 | if len(timestamp) < win: 96 | return None, None 97 | return timestamp, np.transpose(eeg) 98 | 99 | def get_recorded_signal(self): 100 | return np.transpose(self._eeg) 101 | -------------------------------------------------------------------------------- /bionic_apps/games/braindriver/control.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from itertools import cycle 3 | 4 | from numpy import uint8 5 | 6 | from bionic_apps.games.braindriver.commands import ControlCommand 7 | from bionic_apps.games.braindriver.logger import setup_logger, log_info 8 | 9 | GAME_CONTROL_PORT = 5555 10 | LOGGER_NAME = 'GameControl' 11 | 12 | 13 | class GameControl: 14 | 15 | def __init__(self, player_num=1, udp_ip='localhost', udp_port=GAME_CONTROL_PORT, 16 | make_log=False, log_to_stream=False, game_log_conn=None): 17 | self.udp_ip = udp_ip 18 | self.udp_port = udp_port 19 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 20 | self.player_num = player_num 21 | self._log = make_log 22 | self._command_menu = cycle([ControlCommand.LEFT, ControlCommand.RIGHT, ControlCommand.HEADLIGHT]) 23 | self._game_log_conn = game_log_conn 24 | if make_log: 25 | setup_logger(LOGGER_NAME, log_to_stream=log_to_stream) 26 | 27 | def _send_message(self, message): 28 | self.socket.sendto(message, (self.udp_ip, self.udp_port)) 29 | 30 | def _log_message(self, message): 31 | if self._log: 32 | log_info(LOGGER_NAME, message) 33 | 34 | def turn_left(self): 35 | self._send_message(uint8(self.player_num * 10 + ControlCommand.LEFT.value)) 36 | self._log_message('Command: Left turn') 37 | 38 | def turn_right(self): 39 | self._send_message(uint8(self.player_num * 10 + ControlCommand.RIGHT.value)) 40 | self._log_message('Command: Right turn') 41 | 42 | def turn_light_on(self): 43 | self._send_message(uint8(self.player_num * 10 + ControlCommand.HEADLIGHT.value)) 44 | self._log_message('Command: Light on') 45 | 46 | def go_straight(self): 47 | # do not sed anything because it will be registered as wrong command... 48 | self._log_message('Command: Go straight') 49 | 50 | def game_started(self): 51 | self._log_message('Game started!') 52 | 53 | def control_game(self, command): 54 | if command == ControlCommand.LEFT: 55 | self.turn_left() 56 | elif command == ControlCommand.RIGHT: 57 | self.turn_right() 58 | elif command == ControlCommand.HEADLIGHT: 59 | self.turn_light_on() 60 | elif command == ControlCommand.STRAIGHT: 61 | self.go_straight() 62 | else: 63 | raise NotImplementedError('Command {} is not implemented'.format(command)) 64 | 65 | def control_game_with_2_opt(self, switch_cmd=False): 66 | if switch_cmd: 67 | cmd = next(self._command_menu) 68 | else: 69 | cmd = ControlCommand.STRAIGHT 70 | self.control_game(cmd) 71 | 72 | if self._log and self._game_log_conn is not None: 73 | # self._game_logger.log_toggle_switch(cmd) 74 | self._game_log_conn.send(['log', cmd]) 75 | 76 | 77 | def run_demo(make_log=False): 78 | from pynput.keyboard import Key, Listener 79 | 80 | controller = GameControl(make_log=make_log) 81 | 82 | def control(key): 83 | if key == Key.up: 84 | controller.turn_light_on() 85 | elif key == Key.left: 86 | controller.turn_left() 87 | elif key == Key.right: 88 | controller.turn_right() 89 | 90 | def on_press(key): 91 | control(key) 92 | print('{0} pressed'.format( 93 | key)) 94 | 95 | def on_release(key): 96 | # print('{0} release'.format( 97 | # key)) 98 | if key == Key.esc: 99 | # Stop listener 100 | return False 101 | 102 | # Collect events until released 103 | with Listener( 104 | on_press=on_press, 105 | on_release=on_release) as listener: 106 | listener.join() 107 | 108 | 109 | if __name__ == '__main__': 110 | run_demo() 111 | -------------------------------------------------------------------------------- /bionic_apps/ai/classifier.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | from sklearn.metrics import classification_report, confusion_matrix, accuracy_score 4 | 5 | from .keras_networks import EEGNet, DeepConvNet, ShallowConvNet, EEGNetFusion, MI_EEGNet 6 | from .kolcs_neural_networks import VGG, VggType, DenseNet, DenseNetType, CascadeConvRecNet, BasicNet 7 | from .sklearn_classifiers import VotingSVM, get_ensemble_clf 8 | 9 | 10 | class ClassifierType(Enum): 11 | USER_DEFINED = auto() 12 | 13 | # sklearn classifiers 14 | VOTING_SVM = auto() 15 | ENSEMBLE = auto() 16 | VOTING = auto() 17 | 18 | # neural networks 19 | DENSE_NET_121 = auto() 20 | DENSE_NET_169 = auto() 21 | DENSE_NET_201 = auto() 22 | VGG16 = auto() 23 | VGG19 = auto() 24 | CASCADE_CONV_REC = auto() 25 | KOLCS_NET = auto() 26 | 27 | EEG_NET = auto() 28 | DEEP_CONV_NET = auto() 29 | SHALLOW_CONV_NET = auto() 30 | EEG_NET_FUSION = auto() 31 | MI_EEGNET = auto() 32 | 33 | 34 | def init_classifier(classifier_type, input_shape, classes, 35 | *, fs=None, classifier=None, save_path='tf_log/', **kwargs): 36 | if classifier_type is ClassifierType.VOTING_SVM: 37 | classifier = VotingSVM(**kwargs) 38 | elif classifier_type is ClassifierType.ENSEMBLE: 39 | classifier = get_ensemble_clf() 40 | elif classifier_type is ClassifierType.VOTING: 41 | classifier = get_ensemble_clf('voting') 42 | elif classifier_type is ClassifierType.VGG16: 43 | classifier = VGG(VggType.VGG16, input_shape, classes, **kwargs) 44 | elif classifier_type is ClassifierType.VGG19: 45 | classifier = VGG(VggType.VGG19, input_shape, classes, **kwargs) 46 | elif classifier_type is ClassifierType.DENSE_NET_121: 47 | classifier = DenseNet(DenseNetType.DN121, input_shape, classes, **kwargs) 48 | elif classifier_type is ClassifierType.DENSE_NET_169: 49 | classifier = DenseNet(DenseNetType.DN169, input_shape, classes, **kwargs) 50 | elif classifier_type is ClassifierType.DENSE_NET_201: 51 | classifier = DenseNet(DenseNetType.DN201, input_shape, classes, **kwargs) 52 | elif classifier_type is ClassifierType.CASCADE_CONV_REC: 53 | classifier = CascadeConvRecNet(input_shape, classes, **kwargs) 54 | elif classifier_type is ClassifierType.KOLCS_NET: 55 | classifier = BasicNet(input_shape, classes) 56 | elif classifier_type is ClassifierType.EEG_NET: 57 | classifier = EEGNet(input_shape, classes, fs=fs, save_path=save_path, **kwargs) 58 | elif classifier_type is ClassifierType.DEEP_CONV_NET: 59 | classifier = DeepConvNet(input_shape, classes, save_path=save_path, **kwargs) 60 | elif classifier_type is ClassifierType.SHALLOW_CONV_NET: 61 | classifier = ShallowConvNet(input_shape, classes, save_path=save_path, **kwargs) 62 | elif classifier_type is ClassifierType.EEG_NET_FUSION: 63 | classifier = EEGNetFusion(input_shape, classes, fs=fs, save_path=save_path, **kwargs) 64 | elif classifier_type is ClassifierType.MI_EEGNET: 65 | classifier = MI_EEGNet(input_shape, classes, fs=fs, save_path=save_path, **kwargs) 66 | elif classifier_type is ClassifierType.USER_DEFINED: 67 | assert classifier is not None, f'classifier must be defined!' 68 | else: 69 | raise NotImplementedError('Classifier {} is not implemented.'.format(classifier_type.name)) 70 | return classifier 71 | 72 | 73 | def test_classifier(clf, x_test, y_test, le): 74 | y_pred = clf.predict(x_test) 75 | y_pred = le.inverse_transform(y_pred) 76 | y_test = le.inverse_transform(y_test) 77 | 78 | # https://scikit-learn.org/stable/modules/model_evaluation.html#precision-recall-and-f-measures 79 | class_report = classification_report(y_test, y_pred) 80 | conf_matrix = confusion_matrix(y_test, y_pred) 81 | acc = accuracy_score(y_test, y_pred) 82 | 83 | print(class_report) 84 | print(f"Confusion matrix:\n{conf_matrix}\n") 85 | print(f"Accuracy score: {acc}\n") 86 | return acc 87 | -------------------------------------------------------------------------------- /bionic_apps/tests/test_io.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pathlib import Path 3 | 4 | from bionic_apps.databases import Databases 5 | from bionic_apps.preprocess.io import DataLoader, SubjectHandle 6 | from bionic_apps.tests.utils import AVAILABLE_DBS 7 | 8 | 9 | class TestDataLoader(unittest.TestCase): 10 | 11 | def _test_get_subject_list(self): 12 | subj_list = self.loader.get_subject_list() 13 | self.assertIsInstance(subj_list, list) 14 | for subj in subj_list: 15 | self.assertIsInstance(subj, int) 16 | 17 | def _test_get_filenames(self): 18 | for subj in self.loader.get_subject_list(): 19 | file_names = self.loader.get_filenames_for_subject(subj) 20 | self.assertIsInstance(file_names, list) 21 | self.assertTrue(all(Path(file).exists() for file in file_names)) 22 | 23 | def _run_test(self, db_config_ver=-1): 24 | for db_name in AVAILABLE_DBS: 25 | with self.subTest(f'Database: {db_name.name}'): 26 | self.loader.use_db(db_name, db_config_ver) 27 | self.assertIsInstance(self.loader.get_subject_num(), int) 28 | self._test_get_subject_list() 29 | if db_config_ver == 0 and db_name in [Databases.PHYSIONET, Databases.BCI_COMP_IV_2A, 30 | Databases.BCI_COMP_IV_2B, Databases.BCI_COMP_IV_1, 31 | Databases.GIGA]: 32 | with self.assertRaises(NotImplementedError): 33 | self._test_get_filenames() 34 | else: 35 | self._test_get_filenames() 36 | 37 | def test_no_defined_db(self): 38 | self.loader = DataLoader() 39 | self.assertRaises(AssertionError, self.loader.get_subject_num) 40 | 41 | def test_independent_days(self): 42 | self.loader = DataLoader(subject_handle=SubjectHandle.INDEPENDENT_DAYS) 43 | self._run_test() 44 | 45 | def test_independent_days_old_db_config(self): 46 | self.loader = DataLoader(subject_handle=SubjectHandle.INDEPENDENT_DAYS) 47 | self._run_test(db_config_ver=0) 48 | 49 | def test_mix_experiments(self): 50 | self.loader = DataLoader(subject_handle=SubjectHandle.MIX_EXPERIMENTS) 51 | self._run_test() 52 | 53 | def test_bci_comp(self): 54 | self.loader = DataLoader(subject_handle=SubjectHandle.BCI_COMP) 55 | for db_name in AVAILABLE_DBS: 56 | with self.subTest(f'Database: {db_name.name}'): 57 | self.loader.use_db(db_name) 58 | if db_name in [Databases.BCI_COMP_IV_2A, Databases.BCI_COMP_IV_2B, Databases.GIGA]: 59 | self.assertIsInstance(self.loader.get_subject_num(), int) 60 | self._test_get_subject_list() 61 | self._test_get_filenames() 62 | else: 63 | self.assertRaises(ValueError, self.loader.get_subject_num) 64 | 65 | 66 | # @unittest.skipUnless(Path(init_base_config('..')).joinpath(Game_ParadigmD().DIR).exists(), 67 | # 'Data for Game_paradigmD does not exists. Can not test it.') 68 | # class TestDataHandler(unittest.TestCase): 69 | # 70 | # def test_big_data(self): 71 | # subject_list = [1, 2] 72 | # epoch_proc = OfflineDataPreprocessor(base_config_path='..') 73 | # epoch_proc.use_game_par_d() 74 | # feature_extraction = dict( 75 | # feature_type=FeatureType.SPATIAL_AVG_FFT_POW, 76 | # fft_low=14, fft_high=30 77 | # ) 78 | # epoch_proc.run(subject_list, **feature_extraction) 79 | # file_list = epoch_proc.get_processed_db_source(subject_list[0], only_files=True) 80 | # labels = epoch_proc.get_labels() 81 | # label_encoder = LabelEncoder() 82 | # label_encoder.fit(labels) 83 | # file_handler = DataHandler(file_list, label_encoder) 84 | # dataset = file_handler.get_tf_dataset() 85 | # from tensorflow import data as tf_data 86 | # self.assertIsInstance(dataset, tf_data.Dataset) 87 | # for d, l in dataset.take(5): 88 | # print(d.numpy(), l.numpy()) 89 | 90 | 91 | if __name__ == '__main__': 92 | unittest.main() 93 | -------------------------------------------------------------------------------- /bionic_apps/databases/coreg_mindrove/prepare.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import numpy as np 4 | import pandas as pd 5 | 6 | from bionic_apps.databases.eeg.standardize_database import _create_raw 7 | 8 | DATA_PATH = Path(r'D:\Users\Csabi\OneDrive - Pázmány Péter Katolikus Egyetem\MindRove project\database\Coreg_data') 9 | 10 | EMG_CHS = ['EMG1', 'EMG2', 'EMG3', 'EMG4', 'EMG5', 'EMG6', 'EMG7', 'EMG8'] 11 | EEG_CHS = ['EEG1', 'EEG2', 'EEG3', 'EEG4', 'EEG5', 'EEG6'] 12 | LABEL_CONVERTER = ( 13 | ['Idle'] + 14 | (['Rest'] + ['Thumb'] * 3) * 3 + 15 | (['Rest'] + ['Index'] * 3) * 3 + 16 | (['Rest'] + ['Middle'] * 3) * 3 + 17 | (['Rest'] + ['Ring'] * 3) * 3 + 18 | (['Rest'] + ['Small'] * 3) * 3 + 19 | (['Rest'] + ['Wrist FW'] * 3) * 3 + 20 | (['Rest'] + ['Wrist BCK'] * 3) * 3 + 21 | ['Idle'] 22 | ) 23 | 24 | FS = 500 25 | 26 | 27 | def get_annotated_mindrove_raw(file, mode='merged', plot=False): 28 | assert file.suffix == '.csv', f'only .csv files are accepted' 29 | df = pd.read_csv(file, sep=',', encoding='utf8') 30 | data = df[EEG_CHS + EMG_CHS] * 1e-6 31 | task_numbers = df['Task_number'].values 32 | 33 | tr = np.ediff1d(task_numbers) 34 | tr_start = np.insert(tr, 0, 1) 35 | tr_end = np.append(tr, 1) 36 | tr_start = np.arange(len(tr_start))[tr_start > 0] 37 | tr_end = np.arange(len(tr_end))[tr_end > 0] + 1 38 | assert len(tr_start) == len(tr_end) 39 | 40 | if mode == 'distinct': 41 | pass 42 | elif mode == 'merged': 43 | tr_start_merged, tr_end_merged = [], [] 44 | for i in range(len(tr_start)): 45 | if i == 0 or i + 1 == len(tr_start) or i % 4 == 1 or i % 4 == 2: 46 | tr_start_merged.append(tr_start[i]) 47 | if i == 0 or i + 1 == len(tr_start) or i % 4 == 0 or i % 4 == 1: 48 | tr_end_merged.append(tr_end[i]) 49 | if len(tr_start_merged) > len(tr_end_merged): 50 | tr_start_merged.pop(-2) 51 | elif len(tr_start_merged) < len(tr_end_merged): 52 | tr_end_merged.pop(-2) 53 | tr_start, tr_end = np.array(tr_start_merged), np.array(tr_end_merged) 54 | else: 55 | raise NotImplementedError(f'Mode {mode} is not implemented.') 56 | 57 | onset = tr_start / FS 58 | duration = tr_end / FS - onset 59 | ep_labels = np.array([LABEL_CONVERTER[int(lab)] for lab in task_numbers[tr_start]]) 60 | 61 | raw = _create_raw(data.T, 62 | ch_names=EEG_CHS + EMG_CHS, 63 | ch_types=['eeg'] * len(EEG_CHS) + ['emg'] * len(EMG_CHS), 64 | fs=FS, onset=onset, duration=duration, 65 | description=ep_labels 66 | ) 67 | 68 | if plot: 69 | raw.plot(block=True) 70 | 71 | return raw 72 | 73 | 74 | def reannotate_mindrove(base_dir=DATA_PATH, ep_mode='merged'): 75 | assert base_dir.exists(), f'Path {base_dir} is not available.' 76 | emg_files = sorted(base_dir.rglob('*.csv')) 77 | for j, file in enumerate(emg_files): 78 | j += 1 79 | subj = file.stem.split('_')[-1] 80 | print(f'Subject{j} - {subj}') 81 | 82 | raw = get_annotated_mindrove_raw(file, mode=ep_mode, plot=False) 83 | 84 | raw_cp = raw.copy() 85 | iir_params = dict(order=5, ftype='butter', output='sos') 86 | raw_cp.filter(l_freq=1, h_freq=40, method='iir', iir_params=iir_params, skip_by_annotation='edge', 87 | n_jobs=-1, picks='eeg') 88 | raw_cp.filter(l_freq=20, h_freq=150, method='iir', iir_params=iir_params, skip_by_annotation='edge', 89 | n_jobs=-1, picks='emg') 90 | 91 | raw_cp.plot(block=True) 92 | 93 | raw.set_annotations(raw_cp.annotations) 94 | file = str(base_dir.joinpath(f'subject{j:03d}_raw.fif')) 95 | raw.save(file) 96 | 97 | # # testing: 98 | # from mne.io import read_raw_fif 99 | # sraw = read_raw_fif(file) 100 | # 101 | # raw.plot(title='Modified, before save') 102 | # sraw.plot(block=True, title='After save') 103 | 104 | 105 | if __name__ == '__main__': 106 | reannotate_mindrove() 107 | -------------------------------------------------------------------------------- /bionic_apps/games/braindriver/opponents.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | from multiprocessing import Pipe 3 | from random import shuffle 4 | from threading import Thread 5 | from time import sleep 6 | 7 | from numpy.random import randint 8 | 9 | from .control import ControlCommand, GameControl 10 | from .logger import GameLogger 11 | from ...databases.eeg.defaults import ACTIVE 12 | 13 | 14 | class PlayerType(Enum): 15 | MASTER = auto() 16 | RANDOM = auto() 17 | RANDOM_BINARY = auto() 18 | 19 | 20 | class Player(GameControl, Thread): 21 | 22 | def __init__(self, player_num, game_log_conn, reaction_time=1.0, *, daemon=True): 23 | GameControl.__init__(self, player_num=player_num, game_log_conn=game_log_conn) 24 | Thread.__init__(self, daemon=daemon) 25 | self._reaction_time = reaction_time 26 | 27 | def _control_protocol(self): 28 | raise NotImplementedError('Control protocol not implemented') 29 | 30 | def run(self): 31 | while True: 32 | self._control_protocol() 33 | sleep(self._reaction_time) 34 | 35 | 36 | class MasterPlayer(Player): 37 | 38 | def __init__(self, player_num, game_log_conn, reaction_time=1.0, *, daemon=True): 39 | super(MasterPlayer, self).__init__(player_num, game_log_conn, reaction_time, 40 | daemon=daemon) 41 | self._prev_sig = -1 42 | 43 | def _control_protocol(self): 44 | assert self._game_log_conn is not None, 'GameLogger connection must be defined for MasterPlayer!' 45 | # exp_sig = self._game_logger.get_expected_signal(self.player_num) 46 | self._game_log_conn.send(['exp_sig', self.player_num]) 47 | exp_sig = self._game_log_conn.recv() 48 | if exp_sig != self._prev_sig: 49 | cmd = ControlCommand(exp_sig) 50 | self.control_game(cmd) 51 | self._prev_sig = exp_sig 52 | 53 | 54 | class RandomPlayer(Player): 55 | 56 | def __init__(self, player_num, reaction_time=1, *, daemon=True): 57 | super().__init__(player_num, None, reaction_time, daemon=daemon) 58 | 59 | def _control_protocol(self): 60 | cmd = ControlCommand(randint(4)) 61 | self.control_game(cmd) 62 | 63 | 64 | class RandomBinaryPlayer(RandomPlayer): 65 | 66 | def _control_protocol(self): 67 | cmd_list = [ACTIVE, 'none'] 68 | cmd = cmd_list[randint(2)] 69 | self.control_game_with_2_opt(cmd) 70 | 71 | 72 | def create_opponents(main_player=1, players=None, game_log_conn=None, reaction=1.0, *, daemon=True): 73 | """ Function for player creation. 74 | 75 | Creating required types of bot players. 76 | 77 | Parameters 78 | ---------- 79 | main_player: int 80 | the number of main player 81 | players: list of string, list of PlayerType, None 82 | List of player types. Should contain 4 values. 83 | game_log_conn: multiprocessing.connection.Connection, None 84 | Optional predefined multiprocessing connection object 85 | to communicate between GameLogger and Players. 86 | reaction: float 87 | Player reaction time in seconds. 88 | """ 89 | if players is None: 90 | players = [PlayerType.MASTER, PlayerType.RANDOM, PlayerType.RANDOM_BINARY] 91 | shuffle(players) 92 | assert len(players) == 3, '3 player type is required {} were given instead'.format(len(players)) 93 | 94 | player_numbers = list(range(1, 5)) 95 | player_numbers.remove(main_player) 96 | 97 | for num, pl_type in zip(player_numbers, players): 98 | if pl_type is PlayerType.MASTER: 99 | if game_log_conn is None: 100 | game_log_conn, child_conn = Pipe() 101 | GameLogger(daemon=daemon, connection=child_conn).start() 102 | bot = MasterPlayer(num, game_log_conn, daemon=daemon) 103 | elif pl_type is PlayerType.RANDOM: 104 | bot = RandomPlayer(num, reaction_time=reaction, daemon=daemon) 105 | elif pl_type is PlayerType.RANDOM_BINARY: 106 | bot = RandomBinaryPlayer(num, reaction_time=reaction, daemon=daemon) 107 | else: 108 | raise NotImplementedError('{} player is not implemented'.format(pl_type)) 109 | bot.start() 110 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/brainvision/remote_control.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen 2 | import socket 3 | import threading 4 | from time import sleep 5 | 6 | TCP_IP = '127.0.0.1' 7 | TCP_PORT = 6700 8 | BUFFER_SIZE = 1024 9 | 10 | REMOTE_CONTROL_SERVER_PATH = r'C:\Vision\RemoteControlServer\RemoteControlServer.exe' 11 | 12 | APPLICATION_SATE = 'AP' 13 | RECORDER_STATE = 'RS' 14 | ACQUISITION_SATE = 'AQ' 15 | 16 | ANS_WAITING_TIME = 0.1 # sec 17 | 18 | 19 | class RemoteControlClient: 20 | def __init__(self, rcs_process=None, print_received_messages=True): 21 | if rcs_process is None: 22 | rcs_process = Popen(REMOTE_CONTROL_SERVER_PATH) 23 | self._rcs_process = rcs_process 24 | 25 | self._print_answers = print_received_messages 26 | 27 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 | self._sock.connect((TCP_IP, TCP_PORT)) 29 | self._sent_msg = str() 30 | self._state = {APPLICATION_SATE: str(), RECORDER_STATE: str(), ACQUISITION_SATE: str()} 31 | 32 | self._listen_thread = threading.Thread(target=self._listening_message_in_thread, daemon=True) 33 | self._listen_thread.start() 34 | self.ask_msg_protocol() 35 | 36 | def _send_message(self, msg, required_state=None, code=None): 37 | self._waiting_for_required_state(required_state, code) 38 | self._sent_msg = msg 39 | msg += '\r' 40 | self._sock.send(msg.encode()) 41 | 42 | def _waiting_for_required_state(self, state=None, code=None): 43 | if state is not None: 44 | code = str(code) 45 | while self._state[state] != code: 46 | sleep(ANS_WAITING_TIME) 47 | 48 | def _get_message(self): 49 | return self._sock.recv(BUFFER_SIZE).decode().strip('\r') 50 | 51 | def _listening_message_in_thread(self): 52 | while True: 53 | msg = self._get_message() 54 | ans = msg.split(':') 55 | 56 | if ans[0] == self._sent_msg: 57 | self._sent_msg = str() 58 | if ans[1] == 'Error': 59 | print(ans[2]) 60 | if ans[0] == 'VM': 61 | assert ans[1] == '2', 'Only Messaging protocol 2 is supported!' 62 | 63 | if ans[0] in self._state.keys(): 64 | self._state[ans[0]] = ans[1] 65 | # print(ans[0], self._state[ans[0]], ans[1]) 66 | 67 | if self._print_answers: 68 | print(msg) 69 | 70 | def __del__(self): 71 | self.stop_rec() 72 | self.close_recorder() 73 | self.terminate_remote_control_server() 74 | self._sock.close() 75 | 76 | def ask_msg_protocol(self): 77 | self._send_message('VM') 78 | 79 | def open_recorder(self): 80 | self._send_message('O') 81 | 82 | def view_data(self): 83 | self._send_message('M', APPLICATION_SATE, 1) 84 | 85 | def check_impedance(self): 86 | self._send_message('I', APPLICATION_SATE, 1) 87 | 88 | def test_signal(self): 89 | self._send_message('T', APPLICATION_SATE, 1) 90 | 91 | def stop_view(self): 92 | self._send_message('SV') 93 | 94 | def start_rec(self): 95 | self._send_message('S') 96 | 97 | def pause_rec(self): 98 | self._send_message('P') 99 | 100 | def resume_rec(self): 101 | self._send_message('C') 102 | 103 | def stop_rec(self): 104 | self._send_message('Q') 105 | 106 | def close_recorder(self): 107 | self._send_message('X') 108 | 109 | def reset_DC(self): 110 | self._send_message('D') 111 | 112 | def request_recorder_state(self): 113 | self._send_message(RECORDER_STATE) 114 | 115 | def request_application_state(self): 116 | self._send_message(APPLICATION_SATE) 117 | 118 | def request_acquisition_state(self): 119 | self._send_message(ACQUISITION_SATE) 120 | 121 | def send_annotation(self, annotation, ann_type='Stimulus'): 122 | self._send_message('AN:{};{}'.format(annotation, ann_type)) 123 | 124 | def terminate_remote_control_server(self): 125 | self._rcs_process.terminate() 126 | 127 | 128 | if __name__ == '__main__': 129 | import time 130 | 131 | rcc = RemoteControlClient() 132 | rcc.open_recorder() 133 | rcc.check_impedance() 134 | time.sleep(10) 135 | -------------------------------------------------------------------------------- /bionic_apps/legacy/fbcsp.py: -------------------------------------------------------------------------------- 1 | import mne 2 | import numpy as np 3 | from mne.decoding import CSP 4 | # from pyriemann.spatialfilters import CSP 5 | from sklearn.base import BaseEstimator, TransformerMixin 6 | from sklearn.decomposition import PCA 7 | from sklearn.pipeline import make_pipeline 8 | from sklearn_pipeline_bci.utils import filter_mne_obj, window_epochs 9 | 10 | 11 | class FBCSP(BaseEstimator, TransformerMixin): 12 | 13 | def __init__(self, n_components=4): 14 | self.n_components = n_components 15 | self.fbcsp = [] 16 | 17 | def _expand_and_check(self, x): 18 | if len(x.shape) == 3: 19 | x = np.expand_dims(x, axis=0) 20 | assert len(x.shape) == 4, 'Unsupported shape for FBCSP.' 21 | return x 22 | 23 | def fit(self, x, y): # todo: (filt, ep, ch, time) ? 24 | x = self._expand_and_check(x) 25 | self.fbcsp = [] 26 | for f_x in x: 27 | # https://github.com/mne-tools/mne-python/issues/9094 28 | csp = make_pipeline( 29 | mne.decoding.UnsupervisedSpatialFilter(PCA(n_components=32)), 30 | CSP(self.n_components, log=True) 31 | ) 32 | self.fbcsp.append(csp.fit(f_x, y)) 33 | 34 | return self 35 | 36 | def transform(self, x): 37 | if len(self.fbcsp) == 0: 38 | raise RuntimeError('No filters available. Please first fit FBCSP ' 39 | 'decomposition.') 40 | x = self._expand_and_check(x) 41 | 42 | csps = [] 43 | for i, csp in enumerate(self.fbcsp): 44 | csps.append(csp.transform(x[i])) 45 | csps = np.array(csps).transpose((1, 0, 2)) 46 | n, filt, comp = csps.shape 47 | csps = csps.reshape((n, filt * comp)) 48 | return csps 49 | 50 | 51 | class FilterBank: 52 | 53 | def __init__(self, f_low=4, f_high=40, f_step=4, f_width=4, n_jobs=1): 54 | self.filters = [(f, f + f_width) for f in range(f_low, f_high, f_step) 55 | if f + f_width <= f_high] 56 | self.n_jobs = n_jobs 57 | 58 | def transform(self, x): 59 | filter_bank = [filter_mne_obj(x, l_freq=l_freq, h_freq=h_freq, n_jobs=self.n_jobs).get_data() 60 | for l_freq, h_freq in self.filters] 61 | return np.array(filter_bank) 62 | 63 | 64 | class WindowEpochs: 65 | 66 | def __init__(self, window_length, window_step, fs, shuffle=False): 67 | self.window_length = window_length 68 | self.window_step = window_step 69 | self.fs = fs 70 | self.shuffle = shuffle 71 | 72 | def _validate_data(self, data): 73 | if isinstance(data, np.ndarray): 74 | pass 75 | elif isinstance(data, (mne.Epochs, mne.EpochsArray)): 76 | data = data.get_data() 77 | else: 78 | raise TypeError(f'Expected types: mne.Epochs, mne.EpochsArray, ndarray. ' 79 | f'Received {type(data)}') 80 | return data 81 | 82 | def transform(self, x, y): 83 | if isinstance(x, list) or len(x.shape) == 4: # FBCSP 84 | data = [window_epochs(self._validate_data(ep), self.window_length, self.window_step, self.fs) 85 | for ep in x] # np.hstack? 86 | windowed_data_shape = data[0].shape 87 | windowed_data = np.array([np.vstack(d) for d in data]) 88 | else: 89 | data = window_epochs(self._validate_data(x), 90 | window_length=self.window_length, 91 | window_step=self.window_step, fs=self.fs) 92 | windowed_data_shape = data.shape 93 | windowed_data = np.vstack(data) 94 | 95 | groups = np.array([i // windowed_data_shape[1] for i in range(windowed_data_shape[0] * windowed_data_shape[1])]) 96 | labels = np.array([y[i // windowed_data_shape[1]] for i in range(len(groups))]) 97 | 98 | if self.shuffle: 99 | ind = np.arange(len(labels)) 100 | np.random.shuffle(ind) 101 | labels = labels[ind] 102 | groups = groups[ind] 103 | 104 | if len(windowed_data.shape) == 3: 105 | windowed_data = windowed_data[ind] 106 | else: 107 | windowed_data = np.array([d[ind] for d in windowed_data]) 108 | 109 | return windowed_data, labels, groups 110 | -------------------------------------------------------------------------------- /bionic_apps/ai/svm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from ai.interface import ClassifierInterface 3 | from joblib import Parallel, delayed 4 | from sklearn.pipeline import Pipeline 5 | from sklearn.preprocessing import Normalizer, MinMaxScaler, StandardScaler, MaxAbsScaler, RobustScaler, \ 6 | PowerTransformer, QuantileTransformer 7 | from sklearn.svm import SVC 8 | 9 | 10 | class OnlinePipeline(Pipeline): 11 | 12 | def __init__(self, steps, memory=None, verbose=False): 13 | super().__init__(steps, memory, verbose) 14 | self._init_fit = True 15 | 16 | def fit(self, X, y=None, **fit_params): 17 | if self._init_fit: 18 | super().fit(X, y, **fit_params) 19 | self._init_fit = False 20 | else: 21 | for i, step in enumerate(self.steps): 22 | name, est = step 23 | if i < len(self.steps) - 1: 24 | X = est.transform(X) 25 | else: 26 | est.partial_fit(X, y) 27 | 28 | return self 29 | 30 | 31 | def _init_one_svm(svm, pipeline=('norm',), **svm_kargs): 32 | if svm is None: 33 | # svm = OnlinePipeline([('norm', Normalizer()), ('svm', SGDClassifier(**svm_kargs))]) 34 | pipe_list = list() 35 | for el in pipeline: 36 | if el == 'norm': 37 | element = (el, Normalizer()) 38 | elif el == 'standard': 39 | element = (el, StandardScaler()) 40 | elif el == 'minmax': 41 | element = (el, MinMaxScaler()) 42 | elif el == 'maxabs': 43 | element = (el, MaxAbsScaler()) 44 | elif el == 'robust': 45 | element = (el, RobustScaler()) 46 | elif el == 'power': 47 | element = (el, PowerTransformer()) 48 | elif el == 'quantile': 49 | element = (el, QuantileTransformer(output_distribution='normal')) 50 | else: 51 | raise ValueError(f'{el} is not in SVM pipeline options.') 52 | pipe_list.append(element) 53 | pipe_list.append(('svm', SVC(**svm_kargs))) 54 | svm = Pipeline(pipe_list) 55 | return svm 56 | 57 | 58 | def _fit_one_svm(svm, data, label, num): 59 | svm.fit(data, label) 60 | return num, svm 61 | 62 | 63 | class MultiSVM(ClassifierInterface): 64 | def __init__(self, **svm_kwargs): 65 | self._svm_kargs = svm_kwargs 66 | self._svms = dict() 67 | 68 | def _predict(self, i, data): 69 | svm = self._svms[i] 70 | return svm.predict(data) 71 | 72 | def fit(self, X, y, **kwargs): 73 | """ 74 | 75 | Parameters 76 | ---------- 77 | X : numpy.ndarray 78 | EEG data to be processed. shape: (n_samples, n_svm, n_features) 79 | y : numpy.ndarray 80 | labels 81 | 82 | Returns 83 | ------- 84 | 85 | """ 86 | X = np.array(X) 87 | n_svms = X.shape[1] 88 | self._svms = {i: _init_one_svm(self._svms.get(i), **self._svm_kargs) for i in range(n_svms)} 89 | # self._svms = [SVM(*self._svm_args) for _ in range(n_svms)] # serial: 3 times slower 90 | # for i in range(len(self._svms)): 91 | # self._fit_svm(i, X[:, i, :], y) 92 | if len(y.shape) == 2 and y.shape[1] == 1: 93 | y = np.ravel(y) 94 | if n_svms > 1: 95 | svms = Parallel(n_jobs=-2)( 96 | delayed(_fit_one_svm)(self._svms[i], X[:, i, :], y, i) for i in range(n_svms)) 97 | else: 98 | svms = [_fit_one_svm(self._svms[0], X[:, 0, :], y, 0)] 99 | self._svms = dict(svms) 100 | 101 | def predict(self, X): 102 | X = np.array(X) 103 | votes = [self._predict(i, X[:, i, :]) for i in range(X.shape[1])] 104 | # votes.extend([self._predict(X.shape[1] + i, X[:, :, i]) for i in range(X.shape[2])]) 105 | # votes = [self._predict(i, X[:, :, i]) for i in range(X.shape[2])] 106 | 107 | # votes = Parallel(n_jobs=-2)(delayed(self._predict)(i, X[:, i, :]) for i in range(len(self._svms))) 108 | votes = np.array(votes) 109 | res = list() 110 | for i in range(votes.shape[1]): # counting votes 111 | unique, count = np.unique(votes[:, i], return_counts=True) 112 | res.append(unique[np.argmax(count)]) 113 | return res 114 | 115 | 116 | if __name__ == '__main__': 117 | pass 118 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/emotiv/epoc_plus_lsl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | # 3 | # CyKIT 2020.06.05 4 | # ________________________ 5 | # example_epoc_plus.py 6 | # 7 | # Written by Warren 8 | # 9 | """ 10 | CyKIT is needed for this program to run!!! 11 | CyKit-master\Examples\epoc_plus_lsl.py 12 | 13 | usage: python.exe .\example_epoc_plus.py 14 | 15 | ( May need to adjust the key below, based on whether 16 | device is in 14-bit mode or 16-bit mode. ) 17 | 18 | """ 19 | 20 | import os 21 | import queue 22 | import sys 23 | 24 | import cyPyWinUSB as hid 25 | from cyCrypto.Cipher import AES 26 | from pylsl import StreamInfo, StreamOutlet, local_clock 27 | 28 | print(str(sys.path)) 29 | 30 | CYKIT_MASTER_PATH = r'C:\path\to\CyKit-master' # todo: set it! 31 | sys.path.insert(0, CYKIT_MASTER_PATH + r'\py3\cyUSB') 32 | sys.path.insert(0, CYKIT_MASTER_PATH + r'\py3') 33 | 34 | tasks = queue.Queue() 35 | EPOC_PLUS = 'Epoc_plus' 36 | 37 | 38 | class EEG(object): 39 | 40 | def __init__(self): 41 | self.hid = None 42 | self.delimiter = ", " 43 | 44 | devicesUsed = 0 45 | 46 | for device in hid.find_all_hid_devices(): 47 | if device.product_name == 'EEG Signals': 48 | devicesUsed += 1 49 | self.hid = device 50 | self.hid.open() 51 | self.serial_number = device.serial_number 52 | device.set_raw_data_handler(self.dataHandler) 53 | if devicesUsed == 0: 54 | os._exit(0) 55 | sn = self.serial_number 56 | 57 | # EPOC+ in 16-bit Mode. 58 | # k = ['\0'] * 16 59 | k = [sn[-1], sn[-2], sn[-2], sn[-3], sn[-3], sn[-3], sn[-2], sn[-4], sn[-1], sn[-4], sn[-2], sn[-2], sn[-4], 60 | sn[-4], sn[-2], sn[-1]] 61 | 62 | # EPOC+ in 14-bit Mode. 63 | # k = [sn[-1],00,sn[-2],21,sn[-3],00,sn[-4],12,sn[-3],00,sn[-2],68,sn[-1],00,sn[-2],88] 64 | 65 | self.key = str(''.join(k)) 66 | self.cipher = AES.new(self.key.encode("utf8"), AES.MODE_ECB) 67 | 68 | def dataHandler(self, data): 69 | join_data = ''.join(map(chr, data[1:])) 70 | data = self.cipher.decrypt(bytes(join_data, 'latin-1')[0:32]) 71 | if str(data[1]) == "32": # No Gyro Data. 72 | return 73 | tasks.put(data) 74 | 75 | def convertEPOC_PLUS(self, value_1, value_2): 76 | edk_value = "%.8f" % ( 77 | ((int(value_1) * .128205128205129) + 4201.02564096001) + ((int(value_2) - 128) * 32.82051289)) 78 | return edk_value 79 | 80 | def get_data(self): 81 | 82 | data = tasks.get() 83 | # print(str(data[0])) COUNTER 84 | 85 | try: 86 | packet_data = "" 87 | for i in range(2, 16, 2): 88 | packet_data = packet_data + str(self.convertEPOC_PLUS(str(data[i]), str(data[i + 1]))) + self.delimiter 89 | 90 | for i in range(18, len(data), 2): 91 | packet_data = packet_data + str(self.convertEPOC_PLUS(str(data[i]), str(data[i + 1]))) + self.delimiter 92 | 93 | packet_data = packet_data[:-len(self.delimiter)] 94 | return str(packet_data) 95 | 96 | except Exception as exception2: 97 | print(str(exception2)) 98 | 99 | 100 | if __name__ == '__main__': 101 | 102 | electrodes = ['AF3', 'F7', 'F3', 'FC5', 'T7', 'P7', 'O1', 'O2', 'P8', 'T8', 'FC6', 'F4', 'F8', 'AF4'] 103 | 104 | # https://labstreaminglayer.readthedocs.io/projects/liblsl/ref/streaminfo.html 105 | info = StreamInfo('Epoc_plus', 'EEG', 14, 128, 'float32', 'EpocPlusEEG_Stream') 106 | # (Default) Outputs 14 data channels in float format. 128 SPS / 256 SPS (2048 Hz internal) 107 | # Run CyKIT.py, choose MODEL#6 for Epoc+ (default is MODEL#1) 108 | 109 | # append some meta-data 110 | info.desc().append_child_value("manufacturer", "Emotiv") 111 | channels = info.desc().append_child("channels") 112 | 113 | for c in electrodes: 114 | channels.append_child("channel") \ 115 | .append_child_value("label", c) \ 116 | .append_child_value("unit", "microvolts") \ 117 | .append_child_value("type", "EEG") 118 | 119 | # next make an outlet; we set the transmission chunk size to 32 samples and 120 | # the outgoing buffer size to 360 seconds (max.) 121 | outlet = StreamOutlet(info, 32, 360) 122 | 123 | cyHeadset = EEG() 124 | 125 | print("now sending data...") 126 | while 1: 127 | while tasks.empty(): 128 | pass 129 | 130 | mysample = [] 131 | sample = cyHeadset.get_data().split(", ") 132 | for s in sample: 133 | mysample.append(float(s)) 134 | # print(mysample) 135 | # get a time stamp in seconds 136 | stamp = local_clock() 137 | 138 | outlet.push_sample(mysample, stamp) 139 | -------------------------------------------------------------------------------- /bionic_apps/legacy/fbcsp_toolbox.py: -------------------------------------------------------------------------------- 1 | # source: https://fbcsptoolbox.github.io/ 2 | import numpy as np 3 | import scipy.linalg 4 | import scipy.signal as signal 5 | from sklearn.base import BaseEstimator, TransformerMixin 6 | 7 | 8 | class CSP(TransformerMixin, BaseEstimator): 9 | def __init__(self, m_filters=4): 10 | self.m_filters = m_filters 11 | self.eig_vectors = None 12 | 13 | def fit(self, x_train, y_train): 14 | x_data = np.copy(x_train) 15 | y_labels = np.copy(y_train) 16 | n_trials, n_channels, n_samples = x_data.shape 17 | cov_x = np.zeros((2, n_channels, n_channels), dtype=np.float) 18 | for i in range(n_trials): 19 | x_trial = x_data[i, :, :] 20 | y_trial = y_labels[i] 21 | cov_x_trial = np.matmul(x_trial, np.transpose(x_trial)) 22 | cov_x_trial /= np.trace(cov_x_trial) 23 | cov_x[y_trial, :, :] += cov_x_trial 24 | 25 | cov_x = np.asarray([cov_x[cls] / np.sum(y_labels == cls) for cls in range(2)]) 26 | cov_combined = cov_x[0] + cov_x[1] 27 | eig_values, u_mat = scipy.linalg.eig(cov_combined, cov_x[0]) 28 | sort_indices = np.argsort(abs(eig_values))[::-1] 29 | eig_values = eig_values[sort_indices] 30 | u_mat = u_mat[:, sort_indices] 31 | self.eig_vectors = np.transpose(u_mat) 32 | 33 | return self 34 | 35 | def transform(self, x_trial): 36 | if self.eig_vectors is None: 37 | raise RuntimeError('No filters available. Please first fit CSP ' 38 | 'decomposition.') 39 | z_trial = np.matmul(self.eig_vectors, x_trial) 40 | z_trial_selected = z_trial[:self.m_filters, :] 41 | z_trial_selected = np.append(z_trial_selected, z_trial[-self.m_filters:, :], axis=0) 42 | sum_z2 = np.sum(z_trial_selected ** 2, axis=1) 43 | sum_z = np.sum(z_trial_selected, axis=1) 44 | var_z = (sum_z2 - (sum_z ** 2) / z_trial_selected.shape[1]) / (z_trial_selected.shape[1] - 1) 45 | sum_var_z = sum(var_z) 46 | return np.log(var_z / sum_var_z) 47 | 48 | 49 | class FBCSP(TransformerMixin, BaseEstimator): 50 | def __init__(self, m_filters=4): 51 | self.m_filters = m_filters 52 | self.fbcsp_filters_multi = [] 53 | self.csps = [] 54 | 55 | def fit(self, x_train_fb, y_train): 56 | y_classes_unique = np.unique(y_train) 57 | n_classes = len(y_classes_unique) 58 | 59 | def get_csp(x_train_fb, y_train_cls): 60 | fbcsp_filters = {} 61 | for j in range(x_train_fb.shape[0]): 62 | x_train = x_train_fb[j, :, :, :] 63 | fbcsp_filters[j] = CSP(self.m_filters).fit(x_train, y_train_cls) 64 | return fbcsp_filters 65 | 66 | for i in range(n_classes): 67 | cls_of_interest = y_classes_unique[i] 68 | select_class_labels = lambda cls, y_labels: [0 if y == cls else 1 for y in y_labels] 69 | y_train_cls = np.asarray(select_class_labels(cls_of_interest, y_train)) 70 | fbcsp_filters = get_csp(x_train_fb, y_train_cls) 71 | self.fbcsp_filters_multi.append(fbcsp_filters) 72 | 73 | return self 74 | 75 | def transform(self, x_data, class_idx=0): 76 | n_fbanks, n_trials, n_channels, n_samples = x_data.shape 77 | x_features = np.zeros((n_trials, self.m_filters * 2 * len(x_data)), dtype=np.float) 78 | for i in range(n_fbanks): 79 | for k in range(n_trials): 80 | x_trial = np.copy(x_data[i, k, :, :]) 81 | csp_feat = self.fbcsp_filters_multi[i][k].transform(x_trial) 82 | for j in range(self.m_filters): 83 | x_features[k, i * self.m_filters * 2 + (j + 1) * 2 - 2] = csp_feat[j] 84 | x_features[k, i * self.m_filters * 2 + (j + 1) * 2 - 1] = csp_feat[-j - 1] 85 | 86 | return x_features 87 | 88 | 89 | class FilterBank: 90 | def __init__(self, fs): 91 | self.fs = fs 92 | self.f_trans = 2 93 | self.f_pass = np.arange(4, 40, 4) 94 | self.f_width = 4 95 | self.gpass = 3 96 | self.gstop = 30 97 | self.filter_coeff = {} 98 | 99 | def get_filter_coeff(self): 100 | Nyquist_freq = self.fs / 2 101 | 102 | for i, f_low_pass in enumerate(self.f_pass): 103 | f_pass = np.asarray([f_low_pass, f_low_pass + self.f_width]) 104 | f_stop = np.asarray([f_pass[0] - self.f_trans, f_pass[1] + self.f_trans]) 105 | wp = f_pass / Nyquist_freq 106 | ws = f_stop / Nyquist_freq 107 | order, wn = signal.cheb2ord(wp, ws, self.gpass, self.gstop) 108 | b, a = signal.cheby2(order, self.gstop, ws, btype='bandpass') 109 | self.filter_coeff.update({i: {'b': b, 'a': a}}) 110 | 111 | return self.filter_coeff 112 | 113 | def filter_data(self, eeg_data, window_details={}): 114 | n_trials, n_channels, n_samples = eeg_data.shape 115 | if window_details: 116 | n_samples = int(self.fs * (window_details.get('tmax') - window_details.get('tmin'))) + 1 117 | filtered_data = np.zeros((len(self.filter_coeff), n_trials, n_channels, n_samples)) 118 | for i, fb in self.filter_coeff.items(): 119 | b = fb.get('b') 120 | a = fb.get('a') 121 | eeg_data_filtered = np.asarray([signal.lfilter(b, a, eeg_data[j, :, :]) for j in range(n_trials)]) 122 | if window_details: 123 | eeg_data_filtered = eeg_data_filtered[:, :, int((4.5 + window_details.get('tmin')) * self.fs):int( 124 | (4.5 + window_details.get('tmax')) * self.fs) + 1] 125 | filtered_data[i, :, :, :] = eeg_data_filtered 126 | 127 | return filtered_data 128 | -------------------------------------------------------------------------------- /bionic_apps/external_connections/lsl/DataSender.py: -------------------------------------------------------------------------------- 1 | """Example program to demonstrate how to send a multi-channel time-series 2 | with proper meta-data to LSL.""" 3 | 4 | import time 5 | 6 | import numpy as np 7 | from mne.io import read_raw, concatenate_raws 8 | from pylsl import StreamInfo, StreamOutlet, local_clock 9 | 10 | from bionic_apps.utils import init_base_config 11 | 12 | M_BRAIN_TRAIN = 'mBrainTrain' 13 | 14 | 15 | def get_wave(f, t): 16 | return np.sin(f * np.pi * 2 * t) 17 | 18 | 19 | def get_artificial_data(ch_num, length, fs, max_dif=0.01): 20 | t = np.linspace(0, length, length * fs) 21 | signal = get_wave(5, t) + get_wave(11, t) + get_wave(17, t) + get_wave(27, t) 22 | signal = signal / np.linalg.norm(signal) 23 | data = [signal for _ in range(ch_num)] 24 | data = np.array(data) * max_dif 25 | return data 26 | 27 | 28 | def get_data_with_labels(raw): # only for pilot data 29 | from mne import events_from_annotations 30 | fs = raw.info['sfreq'] 31 | events, event_id = events_from_annotations(raw) 32 | 33 | from_ = [i for i, el in enumerate(events[:, 2]) if el == 1001][3:] # init stim 34 | to_ = [i for i, el in enumerate(events[:, 2]) if el == 12][3:] # end stim 35 | from_ = events[from_, 0] 36 | to_ = events[to_, 0] 37 | 38 | raws = [raw.copy().crop(frm / fs, to / fs + 1) for frm, to in zip(from_, to_)] 39 | 40 | raw = raws.pop(0) 41 | for r in raws: 42 | raw.append(r) 43 | del raws 44 | 45 | events, event_id = events_from_annotations(raw) 46 | events[:, 0] = events[:, 0] - raw.first_samp 47 | 48 | ev = {ev[0]: ev[2] for ev in events} 49 | data = raw.get_data() 50 | return data, ev, raw 51 | 52 | 53 | def run(filenames, get_labels=False, eeg_type='', use_artificial_data=False, host='myuid1236', add_extra_data=False): 54 | if isinstance(filenames, str): 55 | filenames = [filenames] 56 | raw = concatenate_raws([read_raw(file) for file in filenames]) 57 | 58 | # strip channel names of "." characters 59 | raw.rename_channels(lambda x: x.strip('.')) 60 | 61 | FS = raw.info['sfreq'] 62 | electrodes = list() 63 | 64 | if eeg_type == M_BRAIN_TRAIN: 65 | # T9 / Tp9, T10 / Tp10 66 | electrodes = ['Fp1', 'Fp2', 'Fz', 'F7', 'F8', 'Fc1', 'Fc2', 'Cz', 67 | 'C3', 'C4', 'T7', 'T8', 'Cpz', 'Cp1', 'Cp2', 'Cp5', 68 | 'Cp6', 'T9', 'T10', 'Pz', 'P3', 'P4', 'O1', 'O2'] 69 | raw = raw.pick_channels(electrodes) 70 | 71 | electrodes = raw.info['ch_names'].copy() 72 | 73 | if get_labels: 74 | electrodes.append('trigger') 75 | 76 | ch_num = len(electrodes) if not add_extra_data else len(electrodes) + 1 77 | info = StreamInfo('MNE', 'EEG', ch_num, FS, 'double64', host) 78 | 79 | # append some meta-data 80 | info.desc().append_child_value("manufacturer", "MNE") 81 | channels = info.desc().append_child("channels") 82 | 83 | for c in electrodes: 84 | channels.append_child("channel") \ 85 | .append_child_value("label", c) \ 86 | .append_child_value("unit", "microvolts") \ 87 | .append_child_value("type", "EEG") 88 | 89 | if add_extra_data: 90 | channels.append_child("channel") \ 91 | .append_child_value("label", "tmp") \ 92 | .append_child_value("unit", "microvolts") \ 93 | .append_child_value("type", "TMP") 94 | 95 | # next make an outlet; we set the transmission chunk size to 32 samples and 96 | # the outgoing buffer size to 360 seconds (max.) 97 | outlet = StreamOutlet(info, 32, 360) 98 | print("now sending data...") 99 | 100 | if use_artificial_data: 101 | data = get_artificial_data(24, 2 * 60, FS) 102 | else: 103 | if get_labels: 104 | data, ev, _ = get_data_with_labels(raw) 105 | else: 106 | data = raw.get_data() 107 | data *= 1e6 108 | 109 | if add_extra_data: 110 | d = data 111 | ch, t = d.shape 112 | data = np.zeros((ch + 1, t)) 113 | data[:-1, :] = d 114 | 115 | stim = 1 116 | prevstamp = local_clock() - 0.125 - 1 / FS 117 | stims = list() 118 | for t in range(np.size(data, axis=1)): 119 | # tic = time.time() 120 | 121 | mysample = list(data[:, t]) 122 | if get_labels: 123 | stim = ev.get(t, stim) 124 | stims.append(stim) 125 | mysample.append(stim) 126 | # get a time stamp in seconds (we pretend that our samples are actually 127 | # 125ms old, e.g., as if coming from some external hardware) 128 | stamp = local_clock() - 0.125 129 | real_sleep_time = stamp - prevstamp 130 | # print('time spent in sleep in hz: {}'.format(1 / (real_sleep_time))) 131 | corr = (FS - 1 / real_sleep_time) 132 | # print('time diff: {} %'.format(real_sleep_time * FS * 100 - 100)) 133 | prevstamp = stamp 134 | # now send it and wait for a bit 135 | outlet.push_sample(mysample, stamp) 136 | time_to_sleep = 1 / (FS + corr) # max(0, sleep_time - (time.time() - tic)) 137 | # print('time to sleep in hz: {}'.format(1 / (ts))) 138 | # time.sleep(ts) 139 | toc = time.time() + time_to_sleep # Busy waiting for realtime sleep on Windows... 140 | while time.time() < toc: 141 | pass 142 | 143 | diffstim = set(stims) 144 | d = {st: 0 for st in diffstim} 145 | for st in stims: 146 | d[st] += 1 147 | print('sent stim:', d) 148 | 149 | 150 | if __name__ == '__main__': 151 | from bionic_apps.handlers import select_files_in_explorer 152 | 153 | base_dir = init_base_config('../../../') 154 | files = select_files_in_explorer(base_dir) 155 | 156 | run(files, get_labels=False, add_extra_data=True) 157 | -------------------------------------------------------------------------------- /bionic_apps/databases/emg/putemg_download.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import urllib.request 4 | import re 5 | 6 | BASE_URL = "https://chmura.put.poznan.pl/s/G285gnQVuCnfQAx/download?path=%2F" 7 | 8 | VIDEO_1080p_DIR = "Video-1080p" 9 | VIDEO_576p_DIR = "Video-576p" 10 | DEPTH_DIR = "Depth" 11 | DATA_HDF5_DIR = "Data-HDF5" 12 | DATA_CSV_DIR = "Data-CSV" 13 | 14 | 15 | def usage(): 16 | print("Usage: {:s} [ ...]".format(os.path.basename(__file__))) 17 | print() 18 | print("Arguments:") 19 | print(" comma-separated list of experiment types " 20 | "(supported types: emg_gestures, emg_force)") 21 | print(" comma-separated list of media " 22 | "(supported types: data-csv, data-hdf5, depth, video-1080p, video-576p)") 23 | print(" [ ...] optional list of two-digit participant IDs, fetches all if none are given") 24 | print() 25 | print("Examples:") 26 | print("{:s} emg_gestures data-hdf5,video-1080p".format(os.path.basename(__file__))) 27 | print("{:s} emg_gestures,emg_force data-csv,depth 03 04 07".format(os.path.basename(__file__))) 28 | exit(1) 29 | 30 | 31 | def parse_record(name): 32 | experiment_name_regexp = r"^(?P\w*)-(?P\d{2})-(?P\w*)-" \ 33 | r"(?P\d{4}-\d{2}-\d{2})-(?P