From c516db0782a250574dc727daa0f6c1eb60597105 Mon Sep 17 00:00:00 2001 From: Madeleine36 Date: Wed, 27 Jul 2022 01:08:40 -0400 Subject: [PATCH 1/3] Added logging to imped win, cleaned up menu, partial support for muse aux channel the new brainflow update that allows the muse aux channel breaks the bandstop data filter, so currently that's disabled and ppl are expected to still be on the previous version --- Board.py | 25 ++++++++++++---- graph_window.py | 38 ++++++++++++++---------- impedance_window.py | 42 ++++++++++++++++++--------- main_menu.py | 70 ++++++--------------------------------------- 4 files changed, 80 insertions(+), 95 deletions(-) diff --git a/Board.py b/Board.py index 0b8ac2b..4c40c5a 100644 --- a/Board.py +++ b/Board.py @@ -15,6 +15,10 @@ GANGLION = "Ganglion" CYTON = "Cyton" CYTON_DAISY = "Cyton-Daisy" +MUSE_2016_BLED = "Muse 2016 BLE Dongle" +MUSE_2_BLED = "Muse 2 BLE Dongle" +MUSE_S_BLED = "Muse S BLE Dongle" +MUSE_2016 = "Muse 2016" MUSE_2 = "Muse 2" MUSE_S = "Muse S" @@ -41,7 +45,7 @@ def get_serial_port(board_id): pass else: # didn't have the bad com port exeption - BoardShim.release_all_sessions() + board.release_session() return params.serial_port BoardShim.release_all_sessions() @@ -138,12 +142,14 @@ def stop(self): def get_board_id(data_type, hardware, model): - """Gets the brainflow board_id from the given arguments + """Gets the brainflow board_id from the given arguments. Note that BLED boards\ + require a BLED112 dongle. Non BLED muse hardware is untested. Args: data_type (String): A string of either "Task live" or "Task simulate" hardware (String): A string of either "Muse" or "OpenBCI" - model (String): A string of either "Muse 2", "Muse S", "Ganglion", "Cyton", or "Cyton-Daisy" + model (String): A string of either "Ganglion", "Cyton", or "Cyton-Daisy", "Muse 2016 BLE Dongle", + "Muse 2 BLE Dongle", "Muse S BLE Dongle", "Muse 2016", "Muse 2","Muse S" Returns: int: The board_id that brainflow uses internally to determine board type @@ -158,10 +164,19 @@ def get_board_id(data_type, hardware, model): elif model == CYTON_DAISY: board_id = 2 elif hardware == MUSE: - if model == MUSE_2: + if model == MUSE_S_BLED: + board_id = 21 + elif model == MUSE_2_BLED: board_id = 22 + elif model == MUSE_2016_BLED: + board_id = 42 + elif model == MUSE_2: + board_id = 38 elif model == MUSE_S: - board_id = 21 + board_id = 39 + elif model == MUSE_2016: + board_id = 41 + elif data_type == SIMULATE: board_id = -1 diff --git a/graph_window.py b/graph_window.py index b4c5774..46cb52a 100644 --- a/graph_window.py +++ b/graph_window.py @@ -84,9 +84,19 @@ def __init__( # save file should be an ok file name to save to with approriate ending ('.csv') self.save_file = save_file self.board_id = get_board_id(data_type, hardware, model) - self.exg_channels = BoardShim.get_exg_channels(self.board_id) - self.marker_channels = BoardShim.get_marker_channel(self.board_id) + + # by default, not using this (turn back on if you have most recent brainflow) + # in the most recent version of brainflow, you can access an additional muse channel, + # correponding to the aux port. However, this update breaks that bandstop filter, + # so make sure to turn that off (in self.update) + if self.board_id in (21,22,42) and False: + # if we are using muyse hardware get a channel for the device's aux port + self.aux_channels = BoardShim.get_other_channels(self.board_id) + self.using_aux_channels = True + else: + self.using_aux_channels = False + self.sampling_rate = BoardShim.get_sampling_rate(self.board_id) self.update_speed_ms = 50 self.window_size = 5 @@ -99,7 +109,8 @@ def __init__( self.chan_num = len(self.exg_channels) self.exg_channels = np.array(self.exg_channels) - self.marker_channels = np.array(self.marker_channels) + if self.using_aux_channels: + self.aux_channels = np.array(self.aux_channels) print('board decription {}'.format(BoardShim.get_board_descr(board_id))) logger.debug('EXG channels is {}'.format(self.exg_channels)) @@ -130,7 +141,11 @@ def __init__( def _init_timeseries(self): self.plots = list() self.curves = list() - for i in range(self.chan_num+1): + if self.using_aux_channels: + num_curves = self.chan_num + len(self.aux_channels) + else: + num_curves = self.chan_num + for i in range(num_curves): p = self.graphWidget.addPlot(row=i, col=0) p.showAxis("left", False) p.setMenuEnabled("left", False) @@ -184,15 +199,6 @@ def update(self): FilterTypes.BUTTERWORTH.value, 0, ) - DataFilter.perform_bandpass( - data[channel], - self.sampling_rate, - 51.0, - 100.0, - 2, - FilterTypes.BUTTERWORTH.value, - 0, - ) DataFilter.perform_bandstop( data[channel], self.sampling_rate, @@ -212,8 +218,10 @@ def update(self): 0, ) self.curves[count].setData(data[channel].tolist()) - self.curves[len(self.exg_channels)].setData(data[self.marker_channels].tolist()) - logger.debug('Marker channel data was {}'.format(data[self.marker_channels].tolist())) + if self.using_aux_channels: + for count, channel in enumerate(self.aux_channels): + self.curves[len(self.exg_channels)+count].setData(data[channel].tolist()) + logger.debug('AUX channel data was {}'.format(data[self.aux_channels].tolist())) logger.debug('Graph window finished updating (successfully got data from board and applied it to graphs)') def closeEvent(self, event): diff --git a/impedance_window.py b/impedance_window.py index d6307b8..4547e21 100644 --- a/impedance_window.py +++ b/impedance_window.py @@ -22,7 +22,22 @@ from PyQt5.QtGui import QPainter, QBrush, QPen, QPolygon import numpy as np import statistics as stats -from multiprocessing import Process, Queue +import logging +log_file = "boiler.log" +logging.basicConfig(level=logging.INFO, filemode="a") + +f = logging.Formatter( + "Logger: %(name)s: %(levelname)s at: %(asctime)s, line %(lineno)d: %(message)s" +) +stdout = logging.StreamHandler(sys.stdout) +boiler_log = logging.FileHandler(log_file) +stdout.setFormatter(f) +boiler_log.setFormatter(f) + +logger = logging.getLogger("ImpedWindow") +logger.addHandler(boiler_log) +logger.addHandler(stdout) +logger.info("Program started at {}".format(time.time())) from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds from brainflow.data_filter import DataFilter, FilterTypes @@ -48,6 +63,7 @@ def __init__( board_id=None, ): super().__init__() + logger.info('Initializing impedance window') # Ensures that the user has not provided a muse, leading to # hard to debug errors later. @@ -181,6 +197,7 @@ def init_hardware(self): # let's start eeg receiving! # self.start_data_stream() + logger.info("Initializing hardware") self.board = Board(board_id=self.board_id, serial_port=self.serial_port, manual_mode = True) print( @@ -196,6 +213,7 @@ def init_hardware(self): # This wild string puts Cyton-Daisy into impedance mode. # DO NOT CHANGE # Think Pulse + logger.info("Configuring Cyton") self.board.board.config_board( "x1040010Xx2040010Xx3040010Xx4040010Xx5040010Xx6040010Xx7040010Xx8040010XxQ040010XxW040010XxE040010XxR040010XxT040010XxY040010XxU040010XxI040010X" ) @@ -214,6 +232,7 @@ def init_hardware(self): # ganglion impedances based on # https://github.com/OpenBCI/brainflow/blob/master/tests/python/ganglion_resist.py # expected result: 5 seconds of resistance data(unknown sampling rate) after that 5 seconds of exg data + logger.info("Configuring Ganglion") self.board.board.config_board("z") print('sent board z, not yet start stream') self.board.board.start_stream(45000, None) @@ -230,31 +249,26 @@ def init_hardware(self): resistance_channels = BoardShim.get_resistance_channels (BoardIds.GANGLION_BOARD.value) print (resistance_channels) + else: + logger.error('Impedance window cannote run using this hardware (Board ID: {}). Try an OpenBCI ganglion or cyton instead.'.format(self.board_id)) def closeEvent(self, event): # this code will autorun just before the window closes # we will check whether streams are running, if they are we will close them - print("close event works") + logger.info("Closing window") self.finished = True self.parent.impedance_window_open = False self.on_end() def loop_start(self): - print("starting loop") + logger.info("starting loop") self.loop_running = True self.loop_timer.timeout.disconnect() self.loop_timer.timeout.connect(self.start_iteration) self.loop_timer.start(1000) self.update() - # def loop_end(self): - # print("ending loop") - # self.loop_running = False - # self.update() - # self.loop_timer.timeout.disconnect() - # self.loop_timer.timeout.connect(self.start_iteration) - # self.loop_timer.start(1000) def start_iteration(self): # called by hitting enter @@ -274,14 +288,14 @@ def start_iteration(self): # use stdev as proxy. chan_rms_uV = np.sqrt(np.sum(self.data ** 2)) self.impedances[count] = ((stats.sqrt( 2.0 ) * (chan_rms_uV) * 1.0e-6) / 6.0e-9 - 2200)/1000 - print(self.impedances) + logger.debug("Imedances: {}".format(self.impedances)) """ HERE """ # need to do some smoothing from the past 6 seconds to take out instantaneous self.loop_start() else: - print("exiting") + logger.info("exiting") def filter_custom(self, chan): DataFilter.perform_highpass( @@ -313,7 +327,7 @@ def display_instructions(self): def keyPressEvent(self, event): if event.key() == Qt.Qt.Key_Space: - print("received user input") + logger.info("received user input") elif event.key() == Qt.Qt.Key_Return or event.key == Qt.Qt.Key_Enter: if self.hardware_connected and not self.running_test: self.running_test = True @@ -323,7 +337,7 @@ def keyPressEvent(self, event): def paintEvent(self, event): # here is where we draw stuff on the screen # you give drawing instructions in pixels - here I'm getting pixel values based on window size - print("paint event runs") + logger.info("paint event runs") painter = QPainter(self) if self.loop_running: radius = self.geometry().width() // 18 diff --git a/main_menu.py b/main_menu.py index cd01053..1f5c36b 100644 --- a/main_menu.py +++ b/main_menu.py @@ -1,59 +1,5 @@ """ -TO DO: - Main Menu: - Add in hardware/model: - unicorn - muse (2/S) - Compartmentalize the board id grab in utils (pass in hardware/model/datatype) - - Impedence menu - Look for muse impedance check scripts - Confirm that the OpenBCI Impedence checks are working properly - - For time sync - add back in pyLSL? - - Render GA ERPs in results window from the either the baseline or the session - Choose from baseline/session - - Logisitics: - Send Paul another Muse and possibly an arduino + light? - -M todo -order of events lets you try and fauil tomopen graph wo selecting com port -one dropdown for hardware (<-eden no likey) -implement impedanece for all,not just cyton daisy - -add support for non openbci hardware -add option to not import tensorflow -in train model, has hard coded 16 channel # (fix) - - - -opens windows: -graph window - shows live timeseries --potentially make it configurable -- label on garph which line is which channel by chcking hardware -impedance window --curently hacked together, obnly cyton daiusy --implement with other -arduino --debug requires putting in 1 --preset for neuorstimduino -- need dosc for how to upload script to arduino using arduino ide, attach led -- currently provides a way to turn led on arduino on and off on command -baseline -- basically like the oddball window -- outputs eeg file in brainflow format -- new plan: use pylsl sender to constantly grab brainflow and events and send them together, so ww can be sure of times -saving -- sqlite prob overkill -- use numpy -- later maybe add sqlite to use if run for long time -remove unecessary windows -- we don't need a model window with tensorflow to train a thing. this isn't koalacademy -ADD SIMULATE AS HARDWARE OPTION -make board id happen in menu window so not passing raw srtrings between windows - +This is the main menu. It starts all the other programs as directed by the user. """ @@ -69,7 +15,8 @@ import time import os import logging -from Board import BCI, CONNECT, CYTON, CYTON_DAISY, GANGLION, MUSE, MUSE_2, MUSE_S, SIMULATE, get_board_id +from Board import BCI, CONNECT, CYTON, CYTON_DAISY, GANGLION, MUSE, MUSE_2, MUSE_S, MUSE_2016, \ + MUSE_S_BLED, MUSE_2_BLED, MUSE_2016_BLED, SIMULATE, get_board_id # Creates the global logger @@ -97,7 +44,7 @@ # results not implemented yet from graph_window import graph_win -if False: # debugging... remebeber to put the tf imports back in session_window +if False: # debugging... remember to put the tf imports back in session_window import tensorflow as tf if sys.platform == "win32": @@ -358,9 +305,7 @@ def handle_hardware_choice(self): if self.hardware_dropdown.currentText() == BCI: self.model_dropdown.addItems([GANGLION, CYTON, CYTON_DAISY]) elif self.hardware_dropdown.currentText() == MUSE: - self.model_dropdown.addItems([MUSE_2, MUSE_S]) - elif self.hardware_dropdown.currentText() == "Blueberry": - self.model_dropdown.addItem("Prototype") + self.model_dropdown.addItems([MUSE_2016_BLED, MUSE_2_BLED, MUSE_S_BLED]) def handle_model_choice(self): """Handles changes to the model dropdown""" @@ -400,7 +345,10 @@ def handle_type_choice(self): self.data_type = self.type_dropdown.currentText() self.graph_window_button.setEnabled(True) self.baseline_window_button.setEnabled(True) - self.impedance_window_button.setEnabled(True) + if self.hardware_dropdown.currentText() != MUSE: + self.impedance_window_button.setEnabled(True) + else: + logger.info('Impedance window is not available for Muse hardware. Try OpenBCI instead.') if self.data_type == CONNECT: self.title.setText("Select BCI Hardware Port") self.bci_port.setEnabled(True) From d7ca666220980ce295a182bab11592ca65f07138 Mon Sep 17 00:00:00 2001 From: Madeleine36 Date: Thu, 28 Jul 2022 20:49:36 -0400 Subject: [PATCH 2/3] More in keeping with 5th channel instructions --- graph_window.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/graph_window.py b/graph_window.py index 46cb52a..28c2405 100644 --- a/graph_window.py +++ b/graph_window.py @@ -90,8 +90,11 @@ def __init__( # in the most recent version of brainflow, you can access an additional muse channel, # correponding to the aux port. However, this update breaks that bandstop filter, # so make sure to turn that off (in self.update) - if self.board_id in (21,22,42) and False: - # if we are using muyse hardware get a channel for the device's aux port + if self.board_id in (21,22,42) and True: + # muse devices have an extra eeg channel. We need to configure th board + # to include it before we strat the stream, so we'll make our Board in + # manual mode so we can start the stream ourselves. + # if we are using muse hardware get a channel for the device's aux port self.aux_channels = BoardShim.get_other_channels(self.board_id) self.using_aux_channels = True else: @@ -102,7 +105,24 @@ def __init__( self.window_size = 5 self.num_points = self.window_size * self.sampling_rate - self.board = Board(data_type, hardware, model, board_id, serial_port=serial_port, num_points = self.num_points) + if self.board_id in (21,22,42) and True: + # muse devices have an extra eeg channel. We need to configure th board + # to include it before we strat the stream, so we'll make our Board in + # manual mode so we can start the stream ourselves. + manual_mode = True + # if we are using muse hardware get a channel for the device's aux port + self.aux_channels = BoardShim.get_other_channels(self.board_id) + self.using_aux_channels = False + else: + manual_mode = False + self.using_aux_channels = False + self.board = Board(data_type, hardware, model, board_id, serial_port=serial_port, num_points = self.num_points,manual_mode = manual_mode) + + if manual_mode: + logger.info('manual mode section ran') + # using muse. configure so we get 5h eeg channel, and start stream + self.board.board.config_board("p50") + self.board.board.start_stream() self.hardware_connected = True logger.info("Hardware connected; stream started.") @@ -199,6 +219,7 @@ def update(self): FilterTypes.BUTTERWORTH.value, 0, ) + logging.info(' channel, data[channel] before bandstop {}\n {}'.format(channel,data[channel])) DataFilter.perform_bandstop( data[channel], self.sampling_rate, From 149e65a6ca3a0a4ec5e87a3c630e8ac92fe587bb Mon Sep 17 00:00:00 2001 From: Madeleine36 Date: Thu, 28 Jul 2022 20:57:11 -0400 Subject: [PATCH 3/3] removed stuff for 5th channel that broke the bandstop filter --- graph_window.py | 46 ++-------------------------------------------- 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/graph_window.py b/graph_window.py index 28c2405..575d751 100644 --- a/graph_window.py +++ b/graph_window.py @@ -86,52 +86,18 @@ def __init__( self.board_id = get_board_id(data_type, hardware, model) self.exg_channels = BoardShim.get_exg_channels(self.board_id) - # by default, not using this (turn back on if you have most recent brainflow) - # in the most recent version of brainflow, you can access an additional muse channel, - # correponding to the aux port. However, this update breaks that bandstop filter, - # so make sure to turn that off (in self.update) - if self.board_id in (21,22,42) and True: - # muse devices have an extra eeg channel. We need to configure th board - # to include it before we strat the stream, so we'll make our Board in - # manual mode so we can start the stream ourselves. - # if we are using muse hardware get a channel for the device's aux port - self.aux_channels = BoardShim.get_other_channels(self.board_id) - self.using_aux_channels = True - else: - self.using_aux_channels = False - self.sampling_rate = BoardShim.get_sampling_rate(self.board_id) self.update_speed_ms = 50 self.window_size = 5 self.num_points = self.window_size * self.sampling_rate - if self.board_id in (21,22,42) and True: - # muse devices have an extra eeg channel. We need to configure th board - # to include it before we strat the stream, so we'll make our Board in - # manual mode so we can start the stream ourselves. - manual_mode = True - # if we are using muse hardware get a channel for the device's aux port - self.aux_channels = BoardShim.get_other_channels(self.board_id) - self.using_aux_channels = False - else: - manual_mode = False - self.using_aux_channels = False - self.board = Board(data_type, hardware, model, board_id, serial_port=serial_port, num_points = self.num_points,manual_mode = manual_mode) - - if manual_mode: - logger.info('manual mode section ran') - # using muse. configure so we get 5h eeg channel, and start stream - self.board.board.config_board("p50") - self.board.board.start_stream() + self.board = Board(data_type, hardware, model, board_id, serial_port=serial_port, num_points = self.num_points) self.hardware_connected = True logger.info("Hardware connected; stream started.") self.chan_num = len(self.exg_channels) self.exg_channels = np.array(self.exg_channels) - if self.using_aux_channels: - self.aux_channels = np.array(self.aux_channels) - print('board decription {}'.format(BoardShim.get_board_descr(board_id))) logger.debug('EXG channels is {}'.format(self.exg_channels)) @@ -161,10 +127,7 @@ def __init__( def _init_timeseries(self): self.plots = list() self.curves = list() - if self.using_aux_channels: - num_curves = self.chan_num + len(self.aux_channels) - else: - num_curves = self.chan_num + num_curves = self.chan_num for i in range(num_curves): p = self.graphWidget.addPlot(row=i, col=0) p.showAxis("left", False) @@ -219,7 +182,6 @@ def update(self): FilterTypes.BUTTERWORTH.value, 0, ) - logging.info(' channel, data[channel] before bandstop {}\n {}'.format(channel,data[channel])) DataFilter.perform_bandstop( data[channel], self.sampling_rate, @@ -239,10 +201,6 @@ def update(self): 0, ) self.curves[count].setData(data[channel].tolist()) - if self.using_aux_channels: - for count, channel in enumerate(self.aux_channels): - self.curves[len(self.exg_channels)+count].setData(data[channel].tolist()) - logger.debug('AUX channel data was {}'.format(data[self.aux_channels].tolist())) logger.debug('Graph window finished updating (successfully got data from board and applied it to graphs)') def closeEvent(self, event):