Esempio n. 1
0
def record(state, amp_name, amp_serial, record_dir, eeg_only):
    # set data file name
    filename = time.strftime(record_dir + "/%Y%m%d-%H%M%S-raw.pcl",
                             time.localtime())
    qc.print_c('>> Output file: %s' % (filename), 'W')

    # test writability
    try:
        qc.make_dirs(record_dir)
        open(
            filename,
            'w').write('The data will written when the recording is finished.')
    except:
        raise RuntimeError('Problem writing to %s. Check permission.' %
                           filename)

    # start a server for sending out data filename when software trigger is used
    outlet = start_server('StreamRecorderInfo', channel_format='string',\
        source_id=filename, stype='Markers')

    # connect to EEG stream server
    sr = StreamReceiver(amp_name=amp_name,
                        amp_serial=amp_serial,
                        eeg_only=eeg_only)

    # start recording
    qc.print_c('\n>> Recording started (PID %d).' % os.getpid(), 'W')
    qc.print_c('\n>> Press Enter to stop recording', 'G')
    tm = qc.Timer(autoreset=True)
    next_sec = 1
    while state.value == 1:
        sr.acquire()
        if sr.get_buflen() > next_sec:
            duration = str(datetime.timedelta(seconds=int(sr.get_buflen())))
            print('RECORDING %s' % duration)
            next_sec += 1
        tm.sleep_atleast(0.01)

    # record stop
    qc.print_c('>> Stop requested. Copying buffer', 'G')
    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()
    }
    qc.print_c('Saving raw data ...', 'W')
    qc.save_obj(filename, data)
    print('Saved to %s\n' % filename)

    qc.print_c('Converting raw file into a fif format.', 'W')
    pcl2fif(filename)
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)
Esempio n. 3
0
def log_decoding_helper(state, event_queue, amp_name=None, amp_serial=None, autostop=False):
    """
    Helper function to run StreamReceiver object in background
    """
    logger.info('Event acquisition subprocess started.')

    # wait for the start signal
    while state.value == 0:
        time.sleep(0.01)
    
    # acquire event values and returns event times and event values
    sr = StreamReceiver(buffer_size=0, amp_name=amp_name, amp_serial=amp_serial)
    tm = qc.Timer(autoreset=True)
    started = False
    while state.value == 1:
        chunk, ts_list = sr.acquire()
        if autostop:
            if started is True:
                if len(ts_list) == 0:
                    state.value = 0
                    break
            elif len(ts_list) > 0:
                started = True
        tm.sleep_atleast(0.001)
    logger.info('Event acquisition subprocess finishing up ...')

    buffers, times = sr.get_buffer()
    events = buffers[:, 0] # first channel is the trigger channel
    event_index = np.where(events != 0)[0]
    event_times = times[event_index].reshape(-1).tolist()
    event_values = events[event_index].tolist()
    assert len(event_times) == len(event_values)
    event_queue.put((event_times, event_values))
Esempio n. 4
0
class Scope(QMainWindow):
    def __init__(self,
                 amp_name,
                 amp_serial,
                 state=mp.Value('i', 1),
                 queue=None):
        super(Scope, self).__init__()

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        redirect_stdout_to_queue(logger, queue, 'INFO')
        logger.info('Viewer launched')

        self.amp_name = amp_name
        self.amp_serial = amp_serial
        self.state = state

        self.init_scope()

    #
    # 	Main init function
    #
    def init_scope(self):

        # pg.setConfigOption('background', 'w')
        # pg.setConfigOption('foreground', 'k')

        self.init_config_file()
        self.init_loop()
        self.init_panel_GUI()
        self.init_scope_GUI()
        self.init_timer()

    #
    #	Initializes config file
    #
    def init_config_file(self):
        self.scope_settings = RawConfigParser(allow_no_value=True,
                                              inline_comment_prefixes=('#',
                                                                       ';'))
        if (len(sys.argv) == 1):
            self.show_channel_names = 0
            self.device_name = ""
        else:
            if (sys.argv[1].find("gtec") > -1):
                self.device_name = "gtec"
                self.show_channel_names = 1
            elif (sys.argv[1].find("biosemi") > -1):
                self.device_name = "biosemi"
                self.show_channel_names = 1
            elif (sys.argv[1].find("hiamp") > -1):
                self.device_name = "hiamp"
                self.show_channel_names = 1
            else:
                self.device_name = ""
                self.show_channel_names = 0
        # self.scope_settings.read(os.getenv("HOME") + "/.scope_settings.ini")
        self.scope_settings.read(
            str(path2_viewerFolder / '.scope_settings.ini'))

    #
    # 	Initialize control panel parameter
    #
    def init_panel_GUI(self):

        self.show_TID_events = False
        self.show_LPT_events = False
        self.show_Key_events = False

        # Event handler
        self.ui.comboBox_scale.activated.connect(
            self.onActivated_combobox_scale)
        self.ui.spinBox_time.valueChanged.connect(
            self.onValueChanged_spinbox_time)
        self.ui.checkBox_car.stateChanged.connect(
            self.onActivated_checkbox_car)
        self.ui.checkBox_bandpass.stateChanged.connect(
            self.onActivated_checkbox_bandpass)
        self.ui.checkBox_showTID.stateChanged.connect(
            self.onActivated_checkbox_TID)
        self.ui.checkBox_showLPT.stateChanged.connect(
            self.onActivated_checkbox_LPT)
        self.ui.checkBox_showKey.stateChanged.connect(
            self.onActivated_checkbox_Key)
        self.ui.pushButton_bp.clicked.connect(self.onClicked_button_bp)
        self.ui.pushButton_rec.clicked.connect(self.onClicked_button_rec)
        self.ui.pushButton_stoprec.clicked.connect(
            self.onClicked_button_stoprec)

        self.ui.pushButton_stoprec.setEnabled(False)
        self.ui.comboBox_scale.setCurrentIndex(4)
        self.ui.checkBox_car.setChecked(
            int(self.scope_settings.get("filtering", "apply_car_filter")))
        self.ui.checkBox_bandpass.setChecked(
            int(self.scope_settings.get("filtering", "apply_bandpass_filter")))
        self.ui.checkBox_showTID.setChecked(
            int(self.scope_settings.get("plot", "show_TID_events")))
        self.ui.checkBox_showLPT.setChecked(
            int(self.scope_settings.get("plot", "show_LPT_events")))
        self.ui.checkBox_showKey.setChecked(
            int(self.scope_settings.get("plot", "show_KEY_events")))
        self.ui.statusBar.showMessage("[Not recording]")

        self.channels_to_show_idx = []
        idx = 0
        for y in range(0, 4):
            for x in range(0, NUM_X_CHANNELS):
                if (idx < self.config['eeg_channels']):
                    # self.table_channels.item(x,y).setTextAlignment(QtCore.Qt.AlignCenter)
                    self.ui.table_channels.item(x, y).setSelected(True)  # Qt5
                    #self.table_channels.setItemSelected(self.table_channels.item(x, y), True) # Qt4 only
                    self.channels_to_show_idx.append(idx)
                else:
                    self.ui.table_channels.setItem(x, y,
                                                   QTableWidgetItem("N/A"))
                    self.ui.table_channels.item(x, y).setFlags(
                        QtCore.Qt.NoItemFlags)
                    self.ui.table_channels.item(x, y).setTextAlignment(
                        QtCore.Qt.AlignCenter)
                idx += 1

        self.ui.table_channels.verticalHeader().setStretchLastSection(True)
        self.ui.table_channels.horizontalHeader().setStretchLastSection(True)
        self.ui.table_channels.itemSelectionChanged.connect(
            self.onSelectionChanged_table)

        self.screen_width = 522
        self.screen_height = 160
        # self.setGeometry(100,100, self.screen_width, self.screen_height)
        # self.setFixedSize(self.screen_width, self.screen_height)
        self.setWindowTitle('EEG Scope Panel')
        self.setFocusPolicy(QtCore.Qt.ClickFocus)
        self.setFocus()
        self.show()

    #
    #	Initialize scope parameters
    #
    def init_scope_GUI(self):

        self.bool_parser = {True: '1', False: '0'}

        # PyQTGraph plot initialization
        self.win = pg.GraphicsWindow()
        self.win.setWindowTitle('EEG Scope')
        self.win.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint)
        self.win.keyPressEvent = self.keyPressEvent
        self.win.show()
        self.main_plot_handler = self.win.addPlot()
        self.win.resize(1280, 800)

        # Scales available in the GUI. If you change the options in the GUI
        # you should change them here as well
        self.scales_range = [1, 10, 25, 50, 100, 250, 500, 1000, 2500, 100000]

        # Scale in uV
        self.scale = int(self.scope_settings.get("plot", "scale_plot"))
        # Time window to show in seconds
        self.seconds_to_show = int(self.scope_settings.get(
            "plot", "time_plot"))

        # Y Tick labels. Use values from the config file.
        self.channel_labels = []
        values = []
        ''' For non-LSL systems having no channel names
        for x in range(0, self.config['eeg_channels']):
            if (self.show_channel_names):
                self.channel_labels.append("(" + str(x + 1) + ") " +
                    self.scope_settings.get("internal",
                    "channel_names_" + self.device_name + str(
                    self.config['eeg_channels'])).split(', ')[x])
            else:
                self.channel_labels.append('CH ' + str(x + 1))
        '''
        ch_names = np.array(self.sr.get_channel_names())
        self.channel_labels = ch_names[self.sr.get_eeg_channels()]
        for x in range(0, len(self.channels_to_show_idx)):
            values.append((-x * self.scale,
                           self.channel_labels[self.channels_to_show_idx[x]]))

        values_axis = []
        values_axis.append(values)
        values_axis.append([])

        # Update table labels with current names
        idx = 0
        for y in range(0, 4):
            for x in range(0, NUM_X_CHANNELS):
                if (idx < self.config['eeg_channels']):
                    self.ui.table_channels.item(x, y).setText(
                        self.channel_labels[idx])
                idx += 1

        # Plot initialization
        self.main_plot_handler.getAxis('left').setTicks(values_axis)
        self.main_plot_handler.setRange(
            xRange=[0, self.seconds_to_show],
            yRange=[
                +1.5 * self.scale,
                -0.5 * self.scale - self.scale * self.config['eeg_channels']
            ])
        self.main_plot_handler.disableAutoRange()
        self.main_plot_handler.showGrid(y=True)
        self.main_plot_handler.setLabel(axis='left',
                                        text='Scale (uV): ' + str(self.scale))
        self.main_plot_handler.setLabel(axis='bottom', text='Time (s)')

        # X axis
        self.x_ticks = np.zeros(self.config['sf'] * self.seconds_to_show)
        for x in range(0, self.config['sf'] * self.seconds_to_show):
            self.x_ticks[x] = (x * 1) / float(self.config['sf'])

        # Plotting colors. If channels > 16, colors will roll back to the beginning
        self.colors = np.array([[255, 0, 0], [0, 255, 0], [0, 0, 255],
                                [255, 255, 0], [0, 255, 255], [255, 0, 255],
                                [128, 100, 100], [0, 128, 0], [0, 128, 128],
                                [128, 128, 0], [255, 128, 128], [128, 0, 128],
                                [128, 255, 0], [255, 128, 0], [0, 255, 128],
                                [128, 0, 255]])

        # We want a lightweight scope, so we downsample the plotting to 64 Hz
        self.subsampling_value = self.config['sf'] / 64

        # EEG data for plotting
        self.data_plot = np.zeros((self.config['sf'] * self.seconds_to_show,
                                   self.config['eeg_channels']))
        self.curve_eeg = []
        for x in range(0, len(self.channels_to_show_idx)):
            self.curve_eeg.append(
                self.main_plot_handler.plot(
                    x=self.x_ticks,
                    y=self.data_plot[:, self.channels_to_show_idx[x]],
                    pen=pg.mkColor(self.colors[self.channels_to_show_idx[x] %
                                               16, :])))
        # self.curve_eeg[-1].setDownsampling(ds=self.subsampling_value, auto=False, method="mean")

        # Events data
        self.events_detected = []
        self.events_curves = []
        self.events_text = []

        # CAR initialization
        self.apply_car = int(
            self.scope_settings.get("filtering", "apply_car_filter"))
        self.matrix_car = np.zeros(
            (self.config['eeg_channels'], self.config['eeg_channels']),
            dtype=float)
        self.matrix_car[:, :] = -1 / float(self.config['eeg_channels'])
        np.fill_diagonal(self.matrix_car,
                         1 - (1 / float(self.config['eeg_channels'])))

        # Laplacian initalization. TO BE DONE
        self.matrix_lap = np.zeros(
            (self.config['eeg_channels'], self.config['eeg_channels']),
            dtype=float)
        np.fill_diagonal(self.matrix_lap, 1)
        '''
        self.matrix_lap[2, 0] = -1
        self.matrix_lap[0, 2] = -0.25
        self.matrix_lap[0, 2] = -0.25
        '''

        # BP initialization
        self.apply_bandpass = int(
            self.scope_settings.get("filtering", "apply_bandpass_filter"))
        if (self.apply_bandpass):
            self.ui.doubleSpinBox_hp.setValue(
                float(
                    self.scope_settings.get(
                        "filtering",
                        "bandpass_cutoff_frequency").split(' ')[0]))
            self.ui.doubleSpinBox_lp.setValue(
                float(
                    self.scope_settings.get(
                        "filtering",
                        "bandpass_cutoff_frequency").split(' ')[1]))
            self.ui.doubleSpinBox_lp.setMinimum(0.1)
            self.ui.doubleSpinBox_lp.setMaximum(self.sr.sample_rate / 2 - 0.1)
            self.ui.doubleSpinBox_lp.setDecimals(1)
            self.ui.doubleSpinBox_lp.setSingleStep(1)
            self.ui.doubleSpinBox_hp.setMinimum(0.1)
            self.ui.doubleSpinBox_hp.setMaximum(self.sr.sample_rate / 2 - 0.1)
            self.ui.doubleSpinBox_hp.setDecimals(1)
            self.ui.doubleSpinBox_hp.setSingleStep(1)
            self.ui.pushButton_bp.click()

        self.ui.checkBox_bandpass.setChecked(self.apply_car)
        self.ui.checkBox_bandpass.setChecked(self.apply_bandpass)

        self.update_title_scope()

        # Help variables
        self.show_help = 0
        self.help = pg.TextItem(
            "CNBI EEG Scope v0.3 \n" +
            "----------------------------------------------------------------------------------\n"
            + "C: De/activate CAR Filter\n" +
            "B: De/activate Bandpass Filter (with current settings)\n" +
            "T: Show/hide TiD events\n" + "L: Show/hide LPT events\n" +
            "K: Show/hide Key events. If not shown, they are NOT recorded!\n" +
            "0-9: Add a user-specific Key event. Do not forget to write down why you marked it.\n"
            +
            "Up, down arrow keys: Increase/decrease the scale, steps of 10 uV\n"
            +
            "Left, right arrow keys: Increase/decrease the time to show, steps of 1 s\n"
            +
            "Spacebar: Stop the scope plotting, whereas data acquisition keeps running (EXPERIMENTAL)\n"
            + "Esc: Exits the scope",
            anchor=(0, 0),
            border=(70, 70, 70),
            fill=pg.mkColor(20, 20, 20, 200),
            color=(255, 255, 255))

        # Stop plot functionality
        self.stop_plot = 0

        # Force repaint even when we shouldn't repaint.
        self.force_repaint = 0

    # For some strange reason when the width is > 1 px the scope runs slow.
    # self.pen_plot = []
    # for x in range(0, self.config['eeg_channels']):
    # 	self.pen_plot.append(pg.mkPen(self.colors[x%16,:], width=3))

    #
    # 	Initializes the BCI loop parameters
    #
    def init_loop_cnbiloop(self):

        self.fin = open(self.scope_settings.get("internal", "path_pipe"), 'r')

        # 12 unsigned ints (4 bytes)
        data = struct.unpack("<12I", self.fin.read(4 * 12))

        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.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)

        # TID initialization
        self.bci = BCI.BciInterface()

    #
    # 	Initializes the BCI loop parameters
    #
    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
        ]
        logger.info('Trigger channel is %d' % self.sr.get_trigger_channel())

        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 = []

    #
    # 	Initializes the QT timer, which will call the update function every 20 ms
    #
    def init_timer(self):

        self.tm = qc.Timer()  # leeq

        QtCore.QCoreApplication.processEvents()
        QtCore.QCoreApplication.flush()
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_loop)
        self.timer.start(20)

    # QtCore.QTimer.singleShot( 20, self.update_loop )

    #
    #	Main update function (connected to the timer)
    #
    def update_loop(self):

        #  Sharing variable to stop at the GUI level
        if not self.state.value:
            logger.info('Viewer stopped')
            sys.exit()

        try:
            # assert self.updating==False, 'thread destroyed?'
            # self.updating= True

            # self.handle_tobiid_input()	# Read TiDs
            self.read_eeg()  # Read new chunk
            if len(self.ts_list) > 0:
                self.filter_signal()  # Filter acquired data
                self.update_ringbuffers()  # Update the plotting infor
                if (not self.stop_plot):
                    self.repaint()  # Call paint event
        except:
            logger.exception('Exception. Dropping into a shell.')
            pdb.set_trace()
        finally:
            # self.updating= False
            # using singleShot instead
            # QtCore.QTimer.singleShot( 20, self.update_loop )
            pass

    #
    #	Read EEG
    #
    def read_eeg(self):

        # if self.updating==True: print( '##### ERROR: thread destroyed ? ######' )
        # self.updating= True

        try:
            # data, self.ts_list= self.sr.inlets[0].pull_chunk(max_samples=self.config['sf']) # [frames][channels]
            data, self.ts_list = self.sr.acquire(blocking=False)

            # TODO: check and change to these two lines
            #self.sr.acquire(blocking=False, decim=DECIM)
            #data, self.ts_list = self.sr.get_window()

            if len(self.ts_list) == 0:
                # self.eeg= None
                # self.tri= None
                return

            n = self.config['eeg_channels']
            '''
            x= np.array( data )
            trg_ch= self.config['tri_channels']
            if trg_ch is not None:
                self.tri= np.reshape( x[:,trg_ch], (-1,1) ) # samples x 1
            self.eeg= np.reshape( x[:,self.sr.eeg_channels], (-1,n) ) # samples x channels
            '''
            trg_ch = self.config['tri_channels']
            if trg_ch is not None:
                self.tri = np.reshape(data[:, trg_ch], (-1, 1))  # samples x 1
            self.eeg = np.reshape(data[:, self.sr.eeg_channels],
                                  (-1, n))  # samples x channels

            if DEBUG_TRIGGER:
                # show trigger value
                try:
                    trg_value = max(self.tri)
                    if trg_value > 0:
                        logger.info('Received trigger %s' % trg_value)
                except:
                    logger.exception('Error! self.tri = %s' % self.tri)

                    # Read exg. self.config.samples*self.config.exg_ch, type float
                    # bexg = np.random.rand( 1, self.config['samples'] * self.config['exg_channels'] )
                    # self.exg = np.reshape(list(bexg), (self.config['samples'],self.config['exg_channels']))
        except WindowsError:
            # print('**** Access violation in read_eeg():\n%s\n%s'% (sys.exc_info()[0], sys.exc_info()[1]))
            pass
        except:
            logger.exception()
            pdb.set_trace()

    #
    #	Read EEG
    #
    def read_eeg_cnbiloop(self):

        # Reading in python is blocking, so it will wait until having the amount of data needed
        # Read timestamp. 1 value, type double
        timestamp = struct.unpack("<d", self.fin.read(8 * 1))
        # Read index. 1 value, type uint64
        index = struct.unpack("<Q", self.fin.read(8 * 1))
        # Read labels. self.config.labels, type double
        labels = struct.unpack("<" + str(self.config['labels']) + "I",
                               self.fin.read(4 * self.config['labels']))
        # Read eeg. self.config.samples*self.config.eeg_ch, type float
        beeg = struct.unpack(
            "<" + str(self.config['samples'] * self.config['eeg_channels']) +
            "f",
            self.fin.read(4 * self.config['samples'] *
                          self.config['eeg_channels']))
        self.eeg = np.reshape(
            list(beeg), (self.config['samples'], self.config['eeg_channels']))
        # Read exg. self.config.samples*self.config.exg_ch, type float
        bexg = struct.unpack(
            "<" + str(self.config['samples'] * self.config['exg_channels']) +
            "f",
            self.fin.read(4 * self.config['samples'] *
                          self.config['exg_channels']))
        self.exg = np.reshape(
            list(bexg), (self.config['samples'], self.config['exg_channels']))
        # Read tri. self.config.samples*self.config.tri_ch, type float
        self.tri = struct.unpack(
            "<" + str(self.config['samples'] * self.config['tri_channels']) +
            "i",
            self.fin.read(4 * self.config['samples'] *
                          self.config['tri_channels']))

    #
    #	Bandpas + CAR filtering
    #
    def filter_signal(self):

        if (self.apply_bandpass):
            for x in range(0, self.eeg.shape[1]):
                self.eeg[:, x], self.zi[:,
                                        x] = lfilter(self.b, self.a,
                                                     self.eeg[:, x], -1,
                                                     self.zi[:, x])

        # We only apply CAR if selected AND there are at least 2 channels. Otherwise it makes no sense
        if (self.apply_car) and (len(self.channels_to_show_idx) > 1):
            self.eeg = np.dot(self.matrix_car, np.transpose(self.eeg))
            self.eeg = np.transpose(self.eeg)

    #
    #	Update ringbuffers and events for plotting
    #
    def update_ringbuffers(self):
        # leeq
        self.data_plot = np.roll(self.data_plot, -len(self.ts_list), 0)
        self.data_plot[-len(self.ts_list):, :] = self.eeg

        # We have to remove those indexes that reached time = 0
        delete_indices_e = []
        delete_indices_c = []
        for x in range(0, len(self.events_detected), 2):
            xh = int(x / 2)
            self.events_detected[x] -= len(self.ts_list)  # leeq
            if (self.events_detected[x] < 0) and (not self.stop_plot):
                delete_indices_e.append(x)
                delete_indices_e.append(x + 1)
                delete_indices_c.append(xh)
                self.events_curves[xh].clear()
                self.main_plot_handler.removeItem(self.events_text[xh])

        self.events_detected = [
            i for j, i in enumerate(self.events_detected)
            if j not in delete_indices_e
        ]
        self.events_curves = [
            i for j, i in enumerate(self.events_curves)
            if j not in delete_indices_c
        ]
        self.events_text = [
            i for j, i in enumerate(self.events_text)
            if j not in delete_indices_c
        ]

        # Find LPT events and add them
        if (self.show_LPT_events) and (not self.stop_plot):
            for x in range(len(self.tri)):
                tri = int(self.tri[x])
                if tri != 0 and (tri > self.last_tri):
                    self.addEventPlot("LPT", tri)
                    logger.info('Trigger %d received' % tri)
                self.last_tri = tri

    #
    #	Called by repaint()
    #
    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()

    #
    #	Update stuff on the interface. Only graphical updates should be added here
    #
    def paintInterface(self, qp):

        # Update EEG channels
        for x in range(0, len(self.channels_to_show_idx)):
            self.curve_eeg[x].setData(
                x=self.x_ticks,
                y=self.data_plot[:, self.channels_to_show_idx[x]] -
                x * self.scale)

        # Update events
        for x in range(0, len(self.events_detected), 2):
            xh = int(x / 2)
            self.events_curves[xh].setData(
                x=np.array([
                    self.x_ticks[self.events_detected[x]],
                    self.x_ticks[self.events_detected[x]]
                ]),
                y=np.array([
                    +1.5 * self.scale, -0.5 * self.scale -
                    self.scale * self.config['eeg_channels']
                ]))
            self.events_text[xh].setPos(self.x_ticks[self.events_detected[x]],
                                        self.scale)

    #
    #	Do necessary stuff when scale has changed
    #
    def update_plot_scale(self, new_scale):

        if (new_scale < 1):
            new_scale = 1
        # commented out by dbdq.
        # else:
        #	new_scale = new_scale - new_scale%10

        self.scale = new_scale

        # Y Tick labels
        values = []
        for x in range(0, len(self.channels_to_show_idx)):
            values.append((-x * self.scale,
                           self.channel_labels[self.channels_to_show_idx[x]]))

        values_axis = []
        values_axis.append(values)
        values_axis.append([])

        self.main_plot_handler.getAxis('left').setTicks(values_axis)
        self.main_plot_handler.setRange(
            yRange=[+self.scale, -self.scale * len(self.channels_to_show_idx)])
        self.main_plot_handler.setLabel(axis='left',
                                        text='Scale (uV): ' + str(self.scale))
        self.trigger_help()

        # We force an immediate repaint to avoid "shakiness".
        if (not self.stop_plot):
            self.force_repaint = 1
            self.repaint()

    #
    #	Do necessary stuff when seconds to show have changed
    #
    def update_plot_seconds(self, new_seconds):

        # Do nothing unless...
        if (new_seconds != self.seconds_to_show) and (new_seconds > 0) and (
                new_seconds < 100):
            self.ui.spinBox_time.setValue(new_seconds)
            self.main_plot_handler.setRange(xRange=[0, new_seconds])
            self.x_ticks = np.zeros(self.config['sf'] * new_seconds)
            for x in range(0, self.config['sf'] * new_seconds):
                self.x_ticks[x] = (x * 1) / float(self.config['sf'])

            if (new_seconds > self.seconds_to_show):
                padded_signal = np.zeros((self.config['sf'] * new_seconds,
                                          self.config['eeg_channels']))
                padded_signal[padded_signal.shape[0] -
                              self.data_plot.shape[0]:, :] = self.data_plot
                for x in range(0, len(self.events_detected), 2):
                    self.events_detected[x] += padded_signal.shape[0] - \
                                               self.data_plot.shape[0]
                self.data_plot = padded_signal

            else:
                for x in range(0, len(self.events_detected), 2):
                    self.events_detected[x] -= self.data_plot.shape[0] - \
                                               self.config['sf'] * new_seconds
                self.data_plot = self.data_plot[self.data_plot.shape[0] -
                                                self.config['sf'] *
                                                new_seconds:, :]

            self.seconds_to_show = new_seconds
            self.trigger_help()

            # We force an immediate repaint to avoid "shakiness".
            if (not self.stop_plot):
                self.force_repaint = 1
                self.repaint()

    #
    # Handle TOBI iD events
    #
    def handle_tobiid_input(self):

        data = None
        try:
            data = self.bci.iDsock_bus.recv(512)
            self.bci.idStreamer_bus.Append(data)
        except:
            self.nS = False
            self.dec = 0
            pass

        # deserialize ID message
        if data:
            if self.bci.idStreamer_bus.Has("<tobiid", "/>"):
                msg = self.bci.idStreamer_bus.Extract("<tobiid", "/>")
                self.bci.id_serializer_bus.Deserialize(msg)
                self.bci.idStreamer_bus.Clear()
                tmpmsg = int(self.bci.id_msg_bus.GetEvent())
                if (self.show_TID_events) and (not self.stop_plot):
                    self.addEventPlot("TID", tmpmsg)

            elif self.bci.idStreamer_bus.Has("<tcstatus", "/>"):
                MsgNum = self.bci.idStreamer_bus.Count("<tcstatus")
                for i in range(1, MsgNum - 1):
                    # Extract most of these messages and trash them
                    msg_useless = self.bci.idStreamer_bus.Extract(
                        "<tcstatus", "/>")

    #
    # 	Add an event to the scope
    #
    def addEventPlot(self, event_name, event_id):
        if (event_name == "TID"):
            color = pg.mkColor(0, 0, 255)
        elif (event_name == "KEY"):
            color = pg.mkColor(255, 0, 0)
        elif (event_name == "LPT"):
            color = pg.mkColor(0, 255, 0)
        else:
            color = pg.mkColor(255, 255, 255)

        self.events_detected.append(self.data_plot.shape[0] - 1)
        self.events_detected.append(event_id)
        self.events_curves.append(
            self.main_plot_handler.plot(
                pen=color,
                x=np.array([self.x_ticks[-1], self.x_ticks[-1]]),
                y=np.array([
                    +1.5 * self.scale,
                    -1.5 * self.scale * self.config['eeg_channels']
                ])))
        # text = pg.TextItem(event_name + "(" + str(self.events_detected[-1]) + ")", anchor=(1.1,0), fill=(0,0,0), color=color)
        text = pg.TextItem(str(self.events_detected[-1]),
                           anchor=(1.1, 0),
                           fill=(0, 0, 0),
                           color=color)
        text.setPos(self.x_ticks[-1], self.scale)
        self.events_text.append(text)
        self.main_plot_handler.addItem(self.events_text[-1])

    #
    #	Calculation of bandpass coefficients.
    #	Order is computed automatically.
    #	Note that if filter is unstable this function crashes (TODO handle problems)
    #
    def butter_bandpass(self, highcut, lowcut, fs, num_ch):
        low = lowcut / (0.5 * fs)
        high = highcut / (0.5 * fs)
        # get the order. TO BE DONE: Sometimes it fails
        #ord = buttord(high, low, 2, 40)
        #b, a = butter(ord[0], [high, low], btype='band')
        b, a = butter(2, [high, low], btype='band')
        zi = np.zeros([a.shape[0] - 1, num_ch])
        return b, a, zi

    #
    #	Updates the title shown in the scope
    #
    def update_title_scope(self):
        if (hasattr(self, 'main_plot_handler')):
            self.main_plot_handler.setTitle(
                title='TLK: ' + self.bool_parser[self.show_TID_events] +
                self.bool_parser[self.show_LPT_events] +
                self.bool_parser[self.show_Key_events] + ', CAR: ' +
                self.bool_parser[self.apply_car] + ', BP: ' +
                self.bool_parser[self.apply_bandpass] + ' [' +
                str(self.ui.doubleSpinBox_hp.value()) + '-' +
                str(self.ui.doubleSpinBox_lp.value()) + '] Hz')
            # ', BP: ' + self.bool_parser[self.apply_bandpass] + (' [' + str(self.doubleSpinBox_hp.value()) + '-' + str(self.doubleSpinBox_lp.value()) + '] Hz' if self.apply_bandpass else ''))

    #
    #	Shows / hide help in the scope window
    #
    def trigger_help(self):
        if self.show_help:
            self.help.setPos(0, self.scale)
            self.main_plot_handler.addItem(self.help)
            self.help.setZValue(1)
        else:
            self.main_plot_handler.removeItem(self.help)

    # ----------------------------------------------------------------------------------------------------
    # 			EVENT HANDLERS
    # ----------------------------------------------------------------------------------------------------
    def onClicked_button_rec(self):
        # Simply call cl_rpc for this.
        if (len(self.lineEdit_recFilename.text()) > 0):
            if ".gdf" in self.lineEdit_recFilename.text():
                self.ui.pushButton_stoprec.setEnabled(True)
                self.ui.pushButton_rec.setEnabled(False)
                # Popen is more efficient than os.open, since it is non-blocking
                subprocess.Popen([
                    "cl_rpc", "openxdf",
                    str(self.ui.lineEdit_recFilename.text()), "dummy_log",
                    "dummy_log"
                ],
                                 close_fds=True)
                self.statusBar.showMessage(
                    "Recording file " +
                    str(self.ui.lineEdit_recFilename.text()))
            elif ".bdf" in self.ui.lineEdit_recFilename.text():
                self.ui.pushButton_stoprec.setEnabled(True)
                self.ui.pushButton_rec.setEnabled(False)
                subprocess.Popen([
                    "cl_rpc", "openxdf",
                    str(self.ui.lineEdit_recFilename.text()), "dummy_log",
                    "dummy_log"
                ],
                                 close_fds=True)
                self.statusBar.showMessage(
                    "Recording file " +
                    str(self.ui.lineEdit_recFilename.text()))
            else:
                pass

    def onClicked_button_stoprec(self):
        subprocess.Popen(["cl_rpc", "closexdf"], close_fds=True)
        self.ui.pushButton_rec.setEnabled(True)
        self.ui.pushButton_stoprec.setEnabled(False)
        self.ui.statusBar.showMessage("Not recording")

    def onActivated_checkbox_bandpass(self):
        self.apply_bandpass = False
        self.ui.pushButton_bp.setEnabled(self.ui.checkBox_bandpass.isChecked())
        self.ui.doubleSpinBox_hp.setEnabled(
            self.ui.checkBox_bandpass.isChecked())
        self.ui.doubleSpinBox_lp.setEnabled(
            self.ui.checkBox_bandpass.isChecked())
        self.update_title_scope()

    def onActivated_checkbox_car(self):
        self.apply_car = self.ui.checkBox_car.isChecked()
        self.update_title_scope()

    def onActivated_checkbox_TID(self):
        self.show_TID_events = self.ui.checkBox_showTID.isChecked()
        self.update_title_scope()

    def onActivated_checkbox_LPT(self):
        self.show_LPT_events = self.ui.checkBox_showLPT.isChecked()
        self.update_title_scope()

    def onActivated_checkbox_Key(self):
        self.show_Key_events = self.ui.checkBox_showKey.isChecked()
        self.update_title_scope()

    def onValueChanged_spinbox_time(self):
        self.update_plot_seconds(self.ui.spinBox_time.value())

    def onActivated_combobox_scale(self):
        self.update_plot_scale(
            self.scales_range[self.ui.comboBox_scale.currentIndex()])

    def onClicked_button_bp(self):
        if (self.ui.doubleSpinBox_lp.value() >
                self.ui.doubleSpinBox_hp.value()):
            self.apply_bandpass = True
            self.b, self.a, self.zi = self.butter_bandpass(
                self.ui.doubleSpinBox_hp.value(),
                self.ui.doubleSpinBox_lp.value(), self.config['sf'],
                self.config['eeg_channels'])
        self.update_title_scope()

    def onSelectionChanged_table(self):

        # Remove current plot
        for x in range(0, len(self.channels_to_show_idx)):
            self.main_plot_handler.removeItem(self.curve_eeg[x])

        # Which channels should I plot?
        self.channels_to_show_idx = []
        self.channels_to_hide_idx = []
        idx = 0
        for y in range(0, 4):
            for x in range(0, NUM_X_CHANNELS):
                if (idx < self.config['eeg_channels']):
                    #if (self.table_channels.isItemSelected( # Qt4 only
                    if (QTableWidgetItem.isSelected(  # Qt5
                            self.ui.table_channels.item(x, y))):
                        self.channels_to_show_idx.append(idx)
                    else:
                        self.channels_to_hide_idx.append(idx)
                    idx += 1

        # Add new plots
        self.curve_eeg = []
        for x in range(0, len(self.channels_to_show_idx)):
            self.curve_eeg.append(
                self.main_plot_handler.plot(
                    x=self.x_ticks,
                    y=self.data_plot[:, self.channels_to_show_idx[x]],
                    pen=self.colors[self.channels_to_show_idx[x] %
                                    NUM_X_CHANNELS, :]))
            self.curve_eeg[-1].setDownsampling(ds=self.subsampling_value,
                                               auto=False,
                                               method="mean")

        # Update CAR so it's computed based only on the shown channels
        if (len(self.channels_to_show_idx) > 1):
            self.matrix_car = np.zeros(
                (self.config['eeg_channels'], self.config['eeg_channels']),
                dtype=float)
            self.matrix_car[:, :] = -1 / float(len(self.channels_to_show_idx))
            np.fill_diagonal(self.matrix_car,
                             1 - (1 / float(len(self.channels_to_show_idx))))
            for x in range(0, len(self.channels_to_hide_idx)):
                self.matrix_car[self.channels_to_hide_idx[x], :] = 0
                self.matrix_car[:, self.channels_to_hide_idx[x]] = 0

        # Refresh the plot
        self.update_plot_scale(self.scale)

    def keyPressEvent(self, event):
        key = event.key()
        if (key == QtCore.Qt.Key_Escape):
            self.closeEvent(None)
        if (key == QtCore.Qt.Key_H):
            self.show_help = not self.show_help
            self.trigger_help()
        if (key == QtCore.Qt.Key_Up):
            # Python's log(x, 10) has a rounding bug. Use log10(x) instead.
            new_scale = self.scale + max(1, 10**int(math.log10(self.scale)))
            self.update_plot_scale(new_scale)
        if (key == QtCore.Qt.Key_Space):
            self.stop_plot = not self.stop_plot
        if (key == QtCore.Qt.Key_Down):
            if self.scale >= 2:
                # Python's log(x, 10) has a rounding bug. Use log10(x) instead.
                new_scale = self.scale - max(
                    1, 10**int(math.log10(self.scale - 1)))
                self.update_plot_scale(new_scale)
        if (key == QtCore.Qt.Key_Left):
            self.update_plot_seconds(self.seconds_to_show - 1)
        if (key == QtCore.Qt.Key_Right):
            self.update_plot_seconds(self.seconds_to_show + 1)
        if (key == QtCore.Qt.Key_L):
            self.ui.checkBox_showLPT.setChecked(
                not self.ui.checkBox_showLPT.isChecked())
        if (key == QtCore.Qt.Key_T):
            self.ui.checkBox_showTID.setChecked(
                not self.ui.checkBox_showTID.isChecked())
        if (key == QtCore.Qt.Key_K):
            self.ui.checkBox_showKey.setChecked(
                not self.ui.checkBox_showKey.isChecked())
        if (key == QtCore.Qt.Key_C):
            self.ui.checkBox_car.setChecked(
                not self.ui.checkBox_car.isChecked())
        if (key == QtCore.Qt.Key_B):
            self.ui.checkBox_bandpass.setChecked(
                not self.ui.checkBox_bandpass.isChecked())
            if self.ui.checkBox_bandpass.isChecked():
                self.ui.pushButton_bp.click()
        if ((key >= QtCore.Qt.Key_0) and (key <= QtCore.Qt.Key_9)):
            if (self.show_Key_events) and (not self.stop_plot):
                self.addEventPlot("KEY", 990 + key - QtCore.Qt.Key_0)
                # self.bci.id_msg_bus.SetEvent(990 + key - QtCore.Qt.Key_0)
                # self.bci.iDsock_bus.sendall(self.bci.id_serializer_bus.Serialize());
                # 666

    #
    #	Function called when a closing event was triggered.
    #
    def closeEvent(self, event):
        '''
        reply = QtGui.QMessageBox.question(self, "Quit", "Are you sure you want to quit?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes)
        if (reply == QtGui.QMessageBox.Yes):
            if (self.pushButton_stoprec.isEnabled()):
                subprocess.Popen(["cl_rpc", "closexdf"], close_fds=True)
            self.fin.close()
            exit()
        '''

        # leeq
        if (self.ui.pushButton_stoprec.isEnabled()):
            subprocess.Popen(["cl_rpc", "closexdf"], close_fds=True)
        with self.state.get_lock():
            self.state.value = 0
    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')

    fmin = 1
    fmax = 40
    psde = mne.decoding.PSDEstimator(
        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()
Esempio n. 6
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()
    # window = [samples x channels]
    window, tslist = stream_receiver.get_window()
    window = window.T  # chanel x samples
    # print(window)

    # # print event values
    # tsnew = np.where(np.array(tslist) > last_ts)[0][0]
    # trigger = np.unique(window[trg_ch, tsnew:])

    # for Biosemi
    # if sr.amp_name=='BioSemi':
    #    trigger= set( [255 & int(x-1) for x in trigger ] )

    # if len(trigger) > 0:
    #     qc.print_c('Triggers: %s' % np.array(trigger), 'G')
Esempio n. 7
0
class RecordingFromHardWare():
    def __init__(self):
        self.start_recording = False
        self.new_data = []
        self.sr = StreamReceiver(buffer_size=0)
        self.record_dir = '%s/records' % os.getcwd()
        self.current_window = np.ndarray([])
        self.current_time_stamps = np.ndarray([])
        self.MRCP_window_size = 6
        print("I RUMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN")

# =============================================================================
#     def runInfinitecording(self):
#         while self.start_recording:
#                self.sr.acquire()
#                if self.sr.get_buflen() > 20:
#                    duration = str(datetime.timedelta(seconds=int(self.sr.get_buflen())))
#                    #recordLogger.info('RECORDING %s' % duration)
#                    #next_sec += 1
#                buffers, times = self.sr.get_buffer()
#                signals = buffers
#                events = None
#
#                 # channels = total channels from amp, including trigger channel
#                data = {'signals':signals, 'timestamps':times, 'events':events,
#                         'sample_rate':self.sr.get_sample_rate(), 'channels':self.sr.get_num_channels(),
#                         'ch_names':self.sr.get_channel_names(), 'lsl_time_offset':self.sr.lsl_time_offset}
#                print(data)
#                self.new_data.append(data)
# =============================================================================

    def startRecording(self):
        # do some stuff
        self.start_recording = True
        #download_thread = threading.Thread(target=self.runInfinitecording)
        recordLogger = logger
        thread = threading.Thread(target=self.record)
        thread.start()

    def stopRecording(self):
        self.start_recording = False
        #Write into file
        #self.new_data = []

    def isRecordingIsRunning(self):
        return self.start_recording

    def set_MRCP_window_size(self, MRCP_window_size):
        self.MRCP_window_size = MRCP_window_size

    def record(self):
        recordLogger = logger
        #redirect_stdout_to_queue(recordLogger, 'INFO')

        # set data file name
        timestamp = time.strftime('%Y%m%d-%H%M%S', time.localtime())
        pcl_file = "%s/%s-raw.pcl" % (self.record_dir, timestamp)
        eve_file = '%s/%s-eve.txt' % (self.record_dir, timestamp)
        recordLogger.info('>> Output file: %s' % (pcl_file))

        # test writability
        try:
            qc.make_dirs(self.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, 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 self.start_recording:
            self.sr.acquire()
            if self.sr.get_buflen() > next_sec:
                duration = str(
                    datetime.timedelta(seconds=int(self.sr.get_buflen())))
                #recordLogger.info('RECORDING %s' % duration)
                next_sec += 1

            #current_buffer, current_times = self.sr.get_buffer()
            self.sr.set_window_size(self.MRCP_window_size)
            self.current_window, self.current_time_stamps = self.sr.get_window(
            )
            #print("window shape: {} time stamps shape: {}".format(self.current_window.shape, self.current_time_stamps.shape))
            tm.sleep_atleast(0.001)

        # record stop
        recordLogger.info('>> Stop requested. Copying buffer')
        buffers, times = self.sr.get_buffer()
        signals = buffers
        events = None

        # channels = total channels from amp, including trigger channel
        data = {
            'signals': signals,
            'timestamps': times,
            'events': events,
            'sample_rate': self.sr.get_sample_rate(),
            'channels': self.sr.get_num_channels(),
            'ch_names': self.sr.get_channel_names(),
            'lsl_time_offset': self.sr.lsl_time_offset
        }
        print("data length : {}".format(data['signals'].shape))
        self.new_data = data['signals']
        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)
Esempio n. 8
0
class BCIDecoder(object):
    """
    Decoder class

    The label order of self.labels and self.label_names match likelihood orders computed by get_prob()

    """

    def __init__(self, classifier=None, buffer_size=1.0, fake=False, amp_serial=None, amp_name=None):
        """
        Params
        ------
        classifier: classifier file
        spatial: spatial filter to use
        buffer_size: length of the signal buffer in seconds
        """

        self.classifier = classifier
        self.buffer_sec = buffer_size
        self.fake = fake
        self.amp_serial = amp_serial
        self.amp_name = amp_name

        if self.fake == False:
            model = qc.load_obj(self.classifier)
            if model == None:
                self.print('Error loading %s' % model)
                sys.exit(-1)
            self.cls = model['cls']
            self.psde = model['psde']
            self.labels = list(self.cls.classes_)
            self.label_names = [model['classes'][k] for k in self.labels]
            self.spatial = model['spatial']
            self.spectral = model['spectral']
            self.notch = model['notch']
            self.w_seconds = model['w_seconds']
            self.w_frames = model['w_frames']
            self.wstep = model['wstep']
            self.sfreq = model['sfreq']
            if not int(self.sfreq * self.w_seconds) == self.w_frames:
                raise RuntimeError('sfreq * w_sec %d != w_frames %d' % (int(self.sfreq * self.w_seconds), self.w_frames))

            if 'multiplier' in model:
                self.multiplier = model['multiplier']
            else:
                self.multiplier = 1

            # Stream Receiver
            self.sr = StreamReceiver(window_size=self.w_seconds, amp_name=self.amp_name, amp_serial=self.amp_serial)
            if self.sfreq != self.sr.sample_rate:
                raise RuntimeError('Amplifier sampling rate (%.1f) != model sampling rate (%.1f). Stop.' % (
                    self.sr.sample_rate, self.sfreq))

            # Map channel indices based on channel names of the streaming server
            self.spatial_ch = model['spatial_ch']
            self.spectral_ch = model['spectral_ch']
            self.notch_ch = model['notch_ch']
            self.ref_new = model['ref_new']
            self.ref_old = model['ref_old']
            self.ch_names = self.sr.get_channel_names()
            mc = model['ch_names']
            self.picks = [self.ch_names.index(mc[p]) for p in model['picks']]
            if self.spatial_ch is not None:
                self.spatial_ch = [self.ch_names.index(mc[p]) for p in model['spatial_ch']]
            if self.spectral_ch is not None:
                self.spectral_ch = [self.ch_names.index(mc[p]) for p in model['spectral_ch']]
            if self.notch_ch is not None:
                self.notch_ch = [self.ch_names.index(mc[p]) for p in model['notch_ch']]
            if self.ref_new is not None:
                self.ref_new = self.ch_names.index(mc[model['ref_new']])
            if self.ref_old is not None:
                self.ref_old = self.ch_names.index(mc[model['ref_old']])

            # PSD buffer
            psd_temp = self.psde.transform(np.zeros((1, len(self.picks), self.w_frames)))
            self.psd_shape = psd_temp.shape
            self.psd_size = psd_temp.size
            self.psd_buffer = np.zeros((0, self.psd_shape[1], self.psd_shape[2]))
            self.ts_buffer = []

        else:
            # Fake left-right decoder
            model = None
            self.psd_shape = None
            self.psd_size = None
            # TODO: parameterize directions using fake_dirs
            self.labels = [11, 9]
            self.label_names = ['LEFT_GO', 'RIGHT_GO']

    def print(self, *args):
        if len(args) > 0: print('[BCIDecoder] ', end='')
        print(*args)

    def get_labels(self):
        """
        Returns
        -------
        Class labels numbers in the same order as the likelihoods returned by get_prob()
        """
        return self.labels

    def get_label_names(self):
        """
        Returns
        -------
        Class label names in the same order as get_labels()
        """
        return self.label_names

    def start(self):
        pass

    def stop(self):
        pass

    def get_prob(self):
        """
        Read the latest window

        Returns
        -------
        The likelihood P(X|C), where X=window, C=model
        """
        if self.fake:
            # fake deocder: biased likelihood for the first class
            probs = [random.uniform(0.0, 1.0)]
            # others class likelihoods are just set to equal
            p_others = (1 - probs[0]) / (len(self.labels) - 1)
            for x in range(1, len(self.labels)):
                probs.append(p_others)
            time.sleep(0.0625)  # simulated delay for PSD + RF
        else:
            self.sr.acquire()
            w, ts = self.sr.get_window()  # w = times x channels
            w = w.T  # -> channels x times

            # apply filters. Important: maintain the original channel order at this point.
            pu.preprocess(w, sfreq=self.sfreq, spatial=self.spatial, spatial_ch=self.spatial_ch,
                          spectral=self.spectral, spectral_ch=self.spectral_ch, notch=self.notch,
                          notch_ch=self.notch_ch, multiplier=self.multiplier)

            # select the same channels used for training
            w = w[self.picks]

            # debug: show max - min
            # c=1; print( '### %d: %.1f - %.1f = %.1f'% ( self.picks[c], max(w[c]), min(w[c]), max(w[c])-min(w[c]) ) )

            # psd = channels x freqs
            psd = self.psde.transform(w.reshape((1, w.shape[0], w.shape[1])))

            # update psd buffer ( < 1 msec overhead )
            self.psd_buffer = np.concatenate((self.psd_buffer, psd), axis=0)
            self.ts_buffer.append(ts[0])
            if ts[0] - self.ts_buffer[0] > self.buffer_sec:
                # search speed comparison for ordered arrays:
                # http://stackoverflow.com/questions/16243955/numpy-first-occurence-of-value-greater-than-existing-value
                t_index = np.searchsorted(self.ts_buffer, ts[0] - 1.0)
                self.ts_buffer = self.ts_buffer[t_index:]
                self.psd_buffer = self.psd_buffer[t_index:, :, :]  # numpy delete is slower
            # assert ts[0] - self.ts_buffer[0] <= self.buffer_sec

            # make a feautre vector and classify
            feats = np.concatenate(psd[0]).reshape(1, -1)

            # compute likelihoods
            probs = self.cls.predict_proba(feats)[0]

        return probs

    def get_prob_unread(self):
        return self.get_prob()

    def get_psd(self):
        """
        Returns
        -------
        The latest computed PSD
        """
        return self.psd_buffer[-1].reshape((1, -1))

    def is_ready(self):
        """
        Ready to decode? Returns True if buffer is not empty.
        """
        return self.sr.is_ready()
Esempio n. 9
0
    # Frequency band for the band-pass filter
    fmin = 1
    fmax = 47
    # Create an estimator of the power spectrum density in the frequency band
    psde = mne.decoding.PSDEstimator(sfreq=sfreq,
                                     fmin=fmin,
                                     fmax=fmax,
                                     bandwidth=None,
                                     adaptive=False,
                                     low_bias=True,
                                     n_jobs=1,
                                     normalization='length',
                                     verbose=None)
    # Main loop
    while True:
        sr.acquire()  # Read data from LSL
        window, tslist = sr.get_window()  # window = [samples x channels]
        window = window.T  # channels 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())

        # Pass data to the processing code
        # - bandpass it
        # - apply the inverse solution to get a signal for all cortical sources
Esempio n. 10
0
class BCIDecoder(object):
    """
    Decoder class

    The label order of self.labels and self.label_names match likelihood orders computed by get_prob()

    """

    def __init__(self, classifier=None, buffer_size=1.0, fake=False, amp_serial=None, amp_name=None):
        """
        Params
        ------
        classifier: classifier file
        spatial: spatial filter to use
        buffer_size: length of the signal buffer in seconds
        """

        self.classifier = classifier
        self.buffer_sec = buffer_size
        self.fake = fake
        self.amp_serial = amp_serial
        self.amp_name = amp_name

        if self.fake == False:
            model = qc.load_obj(self.classifier)
            if model is None:
                logger.error('Classifier model is None.')
                raise ValueError
            self.cls = model['cls']
            self.psde = model['psde']
            self.labels = list(self.cls.classes_)
            self.label_names = [model['classes'][k] for k in self.labels]
            self.spatial = model['spatial']
            self.spectral = model['spectral']
            self.notch = model['notch']
            self.w_seconds = model['w_seconds']
            self.w_frames = model['w_frames']
            self.wstep = model['wstep']
            self.sfreq = model['sfreq']
            if 'decim' not in model:
                model['decim'] = 1
            self.decim = model['decim']
            if not int(round(self.sfreq * self.w_seconds)) == self.w_frames:
                logger.error('sfreq * w_sec %d != w_frames %d' % (int(round(self.sfreq * self.w_seconds)), self.w_frames))
                raise RuntimeError

            if 'multiplier' in model:
                self.multiplier = model['multiplier']
            else:
                self.multiplier = 1

            # Stream Receiver
            self.sr = StreamReceiver(window_size=self.w_seconds, amp_name=self.amp_name, amp_serial=self.amp_serial)
            if self.sfreq != self.sr.sample_rate:
                logger.error('Amplifier sampling rate (%.3f) != model sampling rate (%.3f). Stop.' % (self.sr.sample_rate, self.sfreq))
                raise RuntimeError

            # Map channel indices based on channel names of the streaming server
            self.spatial_ch = model['spatial_ch']
            self.spectral_ch = model['spectral_ch']
            self.notch_ch = model['notch_ch']
            #self.ref_ch = model['ref_ch'] # not supported yet
            self.ch_names = self.sr.get_channel_names()
            mc = model['ch_names']
            self.picks = [self.ch_names.index(mc[p]) for p in model['picks']]
            if self.spatial_ch is not None:
                self.spatial_ch = [self.ch_names.index(mc[p]) for p in model['spatial_ch']]
            if self.spectral_ch is not None:
                self.spectral_ch = [self.ch_names.index(mc[p]) for p in model['spectral_ch']]
            if self.notch_ch is not None:
                self.notch_ch = [self.ch_names.index(mc[p]) for p in model['notch_ch']]

            # PSD buffer
            #psd_temp = self.psde.transform(np.zeros((1, len(self.picks), self.w_frames // self.decim)))
            #self.psd_shape = psd_temp.shape
            #self.psd_size = psd_temp.size
            #self.psd_buffer = np.zeros((0, self.psd_shape[1], self.psd_shape[2]))
            #self.psd_buffer = None

            self.ts_buffer = []

            logger.info_green('Loaded classifier %s (sfreq=%.3f, decim=%d)' % (' vs '.join(self.label_names), self.sfreq, self.decim))
        else:
            # Fake left-right decoder
            model = None
            self.psd_shape = None
            self.psd_size = None
            # TODO: parameterize directions using fake_dirs
            self.labels = [11, 9]
            self.label_names = ['LEFT_GO', 'RIGHT_GO']

    def get_labels(self):
        """
        Returns
        -------
        Class labels numbers in the same order as the likelihoods returned by get_prob()
        """
        return self.labels

    def get_label_names(self):
        """
        Returns
        -------
        Class label names in the same order as get_labels()
        """
        return self.label_names

    def start(self):
        pass

    def stop(self):
        pass

    def get_prob(self, timestamp=False):
        """
        Read the latest window

        Input
        -----
        timestamp: If True, returns LSL timestamp of the leading edge of the window used for decoding.

        Returns
        -------
        The likelihood P(X|C), where X=window, C=model
        """
        if self.fake:
            # fake deocder: biased likelihood for the first class
            probs = [random.uniform(0.0, 1.0)]
            # others class likelihoods are just set to equal
            p_others = (1 - probs[0]) / (len(self.labels) - 1)
            for x in range(1, len(self.labels)):
                probs.append(p_others)
            time.sleep(0.0625)  # simulated delay
            t_prob = pylsl.local_clock()
        else:
            self.sr.acquire(blocking=True)
            w, ts = self.sr.get_window()  # w = times x channels
            t_prob = ts[-1]
            w = w.T  # -> channels x times

            # re-reference channels
            # TODO: add re-referencing function to preprocess()

            # apply filters. Important: maintain the original channel order at this point.
            w = pu.preprocess(w, sfreq=self.sfreq, spatial=self.spatial, spatial_ch=self.spatial_ch,
                          spectral=self.spectral, spectral_ch=self.spectral_ch, notch=self.notch,
                          notch_ch=self.notch_ch, multiplier=self.multiplier, decim=self.decim)

            # select the same channels used for training
            w = w[self.picks]

            # debug: show max - min
            # c=1; print( '### %d: %.1f - %.1f = %.1f'% ( self.picks[c], max(w[c]), min(w[c]), max(w[c])-min(w[c]) ) )

            # psd = channels x freqs
            psd = self.psde.transform(w.reshape((1, w.shape[0], w.shape[1])))

            # make a feautre vector and classify
            feats = np.concatenate(psd[0]).reshape(1, -1)

            # compute likelihoods
            probs = self.cls.predict_proba(feats)[0]

            # update psd buffer ( < 1 msec overhead )
            '''
            if self.psd_buffer is None:
                self.psd_buffer = psd
            else:
                self.psd_buffer = np.concatenate((self.psd_buffer, psd), axis=0)
                # TODO: CHECK THIS BLOCK
                self.ts_buffer.append(ts[0])
                if ts[0] - self.ts_buffer[0] > self.buffer_sec:
                    # search speed comparison for ordered arrays:
                    # http://stackoverflow.com/questions/16243955/numpy-first-occurence-of-value-greater-than-existing-value
                    #t_index = np.searchsorted(self.ts_buffer, ts[0] - 1.0)
                    t_index = np.searchsorted(self.ts_buffer, ts[0] - self.buffer_sec)
                    self.ts_buffer = self.ts_buffer[t_index:]
                    self.psd_buffer = self.psd_buffer[t_index:, :, :]  # numpy delete is slower
                # assert ts[0] - self.ts_buffer[0] <= self.buffer_sec
            '''

        if timestamp:
            return probs, t_prob
        else:
            return probs

    def get_prob_unread(self, timestamp=False):
        return self.get_prob(timestamp)

    def get_psd(self):
        """
        Returns
        -------
        The latest computed PSD
        """
        raise NotImplementedError('Sorry! PSD buffer is under testing.')
        return self.psd_buffer[-1].reshape((1, -1))

    def is_ready(self):
        """
        Ready to decode? Returns True if buffer is not empty.
        """
        return self.sr.is_ready()