def move(self, dir, dx, overlay=False, barcolor=None, caption='', caption_color='W'): if barcolor is None: if dx == self.xl2: c = 'G' else: c = 'R' else: c = barcolor self.glass.fullbar_color(c) color = self.color[c] if dir == 'L': if self.pc_feedback: self.img = self.left_images[dx] if self.glass_feedback: self.glass.move_bar(dir, dx, overlay) elif dir == 'R': if self.pc_feedback: self.img = self.right_images[dx] if self.glass_feedback: self.glass.move_bar(dir, dx, overlay) else: qc.print_c('(viz_bars.py) ERROR: Unknown direction %s' % dir, 'r') self.put_text(caption, caption_color) self.update()
def event_timestamps_to_indices(sigfile, eventfile): """ Convert LSL timestamps to sample indices for separetely recorded events. Parameters: sigfile: raw signal file (Python Pickle) recorded with stream_recorder.py. eventfile: event file where events are indexed with LSL timestamps. Returns: events list, which can be used as an input to mne.io.RawArray.add_events(). """ raw = qc.load_obj(sigfile) ts = raw['timestamps'].reshape(-1) ts_min = min(ts) ts_max = max(ts) events = [] with open(eventfile) as f: for l in f: data = l.strip().split('\t') event_ts = float(data[0]) event_value = int(data[2]) # find the first index not smaller than ts next_index = np.searchsorted(ts, event_ts) if next_index >= len(ts): qc.print_c( '** WARNING: Event %d at time %.3f is out of time range (%.3f - %.3f).' % (event_value, event_ts, ts_min, ts_max), 'y') else: events.append([next_index, 0, event_value]) # print(events[-1]) return events
def any2fif(filename, interactive=False, outdir=None, channel_file=None): """ Generic file format converter """ p = qc.parse_path(filename) if outdir is not None: qc.make_dirs(outdir) if p.ext == 'pcl': eve_file = '%s/%s.txt' % (p.dir, p.name.replace('raw', 'eve')) if os.path.exists(eve_file): qc.print_c('Adding events from %s' % eve_file, 'G') else: eve_file = None pcl2fif(filename, interactive=interactive, outdir=outdir, external_event=eve_file) elif p.ext == 'eeg': eeg2fif(filename, interactive=interactive, outdir=outdir) elif p.ext in ['edf', 'bdf']: bdf2fif(filename, interactive=interactive, outdir=outdir) elif p.ext == 'gdf': gdf2fif(filename, interactive=interactive, outdir=outdir, channel_file=channel_file) elif p.ext == 'xdf': xdf2fif(filename, interactive=interactive, outdir=outdir) else: # unknown format qc.print_c('WARNING: Ignored unrecognized file extension %s. It should be [.pcl | .eeg | .gdf | .bdf]'\ % p.ext, 'r')
def bdf2fif(filename, interactive=False, outdir=None): """ EDF or BioSemi BDF format """ # convert to mat using MATLAB (MNE's edf reader has an offset bug) fdir, fname, fext = qc.parse_path_list(filename) if outdir is None: outdir = fdir elif outdir[-1] != '/': outdir += '/' fiffile = outdir + fname + '.fif' raw = mne.io.read_raw_edf(filename, preload=True) # process event channel if raw.info['chs'][-1]['ch_name'] != 'STI 014': qc.print_c("*** ERROR: The last channel (%s) doesn't seem to be an event channel. Entering debugging mode." % raw.info['chs'][-1]['ch_name']) pdb.set_trace() raw.info['chs'][-1]['ch_name'] = 'TRIGGER' events = mne.find_events(raw, stim_channel='TRIGGER', shortest_event=1, uint_cast=True, consecutive=True) events[:, 2] -= events[:, 1] # set offset to 0 events[:, 1] = 0 # move the event channel to index 0 (for consistency) raw._data = np.concatenate((raw._data[-1, :].reshape(1, -1), raw._data[:-1, :])) raw._data[0] *= 0 # init the event channel raw.info['chs'] = [raw.info['chs'][-1]] + raw.info['chs'][:-1] # add events raw.add_events(events, 'TRIGGER') # save and close raw.save(fiffile, verbose=False, overwrite=True, fmt='double') print('Saved to', fiffile)
def main(record_dir, eeg_only=False): # configure LSL server name and device serial if available if len(sys.argv) == 2: amp_name = sys.argv[1] amp_serial = None elif len(sys.argv) == 3: amp_name, amp_serial = sys.argv[1:3] else: amp_name, amp_serial = pu.search_lsl(ignore_markers=True) if amp_name == 'None': amp_name = None qc.print_c('\nOutput directory: %s' % (record_dir), 'W') # spawn the recorder as a child process qc.print_c('\n>> Press Enter to start recording.', 'G') key = input() state = mp.Value('i', 1) proc = mp.Process(target=record, args=[state, amp_name, amp_serial, record_dir, eeg_only]) proc.start() # clean up time.sleep(1) # required on some Python distribution input() state.value = 0 qc.print_c('(main) Waiting for recorder process to finish.', 'W') proc.join(10) if proc.is_alive(): qc.print_c( '>> ERROR: Recorder process not finishing. Are you running from Spyder?', 'R') qc.print_c('Dropping into a shell', 'R') qc.shell() sys.stdout.flush() print('>> Done.')
def eeg2fif(filename, interactive=False, outdir=None): """ Brain Products EEG format """ fdir, fname, fext = qc.parse_path_list(filename) if outdir is None: outdir = fdir elif outdir[-1] != '/': outdir += '/' eegfile = fdir + fname + '.eeg' matfile = fdir + fname + '.mat' markerfile = fdir + fname + '.vmrk' fiffile = outdir + fname + '.fif' # convert to mat using MATLAB if not os.path.exists(matfile): print('Converting input to mat file') run = "[sig,header]=sload('%s'); save('%s','sig','header');" % (eegfile, matfile) qc.matlab(run) if not os.path.exists(matfile): qc.print_c('>> ERROR: mat file convertion error.', 'r') sys.exit() else: print('MAT file already exists. Skipping conversion.') # extract events events = [] for l in open(markerfile): if 'Stimulus,S' in l: # event, sample_index= l.split(' ')[-1].split(',')[:2] data = l.split(',')[1:3] event = int(data[0][1:]) # ignore 'S' sample_index = int(data[1]) events.append([sample_index, 0, event]) # load data and create fif header mat = scipy.io.loadmat(matfile) # headers= mat['header'] sample_rate = int(mat['header']['SampleRate']) signals = mat['sig'].T # channels x samples nch, t_len = signals.shape ch_names = ['TRIGGER'] + ['CH%d' % (x + 1) for x in range(nch)] ch_info = ['stim'] + ['eeg'] * (nch) info = mne.create_info(ch_names, sample_rate, ch_info, montage='standard_1005') # add event channel eventch = np.zeros([1, signals.shape[1]]) signals = np.concatenate((eventch, signals), axis=0) # create Raw object raw = mne.io.RawArray(signals, info) # add events raw.add_events(events, 'TRIGGER') # save and close raw.save(fiffile, verbose=False, overwrite=True, fmt='double') print('Saved to', fiffile)
def record(recordState, amp_name, amp_serial, record_dir, eeg_only, recordLogger=logger, queue=None): redirect_stdout_to_queue(recordLogger, queue, 'INFO') # set data file name timestamp = time.strftime('%Y%m%d-%H%M%S', time.localtime()) pcl_file = "%s/%s-raw.pcl" % (record_dir, timestamp) eve_file = '%s/%s-eve.txt' % (record_dir, timestamp) recordLogger.info('>> Output file: %s' % (pcl_file)) # test writability try: qc.make_dirs(record_dir) open(pcl_file, 'w').write('The data will written when the recording is finished.') except: raise RuntimeError('Problem writing to %s. Check permission.' % pcl_file) # start a server for sending out data pcl_file when software trigger is used outlet = start_server('StreamRecorderInfo', channel_format='string',\ source_id=eve_file, stype='Markers') # connect to EEG stream server sr = StreamReceiver(buffer_size=0, amp_name=amp_name, amp_serial=amp_serial, eeg_only=eeg_only) # start recording recordLogger.info('\n>> Recording started (PID %d).' % os.getpid()) qc.print_c('\n>> Press Enter to stop recording', 'G') tm = qc.Timer(autoreset=True) next_sec = 1 while recordState.value == 1: sr.acquire() if sr.get_buflen() > next_sec: duration = str(datetime.timedelta(seconds=int(sr.get_buflen()))) recordLogger.info('RECORDING %s' % duration) next_sec += 1 tm.sleep_atleast(0.001) # record stop recordLogger.info('>> Stop requested. Copying buffer') buffers, times = sr.get_buffer() signals = buffers events = None # channels = total channels from amp, including trigger channel data = {'signals':signals, 'timestamps':times, 'events':events, 'sample_rate':sr.get_sample_rate(), 'channels':sr.get_num_channels(), 'ch_names':sr.get_channel_names(), 'lsl_time_offset':sr.lsl_time_offset} recordLogger.info('Saving raw data ...') qc.save_obj(pcl_file, data) recordLogger.info('Saved to %s\n' % pcl_file) # automatically convert to fif and use event file if it exists (software trigger) if os.path.exists(eve_file): recordLogger.info('Found matching event file, adding events.') else: eve_file = None recordLogger.info('Converting raw file into fif.') pcl2fif(pcl_file, external_event=eve_file)
def check_cfg(cfg): if not hasattr(cfg, 'POSITIVE_FEEDBACK'): qc.print_c('Warning: POSITIVE_FEEDBACK undefined. Setting it to False.', 'Y') cfg.POSITIVE_FEEDBACK = False if not hasattr(cfg, 'BAR_REACH_FINISH'): qc.print_c('Warning: BAR_REACH_FINISH undefined. Setting it to False.', 'Y') cfg.BAR_REACH_FINISH = False return cfg
def move(self, dir, dx, overlay=False, barcolor=None, caption='', caption_color='W'): if not overlay: self.draw_cue() if barcolor is None: if dx == self.xl2: c = 'G' else: c = 'R' else: c = barcolor self.glass.fullbar_color(c) color = self.color[c] if dir == 'L': if self.pc_feedback: cv2.rectangle(self.img, (self.xl1 - dx, self.yl1), (self.xl1, self.yr1), color, -1) if self.glass_feedback: self.glass.move_bar(dir, dx, overlay) elif dir == 'U': if self.pc_feedback: cv2.rectangle(self.img, (self.xl1, self.yl1 - dx), (self.xr1, self.yl1), color, -1) if self.glass_feedback: self.glass.move_bar(dir, dx, overlay) elif dir == 'R': if self.pc_feedback: cv2.rectangle(self.img, (self.xr1, self.yl1), (self.xr1 + dx, self.yr1), color, -1) if self.glass_feedback: self.glass.move_bar(dir, dx, overlay) elif dir == 'D': if self.pc_feedback: cv2.rectangle(self.img, (self.xl1, self.yr1), (self.xr1, self.yr1 + dx), color, -1) if self.glass_feedback: self.glass.move_bar(dir, dx, overlay) elif dir == 'B': if self.pc_feedback: cv2.rectangle(self.img, (self.xl1 - dx, self.yl1), (self.xl1, self.yr1), color, -1) cv2.rectangle(self.img, (self.xr1, self.yl1), (self.xr1 + dx, self.yr1), color, -1) if self.glass_feedback: self.glass.move_bar('S', dx, overlay) else: qc.print_c('(viz_bars.py) ERROR: Unknown direction %s' % dir, 'r') self.put_text(caption, caption_color)
def load_cfg(cfg_module): cfg = imp.load_source(cfg_module, cfg_module) critical_vars = [ 'CLS_MI', 'TRIGGER_DEVICE', 'TRIGGER_DEF', 'DIRECTIONS', 'TRIALS_EACH', 'PROB_ALPHA_NEW' ] optional_vars = { 'AMP_NAME': None, 'AMP_SERIAL': None, 'FAKE_CLS': None, 'TRIALS_RANDOMIZE': True, 'BAR_SLOW_START': None, 'PARALLEL_DECODING': None, 'SHOW_TRIALS': True, 'FREE_STYLE': False, 'REFRESH_RATE': 30, 'BAR_BIAS': None, 'POSITIVE_FEEDBACK': False, 'BAR_REACH_FINISH': False, 'FEEDBACK_TYPE': 'BAR', 'BAR_STEP_LEFT': 6, 'BAR_STEP_RIGHT': 6, 'BAR_STEP_UP': 6, 'BAR_STEP_DOWN': 6, 'BAR_STEP_BOTH': 6, 'LOG_PROBS': False, 'SHOW_CUE': True, 'WITH_STIMO': False, 'SCREEN_SIZE': (1920, 1080), 'SCREEN_POS': (0, 0), 'WITH_REX': False, 'DEBUG_PROBS': False, 'LOG_PROBS': False } if not (hasattr(cfg, 'BAR_STEP') or hasattr(cfg, 'BAR_STEP_LEFT') or\ hasattr(cfg, 'BAR_STEP_RIGHT') or hasattr(cfg, 'BAR_STEP_UP') or\ hasattr(cfg, 'BAR_STEP_DOWN') or hasattr(cfg, 'BAR_STEP_BOTH')): raise RuntimeError( 'BAR_STEP or at least two of BAR_STEP_* must be defined.') for key in critical_vars: if not hasattr(cfg, key): raise RuntimeError('%s not defined in config.' % key) for key in optional_vars: if not hasattr(cfg, key): setattr(cfg, key, optional_vars[key]) qc.print_c( 'load_cfg(): Setting undefined parameter %s=%s' % (key, getattr(cfg, key)), 'Y') return cfg
def run(fif_file): print('Loading "%s"' % fif_file) raw, events = pu.load_raw(fif_file) print('Raw info: %s' % raw) print('Channels: %s' % ', '.join(raw.ch_names)) print('Events: %s' % set(events[:, 2])) print('Sampling freq: %.3f Hz' % raw.info['sfreq']) qc.print_c('\n>> Interactive mode start. Type quit or Ctrl+D to finish', 'g') qc.print_c('>> Variables: raw, events\n', 'g') embed()
def search_lsl(ignore_markers=False): import time # look for LSL servers amp_list = [] amp_list_backup = [] while True: streamInfos = pylsl.resolve_streams() if len(streamInfos) > 0: for index, si in enumerate(streamInfos): # LSL XML parser has a bug which crashes so do not use for now #desc = pylsl.StreamInlet(si).info().desc() #amp_serial = desc.child('acquisition').child_value('serial_number').strip() amp_serial = 'N/A' # serial number not supported yet amp_name = si.name() if 'Markers' in amp_name: amp_list_backup.append((index, amp_name, amp_serial)) else: amp_list.append((index, amp_name, amp_serial)) break print('No server available yet on the network...') time.sleep(1) if ignore_markers is False: amp_list += amp_list_backup qc.print_c('-- List of servers --', 'W') for i, (index, amp_name, amp_serial) in enumerate(amp_list): if amp_serial == '': amp_ser = 'N/A' else: amp_ser = amp_serial qc.print_c('%d: %s (Serial %s)' % (i, amp_name, amp_ser), 'W') if len(amp_list) == 1: index = 0 else: index = input( 'Amp index? Hit enter without index to select the first server.\n>> ' ) if index.strip() == '': index = 0 else: index = int(index.strip()) amp_index, amp_name, amp_serial = amp_list[index] si = streamInfos[amp_index] assert amp_name == si.name() # LSL XML parser has a bug which crashes so do not use for now #assert amp_serial == pylsl.StreamInlet(si).info().desc().child('acquisition').child_value('serial_number').strip() print('Selected %s (Serial: %s)' % (amp_name, amp_serial)) return amp_name, amp_serial
def convert2mat(filename, matfile): """ Convert to mat using MATLAB BioSig sload(). """ basename = '.'.join(filename.split('.')[:-1]) # extension= filename.split('.')[-1] matfile = basename + '.mat' if not os.path.exists(matfile): print('>> Converting input to mat file') run = "[sig,header]=sload('%s'); save('%s.mat','sig','header');" % (filename, basename) qc.matlab(run) if not os.path.exists(matfile): qc.print_c('>> ERROR: mat file convertion error.', 'r') sys.exit()
def init_loop(self): self.updating = False self.sr = StreamReceiver(window_size=1, buffer_size=10, amp_serial=self.amp_serial, amp_name=self.amp_name) srate = int(self.sr.sample_rate) # n_channels= self.sr.channels # 12 unsigned ints (4 bytes) ########## TODO: assumkng 32 samples chunk => make it read from LSL header data = [ 'EEG', srate, ['L', 'R'], 32, len(self.sr.get_eeg_channels()), 0, self.sr.get_trigger_channel(), None, None, None, None, None ] qc.print_c('Trigger channel is %d' % self.sr.get_trigger_channel(), 'w') self.config = { 'id': data[0], 'sf': data[1], 'labels': data[2], 'samples': data[3], 'eeg_channels': data[4], 'exg_channels': data[5], 'tri_channels': data[6], 'eeg_type': data[8], 'exg_type': data[9], 'tri_type': data[10], 'lbl_type': data[11], 'tim_size': 1, 'idx_size': 1 } self.tri = np.zeros(self.config['samples']) self.last_tri = 0 self.eeg = np.zeros( (self.config['samples'], self.config['eeg_channels']), dtype=np.float) self.exg = np.zeros( (self.config['samples'], self.config['exg_channels']), dtype=np.float) self.ts_list = [] self.ts_list_tri = []
def load_cfg(cfg_module): mandatory = { 'TRIGGER_DEVICE': 'Arduino', 'TRIGGER_DEF': 'triggerdef_16.ini', 'SCREEN_SIZE': (1680, 1050), 'SCREEN_POS': (0, 0), 'DIRECTIONS': ['L', 'R'], 'DIR_RANDOMIZE': False, 'TRIALS_EACH': 10, 'GAIT_STEPS': 1, 'T_INIT': 5, 'T_GAP': 5, 'T_CUE': 0.1, 'T_DIR_READY': 2, 'T_DIR': 2.5, 'T_RETURN': 1, 'T_STOP': 3, } optional = { 'FEEDBACK_TYPE': 'BAR', 'T_RETURN': 2, 'DIR_RANDOMIZE': True, 'WITH_STIMO': False, 'GLASS_USE': False, 'REFRESH_RATE': 30, 'RANDOMIZE_LENGTH': 0 } cfg = imp.load_source(cfg_module, cfg_module) for key in mandatory: if not hasattr(cfg, key): raise ValueError('%s is a required parameter' % key) for key in optional: if not hasattr(cfg, key): qc.print_c('Warning: Setting undefined %s=%s' % (key, optional[key])) return cfg
def print(self, msg, color='W'): qc.print_c('[StreamReceiver] %s' % msg, color)
amp_name = cfg.AMP_NAME amp_serial = cfg.AMP_SERIAL fake_dirs = None else: amp_name = None amp_serial = None fake_dirs = [v for (k, v) in cfg.DIRECTIONS] # events and triggers tdef = trigger_def(cfg.TRIGGER_DEF) if cfg.TRIGGER_DEVICE is None: input('\n** Warning: No trigger device set. Press Ctrl+C to stop or Enter to continue.') trigger = pyLptControl.Trigger(cfg.TRIGGER_DEVICE) if trigger.init(50) == False: qc.print_c('\n** Error connecting to USB2LPT device. Use a mock trigger instead?', 'R') input('Press Ctrl+C to stop or Enter to continue.') trigger = pyLptControl.MockTrigger() trigger.init(50) # init classification qc.print_c('Initializing decoder.', 'W') decoder_UD = BCIDecoder(cfg.CLS_MI, buffer_size=10.0, fake=(cfg.FAKE_CLS is not None), amp_name=amp_name, amp_serial=amp_serial, fake_dirs=fake_dirs) labels = [tdef.by_value[x] for x in decoder_UD.get_labels()] assert 'UP' in labels and 'DOWN' in labels bar_def_UD = {label:dir for dir, label in cfg.DIRECTIONS} bar_dirs_UD = [bar_def[l] for l in labels] while decoder_UD.is_running() is 0: time.sleep(0.01)
sfreq=sfreq, fmin=fmin, fmax=fmax, bandwidth=None, adaptive=False, low_bias=True, n_jobs=1, normalization='length', verbose=None ) while True: sr.acquire() window, tslist = sr.get_window() # window = [samples x channels] window = window.T # chanel x samples # print event values tsnew = np.where(np.array(tslist) > last_ts)[0][0] trigger = np.unique(window[trg_ch, tsnew:]) if len(trigger) > 0: qc.print_c('Triggers: %s' % np.array(trigger), 'G') # print('[%.1f] Receiving data...' % watchdog.sec()) # ADD YOUR CODE unused_channels = ['TRIGGER', 'X1', 'X2', 'X3', 'A2'] brainhack.eeg_ch_names = EEG_CH_NAMES.copy() brainhack.window_signal = window brainhack.remove_unused_channels(unused_channels) brainhack.convert_to_mne_obj() brainhack.filter_signal(start_freq=fmin, stop_freq=fmax) brainhack.convert_mne_back_to_np_array() brainhack.multiply_inverse_solution() window = brainhack.big_array_with_a_lot_of_sources
def compute_features(cfg): # Load file list ftrain = [] for f in qc.get_file_list(cfg.DATADIR, fullpath=True): if f[-4:] in ['.fif', '.fiff']: ftrain.append(f) # Preprocessing, epoching and PSD computation if len(ftrain) > 1 and cfg.CHANNEL_PICKS is not None and type( cfg.CHANNEL_PICKS[0]) == int: raise RuntimeError( 'When loading multiple EEG files, CHANNEL_PICKS must be list of string, not integers because they may have different channel order.' ) raw, events = pu.load_multi(ftrain) if cfg.REF_CH is not None: pu.rereference(raw, cfg.REF_CH[1], cfg.REF_CH[0]) if cfg.LOAD_EVENTS_FILE is not None: events = mne.read_events(cfg.LOAD_EVENTS_FILE) triggers = {cfg.tdef.by_value[c]: c for c in set(cfg.TRIGGER_DEF)} # Pick channels if cfg.CHANNEL_PICKS is None: chlist = [int(x) for x in pick_types(raw.info, stim=False, eeg=True)] else: chlist = cfg.CHANNEL_PICKS picks = [] for c in chlist: if type(c) == int: picks.append(c) elif type(c) == str: picks.append(raw.ch_names.index(c)) else: raise RuntimeError( 'CHANNEL_PICKS has a value of unknown type %s.\nCHANNEL_PICKS=%s' % (type(c), cfg.CHANNEL_PICKS)) if cfg.EXCLUDES is not None: for c in cfg.EXCLUDES: if type(c) == str: if c not in raw.ch_names: qc.print_c( 'Warning: Exclusion channel %s does not exist. Ignored.' % c, 'Y') continue c_int = raw.ch_names.index(c) elif type(c) == int: c_int = c else: raise RuntimeError( 'EXCLUDES has a value of unknown type %s.\nEXCLUDES=%s' % (type(c), cfg.EXCLUDES)) if c_int in picks: del picks[picks.index(c_int)] if max(picks) > len(raw.ch_names): raise ValueError( '"picks" has a channel index %d while there are only %d channels.' % (max(picks), len(raw.ch_names))) if hasattr(cfg, 'SP_CHANNELS') and cfg.SP_CHANNELS is not None: qc.print_c( 'compute_features(): SP_CHANNELS parameter is not supported yet. Will be set to CHANNEL_PICKS.', 'Y') if hasattr(cfg, 'TP_CHANNELS') and cfg.TP_CHANNELS is not None: qc.print_c( 'compute_features(): TP_CHANNELS parameter is not supported yet. Will be set to CHANNEL_PICKS.', 'Y') if hasattr(cfg, 'NOTCH_CHANNELS') and cfg.NOTCH_CHANNELS is not None: qc.print_c( 'compute_features(): NOTCH_CHANNELS parameter is not supported yet. Will be set to CHANNEL_PICKS.', 'Y') # Read epochs try: # Experimental: multiple epoch ranges if type(cfg.EPOCH[0]) is list: epochs_train = [] for ep in cfg.EPOCH: epoch = Epochs(raw, events, triggers, tmin=ep[0], tmax=ep[1], proj=False, picks=picks, baseline=None, preload=True, verbose=False, detrend=None) # Channels are already selected by 'picks' param so use all channels. pu.preprocess(epoch, spatial=cfg.SP_FILTER, spatial_ch=None, spectral=cfg.TP_FILTER, spectral_ch=None, notch=cfg.NOTCH_FILTER, notch_ch=None, multiplier=cfg.MULTIPLIER, n_jobs=cfg.N_JOBS) epochs_train.append(epoch) else: # Usual method: single epoch range epochs_train = Epochs(raw, events, triggers, tmin=cfg.EPOCH[0], tmax=cfg.EPOCH[1], proj=False, picks=picks, baseline=None, preload=True, verbose=False, detrend=None) # Channels are already selected by 'picks' param so use all channels. pu.preprocess(epochs_train, spatial=cfg.SP_FILTER, spatial_ch=None, spectral=cfg.TP_FILTER, spectral_ch=None, notch=cfg.NOTCH_FILTER, notch_ch=None, multiplier=cfg.MULTIPLIER, n_jobs=cfg.N_JOBS) except: qc.print_c('\n*** (trainer.py) ERROR OCCURRED WHILE EPOCHING ***\n', 'R') # Catch and throw errors from child processes traceback.print_exc() if interactive: print('Dropping into a shell.\n') embed() raise RuntimeError label_set = np.unique(triggers.values()) # Compute features if cfg.FEATURES == 'PSD': featdata = get_psd_feature(epochs_train, cfg.EPOCH, cfg.PSD, feat_picks=None, n_jobs=cfg.N_JOBS) elif cfg.FEATURES == 'TIMELAG': ''' TODO: Implement multiple epochs for timelag feature ''' raise NotImplementedError( 'MULTIPLE EPOCHS NOT IMPLEMENTED YET FOR TIMELAG FEATURE.') elif cfg.FEATURES == 'WAVELET': ''' TODO: Implement multiple epochs for wavelet feature ''' raise NotImplementedError( 'MULTIPLE EPOCHS NOT IMPLEMENTED YET FOR WAVELET FEATURE.') else: raise NotImplementedError('%s feature type is not supported.' % cfg.FEATURES) featdata['picks'] = picks featdata['sfreq'] = raw.info['sfreq'] featdata['ch_names'] = raw.ch_names return featdata
def load_cfg(cfg_file): critical_vars = { 'COMMON': [ 'tdef', 'TRIGGER_DEF', 'EPOCH', 'DATADIR', 'PSD', 'CHANNEL_PICKS', 'SP_FILTER', 'TP_FILTER', 'NOTCH_FILTER', 'FEATURES', 'CLASSIFIER', 'CV_PERFORM' ], 'RF': ['trees', 'max_depth', 'seed'], 'GB': ['trees', 'learning_rate', 'max_depth', 'seed'] } # optional variables with default values optional_vars = { 'LOAD_PSD': False, 'MULTIPLIER': 1, 'EXPORT_GOOD_FEATURES': False, 'FEAT_TOPN': 20, 'EXPORT_CLS': False, 'USE_LOG': False, 'USE_CVA': False, 'REF_CH': None, 'N_JOBS': None, 'EXCLUDES': None, 'CV_IGNORE_THRES': None, 'CV_DECISION_THRES': None, 'BALANCE_SAMPLES': False } cfg_file = qc.forward_slashify(cfg_file) cfg = imp.load_source(cfg_file, cfg_file) for v in critical_vars['COMMON']: if not hasattr(cfg, v): raise RuntimeError('%s not defined in config.' % v) for key in optional_vars: if not hasattr(cfg, key): setattr(cfg, key, optional_vars[key]) qc.print_c( 'load_cfg(): Setting undefined parameter %s=%s' % (key, getattr(cfg, key)), 'Y') # classifier parameters check if cfg.CLASSIFIER == 'RF': if not hasattr(cfg, 'RF'): raise RuntimeError('"RF" not defined in config.') for v in critical_vars['RF']: if v not in cfg.RF: raise RuntimeError('%s not defined in config.' % v) elif cfg.CLASSIFIER == 'GB' or cfg.CLASSIFIER == 'XGB': if not hasattr(cfg, 'GB'): raise RuntimeError('"GB" not defined in config.') for v in critical_vars['GB']: if v not in cfg.GB: raise RuntimeError('%s not defined in config.' % v) elif cfg.CLASSIFIER == 'rLDA' and not hasattr(cfg, 'RLDA_REGULARIZE_COEFF'): raise RuntimeError('"RLDA_REGULARIZE_COEFF" not defined in config.') if cfg.CV_PERFORM is not None: if not hasattr(cfg, 'CV_RANDOM_SEED'): cfg.CV_RANDOM_SEED = None qc.print_c( 'load_cfg(): Setting undefined parameter CV_RANDOM_SEED=%s' % (cfg.CV_RANDOM_SEED), 'Y') if not hasattr(cfg, 'CV_FOLDS'): raise RuntimeError('"CV_FOLDS" not defined in config.') if cfg.CV_PERFORM == 'StratifiedShuffleSplit' and not hasattr( cfg, 'CV_TEST_RATIO'): raise RuntimeError('"CV_TEST_RATIO" not defined in config.') if cfg.N_JOBS is None: cfg.N_JOBS = mp.cpu_count() return cfg
def print(self, *args): qc.print_c('[pyLptControl] ', color='w', end='') print(*args)
def config_run(cfg_module): if not (os.path.exists(cfg_module) and os.path.isfile(cfg_module)): raise IOError('%s cannot be loaded.' % os.path.realpath(cfg_module)) cfg = load_cfg(cfg_module) if cfg.FAKE_CLS is None: # chooose amp if cfg.AMP_NAME is None and cfg.AMP_SERIAL is None: amp_name, amp_serial = pu.search_lsl(ignore_markers=True) else: amp_name = cfg.AMP_NAME amp_serial = cfg.AMP_SERIAL fake_dirs = None else: amp_name = None amp_serial = None fake_dirs = [v for (k, v) in cfg.DIRECTIONS] # events and triggers tdef = trigger_def(cfg.TRIGGER_DEF) if cfg.TRIGGER_DEVICE is None: input( '\n** Warning: No trigger device set. Press Ctrl+C to stop or Enter to continue.' ) trigger = pyLptControl.Trigger(cfg.TRIGGER_DEVICE) if trigger.init(50) == False: qc.print_c( '\n** Error connecting to USB2LPT device. Use a mock trigger instead?', 'R') input('Press Ctrl+C to stop or Enter to continue.') trigger = pyLptControl.MockTrigger() trigger.init(50) # init classification decoder = BCIDecoderDaemon(cfg.CLS_MI, buffer_size=1.0, fake=(cfg.FAKE_CLS is not None), amp_name=amp_name, amp_serial=amp_serial, fake_dirs=fake_dirs, parallel=cfg.PARALLEL_DECODING, alpha_new=cfg.PROB_ALPHA_NEW) # OLD: requires trigger values to be always defined #labels = [tdef.by_value[x] for x in decoder.get_labels()] # NEW: events can be mapped into integers: labels = [] dirdata = set([d[1] for d in cfg.DIRECTIONS]) for x in decoder.get_labels(): if x not in dirdata: labels.append(tdef.by_value[x]) else: labels.append(x) # map class labels to bar directions bar_def = {label: str(dir) for dir, label in cfg.DIRECTIONS} bar_dirs = [bar_def[l] for l in labels] dir_seq = [] for x in range(cfg.TRIALS_EACH): dir_seq.extend(bar_dirs) if cfg.TRIALS_RANDOMIZE: random.shuffle(dir_seq) else: dir_seq = [d[0] for d in cfg.DIRECTIONS] * cfg.TRIALS_EACH num_trials = len(dir_seq) qc.print_c('Initializing decoder.', 'W') while decoder.is_running() is 0: time.sleep(0.01) # bar visual object if cfg.FEEDBACK_TYPE == 'BAR': from pycnbi.protocols.viz_bars import BarVisual visual = BarVisual(cfg.GLASS_USE, screen_pos=cfg.SCREEN_POS, screen_size=cfg.SCREEN_SIZE) elif cfg.FEEDBACK_TYPE == 'BODY': assert hasattr(cfg, 'IMAGE_PATH'), 'IMAGE_PATH is undefined in your config.' from pycnbi.protocols.viz_human import BodyVisual visual = BodyVisual(cfg.IMAGE_PATH, use_glass=cfg.GLASS_USE, screen_pos=cfg.SCREEN_POS, screen_size=cfg.SCREEN_SIZE) visual.put_text('Waiting to start') if cfg.LOG_PROBS: logdir = qc.parse_path_list(cfg.CLS_MI)[0] probs_logfile = time.strftime(logdir + "probs-%Y%m%d-%H%M%S.txt", time.localtime()) else: probs_logfile = None feedback = Feedback(cfg, visual, tdef, trigger, probs_logfile) # start trial = 1 dir_detected = [] prob_history = {c: [] for c in bar_dirs} while trial <= num_trials: if cfg.SHOW_TRIALS: title_text = 'Trial %d / %d' % (trial, num_trials) else: title_text = 'Ready' true_label = dir_seq[trial - 1] # profiling feedback #import cProfile #pr = cProfile.Profile() #pr.enable() result = feedback.classify(decoder, true_label, title_text, bar_dirs, prob_history=prob_history) #pr.disable() #pr.print_stats(sort='time') if result is None: break else: pred_label = result dir_detected.append(pred_label) if cfg.WITH_REX is True and pred_label == true_label: # if cfg.WITH_REX is True: if pred_label == 'U': rex_dir = 'N' elif pred_label == 'L': rex_dir = 'W' elif pred_label == 'R': rex_dir = 'E' elif pred_label == 'D': rex_dir = 'S' else: qc.print_c( 'Warning: Rex cannot execute undefined action %s' % pred_label, 'W') rex_dir = None if rex_dir is not None: visual.move(pred_label, 100, overlay=False, barcolor='B') visual.update() qc.print_c('Executing Rex action %s' % rex_dir, 'W') os.system('%s/Rex/RexControlSimple.exe %s %s' % (pycnbi.ROOT, cfg.REX_COMPORT, rex_dir)) time.sleep(8) if true_label == pred_label: msg = 'Correct' else: msg = 'Wrong' if cfg.TRIALS_RETRY is False or true_label == pred_label: print('Trial %d: %s (%s -> %s)' % (trial, msg, true_label, pred_label)) trial += 1 if len(dir_detected) > 0: # write performance and log results fdir, _, _ = qc.parse_path_list(cfg.CLS_MI) logfile = time.strftime(fdir + "/online-%Y%m%d-%H%M%S.txt", time.localtime()) with open(logfile, 'w') as fout: fout.write('Ground-truth,Prediction\n') for gt, dt in zip(dir_seq, dir_detected): fout.write('%s,%s\n' % (gt, dt)) cfmat, acc = qc.confusion_matrix(dir_seq, dir_detected) fout.write('\nAccuracy %.3f\nConfusion matrix\n' % acc) fout.write(cfmat) print('Log exported to %s' % logfile) print('\nAccuracy %.3f\nConfusion matrix\n' % acc) print(cfmat) visual.finish() if decoder: decoder.stop() ''' # automatic thresholding if prob_history and len(bar_dirs) == 2: total = sum(len(prob_history[c]) for c in prob_history) fout = open(probs_logfile, 'a') msg = 'Automatic threshold optimization.\n' max_acc = 0 max_bias = 0 for bias in np.arange(-0.99, 1.00, 0.01): corrects = 0 for p in prob_history[bar_dirs[0]]: p_biased = (p + bias) / (bias + 1) # new sum = (p+bias) + (1-p) = bias+1 if p_biased >= 0.5: corrects += 1 for p in prob_history[bar_dirs[1]]: p_biased = (p + bias) / (bias + 1) # new sum = (p+bias) + (1-p) = bias+1 if p_biased < 0.5: corrects += 1 acc = corrects / total msg += '%s%.2f: %.3f\n' % (bar_dirs[0], bias, acc) if acc > max_acc: max_acc = acc max_bias = bias msg += 'Max acc = %.3f at bias %.2f\n' % (max_acc, max_bias) fout.write(msg) fout.close() print(msg) ''' print('Finished.')
def preprocess(raw, sfreq=None, spatial=None, spatial_ch=None, spectral=None, spectral_ch=None, notch=None, notch_ch=None, multiplier=1, ch_names=None, n_jobs=1): """ Apply spatial, spectral, notch filters and convert unit. raw is modified in-place. Input ------ raw: mne.io.RawArray | mne.Epochs | numpy.array (n_channels x n_samples) numpy.array type assumes the data has only pure EEG channnels without event channels sfreq: required only if raw is numpy array. spatial: None | 'car' | 'laplacian' Spatial filter type. spatial_ch: None | list (for CAR) | dict (for LAPLACIAN) Reference channels for spatial filtering. May contain channel names. 'car': channel indices used for CAR filtering. If None, use all channels except the trigger channel (index 0). 'laplacian': {channel:[neighbor1, neighbor2, ...], ...} *** Note *** Since PyCNBI puts trigger channel as index 0, data channel starts from index 1. spectral: None | [l_freq, h_freq] Spectral filter. if l_freq is None: lowpass filter is applied. if h_freq is None: highpass filter is applied. if l_freq < h_freq: bandpass filter is applied. if l_freq > h_freq: band-stop filter is applied. spectral_ch: None | list Channel picks for spectra filtering. May contain channel names. notch: None | float | list of frequency in floats Notch filter. notch_ch: None | list Channel picks for notch filtering. May contain channel names. multiplier: float If not 1, multiply data values excluding trigger values. ch_names: None | list If raw is numpy array and channel picks are list of strings, ch_names will be used as a look-up table to convert channel picks to channel numbers. Output ------ True if no error. """ # Check datatype if type(raw) == np.ndarray: # Numpy array: assume we don't have event channel data = raw assert sfreq is not None and sfreq > 0, 'Wrong sfreq value.' assert 2 <= len( data.shape ) <= 3, 'Unknown data shape. The dimension must be 2 or 3.' if len(data.shape) == 3: n_channels = data.shape[1] elif len(data.shape) == 2: n_channels = data.shape[0] eeg_channels = list(range(n_channels)) else: # MNE Raw object: exclude event channel ch_names = raw.ch_names data = raw._data sfreq = raw.info['sfreq'] assert 2 <= len( data.shape ) <= 3, 'Unknown data shape. The dimension must be 2 or 3.' if len(data.shape) == 3: # assert type(raw) is mne.epochs.Epochs n_channels = data.shape[1] elif len(data.shape) == 2: n_channels = data.shape[0] eeg_channels = list(range(n_channels)) tch = find_event_channel(raw) if tch is None: qc.print_c( 'preprocess(): No trigger channel found. Using all channels.', 'W') else: tch_name = ch_names[tch] eeg_channels.pop(tch) # Do unit conversion if multiplier != 1: data[eeg_channels] *= multiplier # Apply spatial filter if spatial is None: pass elif spatial == 'car': if spatial_ch is None: spatial_ch = eeg_channels if type(spatial_ch[0]) == str: assert ch_names is not None, 'preprocess(): ch_names must not be None' spatial_ch_i = [ch_names.index(c) for c in spatial_ch] else: spatial_ch_i = spatial_ch if len(spatial_ch_i) > 1: if len(data.shape) == 2: data[spatial_ch_i] -= np.mean(data[spatial_ch_i], axis=0) elif len(data.shape) == 3: means = np.mean(data[:, spatial_ch_i, :], axis=1) data[:, spatial_ch_i, :] -= means[:, np.newaxis, :] else: raise ValueError('preprocess(): Unknown data shape %s' % str(data.shape)) elif spatial == 'laplacian': if type(spatial_ch) is not dict: raise TypeError( 'preprocess(): For Lapcacian, spatial_ch must be of form {CHANNEL:[NEIGHBORS], ...}' ) if type(spatial_ch.keys()[0]) == str: spatial_ch_i = {} for c in spatial_ch: ref_ch = ch_names.index(c) spatial_ch_i[ref_ch] = [ ch_names.index(n) for n in spatial_ch[c] ] else: spatial_ch_i = spatial_ch if len(spatial_ch_i) > 1: rawcopy = data.copy() for src in spatial_ch: nei = spatial_ch[src] if len(data.shape) == 2: data[src] = rawcopy[src] - np.mean(rawcopy[nei], axis=0) elif len(data.shape) == 3: data[:, src, :] = rawcopy[:, src, :] - np.mean( rawcopy[:, nei, :], axis=1) else: raise ValueError('preprocess(): Unknown data shape %s' % str(data.shape)) else: raise ValueError('preprocess(): Unknown spatial filter %s' % spatial) # Apply spectral filter if spectral is not None: if spectral_ch is None: spectral_ch = eeg_channels if type(spectral_ch[0]) == str: assert ch_names is not None, 'preprocess(): ch_names must not be None' spectral_ch_i = [ch_names.index(c) for c in spectral_ch] else: spectral_ch_i = spectral_ch # fir_design='firwin' is especially important for ICA analysis. See: # http://martinos.org/mne/dev/generated/mne.preprocessing.ICA.html?highlight=score_sources#mne.preprocessing.ICA.score_sources mne.filter.filter_data(data, sfreq, spectral[0], spectral[1], picks=spectral_ch_i, filter_length='auto', l_trans_bandwidth='auto', h_trans_bandwidth='auto', n_jobs=n_jobs, method='fir', iir_params=None, copy=False, phase='zero', fir_window='hamming', fir_design='firwin', verbose='ERROR') # Apply notch filter if notch is not None: assert False if notch_ch is None: notch_ch = eeg_channels if type(notch_ch[0]) == str: assert ch_names is not None, 'preprocess(): ch_names must not be None' notch_ch_i = [ch_names.index(c) for c in notch_ch] else: notch_ch_i = notch_ch mne.filter.notch_filter(data, Fs=sfreq, freqs=notch, notch_widths=3, picks=notch_ch_i, method='fft', n_jobs=n_jobs, copy=False) return True
def acquire(self, blocking=True): """ Reads data into buffer. It is a blocking function as default. Fills the buffer and return the current chunk of data and timestamps. Returns: (data, timestamps) where data: [samples, channels] timestamps: [samples] TODO: add a parameter to set to non-blocking mode. """ if DEBUG_TIME_OFFSET: timestamp_offset = False if len(self.timestamps[0]) == 0: timestamp_offset = True self.watchdog.reset() tslist = [] while self.watchdog.sec() < 5: # retrieve chunk in [frame][ch] if len(tslist) == 0: chunk, tslist = self.inlets[0].pull_chunk( ) # [frames][channels] if blocking == False and len(tslist) == 0: return np.zeros((0, len(self.ch_list))), [] if len(tslist) > 0: if DEBUG_TIME_OFFSET and timestamp_offset is True: lsl_clock = pylsl.local_clock() break time.sleep(0.0005) else: self.print( 'Warning: Timeout occurred while acquiring data. Amp driver bug ?' ) return np.zeros((0, len(self.ch_list))), [] data = np.array(chunk) # BioSemi has pull-up resistor instead of pull-down # import pdb; pdb.set_trace() if self.amp_name == 'BioSemi' and self._lsl_tr_channel is not None: datatype = data.dtype data[:, self._lsl_tr_channel] = (np.bitwise_and( 255, data[:, self._lsl_tr_channel].astype(int)) - 1).astype(datatype) # multiply values (to change unit) if self.multiplier != 1: data[:, self._lsl_eeg_channels] *= self.multiplier if self._lsl_tr_channel is not None: # move trigger channel to 0 and add back to the buffer data = np.concatenate((data[:, self._lsl_tr_channel].reshape( -1, 1), data[:, self._lsl_eeg_channels]), axis=1) else: # add an empty channel with zeros to channel 0 data = np.concatenate((np.zeros( (data.shape[0], 1)), data[:, self._lsl_eeg_channels]), axis=1) # add data to buffer chunk = data.tolist() self.buffers[0].extend(chunk) self.timestamps[0].extend(tslist) if self.bufsize > 0 and len(self.timestamps) > self.bufsize: self.buffers[0] = self.buffers[0][-self.bufsize:] self.timestamps[0] = self.timestamps[0][-self.bufsize:] if DEBUG_TIME_OFFSET and timestamp_offset is True: timestamp_offset = False print('LSL timestamp =', lsl_clock) print('Server timestamp =', self.timestamps[-1][-1]) self.lsl_time_offset = lsl_clock - self.timestamps[-1][-1] print('Offset = %.3f ' % (self.lsl_time_offset), end='') if self.lsl_time_offset > 0.1: qc.print_c( '\n*** WARNING: The server seems to be sending wrong time stamps ***\n\n', 'r') else: qc.print_c('(Synchronized)', 'g') # if we have multiple synchronized amps if len(self.inlets) > 1: for i in range(1, len(self.inlets)): chunk, tslist = self.inlets[i].pull_chunk( max_samples=len(tslist)) # [frames][channels] self.buffers[i].extend(chunk) self.timestamps[i].extend(tslist) if self.bufsize > 0 and len(self.buffers[i]) > self.bufsize: self.buffers[i] = self.buffers[i][-self.bufsize:] # data= array[samples, channels], tslist=[samples] return (data, tslist)
mne.set_log_level('ERROR') os.environ[ 'OMP_NUM_THREADS'] = '1' # actually improves performance for multitaper amp_name, amp_serial = pu.search_lsl() sr = StreamReceiver(window_size=1, buffer_size=1, amp_serial=amp_serial, eeg_only=False, amp_name=amp_name) sfreq = sr.get_sample_rate() watchdog = qc.Timer() tm = qc.Timer(autoreset=True) trg_ch = sr.get_trigger_channel() last_ts = 0 qc.print_c('Trigger channel: %d' % trg_ch, 'G') if SHOW_PSD: psde = mne.decoding.PSDEstimator(sfreq=sfreq, fmin=1, fmax=50, bandwidth=None, \ adaptive=False, low_bias=True, n_jobs=1, normalization='length', verbose=None) while True: sr.acquire() window, tslist = sr.get_window() # window = [samples x channels] window = window.T # chanel x samples # print event values tsnew = np.where(np.array(tslist) > last_ts)[0][0] trigger = np.unique(window[trg_ch, tsnew:]) # for Biosemi
def print(self, msg): qc.print_c('[StreamViewer] %s' % msg, color='W')
from pycnbi.stream_receiver.stream_receiver import StreamReceiver import pycnbi.utils.pycnbi_utils as pu import pycnbi.utils.q_common as qc mne.set_log_level('ERROR') # actually improves performance for multitaper os.environ['OMP_NUM_THREADS'] = '1' amp_name, amp_serial = pu.search_lsl() stream_receiver = StreamReceiver( window_size=1, buffer_size=1, amp_serial=amp_serial, eeg_only=False, amp_name=amp_name) sfreq = stream_receiver.get_sample_rate() watchdog = qc.Timer() tm = qc.Timer(autoreset=True) trg_ch = stream_receiver.get_trigger_channel() last_ts = 0 qc.print_c('Trigger channel: %d' % trg_ch, 'G') if SHOW_PSD: psde = mne.decoding.PSDEstimator(sfreq=sfreq, fmin=1, fmax=50, bandwidth=None, adaptive=False, low_bias=True, n_jobs=1, normalization='length', verbose=None) sfreq = 512 interval = 1. / sfreq fig, ax = plt.subplots(1, 1) time_range = 1 x = np.arange(0, time_range, interval) y = [0]*(time_range*sfreq) lines, = ax.plot(x, y) while True: stream_receiver.acquire()
def test_receiver(): import mne import os CH_INDEX = [1] # channel to monitor TIME_INDEX = None # integer or None. None = average of raw values of the current window SHOW_PSD = False mne.set_log_level('ERROR') os.environ[ 'OMP_NUM_THREADS'] = '1' # actually improves performance for multitaper # connect to LSL server amp_name, amp_serial = pu.search_lsl() sr = StreamReceiver(window_size=1, buffer_size=1, amp_serial=amp_serial, eeg_only=False, amp_name=amp_name) sfreq = sr.get_sample_rate() trg_ch = sr.get_trigger_channel() logger.info('Trigger channel = %d' % trg_ch) # PSD init if SHOW_PSD: psde = mne.decoding.PSDEstimator(sfreq=sfreq, fmin=1, fmax=50, bandwidth=None, \ adaptive=False, low_bias=True, n_jobs=1, normalization='length', verbose=None) watchdog = qc.Timer() tm = qc.Timer(autoreset=True) last_ts = 0 while True: sr.acquire() window, tslist = sr.get_window() # window = [samples x channels] window = window.T # chanel x samples qc.print_c('LSL Diff = %.3f' % (pylsl.local_clock() - tslist[-1]), 'G') # print event values tsnew = np.where(np.array(tslist) > last_ts)[0] if len(tsnew) == 0: logger.warning('There seems to be delay in receiving data.') time.sleep(1) continue trigger = np.unique(window[trg_ch, tsnew[0]:]) # for Biosemi # if sr.amp_name=='BioSemi': # trigger= set( [255 & int(x-1) for x in trigger ] ) if len(trigger) > 0: logger.info('Triggers: %s' % np.array(trigger)) logger.info('[%.1f] Receiving data...' % watchdog.sec()) if TIME_INDEX is None: datatxt = qc.list2string(np.mean(window[CH_INDEX, :], axis=1), '%-15.6f') print('[%.3f : %.3f]' % (tslist[0], tslist[-1]) + ' data: %s' % datatxt) else: datatxt = qc.list2string(window[CH_INDEX, TIME_INDEX], '%-15.6f') print('[%.3f]' % tslist[TIME_INDEX] + ' data: %s' % datatxt) # show PSD if SHOW_PSD: psd = psde.transform( window.reshape((1, window.shape[0], window.shape[1]))) psd = psd.reshape((psd.shape[1], psd.shape[2])) psdmean = np.mean(psd, axis=1) for p in psdmean: print('%.1f' % p, end=' ') last_ts = tslist[-1] tm.sleep_atleast(0.05)
def connect(self, find_any=True): """ Run in child process """ server_found = False amps = [] channels = 0 while server_found == False: if self.amp_name is None and self.amp_serial is None: self.print("Looking for a streaming server...") else: self.print("Looking for %s (Serial %s) ..." % (self.amp_name, self.amp_serial)) streamInfos = pylsl.resolve_streams() # print(streamInfos) if len(streamInfos) > 0: # For now, only 1 amp is supported by a single StreamReceiver object. for si in streamInfos: # is_slave= ('true'==pylsl.StreamInlet(si).info().desc().child('amplifier').child('settings').child('is_slave').first_child().value() ) inlet = pylsl.StreamInlet(si) # LSL XML parser has a bug which crashes so do not use for now #amp_serial = inlet.info().desc().child('acquisition').child_value('serial_number') amp_serial = 'N/A' amp_name = si.name() # qc.print_c('Found %s (%s)'% (amp_name,amp_serial), 'G') # connect to a specific amp only? if self.amp_serial is not None and self.amp_serial != amp_serial: continue # connect to a specific amp only? if self.amp_name is not None and self.amp_name != amp_name: continue # EEG streaming server only? if self.eeg_only and si.type() != 'EEG': continue if 'USBamp' in amp_name: self.print('Found USBamp streaming server %s (type %s, amp_serial %s) @ %s.' \ % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 16 channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'BioSemi' in amp_name: self.print('Found BioSemi streaming server %s (type %s, amp_serial %s) @ %s.' \ % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 0 # or subtract -6684927? (value when trigger==0) channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'SmartBCI' in amp_name: self.print('Found SmartBCI streaming server %s (type %s, amp_serial %s) @ %s.' \ % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 23 channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'StreamPlayer' in amp_name: self.print('Found StreamPlayer streaming server %s (type %s, amp_serial %s) @ %s.' \ % (amp_name, si.type(), amp_serial, si.hostname())) self._lsl_tr_channel = 0 channels += si.channel_count() ch_list = pu.lsl_channel_list(inlet) amps.append(si) server_found = True break elif 'openvibeSignal' in amp_name: self.print('Found an Openvibe signal streaming server %s (type %s, amp_serial %s) @ %s.' \ % (amp_name, si.type(), amp_serial, si.hostname())) ch_list = pu.lsl_channel_list(inlet) self._lsl_tr_channel = find_trigger_channel(ch_list) channels += si.channel_count() amps.append(si) server_found = True # OpenVibe standard unit is Volts, which is not ideal for some numerical computations self.multiplier = 10**6 # change V -> uV unit for OpenVibe sources break elif 'openvibeMarkers' in amp_name: self.print('Found an Openvibe markers server %s (type %s, amp_serial %s) @ %s.' \ % (amp_name, si.type(), amp_serial, si.hostname())) ch_list = pu.lsl_channel_list(inlet) self._lsl_tr_channel = find_trigger_channel(ch_list) channels += si.channel_count() amps.append(si) server_found = True break elif find_any: self.print('Found a streaming server %s (type %s, amp_serial %s) @ %s.' \ % (amp_name, si.type(), amp_serial, si.hostname())) ch_list = pu.lsl_channel_list(inlet) self._lsl_tr_channel = find_trigger_channel(ch_list) channels += si.channel_count() amps.append(si) server_found = True break time.sleep(1) self.amp_name = amp_name # define EEG channel indices self._lsl_eeg_channels = list(range(channels)) if self._lsl_tr_channel is None: self.print('Trigger channel not fonud. Adding an empty channel 0.', 'Y') else: self.print( 'Trigger channel found at index %d. Moving to index 0.' % self._lsl_tr_channel, 'Y') self._lsl_eeg_channels.pop(self._lsl_tr_channel) self._lsl_eeg_channels = np.array(self._lsl_eeg_channels) self.tr_channel = 0 # trigger channel is always set to 0. self.eeg_channels = np.arange( 1, channels) # signal channels start from 1. # create new inlets to read from the stream inlets_master = [] inlets_slaves = [] for amp in amps: inlet = pylsl.StreamInlet(amp) inlets_master.append(inlet) self.buffers.append([]) self.timestamps.append([]) inlets = inlets_master + inlets_slaves sample_rate = amps[0].nominal_srate() self.print('Channels: %d' % channels) self.print('LSL Protocol version: %s' % amps[0].version()) self.print('Source sampling rate: %.1f' % sample_rate) self.print('Unit multiplier: %.1f' % self.multiplier) self.winsize = int(round(self.winsec * sample_rate)) self.bufsize = int(round(self.bufsec * sample_rate)) self.sample_rate = sample_rate self.connected = True self.inlets = inlets # NOTE: not picklable! self.ch_list = ch_list # create channel info if self._lsl_tr_channel is None: self.ch_list = ['TRIGGER'] + self.ch_list else: for i, chn in enumerate(self.ch_list): if chn == 'TRIGGER' or chn == 'TRG' or 'STI ' in chn: self.ch_list.pop(i) self.ch_list = ['TRIGGER'] + self.ch_list break qc.print_c('self.ch_list %s' % self.ch_list, 'Y') # fill in initial buffer self.print('Waiting to fill initial buffer of length %d' % (self.winsize)) while len(self.timestamps[0]) < self.winsize: self.acquire() time.sleep(0.1) self.ready = True self.print('Start receiving stream data.')
def config_run(cfg_module): cfg = load_cfg(cfg_module) # visualizer keys = { 'left': 81, 'right': 83, 'up': 82, 'down': 84, 'pgup': 85, 'pgdn': 86, 'home': 80, 'end': 87, 'space': 32, 'esc': 27, ',': 44, '.': 46, 's': 115, 'c': 99, '[': 91, ']': 93, '1': 49, '!': 33, '2': 50, '@': 64, '3': 51, '#': 35 } color = dict(G=(20, 140, 0), B=(210, 0, 0), R=(0, 50, 200), Y=(0, 215, 235), K=(0, 0, 0), W=(255, 255, 255), w=(200, 200, 200)) dir_sequence = [] for x in range(cfg.TRIALS_EACH): dir_sequence.extend(cfg.DIRECTIONS) random.shuffle(dir_sequence) num_trials = len(cfg.DIRECTIONS) * cfg.TRIALS_EACH tdef = trigger_def(cfg.TRIGGER_DEF) refresh_delay = 1.0 / cfg.REFRESH_RATE state = 'start' trial = 1 # STIMO protocol if cfg.WITH_STIMO is True: print('Opening STIMO serial port (%s / %d bps)' % (cfg.STIMO_COMPORT, cfg.STIMO_BAUDRATE)) import serial ser = serial.Serial(cfg.STIMO_COMPORT, cfg.STIMO_BAUDRATE) print('STIMO serial port %s is_open = %s' % (cfg.STIMO_COMPORT, ser.is_open)) # init trigger if cfg.TRIGGER_DEVICE is None: input( '\n** Warning: No trigger device set. Press Ctrl+C to stop or Enter to continue.' ) trigger = pyLptControl.Trigger(cfg.TRIGGER_DEVICE) if trigger.init(50) == False: print( '\n# Error connecting to USB2LPT device. Use a mock trigger instead?' ) input('Press Ctrl+C to stop or Enter to continue.') trigger = pyLptControl.MockTrigger() trigger.init(50) # visual feedback if cfg.FEEDBACK_TYPE == 'BAR': from pycnbi.protocols.viz_bars import BarVisual visual = BarVisual(cfg.GLASS_USE, screen_pos=cfg.SCREEN_POS, screen_size=cfg.SCREEN_SIZE) elif cfg.FEEDBACK_TYPE == 'BODY': if not hasattr(cfg, 'IMAGE_PATH'): raise ValueError('IMAGE_PATH is undefined in your config.') from pycnbi.protocols.viz_human import BodyVisual visual = BodyVisual(cfg.IMAGE_PATH, use_glass=cfg.GLASS_USE, screen_pos=cfg.SCREEN_POS, screen_size=cfg.SCREEN_SIZE) visual.put_text('Waiting to start ') timer_trigger = qc.Timer() timer_dir = qc.Timer() timer_refresh = qc.Timer() # start while trial <= num_trials: timer_refresh.sleep_atleast(refresh_delay) timer_refresh.reset() # segment= { 'cue':(s,e), 'dir':(s,e), 'label':0-4 } (zero-based) if state == 'start' and timer_trigger.sec() > cfg.T_INIT: state = 'gap_s' visual.fill() timer_trigger.reset() trigger.signal(tdef.INIT) elif state == 'gap_s': visual.put_text('Trial %d / %d' % (trial, num_trials)) state = 'gap' elif state == 'gap' and timer_trigger.sec() > cfg.T_GAP: state = 'cue' visual.fill() visual.draw_cue() trigger.signal(tdef.CUE) timer_trigger.reset() elif state == 'cue' and timer_trigger.sec() > cfg.T_CUE: state = 'dir_r' dir = dir_sequence[trial - 1] if dir == 'L': # left if cfg.FEEDBACK_TYPE == 'BAR': visual.move('L', 100) else: visual.put_text('LEFT') trigger.signal(tdef.LEFT_READY) elif dir == 'R': # right if cfg.FEEDBACK_TYPE == 'BAR': visual.move('R', 100) else: visual.put_text('RIGHT') trigger.signal(tdef.RIGHT_READY) elif dir == 'U': # up if cfg.FEEDBACK_TYPE == 'BAR': visual.move('U', 100) else: visual.put_text('UP') trigger.signal(tdef.UP_READY) elif dir == 'D': # down if cfg.FEEDBACK_TYPE == 'BAR': visual.move('D', 100) else: visual.put_text('DOWN') trigger.signal(tdef.DOWN_READY) elif dir == 'B': # both hands if cfg.FEEDBACK_TYPE == 'BAR': visual.move('L', 100) visual.move('R', 100) else: visual.put_text('BOTH') trigger.signal(tdef.BOTH_READY) else: raise RuntimeError('Unknown direction %d' % dir) gait_steps = 1 timer_trigger.reset() elif state == 'dir_r' and timer_trigger.sec() > cfg.T_DIR_READY: visual.draw_cue() state = 'dir' timer_trigger.reset() timer_dir.reset() t_step = cfg.T_DIR + random.random() * cfg.RANDOMIZE_LENGTH if dir == 'L': # left trigger.signal(tdef.LEFT_GO) elif dir == 'R': # right trigger.signal(tdef.RIGHT_GO) elif dir == 'U': # up trigger.signal(tdef.UP_GO) elif dir == 'D': # down trigger.signal(tdef.DOWN_GO) elif dir == 'B': # both trigger.signal(tdef.BOTH_GO) else: raise RuntimeError('Unknown direction %d' % dir) elif state == 'dir': if timer_trigger.sec() > t_step: if cfg.FEEDBACK_TYPE == 'BODY': if cfg.WITH_STIMO is True: if dir == 'L': # left ser.write(b'1') qc.print_c('STIMO: Sent 1', 'g') trigger.signal(tdef.LEFT_STIMO) elif dir == 'R': # right ser.write(b'2') qc.print_c('STIMO: Sent 2', 'g') trigger.signal(tdef.RIGHT_STIMO) else: if dir == 'L': # left trigger.signal(tdef.LEFT_RETURN) elif dir == 'R': # right trigger.signal(tdef.RIGHT_RETURN) else: trigger.signal(tdef.FEEDBACK) state = 'return' timer_trigger.reset() else: dx = min(100, int(100.0 * timer_dir.sec() / t_step) + 1) if dir == 'L': # L visual.move('L', dx, overlay=True) elif dir == 'R': # R visual.move('R', dx, overlay=True) elif dir == 'U': # U visual.move('U', dx, overlay=True) elif dir == 'D': # D visual.move('D', dx, overlay=True) elif dir == 'B': # Both visual.move('L', dx, overlay=True) visual.move('R', dx, overlay=True) elif state == 'return': if timer_trigger.sec() > cfg.T_RETURN: if gait_steps < cfg.GAIT_STEPS: gait_steps += 1 state = 'dir' visual.move('L', 0) if dir == 'L': dir = 'R' trigger.signal(tdef.RIGHT_GO) else: dir = 'L' trigger.signal(tdef.LEFT_GO) timer_dir.reset() t_step = cfg.T_DIR + random.random() * cfg.RANDOMIZE_LENGTH else: state = 'gap_s' visual.fill() trial += 1 print('trial ' + str(trial - 1) + ' done') trigger.signal(tdef.BLANK) timer_trigger.reset() else: dx = max( 0, int(100.0 * (cfg.T_RETURN - timer_trigger.sec()) / cfg.T_RETURN)) if dir == 'L': # L visual.move('L', dx, overlay=True) elif dir == 'R': # R visual.move('R', dx, overlay=True) elif dir == 'U': # U visual.move('U', dx, overlay=True) elif dir == 'D': # D visual.move('D', dx, overlay=True) elif dir == 'B': # Both visual.move('L', dx, overlay=True) visual.move('R', dx, overlay=True) # wait for start if state == 'start': visual.put_text('Waiting to start ') visual.update() key = 0xFF & cv2.waitKey(1) if key == keys['esc']: break # STIMO protocol if cfg.WITH_STIMO is True: ser.close() print('Closed STIMO serial port %s' % cfg.STIMO_COMPORT)