Exemple #1
0
    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()
Exemple #2
0
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
Exemple #3
0
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')
Exemple #4
0
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)
Exemple #5
0
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.')
Exemple #6
0
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)
Exemple #8
0
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
Exemple #9
0
    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)
Exemple #10
0
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
Exemple #11
0
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()
Exemple #12
0
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
Exemple #13
0
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()
Exemple #14
0
    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 = []
Exemple #15
0
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
Exemple #16
0
 def print(self, msg, color='W'):
     qc.print_c('[StreamReceiver] %s' % msg, color)
Exemple #17
0
            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
Exemple #19
0
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
Exemple #20
0
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
Exemple #21
0
 def print(self, *args):
     qc.print_c('[pyLptControl] ', color='w', end='')
     print(*args)
Exemple #22
0
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.')
Exemple #23
0
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
Exemple #24
0
    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)
Exemple #25
0
    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
Exemple #26
0
 def print(self, msg):
     qc.print_c('[StreamViewer] %s' % msg, color='W')
Exemple #27
0
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()
Exemple #28
0
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)
Exemple #29
0
    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.')
Exemple #30
0
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)