def sample_decoding(decoder): """ Decoding example """ # load trigger definitions for labeling labels = decoder.get_label_names() tm_watchdog = qc.Timer(autoreset=True) tm_cls = qc.Timer() while True: praw = decoder.get_prob_unread() psmooth = decoder.get_prob_smooth() if praw is None: # watch dog if tm_cls.sec() > 5: logger.warning('No classification was done in the last 5 seconds. Are you receiving data streams?') tm_cls.reset() tm_watchdog.sleep_atleast(0.001) continue txt = '[%8.1f msec]' % (tm_cls.msec()) for i, label in enumerate(labels): txt += ' %s %.3f (raw %.3f)' % (label, psmooth[i], praw[i]) maxi = qc.get_index_max(psmooth) txt += ' %s' % labels[maxi] print(txt) tm_cls.reset()
def check_config(cfg_module): mandatory = { 'TRIGGER_DEVICE', 'TRIGGER_DEF', 'SCREEN_SIZE', 'SCREEN_POS', 'DIRECTIONS', 'DIR_RANDOMIZE', 'TRIALS_EACH', 'GAIT_STEPS', 'T_INIT', 'T_GAP', 'T_CUE', 'T_DIR_READY', 'T_DIR', 'T_RETURN', 'T_STOP', } optional = { 'FEEDBACK_TYPE': 'BAR', 'T_RETURN': 2, 'DIR_RANDOMIZE': True, 'WITH_STIMO': False, 'GLASS_USE': False, 'REFRESH_RATE': 30, 'T_DIR_RANDOMIZE': 0 } 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): logger.warning('Setting undefined %s=%s' % (key, optional[key])) return cfg
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): logger.warning( 'Event %d at time %.3f is out of time range (%.3f - %.3f).' % (event_value, event_ts, ts_min, ts_max)) else: events.append([next_index, 0, event_value]) return events
def check_cfg(cfg): if not hasattr(cfg, 'POSITIVE_FEEDBACK'): logger.warning( 'Warning: POSITIVE_FEEDBACK undefined. Setting it to False.') cfg.POSITIVE_FEEDBACK = False if not hasattr(cfg, 'BAR_REACH_FINISH'): logger.warning( 'Warning: BAR_REACH_FINISH undefined. Setting it to False.') cfg.BAR_REACH_FINISH = False return cfg
def stop(self): """ Stop the daemon """ if self.is_running() == 0: logger.warning('Decoder already stopped.') return for running in self.running: running.value = 0 for proc in self.procs: proc.join(10) if proc.is_alive(): logger.warning('Process %s did not die properly.' % proc.pid()) self.reset() logger.info(self.stopmsg)
def sort_by_value(s, rev=False): """ Sort dictionary or list by value and return a sorted list of keys and values. Values must be hashable and unique. """ assert type(s) == dict or type( s) == list, 'Input must be a dictionary or list.' if type(s) == list: s = dict(enumerate(s)) s_rev = dict((v, k) for k, v in s.items()) if Q_VERBOSE > 0 and not len(s_rev) == len(s): logger.warning('sort_by_value(): %d identical values' % (len(s.values()) - len(set(s.values())) + 1)) values = sorted(s_rev, reverse=rev) keys = [s_rev[x] for x in values] return keys, values
def paintEvent(self, e): # Distinguish between paint events from timer and event QT widget resizing, clicking etc (sender is None) # We should only paint when the timer triggered the event. # Just in case, there's a flag to force a repaint even when we shouldn't repaint sender = self.sender() if 'force_repaint' not in self.__dict__.keys(): logger.warning('force_repaint is not set! Is it a Qt bug?') self.force_repaint = 0 if (sender is None) and (not self.force_repaint): pass else: self.force_repaint = 0 qp = QPainter() qp.begin(self) # Update the interface self.paintInterface(qp) qp.end()
def merge_events(trigger_file, events, rawfile_in, rawfile_out): tdef = trigger_def(trigger_file) raw, eve = pu.load_raw(rawfile_in) logger.info('=== Before merging ===') notfounds = [] for key in np.unique(eve[:, 2]): if key in tdef.by_value: logger.info( '%s: %d events' % (tdef.by_value[key], len(np.where(eve[:, 2] == key)[0]))) else: logger.info('%d: %d events' % (key, len(np.where(eve[:, 2] == key)[0]))) notfounds.append(key) if notfounds: for key in notfounds: logger.warning('Key %d was not found in the definition file.' % key) for key in events: ev_src = events[key] ev_out = tdef.by_name[key] x = [] for e in ev_src: x.append(np.where(eve[:, 2] == tdef.by_name[e])[0]) eve[np.concatenate(x), 2] = ev_out # sanity check dups = np.where(0 == np.diff(eve[:, 0]))[0] assert len(dups) == 0 # reset trigger channel raw._data[0] *= 0 raw.add_events(eve, 'TRIGGER') raw.save(rawfile_out, overwrite=True) logger.info('=== After merging ===') for key in np.unique(eve[:, 2]): if key in tdef.by_value: logger.info( '%s: %d events' % (tdef.by_value[key], len(np.where(eve[:, 2] == key)[0]))) else: logger.info('%s: %d events' % (key, len(np.where(eve[:, 2] == key)[0])))
def signal(self, value): if self.lpttype == 'SOFTWARE': if self.verbose is True: logger.info('Sending software trigger %s' % value) return self.write_event(value) elif self.lpttype == 'FAKE': logger.info('Sending FAKE trigger signal %s' % value) return True else: if self.offtimer.is_alive(): logger.warning('You are sending a new signal before the end of the last signal. Signal ignored.') logger.warning('self.delay=%.1f' % self.delay) return False self.set_data(value) if self.verbose is True: logger.info('Sending %s' % value) self.offtimer.start() return True
def check_config(cfg): critical_vars = { 'COMMON': [ 'TRIGGER_DEVICE', 'TRIGGER_FILE', 'SCREEN_SIZE', 'DIRECTIONS', 'DIR_RANDOM', 'TRIALS_EACH' ], 'TIMINGS': [ 'INIT', 'GAP', 'CUE', 'READY', 'READY_RANDOMIZE', 'DIR', 'DIR_RANDOMIZE' ] } optional_vars = { 'FEEDBACK_TYPE': 'BAR', 'FEEDBACK_IMAGE_PATH': None, 'SCREEN_POS': (0, 0), 'DIR_RANDOM': True, 'GLASS_USE': False, 'TRIAL_PAUSE': False, 'REFRESH_RATE': 30 } for key in critical_vars['COMMON']: if not hasattr(cfg, key): raise RuntimeError('%s is a required parameter' % key) if not hasattr(cfg, 'TIMINGS'): logger.error('"TIMINGS" not defined in config.') raise RuntimeError for v in critical_vars['TIMINGS']: if v not in cfg.TIMINGS: logger.error('%s not defined in config.' % v) raise RuntimeError for key in optional_vars: if not hasattr(cfg, key): setattr(cfg, key, optional_vars[key]) logger.warning('Setting undefined %s=%s' % (key, optional[key])) if getattr(cfg, 'TRIGGER_DEVICE') == None: logger.warning( 'The trigger device is set to None! No events will be saved.') raise RuntimeError( 'The trigger device is set to None! No events will be saved.')
def fif2mat(input_path): if os.path.isdir(input_path): out_dir = '%s/mat_files' % input_path qc.make_dirs(out_dir) num_processed = 0 for rawfile in qc.get_file_list(input_path, fullpath=True): if rawfile[-4:] != '.fif': continue fif2mat_file(rawfile, out_dir) num_processed += 1 if num_processed == 0: logger.warning('No fif files found in the path.') elif os.path.isfile(input_path): out_dir = '%s/mat_files' % qc.parse_path(input_path).dir qc.make_dirs(out_dir) fif2mat_file(input_path, out_dir) else: raise ValueError('Neither directory nor file: %s' % input_path) logger.info('Finished.')
def check_config(cfg): """ Ensure that the config file contains the parameters """ critical_vars = { 'COMMON': ['DATA_PATH'], } optional_vars = {} for key in critical_vars['COMMON']: if not hasattr(cfg, key): logger.error('%s is a required parameter' % key) raise RuntimeError for key in optional_vars: if not hasattr(cfg, key): setattr(cfg, key, optional_vars[key]) logger.warning('Setting undefined parameter %s=%s' % (key, getattr(cfg, key))) return cfg
def fix_channel_names(fif_dir, new_channel_names): ''' Change channel names of fif files in a given directory. Input ----- @fif_dir: path to fif files @new_channel_names: list of new channel names Output ------ Modified fif files are saved in fif_dir/corrected/ Kyuhwa Lee, 2019. ''' flist = [] for f in qc.get_file_list(fif_dir): if qc.parse_path(f).ext == 'fif': flist.append(f) if len(flist) > 0: qc.make_dirs('%s/corrected' % fif_dir) for f in qc.get_file_list(fif_dir): logger.info('\nLoading %s' % f) p = qc.parse_path(f) if p.ext == 'fif': raw, eve = pu.load_raw(f) if len(raw.ch_names) != len(new_channel_names): raise RuntimeError( 'The number of new channels do not matach that of fif file.' ) raw.info['ch_names'] = new_channel_names for ch, new_ch in zip(raw.info['chs'], new_channel_names): ch['ch_name'] = new_ch out_fif = '%s/corrected/%s.fif' % (p.dir, p.name) logger.info('Exporting to %s' % out_fif) raw.save(out_fif) else: logger.warning('No fif files found in %s' % fif_dir)
def classify(self, decoder, true_label, title_text, bar_dirs, state='start', prob_history=None): """ Run a single trial """ true_label_index = bar_dirs.index(true_label) self.tm_trigger.reset() if self.bar_bias is not None: bias_idx = bar_dirs.index(self.bar_bias[0]) if self.logf is not None: self.logf.write('True label: %s\n' % true_label) tm_classify = qc.Timer(autoreset=True) self.stimo_timer = qc.Timer() while True: self.tm_display.sleep_atleast(self.refresh_delay) self.tm_display.reset() if state == 'start' and self.tm_trigger.sec( ) > self.cfg.TIMINGS['INIT']: state = 'gap_s' if self.cfg.TRIALS_PAUSE: self.viz.put_text('Press any key') self.viz.update() key = cv2.waitKeyEx() if key == KEYS['esc'] or not self.protocol_state.value: return self.viz.fill() self.tm_trigger.reset() self.trigger.signal(self.tdef.INIT) elif state == 'gap_s': if self.cfg.TIMINGS['GAP'] > 0: self.viz.put_text(title_text) state = 'gap' self.tm_trigger.reset() elif state == 'gap' and self.tm_trigger.sec( ) > self.cfg.TIMINGS['GAP']: state = 'cue' self.viz.fill() self.viz.draw_cue() self.viz.glass_draw_cue() self.trigger.signal(self.tdef.CUE) self.tm_trigger.reset() elif state == 'cue' and self.tm_trigger.sec( ) > self.cfg.TIMINGS['READY']: state = 'dir_r' if self.cfg.SHOW_CUE is True: if self.cfg.FEEDBACK_TYPE == 'BAR': self.viz.move(true_label, 100, overlay=False, barcolor='G') elif self.cfg.FEEDBACK_TYPE == 'BODY': self.viz.put_text(DIRS[true_label], 'R') if true_label == 'L': # left self.trigger.signal(self.tdef.LEFT_READY) elif true_label == 'R': # right self.trigger.signal(self.tdef.RIGHT_READY) elif true_label == 'U': # up self.trigger.signal(self.tdef.UP_READY) elif true_label == 'D': # down self.trigger.signal(self.tdef.DOWN_READY) elif true_label == 'B': # both hands self.trigger.signal(self.tdef.BOTH_READY) else: raise RuntimeError('Unknown direction %s' % true_label) self.tm_trigger.reset() ''' if self.cfg.FEEDBACK_TYPE == 'BODY': self.viz.set_pc_feedback(False) self.viz.move(true_label, 100, overlay=False, barcolor='G') if self.cfg.FEEDBACK_TYPE == 'BODY': self.viz.set_pc_feedback(True) if self.cfg.SHOW_CUE is True: self.viz.put_text(dirs[true_label], 'R') if true_label == 'L': # left self.trigger.signal(self.tdef.LEFREADY) elif true_label == 'R': # right self.trigger.signal(self.tdef.RIGHT_READY) elif true_label == 'U': # up self.trigger.signal(self.tdef.UP_READY) elif true_label == 'D': # down self.trigger.signal(self.tdef.DOWN_READY) elif true_label == 'B': # both hands self.trigger.signal(self.tdef.BOTH_READY) else: raise RuntimeError('Unknown direction %s' % true_label) self.tm_trigger.reset() ''' elif state == 'dir_r' and self.tm_trigger.sec( ) > self.cfg.TIMINGS['DIR_CUE']: self.viz.fill() self.viz.draw_cue() self.viz.glass_draw_cue() state = 'dir' # initialize bar scores bar_label = bar_dirs[0] bar_score = 0 probs = [1.0 / len(bar_dirs)] * len(bar_dirs) self.viz.move(bar_label, bar_score, overlay=False) probs_acc = np.zeros(len(probs)) if true_label == 'L': # left self.trigger.signal(self.tdef.LEFT_GO) elif true_label == 'R': # right self.trigger.signal(self.tdef.RIGHT_GO) elif true_label == 'U': # up self.trigger.signal(self.tdef.UP_GO) elif true_label == 'D': # down self.trigger.signal(self.tdef.DOWN_GO) elif true_label == 'B': # both self.trigger.signal(self.tdef.BOTH_GO) else: raise RuntimeError('Unknown truedirection %s' % true_label) self.tm_watchdog.reset() self.tm_trigger.reset() elif state == 'dir': if self.tm_trigger.sec() > self.cfg.TIMINGS['CLASSIFY'] or ( self.premature_end and bar_score >= 100): if not hasattr( self.cfg, 'SHOW_RESULT') or self.cfg.SHOW_RESULT is True: # show classfication result if self.cfg.WITH_STIMO is True: if self.cfg.STIMO_FULLGAIT_CYCLE is not None and bar_label == 'U': res_color = 'G' elif self.cfg.TRIALS_RETRY is False or bar_label == true_label: res_color = 'G' else: res_color = 'Y' else: res_color = 'Y' if self.cfg.FEEDBACK_TYPE == 'BODY': self.viz.move(bar_label, bar_score, overlay=False, barcolor=res_color, caption=DIRS[bar_label], caption_color=res_color) else: self.viz.move(bar_label, 100, overlay=False, barcolor=res_color) else: res_color = 'Y' if self.cfg.FEEDBACK_TYPE == 'BODY': self.viz.move(bar_label, bar_score, overlay=False, barcolor=res_color, caption='TRIAL END', caption_color=res_color) else: self.viz.move(bar_label, 0, overlay=False, barcolor=res_color) self.trigger.signal(self.tdef.FEEDBACK) # STIMO if self.cfg.WITH_STIMO is True and self.cfg.STIMO_CONTINUOUS is False: if self.cfg.STIMO_FULLGAIT_CYCLE is not None: if bar_label == 'U': self.ser.write( self.cfg.STIMO_FULLGAIT_PATTERN[0]) logger.info('STIMO: Sent 1') time.sleep(self.cfg.STIMO_FULLGAIT_CYCLE) self.ser.write( self.cfg.STIMO_FULLGAIT_PATTERN[1]) logger.info('STIMO: Sent 2') time.sleep(self.cfg.STIMO_FULLGAIT_CYCLE) elif self.cfg.TRIALS_RETRY is False or bar_label == true_label: if bar_label == 'L': self.ser.write(b'1') logger.info('STIMO: Sent 1') elif bar_label == 'R': self.ser.write(b'2') logger.info('STIMO: Sent 2') if self.cfg.DEBUG_PROBS: msg = 'DEBUG: Accumulated probabilities = %s' % qc.list2string( probs_acc, '%.3f') logger.info(msg) if self.logf is not None: self.logf.write(msg + '\n') if self.logf is not None: self.logf.write('%s detected as %s (%d)\n\n' % (true_label, bar_label, bar_score)) self.logf.flush() # end of trial state = 'feedback' self.tm_trigger.reset() else: # classify probs_new = decoder.get_prob_smooth_unread() if probs_new is None: if self.tm_watchdog.sec() > 3: logger.warning( 'No classification being done. Are you receiving data streams?' ) self.tm_watchdog.reset() else: self.tm_watchdog.reset() if prob_history is not None: prob_history[true_label].append( probs_new[true_label_index]) probs_acc += np.array(probs_new) ''' New decoder: already smoothed by the decoder so bias after. ''' probs = list(probs_new) if self.bar_bias is not None: probs[bias_idx] += self.bar_bias[1] newsum = sum(probs) probs = [p / newsum for p in probs] ''' # Method 2: bias and smoothen if self.bar_bias is not None: # print('BEFORE: %.3f %.3f'% (probs_new[0], probs_new[1]) ) probs_new[bias_idx] += self.bar_bias[1] newsum = sum(probs_new) probs_new = [p / newsum for p in probs_new] # print('AFTER: %.3f %.3f'% (probs_new[0], probs_new[1]) ) for i in range(len(probs_new)): probs[i] = probs[i] * self.alpha_old + probs_new[i] * self.alpha_new ''' ''' Original method # Method 1: smoothen and bias for i in range( len(probs_new) ): probs[i] = probs[i] * self.alpha_old + probs_new[i] * self.alpha_new # bias bar if self.bar_bias is not None: probs[bias_idx] += self.bar_bias[1] newsum = sum(probs) probs = [p/newsum for p in probs] ''' # determine the direction # TODO: np.argmax(probs) max_pidx = qc.get_index_max(probs) max_label = bar_dirs[max_pidx] if self.cfg.POSITIVE_FEEDBACK is False or \ (self.cfg.POSITIVE_FEEDBACK and true_label == max_label): dx = probs[max_pidx] if max_label == 'R': dx *= self.bar_step_right elif max_label == 'L': dx *= self.bar_step_left elif max_label == 'U': dx *= self.bar_step_up elif max_label == 'D': dx *= self.bar_step_down elif max_label == 'B': dx *= self.bar_step_both else: logger.debug('Direction %s using bar step %d' % (max_label, self.bar_step_left)) dx *= self.bar_step_left # slow start selected = self.cfg.BAR_SLOW_START['selected'] if self.cfg.BAR_SLOW_START[ selected] and self.tm_trigger.sec( ) < self.cfg.BAR_SLOW_START[selected]: dx *= self.tm_trigger.sec( ) / self.cfg.BAR_SLOW_START[selected][0] # add likelihoods if max_label == bar_label: bar_score += dx else: bar_score -= dx # change of direction if bar_score < 0: bar_score = -bar_score bar_label = max_label bar_score = int(bar_score) if bar_score > 100: bar_score = 100 if self.cfg.FEEDBACK_TYPE == 'BODY': if self.cfg.SHOW_CUE: self.viz.move(bar_label, bar_score, overlay=False, caption=DIRS[true_label], caption_color='G') else: self.viz.move(bar_label, bar_score, overlay=False) else: self.viz.move(bar_label, bar_score, overlay=False) # send the confidence value continuously if self.cfg.WITH_STIMO and self.cfg.STIMO_CONTINUOUS: if self.stimo_timer.sec( ) >= self.cfg.STIMO_COOLOFF: if bar_label == 'U': stimo_code = bar_score else: stimo_code = 0 self.ser.write(bytes([stimo_code])) logger.info('Sent STIMO code %d' % stimo_code) self.stimo_timer.reset() if self.cfg.DEBUG_PROBS: if self.bar_bias is not None: biastxt = '[Bias=%s%.3f] ' % ( self.bar_bias[0], self.bar_bias[1]) else: biastxt = '' msg = '%s%s prob %s acc %s bar %s%d (%.1f ms)' % \ (biastxt, bar_dirs, qc.list2string(probs_new, '%.2f'), qc.list2string(probs, '%.2f'), bar_label, bar_score, tm_classify.msec()) logger.info(msg) if self.logf is not None: self.logf.write(msg + '\n') elif state == 'feedback' and self.tm_trigger.sec( ) > self.cfg.TIMINGS['FEEDBACK']: self.trigger.signal(self.tdef.BLANK) if self.cfg.FEEDBACK_TYPE == 'BODY': state = 'return' self.tm_trigger.reset() else: state = 'gap_s' self.viz.fill() self.viz.update() return bar_label elif state == 'return': self.viz.set_glass_feedback(False) if self.cfg.WITH_STIMO: self.viz.move(bar_label, bar_score, overlay=False, barcolor='B') else: self.viz.move(bar_label, bar_score, overlay=False, barcolor='Y') self.viz.set_glass_feedback(True) bar_score -= 5 if bar_score <= 0: state = 'gap_s' self.viz.fill() self.viz.update() return bar_label self.viz.update() key = cv2.waitKeyEx(1) if key == KEYS['esc'] or not self.protocol_state.value: return elif key == KEYS['space']: dx = 0 bar_score = 0 probs = [1.0 / len(bar_dirs)] * len(bar_dirs) self.viz.move(bar_dirs[0], bar_score, overlay=False) self.viz.update() logger.info('probs and dx reset.') self.tm_trigger.reset() elif key in ARROW_KEYS and ARROW_KEYS[key] in bar_dirs: # change bias on the fly if self.bar_bias is None: self.bar_bias = [ARROW_KEYS[key], BIAS_INCREMENT] else: if ARROW_KEYS[key] == self.bar_bias[0]: self.bar_bias[1] += BIAS_INCREMENT elif self.bar_bias[1] >= BIAS_INCREMENT: self.bar_bias[1] -= BIAS_INCREMENT else: self.bar_bias = [ARROW_KEYS[key], BIAS_INCREMENT] if self.bar_bias[1] == 0: self.bar_bias = None else: bias_idx = bar_dirs.index(self.bar_bias[0])
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: logger.warning('Rex cannot execute undefined action %s' % pred_label) rex_dir = None if rex_dir is not None: bar.move(pred_label, 100, overlay=False, barcolor='B') bar.update() logger.warning('Executing Rex action %s' % rex_dir) 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' logger.info('Trial %d: %s (%s -> %s)' % (trial, msg, true_label, pred_label))
def pcl2fif(filename, interactive=False, outdir=None, external_event=None, offset=0, overwrite=False, precision='single'): """ PyCNBI Python pickle file Params -------- outdir: If None, it will be the subdirectory of the fif file. external_event: Event file in text format. Each row should be: "SAMPLE_INDEX 0 EVENT_TYPE" precision: Data matrix format. 'single' improves backward compatability. """ fdir, fname, fext = qc.parse_path_list(filename) if outdir is None: outdir = fdir + 'fif/' elif outdir[-1] != '/': outdir += '/' data = qc.load_obj(filename) if type(data['signals']) == list: signals_raw = np.array(data['signals'][0]).T # to channels x samples else: signals_raw = data['signals'].T # to channels x samples sample_rate = data['sample_rate'] if 'ch_names' not in data: ch_names = ['CH%d' % (x + 1) for x in range(signals_raw.shape[0])] else: ch_names = data['ch_names'] # search for event channel trig_ch = pu.find_event_channel(signals_raw, ch_names) ''' TODO: REMOVE # exception if trig_ch is None: logger.warning('Inferred event channel is None.') if interactive: logger.warning('If you are sure everything is alright, press Enter.') input() # fix wrong event channel elif trig_ch_guess != trig_ch: logger.warning('Specified event channel (%d) != inferred event channel (%d).' % (trig_ch, trig_ch_guess)) if interactive: input('Press Enter to fix. Event channel will be set to %d.' % trig_ch_guess) ch_names.insert(trig_ch_guess, ch_names.pop(trig_ch)) trig_ch = trig_ch_guess logger.info('New channel list:') for c in ch_names: logger.info('%s' % c) logger.info('Event channel is now set to %d' % trig_ch) ''' # move trigger channel to index 0 if trig_ch is None: # assuming no event channel exists, add a event channel to index 0 for consistency. logger.warning( 'No event channel was not found. Adding a blank event channel to index 0.' ) eventch = np.zeros([1, signals_raw.shape[1]]) signals = np.concatenate((eventch, signals_raw), axis=0) num_eeg_channels = signals_raw.shape[ 0] # data['channels'] is not reliable any more trig_ch = 0 ch_names = ['TRIGGER' ] + ['CH%d' % (x + 1) for x in range(num_eeg_channels)] elif trig_ch == 0: signals = signals_raw num_eeg_channels = data['channels'] - 1 else: # move event channel to 0 logger.info('Moving event channel %d to 0.' % trig_ch) signals = np.concatenate( (signals_raw[[trig_ch]], signals_raw[:trig_ch], signals_raw[trig_ch + 1:]), axis=0) assert signals_raw.shape == signals.shape num_eeg_channels = data['channels'] - 1 ch_names.pop(trig_ch) trig_ch = 0 ch_names.insert(trig_ch, 'TRIGGER') logger.info('New channel list:') for c in ch_names: logger.info('%s' % c) ch_info = ['stim'] + ['eeg'] * num_eeg_channels info = mne.create_info(ch_names, sample_rate, ch_info) # create Raw object raw = mne.io.RawArray(signals, info) raw._times = data['timestamps'] # seems to have no effect if external_event is not None: raw._data[0] = 0 # erase current events events_index = event_timestamps_to_indices(filename, external_event, offset) if len(events_index) == 0: logger.warning('No events were found in the event file') else: logger.info('Found %d events' % len(events_index)) raw.add_events(events_index, stim_channel='TRIGGER') qc.make_dirs(outdir) fiffile = outdir + fname + '.fif' raw.save(fiffile, verbose=False, overwrite=overwrite, fmt=precision) logger.info('Saved to %s' % fiffile) saveChannels2txt(outdir, ch_names) return True
def bdf2fif_matlab(filename, interactive=False, outdir=None): """ BioSemi bdf reader using BioSig toolbox of MATLAB. """ # 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' matfile = outdir + fname + '.mat' if not os.path.exists(matfile): logger.info('Converting input to mat file') run = "[sig,header]=sload('%s'); save('%s','sig','header');" % ( filename, matfile) qc.matlab(run) if not os.path.exists(matfile): logger.error('mat file convertion error.') sys.exit() mat = scipy.io.loadmat(matfile) os.remove(matfile) sample_rate = int(mat['header']['SampleRate']) nch = mat['sig'].shape[1] # assume Biosemi always has the same number of channels if nch == 73: ch_names = CAP['BIOSEMI_64'] extra_ch = nch - len(CAP['BIOSEMI_64_INFO']) extra_names = [] for ch in range(extra_ch): extra_names.append('EXTRA%d' % ch) ch_names = ch_names + extra_names ch_info = CAP['BIOSEMI_64_INFO'] + ['misc'] * extra_ch else: logger.warning('Unrecognized number of channels (%d)' % nch) logger.warning( 'The last channel will be assumed to be trigger. Press Enter to continue, or Ctrl+C to break.' ) if interactive: input() # Set the trigger to be channel 0 because later we will move it to channel 0. ch_names = ['TRIGGER'] + ['CH%d' % (x + 1) for x in range(nch - 1)] ch_info = ['stim'] + ['eeg'] * (nch - 1) signals_raw = mat['sig'].T # -> channels x samples # Note: Biosig's sload() sometimes returns bogus event values so we use the following for events bdf = mne.io.read_raw_edf(filename, preload=True) events = mne.find_events(bdf, stim_channel='TRIGGER', shortest_event=1, consecutive=True) # signals_raw[-1][:]= bdf._data[-1][:] # overwrite with the correct event values # Move the event channel to 0 (for consistency) signals = np.concatenate( (signals_raw[-1, :].reshape(1, -1), signals_raw[:-1, :])) signals[0] *= 0 # init the event channel info = mne.create_info(ch_names, sample_rate, ch_info, montage='standard_1005') # 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') logger.info('Saved to %s' % fiffile) saveChannels2txt(outdir, ch_names)
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): logger.info('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): logger.error('mat file convertion error.') sys.exit() else: logger.warning('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') logger.info('Saved to %s' % fiffile) saveChannels2txt(outdir, ch_names)
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 [samples x channels], timestamps [samples] """ timestamp_offset = False if len(self.timestamps[0]) == 0: timestamp_offset = True self.watchdog.reset() tslist = [] received = False chunk = None while not received: while self.watchdog.sec() < 5: # chunk = [frames]x[ch], tslist = [frames] if len(tslist) == 0: chunk, tslist = self.inlets[0].pull_chunk( max_samples=self.stream_bufsize) if blocking == False and len(tslist) == 0: return np.empty((0, len(self.ch_list))), [] if len(tslist) > 0: if timestamp_offset is True: lsl_clock = pylsl.local_clock() received = True break time.sleep(0.0005) else: logger.warning( 'Timeout occurred while acquiring data. Amp driver bug?') # give up and return empty values to avoid deadlock return np.empty((0, len(self.ch_list))), [] data = np.array(chunk) # BioSemi has pull-up resistor instead of pull-down 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[0]) > self.bufsize: self.buffers[0] = self.buffers[0][-self.bufsize:] self.timestamps[0] = self.timestamps[0][-self.bufsize:] if timestamp_offset is True: timestamp_offset = False logger.info('LSL timestamp = %s' % lsl_clock) logger.info('Server timestamp = %s' % self.timestamps[-1][-1]) self.lsl_time_offset = self.timestamps[-1][-1] - lsl_clock logger.info('Offset = %.3f ' % (self.lsl_time_offset)) if abs(self.lsl_time_offset) > 0.1: logger.warning('LSL server has a high timestamp offset.') else: logger.info_green('LSL time server synchronized') ''' TODO: test the merging of multiple streams # 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)
def classify(self, decoder, true_label, title_text, bar_dirs, state='start'): """ Run a single trial """ self.tm_trigger.reset() if self.bar_bias is not None: bias_idx = bar_dirs.index(self.bar_bias[0]) if self.logf is not None: self.logf.write('True label: %s\n' % true_label) tm_classify = qc.Timer() while True: self.tm_display.sleep_atleast(self.refresh_delay) self.tm_display.reset() if state == 'start' and self.tm_trigger.sec() > self.cfg.T_INIT: state = 'gap_s' self.bar.fill() self.tm_trigger.reset() self.trigger.signal(self.tdef.INIT) elif state == 'gap_s': self.bar.put_text(title_text) state = 'gap' self.tm_trigger.reset() elif state == 'gap' and self.tm_trigger.sec() > self.cfg.T_GAP: state = 'cue' self.bar.fill() self.bar.draw_cue() self.bar.glass_draw_cue() self.trigger.signal(self.tdef.CUE) self.tm_trigger.reset() elif state == 'cue' and self.tm_trigger.sec() > self.cfg.T_READY: state = 'dir_r' if self.cfg.FEEDBACK_TYPE == 'BODY': self.bar.set_pc_feedback(False) self.bar.move(true_label, 100, overlay=False, barcolor='G') if self.cfg.FEEDBACK_TYPE == 'BODY': self.bar.set_pc_feedback(True) if self.cfg.SHOW_CUE is True: self.bar.put_text(dirs[true_label], 'R') if true_label == 'L': # left self.trigger.signal(self.tdef.LEFT_READY) elif true_label == 'R': # right self.trigger.signal(self.tdef.RIGHT_READY) elif true_label == 'U': # up self.trigger.signal(self.tdef.UP_READY) elif true_label == 'D': # down self.trigger.signal(self.tdef.DOWN_READY) elif true_label == 'B': # both hands self.trigger.signal(self.tdef.BOTH_READY) else: raise RuntimeError('Unknown direction %s' % true_label) self.tm_trigger.reset() ################################################################## ################################################################## #qc.print_c('Executing Rex action %s' % 'N', 'W') #os.system('%s/Rex/RexControlSimple.exe %s %s' % (pycnbi.ROOT, 'COM3', 'N')) ################################################################## ################################################################## elif state == 'dir_r' and self.tm_trigger.sec( ) > self.cfg.T_DIR_CUE: self.bar.fill() self.bar.draw_cue() self.bar.glass_draw_cue() state = 'dir' # initialize bar scores bar_label = bar_dirs[0] bar_score = 0 probs = [1.0 / len(bar_dirs)] * len(bar_dirs) self.bar.move(bar_label, bar_score, overlay=False) probs_acc = np.zeros(len(probs)) if true_label == 'L': # left self.trigger.signal(self.tdef.LEFT_GO) elif true_label == 'R': # right self.trigger.signal(self.tdef.RIGHT_GO) elif true_label == 'U': # up self.trigger.signal(self.tdef.UP_GO) elif true_label == 'D': # down self.trigger.signal(self.tdef.DOWN_GO) elif true_label == 'B': # both self.trigger.signal(self.tdef.BOTH_GO) else: raise RuntimeError('Unknown truedirection %s' % true_label) self.tm_watchdog.reset() self.tm_trigger.reset() elif state == 'dir': if self.tm_trigger.sec() > self.cfg.T_CLASSIFY or ( self.premature_end and bar_score >= 100): if not hasattr( self.cfg, 'SHOW_RESULT') or self.cfg.SHOW_RESULT is True: # show classfication result if self.cfg.FEEDBACK_TYPE == 'BODY': self.bar.move(bar_label, bar_score, overlay=False, barcolor='Y', caption=dirs[bar_label], caption_color='Y') else: self.bar.move(bar_label, 100, overlay=False, barcolor='Y') else: self.bar.move(bar_label, bar_score, overlay=False, barcolor='Y', caption='TRIAL END', caption_color='Y') ########################## TEST WITH BAR ############################# ''' if self.cfg.FEEDBACK_TYPE == 'BODY': self.bar.move(bar_label, bar_score, overlay=False, barcolor='Y', caption='TRIAL END', caption_color='Y') else: self.bar.move(bar_label, 0, overlay=False, barcolor='Y') ''' self.trigger.signal(self.tdef.FEEDBACK) probs_acc /= sum(probs_acc) if self.cfg.DEBUG_PROBS: msg = 'DEBUG: Accumulated probabilities = %s' % qc.list2string( probs_acc, '%.3f') logger.info(msg) if self.logf is not None: self.logf.write(msg + '\n') if self.logf is not None: self.logf.write('%s detected as %s (%d)\n\n' % (true_label, bar_label, bar_score)) self.logf.flush() # end of trial state = 'feedback' self.tm_trigger.reset() else: # classify probs_new = decoder.get_prob_unread() if probs_new is None: if self.tm_watchdog.sec() > 3: logger.warning( 'No classification being done. Are you receiving data streams?' ) self.tm_watchdog.reset() else: self.tm_watchdog.reset() # bias and accumulate if self.bar_bias is not None: probs_new[bias_idx] += self.bar_bias[1] newsum = sum(probs_new) probs_new = [p / newsum for p in probs_new] probs_acc += np.array(probs_new) for i in range(len(probs_new)): probs[i] = probs[i] * self.alpha1 + probs_new[ i] * self.alpha2 ''' Original: accumulate and bias # accumulate probs for i in range( len(probs_new) ): probs[i]= probs[i] * self.alpha1 + probs_new[i] * self.alpha2 # bias bar if self.bar_bias is not None: probs[bias_idx] += self.bar_bias[1] newsum= sum(probs) probs= [p/newsum for p in probs] ''' # determine the direction max_pidx = qc.get_index_max(probs) max_label = bar_dirs[max_pidx] if self.cfg.POSITIVE_FEEDBACK is False or \ (self.cfg.POSITIVE_FEEDBACK and true_label == max_label): dx = probs[max_pidx] if max_label == 'R': dx *= self.bar_step_right elif max_label == 'L': dx *= self.bar_step_left elif max_label == 'U': dx *= self.bar_step_up elif max_label == 'D': dx *= self.bar_step_down elif max_label == 'B': dx *= self.bar_step_both else: logger.debug('Direction %s using bar step %d' % (max_label, self.bar_step_left)) dx *= self.bar_step_left ################################################ ################################################ # slower in the beginning #if self.tm_trigger.sec() < 2.0: # dx *= self.tm_trigger.sec() * 0.5 ################################################ ################################################ # add likelihoods if max_label == bar_label: bar_score += dx else: bar_score -= dx # change of direction if bar_score < 0: bar_score = -bar_score bar_label = max_label bar_score = int(bar_score) if bar_score > 100: bar_score = 100 if self.cfg.FEEDBACK_TYPE == 'BODY': if self.cfg.SHOW_CUE: self.bar.move(bar_label, bar_score, overlay=False, caption=dirs[true_label], caption_color='G') else: self.bar.move(bar_label, bar_score, overlay=False) else: self.bar.move(bar_label, bar_score, overlay=False) if self.cfg.DEBUG_PROBS: if self.bar_bias is not None: biastxt = '[Bias=%s%.3f] ' % ( self.bar_bias[0], self.bar_bias[1]) else: biastxt = '' msg = '%s%s raw %s acc %s bar %s%d (%.1f ms)' % \ (biastxt, bar_dirs, qc.list2string(probs_new, '%.2f'), qc.list2string(probs, '%.2f'), bar_label, bar_score, tm_classify.msec()) logger.info(msg) if self.logf is not None: self.logf.write(msg + '\n') tm_classify.reset() elif state == 'feedback' and self.tm_trigger.sec( ) > self.cfg.T_FEEDBACK: self.trigger.signal(self.tdef.BLANK) if self.cfg.FEEDBACK_TYPE == 'BODY': state = 'return' self.tm_trigger.reset() else: state = 'gap_s' self.bar.fill() self.bar.update() return bar_label elif state == 'return': self.bar.set_glass_feedback(False) self.bar.move(bar_label, bar_score, overlay=False, barcolor='Y') self.bar.set_glass_feedback(True) bar_score -= 5 if bar_score <= 0: state = 'gap_s' self.bar.fill() self.bar.update() return bar_label self.bar.update() key = 0xFF & cv2.waitKey(1) if key == keys['esc']: return None if key == keys['space']: dx = 0 bar_score = 0 probs = [1.0 / len(bar_dirs)] * len(bar_dirs) self.bar.move(bar_dirs[0], bar_score, overlay=False) self.bar.update() logger.info('probs and dx reset.')
def __init__(self, state=mp.Value('i', 1), lpttype='USB2LPT', portaddr=None, verbose=True, check_lsl_offset=False): self.evefile = None self.lpttype = lpttype self.verbose = verbose if self.lpttype in ['USB2LPT', 'DESKTOP']: if self.lpttype == 'USB2LPT': if ctypes.sizeof(ctypes.c_voidp) == 4: dllname = 'LptControl_USB2LPT32.dll' # 32 bit else: dllname = 'LptControl_USB2LPT64.dll' # 64 bit if portaddr not in [0x278, 0x378]: logger.warning('LPT port address %d is unusual.' % portaddr) elif self.lpttype == 'DESKTOP': if ctypes.sizeof(ctypes.c_voidp) == 4: dllname = 'LptControl_Desktop32.dll' # 32 bit else: dllname = 'LptControl_Desktop64.dll' # 64 bit if portaddr not in [0x278, 0x378]: logger.warning('LPT port address %d is unusual.' % portaddr) self.portaddr = portaddr search = [] search.append(os.path.dirname(__file__) + '/' + dllname) search.append(os.path.dirname(__file__) + '/libs/' + dllname) search.append(os.getcwd() + '/' + dllname) search.append(os.getcwd() + '/libs/' + dllname) for f in search: if os.path.exists(f): dllpath = f break else: logger.error('Cannot find the required library %s' % dllname) raise RuntimeError logger.info('Loading %s' % dllpath) self.lpt = ctypes.cdll.LoadLibrary(dllpath) elif self.lpttype == 'ARDUINO': import serial, serial.tools.list_ports BAUD_RATE = 115200 # portaddr should be None or in the form of 'COM1', 'COM2', etc. if portaddr is None: arduinos = [x for x in serial.tools.list_ports.grep('Arduino')] if len(arduinos) == 0: logger.error('No Arduino found. Stop.') sys.exit() for i, a in enumerate(arduinos): logger.info('Found %s' % a[0]) try: com_port = arduinos[0].device except AttributeError: # depends on Python distribution com_port = arduinos[0][0] else: com_port = portaddr self.ser = serial.Serial(com_port, BAUD_RATE) time.sleep(1) # doesn't work without this delay. why? logger.info('Connected to %s.' % com_port) elif self.lpttype == 'SOFTWARE': from pycnbi.stream_receiver.stream_receiver import StreamReceiver logger.info('Using software trigger') # get data file location LSL_SERVER = 'StreamRecorderInfo' inlet = cnbi_lsl.start_client(LSL_SERVER, state) evefile = inlet.info().source_id() eveoffset_file = evefile[:-4] + '-offset.txt' logger.info('Event file is: %s' % evefile) self.evefile = open(evefile, 'a') if check_lsl_offset: # check server LSL time server integrity logger.info("Checking LSL server's timestamp integrity for logging software triggers.") 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) local_time = pylsl.local_clock() server_time = sr.get_window_list()[1][-1] lsl_time_offset = local_time - server_time with open(eveoffset_file, 'a') as f: f.write('Local time: %.6f, Server time: %.6f, Offset: %.6f\n' % (local_time, server_time, lsl_time_offset)) logger.info('LSL timestamp offset (%.3f) saved to %s' % (lsl_time_offset, eveoffset_file)) elif self.lpttype == 'FAKE' or self.lpttype is None or self.lpttype is False: logger.warning('Using a fake trigger.') self.lpttype = 'FAKE' self.lpt = None else: logger.error('Unrecognized lpttype device name %s' % lpttype) sys.exit(-1)
def compute_features(cfg): ''' Compute features using config specification. Performs preprocessing, epcoching and feature computation. Input ===== Config file object Output ====== Feature data in dictionary - X_data: feature vectors - Y_data: feature labels - wlen: window length in seconds - w_frames: window length in frames - psde: MNE PSD estimator object - picks: channels used for feature computation - sfreq: sampling frequency - ch_names: channel names - times: feature timestamp (leading edge of a window) ''' # Preprocessing, epoching and PSD computation ftrain = [] for f in qc.get_file_list(cfg.DATA_PATH, fullpath=True): if f[-4:] in ['.fif', '.fiff']: ftrain.append(f) if len(ftrain) > 1 and cfg.PICKED_CHANNELS is not None and type( cfg.PICKED_CHANNELS[0]) == int: logger.error( 'When loading multiple EEG files, PICKED_CHANNELS must be list of string, not integers because they may have different channel order.' ) raise RuntimeError raw, events = pu.load_multi(ftrain) reref = cfg.REREFERENCE[cfg.REREFERENCE['selected']] if reref is not None: pu.rereference(raw, reref['New'], reref['Old']) if cfg.LOAD_EVENTS[cfg.LOAD_EVENTS['selected']] is not None: events = mne.read_events(cfg.LOAD_EVENTS[cfg.LOAD_EVENTS['selected']]) trigger_def_int = set() for a in cfg.TRIGGER_DEF: trigger_def_int.add(getattr(cfg.tdef, a)) triggers = {cfg.tdef.by_value[c]: c for c in trigger_def_int} # Pick channels if cfg.PICKED_CHANNELS is None: chlist = [int(x) for x in pick_types(raw.info, stim=False, eeg=True)] else: chlist = cfg.PICKED_CHANNELS picks = [] for c in chlist: if type(c) == int: picks.append(c) elif type(c) == str: picks.append(raw.ch_names.index(c)) else: logger.error( 'PICKED_CHANNELS has a value of unknown type %s.\nPICKED_CHANNELS=%s' % (type(c), cfg.PICKED_CHANNELS)) raise RuntimeError if cfg.EXCLUDED_CHANNELS is not None: for c in cfg.EXCLUDED_CHANNELS: if type(c) == str: if c not in raw.ch_names: logger.warning( 'Exclusion channel %s does not exist. Ignored.' % c) continue c_int = raw.ch_names.index(c) elif type(c) == int: c_int = c else: logger.error( 'EXCLUDED_CHANNELS has a value of unknown type %s.\nEXCLUDED_CHANNELS=%s' % (type(c), cfg.EXCLUDED_CHANNELS)) raise RuntimeError if c_int in picks: del picks[picks.index(c_int)] if max(picks) > len(raw.ch_names): logger.error( '"picks" has a channel index %d while there are only %d channels.' % (max(picks), len(raw.ch_names))) raise ValueError if hasattr(cfg, 'SP_CHANNELS') and cfg.SP_CHANNELS is not None: logger.warning( 'SP_CHANNELS parameter is not supported yet. Will be set to PICKED_CHANNELS.' ) if hasattr(cfg, 'TP_CHANNELS') and cfg.TP_CHANNELS is not None: logger.warning( 'TP_CHANNELS parameter is not supported yet. Will be set to PICKED_CHANNELS.' ) if hasattr(cfg, 'NOTCH_CHANNELS') and cfg.NOTCH_CHANNELS is not None: logger.warning( 'NOTCH_CHANNELS parameter is not supported yet. Will be set to PICKED_CHANNELS.' ) if 'decim' not in cfg.FEATURES['PSD']: cfg.FEATURES['PSD']['decim'] = 1 logger.warning('PSD["decim"] undefined. Set to 1.') # 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) 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, on_missing='warning') except: logger.exception('Problem while epoching.') raise RuntimeError label_set = np.unique(triggers.values()) # Compute features if cfg.FEATURES['selected'] == 'PSD': preprocess = dict(sfreq=epochs_train.info['sfreq'], spatial=cfg.SP_FILTER, spatial_ch=None, spectral=cfg.TP_FILTER[cfg.TP_FILTER['selected']], spectral_ch=None, notch=cfg.NOTCH_FILTER[cfg.NOTCH_FILTER['selected']], notch_ch=None, multiplier=cfg.MULTIPLIER, ch_names=None, rereference=None, decim=cfg.FEATURES['PSD']['decim'], n_jobs=cfg.N_JOBS) featdata = get_psd_feature(epochs_train, cfg.EPOCH, cfg.FEATURES['PSD'], picks=None, preprocess=preprocess, n_jobs=cfg.N_JOBS) elif cfg.FEATURES == 'TIMELAG': ''' TODO: Implement multiple epochs for timelag feature ''' logger.error( 'MULTIPLE EPOCHS NOT IMPLEMENTED YET FOR TIMELAG FEATURE.') raise NotImplementedError elif cfg.FEATURES == 'WAVELET': ''' TODO: Implement multiple epochs for wavelet feature ''' logger.error( 'MULTIPLE EPOCHS NOT IMPLEMENTED YET FOR WAVELET FEATURE.') raise NotImplementedError else: logger.error('%s feature type is not supported.' % cfg.FEATURES) raise NotImplementedError featdata['picks'] = picks featdata['sfreq'] = raw.info['sfreq'] featdata['ch_names'] = raw.ch_names return featdata
def slice_win(epochs_data, w_starts, w_length, psde, picks=None, title=None, flatten=True, preprocess=None, verbose=False): ''' Compute PSD values of a sliding window Params epochs_data ([channels]x[samples]): raw epoch data w_starts (list): starting indices of sample segments w_length (int): window length in number of samples psde: MNE PSDEstimator object picks (list): subset of channels within epochs_data title (string): print out the title associated with PID flatten (boolean): generate concatenated feature vectors If True: X = [windows] x [channels x freqs] If False: X = [windows] x [channels] x [freqs] preprocess (dict): None or parameters for pycnbi_utils.preprocess() with the following keys: sfreq, spatial, spatial_ch, spectral, spectral_ch, notch, notch_ch, multiplier, ch_names, rereference, decim, n_jobs Returns: [windows] x [channels*freqs] or [windows] x [channels] x [freqs] ''' # raise error for wrong indexing def WrongIndexError(Exception): logger.error('%s' % Exception) if type(w_length) is not int: logger.warning('w_length type is %s. Converting to int.' % type(w_length)) w_length = int(w_length) if title is None: title = '[PID %d] Frames %d-%d' % (os.getpid(), w_starts[0], w_starts[-1] + w_length - 1) else: title = '[PID %d] %s' % (os.getpid(), title) if preprocess is not None and preprocess['decim'] != 1: title += ' (decim factor %d)' % preprocess['decim'] logger.info(title) X = None for n in w_starts: n = int(round(n)) if n >= epochs_data.shape[1]: logger.error( 'w_starts has an out-of-bounds index %d for epoch length %d.' % (n, epochs_data.shape[1])) raise WrongIndexError window = epochs_data[:, n:(n + w_length)] if preprocess is not None: window = pu.preprocess(window, sfreq=preprocess['sfreq'], spatial=preprocess['spatial'], spatial_ch=preprocess['spatial_ch'], spectral=preprocess['spectral'], spectral_ch=preprocess['spectral_ch'], notch=preprocess['notch'], notch_ch=preprocess['notch_ch'], multiplier=preprocess['multiplier'], ch_names=preprocess['ch_names'], rereference=preprocess['rereference'], decim=preprocess['decim'], n_jobs=preprocess['n_jobs']) # dimension: psde.transform( [epochs x channels x times] ) psd = psde.transform( window.reshape((1, window.shape[0], window.shape[1]))) psd = psd.reshape((psd.shape[0], psd.shape[1] * psd.shape[2])) if picks: psd = psd[0][picks] psd = psd.reshape((1, len(psd))) if X is None: X = psd else: X = np.concatenate((X, psd), axis=0) if verbose == True: logger.info('[PID %d] processing frame %d / %d' % (os.getpid(), n, w_starts[-1])) return X
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: logger.info("Looking for a streaming server...") else: logger.info("Looking for %s (Serial %s) ..." % (self.amp_name, self.amp_serial)) streamInfos = pylsl.resolve_streams() 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() # 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: logger.info( '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: logger.info( '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: logger.info( '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: logger.info( '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: logger.info( '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_event_channel( ch_names=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: logger.info( '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_event_channel( ch_names=ch_list) channels += si.channel_count() amps.append(si) server_found = True break elif find_any: logger.info( '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_event_channel( ch_names=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: logger.warning( 'Trigger channel not fonud. Adding an empty channel 0.') else: if self._lsl_tr_channel != 0: logger.info_yellow( 'Trigger channel found at index %d. Moving to index 0.' % self._lsl_tr_channel) 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: # data type of the 2nd argument (max_buflen) is int according to LSL C++ specification! inlet = pylsl.StreamInlet(amp, max_buflen=self.stream_bufsec) inlets_master.append(inlet) self.buffers.append([]) self.timestamps.append([]) inlets = inlets_master + inlets_slaves sample_rate = amps[0].nominal_srate() logger.info('Channels: %d' % channels) logger.info('LSL Protocol version: %s' % amps[0].version()) logger.info('Source sampling rate: %.1f' % sample_rate) logger.info('Unit multiplier: %.1f' % self.multiplier) #self.winsize = int(self.winsec * sample_rate) #self.bufsize = int(self.bufsec * sample_rate) self.winsize = int(round(self.winsec * sample_rate)) self.bufsize = int(round(self.bufsec * sample_rate)) self.stream_bufsize = int(round(self.stream_bufsec * sample_rate)) self.sample_rate = sample_rate self.connected = True self.ch_list = ch_list self.inlets = inlets # Note: not picklable! # TODO: check if there's any problem with multiple inlets if len(self.inlets) > 1: logger.warning( 'Merging of multiple acquisition servers is not supported yet. Only %s will be used.' % amps[0].name()) ''' for i in range(1, len(self.inlets)): chunk, tslist = self.inlets[i].pull_chunk(max_samples=self.stream_bufsize) 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:] ''' # 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 logger.info('self.ch_list %s' % self.ch_list) # fill in initial buffer logger.info('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 logger.info('Start receiving stream data.')
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)
""" import gzip import pycnbi.utils.q_common as qc from pycnbi.protocols.viz_human import read_images from pycnbi import logger try: import cPickle as pickle # Python 2 (cPickle = C version of pickle) except ImportError: import pickle # Python 3 (C version is the default) if __name__ == '__main__': LEFT_IMAGE_DIR = r'D:\work\pycnbi_protocols\BodyFeedback\left_behind' RIGHT_IMAGE_DIR = r'D:\work\pycnbi_protocols\BodyFeedback\right_behind' EXPORT_IMAGE_DIR = r'D:\work\pycnbi_protocols\BodyFeedback' if pickle.HIGHEST_PROTOCOL >= 4: outfile = '%s/BodyVisuals.pkl' % EXPORT_IMAGE_DIR tm = qc.Timer() logger.info('Reading images from %s' % LEFT_IMAGE_DIR ) left_images = read_images(LEFT_IMAGE_DIR) logger.info('Reading images from %s' % RIGHT_IMAGE_DIR) right_images = read_images(RIGHT_IMAGE_DIR) logger.info('Took %.1f s. Start exporting ...' % tm.sec()) img_data = {'left_images':left_images, 'right_images':right_images} with gzip.open(outfile, 'wb') as fp: pickle.dump(img_data, fp) logger.info('Exported to %s' % outfile) else: logger.warning('Your Python pickle protocol version is less than 4, which will be slower with loading a pickle object.')
def check_config(cfg): critical_vars = { 'COMMON': [ 'TRIGGER_FILE', 'TRIGGER_DEF', 'EPOCH', 'DATA_PATH', 'PICKED_CHANNELS', 'SP_FILTER', 'SP_CHANNELS', 'TP_FILTER', 'NOTCH_FILTER', 'FEATURES', 'CLASSIFIER', 'CV_PERFORM' ], 'RF': ['trees', 'depth', 'seed'], 'GB': ['trees', 'learning_rate', 'depth', 'seed'], 'LDA': [], 'rLDA': ['r_coeff'], 'StratifiedShuffleSplit': ['test_ratio', 'folds', 'seed', 'export_result'], 'LeaveOneOut': ['export_result'] } # optional variables with default values optional_vars = { 'MULTIPLIER': 1, 'EXPORT_GOOD_FEATURES': False, 'FEAT_TOPN': 10, 'EXPORT_CLS': False, 'REREFERENCE': None, 'N_JOBS': None, 'EXCLUDED_CHANNELS': None, 'LOAD_EVENTS': None, 'CV': { 'IGNORE_THRES': None, 'DECISION_THRES': None, 'BALANCE_SAMPLES': False }, } for v in critical_vars['COMMON']: if not hasattr(cfg, v): logger.error('%s not defined in config.' % v) raise RuntimeError for key in optional_vars: if not hasattr(cfg, key): setattr(cfg, key, optional_vars[key]) logger.warning('Setting undefined parameter %s=%s' % (key, getattr(cfg, key))) if 'decim' not in cfg.FEATURES['PSD']: cfg.FEATURES['PSD']['decim'] = 1 # classifier parameters check selected_classifier = cfg.CLASSIFIER[cfg.CLASSIFIER['selected']] if selected_classifier == 'RF': if 'RF' not in cfg.CLASSIFIER: logger.error('"RF" not defined in config.') raise RuntimeError for v in critical_vars['RF']: if v not in cfg.CLASSIFIER['RF']: logger.error('%s not defined in config.' % v) raise RuntimeError elif selected_classifier == 'GB' or selected_classifier == 'XGB': if 'GB' not in cfg.CLASSIFIER: logger.error('"GB" not defined in config.') raise RuntimeError for v in critical_vars['GB']: if v not in cfg.CLASSIFIER[selected_classifier]: logger.error('%s not defined in config.' % v) raise RuntimeError elif selected_classifier == 'rLDA': if 'rLDA' not in cfg.CLASSIFIER: logger.error('"rLDA" not defined in config.') raise RuntimeError for v in critical_vars['rLDA']: if v not in cfg.CLASSIFIER['rLDA']: logger.error('%s not defined in config.' % v) raise RuntimeError cv_selected = cfg.CV_PERFORM['selected'] if cfg.CV_PERFORM[cv_selected] is not None: if cv_selected == 'StratifiedShuffleSplit': if 'StratifiedShuffleSplit' not in cfg.CV_PERFORM: logger.error('"StratifiedShuffleSplit" not defined in config.') raise RuntimeError for v in critical_vars['StratifiedShuffleSplit']: if v not in cfg.CV_PERFORM[cv_selected]: logger.error('%s not defined in config.' % v) raise RuntimeError elif cv_selected == 'LeaveOneOut': if 'LeaveOneOut' not in cfg.CV_PERFORM: logger.error('"LeaveOneOut" not defined in config.') raise RuntimeError for v in critical_vars['LeaveOneOut']: if v not in cfg.CV_PERFORM[cv_selected]: logger.error('%s not defined in config.' % v) raise RuntimeError if cfg.N_JOBS is None: cfg.N_JOBS = mp.cpu_count() return cfg
def check_config(cfg): critical_vars = { 'COMMON': [ #'tdef', 'TRIGGER_DEF', 'EPOCH', 'DATA_PATH', 'PSD', 'PICKED_CHANNELS', '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 = { 'MULTIPLIER': 1, 'EXPORT_GOOD_FEATURES': False, 'FEAT_TOPN': 10, 'EXPORT_CLS': False, 'REF_CHANNELS': None, 'N_JOBS': None, 'EXCLUDED_CHANNELS': None, 'CV_IGNORE_THRES': None, 'CV_DECISION_THRES': None, 'BALANCE_SAMPLES': False } 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]) logger.warning('Setting undefined parameter %s=%s' % (key, getattr(cfg, key))) if 'decim' not in cfg.PSD: cfg.PSD['decim'] = 1 # 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 logger.warning('Setting undefined parameter CV_RANDOM_SEED=%s' % (cfg.CV_RANDOM_SEED)) 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 preprocess(raw, sfreq=None, spatial=None, spatial_ch=None, spectral=None, spectral_ch=None, notch=None, notch_ch=None, multiplier=1, ch_names=None, rereference=None, decim=None, n_jobs=1): """ Apply spatial, spectral, notch filters and convert unit. raw is modified in-place. Input ------ raw: mne.io.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. rereference: Not supported yet. decim: None | int Apply low-pass filter and decimate (downsample). sfreq must be given. Ignored if 1. Output ------ Same input data structure. Note: To save computation time, input data may be modified in-place. TODO: Add an option to disable in-place modification. """ # 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)) if decim is not None and decim != 1: if sfreq is None: logger.error('Decimation cannot be applied if sfreq is None.') raise ValueError 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: logger.warning('No trigger channel found. Using all channels.') else: tch_name = ch_names[tch] eeg_channels.pop(tch) # Re-reference channels if rereference is not None: logger.error('re-referencing not implemented yet. Sorry.') raise NotImplementedError # 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: logger.error('Unknown data shape %s' % str(data.shape)) raise ValueError elif spatial == 'laplacian': if type(spatial_ch) is not dict: logger.error( 'preprocess(): For Lapcacian, spatial_ch must be of form {CHANNEL:[NEIGHBORS], ...}' ) raise TypeError 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: logger.error('preprocess(): Unknown data shape %s' % str(data.shape)) raise ValueError else: logger.error('preprocess(): Unknown spatial filter %s' % spatial) raise ValueError # Downsample if decim is not None and decim != 1: if type(raw) == np.ndarray: data = mne.filter.resample(data, down=decim, npad='auto', window='boxcar', n_jobs=1) else: # resample() of Raw* and Epochs object internally calls mne.filter.resample() raw = raw.resample(raw.info['sfreq'] / decim, npad='auto', window='boxcar', n_jobs=1) data = raw._data sfreq /= decim # 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: 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=5, picks=notch_ch_i, method='fft', n_jobs=n_jobs, copy=False) if type(raw) == np.ndarray: raw = data return raw
def __init__(self): logger.warning(' WARNING: MockTrigger class is deprecated.') logger.warning(" Use Trigger('FAKE') instead.")